diff --git a/CHANGELOG.md b/CHANGELOG.md index 14aff2f8..26bc38c8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ -## 1.3.0 +## 2.0.0 +* Add peripheral mode on Android, iOS, macOS, and Windows * Add `requestConnectionPriority` to allow tuning BLE connection intervals on Android +* BREAKING CHANGE: `getBluetoothAvailabilityState` renamed to `getAvailabilityState` * Add SPM support on Apple ## 1.2.0 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..db1daf57 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,71 @@ +# Contributing + +Thank you for helping improve Universal BLE. This document describes how we work in this repository and what we expect from contributions. + +## Reporting issues + +Use [GitHub Issues](https://github.com/Navideck/universal_ble/issues). Include the platform (Android, iOS, macOS, Windows, Linux, Web), Flutter/Dart versions, and a minimal way to reproduce the problem when possible. + +## Pull requests + +- Open PRs against the `main` branch. +- Keep changes focused: one logical concern per PR is easier to review than a large mixed refactor. +- Update `CHANGELOG.md` when the change is user-visible (new behavior, fixes, or breaking API changes). Follow the existing bullet style (`* …`, `* BREAKING CHANGE: …` where appropriate). +- Do not commit secrets, local paths, or generated build artifacts unrelated to the change. + +## Environment + +Requirements are defined in `pubspec.yaml` (Dart SDK and Flutter). Use a stable Flutter channel unless a maintainer asks otherwise. + +From the repo root: + +```sh +flutter pub get +``` + +## Checks to run before opening a PR + +These mirror [.github/workflows/pull_request.yml](.github/workflows/pull_request.yml): + +```sh +flutter analyze +flutter test +flutter test --platform chrome +``` + +Fix any analyzer issues. The project uses [flutter_lints](https://pub.dev/packages/flutter_lints) via [analysis_options.yaml](analysis_options.yaml) (which includes `package:flutter_lints/flutter.yaml`). + +Format Dart code with the SDK formatter: + +```sh +dart format . +``` + +If you only touched specific files, you may format those paths instead of the whole tree. + +## Pigeon and generated code + +Host–native APIs are defined in [pigeon/universal_ble.dart](pigeon/universal_ble.dart). If you change that file, regenerate outputs and include them in the same PR: + +```sh +./build_pigeon.sh +``` + +That runs `dart run pigeon --input pigeon/universal_ble.dart` and formats `lib/src/universal_ble_pigeon/universal_ble.g.dart`. Regenerated Kotlin, Swift, and C++ files land under `android/`, `darwin/`, and `windows/` as configured in the Pigeon `@ConfigurePigeon` block—keep those in sync with the Dart definitions. + +## Code conventions + +- **Dart:** Follow effective Dart style, existing naming in `lib/`, and analyzer rules. Prefer extending existing patterns (platform interface → pigeon channel → native implementations) over new parallel abstractions unless discussed first. +- **Native:** Match the style and structure of the surrounding file on each platform (Kotlin, Swift, C++). When a Pigeon API changes, update every generated implementation and any hand-written glue so all targets stay consistent. +- **Tests:** Add or extend tests under `test/` when behavior is non-trivial or regression-prone. Use `flutter_test` like the existing suite. +- **Example:** If the change affects how integrators use the plugin, consider updating the `example/` app so it stays a working reference. + +## Platform-specific APIs and parameters + +- **Single-platform features:** Prefer not adding a new public API when only one platform can implement it, unless none of the existing APIs can be extended or adapted to cover the behavior. For example, something like Android-only `requestConnectionPriority` should only become its own method if `connect`, `platformConfig`, or another existing entry point cannot reasonably subsume it. +- **Single-platform parameters:** When a value applies to one platform only, attach it via a platform-scoped bag (for example `startScan` takes an optional `platformConfig` object for options that only affect a given OS). That keeps the main method signature stable and makes it obvious which settings are platform-specific. +- **Shared parameters:** If more than one platform supports the same option, add it as a normal method parameter on the shared API. Document that implementations on platforms without that capability must ignore the parameter (no-op or documented limitation). + +## License + +By contributing, you agree that your contributions will be licensed under the same terms as the project: [BSD 3-Clause License](LICENSE). diff --git a/README.md b/README.md index 73ea2124..acf1c78f 100644 --- a/README.md +++ b/README.md @@ -33,9 +33,12 @@ A cross-platform (Android/iOS/macOS/Windows/Linux/Web) Bluetooth Low Energy (BLE - [Error Handling](#error-handling) - [UUID Format Agnostic](#uuid-format-agnostic) - [Permissions](#permissions) +- [Peripheral Mode](#peripheral-mode) ## API Support +### Client Mode (`UniversalBle`) + | | Android | iOS | macOS | Windows | Linux | Web | | :---------------------------- | :-----: | :-: | :---: | :-----: | :---: | :-: | | startScan/stopScan | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | @@ -50,7 +53,7 @@ A cross-platform (Android/iOS/macOS/Windows/Linux/Web) Bluetooth Low Energy (BLE | unpair | ✔️ | ❌ | ❌ | ✔️ | ✔️ | ❌ | | isPaired | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | | onPairingStateChange | ✔️ | ⏺ | ⏺ | ✔️ | ✔️ | ⏺ | -| getBluetoothAvailabilityState | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ❌ | +| getAvailabilityState | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ❌ | | enable/disable Bluetooth | ✔️ | ❌ | ❌ | ✔️ | ✔️ | ❌ | | onAvailabilityChange | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | | requestMtu | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ❌ | @@ -58,6 +61,28 @@ A cross-platform (Android/iOS/macOS/Windows/Linux/Web) Bluetooth Low Energy (BLE | readRssi | ✔️ | ✔️ | ✔️ | ❌ | 🚧 | ❌ | | requestPermissions | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | +### Peripheral Mode (`UniversalBlePeripheral`) + +| API | Android | iOS | macOS | Windows | Linux | Web | +| :----------------------- | :-----: | :-: | :---: | :-----: | :---: | :-: | +| getStaticCapabilities | ✔️ | ✔️ | ✔️ | ✔️ | 🚧 | ❌ | +| getReadinessState\* | ✔️ | ✔️ | ✔️ | ✔️ | 🚧 | ❌ | +| getAdvertisingState | ✔️ | ✔️ | ✔️ | ✔️ | 🚧 | ❌ | +| addService | ✔️ | ✔️ | ✔️ | ✔️ | 🚧 | ❌ | +| removeService | ✔️ | ✔️ | ✔️ | ✔️ | 🚧 | ❌ | +| clearServices | ✔️ | ✔️ | ✔️ | ✔️ | 🚧 | ❌ | +| getServices | ✔️ | ✔️ | ✔️ | ✔️ | 🚧 | ❌ | +| startAdvertising | ✔️ | ✔️ | ✔️ | ✔️ | 🚧 | ❌ | +| stopAdvertising | ✔️ | ✔️ | ✔️ | ✔️ | 🚧 | ❌ | +| updateCharacteristicValue\*\* | ✔️ | ✔️ | ✔️ | ✔️ | 🚧 | ❌ | +| getSubscribedClients | ✔️ | ✔️ | ✔️ | ✔️ | 🚧 | ❌ | +| getMaximumNotifyLength | ✔️ | ✔️ | ✔️ | ✔️ | 🚧 | ❌ | +| events stream\*\*\* | ✔️ | ✔️ | ✔️ | ✔️ | 🚧 | ❌ | + +\* `getReadinessState` returns a snapshot state. Use `eventStream` for ongoing runtime changes. +\*\* `updateCharacteristicValue` supports broadcast to all subscribed devices or a specific device via `PeripheralUpdateTarget`. +\*\*\* events include advertising state changes, MTU changes, subscription changes, and related peripheral events. + ## Getting Started Add universal_ble in your pubspec.yaml: @@ -70,6 +95,7 @@ dependencies: and import it wherever you want to use it: ```dart +import 'dart:typed_data'; import 'package:universal_ble/universal_ble.dart'; ``` @@ -110,7 +136,7 @@ UniversalBle.isScanning(); Before initiating a scan, ensure that Bluetooth is available: ```dart -AvailabilityState state = await UniversalBle.getBluetoothAvailabilityState(); +AvailabilityState state = await UniversalBle.getAvailabilityState(); // Start scan only if Bluetooth is powered on if (state == AvailabilityState.poweredOn) { UniversalBle.startScan(); @@ -402,7 +428,7 @@ bleDevice.unpair(); ```dart // Get current Bluetooth availability state -AvailabilityState availabilityState = UniversalBle.getBluetoothAvailabilityState(); // e.g. poweredOff or poweredOn, +AvailabilityState availabilityState = UniversalBle.getAvailabilityState(); // e.g. poweredOff or poweredOn, // Receive Bluetooth availability changes UniversalBle.onAvailabilityChange = (state) { @@ -440,7 +466,7 @@ explicitly controlled by applications: * **Android ≤ 13**: Apps may request MTU once per connection (up to 517). If never requested, the default MTU is 23. - * **Android 14+**: The first GATT client effectively drives MTU negotiation + * **Android 14+**: The first Bluetooth client effectively drives MTU negotiation to 517 (or the link’s maximum); subsequent MTU requests are ignored. * **Windows** @@ -451,7 +477,7 @@ explicitly controlled by applications: * **Linux (BlueZ)** * MTU is negotiated automatically by default. - * The standard D-Bus GATT API does not expose MTU control. + * The standard D-Bus Bluetooth API does not expose MTU control. * MTU can be requested via BlueZ tools or lower-level APIs, but most apps treat it as stack-defined. @@ -606,6 +632,175 @@ try { The error parser automatically converts platform-specific error formats (strings, numeric codes, PlatformExceptions) into the unified `UniversalBleErrorCode` enum, ensuring consistent error handling across all platforms. +## Peripheral Mode + +`universal_ble` provides peripheral mode through `UniversalBlePeripheralClient`, so your app can advertise as a peripheral "server" in addition to client mode. + +### Setup + +```dart +import 'dart:typed_data'; +import 'package:flutter/foundation.dart'; +import 'package:universal_ble/universal_ble.dart'; + +final peripheral = UniversalBlePeripheralClient(); + +final caps = await peripheral.getStaticCapabilities(); +if (!caps.supportsPeripheralMode) return; + +final readiness = await peripheral.getReadinessState(); +if (readiness != UniversalBlePeripheralReadinessState.ready) return; +``` + +### Service Management + +```dart +await peripheral.addService( + BleService("0000180F-0000-1000-8000-00805F9B34FB", [ + BleCharacteristic( + "00002A19-0000-1000-8000-00805F9B34FB", + [CharacteristicProperty.read, CharacteristicProperty.notify], + [BleDescriptor("00002902-0000-1000-8000-00805F9B34FB")], + ), + ]), + primary: true, +); + +await peripheral.addService( + BleService("0000180D-0000-1000-8000-00805F9B34FB", [ + BleCharacteristic( + "00002A37-0000-1000-8000-00805F9B34FB", + [ + CharacteristicProperty.read, + CharacteristicProperty.notify, + CharacteristicProperty.write, + ], + [], + ), + ]), +); + +final services = await peripheral.getServices(); +await peripheral.removeService(const PeripheralServiceId("0000180D-0000-1000-8000-00805F9B34FB")); +``` + +### Advertising + +On **Android**, passing `localName` may temporarily change the system Bluetooth device name (so it can appear in the advertisement). The plugin restores the previous name when advertising stops, if starting advertising fails, or when the plugin is disposed. + +On **Windows**, `GattServiceProvider`-based advertising does not support `localName`, manufacturer data, or a scan-response flag; omit them (as below) or the call will return a not-supported error. + +```dart +final isWindows = !kIsWeb && defaultTargetPlatform == TargetPlatform.windows; + +await peripheral.startAdvertising( + services: const [ + PeripheralServiceId("0000180F-0000-1000-8000-00805F9B34FB"), + ], + localName: isWindows ? null : "UniversalBlePeripheral", + manufacturerData: isWindows + ? null + : ManufacturerData( + 0x012D, + Uint8List.fromList([0x03, 0x00, 0x64, 0x00]), + ), + addManufacturerDataInScanResponse: isWindows + ? false + : caps.supportsManufacturerDataInScanResponse, +); + +final advertisingState = await peripheral.getAdvertisingState(); +if (advertisingState == UniversalBlePeripheralAdvertisingState.advertising) { + // Peripheral is advertising. +} + +await peripheral.stopAdvertising(); +``` + +### Request Handlers + +```dart +peripheral.setRequestHandlers( + PeripheralRequestHandlers( + onReadRequest: (deviceId, characteristicId, offset, value) { + return BleReadRequestResult(value: value ?? Uint8List(0)); + }, + onWriteRequest: (deviceId, characteristicId, offset, value) { + return const BleWriteRequestResult(); + }, + onDescriptorReadRequest: + (deviceId, characteristicId, descriptorId, offset, value) { + return BleReadRequestResult(value: value ?? Uint8List(0)); + }, + onDescriptorWriteRequest: + (deviceId, characteristicId, descriptorId, offset, value) { + return const BleWriteRequestResult(); + }, + ), +); +``` + +### Characteristic Updates + +```dart +await peripheral.updateCharacteristicValue( + characteristicId: const PeripheralCharacteristicId( + "00002A19-0000-1000-8000-00805F9B34FB", + ), + value: Uint8List.fromList([92]), +); +``` + +### Subscribed Clients and Notify Length + +```dart +final subscribers = await peripheral.getSubscribedClients( + const PeripheralCharacteristicId("00002A19-0000-1000-8000-00805F9B34FB"), +); + +for (final deviceId in subscribers) { + final maxNotifyLength = await peripheral.getMaximumNotifyLength(deviceId); + // maxNotifyLength can be null when unknown for this device. +} +``` + +### Event Stream + +```dart +final sub = peripheral.eventStream.listen((event) { + switch (event) { + case UniversalBlePeripheralAdvertisingStateChanged(): + // event.state / event.error + break; + case UniversalBlePeripheralCharacteristicSubscriptionChanged(): + // event.deviceId / event.characteristicId / event.isSubscribed + break; + case UniversalBlePeripheralConnectionStateChanged(): + // event.deviceId / event.connected + break; + case UniversalBlePeripheralMtuChanged(): + // event.deviceId / event.mtu + break; + case UniversalBlePeripheralServiceAdded(): + // event.serviceId / event.error + break; + } +}); +``` + +### Breaking changes + +- `isSupported()` was replaced by `getStaticCapabilities().supportsPeripheralMode`. +- `isAdvertising()` was replaced by `getAdvertisingState()`. +- Static callback setters were replaced by `eventStream` + `setRequestHandlers(...)`. +- `UniversalBlePeripheralClient` is the recommended API; `UniversalBlePeripheral` remains as a singleton facade. + +### Platform notes + +- Linux/Web currently return unsupported for peripheral mode. +- Windows peripheral advertising does not expose all advertising payload customization options from Android/Apple stacks. +- iOS/macOS setup (including required `Info.plist` keys for peripheral usage) is documented in [Permissions → iOS / macOS](#ios--macos). + ## UUID Format Agnostic Universal BLE is agnostic to the UUID format of services and characteristics regardless of the platform the app runs on. When passing a UUID, you can pass it in any format (long/short) or character case (upper/lower case) you want. Universal BLE will take care of necessary conversions, across all platforms, so that you don't need to worry about underlying platform differences. @@ -692,7 +887,21 @@ await UniversalBle.startScan(); ### iOS / macOS -Add `NSBluetoothPeripheralUsageDescription` and `NSBluetoothAlwaysUsageDescription` to Info.plist of your iOS and macOS app. +For Bluetooth usage (including peripheral mode), add both keys to your app's `Info.plist`: + +- `NSBluetoothAlwaysUsageDescription`: message shown when the app requests Bluetooth access. +- `NSBluetoothPeripheralUsageDescription`: message used for peripheral role access on Apple platforms. + +Example: + +```xml +NSBluetoothAlwaysUsageDescription +This app uses Bluetooth to scan, connect, and advertise to nearby devices. +NSBluetoothPeripheralUsageDescription +This app uses Bluetooth to advertise services to nearby devices. +``` + +Use clear, user-facing text that explains why Bluetooth is needed in your app. Add the `Bluetooth` capability to the macOS app from Xcode. @@ -825,7 +1034,7 @@ Future resetBleState() async { // Check Bluetooth availability AvailabilityState availabilityState = - await UniversalBle.getBluetoothAvailabilityState(); + await UniversalBle.getAvailabilityState(); // Skip if Bluetooth is not powered on if (availabilityState != AvailabilityState.poweredOn) { @@ -865,7 +1074,12 @@ Future resetBleState() async { ## Example app -This repo includes an [example app](example/) you can run to try the API. For a full-blown app, check [Universal-BLE](https://github.com/Navideck/Universal-BLE). +This repo includes an [example app](example/) with two tabs: + +- `Client`: scanning and device communication workflows. +- `Peripheral`: peripheral server and advertising workflows. + +For a full-blown app, check [Universal-BLE](https://github.com/Navideck/Universal-BLE). ## Low level API diff --git a/android/src/main/kotlin/com/navideck/universal_ble/UniversalBle.g.kt b/android/src/main/kotlin/com/navideck/universal_ble/UniversalBle.g.kt index 21188097..946b2d51 100644 --- a/android/src/main/kotlin/com/navideck/universal_ble/UniversalBle.g.kt +++ b/android/src/main/kotlin/com/navideck/universal_ble/UniversalBle.g.kt @@ -1,4 +1,4 @@ -// Autogenerated from Pigeon (v26.1.4), do not edit directly. +// Autogenerated from Pigeon (v26.3.3), do not edit directly. // See also: https://pub.dev/packages/pigeon @file:Suppress("UNCHECKED_CAST", "ArrayInDataClass") @@ -37,36 +37,150 @@ private object UniversalBlePigeonUtils { ) } } + fun doubleEquals(a: Double, b: Double): Boolean { + // Normalize -0.0 to 0.0 and handle NaN equality. + return (if (a == 0.0) 0.0 else a) == (if (b == 0.0) 0.0 else b) || (a.isNaN() && b.isNaN()) + } + + fun floatEquals(a: Float, b: Float): Boolean { + // Normalize -0.0 to 0.0 and handle NaN equality. + return (if (a == 0.0f) 0.0f else a) == (if (b == 0.0f) 0.0f else b) || (a.isNaN() && b.isNaN()) + } + + fun doubleHash(d: Double): Int { + // Normalize -0.0 to 0.0 and handle NaN to ensure consistent hash codes. + val normalized = if (d == 0.0) 0.0 else d + val bits = java.lang.Double.doubleToLongBits(normalized) + return (bits xor (bits ushr 32)).toInt() + } + + fun floatHash(f: Float): Int { + // Normalize -0.0 to 0.0 and handle NaN to ensure consistent hash codes. + val normalized = if (f == 0.0f) 0.0f else f + return java.lang.Float.floatToIntBits(normalized) + } + fun deepEquals(a: Any?, b: Any?): Boolean { + if (a === b) { + return true + } + if (a == null || b == null) { + return false + } if (a is ByteArray && b is ByteArray) { - return a.contentEquals(b) + return a.contentEquals(b) } if (a is IntArray && b is IntArray) { - return a.contentEquals(b) + return a.contentEquals(b) } if (a is LongArray && b is LongArray) { - return a.contentEquals(b) + return a.contentEquals(b) } if (a is DoubleArray && b is DoubleArray) { - return a.contentEquals(b) + if (a.size != b.size) return false + for (i in a.indices) { + if (!doubleEquals(a[i], b[i])) return false + } + return true + } + if (a is FloatArray && b is FloatArray) { + if (a.size != b.size) return false + for (i in a.indices) { + if (!floatEquals(a[i], b[i])) return false + } + return true } if (a is Array<*> && b is Array<*>) { - return a.size == b.size && - a.indices.all{ deepEquals(a[it], b[it]) } + if (a.size != b.size) return false + for (i in a.indices) { + if (!deepEquals(a[i], b[i])) return false + } + return true } if (a is List<*> && b is List<*>) { - return a.size == b.size && - a.indices.all{ deepEquals(a[it], b[it]) } + if (a.size != b.size) return false + val iterA = a.iterator() + val iterB = b.iterator() + while (iterA.hasNext() && iterB.hasNext()) { + if (!deepEquals(iterA.next(), iterB.next())) return false + } + return true } if (a is Map<*, *> && b is Map<*, *>) { - return a.size == b.size && a.all { - (b as Map).contains(it.key) && - deepEquals(it.value, b[it.key]) + if (a.size != b.size) return false + for (entry in a) { + val key = entry.key + var found = false + for (bEntry in b) { + if (deepEquals(key, bEntry.key)) { + if (deepEquals(entry.value, bEntry.value)) { + found = true + break + } else { + return false + } + } + } + if (!found) return false } + return true + } + if (a is Double && b is Double) { + return doubleEquals(a, b) + } + if (a is Float && b is Float) { + return floatEquals(a, b) } return a == b } - + + fun deepHash(value: Any?): Int { + return when (value) { + null -> 0 + is ByteArray -> value.contentHashCode() + is IntArray -> value.contentHashCode() + is LongArray -> value.contentHashCode() + is DoubleArray -> { + var result = 1 + for (item in value) { + result = 31 * result + doubleHash(item) + } + result + } + is FloatArray -> { + var result = 1 + for (item in value) { + result = 31 * result + floatHash(item) + } + result + } + is Array<*> -> { + var result = 1 + for (item in value) { + result = 31 * result + deepHash(item) + } + result + } + is List<*> -> { + var result = 1 + for (item in value) { + result = 31 * result + deepHash(item) + } + result + } + is Map<*, *> -> { + var result = 0 + for (entry in value) { + result += ((deepHash(entry.key) * 31) xor deepHash(entry.value)) + } + result + } + is Double -> doubleHash(value) + is Float -> floatHash(value) + else -> value.hashCode() + } + } + } /** @@ -110,6 +224,34 @@ enum class AndroidScanMode(val raw: Int) { } } +enum class PeripheralReadinessState(val raw: Int) { + UNKNOWN(0), + READY(1), + BLUETOOTH_OFF(2), + UNAUTHORIZED(3), + UNSUPPORTED(4); + + companion object { + fun ofRaw(raw: Int): PeripheralReadinessState? { + return values().firstOrNull { it.raw == raw } + } + } +} + +enum class PeripheralAdvertisingState(val raw: Int) { + IDLE(0), + STARTING(1), + ADVERTISING(2), + STOPPING(3), + ERROR(4); + + companion object { + fun ofRaw(raw: Int): PeripheralAdvertisingState? { + return values().firstOrNull { it.raw == raw } + } + } +} + /** Unified error codes for all platforms */ enum class UniversalBleErrorCode(val raw: Int) { UNKNOWN_ERROR(0), @@ -219,15 +361,28 @@ data class UniversalBleScanResult ( ) } override fun equals(other: Any?): Boolean { - if (other !is UniversalBleScanResult) { + if (other == null || other.javaClass != javaClass) { return false } if (this === other) { return true } - return UniversalBlePigeonUtils.deepEquals(toList(), other.toList()) } + val other = other as UniversalBleScanResult + return UniversalBlePigeonUtils.deepEquals(this.deviceId, other.deviceId) && UniversalBlePigeonUtils.deepEquals(this.name, other.name) && UniversalBlePigeonUtils.deepEquals(this.isPaired, other.isPaired) && UniversalBlePigeonUtils.deepEquals(this.rssi, other.rssi) && UniversalBlePigeonUtils.deepEquals(this.manufacturerDataList, other.manufacturerDataList) && UniversalBlePigeonUtils.deepEquals(this.serviceData, other.serviceData) && UniversalBlePigeonUtils.deepEquals(this.services, other.services) && UniversalBlePigeonUtils.deepEquals(this.timestamp, other.timestamp) + } - override fun hashCode(): Int = toList().hashCode() + override fun hashCode(): Int { + var result = javaClass.hashCode() + result = 31 * result + UniversalBlePigeonUtils.deepHash(this.deviceId) + result = 31 * result + UniversalBlePigeonUtils.deepHash(this.name) + result = 31 * result + UniversalBlePigeonUtils.deepHash(this.isPaired) + result = 31 * result + UniversalBlePigeonUtils.deepHash(this.rssi) + result = 31 * result + UniversalBlePigeonUtils.deepHash(this.manufacturerDataList) + result = 31 * result + UniversalBlePigeonUtils.deepHash(this.serviceData) + result = 31 * result + UniversalBlePigeonUtils.deepHash(this.services) + result = 31 * result + UniversalBlePigeonUtils.deepHash(this.timestamp) + return result + } } /** Generated class from Pigeon that represents data sent in messages. */ @@ -250,15 +405,22 @@ data class UniversalBleService ( ) } override fun equals(other: Any?): Boolean { - if (other !is UniversalBleService) { + if (other == null || other.javaClass != javaClass) { return false } if (this === other) { return true } - return UniversalBlePigeonUtils.deepEquals(toList(), other.toList()) } + val other = other as UniversalBleService + return UniversalBlePigeonUtils.deepEquals(this.uuid, other.uuid) && UniversalBlePigeonUtils.deepEquals(this.characteristics, other.characteristics) + } - override fun hashCode(): Int = toList().hashCode() + override fun hashCode(): Int { + var result = javaClass.hashCode() + result = 31 * result + UniversalBlePigeonUtils.deepHash(this.uuid) + result = 31 * result + UniversalBlePigeonUtils.deepHash(this.characteristics) + return result + } } /** Generated class from Pigeon that represents data sent in messages. */ @@ -284,15 +446,23 @@ data class UniversalBleCharacteristic ( ) } override fun equals(other: Any?): Boolean { - if (other !is UniversalBleCharacteristic) { + if (other == null || other.javaClass != javaClass) { return false } if (this === other) { return true } - return UniversalBlePigeonUtils.deepEquals(toList(), other.toList()) } + val other = other as UniversalBleCharacteristic + return UniversalBlePigeonUtils.deepEquals(this.uuid, other.uuid) && UniversalBlePigeonUtils.deepEquals(this.properties, other.properties) && UniversalBlePigeonUtils.deepEquals(this.descriptors, other.descriptors) + } - override fun hashCode(): Int = toList().hashCode() + override fun hashCode(): Int { + var result = javaClass.hashCode() + result = 31 * result + UniversalBlePigeonUtils.deepHash(this.uuid) + result = 31 * result + UniversalBlePigeonUtils.deepHash(this.properties) + result = 31 * result + UniversalBlePigeonUtils.deepHash(this.descriptors) + return result + } } /** Generated class from Pigeon that represents data sent in messages. */ @@ -312,15 +482,21 @@ data class UniversalBleDescriptor ( ) } override fun equals(other: Any?): Boolean { - if (other !is UniversalBleDescriptor) { + if (other == null || other.javaClass != javaClass) { return false } if (this === other) { return true } - return UniversalBlePigeonUtils.deepEquals(toList(), other.toList()) } + val other = other as UniversalBleDescriptor + return UniversalBlePigeonUtils.deepEquals(this.uuid, other.uuid) + } - override fun hashCode(): Int = toList().hashCode() + override fun hashCode(): Int { + var result = javaClass.hashCode() + result = 31 * result + UniversalBlePigeonUtils.deepHash(this.uuid) + return result + } } /** @@ -355,15 +531,23 @@ data class AndroidOptions ( ) } override fun equals(other: Any?): Boolean { - if (other !is AndroidOptions) { + if (other == null || other.javaClass != javaClass) { return false } if (this === other) { return true } - return UniversalBlePigeonUtils.deepEquals(toList(), other.toList()) } + val other = other as AndroidOptions + return UniversalBlePigeonUtils.deepEquals(this.requestLocationPermission, other.requestLocationPermission) && UniversalBlePigeonUtils.deepEquals(this.scanMode, other.scanMode) && UniversalBlePigeonUtils.deepEquals(this.reportDelayMillis, other.reportDelayMillis) + } - override fun hashCode(): Int = toList().hashCode() + override fun hashCode(): Int { + var result = javaClass.hashCode() + result = 31 * result + UniversalBlePigeonUtils.deepHash(this.requestLocationPermission) + result = 31 * result + UniversalBlePigeonUtils.deepHash(this.scanMode) + result = 31 * result + UniversalBlePigeonUtils.deepHash(this.reportDelayMillis) + return result + } } /** Generated class from Pigeon that represents data sent in messages. */ @@ -383,15 +567,21 @@ data class UniversalScanConfig ( ) } override fun equals(other: Any?): Boolean { - if (other !is UniversalScanConfig) { + if (other == null || other.javaClass != javaClass) { return false } if (this === other) { return true } - return UniversalBlePigeonUtils.deepEquals(toList(), other.toList()) } + val other = other as UniversalScanConfig + return UniversalBlePigeonUtils.deepEquals(this.android, other.android) + } - override fun hashCode(): Int = toList().hashCode() + override fun hashCode(): Int { + var result = javaClass.hashCode() + result = 31 * result + UniversalBlePigeonUtils.deepHash(this.android) + return result + } } /** @@ -421,15 +611,23 @@ data class UniversalScanFilter ( ) } override fun equals(other: Any?): Boolean { - if (other !is UniversalScanFilter) { + if (other == null || other.javaClass != javaClass) { return false } if (this === other) { return true } - return UniversalBlePigeonUtils.deepEquals(toList(), other.toList()) } + val other = other as UniversalScanFilter + return UniversalBlePigeonUtils.deepEquals(this.withServices, other.withServices) && UniversalBlePigeonUtils.deepEquals(this.withNamePrefix, other.withNamePrefix) && UniversalBlePigeonUtils.deepEquals(this.withManufacturerData, other.withManufacturerData) + } - override fun hashCode(): Int = toList().hashCode() + override fun hashCode(): Int { + var result = javaClass.hashCode() + result = 31 * result + UniversalBlePigeonUtils.deepHash(this.withServices) + result = 31 * result + UniversalBlePigeonUtils.deepHash(this.withNamePrefix) + result = 31 * result + UniversalBlePigeonUtils.deepHash(this.withManufacturerData) + return result + } } /** Generated class from Pigeon that represents data sent in messages. */ @@ -455,15 +653,23 @@ data class UniversalManufacturerDataFilter ( ) } override fun equals(other: Any?): Boolean { - if (other !is UniversalManufacturerDataFilter) { + if (other == null || other.javaClass != javaClass) { return false } if (this === other) { return true } - return UniversalBlePigeonUtils.deepEquals(toList(), other.toList()) } + val other = other as UniversalManufacturerDataFilter + return UniversalBlePigeonUtils.deepEquals(this.companyIdentifier, other.companyIdentifier) && UniversalBlePigeonUtils.deepEquals(this.data, other.data) && UniversalBlePigeonUtils.deepEquals(this.mask, other.mask) + } - override fun hashCode(): Int = toList().hashCode() + override fun hashCode(): Int { + var result = javaClass.hashCode() + result = 31 * result + UniversalBlePigeonUtils.deepHash(this.companyIdentifier) + result = 31 * result + UniversalBlePigeonUtils.deepHash(this.data) + result = 31 * result + UniversalBlePigeonUtils.deepHash(this.mask) + return result + } } /** Generated class from Pigeon that represents data sent in messages. */ @@ -486,15 +692,278 @@ data class UniversalManufacturerData ( ) } override fun equals(other: Any?): Boolean { - if (other !is UniversalManufacturerData) { + if (other == null || other.javaClass != javaClass) { + return false + } + if (this === other) { + return true + } + val other = other as UniversalManufacturerData + return UniversalBlePigeonUtils.deepEquals(this.companyIdentifier, other.companyIdentifier) && UniversalBlePigeonUtils.deepEquals(this.data, other.data) + } + + override fun hashCode(): Int { + var result = javaClass.hashCode() + result = 31 * result + UniversalBlePigeonUtils.deepHash(this.companyIdentifier) + result = 31 * result + UniversalBlePigeonUtils.deepHash(this.data) + return result + } +} + +/** Generated class from Pigeon that represents data sent in messages. */ +data class PeripheralService ( + val uuid: String, + val primary: Boolean, + val characteristics: List +) + { + companion object { + fun fromList(pigeonVar_list: List): PeripheralService { + val uuid = pigeonVar_list[0] as String + val primary = pigeonVar_list[1] as Boolean + val characteristics = pigeonVar_list[2] as List + return PeripheralService(uuid, primary, characteristics) + } + } + fun toList(): List { + return listOf( + uuid, + primary, + characteristics, + ) + } + override fun equals(other: Any?): Boolean { + if (other == null || other.javaClass != javaClass) { + return false + } + if (this === other) { + return true + } + val other = other as PeripheralService + return UniversalBlePigeonUtils.deepEquals(this.uuid, other.uuid) && UniversalBlePigeonUtils.deepEquals(this.primary, other.primary) && UniversalBlePigeonUtils.deepEquals(this.characteristics, other.characteristics) + } + + override fun hashCode(): Int { + var result = javaClass.hashCode() + result = 31 * result + UniversalBlePigeonUtils.deepHash(this.uuid) + result = 31 * result + UniversalBlePigeonUtils.deepHash(this.primary) + result = 31 * result + UniversalBlePigeonUtils.deepHash(this.characteristics) + return result + } +} + +/** Generated class from Pigeon that represents data sent in messages. */ +data class PeripheralCharacteristic ( + val uuid: String, + val properties: List, + val permissions: List, + val descriptors: List? = null, + val value: ByteArray? = null +) + { + companion object { + fun fromList(pigeonVar_list: List): PeripheralCharacteristic { + val uuid = pigeonVar_list[0] as String + val properties = pigeonVar_list[1] as List + val permissions = pigeonVar_list[2] as List + val descriptors = pigeonVar_list[3] as List? + val value = pigeonVar_list[4] as ByteArray? + return PeripheralCharacteristic(uuid, properties, permissions, descriptors, value) + } + } + fun toList(): List { + return listOf( + uuid, + properties, + permissions, + descriptors, + value, + ) + } + override fun equals(other: Any?): Boolean { + if (other == null || other.javaClass != javaClass) { + return false + } + if (this === other) { + return true + } + val other = other as PeripheralCharacteristic + return UniversalBlePigeonUtils.deepEquals(this.uuid, other.uuid) && UniversalBlePigeonUtils.deepEquals(this.properties, other.properties) && UniversalBlePigeonUtils.deepEquals(this.permissions, other.permissions) && UniversalBlePigeonUtils.deepEquals(this.descriptors, other.descriptors) && UniversalBlePigeonUtils.deepEquals(this.value, other.value) + } + + override fun hashCode(): Int { + var result = javaClass.hashCode() + result = 31 * result + UniversalBlePigeonUtils.deepHash(this.uuid) + result = 31 * result + UniversalBlePigeonUtils.deepHash(this.properties) + result = 31 * result + UniversalBlePigeonUtils.deepHash(this.permissions) + result = 31 * result + UniversalBlePigeonUtils.deepHash(this.descriptors) + result = 31 * result + UniversalBlePigeonUtils.deepHash(this.value) + return result + } +} + +/** Generated class from Pigeon that represents data sent in messages. */ +data class PeripheralDescriptor ( + val uuid: String, + val value: ByteArray? = null, + val permissions: List? = null +) + { + companion object { + fun fromList(pigeonVar_list: List): PeripheralDescriptor { + val uuid = pigeonVar_list[0] as String + val value = pigeonVar_list[1] as ByteArray? + val permissions = pigeonVar_list[2] as List? + return PeripheralDescriptor(uuid, value, permissions) + } + } + fun toList(): List { + return listOf( + uuid, + value, + permissions, + ) + } + override fun equals(other: Any?): Boolean { + if (other == null || other.javaClass != javaClass) { + return false + } + if (this === other) { + return true + } + val other = other as PeripheralDescriptor + return UniversalBlePigeonUtils.deepEquals(this.uuid, other.uuid) && UniversalBlePigeonUtils.deepEquals(this.value, other.value) && UniversalBlePigeonUtils.deepEquals(this.permissions, other.permissions) + } + + override fun hashCode(): Int { + var result = javaClass.hashCode() + result = 31 * result + UniversalBlePigeonUtils.deepHash(this.uuid) + result = 31 * result + UniversalBlePigeonUtils.deepHash(this.value) + result = 31 * result + UniversalBlePigeonUtils.deepHash(this.permissions) + return result + } +} + +/** Generated class from Pigeon that represents data sent in messages. */ +data class PeripheralReadRequestResult ( + val value: ByteArray, + val offset: Long? = null, + val status: Long? = null +) + { + companion object { + fun fromList(pigeonVar_list: List): PeripheralReadRequestResult { + val value = pigeonVar_list[0] as ByteArray + val offset = pigeonVar_list[1] as Long? + val status = pigeonVar_list[2] as Long? + return PeripheralReadRequestResult(value, offset, status) + } + } + fun toList(): List { + return listOf( + value, + offset, + status, + ) + } + override fun equals(other: Any?): Boolean { + if (other == null || other.javaClass != javaClass) { + return false + } + if (this === other) { + return true + } + val other = other as PeripheralReadRequestResult + return UniversalBlePigeonUtils.deepEquals(this.value, other.value) && UniversalBlePigeonUtils.deepEquals(this.offset, other.offset) && UniversalBlePigeonUtils.deepEquals(this.status, other.status) + } + + override fun hashCode(): Int { + var result = javaClass.hashCode() + result = 31 * result + UniversalBlePigeonUtils.deepHash(this.value) + result = 31 * result + UniversalBlePigeonUtils.deepHash(this.offset) + result = 31 * result + UniversalBlePigeonUtils.deepHash(this.status) + return result + } +} + +/** Generated class from Pigeon that represents data sent in messages. */ +data class PeripheralWriteRequestResult ( + val value: ByteArray? = null, + val offset: Long? = null, + val status: Long? = null +) + { + companion object { + fun fromList(pigeonVar_list: List): PeripheralWriteRequestResult { + val value = pigeonVar_list[0] as ByteArray? + val offset = pigeonVar_list[1] as Long? + val status = pigeonVar_list[2] as Long? + return PeripheralWriteRequestResult(value, offset, status) + } + } + fun toList(): List { + return listOf( + value, + offset, + status, + ) + } + override fun equals(other: Any?): Boolean { + if (other == null || other.javaClass != javaClass) { + return false + } + if (this === other) { + return true + } + val other = other as PeripheralWriteRequestResult + return UniversalBlePigeonUtils.deepEquals(this.value, other.value) && UniversalBlePigeonUtils.deepEquals(this.offset, other.offset) && UniversalBlePigeonUtils.deepEquals(this.status, other.status) + } + + override fun hashCode(): Int { + var result = javaClass.hashCode() + result = 31 * result + UniversalBlePigeonUtils.deepHash(this.value) + result = 31 * result + UniversalBlePigeonUtils.deepHash(this.offset) + result = 31 * result + UniversalBlePigeonUtils.deepHash(this.status) + return result + } +} + +/** Generated class from Pigeon that represents data sent in messages. */ +data class PeripheralManufacturerData ( + val manufacturerId: Long, + val data: ByteArray +) + { + companion object { + fun fromList(pigeonVar_list: List): PeripheralManufacturerData { + val manufacturerId = pigeonVar_list[0] as Long + val data = pigeonVar_list[1] as ByteArray + return PeripheralManufacturerData(manufacturerId, data) + } + } + fun toList(): List { + return listOf( + manufacturerId, + data, + ) + } + override fun equals(other: Any?): Boolean { + if (other == null || other.javaClass != javaClass) { return false } if (this === other) { return true } - return UniversalBlePigeonUtils.deepEquals(toList(), other.toList()) } + val other = other as PeripheralManufacturerData + return UniversalBlePigeonUtils.deepEquals(this.manufacturerId, other.manufacturerId) && UniversalBlePigeonUtils.deepEquals(this.data, other.data) + } - override fun hashCode(): Int = toList().hashCode() + override fun hashCode(): Int { + var result = javaClass.hashCode() + result = 31 * result + UniversalBlePigeonUtils.deepHash(this.manufacturerId) + result = 31 * result + UniversalBlePigeonUtils.deepHash(this.data) + return result + } } private open class UniversalBlePigeonCodec : StandardMessageCodec() { override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? { @@ -511,105 +980,177 @@ private open class UniversalBlePigeonCodec : StandardMessageCodec() { } 131.toByte() -> { return (readValue(buffer) as Long?)?.let { - UniversalBleErrorCode.ofRaw(it.toInt()) + PeripheralReadinessState.ofRaw(it.toInt()) } } 132.toByte() -> { + return (readValue(buffer) as Long?)?.let { + PeripheralAdvertisingState.ofRaw(it.toInt()) + } + } + 133.toByte() -> { + return (readValue(buffer) as Long?)?.let { + UniversalBleErrorCode.ofRaw(it.toInt()) + } + } + 134.toByte() -> { return (readValue(buffer) as? List)?.let { UniversalBleScanResult.fromList(it) } } - 133.toByte() -> { + 135.toByte() -> { return (readValue(buffer) as? List)?.let { UniversalBleService.fromList(it) } } - 134.toByte() -> { + 136.toByte() -> { return (readValue(buffer) as? List)?.let { UniversalBleCharacteristic.fromList(it) } } - 135.toByte() -> { + 137.toByte() -> { return (readValue(buffer) as? List)?.let { UniversalBleDescriptor.fromList(it) } } - 136.toByte() -> { + 138.toByte() -> { return (readValue(buffer) as? List)?.let { AndroidOptions.fromList(it) } } - 137.toByte() -> { + 139.toByte() -> { return (readValue(buffer) as? List)?.let { UniversalScanConfig.fromList(it) } } - 138.toByte() -> { + 140.toByte() -> { return (readValue(buffer) as? List)?.let { UniversalScanFilter.fromList(it) } } - 139.toByte() -> { + 141.toByte() -> { return (readValue(buffer) as? List)?.let { UniversalManufacturerDataFilter.fromList(it) } } - 140.toByte() -> { + 142.toByte() -> { return (readValue(buffer) as? List)?.let { UniversalManufacturerData.fromList(it) } } - else -> super.readValueOfType(type, buffer) - } - } - override fun writeValue(stream: ByteArrayOutputStream, value: Any?) { - when (value) { - is UniversalBleLogLevel -> { - stream.write(129) - writeValue(stream, value.raw.toLong()) + 143.toByte() -> { + return (readValue(buffer) as? List)?.let { + PeripheralService.fromList(it) + } } - is AndroidScanMode -> { - stream.write(130) - writeValue(stream, value.raw.toLong()) + 144.toByte() -> { + return (readValue(buffer) as? List)?.let { + PeripheralCharacteristic.fromList(it) + } } - is UniversalBleErrorCode -> { + 145.toByte() -> { + return (readValue(buffer) as? List)?.let { + PeripheralDescriptor.fromList(it) + } + } + 146.toByte() -> { + return (readValue(buffer) as? List)?.let { + PeripheralReadRequestResult.fromList(it) + } + } + 147.toByte() -> { + return (readValue(buffer) as? List)?.let { + PeripheralWriteRequestResult.fromList(it) + } + } + 148.toByte() -> { + return (readValue(buffer) as? List)?.let { + PeripheralManufacturerData.fromList(it) + } + } + else -> super.readValueOfType(type, buffer) + } + } + override fun writeValue(stream: ByteArrayOutputStream, value: Any?) { + when (value) { + is UniversalBleLogLevel -> { + stream.write(129) + writeValue(stream, value.raw.toLong()) + } + is AndroidScanMode -> { + stream.write(130) + writeValue(stream, value.raw.toLong()) + } + is PeripheralReadinessState -> { stream.write(131) writeValue(stream, value.raw.toLong()) } - is UniversalBleScanResult -> { + is PeripheralAdvertisingState -> { stream.write(132) + writeValue(stream, value.raw.toLong()) + } + is UniversalBleErrorCode -> { + stream.write(133) + writeValue(stream, value.raw.toLong()) + } + is UniversalBleScanResult -> { + stream.write(134) writeValue(stream, value.toList()) } is UniversalBleService -> { - stream.write(133) + stream.write(135) writeValue(stream, value.toList()) } is UniversalBleCharacteristic -> { - stream.write(134) + stream.write(136) writeValue(stream, value.toList()) } is UniversalBleDescriptor -> { - stream.write(135) + stream.write(137) writeValue(stream, value.toList()) } is AndroidOptions -> { - stream.write(136) + stream.write(138) writeValue(stream, value.toList()) } is UniversalScanConfig -> { - stream.write(137) + stream.write(139) writeValue(stream, value.toList()) } is UniversalScanFilter -> { - stream.write(138) + stream.write(140) writeValue(stream, value.toList()) } is UniversalManufacturerDataFilter -> { - stream.write(139) + stream.write(141) writeValue(stream, value.toList()) } is UniversalManufacturerData -> { - stream.write(140) + stream.write(142) + writeValue(stream, value.toList()) + } + is PeripheralService -> { + stream.write(143) + writeValue(stream, value.toList()) + } + is PeripheralCharacteristic -> { + stream.write(144) + writeValue(stream, value.toList()) + } + is PeripheralDescriptor -> { + stream.write(145) + writeValue(stream, value.toList()) + } + is PeripheralReadRequestResult -> { + stream.write(146) + writeValue(stream, value.toList()) + } + is PeripheralWriteRequestResult -> { + stream.write(147) + writeValue(stream, value.toList()) + } + is PeripheralManufacturerData -> { + stream.write(148) writeValue(stream, value.toList()) } else -> super.writeValue(stream, value) @@ -1197,3 +1738,397 @@ class UniversalBleCallbackChannel(private val binaryMessenger: BinaryMessenger, } } } +/** + * Flutter -> Native (peripheral) + * + * Generated interface from Pigeon that represents a handler of messages from Flutter. + */ +interface UniversalBlePeripheralChannel { + fun getAdvertisingState(): PeripheralAdvertisingState + fun getReadinessState(): PeripheralReadinessState + fun stopAdvertising() + fun addService(service: PeripheralService) + fun removeService(serviceId: String) + fun clearServices() + fun getServices(): List + fun startAdvertising(services: List, localName: String?, timeout: Long?, manufacturerData: PeripheralManufacturerData?, addManufacturerDataInScanResponse: Boolean) + fun updateCharacteristic(characteristicId: String, value: ByteArray, deviceId: String?) + /** + * Returns peripheral-client device ids currently subscribed to [characteristicId] + * (e.g. HID report characteristic). Used to restore app state after restart. + */ + fun getSubscribedClients(characteristicId: String): List + /** Returns max characteristic notify payload length for a connected client. */ + fun getMaximumNotifyLength(deviceId: String): Long? + + companion object { + /** The codec used by UniversalBlePeripheralChannel. */ + val codec: MessageCodec by lazy { + UniversalBlePigeonCodec() + } + /** Sets up an instance of `UniversalBlePeripheralChannel` to handle messages through the `binaryMessenger`. */ + @JvmOverloads + fun setUp(binaryMessenger: BinaryMessenger, api: UniversalBlePeripheralChannel?, messageChannelSuffix: String = "") { + val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else "" + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.universal_ble.UniversalBlePeripheralChannel.getAdvertisingState$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { _, reply -> + val wrapped: List = try { + listOf(api.getAdvertisingState()) + } catch (exception: Throwable) { + UniversalBlePigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.universal_ble.UniversalBlePeripheralChannel.getReadinessState$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { _, reply -> + val wrapped: List = try { + listOf(api.getReadinessState()) + } catch (exception: Throwable) { + UniversalBlePigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.universal_ble.UniversalBlePeripheralChannel.stopAdvertising$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { _, reply -> + val wrapped: List = try { + api.stopAdvertising() + listOf(null) + } catch (exception: Throwable) { + UniversalBlePigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.universal_ble.UniversalBlePeripheralChannel.addService$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val serviceArg = args[0] as PeripheralService + val wrapped: List = try { + api.addService(serviceArg) + listOf(null) + } catch (exception: Throwable) { + UniversalBlePigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.universal_ble.UniversalBlePeripheralChannel.removeService$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val serviceIdArg = args[0] as String + val wrapped: List = try { + api.removeService(serviceIdArg) + listOf(null) + } catch (exception: Throwable) { + UniversalBlePigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.universal_ble.UniversalBlePeripheralChannel.clearServices$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { _, reply -> + val wrapped: List = try { + api.clearServices() + listOf(null) + } catch (exception: Throwable) { + UniversalBlePigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.universal_ble.UniversalBlePeripheralChannel.getServices$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { _, reply -> + val wrapped: List = try { + listOf(api.getServices()) + } catch (exception: Throwable) { + UniversalBlePigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.universal_ble.UniversalBlePeripheralChannel.startAdvertising$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val servicesArg = args[0] as List + val localNameArg = args[1] as String? + val timeoutArg = args[2] as Long? + val manufacturerDataArg = args[3] as PeripheralManufacturerData? + val addManufacturerDataInScanResponseArg = args[4] as Boolean + val wrapped: List = try { + api.startAdvertising(servicesArg, localNameArg, timeoutArg, manufacturerDataArg, addManufacturerDataInScanResponseArg) + listOf(null) + } catch (exception: Throwable) { + UniversalBlePigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.universal_ble.UniversalBlePeripheralChannel.updateCharacteristic$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val characteristicIdArg = args[0] as String + val valueArg = args[1] as ByteArray + val deviceIdArg = args[2] as String? + val wrapped: List = try { + api.updateCharacteristic(characteristicIdArg, valueArg, deviceIdArg) + listOf(null) + } catch (exception: Throwable) { + UniversalBlePigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.universal_ble.UniversalBlePeripheralChannel.getSubscribedClients$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val characteristicIdArg = args[0] as String + val wrapped: List = try { + listOf(api.getSubscribedClients(characteristicIdArg)) + } catch (exception: Throwable) { + UniversalBlePigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.universal_ble.UniversalBlePeripheralChannel.getMaximumNotifyLength$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val deviceIdArg = args[0] as String + val wrapped: List = try { + listOf(api.getMaximumNotifyLength(deviceIdArg)) + } catch (exception: Throwable) { + UniversalBlePigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + } + } +} +/** + * Native -> Flutter (peripheral) + * + * Generated class from Pigeon that represents Flutter messages that can be called from Kotlin. + */ +class UniversalBlePeripheralCallback(private val binaryMessenger: BinaryMessenger, private val messageChannelSuffix: String = "") { + companion object { + /** The codec used by UniversalBlePeripheralCallback. */ + val codec: MessageCodec by lazy { + UniversalBlePigeonCodec() + } + } + fun onReadRequest(deviceIdArg: String, characteristicIdArg: String, offsetArg: Long, valueArg: ByteArray?, callback: (Result) -> Unit) +{ + val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else "" + val channelName = "dev.flutter.pigeon.universal_ble.UniversalBlePeripheralCallback.onReadRequest$separatedMessageChannelSuffix" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(deviceIdArg, characteristicIdArg, offsetArg, valueArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + val output = it[0] as PeripheralReadRequestResult? + callback(Result.success(output)) + } + } else { + callback(Result.failure(UniversalBlePigeonUtils.createConnectionError(channelName))) + } + } + } + fun onWriteRequest(deviceIdArg: String, characteristicIdArg: String, offsetArg: Long, valueArg: ByteArray?, callback: (Result) -> Unit) +{ + val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else "" + val channelName = "dev.flutter.pigeon.universal_ble.UniversalBlePeripheralCallback.onWriteRequest$separatedMessageChannelSuffix" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(deviceIdArg, characteristicIdArg, offsetArg, valueArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + val output = it[0] as PeripheralWriteRequestResult? + callback(Result.success(output)) + } + } else { + callback(Result.failure(UniversalBlePigeonUtils.createConnectionError(channelName))) + } + } + } + fun onDescriptorReadRequest(deviceIdArg: String, characteristicIdArg: String, descriptorIdArg: String, offsetArg: Long, valueArg: ByteArray?, callback: (Result) -> Unit) +{ + val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else "" + val channelName = "dev.flutter.pigeon.universal_ble.UniversalBlePeripheralCallback.onDescriptorReadRequest$separatedMessageChannelSuffix" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(deviceIdArg, characteristicIdArg, descriptorIdArg, offsetArg, valueArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + val output = it[0] as PeripheralReadRequestResult? + callback(Result.success(output)) + } + } else { + callback(Result.failure(UniversalBlePigeonUtils.createConnectionError(channelName))) + } + } + } + fun onDescriptorWriteRequest(deviceIdArg: String, characteristicIdArg: String, descriptorIdArg: String, offsetArg: Long, valueArg: ByteArray?, callback: (Result) -> Unit) +{ + val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else "" + val channelName = "dev.flutter.pigeon.universal_ble.UniversalBlePeripheralCallback.onDescriptorWriteRequest$separatedMessageChannelSuffix" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(deviceIdArg, characteristicIdArg, descriptorIdArg, offsetArg, valueArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + val output = it[0] as PeripheralWriteRequestResult? + callback(Result.success(output)) + } + } else { + callback(Result.failure(UniversalBlePigeonUtils.createConnectionError(channelName))) + } + } + } + fun onCharacteristicSubscriptionChange(deviceIdArg: String, characteristicIdArg: String, isSubscribedArg: Boolean, nameArg: String?, callback: (Result) -> Unit) +{ + val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else "" + val channelName = "dev.flutter.pigeon.universal_ble.UniversalBlePeripheralCallback.onCharacteristicSubscriptionChange$separatedMessageChannelSuffix" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(deviceIdArg, characteristicIdArg, isSubscribedArg, nameArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback(Result.failure(UniversalBlePigeonUtils.createConnectionError(channelName))) + } + } + } + fun onAdvertisingStateChange(stateArg: PeripheralAdvertisingState, errorArg: String?, callback: (Result) -> Unit) +{ + val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else "" + val channelName = "dev.flutter.pigeon.universal_ble.UniversalBlePeripheralCallback.onAdvertisingStateChange$separatedMessageChannelSuffix" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(stateArg, errorArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback(Result.failure(UniversalBlePigeonUtils.createConnectionError(channelName))) + } + } + } + fun onServiceAdded(serviceIdArg: String, errorArg: String?, callback: (Result) -> Unit) +{ + val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else "" + val channelName = "dev.flutter.pigeon.universal_ble.UniversalBlePeripheralCallback.onServiceAdded$separatedMessageChannelSuffix" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(serviceIdArg, errorArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback(Result.failure(UniversalBlePigeonUtils.createConnectionError(channelName))) + } + } + } + fun onMtuChange(deviceIdArg: String, mtuArg: Long, callback: (Result) -> Unit) +{ + val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else "" + val channelName = "dev.flutter.pigeon.universal_ble.UniversalBlePeripheralCallback.onMtuChange$separatedMessageChannelSuffix" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(deviceIdArg, mtuArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback(Result.failure(UniversalBlePigeonUtils.createConnectionError(channelName))) + } + } + } + fun onConnectionStateChange(deviceIdArg: String, connectedArg: Boolean, callback: (Result) -> Unit) +{ + val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else "" + val channelName = "dev.flutter.pigeon.universal_ble.UniversalBlePeripheralCallback.onConnectionStateChange$separatedMessageChannelSuffix" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(deviceIdArg, connectedArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback(Result.failure(UniversalBlePigeonUtils.createConnectionError(channelName))) + } + } + } +} diff --git a/android/src/main/kotlin/com/navideck/universal_ble/UniversalBlePeripheralExtensions.kt b/android/src/main/kotlin/com/navideck/universal_ble/UniversalBlePeripheralExtensions.kt new file mode 100644 index 00000000..86e96ba3 --- /dev/null +++ b/android/src/main/kotlin/com/navideck/universal_ble/UniversalBlePeripheralExtensions.kt @@ -0,0 +1,158 @@ +package com.navideck.universal_ble + +import android.app.Activity +import android.bluetooth.BluetoothGattCharacteristic +import android.bluetooth.BluetoothGattDescriptor +import android.bluetooth.BluetoothGattService +import android.content.pm.PackageManager +import android.util.Log +import java.util.Collections +import java.util.UUID + +private val bluetoothGattCharacteristics: + MutableMap = HashMap() +private val descriptorValueReadMap: MutableMap = HashMap() +val subscribedCharDevicesMap: MutableMap> = HashMap() +const val peripheralDescriptorCCUUID = "00002902-0000-1000-8000-00805f9b34fb" + +fun clearPeripheralCaches() { + synchronized(bluetoothGattCharacteristics) { + bluetoothGattCharacteristics.clear() + } + synchronized(descriptorValueReadMap) { + descriptorValueReadMap.clear() + } + synchronized(subscribedCharDevicesMap) { + subscribedCharDevicesMap.clear() + } +} + +fun Activity.havePermission(permissions: Array): Boolean { + for (perm in permissions) { + if (checkCallingOrSelfPermission(perm) != PackageManager.PERMISSION_GRANTED) { + return false + } + } + return true +} + +fun PeripheralService.toGattService(): BluetoothGattService { + val service = BluetoothGattService( + UUID.fromString(uuid), + if (primary) BluetoothGattService.SERVICE_TYPE_PRIMARY else BluetoothGattService.SERVICE_TYPE_SECONDARY, + ) + characteristics.forEach { + service.addCharacteristic(it.toGattCharacteristic()) + } + return service +} + +fun PeripheralCharacteristic.toGattCharacteristic(): BluetoothGattCharacteristic { + val characteristic = BluetoothGattCharacteristic( + UUID.fromString(uuid), + properties.toPropertiesList(), + permissions.toPermissionsList(), + ) + value?.let { characteristic.value = it } + descriptors?.forEach { + characteristic.addDescriptor(it.toGattDescriptor()) + } + + addCCDescriptorIfRequired(this, characteristic) + synchronized(bluetoothGattCharacteristics) { + if (bluetoothGattCharacteristics[uuid] == null) { + bluetoothGattCharacteristics[uuid] = characteristic + } + } + return characteristic +} + +private fun addCCDescriptorIfRequired( + peripheralCharacteristic: PeripheralCharacteristic, + characteristic: BluetoothGattCharacteristic, +) { + val hasNotifyOrIndicate = + characteristic.properties and BluetoothGattCharacteristic.PROPERTY_NOTIFY != 0 || + characteristic.properties and BluetoothGattCharacteristic.PROPERTY_INDICATE != 0 + if (!hasNotifyOrIndicate) return + + var hasCccd = false + for (descriptor in peripheralCharacteristic.descriptors ?: Collections.emptyList()) { + if (descriptor.uuid.equals(peripheralDescriptorCCUUID, ignoreCase = true)) { + hasCccd = true + break + } + } + if (hasCccd) return + + val cccd = BluetoothGattDescriptor( + UUID.fromString(peripheralDescriptorCCUUID), + BluetoothGattDescriptor.PERMISSION_READ or BluetoothGattDescriptor.PERMISSION_WRITE, + ) + cccd.value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE + characteristic.addDescriptor(cccd) + Log.d("UniversalBlePeripheral", "Added CCCD for ${characteristic.uuid}") +} + +fun PeripheralDescriptor.toGattDescriptor(): BluetoothGattDescriptor { + val permission = permissions?.toPermissionsList() + ?: BluetoothGattDescriptor.PERMISSION_READ or BluetoothGattDescriptor.PERMISSION_WRITE + val descriptor = BluetoothGattDescriptor(UUID.fromString(uuid), permission) + value?.let { + descriptor.value = it + synchronized(descriptorValueReadMap) { + descriptorValueReadMap[uuid.lowercase()] = it + } + } + return descriptor +} + +fun BluetoothGattDescriptor.getCacheValue(): ByteArray? = + synchronized(descriptorValueReadMap) { + descriptorValueReadMap[uuid.toString().lowercase()] + } + +fun String.findCharacteristic(): BluetoothGattCharacteristic? = + synchronized(bluetoothGattCharacteristics) { + bluetoothGattCharacteristics[this] + } + +fun String.findService(): BluetoothGattService? { + synchronized(bluetoothGattCharacteristics) { + for (characteristic in bluetoothGattCharacteristics.values) { + if (characteristic.service?.uuid.toString() == this) { + return characteristic.service + } + } + } + return null +} + +private fun List.toPropertiesList(): Int = + map { it.toInt() }.fold(0) { acc, i -> acc or i.toPropertyBits() } + +private fun List.toPermissionsList(): Int = + map { it.toInt() }.fold(0) { acc, i -> acc or i.toPermissionBits() } + +private fun Int.toPropertyBits(): Int = when (this) { + 0 -> BluetoothGattCharacteristic.PROPERTY_BROADCAST + 1 -> BluetoothGattCharacteristic.PROPERTY_READ + 2 -> BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE + 3 -> BluetoothGattCharacteristic.PROPERTY_WRITE + 4 -> BluetoothGattCharacteristic.PROPERTY_NOTIFY + 5 -> BluetoothGattCharacteristic.PROPERTY_INDICATE + 6 -> BluetoothGattCharacteristic.PROPERTY_SIGNED_WRITE + 7 -> BluetoothGattCharacteristic.PROPERTY_EXTENDED_PROPS + // Android uses dedicated encrypted notify/indicate property bits. + 8 -> 0x100 // PROPERTY_NOTIFY_ENCRYPTED + 9 -> 0x200 // PROPERTY_INDICATE_ENCRYPTED + else -> 0 +} + +private fun Int.toPermissionBits(): Int = when (this) { + 0 -> BluetoothGattCharacteristic.PERMISSION_READ + 1 -> BluetoothGattCharacteristic.PERMISSION_WRITE + 2 -> BluetoothGattCharacteristic.PERMISSION_READ_ENCRYPTED + 3 -> BluetoothGattCharacteristic.PERMISSION_WRITE_ENCRYPTED + else -> 0 +} diff --git a/android/src/main/kotlin/com/navideck/universal_ble/UniversalBlePeripheralPlugin.kt b/android/src/main/kotlin/com/navideck/universal_ble/UniversalBlePeripheralPlugin.kt new file mode 100644 index 00000000..0a2fe387 --- /dev/null +++ b/android/src/main/kotlin/com/navideck/universal_ble/UniversalBlePeripheralPlugin.kt @@ -0,0 +1,550 @@ +package com.navideck.universal_ble + +import android.annotation.SuppressLint +import android.app.Activity +import android.bluetooth.BluetoothAdapter +import android.bluetooth.BluetoothDevice +import android.bluetooth.BluetoothGatt +import android.bluetooth.BluetoothGattCharacteristic +import android.bluetooth.BluetoothGattDescriptor +import android.bluetooth.BluetoothGattServer +import android.bluetooth.BluetoothGattServerCallback +import android.bluetooth.BluetoothGattService +import android.bluetooth.BluetoothManager +import android.bluetooth.BluetoothProfile +import android.bluetooth.le.AdvertiseCallback +import android.bluetooth.le.AdvertiseData +import android.bluetooth.le.AdvertiseSettings +import android.bluetooth.le.BluetoothLeAdvertiser +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Context.RECEIVER_NOT_EXPORTED +import android.content.Intent +import android.content.IntentFilter +import android.os.Build +import android.os.Handler +import android.os.ParcelUuid +import android.util.Log +import io.flutter.plugin.common.BinaryMessenger + +private const val TAG = "UniversalBlePeripheral" + +@SuppressLint("MissingPermission") +class UniversalBlePeripheralPlugin( + private val applicationContext: Context, + messenger: BinaryMessenger, +) : UniversalBlePeripheralChannel { + private var activity: Activity? = null + private val callback = UniversalBlePeripheralCallback(messenger) + private val handler = Handler(applicationContext.mainLooper) + private val bluetoothManager = + applicationContext.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager + private var bluetoothLeAdvertiser: BluetoothLeAdvertiser? = null + private var gattServer: BluetoothGattServer? = null + private val bluetoothDevicesMap: MutableMap = HashMap() + private val mtuByDeviceId: MutableMap = HashMap() + private val listOfDevicesWaitingForBond = mutableListOf() + private val emptyBytes = byteArrayOf() + private var advertisingState: PeripheralAdvertisingState = PeripheralAdvertisingState.IDLE + private var receiverRegistered = false + private var originalAdapterName: String? = null + private var adapterNameOverridden = false + + init { + kotlin.runCatching { initializePeripheral() }.onFailure { + Log.w(TAG, "Deferred peripheral init: ${it.message}") + } + } + + fun attachActivity(activity: Activity?) { + this.activity = activity + } + + fun dispose() { + if (receiverRegistered) { + kotlin.runCatching { applicationContext.unregisterReceiver(broadcastReceiver) } + receiverRegistered = false + } + kotlin.runCatching { bluetoothLeAdvertiser?.stopAdvertising(advertiseCallback) } + restoreAdapterNameIfNeeded() + gattServer?.close() + gattServer = null + bluetoothDevicesMap.clear() + synchronized(listOfDevicesWaitingForBond) { + listOfDevicesWaitingForBond.clear() + } + synchronized(mtuByDeviceId) { mtuByDeviceId.clear() } + clearPeripheralCaches() + } + + private fun initializePeripheral() { + val adapter = bluetoothManager.adapter + ?: throw UnsupportedOperationException("Bluetooth is not available.") + if (bluetoothLeAdvertiser != null && gattServer != null && receiverRegistered) return + bluetoothLeAdvertiser = adapter.bluetoothLeAdvertiser + ?: throw UnsupportedOperationException( + "Bluetooth LE Advertising not supported on this device.", + ) + gattServer = bluetoothManager.openGattServer(applicationContext, gattServerCallback) + ?: throw UnsupportedOperationException("gattServer is null, check Bluetooth is ON.") + + if (!receiverRegistered) { + val intentFilter = IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + applicationContext.registerReceiver( + broadcastReceiver, + intentFilter, + RECEIVER_NOT_EXPORTED, + ) + } else { + @Suppress("DEPRECATION") + applicationContext.registerReceiver(broadcastReceiver, intentFilter) + } + receiverRegistered = true + } + } + + override fun getAdvertisingState(): PeripheralAdvertisingState = advertisingState + + override fun getReadinessState(): PeripheralReadinessState { + val adapter = bluetoothManager.adapter ?: return PeripheralReadinessState.UNSUPPORTED + if (!adapter.isMultipleAdvertisementSupported) return PeripheralReadinessState.UNSUPPORTED + if (!adapter.isEnabled) return PeripheralReadinessState.BLUETOOTH_OFF + return PeripheralReadinessState.READY + } + + override fun addService(service: PeripheralService) { + initializePeripheral() + gattServer?.addService(service.toGattService()) + } + + override fun removeService(serviceId: String) { + serviceId.findService()?.let { gattServer?.removeService(it) } + } + + override fun clearServices() { + initializePeripheral() + gattServer?.clearServices() + } + + override fun getServices(): List = + runCatching { + initializePeripheral() + gattServer?.services?.map { it.uuid.toString() } ?: emptyList() + }.getOrDefault(emptyList()) + + override fun startAdvertising( + services: List, + localName: String?, + timeout: Long?, + manufacturerData: PeripheralManufacturerData?, + addManufacturerDataInScanResponse: Boolean, + ) { + initializePeripheral() + advertisingState = PeripheralAdvertisingState.STARTING + callback.onAdvertisingStateChange(PeripheralAdvertisingState.STARTING, null) {} + if (!isBluetoothEnabled()) { + activity?.startActivityForResult( + Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE), + 0xB1E, + ) + throw Exception("Bluetooth is not enabled") + } + + handler.post { + val adapter = bluetoothManager.adapter + if (localName != null && adapter != null && adapter.name != localName) { + if (!adapterNameOverridden) { + originalAdapterName = adapter.name + } + adapter.name = localName + adapterNameOverridden = true + } + val advertiseSettings = AdvertiseSettings.Builder() + .setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_HIGH) + .setConnectable(true) + .setTimeout(timeout?.toInt() ?: 0) + .setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY) + .build() + + val advertiseDataBuilder = AdvertiseData.Builder() + .setIncludeTxPowerLevel(false) + .setIncludeDeviceName(localName != null) + val scanResponseBuilder = AdvertiseData.Builder() + .setIncludeTxPowerLevel(false) + .setIncludeDeviceName(localName != null) + + manufacturerData?.let { + if (addManufacturerDataInScanResponse) { + scanResponseBuilder.addManufacturerData( + it.manufacturerId.toInt(), + it.data, + ) + } else { + advertiseDataBuilder.addManufacturerData( + it.manufacturerId.toInt(), + it.data, + ) + } + } + services.forEach { advertiseDataBuilder.addServiceUuid(ParcelUuid.fromString(it)) } + + bluetoothLeAdvertiser?.startAdvertising( + advertiseSettings, + advertiseDataBuilder.build(), + scanResponseBuilder.build(), + advertiseCallback, + ) + } + } + + override fun stopAdvertising() { + initializePeripheral() + advertisingState = PeripheralAdvertisingState.STOPPING + callback.onAdvertisingStateChange(PeripheralAdvertisingState.STOPPING, null) {} + handler.post { + bluetoothLeAdvertiser?.stopAdvertising(advertiseCallback) + restoreAdapterNameIfNeeded() + advertisingState = PeripheralAdvertisingState.IDLE + callback.onAdvertisingStateChange(PeripheralAdvertisingState.IDLE, null) {} + } + } + + override fun updateCharacteristic(characteristicId: String, value: ByteArray, deviceId: String?) { + initializePeripheral() + val characteristic = + characteristicId.findCharacteristic() ?: throw Exception("Characteristic not found") + characteristic.value = value + val targetDevices = synchronized(bluetoothDevicesMap) { + if (deviceId != null) { + listOf(bluetoothDevicesMap[deviceId] ?: throw Exception("Device not found")) + } else { + bluetoothDevicesMap.values.toList() + } + } + val indicate = + (characteristic.properties and BluetoothGattCharacteristic.PROPERTY_INDICATE) != 0 + targetDevices.forEach { device -> + handler.post { + gattServer?.notifyCharacteristicChanged(device, characteristic, indicate) + } + } + } + + override fun getSubscribedClients(characteristicId: String): List { + synchronized(subscribedCharDevicesMap) { + return subscribedCharDevicesMap.entries + .filter { (_, chars) -> + chars.any { it.equals(characteristicId, ignoreCase = true) } + } + .map { it.key } + } + } + + override fun getMaximumNotifyLength(deviceId: String): Long? { + val mtu = synchronized(mtuByDeviceId) { mtuByDeviceId[deviceId] } ?: return null + return (mtu - 3).coerceAtLeast(0).toLong() + } + + private fun isBluetoothEnabled(): Boolean = + bluetoothManager.adapter?.isEnabled ?: false + + private fun onConnectionUpdate(device: BluetoothDevice, status: Int, newState: Int) { + Log.e(TAG, "onConnectionStateChange: $status -> $newState") + handler.post { + callback.onConnectionStateChange( + device.address, + newState == BluetoothProfile.STATE_CONNECTED, + ) {} + } + if (newState == BluetoothProfile.STATE_DISCONNECTED) cleanConnection(device) + } + + private fun cleanConnection(device: BluetoothDevice) { + val deviceAddress = device.address + val subscribedCharUUID = synchronized(subscribedCharDevicesMap) { + val current = subscribedCharDevicesMap[deviceAddress]?.toList() ?: emptyList() + subscribedCharDevicesMap.remove(deviceAddress) + current + } + subscribedCharUUID.forEach { charUUID -> + handler.post { + callback.onCharacteristicSubscriptionChange( + deviceAddress, + charUUID, + false, + device.name, + ) {} + } + } + } + + private val advertiseCallback = object : AdvertiseCallback() { + override fun onStartFailure(errorCode: Int) { + super.onStartFailure(errorCode) + handler.post { + restoreAdapterNameIfNeeded() + val errorMessage = when (errorCode) { + ADVERTISE_FAILED_ALREADY_STARTED -> "Already started" + ADVERTISE_FAILED_DATA_TOO_LARGE -> "Data too large" + ADVERTISE_FAILED_FEATURE_UNSUPPORTED -> "Feature unsupported" + ADVERTISE_FAILED_INTERNAL_ERROR -> "Internal error" + ADVERTISE_FAILED_TOO_MANY_ADVERTISERS -> "Too many advertisers" + else -> "Failed to start advertising: $errorCode" + } + advertisingState = PeripheralAdvertisingState.ERROR + callback.onAdvertisingStateChange(PeripheralAdvertisingState.ERROR, errorMessage) {} + } + } + + override fun onStartSuccess(settingsInEffect: AdvertiseSettings?) { + super.onStartSuccess(settingsInEffect) + advertisingState = PeripheralAdvertisingState.ADVERTISING + handler.post { + callback.onAdvertisingStateChange(PeripheralAdvertisingState.ADVERTISING, null) {} + } + } + } + + private val gattServerCallback = object : BluetoothGattServerCallback() { + override fun onConnectionStateChange(device: BluetoothDevice, status: Int, newState: Int) { + super.onConnectionStateChange(device, status, newState) + when (newState) { + BluetoothProfile.STATE_CONNECTED -> { + synchronized(bluetoothDevicesMap) { + bluetoothDevicesMap[device.address] = device + } + if (device.bondState == BluetoothDevice.BOND_NONE) { + synchronized(listOfDevicesWaitingForBond) { + listOfDevicesWaitingForBond.add(device.address) + } + device.createBond() + } else if (device.bondState == BluetoothDevice.BOND_BONDED) { + handler.post { gattServer?.connect(device, true) } + } + onConnectionUpdate(device, status, newState) + } + BluetoothProfile.STATE_DISCONNECTED -> { + synchronized(bluetoothDevicesMap) { + bluetoothDevicesMap.remove(device.address) + } + onConnectionUpdate(device, status, newState) + } + } + } + + override fun onMtuChanged(device: BluetoothDevice?, mtu: Int) { + super.onMtuChanged(device, mtu) + device?.address?.let { address -> + synchronized(mtuByDeviceId) { mtuByDeviceId[address] = mtu } + handler.post { callback.onMtuChange(address, mtu.toLong()) {} } + } + } + + override fun onCharacteristicReadRequest( + device: BluetoothDevice, + requestId: Int, + offset: Int, + characteristic: BluetoothGattCharacteristic, + ) { + super.onCharacteristicReadRequest(device, requestId, offset, characteristic) + if (gattServer == null) return + handler.post { + callback.onReadRequest( + deviceIdArg = device.address, + characteristicIdArg = characteristic.uuid.toString(), + offsetArg = offset.toLong(), + valueArg = characteristic.value, + ) { result -> + val readResult = result.getOrNull() + if (readResult == null) { + gattServer?.sendResponse( + device, requestId, BluetoothGatt.GATT_FAILURE, 0, emptyBytes, + ) + } else { + gattServer?.sendResponse( + device, + requestId, + readResult.status?.toInt() ?: BluetoothGatt.GATT_SUCCESS, + readResult.offset?.toInt() ?: 0, + readResult.value, + ) + } + } + } + } + + override fun onCharacteristicWriteRequest( + device: BluetoothDevice, + requestId: Int, + characteristic: BluetoothGattCharacteristic, + preparedWrite: Boolean, + responseNeeded: Boolean, + offset: Int, + value: ByteArray, + ) { + super.onCharacteristicWriteRequest( + device, requestId, characteristic, preparedWrite, responseNeeded, offset, value, + ) + handler.post { + callback.onWriteRequest( + deviceIdArg = device.address, + characteristicIdArg = characteristic.uuid.toString(), + offsetArg = offset.toLong(), + valueArg = value, + ) { writeResponse -> + val writeResult = writeResponse.getOrNull() + if (responseNeeded) { + gattServer?.sendResponse( + device, + requestId, + writeResult?.status?.toInt() ?: BluetoothGatt.GATT_SUCCESS, + writeResult?.offset?.toInt() ?: 0, + writeResult?.value ?: emptyBytes, + ) + } + } + } + } + + override fun onServiceAdded(status: Int, service: BluetoothGattService) { + super.onServiceAdded(status, service) + val error = if (status != BluetoothGatt.GATT_SUCCESS) "Adding Service failed" else null + handler.post { callback.onServiceAdded(service.uuid.toString(), error) {} } + } + + override fun onDescriptorReadRequest( + device: BluetoothDevice, + requestId: Int, + offset: Int, + descriptor: BluetoothGattDescriptor, + ) { + super.onDescriptorReadRequest(device, requestId, offset, descriptor) + handler.post { + callback.onDescriptorReadRequest( + deviceIdArg = device.address, + characteristicIdArg = descriptor.characteristic.uuid.toString(), + descriptorIdArg = descriptor.uuid.toString(), + offsetArg = offset.toLong(), + valueArg = descriptor.getCacheValue(), + ) { result -> + val readResult = result.getOrNull() + if (readResult == null) { + gattServer?.sendResponse( + device, + requestId, + BluetoothGatt.GATT_FAILURE, + offset, + emptyBytes, + ) + } else { + gattServer?.sendResponse( + device, + requestId, + readResult.status?.toInt() ?: BluetoothGatt.GATT_SUCCESS, + readResult.offset?.toInt() ?: offset, + readResult.value, + ) + } + } + } + } + + override fun onDescriptorWriteRequest( + device: BluetoothDevice?, + requestId: Int, + descriptor: BluetoothGattDescriptor, + preparedWrite: Boolean, + responseNeeded: Boolean, + offset: Int, + value: ByteArray?, + ) { + super.onDescriptorWriteRequest( + device, requestId, descriptor, preparedWrite, responseNeeded, offset, value, + ) + descriptor.value = value + if (descriptor.uuid.toString().lowercase() == peripheralDescriptorCCUUID.lowercase()) { + val isSubscribed = + BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE.contentEquals(value) || + BluetoothGattDescriptor.ENABLE_INDICATION_VALUE.contentEquals(value) + val characteristicId = descriptor.characteristic.uuid.toString() + device?.address?.let { address -> + handler.post { + callback.onCharacteristicSubscriptionChange( + address, characteristicId, isSubscribed, device.name, + ) {} + } + synchronized(subscribedCharDevicesMap) { + val charList = subscribedCharDevicesMap[address] ?: mutableListOf() + if (isSubscribed) { + charList.add(characteristicId) + } else { + charList.remove(characteristicId) + } + subscribedCharDevicesMap[address] = charList + } + } + } + if (responseNeeded) { + handler.post { + callback.onDescriptorWriteRequest( + deviceIdArg = device?.address ?: "", + characteristicIdArg = descriptor.characteristic.uuid.toString(), + descriptorIdArg = descriptor.uuid.toString(), + offsetArg = offset.toLong(), + valueArg = value, + ) { writeResponse -> + val writeResult = writeResponse.getOrNull() + gattServer?.sendResponse( + device, + requestId, + writeResult?.status?.toInt() ?: BluetoothGatt.GATT_SUCCESS, + writeResult?.offset?.toInt() ?: offset, + writeResult?.value ?: value ?: emptyBytes, + ) + } + } + } + } + } + + private val broadcastReceiver = object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + when (intent.action) { + BluetoothDevice.ACTION_BOND_STATE_CHANGED -> { + val state = intent.getIntExtra( + BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.ERROR, + ) + val device: BluetoothDevice? = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE, BluetoothDevice::class.java) + } else { + @Suppress("DEPRECATION") + intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE) + } + val waitingForConnection = synchronized(listOfDevicesWaitingForBond) { + listOfDevicesWaitingForBond.contains(device?.address) + } + if (state == BluetoothDevice.BOND_BONDED && device != null && waitingForConnection) { + synchronized(listOfDevicesWaitingForBond) { + listOfDevicesWaitingForBond.remove(device.address) + } + synchronized(bluetoothDevicesMap) { + bluetoothDevicesMap[device.address] = device + } + handler.post { gattServer?.connect(device, true) } + } + } + } + } + } + + private fun restoreAdapterNameIfNeeded() { + if (!adapterNameOverridden) return + val adapter = bluetoothManager.adapter ?: return + kotlin.runCatching { + adapter.name = originalAdapterName + } + adapterNameOverridden = false + originalAdapterName = null + } +} diff --git a/android/src/main/kotlin/com/navideck/universal_ble/UniversalBlePlugin.kt b/android/src/main/kotlin/com/navideck/universal_ble/UniversalBlePlugin.kt index beac594f..eb5247d4 100644 --- a/android/src/main/kotlin/com/navideck/universal_ble/UniversalBlePlugin.kt +++ b/android/src/main/kotlin/com/navideck/universal_ble/UniversalBlePlugin.kt @@ -50,6 +50,7 @@ class UniversalBlePlugin : UniversalBlePlatformChannel, BluetoothGattCallback(), private lateinit var safeScanner: SafeScanner private val cachedServicesMap = mutableMapOf>() private val universalBleFilterUtil = UniversalBleFilterUtil() + private lateinit var peripheralPlugin: UniversalBlePeripheralPlugin // Flutter Futures private var bluetoothEnableRequestFuture: ((Result) -> Unit)? = null @@ -65,6 +66,11 @@ class UniversalBlePlugin : UniversalBlePlatformChannel, BluetoothGattCallback(), override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { UniversalBlePlatformChannel.setUp(flutterPluginBinding.binaryMessenger, this) + peripheralPlugin = UniversalBlePeripheralPlugin( + flutterPluginBinding.applicationContext, + flutterPluginBinding.binaryMessenger + ) + UniversalBlePeripheralChannel.setUp(flutterPluginBinding.binaryMessenger, peripheralPlugin) callbackChannel = UniversalBleCallbackChannel(flutterPluginBinding.binaryMessenger) context = flutterPluginBinding.applicationContext mainThreadHandler = Handler(Looper.getMainLooper()) @@ -85,6 +91,8 @@ class UniversalBlePlugin : UniversalBlePlatformChannel, BluetoothGattCallback(), override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) { bluetoothManager.adapter.bluetoothLeScanner?.stopScan(scanCallback) context.unregisterReceiver(broadcastReceiver) + peripheralPlugin.dispose() + UniversalBlePeripheralChannel.setUp(binding.binaryMessenger, null) callbackChannel = null mainThreadHandler = null permissionHandler = null @@ -1355,6 +1363,7 @@ class UniversalBlePlugin : UniversalBlePlatformChannel, BluetoothGattCallback(), override fun onAttachedToActivity(binding: ActivityPluginBinding) { activity = binding.activity + peripheralPlugin.attachActivity(binding.activity) binding.addActivityResultListener(this) binding.addRequestPermissionsResultListener(this) permissionHandler?.attachActivity(binding.activity) @@ -1362,6 +1371,7 @@ class UniversalBlePlugin : UniversalBlePlatformChannel, BluetoothGattCallback(), override fun onDetachedFromActivity() { activity = null + peripheralPlugin.attachActivity(null) permissionHandler?.attachActivity(null) } @@ -1371,6 +1381,7 @@ class UniversalBlePlugin : UniversalBlePlatformChannel, BluetoothGattCallback(), override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) { activity = binding.activity + peripheralPlugin.attachActivity(binding.activity) permissionHandler?.attachActivity(binding.activity) } diff --git a/darwin/universal_ble/Sources/universal_ble/UniversalBle.g.swift b/darwin/universal_ble/Sources/universal_ble/UniversalBle.g.swift index dc40b39f..45a74ea2 100644 --- a/darwin/universal_ble/Sources/universal_ble/UniversalBle.g.swift +++ b/darwin/universal_ble/Sources/universal_ble/UniversalBle.g.swift @@ -1,4 +1,4 @@ -// Autogenerated from Pigeon (v26.1.4), do not edit directly. +// Autogenerated from Pigeon (v26.3.3), do not edit directly. // See also: https://pub.dev/packages/pigeon import Foundation @@ -50,7 +50,7 @@ private func wrapError(_ error: Any) -> [Any?] { } return [ "\(error)", - "\(type(of: error))", + "\(Swift.type(of: error))", "Stacktrace: \(Thread.callStackSymbols)", ] } @@ -68,6 +68,19 @@ private func nilOrValue(_ value: Any?) -> T? { return value as! T? } +private func doubleEqualsUniversalBle(_ lhs: Double, _ rhs: Double) -> Bool { + return (lhs.isNaN && rhs.isNaN) || lhs == rhs +} + +private func doubleHashUniversalBle(_ value: Double, _ hasher: inout Hasher) { + if value.isNaN { + hasher.combine(0x7FF8000000000000) + } else { + // Normalize -0.0 to 0.0 + hasher.combine(value == 0 ? 0 : value) + } +} + func deepEqualsUniversalBle(_ lhs: Any?, _ rhs: Any?) -> Bool { let cleanLhs = nilOrValue(lhs) as Any? let cleanRhs = nilOrValue(rhs) as Any? @@ -78,59 +91,92 @@ func deepEqualsUniversalBle(_ lhs: Any?, _ rhs: Any?) -> Bool { case (nil, _), (_, nil): return false - case is (Void, Void): + case (let lhs as AnyObject, let rhs as AnyObject) where lhs === rhs: return true - case let (cleanLhsHashable, cleanRhsHashable) as (AnyHashable, AnyHashable): - return cleanLhsHashable == cleanRhsHashable + case is (Void, Void): + return true - case let (cleanLhsArray, cleanRhsArray) as ([Any?], [Any?]): - guard cleanLhsArray.count == cleanRhsArray.count else { return false } - for (index, element) in cleanLhsArray.enumerated() { - if !deepEqualsUniversalBle(element, cleanRhsArray[index]) { + case (let lhsArray, let rhsArray) as ([Any?], [Any?]): + guard lhsArray.count == rhsArray.count else { return false } + for (index, element) in lhsArray.enumerated() { + if !deepEqualsUniversalBle(element, rhsArray[index]) { return false } } return true - case let (cleanLhsDictionary, cleanRhsDictionary) as ([AnyHashable: Any?], [AnyHashable: Any?]): - guard cleanLhsDictionary.count == cleanRhsDictionary.count else { return false } - for (key, cleanLhsValue) in cleanLhsDictionary { - guard cleanRhsDictionary.index(forKey: key) != nil else { return false } - if !deepEqualsUniversalBle(cleanLhsValue, cleanRhsDictionary[key]!) { + case (let lhsArray, let rhsArray) as ([Double], [Double]): + guard lhsArray.count == rhsArray.count else { return false } + for (index, element) in lhsArray.enumerated() { + if !doubleEqualsUniversalBle(element, rhsArray[index]) { return false } } return true + case (let lhsDictionary, let rhsDictionary) as ([AnyHashable: Any?], [AnyHashable: Any?]): + guard lhsDictionary.count == rhsDictionary.count else { return false } + for (lhsKey, lhsValue) in lhsDictionary { + var found = false + for (rhsKey, rhsValue) in rhsDictionary { + if deepEqualsUniversalBle(lhsKey, rhsKey) { + if deepEqualsUniversalBle(lhsValue, rhsValue) { + found = true + break + } else { + return false + } + } + } + if !found { return false } + } + return true + + case (let lhs as Double, let rhs as Double): + return doubleEqualsUniversalBle(lhs, rhs) + + case (let lhsHashable, let rhsHashable) as (AnyHashable, AnyHashable): + return lhsHashable == rhsHashable + default: - // Any other type shouldn't be able to be used with pigeon. File an issue if you find this to be untrue. return false } } func deepHashUniversalBle(value: Any?, hasher: inout Hasher) { - if let valueList = value as? [AnyHashable] { - for item in valueList { deepHashUniversalBle(value: item, hasher: &hasher) } - return - } - - if let valueDict = value as? [AnyHashable: AnyHashable] { - for key in valueDict.keys { - hasher.combine(key) - deepHashUniversalBle(value: valueDict[key]!, hasher: &hasher) + let cleanValue = nilOrValue(value) as Any? + if let cleanValue = cleanValue { + if let doubleValue = cleanValue as? Double { + doubleHashUniversalBle(doubleValue, &hasher) + } else if let valueList = cleanValue as? [Any?] { + for item in valueList { + deepHashUniversalBle(value: item, hasher: &hasher) + } + } else if let valueList = cleanValue as? [Double] { + for item in valueList { + doubleHashUniversalBle(item, &hasher) + } + } else if let valueDict = cleanValue as? [AnyHashable: Any?] { + var result = 0 + for (key, value) in valueDict { + var entryKeyHasher = Hasher() + deepHashUniversalBle(value: key, hasher: &entryKeyHasher) + var entryValueHasher = Hasher() + deepHashUniversalBle(value: value, hasher: &entryValueHasher) + result = result &+ ((entryKeyHasher.finalize() &* 31) ^ entryValueHasher.finalize()) + } + hasher.combine(result) + } else if let hashableValue = cleanValue as? AnyHashable { + hasher.combine(hashableValue) + } else { + hasher.combine(String(describing: cleanValue)) } - return - } - - if let hashableValue = value as? AnyHashable { - hasher.combine(hashableValue.hashValue) + } else { + hasher.combine(0) } - - return hasher.combine(String(describing: value)) } - enum UniversalBleLogLevel: Int { case none = 0 @@ -149,6 +195,22 @@ enum AndroidScanMode: Int { case opportunistic = 3 } +enum PeripheralReadinessState: Int { + case unknown = 0 + case ready = 1 + case bluetoothOff = 2 + case unauthorized = 3 + case unsupported = 4 +} + +enum PeripheralAdvertisingState: Int { + case idle = 0 + case starting = 1 + case advertising = 2 + case stopping = 3 + case error = 4 +} + /// Unified error codes for all platforms enum UniversalBleErrorCode: Int { case unknownError = 0 @@ -261,9 +323,22 @@ struct UniversalBleScanResult: Hashable { ] } static func == (lhs: UniversalBleScanResult, rhs: UniversalBleScanResult) -> Bool { - return deepEqualsUniversalBle(lhs.toList(), rhs.toList()) } + if Swift.type(of: lhs) != Swift.type(of: rhs) { + return false + } + return deepEqualsUniversalBle(lhs.deviceId, rhs.deviceId) && deepEqualsUniversalBle(lhs.name, rhs.name) && deepEqualsUniversalBle(lhs.isPaired, rhs.isPaired) && deepEqualsUniversalBle(lhs.rssi, rhs.rssi) && deepEqualsUniversalBle(lhs.manufacturerDataList, rhs.manufacturerDataList) && deepEqualsUniversalBle(lhs.serviceData, rhs.serviceData) && deepEqualsUniversalBle(lhs.services, rhs.services) && deepEqualsUniversalBle(lhs.timestamp, rhs.timestamp) + } + func hash(into hasher: inout Hasher) { - deepHashUniversalBle(value: toList(), hasher: &hasher) + hasher.combine("UniversalBleScanResult") + deepHashUniversalBle(value: deviceId, hasher: &hasher) + deepHashUniversalBle(value: name, hasher: &hasher) + deepHashUniversalBle(value: isPaired, hasher: &hasher) + deepHashUniversalBle(value: rssi, hasher: &hasher) + deepHashUniversalBle(value: manufacturerDataList, hasher: &hasher) + deepHashUniversalBle(value: serviceData, hasher: &hasher) + deepHashUniversalBle(value: services, hasher: &hasher) + deepHashUniversalBle(value: timestamp, hasher: &hasher) } } @@ -290,9 +365,16 @@ struct UniversalBleService: Hashable { ] } static func == (lhs: UniversalBleService, rhs: UniversalBleService) -> Bool { - return deepEqualsUniversalBle(lhs.toList(), rhs.toList()) } + if Swift.type(of: lhs) != Swift.type(of: rhs) { + return false + } + return deepEqualsUniversalBle(lhs.uuid, rhs.uuid) && deepEqualsUniversalBle(lhs.characteristics, rhs.characteristics) + } + func hash(into hasher: inout Hasher) { - deepHashUniversalBle(value: toList(), hasher: &hasher) + hasher.combine("UniversalBleService") + deepHashUniversalBle(value: uuid, hasher: &hasher) + deepHashUniversalBle(value: characteristics, hasher: &hasher) } } @@ -323,9 +405,17 @@ struct UniversalBleCharacteristic: Hashable { ] } static func == (lhs: UniversalBleCharacteristic, rhs: UniversalBleCharacteristic) -> Bool { - return deepEqualsUniversalBle(lhs.toList(), rhs.toList()) } + if Swift.type(of: lhs) != Swift.type(of: rhs) { + return false + } + return deepEqualsUniversalBle(lhs.uuid, rhs.uuid) && deepEqualsUniversalBle(lhs.properties, rhs.properties) && deepEqualsUniversalBle(lhs.descriptors, rhs.descriptors) + } + func hash(into hasher: inout Hasher) { - deepHashUniversalBle(value: toList(), hasher: &hasher) + hasher.combine("UniversalBleCharacteristic") + deepHashUniversalBle(value: uuid, hasher: &hasher) + deepHashUniversalBle(value: properties, hasher: &hasher) + deepHashUniversalBle(value: descriptors, hasher: &hasher) } } @@ -348,9 +438,15 @@ struct UniversalBleDescriptor: Hashable { ] } static func == (lhs: UniversalBleDescriptor, rhs: UniversalBleDescriptor) -> Bool { - return deepEqualsUniversalBle(lhs.toList(), rhs.toList()) } + if Swift.type(of: lhs) != Swift.type(of: rhs) { + return false + } + return deepEqualsUniversalBle(lhs.uuid, rhs.uuid) + } + func hash(into hasher: inout Hasher) { - deepHashUniversalBle(value: toList(), hasher: &hasher) + hasher.combine("UniversalBleDescriptor") + deepHashUniversalBle(value: uuid, hasher: &hasher) } } @@ -388,9 +484,17 @@ struct AndroidOptions: Hashable { ] } static func == (lhs: AndroidOptions, rhs: AndroidOptions) -> Bool { - return deepEqualsUniversalBle(lhs.toList(), rhs.toList()) } + if Swift.type(of: lhs) != Swift.type(of: rhs) { + return false + } + return deepEqualsUniversalBle(lhs.requestLocationPermission, rhs.requestLocationPermission) && deepEqualsUniversalBle(lhs.scanMode, rhs.scanMode) && deepEqualsUniversalBle(lhs.reportDelayMillis, rhs.reportDelayMillis) + } + func hash(into hasher: inout Hasher) { - deepHashUniversalBle(value: toList(), hasher: &hasher) + hasher.combine("AndroidOptions") + deepHashUniversalBle(value: requestLocationPermission, hasher: &hasher) + deepHashUniversalBle(value: scanMode, hasher: &hasher) + deepHashUniversalBle(value: reportDelayMillis, hasher: &hasher) } } @@ -413,9 +517,15 @@ struct UniversalScanConfig: Hashable { ] } static func == (lhs: UniversalScanConfig, rhs: UniversalScanConfig) -> Bool { - return deepEqualsUniversalBle(lhs.toList(), rhs.toList()) } + if Swift.type(of: lhs) != Swift.type(of: rhs) { + return false + } + return deepEqualsUniversalBle(lhs.android, rhs.android) + } + func hash(into hasher: inout Hasher) { - deepHashUniversalBle(value: toList(), hasher: &hasher) + hasher.combine("UniversalScanConfig") + deepHashUniversalBle(value: android, hasher: &hasher) } } @@ -448,9 +558,17 @@ struct UniversalScanFilter: Hashable { ] } static func == (lhs: UniversalScanFilter, rhs: UniversalScanFilter) -> Bool { - return deepEqualsUniversalBle(lhs.toList(), rhs.toList()) } + if Swift.type(of: lhs) != Swift.type(of: rhs) { + return false + } + return deepEqualsUniversalBle(lhs.withServices, rhs.withServices) && deepEqualsUniversalBle(lhs.withNamePrefix, rhs.withNamePrefix) && deepEqualsUniversalBle(lhs.withManufacturerData, rhs.withManufacturerData) + } + func hash(into hasher: inout Hasher) { - deepHashUniversalBle(value: toList(), hasher: &hasher) + hasher.combine("UniversalScanFilter") + deepHashUniversalBle(value: withServices, hasher: &hasher) + deepHashUniversalBle(value: withNamePrefix, hasher: &hasher) + deepHashUniversalBle(value: withManufacturerData, hasher: &hasher) } } @@ -481,9 +599,17 @@ struct UniversalManufacturerDataFilter: Hashable { ] } static func == (lhs: UniversalManufacturerDataFilter, rhs: UniversalManufacturerDataFilter) -> Bool { - return deepEqualsUniversalBle(lhs.toList(), rhs.toList()) } + if Swift.type(of: lhs) != Swift.type(of: rhs) { + return false + } + return deepEqualsUniversalBle(lhs.companyIdentifier, rhs.companyIdentifier) && deepEqualsUniversalBle(lhs.data, rhs.data) && deepEqualsUniversalBle(lhs.mask, rhs.mask) + } + func hash(into hasher: inout Hasher) { - deepHashUniversalBle(value: toList(), hasher: &hasher) + hasher.combine("UniversalManufacturerDataFilter") + deepHashUniversalBle(value: companyIdentifier, hasher: &hasher) + deepHashUniversalBle(value: data, hasher: &hasher) + deepHashUniversalBle(value: mask, hasher: &hasher) } } @@ -510,9 +636,267 @@ struct UniversalManufacturerData: Hashable { ] } static func == (lhs: UniversalManufacturerData, rhs: UniversalManufacturerData) -> Bool { - return deepEqualsUniversalBle(lhs.toList(), rhs.toList()) } + if Swift.type(of: lhs) != Swift.type(of: rhs) { + return false + } + return deepEqualsUniversalBle(lhs.companyIdentifier, rhs.companyIdentifier) && deepEqualsUniversalBle(lhs.data, rhs.data) + } + + func hash(into hasher: inout Hasher) { + hasher.combine("UniversalManufacturerData") + deepHashUniversalBle(value: companyIdentifier, hasher: &hasher) + deepHashUniversalBle(value: data, hasher: &hasher) + } +} + +/// Generated class from Pigeon that represents data sent in messages. +struct PeripheralService: Hashable { + var uuid: String + var primary: Bool + var characteristics: [PeripheralCharacteristic] + + + // swift-format-ignore: AlwaysUseLowerCamelCase + static func fromList(_ pigeonVar_list: [Any?]) -> PeripheralService? { + let uuid = pigeonVar_list[0] as! String + let primary = pigeonVar_list[1] as! Bool + let characteristics = pigeonVar_list[2] as! [PeripheralCharacteristic] + + return PeripheralService( + uuid: uuid, + primary: primary, + characteristics: characteristics + ) + } + func toList() -> [Any?] { + return [ + uuid, + primary, + characteristics, + ] + } + static func == (lhs: PeripheralService, rhs: PeripheralService) -> Bool { + if Swift.type(of: lhs) != Swift.type(of: rhs) { + return false + } + return deepEqualsUniversalBle(lhs.uuid, rhs.uuid) && deepEqualsUniversalBle(lhs.primary, rhs.primary) && deepEqualsUniversalBle(lhs.characteristics, rhs.characteristics) + } + + func hash(into hasher: inout Hasher) { + hasher.combine("PeripheralService") + deepHashUniversalBle(value: uuid, hasher: &hasher) + deepHashUniversalBle(value: primary, hasher: &hasher) + deepHashUniversalBle(value: characteristics, hasher: &hasher) + } +} + +/// Generated class from Pigeon that represents data sent in messages. +struct PeripheralCharacteristic: Hashable { + var uuid: String + var properties: [Int64] + var permissions: [Int64] + var descriptors: [PeripheralDescriptor]? = nil + var value: FlutterStandardTypedData? = nil + + + // swift-format-ignore: AlwaysUseLowerCamelCase + static func fromList(_ pigeonVar_list: [Any?]) -> PeripheralCharacteristic? { + let uuid = pigeonVar_list[0] as! String + let properties = pigeonVar_list[1] as! [Int64] + let permissions = pigeonVar_list[2] as! [Int64] + let descriptors: [PeripheralDescriptor]? = nilOrValue(pigeonVar_list[3]) + let value: FlutterStandardTypedData? = nilOrValue(pigeonVar_list[4]) + + return PeripheralCharacteristic( + uuid: uuid, + properties: properties, + permissions: permissions, + descriptors: descriptors, + value: value + ) + } + func toList() -> [Any?] { + return [ + uuid, + properties, + permissions, + descriptors, + value, + ] + } + static func == (lhs: PeripheralCharacteristic, rhs: PeripheralCharacteristic) -> Bool { + if Swift.type(of: lhs) != Swift.type(of: rhs) { + return false + } + return deepEqualsUniversalBle(lhs.uuid, rhs.uuid) && deepEqualsUniversalBle(lhs.properties, rhs.properties) && deepEqualsUniversalBle(lhs.permissions, rhs.permissions) && deepEqualsUniversalBle(lhs.descriptors, rhs.descriptors) && deepEqualsUniversalBle(lhs.value, rhs.value) + } + + func hash(into hasher: inout Hasher) { + hasher.combine("PeripheralCharacteristic") + deepHashUniversalBle(value: uuid, hasher: &hasher) + deepHashUniversalBle(value: properties, hasher: &hasher) + deepHashUniversalBle(value: permissions, hasher: &hasher) + deepHashUniversalBle(value: descriptors, hasher: &hasher) + deepHashUniversalBle(value: value, hasher: &hasher) + } +} + +/// Generated class from Pigeon that represents data sent in messages. +struct PeripheralDescriptor: Hashable { + var uuid: String + var value: FlutterStandardTypedData? = nil + var permissions: [Int64]? = nil + + + // swift-format-ignore: AlwaysUseLowerCamelCase + static func fromList(_ pigeonVar_list: [Any?]) -> PeripheralDescriptor? { + let uuid = pigeonVar_list[0] as! String + let value: FlutterStandardTypedData? = nilOrValue(pigeonVar_list[1]) + let permissions: [Int64]? = nilOrValue(pigeonVar_list[2]) + + return PeripheralDescriptor( + uuid: uuid, + value: value, + permissions: permissions + ) + } + func toList() -> [Any?] { + return [ + uuid, + value, + permissions, + ] + } + static func == (lhs: PeripheralDescriptor, rhs: PeripheralDescriptor) -> Bool { + if Swift.type(of: lhs) != Swift.type(of: rhs) { + return false + } + return deepEqualsUniversalBle(lhs.uuid, rhs.uuid) && deepEqualsUniversalBle(lhs.value, rhs.value) && deepEqualsUniversalBle(lhs.permissions, rhs.permissions) + } + + func hash(into hasher: inout Hasher) { + hasher.combine("PeripheralDescriptor") + deepHashUniversalBle(value: uuid, hasher: &hasher) + deepHashUniversalBle(value: value, hasher: &hasher) + deepHashUniversalBle(value: permissions, hasher: &hasher) + } +} + +/// Generated class from Pigeon that represents data sent in messages. +struct PeripheralReadRequestResult: Hashable { + var value: FlutterStandardTypedData + var offset: Int64? = nil + var status: Int64? = nil + + + // swift-format-ignore: AlwaysUseLowerCamelCase + static func fromList(_ pigeonVar_list: [Any?]) -> PeripheralReadRequestResult? { + let value = pigeonVar_list[0] as! FlutterStandardTypedData + let offset: Int64? = nilOrValue(pigeonVar_list[1]) + let status: Int64? = nilOrValue(pigeonVar_list[2]) + + return PeripheralReadRequestResult( + value: value, + offset: offset, + status: status + ) + } + func toList() -> [Any?] { + return [ + value, + offset, + status, + ] + } + static func == (lhs: PeripheralReadRequestResult, rhs: PeripheralReadRequestResult) -> Bool { + if Swift.type(of: lhs) != Swift.type(of: rhs) { + return false + } + return deepEqualsUniversalBle(lhs.value, rhs.value) && deepEqualsUniversalBle(lhs.offset, rhs.offset) && deepEqualsUniversalBle(lhs.status, rhs.status) + } + func hash(into hasher: inout Hasher) { - deepHashUniversalBle(value: toList(), hasher: &hasher) + hasher.combine("PeripheralReadRequestResult") + deepHashUniversalBle(value: value, hasher: &hasher) + deepHashUniversalBle(value: offset, hasher: &hasher) + deepHashUniversalBle(value: status, hasher: &hasher) + } +} + +/// Generated class from Pigeon that represents data sent in messages. +struct PeripheralWriteRequestResult: Hashable { + var value: FlutterStandardTypedData? = nil + var offset: Int64? = nil + var status: Int64? = nil + + + // swift-format-ignore: AlwaysUseLowerCamelCase + static func fromList(_ pigeonVar_list: [Any?]) -> PeripheralWriteRequestResult? { + let value: FlutterStandardTypedData? = nilOrValue(pigeonVar_list[0]) + let offset: Int64? = nilOrValue(pigeonVar_list[1]) + let status: Int64? = nilOrValue(pigeonVar_list[2]) + + return PeripheralWriteRequestResult( + value: value, + offset: offset, + status: status + ) + } + func toList() -> [Any?] { + return [ + value, + offset, + status, + ] + } + static func == (lhs: PeripheralWriteRequestResult, rhs: PeripheralWriteRequestResult) -> Bool { + if Swift.type(of: lhs) != Swift.type(of: rhs) { + return false + } + return deepEqualsUniversalBle(lhs.value, rhs.value) && deepEqualsUniversalBle(lhs.offset, rhs.offset) && deepEqualsUniversalBle(lhs.status, rhs.status) + } + + func hash(into hasher: inout Hasher) { + hasher.combine("PeripheralWriteRequestResult") + deepHashUniversalBle(value: value, hasher: &hasher) + deepHashUniversalBle(value: offset, hasher: &hasher) + deepHashUniversalBle(value: status, hasher: &hasher) + } +} + +/// Generated class from Pigeon that represents data sent in messages. +struct PeripheralManufacturerData: Hashable { + var manufacturerId: Int64 + var data: FlutterStandardTypedData + + + // swift-format-ignore: AlwaysUseLowerCamelCase + static func fromList(_ pigeonVar_list: [Any?]) -> PeripheralManufacturerData? { + let manufacturerId = pigeonVar_list[0] as! Int64 + let data = pigeonVar_list[1] as! FlutterStandardTypedData + + return PeripheralManufacturerData( + manufacturerId: manufacturerId, + data: data + ) + } + func toList() -> [Any?] { + return [ + manufacturerId, + data, + ] + } + static func == (lhs: PeripheralManufacturerData, rhs: PeripheralManufacturerData) -> Bool { + if Swift.type(of: lhs) != Swift.type(of: rhs) { + return false + } + return deepEqualsUniversalBle(lhs.manufacturerId, rhs.manufacturerId) && deepEqualsUniversalBle(lhs.data, rhs.data) + } + + func hash(into hasher: inout Hasher) { + hasher.combine("PeripheralManufacturerData") + deepHashUniversalBle(value: manufacturerId, hasher: &hasher) + deepHashUniversalBle(value: data, hasher: &hasher) } } @@ -534,27 +918,51 @@ private class UniversalBlePigeonCodecReader: FlutterStandardReader { case 131: let enumResultAsInt: Int? = nilOrValue(self.readValue() as! Int?) if let enumResultAsInt = enumResultAsInt { - return UniversalBleErrorCode(rawValue: enumResultAsInt) + return PeripheralReadinessState(rawValue: enumResultAsInt) } return nil case 132: - return UniversalBleScanResult.fromList(self.readValue() as! [Any?]) + let enumResultAsInt: Int? = nilOrValue(self.readValue() as! Int?) + if let enumResultAsInt = enumResultAsInt { + return PeripheralAdvertisingState(rawValue: enumResultAsInt) + } + return nil case 133: - return UniversalBleService.fromList(self.readValue() as! [Any?]) + let enumResultAsInt: Int? = nilOrValue(self.readValue() as! Int?) + if let enumResultAsInt = enumResultAsInt { + return UniversalBleErrorCode(rawValue: enumResultAsInt) + } + return nil case 134: - return UniversalBleCharacteristic.fromList(self.readValue() as! [Any?]) + return UniversalBleScanResult.fromList(self.readValue() as! [Any?]) case 135: - return UniversalBleDescriptor.fromList(self.readValue() as! [Any?]) + return UniversalBleService.fromList(self.readValue() as! [Any?]) case 136: - return AndroidOptions.fromList(self.readValue() as! [Any?]) + return UniversalBleCharacteristic.fromList(self.readValue() as! [Any?]) case 137: - return UniversalScanConfig.fromList(self.readValue() as! [Any?]) + return UniversalBleDescriptor.fromList(self.readValue() as! [Any?]) case 138: - return UniversalScanFilter.fromList(self.readValue() as! [Any?]) + return AndroidOptions.fromList(self.readValue() as! [Any?]) case 139: - return UniversalManufacturerDataFilter.fromList(self.readValue() as! [Any?]) + return UniversalScanConfig.fromList(self.readValue() as! [Any?]) case 140: + return UniversalScanFilter.fromList(self.readValue() as! [Any?]) + case 141: + return UniversalManufacturerDataFilter.fromList(self.readValue() as! [Any?]) + case 142: return UniversalManufacturerData.fromList(self.readValue() as! [Any?]) + case 143: + return PeripheralService.fromList(self.readValue() as! [Any?]) + case 144: + return PeripheralCharacteristic.fromList(self.readValue() as! [Any?]) + case 145: + return PeripheralDescriptor.fromList(self.readValue() as! [Any?]) + case 146: + return PeripheralReadRequestResult.fromList(self.readValue() as! [Any?]) + case 147: + return PeripheralWriteRequestResult.fromList(self.readValue() as! [Any?]) + case 148: + return PeripheralManufacturerData.fromList(self.readValue() as! [Any?]) default: return super.readValue(ofType: type) } @@ -569,35 +977,59 @@ private class UniversalBlePigeonCodecWriter: FlutterStandardWriter { } else if let value = value as? AndroidScanMode { super.writeByte(130) super.writeValue(value.rawValue) - } else if let value = value as? UniversalBleErrorCode { + } else if let value = value as? PeripheralReadinessState { super.writeByte(131) super.writeValue(value.rawValue) - } else if let value = value as? UniversalBleScanResult { + } else if let value = value as? PeripheralAdvertisingState { super.writeByte(132) + super.writeValue(value.rawValue) + } else if let value = value as? UniversalBleErrorCode { + super.writeByte(133) + super.writeValue(value.rawValue) + } else if let value = value as? UniversalBleScanResult { + super.writeByte(134) super.writeValue(value.toList()) } else if let value = value as? UniversalBleService { - super.writeByte(133) + super.writeByte(135) super.writeValue(value.toList()) } else if let value = value as? UniversalBleCharacteristic { - super.writeByte(134) + super.writeByte(136) super.writeValue(value.toList()) } else if let value = value as? UniversalBleDescriptor { - super.writeByte(135) + super.writeByte(137) super.writeValue(value.toList()) } else if let value = value as? AndroidOptions { - super.writeByte(136) + super.writeByte(138) super.writeValue(value.toList()) } else if let value = value as? UniversalScanConfig { - super.writeByte(137) + super.writeByte(139) super.writeValue(value.toList()) } else if let value = value as? UniversalScanFilter { - super.writeByte(138) + super.writeByte(140) super.writeValue(value.toList()) } else if let value = value as? UniversalManufacturerDataFilter { - super.writeByte(139) + super.writeByte(141) super.writeValue(value.toList()) } else if let value = value as? UniversalManufacturerData { - super.writeByte(140) + super.writeByte(142) + super.writeValue(value.toList()) + } else if let value = value as? PeripheralService { + super.writeByte(143) + super.writeValue(value.toList()) + } else if let value = value as? PeripheralCharacteristic { + super.writeByte(144) + super.writeValue(value.toList()) + } else if let value = value as? PeripheralDescriptor { + super.writeByte(145) + super.writeValue(value.toList()) + } else if let value = value as? PeripheralReadRequestResult { + super.writeByte(146) + super.writeValue(value.toList()) + } else if let value = value as? PeripheralWriteRequestResult { + super.writeByte(147) + super.writeValue(value.toList()) + } else if let value = value as? PeripheralManufacturerData { + super.writeByte(148) super.writeValue(value.toList()) } else { super.writeValue(value) @@ -1145,3 +1577,386 @@ class UniversalBleCallbackChannel: UniversalBleCallbackChannelProtocol { } } } +/// Flutter -> Native (peripheral) +/// +/// Generated protocol from Pigeon that represents a handler of messages from Flutter. +protocol UniversalBlePeripheralChannel { + func getAdvertisingState() throws -> PeripheralAdvertisingState + func getReadinessState() throws -> PeripheralReadinessState + func stopAdvertising() throws + func addService(service: PeripheralService) throws + func removeService(serviceId: String) throws + func clearServices() throws + func getServices() throws -> [String] + func startAdvertising(services: [String], localName: String?, timeout: Int64?, manufacturerData: PeripheralManufacturerData?, addManufacturerDataInScanResponse: Bool) throws + func updateCharacteristic(characteristicId: String, value: FlutterStandardTypedData, deviceId: String?) throws + /// Returns peripheral-client device ids currently subscribed to [characteristicId] + /// (e.g. HID report characteristic). Used to restore app state after restart. + func getSubscribedClients(characteristicId: String) throws -> [String] + /// Returns max characteristic notify payload length for a connected client. + func getMaximumNotifyLength(deviceId: String) throws -> Int64? +} + +/// Generated setup class from Pigeon to handle messages through the `binaryMessenger`. +class UniversalBlePeripheralChannelSetup { + static var codec: FlutterStandardMessageCodec { UniversalBlePigeonCodec.shared } + /// Sets up an instance of `UniversalBlePeripheralChannel` to handle messages through the `binaryMessenger`. + static func setUp(binaryMessenger: FlutterBinaryMessenger, api: UniversalBlePeripheralChannel?, messageChannelSuffix: String = "") { + let channelSuffix = messageChannelSuffix.count > 0 ? ".\(messageChannelSuffix)" : "" + let getAdvertisingStateChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.universal_ble.UniversalBlePeripheralChannel.getAdvertisingState\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + getAdvertisingStateChannel.setMessageHandler { _, reply in + do { + let result = try api.getAdvertisingState() + reply(wrapResult(result)) + } catch { + reply(wrapError(error)) + } + } + } else { + getAdvertisingStateChannel.setMessageHandler(nil) + } + let getReadinessStateChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.universal_ble.UniversalBlePeripheralChannel.getReadinessState\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + getReadinessStateChannel.setMessageHandler { _, reply in + do { + let result = try api.getReadinessState() + reply(wrapResult(result)) + } catch { + reply(wrapError(error)) + } + } + } else { + getReadinessStateChannel.setMessageHandler(nil) + } + let stopAdvertisingChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.universal_ble.UniversalBlePeripheralChannel.stopAdvertising\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + stopAdvertisingChannel.setMessageHandler { _, reply in + do { + try api.stopAdvertising() + reply(wrapResult(nil)) + } catch { + reply(wrapError(error)) + } + } + } else { + stopAdvertisingChannel.setMessageHandler(nil) + } + let addServiceChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.universal_ble.UniversalBlePeripheralChannel.addService\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + addServiceChannel.setMessageHandler { message, reply in + let args = message as! [Any?] + let serviceArg = args[0] as! PeripheralService + do { + try api.addService(service: serviceArg) + reply(wrapResult(nil)) + } catch { + reply(wrapError(error)) + } + } + } else { + addServiceChannel.setMessageHandler(nil) + } + let removeServiceChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.universal_ble.UniversalBlePeripheralChannel.removeService\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + removeServiceChannel.setMessageHandler { message, reply in + let args = message as! [Any?] + let serviceIdArg = args[0] as! String + do { + try api.removeService(serviceId: serviceIdArg) + reply(wrapResult(nil)) + } catch { + reply(wrapError(error)) + } + } + } else { + removeServiceChannel.setMessageHandler(nil) + } + let clearServicesChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.universal_ble.UniversalBlePeripheralChannel.clearServices\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + clearServicesChannel.setMessageHandler { _, reply in + do { + try api.clearServices() + reply(wrapResult(nil)) + } catch { + reply(wrapError(error)) + } + } + } else { + clearServicesChannel.setMessageHandler(nil) + } + let getServicesChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.universal_ble.UniversalBlePeripheralChannel.getServices\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + getServicesChannel.setMessageHandler { _, reply in + do { + let result = try api.getServices() + reply(wrapResult(result)) + } catch { + reply(wrapError(error)) + } + } + } else { + getServicesChannel.setMessageHandler(nil) + } + let startAdvertisingChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.universal_ble.UniversalBlePeripheralChannel.startAdvertising\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + startAdvertisingChannel.setMessageHandler { message, reply in + let args = message as! [Any?] + let servicesArg = args[0] as! [String] + let localNameArg: String? = nilOrValue(args[1]) + let timeoutArg: Int64? = nilOrValue(args[2]) + let manufacturerDataArg: PeripheralManufacturerData? = nilOrValue(args[3]) + let addManufacturerDataInScanResponseArg = args[4] as! Bool + do { + try api.startAdvertising(services: servicesArg, localName: localNameArg, timeout: timeoutArg, manufacturerData: manufacturerDataArg, addManufacturerDataInScanResponse: addManufacturerDataInScanResponseArg) + reply(wrapResult(nil)) + } catch { + reply(wrapError(error)) + } + } + } else { + startAdvertisingChannel.setMessageHandler(nil) + } + let updateCharacteristicChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.universal_ble.UniversalBlePeripheralChannel.updateCharacteristic\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + updateCharacteristicChannel.setMessageHandler { message, reply in + let args = message as! [Any?] + let characteristicIdArg = args[0] as! String + let valueArg = args[1] as! FlutterStandardTypedData + let deviceIdArg: String? = nilOrValue(args[2]) + do { + try api.updateCharacteristic(characteristicId: characteristicIdArg, value: valueArg, deviceId: deviceIdArg) + reply(wrapResult(nil)) + } catch { + reply(wrapError(error)) + } + } + } else { + updateCharacteristicChannel.setMessageHandler(nil) + } + /// Returns peripheral-client device ids currently subscribed to [characteristicId] + /// (e.g. HID report characteristic). Used to restore app state after restart. + let getSubscribedClientsChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.universal_ble.UniversalBlePeripheralChannel.getSubscribedClients\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + getSubscribedClientsChannel.setMessageHandler { message, reply in + let args = message as! [Any?] + let characteristicIdArg = args[0] as! String + do { + let result = try api.getSubscribedClients(characteristicId: characteristicIdArg) + reply(wrapResult(result)) + } catch { + reply(wrapError(error)) + } + } + } else { + getSubscribedClientsChannel.setMessageHandler(nil) + } + /// Returns max characteristic notify payload length for a connected client. + let getMaximumNotifyLengthChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.universal_ble.UniversalBlePeripheralChannel.getMaximumNotifyLength\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + getMaximumNotifyLengthChannel.setMessageHandler { message, reply in + let args = message as! [Any?] + let deviceIdArg = args[0] as! String + do { + let result = try api.getMaximumNotifyLength(deviceId: deviceIdArg) + reply(wrapResult(result)) + } catch { + reply(wrapError(error)) + } + } + } else { + getMaximumNotifyLengthChannel.setMessageHandler(nil) + } + } +} +/// Native -> Flutter (peripheral) +/// +/// Generated protocol from Pigeon that represents Flutter messages that can be called from Swift. +protocol UniversalBlePeripheralCallbackProtocol { + func onReadRequest(deviceId deviceIdArg: String, characteristicId characteristicIdArg: String, offset offsetArg: Int64, value valueArg: FlutterStandardTypedData?, completion: @escaping (Result) -> Void) + func onWriteRequest(deviceId deviceIdArg: String, characteristicId characteristicIdArg: String, offset offsetArg: Int64, value valueArg: FlutterStandardTypedData?, completion: @escaping (Result) -> Void) + func onDescriptorReadRequest(deviceId deviceIdArg: String, characteristicId characteristicIdArg: String, descriptorId descriptorIdArg: String, offset offsetArg: Int64, value valueArg: FlutterStandardTypedData?, completion: @escaping (Result) -> Void) + func onDescriptorWriteRequest(deviceId deviceIdArg: String, characteristicId characteristicIdArg: String, descriptorId descriptorIdArg: String, offset offsetArg: Int64, value valueArg: FlutterStandardTypedData?, completion: @escaping (Result) -> Void) + func onCharacteristicSubscriptionChange(deviceId deviceIdArg: String, characteristicId characteristicIdArg: String, isSubscribed isSubscribedArg: Bool, name nameArg: String?, completion: @escaping (Result) -> Void) + func onAdvertisingStateChange(state stateArg: PeripheralAdvertisingState, error errorArg: String?, completion: @escaping (Result) -> Void) + func onServiceAdded(serviceId serviceIdArg: String, error errorArg: String?, completion: @escaping (Result) -> Void) + func onMtuChange(deviceId deviceIdArg: String, mtu mtuArg: Int64, completion: @escaping (Result) -> Void) + func onConnectionStateChange(deviceId deviceIdArg: String, connected connectedArg: Bool, completion: @escaping (Result) -> Void) +} +class UniversalBlePeripheralCallback: UniversalBlePeripheralCallbackProtocol { + private let binaryMessenger: FlutterBinaryMessenger + private let messageChannelSuffix: String + init(binaryMessenger: FlutterBinaryMessenger, messageChannelSuffix: String = "") { + self.binaryMessenger = binaryMessenger + self.messageChannelSuffix = messageChannelSuffix.count > 0 ? ".\(messageChannelSuffix)" : "" + } + var codec: UniversalBlePigeonCodec { + return UniversalBlePigeonCodec.shared + } + func onReadRequest(deviceId deviceIdArg: String, characteristicId characteristicIdArg: String, offset offsetArg: Int64, value valueArg: FlutterStandardTypedData?, completion: @escaping (Result) -> Void) { + let channelName: String = "dev.flutter.pigeon.universal_ble.UniversalBlePeripheralCallback.onReadRequest\(messageChannelSuffix)" + let channel = FlutterBasicMessageChannel(name: channelName, binaryMessenger: binaryMessenger, codec: codec) + channel.sendMessage([deviceIdArg, characteristicIdArg, offsetArg, valueArg] as [Any?]) { response in + guard let listResponse = response as? [Any?] else { + completion(.failure(createConnectionError(withChannelName: channelName))) + return + } + if listResponse.count > 1 { + let code: String = listResponse[0] as! String + let message: String? = nilOrValue(listResponse[1]) + let details: String? = nilOrValue(listResponse[2]) + completion(.failure(PigeonError(code: code, message: message, details: details))) + } else { + let result: PeripheralReadRequestResult? = nilOrValue(listResponse[0]) + completion(.success(result)) + } + } + } + func onWriteRequest(deviceId deviceIdArg: String, characteristicId characteristicIdArg: String, offset offsetArg: Int64, value valueArg: FlutterStandardTypedData?, completion: @escaping (Result) -> Void) { + let channelName: String = "dev.flutter.pigeon.universal_ble.UniversalBlePeripheralCallback.onWriteRequest\(messageChannelSuffix)" + let channel = FlutterBasicMessageChannel(name: channelName, binaryMessenger: binaryMessenger, codec: codec) + channel.sendMessage([deviceIdArg, characteristicIdArg, offsetArg, valueArg] as [Any?]) { response in + guard let listResponse = response as? [Any?] else { + completion(.failure(createConnectionError(withChannelName: channelName))) + return + } + if listResponse.count > 1 { + let code: String = listResponse[0] as! String + let message: String? = nilOrValue(listResponse[1]) + let details: String? = nilOrValue(listResponse[2]) + completion(.failure(PigeonError(code: code, message: message, details: details))) + } else { + let result: PeripheralWriteRequestResult? = nilOrValue(listResponse[0]) + completion(.success(result)) + } + } + } + func onDescriptorReadRequest(deviceId deviceIdArg: String, characteristicId characteristicIdArg: String, descriptorId descriptorIdArg: String, offset offsetArg: Int64, value valueArg: FlutterStandardTypedData?, completion: @escaping (Result) -> Void) { + let channelName: String = "dev.flutter.pigeon.universal_ble.UniversalBlePeripheralCallback.onDescriptorReadRequest\(messageChannelSuffix)" + let channel = FlutterBasicMessageChannel(name: channelName, binaryMessenger: binaryMessenger, codec: codec) + channel.sendMessage([deviceIdArg, characteristicIdArg, descriptorIdArg, offsetArg, valueArg] as [Any?]) { response in + guard let listResponse = response as? [Any?] else { + completion(.failure(createConnectionError(withChannelName: channelName))) + return + } + if listResponse.count > 1 { + let code: String = listResponse[0] as! String + let message: String? = nilOrValue(listResponse[1]) + let details: String? = nilOrValue(listResponse[2]) + completion(.failure(PigeonError(code: code, message: message, details: details))) + } else { + let result: PeripheralReadRequestResult? = nilOrValue(listResponse[0]) + completion(.success(result)) + } + } + } + func onDescriptorWriteRequest(deviceId deviceIdArg: String, characteristicId characteristicIdArg: String, descriptorId descriptorIdArg: String, offset offsetArg: Int64, value valueArg: FlutterStandardTypedData?, completion: @escaping (Result) -> Void) { + let channelName: String = "dev.flutter.pigeon.universal_ble.UniversalBlePeripheralCallback.onDescriptorWriteRequest\(messageChannelSuffix)" + let channel = FlutterBasicMessageChannel(name: channelName, binaryMessenger: binaryMessenger, codec: codec) + channel.sendMessage([deviceIdArg, characteristicIdArg, descriptorIdArg, offsetArg, valueArg] as [Any?]) { response in + guard let listResponse = response as? [Any?] else { + completion(.failure(createConnectionError(withChannelName: channelName))) + return + } + if listResponse.count > 1 { + let code: String = listResponse[0] as! String + let message: String? = nilOrValue(listResponse[1]) + let details: String? = nilOrValue(listResponse[2]) + completion(.failure(PigeonError(code: code, message: message, details: details))) + } else { + let result: PeripheralWriteRequestResult? = nilOrValue(listResponse[0]) + completion(.success(result)) + } + } + } + func onCharacteristicSubscriptionChange(deviceId deviceIdArg: String, characteristicId characteristicIdArg: String, isSubscribed isSubscribedArg: Bool, name nameArg: String?, completion: @escaping (Result) -> Void) { + let channelName: String = "dev.flutter.pigeon.universal_ble.UniversalBlePeripheralCallback.onCharacteristicSubscriptionChange\(messageChannelSuffix)" + let channel = FlutterBasicMessageChannel(name: channelName, binaryMessenger: binaryMessenger, codec: codec) + channel.sendMessage([deviceIdArg, characteristicIdArg, isSubscribedArg, nameArg] as [Any?]) { response in + guard let listResponse = response as? [Any?] else { + completion(.failure(createConnectionError(withChannelName: channelName))) + return + } + if listResponse.count > 1 { + let code: String = listResponse[0] as! String + let message: String? = nilOrValue(listResponse[1]) + let details: String? = nilOrValue(listResponse[2]) + completion(.failure(PigeonError(code: code, message: message, details: details))) + } else { + completion(.success(())) + } + } + } + func onAdvertisingStateChange(state stateArg: PeripheralAdvertisingState, error errorArg: String?, completion: @escaping (Result) -> Void) { + let channelName: String = "dev.flutter.pigeon.universal_ble.UniversalBlePeripheralCallback.onAdvertisingStateChange\(messageChannelSuffix)" + let channel = FlutterBasicMessageChannel(name: channelName, binaryMessenger: binaryMessenger, codec: codec) + channel.sendMessage([stateArg, errorArg] as [Any?]) { response in + guard let listResponse = response as? [Any?] else { + completion(.failure(createConnectionError(withChannelName: channelName))) + return + } + if listResponse.count > 1 { + let code: String = listResponse[0] as! String + let message: String? = nilOrValue(listResponse[1]) + let details: String? = nilOrValue(listResponse[2]) + completion(.failure(PigeonError(code: code, message: message, details: details))) + } else { + completion(.success(())) + } + } + } + func onServiceAdded(serviceId serviceIdArg: String, error errorArg: String?, completion: @escaping (Result) -> Void) { + let channelName: String = "dev.flutter.pigeon.universal_ble.UniversalBlePeripheralCallback.onServiceAdded\(messageChannelSuffix)" + let channel = FlutterBasicMessageChannel(name: channelName, binaryMessenger: binaryMessenger, codec: codec) + channel.sendMessage([serviceIdArg, errorArg] as [Any?]) { response in + guard let listResponse = response as? [Any?] else { + completion(.failure(createConnectionError(withChannelName: channelName))) + return + } + if listResponse.count > 1 { + let code: String = listResponse[0] as! String + let message: String? = nilOrValue(listResponse[1]) + let details: String? = nilOrValue(listResponse[2]) + completion(.failure(PigeonError(code: code, message: message, details: details))) + } else { + completion(.success(())) + } + } + } + func onMtuChange(deviceId deviceIdArg: String, mtu mtuArg: Int64, completion: @escaping (Result) -> Void) { + let channelName: String = "dev.flutter.pigeon.universal_ble.UniversalBlePeripheralCallback.onMtuChange\(messageChannelSuffix)" + let channel = FlutterBasicMessageChannel(name: channelName, binaryMessenger: binaryMessenger, codec: codec) + channel.sendMessage([deviceIdArg, mtuArg] as [Any?]) { response in + guard let listResponse = response as? [Any?] else { + completion(.failure(createConnectionError(withChannelName: channelName))) + return + } + if listResponse.count > 1 { + let code: String = listResponse[0] as! String + let message: String? = nilOrValue(listResponse[1]) + let details: String? = nilOrValue(listResponse[2]) + completion(.failure(PigeonError(code: code, message: message, details: details))) + } else { + completion(.success(())) + } + } + } + func onConnectionStateChange(deviceId deviceIdArg: String, connected connectedArg: Bool, completion: @escaping (Result) -> Void) { + let channelName: String = "dev.flutter.pigeon.universal_ble.UniversalBlePeripheralCallback.onConnectionStateChange\(messageChannelSuffix)" + let channel = FlutterBasicMessageChannel(name: channelName, binaryMessenger: binaryMessenger, codec: codec) + channel.sendMessage([deviceIdArg, connectedArg] as [Any?]) { response in + guard let listResponse = response as? [Any?] else { + completion(.failure(createConnectionError(withChannelName: channelName))) + return + } + if listResponse.count > 1 { + let code: String = listResponse[0] as! String + let message: String? = nilOrValue(listResponse[1]) + let details: String? = nilOrValue(listResponse[2]) + completion(.failure(PigeonError(code: code, message: message, details: details))) + } else { + completion(.success(())) + } + } + } +} diff --git a/darwin/universal_ble/Sources/universal_ble/UniversalBlePeripheralExtensions.swift b/darwin/universal_ble/Sources/universal_ble/UniversalBlePeripheralExtensions.swift new file mode 100644 index 00000000..3d3fd86b --- /dev/null +++ b/darwin/universal_ble/Sources/universal_ble/UniversalBlePeripheralExtensions.swift @@ -0,0 +1,137 @@ +#if os(iOS) + import Flutter +#elseif os(macOS) + import FlutterMacOS +#else + #error("Unsupported platform.") +#endif +import CoreBluetooth +import Foundation + +enum UniversalBlePeripheralError: Error { + case notFound(String) +} + +var peripheralCharacteristicsList = [CBMutableCharacteristic]() +var peripheralServicesList = [CBMutableService]() + +func clearPeripheralCaches() { + peripheralCharacteristicsList.removeAll() + peripheralServicesList.removeAll() +} + +extension PeripheralService { + func toCBService() -> CBMutableService { + let service = CBMutableService(type: CBUUID(string: uuid), primary: primary) + let chars = characteristics.compactMap { $0.toCBCharacteristic() } + if !chars.isEmpty { + service.characteristics = chars + } + peripheralServicesList.removeAll { + $0.uuid.uuidString.lowercased() == service.uuid.uuidString.lowercased() + } + peripheralServicesList.append(service) + return service + } +} + +extension PeripheralDescriptor { + func toCBMutableDescriptor() -> CBMutableDescriptor { + return CBMutableDescriptor(type: CBUUID(string: uuid), value: value?.toData()) + } +} + +extension PeripheralCharacteristic { + func toCBCharacteristic() -> CBMutableCharacteristic { + let properties = self.properties.compactMap { $0.toCBCharacteristicProperties() } + let permissions = self.permissions.compactMap { $0.toCBAttributePermissions() } + let combinedProperties = properties.reduce(CBCharacteristicProperties()) { $0.union($1) } + let combinedPermissions = permissions.reduce(CBAttributePermissions()) { $0.union($1) } + let characteristic = CBMutableCharacteristic( + type: CBUUID(string: uuid), + properties: combinedProperties, + value: value?.toData(), + permissions: combinedPermissions + ) + characteristic.descriptors = descriptors?.compactMap { $0.toCBMutableDescriptor() } + peripheralCharacteristicsList.removeAll { + $0.uuid.uuidString.lowercased() == characteristic.uuid.uuidString.lowercased() + } + peripheralCharacteristicsList.append(characteristic) + return characteristic + } +} + +extension String { + func findPeripheralCharacteristic() -> CBMutableCharacteristic? { + let target = lowercased() + return peripheralCharacteristicsList.first { + $0.uuid.uuidString.lowercased() == target + } + } + + func findPeripheralService() -> CBMutableService? { + let target = lowercased() + return peripheralServicesList.first { + $0.uuid.uuidString.lowercased() == target + } + } +} + +extension Int64 { + func toCBCharacteristicProperties() -> CBCharacteristicProperties? { + switch self { + case 0: return .broadcast + case 1: return .read + case 2: return .writeWithoutResponse + case 3: return .write + case 4: return .notify + case 5: return .indicate + case 6: return .authenticatedSignedWrites + case 7: return .extendedProperties + case 8: return .notifyEncryptionRequired + case 9: return .indicateEncryptionRequired + default: return nil + } + } + + func toCBAttributePermissions() -> CBAttributePermissions? { + switch self { + case 0: return .readable + case 1: return .writeable + case 2: return .readEncryptionRequired + case 3: return .writeEncryptionRequired + default: return nil + } + } + + func toCBATTErrorCode() -> CBATTError.Code { + switch self { + case 0: return .success + case 1: return .invalidHandle + case 2: return .readNotPermitted + case 3: return .writeNotPermitted + case 4: return .invalidPdu + case 5: return .insufficientAuthentication + case 6: return .requestNotSupported + case 7: return .invalidOffset + case 8: return .insufficientAuthorization + case 9: return .prepareQueueFull + case 10: return .attributeNotFound + case 11: return .attributeNotLong + case 12: return .insufficientEncryptionKeySize + case 13: return .invalidAttributeValueLength + case 14: return .unlikelyError + case 15: return .insufficientEncryption + case 16: return .unsupportedGroupType + case 17: return .insufficientResources + default: return .success + } + } +} + +extension Data { + func toFlutterBytes() -> FlutterStandardTypedData { + FlutterStandardTypedData(bytes: self) + } +} diff --git a/darwin/universal_ble/Sources/universal_ble/UniversalBlePeripheralPlugin.swift b/darwin/universal_ble/Sources/universal_ble/UniversalBlePeripheralPlugin.swift new file mode 100644 index 00000000..c97cbee6 --- /dev/null +++ b/darwin/universal_ble/Sources/universal_ble/UniversalBlePeripheralPlugin.swift @@ -0,0 +1,330 @@ +#if os(iOS) + import Flutter +#elseif os(macOS) + import Cocoa + import FlutterMacOS +#endif +import CoreBluetooth +import Foundation + +final class UniversalBlePeripheralPlugin: NSObject, UniversalBlePeripheralChannel, + CBPeripheralManagerDelegate +{ + private let callbackChannel: UniversalBlePeripheralCallback + private var advertisingState: PeripheralAdvertisingState = .idle + private lazy var peripheralManager: CBPeripheralManager = + .init(delegate: self, queue: nil, options: nil) + private let centralsLock = NSLock() + private var centralsById = [String: CBCentral]() + private var centralCharacteristicSubscriptions = [String: Set]() + + init(callbackChannel: UniversalBlePeripheralCallback) { + self.callbackChannel = callbackChannel + super.init() + _ = peripheralManager.isAdvertising + } + + deinit { + peripheralManager.stopAdvertising() + peripheralManager.removeAllServices() + clearPeripheralCaches() + clearCentrals() + } + + func getAdvertisingState() throws -> PeripheralAdvertisingState { + advertisingState + } + + func getReadinessState() throws -> PeripheralReadinessState { + switch peripheralManager.state { + case .poweredOn: + return .ready + case .poweredOff: + return .bluetoothOff + case .unauthorized: + return .unauthorized + case .unsupported: + return .unsupported + case .unknown, .resetting: + return .unknown + @unknown default: + return .unknown + } + } + + func stopAdvertising() throws { + advertisingState = .stopping + callbackChannel.onAdvertisingStateChange(state: .stopping, error: nil) { _ in } + peripheralManager.stopAdvertising() + advertisingState = .idle + callbackChannel.onAdvertisingStateChange(state: .idle, error: nil) { _ in } + } + + func addService(service: PeripheralService) throws { + peripheralManager.add(service.toCBService()) + } + + func removeService(serviceId: String) throws { + if let service = serviceId.findPeripheralService() { + peripheralManager.remove(service) + peripheralServicesList.removeAll { + $0.uuid.uuidString.lowercased() == service.uuid.uuidString.lowercased() + } + } + } + + func clearServices() throws { + peripheralManager.removeAllServices() + peripheralServicesList.removeAll() + } + + func getServices() throws -> [String] { + peripheralServicesList.map { $0.uuid.uuidString } + } + + func startAdvertising( + services: [String], + localName: String?, + timeout: Int64?, + manufacturerData: PeripheralManufacturerData?, + addManufacturerDataInScanResponse: Bool + ) throws { + if let timeout, timeout > 0 { + throw NSError( + domain: "UniversalBlePeripheral", + code: -1, + userInfo: [NSLocalizedDescriptionKey: "Advertising timeout is not supported on Darwin."] + ) + } + if addManufacturerDataInScanResponse { + throw NSError( + domain: "UniversalBlePeripheral", + code: -1, + userInfo: [ + NSLocalizedDescriptionKey: + "Scan response manufacturer data placement is not supported on Darwin." + ] + ) + } + let cbServices = services.map { CBUUID(string: $0) } + var advertisementData: [String: Any] = [CBAdvertisementDataServiceUUIDsKey: cbServices] + if let localName { + advertisementData[CBAdvertisementDataLocalNameKey] = localName + } + if let manufacturerData { + // CoreBluetooth expects the full AD manufacturer field: company ID (LE uint16) + // followed by payload. Dart's `ManufacturerData` keeps id and payload separate. + let companyId = UInt16(truncatingIfNeeded: manufacturerData.manufacturerId) + var manufacturerField = Data() + manufacturerField.append(UInt8(companyId & 0x00ff)) + manufacturerField.append(UInt8((companyId >> 8) & 0x00ff)) + manufacturerField.append(manufacturerData.data.data) + advertisementData[CBAdvertisementDataManufacturerDataKey] = manufacturerField + } + advertisingState = .starting + callbackChannel.onAdvertisingStateChange(state: .starting, error: nil) { _ in } + peripheralManager.startAdvertising(advertisementData) + } + + func updateCharacteristic( + characteristicId: String, + value: FlutterStandardTypedData, + deviceId: String? + ) throws { + guard let characteristic = characteristicId.findPeripheralCharacteristic() else { + throw UniversalBlePeripheralError.notFound("\(characteristicId) characteristic not found") + } + if let deviceId { + guard let central = central(for: deviceId) else { + throw UniversalBlePeripheralError.notFound("\(deviceId) device not found") + } + peripheralManager.updateValue( + value.toData(), + for: characteristic, + onSubscribedCentrals: [central] + ) + } else { + peripheralManager.updateValue(value.toData(), for: characteristic, onSubscribedCentrals: nil) + } + } + + nonisolated func peripheralManagerDidStartAdvertising( + _: CBPeripheralManager, + error: Error? + ) { + advertisingState = error == nil ? .advertising : .error + callbackChannel.onAdvertisingStateChange( + state: advertisingState, + error: error?.localizedDescription + ) { _ in } + } + + nonisolated func peripheralManagerDidUpdateState(_ peripheral: CBPeripheralManager) { + if peripheral.state != .poweredOn { + advertisingState = .idle + callbackChannel.onAdvertisingStateChange(state: .idle, error: nil) { _ in } + } + } + + nonisolated func peripheralManager( + _: CBPeripheralManager, + didAdd service: CBService, + error: Error? + ) { + callbackChannel.onServiceAdded( + serviceId: service.uuid.uuidString, + error: error?.localizedDescription + ) { _ in } + } + + nonisolated func peripheralManager( + _: CBPeripheralManager, + central: CBCentral, + didSubscribeTo characteristic: CBCharacteristic + ) { + upsertCentralSubscription( + central: central, + characteristicId: characteristic.uuid.uuidString + ) + callbackChannel.onCharacteristicSubscriptionChange( + deviceId: central.identifier.uuidString, + characteristicId: characteristic.uuid.uuidString, + isSubscribed: true, + name: nil + ) { _ in } + callbackChannel.onMtuChange( + deviceId: central.identifier.uuidString, + mtu: Int64(central.maximumUpdateValueLength) + ) { _ in } + } + + nonisolated func peripheralManager( + _: CBPeripheralManager, + central: CBCentral, + didUnsubscribeFrom characteristic: CBCharacteristic + ) { + removeCentralSubscription( + centralId: central.identifier.uuidString, + characteristicId: characteristic.uuid.uuidString + ) + callbackChannel.onCharacteristicSubscriptionChange( + deviceId: central.identifier.uuidString, + characteristicId: characteristic.uuid.uuidString, + isSubscribed: false, + name: nil + ) { _ in } + } + + nonisolated func peripheralManager( + _: CBPeripheralManager, + didReceiveRead request: CBATTRequest + ) { + callbackChannel.onReadRequest( + deviceId: request.central.identifier.uuidString, + characteristicId: request.characteristic.uuid.uuidString, + offset: Int64(request.offset), + value: request.value?.toFlutterBytes() + ) { readReq in + do { + let result = try readReq.get() + let status = result?.status?.toCBATTErrorCode() ?? .success + guard status == .success else { + self.peripheralManager.respond(to: request, withResult: status) + return + } + guard let fullData = result?.value.toData() as Data? else { + self.peripheralManager.respond(to: request, withResult: .requestNotSupported) + return + } + let offset = Int(request.offset) + guard offset <= fullData.count else { + self.peripheralManager.respond(to: request, withResult: .invalidOffset) + return + } + let sliced = fullData.subdata(in: offset..() + subscriptions.insert(characteristicId) + centralCharacteristicSubscriptions[centralId] = subscriptions + centralsLock.unlock() + } + + private func removeCentralSubscription(centralId: String, characteristicId: String) { + centralsLock.lock() + var subscriptions = centralCharacteristicSubscriptions[centralId] ?? Set() + subscriptions.remove(characteristicId) + if subscriptions.isEmpty { + centralCharacteristicSubscriptions.removeValue(forKey: centralId) + centralsById.removeValue(forKey: centralId) + } else { + centralCharacteristicSubscriptions[centralId] = subscriptions + } + centralsLock.unlock() + } + + private func central(for id: String) -> CBCentral? { + centralsLock.lock() + let central = centralsById[id] + centralsLock.unlock() + return central + } + + private func clearCentrals() { + centralsLock.lock() + centralsById.removeAll() + centralCharacteristicSubscriptions.removeAll() + centralsLock.unlock() + } + + func getSubscribedClients(characteristicId: String) throws -> [String] { + let target = characteristicId.uppercased() + centralsLock.lock() + defer { centralsLock.unlock() } + return centralCharacteristicSubscriptions.compactMap { centralId, chars in + chars.contains(where: { $0.uppercased() == target }) ? centralId : nil + } + } + + func getMaximumNotifyLength(deviceId: String) throws -> Int64? { + guard let central = central(for: deviceId) else { + return nil + } + return Int64(central.maximumUpdateValueLength) + } +} diff --git a/darwin/universal_ble/Sources/universal_ble/UniversalBlePlugin.swift b/darwin/universal_ble/Sources/universal_ble/UniversalBlePlugin.swift index 4f6b1fb3..042a2e97 100644 --- a/darwin/universal_ble/Sources/universal_ble/UniversalBlePlugin.swift +++ b/darwin/universal_ble/Sources/universal_ble/UniversalBlePlugin.swift @@ -18,7 +18,13 @@ public class UniversalBlePlugin: NSObject, FlutterPlugin { #endif let callbackChannel = UniversalBleCallbackChannel(binaryMessenger: messenger) let api = BleCentralDarwin(callbackChannel: callbackChannel) + let peripheralCallbackChannel = UniversalBlePeripheralCallback(binaryMessenger: messenger) + let peripheralApi = UniversalBlePeripheralPlugin(callbackChannel: peripheralCallbackChannel) UniversalBlePlatformChannelSetup.setUp(binaryMessenger: messenger, api: api) + UniversalBlePeripheralChannelSetup.setUp( + binaryMessenger: messenger, + api: peripheralApi + ) } } diff --git a/example/ios/Flutter/Debug.xcconfig b/example/ios/Flutter/Debug.xcconfig index 592ceee8..ec97fc6f 100644 --- a/example/ios/Flutter/Debug.xcconfig +++ b/example/ios/Flutter/Debug.xcconfig @@ -1 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "Generated.xcconfig" diff --git a/example/ios/Flutter/Release.xcconfig b/example/ios/Flutter/Release.xcconfig index 592ceee8..c4855bfe 100644 --- a/example/ios/Flutter/Release.xcconfig +++ b/example/ios/Flutter/Release.xcconfig @@ -1 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "Generated.xcconfig" diff --git a/example/ios/Podfile b/example/ios/Podfile new file mode 100644 index 00000000..620e46eb --- /dev/null +++ b/example/ios/Podfile @@ -0,0 +1,43 @@ +# Uncomment this line to define a global platform for your project +# platform :ios, '13.0' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_ios_podfile_setup + +target 'Runner' do + use_frameworks! + + flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) + target 'RunnerTests' do + inherit! :search_paths + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_ios_build_settings(target) + end +end diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock new file mode 100644 index 00000000..0b74389f --- /dev/null +++ b/example/ios/Podfile.lock @@ -0,0 +1,29 @@ +PODS: + - Flutter (1.0.0) + - integration_test (0.0.1): + - Flutter + - universal_ble (0.0.1): + - Flutter + - FlutterMacOS + +DEPENDENCIES: + - Flutter (from `Flutter`) + - integration_test (from `.symlinks/plugins/integration_test/ios`) + - universal_ble (from `.symlinks/plugins/universal_ble/darwin`) + +EXTERNAL SOURCES: + Flutter: + :path: Flutter + integration_test: + :path: ".symlinks/plugins/integration_test/ios" + universal_ble: + :path: ".symlinks/plugins/universal_ble/darwin" + +SPEC CHECKSUMS: + Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467 + integration_test: 4a889634ef21a45d28d50d622cf412dc6d9f586e + universal_ble: a322ebabee64f0ec27a313e89a5dd6967f37a60f + +PODFILE CHECKSUM: 3c63482e143d1b91d2d2560aee9fb04ecc74ac7e + +COCOAPODS: 1.16.2 diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj index ea2a2e8a..730c7de2 100644 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -10,11 +10,13 @@ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 3C020DDC565FF8D37F3235C5 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E92536821956EA00892DD639 /* Pods_Runner.framework */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; - 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; + B2742FE9E6C1C51A78D26865 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9AB88605F389EEED340E5872 /* Pods_RunnerTests.framework */; }; + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -41,14 +43,18 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 0D9A3CA727A1375BDCCC70CF /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 30B9BF32CD25D1ECB8BB5DCA /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 411AF23EC127806AE083685B /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; + 4414252E2351EBDB7761F83A /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + 6CF659084054FE2807275A3A /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; - 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; @@ -57,6 +63,10 @@ 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 9AB88605F389EEED340E5872 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + A11EED44BE0324AD23055698 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + E92536821956EA00892DD639 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -64,6 +74,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + B2742FE9E6C1C51A78D26865 /* Pods_RunnerTests.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -72,6 +83,7 @@ buildActionMask = 2147483647; files = ( 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */, + 3C020DDC565FF8D37F3235C5 /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -86,6 +98,15 @@ path = RunnerTests; sourceTree = ""; }; + 68033A1F47BF85C19A1ED513 /* Frameworks */ = { + isa = PBXGroup; + children = ( + E92536821956EA00892DD639 /* Pods_Runner.framework */, + 9AB88605F389EEED340E5872 /* Pods_RunnerTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( @@ -106,6 +127,7 @@ 97C146EF1CF9000F007C117D /* Products */, 331C8082294A63A400263BE5 /* RunnerTests */, FAF8A3758B0770B81319B3AE /* Pods */, + 68033A1F47BF85C19A1ED513 /* Frameworks */, ); sourceTree = ""; }; @@ -136,6 +158,12 @@ FAF8A3758B0770B81319B3AE /* Pods */ = { isa = PBXGroup; children = ( + 30B9BF32CD25D1ECB8BB5DCA /* Pods-Runner.debug.xcconfig */, + 4414252E2351EBDB7761F83A /* Pods-Runner.release.xcconfig */, + A11EED44BE0324AD23055698 /* Pods-Runner.profile.xcconfig */, + 411AF23EC127806AE083685B /* Pods-RunnerTests.debug.xcconfig */, + 0D9A3CA727A1375BDCCC70CF /* Pods-RunnerTests.release.xcconfig */, + 6CF659084054FE2807275A3A /* Pods-RunnerTests.profile.xcconfig */, ); path = Pods; sourceTree = ""; @@ -147,6 +175,7 @@ isa = PBXNativeTarget; buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; buildPhases = ( + 7E79F47357B64513A488AC3E /* [CP] Check Pods Manifest.lock */, 331C807D294A63A400263BE5 /* Sources */, 331C807F294A63A400263BE5 /* Resources */, 8EE4584064C335A1F66BB6D9 /* Frameworks */, @@ -162,24 +191,26 @@ productType = "com.apple.product-type.bundle.unit-test"; }; 97C146ED1CF9000F007C117D /* Runner */ = { + packageProductDependencies = ( + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */, + ); isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( + 3CE2E408302E9BF272A3BE07 /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + 2F5EDC3CC74E7431E4CB95DF /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); dependencies = ( ); name = Runner; - packageProductDependencies = ( - 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */, - ); productName = Runner; productReference = 97C146EE1CF9000F007C117D /* Runner.app */; productType = "com.apple.product-type.application"; @@ -188,6 +219,9 @@ /* Begin PBXProject section */ 97C146E61CF9000F007C117D /* Project object */ = { + packageReferences = ( + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */, + ); isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = YES; @@ -213,9 +247,6 @@ Base, ); mainGroup = 97C146E51CF9000F007C117D; - packageReferences = ( - 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */, - ); productRefGroup = 97C146EF1CF9000F007C117D /* Products */; projectDirPath = ""; projectRoot = ""; @@ -248,6 +279,23 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ + 2F5EDC3CC74E7431E4CB95DF /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; @@ -264,6 +312,50 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; + 3CE2E408302E9BF272A3BE07 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 7E79F47357B64513A488AC3E /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; @@ -370,7 +462,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.1; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -403,6 +495,7 @@ }; 331C8088294A63A400263BE5 /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 411AF23EC127806AE083685B /* Pods-RunnerTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; @@ -420,6 +513,7 @@ }; 331C8089294A63A400263BE5 /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 0D9A3CA727A1375BDCCC70CF /* Pods-RunnerTests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; @@ -435,6 +529,7 @@ }; 331C808A294A63A400263BE5 /* Profile */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 6CF659084054FE2807275A3A /* Pods-RunnerTests.profile.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; @@ -495,7 +590,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.1; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -544,7 +639,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.1; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -634,14 +729,12 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ - /* Begin XCLocalSwiftPackageReference section */ - 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */ = { + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */ = { isa = XCLocalSwiftPackageReference; relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; }; /* End XCLocalSwiftPackageReference section */ - /* Begin XCSwiftPackageProductDependency section */ 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = { isa = XCSwiftPackageProductDependency; diff --git a/example/lib/data/mock_universal_ble.dart b/example/lib/data/mock_universal_ble.dart index 44a681ac..63413d4b 100644 --- a/example/lib/data/mock_universal_ble.dart +++ b/example/lib/data/mock_universal_ble.dart @@ -66,7 +66,7 @@ class MockUniversalBle extends UniversalBlePlatform { } @override - Future getBluetoothAvailabilityState() async { + Future getAvailabilityState() async { return AvailabilityState.poweredOn; } diff --git a/example/lib/home/home.dart b/example/lib/home/home.dart index 0ab9c57b..28117c1b 100644 --- a/example/lib/home/home.dart +++ b/example/lib/home/home.dart @@ -11,14 +11,15 @@ import 'package:universal_ble_example/peripheral_details/peripheral_detail_page. import 'package:universal_ble_example/widgets/platform_button.dart'; import 'package:universal_ble_example/widgets/responsive_buttons_grid.dart'; -class MyApp extends StatefulWidget { - const MyApp({super.key}); +class CentralHome extends StatefulWidget { + final bool showAppBar; + const CentralHome({super.key, this.showAppBar = true}); @override - State createState() => _MyAppState(); + State createState() => _CentralHomeState(); } -class _MyAppState extends State { +class _CentralHomeState extends State { final _bleDevices = []; final _hiddenDevices = []; bool _isScanning = false; @@ -150,22 +151,24 @@ class _MyAppState extends State { @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar( - title: const Text('Universal BLE'), - elevation: 4, - actions: [ - if (_isScanning) - const Padding( - padding: EdgeInsets.all(8.0), - child: SizedBox( - width: 20, - height: 20, - child: CircularProgressIndicator.adaptive( - strokeWidth: 2, - )), - ), - ], - ), + appBar: widget.showAppBar + ? AppBar( + title: const Text('Universal BLE - Central'), + elevation: 4, + actions: [ + if (_isScanning) + const Padding( + padding: EdgeInsets.all(8.0), + child: SizedBox( + width: 20, + height: 20, + child: CircularProgressIndicator.adaptive( + strokeWidth: 2, + )), + ), + ], + ) + : null, body: Column( children: [ Padding( @@ -218,13 +221,14 @@ class _MyAppState extends State { ), if (BleCapabilities.requiresRuntimePermission) ...[ PlatformButton( - text: 'Has Permissions', + text: 'Is Permission Granted', onPressed: () async { try { - bool hasPermissions = await UniversalBle.hasPermissions( + bool granted = + await UniversalBle.hasPermissions( withAndroidFineLocation: false, ); - showSnackbar("Has Permissions: $hasPermissions"); + showSnackbar("Is Permission Granted: $granted"); } catch (e) { showSnackbar(e.toString()); } diff --git a/example/lib/main.dart b/example/lib/main.dart index 01f1d477..c73bb0ea 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:universal_ble/universal_ble.dart'; -import 'package:universal_ble_example/home/home.dart'; +import 'package:universal_ble_example/home/home.dart' show CentralHome; +import 'package:universal_ble_example/peripheral/peripheral_home.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); @@ -11,7 +12,35 @@ void main() async { debugShowCheckedModeBanner: false, darkTheme: ThemeData.dark(), themeMode: ThemeMode.system, - home: const MyApp(), + home: const _TabbedExample(), ), ); } + +class _TabbedExample extends StatelessWidget { + const _TabbedExample(); + + @override + Widget build(BuildContext context) { + return DefaultTabController( + length: 2, + child: Scaffold( + appBar: AppBar( + title: const Text('Universal BLE'), + bottom: const TabBar( + tabs: [ + Tab(text: 'Central'), + Tab(text: 'Peripheral'), + ], + ), + ), + body: const TabBarView( + children: [ + CentralHome(showAppBar: false), + PeripheralHome(), + ], + ), + ), + ); + } +} diff --git a/example/lib/peripheral/peripheral_home.dart b/example/lib/peripheral/peripheral_home.dart new file mode 100644 index 00000000..a0b90ce4 --- /dev/null +++ b/example/lib/peripheral/peripheral_home.dart @@ -0,0 +1,211 @@ +import 'dart:async'; +import 'dart:convert'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:universal_ble/universal_ble.dart'; + +class PeripheralHome extends StatefulWidget { + const PeripheralHome({super.key}); + + @override + State createState() => _PeripheralHomeState(); +} + +class _PeripheralHomeState extends State { + final UniversalBlePeripheralClient _peripheral = UniversalBlePeripheralClient(); + final List _logs = []; + StreamSubscription? _eventSub; + bool _initialized = false; + UniversalBlePeripheralAdvertisingState _advertisingState = + UniversalBlePeripheralAdvertisingState.idle; + + static const String _serviceBattery = '0000180F-0000-1000-8000-00805F9B34FB'; + static const String _charBattery = '00002A19-0000-1000-8000-00805F9B34FB'; + static const String _serviceTest = '0000180D-0000-1000-8000-00805F9B34FB'; + static const String _charTest = '00002A18-0000-1000-8000-00805F9B34FB'; + + @override + void initState() { + super.initState(); + _eventSub = _peripheral.eventStream.listen((event) { + switch (event) { + case UniversalBlePeripheralAdvertisingStateChanged(): + setState(() { + _advertisingState = event.state; + }); + _log('Advertising state: ${event.state.name} ${event.error ?? ''}'.trim()); + case UniversalBlePeripheralServiceAdded(): + _log('Service added: ${event.serviceId} ${event.error ?? ''}'.trim()); + case UniversalBlePeripheralCharacteristicSubscriptionChanged(): + _log( + 'Subscription ${event.isSubscribed ? 'on' : 'off'}: ' + '${event.name ?? event.deviceId} -> ${event.characteristicId}', + ); + case UniversalBlePeripheralConnectionStateChanged(): + _log( + 'Connection: ${event.deviceId} connected=${event.connected}', + ); + case UniversalBlePeripheralMtuChanged(): + _log('MTU: ${event.deviceId} mtu=${event.mtu}'); + } + }); + _peripheral.setRequestHandlers( + PeripheralRequestHandlers( + onReadRequest: (deviceId, characteristicId, _, __) { + _log('Read request: $deviceId ${characteristicId.value}'); + return BleReadRequestResult( + value: Uint8List.fromList(utf8.encode('Hello World')), + ); + }, + onWriteRequest: (deviceId, characteristicId, _, value) { + _log('Write request: $deviceId ${characteristicId.value} $value'); + return const BleWriteRequestResult(); + }, + ), + ); + } + + void _log(String text) { + setState(() { + _logs.insert(0, text); + }); + } + + Future _initialize() async { + final supported = + (await _peripheral.getStaticCapabilities()).supportsPeripheralMode; + setState(() { + _initialized = supported; + }); + final readiness = await _peripheral.getReadinessState(); + _log('Peripheral ready check. supported=$supported readiness=${readiness.name}'); + } + + Future _addServices() async { + await _peripheral.addService( + BleService(_serviceBattery, [ + BleCharacteristic( + _charBattery, + [CharacteristicProperty.read, CharacteristicProperty.notify], + [], + ), + ]), + ); + await _peripheral.addService( + BleService(_serviceTest, [ + BleCharacteristic( + _charTest, + [ + CharacteristicProperty.read, + CharacteristicProperty.notify, + CharacteristicProperty.write, + ], + [BleDescriptor('00002908-0000-1000-8000-00805F9B34FB')], + ), + ]), + ); + _log('Services queued'); + } + + Future _startAdvertising() async { + final isWindows = !kIsWeb && defaultTargetPlatform == TargetPlatform.windows; + await _peripheral.startAdvertising( + services: [ + const PeripheralServiceId(_serviceBattery), + const PeripheralServiceId(_serviceTest), + ], + localName: isWindows ? null : 'UniversalBlePeripheral', + manufacturerData: isWindows + ? null + : ManufacturerData( + 0x012D, + Uint8List.fromList([0x03, 0x00, 0x64, 0x00]), + ), + addManufacturerDataInScanResponse: !isWindows, + ); + _log('Start advertising requested'); + } + + @override + Widget build(BuildContext context) { + return Column( + children: [ + Padding( + padding: const EdgeInsets.all(8.0), + child: Wrap( + spacing: 8, + runSpacing: 8, + children: [ + ElevatedButton( + onPressed: _initialize, + child: Text(_initialized ? 'Reinitialize' : 'Initialize'), + ), + ElevatedButton( + onPressed: _initialized ? _addServices : null, + child: const Text('Add Services'), + ), + ElevatedButton( + onPressed: _initialized ? _startAdvertising : null, + child: const Text('Start Advertising'), + ), + ElevatedButton( + onPressed: _initialized + ? () async { + await _peripheral.stopAdvertising(); + setState(() { + _advertisingState = + UniversalBlePeripheralAdvertisingState.idle; + }); + _log('Stop advertising requested'); + } + : null, + child: const Text('Stop Advertising'), + ), + ElevatedButton( + onPressed: _initialized + ? () async { + await _peripheral.updateCharacteristicValue( + characteristicId: const PeripheralCharacteristicId( + _charTest, + ), + value: Uint8List.fromList(utf8.encode('Test Data')), + ); + _log('Characteristic updated'); + } + : null, + child: const Text('Update Characteristic'), + ), + ], + ), + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 12), + child: Row( + children: [ + Text('Initialized: $_initialized'), + const SizedBox(width: 16), + Text('Advertising: ${_advertisingState.name}'), + ], + ), + ), + const Divider(), + Expanded( + child: ListView.builder( + itemCount: _logs.length, + itemBuilder: (context, index) => ListTile( + dense: true, + title: Text(_logs[index]), + ), + ), + ), + ], + ); + } + + @override + void dispose() { + _eventSub?.cancel(); + super.dispose(); + } +} diff --git a/example/macos/Flutter/Flutter-Debug.xcconfig b/example/macos/Flutter/Flutter-Debug.xcconfig index c2efd0b6..4b81f9b2 100644 --- a/example/macos/Flutter/Flutter-Debug.xcconfig +++ b/example/macos/Flutter/Flutter-Debug.xcconfig @@ -1 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "ephemeral/Flutter-Generated.xcconfig" diff --git a/example/macos/Flutter/Flutter-Release.xcconfig b/example/macos/Flutter/Flutter-Release.xcconfig index c2efd0b6..5caa9d15 100644 --- a/example/macos/Flutter/Flutter-Release.xcconfig +++ b/example/macos/Flutter/Flutter-Release.xcconfig @@ -1 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "ephemeral/Flutter-Generated.xcconfig" diff --git a/example/macos/Podfile b/example/macos/Podfile new file mode 100644 index 00000000..ff5ddb3b --- /dev/null +++ b/example/macos/Podfile @@ -0,0 +1,42 @@ +platform :osx, '10.15' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_macos_podfile_setup + +target 'Runner' do + use_frameworks! + + flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) + target 'RunnerTests' do + inherit! :search_paths + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_macos_build_settings(target) + end +end diff --git a/example/macos/Podfile.lock b/example/macos/Podfile.lock new file mode 100644 index 00000000..f496afd0 --- /dev/null +++ b/example/macos/Podfile.lock @@ -0,0 +1,23 @@ +PODS: + - FlutterMacOS (1.0.0) + - universal_ble (0.0.1): + - Flutter + - FlutterMacOS + +DEPENDENCIES: + - FlutterMacOS (from `Flutter/ephemeral`) + - universal_ble (from `Flutter/ephemeral/.symlinks/plugins/universal_ble/darwin`) + +EXTERNAL SOURCES: + FlutterMacOS: + :path: Flutter/ephemeral + universal_ble: + :path: Flutter/ephemeral/.symlinks/plugins/universal_ble/darwin + +SPEC CHECKSUMS: + FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1 + universal_ble: 45519b2aeafe62761e2c6309f8927edb5288b914 + +PODFILE CHECKSUM: 9ebaf0ce3d369aaa26a9ea0e159195ed94724cf3 + +COCOAPODS: 1.16.2 diff --git a/lib/src/extensions/ble_characteristic_extension.dart b/lib/src/extensions/ble_characteristic_extension.dart index f277ce63..6107abb2 100644 --- a/lib/src/extensions/ble_characteristic_extension.dart +++ b/lib/src/extensions/ble_characteristic_extension.dart @@ -85,7 +85,7 @@ class CharacteristicSubscription { final bool isSupported; CharacteristicSubscription(this._characteristic, this._property) - : isSupported = _characteristic.properties.contains(_property); + : isSupported = _characteristic.properties.contains(_property); /// Registers a listener for incoming data from the characteristic. StreamSubscription listen( diff --git a/lib/src/models/ble_capabilities.dart b/lib/src/models/ble_capabilities.dart index baa93e3d..f54a7ae6 100644 --- a/lib/src/models/ble_capabilities.dart +++ b/lib/src/models/ble_capabilities.dart @@ -22,11 +22,10 @@ class BleCapabilities { /// but it very unreliable, therefore we return false. static final triggersConfirmOnlyPairing = defaultTargetPlatform != TargetPlatform.windows && - defaultTargetPlatform != TargetPlatform.linux; + defaultTargetPlatform != TargetPlatform.linux; /// Returns true if pair()/unpair() are supported on the platform. - static bool hasSystemPairingApi = - !kIsWeb && + static bool hasSystemPairingApi = !kIsWeb && (defaultTargetPlatform == TargetPlatform.android || defaultTargetPlatform == TargetPlatform.windows || defaultTargetPlatform == TargetPlatform.linux); diff --git a/lib/src/models/ble_device.dart b/lib/src/models/ble_device.dart index 464d7e8d..87aefbda 100644 --- a/lib/src/models/ble_device.dart +++ b/lib/src/models/ble_device.dart @@ -55,9 +55,9 @@ class BleDevice { this.manufacturerDataList = const [], Map serviceData = const {}, this.timestamp, - }) : serviceData = _validateServiceData(serviceData), - rawName = name, - name = name?.replaceAll(RegExp(r'[^ -~]'), '').trim(); + }) : serviceData = _validateServiceData(serviceData), + rawName = name, + name = name?.replaceAll(RegExp(r'[^ -~]'), '').trim(); DateTime? get timestampDateTime => timestamp != null ? DateTime.fromMillisecondsSinceEpoch(timestamp!) diff --git a/lib/src/models/ble_service.dart b/lib/src/models/ble_service.dart index ee179e29..c1a97c8c 100644 --- a/lib/src/models/ble_service.dart +++ b/lib/src/models/ble_service.dart @@ -1,3 +1,4 @@ +import 'package:flutter/foundation.dart'; import 'package:universal_ble/universal_ble.dart'; class BleService { @@ -5,7 +6,7 @@ class BleService { List characteristics; BleService(String uuid, this.characteristics) - : uuid = BleUuidParser.string(uuid); + : uuid = BleUuidParser.string(uuid); @override String toString() { @@ -20,7 +21,7 @@ class BleCharacteristic { ({String deviceId, String serviceId})? metaData; BleCharacteristic(String uuid, this.properties, this.descriptors) - : uuid = BleUuidParser.string(uuid); + : uuid = BleUuidParser.string(uuid); BleCharacteristic.withMetaData({ required String deviceId, @@ -28,11 +29,11 @@ class BleCharacteristic { required String uuid, required this.properties, required this.descriptors, - }) : uuid = BleUuidParser.string(uuid), - metaData = ( - deviceId: deviceId, - serviceId: BleUuidParser.string(serviceId), - ); + }) : uuid = BleUuidParser.string(uuid), + metaData = ( + deviceId: deviceId, + serviceId: BleUuidParser.string(serviceId), + ); @override String toString() { @@ -55,19 +56,23 @@ class BleCharacteristic { class BleDescriptor { String uuid; - BleDescriptor(String uuid) : uuid = BleUuidParser.string(uuid); + /// Initial attribute value for this descriptor (e.g. HID Report Reference 0x2908). + Uint8List? value; + + BleDescriptor(String uuid, {this.value}) : uuid = BleUuidParser.string(uuid); @override - String toString() => 'BleDescriptor{uuid: $uuid}'; + String toString() => 'BleDescriptor{uuid: $uuid, value: $value}'; @override bool operator ==(Object other) { if (other is! BleDescriptor) return false; - return other.uuid == uuid; + return other.uuid == uuid && listEquals(other.value, value); } @override - int get hashCode => uuid.hashCode; + int get hashCode => + Object.hash(uuid, value == null ? null : Object.hashAll(value!)); } enum CharacteristicProperty { diff --git a/lib/src/models/ble_uuid_parser.dart b/lib/src/models/ble_uuid_parser.dart index c157b7aa..8ab3ba10 100644 --- a/lib/src/models/ble_uuid_parser.dart +++ b/lib/src/models/ble_uuid_parser.dart @@ -20,8 +20,7 @@ class BleUuidParser { if (!uuid.contains("-")) { if (uuid.length != 32) throw const FormatException("Invalid UUID"); - uuid = - "${uuid.substring(0, 8)}-${uuid.substring(8, 12)}" + uuid = "${uuid.substring(0, 8)}-${uuid.substring(8, 12)}" "-${uuid.substring(12, 16)}-${uuid.substring(16, 20)}-${uuid.substring(20, 32)}"; } diff --git a/lib/src/models/model_exports.dart b/lib/src/models/model_exports.dart index 7533772d..55ffbd9b 100644 --- a/lib/src/models/model_exports.dart +++ b/lib/src/models/model_exports.dart @@ -13,3 +13,4 @@ export 'package:universal_ble/src/models/ble_device.dart'; export 'package:universal_ble/src/models/ble_command.dart'; export 'package:universal_ble/src/models/ble_capabilities.dart'; export 'package:universal_ble/src/models/ble_connection_priority.dart'; +export 'package:universal_ble/src/models/peripheral/peripheral_exports.dart'; diff --git a/lib/src/models/peripheral/attribute_permissions.dart b/lib/src/models/peripheral/attribute_permissions.dart new file mode 100644 index 00000000..0d665601 --- /dev/null +++ b/lib/src/models/peripheral/attribute_permissions.dart @@ -0,0 +1,6 @@ +enum AttributePermission { + readable, + writeable, + readEncryptionRequired, + writeEncryptionRequired; +} diff --git a/lib/src/models/peripheral/peripheral_exports.dart b/lib/src/models/peripheral/peripheral_exports.dart new file mode 100644 index 00000000..e2956739 --- /dev/null +++ b/lib/src/models/peripheral/peripheral_exports.dart @@ -0,0 +1,2 @@ +export 'package:universal_ble/src/models/peripheral/attribute_permissions.dart'; +export 'package:universal_ble/src/models/peripheral/peripheral_request_result.dart'; diff --git a/lib/src/models/peripheral/peripheral_request_result.dart b/lib/src/models/peripheral/peripheral_request_result.dart new file mode 100644 index 00000000..28f6ccf2 --- /dev/null +++ b/lib/src/models/peripheral/peripheral_request_result.dart @@ -0,0 +1,25 @@ +import 'dart:typed_data'; + +class BleReadRequestResult { + final Uint8List value; + final int? offset; + final int? status; + + const BleReadRequestResult({ + required this.value, + this.offset, + this.status, + }); +} + +class BleWriteRequestResult { + final Uint8List? value; + final int? offset; + final int? status; + + const BleWriteRequestResult({ + this.value, + this.offset, + this.status, + }); +} diff --git a/lib/src/universal_ble.dart b/lib/src/universal_ble.dart index 70627e61..52ccff08 100644 --- a/lib/src/universal_ble.dart +++ b/lib/src/universal_ble.dart @@ -58,7 +58,8 @@ class UniversalBle { static Stream characteristicValueStream( String deviceId, String characteristicId, - ) => _platform.characteristicValueStream(deviceId, characteristicId); + ) => + _platform.characteristicValueStream(deviceId, characteristicId); /// Pairing state stream static Stream pairingStateStream(String deviceId) => @@ -66,9 +67,9 @@ class UniversalBle { /// Get Bluetooth availability state. /// To be notified of updates, set [onAvailabilityChange] listener. - static Future getBluetoothAvailabilityState() async { + static Future getAvailabilityState() async { return await _bleCommandQueue.queueCommand( - () => _platform.getBluetoothAvailabilityState(), + () => _platform.getAvailabilityState(), ); } @@ -155,9 +156,9 @@ class UniversalBle { _platform .connect(deviceId, connectionTimeout: timeout, autoConnect: autoConnect) .catchError((error) { - if (completer.isCompleted) return; - completer.completeError(ConnectionException(error)); - }); + if (completer.isCompleted) return; + completer.completeError(ConnectionException(error)); + }); if (!await completer.future.timeout(timeout)) { throw ConnectionException("Failed to connect"); @@ -183,14 +184,14 @@ class UniversalBle { await _bleCommandQueue .queueCommand( - () => _platform.disconnect(deviceId), - timeout: timeout, - deviceId: deviceId, - ) + () => _platform.disconnect(deviceId), + timeout: timeout, + deviceId: deviceId, + ) .catchError((error) { - if (completer.isCompleted) return; - completer.completeError(ConnectionException(error)); - }); + if (completer.isCompleted) return; + completer.completeError(ConnectionException(error)); + }); if (connectionState == BleConnectionState.disconnected || connectionState == BleConnectionState.disconnecting) { @@ -547,11 +548,9 @@ class UniversalBle { static set onAvailabilityChange(OnAvailabilityChange? onAvailabilityChange) { _platform.onAvailabilityChange = onAvailabilityChange; if (onAvailabilityChange != null) { - getBluetoothAvailabilityState() - .then((value) { - onAvailabilityChange(value); - }) - .onError((error, stackTrace) => null); + getAvailabilityState().then((value) { + onAvailabilityChange(value); + }).onError((error, stackTrace) => null); } } @@ -619,32 +618,28 @@ class UniversalBle { } connectionSubscription = _platform - .bleConnectionUpdateStreamController - .stream + .bleConnectionUpdateStreamController.stream .where((e) => e.deviceId == deviceId) .listen( - (e) { - cancelSubscription(); - if (e.error != null) { - handleError(e.error); - } else { - if (!completer.isCompleted) { - completer.complete(e.isConnected); - } - } - }, - onError: handleError, - cancelOnError: true, - ); + (e) { + cancelSubscription(); + if (e.error != null) { + handleError(e.error); + } else { + if (!completer.isCompleted) { + completer.complete(e.isConnected); + } + } + }, + onError: handleError, + cancelOnError: true, + ); - completer.future - .timeout(timeout) - .then((_) { - cancelSubscription(); - }) - .catchError((_) { - cancelSubscription(); - }); + completer.future.timeout(timeout).then((_) { + cancelSubscription(); + }).catchError((_) { + cancelSubscription(); + }); return completer; } diff --git a/lib/src/universal_ble_exceptions.dart b/lib/src/universal_ble_exceptions.dart index 0ce8c82d..194284d7 100644 --- a/lib/src/universal_ble_exceptions.dart +++ b/lib/src/universal_ble_exceptions.dart @@ -42,11 +42,11 @@ class ConnectionException extends UniversalBleException { }); ConnectionException([dynamic error]) - : this._( - code: UniversalBleErrorParser.getCode(error), - message: _errorParser(error), - details: error, - ); + : this._( + code: UniversalBleErrorParser.getCode(error), + message: _errorParser(error), + details: error, + ); } /// Exception thrown when pairing-related errors occur @@ -59,11 +59,11 @@ class PairingException extends UniversalBleException { /// Legacy constructor for backward compatibility PairingException([dynamic error]) - : this._( - code: UniversalBleErrorParser.getCode(error), - message: _errorParser(error), - details: error, - ); + : this._( + code: UniversalBleErrorParser.getCode(error), + message: _errorParser(error), + details: error, + ); } /// Exception thrown when Web Bluetooth API is globally disabled diff --git a/lib/src/universal_ble_linux/universal_ble_linux.dart b/lib/src/universal_ble_linux/universal_ble_linux.dart index aa94693b..3ade9bad 100644 --- a/lib/src/universal_ble_linux/universal_ble_linux.dart +++ b/lib/src/universal_ble_linux/universal_ble_linux.dart @@ -31,7 +31,7 @@ class UniversalBleLinux extends UniversalBlePlatform { {}; @override - Future getBluetoothAvailabilityState() async { + Future getAvailabilityState() async { await _ensureInitialized(); BlueZAdapter? adapter = _activeAdapter; @@ -171,25 +171,23 @@ class UniversalBleLinux extends UniversalBlePlatform { ) async { final device = _findDeviceById(deviceId); if (device.gattServices.isEmpty && !device.servicesResolved) { - await device.propertiesChanged - .firstWhere((element) { - if (element.contains(BluezProperty.connected)) { - if (!device.connected) { - UniversalLogger.logInfo( - "DiscoverServicesFailed: Device disconnected", - ); - return true; - } - } - return element.contains(BluezProperty.servicesResolved); - }) - .timeout( - const Duration(seconds: 10), - onTimeout: () { - UniversalLogger.logInfo("DiscoverServicesFailed: Timeout"); - return []; - }, - ); + await device.propertiesChanged.firstWhere((element) { + if (element.contains(BluezProperty.connected)) { + if (!device.connected) { + UniversalLogger.logInfo( + "DiscoverServicesFailed: Device disconnected", + ); + return true; + } + } + return element.contains(BluezProperty.servicesResolved); + }).timeout( + const Duration(seconds: 10), + onTimeout: () { + UniversalLogger.logInfo("DiscoverServicesFailed: Timeout"); + return []; + }, + ); } // Few ble devices requires delay to perform operations after discovering services @@ -220,8 +218,8 @@ class UniversalBleLinux extends UniversalBlePlatform { properties: properties, descriptors: withDescriptors ? e.descriptors - .map((e) => BleDescriptor(e.uuid.toString())) - .toList() + .map((e) => BleDescriptor(e.uuid.toString())) + .toList() : [], ); }).toList(); @@ -237,13 +235,13 @@ class UniversalBleLinux extends UniversalBlePlatform { ) { final device = _findDeviceById(deviceId); final s = device.gattServices.cast().firstWhere( - (s) => s?.uuid.toString() == service, - orElse: () => null, - ); + (s) => s?.uuid.toString() == service, + orElse: () => null, + ); final c = s?.characteristics.cast().firstWhere( - (c) => c?.uuid.toString() == characteristic, - orElse: () => null, - ); + (c) => c?.uuid.toString() == characteristic, + orElse: () => null, + ); if (c == null) { throw UniversalBleException( @@ -281,30 +279,29 @@ class UniversalBleLinux extends UniversalBlePlatform { _characteristicPropertiesSubscriptions[characteristicKey]?.cancel(); } - _characteristicPropertiesSubscriptions[characteristicKey] = char - .propertiesChanged - .listen((List properties) { - for (String property in properties) { - switch (property) { - case BluezProperty.value: - UniversalLogger.logVerbose( - "NOTIFY <- $deviceId $service $characteristic len=${char.value.length} data=${char.value}", - withTimestamp: true, - ); - updateCharacteristicValue( - deviceId, - characteristic, - Uint8List.fromList(char.value), - DateTime.now().millisecondsSinceEpoch, - ); - break; - default: - UniversalLogger.logInfo( - "UnhandledCharValuePropertyChange: $property", - ); - } - } - }); + _characteristicPropertiesSubscriptions[characteristicKey] = + char.propertiesChanged.listen((List properties) { + for (String property in properties) { + switch (property) { + case BluezProperty.value: + UniversalLogger.logVerbose( + "NOTIFY <- $deviceId $service $characteristic len=${char.value.length} data=${char.value}", + withTimestamp: true, + ); + updateCharacteristicValue( + deviceId, + characteristic, + Uint8List.fromList(char.value), + DateTime.now().millisecondsSinceEpoch, + ); + break; + default: + UniversalLogger.logInfo( + "UnhandledCharValuePropertyChange: $property", + ); + } + } + }); } else { if (char.notifying) await char.stopNotify(); _characteristicPropertiesSubscriptions @@ -446,9 +443,8 @@ class UniversalBleLinux extends UniversalBlePlatform { @override Future> getSystemDevices(List? withServices) async { await _ensureInitialized(); - List devices = _client.devices - .where((device) => device.connected) - .toList(); + List devices = + _client.devices.where((device) => device.connected).toList(); if (withServices != null && withServices.isNotEmpty) { devices = devices.where((device) { if (device.servicesResolved) { @@ -491,9 +487,9 @@ class UniversalBleLinux extends UniversalBlePlatform { BlueZDevice? _getDeviceById(String deviceId) { return _devices[deviceId] ?? _client.devices.cast().firstWhere( - (device) => device?.address == deviceId, - orElse: () => null, - ); + (device) => device?.address == deviceId, + orElse: () => null, + ); } Future _ensureInitialized() async { @@ -577,23 +573,21 @@ class UniversalBleLinux extends UniversalBlePlatform { _devices[device.address] = device; // Setup advertisements Listener - _deviceAdvertisementSubscriptions[device.address] ??= device - .propertiesChanged - .where((e) { - return e.contains(BluezProperty.rssi) || - e.contains(BluezProperty.manufacturerData) || - e.contains(BluezProperty.uuids) || - e.contains(BluezProperty.serviceData); - }) - .listen((_) { - if (_bleFilter.shouldAcceptDevice(bleDevice)) { - updateScanResult(device.toBleDevice()); - } - }); + _deviceAdvertisementSubscriptions[device.address] ??= + device.propertiesChanged.where((e) { + return e.contains(BluezProperty.rssi) || + e.contains(BluezProperty.manufacturerData) || + e.contains(BluezProperty.uuids) || + e.contains(BluezProperty.serviceData); + }).listen((_) { + if (_bleFilter.shouldAcceptDevice(bleDevice)) { + updateScanResult(device.toBleDevice()); + } + }); // Setup update listener - _deviceUpdateStreamSubscriptions[device - .address] ??= device.propertiesChanged.listen((properties) { + _deviceUpdateStreamSubscriptions[device.address] ??= + device.propertiesChanged.listen((properties) { for (final property in properties) { switch (property) { // Connection/Pair updates diff --git a/lib/src/universal_ble_peripheral/universal_ble_peripheral.dart b/lib/src/universal_ble_peripheral/universal_ble_peripheral.dart new file mode 100644 index 00000000..44672e2a --- /dev/null +++ b/lib/src/universal_ble_peripheral/universal_ble_peripheral.dart @@ -0,0 +1,166 @@ +import 'package:flutter/foundation.dart'; +import 'package:universal_ble/src/universal_ble_peripheral/universal_ble_peripheral_pigeon.dart'; +import 'package:universal_ble/universal_ble.dart'; + +class UniversalBlePeripheralClient { + UniversalBlePeripheralPlatform _platform; + + UniversalBlePeripheralClient({UniversalBlePeripheralPlatform? platform}) + : _platform = platform ?? UniversalBlePeripheral._defaultPlatform(); + + void setPlatform(UniversalBlePeripheralPlatform platform) { + _platform.dispose(); + _platform = platform; + } + + Stream get eventStream => _platform.eventStream; + + void setRequestHandlers(PeripheralRequestHandlers handlers) => + _platform.setRequestHandlers(handlers); + + Future getReadinessState() => + _platform.getReadinessState(); + + Future getAdvertisingState() => + _platform.getAdvertisingState(); + + Future getStaticCapabilities() => + _platform.getStaticCapabilities(); + + Future addService( + BleService service, { + bool primary = true, + Duration? timeout, + }) => + _platform.addService(service, primary: primary, timeout: timeout); + + Future removeService(PeripheralServiceId serviceId) => + _platform.removeService( + PeripheralServiceId(BleUuidParser.string(serviceId.value)), + ); + + Future clearServices() => _platform.clearServices(); + + Future> getServices() => _platform.getServices(); + + Future startAdvertising({ + required List services, + String? localName, + int? timeout, + ManufacturerData? manufacturerData, + bool addManufacturerDataInScanResponse = false, + }) => + _platform.startAdvertising( + services: services + .map((e) => PeripheralServiceId(BleUuidParser.string(e.value))) + .toList(), + localName: localName, + timeout: timeout, + manufacturerData: manufacturerData, + addManufacturerDataInScanResponse: addManufacturerDataInScanResponse, + ); + + Future stopAdvertising() => _platform.stopAdvertising(); + + Future updateCharacteristicValue({ + required PeripheralCharacteristicId characteristicId, + required Uint8List value, + PeripheralUpdateTarget target = const PeripheralUpdateAllSubscribed(), + }) => + _platform.updateCharacteristicValue( + characteristicId: PeripheralCharacteristicId( + BleUuidParser.string(characteristicId.value), + ), + value: value, + target: target, + ); + + Future> getSubscribedClients(PeripheralCharacteristicId characteristicId) => + _platform.getSubscribedClients( + PeripheralCharacteristicId(BleUuidParser.string(characteristicId.value)), + ); + + Future getMaximumNotifyLength(String deviceId) => + _platform.getMaximumNotifyLength(deviceId); +} + +class UniversalBlePeripheral { + static UniversalBlePeripheralClient? _clientInstance; + static UniversalBlePeripheralClient get _client => + _clientInstance ??= UniversalBlePeripheralClient(); + + static UniversalBlePeripheralPlatform _defaultPlatform() { + if (kIsWeb || defaultTargetPlatform == TargetPlatform.linux) { + return UniversalBlePeripheralUnsupported(); + } + return UniversalBlePeripheralPigeon.instance; + } + + static void setInstance(UniversalBlePeripheralPlatform instance) { + if (_clientInstance == null) { + _clientInstance = UniversalBlePeripheralClient(platform: instance); + return; + } + _client.setPlatform(instance); + } + + static Stream get eventStream => + _client.eventStream; + + static void setRequestHandlers(PeripheralRequestHandlers handlers) => + _client.setRequestHandlers(handlers); + + static Future getReadinessState() => + _client.getReadinessState(); + + static Future getAdvertisingState() => + _client.getAdvertisingState(); + + static Future getStaticCapabilities() => + _client.getStaticCapabilities(); + + static Future addService( + BleService service, { + bool primary = true, + Duration? timeout, + }) => + _client.addService(service, primary: primary, timeout: timeout); + + static Future removeService(PeripheralServiceId serviceId) => + _client.removeService(serviceId); + static Future clearServices() => _client.clearServices(); + static Future> getServices() => _client.getServices(); + + static Future startAdvertising({ + required List services, + String? localName, + int? timeout, + ManufacturerData? manufacturerData, + bool addManufacturerDataInScanResponse = false, + }) => + _client.startAdvertising( + services: services, + localName: localName, + timeout: timeout, + manufacturerData: manufacturerData, + addManufacturerDataInScanResponse: addManufacturerDataInScanResponse, + ); + + static Future stopAdvertising() => _client.stopAdvertising(); + + static Future updateCharacteristicValue({ + required PeripheralCharacteristicId characteristicId, + required Uint8List value, + PeripheralUpdateTarget target = const PeripheralUpdateAllSubscribed(), + }) => + _client.updateCharacteristicValue( + characteristicId: characteristicId, value: value, target: target); + + /// Returns client device ids currently subscribed to [characteristicId] + /// (e.g. HID report characteristic). Used to restore in-app state after restart. + static Future> getSubscribedClients(PeripheralCharacteristicId characteristicId) => + _client.getSubscribedClients(characteristicId); + + static Future getMaximumNotifyLength(String deviceId) => + _client.getMaximumNotifyLength(deviceId); +} diff --git a/lib/src/universal_ble_peripheral/universal_ble_peripheral_mapper.dart b/lib/src/universal_ble_peripheral/universal_ble_peripheral_mapper.dart new file mode 100644 index 00000000..231eeb6f --- /dev/null +++ b/lib/src/universal_ble_peripheral/universal_ble_peripheral_mapper.dart @@ -0,0 +1,45 @@ +import 'package:universal_ble/src/universal_ble_pigeon/universal_ble.g.dart' + as pigeon; +import 'package:universal_ble/universal_ble.dart'; + +class UniversalBlePeripheralMapper { + static pigeon.PeripheralService toPigeonService( + BleService service, { + required bool primary, + }) { + return pigeon.PeripheralService( + uuid: BleUuidParser.string(service.uuid), + primary: primary, + characteristics: service.characteristics + .map( + (c) => pigeon.PeripheralCharacteristic( + uuid: BleUuidParser.string(c.uuid), + properties: c.properties.map((e) => e.index).toList(), + // Attribute permissions are peripheral-only and optional. + permissions: const [], + descriptors: c.descriptors + .map( + (d) => pigeon.PeripheralDescriptor( + uuid: BleUuidParser.string(d.uuid), + value: d.value, + permissions: const [], + ), + ) + .toList(), + value: null, + ), + ) + .toList(), + ); + } + + static pigeon.PeripheralManufacturerData? toPigeonManufacturerData( + ManufacturerData? manufacturerData, + ) { + if (manufacturerData == null) return null; + return pigeon.PeripheralManufacturerData( + manufacturerId: manufacturerData.companyId, + data: manufacturerData.payload, + ); + } +} diff --git a/lib/src/universal_ble_peripheral/universal_ble_peripheral_pigeon.dart b/lib/src/universal_ble_peripheral/universal_ble_peripheral_pigeon.dart new file mode 100644 index 00000000..92e4a6ae --- /dev/null +++ b/lib/src/universal_ble_peripheral/universal_ble_peripheral_pigeon.dart @@ -0,0 +1,361 @@ +import 'dart:async'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart'; +import 'package:universal_ble/src/universal_ble_pigeon/universal_ble.g.dart' + as pigeon; +import 'package:universal_ble/src/universal_ble_peripheral/universal_ble_peripheral_mapper.dart'; +import 'package:universal_ble/universal_ble.dart'; + +class UniversalBlePeripheralPigeon extends UniversalBlePeripheralPlatform + implements pigeon.UniversalBlePeripheralCallback { + UniversalBlePeripheralPigeon._() { + pigeon.UniversalBlePeripheralCallback.setUp(this); + } + + static UniversalBlePeripheralPigeon? _instance; + static UniversalBlePeripheralPigeon get instance => + _instance ??= UniversalBlePeripheralPigeon._(); + + final pigeon.UniversalBlePeripheralChannel _channel = + pigeon.UniversalBlePeripheralChannel(); + final StreamController<({String serviceId, String? error})> + _serviceResultController = + StreamController<({String serviceId, String? error})>.broadcast(); + final StreamController _eventController = + StreamController.broadcast(); + bool _disposed = false; + OnPeripheralReadRequest? _readRequestHandler; + OnPeripheralWriteRequest? _writeRequestHandler; + OnPeripheralDescriptorReadRequest? _descriptorReadRequestHandler; + OnPeripheralDescriptorWriteRequest? _descriptorWriteRequestHandler; + + @override + Stream get eventStream => + _eventController.stream; + + @override + void setRequestHandlers(PeripheralRequestHandlers handlers) { + _readRequestHandler = handlers.onReadRequest; + _writeRequestHandler = handlers.onWriteRequest; + _descriptorReadRequestHandler = handlers.onDescriptorReadRequest; + _descriptorWriteRequestHandler = handlers.onDescriptorWriteRequest; + } + + @override + Future getReadinessState() async { + final state = await _channel.getReadinessState(); + return switch (state) { + pigeon.PeripheralReadinessState.ready => + UniversalBlePeripheralReadinessState.ready, + pigeon.PeripheralReadinessState.bluetoothOff => + UniversalBlePeripheralReadinessState.bluetoothOff, + pigeon.PeripheralReadinessState.unauthorized => + UniversalBlePeripheralReadinessState.unauthorized, + pigeon.PeripheralReadinessState.unsupported => + UniversalBlePeripheralReadinessState.unsupported, + pigeon.PeripheralReadinessState.unknown => + UniversalBlePeripheralReadinessState.unknown, + }; + } + + @override + Future getAdvertisingState() async { + final state = await _channel.getAdvertisingState(); + return switch (state) { + pigeon.PeripheralAdvertisingState.idle => + UniversalBlePeripheralAdvertisingState.idle, + pigeon.PeripheralAdvertisingState.starting => + UniversalBlePeripheralAdvertisingState.starting, + pigeon.PeripheralAdvertisingState.advertising => + UniversalBlePeripheralAdvertisingState.advertising, + pigeon.PeripheralAdvertisingState.stopping => + UniversalBlePeripheralAdvertisingState.stopping, + pigeon.PeripheralAdvertisingState.error => + UniversalBlePeripheralAdvertisingState.error, + }; + } + + @override + Future getStaticCapabilities() async { + final readiness = await getReadinessState(); + final platform = defaultTargetPlatform; + final supported = readiness != UniversalBlePeripheralReadinessState.unsupported; + + final supportsManufacturerDataInScanResponse = + platform == TargetPlatform.android; + final supportsAdvertisingTimeout = platform == TargetPlatform.android; + + return UniversalBlePeripheralCapabilities( + supportsPeripheralMode: supported, + supportsManufacturerDataInAdvertisement: supported, + supportsManufacturerDataInScanResponse: + supported && supportsManufacturerDataInScanResponse, + supportsServiceDataInAdvertisement: false, + supportsServiceDataInScanResponse: false, + supportsTargetedCharacteristicUpdate: supported, + supportsAdvertisingTimeout: supported && supportsAdvertisingTimeout, + ); + } + + @override + Future addService( + BleService service, { + bool primary = true, + Duration? timeout, + }) async { + final String serviceId = BleUuidParser.string(service.uuid); + final Completer completer = Completer(); + _serviceResultController.stream + .where((e) => BleUuidParser.compareStrings(e.serviceId, serviceId)) + .first + .timeout( + timeout ?? const Duration(seconds: 5), + onTimeout: () => + (serviceId: serviceId, error: 'Service add timed out'), + ) + .then((e) { + if (completer.isCompleted) return; + if (e.error != null) { + completer.completeError( + PlatformException(code: 'service-add-failed', message: e.error), + ); + } else { + completer.complete(); + } + }); + + await _channel.addService( + UniversalBlePeripheralMapper.toPigeonService(service, primary: primary), + ); + await completer.future; + } + + @override + Future removeService(PeripheralServiceId serviceId) => + _channel.removeService(BleUuidParser.string(serviceId.value)); + + @override + Future clearServices() => _channel.clearServices(); + + @override + Future> getServices() async => + (await _channel.getServices()).map(PeripheralServiceId.new).toList(); + + @override + Future startAdvertising({ + required List services, + String? localName, + int? timeout, + ManufacturerData? manufacturerData, + bool addManufacturerDataInScanResponse = false, + }) { + return _channel.startAdvertising( + services.map((e) => BleUuidParser.string(e.value)).toList(), + localName, + timeout, + UniversalBlePeripheralMapper.toPigeonManufacturerData(manufacturerData), + addManufacturerDataInScanResponse, + ); + } + + @override + Future stopAdvertising() => _channel.stopAdvertising(); + + @override + Future updateCharacteristicValue({ + required PeripheralCharacteristicId characteristicId, + required Uint8List value, + PeripheralUpdateTarget target = const PeripheralUpdateAllSubscribed(), + }) { + final String? deviceId = switch (target) { + PeripheralUpdateAllSubscribed() => null, + PeripheralUpdateSingleDevice(deviceId: final id) => id, + }; + return _channel.updateCharacteristic( + BleUuidParser.string(characteristicId.value), + value, + deviceId, + ); + } + + @override + Future> getSubscribedClients(PeripheralCharacteristicId characteristicId) => + _channel.getSubscribedClients(characteristicId.value); + + @override + Future getMaximumNotifyLength(String deviceId) => + _channel.getMaximumNotifyLength(deviceId); + + @override + void onAdvertisingStateChange( + pigeon.PeripheralAdvertisingState state, + String? error, + ) { + final mapped = switch (state) { + pigeon.PeripheralAdvertisingState.idle => + UniversalBlePeripheralAdvertisingState.idle, + pigeon.PeripheralAdvertisingState.starting => + UniversalBlePeripheralAdvertisingState.starting, + pigeon.PeripheralAdvertisingState.advertising => + UniversalBlePeripheralAdvertisingState.advertising, + pigeon.PeripheralAdvertisingState.stopping => + UniversalBlePeripheralAdvertisingState.stopping, + pigeon.PeripheralAdvertisingState.error => + UniversalBlePeripheralAdvertisingState.error, + }; + if (!_eventController.isClosed) { + _eventController.add( + UniversalBlePeripheralAdvertisingStateChanged(mapped, error), + ); + } + } + + @override + void onCharacteristicSubscriptionChange( + String deviceId, + String characteristicId, + bool isSubscribed, + String? name, + ) { + if (!_eventController.isClosed) { + _eventController.add( + UniversalBlePeripheralCharacteristicSubscriptionChanged( + deviceId: deviceId, + characteristicId: characteristicId, + isSubscribed: isSubscribed, + name: name, + ), + ); + } + } + + @override + void onConnectionStateChange(String deviceId, bool connected) { + if (!_eventController.isClosed) { + _eventController.add( + UniversalBlePeripheralConnectionStateChanged(deviceId, connected), + ); + } + } + + @override + void onMtuChange(String deviceId, int mtu) { + if (!_eventController.isClosed) { + _eventController.add(UniversalBlePeripheralMtuChanged(deviceId, mtu)); + } + } + + @override + pigeon.PeripheralReadRequestResult? onReadRequest( + String deviceId, + String characteristicId, + int offset, + Uint8List? value, + ) { + final result = _readRequestHandler?.call( + deviceId, + PeripheralCharacteristicId(characteristicId), + offset, + value, + ); + if (result == null) return null; + return pigeon.PeripheralReadRequestResult( + value: result.value, + offset: result.offset, + status: result.status, + ); + } + + @override + void onServiceAdded(String serviceId, String? error) { + if (!_eventController.isClosed) { + _eventController.add(UniversalBlePeripheralServiceAdded(serviceId, error)); + } + if (!_serviceResultController.isClosed) { + _serviceResultController.add((serviceId: serviceId, error: error)); + } + } + + @override + pigeon.PeripheralWriteRequestResult? onWriteRequest( + String deviceId, + String characteristicId, + int offset, + Uint8List? value, + ) { + final result = _writeRequestHandler?.call( + deviceId, + PeripheralCharacteristicId(characteristicId), + offset, + value, + ); + if (result == null) return null; + return pigeon.PeripheralWriteRequestResult( + value: result.value, + offset: result.offset, + status: result.status, + ); + } + + @override + pigeon.PeripheralReadRequestResult? onDescriptorReadRequest( + String deviceId, + String characteristicId, + String descriptorId, + int offset, + Uint8List? value, + ) { + final result = _descriptorReadRequestHandler?.call( + deviceId, + PeripheralCharacteristicId(characteristicId), + PeripheralDescriptorId(descriptorId), + offset, + value, + ); + if (result == null) return null; + return pigeon.PeripheralReadRequestResult( + value: result.value, + offset: result.offset, + status: result.status, + ); + } + + @override + pigeon.PeripheralWriteRequestResult? onDescriptorWriteRequest( + String deviceId, + String characteristicId, + String descriptorId, + int offset, + Uint8List? value, + ) { + final result = _descriptorWriteRequestHandler?.call( + deviceId, + PeripheralCharacteristicId(characteristicId), + PeripheralDescriptorId(descriptorId), + offset, + value, + ); + if (result == null) return null; + return pigeon.PeripheralWriteRequestResult( + value: result.value, + offset: result.offset, + status: result.status, + ); + } + + @override + void dispose() { + if (_disposed) return; + _disposed = true; + pigeon.UniversalBlePeripheralCallback.setUp(null); + if (!_serviceResultController.isClosed) { + _serviceResultController.close(); + } + if (!_eventController.isClosed) { + _eventController.close(); + } + if (identical(_instance, this)) { + _instance = null; + } + } +} diff --git a/lib/src/universal_ble_peripheral/universal_ble_peripheral_platform_interface.dart b/lib/src/universal_ble_peripheral/universal_ble_peripheral_platform_interface.dart new file mode 100644 index 00000000..9be35a8a --- /dev/null +++ b/lib/src/universal_ble_peripheral/universal_ble_peripheral_platform_interface.dart @@ -0,0 +1,286 @@ +import 'dart:typed_data'; + +import 'package:universal_ble/universal_ble.dart'; + +typedef OnPeripheralReadRequest = BleReadRequestResult? Function( + String deviceId, + PeripheralCharacteristicId characteristicId, + int offset, + Uint8List? value, +); +typedef OnPeripheralWriteRequest = BleWriteRequestResult? Function( + String deviceId, + PeripheralCharacteristicId characteristicId, + int offset, + Uint8List? value, +); +typedef OnPeripheralDescriptorReadRequest = BleReadRequestResult? Function( + String deviceId, + PeripheralCharacteristicId characteristicId, + PeripheralDescriptorId descriptorId, + int offset, + Uint8List? value, +); +typedef OnPeripheralDescriptorWriteRequest = BleWriteRequestResult? Function( + String deviceId, + PeripheralCharacteristicId characteristicId, + PeripheralDescriptorId descriptorId, + int offset, + Uint8List? value, +); + +enum UniversalBlePeripheralReadinessState { + unknown, + ready, + bluetoothOff, + unauthorized, + unsupported, +} + +enum UniversalBlePeripheralAdvertisingState { + idle, + starting, + advertising, + stopping, + error, +} + +sealed class UniversalBlePeripheralEvent {} + +class UniversalBlePeripheralAdvertisingStateChanged + extends UniversalBlePeripheralEvent { + final UniversalBlePeripheralAdvertisingState state; + final String? error; + UniversalBlePeripheralAdvertisingStateChanged(this.state, this.error); +} + +class UniversalBlePeripheralCharacteristicSubscriptionChanged + extends UniversalBlePeripheralEvent { + final String deviceId; + final String characteristicId; + final bool isSubscribed; + final String? name; + UniversalBlePeripheralCharacteristicSubscriptionChanged({ + required this.deviceId, + required this.characteristicId, + required this.isSubscribed, + required this.name, + }); +} + +class UniversalBlePeripheralConnectionStateChanged + extends UniversalBlePeripheralEvent { + final String deviceId; + final bool connected; + UniversalBlePeripheralConnectionStateChanged(this.deviceId, this.connected); +} + +class UniversalBlePeripheralServiceAdded extends UniversalBlePeripheralEvent { + final String serviceId; + final String? error; + UniversalBlePeripheralServiceAdded(this.serviceId, this.error); +} + +class UniversalBlePeripheralMtuChanged extends UniversalBlePeripheralEvent { + final String deviceId; + final int mtu; + UniversalBlePeripheralMtuChanged(this.deviceId, this.mtu); +} + +class UniversalBlePeripheralCapabilities { + final bool supportsPeripheralMode; + final bool supportsManufacturerDataInAdvertisement; + final bool supportsManufacturerDataInScanResponse; + final bool supportsServiceDataInAdvertisement; + final bool supportsServiceDataInScanResponse; + final bool supportsTargetedCharacteristicUpdate; + final bool supportsAdvertisingTimeout; + + const UniversalBlePeripheralCapabilities({ + required this.supportsPeripheralMode, + required this.supportsManufacturerDataInAdvertisement, + required this.supportsManufacturerDataInScanResponse, + required this.supportsServiceDataInAdvertisement, + required this.supportsServiceDataInScanResponse, + required this.supportsTargetedCharacteristicUpdate, + required this.supportsAdvertisingTimeout, + }); +} + +class PeripheralServiceId { + final String value; + const PeripheralServiceId(this.value); +} + +class PeripheralCharacteristicId { + final String value; + const PeripheralCharacteristicId(this.value); +} + +class PeripheralDescriptorId { + final String value; + const PeripheralDescriptorId(this.value); +} + +class PeripheralRequestHandlers { + final OnPeripheralReadRequest? onReadRequest; + final OnPeripheralWriteRequest? onWriteRequest; + final OnPeripheralDescriptorReadRequest? onDescriptorReadRequest; + final OnPeripheralDescriptorWriteRequest? onDescriptorWriteRequest; + + const PeripheralRequestHandlers({ + this.onReadRequest, + this.onWriteRequest, + this.onDescriptorReadRequest, + this.onDescriptorWriteRequest, + }); +} + +sealed class PeripheralUpdateTarget { + const PeripheralUpdateTarget(); +} + +class PeripheralUpdateAllSubscribed extends PeripheralUpdateTarget { + const PeripheralUpdateAllSubscribed(); +} + +class PeripheralUpdateSingleDevice extends PeripheralUpdateTarget { + final String deviceId; + const PeripheralUpdateSingleDevice(this.deviceId); +} + +abstract class UniversalBlePeripheralPlatform { + Stream get eventStream; + + void setRequestHandlers(PeripheralRequestHandlers handlers); + + Future getReadinessState(); + Future getAdvertisingState(); + Future getStaticCapabilities(); + + Future addService( + BleService service, { + bool primary = true, + Duration? timeout, + }); + + Future removeService(PeripheralServiceId serviceId); + Future clearServices(); + Future> getServices(); + Future startAdvertising({ + required List services, + String? localName, + int? timeout, + ManufacturerData? manufacturerData, + bool addManufacturerDataInScanResponse = false, + }); + Future stopAdvertising(); + Future updateCharacteristicValue({ + required PeripheralCharacteristicId characteristicId, + required Uint8List value, + PeripheralUpdateTarget target = const PeripheralUpdateAllSubscribed(), + }); + + /// Returns GATT client device ids currently subscribed to [characteristicId]. + Future> getSubscribedClients(PeripheralCharacteristicId characteristicId); + Future getMaximumNotifyLength(String deviceId); + + /// Called when this platform implementation is being replaced. + /// + /// Default is no-op so existing custom implementations remain compatible. + void dispose() {} +} + +class UniversalBlePeripheralUnsupported extends UniversalBlePeripheralPlatform { + UnsupportedError _notSupported() => + UnsupportedError('BLE peripheral mode is not supported on this platform'); + + @override + Stream get eventStream => + const Stream.empty(); + + @override + void setRequestHandlers(PeripheralRequestHandlers handlers) {} + + @override + Future addService( + BleService service, { + bool primary = true, + Duration? timeout, + }) async { + throw _notSupported(); + } + + @override + Future clearServices() async { + throw _notSupported(); + } + + @override + Future> getServices() async { + throw _notSupported(); + } + + @override + Future getAdvertisingState() async { + throw _notSupported(); + } + + @override + Future getStaticCapabilities() async { + return const UniversalBlePeripheralCapabilities( + supportsPeripheralMode: false, + supportsManufacturerDataInAdvertisement: false, + supportsManufacturerDataInScanResponse: false, + supportsServiceDataInAdvertisement: false, + supportsServiceDataInScanResponse: false, + supportsTargetedCharacteristicUpdate: false, + supportsAdvertisingTimeout: false, + ); + } + + @override + Future getReadinessState() async { + return UniversalBlePeripheralReadinessState.unsupported; + } + + @override + Future removeService(PeripheralServiceId serviceId) async { + throw _notSupported(); + } + + @override + Future startAdvertising({ + required List services, + String? localName, + int? timeout, + ManufacturerData? manufacturerData, + bool addManufacturerDataInScanResponse = false, + }) async { + throw _notSupported(); + } + + @override + Future stopAdvertising() async { + throw _notSupported(); + } + + @override + Future updateCharacteristicValue({ + required PeripheralCharacteristicId characteristicId, + required Uint8List value, + PeripheralUpdateTarget target = const PeripheralUpdateAllSubscribed(), + }) async { + throw _notSupported(); + } + + @override + Future> getSubscribedClients(PeripheralCharacteristicId characteristicId) async { + throw _notSupported(); + } + + @override + Future getMaximumNotifyLength(String deviceId) async { + return null; + } +} diff --git a/lib/src/universal_ble_pigeon/universal_ble.g.dart b/lib/src/universal_ble_pigeon/universal_ble.g.dart index 9e023e03..33864118 100644 --- a/lib/src/universal_ble_pigeon/universal_ble.g.dart +++ b/lib/src/universal_ble_pigeon/universal_ble.g.dart @@ -1,25 +1,41 @@ -// Autogenerated from Pigeon (v26.1.4), do not edit directly. +// Autogenerated from Pigeon (v26.3.3), do not edit directly. // See also: https://pub.dev/packages/pigeon -// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, omit_obvious_local_variable_types, unused_shown_name, unnecessary_import, no_leading_underscores_for_local_identifiers +// ignore_for_file: unused_import, unused_shown_name +// ignore_for_file: type=lint import 'dart:async'; -import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List; +import 'dart:typed_data' show Float64List, Int32List, Int64List; -import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer; import 'package:flutter/services.dart'; +import 'package:meta/meta.dart' show immutable, protected, visibleForTesting; -PlatformException _createConnectionError(String channelName) { - return PlatformException( - code: 'channel-error', - message: 'Unable to establish connection on channel: "$channelName".', - ); +Object? _extractReplyValueOrThrow( + List? replyList, + String channelName, { + required bool isNullValid, +}) { + if (replyList == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel: "$channelName".', + ); + } else if (replyList.length > 1) { + throw PlatformException( + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], + ); + } else if (!isNullValid && (replyList.isNotEmpty && replyList[0] == null)) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } + return replyList.firstOrNull; } -List wrapResponse({ - Object? result, - PlatformException? error, - bool empty = false, -}) { +List wrapResponse( + {Object? result, PlatformException? error, bool empty = false}) { if (empty) { return []; } @@ -30,27 +46,99 @@ List wrapResponse({ } bool _deepEquals(Object? a, Object? b) { + if (identical(a, b)) { + return true; + } + if (a is double && b is double) { + if (a.isNaN && b.isNaN) { + return true; + } + return a == b; + } if (a is List && b is List) { return a.length == b.length && - a.indexed.every( - ((int, dynamic) item) => _deepEquals(item.$2, b[item.$1]), - ); + a.indexed + .every(((int, dynamic) item) => _deepEquals(item.$2, b[item.$1])); } if (a is Map && b is Map) { - return a.length == b.length && - a.entries.every( - (MapEntry entry) => - (b as Map).containsKey(entry.key) && - _deepEquals(entry.value, b[entry.key]), - ); + if (a.length != b.length) { + return false; + } + for (final MapEntry entryA in a.entries) { + bool found = false; + for (final MapEntry entryB in b.entries) { + if (_deepEquals(entryA.key, entryB.key)) { + if (_deepEquals(entryA.value, entryB.value)) { + found = true; + break; + } else { + return false; + } + } + } + if (!found) { + return false; + } + } + return true; } return a == b; } -enum UniversalBleLogLevel { none, error, warning, info, debug, verbose } +int _deepHash(Object? value) { + if (value is List) { + return Object.hashAll(value.map(_deepHash)); + } + if (value is Map) { + int result = 0; + for (final MapEntry entry in value.entries) { + result += (_deepHash(entry.key) * 31) ^ _deepHash(entry.value); + } + return result; + } + if (value is double && value.isNaN) { + // Normalize NaN to a consistent hash. + return 0x7FF8000000000000.hashCode; + } + if (value is double && value == 0.0) { + // Normalize -0.0 to 0.0 so they have the same hash code. + return 0.0.hashCode; + } + return value.hashCode; +} + +enum UniversalBleLogLevel { + none, + error, + warning, + info, + debug, + verbose, +} /// Scan config -enum AndroidScanMode { balanced, lowLatency, lowPower, opportunistic } +enum AndroidScanMode { + balanced, + lowLatency, + lowPower, + opportunistic, +} + +enum PeripheralReadinessState { + unknown, + ready, + bluetoothOff, + unauthorized, + unsupported, +} + +enum PeripheralAdvertisingState { + idle, + starting, + advertising, + stopping, + error, +} /// Unified error codes for all platforms enum UniversalBleErrorCode { @@ -169,10 +257,10 @@ class UniversalBleScanResult { name: result[1] as String?, isPaired: result[2] as bool?, rssi: result[3] as int?, - manufacturerDataList: (result[4] as List?) - ?.cast(), - serviceData: (result[5] as Map?) - ?.cast(), + manufacturerDataList: + (result[4] as List?)?.cast(), + serviceData: + (result[5] as Map?)?.cast(), services: (result[6] as List?)?.cast(), timestamp: result[7] as int?, ); @@ -187,23 +275,36 @@ class UniversalBleScanResult { if (identical(this, other)) { return true; } - return _deepEquals(encode(), other.encode()); + return _deepEquals(deviceId, other.deviceId) && + _deepEquals(name, other.name) && + _deepEquals(isPaired, other.isPaired) && + _deepEquals(rssi, other.rssi) && + _deepEquals(manufacturerDataList, other.manufacturerDataList) && + _deepEquals(serviceData, other.serviceData) && + _deepEquals(services, other.services) && + _deepEquals(timestamp, other.timestamp); } @override // ignore: avoid_equals_and_hash_code_on_mutable_classes - int get hashCode => Object.hashAll(_toList()); + int get hashCode => _deepHash([runtimeType, ..._toList()]); } class UniversalBleService { - UniversalBleService({required this.uuid, this.characteristics}); + UniversalBleService({ + required this.uuid, + this.characteristics, + }); String uuid; List? characteristics; List _toList() { - return [uuid, characteristics]; + return [ + uuid, + characteristics, + ]; } Object encode() { @@ -214,8 +315,8 @@ class UniversalBleService { result as List; return UniversalBleService( uuid: result[0]! as String, - characteristics: (result[1] as List?) - ?.cast(), + characteristics: + (result[1] as List?)?.cast(), ); } @@ -228,12 +329,13 @@ class UniversalBleService { if (identical(this, other)) { return true; } - return _deepEquals(encode(), other.encode()); + return _deepEquals(uuid, other.uuid) && + _deepEquals(characteristics, other.characteristics); } @override // ignore: avoid_equals_and_hash_code_on_mutable_classes - int get hashCode => Object.hashAll(_toList()); + int get hashCode => _deepHash([runtimeType, ..._toList()]); } class UniversalBleCharacteristic { @@ -250,7 +352,11 @@ class UniversalBleCharacteristic { List descriptors; List _toList() { - return [uuid, properties, descriptors]; + return [ + uuid, + properties, + descriptors, + ]; } Object encode() { @@ -261,9 +367,8 @@ class UniversalBleCharacteristic { result as List; return UniversalBleCharacteristic( uuid: result[0]! as String, - properties: (result[1] as List?)!.cast(), - descriptors: (result[2] as List?)! - .cast(), + properties: (result[1]! as List).cast(), + descriptors: (result[2]! as List).cast(), ); } @@ -277,21 +382,27 @@ class UniversalBleCharacteristic { if (identical(this, other)) { return true; } - return _deepEquals(encode(), other.encode()); + return _deepEquals(uuid, other.uuid) && + _deepEquals(properties, other.properties) && + _deepEquals(descriptors, other.descriptors); } @override // ignore: avoid_equals_and_hash_code_on_mutable_classes - int get hashCode => Object.hashAll(_toList()); + int get hashCode => _deepHash([runtimeType, ..._toList()]); } class UniversalBleDescriptor { - UniversalBleDescriptor({required this.uuid}); + UniversalBleDescriptor({ + required this.uuid, + }); String uuid; List _toList() { - return [uuid]; + return [ + uuid, + ]; } Object encode() { @@ -300,7 +411,9 @@ class UniversalBleDescriptor { static UniversalBleDescriptor decode(Object result) { result as List; - return UniversalBleDescriptor(uuid: result[0]! as String); + return UniversalBleDescriptor( + uuid: result[0]! as String, + ); } @override @@ -312,12 +425,12 @@ class UniversalBleDescriptor { if (identical(this, other)) { return true; } - return _deepEquals(encode(), other.encode()); + return _deepEquals(uuid, other.uuid); } @override // ignore: avoid_equals_and_hash_code_on_mutable_classes - int get hashCode => Object.hashAll(_toList()); + int get hashCode => _deepHash([runtimeType, ..._toList()]); } /// Android options to scan devices @@ -340,7 +453,11 @@ class AndroidOptions { int? reportDelayMillis; List _toList() { - return [requestLocationPermission, scanMode, reportDelayMillis]; + return [ + requestLocationPermission, + scanMode, + reportDelayMillis, + ]; } Object encode() { @@ -365,21 +482,28 @@ class AndroidOptions { if (identical(this, other)) { return true; } - return _deepEquals(encode(), other.encode()); + return _deepEquals( + requestLocationPermission, other.requestLocationPermission) && + _deepEquals(scanMode, other.scanMode) && + _deepEquals(reportDelayMillis, other.reportDelayMillis); } @override // ignore: avoid_equals_and_hash_code_on_mutable_classes - int get hashCode => Object.hashAll(_toList()); + int get hashCode => _deepHash([runtimeType, ..._toList()]); } class UniversalScanConfig { - UniversalScanConfig({this.android}); + UniversalScanConfig({ + this.android, + }); AndroidOptions? android; List _toList() { - return [android]; + return [ + android, + ]; } Object encode() { @@ -388,7 +512,9 @@ class UniversalScanConfig { static UniversalScanConfig decode(Object result) { result as List; - return UniversalScanConfig(android: result[0] as AndroidOptions?); + return UniversalScanConfig( + android: result[0] as AndroidOptions?, + ); } @override @@ -400,12 +526,12 @@ class UniversalScanConfig { if (identical(this, other)) { return true; } - return _deepEquals(encode(), other.encode()); + return _deepEquals(android, other.android); } @override // ignore: avoid_equals_and_hash_code_on_mutable_classes - int get hashCode => Object.hashAll(_toList()); + int get hashCode => _deepHash([runtimeType, ..._toList()]); } /// Scan Filters @@ -423,7 +549,11 @@ class UniversalScanFilter { List withManufacturerData; List _toList() { - return [withServices, withNamePrefix, withManufacturerData]; + return [ + withServices, + withNamePrefix, + withManufacturerData, + ]; } Object encode() { @@ -433,10 +563,10 @@ class UniversalScanFilter { static UniversalScanFilter decode(Object result) { result as List; return UniversalScanFilter( - withServices: (result[0] as List?)!.cast(), - withNamePrefix: (result[1] as List?)!.cast(), - withManufacturerData: (result[2] as List?)! - .cast(), + withServices: (result[0]! as List).cast(), + withNamePrefix: (result[1]! as List).cast(), + withManufacturerData: + (result[2]! as List).cast(), ); } @@ -449,12 +579,14 @@ class UniversalScanFilter { if (identical(this, other)) { return true; } - return _deepEquals(encode(), other.encode()); + return _deepEquals(withServices, other.withServices) && + _deepEquals(withNamePrefix, other.withNamePrefix) && + _deepEquals(withManufacturerData, other.withManufacturerData); } @override // ignore: avoid_equals_and_hash_code_on_mutable_classes - int get hashCode => Object.hashAll(_toList()); + int get hashCode => _deepHash([runtimeType, ..._toList()]); } class UniversalManufacturerDataFilter { @@ -471,7 +603,11 @@ class UniversalManufacturerDataFilter { Uint8List? mask; List _toList() { - return [companyIdentifier, data, mask]; + return [ + companyIdentifier, + data, + mask, + ]; } Object encode() { @@ -497,12 +633,14 @@ class UniversalManufacturerDataFilter { if (identical(this, other)) { return true; } - return _deepEquals(encode(), other.encode()); + return _deepEquals(companyIdentifier, other.companyIdentifier) && + _deepEquals(data, other.data) && + _deepEquals(mask, other.mask); } @override // ignore: avoid_equals_and_hash_code_on_mutable_classes - int get hashCode => Object.hashAll(_toList()); + int get hashCode => _deepHash([runtimeType, ..._toList()]); } class UniversalManufacturerData { @@ -516,7 +654,10 @@ class UniversalManufacturerData { Uint8List data; List _toList() { - return [companyIdentifier, data]; + return [ + companyIdentifier, + data, + ]; } Object encode() { @@ -541,286 +682,1107 @@ class UniversalManufacturerData { if (identical(this, other)) { return true; } - return _deepEquals(encode(), other.encode()); + return _deepEquals(companyIdentifier, other.companyIdentifier) && + _deepEquals(data, other.data); } @override // ignore: avoid_equals_and_hash_code_on_mutable_classes - int get hashCode => Object.hashAll(_toList()); + int get hashCode => _deepHash([runtimeType, ..._toList()]); } -class _PigeonCodec extends StandardMessageCodec { - const _PigeonCodec(); - @override - void writeValue(WriteBuffer buffer, Object? value) { - if (value is int) { - buffer.putUint8(4); - buffer.putInt64(value); - } else if (value is UniversalBleLogLevel) { - buffer.putUint8(129); - writeValue(buffer, value.index); - } else if (value is AndroidScanMode) { - buffer.putUint8(130); - writeValue(buffer, value.index); - } else if (value is UniversalBleErrorCode) { - buffer.putUint8(131); - writeValue(buffer, value.index); - } else if (value is UniversalBleScanResult) { - buffer.putUint8(132); - writeValue(buffer, value.encode()); - } else if (value is UniversalBleService) { - buffer.putUint8(133); - writeValue(buffer, value.encode()); - } else if (value is UniversalBleCharacteristic) { - buffer.putUint8(134); - writeValue(buffer, value.encode()); - } else if (value is UniversalBleDescriptor) { - buffer.putUint8(135); - writeValue(buffer, value.encode()); - } else if (value is AndroidOptions) { - buffer.putUint8(136); - writeValue(buffer, value.encode()); - } else if (value is UniversalScanConfig) { - buffer.putUint8(137); - writeValue(buffer, value.encode()); - } else if (value is UniversalScanFilter) { - buffer.putUint8(138); - writeValue(buffer, value.encode()); - } else if (value is UniversalManufacturerDataFilter) { - buffer.putUint8(139); - writeValue(buffer, value.encode()); - } else if (value is UniversalManufacturerData) { - buffer.putUint8(140); - writeValue(buffer, value.encode()); - } else { - super.writeValue(buffer, value); - } +class PeripheralService { + PeripheralService({ + required this.uuid, + required this.primary, + required this.characteristics, + }); + + String uuid; + + bool primary; + + List characteristics; + + List _toList() { + return [ + uuid, + primary, + characteristics, + ]; + } + + Object encode() { + return _toList(); + } + + static PeripheralService decode(Object result) { + result as List; + return PeripheralService( + uuid: result[0]! as String, + primary: result[1]! as bool, + characteristics: + (result[2]! as List).cast(), + ); } @override - Object? readValueOfType(int type, ReadBuffer buffer) { - switch (type) { - case 129: - final value = readValue(buffer) as int?; - return value == null ? null : UniversalBleLogLevel.values[value]; - case 130: - final value = readValue(buffer) as int?; - return value == null ? null : AndroidScanMode.values[value]; - case 131: - final value = readValue(buffer) as int?; - return value == null ? null : UniversalBleErrorCode.values[value]; - case 132: - return UniversalBleScanResult.decode(readValue(buffer)!); - case 133: - return UniversalBleService.decode(readValue(buffer)!); - case 134: - return UniversalBleCharacteristic.decode(readValue(buffer)!); - case 135: - return UniversalBleDescriptor.decode(readValue(buffer)!); - case 136: - return AndroidOptions.decode(readValue(buffer)!); - case 137: - return UniversalScanConfig.decode(readValue(buffer)!); - case 138: - return UniversalScanFilter.decode(readValue(buffer)!); - case 139: - return UniversalManufacturerDataFilter.decode(readValue(buffer)!); - case 140: - return UniversalManufacturerData.decode(readValue(buffer)!); - default: - return super.readValueOfType(type, buffer); + // ignore: avoid_equals_and_hash_code_on_mutable_classes + bool operator ==(Object other) { + if (other is! PeripheralService || other.runtimeType != runtimeType) { + return false; + } + if (identical(this, other)) { + return true; } + return _deepEquals(uuid, other.uuid) && + _deepEquals(primary, other.primary) && + _deepEquals(characteristics, other.characteristics); } + + @override + // ignore: avoid_equals_and_hash_code_on_mutable_classes + int get hashCode => _deepHash([runtimeType, ..._toList()]); } -/// Flutter -> Native -class UniversalBlePlatformChannel { - /// Constructor for [UniversalBlePlatformChannel]. The [binaryMessenger] named argument is - /// available for dependency injection. If it is left null, the default - /// BinaryMessenger will be used which routes to the host platform. - UniversalBlePlatformChannel({ - BinaryMessenger? binaryMessenger, - String messageChannelSuffix = '', - }) : pigeonVar_binaryMessenger = binaryMessenger, - pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty - ? '.$messageChannelSuffix' - : ''; - final BinaryMessenger? pigeonVar_binaryMessenger; +class PeripheralCharacteristic { + PeripheralCharacteristic({ + required this.uuid, + required this.properties, + required this.permissions, + this.descriptors, + this.value, + }); - static const MessageCodec pigeonChannelCodec = _PigeonCodec(); + String uuid; - final String pigeonVar_messageChannelSuffix; + List properties; - Future getBluetoothAvailabilityState() async { - final pigeonVar_channelName = - 'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.getBluetoothAvailabilityState$pigeonVar_messageChannelSuffix'; - final pigeonVar_channel = BasicMessageChannel( - pigeonVar_channelName, - pigeonChannelCodec, - binaryMessenger: pigeonVar_binaryMessenger, - ); - final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); - final pigeonVar_replyList = await pigeonVar_sendFuture as List?; - if (pigeonVar_replyList == null) { - throw _createConnectionError(pigeonVar_channelName); - } else if (pigeonVar_replyList.length > 1) { - throw PlatformException( - code: pigeonVar_replyList[0]! as String, - message: pigeonVar_replyList[1] as String?, - details: pigeonVar_replyList[2], - ); - } else if (pigeonVar_replyList[0] == null) { - throw PlatformException( - code: 'null-error', - message: 'Host platform returned null value for non-null return value.', - ); - } else { - return (pigeonVar_replyList[0] as int?)!; - } - } + List permissions; - Future hasPermissions(bool withAndroidFineLocation) async { - final pigeonVar_channelName = - 'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.hasPermissions$pigeonVar_messageChannelSuffix'; - final pigeonVar_channel = BasicMessageChannel( - pigeonVar_channelName, - pigeonChannelCodec, - binaryMessenger: pigeonVar_binaryMessenger, - ); - final Future pigeonVar_sendFuture = pigeonVar_channel.send( - [withAndroidFineLocation], - ); - final pigeonVar_replyList = await pigeonVar_sendFuture as List?; - if (pigeonVar_replyList == null) { - throw _createConnectionError(pigeonVar_channelName); - } else if (pigeonVar_replyList.length > 1) { - throw PlatformException( - code: pigeonVar_replyList[0]! as String, - message: pigeonVar_replyList[1] as String?, - details: pigeonVar_replyList[2], - ); - } else if (pigeonVar_replyList[0] == null) { - throw PlatformException( - code: 'null-error', - message: 'Host platform returned null value for non-null return value.', - ); - } else { - return (pigeonVar_replyList[0] as bool?)!; - } + List? descriptors; + + Uint8List? value; + + List _toList() { + return [ + uuid, + properties, + permissions, + descriptors, + value, + ]; } - Future requestPermissions(bool withAndroidFineLocation) async { - final pigeonVar_channelName = - 'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.requestPermissions$pigeonVar_messageChannelSuffix'; - final pigeonVar_channel = BasicMessageChannel( - pigeonVar_channelName, - pigeonChannelCodec, - binaryMessenger: pigeonVar_binaryMessenger, - ); - final Future pigeonVar_sendFuture = pigeonVar_channel.send( - [withAndroidFineLocation], - ); - final pigeonVar_replyList = await pigeonVar_sendFuture as List?; - if (pigeonVar_replyList == null) { - throw _createConnectionError(pigeonVar_channelName); - } else if (pigeonVar_replyList.length > 1) { - throw PlatformException( - code: pigeonVar_replyList[0]! as String, - message: pigeonVar_replyList[1] as String?, - details: pigeonVar_replyList[2], - ); - } else { - return; - } + Object encode() { + return _toList(); } - Future enableBluetooth() async { - final pigeonVar_channelName = - 'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.enableBluetooth$pigeonVar_messageChannelSuffix'; - final pigeonVar_channel = BasicMessageChannel( - pigeonVar_channelName, - pigeonChannelCodec, - binaryMessenger: pigeonVar_binaryMessenger, + static PeripheralCharacteristic decode(Object result) { + result as List; + return PeripheralCharacteristic( + uuid: result[0]! as String, + properties: (result[1]! as List).cast(), + permissions: (result[2]! as List).cast(), + descriptors: (result[3] as List?)?.cast(), + value: result[4] as Uint8List?, ); - final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); - final pigeonVar_replyList = await pigeonVar_sendFuture as List?; - if (pigeonVar_replyList == null) { - throw _createConnectionError(pigeonVar_channelName); - } else if (pigeonVar_replyList.length > 1) { - throw PlatformException( - code: pigeonVar_replyList[0]! as String, - message: pigeonVar_replyList[1] as String?, - details: pigeonVar_replyList[2], - ); - } else if (pigeonVar_replyList[0] == null) { - throw PlatformException( - code: 'null-error', - message: 'Host platform returned null value for non-null return value.', - ); - } else { - return (pigeonVar_replyList[0] as bool?)!; - } } - Future disableBluetooth() async { - final pigeonVar_channelName = - 'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.disableBluetooth$pigeonVar_messageChannelSuffix'; - final pigeonVar_channel = BasicMessageChannel( - pigeonVar_channelName, - pigeonChannelCodec, - binaryMessenger: pigeonVar_binaryMessenger, - ); - final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); - final pigeonVar_replyList = await pigeonVar_sendFuture as List?; - if (pigeonVar_replyList == null) { - throw _createConnectionError(pigeonVar_channelName); - } else if (pigeonVar_replyList.length > 1) { - throw PlatformException( - code: pigeonVar_replyList[0]! as String, - message: pigeonVar_replyList[1] as String?, - details: pigeonVar_replyList[2], - ); - } else if (pigeonVar_replyList[0] == null) { - throw PlatformException( - code: 'null-error', - message: 'Host platform returned null value for non-null return value.', - ); - } else { - return (pigeonVar_replyList[0] as bool?)!; + @override + // ignore: avoid_equals_and_hash_code_on_mutable_classes + bool operator ==(Object other) { + if (other is! PeripheralCharacteristic || + other.runtimeType != runtimeType) { + return false; + } + if (identical(this, other)) { + return true; } + return _deepEquals(uuid, other.uuid) && + _deepEquals(properties, other.properties) && + _deepEquals(permissions, other.permissions) && + _deepEquals(descriptors, other.descriptors) && + _deepEquals(value, other.value); } - Future startScan( - UniversalScanFilter? filter, - UniversalScanConfig? config, - ) async { - final pigeonVar_channelName = - 'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.startScan$pigeonVar_messageChannelSuffix'; - final pigeonVar_channel = BasicMessageChannel( + @override + // ignore: avoid_equals_and_hash_code_on_mutable_classes + int get hashCode => _deepHash([runtimeType, ..._toList()]); +} + +class PeripheralDescriptor { + PeripheralDescriptor({ + required this.uuid, + this.value, + this.permissions, + }); + + String uuid; + + Uint8List? value; + + List? permissions; + + List _toList() { + return [ + uuid, + value, + permissions, + ]; + } + + Object encode() { + return _toList(); + } + + static PeripheralDescriptor decode(Object result) { + result as List; + return PeripheralDescriptor( + uuid: result[0]! as String, + value: result[1] as Uint8List?, + permissions: (result[2] as List?)?.cast(), + ); + } + + @override + // ignore: avoid_equals_and_hash_code_on_mutable_classes + bool operator ==(Object other) { + if (other is! PeripheralDescriptor || other.runtimeType != runtimeType) { + return false; + } + if (identical(this, other)) { + return true; + } + return _deepEquals(uuid, other.uuid) && + _deepEquals(value, other.value) && + _deepEquals(permissions, other.permissions); + } + + @override + // ignore: avoid_equals_and_hash_code_on_mutable_classes + int get hashCode => _deepHash([runtimeType, ..._toList()]); +} + +class PeripheralReadRequestResult { + PeripheralReadRequestResult({ + required this.value, + this.offset, + this.status, + }); + + Uint8List value; + + int? offset; + + int? status; + + List _toList() { + return [ + value, + offset, + status, + ]; + } + + Object encode() { + return _toList(); + } + + static PeripheralReadRequestResult decode(Object result) { + result as List; + return PeripheralReadRequestResult( + value: result[0]! as Uint8List, + offset: result[1] as int?, + status: result[2] as int?, + ); + } + + @override + // ignore: avoid_equals_and_hash_code_on_mutable_classes + bool operator ==(Object other) { + if (other is! PeripheralReadRequestResult || + other.runtimeType != runtimeType) { + return false; + } + if (identical(this, other)) { + return true; + } + return _deepEquals(value, other.value) && + _deepEquals(offset, other.offset) && + _deepEquals(status, other.status); + } + + @override + // ignore: avoid_equals_and_hash_code_on_mutable_classes + int get hashCode => _deepHash([runtimeType, ..._toList()]); +} + +class PeripheralWriteRequestResult { + PeripheralWriteRequestResult({ + this.value, + this.offset, + this.status, + }); + + Uint8List? value; + + int? offset; + + int? status; + + List _toList() { + return [ + value, + offset, + status, + ]; + } + + Object encode() { + return _toList(); + } + + static PeripheralWriteRequestResult decode(Object result) { + result as List; + return PeripheralWriteRequestResult( + value: result[0] as Uint8List?, + offset: result[1] as int?, + status: result[2] as int?, + ); + } + + @override + // ignore: avoid_equals_and_hash_code_on_mutable_classes + bool operator ==(Object other) { + if (other is! PeripheralWriteRequestResult || + other.runtimeType != runtimeType) { + return false; + } + if (identical(this, other)) { + return true; + } + return _deepEquals(value, other.value) && + _deepEquals(offset, other.offset) && + _deepEquals(status, other.status); + } + + @override + // ignore: avoid_equals_and_hash_code_on_mutable_classes + int get hashCode => _deepHash([runtimeType, ..._toList()]); +} + +class PeripheralManufacturerData { + PeripheralManufacturerData({ + required this.manufacturerId, + required this.data, + }); + + int manufacturerId; + + Uint8List data; + + List _toList() { + return [ + manufacturerId, + data, + ]; + } + + Object encode() { + return _toList(); + } + + static PeripheralManufacturerData decode(Object result) { + result as List; + return PeripheralManufacturerData( + manufacturerId: result[0]! as int, + data: result[1]! as Uint8List, + ); + } + + @override + // ignore: avoid_equals_and_hash_code_on_mutable_classes + bool operator ==(Object other) { + if (other is! PeripheralManufacturerData || + other.runtimeType != runtimeType) { + return false; + } + if (identical(this, other)) { + return true; + } + return _deepEquals(manufacturerId, other.manufacturerId) && + _deepEquals(data, other.data); + } + + @override + // ignore: avoid_equals_and_hash_code_on_mutable_classes + int get hashCode => _deepHash([runtimeType, ..._toList()]); +} + +class _PigeonCodec extends StandardMessageCodec { + const _PigeonCodec(); + @override + void writeValue(WriteBuffer buffer, Object? value) { + if (value is int) { + buffer.putUint8(4); + buffer.putInt64(value); + } else if (value is UniversalBleLogLevel) { + buffer.putUint8(129); + writeValue(buffer, value.index); + } else if (value is AndroidScanMode) { + buffer.putUint8(130); + writeValue(buffer, value.index); + } else if (value is PeripheralReadinessState) { + buffer.putUint8(131); + writeValue(buffer, value.index); + } else if (value is PeripheralAdvertisingState) { + buffer.putUint8(132); + writeValue(buffer, value.index); + } else if (value is UniversalBleErrorCode) { + buffer.putUint8(133); + writeValue(buffer, value.index); + } else if (value is UniversalBleScanResult) { + buffer.putUint8(134); + writeValue(buffer, value.encode()); + } else if (value is UniversalBleService) { + buffer.putUint8(135); + writeValue(buffer, value.encode()); + } else if (value is UniversalBleCharacteristic) { + buffer.putUint8(136); + writeValue(buffer, value.encode()); + } else if (value is UniversalBleDescriptor) { + buffer.putUint8(137); + writeValue(buffer, value.encode()); + } else if (value is AndroidOptions) { + buffer.putUint8(138); + writeValue(buffer, value.encode()); + } else if (value is UniversalScanConfig) { + buffer.putUint8(139); + writeValue(buffer, value.encode()); + } else if (value is UniversalScanFilter) { + buffer.putUint8(140); + writeValue(buffer, value.encode()); + } else if (value is UniversalManufacturerDataFilter) { + buffer.putUint8(141); + writeValue(buffer, value.encode()); + } else if (value is UniversalManufacturerData) { + buffer.putUint8(142); + writeValue(buffer, value.encode()); + } else if (value is PeripheralService) { + buffer.putUint8(143); + writeValue(buffer, value.encode()); + } else if (value is PeripheralCharacteristic) { + buffer.putUint8(144); + writeValue(buffer, value.encode()); + } else if (value is PeripheralDescriptor) { + buffer.putUint8(145); + writeValue(buffer, value.encode()); + } else if (value is PeripheralReadRequestResult) { + buffer.putUint8(146); + writeValue(buffer, value.encode()); + } else if (value is PeripheralWriteRequestResult) { + buffer.putUint8(147); + writeValue(buffer, value.encode()); + } else if (value is PeripheralManufacturerData) { + buffer.putUint8(148); + writeValue(buffer, value.encode()); + } else { + super.writeValue(buffer, value); + } + } + + @override + Object? readValueOfType(int type, ReadBuffer buffer) { + switch (type) { + case 129: + final value = readValue(buffer) as int?; + return value == null ? null : UniversalBleLogLevel.values[value]; + case 130: + final value = readValue(buffer) as int?; + return value == null ? null : AndroidScanMode.values[value]; + case 131: + final value = readValue(buffer) as int?; + return value == null ? null : PeripheralReadinessState.values[value]; + case 132: + final value = readValue(buffer) as int?; + return value == null ? null : PeripheralAdvertisingState.values[value]; + case 133: + final value = readValue(buffer) as int?; + return value == null ? null : UniversalBleErrorCode.values[value]; + case 134: + return UniversalBleScanResult.decode(readValue(buffer)!); + case 135: + return UniversalBleService.decode(readValue(buffer)!); + case 136: + return UniversalBleCharacteristic.decode(readValue(buffer)!); + case 137: + return UniversalBleDescriptor.decode(readValue(buffer)!); + case 138: + return AndroidOptions.decode(readValue(buffer)!); + case 139: + return UniversalScanConfig.decode(readValue(buffer)!); + case 140: + return UniversalScanFilter.decode(readValue(buffer)!); + case 141: + return UniversalManufacturerDataFilter.decode(readValue(buffer)!); + case 142: + return UniversalManufacturerData.decode(readValue(buffer)!); + case 143: + return PeripheralService.decode(readValue(buffer)!); + case 144: + return PeripheralCharacteristic.decode(readValue(buffer)!); + case 145: + return PeripheralDescriptor.decode(readValue(buffer)!); + case 146: + return PeripheralReadRequestResult.decode(readValue(buffer)!); + case 147: + return PeripheralWriteRequestResult.decode(readValue(buffer)!); + case 148: + return PeripheralManufacturerData.decode(readValue(buffer)!); + default: + return super.readValueOfType(type, buffer); + } + } +} + +/// Flutter -> Native +class UniversalBlePlatformChannel { + /// Constructor for [UniversalBlePlatformChannel]. The [binaryMessenger] named argument is + /// available for dependency injection. If it is left null, the default + /// BinaryMessenger will be used which routes to the host platform. + UniversalBlePlatformChannel( + {BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''}) + : pigeonVar_binaryMessenger = binaryMessenger, + pigeonVar_messageChannelSuffix = + messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; + final BinaryMessenger? pigeonVar_binaryMessenger; + + static const MessageCodec pigeonChannelCodec = _PigeonCodec(); + + final String pigeonVar_messageChannelSuffix; + + Future getBluetoothAvailabilityState() async { + final pigeonVar_channelName = + 'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.getBluetoothAvailabilityState$pigeonVar_messageChannelSuffix'; + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + + final Object? pigeonVar_replyValue = _extractReplyValueOrThrow( + pigeonVar_replyList, + pigeonVar_channelName, + isNullValid: false, + ); + return pigeonVar_replyValue! as int; + } + + Future hasPermissions(bool withAndroidFineLocation) async { + final pigeonVar_channelName = + 'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.hasPermissions$pigeonVar_messageChannelSuffix'; + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = + pigeonVar_channel.send([withAndroidFineLocation]); + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + + final Object? pigeonVar_replyValue = _extractReplyValueOrThrow( + pigeonVar_replyList, + pigeonVar_channelName, + isNullValid: false, + ); + return pigeonVar_replyValue! as bool; + } + + Future requestPermissions(bool withAndroidFineLocation) async { + final pigeonVar_channelName = + 'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.requestPermissions$pigeonVar_messageChannelSuffix'; + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = + pigeonVar_channel.send([withAndroidFineLocation]); + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + + _extractReplyValueOrThrow( + pigeonVar_replyList, + pigeonVar_channelName, + isNullValid: true, + ); + } + + Future enableBluetooth() async { + final pigeonVar_channelName = + 'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.enableBluetooth$pigeonVar_messageChannelSuffix'; + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + + final Object? pigeonVar_replyValue = _extractReplyValueOrThrow( + pigeonVar_replyList, + pigeonVar_channelName, + isNullValid: false, + ); + return pigeonVar_replyValue! as bool; + } + + Future disableBluetooth() async { + final pigeonVar_channelName = + 'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.disableBluetooth$pigeonVar_messageChannelSuffix'; + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + + final Object? pigeonVar_replyValue = _extractReplyValueOrThrow( + pigeonVar_replyList, + pigeonVar_channelName, + isNullValid: false, + ); + return pigeonVar_replyValue! as bool; + } + + Future startScan( + UniversalScanFilter? filter, UniversalScanConfig? config) async { + final pigeonVar_channelName = + 'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.startScan$pigeonVar_messageChannelSuffix'; + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = + pigeonVar_channel.send([filter, config]); + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + + _extractReplyValueOrThrow( + pigeonVar_replyList, + pigeonVar_channelName, + isNullValid: true, + ); + } + + Future stopScan() async { + final pigeonVar_channelName = + 'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.stopScan$pigeonVar_messageChannelSuffix'; + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + + _extractReplyValueOrThrow( + pigeonVar_replyList, + pigeonVar_channelName, + isNullValid: true, + ); + } + + Future isScanning() async { + final pigeonVar_channelName = + 'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.isScanning$pigeonVar_messageChannelSuffix'; + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + + final Object? pigeonVar_replyValue = _extractReplyValueOrThrow( + pigeonVar_replyList, + pigeonVar_channelName, + isNullValid: false, + ); + return pigeonVar_replyValue! as bool; + } + + Future connect(String deviceId, {bool? autoConnect}) async { + final pigeonVar_channelName = + 'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.connect$pigeonVar_messageChannelSuffix'; + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = + pigeonVar_channel.send([deviceId, autoConnect]); + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + + _extractReplyValueOrThrow( + pigeonVar_replyList, + pigeonVar_channelName, + isNullValid: true, + ); + } + + Future disconnect(String deviceId) async { + final pigeonVar_channelName = + 'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.disconnect$pigeonVar_messageChannelSuffix'; + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = + pigeonVar_channel.send([deviceId]); + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + + _extractReplyValueOrThrow( + pigeonVar_replyList, + pigeonVar_channelName, + isNullValid: true, + ); + } + + Future setNotifiable(String deviceId, String service, + String characteristic, int bleInputProperty) async { + final pigeonVar_channelName = + 'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.setNotifiable$pigeonVar_messageChannelSuffix'; + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel + .send([deviceId, service, characteristic, bleInputProperty]); + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + + _extractReplyValueOrThrow( + pigeonVar_replyList, + pigeonVar_channelName, + isNullValid: true, + ); + } + + Future> discoverServices( + String deviceId, bool withDescriptors) async { + final pigeonVar_channelName = + 'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.discoverServices$pigeonVar_messageChannelSuffix'; + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = + pigeonVar_channel.send([deviceId, withDescriptors]); + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + + final Object? pigeonVar_replyValue = _extractReplyValueOrThrow( + pigeonVar_replyList, + pigeonVar_channelName, + isNullValid: false, + ); + return (pigeonVar_replyValue! as List).cast(); + } + + Future readValue( + String deviceId, String service, String characteristic) async { + final pigeonVar_channelName = + 'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.readValue$pigeonVar_messageChannelSuffix'; + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = + pigeonVar_channel.send([deviceId, service, characteristic]); + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + + final Object? pigeonVar_replyValue = _extractReplyValueOrThrow( + pigeonVar_replyList, + pigeonVar_channelName, + isNullValid: false, + ); + return pigeonVar_replyValue! as Uint8List; + } + + Future requestMtu(String deviceId, int expectedMtu) async { + final pigeonVar_channelName = + 'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.requestMtu$pigeonVar_messageChannelSuffix'; + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = + pigeonVar_channel.send([deviceId, expectedMtu]); + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + + final Object? pigeonVar_replyValue = _extractReplyValueOrThrow( + pigeonVar_replyList, + pigeonVar_channelName, + isNullValid: false, + ); + return pigeonVar_replyValue! as int; + } + + Future writeValue(String deviceId, String service, + String characteristic, Uint8List value, int bleOutputProperty) async { + final pigeonVar_channelName = + 'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.writeValue$pigeonVar_messageChannelSuffix'; + final pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, ); final Future pigeonVar_sendFuture = pigeonVar_channel.send( - [filter, config], + [deviceId, service, characteristic, value, bleOutputProperty]); + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + + _extractReplyValueOrThrow( + pigeonVar_replyList, + pigeonVar_channelName, + isNullValid: true, + ); + } + + Future isPaired(String deviceId) async { + final pigeonVar_channelName = + 'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.isPaired$pigeonVar_messageChannelSuffix'; + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = + pigeonVar_channel.send([deviceId]); + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + + final Object? pigeonVar_replyValue = _extractReplyValueOrThrow( + pigeonVar_replyList, + pigeonVar_channelName, + isNullValid: false, + ); + return pigeonVar_replyValue! as bool; + } + + Future pair(String deviceId) async { + final pigeonVar_channelName = + 'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.pair$pigeonVar_messageChannelSuffix'; + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, ); + final Future pigeonVar_sendFuture = + pigeonVar_channel.send([deviceId]); final pigeonVar_replyList = await pigeonVar_sendFuture as List?; - if (pigeonVar_replyList == null) { - throw _createConnectionError(pigeonVar_channelName); - } else if (pigeonVar_replyList.length > 1) { - throw PlatformException( - code: pigeonVar_replyList[0]! as String, - message: pigeonVar_replyList[1] as String?, - details: pigeonVar_replyList[2], - ); - } else { - return; + + final Object? pigeonVar_replyValue = _extractReplyValueOrThrow( + pigeonVar_replyList, + pigeonVar_channelName, + isNullValid: false, + ); + return pigeonVar_replyValue! as bool; + } + + Future unPair(String deviceId) async { + final pigeonVar_channelName = + 'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.unPair$pigeonVar_messageChannelSuffix'; + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = + pigeonVar_channel.send([deviceId]); + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + + _extractReplyValueOrThrow( + pigeonVar_replyList, + pigeonVar_channelName, + isNullValid: true, + ); + } + + Future> getSystemDevices( + List withServices) async { + final pigeonVar_channelName = + 'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.getSystemDevices$pigeonVar_messageChannelSuffix'; + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = + pigeonVar_channel.send([withServices]); + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + + final Object? pigeonVar_replyValue = _extractReplyValueOrThrow( + pigeonVar_replyList, + pigeonVar_channelName, + isNullValid: false, + ); + return (pigeonVar_replyValue! as List) + .cast(); + } + + Future getConnectionState(String deviceId) async { + final pigeonVar_channelName = + 'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.getConnectionState$pigeonVar_messageChannelSuffix'; + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = + pigeonVar_channel.send([deviceId]); + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + + final Object? pigeonVar_replyValue = _extractReplyValueOrThrow( + pigeonVar_replyList, + pigeonVar_channelName, + isNullValid: false, + ); + return pigeonVar_replyValue! as int; + } + + Future readRssi(String deviceId) async { + final pigeonVar_channelName = + 'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.readRssi$pigeonVar_messageChannelSuffix'; + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = + pigeonVar_channel.send([deviceId]); + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + + final Object? pigeonVar_replyValue = _extractReplyValueOrThrow( + pigeonVar_replyList, + pigeonVar_channelName, + isNullValid: false, + ); + return pigeonVar_replyValue! as int; + } + + Future requestConnectionPriority(String deviceId, int priority) async { + final pigeonVar_channelName = + 'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.requestConnectionPriority$pigeonVar_messageChannelSuffix'; + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = + pigeonVar_channel.send([deviceId, priority]); + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + + _extractReplyValueOrThrow( + pigeonVar_replyList, + pigeonVar_channelName, + isNullValid: true, + ); + } + + Future setLogLevel(UniversalBleLogLevel logLevel) async { + final pigeonVar_channelName = + 'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.setLogLevel$pigeonVar_messageChannelSuffix'; + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = + pigeonVar_channel.send([logLevel]); + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + + _extractReplyValueOrThrow( + pigeonVar_replyList, + pigeonVar_channelName, + isNullValid: true, + ); + } +} + +/// Native -> Flutter +abstract class UniversalBleCallbackChannel { + static const MessageCodec pigeonChannelCodec = _PigeonCodec(); + + void onAvailabilityChanged(int state); + + void onPairStateChange(String deviceId, bool isPaired, String? error); + + void onScanResult(UniversalBleScanResult result); + + void onValueChanged(String deviceId, String characteristicId, Uint8List value, + int? timestamp); + + void onConnectionChanged(String deviceId, bool connected, String? error); + + static void setUp( + UniversalBleCallbackChannel? api, { + BinaryMessenger? binaryMessenger, + String messageChannelSuffix = '', + }) { + messageChannelSuffix = + messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; + { + final pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.universal_ble.UniversalBleCallbackChannel.onAvailabilityChanged$messageChannelSuffix', + pigeonChannelCodec, + binaryMessenger: binaryMessenger); + if (api == null) { + pigeonVar_channel.setMessageHandler(null); + } else { + pigeonVar_channel.setMessageHandler((Object? message) async { + final List args = message! as List; + final int arg_state = args[0]! as int; + try { + api.onAvailabilityChanged(arg_state); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } + { + final pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.universal_ble.UniversalBleCallbackChannel.onPairStateChange$messageChannelSuffix', + pigeonChannelCodec, + binaryMessenger: binaryMessenger); + if (api == null) { + pigeonVar_channel.setMessageHandler(null); + } else { + pigeonVar_channel.setMessageHandler((Object? message) async { + final List args = message! as List; + final String arg_deviceId = args[0]! as String; + final bool arg_isPaired = args[1]! as bool; + final String? arg_error = args[2] as String?; + try { + api.onPairStateChange(arg_deviceId, arg_isPaired, arg_error); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } + { + final pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.universal_ble.UniversalBleCallbackChannel.onScanResult$messageChannelSuffix', + pigeonChannelCodec, + binaryMessenger: binaryMessenger); + if (api == null) { + pigeonVar_channel.setMessageHandler(null); + } else { + pigeonVar_channel.setMessageHandler((Object? message) async { + final List args = message! as List; + final UniversalBleScanResult arg_result = + args[0]! as UniversalBleScanResult; + try { + api.onScanResult(arg_result); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } + { + final pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.universal_ble.UniversalBleCallbackChannel.onValueChanged$messageChannelSuffix', + pigeonChannelCodec, + binaryMessenger: binaryMessenger); + if (api == null) { + pigeonVar_channel.setMessageHandler(null); + } else { + pigeonVar_channel.setMessageHandler((Object? message) async { + final List args = message! as List; + final String arg_deviceId = args[0]! as String; + final String arg_characteristicId = args[1]! as String; + final Uint8List arg_value = args[2]! as Uint8List; + final int? arg_timestamp = args[3] as int?; + try { + api.onValueChanged( + arg_deviceId, arg_characteristicId, arg_value, arg_timestamp); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } + { + final pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.universal_ble.UniversalBleCallbackChannel.onConnectionChanged$messageChannelSuffix', + pigeonChannelCodec, + binaryMessenger: binaryMessenger); + if (api == null) { + pigeonVar_channel.setMessageHandler(null); + } else { + pigeonVar_channel.setMessageHandler((Object? message) async { + final List args = message! as List; + final String arg_deviceId = args[0]! as String; + final bool arg_connected = args[1]! as bool; + final String? arg_error = args[2] as String?; + try { + api.onConnectionChanged(arg_deviceId, arg_connected, arg_error); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } + }); + } } } +} - Future stopScan() async { +/// Flutter -> Native (peripheral) +class UniversalBlePeripheralChannel { + /// Constructor for [UniversalBlePeripheralChannel]. The [binaryMessenger] named argument is + /// available for dependency injection. If it is left null, the default + /// BinaryMessenger will be used which routes to the host platform. + UniversalBlePeripheralChannel( + {BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''}) + : pigeonVar_binaryMessenger = binaryMessenger, + pigeonVar_messageChannelSuffix = + messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; + final BinaryMessenger? pigeonVar_binaryMessenger; + + static const MessageCodec pigeonChannelCodec = _PigeonCodec(); + + final String pigeonVar_messageChannelSuffix; + + Future getAdvertisingState() async { final pigeonVar_channelName = - 'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.stopScan$pigeonVar_messageChannelSuffix'; + 'dev.flutter.pigeon.universal_ble.UniversalBlePeripheralChannel.getAdvertisingState$pigeonVar_messageChannelSuffix'; final pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, @@ -828,22 +1790,18 @@ class UniversalBlePlatformChannel { ); final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); final pigeonVar_replyList = await pigeonVar_sendFuture as List?; - if (pigeonVar_replyList == null) { - throw _createConnectionError(pigeonVar_channelName); - } else if (pigeonVar_replyList.length > 1) { - throw PlatformException( - code: pigeonVar_replyList[0]! as String, - message: pigeonVar_replyList[1] as String?, - details: pigeonVar_replyList[2], - ); - } else { - return; - } + + final Object? pigeonVar_replyValue = _extractReplyValueOrThrow( + pigeonVar_replyList, + pigeonVar_channelName, + isNullValid: false, + ); + return pigeonVar_replyValue! as PeripheralAdvertisingState; } - Future isScanning() async { + Future getReadinessState() async { final pigeonVar_channelName = - 'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.isScanning$pigeonVar_messageChannelSuffix'; + 'dev.flutter.pigeon.universal_ble.UniversalBlePeripheralChannel.getReadinessState$pigeonVar_messageChannelSuffix'; final pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, @@ -851,678 +1809,477 @@ class UniversalBlePlatformChannel { ); final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); final pigeonVar_replyList = await pigeonVar_sendFuture as List?; - if (pigeonVar_replyList == null) { - throw _createConnectionError(pigeonVar_channelName); - } else if (pigeonVar_replyList.length > 1) { - throw PlatformException( - code: pigeonVar_replyList[0]! as String, - message: pigeonVar_replyList[1] as String?, - details: pigeonVar_replyList[2], - ); - } else if (pigeonVar_replyList[0] == null) { - throw PlatformException( - code: 'null-error', - message: 'Host platform returned null value for non-null return value.', - ); - } else { - return (pigeonVar_replyList[0] as bool?)!; - } - } - Future connect(String deviceId, {bool? autoConnect}) async { - final pigeonVar_channelName = - 'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.connect$pigeonVar_messageChannelSuffix'; - final pigeonVar_channel = BasicMessageChannel( + final Object? pigeonVar_replyValue = _extractReplyValueOrThrow( + pigeonVar_replyList, pigeonVar_channelName, - pigeonChannelCodec, - binaryMessenger: pigeonVar_binaryMessenger, - ); - final Future pigeonVar_sendFuture = pigeonVar_channel.send( - [deviceId, autoConnect], + isNullValid: false, ); - final pigeonVar_replyList = await pigeonVar_sendFuture as List?; - if (pigeonVar_replyList == null) { - throw _createConnectionError(pigeonVar_channelName); - } else if (pigeonVar_replyList.length > 1) { - throw PlatformException( - code: pigeonVar_replyList[0]! as String, - message: pigeonVar_replyList[1] as String?, - details: pigeonVar_replyList[2], - ); - } else { - return; - } + return pigeonVar_replyValue! as PeripheralReadinessState; } - Future disconnect(String deviceId) async { + Future stopAdvertising() async { final pigeonVar_channelName = - 'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.disconnect$pigeonVar_messageChannelSuffix'; + 'dev.flutter.pigeon.universal_ble.UniversalBlePeripheralChannel.stopAdvertising$pigeonVar_messageChannelSuffix'; final pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, ); - final Future pigeonVar_sendFuture = pigeonVar_channel.send( - [deviceId], - ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); final pigeonVar_replyList = await pigeonVar_sendFuture as List?; - if (pigeonVar_replyList == null) { - throw _createConnectionError(pigeonVar_channelName); - } else if (pigeonVar_replyList.length > 1) { - throw PlatformException( - code: pigeonVar_replyList[0]! as String, - message: pigeonVar_replyList[1] as String?, - details: pigeonVar_replyList[2], - ); - } else { - return; - } - } - Future setNotifiable( - String deviceId, - String service, - String characteristic, - int bleInputProperty, - ) async { - final pigeonVar_channelName = - 'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.setNotifiable$pigeonVar_messageChannelSuffix'; - final pigeonVar_channel = BasicMessageChannel( + _extractReplyValueOrThrow( + pigeonVar_replyList, pigeonVar_channelName, - pigeonChannelCodec, - binaryMessenger: pigeonVar_binaryMessenger, + isNullValid: true, ); - final Future pigeonVar_sendFuture = pigeonVar_channel.send( - [deviceId, service, characteristic, bleInputProperty], - ); - final pigeonVar_replyList = await pigeonVar_sendFuture as List?; - if (pigeonVar_replyList == null) { - throw _createConnectionError(pigeonVar_channelName); - } else if (pigeonVar_replyList.length > 1) { - throw PlatformException( - code: pigeonVar_replyList[0]! as String, - message: pigeonVar_replyList[1] as String?, - details: pigeonVar_replyList[2], - ); - } else { - return; - } } - Future> discoverServices( - String deviceId, - bool withDescriptors, - ) async { + Future addService(PeripheralService service) async { final pigeonVar_channelName = - 'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.discoverServices$pigeonVar_messageChannelSuffix'; + 'dev.flutter.pigeon.universal_ble.UniversalBlePeripheralChannel.addService$pigeonVar_messageChannelSuffix'; final pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, ); - final Future pigeonVar_sendFuture = pigeonVar_channel.send( - [deviceId, withDescriptors], - ); + final Future pigeonVar_sendFuture = + pigeonVar_channel.send([service]); final pigeonVar_replyList = await pigeonVar_sendFuture as List?; - if (pigeonVar_replyList == null) { - throw _createConnectionError(pigeonVar_channelName); - } else if (pigeonVar_replyList.length > 1) { - throw PlatformException( - code: pigeonVar_replyList[0]! as String, - message: pigeonVar_replyList[1] as String?, - details: pigeonVar_replyList[2], - ); - } else if (pigeonVar_replyList[0] == null) { - throw PlatformException( - code: 'null-error', - message: 'Host platform returned null value for non-null return value.', - ); - } else { - return (pigeonVar_replyList[0] as List?)! - .cast(); - } - } - Future readValue( - String deviceId, - String service, - String characteristic, - ) async { - final pigeonVar_channelName = - 'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.readValue$pigeonVar_messageChannelSuffix'; - final pigeonVar_channel = BasicMessageChannel( + _extractReplyValueOrThrow( + pigeonVar_replyList, pigeonVar_channelName, - pigeonChannelCodec, - binaryMessenger: pigeonVar_binaryMessenger, - ); - final Future pigeonVar_sendFuture = pigeonVar_channel.send( - [deviceId, service, characteristic], + isNullValid: true, ); - final pigeonVar_replyList = await pigeonVar_sendFuture as List?; - if (pigeonVar_replyList == null) { - throw _createConnectionError(pigeonVar_channelName); - } else if (pigeonVar_replyList.length > 1) { - throw PlatformException( - code: pigeonVar_replyList[0]! as String, - message: pigeonVar_replyList[1] as String?, - details: pigeonVar_replyList[2], - ); - } else if (pigeonVar_replyList[0] == null) { - throw PlatformException( - code: 'null-error', - message: 'Host platform returned null value for non-null return value.', - ); - } else { - return (pigeonVar_replyList[0] as Uint8List?)!; - } } - Future requestMtu(String deviceId, int expectedMtu) async { + Future removeService(String serviceId) async { final pigeonVar_channelName = - 'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.requestMtu$pigeonVar_messageChannelSuffix'; + 'dev.flutter.pigeon.universal_ble.UniversalBlePeripheralChannel.removeService$pigeonVar_messageChannelSuffix'; final pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, ); - final Future pigeonVar_sendFuture = pigeonVar_channel.send( - [deviceId, expectedMtu], - ); + final Future pigeonVar_sendFuture = + pigeonVar_channel.send([serviceId]); final pigeonVar_replyList = await pigeonVar_sendFuture as List?; - if (pigeonVar_replyList == null) { - throw _createConnectionError(pigeonVar_channelName); - } else if (pigeonVar_replyList.length > 1) { - throw PlatformException( - code: pigeonVar_replyList[0]! as String, - message: pigeonVar_replyList[1] as String?, - details: pigeonVar_replyList[2], - ); - } else if (pigeonVar_replyList[0] == null) { - throw PlatformException( - code: 'null-error', - message: 'Host platform returned null value for non-null return value.', - ); - } else { - return (pigeonVar_replyList[0] as int?)!; - } - } - Future writeValue( - String deviceId, - String service, - String characteristic, - Uint8List value, - int bleOutputProperty, - ) async { - final pigeonVar_channelName = - 'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.writeValue$pigeonVar_messageChannelSuffix'; - final pigeonVar_channel = BasicMessageChannel( + _extractReplyValueOrThrow( + pigeonVar_replyList, pigeonVar_channelName, - pigeonChannelCodec, - binaryMessenger: pigeonVar_binaryMessenger, - ); - final Future pigeonVar_sendFuture = pigeonVar_channel.send( - [deviceId, service, characteristic, value, bleOutputProperty], + isNullValid: true, ); - final pigeonVar_replyList = await pigeonVar_sendFuture as List?; - if (pigeonVar_replyList == null) { - throw _createConnectionError(pigeonVar_channelName); - } else if (pigeonVar_replyList.length > 1) { - throw PlatformException( - code: pigeonVar_replyList[0]! as String, - message: pigeonVar_replyList[1] as String?, - details: pigeonVar_replyList[2], - ); - } else { - return; - } } - Future isPaired(String deviceId) async { + Future clearServices() async { final pigeonVar_channelName = - 'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.isPaired$pigeonVar_messageChannelSuffix'; + 'dev.flutter.pigeon.universal_ble.UniversalBlePeripheralChannel.clearServices$pigeonVar_messageChannelSuffix'; final pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, ); - final Future pigeonVar_sendFuture = pigeonVar_channel.send( - [deviceId], - ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); final pigeonVar_replyList = await pigeonVar_sendFuture as List?; - if (pigeonVar_replyList == null) { - throw _createConnectionError(pigeonVar_channelName); - } else if (pigeonVar_replyList.length > 1) { - throw PlatformException( - code: pigeonVar_replyList[0]! as String, - message: pigeonVar_replyList[1] as String?, - details: pigeonVar_replyList[2], - ); - } else if (pigeonVar_replyList[0] == null) { - throw PlatformException( - code: 'null-error', - message: 'Host platform returned null value for non-null return value.', - ); - } else { - return (pigeonVar_replyList[0] as bool?)!; - } - } - Future pair(String deviceId) async { - final pigeonVar_channelName = - 'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.pair$pigeonVar_messageChannelSuffix'; - final pigeonVar_channel = BasicMessageChannel( + _extractReplyValueOrThrow( + pigeonVar_replyList, pigeonVar_channelName, - pigeonChannelCodec, - binaryMessenger: pigeonVar_binaryMessenger, + isNullValid: true, ); - final Future pigeonVar_sendFuture = pigeonVar_channel.send( - [deviceId], - ); - final pigeonVar_replyList = await pigeonVar_sendFuture as List?; - if (pigeonVar_replyList == null) { - throw _createConnectionError(pigeonVar_channelName); - } else if (pigeonVar_replyList.length > 1) { - throw PlatformException( - code: pigeonVar_replyList[0]! as String, - message: pigeonVar_replyList[1] as String?, - details: pigeonVar_replyList[2], - ); - } else if (pigeonVar_replyList[0] == null) { - throw PlatformException( - code: 'null-error', - message: 'Host platform returned null value for non-null return value.', - ); - } else { - return (pigeonVar_replyList[0] as bool?)!; - } } - Future unPair(String deviceId) async { + Future> getServices() async { final pigeonVar_channelName = - 'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.unPair$pigeonVar_messageChannelSuffix'; + 'dev.flutter.pigeon.universal_ble.UniversalBlePeripheralChannel.getServices$pigeonVar_messageChannelSuffix'; final pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, ); - final Future pigeonVar_sendFuture = pigeonVar_channel.send( - [deviceId], - ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); final pigeonVar_replyList = await pigeonVar_sendFuture as List?; - if (pigeonVar_replyList == null) { - throw _createConnectionError(pigeonVar_channelName); - } else if (pigeonVar_replyList.length > 1) { - throw PlatformException( - code: pigeonVar_replyList[0]! as String, - message: pigeonVar_replyList[1] as String?, - details: pigeonVar_replyList[2], - ); - } else { - return; - } + + final Object? pigeonVar_replyValue = _extractReplyValueOrThrow( + pigeonVar_replyList, + pigeonVar_channelName, + isNullValid: false, + ); + return (pigeonVar_replyValue! as List).cast(); } - Future> getSystemDevices( - List withServices, - ) async { + Future startAdvertising( + List services, + String? localName, + int? timeout, + PeripheralManufacturerData? manufacturerData, + bool addManufacturerDataInScanResponse) async { final pigeonVar_channelName = - 'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.getSystemDevices$pigeonVar_messageChannelSuffix'; + 'dev.flutter.pigeon.universal_ble.UniversalBlePeripheralChannel.startAdvertising$pigeonVar_messageChannelSuffix'; final pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, ); - final Future pigeonVar_sendFuture = pigeonVar_channel.send( - [withServices], - ); + final Future pigeonVar_sendFuture = + pigeonVar_channel.send([ + services, + localName, + timeout, + manufacturerData, + addManufacturerDataInScanResponse + ]); final pigeonVar_replyList = await pigeonVar_sendFuture as List?; - if (pigeonVar_replyList == null) { - throw _createConnectionError(pigeonVar_channelName); - } else if (pigeonVar_replyList.length > 1) { - throw PlatformException( - code: pigeonVar_replyList[0]! as String, - message: pigeonVar_replyList[1] as String?, - details: pigeonVar_replyList[2], - ); - } else if (pigeonVar_replyList[0] == null) { - throw PlatformException( - code: 'null-error', - message: 'Host platform returned null value for non-null return value.', - ); - } else { - return (pigeonVar_replyList[0] as List?)! - .cast(); - } + + _extractReplyValueOrThrow( + pigeonVar_replyList, + pigeonVar_channelName, + isNullValid: true, + ); } - Future getConnectionState(String deviceId) async { + Future updateCharacteristic( + String characteristicId, Uint8List value, String? deviceId) async { final pigeonVar_channelName = - 'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.getConnectionState$pigeonVar_messageChannelSuffix'; + 'dev.flutter.pigeon.universal_ble.UniversalBlePeripheralChannel.updateCharacteristic$pigeonVar_messageChannelSuffix'; final pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, ); - final Future pigeonVar_sendFuture = pigeonVar_channel.send( - [deviceId], - ); + final Future pigeonVar_sendFuture = + pigeonVar_channel.send([characteristicId, value, deviceId]); final pigeonVar_replyList = await pigeonVar_sendFuture as List?; - if (pigeonVar_replyList == null) { - throw _createConnectionError(pigeonVar_channelName); - } else if (pigeonVar_replyList.length > 1) { - throw PlatformException( - code: pigeonVar_replyList[0]! as String, - message: pigeonVar_replyList[1] as String?, - details: pigeonVar_replyList[2], - ); - } else if (pigeonVar_replyList[0] == null) { - throw PlatformException( - code: 'null-error', - message: 'Host platform returned null value for non-null return value.', - ); - } else { - return (pigeonVar_replyList[0] as int?)!; - } + + _extractReplyValueOrThrow( + pigeonVar_replyList, + pigeonVar_channelName, + isNullValid: true, + ); } - Future readRssi(String deviceId) async { + /// Returns peripheral-client device ids currently subscribed to [characteristicId] + /// (e.g. HID report characteristic). Used to restore app state after restart. + Future> getSubscribedClients(String characteristicId) async { final pigeonVar_channelName = - 'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.readRssi$pigeonVar_messageChannelSuffix'; + 'dev.flutter.pigeon.universal_ble.UniversalBlePeripheralChannel.getSubscribedClients$pigeonVar_messageChannelSuffix'; final pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, ); - final Future pigeonVar_sendFuture = pigeonVar_channel.send( - [deviceId], - ); + final Future pigeonVar_sendFuture = + pigeonVar_channel.send([characteristicId]); final pigeonVar_replyList = await pigeonVar_sendFuture as List?; - if (pigeonVar_replyList == null) { - throw _createConnectionError(pigeonVar_channelName); - } else if (pigeonVar_replyList.length > 1) { - throw PlatformException( - code: pigeonVar_replyList[0]! as String, - message: pigeonVar_replyList[1] as String?, - details: pigeonVar_replyList[2], - ); - } else if (pigeonVar_replyList[0] == null) { - throw PlatformException( - code: 'null-error', - message: 'Host platform returned null value for non-null return value.', - ); - } else { - return (pigeonVar_replyList[0] as int?)!; - } - } - Future requestConnectionPriority(String deviceId, int priority) async { - final pigeonVar_channelName = - 'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.requestConnectionPriority$pigeonVar_messageChannelSuffix'; - final pigeonVar_channel = BasicMessageChannel( - pigeonVar_channelName, - pigeonChannelCodec, - binaryMessenger: pigeonVar_binaryMessenger); - final Future pigeonVar_sendFuture = - pigeonVar_channel.send([deviceId, priority]); - final pigeonVar_replyList = - await pigeonVar_sendFuture as List?; - if (pigeonVar_replyList == null) { - throw _createConnectionError(pigeonVar_channelName); - } else if (pigeonVar_replyList.length > 1) { - throw PlatformException( - code: pigeonVar_replyList[0]! as String, - message: pigeonVar_replyList[1] as String?, - details: pigeonVar_replyList[2], - ); - } else { - return; - } + final Object? pigeonVar_replyValue = _extractReplyValueOrThrow( + pigeonVar_replyList, + pigeonVar_channelName, + isNullValid: false, + ); + return (pigeonVar_replyValue! as List).cast(); } - Future setLogLevel(UniversalBleLogLevel logLevel) async { + /// Returns max characteristic notify payload length for a connected client. + Future getMaximumNotifyLength(String deviceId) async { final pigeonVar_channelName = - 'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.setLogLevel$pigeonVar_messageChannelSuffix'; + 'dev.flutter.pigeon.universal_ble.UniversalBlePeripheralChannel.getMaximumNotifyLength$pigeonVar_messageChannelSuffix'; final pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, ); - final Future pigeonVar_sendFuture = pigeonVar_channel.send( - [logLevel], - ); + final Future pigeonVar_sendFuture = + pigeonVar_channel.send([deviceId]); final pigeonVar_replyList = await pigeonVar_sendFuture as List?; - if (pigeonVar_replyList == null) { - throw _createConnectionError(pigeonVar_channelName); - } else if (pigeonVar_replyList.length > 1) { - throw PlatformException( - code: pigeonVar_replyList[0]! as String, - message: pigeonVar_replyList[1] as String?, - details: pigeonVar_replyList[2], - ); - } else { - return; - } + + final Object? pigeonVar_replyValue = _extractReplyValueOrThrow( + pigeonVar_replyList, + pigeonVar_channelName, + isNullValid: true, + ); + return pigeonVar_replyValue as int?; } } -/// Native -> Flutter -abstract class UniversalBleCallbackChannel { +/// Native -> Flutter (peripheral) +abstract class UniversalBlePeripheralCallback { static const MessageCodec pigeonChannelCodec = _PigeonCodec(); - void onAvailabilityChanged(int state); + PeripheralReadRequestResult? onReadRequest( + String deviceId, String characteristicId, int offset, Uint8List? value); - void onPairStateChange(String deviceId, bool isPaired, String? error); + PeripheralWriteRequestResult? onWriteRequest( + String deviceId, String characteristicId, int offset, Uint8List? value); - void onScanResult(UniversalBleScanResult result); + PeripheralReadRequestResult? onDescriptorReadRequest( + String deviceId, + String characteristicId, + String descriptorId, + int offset, + Uint8List? value); - void onValueChanged( - String deviceId, - String characteristicId, - Uint8List value, - int? timestamp, - ); + PeripheralWriteRequestResult? onDescriptorWriteRequest( + String deviceId, + String characteristicId, + String descriptorId, + int offset, + Uint8List? value); - void onConnectionChanged(String deviceId, bool connected, String? error); + void onCharacteristicSubscriptionChange(String deviceId, + String characteristicId, bool isSubscribed, String? name); + + void onAdvertisingStateChange( + PeripheralAdvertisingState state, String? error); + + void onServiceAdded(String serviceId, String? error); + + void onMtuChange(String deviceId, int mtu); + + void onConnectionStateChange(String deviceId, bool connected); static void setUp( - UniversalBleCallbackChannel? api, { + UniversalBlePeripheralCallback? api, { BinaryMessenger? binaryMessenger, String messageChannelSuffix = '', }) { - messageChannelSuffix = messageChannelSuffix.isNotEmpty - ? '.$messageChannelSuffix' - : ''; + messageChannelSuffix = + messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; + { + final pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.universal_ble.UniversalBlePeripheralCallback.onReadRequest$messageChannelSuffix', + pigeonChannelCodec, + binaryMessenger: binaryMessenger); + if (api == null) { + pigeonVar_channel.setMessageHandler(null); + } else { + pigeonVar_channel.setMessageHandler((Object? message) async { + final List args = message! as List; + final String arg_deviceId = args[0]! as String; + final String arg_characteristicId = args[1]! as String; + final int arg_offset = args[2]! as int; + final Uint8List? arg_value = args[3] as Uint8List?; + try { + final PeripheralReadRequestResult? output = api.onReadRequest( + arg_deviceId, arg_characteristicId, arg_offset, arg_value); + return wrapResponse(result: output); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } + { + final pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.universal_ble.UniversalBlePeripheralCallback.onWriteRequest$messageChannelSuffix', + pigeonChannelCodec, + binaryMessenger: binaryMessenger); + if (api == null) { + pigeonVar_channel.setMessageHandler(null); + } else { + pigeonVar_channel.setMessageHandler((Object? message) async { + final List args = message! as List; + final String arg_deviceId = args[0]! as String; + final String arg_characteristicId = args[1]! as String; + final int arg_offset = args[2]! as int; + final Uint8List? arg_value = args[3] as Uint8List?; + try { + final PeripheralWriteRequestResult? output = api.onWriteRequest( + arg_deviceId, arg_characteristicId, arg_offset, arg_value); + return wrapResponse(result: output); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } + { + final pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.universal_ble.UniversalBlePeripheralCallback.onDescriptorReadRequest$messageChannelSuffix', + pigeonChannelCodec, + binaryMessenger: binaryMessenger); + if (api == null) { + pigeonVar_channel.setMessageHandler(null); + } else { + pigeonVar_channel.setMessageHandler((Object? message) async { + final List args = message! as List; + final String arg_deviceId = args[0]! as String; + final String arg_characteristicId = args[1]! as String; + final String arg_descriptorId = args[2]! as String; + final int arg_offset = args[3]! as int; + final Uint8List? arg_value = args[4] as Uint8List?; + try { + final PeripheralReadRequestResult? output = + api.onDescriptorReadRequest(arg_deviceId, arg_characteristicId, + arg_descriptorId, arg_offset, arg_value); + return wrapResponse(result: output); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } { final pigeonVar_channel = BasicMessageChannel( - 'dev.flutter.pigeon.universal_ble.UniversalBleCallbackChannel.onAvailabilityChanged$messageChannelSuffix', - pigeonChannelCodec, - binaryMessenger: binaryMessenger, - ); + 'dev.flutter.pigeon.universal_ble.UniversalBlePeripheralCallback.onDescriptorWriteRequest$messageChannelSuffix', + pigeonChannelCodec, + binaryMessenger: binaryMessenger); if (api == null) { pigeonVar_channel.setMessageHandler(null); } else { pigeonVar_channel.setMessageHandler((Object? message) async { - assert( - message != null, - 'Argument for dev.flutter.pigeon.universal_ble.UniversalBleCallbackChannel.onAvailabilityChanged was null.', - ); - final List args = (message as List?)!; - final int? arg_state = (args[0] as int?); - assert( - arg_state != null, - 'Argument for dev.flutter.pigeon.universal_ble.UniversalBleCallbackChannel.onAvailabilityChanged was null, expected non-null int.', - ); + final List args = message! as List; + final String arg_deviceId = args[0]! as String; + final String arg_characteristicId = args[1]! as String; + final String arg_descriptorId = args[2]! as String; + final int arg_offset = args[3]! as int; + final Uint8List? arg_value = args[4] as Uint8List?; try { - api.onAvailabilityChanged(arg_state!); + final PeripheralWriteRequestResult? output = + api.onDescriptorWriteRequest(arg_deviceId, arg_characteristicId, + arg_descriptorId, arg_offset, arg_value); + return wrapResponse(result: output); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } + { + final pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.universal_ble.UniversalBlePeripheralCallback.onCharacteristicSubscriptionChange$messageChannelSuffix', + pigeonChannelCodec, + binaryMessenger: binaryMessenger); + if (api == null) { + pigeonVar_channel.setMessageHandler(null); + } else { + pigeonVar_channel.setMessageHandler((Object? message) async { + final List args = message! as List; + final String arg_deviceId = args[0]! as String; + final String arg_characteristicId = args[1]! as String; + final bool arg_isSubscribed = args[2]! as bool; + final String? arg_name = args[3] as String?; + try { + api.onCharacteristicSubscriptionChange( + arg_deviceId, arg_characteristicId, arg_isSubscribed, arg_name); return wrapResponse(empty: true); } on PlatformException catch (e) { return wrapResponse(error: e); } catch (e) { return wrapResponse( - error: PlatformException(code: 'error', message: e.toString()), - ); + error: PlatformException(code: 'error', message: e.toString())); } }); } } { final pigeonVar_channel = BasicMessageChannel( - 'dev.flutter.pigeon.universal_ble.UniversalBleCallbackChannel.onPairStateChange$messageChannelSuffix', - pigeonChannelCodec, - binaryMessenger: binaryMessenger, - ); + 'dev.flutter.pigeon.universal_ble.UniversalBlePeripheralCallback.onAdvertisingStateChange$messageChannelSuffix', + pigeonChannelCodec, + binaryMessenger: binaryMessenger); if (api == null) { pigeonVar_channel.setMessageHandler(null); } else { pigeonVar_channel.setMessageHandler((Object? message) async { - assert( - message != null, - 'Argument for dev.flutter.pigeon.universal_ble.UniversalBleCallbackChannel.onPairStateChange was null.', - ); - final List args = (message as List?)!; - final String? arg_deviceId = (args[0] as String?); - assert( - arg_deviceId != null, - 'Argument for dev.flutter.pigeon.universal_ble.UniversalBleCallbackChannel.onPairStateChange was null, expected non-null String.', - ); - final bool? arg_isPaired = (args[1] as bool?); - assert( - arg_isPaired != null, - 'Argument for dev.flutter.pigeon.universal_ble.UniversalBleCallbackChannel.onPairStateChange was null, expected non-null bool.', - ); - final String? arg_error = (args[2] as String?); + final List args = message! as List; + final PeripheralAdvertisingState arg_state = + args[0]! as PeripheralAdvertisingState; + final String? arg_error = args[1] as String?; try { - api.onPairStateChange(arg_deviceId!, arg_isPaired!, arg_error); + api.onAdvertisingStateChange(arg_state, arg_error); return wrapResponse(empty: true); } on PlatformException catch (e) { return wrapResponse(error: e); } catch (e) { return wrapResponse( - error: PlatformException(code: 'error', message: e.toString()), - ); + error: PlatformException(code: 'error', message: e.toString())); } }); } } { final pigeonVar_channel = BasicMessageChannel( - 'dev.flutter.pigeon.universal_ble.UniversalBleCallbackChannel.onScanResult$messageChannelSuffix', - pigeonChannelCodec, - binaryMessenger: binaryMessenger, - ); + 'dev.flutter.pigeon.universal_ble.UniversalBlePeripheralCallback.onServiceAdded$messageChannelSuffix', + pigeonChannelCodec, + binaryMessenger: binaryMessenger); if (api == null) { pigeonVar_channel.setMessageHandler(null); } else { pigeonVar_channel.setMessageHandler((Object? message) async { - assert( - message != null, - 'Argument for dev.flutter.pigeon.universal_ble.UniversalBleCallbackChannel.onScanResult was null.', - ); - final List args = (message as List?)!; - final UniversalBleScanResult? arg_result = - (args[0] as UniversalBleScanResult?); - assert( - arg_result != null, - 'Argument for dev.flutter.pigeon.universal_ble.UniversalBleCallbackChannel.onScanResult was null, expected non-null UniversalBleScanResult.', - ); + final List args = message! as List; + final String arg_serviceId = args[0]! as String; + final String? arg_error = args[1] as String?; try { - api.onScanResult(arg_result!); + api.onServiceAdded(arg_serviceId, arg_error); return wrapResponse(empty: true); } on PlatformException catch (e) { return wrapResponse(error: e); } catch (e) { return wrapResponse( - error: PlatformException(code: 'error', message: e.toString()), - ); + error: PlatformException(code: 'error', message: e.toString())); } }); } } { final pigeonVar_channel = BasicMessageChannel( - 'dev.flutter.pigeon.universal_ble.UniversalBleCallbackChannel.onValueChanged$messageChannelSuffix', - pigeonChannelCodec, - binaryMessenger: binaryMessenger, - ); + 'dev.flutter.pigeon.universal_ble.UniversalBlePeripheralCallback.onMtuChange$messageChannelSuffix', + pigeonChannelCodec, + binaryMessenger: binaryMessenger); if (api == null) { pigeonVar_channel.setMessageHandler(null); } else { pigeonVar_channel.setMessageHandler((Object? message) async { - assert( - message != null, - 'Argument for dev.flutter.pigeon.universal_ble.UniversalBleCallbackChannel.onValueChanged was null.', - ); - final List args = (message as List?)!; - final String? arg_deviceId = (args[0] as String?); - assert( - arg_deviceId != null, - 'Argument for dev.flutter.pigeon.universal_ble.UniversalBleCallbackChannel.onValueChanged was null, expected non-null String.', - ); - final String? arg_characteristicId = (args[1] as String?); - assert( - arg_characteristicId != null, - 'Argument for dev.flutter.pigeon.universal_ble.UniversalBleCallbackChannel.onValueChanged was null, expected non-null String.', - ); - final Uint8List? arg_value = (args[2] as Uint8List?); - assert( - arg_value != null, - 'Argument for dev.flutter.pigeon.universal_ble.UniversalBleCallbackChannel.onValueChanged was null, expected non-null Uint8List.', - ); - final int? arg_timestamp = (args[3] as int?); + final List args = message! as List; + final String arg_deviceId = args[0]! as String; + final int arg_mtu = args[1]! as int; try { - api.onValueChanged( - arg_deviceId!, - arg_characteristicId!, - arg_value!, - arg_timestamp, - ); + api.onMtuChange(arg_deviceId, arg_mtu); return wrapResponse(empty: true); } on PlatformException catch (e) { return wrapResponse(error: e); } catch (e) { return wrapResponse( - error: PlatformException(code: 'error', message: e.toString()), - ); + error: PlatformException(code: 'error', message: e.toString())); } }); } } { final pigeonVar_channel = BasicMessageChannel( - 'dev.flutter.pigeon.universal_ble.UniversalBleCallbackChannel.onConnectionChanged$messageChannelSuffix', - pigeonChannelCodec, - binaryMessenger: binaryMessenger, - ); + 'dev.flutter.pigeon.universal_ble.UniversalBlePeripheralCallback.onConnectionStateChange$messageChannelSuffix', + pigeonChannelCodec, + binaryMessenger: binaryMessenger); if (api == null) { pigeonVar_channel.setMessageHandler(null); } else { pigeonVar_channel.setMessageHandler((Object? message) async { - assert( - message != null, - 'Argument for dev.flutter.pigeon.universal_ble.UniversalBleCallbackChannel.onConnectionChanged was null.', - ); - final List args = (message as List?)!; - final String? arg_deviceId = (args[0] as String?); - assert( - arg_deviceId != null, - 'Argument for dev.flutter.pigeon.universal_ble.UniversalBleCallbackChannel.onConnectionChanged was null, expected non-null String.', - ); - final bool? arg_connected = (args[1] as bool?); - assert( - arg_connected != null, - 'Argument for dev.flutter.pigeon.universal_ble.UniversalBleCallbackChannel.onConnectionChanged was null, expected non-null bool.', - ); - final String? arg_error = (args[2] as String?); + final List args = message! as List; + final String arg_deviceId = args[0]! as String; + final bool arg_connected = args[1]! as bool; try { - api.onConnectionChanged(arg_deviceId!, arg_connected!, arg_error); + api.onConnectionStateChange(arg_deviceId, arg_connected); return wrapResponse(empty: true); } on PlatformException catch (e) { return wrapResponse(error: e); } catch (e) { return wrapResponse( - error: PlatformException(code: 'error', message: e.toString()), - ); + error: PlatformException(code: 'error', message: e.toString())); } }); } diff --git a/lib/src/universal_ble_pigeon/universal_ble_pigeon_channel.dart b/lib/src/universal_ble_pigeon/universal_ble_pigeon_channel.dart index eb464191..f406dca4 100644 --- a/lib/src/universal_ble_pigeon/universal_ble_pigeon_channel.dart +++ b/lib/src/universal_ble_pigeon/universal_ble_pigeon_channel.dart @@ -16,7 +16,7 @@ class UniversalBlePigeonChannel extends UniversalBlePlatform { final _channel = UniversalBlePlatformChannel(); @override - Future getBluetoothAvailabilityState() async { + Future getAvailabilityState() async { int state = await _executeWithErrorHandling( () => _channel.getBluetoothAvailabilityState(), ); @@ -75,9 +75,10 @@ class UniversalBlePigeonChannel extends UniversalBlePlatform { String deviceId, { Duration? connectionTimeout, bool autoConnect = false, - }) => _executeWithErrorHandling( - () => _channel.connect(deviceId, autoConnect: autoConnect), - ); + }) => + _executeWithErrorHandling( + () => _channel.connect(deviceId, autoConnect: autoConnect), + ); @override Future disconnect(String deviceId) => @@ -90,8 +91,8 @@ class UniversalBlePigeonChannel extends UniversalBlePlatform { ) async { List universalBleServices = await _executeWithErrorHandling( - () => _channel.discoverServices(deviceId, withDescriptors), - ); + () => _channel.discoverServices(deviceId, withDescriptors), + ); return List.from( universalBleServices .where((e) => e != null) @@ -179,7 +180,9 @@ class UniversalBlePigeonChannel extends UniversalBlePlatform { _executeWithErrorHandling(() => _channel.unPair(deviceId)); @override - Future hasPermissions({bool withAndroidFineLocation = false}) async { + Future hasPermissions({ + bool withAndroidFineLocation = false, + }) async { return await _executeWithErrorHandling( () => _channel.hasPermissions(withAndroidFineLocation), ); @@ -206,8 +209,8 @@ class UniversalBlePigeonChannel extends UniversalBlePlatform { @override Future setLogLevel(BleLogLevel logLevel) => _executeWithErrorHandling( - () => _channel.setLogLevel(logLevel.toUniversalBleLogLevel()), - ); + () => _channel.setLogLevel(logLevel.toUniversalBleLogLevel()), + ); /// To set listeners void _setupListeners() { @@ -310,7 +313,8 @@ class _UniversalBleCallbackHandler extends UniversalBleCallbackChannel { String characteristicId, Uint8List value, int? timestamp, - ) => valueChanged(deviceId, characteristicId, value, timestamp); + ) => + valueChanged(deviceId, characteristicId, value, timestamp); @override void onPairStateChange(String deviceId, bool isPaired, String? error) => @@ -327,8 +331,7 @@ extension _UniversalBleScanResultExtension on UniversalBleScanResult { isSystemDevice: isSystemDevice, services: services?.map(BleUuidParser.string).toList() ?? [], timestamp: timestamp, - manufacturerDataList: - manufacturerDataList + manufacturerDataList: manufacturerDataList ?.map((e) => ManufacturerData(e.companyIdentifier, e.data)) .toList() ?? [], @@ -361,13 +364,13 @@ extension _ScanFilterExtension on ScanFilter? { extension _BleLogLevelExtension on BleLogLevel { UniversalBleLogLevel toUniversalBleLogLevel() => switch (this) { - BleLogLevel.none => UniversalBleLogLevel.none, - BleLogLevel.error => UniversalBleLogLevel.error, - BleLogLevel.warning => UniversalBleLogLevel.warning, - BleLogLevel.info => UniversalBleLogLevel.info, - BleLogLevel.debug => UniversalBleLogLevel.debug, - BleLogLevel.verbose => UniversalBleLogLevel.verbose, - }; + BleLogLevel.none => UniversalBleLogLevel.none, + BleLogLevel.error => UniversalBleLogLevel.error, + BleLogLevel.warning => UniversalBleLogLevel.warning, + BleLogLevel.info => UniversalBleLogLevel.info, + BleLogLevel.debug => UniversalBleLogLevel.debug, + BleLogLevel.verbose => UniversalBleLogLevel.verbose, + }; } extension _PlatformConfigExtension on PlatformConfig? { diff --git a/lib/src/universal_ble_platform_interface.dart b/lib/src/universal_ble_platform_interface.dart index d2c75b92..cd4a8c5c 100644 --- a/lib/src/universal_ble_platform_interface.dart +++ b/lib/src/universal_ble_platform_interface.dart @@ -16,15 +16,11 @@ abstract class UniversalBlePlatform { final _scanStreamController = UniversalBleStreamController(); - final bleConnectionUpdateStreamController = - UniversalBleStreamController< - ({String deviceId, bool isConnected, String? error}) - >(); + final bleConnectionUpdateStreamController = UniversalBleStreamController< + ({String deviceId, bool isConnected, String? error})>(); - final _valueStreamController = - UniversalBleStreamController< - ({String deviceId, String characteristicId, Uint8List value}) - >(); + final _valueStreamController = UniversalBleStreamController< + ({String deviceId, String characteristicId, Uint8List value})>(); final _pairStateStreamController = UniversalBleStreamController<({String deviceId, bool isPaired})>(); @@ -32,16 +28,18 @@ abstract class UniversalBlePlatform { /// Send latest availability state upon subscribing late final _availabilityStreamController = UniversalBleStreamController( - initialEvent: getBluetoothAvailabilityState, - ); + initialEvent: getAvailabilityState, + ); - Future getBluetoothAvailabilityState(); + Future getAvailabilityState(); Future enableBluetooth(); Future disableBluetooth(); - Future hasPermissions({bool withAndroidFineLocation = false}) async { + Future hasPermissions({ + bool withAndroidFineLocation = false, + }) async { return true; } @@ -133,18 +131,15 @@ abstract class UniversalBlePlatform { String characteristicId, ) { characteristicId = BleUuidParser.string(characteristicId); - return _valueStreamController.stream - .where((e) { - return e.deviceId == deviceId && - e.characteristicId == characteristicId; - }) - .map((e) => e.value); + return _valueStreamController.stream.where((e) { + return e.deviceId == deviceId && e.characteristicId == characteristicId; + }).map((e) => e.value); } - Stream pairingStateStream(String deviceId) => _pairStateStreamController - .stream - .where((e) => e.deviceId == deviceId) - .map((e) => e.isPaired); + Stream pairingStateStream(String deviceId) => + _pairStateStreamController.stream + .where((e) => e.deviceId == deviceId) + .map((e) => e.isPaired); /// Update Handlers void updateScanResult(BleDevice bleDevice) { @@ -209,16 +204,15 @@ abstract class UniversalBlePlatform { } // Callback types -typedef OnConnectionChange = - void Function(String deviceId, bool isConnected, String? error); - -typedef OnValueChange = - void Function( - String deviceId, - String characteristicId, - Uint8List value, - int? timestamp, - ); +typedef OnConnectionChange = void Function( + String deviceId, bool isConnected, String? error); + +typedef OnValueChange = void Function( + String deviceId, + String characteristicId, + Uint8List value, + int? timestamp, +); typedef OnScanResult = void Function(BleDevice scanResult); diff --git a/lib/src/universal_ble_web/universal_ble_web.dart b/lib/src/universal_ble_web/universal_ble_web.dart index 2b7f870e..ac5b1d2e 100644 --- a/lib/src/universal_ble_web/universal_ble_web.dart +++ b/lib/src/universal_ble_web/universal_ble_web.dart @@ -75,7 +75,7 @@ class UniversalBleWeb extends UniversalBlePlatform { } @override - Future getBluetoothAvailabilityState() async { + Future getAvailabilityState() async { bool isSupported = FlutterWebBluetooth.instance.isBluetoothApiSupported; if (!isSupported) return AvailabilityState.unsupported; bool isAvailable = await FlutterWebBluetooth.instance.getAvailability(); diff --git a/lib/universal_ble.dart b/lib/universal_ble.dart index aaaeec66..a966d263 100644 --- a/lib/universal_ble.dart +++ b/lib/universal_ble.dart @@ -3,6 +3,8 @@ library; export 'package:universal_ble/src/universal_ble_exceptions.dart'; export 'package:universal_ble/src/universal_ble_platform_interface.dart'; export 'package:universal_ble/src/universal_ble.dart'; +export 'package:universal_ble/src/universal_ble_peripheral/universal_ble_peripheral.dart'; +export 'package:universal_ble/src/universal_ble_peripheral/universal_ble_peripheral_platform_interface.dart'; export 'package:universal_ble/src/models/model_exports.dart'; export 'package:universal_ble/src/extensions/exports.dart'; export 'package:universal_ble/src/universal_ble_pigeon/universal_ble.g.dart' diff --git a/lib/universal_ble_peripheral.dart b/lib/universal_ble_peripheral.dart new file mode 100644 index 00000000..bd37d01c --- /dev/null +++ b/lib/universal_ble_peripheral.dart @@ -0,0 +1,5 @@ +library; + +export 'package:universal_ble/src/universal_ble_peripheral/universal_ble_peripheral.dart'; +export 'package:universal_ble/src/universal_ble_peripheral/universal_ble_peripheral_platform_interface.dart'; +export 'package:universal_ble/src/models/peripheral/peripheral_exports.dart'; diff --git a/pigeon/universal_ble.dart b/pigeon/universal_ble.dart index aa92a36b..694e835b 100644 --- a/pigeon/universal_ble.dart +++ b/pigeon/universal_ble.dart @@ -239,6 +239,149 @@ class UniversalManufacturerData { }); } +class PeripheralService { + String uuid; + bool primary; + List characteristics; + PeripheralService(this.uuid, this.primary, this.characteristics); +} + +class PeripheralCharacteristic { + String uuid; + List properties; + List permissions; + List? descriptors; + Uint8List? value; + + PeripheralCharacteristic( + this.uuid, + this.properties, + this.permissions, + this.descriptors, + this.value, + ); +} + +class PeripheralDescriptor { + String uuid; + Uint8List? value; + List? permissions; + PeripheralDescriptor(this.uuid, this.value, this.permissions); +} + +class PeripheralReadRequestResult { + Uint8List value; + int? offset; + int? status; + PeripheralReadRequestResult({required this.value, this.offset, this.status}); +} + +class PeripheralWriteRequestResult { + Uint8List? value; + int? offset; + int? status; + PeripheralWriteRequestResult({this.value, this.offset, this.status}); +} + +class PeripheralManufacturerData { + int manufacturerId; + Uint8List data; + PeripheralManufacturerData( + {required this.manufacturerId, required this.data}); +} + +enum PeripheralReadinessState { + unknown, + ready, + bluetoothOff, + unauthorized, + unsupported, +} + +enum PeripheralAdvertisingState { + idle, + starting, + advertising, + stopping, + error, +} + +/// Flutter -> Native (peripheral) +@HostApi() +abstract class UniversalBlePeripheralChannel { + PeripheralAdvertisingState getAdvertisingState(); + PeripheralReadinessState getReadinessState(); + void stopAdvertising(); + void addService(PeripheralService service); + void removeService(String serviceId); + void clearServices(); + List getServices(); + void startAdvertising( + List services, + String? localName, + int? timeout, + PeripheralManufacturerData? manufacturerData, + bool addManufacturerDataInScanResponse, + ); + void updateCharacteristic( + String characteristicId, + Uint8List value, + String? deviceId, + ); + + /// Returns peripheral-client device ids currently subscribed to [characteristicId] + /// (e.g. HID report characteristic). Used to restore app state after restart. + List getSubscribedClients(String characteristicId); + + /// Returns max characteristic notify payload length for a connected client. + int? getMaximumNotifyLength(String deviceId); +} + +/// Native -> Flutter (peripheral) +@FlutterApi() +abstract class UniversalBlePeripheralCallback { + PeripheralReadRequestResult? onReadRequest( + String deviceId, + String characteristicId, + int offset, + Uint8List? value, + ); + + PeripheralWriteRequestResult? onWriteRequest( + String deviceId, + String characteristicId, + int offset, + Uint8List? value, + ); + + PeripheralReadRequestResult? onDescriptorReadRequest( + String deviceId, + String characteristicId, + String descriptorId, + int offset, + Uint8List? value, + ); + + PeripheralWriteRequestResult? onDescriptorWriteRequest( + String deviceId, + String characteristicId, + String descriptorId, + int offset, + Uint8List? value, + ); + + void onCharacteristicSubscriptionChange( + String deviceId, + String characteristicId, + bool isSubscribed, + String? name, + ); + void onAdvertisingStateChange(PeripheralAdvertisingState state, String? error); + void onServiceAdded(String serviceId, String? error); + void onMtuChange(String deviceId, int mtu); + void onConnectionStateChange(String deviceId, bool connected); +} + /// Unified error codes for all platforms enum UniversalBleErrorCode { // General errors diff --git a/pubspec.yaml b/pubspec.yaml index 487daff9..e730795a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: universal_ble description: A cross-platform (Android/iOS/macOS/Windows/Linux/Web) Bluetooth Low Energy (BLE) plugin for Flutter -version: 1.3.0 +version: 2.0.0 homepage: https://navideck.com repository: https://github.com/Navideck/universal_ble issue_tracker: https://github.com/Navideck/universal_ble/issues diff --git a/test/universal_ble_peripheral_test.dart b/test/universal_ble_peripheral_test.dart new file mode 100644 index 00000000..fb5e06d7 --- /dev/null +++ b/test/universal_ble_peripheral_test.dart @@ -0,0 +1,271 @@ +import 'dart:typed_data'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:universal_ble/src/universal_ble_peripheral/universal_ble_peripheral_mapper.dart'; +import 'package:universal_ble/universal_ble.dart'; + +class _FakePeripheralPlatform extends UniversalBlePeripheralPlatform { + int disposeCount = 0; + PeripheralServiceId? removedServiceId; + PeripheralCharacteristicId? updatedCharacteristicId; + PeripheralCharacteristicId? subscribedCharacteristicId; + String? maxNotifyLengthDeviceId; + List? advertisedServices; + + @override + Stream get eventStream => + const Stream.empty(); + + @override + void setRequestHandlers(PeripheralRequestHandlers handlers) {} + + @override + Future addService( + BleService service, { + bool primary = true, + Duration? timeout, + }) async {} + + @override + Future clearServices() async {} + + @override + void dispose() { + disposeCount += 1; + } + + @override + Future getAdvertisingState() async => + UniversalBlePeripheralAdvertisingState.idle; + + @override + Future getStaticCapabilities() async => + const UniversalBlePeripheralCapabilities( + supportsPeripheralMode: true, + supportsManufacturerDataInAdvertisement: true, + supportsManufacturerDataInScanResponse: true, + supportsServiceDataInAdvertisement: false, + supportsServiceDataInScanResponse: false, + supportsTargetedCharacteristicUpdate: true, + supportsAdvertisingTimeout: true, + ); + + @override + Future getReadinessState() async => + UniversalBlePeripheralReadinessState.ready; + + @override + Future> getServices() async => const []; + + @override + Future> getSubscribedClients( + PeripheralCharacteristicId characteristicId, + ) async { + subscribedCharacteristicId = characteristicId; + return [characteristicId.value]; + } + + @override + Future getMaximumNotifyLength(String deviceId) async { + maxNotifyLengthDeviceId = deviceId; + return 20; + } + + @override + Future removeService(PeripheralServiceId serviceId) async { + removedServiceId = serviceId; + } + + @override + Future startAdvertising({ + required List services, + String? localName, + int? timeout, + ManufacturerData? manufacturerData, + bool addManufacturerDataInScanResponse = false, + }) async { + advertisedServices = services; + } + + @override + Future stopAdvertising() async {} + + @override + Future updateCharacteristicValue({ + required PeripheralCharacteristicId characteristicId, + required Uint8List value, + PeripheralUpdateTarget target = const PeripheralUpdateAllSubscribed(), + }) async { + updatedCharacteristicId = characteristicId; + } +} + +void main() { + test('peripheral request result models store values', () { + final read = BleReadRequestResult( + value: Uint8List.fromList([1, 2, 3]), + offset: 1, + status: 0, + ); + const write = BleWriteRequestResult(offset: 2, status: 0); + + expect(read.value, Uint8List.fromList([1, 2, 3])); + expect(read.offset, 1); + expect(read.status, 0); + expect(write.offset, 2); + expect(write.status, 0); + }); + + test('peripheral uses shared model types', () { + final service = BleService('180f', [ + BleCharacteristic( + '2a19', + [CharacteristicProperty.read, CharacteristicProperty.notify], + [BleDescriptor('2908')], + ), + ]); + + expect(service.uuid, BleUuidParser.string('180f')); + expect(service.characteristics.single.uuid, BleUuidParser.string('2a19')); + expect( + service.characteristics.single.properties.contains( + CharacteristicProperty.notify, + ), + true, + ); + }); + + test('mapper converts service/characteristic/descriptor to pigeon types', () { + final service = BleService('180f', [ + BleCharacteristic( + '2a19', + [CharacteristicProperty.read, CharacteristicProperty.indicate], + [BleDescriptor('2902')], + ), + ]); + + final mapped = UniversalBlePeripheralMapper.toPigeonService( + service, + primary: true, + ); + + expect(mapped.uuid, BleUuidParser.string('180f')); + expect(mapped.primary, isTrue); + expect(mapped.characteristics, hasLength(1)); + + final characteristic = mapped.characteristics.single; + expect(characteristic.uuid, BleUuidParser.string('2a19')); + expect( + characteristic.properties, + equals([ + CharacteristicProperty.read.index, + CharacteristicProperty.indicate.index, + ]), + ); + expect(characteristic.descriptors, hasLength(1)); + final descriptors = characteristic.descriptors; + expect(descriptors, isNotNull); + expect( + descriptors!.single.uuid, + BleUuidParser.string('2902'), + ); + expect(descriptors.single.value, isNull); + }); + + test('mapper forwards descriptor initial value to pigeon', () { + final service = BleService('1812', [ + BleCharacteristic( + '2a4d', + [CharacteristicProperty.notify], + [ + BleDescriptor( + '2908', + value: Uint8List.fromList([0x00, 0x01]), + ), + ], + ), + ]); + + final mapped = UniversalBlePeripheralMapper.toPigeonService( + service, + primary: true, + ); + + expect( + mapped.characteristics.single.descriptors!.single.value, + Uint8List.fromList([0x00, 0x01]), + ); + }); + + test('mapper converts manufacturer data to pigeon type', () { + final data = ManufacturerData(0x004C, Uint8List.fromList([0x01, 0x02])); + + final mapped = UniversalBlePeripheralMapper.toPigeonManufacturerData(data); + + expect(mapped, isNotNull); + expect(mapped!.manufacturerId, 0x004C); + expect(mapped.data, Uint8List.fromList([0x01, 0x02])); + }); + + test('instance client normalizes service IDs for remove/start', () async { + final fake = _FakePeripheralPlatform(); + final client = UniversalBlePeripheralClient(platform: fake); + + await client.removeService(const PeripheralServiceId('180f')); + await client.startAdvertising( + services: const [PeripheralServiceId('180f')], + ); + + expect( + fake.removedServiceId?.value, + BleUuidParser.string('180f'), + ); + expect( + fake.advertisedServices?.single.value, + BleUuidParser.string('180f'), + ); + }); + + test('static setInstance disposes previous platform', () async { + final first = _FakePeripheralPlatform(); + final second = _FakePeripheralPlatform(); + UniversalBlePeripheral.setInstance(first); + UniversalBlePeripheral.setInstance(second); + + expect(first.disposeCount, 1); + }); + + test('instance client exposes platform capabilities', () async { + final fake = _FakePeripheralPlatform(); + final client = UniversalBlePeripheralClient(platform: fake); + final caps = await client.getStaticCapabilities(); + + expect(caps.supportsPeripheralMode, isTrue); + expect(caps.supportsManufacturerDataInAdvertisement, isTrue); + expect(caps.supportsTargetedCharacteristicUpdate, isTrue); + }); + + test('instance client normalizes characteristic IDs in APIs', () async { + final fake = _FakePeripheralPlatform(); + final client = UniversalBlePeripheralClient(platform: fake); + + await client.updateCharacteristicValue( + characteristicId: const PeripheralCharacteristicId('2a19'), + value: Uint8List.fromList([1]), + ); + await client.getSubscribedClients(const PeripheralCharacteristicId('2a19')); + + expect(fake.updatedCharacteristicId?.value, BleUuidParser.string('2a19')); + expect(fake.subscribedCharacteristicId?.value, BleUuidParser.string('2a19')); + }); + + test('instance client forwards max notify length deviceId', () async { + final fake = _FakePeripheralPlatform(); + final client = UniversalBlePeripheralClient(platform: fake); + + final maxLen = await client.getMaximumNotifyLength('aa:bb:cc'); + + expect(maxLen, 20); + expect(fake.maxNotifyLengthDeviceId, 'aa:bb:cc'); + }); +} diff --git a/test/universal_ble_test_mock.dart b/test/universal_ble_test_mock.dart index e8dd54a7..9676a398 100644 --- a/test/universal_ble_test_mock.dart +++ b/test/universal_ble_test_mock.dart @@ -30,7 +30,7 @@ abstract class UniversalBlePlatformMock extends UniversalBlePlatform { } @override - Future getBluetoothAvailabilityState() { + Future getAvailabilityState() { throw UnimplementedError(); } diff --git a/windows/src/generated/universal_ble.g.cpp b/windows/src/generated/universal_ble.g.cpp index 90171ad3..25b1abea 100644 --- a/windows/src/generated/universal_ble.g.cpp +++ b/windows/src/generated/universal_ble.g.cpp @@ -1,4 +1,4 @@ -// Autogenerated from Pigeon (v26.1.4), do not edit directly. +// Autogenerated from Pigeon (v26.3.3), do not edit directly. // See also: https://pub.dev/packages/pigeon #undef _HAS_EXCEPTIONS @@ -10,16 +10,18 @@ #include #include +#include +#include #include #include #include namespace universal_ble { -using flutter::BasicMessageChannel; -using flutter::CustomEncodableValue; -using flutter::EncodableList; -using flutter::EncodableMap; -using flutter::EncodableValue; +using ::flutter::BasicMessageChannel; +using ::flutter::CustomEncodableValue; +using ::flutter::EncodableList; +using ::flutter::EncodableMap; +using ::flutter::EncodableValue; FlutterError CreateConnectionError(const std::string channel_name) { return FlutterError( @@ -28,6 +30,201 @@ FlutterError CreateConnectionError(const std::string channel_name) { EncodableValue("")); } +namespace { +template +bool PigeonInternalDeepEquals(const T& a, const T& b); + +bool PigeonInternalDeepEquals(const double& a, const double& b); + +template +bool PigeonInternalDeepEquals(const std::vector& a, const std::vector& b); + +template +bool PigeonInternalDeepEquals(const std::map& a, const std::map& b); + +template +bool PigeonInternalDeepEquals(const std::optional& a, const std::optional& b); + +template +bool PigeonInternalDeepEquals(const std::unique_ptr& a, const std::unique_ptr& b); + +bool PigeonInternalDeepEquals(const ::flutter::EncodableValue& a, const ::flutter::EncodableValue& b); + +template +bool PigeonInternalDeepEquals(const T& a, const T& b) { + return a == b; +} + +template +bool PigeonInternalDeepEquals(const std::vector& a, const std::vector& b) { + if (a.size() != b.size()) { + return false; + } + for (size_t i = 0; i < a.size(); ++i) { + if (!PigeonInternalDeepEquals(a[i], b[i])) { + return false; + } + } + return true; +} + +template +bool PigeonInternalDeepEquals(const std::map& a, const std::map& b) { + if (a.size() != b.size()) { + return false; + } + for (const auto& kv : a) { + bool found = false; + for (const auto& b_kv : b) { + if (PigeonInternalDeepEquals(kv.first, b_kv.first)) { + if (PigeonInternalDeepEquals(kv.second, b_kv.second)) { + found = true; + break; + } else { + return false; + } + } + } + if (!found) { + return false; + } + } + return true; +} + +bool PigeonInternalDeepEquals(const double& a, const double& b) { + // Normalize -0.0 to 0.0 and handle NaN equality. + return (a == b) || (std::isnan(a) && std::isnan(b)); +} + +template +bool PigeonInternalDeepEquals(const std::optional& a, const std::optional& b) { + if (!a && !b) { + return true; + } + if (!a || !b) { + return false; + } + return PigeonInternalDeepEquals(*a, *b); +} + +template +bool PigeonInternalDeepEquals(const std::unique_ptr& a, const std::unique_ptr& b) { + if (a.get() == b.get()) { + return true; + } + if (!a || !b) { + return false; + } + return PigeonInternalDeepEquals(*a, *b); +} + +bool PigeonInternalDeepEquals(const ::flutter::EncodableValue& a, const ::flutter::EncodableValue& b) { + if (a.index() != b.index()) { + return false; + } + if (const double* da = std::get_if(&a)) { + return PigeonInternalDeepEquals(*da, std::get(b)); + } else if (const ::flutter::EncodableList* la = std::get_if<::flutter::EncodableList>(&a)) { + return PigeonInternalDeepEquals(*la, std::get<::flutter::EncodableList>(b)); + } else if (const ::flutter::EncodableMap* ma = std::get_if<::flutter::EncodableMap>(&a)) { + return PigeonInternalDeepEquals(*ma, std::get<::flutter::EncodableMap>(b)); + } + return a == b; +} + +template +size_t PigeonInternalDeepHash(const T& v); + +size_t PigeonInternalDeepHash(const double& v); + +template +size_t PigeonInternalDeepHash(const std::vector& v); + +template +size_t PigeonInternalDeepHash(const std::map& v); + +template +size_t PigeonInternalDeepHash(const std::optional& v); + +template +size_t PigeonInternalDeepHash(const std::unique_ptr& v); + +size_t PigeonInternalDeepHash(const ::flutter::EncodableValue& v); + +template +size_t PigeonInternalDeepHash(const T& v) { + return std::hash()(v); +} + +template +size_t PigeonInternalDeepHash(const std::vector& v) { + size_t result = 1; + for (const auto& item : v) { + result = result * 31 + PigeonInternalDeepHash(item); + } + return result; +} + +template +size_t PigeonInternalDeepHash(const std::map& v) { + size_t result = 0; + for (const auto& kv : v) { + result += ((PigeonInternalDeepHash(kv.first) * 31) ^ PigeonInternalDeepHash(kv.second)); + } + return result; +} + +size_t PigeonInternalDeepHash(const double& v) { + if (std::isnan(v)) { + // Normalize NaN to a consistent hash. + return std::hash()(std::numeric_limits::quiet_NaN()); + } + if (v == 0.0) { + // Normalize -0.0 to 0.0 so they have the same hash code. + return std::hash()(0.0); + } + return std::hash()(v); +} + +template +size_t PigeonInternalDeepHash(const std::optional& v) { + return v ? PigeonInternalDeepHash(*v) : 0; +} + +template +size_t PigeonInternalDeepHash(const std::unique_ptr& v) { + return v ? PigeonInternalDeepHash(*v) : 0; +} + +size_t PigeonInternalDeepHash(const ::flutter::EncodableValue& v) { + size_t result = v.index(); + if (const double* dv = std::get_if(&v)) { + result = result * 31 + PigeonInternalDeepHash(*dv); + } else if (const ::flutter::EncodableList* lv = + std::get_if<::flutter::EncodableList>(&v)) { + result = result * 31 + PigeonInternalDeepHash(*lv); + } else if (const ::flutter::EncodableMap* mv = + std::get_if<::flutter::EncodableMap>(&v)) { + result = result * 31 + PigeonInternalDeepHash(*mv); + } else { + std::visit( + [&result](const auto& val) { + using T = std::decay_t; + if constexpr (!std::is_same_v && + !std::is_same_v && + !std::is_same_v && + !std::is_same_v && + !std::is_same_v) { + result = result * 31 + PigeonInternalDeepHash(val); + } + }, + v); + } + return result; +} + +} // namespace // UniversalBleScanResult UniversalBleScanResult::UniversalBleScanResult(const std::string& device_id) @@ -199,6 +396,31 @@ UniversalBleScanResult UniversalBleScanResult::FromEncodableList(const Encodable return decoded; } +bool UniversalBleScanResult::operator==(const UniversalBleScanResult& other) const { + return PigeonInternalDeepEquals(device_id_, other.device_id_) && PigeonInternalDeepEquals(name_, other.name_) && PigeonInternalDeepEquals(is_paired_, other.is_paired_) && PigeonInternalDeepEquals(rssi_, other.rssi_) && PigeonInternalDeepEquals(manufacturer_data_list_, other.manufacturer_data_list_) && PigeonInternalDeepEquals(service_data_, other.service_data_) && PigeonInternalDeepEquals(services_, other.services_) && PigeonInternalDeepEquals(timestamp_, other.timestamp_); +} + +bool UniversalBleScanResult::operator!=(const UniversalBleScanResult& other) const { + return !(*this == other); +} + +size_t UniversalBleScanResult::Hash() const { + size_t result = 1; + result = result * 31 + PigeonInternalDeepHash(device_id_); + result = result * 31 + PigeonInternalDeepHash(name_); + result = result * 31 + PigeonInternalDeepHash(is_paired_); + result = result * 31 + PigeonInternalDeepHash(rssi_); + result = result * 31 + PigeonInternalDeepHash(manufacturer_data_list_); + result = result * 31 + PigeonInternalDeepHash(service_data_); + result = result * 31 + PigeonInternalDeepHash(services_); + result = result * 31 + PigeonInternalDeepHash(timestamp_); + return result; +} + +size_t PigeonInternalDeepHash(const UniversalBleScanResult& v) { + return v.Hash(); +} + // UniversalBleService UniversalBleService::UniversalBleService(const std::string& uuid) @@ -250,6 +472,25 @@ UniversalBleService UniversalBleService::FromEncodableList(const EncodableList& return decoded; } +bool UniversalBleService::operator==(const UniversalBleService& other) const { + return PigeonInternalDeepEquals(uuid_, other.uuid_) && PigeonInternalDeepEquals(characteristics_, other.characteristics_); +} + +bool UniversalBleService::operator!=(const UniversalBleService& other) const { + return !(*this == other); +} + +size_t UniversalBleService::Hash() const { + size_t result = 1; + result = result * 31 + PigeonInternalDeepHash(uuid_); + result = result * 31 + PigeonInternalDeepHash(characteristics_); + return result; +} + +size_t PigeonInternalDeepHash(const UniversalBleService& v) { + return v.Hash(); +} + // UniversalBleCharacteristic UniversalBleCharacteristic::UniversalBleCharacteristic( @@ -304,6 +545,26 @@ UniversalBleCharacteristic UniversalBleCharacteristic::FromEncodableList(const E return decoded; } +bool UniversalBleCharacteristic::operator==(const UniversalBleCharacteristic& other) const { + return PigeonInternalDeepEquals(uuid_, other.uuid_) && PigeonInternalDeepEquals(properties_, other.properties_) && PigeonInternalDeepEquals(descriptors_, other.descriptors_); +} + +bool UniversalBleCharacteristic::operator!=(const UniversalBleCharacteristic& other) const { + return !(*this == other); +} + +size_t UniversalBleCharacteristic::Hash() const { + size_t result = 1; + result = result * 31 + PigeonInternalDeepHash(uuid_); + result = result * 31 + PigeonInternalDeepHash(properties_); + result = result * 31 + PigeonInternalDeepHash(descriptors_); + return result; +} + +size_t PigeonInternalDeepHash(const UniversalBleCharacteristic& v) { + return v.Hash(); +} + // UniversalBleDescriptor UniversalBleDescriptor::UniversalBleDescriptor(const std::string& uuid) @@ -331,6 +592,24 @@ UniversalBleDescriptor UniversalBleDescriptor::FromEncodableList(const Encodable return decoded; } +bool UniversalBleDescriptor::operator==(const UniversalBleDescriptor& other) const { + return PigeonInternalDeepEquals(uuid_, other.uuid_); +} + +bool UniversalBleDescriptor::operator!=(const UniversalBleDescriptor& other) const { + return !(*this == other); +} + +size_t UniversalBleDescriptor::Hash() const { + size_t result = 1; + result = result * 31 + PigeonInternalDeepHash(uuid_); + return result; +} + +size_t PigeonInternalDeepHash(const UniversalBleDescriptor& v) { + return v.Hash(); +} + // AndroidOptions AndroidOptions::AndroidOptions() {} @@ -408,6 +687,26 @@ AndroidOptions AndroidOptions::FromEncodableList(const EncodableList& list) { return decoded; } +bool AndroidOptions::operator==(const AndroidOptions& other) const { + return PigeonInternalDeepEquals(request_location_permission_, other.request_location_permission_) && PigeonInternalDeepEquals(scan_mode_, other.scan_mode_) && PigeonInternalDeepEquals(report_delay_millis_, other.report_delay_millis_); +} + +bool AndroidOptions::operator!=(const AndroidOptions& other) const { + return !(*this == other); +} + +size_t AndroidOptions::Hash() const { + size_t result = 1; + result = result * 31 + PigeonInternalDeepHash(request_location_permission_); + result = result * 31 + PigeonInternalDeepHash(scan_mode_); + result = result * 31 + PigeonInternalDeepHash(report_delay_millis_); + return result; +} + +size_t PigeonInternalDeepHash(const AndroidOptions& v) { + return v.Hash(); +} + // UniversalScanConfig UniversalScanConfig::UniversalScanConfig() {} @@ -452,6 +751,24 @@ UniversalScanConfig UniversalScanConfig::FromEncodableList(const EncodableList& return decoded; } +bool UniversalScanConfig::operator==(const UniversalScanConfig& other) const { + return PigeonInternalDeepEquals(android_, other.android_); +} + +bool UniversalScanConfig::operator!=(const UniversalScanConfig& other) const { + return !(*this == other); +} + +size_t UniversalScanConfig::Hash() const { + size_t result = 1; + result = result * 31 + PigeonInternalDeepHash(android_); + return result; +} + +size_t PigeonInternalDeepHash(const UniversalScanConfig& v) { + return v.Hash(); +} + // UniversalScanFilter UniversalScanFilter::UniversalScanFilter( @@ -506,6 +823,26 @@ UniversalScanFilter UniversalScanFilter::FromEncodableList(const EncodableList& return decoded; } +bool UniversalScanFilter::operator==(const UniversalScanFilter& other) const { + return PigeonInternalDeepEquals(with_services_, other.with_services_) && PigeonInternalDeepEquals(with_name_prefix_, other.with_name_prefix_) && PigeonInternalDeepEquals(with_manufacturer_data_, other.with_manufacturer_data_); +} + +bool UniversalScanFilter::operator!=(const UniversalScanFilter& other) const { + return !(*this == other); +} + +size_t UniversalScanFilter::Hash() const { + size_t result = 1; + result = result * 31 + PigeonInternalDeepHash(with_services_); + result = result * 31 + PigeonInternalDeepHash(with_name_prefix_); + result = result * 31 + PigeonInternalDeepHash(with_manufacturer_data_); + return result; +} + +size_t PigeonInternalDeepHash(const UniversalScanFilter& v) { + return v.Hash(); +} + // UniversalManufacturerDataFilter UniversalManufacturerDataFilter::UniversalManufacturerDataFilter(int64_t company_identifier) @@ -577,6 +914,26 @@ UniversalManufacturerDataFilter UniversalManufacturerDataFilter::FromEncodableLi return decoded; } +bool UniversalManufacturerDataFilter::operator==(const UniversalManufacturerDataFilter& other) const { + return PigeonInternalDeepEquals(company_identifier_, other.company_identifier_) && PigeonInternalDeepEquals(data_, other.data_) && PigeonInternalDeepEquals(mask_, other.mask_); +} + +bool UniversalManufacturerDataFilter::operator!=(const UniversalManufacturerDataFilter& other) const { + return !(*this == other); +} + +size_t UniversalManufacturerDataFilter::Hash() const { + size_t result = 1; + result = result * 31 + PigeonInternalDeepHash(company_identifier_); + result = result * 31 + PigeonInternalDeepHash(data_); + result = result * 31 + PigeonInternalDeepHash(mask_); + return result; +} + +size_t PigeonInternalDeepHash(const UniversalManufacturerDataFilter& v) { + return v.Hash(); +} + // UniversalManufacturerData UniversalManufacturerData::UniversalManufacturerData( @@ -618,152 +975,1085 @@ UniversalManufacturerData UniversalManufacturerData::FromEncodableList(const Enc return decoded; } +bool UniversalManufacturerData::operator==(const UniversalManufacturerData& other) const { + return PigeonInternalDeepEquals(company_identifier_, other.company_identifier_) && PigeonInternalDeepEquals(data_, other.data_); +} -PigeonInternalCodecSerializer::PigeonInternalCodecSerializer() {} +bool UniversalManufacturerData::operator!=(const UniversalManufacturerData& other) const { + return !(*this == other); +} -EncodableValue PigeonInternalCodecSerializer::ReadValueOfType( - uint8_t type, - flutter::ByteStreamReader* stream) const { - switch (type) { - case 129: { - const auto& encodable_enum_arg = ReadValue(stream); - const int64_t enum_arg_value = encodable_enum_arg.IsNull() ? 0 : encodable_enum_arg.LongValue(); - return encodable_enum_arg.IsNull() ? EncodableValue() : CustomEncodableValue(static_cast(enum_arg_value)); - } - case 130: { - const auto& encodable_enum_arg = ReadValue(stream); - const int64_t enum_arg_value = encodable_enum_arg.IsNull() ? 0 : encodable_enum_arg.LongValue(); - return encodable_enum_arg.IsNull() ? EncodableValue() : CustomEncodableValue(static_cast(enum_arg_value)); - } - case 131: { - const auto& encodable_enum_arg = ReadValue(stream); - const int64_t enum_arg_value = encodable_enum_arg.IsNull() ? 0 : encodable_enum_arg.LongValue(); - return encodable_enum_arg.IsNull() ? EncodableValue() : CustomEncodableValue(static_cast(enum_arg_value)); - } - case 132: { - return CustomEncodableValue(UniversalBleScanResult::FromEncodableList(std::get(ReadValue(stream)))); - } - case 133: { - return CustomEncodableValue(UniversalBleService::FromEncodableList(std::get(ReadValue(stream)))); - } - case 134: { - return CustomEncodableValue(UniversalBleCharacteristic::FromEncodableList(std::get(ReadValue(stream)))); - } - case 135: { - return CustomEncodableValue(UniversalBleDescriptor::FromEncodableList(std::get(ReadValue(stream)))); - } - case 136: { - return CustomEncodableValue(AndroidOptions::FromEncodableList(std::get(ReadValue(stream)))); - } - case 137: { - return CustomEncodableValue(UniversalScanConfig::FromEncodableList(std::get(ReadValue(stream)))); - } - case 138: { - return CustomEncodableValue(UniversalScanFilter::FromEncodableList(std::get(ReadValue(stream)))); - } - case 139: { - return CustomEncodableValue(UniversalManufacturerDataFilter::FromEncodableList(std::get(ReadValue(stream)))); - } - case 140: { - return CustomEncodableValue(UniversalManufacturerData::FromEncodableList(std::get(ReadValue(stream)))); - } - default: - return flutter::StandardCodecSerializer::ReadValueOfType(type, stream); - } +size_t UniversalManufacturerData::Hash() const { + size_t result = 1; + result = result * 31 + PigeonInternalDeepHash(company_identifier_); + result = result * 31 + PigeonInternalDeepHash(data_); + return result; } -void PigeonInternalCodecSerializer::WriteValue( - const EncodableValue& value, - flutter::ByteStreamWriter* stream) const { - if (const CustomEncodableValue* custom_value = std::get_if(&value)) { - if (custom_value->type() == typeid(UniversalBleLogLevel)) { - stream->WriteByte(129); - WriteValue(EncodableValue(static_cast(std::any_cast(*custom_value))), stream); - return; - } - if (custom_value->type() == typeid(AndroidScanMode)) { - stream->WriteByte(130); - WriteValue(EncodableValue(static_cast(std::any_cast(*custom_value))), stream); - return; - } - if (custom_value->type() == typeid(UniversalBleErrorCode)) { - stream->WriteByte(131); - WriteValue(EncodableValue(static_cast(std::any_cast(*custom_value))), stream); - return; - } - if (custom_value->type() == typeid(UniversalBleScanResult)) { - stream->WriteByte(132); - WriteValue(EncodableValue(std::any_cast(*custom_value).ToEncodableList()), stream); - return; - } - if (custom_value->type() == typeid(UniversalBleService)) { - stream->WriteByte(133); - WriteValue(EncodableValue(std::any_cast(*custom_value).ToEncodableList()), stream); - return; - } - if (custom_value->type() == typeid(UniversalBleCharacteristic)) { - stream->WriteByte(134); - WriteValue(EncodableValue(std::any_cast(*custom_value).ToEncodableList()), stream); - return; - } - if (custom_value->type() == typeid(UniversalBleDescriptor)) { - stream->WriteByte(135); - WriteValue(EncodableValue(std::any_cast(*custom_value).ToEncodableList()), stream); - return; - } - if (custom_value->type() == typeid(AndroidOptions)) { - stream->WriteByte(136); - WriteValue(EncodableValue(std::any_cast(*custom_value).ToEncodableList()), stream); - return; - } - if (custom_value->type() == typeid(UniversalScanConfig)) { - stream->WriteByte(137); - WriteValue(EncodableValue(std::any_cast(*custom_value).ToEncodableList()), stream); - return; - } - if (custom_value->type() == typeid(UniversalScanFilter)) { - stream->WriteByte(138); - WriteValue(EncodableValue(std::any_cast(*custom_value).ToEncodableList()), stream); - return; - } - if (custom_value->type() == typeid(UniversalManufacturerDataFilter)) { - stream->WriteByte(139); - WriteValue(EncodableValue(std::any_cast(*custom_value).ToEncodableList()), stream); - return; - } - if (custom_value->type() == typeid(UniversalManufacturerData)) { - stream->WriteByte(140); - WriteValue(EncodableValue(std::any_cast(*custom_value).ToEncodableList()), stream); - return; - } - } - flutter::StandardCodecSerializer::WriteValue(value, stream); +size_t PigeonInternalDeepHash(const UniversalManufacturerData& v) { + return v.Hash(); } -/// The codec used by UniversalBlePlatformChannel. -const flutter::StandardMessageCodec& UniversalBlePlatformChannel::GetCodec() { - return flutter::StandardMessageCodec::GetInstance(&PigeonInternalCodecSerializer::GetInstance()); +// PeripheralService + +PeripheralService::PeripheralService( + const std::string& uuid, + bool primary, + const EncodableList& characteristics) + : uuid_(uuid), + primary_(primary), + characteristics_(characteristics) {} + +const std::string& PeripheralService::uuid() const { + return uuid_; } -// Sets up an instance of `UniversalBlePlatformChannel` to handle messages through the `binary_messenger`. -void UniversalBlePlatformChannel::SetUp( - flutter::BinaryMessenger* binary_messenger, - UniversalBlePlatformChannel* api) { - UniversalBlePlatformChannel::SetUp(binary_messenger, api, ""); +void PeripheralService::set_uuid(std::string_view value_arg) { + uuid_ = value_arg; } -void UniversalBlePlatformChannel::SetUp( - flutter::BinaryMessenger* binary_messenger, - UniversalBlePlatformChannel* api, - const std::string& message_channel_suffix) { - const std::string prepended_suffix = message_channel_suffix.length() > 0 ? std::string(".") + message_channel_suffix : ""; - { - BasicMessageChannel<> channel(binary_messenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.getBluetoothAvailabilityState" + prepended_suffix, &GetCodec()); - if (api != nullptr) { - channel.SetMessageHandler([api](const EncodableValue& message, const flutter::MessageReply& reply) { - try { - api->GetBluetoothAvailabilityState([reply](ErrorOr&& output) { - if (output.has_error()) { + +bool PeripheralService::primary() const { + return primary_; +} + +void PeripheralService::set_primary(bool value_arg) { + primary_ = value_arg; +} + + +const EncodableList& PeripheralService::characteristics() const { + return characteristics_; +} + +void PeripheralService::set_characteristics(const EncodableList& value_arg) { + characteristics_ = value_arg; +} + + +EncodableList PeripheralService::ToEncodableList() const { + EncodableList list; + list.reserve(3); + list.push_back(EncodableValue(uuid_)); + list.push_back(EncodableValue(primary_)); + list.push_back(EncodableValue(characteristics_)); + return list; +} + +PeripheralService PeripheralService::FromEncodableList(const EncodableList& list) { + PeripheralService decoded( + std::get(list[0]), + std::get(list[1]), + std::get(list[2])); + return decoded; +} + +bool PeripheralService::operator==(const PeripheralService& other) const { + return PigeonInternalDeepEquals(uuid_, other.uuid_) && PigeonInternalDeepEquals(primary_, other.primary_) && PigeonInternalDeepEquals(characteristics_, other.characteristics_); +} + +bool PeripheralService::operator!=(const PeripheralService& other) const { + return !(*this == other); +} + +size_t PeripheralService::Hash() const { + size_t result = 1; + result = result * 31 + PigeonInternalDeepHash(uuid_); + result = result * 31 + PigeonInternalDeepHash(primary_); + result = result * 31 + PigeonInternalDeepHash(characteristics_); + return result; +} + +size_t PigeonInternalDeepHash(const PeripheralService& v) { + return v.Hash(); +} + +// PeripheralCharacteristic + +PeripheralCharacteristic::PeripheralCharacteristic( + const std::string& uuid, + const EncodableList& properties, + const EncodableList& permissions) + : uuid_(uuid), + properties_(properties), + permissions_(permissions) {} + +PeripheralCharacteristic::PeripheralCharacteristic( + const std::string& uuid, + const EncodableList& properties, + const EncodableList& permissions, + const EncodableList* descriptors, + const std::vector* value) + : uuid_(uuid), + properties_(properties), + permissions_(permissions), + descriptors_(descriptors ? std::optional(*descriptors) : std::nullopt), + value_(value ? std::optional>(*value) : std::nullopt) {} + +const std::string& PeripheralCharacteristic::uuid() const { + return uuid_; +} + +void PeripheralCharacteristic::set_uuid(std::string_view value_arg) { + uuid_ = value_arg; +} + + +const EncodableList& PeripheralCharacteristic::properties() const { + return properties_; +} + +void PeripheralCharacteristic::set_properties(const EncodableList& value_arg) { + properties_ = value_arg; +} + + +const EncodableList& PeripheralCharacteristic::permissions() const { + return permissions_; +} + +void PeripheralCharacteristic::set_permissions(const EncodableList& value_arg) { + permissions_ = value_arg; +} + + +const EncodableList* PeripheralCharacteristic::descriptors() const { + return descriptors_ ? &(*descriptors_) : nullptr; +} + +void PeripheralCharacteristic::set_descriptors(const EncodableList* value_arg) { + descriptors_ = value_arg ? std::optional(*value_arg) : std::nullopt; +} + +void PeripheralCharacteristic::set_descriptors(const EncodableList& value_arg) { + descriptors_ = value_arg; +} + + +const std::vector* PeripheralCharacteristic::value() const { + return value_ ? &(*value_) : nullptr; +} + +void PeripheralCharacteristic::set_value(const std::vector* value_arg) { + value_ = value_arg ? std::optional>(*value_arg) : std::nullopt; +} + +void PeripheralCharacteristic::set_value(const std::vector& value_arg) { + value_ = value_arg; +} + + +EncodableList PeripheralCharacteristic::ToEncodableList() const { + EncodableList list; + list.reserve(5); + list.push_back(EncodableValue(uuid_)); + list.push_back(EncodableValue(properties_)); + list.push_back(EncodableValue(permissions_)); + list.push_back(descriptors_ ? EncodableValue(*descriptors_) : EncodableValue()); + list.push_back(value_ ? EncodableValue(*value_) : EncodableValue()); + return list; +} + +PeripheralCharacteristic PeripheralCharacteristic::FromEncodableList(const EncodableList& list) { + PeripheralCharacteristic decoded( + std::get(list[0]), + std::get(list[1]), + std::get(list[2])); + auto& encodable_descriptors = list[3]; + if (!encodable_descriptors.IsNull()) { + decoded.set_descriptors(std::get(encodable_descriptors)); + } + auto& encodable_value = list[4]; + if (!encodable_value.IsNull()) { + decoded.set_value(std::get>(encodable_value)); + } + return decoded; +} + +bool PeripheralCharacteristic::operator==(const PeripheralCharacteristic& other) const { + return PigeonInternalDeepEquals(uuid_, other.uuid_) && PigeonInternalDeepEquals(properties_, other.properties_) && PigeonInternalDeepEquals(permissions_, other.permissions_) && PigeonInternalDeepEquals(descriptors_, other.descriptors_) && PigeonInternalDeepEquals(value_, other.value_); +} + +bool PeripheralCharacteristic::operator!=(const PeripheralCharacteristic& other) const { + return !(*this == other); +} + +size_t PeripheralCharacteristic::Hash() const { + size_t result = 1; + result = result * 31 + PigeonInternalDeepHash(uuid_); + result = result * 31 + PigeonInternalDeepHash(properties_); + result = result * 31 + PigeonInternalDeepHash(permissions_); + result = result * 31 + PigeonInternalDeepHash(descriptors_); + result = result * 31 + PigeonInternalDeepHash(value_); + return result; +} + +size_t PigeonInternalDeepHash(const PeripheralCharacteristic& v) { + return v.Hash(); +} + +// PeripheralDescriptor + +PeripheralDescriptor::PeripheralDescriptor(const std::string& uuid) + : uuid_(uuid) {} + +PeripheralDescriptor::PeripheralDescriptor( + const std::string& uuid, + const std::vector* value, + const EncodableList* permissions) + : uuid_(uuid), + value_(value ? std::optional>(*value) : std::nullopt), + permissions_(permissions ? std::optional(*permissions) : std::nullopt) {} + +const std::string& PeripheralDescriptor::uuid() const { + return uuid_; +} + +void PeripheralDescriptor::set_uuid(std::string_view value_arg) { + uuid_ = value_arg; +} + + +const std::vector* PeripheralDescriptor::value() const { + return value_ ? &(*value_) : nullptr; +} + +void PeripheralDescriptor::set_value(const std::vector* value_arg) { + value_ = value_arg ? std::optional>(*value_arg) : std::nullopt; +} + +void PeripheralDescriptor::set_value(const std::vector& value_arg) { + value_ = value_arg; +} + + +const EncodableList* PeripheralDescriptor::permissions() const { + return permissions_ ? &(*permissions_) : nullptr; +} + +void PeripheralDescriptor::set_permissions(const EncodableList* value_arg) { + permissions_ = value_arg ? std::optional(*value_arg) : std::nullopt; +} + +void PeripheralDescriptor::set_permissions(const EncodableList& value_arg) { + permissions_ = value_arg; +} + + +EncodableList PeripheralDescriptor::ToEncodableList() const { + EncodableList list; + list.reserve(3); + list.push_back(EncodableValue(uuid_)); + list.push_back(value_ ? EncodableValue(*value_) : EncodableValue()); + list.push_back(permissions_ ? EncodableValue(*permissions_) : EncodableValue()); + return list; +} + +PeripheralDescriptor PeripheralDescriptor::FromEncodableList(const EncodableList& list) { + PeripheralDescriptor decoded( + std::get(list[0])); + auto& encodable_value = list[1]; + if (!encodable_value.IsNull()) { + decoded.set_value(std::get>(encodable_value)); + } + auto& encodable_permissions = list[2]; + if (!encodable_permissions.IsNull()) { + decoded.set_permissions(std::get(encodable_permissions)); + } + return decoded; +} + +bool PeripheralDescriptor::operator==(const PeripheralDescriptor& other) const { + return PigeonInternalDeepEquals(uuid_, other.uuid_) && PigeonInternalDeepEquals(value_, other.value_) && PigeonInternalDeepEquals(permissions_, other.permissions_); +} + +bool PeripheralDescriptor::operator!=(const PeripheralDescriptor& other) const { + return !(*this == other); +} + +size_t PeripheralDescriptor::Hash() const { + size_t result = 1; + result = result * 31 + PigeonInternalDeepHash(uuid_); + result = result * 31 + PigeonInternalDeepHash(value_); + result = result * 31 + PigeonInternalDeepHash(permissions_); + return result; +} + +size_t PigeonInternalDeepHash(const PeripheralDescriptor& v) { + return v.Hash(); +} + +// PeripheralReadRequestResult + +PeripheralReadRequestResult::PeripheralReadRequestResult(const std::vector& value) + : value_(value) {} + +PeripheralReadRequestResult::PeripheralReadRequestResult( + const std::vector& value, + const int64_t* offset, + const int64_t* status) + : value_(value), + offset_(offset ? std::optional(*offset) : std::nullopt), + status_(status ? std::optional(*status) : std::nullopt) {} + +const std::vector& PeripheralReadRequestResult::value() const { + return value_; +} + +void PeripheralReadRequestResult::set_value(const std::vector& value_arg) { + value_ = value_arg; +} + + +const int64_t* PeripheralReadRequestResult::offset() const { + return offset_ ? &(*offset_) : nullptr; +} + +void PeripheralReadRequestResult::set_offset(const int64_t* value_arg) { + offset_ = value_arg ? std::optional(*value_arg) : std::nullopt; +} + +void PeripheralReadRequestResult::set_offset(int64_t value_arg) { + offset_ = value_arg; +} + + +const int64_t* PeripheralReadRequestResult::status() const { + return status_ ? &(*status_) : nullptr; +} + +void PeripheralReadRequestResult::set_status(const int64_t* value_arg) { + status_ = value_arg ? std::optional(*value_arg) : std::nullopt; +} + +void PeripheralReadRequestResult::set_status(int64_t value_arg) { + status_ = value_arg; +} + + +EncodableList PeripheralReadRequestResult::ToEncodableList() const { + EncodableList list; + list.reserve(3); + list.push_back(EncodableValue(value_)); + list.push_back(offset_ ? EncodableValue(*offset_) : EncodableValue()); + list.push_back(status_ ? EncodableValue(*status_) : EncodableValue()); + return list; +} + +PeripheralReadRequestResult PeripheralReadRequestResult::FromEncodableList(const EncodableList& list) { + PeripheralReadRequestResult decoded( + std::get>(list[0])); + auto& encodable_offset = list[1]; + if (!encodable_offset.IsNull()) { + decoded.set_offset(std::get(encodable_offset)); + } + auto& encodable_status = list[2]; + if (!encodable_status.IsNull()) { + decoded.set_status(std::get(encodable_status)); + } + return decoded; +} + +bool PeripheralReadRequestResult::operator==(const PeripheralReadRequestResult& other) const { + return PigeonInternalDeepEquals(value_, other.value_) && PigeonInternalDeepEquals(offset_, other.offset_) && PigeonInternalDeepEquals(status_, other.status_); +} + +bool PeripheralReadRequestResult::operator!=(const PeripheralReadRequestResult& other) const { + return !(*this == other); +} + +size_t PeripheralReadRequestResult::Hash() const { + size_t result = 1; + result = result * 31 + PigeonInternalDeepHash(value_); + result = result * 31 + PigeonInternalDeepHash(offset_); + result = result * 31 + PigeonInternalDeepHash(status_); + return result; +} + +size_t PigeonInternalDeepHash(const PeripheralReadRequestResult& v) { + return v.Hash(); +} + +// PeripheralWriteRequestResult + +PeripheralWriteRequestResult::PeripheralWriteRequestResult() {} + +PeripheralWriteRequestResult::PeripheralWriteRequestResult( + const std::vector* value, + const int64_t* offset, + const int64_t* status) + : value_(value ? std::optional>(*value) : std::nullopt), + offset_(offset ? std::optional(*offset) : std::nullopt), + status_(status ? std::optional(*status) : std::nullopt) {} + +const std::vector* PeripheralWriteRequestResult::value() const { + return value_ ? &(*value_) : nullptr; +} + +void PeripheralWriteRequestResult::set_value(const std::vector* value_arg) { + value_ = value_arg ? std::optional>(*value_arg) : std::nullopt; +} + +void PeripheralWriteRequestResult::set_value(const std::vector& value_arg) { + value_ = value_arg; +} + + +const int64_t* PeripheralWriteRequestResult::offset() const { + return offset_ ? &(*offset_) : nullptr; +} + +void PeripheralWriteRequestResult::set_offset(const int64_t* value_arg) { + offset_ = value_arg ? std::optional(*value_arg) : std::nullopt; +} + +void PeripheralWriteRequestResult::set_offset(int64_t value_arg) { + offset_ = value_arg; +} + + +const int64_t* PeripheralWriteRequestResult::status() const { + return status_ ? &(*status_) : nullptr; +} + +void PeripheralWriteRequestResult::set_status(const int64_t* value_arg) { + status_ = value_arg ? std::optional(*value_arg) : std::nullopt; +} + +void PeripheralWriteRequestResult::set_status(int64_t value_arg) { + status_ = value_arg; +} + + +EncodableList PeripheralWriteRequestResult::ToEncodableList() const { + EncodableList list; + list.reserve(3); + list.push_back(value_ ? EncodableValue(*value_) : EncodableValue()); + list.push_back(offset_ ? EncodableValue(*offset_) : EncodableValue()); + list.push_back(status_ ? EncodableValue(*status_) : EncodableValue()); + return list; +} + +PeripheralWriteRequestResult PeripheralWriteRequestResult::FromEncodableList(const EncodableList& list) { + PeripheralWriteRequestResult decoded; + auto& encodable_value = list[0]; + if (!encodable_value.IsNull()) { + decoded.set_value(std::get>(encodable_value)); + } + auto& encodable_offset = list[1]; + if (!encodable_offset.IsNull()) { + decoded.set_offset(std::get(encodable_offset)); + } + auto& encodable_status = list[2]; + if (!encodable_status.IsNull()) { + decoded.set_status(std::get(encodable_status)); + } + return decoded; +} + +bool PeripheralWriteRequestResult::operator==(const PeripheralWriteRequestResult& other) const { + return PigeonInternalDeepEquals(value_, other.value_) && PigeonInternalDeepEquals(offset_, other.offset_) && PigeonInternalDeepEquals(status_, other.status_); +} + +bool PeripheralWriteRequestResult::operator!=(const PeripheralWriteRequestResult& other) const { + return !(*this == other); +} + +size_t PeripheralWriteRequestResult::Hash() const { + size_t result = 1; + result = result * 31 + PigeonInternalDeepHash(value_); + result = result * 31 + PigeonInternalDeepHash(offset_); + result = result * 31 + PigeonInternalDeepHash(status_); + return result; +} + +size_t PigeonInternalDeepHash(const PeripheralWriteRequestResult& v) { + return v.Hash(); +} + +// PeripheralManufacturerData + +PeripheralManufacturerData::PeripheralManufacturerData( + int64_t manufacturer_id, + const std::vector& data) + : manufacturer_id_(manufacturer_id), + data_(data) {} + +int64_t PeripheralManufacturerData::manufacturer_id() const { + return manufacturer_id_; +} + +void PeripheralManufacturerData::set_manufacturer_id(int64_t value_arg) { + manufacturer_id_ = value_arg; +} + + +const std::vector& PeripheralManufacturerData::data() const { + return data_; +} + +void PeripheralManufacturerData::set_data(const std::vector& value_arg) { + data_ = value_arg; +} + + +EncodableList PeripheralManufacturerData::ToEncodableList() const { + EncodableList list; + list.reserve(2); + list.push_back(EncodableValue(manufacturer_id_)); + list.push_back(EncodableValue(data_)); + return list; +} + +PeripheralManufacturerData PeripheralManufacturerData::FromEncodableList(const EncodableList& list) { + PeripheralManufacturerData decoded( + std::get(list[0]), + std::get>(list[1])); + return decoded; +} + +bool PeripheralManufacturerData::operator==(const PeripheralManufacturerData& other) const { + return PigeonInternalDeepEquals(manufacturer_id_, other.manufacturer_id_) && PigeonInternalDeepEquals(data_, other.data_); +} + +bool PeripheralManufacturerData::operator!=(const PeripheralManufacturerData& other) const { + return !(*this == other); +} + +size_t PeripheralManufacturerData::Hash() const { + size_t result = 1; + result = result * 31 + PigeonInternalDeepHash(manufacturer_id_); + result = result * 31 + PigeonInternalDeepHash(data_); + return result; +} + +size_t PigeonInternalDeepHash(const PeripheralManufacturerData& v) { + return v.Hash(); +} + + +PigeonInternalCodecSerializer::PigeonInternalCodecSerializer() {} + +EncodableValue PigeonInternalCodecSerializer::ReadValueOfType( + uint8_t type, + ::flutter::ByteStreamReader* stream) const { + switch (type) { + case 129: { + const auto& encodable_enum_arg = ReadValue(stream); + const int64_t enum_arg_value = encodable_enum_arg.IsNull() ? 0 : encodable_enum_arg.LongValue(); + return encodable_enum_arg.IsNull() ? EncodableValue() : CustomEncodableValue(static_cast(enum_arg_value)); + } + case 130: { + const auto& encodable_enum_arg = ReadValue(stream); + const int64_t enum_arg_value = encodable_enum_arg.IsNull() ? 0 : encodable_enum_arg.LongValue(); + return encodable_enum_arg.IsNull() ? EncodableValue() : CustomEncodableValue(static_cast(enum_arg_value)); + } + case 131: { + const auto& encodable_enum_arg = ReadValue(stream); + const int64_t enum_arg_value = encodable_enum_arg.IsNull() ? 0 : encodable_enum_arg.LongValue(); + return encodable_enum_arg.IsNull() ? EncodableValue() : CustomEncodableValue(static_cast(enum_arg_value)); + } + case 132: { + const auto& encodable_enum_arg = ReadValue(stream); + const int64_t enum_arg_value = encodable_enum_arg.IsNull() ? 0 : encodable_enum_arg.LongValue(); + return encodable_enum_arg.IsNull() ? EncodableValue() : CustomEncodableValue(static_cast(enum_arg_value)); + } + case 133: { + const auto& encodable_enum_arg = ReadValue(stream); + const int64_t enum_arg_value = encodable_enum_arg.IsNull() ? 0 : encodable_enum_arg.LongValue(); + return encodable_enum_arg.IsNull() ? EncodableValue() : CustomEncodableValue(static_cast(enum_arg_value)); + } + case 134: { + return CustomEncodableValue(UniversalBleScanResult::FromEncodableList(std::get(ReadValue(stream)))); + } + case 135: { + return CustomEncodableValue(UniversalBleService::FromEncodableList(std::get(ReadValue(stream)))); + } + case 136: { + return CustomEncodableValue(UniversalBleCharacteristic::FromEncodableList(std::get(ReadValue(stream)))); + } + case 137: { + return CustomEncodableValue(UniversalBleDescriptor::FromEncodableList(std::get(ReadValue(stream)))); + } + case 138: { + return CustomEncodableValue(AndroidOptions::FromEncodableList(std::get(ReadValue(stream)))); + } + case 139: { + return CustomEncodableValue(UniversalScanConfig::FromEncodableList(std::get(ReadValue(stream)))); + } + case 140: { + return CustomEncodableValue(UniversalScanFilter::FromEncodableList(std::get(ReadValue(stream)))); + } + case 141: { + return CustomEncodableValue(UniversalManufacturerDataFilter::FromEncodableList(std::get(ReadValue(stream)))); + } + case 142: { + return CustomEncodableValue(UniversalManufacturerData::FromEncodableList(std::get(ReadValue(stream)))); + } + case 143: { + return CustomEncodableValue(PeripheralService::FromEncodableList(std::get(ReadValue(stream)))); + } + case 144: { + return CustomEncodableValue(PeripheralCharacteristic::FromEncodableList(std::get(ReadValue(stream)))); + } + case 145: { + return CustomEncodableValue(PeripheralDescriptor::FromEncodableList(std::get(ReadValue(stream)))); + } + case 146: { + return CustomEncodableValue(PeripheralReadRequestResult::FromEncodableList(std::get(ReadValue(stream)))); + } + case 147: { + return CustomEncodableValue(PeripheralWriteRequestResult::FromEncodableList(std::get(ReadValue(stream)))); + } + case 148: { + return CustomEncodableValue(PeripheralManufacturerData::FromEncodableList(std::get(ReadValue(stream)))); + } + default: + return ::flutter::StandardCodecSerializer::ReadValueOfType(type, stream); + } +} + +void PigeonInternalCodecSerializer::WriteValue( + const EncodableValue& value, + ::flutter::ByteStreamWriter* stream) const { + if (const CustomEncodableValue* custom_value = std::get_if(&value)) { + if (custom_value->type() == typeid(UniversalBleLogLevel)) { + stream->WriteByte(129); + WriteValue(EncodableValue(static_cast(std::any_cast(*custom_value))), stream); + return; + } + if (custom_value->type() == typeid(AndroidScanMode)) { + stream->WriteByte(130); + WriteValue(EncodableValue(static_cast(std::any_cast(*custom_value))), stream); + return; + } + if (custom_value->type() == typeid(PeripheralReadinessState)) { + stream->WriteByte(131); + WriteValue(EncodableValue(static_cast(std::any_cast(*custom_value))), stream); + return; + } + if (custom_value->type() == typeid(PeripheralAdvertisingState)) { + stream->WriteByte(132); + WriteValue(EncodableValue(static_cast(std::any_cast(*custom_value))), stream); + return; + } + if (custom_value->type() == typeid(UniversalBleErrorCode)) { + stream->WriteByte(133); + WriteValue(EncodableValue(static_cast(std::any_cast(*custom_value))), stream); + return; + } + if (custom_value->type() == typeid(UniversalBleScanResult)) { + stream->WriteByte(134); + WriteValue(EncodableValue(std::any_cast(*custom_value).ToEncodableList()), stream); + return; + } + if (custom_value->type() == typeid(UniversalBleService)) { + stream->WriteByte(135); + WriteValue(EncodableValue(std::any_cast(*custom_value).ToEncodableList()), stream); + return; + } + if (custom_value->type() == typeid(UniversalBleCharacteristic)) { + stream->WriteByte(136); + WriteValue(EncodableValue(std::any_cast(*custom_value).ToEncodableList()), stream); + return; + } + if (custom_value->type() == typeid(UniversalBleDescriptor)) { + stream->WriteByte(137); + WriteValue(EncodableValue(std::any_cast(*custom_value).ToEncodableList()), stream); + return; + } + if (custom_value->type() == typeid(AndroidOptions)) { + stream->WriteByte(138); + WriteValue(EncodableValue(std::any_cast(*custom_value).ToEncodableList()), stream); + return; + } + if (custom_value->type() == typeid(UniversalScanConfig)) { + stream->WriteByte(139); + WriteValue(EncodableValue(std::any_cast(*custom_value).ToEncodableList()), stream); + return; + } + if (custom_value->type() == typeid(UniversalScanFilter)) { + stream->WriteByte(140); + WriteValue(EncodableValue(std::any_cast(*custom_value).ToEncodableList()), stream); + return; + } + if (custom_value->type() == typeid(UniversalManufacturerDataFilter)) { + stream->WriteByte(141); + WriteValue(EncodableValue(std::any_cast(*custom_value).ToEncodableList()), stream); + return; + } + if (custom_value->type() == typeid(UniversalManufacturerData)) { + stream->WriteByte(142); + WriteValue(EncodableValue(std::any_cast(*custom_value).ToEncodableList()), stream); + return; + } + if (custom_value->type() == typeid(PeripheralService)) { + stream->WriteByte(143); + WriteValue(EncodableValue(std::any_cast(*custom_value).ToEncodableList()), stream); + return; + } + if (custom_value->type() == typeid(PeripheralCharacteristic)) { + stream->WriteByte(144); + WriteValue(EncodableValue(std::any_cast(*custom_value).ToEncodableList()), stream); + return; + } + if (custom_value->type() == typeid(PeripheralDescriptor)) { + stream->WriteByte(145); + WriteValue(EncodableValue(std::any_cast(*custom_value).ToEncodableList()), stream); + return; + } + if (custom_value->type() == typeid(PeripheralReadRequestResult)) { + stream->WriteByte(146); + WriteValue(EncodableValue(std::any_cast(*custom_value).ToEncodableList()), stream); + return; + } + if (custom_value->type() == typeid(PeripheralWriteRequestResult)) { + stream->WriteByte(147); + WriteValue(EncodableValue(std::any_cast(*custom_value).ToEncodableList()), stream); + return; + } + if (custom_value->type() == typeid(PeripheralManufacturerData)) { + stream->WriteByte(148); + WriteValue(EncodableValue(std::any_cast(*custom_value).ToEncodableList()), stream); + return; + } + } + ::flutter::StandardCodecSerializer::WriteValue(value, stream); +} + +/// The codec used by UniversalBlePlatformChannel. +const ::flutter::StandardMessageCodec& UniversalBlePlatformChannel::GetCodec() { + return ::flutter::StandardMessageCodec::GetInstance(&PigeonInternalCodecSerializer::GetInstance()); +} + +// Sets up an instance of `UniversalBlePlatformChannel` to handle messages through the `binary_messenger`. +void UniversalBlePlatformChannel::SetUp( + ::flutter::BinaryMessenger* binary_messenger, + UniversalBlePlatformChannel* api) { + UniversalBlePlatformChannel::SetUp(binary_messenger, api, ""); +} + +void UniversalBlePlatformChannel::SetUp( + ::flutter::BinaryMessenger* binary_messenger, + UniversalBlePlatformChannel* api, + const std::string& message_channel_suffix) { + const std::string prepended_suffix = message_channel_suffix.length() > 0 ? std::string(".") + message_channel_suffix : ""; + { + BasicMessageChannel<> channel(binary_messenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.getBluetoothAvailabilityState" + prepended_suffix, &GetCodec()); + if (api != nullptr) { + channel.SetMessageHandler([api](const EncodableValue& message, const ::flutter::MessageReply& reply) { + try { + api->GetBluetoothAvailabilityState([reply](ErrorOr&& output) { + if (output.has_error()) { + reply(WrapError(output.error())); + return; + } + EncodableList wrapped; + wrapped.push_back(EncodableValue(std::move(output).TakeValue())); + reply(EncodableValue(std::move(wrapped))); + }); + } catch (const std::exception& exception) { + reply(WrapError(exception.what())); + } + }); + } else { + channel.SetMessageHandler(nullptr); + } + } + { + BasicMessageChannel<> channel(binary_messenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.hasPermissions" + prepended_suffix, &GetCodec()); + if (api != nullptr) { + channel.SetMessageHandler([api](const EncodableValue& message, const ::flutter::MessageReply& reply) { + try { + const auto& args = std::get(message); + const auto& encodable_with_android_fine_location_arg = args.at(0); + if (encodable_with_android_fine_location_arg.IsNull()) { + reply(WrapError("with_android_fine_location_arg unexpectedly null.")); + return; + } + const auto& with_android_fine_location_arg = std::get(encodable_with_android_fine_location_arg); + ErrorOr output = api->HasPermissions(with_android_fine_location_arg); + if (output.has_error()) { + reply(WrapError(output.error())); + return; + } + EncodableList wrapped; + wrapped.push_back(EncodableValue(std::move(output).TakeValue())); + reply(EncodableValue(std::move(wrapped))); + } catch (const std::exception& exception) { + reply(WrapError(exception.what())); + } + }); + } else { + channel.SetMessageHandler(nullptr); + } + } + { + BasicMessageChannel<> channel(binary_messenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.requestPermissions" + prepended_suffix, &GetCodec()); + if (api != nullptr) { + channel.SetMessageHandler([api](const EncodableValue& message, const ::flutter::MessageReply& reply) { + try { + const auto& args = std::get(message); + const auto& encodable_with_android_fine_location_arg = args.at(0); + if (encodable_with_android_fine_location_arg.IsNull()) { + reply(WrapError("with_android_fine_location_arg unexpectedly null.")); + return; + } + const auto& with_android_fine_location_arg = std::get(encodable_with_android_fine_location_arg); + api->RequestPermissions(with_android_fine_location_arg, [reply](std::optional&& output) { + if (output.has_value()) { + reply(WrapError(output.value())); + return; + } + EncodableList wrapped; + wrapped.push_back(EncodableValue()); + reply(EncodableValue(std::move(wrapped))); + }); + } catch (const std::exception& exception) { + reply(WrapError(exception.what())); + } + }); + } else { + channel.SetMessageHandler(nullptr); + } + } + { + BasicMessageChannel<> channel(binary_messenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.enableBluetooth" + prepended_suffix, &GetCodec()); + if (api != nullptr) { + channel.SetMessageHandler([api](const EncodableValue& message, const ::flutter::MessageReply& reply) { + try { + api->EnableBluetooth([reply](ErrorOr&& output) { + if (output.has_error()) { + reply(WrapError(output.error())); + return; + } + EncodableList wrapped; + wrapped.push_back(EncodableValue(std::move(output).TakeValue())); + reply(EncodableValue(std::move(wrapped))); + }); + } catch (const std::exception& exception) { + reply(WrapError(exception.what())); + } + }); + } else { + channel.SetMessageHandler(nullptr); + } + } + { + BasicMessageChannel<> channel(binary_messenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.disableBluetooth" + prepended_suffix, &GetCodec()); + if (api != nullptr) { + channel.SetMessageHandler([api](const EncodableValue& message, const ::flutter::MessageReply& reply) { + try { + api->DisableBluetooth([reply](ErrorOr&& output) { + if (output.has_error()) { + reply(WrapError(output.error())); + return; + } + EncodableList wrapped; + wrapped.push_back(EncodableValue(std::move(output).TakeValue())); + reply(EncodableValue(std::move(wrapped))); + }); + } catch (const std::exception& exception) { + reply(WrapError(exception.what())); + } + }); + } else { + channel.SetMessageHandler(nullptr); + } + } + { + BasicMessageChannel<> channel(binary_messenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.startScan" + prepended_suffix, &GetCodec()); + if (api != nullptr) { + channel.SetMessageHandler([api](const EncodableValue& message, const ::flutter::MessageReply& reply) { + try { + const auto& args = std::get(message); + const auto& encodable_filter_arg = args.at(0); + const auto* filter_arg = encodable_filter_arg.IsNull() ? nullptr : &(std::any_cast(std::get(encodable_filter_arg))); + const auto& encodable_config_arg = args.at(1); + const auto* config_arg = encodable_config_arg.IsNull() ? nullptr : &(std::any_cast(std::get(encodable_config_arg))); + std::optional output = api->StartScan(filter_arg, config_arg); + if (output.has_value()) { + reply(WrapError(output.value())); + return; + } + EncodableList wrapped; + wrapped.push_back(EncodableValue()); + reply(EncodableValue(std::move(wrapped))); + } catch (const std::exception& exception) { + reply(WrapError(exception.what())); + } + }); + } else { + channel.SetMessageHandler(nullptr); + } + } + { + BasicMessageChannel<> channel(binary_messenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.stopScan" + prepended_suffix, &GetCodec()); + if (api != nullptr) { + channel.SetMessageHandler([api](const EncodableValue& message, const ::flutter::MessageReply& reply) { + try { + std::optional output = api->StopScan(); + if (output.has_value()) { + reply(WrapError(output.value())); + return; + } + EncodableList wrapped; + wrapped.push_back(EncodableValue()); + reply(EncodableValue(std::move(wrapped))); + } catch (const std::exception& exception) { + reply(WrapError(exception.what())); + } + }); + } else { + channel.SetMessageHandler(nullptr); + } + } + { + BasicMessageChannel<> channel(binary_messenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.isScanning" + prepended_suffix, &GetCodec()); + if (api != nullptr) { + channel.SetMessageHandler([api](const EncodableValue& message, const ::flutter::MessageReply& reply) { + try { + ErrorOr output = api->IsScanning(); + if (output.has_error()) { + reply(WrapError(output.error())); + return; + } + EncodableList wrapped; + wrapped.push_back(EncodableValue(std::move(output).TakeValue())); + reply(EncodableValue(std::move(wrapped))); + } catch (const std::exception& exception) { + reply(WrapError(exception.what())); + } + }); + } else { + channel.SetMessageHandler(nullptr); + } + } + { + BasicMessageChannel<> channel(binary_messenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.connect" + prepended_suffix, &GetCodec()); + if (api != nullptr) { + channel.SetMessageHandler([api](const EncodableValue& message, const ::flutter::MessageReply& reply) { + try { + const auto& args = std::get(message); + const auto& encodable_device_id_arg = args.at(0); + if (encodable_device_id_arg.IsNull()) { + reply(WrapError("device_id_arg unexpectedly null.")); + return; + } + const auto& device_id_arg = std::get(encodable_device_id_arg); + const auto& encodable_auto_connect_arg = args.at(1); + const auto* auto_connect_arg = std::get_if(&encodable_auto_connect_arg); + std::optional output = api->Connect(device_id_arg, auto_connect_arg); + if (output.has_value()) { + reply(WrapError(output.value())); + return; + } + EncodableList wrapped; + wrapped.push_back(EncodableValue()); + reply(EncodableValue(std::move(wrapped))); + } catch (const std::exception& exception) { + reply(WrapError(exception.what())); + } + }); + } else { + channel.SetMessageHandler(nullptr); + } + } + { + BasicMessageChannel<> channel(binary_messenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.disconnect" + prepended_suffix, &GetCodec()); + if (api != nullptr) { + channel.SetMessageHandler([api](const EncodableValue& message, const ::flutter::MessageReply& reply) { + try { + const auto& args = std::get(message); + const auto& encodable_device_id_arg = args.at(0); + if (encodable_device_id_arg.IsNull()) { + reply(WrapError("device_id_arg unexpectedly null.")); + return; + } + const auto& device_id_arg = std::get(encodable_device_id_arg); + std::optional output = api->Disconnect(device_id_arg); + if (output.has_value()) { + reply(WrapError(output.value())); + return; + } + EncodableList wrapped; + wrapped.push_back(EncodableValue()); + reply(EncodableValue(std::move(wrapped))); + } catch (const std::exception& exception) { + reply(WrapError(exception.what())); + } + }); + } else { + channel.SetMessageHandler(nullptr); + } + } + { + BasicMessageChannel<> channel(binary_messenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.setNotifiable" + prepended_suffix, &GetCodec()); + if (api != nullptr) { + channel.SetMessageHandler([api](const EncodableValue& message, const ::flutter::MessageReply& reply) { + try { + const auto& args = std::get(message); + const auto& encodable_device_id_arg = args.at(0); + if (encodable_device_id_arg.IsNull()) { + reply(WrapError("device_id_arg unexpectedly null.")); + return; + } + const auto& device_id_arg = std::get(encodable_device_id_arg); + const auto& encodable_service_arg = args.at(1); + if (encodable_service_arg.IsNull()) { + reply(WrapError("service_arg unexpectedly null.")); + return; + } + const auto& service_arg = std::get(encodable_service_arg); + const auto& encodable_characteristic_arg = args.at(2); + if (encodable_characteristic_arg.IsNull()) { + reply(WrapError("characteristic_arg unexpectedly null.")); + return; + } + const auto& characteristic_arg = std::get(encodable_characteristic_arg); + const auto& encodable_ble_input_property_arg = args.at(3); + if (encodable_ble_input_property_arg.IsNull()) { + reply(WrapError("ble_input_property_arg unexpectedly null.")); + return; + } + const int64_t ble_input_property_arg = encodable_ble_input_property_arg.LongValue(); + api->SetNotifiable(device_id_arg, service_arg, characteristic_arg, ble_input_property_arg, [reply](std::optional&& output) { + if (output.has_value()) { + reply(WrapError(output.value())); + return; + } + EncodableList wrapped; + wrapped.push_back(EncodableValue()); + reply(EncodableValue(std::move(wrapped))); + }); + } catch (const std::exception& exception) { + reply(WrapError(exception.what())); + } + }); + } else { + channel.SetMessageHandler(nullptr); + } + } + { + BasicMessageChannel<> channel(binary_messenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.discoverServices" + prepended_suffix, &GetCodec()); + if (api != nullptr) { + channel.SetMessageHandler([api](const EncodableValue& message, const ::flutter::MessageReply& reply) { + try { + const auto& args = std::get(message); + const auto& encodable_device_id_arg = args.at(0); + if (encodable_device_id_arg.IsNull()) { + reply(WrapError("device_id_arg unexpectedly null.")); + return; + } + const auto& device_id_arg = std::get(encodable_device_id_arg); + const auto& encodable_with_descriptors_arg = args.at(1); + if (encodable_with_descriptors_arg.IsNull()) { + reply(WrapError("with_descriptors_arg unexpectedly null.")); + return; + } + const auto& with_descriptors_arg = std::get(encodable_with_descriptors_arg); + api->DiscoverServices(device_id_arg, with_descriptors_arg, [reply](ErrorOr&& output) { + if (output.has_error()) { reply(WrapError(output.error())); return; } @@ -780,25 +2070,38 @@ void UniversalBlePlatformChannel::SetUp( } } { - BasicMessageChannel<> channel(binary_messenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.hasPermissions" + prepended_suffix, &GetCodec()); + BasicMessageChannel<> channel(binary_messenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.readValue" + prepended_suffix, &GetCodec()); if (api != nullptr) { - channel.SetMessageHandler([api](const EncodableValue& message, const flutter::MessageReply& reply) { + channel.SetMessageHandler([api](const EncodableValue& message, const ::flutter::MessageReply& reply) { try { const auto& args = std::get(message); - const auto& encodable_with_android_fine_location_arg = args.at(0); - if (encodable_with_android_fine_location_arg.IsNull()) { - reply(WrapError("with_android_fine_location_arg unexpectedly null.")); + const auto& encodable_device_id_arg = args.at(0); + if (encodable_device_id_arg.IsNull()) { + reply(WrapError("device_id_arg unexpectedly null.")); return; } - const auto& with_android_fine_location_arg = std::get(encodable_with_android_fine_location_arg); - ErrorOr output = api->HasPermissions(with_android_fine_location_arg); - if (output.has_error()) { - reply(WrapError(output.error())); + const auto& device_id_arg = std::get(encodable_device_id_arg); + const auto& encodable_service_arg = args.at(1); + if (encodable_service_arg.IsNull()) { + reply(WrapError("service_arg unexpectedly null.")); return; } - EncodableList wrapped; - wrapped.push_back(EncodableValue(std::move(output).TakeValue())); - reply(EncodableValue(std::move(wrapped))); + const auto& service_arg = std::get(encodable_service_arg); + const auto& encodable_characteristic_arg = args.at(2); + if (encodable_characteristic_arg.IsNull()) { + reply(WrapError("characteristic_arg unexpectedly null.")); + return; + } + const auto& characteristic_arg = std::get(encodable_characteristic_arg); + api->ReadValue(device_id_arg, service_arg, characteristic_arg, [reply](ErrorOr>&& output) { + if (output.has_error()) { + reply(WrapError(output.error())); + return; + } + EncodableList wrapped; + wrapped.push_back(EncodableValue(std::move(output).TakeValue())); + reply(EncodableValue(std::move(wrapped))); + }); } catch (const std::exception& exception) { reply(WrapError(exception.what())); } @@ -808,24 +2111,198 @@ void UniversalBlePlatformChannel::SetUp( } } { - BasicMessageChannel<> channel(binary_messenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.requestPermissions" + prepended_suffix, &GetCodec()); + BasicMessageChannel<> channel(binary_messenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.requestMtu" + prepended_suffix, &GetCodec()); if (api != nullptr) { - channel.SetMessageHandler([api](const EncodableValue& message, const flutter::MessageReply& reply) { + channel.SetMessageHandler([api](const EncodableValue& message, const ::flutter::MessageReply& reply) { try { const auto& args = std::get(message); - const auto& encodable_with_android_fine_location_arg = args.at(0); - if (encodable_with_android_fine_location_arg.IsNull()) { - reply(WrapError("with_android_fine_location_arg unexpectedly null.")); + const auto& encodable_device_id_arg = args.at(0); + if (encodable_device_id_arg.IsNull()) { + reply(WrapError("device_id_arg unexpectedly null.")); return; } - const auto& with_android_fine_location_arg = std::get(encodable_with_android_fine_location_arg); - api->RequestPermissions(with_android_fine_location_arg, [reply](std::optional&& output) { + const auto& device_id_arg = std::get(encodable_device_id_arg); + const auto& encodable_expected_mtu_arg = args.at(1); + if (encodable_expected_mtu_arg.IsNull()) { + reply(WrapError("expected_mtu_arg unexpectedly null.")); + return; + } + const int64_t expected_mtu_arg = encodable_expected_mtu_arg.LongValue(); + api->RequestMtu(device_id_arg, expected_mtu_arg, [reply](ErrorOr&& output) { + if (output.has_error()) { + reply(WrapError(output.error())); + return; + } + EncodableList wrapped; + wrapped.push_back(EncodableValue(std::move(output).TakeValue())); + reply(EncodableValue(std::move(wrapped))); + }); + } catch (const std::exception& exception) { + reply(WrapError(exception.what())); + } + }); + } else { + channel.SetMessageHandler(nullptr); + } + } + { + BasicMessageChannel<> channel(binary_messenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.writeValue" + prepended_suffix, &GetCodec()); + if (api != nullptr) { + channel.SetMessageHandler([api](const EncodableValue& message, const ::flutter::MessageReply& reply) { + try { + const auto& args = std::get(message); + const auto& encodable_device_id_arg = args.at(0); + if (encodable_device_id_arg.IsNull()) { + reply(WrapError("device_id_arg unexpectedly null.")); + return; + } + const auto& device_id_arg = std::get(encodable_device_id_arg); + const auto& encodable_service_arg = args.at(1); + if (encodable_service_arg.IsNull()) { + reply(WrapError("service_arg unexpectedly null.")); + return; + } + const auto& service_arg = std::get(encodable_service_arg); + const auto& encodable_characteristic_arg = args.at(2); + if (encodable_characteristic_arg.IsNull()) { + reply(WrapError("characteristic_arg unexpectedly null.")); + return; + } + const auto& characteristic_arg = std::get(encodable_characteristic_arg); + const auto& encodable_value_arg = args.at(3); + if (encodable_value_arg.IsNull()) { + reply(WrapError("value_arg unexpectedly null.")); + return; + } + const auto& value_arg = std::get>(encodable_value_arg); + const auto& encodable_ble_output_property_arg = args.at(4); + if (encodable_ble_output_property_arg.IsNull()) { + reply(WrapError("ble_output_property_arg unexpectedly null.")); + return; + } + const int64_t ble_output_property_arg = encodable_ble_output_property_arg.LongValue(); + api->WriteValue(device_id_arg, service_arg, characteristic_arg, value_arg, ble_output_property_arg, [reply](std::optional&& output) { if (output.has_value()) { reply(WrapError(output.value())); return; } EncodableList wrapped; - wrapped.push_back(EncodableValue()); + wrapped.push_back(EncodableValue()); + reply(EncodableValue(std::move(wrapped))); + }); + } catch (const std::exception& exception) { + reply(WrapError(exception.what())); + } + }); + } else { + channel.SetMessageHandler(nullptr); + } + } + { + BasicMessageChannel<> channel(binary_messenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.isPaired" + prepended_suffix, &GetCodec()); + if (api != nullptr) { + channel.SetMessageHandler([api](const EncodableValue& message, const ::flutter::MessageReply& reply) { + try { + const auto& args = std::get(message); + const auto& encodable_device_id_arg = args.at(0); + if (encodable_device_id_arg.IsNull()) { + reply(WrapError("device_id_arg unexpectedly null.")); + return; + } + const auto& device_id_arg = std::get(encodable_device_id_arg); + api->IsPaired(device_id_arg, [reply](ErrorOr&& output) { + if (output.has_error()) { + reply(WrapError(output.error())); + return; + } + EncodableList wrapped; + wrapped.push_back(EncodableValue(std::move(output).TakeValue())); + reply(EncodableValue(std::move(wrapped))); + }); + } catch (const std::exception& exception) { + reply(WrapError(exception.what())); + } + }); + } else { + channel.SetMessageHandler(nullptr); + } + } + { + BasicMessageChannel<> channel(binary_messenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.pair" + prepended_suffix, &GetCodec()); + if (api != nullptr) { + channel.SetMessageHandler([api](const EncodableValue& message, const ::flutter::MessageReply& reply) { + try { + const auto& args = std::get(message); + const auto& encodable_device_id_arg = args.at(0); + if (encodable_device_id_arg.IsNull()) { + reply(WrapError("device_id_arg unexpectedly null.")); + return; + } + const auto& device_id_arg = std::get(encodable_device_id_arg); + api->Pair(device_id_arg, [reply](ErrorOr&& output) { + if (output.has_error()) { + reply(WrapError(output.error())); + return; + } + EncodableList wrapped; + wrapped.push_back(EncodableValue(std::move(output).TakeValue())); + reply(EncodableValue(std::move(wrapped))); + }); + } catch (const std::exception& exception) { + reply(WrapError(exception.what())); + } + }); + } else { + channel.SetMessageHandler(nullptr); + } + } + { + BasicMessageChannel<> channel(binary_messenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.unPair" + prepended_suffix, &GetCodec()); + if (api != nullptr) { + channel.SetMessageHandler([api](const EncodableValue& message, const ::flutter::MessageReply& reply) { + try { + const auto& args = std::get(message); + const auto& encodable_device_id_arg = args.at(0); + if (encodable_device_id_arg.IsNull()) { + reply(WrapError("device_id_arg unexpectedly null.")); + return; + } + const auto& device_id_arg = std::get(encodable_device_id_arg); + std::optional output = api->UnPair(device_id_arg); + if (output.has_value()) { + reply(WrapError(output.value())); + return; + } + EncodableList wrapped; + wrapped.push_back(EncodableValue()); + reply(EncodableValue(std::move(wrapped))); + } catch (const std::exception& exception) { + reply(WrapError(exception.what())); + } + }); + } else { + channel.SetMessageHandler(nullptr); + } + } + { + BasicMessageChannel<> channel(binary_messenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.getSystemDevices" + prepended_suffix, &GetCodec()); + if (api != nullptr) { + channel.SetMessageHandler([api](const EncodableValue& message, const ::flutter::MessageReply& reply) { + try { + const auto& args = std::get(message); + const auto& encodable_with_services_arg = args.at(0); + if (encodable_with_services_arg.IsNull()) { + reply(WrapError("with_services_arg unexpectedly null.")); + return; + } + const auto& with_services_arg = std::get(encodable_with_services_arg); + api->GetSystemDevices(with_services_arg, [reply](ErrorOr&& output) { + if (output.has_error()) { + reply(WrapError(output.error())); + return; + } + EncodableList wrapped; + wrapped.push_back(EncodableValue(std::move(output).TakeValue())); reply(EncodableValue(std::move(wrapped))); }); } catch (const std::exception& exception) { @@ -837,19 +2314,25 @@ void UniversalBlePlatformChannel::SetUp( } } { - BasicMessageChannel<> channel(binary_messenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.enableBluetooth" + prepended_suffix, &GetCodec()); + BasicMessageChannel<> channel(binary_messenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.getConnectionState" + prepended_suffix, &GetCodec()); if (api != nullptr) { - channel.SetMessageHandler([api](const EncodableValue& message, const flutter::MessageReply& reply) { + channel.SetMessageHandler([api](const EncodableValue& message, const ::flutter::MessageReply& reply) { try { - api->EnableBluetooth([reply](ErrorOr&& output) { - if (output.has_error()) { - reply(WrapError(output.error())); - return; - } - EncodableList wrapped; - wrapped.push_back(EncodableValue(std::move(output).TakeValue())); - reply(EncodableValue(std::move(wrapped))); - }); + const auto& args = std::get(message); + const auto& encodable_device_id_arg = args.at(0); + if (encodable_device_id_arg.IsNull()) { + reply(WrapError("device_id_arg unexpectedly null.")); + return; + } + const auto& device_id_arg = std::get(encodable_device_id_arg); + ErrorOr output = api->GetConnectionState(device_id_arg); + if (output.has_error()) { + reply(WrapError(output.error())); + return; + } + EncodableList wrapped; + wrapped.push_back(EncodableValue(std::move(output).TakeValue())); + reply(EncodableValue(std::move(wrapped))); } catch (const std::exception& exception) { reply(WrapError(exception.what())); } @@ -859,11 +2342,18 @@ void UniversalBlePlatformChannel::SetUp( } } { - BasicMessageChannel<> channel(binary_messenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.disableBluetooth" + prepended_suffix, &GetCodec()); + BasicMessageChannel<> channel(binary_messenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.readRssi" + prepended_suffix, &GetCodec()); if (api != nullptr) { - channel.SetMessageHandler([api](const EncodableValue& message, const flutter::MessageReply& reply) { + channel.SetMessageHandler([api](const EncodableValue& message, const ::flutter::MessageReply& reply) { try { - api->DisableBluetooth([reply](ErrorOr&& output) { + const auto& args = std::get(message); + const auto& encodable_device_id_arg = args.at(0); + if (encodable_device_id_arg.IsNull()) { + reply(WrapError("device_id_arg unexpectedly null.")); + return; + } + const auto& device_id_arg = std::get(encodable_device_id_arg); + api->ReadRssi(device_id_arg, [reply](ErrorOr&& output) { if (output.has_error()) { reply(WrapError(output.error())); return; @@ -881,23 +2371,32 @@ void UniversalBlePlatformChannel::SetUp( } } { - BasicMessageChannel<> channel(binary_messenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.startScan" + prepended_suffix, &GetCodec()); + BasicMessageChannel<> channel(binary_messenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.requestConnectionPriority" + prepended_suffix, &GetCodec()); if (api != nullptr) { - channel.SetMessageHandler([api](const EncodableValue& message, const flutter::MessageReply& reply) { + channel.SetMessageHandler([api](const EncodableValue& message, const ::flutter::MessageReply& reply) { try { const auto& args = std::get(message); - const auto& encodable_filter_arg = args.at(0); - const auto* filter_arg = encodable_filter_arg.IsNull() ? nullptr : &(std::any_cast(std::get(encodable_filter_arg))); - const auto& encodable_config_arg = args.at(1); - const auto* config_arg = encodable_config_arg.IsNull() ? nullptr : &(std::any_cast(std::get(encodable_config_arg))); - std::optional output = api->StartScan(filter_arg, config_arg); - if (output.has_value()) { - reply(WrapError(output.value())); + const auto& encodable_device_id_arg = args.at(0); + if (encodable_device_id_arg.IsNull()) { + reply(WrapError("device_id_arg unexpectedly null.")); return; } - EncodableList wrapped; - wrapped.push_back(EncodableValue()); - reply(EncodableValue(std::move(wrapped))); + const auto& device_id_arg = std::get(encodable_device_id_arg); + const auto& encodable_priority_arg = args.at(1); + if (encodable_priority_arg.IsNull()) { + reply(WrapError("priority_arg unexpectedly null.")); + return; + } + const int64_t priority_arg = encodable_priority_arg.LongValue(); + api->RequestConnectionPriority(device_id_arg, priority_arg, [reply](std::optional&& output) { + if (output.has_value()) { + reply(WrapError(output.value())); + return; + } + EncodableList wrapped; + wrapped.push_back(EncodableValue()); + reply(EncodableValue(std::move(wrapped))); + }); } catch (const std::exception& exception) { reply(WrapError(exception.what())); } @@ -907,11 +2406,18 @@ void UniversalBlePlatformChannel::SetUp( } } { - BasicMessageChannel<> channel(binary_messenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.stopScan" + prepended_suffix, &GetCodec()); + BasicMessageChannel<> channel(binary_messenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.setLogLevel" + prepended_suffix, &GetCodec()); if (api != nullptr) { - channel.SetMessageHandler([api](const EncodableValue& message, const flutter::MessageReply& reply) { + channel.SetMessageHandler([api](const EncodableValue& message, const ::flutter::MessageReply& reply) { try { - std::optional output = api->StopScan(); + const auto& args = std::get(message); + const auto& encodable_log_level_arg = args.at(0); + if (encodable_log_level_arg.IsNull()) { + reply(WrapError("log_level_arg unexpectedly null.")); + return; + } + const auto& log_level_arg = std::any_cast(std::get(encodable_log_level_arg)); + std::optional output = api->SetLogLevel(log_level_arg); if (output.has_value()) { reply(WrapError(output.value())); return; @@ -927,18 +2433,207 @@ void UniversalBlePlatformChannel::SetUp( channel.SetMessageHandler(nullptr); } } +} + +EncodableValue UniversalBlePlatformChannel::WrapError(std::string_view error_message) { + return EncodableValue(EncodableList{ + EncodableValue(std::string(error_message)), + EncodableValue("Error"), + EncodableValue() + }); +} + +EncodableValue UniversalBlePlatformChannel::WrapError(const FlutterError& error) { + return EncodableValue(EncodableList{ + EncodableValue(error.code()), + EncodableValue(error.message()), + error.details() + }); +} + +// Generated class from Pigeon that represents Flutter messages that can be called from C++. +UniversalBleCallbackChannel::UniversalBleCallbackChannel(::flutter::BinaryMessenger* binary_messenger) + : binary_messenger_(binary_messenger), + message_channel_suffix_("") {} + +UniversalBleCallbackChannel::UniversalBleCallbackChannel( + ::flutter::BinaryMessenger* binary_messenger, + const std::string& message_channel_suffix) + : binary_messenger_(binary_messenger), + message_channel_suffix_(message_channel_suffix.length() > 0 ? std::string(".") + message_channel_suffix : "") {} + +const ::flutter::StandardMessageCodec& UniversalBleCallbackChannel::GetCodec() { + return ::flutter::StandardMessageCodec::GetInstance(&PigeonInternalCodecSerializer::GetInstance()); +} + +void UniversalBleCallbackChannel::OnAvailabilityChanged( + int64_t state_arg, + std::function&& on_success, + std::function&& on_error) { + const std::string channel_name = "dev.flutter.pigeon.universal_ble.UniversalBleCallbackChannel.onAvailabilityChanged" + message_channel_suffix_; + BasicMessageChannel<> channel(binary_messenger_, channel_name, &GetCodec()); + EncodableValue encoded_api_arguments = EncodableValue(EncodableList{ + EncodableValue(state_arg), + }); + channel.Send(encoded_api_arguments, [channel_name, on_success = std::move(on_success), on_error = std::move(on_error)](const uint8_t* reply, size_t reply_size) { + std::unique_ptr response = GetCodec().DecodeMessage(reply, reply_size); + const auto& encodable_return_value = *response; + const auto* list_return_value = std::get_if(&encodable_return_value); + if (list_return_value) { + if (list_return_value->size() > 1) { + on_error(FlutterError(std::get(list_return_value->at(0)), std::get(list_return_value->at(1)), list_return_value->at(2))); + } else { + on_success(); + } + } else { + on_error(CreateConnectionError(channel_name)); + } + }); +} + +void UniversalBleCallbackChannel::OnPairStateChange( + const std::string& device_id_arg, + bool is_paired_arg, + const std::string* error_arg, + std::function&& on_success, + std::function&& on_error) { + const std::string channel_name = "dev.flutter.pigeon.universal_ble.UniversalBleCallbackChannel.onPairStateChange" + message_channel_suffix_; + BasicMessageChannel<> channel(binary_messenger_, channel_name, &GetCodec()); + EncodableValue encoded_api_arguments = EncodableValue(EncodableList{ + EncodableValue(device_id_arg), + EncodableValue(is_paired_arg), + error_arg ? EncodableValue(*error_arg) : EncodableValue(), + }); + channel.Send(encoded_api_arguments, [channel_name, on_success = std::move(on_success), on_error = std::move(on_error)](const uint8_t* reply, size_t reply_size) { + std::unique_ptr response = GetCodec().DecodeMessage(reply, reply_size); + const auto& encodable_return_value = *response; + const auto* list_return_value = std::get_if(&encodable_return_value); + if (list_return_value) { + if (list_return_value->size() > 1) { + on_error(FlutterError(std::get(list_return_value->at(0)), std::get(list_return_value->at(1)), list_return_value->at(2))); + } else { + on_success(); + } + } else { + on_error(CreateConnectionError(channel_name)); + } + }); +} + +void UniversalBleCallbackChannel::OnScanResult( + const UniversalBleScanResult& result_arg, + std::function&& on_success, + std::function&& on_error) { + const std::string channel_name = "dev.flutter.pigeon.universal_ble.UniversalBleCallbackChannel.onScanResult" + message_channel_suffix_; + BasicMessageChannel<> channel(binary_messenger_, channel_name, &GetCodec()); + EncodableValue encoded_api_arguments = EncodableValue(EncodableList{ + CustomEncodableValue(result_arg), + }); + channel.Send(encoded_api_arguments, [channel_name, on_success = std::move(on_success), on_error = std::move(on_error)](const uint8_t* reply, size_t reply_size) { + std::unique_ptr response = GetCodec().DecodeMessage(reply, reply_size); + const auto& encodable_return_value = *response; + const auto* list_return_value = std::get_if(&encodable_return_value); + if (list_return_value) { + if (list_return_value->size() > 1) { + on_error(FlutterError(std::get(list_return_value->at(0)), std::get(list_return_value->at(1)), list_return_value->at(2))); + } else { + on_success(); + } + } else { + on_error(CreateConnectionError(channel_name)); + } + }); +} + +void UniversalBleCallbackChannel::OnValueChanged( + const std::string& device_id_arg, + const std::string& characteristic_id_arg, + const std::vector& value_arg, + const int64_t* timestamp_arg, + std::function&& on_success, + std::function&& on_error) { + const std::string channel_name = "dev.flutter.pigeon.universal_ble.UniversalBleCallbackChannel.onValueChanged" + message_channel_suffix_; + BasicMessageChannel<> channel(binary_messenger_, channel_name, &GetCodec()); + EncodableValue encoded_api_arguments = EncodableValue(EncodableList{ + EncodableValue(device_id_arg), + EncodableValue(characteristic_id_arg), + EncodableValue(value_arg), + timestamp_arg ? EncodableValue(*timestamp_arg) : EncodableValue(), + }); + channel.Send(encoded_api_arguments, [channel_name, on_success = std::move(on_success), on_error = std::move(on_error)](const uint8_t* reply, size_t reply_size) { + std::unique_ptr response = GetCodec().DecodeMessage(reply, reply_size); + const auto& encodable_return_value = *response; + const auto* list_return_value = std::get_if(&encodable_return_value); + if (list_return_value) { + if (list_return_value->size() > 1) { + on_error(FlutterError(std::get(list_return_value->at(0)), std::get(list_return_value->at(1)), list_return_value->at(2))); + } else { + on_success(); + } + } else { + on_error(CreateConnectionError(channel_name)); + } + }); +} + +void UniversalBleCallbackChannel::OnConnectionChanged( + const std::string& device_id_arg, + bool connected_arg, + const std::string* error_arg, + std::function&& on_success, + std::function&& on_error) { + const std::string channel_name = "dev.flutter.pigeon.universal_ble.UniversalBleCallbackChannel.onConnectionChanged" + message_channel_suffix_; + BasicMessageChannel<> channel(binary_messenger_, channel_name, &GetCodec()); + EncodableValue encoded_api_arguments = EncodableValue(EncodableList{ + EncodableValue(device_id_arg), + EncodableValue(connected_arg), + error_arg ? EncodableValue(*error_arg) : EncodableValue(), + }); + channel.Send(encoded_api_arguments, [channel_name, on_success = std::move(on_success), on_error = std::move(on_error)](const uint8_t* reply, size_t reply_size) { + std::unique_ptr response = GetCodec().DecodeMessage(reply, reply_size); + const auto& encodable_return_value = *response; + const auto* list_return_value = std::get_if(&encodable_return_value); + if (list_return_value) { + if (list_return_value->size() > 1) { + on_error(FlutterError(std::get(list_return_value->at(0)), std::get(list_return_value->at(1)), list_return_value->at(2))); + } else { + on_success(); + } + } else { + on_error(CreateConnectionError(channel_name)); + } + }); +} + +/// The codec used by UniversalBlePeripheralChannel. +const ::flutter::StandardMessageCodec& UniversalBlePeripheralChannel::GetCodec() { + return ::flutter::StandardMessageCodec::GetInstance(&PigeonInternalCodecSerializer::GetInstance()); +} + +// Sets up an instance of `UniversalBlePeripheralChannel` to handle messages through the `binary_messenger`. +void UniversalBlePeripheralChannel::SetUp( + ::flutter::BinaryMessenger* binary_messenger, + UniversalBlePeripheralChannel* api) { + UniversalBlePeripheralChannel::SetUp(binary_messenger, api, ""); +} + +void UniversalBlePeripheralChannel::SetUp( + ::flutter::BinaryMessenger* binary_messenger, + UniversalBlePeripheralChannel* api, + const std::string& message_channel_suffix) { + const std::string prepended_suffix = message_channel_suffix.length() > 0 ? std::string(".") + message_channel_suffix : ""; { - BasicMessageChannel<> channel(binary_messenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.isScanning" + prepended_suffix, &GetCodec()); + BasicMessageChannel<> channel(binary_messenger, "dev.flutter.pigeon.universal_ble.UniversalBlePeripheralChannel.getAdvertisingState" + prepended_suffix, &GetCodec()); if (api != nullptr) { - channel.SetMessageHandler([api](const EncodableValue& message, const flutter::MessageReply& reply) { + channel.SetMessageHandler([api](const EncodableValue& message, const ::flutter::MessageReply& reply) { try { - ErrorOr output = api->IsScanning(); + ErrorOr output = api->GetAdvertisingState(); if (output.has_error()) { reply(WrapError(output.error())); return; } EncodableList wrapped; - wrapped.push_back(EncodableValue(std::move(output).TakeValue())); + wrapped.push_back(CustomEncodableValue(std::move(output).TakeValue())); reply(EncodableValue(std::move(wrapped))); } catch (const std::exception& exception) { reply(WrapError(exception.what())); @@ -949,26 +2644,17 @@ void UniversalBlePlatformChannel::SetUp( } } { - BasicMessageChannel<> channel(binary_messenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.connect" + prepended_suffix, &GetCodec()); + BasicMessageChannel<> channel(binary_messenger, "dev.flutter.pigeon.universal_ble.UniversalBlePeripheralChannel.getReadinessState" + prepended_suffix, &GetCodec()); if (api != nullptr) { - channel.SetMessageHandler([api](const EncodableValue& message, const flutter::MessageReply& reply) { + channel.SetMessageHandler([api](const EncodableValue& message, const ::flutter::MessageReply& reply) { try { - const auto& args = std::get(message); - const auto& encodable_device_id_arg = args.at(0); - if (encodable_device_id_arg.IsNull()) { - reply(WrapError("device_id_arg unexpectedly null.")); - return; - } - const auto& device_id_arg = std::get(encodable_device_id_arg); - const auto& encodable_auto_connect_arg = args.at(1); - const auto* auto_connect_arg = std::get_if(&encodable_auto_connect_arg); - std::optional output = api->Connect(device_id_arg, auto_connect_arg); - if (output.has_value()) { - reply(WrapError(output.value())); + ErrorOr output = api->GetReadinessState(); + if (output.has_error()) { + reply(WrapError(output.error())); return; } EncodableList wrapped; - wrapped.push_back(EncodableValue()); + wrapped.push_back(CustomEncodableValue(std::move(output).TakeValue())); reply(EncodableValue(std::move(wrapped))); } catch (const std::exception& exception) { reply(WrapError(exception.what())); @@ -979,18 +2665,11 @@ void UniversalBlePlatformChannel::SetUp( } } { - BasicMessageChannel<> channel(binary_messenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.disconnect" + prepended_suffix, &GetCodec()); + BasicMessageChannel<> channel(binary_messenger, "dev.flutter.pigeon.universal_ble.UniversalBlePeripheralChannel.stopAdvertising" + prepended_suffix, &GetCodec()); if (api != nullptr) { - channel.SetMessageHandler([api](const EncodableValue& message, const flutter::MessageReply& reply) { + channel.SetMessageHandler([api](const EncodableValue& message, const ::flutter::MessageReply& reply) { try { - const auto& args = std::get(message); - const auto& encodable_device_id_arg = args.at(0); - if (encodable_device_id_arg.IsNull()) { - reply(WrapError("device_id_arg unexpectedly null.")); - return; - } - const auto& device_id_arg = std::get(encodable_device_id_arg); - std::optional output = api->Disconnect(device_id_arg); + std::optional output = api->StopAdvertising(); if (output.has_value()) { reply(WrapError(output.value())); return; @@ -1007,237 +2686,25 @@ void UniversalBlePlatformChannel::SetUp( } } { - BasicMessageChannel<> channel(binary_messenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.setNotifiable" + prepended_suffix, &GetCodec()); - if (api != nullptr) { - channel.SetMessageHandler([api](const EncodableValue& message, const flutter::MessageReply& reply) { - try { - const auto& args = std::get(message); - const auto& encodable_device_id_arg = args.at(0); - if (encodable_device_id_arg.IsNull()) { - reply(WrapError("device_id_arg unexpectedly null.")); - return; - } - const auto& device_id_arg = std::get(encodable_device_id_arg); - const auto& encodable_service_arg = args.at(1); - if (encodable_service_arg.IsNull()) { - reply(WrapError("service_arg unexpectedly null.")); - return; - } - const auto& service_arg = std::get(encodable_service_arg); - const auto& encodable_characteristic_arg = args.at(2); - if (encodable_characteristic_arg.IsNull()) { - reply(WrapError("characteristic_arg unexpectedly null.")); - return; - } - const auto& characteristic_arg = std::get(encodable_characteristic_arg); - const auto& encodable_ble_input_property_arg = args.at(3); - if (encodable_ble_input_property_arg.IsNull()) { - reply(WrapError("ble_input_property_arg unexpectedly null.")); - return; - } - const int64_t ble_input_property_arg = encodable_ble_input_property_arg.LongValue(); - api->SetNotifiable(device_id_arg, service_arg, characteristic_arg, ble_input_property_arg, [reply](std::optional&& output) { - if (output.has_value()) { - reply(WrapError(output.value())); - return; - } - EncodableList wrapped; - wrapped.push_back(EncodableValue()); - reply(EncodableValue(std::move(wrapped))); - }); - } catch (const std::exception& exception) { - reply(WrapError(exception.what())); - } - }); - } else { - channel.SetMessageHandler(nullptr); - } - } - { - BasicMessageChannel<> channel(binary_messenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.discoverServices" + prepended_suffix, &GetCodec()); - if (api != nullptr) { - channel.SetMessageHandler([api](const EncodableValue& message, const flutter::MessageReply& reply) { - try { - const auto& args = std::get(message); - const auto& encodable_device_id_arg = args.at(0); - if (encodable_device_id_arg.IsNull()) { - reply(WrapError("device_id_arg unexpectedly null.")); - return; - } - const auto& device_id_arg = std::get(encodable_device_id_arg); - const auto& encodable_with_descriptors_arg = args.at(1); - if (encodable_with_descriptors_arg.IsNull()) { - reply(WrapError("with_descriptors_arg unexpectedly null.")); - return; - } - const auto& with_descriptors_arg = std::get(encodable_with_descriptors_arg); - api->DiscoverServices(device_id_arg, with_descriptors_arg, [reply](ErrorOr&& output) { - if (output.has_error()) { - reply(WrapError(output.error())); - return; - } - EncodableList wrapped; - wrapped.push_back(EncodableValue(std::move(output).TakeValue())); - reply(EncodableValue(std::move(wrapped))); - }); - } catch (const std::exception& exception) { - reply(WrapError(exception.what())); - } - }); - } else { - channel.SetMessageHandler(nullptr); - } - } - { - BasicMessageChannel<> channel(binary_messenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.readValue" + prepended_suffix, &GetCodec()); - if (api != nullptr) { - channel.SetMessageHandler([api](const EncodableValue& message, const flutter::MessageReply& reply) { - try { - const auto& args = std::get(message); - const auto& encodable_device_id_arg = args.at(0); - if (encodable_device_id_arg.IsNull()) { - reply(WrapError("device_id_arg unexpectedly null.")); - return; - } - const auto& device_id_arg = std::get(encodable_device_id_arg); - const auto& encodable_service_arg = args.at(1); - if (encodable_service_arg.IsNull()) { - reply(WrapError("service_arg unexpectedly null.")); - return; - } - const auto& service_arg = std::get(encodable_service_arg); - const auto& encodable_characteristic_arg = args.at(2); - if (encodable_characteristic_arg.IsNull()) { - reply(WrapError("characteristic_arg unexpectedly null.")); - return; - } - const auto& characteristic_arg = std::get(encodable_characteristic_arg); - api->ReadValue(device_id_arg, service_arg, characteristic_arg, [reply](ErrorOr>&& output) { - if (output.has_error()) { - reply(WrapError(output.error())); - return; - } - EncodableList wrapped; - wrapped.push_back(EncodableValue(std::move(output).TakeValue())); - reply(EncodableValue(std::move(wrapped))); - }); - } catch (const std::exception& exception) { - reply(WrapError(exception.what())); - } - }); - } else { - channel.SetMessageHandler(nullptr); - } - } - { - BasicMessageChannel<> channel(binary_messenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.requestMtu" + prepended_suffix, &GetCodec()); - if (api != nullptr) { - channel.SetMessageHandler([api](const EncodableValue& message, const flutter::MessageReply& reply) { - try { - const auto& args = std::get(message); - const auto& encodable_device_id_arg = args.at(0); - if (encodable_device_id_arg.IsNull()) { - reply(WrapError("device_id_arg unexpectedly null.")); - return; - } - const auto& device_id_arg = std::get(encodable_device_id_arg); - const auto& encodable_expected_mtu_arg = args.at(1); - if (encodable_expected_mtu_arg.IsNull()) { - reply(WrapError("expected_mtu_arg unexpectedly null.")); - return; - } - const int64_t expected_mtu_arg = encodable_expected_mtu_arg.LongValue(); - api->RequestMtu(device_id_arg, expected_mtu_arg, [reply](ErrorOr&& output) { - if (output.has_error()) { - reply(WrapError(output.error())); - return; - } - EncodableList wrapped; - wrapped.push_back(EncodableValue(std::move(output).TakeValue())); - reply(EncodableValue(std::move(wrapped))); - }); - } catch (const std::exception& exception) { - reply(WrapError(exception.what())); - } - }); - } else { - channel.SetMessageHandler(nullptr); - } - } - { - BasicMessageChannel<> channel(binary_messenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.writeValue" + prepended_suffix, &GetCodec()); + BasicMessageChannel<> channel(binary_messenger, "dev.flutter.pigeon.universal_ble.UniversalBlePeripheralChannel.addService" + prepended_suffix, &GetCodec()); if (api != nullptr) { - channel.SetMessageHandler([api](const EncodableValue& message, const flutter::MessageReply& reply) { + channel.SetMessageHandler([api](const EncodableValue& message, const ::flutter::MessageReply& reply) { try { const auto& args = std::get(message); - const auto& encodable_device_id_arg = args.at(0); - if (encodable_device_id_arg.IsNull()) { - reply(WrapError("device_id_arg unexpectedly null.")); - return; - } - const auto& device_id_arg = std::get(encodable_device_id_arg); - const auto& encodable_service_arg = args.at(1); + const auto& encodable_service_arg = args.at(0); if (encodable_service_arg.IsNull()) { reply(WrapError("service_arg unexpectedly null.")); return; } - const auto& service_arg = std::get(encodable_service_arg); - const auto& encodable_characteristic_arg = args.at(2); - if (encodable_characteristic_arg.IsNull()) { - reply(WrapError("characteristic_arg unexpectedly null.")); - return; - } - const auto& characteristic_arg = std::get(encodable_characteristic_arg); - const auto& encodable_value_arg = args.at(3); - if (encodable_value_arg.IsNull()) { - reply(WrapError("value_arg unexpectedly null.")); - return; - } - const auto& value_arg = std::get>(encodable_value_arg); - const auto& encodable_ble_output_property_arg = args.at(4); - if (encodable_ble_output_property_arg.IsNull()) { - reply(WrapError("ble_output_property_arg unexpectedly null.")); - return; - } - const int64_t ble_output_property_arg = encodable_ble_output_property_arg.LongValue(); - api->WriteValue(device_id_arg, service_arg, characteristic_arg, value_arg, ble_output_property_arg, [reply](std::optional&& output) { - if (output.has_value()) { - reply(WrapError(output.value())); - return; - } - EncodableList wrapped; - wrapped.push_back(EncodableValue()); - reply(EncodableValue(std::move(wrapped))); - }); - } catch (const std::exception& exception) { - reply(WrapError(exception.what())); - } - }); - } else { - channel.SetMessageHandler(nullptr); - } - } - { - BasicMessageChannel<> channel(binary_messenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.isPaired" + prepended_suffix, &GetCodec()); - if (api != nullptr) { - channel.SetMessageHandler([api](const EncodableValue& message, const flutter::MessageReply& reply) { - try { - const auto& args = std::get(message); - const auto& encodable_device_id_arg = args.at(0); - if (encodable_device_id_arg.IsNull()) { - reply(WrapError("device_id_arg unexpectedly null.")); + const auto& service_arg = std::any_cast(std::get(encodable_service_arg)); + std::optional output = api->AddService(service_arg); + if (output.has_value()) { + reply(WrapError(output.value())); return; } - const auto& device_id_arg = std::get(encodable_device_id_arg); - api->IsPaired(device_id_arg, [reply](ErrorOr&& output) { - if (output.has_error()) { - reply(WrapError(output.error())); - return; - } - EncodableList wrapped; - wrapped.push_back(EncodableValue(std::move(output).TakeValue())); - reply(EncodableValue(std::move(wrapped))); - }); + EncodableList wrapped; + wrapped.push_back(EncodableValue()); + reply(EncodableValue(std::move(wrapped))); } catch (const std::exception& exception) { reply(WrapError(exception.what())); } @@ -1247,26 +2714,25 @@ void UniversalBlePlatformChannel::SetUp( } } { - BasicMessageChannel<> channel(binary_messenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.pair" + prepended_suffix, &GetCodec()); + BasicMessageChannel<> channel(binary_messenger, "dev.flutter.pigeon.universal_ble.UniversalBlePeripheralChannel.removeService" + prepended_suffix, &GetCodec()); if (api != nullptr) { - channel.SetMessageHandler([api](const EncodableValue& message, const flutter::MessageReply& reply) { + channel.SetMessageHandler([api](const EncodableValue& message, const ::flutter::MessageReply& reply) { try { const auto& args = std::get(message); - const auto& encodable_device_id_arg = args.at(0); - if (encodable_device_id_arg.IsNull()) { - reply(WrapError("device_id_arg unexpectedly null.")); + const auto& encodable_service_id_arg = args.at(0); + if (encodable_service_id_arg.IsNull()) { + reply(WrapError("service_id_arg unexpectedly null.")); return; } - const auto& device_id_arg = std::get(encodable_device_id_arg); - api->Pair(device_id_arg, [reply](ErrorOr&& output) { - if (output.has_error()) { - reply(WrapError(output.error())); - return; - } - EncodableList wrapped; - wrapped.push_back(EncodableValue(std::move(output).TakeValue())); - reply(EncodableValue(std::move(wrapped))); - }); + const auto& service_id_arg = std::get(encodable_service_id_arg); + std::optional output = api->RemoveService(service_id_arg); + if (output.has_value()) { + reply(WrapError(output.value())); + return; + } + EncodableList wrapped; + wrapped.push_back(EncodableValue()); + reply(EncodableValue(std::move(wrapped))); } catch (const std::exception& exception) { reply(WrapError(exception.what())); } @@ -1276,18 +2742,11 @@ void UniversalBlePlatformChannel::SetUp( } } { - BasicMessageChannel<> channel(binary_messenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.unPair" + prepended_suffix, &GetCodec()); + BasicMessageChannel<> channel(binary_messenger, "dev.flutter.pigeon.universal_ble.UniversalBlePeripheralChannel.clearServices" + prepended_suffix, &GetCodec()); if (api != nullptr) { - channel.SetMessageHandler([api](const EncodableValue& message, const flutter::MessageReply& reply) { + channel.SetMessageHandler([api](const EncodableValue& message, const ::flutter::MessageReply& reply) { try { - const auto& args = std::get(message); - const auto& encodable_device_id_arg = args.at(0); - if (encodable_device_id_arg.IsNull()) { - reply(WrapError("device_id_arg unexpectedly null.")); - return; - } - const auto& device_id_arg = std::get(encodable_device_id_arg); - std::optional output = api->UnPair(device_id_arg); + std::optional output = api->ClearServices(); if (output.has_value()) { reply(WrapError(output.value())); return; @@ -1304,26 +2763,18 @@ void UniversalBlePlatformChannel::SetUp( } } { - BasicMessageChannel<> channel(binary_messenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.getSystemDevices" + prepended_suffix, &GetCodec()); + BasicMessageChannel<> channel(binary_messenger, "dev.flutter.pigeon.universal_ble.UniversalBlePeripheralChannel.getServices" + prepended_suffix, &GetCodec()); if (api != nullptr) { - channel.SetMessageHandler([api](const EncodableValue& message, const flutter::MessageReply& reply) { + channel.SetMessageHandler([api](const EncodableValue& message, const ::flutter::MessageReply& reply) { try { - const auto& args = std::get(message); - const auto& encodable_with_services_arg = args.at(0); - if (encodable_with_services_arg.IsNull()) { - reply(WrapError("with_services_arg unexpectedly null.")); + ErrorOr output = api->GetServices(); + if (output.has_error()) { + reply(WrapError(output.error())); return; } - const auto& with_services_arg = std::get(encodable_with_services_arg); - api->GetSystemDevices(with_services_arg, [reply](ErrorOr&& output) { - if (output.has_error()) { - reply(WrapError(output.error())); - return; - } - EncodableList wrapped; - wrapped.push_back(EncodableValue(std::move(output).TakeValue())); - reply(EncodableValue(std::move(wrapped))); - }); + EncodableList wrapped; + wrapped.push_back(EncodableValue(std::move(output).TakeValue())); + reply(EncodableValue(std::move(wrapped))); } catch (const std::exception& exception) { reply(WrapError(exception.what())); } @@ -1333,24 +2784,36 @@ void UniversalBlePlatformChannel::SetUp( } } { - BasicMessageChannel<> channel(binary_messenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.getConnectionState" + prepended_suffix, &GetCodec()); + BasicMessageChannel<> channel(binary_messenger, "dev.flutter.pigeon.universal_ble.UniversalBlePeripheralChannel.startAdvertising" + prepended_suffix, &GetCodec()); if (api != nullptr) { - channel.SetMessageHandler([api](const EncodableValue& message, const flutter::MessageReply& reply) { + channel.SetMessageHandler([api](const EncodableValue& message, const ::flutter::MessageReply& reply) { try { const auto& args = std::get(message); - const auto& encodable_device_id_arg = args.at(0); - if (encodable_device_id_arg.IsNull()) { - reply(WrapError("device_id_arg unexpectedly null.")); + const auto& encodable_services_arg = args.at(0); + if (encodable_services_arg.IsNull()) { + reply(WrapError("services_arg unexpectedly null.")); return; } - const auto& device_id_arg = std::get(encodable_device_id_arg); - ErrorOr output = api->GetConnectionState(device_id_arg); - if (output.has_error()) { - reply(WrapError(output.error())); + const auto& services_arg = std::get(encodable_services_arg); + const auto& encodable_local_name_arg = args.at(1); + const auto* local_name_arg = std::get_if(&encodable_local_name_arg); + const auto& encodable_timeout_arg = args.at(2); + const auto* timeout_arg = std::get_if(&encodable_timeout_arg); + const auto& encodable_manufacturer_data_arg = args.at(3); + const auto* manufacturer_data_arg = encodable_manufacturer_data_arg.IsNull() ? nullptr : &(std::any_cast(std::get(encodable_manufacturer_data_arg))); + const auto& encodable_add_manufacturer_data_in_scan_response_arg = args.at(4); + if (encodable_add_manufacturer_data_in_scan_response_arg.IsNull()) { + reply(WrapError("add_manufacturer_data_in_scan_response_arg unexpectedly null.")); + return; + } + const auto& add_manufacturer_data_in_scan_response_arg = std::get(encodable_add_manufacturer_data_in_scan_response_arg); + std::optional output = api->StartAdvertising(services_arg, local_name_arg, timeout_arg, manufacturer_data_arg, add_manufacturer_data_in_scan_response_arg); + if (output.has_value()) { + reply(WrapError(output.value())); return; } EncodableList wrapped; - wrapped.push_back(EncodableValue(std::move(output).TakeValue())); + wrapped.push_back(EncodableValue()); reply(EncodableValue(std::move(wrapped))); } catch (const std::exception& exception) { reply(WrapError(exception.what())); @@ -1361,26 +2824,33 @@ void UniversalBlePlatformChannel::SetUp( } } { - BasicMessageChannel<> channel(binary_messenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.readRssi" + prepended_suffix, &GetCodec()); + BasicMessageChannel<> channel(binary_messenger, "dev.flutter.pigeon.universal_ble.UniversalBlePeripheralChannel.updateCharacteristic" + prepended_suffix, &GetCodec()); if (api != nullptr) { - channel.SetMessageHandler([api](const EncodableValue& message, const flutter::MessageReply& reply) { + channel.SetMessageHandler([api](const EncodableValue& message, const ::flutter::MessageReply& reply) { try { const auto& args = std::get(message); - const auto& encodable_device_id_arg = args.at(0); - if (encodable_device_id_arg.IsNull()) { - reply(WrapError("device_id_arg unexpectedly null.")); + const auto& encodable_characteristic_id_arg = args.at(0); + if (encodable_characteristic_id_arg.IsNull()) { + reply(WrapError("characteristic_id_arg unexpectedly null.")); return; } - const auto& device_id_arg = std::get(encodable_device_id_arg); - api->ReadRssi(device_id_arg, [reply](ErrorOr&& output) { - if (output.has_error()) { - reply(WrapError(output.error())); - return; - } - EncodableList wrapped; - wrapped.push_back(EncodableValue(std::move(output).TakeValue())); - reply(EncodableValue(std::move(wrapped))); - }); + const auto& characteristic_id_arg = std::get(encodable_characteristic_id_arg); + const auto& encodable_value_arg = args.at(1); + if (encodable_value_arg.IsNull()) { + reply(WrapError("value_arg unexpectedly null.")); + return; + } + const auto& value_arg = std::get>(encodable_value_arg); + const auto& encodable_device_id_arg = args.at(2); + const auto* device_id_arg = std::get_if(&encodable_device_id_arg); + std::optional output = api->UpdateCharacteristic(characteristic_id_arg, value_arg, device_id_arg); + if (output.has_value()) { + reply(WrapError(output.value())); + return; + } + EncodableList wrapped; + wrapped.push_back(EncodableValue()); + reply(EncodableValue(std::move(wrapped))); } catch (const std::exception& exception) { reply(WrapError(exception.what())); } @@ -1390,32 +2860,25 @@ void UniversalBlePlatformChannel::SetUp( } } { - BasicMessageChannel<> channel(binary_messenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.requestConnectionPriority" + prepended_suffix, &GetCodec()); + BasicMessageChannel<> channel(binary_messenger, "dev.flutter.pigeon.universal_ble.UniversalBlePeripheralChannel.getSubscribedClients" + prepended_suffix, &GetCodec()); if (api != nullptr) { - channel.SetMessageHandler([api](const EncodableValue& message, const flutter::MessageReply& reply) { + channel.SetMessageHandler([api](const EncodableValue& message, const ::flutter::MessageReply& reply) { try { const auto& args = std::get(message); - const auto& encodable_device_id_arg = args.at(0); - if (encodable_device_id_arg.IsNull()) { - reply(WrapError("device_id_arg unexpectedly null.")); + const auto& encodable_characteristic_id_arg = args.at(0); + if (encodable_characteristic_id_arg.IsNull()) { + reply(WrapError("characteristic_id_arg unexpectedly null.")); return; } - const auto& device_id_arg = std::get(encodable_device_id_arg); - const auto& encodable_priority_arg = args.at(1); - if (encodable_priority_arg.IsNull()) { - reply(WrapError("priority_arg unexpectedly null.")); + const auto& characteristic_id_arg = std::get(encodable_characteristic_id_arg); + ErrorOr output = api->GetSubscribedClients(characteristic_id_arg); + if (output.has_error()) { + reply(WrapError(output.error())); return; } - const int64_t priority_arg = encodable_priority_arg.LongValue(); - api->RequestConnectionPriority(device_id_arg, priority_arg, [reply](std::optional&& output) { - if (output.has_value()) { - reply(WrapError(output.value())); - return; - } - EncodableList wrapped; - wrapped.push_back(EncodableValue()); - reply(EncodableValue(std::move(wrapped))); - }); + EncodableList wrapped; + wrapped.push_back(EncodableValue(std::move(output).TakeValue())); + reply(EncodableValue(std::move(wrapped))); } catch (const std::exception& exception) { reply(WrapError(exception.what())); } @@ -1425,24 +2888,29 @@ void UniversalBlePlatformChannel::SetUp( } } { - BasicMessageChannel<> channel(binary_messenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.setLogLevel" + prepended_suffix, &GetCodec()); + BasicMessageChannel<> channel(binary_messenger, "dev.flutter.pigeon.universal_ble.UniversalBlePeripheralChannel.getMaximumNotifyLength" + prepended_suffix, &GetCodec()); if (api != nullptr) { - channel.SetMessageHandler([api](const EncodableValue& message, const flutter::MessageReply& reply) { + channel.SetMessageHandler([api](const EncodableValue& message, const ::flutter::MessageReply& reply) { try { const auto& args = std::get(message); - const auto& encodable_log_level_arg = args.at(0); - if (encodable_log_level_arg.IsNull()) { - reply(WrapError("log_level_arg unexpectedly null.")); + const auto& encodable_device_id_arg = args.at(0); + if (encodable_device_id_arg.IsNull()) { + reply(WrapError("device_id_arg unexpectedly null.")); return; } - const auto& log_level_arg = std::any_cast(std::get(encodable_log_level_arg)); - std::optional output = api->SetLogLevel(log_level_arg); - if (output.has_value()) { - reply(WrapError(output.value())); + const auto& device_id_arg = std::get(encodable_device_id_arg); + ErrorOr> output = api->GetMaximumNotifyLength(device_id_arg); + if (output.has_error()) { + reply(WrapError(output.error())); return; } EncodableList wrapped; - wrapped.push_back(EncodableValue()); + auto output_optional = std::move(output).TakeValue(); + if (output_optional) { + wrapped.push_back(EncodableValue(std::move(output_optional).value())); + } else { + wrapped.push_back(EncodableValue()); + } reply(EncodableValue(std::move(wrapped))); } catch (const std::exception& exception) { reply(WrapError(exception.what())); @@ -1454,7 +2922,7 @@ void UniversalBlePlatformChannel::SetUp( } } -EncodableValue UniversalBlePlatformChannel::WrapError(std::string_view error_message) { +EncodableValue UniversalBlePeripheralChannel::WrapError(std::string_view error_message) { return EncodableValue(EncodableList{ EncodableValue(std::string(error_message)), EncodableValue("Error"), @@ -1462,7 +2930,7 @@ EncodableValue UniversalBlePlatformChannel::WrapError(std::string_view error_mes }); } -EncodableValue UniversalBlePlatformChannel::WrapError(const FlutterError& error) { +EncodableValue UniversalBlePeripheralChannel::WrapError(const FlutterError& error) { return EncodableValue(EncodableList{ EncodableValue(error.code()), EncodableValue(error.message()), @@ -1471,28 +2939,166 @@ EncodableValue UniversalBlePlatformChannel::WrapError(const FlutterError& error) } // Generated class from Pigeon that represents Flutter messages that can be called from C++. -UniversalBleCallbackChannel::UniversalBleCallbackChannel(flutter::BinaryMessenger* binary_messenger) +UniversalBlePeripheralCallback::UniversalBlePeripheralCallback(::flutter::BinaryMessenger* binary_messenger) : binary_messenger_(binary_messenger), message_channel_suffix_("") {} -UniversalBleCallbackChannel::UniversalBleCallbackChannel( - flutter::BinaryMessenger* binary_messenger, +UniversalBlePeripheralCallback::UniversalBlePeripheralCallback( + ::flutter::BinaryMessenger* binary_messenger, const std::string& message_channel_suffix) : binary_messenger_(binary_messenger), message_channel_suffix_(message_channel_suffix.length() > 0 ? std::string(".") + message_channel_suffix : "") {} -const flutter::StandardMessageCodec& UniversalBleCallbackChannel::GetCodec() { - return flutter::StandardMessageCodec::GetInstance(&PigeonInternalCodecSerializer::GetInstance()); +const ::flutter::StandardMessageCodec& UniversalBlePeripheralCallback::GetCodec() { + return ::flutter::StandardMessageCodec::GetInstance(&PigeonInternalCodecSerializer::GetInstance()); } -void UniversalBleCallbackChannel::OnAvailabilityChanged( - int64_t state_arg, +void UniversalBlePeripheralCallback::OnReadRequest( + const std::string& device_id_arg, + const std::string& characteristic_id_arg, + int64_t offset_arg, + const std::vector* value_arg, + std::function&& on_success, + std::function&& on_error) { + const std::string channel_name = "dev.flutter.pigeon.universal_ble.UniversalBlePeripheralCallback.onReadRequest" + message_channel_suffix_; + BasicMessageChannel<> channel(binary_messenger_, channel_name, &GetCodec()); + EncodableValue encoded_api_arguments = EncodableValue(EncodableList{ + EncodableValue(device_id_arg), + EncodableValue(characteristic_id_arg), + EncodableValue(offset_arg), + value_arg ? EncodableValue(*value_arg) : EncodableValue(), + }); + channel.Send(encoded_api_arguments, [channel_name, on_success = std::move(on_success), on_error = std::move(on_error)](const uint8_t* reply, size_t reply_size) { + std::unique_ptr response = GetCodec().DecodeMessage(reply, reply_size); + const auto& encodable_return_value = *response; + const auto* list_return_value = std::get_if(&encodable_return_value); + if (list_return_value) { + if (list_return_value->size() > 1) { + on_error(FlutterError(std::get(list_return_value->at(0)), std::get(list_return_value->at(1)), list_return_value->at(2))); + } else { + const auto* return_value = list_return_value->at(0).IsNull() ? nullptr : &(std::any_cast(std::get(list_return_value->at(0)))); + on_success(return_value); + } + } else { + on_error(CreateConnectionError(channel_name)); + } + }); +} + +void UniversalBlePeripheralCallback::OnWriteRequest( + const std::string& device_id_arg, + const std::string& characteristic_id_arg, + int64_t offset_arg, + const std::vector* value_arg, + std::function&& on_success, + std::function&& on_error) { + const std::string channel_name = "dev.flutter.pigeon.universal_ble.UniversalBlePeripheralCallback.onWriteRequest" + message_channel_suffix_; + BasicMessageChannel<> channel(binary_messenger_, channel_name, &GetCodec()); + EncodableValue encoded_api_arguments = EncodableValue(EncodableList{ + EncodableValue(device_id_arg), + EncodableValue(characteristic_id_arg), + EncodableValue(offset_arg), + value_arg ? EncodableValue(*value_arg) : EncodableValue(), + }); + channel.Send(encoded_api_arguments, [channel_name, on_success = std::move(on_success), on_error = std::move(on_error)](const uint8_t* reply, size_t reply_size) { + std::unique_ptr response = GetCodec().DecodeMessage(reply, reply_size); + const auto& encodable_return_value = *response; + const auto* list_return_value = std::get_if(&encodable_return_value); + if (list_return_value) { + if (list_return_value->size() > 1) { + on_error(FlutterError(std::get(list_return_value->at(0)), std::get(list_return_value->at(1)), list_return_value->at(2))); + } else { + const auto* return_value = list_return_value->at(0).IsNull() ? nullptr : &(std::any_cast(std::get(list_return_value->at(0)))); + on_success(return_value); + } + } else { + on_error(CreateConnectionError(channel_name)); + } + }); +} + +void UniversalBlePeripheralCallback::OnDescriptorReadRequest( + const std::string& device_id_arg, + const std::string& characteristic_id_arg, + const std::string& descriptor_id_arg, + int64_t offset_arg, + const std::vector* value_arg, + std::function&& on_success, + std::function&& on_error) { + const std::string channel_name = "dev.flutter.pigeon.universal_ble.UniversalBlePeripheralCallback.onDescriptorReadRequest" + message_channel_suffix_; + BasicMessageChannel<> channel(binary_messenger_, channel_name, &GetCodec()); + EncodableValue encoded_api_arguments = EncodableValue(EncodableList{ + EncodableValue(device_id_arg), + EncodableValue(characteristic_id_arg), + EncodableValue(descriptor_id_arg), + EncodableValue(offset_arg), + value_arg ? EncodableValue(*value_arg) : EncodableValue(), + }); + channel.Send(encoded_api_arguments, [channel_name, on_success = std::move(on_success), on_error = std::move(on_error)](const uint8_t* reply, size_t reply_size) { + std::unique_ptr response = GetCodec().DecodeMessage(reply, reply_size); + const auto& encodable_return_value = *response; + const auto* list_return_value = std::get_if(&encodable_return_value); + if (list_return_value) { + if (list_return_value->size() > 1) { + on_error(FlutterError(std::get(list_return_value->at(0)), std::get(list_return_value->at(1)), list_return_value->at(2))); + } else { + const auto* return_value = list_return_value->at(0).IsNull() ? nullptr : &(std::any_cast(std::get(list_return_value->at(0)))); + on_success(return_value); + } + } else { + on_error(CreateConnectionError(channel_name)); + } + }); +} + +void UniversalBlePeripheralCallback::OnDescriptorWriteRequest( + const std::string& device_id_arg, + const std::string& characteristic_id_arg, + const std::string& descriptor_id_arg, + int64_t offset_arg, + const std::vector* value_arg, + std::function&& on_success, + std::function&& on_error) { + const std::string channel_name = "dev.flutter.pigeon.universal_ble.UniversalBlePeripheralCallback.onDescriptorWriteRequest" + message_channel_suffix_; + BasicMessageChannel<> channel(binary_messenger_, channel_name, &GetCodec()); + EncodableValue encoded_api_arguments = EncodableValue(EncodableList{ + EncodableValue(device_id_arg), + EncodableValue(characteristic_id_arg), + EncodableValue(descriptor_id_arg), + EncodableValue(offset_arg), + value_arg ? EncodableValue(*value_arg) : EncodableValue(), + }); + channel.Send(encoded_api_arguments, [channel_name, on_success = std::move(on_success), on_error = std::move(on_error)](const uint8_t* reply, size_t reply_size) { + std::unique_ptr response = GetCodec().DecodeMessage(reply, reply_size); + const auto& encodable_return_value = *response; + const auto* list_return_value = std::get_if(&encodable_return_value); + if (list_return_value) { + if (list_return_value->size() > 1) { + on_error(FlutterError(std::get(list_return_value->at(0)), std::get(list_return_value->at(1)), list_return_value->at(2))); + } else { + const auto* return_value = list_return_value->at(0).IsNull() ? nullptr : &(std::any_cast(std::get(list_return_value->at(0)))); + on_success(return_value); + } + } else { + on_error(CreateConnectionError(channel_name)); + } + }); +} + +void UniversalBlePeripheralCallback::OnCharacteristicSubscriptionChange( + const std::string& device_id_arg, + const std::string& characteristic_id_arg, + bool is_subscribed_arg, + const std::string* name_arg, std::function&& on_success, std::function&& on_error) { - const std::string channel_name = "dev.flutter.pigeon.universal_ble.UniversalBleCallbackChannel.onAvailabilityChanged" + message_channel_suffix_; + const std::string channel_name = "dev.flutter.pigeon.universal_ble.UniversalBlePeripheralCallback.onCharacteristicSubscriptionChange" + message_channel_suffix_; BasicMessageChannel<> channel(binary_messenger_, channel_name, &GetCodec()); EncodableValue encoded_api_arguments = EncodableValue(EncodableList{ - EncodableValue(state_arg), + EncodableValue(device_id_arg), + EncodableValue(characteristic_id_arg), + EncodableValue(is_subscribed_arg), + name_arg ? EncodableValue(*name_arg) : EncodableValue(), }); channel.Send(encoded_api_arguments, [channel_name, on_success = std::move(on_success), on_error = std::move(on_error)](const uint8_t* reply, size_t reply_size) { std::unique_ptr response = GetCodec().DecodeMessage(reply, reply_size); @@ -1510,17 +3116,15 @@ void UniversalBleCallbackChannel::OnAvailabilityChanged( }); } -void UniversalBleCallbackChannel::OnPairStateChange( - const std::string& device_id_arg, - bool is_paired_arg, +void UniversalBlePeripheralCallback::OnAdvertisingStateChange( + const PeripheralAdvertisingState& state_arg, const std::string* error_arg, std::function&& on_success, std::function&& on_error) { - const std::string channel_name = "dev.flutter.pigeon.universal_ble.UniversalBleCallbackChannel.onPairStateChange" + message_channel_suffix_; + const std::string channel_name = "dev.flutter.pigeon.universal_ble.UniversalBlePeripheralCallback.onAdvertisingStateChange" + message_channel_suffix_; BasicMessageChannel<> channel(binary_messenger_, channel_name, &GetCodec()); EncodableValue encoded_api_arguments = EncodableValue(EncodableList{ - EncodableValue(device_id_arg), - EncodableValue(is_paired_arg), + CustomEncodableValue(state_arg), error_arg ? EncodableValue(*error_arg) : EncodableValue(), }); channel.Send(encoded_api_arguments, [channel_name, on_success = std::move(on_success), on_error = std::move(on_error)](const uint8_t* reply, size_t reply_size) { @@ -1539,14 +3143,16 @@ void UniversalBleCallbackChannel::OnPairStateChange( }); } -void UniversalBleCallbackChannel::OnScanResult( - const UniversalBleScanResult& result_arg, +void UniversalBlePeripheralCallback::OnServiceAdded( + const std::string& service_id_arg, + const std::string* error_arg, std::function&& on_success, std::function&& on_error) { - const std::string channel_name = "dev.flutter.pigeon.universal_ble.UniversalBleCallbackChannel.onScanResult" + message_channel_suffix_; + const std::string channel_name = "dev.flutter.pigeon.universal_ble.UniversalBlePeripheralCallback.onServiceAdded" + message_channel_suffix_; BasicMessageChannel<> channel(binary_messenger_, channel_name, &GetCodec()); EncodableValue encoded_api_arguments = EncodableValue(EncodableList{ - CustomEncodableValue(result_arg), + EncodableValue(service_id_arg), + error_arg ? EncodableValue(*error_arg) : EncodableValue(), }); channel.Send(encoded_api_arguments, [channel_name, on_success = std::move(on_success), on_error = std::move(on_error)](const uint8_t* reply, size_t reply_size) { std::unique_ptr response = GetCodec().DecodeMessage(reply, reply_size); @@ -1564,20 +3170,16 @@ void UniversalBleCallbackChannel::OnScanResult( }); } -void UniversalBleCallbackChannel::OnValueChanged( +void UniversalBlePeripheralCallback::OnMtuChange( const std::string& device_id_arg, - const std::string& characteristic_id_arg, - const std::vector& value_arg, - const int64_t* timestamp_arg, + int64_t mtu_arg, std::function&& on_success, std::function&& on_error) { - const std::string channel_name = "dev.flutter.pigeon.universal_ble.UniversalBleCallbackChannel.onValueChanged" + message_channel_suffix_; + const std::string channel_name = "dev.flutter.pigeon.universal_ble.UniversalBlePeripheralCallback.onMtuChange" + message_channel_suffix_; BasicMessageChannel<> channel(binary_messenger_, channel_name, &GetCodec()); EncodableValue encoded_api_arguments = EncodableValue(EncodableList{ EncodableValue(device_id_arg), - EncodableValue(characteristic_id_arg), - EncodableValue(value_arg), - timestamp_arg ? EncodableValue(*timestamp_arg) : EncodableValue(), + EncodableValue(mtu_arg), }); channel.Send(encoded_api_arguments, [channel_name, on_success = std::move(on_success), on_error = std::move(on_error)](const uint8_t* reply, size_t reply_size) { std::unique_ptr response = GetCodec().DecodeMessage(reply, reply_size); @@ -1595,18 +3197,16 @@ void UniversalBleCallbackChannel::OnValueChanged( }); } -void UniversalBleCallbackChannel::OnConnectionChanged( +void UniversalBlePeripheralCallback::OnConnectionStateChange( const std::string& device_id_arg, bool connected_arg, - const std::string* error_arg, std::function&& on_success, std::function&& on_error) { - const std::string channel_name = "dev.flutter.pigeon.universal_ble.UniversalBleCallbackChannel.onConnectionChanged" + message_channel_suffix_; + const std::string channel_name = "dev.flutter.pigeon.universal_ble.UniversalBlePeripheralCallback.onConnectionStateChange" + message_channel_suffix_; BasicMessageChannel<> channel(binary_messenger_, channel_name, &GetCodec()); EncodableValue encoded_api_arguments = EncodableValue(EncodableList{ EncodableValue(device_id_arg), EncodableValue(connected_arg), - error_arg ? EncodableValue(*error_arg) : EncodableValue(), }); channel.Send(encoded_api_arguments, [channel_name, on_success = std::move(on_success), on_error = std::move(on_error)](const uint8_t* reply, size_t reply_size) { std::unique_ptr response = GetCodec().DecodeMessage(reply, reply_size); diff --git a/windows/src/generated/universal_ble.g.h b/windows/src/generated/universal_ble.g.h index 88cb5177..8951dfcc 100644 --- a/windows/src/generated/universal_ble.g.h +++ b/windows/src/generated/universal_ble.g.h @@ -1,4 +1,4 @@ -// Autogenerated from Pigeon (v26.1.4), do not edit directly. +// Autogenerated from Pigeon (v26.3.3), do not edit directly. // See also: https://pub.dev/packages/pigeon #ifndef PIGEON_UNIVERSAL_BLE_G_H_ @@ -23,17 +23,17 @@ class FlutterError { : code_(code) {} explicit FlutterError(const std::string& code, const std::string& message) : code_(code), message_(message) {} - explicit FlutterError(const std::string& code, const std::string& message, const flutter::EncodableValue& details) + explicit FlutterError(const std::string& code, const std::string& message, const ::flutter::EncodableValue& details) : code_(code), message_(message), details_(details) {} const std::string& code() const { return code_; } const std::string& message() const { return message_; } - const flutter::EncodableValue& details() const { return details_; } + const ::flutter::EncodableValue& details() const { return details_; } private: std::string code_; std::string message_; - flutter::EncodableValue details_; + ::flutter::EncodableValue details_; }; template class ErrorOr { @@ -50,6 +50,8 @@ template class ErrorOr { private: friend class UniversalBlePlatformChannel; friend class UniversalBleCallbackChannel; + friend class UniversalBlePeripheralChannel; + friend class UniversalBlePeripheralCallback; ErrorOr() = default; T TakeValue() && { return std::get(std::move(v_)); } @@ -74,6 +76,22 @@ enum class AndroidScanMode { kOpportunistic = 3 }; +enum class PeripheralReadinessState { + kUnknown = 0, + kReady = 1, + kBluetoothOff = 2, + kUnauthorized = 3, + kUnsupported = 4 +}; + +enum class PeripheralAdvertisingState { + kIdle = 0, + kStarting = 1, + kAdvertising = 2, + kStopping = 3, + kError = 4 +}; + // Unified error codes for all platforms enum class UniversalBleErrorCode { kUnknownError = 0, @@ -152,9 +170,9 @@ class UniversalBleScanResult { const std::string* name, const bool* is_paired, const int64_t* rssi, - const flutter::EncodableList* manufacturer_data_list, - const flutter::EncodableMap* service_data, - const flutter::EncodableList* services, + const ::flutter::EncodableList* manufacturer_data_list, + const ::flutter::EncodableMap* service_data, + const ::flutter::EncodableList* services, const int64_t* timestamp); const std::string& device_id() const; @@ -172,35 +190,41 @@ class UniversalBleScanResult { void set_rssi(const int64_t* value_arg); void set_rssi(int64_t value_arg); - const flutter::EncodableList* manufacturer_data_list() const; - void set_manufacturer_data_list(const flutter::EncodableList* value_arg); - void set_manufacturer_data_list(const flutter::EncodableList& value_arg); + const ::flutter::EncodableList* manufacturer_data_list() const; + void set_manufacturer_data_list(const ::flutter::EncodableList* value_arg); + void set_manufacturer_data_list(const ::flutter::EncodableList& value_arg); - const flutter::EncodableMap* service_data() const; - void set_service_data(const flutter::EncodableMap* value_arg); - void set_service_data(const flutter::EncodableMap& value_arg); + const ::flutter::EncodableMap* service_data() const; + void set_service_data(const ::flutter::EncodableMap* value_arg); + void set_service_data(const ::flutter::EncodableMap& value_arg); - const flutter::EncodableList* services() const; - void set_services(const flutter::EncodableList* value_arg); - void set_services(const flutter::EncodableList& value_arg); + const ::flutter::EncodableList* services() const; + void set_services(const ::flutter::EncodableList* value_arg); + void set_services(const ::flutter::EncodableList& value_arg); const int64_t* timestamp() const; void set_timestamp(const int64_t* value_arg); void set_timestamp(int64_t value_arg); + bool operator==(const UniversalBleScanResult& other) const; + bool operator!=(const UniversalBleScanResult& other) const; + /// Returns a hash code value for the object. This method is supported for the benefit of hash tables. + size_t Hash() const; private: - static UniversalBleScanResult FromEncodableList(const flutter::EncodableList& list); - flutter::EncodableList ToEncodableList() const; + static UniversalBleScanResult FromEncodableList(const ::flutter::EncodableList& list); + ::flutter::EncodableList ToEncodableList() const; friend class UniversalBlePlatformChannel; friend class UniversalBleCallbackChannel; + friend class UniversalBlePeripheralChannel; + friend class UniversalBlePeripheralCallback; friend class PigeonInternalCodecSerializer; std::string device_id_; std::optional name_; std::optional is_paired_; std::optional rssi_; - std::optional manufacturer_data_list_; - std::optional service_data_; - std::optional services_; + std::optional<::flutter::EncodableList> manufacturer_data_list_; + std::optional<::flutter::EncodableMap> service_data_; + std::optional<::flutter::EncodableList> services_; std::optional timestamp_; }; @@ -214,23 +238,29 @@ class UniversalBleService { // Constructs an object setting all fields. explicit UniversalBleService( const std::string& uuid, - const flutter::EncodableList* characteristics); + const ::flutter::EncodableList* characteristics); const std::string& uuid() const; void set_uuid(std::string_view value_arg); - const flutter::EncodableList* characteristics() const; - void set_characteristics(const flutter::EncodableList* value_arg); - void set_characteristics(const flutter::EncodableList& value_arg); + const ::flutter::EncodableList* characteristics() const; + void set_characteristics(const ::flutter::EncodableList* value_arg); + void set_characteristics(const ::flutter::EncodableList& value_arg); + bool operator==(const UniversalBleService& other) const; + bool operator!=(const UniversalBleService& other) const; + /// Returns a hash code value for the object. This method is supported for the benefit of hash tables. + size_t Hash() const; private: - static UniversalBleService FromEncodableList(const flutter::EncodableList& list); - flutter::EncodableList ToEncodableList() const; + static UniversalBleService FromEncodableList(const ::flutter::EncodableList& list); + ::flutter::EncodableList ToEncodableList() const; friend class UniversalBlePlatformChannel; friend class UniversalBleCallbackChannel; + friend class UniversalBlePeripheralChannel; + friend class UniversalBlePeripheralCallback; friend class PigeonInternalCodecSerializer; std::string uuid_; - std::optional characteristics_; + std::optional<::flutter::EncodableList> characteristics_; }; @@ -240,27 +270,33 @@ class UniversalBleCharacteristic { // Constructs an object setting all fields. explicit UniversalBleCharacteristic( const std::string& uuid, - const flutter::EncodableList& properties, - const flutter::EncodableList& descriptors); + const ::flutter::EncodableList& properties, + const ::flutter::EncodableList& descriptors); const std::string& uuid() const; void set_uuid(std::string_view value_arg); - const flutter::EncodableList& properties() const; - void set_properties(const flutter::EncodableList& value_arg); + const ::flutter::EncodableList& properties() const; + void set_properties(const ::flutter::EncodableList& value_arg); - const flutter::EncodableList& descriptors() const; - void set_descriptors(const flutter::EncodableList& value_arg); + const ::flutter::EncodableList& descriptors() const; + void set_descriptors(const ::flutter::EncodableList& value_arg); + bool operator==(const UniversalBleCharacteristic& other) const; + bool operator!=(const UniversalBleCharacteristic& other) const; + /// Returns a hash code value for the object. This method is supported for the benefit of hash tables. + size_t Hash() const; private: - static UniversalBleCharacteristic FromEncodableList(const flutter::EncodableList& list); - flutter::EncodableList ToEncodableList() const; + static UniversalBleCharacteristic FromEncodableList(const ::flutter::EncodableList& list); + ::flutter::EncodableList ToEncodableList() const; friend class UniversalBlePlatformChannel; friend class UniversalBleCallbackChannel; + friend class UniversalBlePeripheralChannel; + friend class UniversalBlePeripheralCallback; friend class PigeonInternalCodecSerializer; std::string uuid_; - flutter::EncodableList properties_; - flutter::EncodableList descriptors_; + ::flutter::EncodableList properties_; + ::flutter::EncodableList descriptors_; }; @@ -273,11 +309,17 @@ class UniversalBleDescriptor { const std::string& uuid() const; void set_uuid(std::string_view value_arg); + bool operator==(const UniversalBleDescriptor& other) const; + bool operator!=(const UniversalBleDescriptor& other) const; + /// Returns a hash code value for the object. This method is supported for the benefit of hash tables. + size_t Hash() const; private: - static UniversalBleDescriptor FromEncodableList(const flutter::EncodableList& list); - flutter::EncodableList ToEncodableList() const; + static UniversalBleDescriptor FromEncodableList(const ::flutter::EncodableList& list); + ::flutter::EncodableList ToEncodableList() const; friend class UniversalBlePlatformChannel; friend class UniversalBleCallbackChannel; + friend class UniversalBlePeripheralChannel; + friend class UniversalBlePeripheralCallback; friend class PigeonInternalCodecSerializer; std::string uuid_; }; @@ -314,12 +356,18 @@ class AndroidOptions { void set_report_delay_millis(const int64_t* value_arg); void set_report_delay_millis(int64_t value_arg); + bool operator==(const AndroidOptions& other) const; + bool operator!=(const AndroidOptions& other) const; + /// Returns a hash code value for the object. This method is supported for the benefit of hash tables. + size_t Hash() const; private: - static AndroidOptions FromEncodableList(const flutter::EncodableList& list); - flutter::EncodableList ToEncodableList() const; + static AndroidOptions FromEncodableList(const ::flutter::EncodableList& list); + ::flutter::EncodableList ToEncodableList() const; friend class UniversalScanConfig; friend class UniversalBlePlatformChannel; friend class UniversalBleCallbackChannel; + friend class UniversalBlePeripheralChannel; + friend class UniversalBlePeripheralCallback; friend class PigeonInternalCodecSerializer; std::optional request_location_permission_; std::optional scan_mode_; @@ -345,11 +393,17 @@ class UniversalScanConfig { void set_android(const AndroidOptions* value_arg); void set_android(const AndroidOptions& value_arg); + bool operator==(const UniversalScanConfig& other) const; + bool operator!=(const UniversalScanConfig& other) const; + /// Returns a hash code value for the object. This method is supported for the benefit of hash tables. + size_t Hash() const; private: - static UniversalScanConfig FromEncodableList(const flutter::EncodableList& list); - flutter::EncodableList ToEncodableList() const; + static UniversalScanConfig FromEncodableList(const ::flutter::EncodableList& list); + ::flutter::EncodableList ToEncodableList() const; friend class UniversalBlePlatformChannel; friend class UniversalBleCallbackChannel; + friend class UniversalBlePeripheralChannel; + friend class UniversalBlePeripheralCallback; friend class PigeonInternalCodecSerializer; std::unique_ptr android_; }; @@ -362,28 +416,34 @@ class UniversalScanFilter { public: // Constructs an object setting all fields. explicit UniversalScanFilter( - const flutter::EncodableList& with_services, - const flutter::EncodableList& with_name_prefix, - const flutter::EncodableList& with_manufacturer_data); + const ::flutter::EncodableList& with_services, + const ::flutter::EncodableList& with_name_prefix, + const ::flutter::EncodableList& with_manufacturer_data); - const flutter::EncodableList& with_services() const; - void set_with_services(const flutter::EncodableList& value_arg); + const ::flutter::EncodableList& with_services() const; + void set_with_services(const ::flutter::EncodableList& value_arg); - const flutter::EncodableList& with_name_prefix() const; - void set_with_name_prefix(const flutter::EncodableList& value_arg); + const ::flutter::EncodableList& with_name_prefix() const; + void set_with_name_prefix(const ::flutter::EncodableList& value_arg); - const flutter::EncodableList& with_manufacturer_data() const; - void set_with_manufacturer_data(const flutter::EncodableList& value_arg); + const ::flutter::EncodableList& with_manufacturer_data() const; + void set_with_manufacturer_data(const ::flutter::EncodableList& value_arg); + bool operator==(const UniversalScanFilter& other) const; + bool operator!=(const UniversalScanFilter& other) const; + /// Returns a hash code value for the object. This method is supported for the benefit of hash tables. + size_t Hash() const; private: - static UniversalScanFilter FromEncodableList(const flutter::EncodableList& list); - flutter::EncodableList ToEncodableList() const; + static UniversalScanFilter FromEncodableList(const ::flutter::EncodableList& list); + ::flutter::EncodableList ToEncodableList() const; friend class UniversalBlePlatformChannel; friend class UniversalBleCallbackChannel; + friend class UniversalBlePeripheralChannel; + friend class UniversalBlePeripheralCallback; friend class PigeonInternalCodecSerializer; - flutter::EncodableList with_services_; - flutter::EncodableList with_name_prefix_; - flutter::EncodableList with_manufacturer_data_; + ::flutter::EncodableList with_services_; + ::flutter::EncodableList with_name_prefix_; + ::flutter::EncodableList with_manufacturer_data_; }; @@ -410,11 +470,17 @@ class UniversalManufacturerDataFilter { void set_mask(const std::vector* value_arg); void set_mask(const std::vector& value_arg); + bool operator==(const UniversalManufacturerDataFilter& other) const; + bool operator!=(const UniversalManufacturerDataFilter& other) const; + /// Returns a hash code value for the object. This method is supported for the benefit of hash tables. + size_t Hash() const; private: - static UniversalManufacturerDataFilter FromEncodableList(const flutter::EncodableList& list); - flutter::EncodableList ToEncodableList() const; + static UniversalManufacturerDataFilter FromEncodableList(const ::flutter::EncodableList& list); + ::flutter::EncodableList ToEncodableList() const; friend class UniversalBlePlatformChannel; friend class UniversalBleCallbackChannel; + friend class UniversalBlePeripheralChannel; + friend class UniversalBlePeripheralCallback; friend class PigeonInternalCodecSerializer; int64_t company_identifier_; std::optional> data_; @@ -436,18 +502,269 @@ class UniversalManufacturerData { const std::vector& data() const; void set_data(const std::vector& value_arg); + bool operator==(const UniversalManufacturerData& other) const; + bool operator!=(const UniversalManufacturerData& other) const; + /// Returns a hash code value for the object. This method is supported for the benefit of hash tables. + size_t Hash() const; private: - static UniversalManufacturerData FromEncodableList(const flutter::EncodableList& list); - flutter::EncodableList ToEncodableList() const; + static UniversalManufacturerData FromEncodableList(const ::flutter::EncodableList& list); + ::flutter::EncodableList ToEncodableList() const; friend class UniversalBlePlatformChannel; friend class UniversalBleCallbackChannel; + friend class UniversalBlePeripheralChannel; + friend class UniversalBlePeripheralCallback; friend class PigeonInternalCodecSerializer; int64_t company_identifier_; std::vector data_; }; -class PigeonInternalCodecSerializer : public flutter::StandardCodecSerializer { +// Generated class from Pigeon that represents data sent in messages. +class PeripheralService { + public: + // Constructs an object setting all fields. + explicit PeripheralService( + const std::string& uuid, + bool primary, + const ::flutter::EncodableList& characteristics); + + const std::string& uuid() const; + void set_uuid(std::string_view value_arg); + + bool primary() const; + void set_primary(bool value_arg); + + const ::flutter::EncodableList& characteristics() const; + void set_characteristics(const ::flutter::EncodableList& value_arg); + + bool operator==(const PeripheralService& other) const; + bool operator!=(const PeripheralService& other) const; + /// Returns a hash code value for the object. This method is supported for the benefit of hash tables. + size_t Hash() const; + private: + static PeripheralService FromEncodableList(const ::flutter::EncodableList& list); + ::flutter::EncodableList ToEncodableList() const; + friend class UniversalBlePlatformChannel; + friend class UniversalBleCallbackChannel; + friend class UniversalBlePeripheralChannel; + friend class UniversalBlePeripheralCallback; + friend class PigeonInternalCodecSerializer; + std::string uuid_; + bool primary_; + ::flutter::EncodableList characteristics_; +}; + + +// Generated class from Pigeon that represents data sent in messages. +class PeripheralCharacteristic { + public: + // Constructs an object setting all non-nullable fields. + explicit PeripheralCharacteristic( + const std::string& uuid, + const ::flutter::EncodableList& properties, + const ::flutter::EncodableList& permissions); + + // Constructs an object setting all fields. + explicit PeripheralCharacteristic( + const std::string& uuid, + const ::flutter::EncodableList& properties, + const ::flutter::EncodableList& permissions, + const ::flutter::EncodableList* descriptors, + const std::vector* value); + + const std::string& uuid() const; + void set_uuid(std::string_view value_arg); + + const ::flutter::EncodableList& properties() const; + void set_properties(const ::flutter::EncodableList& value_arg); + + const ::flutter::EncodableList& permissions() const; + void set_permissions(const ::flutter::EncodableList& value_arg); + + const ::flutter::EncodableList* descriptors() const; + void set_descriptors(const ::flutter::EncodableList* value_arg); + void set_descriptors(const ::flutter::EncodableList& value_arg); + + const std::vector* value() const; + void set_value(const std::vector* value_arg); + void set_value(const std::vector& value_arg); + + bool operator==(const PeripheralCharacteristic& other) const; + bool operator!=(const PeripheralCharacteristic& other) const; + /// Returns a hash code value for the object. This method is supported for the benefit of hash tables. + size_t Hash() const; + private: + static PeripheralCharacteristic FromEncodableList(const ::flutter::EncodableList& list); + ::flutter::EncodableList ToEncodableList() const; + friend class UniversalBlePlatformChannel; + friend class UniversalBleCallbackChannel; + friend class UniversalBlePeripheralChannel; + friend class UniversalBlePeripheralCallback; + friend class PigeonInternalCodecSerializer; + std::string uuid_; + ::flutter::EncodableList properties_; + ::flutter::EncodableList permissions_; + std::optional<::flutter::EncodableList> descriptors_; + std::optional> value_; +}; + + +// Generated class from Pigeon that represents data sent in messages. +class PeripheralDescriptor { + public: + // Constructs an object setting all non-nullable fields. + explicit PeripheralDescriptor(const std::string& uuid); + + // Constructs an object setting all fields. + explicit PeripheralDescriptor( + const std::string& uuid, + const std::vector* value, + const ::flutter::EncodableList* permissions); + + const std::string& uuid() const; + void set_uuid(std::string_view value_arg); + + const std::vector* value() const; + void set_value(const std::vector* value_arg); + void set_value(const std::vector& value_arg); + + const ::flutter::EncodableList* permissions() const; + void set_permissions(const ::flutter::EncodableList* value_arg); + void set_permissions(const ::flutter::EncodableList& value_arg); + + bool operator==(const PeripheralDescriptor& other) const; + bool operator!=(const PeripheralDescriptor& other) const; + /// Returns a hash code value for the object. This method is supported for the benefit of hash tables. + size_t Hash() const; + private: + static PeripheralDescriptor FromEncodableList(const ::flutter::EncodableList& list); + ::flutter::EncodableList ToEncodableList() const; + friend class UniversalBlePlatformChannel; + friend class UniversalBleCallbackChannel; + friend class UniversalBlePeripheralChannel; + friend class UniversalBlePeripheralCallback; + friend class PigeonInternalCodecSerializer; + std::string uuid_; + std::optional> value_; + std::optional<::flutter::EncodableList> permissions_; +}; + + +// Generated class from Pigeon that represents data sent in messages. +class PeripheralReadRequestResult { + public: + // Constructs an object setting all non-nullable fields. + explicit PeripheralReadRequestResult(const std::vector& value); + + // Constructs an object setting all fields. + explicit PeripheralReadRequestResult( + const std::vector& value, + const int64_t* offset, + const int64_t* status); + + const std::vector& value() const; + void set_value(const std::vector& value_arg); + + const int64_t* offset() const; + void set_offset(const int64_t* value_arg); + void set_offset(int64_t value_arg); + + const int64_t* status() const; + void set_status(const int64_t* value_arg); + void set_status(int64_t value_arg); + + bool operator==(const PeripheralReadRequestResult& other) const; + bool operator!=(const PeripheralReadRequestResult& other) const; + /// Returns a hash code value for the object. This method is supported for the benefit of hash tables. + size_t Hash() const; + private: + static PeripheralReadRequestResult FromEncodableList(const ::flutter::EncodableList& list); + ::flutter::EncodableList ToEncodableList() const; + friend class UniversalBlePlatformChannel; + friend class UniversalBleCallbackChannel; + friend class UniversalBlePeripheralChannel; + friend class UniversalBlePeripheralCallback; + friend class PigeonInternalCodecSerializer; + std::vector value_; + std::optional offset_; + std::optional status_; +}; + + +// Generated class from Pigeon that represents data sent in messages. +class PeripheralWriteRequestResult { + public: + // Constructs an object setting all non-nullable fields. + PeripheralWriteRequestResult(); + + // Constructs an object setting all fields. + explicit PeripheralWriteRequestResult( + const std::vector* value, + const int64_t* offset, + const int64_t* status); + + const std::vector* value() const; + void set_value(const std::vector* value_arg); + void set_value(const std::vector& value_arg); + + const int64_t* offset() const; + void set_offset(const int64_t* value_arg); + void set_offset(int64_t value_arg); + + const int64_t* status() const; + void set_status(const int64_t* value_arg); + void set_status(int64_t value_arg); + + bool operator==(const PeripheralWriteRequestResult& other) const; + bool operator!=(const PeripheralWriteRequestResult& other) const; + /// Returns a hash code value for the object. This method is supported for the benefit of hash tables. + size_t Hash() const; + private: + static PeripheralWriteRequestResult FromEncodableList(const ::flutter::EncodableList& list); + ::flutter::EncodableList ToEncodableList() const; + friend class UniversalBlePlatformChannel; + friend class UniversalBleCallbackChannel; + friend class UniversalBlePeripheralChannel; + friend class UniversalBlePeripheralCallback; + friend class PigeonInternalCodecSerializer; + std::optional> value_; + std::optional offset_; + std::optional status_; +}; + + +// Generated class from Pigeon that represents data sent in messages. +class PeripheralManufacturerData { + public: + // Constructs an object setting all fields. + explicit PeripheralManufacturerData( + int64_t manufacturer_id, + const std::vector& data); + + int64_t manufacturer_id() const; + void set_manufacturer_id(int64_t value_arg); + + const std::vector& data() const; + void set_data(const std::vector& value_arg); + + bool operator==(const PeripheralManufacturerData& other) const; + bool operator!=(const PeripheralManufacturerData& other) const; + /// Returns a hash code value for the object. This method is supported for the benefit of hash tables. + size_t Hash() const; + private: + static PeripheralManufacturerData FromEncodableList(const ::flutter::EncodableList& list); + ::flutter::EncodableList ToEncodableList() const; + friend class UniversalBlePlatformChannel; + friend class UniversalBleCallbackChannel; + friend class UniversalBlePeripheralChannel; + friend class UniversalBlePeripheralCallback; + friend class PigeonInternalCodecSerializer; + int64_t manufacturer_id_; + std::vector data_; +}; + + +class PigeonInternalCodecSerializer : public ::flutter::StandardCodecSerializer { public: PigeonInternalCodecSerializer(); inline static PigeonInternalCodecSerializer& GetInstance() { @@ -456,12 +773,12 @@ class PigeonInternalCodecSerializer : public flutter::StandardCodecSerializer { } void WriteValue( - const flutter::EncodableValue& value, - flutter::ByteStreamWriter* stream) const override; + const ::flutter::EncodableValue& value, + ::flutter::ByteStreamWriter* stream) const override; protected: - flutter::EncodableValue ReadValueOfType( + ::flutter::EncodableValue ReadValueOfType( uint8_t type, - flutter::ByteStreamReader* stream) const override; + ::flutter::ByteStreamReader* stream) const override; }; // Flutter -> Native @@ -497,7 +814,7 @@ class UniversalBlePlatformChannel { virtual void DiscoverServices( const std::string& device_id, bool with_descriptors, - std::function reply)> result) = 0; + std::function reply)> result) = 0; virtual void ReadValue( const std::string& device_id, const std::string& service, @@ -522,8 +839,8 @@ class UniversalBlePlatformChannel { std::function reply)> result) = 0; virtual std::optional UnPair(const std::string& device_id) = 0; virtual void GetSystemDevices( - const flutter::EncodableList& with_services, - std::function reply)> result) = 0; + const ::flutter::EncodableList& with_services, + std::function reply)> result) = 0; virtual ErrorOr GetConnectionState(const std::string& device_id) = 0; virtual void ReadRssi( const std::string& device_id, @@ -535,17 +852,17 @@ class UniversalBlePlatformChannel { virtual std::optional SetLogLevel(const UniversalBleLogLevel& log_level) = 0; // The codec used by UniversalBlePlatformChannel. - static const flutter::StandardMessageCodec& GetCodec(); + static const ::flutter::StandardMessageCodec& GetCodec(); // Sets up an instance of `UniversalBlePlatformChannel` to handle messages through the `binary_messenger`. static void SetUp( - flutter::BinaryMessenger* binary_messenger, + ::flutter::BinaryMessenger* binary_messenger, UniversalBlePlatformChannel* api); static void SetUp( - flutter::BinaryMessenger* binary_messenger, + ::flutter::BinaryMessenger* binary_messenger, UniversalBlePlatformChannel* api, const std::string& message_channel_suffix); - static flutter::EncodableValue WrapError(std::string_view error_message); - static flutter::EncodableValue WrapError(const FlutterError& error); + static ::flutter::EncodableValue WrapError(std::string_view error_message); + static ::flutter::EncodableValue WrapError(const FlutterError& error); protected: UniversalBlePlatformChannel() = default; }; @@ -554,11 +871,11 @@ class UniversalBlePlatformChannel { // Generated class from Pigeon that represents Flutter messages that can be called from C++. class UniversalBleCallbackChannel { public: - UniversalBleCallbackChannel(flutter::BinaryMessenger* binary_messenger); + UniversalBleCallbackChannel(::flutter::BinaryMessenger* binary_messenger); UniversalBleCallbackChannel( - flutter::BinaryMessenger* binary_messenger, + ::flutter::BinaryMessenger* binary_messenger, const std::string& message_channel_suffix); - static const flutter::StandardMessageCodec& GetCodec(); + static const ::flutter::StandardMessageCodec& GetCodec(); void OnAvailabilityChanged( int64_t state, std::function&& on_success, @@ -587,7 +904,125 @@ class UniversalBleCallbackChannel { std::function&& on_success, std::function&& on_error); private: - flutter::BinaryMessenger* binary_messenger_; + ::flutter::BinaryMessenger* binary_messenger_; + std::string message_channel_suffix_; +}; + +// Flutter -> Native (peripheral) +// +// Generated interface from Pigeon that represents a handler of messages from Flutter. +class UniversalBlePeripheralChannel { + public: + UniversalBlePeripheralChannel(const UniversalBlePeripheralChannel&) = delete; + UniversalBlePeripheralChannel& operator=(const UniversalBlePeripheralChannel&) = delete; + virtual ~UniversalBlePeripheralChannel() {} + virtual ErrorOr GetAdvertisingState() = 0; + virtual ErrorOr GetReadinessState() = 0; + virtual std::optional StopAdvertising() = 0; + virtual std::optional AddService(const PeripheralService& service) = 0; + virtual std::optional RemoveService(const std::string& service_id) = 0; + virtual std::optional ClearServices() = 0; + virtual ErrorOr<::flutter::EncodableList> GetServices() = 0; + virtual std::optional StartAdvertising( + const ::flutter::EncodableList& services, + const std::string* local_name, + const int64_t* timeout, + const PeripheralManufacturerData* manufacturer_data, + bool add_manufacturer_data_in_scan_response) = 0; + virtual std::optional UpdateCharacteristic( + const std::string& characteristic_id, + const std::vector& value, + const std::string* device_id) = 0; + // Returns peripheral-client device ids currently subscribed to [characteristicId] + // (e.g. HID report characteristic). Used to restore app state after restart. + virtual ErrorOr<::flutter::EncodableList> GetSubscribedClients(const std::string& characteristic_id) = 0; + // Returns max characteristic notify payload length for a connected client. + virtual ErrorOr> GetMaximumNotifyLength(const std::string& device_id) = 0; + + // The codec used by UniversalBlePeripheralChannel. + static const ::flutter::StandardMessageCodec& GetCodec(); + // Sets up an instance of `UniversalBlePeripheralChannel` to handle messages through the `binary_messenger`. + static void SetUp( + ::flutter::BinaryMessenger* binary_messenger, + UniversalBlePeripheralChannel* api); + static void SetUp( + ::flutter::BinaryMessenger* binary_messenger, + UniversalBlePeripheralChannel* api, + const std::string& message_channel_suffix); + static ::flutter::EncodableValue WrapError(std::string_view error_message); + static ::flutter::EncodableValue WrapError(const FlutterError& error); + protected: + UniversalBlePeripheralChannel() = default; +}; +// Native -> Flutter (peripheral) +// +// Generated class from Pigeon that represents Flutter messages that can be called from C++. +class UniversalBlePeripheralCallback { + public: + UniversalBlePeripheralCallback(::flutter::BinaryMessenger* binary_messenger); + UniversalBlePeripheralCallback( + ::flutter::BinaryMessenger* binary_messenger, + const std::string& message_channel_suffix); + static const ::flutter::StandardMessageCodec& GetCodec(); + void OnReadRequest( + const std::string& device_id, + const std::string& characteristic_id, + int64_t offset, + const std::vector* value, + std::function&& on_success, + std::function&& on_error); + void OnWriteRequest( + const std::string& device_id, + const std::string& characteristic_id, + int64_t offset, + const std::vector* value, + std::function&& on_success, + std::function&& on_error); + void OnDescriptorReadRequest( + const std::string& device_id, + const std::string& characteristic_id, + const std::string& descriptor_id, + int64_t offset, + const std::vector* value, + std::function&& on_success, + std::function&& on_error); + void OnDescriptorWriteRequest( + const std::string& device_id, + const std::string& characteristic_id, + const std::string& descriptor_id, + int64_t offset, + const std::vector* value, + std::function&& on_success, + std::function&& on_error); + void OnCharacteristicSubscriptionChange( + const std::string& device_id, + const std::string& characteristic_id, + bool is_subscribed, + const std::string* name, + std::function&& on_success, + std::function&& on_error); + void OnAdvertisingStateChange( + const PeripheralAdvertisingState& state, + const std::string* error, + std::function&& on_success, + std::function&& on_error); + void OnServiceAdded( + const std::string& service_id, + const std::string* error, + std::function&& on_success, + std::function&& on_error); + void OnMtuChange( + const std::string& device_id, + int64_t mtu, + std::function&& on_success, + std::function&& on_error); + void OnConnectionStateChange( + const std::string& device_id, + bool connected, + std::function&& on_success, + std::function&& on_error); + private: + ::flutter::BinaryMessenger* binary_messenger_; std::string message_channel_suffix_; }; diff --git a/windows/src/universal_ble_plugin.cpp b/windows/src/universal_ble_plugin.cpp index 3bf1c2e1..b3baf409 100644 --- a/windows/src/universal_ble_plugin.cpp +++ b/windows/src/universal_ble_plugin.cpp @@ -6,6 +6,7 @@ #include #include +#include #include #include #include @@ -39,6 +40,7 @@ void UniversalBlePlugin::RegisterWithRegistrar( flutter::PluginRegistrarWindows *registrar) { auto plugin = std::make_unique(registrar); SetUp(registrar->messenger(), plugin.get()); + UniversalBlePeripheralChannel::SetUp(registrar->messenger(), plugin.get()); callback_channel = std::make_unique(registrar->messenger()); registrar->AddPlugin(std::move(plugin)); @@ -46,11 +48,16 @@ void UniversalBlePlugin::RegisterWithRegistrar( UniversalBlePlugin::UniversalBlePlugin( flutter::PluginRegistrarWindows *registrar) - : ui_thread_handler_(registrar) { + : registrar_(registrar), ui_thread_handler_(registrar) { + peripheral_callback_channel_ = + std::make_unique(registrar->messenger()); InitializeAsync(); } -UniversalBlePlugin::~UniversalBlePlugin() = default; +UniversalBlePlugin::~UniversalBlePlugin() { + ClearServices(); + peripheral_callback_channel_.reset(); +} // UniversalBlePlatformChannel implementation. void UniversalBlePlugin::GetBluetoothAvailabilityState( @@ -1613,4 +1620,778 @@ void UniversalBlePlugin::GattCharacteristicValueChanged( } } +ErrorOr UniversalBlePlugin::GetAdvertisingState() { + std::lock_guard lock(peripheral_mutex_); + if (peripheral_service_provider_map_.empty()) { + return PeripheralAdvertisingState::kIdle; + } + return ArePeripheralAdvertisingTargetsStarted() + ? PeripheralAdvertisingState::kAdvertising + : PeripheralAdvertisingState::kIdle; +} + +ErrorOr UniversalBlePlugin::GetReadinessState() { + if (!bluetooth_radio_) { + return PeripheralReadinessState::kUnsupported; + } + return PeripheralReadinessState::kReady; +} + +std::optional UniversalBlePlugin::StopAdvertising() { + std::lock_guard lock(peripheral_mutex_); + peripheral_advertising_targets_lc_.clear(); + for (auto const &[key, provider] : peripheral_service_provider_map_) { + try { + provider->obj.StopAdvertising(); + } catch (...) { + } + } + ui_thread_handler_.Post([this] { + peripheral_callback_channel_->OnAdvertisingStateChange( + PeripheralAdvertisingState::kIdle, nullptr, SuccessCallback, + ErrorCallback); + }); + return std::nullopt; +} + +std::optional UniversalBlePlugin::AddService( + const PeripheralService &service) { + PeripheralAddServiceAsync(service); + return std::nullopt; +} + +std::optional UniversalBlePlugin::RemoveService( + const std::string &service_id) { + std::lock_guard lock(peripheral_mutex_); + const std::string service_id_lc = to_lower_case(service_id); + peripheral_advertising_targets_lc_.erase( + std::remove(peripheral_advertising_targets_lc_.begin(), + peripheral_advertising_targets_lc_.end(), service_id_lc), + peripheral_advertising_targets_lc_.end()); + const auto it = peripheral_service_provider_map_.find(service_id_lc); + if (it == peripheral_service_provider_map_.end()) { + return FlutterError("not-found", "Service not found", nullptr); + } + DisposePeripheralServiceProvider(it->second.get()); + peripheral_service_provider_map_.erase(it); + return std::nullopt; +} + +std::optional UniversalBlePlugin::ClearServices() { + std::lock_guard lock(peripheral_mutex_); + peripheral_advertising_targets_lc_.clear(); + for (auto const &[_, provider] : peripheral_service_provider_map_) { + DisposePeripheralServiceProvider(provider.get()); + } + peripheral_service_provider_map_.clear(); + return std::nullopt; +} + +ErrorOr UniversalBlePlugin::GetServices() { + std::lock_guard lock(peripheral_mutex_); + flutter::EncodableList services; + for (auto const &[key, _] : peripheral_service_provider_map_) { + services.emplace_back(key); + } + return services; +} + +ErrorOr UniversalBlePlugin::GetSubscribedClients( + const std::string &characteristic_id) { + std::lock_guard lock(peripheral_mutex_); + flutter::EncodableList out; + auto *char_obj = FindPeripheralGattCharacteristicObject(characteristic_id); + if (char_obj == nullptr || char_obj->obj == nullptr) { + return out; + } + try { + auto clients = char_obj->obj.SubscribedClients(); + for (uint32_t i = 0; i < clients.Size(); ++i) { + auto client = clients.GetAt(i); + out.push_back(flutter::EncodableValue(ParsePeripheralBluetoothClientId( + client.Session().DeviceId().Id()))); + } + } catch (...) { + } + return out; +} + +ErrorOr> UniversalBlePlugin::GetMaximumNotifyLength( + const std::string &device_id) { + std::lock_guard lock(peripheral_mutex_); + for (auto const &[_, service_provider] : peripheral_service_provider_map_) { + for (auto const &[_, characteristic_object] : + service_provider->characteristics) { + try { + auto clients = characteristic_object->obj.SubscribedClients(); + for (uint32_t i = 0; i < clients.Size(); ++i) { + auto client = clients.GetAt(i); + auto id = ParsePeripheralBluetoothClientId(client.Session().DeviceId().Id()); + if (to_lower_case(id) != to_lower_case(device_id)) { + continue; + } + const int64_t pdu_size = static_cast(client.Session().MaxPduSize()); + return std::max(0, pdu_size - 3); + } + } catch (...) { + } + } + } + return std::optional{}; +} + +std::optional UniversalBlePlugin::StartAdvertising( + const flutter::EncodableList &services, const std::string *local_name, + const int64_t *timeout, + const PeripheralManufacturerData *manufacturer_data, + bool add_manufacturer_data_in_scan_response) { + std::lock_guard lock(peripheral_mutex_); + if (peripheral_service_provider_map_.empty()) { + return FlutterError("failed", "No services added to advertise", nullptr); + } + if (local_name != nullptr) { + return FlutterError( + "not-supported", + "Windows GattServiceProvider advertising does not support overriding local name", + nullptr); + } + if (manufacturer_data != nullptr || add_manufacturer_data_in_scan_response) { + return FlutterError( + "not-supported", + "Windows GattServiceProvider advertising does not support manufacturer data", + nullptr); + } + if (timeout != nullptr && *timeout > 0) { + return FlutterError( + "not-supported", + "Windows GattServiceProvider advertising timeout is not supported", + nullptr); + } + try { + std::vector selected_services_lc; + selected_services_lc.reserve(services.size()); + for (const auto &service_encoded : services) { + const auto &service_id = std::get(service_encoded); + const auto service_id_lc = to_lower_case(service_id); + selected_services_lc.push_back(service_id_lc); + if (peripheral_service_provider_map_.count(service_id_lc) == 0) { + return FlutterError("not-found", "Service not found for advertising", + service_id); + } + } + + auto params = GattServiceProviderAdvertisingParameters(); + params.IsDiscoverable(true); + params.IsConnectable(true); + // TODO: migrate to BluetoothLEAdvertisementPublisher to support richer + // payload customization (local name, manufacturer data, scan response). + for (auto const &[key, provider] : peripheral_service_provider_map_) { + const bool should_start = selected_services_lc.empty() || + std::find(selected_services_lc.begin(), + selected_services_lc.end(), + to_lower_case(key)) != + selected_services_lc.end(); + if (!should_start) { + continue; + } + if (provider->obj.AdvertisementStatus() != + GattServiceProviderAdvertisementStatus::Started) { + provider->obj.StartAdvertising(params); + } + } + peripheral_advertising_targets_lc_ = std::move(selected_services_lc); + return std::nullopt; + } catch (...) { + return FlutterError("failed", "Failed to start advertising", nullptr); + } +} + +std::optional UniversalBlePlugin::UpdateCharacteristic( + const std::string &characteristic_id, const std::vector &value, + const std::string *device_id) { + GattLocalCharacteristic local_char = nullptr; + IBuffer buffer = nullptr; + { + std::lock_guard lock(peripheral_mutex_); + if (device_id != nullptr) { + return FlutterError( + "not-supported", + "Windows does not support targeting a specific device for notifications", + nullptr); + } + bool ambiguous_match = false; + auto *characteristic_object = + FindPeripheralGattCharacteristicObject(characteristic_id, &ambiguous_match); + if (ambiguous_match) { + return FlutterError( + "ambiguous-characteristic", + "Characteristic UUID exists in multiple services; cannot update uniquely", + nullptr); + } + if (characteristic_object == nullptr) { + return FlutterError("not-found", "Characteristic not found", nullptr); + } + IBuffer bytes = from_bytevc(value); + DataWriter writer; + writer.ByteOrder(ByteOrder::LittleEndian); + writer.WriteBuffer(bytes); + local_char = characteristic_object->obj; + buffer = writer.DetachBuffer(); + } + + try { + std::future notify_future = std::async( + std::launch::async, [local_char, buffer]() { + auto op = local_char.NotifyValueAsync(buffer); + return op.get(); + }); + const bool notify_result = notify_future.get(); + if (!notify_result) { + return FlutterError( + "failed", + "Failed to notify subscribed clients for characteristic update", + nullptr); + } + } catch (...) { + return FlutterError( + "failed", + "Failed to notify subscribed clients for characteristic update", + nullptr); + } + return std::nullopt; +} + +fire_and_forget +UniversalBlePlugin::PeripheralAddServiceAsync(const PeripheralService &service) { + const auto service_uuid = service.uuid(); + try { + auto service_provider_result = + co_await GattServiceProvider::CreateAsync(uuid_to_guid(service_uuid)); + if (service_provider_result.Error() != BluetoothError::Success) { + auto err = std::string("Failed to create service provider: ") + service_uuid + + ", error: " + + ParsePeripheralBluetoothError(service_provider_result.Error()); + ui_thread_handler_.Post([this, service_uuid, err] { + peripheral_callback_channel_->OnServiceAdded(service_uuid, &err, + SuccessCallback, + ErrorCallback); + }); + co_return; + } + + GattServiceProvider service_provider = service_provider_result.ServiceProvider(); + auto service_provider_object = + std::make_unique(); + service_provider_object->obj = service_provider; + + const auto characteristics = service.characteristics(); + for (auto characteristic_encoded : characteristics) { + PeripheralCharacteristic characteristic = + std::any_cast( + std::get(characteristic_encoded)); + auto params = GattLocalCharacteristicParameters(); + const auto characteristic_uuid = characteristic.uuid(); + for (auto property_encoded : characteristic.properties()) { + int property = static_cast(std::get(property_encoded)); + params.CharacteristicProperties( + params.CharacteristicProperties() | + ToPeripheralGattCharacteristicProperties(property)); + } + for (auto permission_encoded : characteristic.permissions()) { + int permission = static_cast(std::get(permission_encoded)); + switch (ToPeripheralBlePermission(permission)) { + case PeripheralBlePermission::readable: + params.ReadProtectionLevel(GattProtectionLevel::Plain); + break; + case PeripheralBlePermission::writeable: + params.WriteProtectionLevel(GattProtectionLevel::Plain); + break; + case PeripheralBlePermission::readEncryptionRequired: + params.ReadProtectionLevel(GattProtectionLevel::EncryptionRequired); + break; + case PeripheralBlePermission::writeEncryptionRequired: + params.WriteProtectionLevel(GattProtectionLevel::EncryptionRequired); + break; + default: + break; + } + } + if (characteristic.value() != nullptr) { + params.StaticValue(from_bytevc(*characteristic.value())); + } + + auto characteristic_result = + co_await service_provider.Service().CreateCharacteristicAsync( + uuid_to_guid(characteristic_uuid), params); + if (characteristic_result.Error() != BluetoothError::Success) { + continue; + } + + auto gatt_local_characteristic = characteristic_result.Characteristic(); + auto characteristic_object = + std::make_unique(); + characteristic_object->obj = gatt_local_characteristic; + characteristic_object->stored_clients = + gatt_local_characteristic.SubscribedClients(); + characteristic_object->read_requested_token = + gatt_local_characteristic.ReadRequested( + {this, &UniversalBlePlugin::PeripheralReadRequestedAsync}); + characteristic_object->write_requested_token = + gatt_local_characteristic.WriteRequested( + {this, &UniversalBlePlugin::PeripheralWriteRequestedAsync}); + characteristic_object->subscribed_clients_changed_token = + gatt_local_characteristic.SubscribedClientsChanged( + {this, &UniversalBlePlugin::PeripheralSubscribedClientsChanged}); + + service_provider_object->characteristics.insert_or_assign( + guid_to_uuid(gatt_local_characteristic.Uuid()), + std::move(characteristic_object)); + + const auto descriptors_ptr = characteristic.descriptors(); + if (descriptors_ptr != nullptr) { + for (auto descriptor_encoded : *descriptors_ptr) { + PeripheralDescriptor descriptor = std::any_cast( + std::get(descriptor_encoded)); + auto descriptor_params = GattLocalDescriptorParameters(); + const auto permissions = descriptor.permissions(); + if (permissions != nullptr) { + for (auto permission_encoded : *permissions) { + int permission = + static_cast(std::get(permission_encoded)); + switch (ToPeripheralBlePermission(permission)) { + case PeripheralBlePermission::readable: + descriptor_params.ReadProtectionLevel(GattProtectionLevel::Plain); + break; + case PeripheralBlePermission::writeable: + descriptor_params.WriteProtectionLevel( + GattProtectionLevel::Plain); + break; + case PeripheralBlePermission::readEncryptionRequired: + descriptor_params.ReadProtectionLevel( + GattProtectionLevel::EncryptionRequired); + break; + case PeripheralBlePermission::writeEncryptionRequired: + descriptor_params.WriteProtectionLevel( + GattProtectionLevel::EncryptionRequired); + break; + default: + break; + } + } + } + if (descriptor.value() != nullptr) { + descriptor_params.StaticValue(from_bytevc(*descriptor.value())); + } + co_await gatt_local_characteristic.CreateDescriptorAsync( + uuid_to_guid(descriptor.uuid()), descriptor_params); + } + } + } + + service_provider_object->advertisement_status_changed_token = + service_provider.AdvertisementStatusChanged( + {this, &UniversalBlePlugin::PeripheralAdvertisementStatusChanged}); + { + std::lock_guard lock(peripheral_mutex_); + peripheral_service_provider_map_.insert_or_assign( + guid_to_uuid(service_provider.Service().Uuid()), + std::move(service_provider_object)); + } + + ui_thread_handler_.Post([this, service_uuid] { + peripheral_callback_channel_->OnServiceAdded(service_uuid, nullptr, + SuccessCallback, + ErrorCallback); + }); + } catch (const winrt::hresult_error &e) { + auto err = winrt::to_string(e.message()); + ui_thread_handler_.Post([this, service_uuid, err] { + peripheral_callback_channel_->OnServiceAdded(service_uuid, &err, + SuccessCallback, + ErrorCallback); + }); + } catch (...) { + auto err = std::string("Unknown error"); + ui_thread_handler_.Post([this, service_uuid, err] { + peripheral_callback_channel_->OnServiceAdded(service_uuid, &err, + SuccessCallback, + ErrorCallback); + }); + } +} + +fire_and_forget UniversalBlePlugin::PeripheralSubscribedClientsChanged( + GattLocalCharacteristic const &local_char, IInspectable const &) { + const auto characteristic_id = guid_to_uuid(local_char.Uuid()); + IVectorView current_clients = nullptr; + IVectorView old_clients = nullptr; + { + std::lock_guard lock(peripheral_mutex_); + auto *characteristic_object = + FindPeripheralGattCharacteristicObject(characteristic_id); + if (characteristic_object == nullptr) { + co_return; + } + current_clients = local_char.SubscribedClients(); + old_clients = characteristic_object->stored_clients; + characteristic_object->stored_clients = current_clients; + } + + for (uint32_t i = 0; i < current_clients.Size(); ++i) { + auto client = current_clients.GetAt(i); + bool found = false; + for (uint32_t j = 0; j < old_clients.Size(); ++j) { + if (old_clients.GetAt(j) == client) { + found = true; + break; + } + } + if (!found) { + const auto device_id = + ParsePeripheralBluetoothClientId(client.Session().DeviceId().Id()); + std::string device_name; + try { + auto device_info = + co_await DeviceInformation::CreateFromIdAsync(client.Session().DeviceId().Id()); + device_name = winrt::to_string(device_info.Name()); + } catch (...) { + } + ui_thread_handler_.Post([this, device_id, characteristic_id, device_name] { + const std::string *name_ptr = device_name.empty() ? nullptr : &device_name; + peripheral_callback_channel_->OnCharacteristicSubscriptionChange( + device_id, characteristic_id, true, name_ptr, SuccessCallback, + ErrorCallback); + }); + const int64_t mtu = client.Session().MaxPduSize(); + ui_thread_handler_.Post([this, device_id, mtu] { + peripheral_callback_channel_->OnMtuChange(device_id, mtu, + SuccessCallback, + ErrorCallback); + }); + } + } + + for (uint32_t i = 0; i < old_clients.Size(); ++i) { + auto client = old_clients.GetAt(i); + bool found = false; + for (uint32_t j = 0; j < current_clients.Size(); ++j) { + if (current_clients.GetAt(j) == client) { + found = true; + break; + } + } + if (!found) { + const auto device_id = + ParsePeripheralBluetoothClientId(client.Session().DeviceId().Id()); + ui_thread_handler_.Post([this, device_id, characteristic_id] { + peripheral_callback_channel_->OnCharacteristicSubscriptionChange( + device_id, characteristic_id, false, nullptr, SuccessCallback, + ErrorCallback); + }); + } + } + +} + +fire_and_forget UniversalBlePlugin::PeripheralReadRequestedAsync( + GattLocalCharacteristic const &local_char, GattReadRequestedEventArgs args) { + auto deferral = args.GetDeferral(); + try { + auto request = co_await args.GetRequestAsync(); + if (request == nullptr) { + deferral.Complete(); + co_return; + } + + const auto characteristic_id = guid_to_uuid(local_char.Uuid()); + const auto device_id = + ParsePeripheralBluetoothClientId(args.Session().DeviceId().Id()); + const int64_t offset = request.Offset(); + auto value_holder = std::make_shared>(); + std::vector *value_ptr = nullptr; + if (local_char.StaticValue() != nullptr) { + *value_holder = to_bytevc(local_char.StaticValue()); + value_ptr = value_holder.get(); + } + ui_thread_handler_.Post([this, device_id, characteristic_id, offset, value_ptr, + value_holder, request, deferral] { + peripheral_callback_channel_->OnReadRequest( + device_id, characteristic_id, offset, value_ptr, + [request, deferral](const PeripheralReadRequestResult *result) { + if (result != nullptr) { + if (result->status() != nullptr) { + request.RespondWithProtocolError( + ToGattProtocolError(*result->status())); + deferral.Complete(); + return; + } + DataWriter writer; + writer.ByteOrder(ByteOrder::LittleEndian); + writer.WriteBuffer(from_bytevc(result->value())); + request.RespondWithValue(writer.DetachBuffer()); + } else { + request.RespondWithProtocolError(0x01); + } + deferral.Complete(); + }, + [request, deferral](const FlutterError &error) { + request.RespondWithProtocolError(0x0E); + deferral.Complete(); + }); + }); + } catch (...) { + deferral.Complete(); + } +} + +fire_and_forget UniversalBlePlugin::PeripheralWriteRequestedAsync( + GattLocalCharacteristic const &local_char, + GattWriteRequestedEventArgs args) { + auto deferral = args.GetDeferral(); + try { + auto request = co_await args.GetRequestAsync(); + if (request == nullptr) { + deferral.Complete(); + co_return; + } + const auto characteristic_id = guid_to_uuid(local_char.Uuid()); + const auto device_id = + ParsePeripheralBluetoothClientId(args.Session().DeviceId().Id()); + const int64_t offset = request.Offset(); + auto value_holder = std::make_shared>(to_bytevc(request.Value())); + ui_thread_handler_.Post([this, device_id, characteristic_id, offset, + value_holder, request, deferral] { + peripheral_callback_channel_->OnWriteRequest( + device_id, characteristic_id, offset, value_holder.get(), + [request, deferral](const PeripheralWriteRequestResult *result) { + if (result != nullptr && result->status() != nullptr) { + request.RespondWithProtocolError( + ToGattProtocolError(*result->status())); + } else { + request.Respond(); + } + deferral.Complete(); + }, + [request, deferral](const FlutterError &error) { + request.RespondWithProtocolError(0x0E); + deferral.Complete(); + }); + }); + } catch (...) { + deferral.Complete(); + } +} + +void UniversalBlePlugin::PeripheralAdvertisementStatusChanged( + GattServiceProvider const &sender, + GattServiceProviderAdvertisementStatusChangedEventArgs const &args) { + if (args.Error() != BluetoothError::Success) { + auto error_str = ParsePeripheralBluetoothError(args.Error()); + ui_thread_handler_.Post([this, error_str] { + peripheral_callback_channel_->OnAdvertisingStateChange( + PeripheralAdvertisingState::kError, &error_str, SuccessCallback, + ErrorCallback); + }); + return; + } + std::lock_guard lock(peripheral_mutex_); + if (ArePeripheralAdvertisingTargetsStarted()) { + ui_thread_handler_.Post([this] { + peripheral_callback_channel_->OnAdvertisingStateChange( + PeripheralAdvertisingState::kAdvertising, nullptr, SuccessCallback, + ErrorCallback); + }); + } +} + +void UniversalBlePlugin::DisposePeripheralServiceProvider( + PeripheralGattServiceProviderObject *service_provider_object) { + if (service_provider_object == nullptr) { + return; + } + try { + if (service_provider_object->obj.AdvertisementStatus() == + GattServiceProviderAdvertisementStatus::Started) { + service_provider_object->obj.StopAdvertising(); + } + } catch (...) { + } + try { + service_provider_object->obj.AdvertisementStatusChanged( + service_provider_object->advertisement_status_changed_token); + } catch (...) { + } + for (auto const &[_, characteristic_object] : + service_provider_object->characteristics) { + try { + characteristic_object->obj.ReadRequested( + characteristic_object->read_requested_token); + characteristic_object->obj.WriteRequested( + characteristic_object->write_requested_token); + characteristic_object->obj.SubscribedClientsChanged( + characteristic_object->subscribed_clients_changed_token); + } catch (...) { + } + } +} + +PeripheralGattCharacteristicObject * +UniversalBlePlugin::FindPeripheralGattCharacteristicObject( + const std::string &characteristic_id, bool *ambiguous_match) { + const auto characteristic_id_lc = to_lower_case(characteristic_id); + PeripheralGattCharacteristicObject *first_match = nullptr; + for (auto const &[_, service_provider] : peripheral_service_provider_map_) { + for (auto const &[char_key, characteristic_object] : + service_provider->characteristics) { + if (to_lower_case(char_key) == characteristic_id_lc) { + if (first_match == nullptr) { + first_match = characteristic_object.get(); + } else { + if (ambiguous_match != nullptr) { + *ambiguous_match = true; + } + return nullptr; + } + } + } + } + return first_match; +} + +bool UniversalBlePlugin::ArePeripheralAdvertisingTargetsStarted() const { + if (peripheral_service_provider_map_.empty()) { + return false; + } + if (peripheral_advertising_targets_lc_.empty()) { + for (auto const &[_, service_provider] : peripheral_service_provider_map_) { + if (service_provider->obj.AdvertisementStatus() != + GattServiceProviderAdvertisementStatus::Started) { + return false; + } + } + return true; + } + for (const auto &target_id : peripheral_advertising_targets_lc_) { + const auto it = peripheral_service_provider_map_.find(target_id); + if (it == peripheral_service_provider_map_.end()) { + return false; + } + if (it->second->obj.AdvertisementStatus() != + GattServiceProviderAdvertisementStatus::Started) { + return false; + } + } + return true; +} + +GattCharacteristicProperties +UniversalBlePlugin::ToPeripheralGattCharacteristicProperties(int property) { + switch (property) { + case 0: + return GattCharacteristicProperties::Broadcast; + case 1: + return GattCharacteristicProperties::Read; + case 2: + return GattCharacteristicProperties::WriteWithoutResponse; + case 3: + return GattCharacteristicProperties::Write; + case 4: + return GattCharacteristicProperties::Notify; + case 5: + return GattCharacteristicProperties::Indicate; + case 6: + return GattCharacteristicProperties::AuthenticatedSignedWrites; + case 7: + return GattCharacteristicProperties::ExtendedProperties; + case 8: + return GattCharacteristicProperties::Notify; + case 9: + return GattCharacteristicProperties::Indicate; + default: + return GattCharacteristicProperties::None; + } +} + +PeripheralBlePermission +UniversalBlePlugin::ToPeripheralBlePermission(int permission) { + switch (permission) { + case 0: + return PeripheralBlePermission::readable; + case 1: + return PeripheralBlePermission::writeable; + case 2: + return PeripheralBlePermission::readEncryptionRequired; + case 3: + return PeripheralBlePermission::writeEncryptionRequired; + default: + return PeripheralBlePermission::none; + } +} + +std::string UniversalBlePlugin::PeripheralAdvertisementStatusToString( + GattServiceProviderAdvertisementStatus status) { + switch (status) { + case GattServiceProviderAdvertisementStatus::Created: + return "Created"; + case GattServiceProviderAdvertisementStatus::Started: + return "Started"; + case GattServiceProviderAdvertisementStatus::Stopped: + return "Stopped"; + case GattServiceProviderAdvertisementStatus::Aborted: + return "Aborted"; + case GattServiceProviderAdvertisementStatus::StartedWithoutAllAdvertisementData: + return "StartedWithoutAllAdvertisementData"; + default: + return "Unknown"; + } +} + +std::string UniversalBlePlugin::ParsePeripheralBluetoothClientId( + hstring client_id) { + auto id = winrt::to_string(client_id); + const auto pos = id.find_last_of('-'); + if (pos != std::string::npos) { + return id.substr(pos + 1); + } + return id; +} + +std::string +UniversalBlePlugin::ParsePeripheralBluetoothError(BluetoothError error) { + switch (error) { + case BluetoothError::Success: + return "Success"; + case BluetoothError::RadioNotAvailable: + return "RadioNotAvailable"; + case BluetoothError::ResourceInUse: + return "ResourceInUse"; + case BluetoothError::DeviceNotConnected: + return "DeviceNotConnected"; + case BluetoothError::OtherError: + return "OtherError"; + case BluetoothError::DisabledByPolicy: + return "DisabledByPolicy"; + case BluetoothError::NotSupported: + return "NotSupported"; + case BluetoothError::DisabledByUser: + return "DisabledByUser"; + case BluetoothError::ConsentRequired: + return "ConsentRequired"; + case BluetoothError::TransportNotSupported: + return "TransportNotSupported"; + default: + return "Unknown"; + } +} + +uint8_t UniversalBlePlugin::ToGattProtocolError(int64_t status_code) { + if (status_code < 0) { + return 0x01; + } + if (status_code > 0xFF) { + return 0xFF; + } + return static_cast(status_code); +} + } // namespace universal_ble diff --git a/windows/src/universal_ble_plugin.h b/windows/src/universal_ble_plugin.h index 867ed41e..00ddf620 100644 --- a/windows/src/universal_ble_plugin.h +++ b/windows/src/universal_ble_plugin.h @@ -22,6 +22,7 @@ #include "ui_thread_handler.hpp" #include "universal_ble_thread_safe.h" #include +#include namespace universal_ble { struct GattCharacteristicObject { @@ -34,6 +35,29 @@ struct GattServiceObject { std::unordered_map characteristics; }; +struct PeripheralGattCharacteristicObject { + GattLocalCharacteristic obj = nullptr; + event_token read_requested_token{}; + event_token write_requested_token{}; + event_token subscribed_clients_changed_token{}; + IVectorView stored_clients = nullptr; +}; + +struct PeripheralGattServiceProviderObject { + GattServiceProvider obj = nullptr; + event_token advertisement_status_changed_token{}; + std::unordered_map> + characteristics; +}; + +enum class PeripheralBlePermission { + none, + readable, + writeable, + readEncryptionRequired, + writeEncryptionRequired, +}; + struct BluetoothDeviceAgent { BluetoothLEDevice device; event_token connection_status_changed_token; @@ -66,7 +90,8 @@ struct BluetoothDeviceAgent { }; class UniversalBlePlugin : public flutter::Plugin, - public UniversalBlePlatformChannel { + public UniversalBlePlatformChannel, + public UniversalBlePeripheralChannel { public: static void RegisterWithRegistrar(flutter::PluginRegistrarWindows *registrar); @@ -167,6 +192,43 @@ class UniversalBlePlugin : public flutter::Plugin, void GattCharacteristicValueChanged(const GattCharacteristic &sender, const GattValueChangedEventArgs &args); + // Peripheral runtime state + std::unordered_map> + peripheral_service_provider_map_{}; + /// Lowercased service UUIDs from the last successful `StartAdvertising` call. + /// Empty means all registered services were selected. + std::vector peripheral_advertising_targets_lc_{}; + event_revoker peripheral_radio_state_changed_revoker_; + std::unique_ptr peripheral_callback_channel_; + std::mutex peripheral_mutex_; + + // Peripheral helpers + fire_and_forget PeripheralAddServiceAsync(const PeripheralService &service); + fire_and_forget PeripheralReadRequestedAsync( + GattLocalCharacteristic const &local_char, + GattReadRequestedEventArgs args); + fire_and_forget PeripheralWriteRequestedAsync( + GattLocalCharacteristic const &local_char, + GattWriteRequestedEventArgs args); + fire_and_forget PeripheralSubscribedClientsChanged( + GattLocalCharacteristic const &local_char, IInspectable const &args); + void PeripheralAdvertisementStatusChanged( + GattServiceProvider const &sender, + GattServiceProviderAdvertisementStatusChangedEventArgs const &args); + void DisposePeripheralServiceProvider( + PeripheralGattServiceProviderObject *service_provider_object); + PeripheralGattCharacteristicObject *FindPeripheralGattCharacteristicObject( + const std::string &characteristic_id, + bool *ambiguous_match = nullptr); + bool ArePeripheralAdvertisingTargetsStarted() const; + static uint8_t ToGattProtocolError(int64_t status_code); + static GattCharacteristicProperties ToPeripheralGattCharacteristicProperties( + int property); + static PeripheralBlePermission ToPeripheralBlePermission(int permission); + static std::string PeripheralAdvertisementStatusToString( + GattServiceProviderAdvertisementStatus status); + static std::string ParsePeripheralBluetoothClientId(hstring client_id); + static std::string ParsePeripheralBluetoothError(BluetoothError error); // UniversalBlePlatformChannel implementation. void GetBluetoothAvailabilityState( @@ -221,6 +283,27 @@ class UniversalBlePlugin : public flutter::Plugin, GetSystemDevices(const flutter::EncodableList &with_services, std::function reply)> result) override; + + // UniversalBlePeripheralChannel implementation. + ErrorOr GetAdvertisingState() override; + ErrorOr GetReadinessState() override; + std::optional StopAdvertising() override; + std::optional AddService(const PeripheralService &service) override; + std::optional RemoveService(const std::string &service_id) override; + std::optional ClearServices() override; + ErrorOr GetServices() override; + std::optional StartAdvertising( + const flutter::EncodableList &services, const std::string *local_name, + const int64_t *timeout, + const PeripheralManufacturerData *manufacturer_data, + bool add_manufacturer_data_in_scan_response) override; + std::optional UpdateCharacteristic( + const std::string &characteristic_id, const std::vector &value, + const std::string *device_id) override; + ErrorOr GetSubscribedClients( + const std::string &characteristic_id) override; + ErrorOr> GetMaximumNotifyLength( + const std::string &device_id) override; }; } // namespace universal_ble