diff --git a/CHANGELOG.md b/CHANGELOG.md index 14aff2f..d850d77 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,5 @@ ## 1.3.0 +* Add Linux BLE peripheral/GATT-server support through BlueZ GATT application and LE advertisement registration * Add `requestConnectionPriority` to allow tuning BLE connection intervals on Android * Add SPM support on Apple diff --git a/README.md b/README.md index 73ea212..7e9b1c3 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,7 @@ A cross-platform (Android/iOS/macOS/Windows/Linux/Web) Bluetooth Low Energy (BLE - [Connecting](#connecting) - [Discovering Services](#discovering-services) - [Reading & Writing data](#reading--writing-data) +- [Peripheral Mode](#peripheral-mode) - [Pairing](#pairing) - [Bluetooth Availability](#bluetooth-availability) - [Requesting MTU](#requesting-mtu) @@ -57,6 +58,7 @@ A cross-platform (Android/iOS/macOS/Windows/Linux/Web) Bluetooth Low Energy (BLE | requestConnectionPriority | ✔️ | ❌ | ❌ | ❌ | ❌ | ❌ | | readRssi | ✔️ | ✔️ | ✔️ | ❌ | 🚧 | ❌ | | requestPermissions | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | +| peripheral/GATT server | ✔️ | 🚧 | 🚧 | 🚧 | ✔️ | ❌ | ## Getting Started @@ -338,6 +340,49 @@ Unsubscribe from notifications and indications of this characteristic. await characteristic.unsubscribe(); ``` +## Peripheral Mode + +`UniversalBlePeripheral` exposes a local GATT-server API for apps that need to advertise services and accept central/client writes. Android and Linux are implemented; Apple and Windows currently report `notSupported` until their platform server implementations are added. Web browsers do not expose a standard GATT-server API. + +On Linux, peripheral mode uses BlueZ `GattManager1` and `LEAdvertisingManager1`. The Bluetooth adapter must be powered, support the peripheral role, and the app process must be allowed to register GATT applications and LE advertisements on the system bus. + +```dart +await UniversalBle.requestPermissions(withAndroidBluetoothAdvertise: true); + +await UniversalBlePeripheral.start( + BlePeripheralConfig( + advertisedName: 'DALI Relay', + services: [ + BlePeripheralService( + uuid: 'fff0', + characteristics: [ + BlePeripheralCharacteristic( + uuid: 'fff1', + properties: const [ + CharacteristicProperty.read, + CharacteristicProperty.write, + CharacteristicProperty.writeWithoutResponse, + CharacteristicProperty.notify, + ], + permissions: const [ + BlePeripheralCharacteristicPermission.read, + BlePeripheralCharacteristicPermission.write, + ], + ), + ], + ), + ], + ), +); + +UniversalBlePeripheral.writeStream.listen((event) { + debugPrint('Central ${event.deviceId} wrote ${event.value}'); +}); + +await UniversalBlePeripheral.notify('fff0', 'fff1', Uint8List.fromList([1, 2])); +await UniversalBlePeripheral.stop(); +``` + ### Pairing #### Trigger pairing diff --git a/android/src/main/AndroidManifest.xml b/android/src/main/AndroidManifest.xml index 067f1cb..766c9e8 100644 --- a/android/src/main/AndroidManifest.xml +++ b/android/src/main/AndroidManifest.xml @@ -7,4 +7,5 @@ + diff --git a/android/src/main/kotlin/com/navideck/universal_ble/PermissionHandler.kt b/android/src/main/kotlin/com/navideck/universal_ble/PermissionHandler.kt index 2245ba5..0c80c82 100644 --- a/android/src/main/kotlin/com/navideck/universal_ble/PermissionHandler.kt +++ b/android/src/main/kotlin/com/navideck/universal_ble/PermissionHandler.kt @@ -35,12 +35,12 @@ class PermissionHandler( /** * Check if we have required permissions */ - fun hasPermissions(withFineLocation: Boolean): Boolean { - val validationError = validateRequiredPermissions(withFineLocation) + fun hasPermissions(withFineLocation: Boolean, withAdvertise: Boolean): Boolean { + val validationError = validateRequiredPermissions(withFineLocation, withAdvertise) if (validationError != null) { throw validationError } - val permissionsToRequest = getRequiredPermissions(withFineLocation) + val permissionsToRequest = getRequiredPermissions(withFineLocation, withAdvertise) return permissionsToRequest.isEmpty() } @@ -55,17 +55,18 @@ class PermissionHandler( */ fun requestPermissions( withFineLocation: Boolean, + withAdvertise: Boolean, callback: (Result) -> Unit, ) { // Validate required permissions are declared in manifest - val validationError = validateRequiredPermissions(withFineLocation) + val validationError = validateRequiredPermissions(withFineLocation, withAdvertise) if (validationError != null) { callback(Result.failure(validationError)) return } // Check which permissions need to be requested - val permissionsToRequest = getRequiredPermissions(withFineLocation) + val permissionsToRequest = getRequiredPermissions(withFineLocation, withAdvertise) if (permissionsToRequest.isEmpty()) { // All required permissions are already granted @@ -163,7 +164,7 @@ class PermissionHandler( * * Returns a list of permissions that need to be requested (excluding already granted ones) */ - private fun getRequiredPermissions(withFineLocation: Boolean): List { + private fun getRequiredPermissions(withFineLocation: Boolean, withAdvertise: Boolean): List { val permissionsToRequest = mutableListOf() // Android 12+ (API 31+) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { @@ -175,6 +176,9 @@ class PermissionHandler( if (!hasPermissionGranted(Manifest.permission.BLUETOOTH_CONNECT)) { permissionsToRequest.add(Manifest.permission.BLUETOOTH_CONNECT) } + if (withAdvertise && !hasPermissionGranted(Manifest.permission.BLUETOOTH_ADVERTISE)) { + permissionsToRequest.add(Manifest.permission.BLUETOOTH_ADVERTISE) + } // Location permission is optional - only request if user wants it if (withFineLocation) { // Prefer ACCESS_FINE_LOCATION over ACCESS_COARSE_LOCATION @@ -236,7 +240,7 @@ class PermissionHandler( * @param withFineLocation Whether location permission should be requested * @return FlutterError if validation fails, null if all required permissions are declared */ - private fun validateRequiredPermissions(withFineLocation: Boolean): FlutterError? { + private fun validateRequiredPermissions(withFineLocation: Boolean, withAdvertise: Boolean): FlutterError? { val sdkInt = Build.VERSION.SDK_INT val missingPermissions = mutableListOf() @@ -259,6 +263,10 @@ class PermissionHandler( missingPermissions.add(Manifest.permission.BLUETOOTH_CONNECT) } + if (withAdvertise && !hasPermissionInManifest(Manifest.permission.BLUETOOTH_ADVERTISE)) { + missingPermissions.add(Manifest.permission.BLUETOOTH_ADVERTISE) + } + // Location permission is optional on Android 12+ (depends on neverForLocation and withFineLocation) // Only validate if it's actually needed if (withFineLocation && !hasDeclaredLocationPermission) { 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 2118809..6accfb0 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.4), 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() + } + } + } /** @@ -79,7 +193,7 @@ class FlutterError ( val code: String, override val message: String? = null, val details: Any? = null -) : Throwable() +) : RuntimeException() enum class UniversalBleLogLevel(val raw: Int) { NONE(0), @@ -219,15 +333,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 +377,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 +418,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 +454,235 @@ 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 + } +} + +/** Generated class from Pigeon that represents data sent in messages. */ +data class UniversalBlePeripheralConfig ( + val advertisedName: String, + val services: List +) + { + companion object { + fun fromList(pigeonVar_list: List): UniversalBlePeripheralConfig { + val advertisedName = pigeonVar_list[0] as String + val services = pigeonVar_list[1] as List + return UniversalBlePeripheralConfig(advertisedName, services) + } + } + fun toList(): List { + return listOf( + advertisedName, + services, + ) + } + override fun equals(other: Any?): Boolean { + if (other == null || other.javaClass != javaClass) { + return false + } + if (this === other) { + return true + } + val other = other as UniversalBlePeripheralConfig + return UniversalBlePigeonUtils.deepEquals(this.advertisedName, other.advertisedName) && UniversalBlePigeonUtils.deepEquals(this.services, other.services) + } + + override fun hashCode(): Int { + var result = javaClass.hashCode() + result = 31 * result + UniversalBlePigeonUtils.deepHash(this.advertisedName) + result = 31 * result + UniversalBlePigeonUtils.deepHash(this.services) + return result + } +} + +/** Generated class from Pigeon that represents data sent in messages. */ +data class UniversalBlePeripheralService ( + val uuid: String, + val characteristics: List +) + { + companion object { + fun fromList(pigeonVar_list: List): UniversalBlePeripheralService { + val uuid = pigeonVar_list[0] as String + val characteristics = pigeonVar_list[1] as List + return UniversalBlePeripheralService(uuid, characteristics) + } + } + fun toList(): List { + return listOf( + uuid, + characteristics, + ) + } + override fun equals(other: Any?): Boolean { + if (other == null || other.javaClass != javaClass) { + return false + } + if (this === other) { + return true + } + val other = other as UniversalBlePeripheralService + return UniversalBlePigeonUtils.deepEquals(this.uuid, other.uuid) && 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.characteristics) + return result + } +} + +/** Generated class from Pigeon that represents data sent in messages. */ +data class UniversalBlePeripheralCharacteristic ( + val uuid: String, + val properties: List, + val permissions: List, + val descriptors: List, + val initialValue: ByteArray? = null +) + { + companion object { + fun fromList(pigeonVar_list: List): UniversalBlePeripheralCharacteristic { + 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 initialValue = pigeonVar_list[4] as ByteArray? + return UniversalBlePeripheralCharacteristic(uuid, properties, permissions, descriptors, initialValue) + } + } + fun toList(): List { + return listOf( + uuid, + properties, + permissions, + descriptors, + initialValue, + ) + } + override fun equals(other: Any?): Boolean { + if (other == null || other.javaClass != javaClass) { + return false + } + if (this === other) { + return true + } + val other = other as UniversalBlePeripheralCharacteristic + 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.initialValue, other.initialValue) + } + + 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.initialValue) + return result + } +} + +/** Generated class from Pigeon that represents data sent in messages. */ +data class UniversalBlePeripheralDescriptor ( + val uuid: String, + val permissions: List, + val initialValue: ByteArray? = null +) + { + companion object { + fun fromList(pigeonVar_list: List): UniversalBlePeripheralDescriptor { + val uuid = pigeonVar_list[0] as String + val permissions = pigeonVar_list[1] as List + val initialValue = pigeonVar_list[2] as ByteArray? + return UniversalBlePeripheralDescriptor(uuid, permissions, initialValue) + } + } + fun toList(): List { + return listOf( + uuid, + permissions, + initialValue, + ) + } + override fun equals(other: Any?): Boolean { + if (other == null || other.javaClass != javaClass) { + return false + } + if (this === other) { + return true + } + val other = other as UniversalBlePeripheralDescriptor + return UniversalBlePigeonUtils.deepEquals(this.uuid, other.uuid) && UniversalBlePigeonUtils.deepEquals(this.permissions, other.permissions) && UniversalBlePigeonUtils.deepEquals(this.initialValue, other.initialValue) + } + + override fun hashCode(): Int { + var result = javaClass.hashCode() + result = 31 * result + UniversalBlePigeonUtils.deepHash(this.uuid) + result = 31 * result + UniversalBlePigeonUtils.deepHash(this.permissions) + result = 31 * result + UniversalBlePigeonUtils.deepHash(this.initialValue) + return result + } +} + +/** Generated class from Pigeon that represents data sent in messages. */ +data class UniversalBlePeripheralWriteEvent ( + val deviceId: String, + val service: String, + val characteristic: String, + val value: ByteArray +) + { + companion object { + fun fromList(pigeonVar_list: List): UniversalBlePeripheralWriteEvent { + val deviceId = pigeonVar_list[0] as String + val service = pigeonVar_list[1] as String + val characteristic = pigeonVar_list[2] as String + val value = pigeonVar_list[3] as ByteArray + return UniversalBlePeripheralWriteEvent(deviceId, service, characteristic, value) + } + } + fun toList(): List { + return listOf( + deviceId, + service, + characteristic, + value, + ) + } + override fun equals(other: Any?): Boolean { + if (other == null || other.javaClass != javaClass) { + return false + } + if (this === other) { + return true + } + val other = other as UniversalBlePeripheralWriteEvent + return UniversalBlePigeonUtils.deepEquals(this.deviceId, other.deviceId) && UniversalBlePigeonUtils.deepEquals(this.service, other.service) && UniversalBlePigeonUtils.deepEquals(this.characteristic, other.characteristic) && UniversalBlePigeonUtils.deepEquals(this.value, other.value) + } + + override fun hashCode(): Int { + var result = javaClass.hashCode() + result = 31 * result + UniversalBlePigeonUtils.deepHash(this.deviceId) + result = 31 * result + UniversalBlePigeonUtils.deepHash(this.service) + result = 31 * result + UniversalBlePigeonUtils.deepHash(this.characteristic) + result = 31 * result + UniversalBlePigeonUtils.deepHash(this.value) + return result + } } /** @@ -355,15 +717,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 +753,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 +797,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 +839,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 +878,22 @@ 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 } - return UniversalBlePigeonUtils.deepEquals(toList(), other.toList()) } + val other = other as UniversalManufacturerData + return UniversalBlePigeonUtils.deepEquals(this.companyIdentifier, other.companyIdentifier) && 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.companyIdentifier) + result = 31 * result + UniversalBlePigeonUtils.deepHash(this.data) + return result + } } private open class UniversalBlePigeonCodec : StandardMessageCodec() { override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? { @@ -536,25 +935,50 @@ private open class UniversalBlePigeonCodec : StandardMessageCodec() { } 136.toByte() -> { return (readValue(buffer) as? List)?.let { - AndroidOptions.fromList(it) + UniversalBlePeripheralConfig.fromList(it) } } 137.toByte() -> { return (readValue(buffer) as? List)?.let { - UniversalScanConfig.fromList(it) + UniversalBlePeripheralService.fromList(it) } } 138.toByte() -> { return (readValue(buffer) as? List)?.let { - UniversalScanFilter.fromList(it) + UniversalBlePeripheralCharacteristic.fromList(it) } } 139.toByte() -> { return (readValue(buffer) as? List)?.let { - UniversalManufacturerDataFilter.fromList(it) + UniversalBlePeripheralDescriptor.fromList(it) } } 140.toByte() -> { + return (readValue(buffer) as? List)?.let { + UniversalBlePeripheralWriteEvent.fromList(it) + } + } + 141.toByte() -> { + return (readValue(buffer) as? List)?.let { + AndroidOptions.fromList(it) + } + } + 142.toByte() -> { + return (readValue(buffer) as? List)?.let { + UniversalScanConfig.fromList(it) + } + } + 143.toByte() -> { + return (readValue(buffer) as? List)?.let { + UniversalScanFilter.fromList(it) + } + } + 144.toByte() -> { + return (readValue(buffer) as? List)?.let { + UniversalManufacturerDataFilter.fromList(it) + } + } + 145.toByte() -> { return (readValue(buffer) as? List)?.let { UniversalManufacturerData.fromList(it) } @@ -592,26 +1016,46 @@ private open class UniversalBlePigeonCodec : StandardMessageCodec() { stream.write(135) writeValue(stream, value.toList()) } - is AndroidOptions -> { + is UniversalBlePeripheralConfig -> { stream.write(136) writeValue(stream, value.toList()) } - is UniversalScanConfig -> { + is UniversalBlePeripheralService -> { stream.write(137) writeValue(stream, value.toList()) } - is UniversalScanFilter -> { + is UniversalBlePeripheralCharacteristic -> { stream.write(138) writeValue(stream, value.toList()) } - is UniversalManufacturerDataFilter -> { + is UniversalBlePeripheralDescriptor -> { stream.write(139) writeValue(stream, value.toList()) } - is UniversalManufacturerData -> { + is UniversalBlePeripheralWriteEvent -> { stream.write(140) writeValue(stream, value.toList()) } + is AndroidOptions -> { + stream.write(141) + writeValue(stream, value.toList()) + } + is UniversalScanConfig -> { + stream.write(142) + writeValue(stream, value.toList()) + } + is UniversalScanFilter -> { + stream.write(143) + writeValue(stream, value.toList()) + } + is UniversalManufacturerDataFilter -> { + stream.write(144) + writeValue(stream, value.toList()) + } + is UniversalManufacturerData -> { + stream.write(145) + writeValue(stream, value.toList()) + } else -> super.writeValue(stream, value) } } @@ -625,8 +1069,8 @@ private open class UniversalBlePigeonCodec : StandardMessageCodec() { */ interface UniversalBlePlatformChannel { fun getBluetoothAvailabilityState(callback: (Result) -> Unit) - fun hasPermissions(withAndroidFineLocation: Boolean): Boolean - fun requestPermissions(withAndroidFineLocation: Boolean, callback: (Result) -> Unit) + fun hasPermissions(withAndroidFineLocation: Boolean, withAndroidBluetoothAdvertise: Boolean): Boolean + fun requestPermissions(withAndroidFineLocation: Boolean, withAndroidBluetoothAdvertise: Boolean, callback: (Result) -> Unit) fun enableBluetooth(callback: (Result) -> Unit) fun disableBluetooth(callback: (Result) -> Unit) fun startScan(filter: UniversalScanFilter?, config: UniversalScanConfig?) @@ -646,6 +1090,11 @@ interface UniversalBlePlatformChannel { fun getConnectionState(deviceId: String): Long fun readRssi(deviceId: String, callback: (Result) -> Unit) fun requestConnectionPriority(deviceId: String, priority: Long, callback: (Result) -> Unit) + fun isPeripheralSupported(): Boolean + fun startPeripheral(config: UniversalBlePeripheralConfig, callback: (Result) -> Unit) + fun stopPeripheral(callback: (Result) -> Unit) + fun updatePeripheralCharacteristicValue(service: String, characteristic: String, value: ByteArray, callback: (Result) -> Unit) + fun notifyPeripheralCharacteristic(service: String, characteristic: String, value: ByteArray, indicate: Boolean, callback: (Result) -> Unit) fun setLogLevel(logLevel: UniversalBleLogLevel) companion object { @@ -681,8 +1130,9 @@ interface UniversalBlePlatformChannel { channel.setMessageHandler { message, reply -> val args = message as List val withAndroidFineLocationArg = args[0] as Boolean + val withAndroidBluetoothAdvertiseArg = args[1] as Boolean val wrapped: List = try { - listOf(api.hasPermissions(withAndroidFineLocationArg)) + listOf(api.hasPermissions(withAndroidFineLocationArg, withAndroidBluetoothAdvertiseArg)) } catch (exception: Throwable) { UniversalBlePigeonUtils.wrapError(exception) } @@ -698,7 +1148,8 @@ interface UniversalBlePlatformChannel { channel.setMessageHandler { message, reply -> val args = message as List val withAndroidFineLocationArg = args[0] as Boolean - api.requestPermissions(withAndroidFineLocationArg) { result: Result -> + val withAndroidBluetoothAdvertiseArg = args[1] as Boolean + api.requestPermissions(withAndroidFineLocationArg, withAndroidBluetoothAdvertiseArg) { result: Result -> val error = result.exceptionOrNull() if (error != null) { reply.reply(UniversalBlePigeonUtils.wrapError(error)) @@ -1078,6 +1529,100 @@ interface UniversalBlePlatformChannel { channel.setMessageHandler(null) } } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.isPeripheralSupported$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { _, reply -> + val wrapped: List = try { + listOf(api.isPeripheralSupported()) + } catch (exception: Throwable) { + UniversalBlePigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.startPeripheral$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val configArg = args[0] as UniversalBlePeripheralConfig + api.startPeripheral(configArg) { result: Result -> + val error = result.exceptionOrNull() + if (error != null) { + reply.reply(UniversalBlePigeonUtils.wrapError(error)) + } else { + reply.reply(UniversalBlePigeonUtils.wrapResult(null)) + } + } + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.stopPeripheral$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { _, reply -> + api.stopPeripheral{ result: Result -> + val error = result.exceptionOrNull() + if (error != null) { + reply.reply(UniversalBlePigeonUtils.wrapError(error)) + } else { + reply.reply(UniversalBlePigeonUtils.wrapResult(null)) + } + } + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.updatePeripheralCharacteristicValue$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val serviceArg = args[0] as String + val characteristicArg = args[1] as String + val valueArg = args[2] as ByteArray + api.updatePeripheralCharacteristicValue(serviceArg, characteristicArg, valueArg) { result: Result -> + val error = result.exceptionOrNull() + if (error != null) { + reply.reply(UniversalBlePigeonUtils.wrapError(error)) + } else { + reply.reply(UniversalBlePigeonUtils.wrapResult(null)) + } + } + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.notifyPeripheralCharacteristic$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val serviceArg = args[0] as String + val characteristicArg = args[1] as String + val valueArg = args[2] as ByteArray + val indicateArg = args[3] as Boolean + api.notifyPeripheralCharacteristic(serviceArg, characteristicArg, valueArg, indicateArg) { result: Result -> + val error = result.exceptionOrNull() + if (error != null) { + reply.reply(UniversalBlePigeonUtils.wrapError(error)) + } else { + reply.reply(UniversalBlePigeonUtils.wrapResult(null)) + } + } + } + } else { + channel.setMessageHandler(null) + } + } run { val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.setLogLevel$separatedMessageChannelSuffix", codec) if (api != null) { @@ -1125,7 +1670,7 @@ class UniversalBleCallbackChannel(private val binaryMessenger: BinaryMessenger, } } else { callback(Result.failure(UniversalBlePigeonUtils.createConnectionError(channelName))) - } + } } } fun onPairStateChange(deviceIdArg: String, isPairedArg: Boolean, errorArg: String?, callback: (Result) -> Unit) @@ -1142,7 +1687,7 @@ class UniversalBleCallbackChannel(private val binaryMessenger: BinaryMessenger, } } else { callback(Result.failure(UniversalBlePigeonUtils.createConnectionError(channelName))) - } + } } } fun onScanResult(resultArg: UniversalBleScanResult, callback: (Result) -> Unit) @@ -1159,7 +1704,7 @@ class UniversalBleCallbackChannel(private val binaryMessenger: BinaryMessenger, } } else { callback(Result.failure(UniversalBlePigeonUtils.createConnectionError(channelName))) - } + } } } fun onValueChanged(deviceIdArg: String, characteristicIdArg: String, valueArg: ByteArray, timestampArg: Long?, callback: (Result) -> Unit) @@ -1176,7 +1721,7 @@ class UniversalBleCallbackChannel(private val binaryMessenger: BinaryMessenger, } } else { callback(Result.failure(UniversalBlePigeonUtils.createConnectionError(channelName))) - } + } } } fun onConnectionChanged(deviceIdArg: String, connectedArg: Boolean, errorArg: String?, callback: (Result) -> Unit) @@ -1193,7 +1738,58 @@ class UniversalBleCallbackChannel(private val binaryMessenger: BinaryMessenger, } } else { callback(Result.failure(UniversalBlePigeonUtils.createConnectionError(channelName))) - } + } + } + } + fun onPeripheralConnectionChanged(deviceIdArg: String, connectedArg: Boolean, callback: (Result) -> Unit) +{ + val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else "" + val channelName = "dev.flutter.pigeon.universal_ble.UniversalBleCallbackChannel.onPeripheralConnectionChanged$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))) + } + } + } + fun onPeripheralWrite(eventArg: UniversalBlePeripheralWriteEvent, callback: (Result) -> Unit) +{ + val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else "" + val channelName = "dev.flutter.pigeon.universal_ble.UniversalBleCallbackChannel.onPeripheralWrite$separatedMessageChannelSuffix" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(eventArg)) { + 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 onPeripheralSubscriptionChanged(deviceIdArg: String, serviceArg: String, characteristicArg: String, subscribedArg: Boolean, callback: (Result) -> Unit) +{ + val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else "" + val channelName = "dev.flutter.pigeon.universal_ble.UniversalBleCallbackChannel.onPeripheralSubscriptionChanged$separatedMessageChannelSuffix" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(deviceIdArg, serviceArg, characteristicArg, subscribedArg)) { + 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/UniversalBlePlugin.kt b/android/src/main/kotlin/com/navideck/universal_ble/UniversalBlePlugin.kt index beac594..36dd956 100644 --- a/android/src/main/kotlin/com/navideck/universal_ble/UniversalBlePlugin.kt +++ b/android/src/main/kotlin/com/navideck/universal_ble/UniversalBlePlugin.kt @@ -9,9 +9,16 @@ import android.bluetooth.BluetoothGatt import android.bluetooth.BluetoothGattCallback 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.BluetoothStatusCodes +import android.bluetooth.le.AdvertiseCallback +import android.bluetooth.le.AdvertiseData +import android.bluetooth.le.AdvertiseSettings +import android.bluetooth.le.BluetoothLeAdvertiser import android.bluetooth.le.ScanCallback import android.bluetooth.le.ScanFilter import android.bluetooth.le.ScanResult @@ -24,6 +31,7 @@ import android.content.IntentFilter import android.os.Build import android.os.Handler import android.os.Looper +import android.os.ParcelUuid import io.flutter.embedding.engine.plugins.FlutterPlugin import io.flutter.embedding.engine.plugins.activity.ActivityAware import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding @@ -63,6 +71,12 @@ class UniversalBlePlugin : UniversalBlePlatformChannel, BluetoothGattCallback(), private val rssiResultFutureList = mutableListOf() private val autoConnectDevices = mutableSetOf() + private var peripheralGattServer: BluetoothGattServer? = null + private var peripheralAdvertiser: BluetoothLeAdvertiser? = null + private var peripheralPendingStartCallback: ((Result) -> Unit)? = null + private val peripheralConnectedDevices = mutableSetOf() + private val peripheralCharacteristics = mutableMapOf() + override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { UniversalBlePlatformChannel.setUp(flutterPluginBinding.binaryMessenger, this) callbackChannel = UniversalBleCallbackChannel(flutterPluginBinding.binaryMessenger) @@ -84,6 +98,7 @@ class UniversalBlePlugin : UniversalBlePlatformChannel, BluetoothGattCallback(), override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) { bluetoothManager.adapter.bluetoothLeScanner?.stopScan(scanCallback) + stopPeripheralInternal() context.unregisterReceiver(broadcastReceiver) callbackChannel = null mainThreadHandler = null @@ -99,12 +114,19 @@ class UniversalBlePlugin : UniversalBlePlatformChannel, BluetoothGattCallback(), ) } - override fun hasPermissions(withAndroidFineLocation: Boolean): Boolean { - return permissionHandler?.hasPermissions(withAndroidFineLocation) ?: false + override fun hasPermissions( + withAndroidFineLocation: Boolean, + withAndroidBluetoothAdvertise: Boolean, + ): Boolean { + return permissionHandler?.hasPermissions( + withAndroidFineLocation, + withAndroidBluetoothAdvertise, + ) ?: false } override fun requestPermissions( withAndroidFineLocation: Boolean, + withAndroidBluetoothAdvertise: Boolean, callback: (Result) -> Unit, ) { if (permissionHandler == null) { @@ -117,7 +139,11 @@ class UniversalBlePlugin : UniversalBlePlatformChannel, BluetoothGattCallback(), ) ) } - permissionHandler?.requestPermissions(withAndroidFineLocation, callback) + permissionHandler?.requestPermissions( + withAndroidFineLocation, + withAndroidBluetoothAdvertise, + callback, + ) } override fun enableBluetooth(callback: (Result) -> Unit) { @@ -801,6 +827,319 @@ class UniversalBlePlugin : UniversalBlePlatformChannel, BluetoothGattCallback(), } } + override fun isPeripheralSupported(): Boolean { + val adapter = bluetoothManager.adapter ?: return false + return adapter.isEnabled && adapter.isMultipleAdvertisementSupported + } + + override fun startPeripheral( + config: UniversalBlePeripheralConfig, + callback: (Result) -> Unit, + ) { + val adapter = bluetoothManager.adapter + if (adapter == null || !adapter.isEnabled) { + callback( + Result.failure( + createFlutterError( + UniversalBleErrorCode.BLUETOOTH_NOT_ENABLED, + "Bluetooth not enabled" + ) + ) + ) + return + } + if (!adapter.isMultipleAdvertisementSupported) { + callback( + Result.failure( + createFlutterError( + UniversalBleErrorCode.NOT_SUPPORTED, + "BLE peripheral advertising is not supported on this device" + ) + ) + ) + return + } + if (peripheralPendingStartCallback != null) { + callback( + Result.failure( + createFlutterError( + UniversalBleErrorCode.OPERATION_IN_PROGRESS, + "Peripheral start already in progress" + ) + ) + ) + return + } + + stopPeripheralInternal() + + val server = bluetoothManager.openGattServer(context, peripheralGattServerCallback) + if (server == null) { + callback( + Result.failure( + createFlutterError( + UniversalBleErrorCode.FAILED, + "Unable to open GATT server" + ) + ) + ) + return + } + + try { + peripheralCharacteristics.clear() + config.services.forEach { serviceConfig -> + val service = BluetoothGattService( + UUID.fromString(serviceConfig.uuid), + BluetoothGattService.SERVICE_TYPE_PRIMARY, + ) + serviceConfig.characteristics.forEach { characteristicConfig -> + val characteristic = BluetoothGattCharacteristic( + UUID.fromString(characteristicConfig.uuid), + characteristicConfig.properties.toPeripheralPropertyFlags(), + characteristicConfig.permissions.toPeripheralPermissionFlags(), + ) + @Suppress("DEPRECATION") + characteristic.value = characteristicConfig.initialValue ?: ByteArray(0) + characteristicConfig.descriptors.forEach { descriptorConfig -> + val descriptor = BluetoothGattDescriptor( + UUID.fromString(descriptorConfig.uuid), + descriptorConfig.permissions.toPeripheralPermissionFlags(), + ) + @Suppress("DEPRECATION") + descriptor.value = descriptorConfig.initialValue ?: ByteArray(0) + characteristic.addDescriptor(descriptor) + } + if (characteristicConfig.properties.any { + it == CharacteristicProperty.Notify.value || + it == CharacteristicProperty.Indicate.value + } && characteristic.getDescriptor(ccdCharacteristic) == null + ) { + characteristic.addDescriptor( + BluetoothGattDescriptor( + ccdCharacteristic, + BluetoothGattDescriptor.PERMISSION_READ or + BluetoothGattDescriptor.PERMISSION_WRITE, + ) + ) + } + service.addCharacteristic(characteristic) + peripheralCharacteristics[peripheralCharacteristicKey(serviceConfig.uuid, characteristicConfig.uuid)] = characteristic + } + if (!server.addService(service)) { + throw createFlutterError( + UniversalBleErrorCode.FAILED, + "Failed to add GATT service ${serviceConfig.uuid}" + ) + } + } + } catch (e: FlutterError) { + server.close() + peripheralCharacteristics.clear() + callback(Result.failure(e)) + return + } catch (e: Exception) { + server.close() + peripheralCharacteristics.clear() + callback( + Result.failure( + createFlutterError( + UniversalBleErrorCode.FAILED, + "Failed to create GATT services", + e.toString() + ) + ) + ) + return + } + + peripheralGattServer = server + try { + adapter.name = config.advertisedName + } catch (_: SecurityException) { + } + + val advertiser = adapter.bluetoothLeAdvertiser + if (advertiser == null) { + stopPeripheralInternal() + callback( + Result.failure( + createFlutterError( + UniversalBleErrorCode.NOT_SUPPORTED, + "BLE advertiser not available" + ) + ) + ) + return + } + + val settings = AdvertiseSettings.Builder() + .setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_BALANCED) + .setConnectable(true) + .setTimeout(0) + .setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_MEDIUM) + .build() + val dataBuilder = AdvertiseData.Builder().setIncludeDeviceName(true) + config.services.forEach { service -> + dataBuilder.addServiceUuid(ParcelUuid(UUID.fromString(service.uuid))) + } + peripheralAdvertiser = advertiser + peripheralPendingStartCallback = callback + advertiser.startAdvertising(settings, dataBuilder.build(), peripheralAdvertiseCallback) + } + + override fun stopPeripheral(callback: (Result) -> Unit) { + peripheralPendingStartCallback?.invoke( + Result.failure( + createFlutterError( + UniversalBleErrorCode.OPERATION_CANCELLED, + "Peripheral start cancelled" + ) + ) + ) + peripheralPendingStartCallback = null + stopPeripheralInternal() + callback(Result.success(Unit)) + } + + override fun updatePeripheralCharacteristicValue( + service: String, + characteristic: String, + value: ByteArray, + callback: (Result) -> Unit, + ) { + val gattCharacteristic = peripheralCharacteristics[peripheralCharacteristicKey(service, characteristic)] + if (gattCharacteristic == null) { + callback( + Result.failure( + createFlutterError( + UniversalBleErrorCode.CHARACTERISTIC_NOT_FOUND, + "Unknown peripheral characteristic $characteristic" + ) + ) + ) + return + } + @Suppress("DEPRECATION") + gattCharacteristic.value = value + callback(Result.success(Unit)) + } + + override fun notifyPeripheralCharacteristic( + service: String, + characteristic: String, + value: ByteArray, + indicate: Boolean, + callback: (Result) -> Unit, + ) { + val server = peripheralGattServer + val gattCharacteristic = peripheralCharacteristics[peripheralCharacteristicKey(service, characteristic)] + if (server == null || gattCharacteristic == null) { + callback( + Result.failure( + createFlutterError( + UniversalBleErrorCode.CHARACTERISTIC_NOT_FOUND, + "Unknown peripheral characteristic $characteristic" + ) + ) + ) + return + } + @Suppress("DEPRECATION") + gattCharacteristic.value = value + val devices = synchronized(peripheralConnectedDevices) { + peripheralConnectedDevices.toList() + } + devices.forEach { device -> + server.notifyCharacteristicChanged(device, gattCharacteristic, indicate) + } + callback(Result.success(Unit)) + } + + private fun stopPeripheralInternal() { + peripheralAdvertiser?.stopAdvertising(peripheralAdvertiseCallback) + peripheralAdvertiser = null + peripheralGattServer?.close() + peripheralGattServer = null + synchronized(peripheralConnectedDevices) { + peripheralConnectedDevices.clear() + } + peripheralCharacteristics.clear() + } + + private fun peripheralCharacteristicKey(service: String, characteristic: String): String { + return "${service.lowercase()}|${characteristic.lowercase()}" + } + + private fun List.toPeripheralPropertyFlags(): Int { + var flags = 0 + forEach { property -> + when (CharacteristicProperty.entries.firstOrNull { it.value == property }) { + CharacteristicProperty.Broadcast -> flags = flags or BluetoothGattCharacteristic.PROPERTY_BROADCAST + CharacteristicProperty.Read -> flags = flags or BluetoothGattCharacteristic.PROPERTY_READ + CharacteristicProperty.WriteWithoutResponse -> flags = flags or BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE + CharacteristicProperty.Write -> flags = flags or BluetoothGattCharacteristic.PROPERTY_WRITE + CharacteristicProperty.Notify -> flags = flags or BluetoothGattCharacteristic.PROPERTY_NOTIFY + CharacteristicProperty.Indicate -> flags = flags or BluetoothGattCharacteristic.PROPERTY_INDICATE + CharacteristicProperty.AuthenticatedSignedWrites -> flags = flags or BluetoothGattCharacteristic.PROPERTY_SIGNED_WRITE + CharacteristicProperty.ExtendedProperties -> flags = flags or BluetoothGattCharacteristic.PROPERTY_EXTENDED_PROPS + null -> {} + } + } + return flags + } + + private fun List.toPeripheralPermissionFlags(): Int { + var flags = 0 + forEach { permission -> + when (permission) { + 0L -> flags = flags or BluetoothGattCharacteristic.PERMISSION_READ + 1L -> flags = flags or BluetoothGattCharacteristic.PERMISSION_WRITE + } + } + return flags + } + + private fun callbackPeripheralConnection(deviceId: String, connected: Boolean) { + mainThreadHandler?.post { + callbackChannel?.onPeripheralConnectionChanged(deviceId, connected) {} + } + } + + private fun callbackPeripheralSubscription( + deviceId: String, + service: String, + characteristic: String, + subscribed: Boolean, + ) { + mainThreadHandler?.post { + callbackChannel?.onPeripheralSubscriptionChanged( + deviceId, + service, + characteristic, + subscribed, + ) {} + } + } + + private fun callbackPeripheralWrite( + deviceId: String, + service: String, + characteristic: String, + value: ByteArray, + ) { + mainThreadHandler?.post { + callbackChannel?.onPeripheralWrite( + UniversalBlePeripheralWriteEvent( + deviceId, + service, + characteristic, + value, + ) + ) {} + } + } + override fun onMtuChanged(gatt: BluetoothGatt?, mtu: Int, status: Int) { val deviceId = gatt?.device?.address ?: return mtuResultFutureList.removeAll { @@ -1191,6 +1530,157 @@ class UniversalBlePlugin : UniversalBlePlatformChannel, BluetoothGattCallback(), } } + private val peripheralAdvertiseCallback = object : AdvertiseCallback() { + override fun onStartSuccess(settingsInEffect: AdvertiseSettings) { + peripheralPendingStartCallback?.invoke(Result.success(Unit)) + peripheralPendingStartCallback = null + } + + override fun onStartFailure(errorCode: Int) { + peripheralPendingStartCallback?.invoke( + Result.failure( + createFlutterError( + UniversalBleErrorCode.FAILED, + "BLE advertising failed with code $errorCode", + errorCode.toString() + ) + ) + ) + peripheralPendingStartCallback = null + stopPeripheralInternal() + } + } + + private val peripheralGattServerCallback = object : BluetoothGattServerCallback() { + override fun onConnectionStateChange(device: BluetoothDevice, status: Int, newState: Int) { + if (newState == BluetoothProfile.STATE_CONNECTED) { + synchronized(peripheralConnectedDevices) { + peripheralConnectedDevices.add(device) + } + callbackPeripheralConnection(device.address, true) + } else if (newState == BluetoothProfile.STATE_DISCONNECTED) { + synchronized(peripheralConnectedDevices) { + peripheralConnectedDevices.remove(device) + } + callbackPeripheralConnection(device.address, false) + } + } + + override fun onCharacteristicReadRequest( + device: BluetoothDevice, + requestId: Int, + offset: Int, + characteristic: BluetoothGattCharacteristic, + ) { + @Suppress("DEPRECATION") + val value = characteristic.value ?: ByteArray(0) + if (offset > value.size) { + peripheralGattServer?.sendResponse( + device, + requestId, + BluetoothGatt.GATT_INVALID_OFFSET, + offset, + null, + ) + return + } + peripheralGattServer?.sendResponse( + device, + requestId, + BluetoothGatt.GATT_SUCCESS, + offset, + value.copyOfRange(offset, value.size), + ) + } + + override fun onCharacteristicWriteRequest( + device: BluetoothDevice, + requestId: Int, + characteristic: BluetoothGattCharacteristic, + preparedWrite: Boolean, + responseNeeded: Boolean, + offset: Int, + value: ByteArray, + ) { + @Suppress("DEPRECATION") + characteristic.value = value + if (responseNeeded) { + peripheralGattServer?.sendResponse( + device, + requestId, + BluetoothGatt.GATT_SUCCESS, + offset, + null, + ) + } + callbackPeripheralWrite( + device.address, + characteristic.service.uuid.toString(), + characteristic.uuid.toString(), + value, + ) + } + + override fun onDescriptorReadRequest( + device: BluetoothDevice, + requestId: Int, + offset: Int, + descriptor: BluetoothGattDescriptor, + ) { + @Suppress("DEPRECATION") + val value = descriptor.value ?: ByteArray(0) + if (offset > value.size) { + peripheralGattServer?.sendResponse( + device, + requestId, + BluetoothGatt.GATT_INVALID_OFFSET, + offset, + null, + ) + return + } + peripheralGattServer?.sendResponse( + device, + requestId, + BluetoothGatt.GATT_SUCCESS, + offset, + value.copyOfRange(offset, value.size), + ) + } + + override fun onDescriptorWriteRequest( + device: BluetoothDevice, + requestId: Int, + descriptor: BluetoothGattDescriptor, + preparedWrite: Boolean, + responseNeeded: Boolean, + offset: Int, + value: ByteArray, + ) { + @Suppress("DEPRECATION") + descriptor.value = value + if (responseNeeded) { + peripheralGattServer?.sendResponse( + device, + requestId, + BluetoothGatt.GATT_SUCCESS, + offset, + null, + ) + } + if (descriptor.uuid == ccdCharacteristic) { + val subscribed = value.contentEquals(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE) || + value.contentEquals(BluetoothGattDescriptor.ENABLE_INDICATION_VALUE) + callbackPeripheralSubscription( + device.address, + descriptor.characteristic.service.uuid.toString(), + descriptor.characteristic.uuid.toString(), + subscribed, + ) + } + } + } + override fun onConnectionStateChange( gatt: BluetoothGatt, diff --git a/darwin/universal_ble/Sources/universal_ble/UniversalBle.g.swift b/darwin/universal_ble/Sources/universal_ble/UniversalBle.g.swift index dc40b39..7dd1cd5 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.4), 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 (let lhs as AnyObject, let rhs as AnyObject) where lhs === rhs: + return true + case is (Void, Void): return true - case let (cleanLhsHashable, cleanRhsHashable) as (AnyHashable, AnyHashable): - return cleanLhsHashable == cleanRhsHashable - - 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 + } else { + hasher.combine(0) } - - if let hashableValue = value as? AnyHashable { - hasher.combine(hashableValue.hashValue) - } - - return hasher.combine(String(describing: value)) } - enum UniversalBleLogLevel: Int { case none = 0 @@ -261,9 +307,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 +349,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 +389,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 +422,225 @@ 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) + } +} + +/// Generated class from Pigeon that represents data sent in messages. +struct UniversalBlePeripheralConfig: Hashable { + var advertisedName: String + var services: [UniversalBlePeripheralService] + + + // swift-format-ignore: AlwaysUseLowerCamelCase + static func fromList(_ pigeonVar_list: [Any?]) -> UniversalBlePeripheralConfig? { + let advertisedName = pigeonVar_list[0] as! String + let services = pigeonVar_list[1] as! [UniversalBlePeripheralService] + + return UniversalBlePeripheralConfig( + advertisedName: advertisedName, + services: services + ) + } + func toList() -> [Any?] { + return [ + advertisedName, + services, + ] + } + static func == (lhs: UniversalBlePeripheralConfig, rhs: UniversalBlePeripheralConfig) -> Bool { + if Swift.type(of: lhs) != Swift.type(of: rhs) { + return false + } + return deepEqualsUniversalBle(lhs.advertisedName, rhs.advertisedName) && deepEqualsUniversalBle(lhs.services, rhs.services) + } + + func hash(into hasher: inout Hasher) { + hasher.combine("UniversalBlePeripheralConfig") + deepHashUniversalBle(value: advertisedName, hasher: &hasher) + deepHashUniversalBle(value: services, hasher: &hasher) + } +} + +/// Generated class from Pigeon that represents data sent in messages. +struct UniversalBlePeripheralService: Hashable { + var uuid: String + var characteristics: [UniversalBlePeripheralCharacteristic] + + + // swift-format-ignore: AlwaysUseLowerCamelCase + static func fromList(_ pigeonVar_list: [Any?]) -> UniversalBlePeripheralService? { + let uuid = pigeonVar_list[0] as! String + let characteristics = pigeonVar_list[1] as! [UniversalBlePeripheralCharacteristic] + + return UniversalBlePeripheralService( + uuid: uuid, + characteristics: characteristics + ) + } + func toList() -> [Any?] { + return [ + uuid, + characteristics, + ] + } + static func == (lhs: UniversalBlePeripheralService, rhs: UniversalBlePeripheralService) -> Bool { + 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) { + hasher.combine("UniversalBlePeripheralService") + deepHashUniversalBle(value: uuid, hasher: &hasher) + deepHashUniversalBle(value: characteristics, hasher: &hasher) + } +} + +/// Generated class from Pigeon that represents data sent in messages. +struct UniversalBlePeripheralCharacteristic: Hashable { + var uuid: String + var properties: [Int64] + var permissions: [Int64] + var descriptors: [UniversalBlePeripheralDescriptor] + var initialValue: FlutterStandardTypedData? = nil + + + // swift-format-ignore: AlwaysUseLowerCamelCase + static func fromList(_ pigeonVar_list: [Any?]) -> UniversalBlePeripheralCharacteristic? { + let uuid = pigeonVar_list[0] as! String + let properties = pigeonVar_list[1] as! [Int64] + let permissions = pigeonVar_list[2] as! [Int64] + let descriptors = pigeonVar_list[3] as! [UniversalBlePeripheralDescriptor] + let initialValue: FlutterStandardTypedData? = nilOrValue(pigeonVar_list[4]) + + return UniversalBlePeripheralCharacteristic( + uuid: uuid, + properties: properties, + permissions: permissions, + descriptors: descriptors, + initialValue: initialValue + ) + } + func toList() -> [Any?] { + return [ + uuid, + properties, + permissions, + descriptors, + initialValue, + ] + } + static func == (lhs: UniversalBlePeripheralCharacteristic, rhs: UniversalBlePeripheralCharacteristic) -> 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.initialValue, rhs.initialValue) + } + + func hash(into hasher: inout Hasher) { + hasher.combine("UniversalBlePeripheralCharacteristic") + deepHashUniversalBle(value: uuid, hasher: &hasher) + deepHashUniversalBle(value: properties, hasher: &hasher) + deepHashUniversalBle(value: permissions, hasher: &hasher) + deepHashUniversalBle(value: descriptors, hasher: &hasher) + deepHashUniversalBle(value: initialValue, hasher: &hasher) + } +} + +/// Generated class from Pigeon that represents data sent in messages. +struct UniversalBlePeripheralDescriptor: Hashable { + var uuid: String + var permissions: [Int64] + var initialValue: FlutterStandardTypedData? = nil + + + // swift-format-ignore: AlwaysUseLowerCamelCase + static func fromList(_ pigeonVar_list: [Any?]) -> UniversalBlePeripheralDescriptor? { + let uuid = pigeonVar_list[0] as! String + let permissions = pigeonVar_list[1] as! [Int64] + let initialValue: FlutterStandardTypedData? = nilOrValue(pigeonVar_list[2]) + + return UniversalBlePeripheralDescriptor( + uuid: uuid, + permissions: permissions, + initialValue: initialValue + ) + } + func toList() -> [Any?] { + return [ + uuid, + permissions, + initialValue, + ] + } + static func == (lhs: UniversalBlePeripheralDescriptor, rhs: UniversalBlePeripheralDescriptor) -> Bool { + if Swift.type(of: lhs) != Swift.type(of: rhs) { + return false + } + return deepEqualsUniversalBle(lhs.uuid, rhs.uuid) && deepEqualsUniversalBle(lhs.permissions, rhs.permissions) && deepEqualsUniversalBle(lhs.initialValue, rhs.initialValue) + } + + func hash(into hasher: inout Hasher) { + hasher.combine("UniversalBlePeripheralDescriptor") + deepHashUniversalBle(value: uuid, hasher: &hasher) + deepHashUniversalBle(value: permissions, hasher: &hasher) + deepHashUniversalBle(value: initialValue, hasher: &hasher) + } +} + +/// Generated class from Pigeon that represents data sent in messages. +struct UniversalBlePeripheralWriteEvent: Hashable { + var deviceId: String + var service: String + var characteristic: String + var value: FlutterStandardTypedData + + + // swift-format-ignore: AlwaysUseLowerCamelCase + static func fromList(_ pigeonVar_list: [Any?]) -> UniversalBlePeripheralWriteEvent? { + let deviceId = pigeonVar_list[0] as! String + let service = pigeonVar_list[1] as! String + let characteristic = pigeonVar_list[2] as! String + let value = pigeonVar_list[3] as! FlutterStandardTypedData + + return UniversalBlePeripheralWriteEvent( + deviceId: deviceId, + service: service, + characteristic: characteristic, + value: value + ) + } + func toList() -> [Any?] { + return [ + deviceId, + service, + characteristic, + value, + ] + } + static func == (lhs: UniversalBlePeripheralWriteEvent, rhs: UniversalBlePeripheralWriteEvent) -> Bool { + if Swift.type(of: lhs) != Swift.type(of: rhs) { + return false + } + return deepEqualsUniversalBle(lhs.deviceId, rhs.deviceId) && deepEqualsUniversalBle(lhs.service, rhs.service) && deepEqualsUniversalBle(lhs.characteristic, rhs.characteristic) && deepEqualsUniversalBle(lhs.value, rhs.value) + } + + func hash(into hasher: inout Hasher) { + hasher.combine("UniversalBlePeripheralWriteEvent") + deepHashUniversalBle(value: deviceId, hasher: &hasher) + deepHashUniversalBle(value: service, hasher: &hasher) + deepHashUniversalBle(value: characteristic, hasher: &hasher) + deepHashUniversalBle(value: value, hasher: &hasher) } } @@ -388,9 +678,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 +711,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 +752,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 +793,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 +830,16 @@ 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) { - deepHashUniversalBle(value: toList(), hasher: &hasher) + hasher.combine("UniversalManufacturerData") + deepHashUniversalBle(value: companyIdentifier, hasher: &hasher) + deepHashUniversalBle(value: data, hasher: &hasher) } } @@ -546,14 +873,24 @@ private class UniversalBlePigeonCodecReader: FlutterStandardReader { case 135: return UniversalBleDescriptor.fromList(self.readValue() as! [Any?]) case 136: - return AndroidOptions.fromList(self.readValue() as! [Any?]) + return UniversalBlePeripheralConfig.fromList(self.readValue() as! [Any?]) case 137: - return UniversalScanConfig.fromList(self.readValue() as! [Any?]) + return UniversalBlePeripheralService.fromList(self.readValue() as! [Any?]) case 138: - return UniversalScanFilter.fromList(self.readValue() as! [Any?]) + return UniversalBlePeripheralCharacteristic.fromList(self.readValue() as! [Any?]) case 139: - return UniversalManufacturerDataFilter.fromList(self.readValue() as! [Any?]) + return UniversalBlePeripheralDescriptor.fromList(self.readValue() as! [Any?]) case 140: + return UniversalBlePeripheralWriteEvent.fromList(self.readValue() as! [Any?]) + case 141: + return AndroidOptions.fromList(self.readValue() as! [Any?]) + case 142: + return UniversalScanConfig.fromList(self.readValue() as! [Any?]) + case 143: + return UniversalScanFilter.fromList(self.readValue() as! [Any?]) + case 144: + return UniversalManufacturerDataFilter.fromList(self.readValue() as! [Any?]) + case 145: return UniversalManufacturerData.fromList(self.readValue() as! [Any?]) default: return super.readValue(ofType: type) @@ -584,21 +921,36 @@ private class UniversalBlePigeonCodecWriter: FlutterStandardWriter { } else if let value = value as? UniversalBleDescriptor { super.writeByte(135) super.writeValue(value.toList()) - } else if let value = value as? AndroidOptions { + } else if let value = value as? UniversalBlePeripheralConfig { super.writeByte(136) super.writeValue(value.toList()) - } else if let value = value as? UniversalScanConfig { + } else if let value = value as? UniversalBlePeripheralService { super.writeByte(137) super.writeValue(value.toList()) - } else if let value = value as? UniversalScanFilter { + } else if let value = value as? UniversalBlePeripheralCharacteristic { super.writeByte(138) super.writeValue(value.toList()) - } else if let value = value as? UniversalManufacturerDataFilter { + } else if let value = value as? UniversalBlePeripheralDescriptor { super.writeByte(139) super.writeValue(value.toList()) - } else if let value = value as? UniversalManufacturerData { + } else if let value = value as? UniversalBlePeripheralWriteEvent { super.writeByte(140) super.writeValue(value.toList()) + } else if let value = value as? AndroidOptions { + super.writeByte(141) + super.writeValue(value.toList()) + } else if let value = value as? UniversalScanConfig { + super.writeByte(142) + super.writeValue(value.toList()) + } else if let value = value as? UniversalScanFilter { + super.writeByte(143) + super.writeValue(value.toList()) + } else if let value = value as? UniversalManufacturerDataFilter { + super.writeByte(144) + super.writeValue(value.toList()) + } else if let value = value as? UniversalManufacturerData { + super.writeByte(145) + super.writeValue(value.toList()) } else { super.writeValue(value) } @@ -625,8 +977,8 @@ class UniversalBlePigeonCodec: FlutterStandardMessageCodec, @unchecked Sendable /// Generated protocol from Pigeon that represents a handler of messages from Flutter. protocol UniversalBlePlatformChannel { func getBluetoothAvailabilityState(completion: @escaping (Result) -> Void) - func hasPermissions(withAndroidFineLocation: Bool) throws -> Bool - func requestPermissions(withAndroidFineLocation: Bool, completion: @escaping (Result) -> Void) + func hasPermissions(withAndroidFineLocation: Bool, withAndroidBluetoothAdvertise: Bool) throws -> Bool + func requestPermissions(withAndroidFineLocation: Bool, withAndroidBluetoothAdvertise: Bool, completion: @escaping (Result) -> Void) func enableBluetooth(completion: @escaping (Result) -> Void) func disableBluetooth(completion: @escaping (Result) -> Void) func startScan(filter: UniversalScanFilter?, config: UniversalScanConfig?) throws @@ -646,6 +998,11 @@ protocol UniversalBlePlatformChannel { func getConnectionState(deviceId: String) throws -> Int64 func readRssi(deviceId: String, completion: @escaping (Result) -> Void) func requestConnectionPriority(deviceId: String, priority: Int64, completion: @escaping (Result) -> Void) + func isPeripheralSupported() throws -> Bool + func startPeripheral(config: UniversalBlePeripheralConfig, completion: @escaping (Result) -> Void) + func stopPeripheral(completion: @escaping (Result) -> Void) + func updatePeripheralCharacteristicValue(service: String, characteristic: String, value: FlutterStandardTypedData, completion: @escaping (Result) -> Void) + func notifyPeripheralCharacteristic(service: String, characteristic: String, value: FlutterStandardTypedData, indicate: Bool, completion: @escaping (Result) -> Void) func setLogLevel(logLevel: UniversalBleLogLevel) throws } @@ -675,8 +1032,9 @@ class UniversalBlePlatformChannelSetup { hasPermissionsChannel.setMessageHandler { message, reply in let args = message as! [Any?] let withAndroidFineLocationArg = args[0] as! Bool + let withAndroidBluetoothAdvertiseArg = args[1] as! Bool do { - let result = try api.hasPermissions(withAndroidFineLocation: withAndroidFineLocationArg) + let result = try api.hasPermissions(withAndroidFineLocation: withAndroidFineLocationArg, withAndroidBluetoothAdvertise: withAndroidBluetoothAdvertiseArg) reply(wrapResult(result)) } catch { reply(wrapError(error)) @@ -690,7 +1048,8 @@ class UniversalBlePlatformChannelSetup { requestPermissionsChannel.setMessageHandler { message, reply in let args = message as! [Any?] let withAndroidFineLocationArg = args[0] as! Bool - api.requestPermissions(withAndroidFineLocation: withAndroidFineLocationArg) { result in + let withAndroidBluetoothAdvertiseArg = args[1] as! Bool + api.requestPermissions(withAndroidFineLocation: withAndroidFineLocationArg, withAndroidBluetoothAdvertise: withAndroidBluetoothAdvertiseArg) { result in switch result { case .success: reply(wrapResult(nil)) @@ -1017,6 +1376,90 @@ class UniversalBlePlatformChannelSetup { } else { requestConnectionPriorityChannel.setMessageHandler(nil) } + let isPeripheralSupportedChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.isPeripheralSupported\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + isPeripheralSupportedChannel.setMessageHandler { _, reply in + do { + let result = try api.isPeripheralSupported() + reply(wrapResult(result)) + } catch { + reply(wrapError(error)) + } + } + } else { + isPeripheralSupportedChannel.setMessageHandler(nil) + } + let startPeripheralChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.startPeripheral\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + startPeripheralChannel.setMessageHandler { message, reply in + let args = message as! [Any?] + let configArg = args[0] as! UniversalBlePeripheralConfig + api.startPeripheral(config: configArg) { result in + switch result { + case .success: + reply(wrapResult(nil)) + case .failure(let error): + reply(wrapError(error)) + } + } + } + } else { + startPeripheralChannel.setMessageHandler(nil) + } + let stopPeripheralChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.stopPeripheral\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + stopPeripheralChannel.setMessageHandler { _, reply in + api.stopPeripheral { result in + switch result { + case .success: + reply(wrapResult(nil)) + case .failure(let error): + reply(wrapError(error)) + } + } + } + } else { + stopPeripheralChannel.setMessageHandler(nil) + } + let updatePeripheralCharacteristicValueChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.updatePeripheralCharacteristicValue\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + updatePeripheralCharacteristicValueChannel.setMessageHandler { message, reply in + let args = message as! [Any?] + let serviceArg = args[0] as! String + let characteristicArg = args[1] as! String + let valueArg = args[2] as! FlutterStandardTypedData + api.updatePeripheralCharacteristicValue(service: serviceArg, characteristic: characteristicArg, value: valueArg) { result in + switch result { + case .success: + reply(wrapResult(nil)) + case .failure(let error): + reply(wrapError(error)) + } + } + } + } else { + updatePeripheralCharacteristicValueChannel.setMessageHandler(nil) + } + let notifyPeripheralCharacteristicChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.notifyPeripheralCharacteristic\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + notifyPeripheralCharacteristicChannel.setMessageHandler { message, reply in + let args = message as! [Any?] + let serviceArg = args[0] as! String + let characteristicArg = args[1] as! String + let valueArg = args[2] as! FlutterStandardTypedData + let indicateArg = args[3] as! Bool + api.notifyPeripheralCharacteristic(service: serviceArg, characteristic: characteristicArg, value: valueArg, indicate: indicateArg) { result in + switch result { + case .success: + reply(wrapResult(nil)) + case .failure(let error): + reply(wrapError(error)) + } + } + } + } else { + notifyPeripheralCharacteristicChannel.setMessageHandler(nil) + } let setLogLevelChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.setLogLevel\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) if let api = api { setLogLevelChannel.setMessageHandler { message, reply in @@ -1043,6 +1486,9 @@ protocol UniversalBleCallbackChannelProtocol { func onScanResult(result resultArg: UniversalBleScanResult, completion: @escaping (Result) -> Void) func onValueChanged(deviceId deviceIdArg: String, characteristicId characteristicIdArg: String, value valueArg: FlutterStandardTypedData, timestamp timestampArg: Int64?, completion: @escaping (Result) -> Void) func onConnectionChanged(deviceId deviceIdArg: String, connected connectedArg: Bool, error errorArg: String?, completion: @escaping (Result) -> Void) + func onPeripheralConnectionChanged(deviceId deviceIdArg: String, connected connectedArg: Bool, completion: @escaping (Result) -> Void) + func onPeripheralWrite(event eventArg: UniversalBlePeripheralWriteEvent, completion: @escaping (Result) -> Void) + func onPeripheralSubscriptionChanged(deviceId deviceIdArg: String, service serviceArg: String, characteristic characteristicArg: String, subscribed subscribedArg: Bool, completion: @escaping (Result) -> Void) } class UniversalBleCallbackChannel: UniversalBleCallbackChannelProtocol { private let binaryMessenger: FlutterBinaryMessenger @@ -1144,4 +1590,58 @@ class UniversalBleCallbackChannel: UniversalBleCallbackChannelProtocol { } } } + func onPeripheralConnectionChanged(deviceId deviceIdArg: String, connected connectedArg: Bool, completion: @escaping (Result) -> Void) { + let channelName: String = "dev.flutter.pigeon.universal_ble.UniversalBleCallbackChannel.onPeripheralConnectionChanged\(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(())) + } + } + } + func onPeripheralWrite(event eventArg: UniversalBlePeripheralWriteEvent, completion: @escaping (Result) -> Void) { + let channelName: String = "dev.flutter.pigeon.universal_ble.UniversalBleCallbackChannel.onPeripheralWrite\(messageChannelSuffix)" + let channel = FlutterBasicMessageChannel(name: channelName, binaryMessenger: binaryMessenger, codec: codec) + channel.sendMessage([eventArg] 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 onPeripheralSubscriptionChanged(deviceId deviceIdArg: String, service serviceArg: String, characteristic characteristicArg: String, subscribed subscribedArg: Bool, completion: @escaping (Result) -> Void) { + let channelName: String = "dev.flutter.pigeon.universal_ble.UniversalBleCallbackChannel.onPeripheralSubscriptionChanged\(messageChannelSuffix)" + let channel = FlutterBasicMessageChannel(name: channelName, binaryMessenger: binaryMessenger, codec: codec) + channel.sendMessage([deviceIdArg, serviceArg, characteristicArg, subscribedArg] 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/UniversalBlePlugin.swift b/darwin/universal_ble/Sources/universal_ble/UniversalBlePlugin.swift index 4f6b1fb..7005b68 100644 --- a/darwin/universal_ble/Sources/universal_ble/UniversalBlePlugin.swift +++ b/darwin/universal_ble/Sources/universal_ble/UniversalBlePlugin.swift @@ -58,11 +58,15 @@ private class BleCentralDarwin: NSObject, UniversalBlePlatformChannel, CBCentral } } - func hasPermissions(withAndroidFineLocation _: Bool) throws -> Bool { + func hasPermissions(withAndroidFineLocation _: Bool, withAndroidBluetoothAdvertise _: Bool) throws -> Bool { return CBCentralManager.authorization == .allowedAlways } - func requestPermissions(withAndroidFineLocation _: Bool, completion: @escaping (Result) -> Void) { + func requestPermissions( + withAndroidFineLocation _: Bool, + withAndroidBluetoothAdvertise _: Bool, + completion: @escaping (Result) -> Void + ) { if manager.state != .unknown { completePermissionRequest(completion: completion) } else { @@ -379,6 +383,37 @@ private class BleCentralDarwin: NSObject, UniversalBlePlatformChannel, CBCentral completion(.failure(createFlutterError(code: .notSupported, message: "requestConnectionPriority is not supported on Apple platforms"))) } + func isPeripheralSupported() throws -> Bool { + false + } + + func startPeripheral(config _: UniversalBlePeripheralConfig, completion: @escaping (Result) -> Void) { + completion(.failure(createFlutterError(code: .notSupported, message: "BLE peripheral mode is not implemented on Apple platforms yet"))) + } + + func stopPeripheral(completion: @escaping (Result) -> Void) { + completion(.success(())) + } + + func updatePeripheralCharacteristicValue( + service _: String, + characteristic _: String, + value _: FlutterStandardTypedData, + completion: @escaping (Result) -> Void + ) { + completion(.failure(createFlutterError(code: .notSupported, message: "BLE peripheral mode is not implemented on Apple platforms yet"))) + } + + func notifyPeripheralCharacteristic( + service _: String, + characteristic _: String, + value _: FlutterStandardTypedData, + indicate _: Bool, + completion: @escaping (Result) -> Void + ) { + completion(.failure(createFlutterError(code: .notSupported, message: "BLE peripheral mode is not implemented on Apple platforms yet"))) + } + func readRssi(deviceId: String, completion: @escaping (Result) -> Void) { UniversalBleLogger.shared.logDebug("READ_RSSI -> \(deviceId)") guard let peripheral = deviceId.findPeripheral(manager: manager) else { diff --git a/example/android/app/src/main/AndroidManifest.xml b/example/android/app/src/main/AndroidManifest.xml index 191b9de..b4ba912 100644 --- a/example/android/app/src/main/AndroidManifest.xml +++ b/example/android/app/src/main/AndroidManifest.xml @@ -1,6 +1,7 @@ + diff --git a/example/lib/data/mock_universal_ble.dart b/example/lib/data/mock_universal_ble.dart index 44a681a..4df4876 100644 --- a/example/lib/data/mock_universal_ble.dart +++ b/example/lib/data/mock_universal_ble.dart @@ -54,8 +54,7 @@ class MockUniversalBle extends UniversalBlePlatform { } @override - Future> discoverServices( - String deviceId, bool withDescriptors) async { + Future> discoverServices(String deviceId, bool withDescriptors) async { return [_mockService]; } @@ -87,11 +86,7 @@ class MockUniversalBle extends UniversalBlePlatform { } @override - Future writeValue( - String deviceId, - String service, - String characteristic, - Uint8List value, + Future writeValue(String deviceId, String service, String characteristic, Uint8List value, BleOutputProperty bleOutputProperty) async { await Future.delayed(const Duration(milliseconds: 500)); _serviceValue = value; @@ -110,8 +105,8 @@ class MockUniversalBle extends UniversalBlePlatform { ) async {} @override - Future setNotifiable(String deviceId, String service, - String characteristic, BleInputProperty bleInputProperty) async {} + Future setNotifiable(String deviceId, String service, String characteristic, + BleInputProperty bleInputProperty) async {} @override Future isPaired(String deviceId) async { @@ -141,7 +136,10 @@ class MockUniversalBle extends UniversalBlePlatform { } @override - Future requestPermissions({bool withAndroidFineLocation = false}) { + Future requestPermissions({ + bool withAndroidFineLocation = false, + bool withAndroidBluetoothAdvertise = false, + }) { throw UnimplementedError(); } diff --git a/example/pubspec.lock b/example/pubspec.lock index 7c0e28f..374f080 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -333,7 +333,7 @@ packages: path: ".." relative: true source: path - version: "1.2.0" + version: "1.3.0" vector_math: dependency: transitive description: diff --git a/lib/src/models/ble_peripheral.dart b/lib/src/models/ble_peripheral.dart new file mode 100644 index 0000000..752a7fd --- /dev/null +++ b/lib/src/models/ble_peripheral.dart @@ -0,0 +1,88 @@ +import 'dart:typed_data'; + +import 'package:universal_ble/universal_ble.dart'; + +enum BlePeripheralCharacteristicPermission { read, write } + +class BlePeripheralConfig { + BlePeripheralConfig({required this.advertisedName, required this.services}); + + final String advertisedName; + final List services; +} + +class BlePeripheralService { + BlePeripheralService({required String uuid, required this.characteristics}) + : uuid = BleUuidParser.string(uuid); + + final String uuid; + final List characteristics; +} + +class BlePeripheralCharacteristic { + BlePeripheralCharacteristic({ + required String uuid, + required this.properties, + required this.permissions, + this.descriptors = const [], + Uint8List? initialValue, + }) : uuid = BleUuidParser.string(uuid), + initialValue = initialValue == null ? null : Uint8List.fromList(initialValue); + + final String uuid; + final List properties; + final List permissions; + final List descriptors; + final Uint8List? initialValue; +} + +class BlePeripheralDescriptor { + BlePeripheralDescriptor({ + required String uuid, + required this.permissions, + Uint8List? initialValue, + }) : uuid = BleUuidParser.string(uuid), + initialValue = initialValue == null ? null : Uint8List.fromList(initialValue); + + final String uuid; + final List permissions; + final Uint8List? initialValue; +} + +class BlePeripheralWriteEvent { + BlePeripheralWriteEvent({ + required this.deviceId, + required String service, + required String characteristic, + required Uint8List value, + }) : service = BleUuidParser.string(service), + characteristic = BleUuidParser.string(characteristic), + value = Uint8List.fromList(value); + + final String deviceId; + final String service; + final String characteristic; + final Uint8List value; +} + +class BlePeripheralConnectionEvent { + BlePeripheralConnectionEvent({required this.deviceId, required this.connected}); + + final String deviceId; + final bool connected; +} + +class BlePeripheralSubscriptionEvent { + BlePeripheralSubscriptionEvent({ + required this.deviceId, + required String service, + required String characteristic, + required this.subscribed, + }) : service = BleUuidParser.string(service), + characteristic = BleUuidParser.string(characteristic); + + final String deviceId; + final String service; + final String characteristic; + final bool subscribed; +} diff --git a/lib/src/models/model_exports.dart b/lib/src/models/model_exports.dart index 7533772..6195762 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/ble_peripheral.dart'; diff --git a/lib/src/universal_ble.dart b/lib/src/universal_ble.dart index 70627e6..555340b 100644 --- a/lib/src/universal_ble.dart +++ b/lib/src/universal_ble.dart @@ -14,8 +14,7 @@ class UniversalBle { static final BleCommandQueue _bleCommandQueue = BleCommandQueue(); /// Set custom platform specific implementation (e.g. for testing). - static void setInstance(UniversalBlePlatform instance) => - _platform = instance; + static void setInstance(UniversalBlePlatform instance) => _platform = instance; /// Set global timeout for all commands. /// Default timeout is 10 seconds. @@ -47,40 +46,36 @@ class UniversalBle { static Stream get scanStream => _platform.scanStream; /// Bluetooth availability state stream - static Stream get availabilityStream => - _platform.availabilityStream; + static Stream get availabilityStream => _platform.availabilityStream; /// Connection stream of a device - static Stream connectionStream(String deviceId) => - _platform.connectionStream(deviceId); + static Stream connectionStream(String deviceId) => _platform.connectionStream(deviceId); /// Characteristic value stream - static Stream characteristicValueStream( - String deviceId, - String characteristicId, - ) => _platform.characteristicValueStream(deviceId, characteristicId); + static Stream characteristicValueStream(String deviceId, String characteristicId) => + _platform.characteristicValueStream(deviceId, characteristicId); /// Pairing state stream - static Stream pairingStateStream(String deviceId) => - _platform.pairingStateStream(deviceId); + static Stream pairingStateStream(String deviceId) => _platform.pairingStateStream(deviceId); /// Get Bluetooth availability state. /// To be notified of updates, set [onAvailabilityChange] listener. static Future getBluetoothAvailabilityState() async { - return await _bleCommandQueue.queueCommand( - () => _platform.getBluetoothAvailabilityState(), - ); + return await _bleCommandQueue.queueCommand(() => _platform.getBluetoothAvailabilityState()); } /// Check if has permissions. /// [withAndroidFineLocation] is used to check fine location permission on Android 12+ (API 31+). + /// [withAndroidBluetoothAdvertise] is used to check Android 12+ advertise permission for peripheral mode. /// On Android lower than 12, this method will check location permission regardless of the [withAndroidFineLocation] value. /// `Windows`, `Linux` and `Web` will always return true. static Future hasPermissions({ bool withAndroidFineLocation = false, + bool withAndroidBluetoothAdvertise = false, }) async { return _platform.hasPermissions( withAndroidFineLocation: withAndroidFineLocation, + withAndroidBluetoothAdvertise: withAndroidBluetoothAdvertise, ); } @@ -88,13 +83,16 @@ class UniversalBle { /// if all permissions are already granted or granted by user, this method will succeed. /// it will throw exception if permissions are denied by user. /// [withAndroidFineLocation] is used to request fine location permission on Android 12+ (API 31+). + /// [withAndroidBluetoothAdvertise] is used to request Android 12+ advertise permission for peripheral mode. /// on Android lower than 12, this method will request location permission regardless of the [withAndroidFineLocation] value. /// `Windows`, `Linux` and `Web` will always succeed. static Future requestPermissions({ bool withAndroidFineLocation = false, + bool withAndroidBluetoothAdvertise = false, }) async { return _platform.requestPermissions( withAndroidFineLocation: withAndroidFineLocation, + withAndroidBluetoothAdvertise: withAndroidBluetoothAdvertise, ); } @@ -102,15 +100,9 @@ class UniversalBle { /// Scan results will arrive in [onScanResult] listener. /// It might throw errors if Bluetooth is not available. /// `webRequestOptions` is supported on Web only. - static Future startScan({ - ScanFilter? scanFilter, - PlatformConfig? platformConfig, - }) async { + static Future startScan({ScanFilter? scanFilter, PlatformConfig? platformConfig}) async { return await _bleCommandQueue.queueCommandWithoutTimeout( - () => _platform.startScan( - scanFilter: scanFilter, - platformConfig: platformConfig, - ), + () => _platform.startScan(scanFilter: scanFilter, platformConfig: platformConfig), ); } @@ -118,9 +110,7 @@ class UniversalBle { /// Set [onScanResult] listener to `null` if you don't need it anymore. /// It might throw errors if Bluetooth is not available. static Future stopScan() async { - return await _bleCommandQueue.queueCommandWithoutTimeout( - () => _platform.stopScan(), - ); + return await _bleCommandQueue.queueCommandWithoutTimeout(() => _platform.stopScan()); } /// Check if currently scanning for devices. @@ -147,17 +137,14 @@ class UniversalBle { bool autoConnect = false, }) async { timeout ??= const Duration(seconds: 60); - Completer completer = _connectionEventCompleter( - deviceId, - timeout: timeout, - ); + Completer completer = _connectionEventCompleter(deviceId, timeout: timeout); - _platform - .connect(deviceId, connectionTimeout: timeout, autoConnect: autoConnect) - .catchError((error) { - if (completer.isCompleted) return; - completer.completeError(ConnectionException(error)); - }); + _platform.connect(deviceId, connectionTimeout: timeout, autoConnect: autoConnect).catchError(( + error, + ) { + if (completer.isCompleted) return; + completer.completeError(ConnectionException(error)); + }); if (!await completer.future.timeout(timeout)) { throw ConnectionException("Failed to connect"); @@ -176,17 +163,10 @@ class UniversalBle { } try { - Completer completer = _connectionEventCompleter( - deviceId, - timeout: timeout, - ); + Completer completer = _connectionEventCompleter(deviceId, timeout: timeout); await _bleCommandQueue - .queueCommand( - () => _platform.disconnect(deviceId), - timeout: timeout, - deviceId: deviceId, - ) + .queueCommand(() => _platform.disconnect(deviceId), timeout: timeout, deviceId: deviceId) .catchError((error) { if (completer.isCompleted) return; completer.completeError(ConnectionException(error)); @@ -204,9 +184,7 @@ class UniversalBle { } if (await completer.future.timeout(timeout)) { - UniversalLogger.logError( - "Device $deviceId is still connected after disconnect attempt", - ); + UniversalLogger.logError("Device $deviceId is still connected after disconnect attempt"); } } catch (e) { UniversalLogger.logError("Disconnect failed: $e"); @@ -315,9 +293,7 @@ class UniversalBle { BleUuidParser.string(service), BleUuidParser.string(characteristic), value, - withoutResponse - ? BleOutputProperty.withoutResponse - : BleOutputProperty.withResponse, + withoutResponse ? BleOutputProperty.withoutResponse : BleOutputProperty.withResponse, ), timeout: timeout, deviceId: deviceId, @@ -339,11 +315,7 @@ class UniversalBle { /// /// **Best Practices:** Design for default ATT MTU (23 bytes), treat requests as /// opportunistic, and implement fragmentation for larger payloads. - static Future requestMtu( - String deviceId, - int expectedMtu, { - Duration? timeout, - }) async { + static Future requestMtu(String deviceId, int expectedMtu, {Duration? timeout}) async { return await _bleCommandQueue.queueCommand( () => _platform.requestMtu(deviceId, expectedMtu), timeout: timeout, @@ -445,11 +417,7 @@ class UniversalBle { /// /// On `Web/Windows` and `Web/Linux`, it does not work for devices that use `ConfirmOnly` pairing. /// Can throw `PairingException`, `ConnectionException` or `PlatformException`. - static Future pair( - String deviceId, { - BleCommand? pairingCommand, - Duration? timeout, - }) async { + static Future pair(String deviceId, {BleCommand? pairingCommand, Duration? timeout}) async { if (BleCapabilities.hasSystemPairingApi) { bool paired = await _bleCommandQueue.queueCommand( () => _platform.pair(deviceId), @@ -461,11 +429,7 @@ class UniversalBle { if (pairingCommand == null) { UniversalLogger.logWarning("PairingCommand required to get result"); } - await _connectAndExecuteBleCommand( - deviceId, - pairingCommand, - timeout: timeout, - ); + await _connectAndExecuteBleCommand(deviceId, pairingCommand, timeout: timeout); } } @@ -497,10 +461,7 @@ class UniversalBle { /// Returns connection state of the device. /// All platforms will return `Connected/Disconnected` states. /// `Android` and `Apple` can also return `Connecting/Disconnecting` states. - static Future getConnectionState( - String deviceId, { - Duration? timeout, - }) async { + static Future getConnectionState(String deviceId, {Duration? timeout}) async { return await _bleCommandQueue.queueCommand( () => _platform.getConnectionState(deviceId), timeout: timeout, @@ -511,10 +472,7 @@ class UniversalBle { /// It might throw errors if Bluetooth is not available. /// Not supported on `Web` and `Apple`. static Future enableBluetooth({Duration? timeout}) async { - return await _bleCommandQueue.queueCommand( - () => _platform.enableBluetooth(), - timeout: timeout, - ); + return await _bleCommandQueue.queueCommand(() => _platform.enableBluetooth(), timeout: timeout); } /// Disable Bluetooth. @@ -540,8 +498,7 @@ class UniversalBle { /// For this feature to work, you need to enable the `chrome://flags/#enable-experimental-web-platform-features` flag. /// Not every browser supports this API yet. /// Even if the browser supports it, sometimes it won't fire any advertisement events even though the device may be sending them. - static bool receivesAdvertisements(String deviceId) => - _platform.receivesAdvertisements(deviceId); + static bool receivesAdvertisements(String deviceId) => _platform.receivesAdvertisements(deviceId); /// Get Bluetooth state availability. static set onAvailabilityChange(OnAvailabilityChange? onAvailabilityChange) { @@ -555,21 +512,14 @@ class UniversalBle { } } - @Deprecated( - "Use [subscribeNotifications] or [subscribeIndications] or [unsubscribe] instead", - ) + @Deprecated("Use [subscribeNotifications] or [subscribeIndications] or [unsubscribe] instead") static Future setNotifiable( String deviceId, String service, String characteristic, BleInputProperty bleInputProperty, ) async { - return _sendBleInputPropertyCommand( - deviceId, - service, - characteristic, - bleInputProperty, - ); + return _sendBleInputPropertyCommand(deviceId, service, characteristic, bleInputProperty); } @Deprecated("Use [write] instead") @@ -599,10 +549,7 @@ class UniversalBle { return read(deviceId, service, characteristic, timeout: timeout); } - static Completer _connectionEventCompleter( - String deviceId, { - Duration? timeout, - }) { + static Completer _connectionEventCompleter(String deviceId, {Duration? timeout}) { timeout ??= const Duration(seconds: 60); StreamSubscription? connectionSubscription; Completer completer = Completer(); @@ -618,9 +565,7 @@ class UniversalBle { completer.completeError(ConnectionException(error)); } - connectionSubscription = _platform - .bleConnectionUpdateStreamController - .stream + connectionSubscription = _platform.bleConnectionUpdateStreamController.stream .where((e) => e.deviceId == deviceId) .listen( (e) { @@ -681,10 +626,7 @@ class UniversalBle { await connect(deviceId, timeout: timeout); } - List services = await discoverServices( - deviceId, - timeout: timeout, - ); + List services = await discoverServices(deviceId, timeout: timeout); UniversalLogger.logInfo("Discovered services: ${services.length}"); if (bleCommand == null) { @@ -737,10 +679,7 @@ class UniversalBle { for (BleService service in services) { if (BleUuidParser.compareStrings(service.uuid, bleCommand.service)) { for (BleCharacteristic char in service.characteristics) { - if (BleUuidParser.compareStrings( - char.uuid, - bleCommand.characteristic, - )) { + if (BleUuidParser.compareStrings(char.uuid, bleCommand.characteristic)) { characteristic = char; break; } @@ -756,16 +695,10 @@ class UniversalBle { bool? withoutResponse; if (characteristic.properties.contains(CharacteristicProperty.write)) { withoutResponse = false; - } else if (characteristic.properties.contains( - CharacteristicProperty.writeWithoutResponse, - )) { + } else if (characteristic.properties.contains(CharacteristicProperty.writeWithoutResponse)) { withoutResponse = true; - } else if (!characteristic.properties.contains( - CharacteristicProperty.read, - )) { - throw PairingException( - "BleCommand does not support read or write operation", - ); + } else if (!characteristic.properties.contains(CharacteristicProperty.read)) { + throw PairingException("BleCommand does not support read or write operation"); } Uint8List? value = bleCommand.writeValue; @@ -799,16 +732,14 @@ class UniversalBle { _bleCommandQueue.onQueueUpdate = onQueueUpdate; /// Get scan results. - static set onScanResult(OnScanResult? onScanResult) => - _platform.onScanResult = onScanResult; + static set onScanResult(OnScanResult? onScanResult) => _platform.onScanResult = onScanResult; /// Get connection state changes. static set onConnectionChange(OnConnectionChange? onConnectionChange) => _platform.onConnectionChange = onConnectionChange; /// Get characteristic value updates, after calling [subscribeNotifications] or [subscribeIndications] - static set onValueChange(OnValueChange? onValueChange) => - _platform.onValueChange = onValueChange; + static set onValueChange(OnValueChange? onValueChange) => _platform.onValueChange = onValueChange; /// Get pair state changes. static set onPairingStateChange(OnPairingStateChange pairingStateChange) => @@ -822,3 +753,43 @@ class UniversalBle { return UniversalBlePigeonChannel.instance; } } + +class UniversalBlePeripheral { + static Stream get connectionStream => + UniversalBle._platform.peripheralConnectionStream; + + static Stream get writeStream => + UniversalBle._platform.peripheralWriteStream; + + static Stream get subscriptionStream => + UniversalBle._platform.peripheralSubscriptionStream; + + static Future isSupported() => UniversalBle._platform.isPeripheralSupported(); + + static Future start(BlePeripheralConfig config) => + UniversalBle._platform.startPeripheral(config); + + static Future stop() => UniversalBle._platform.stopPeripheral(); + + static Future updateCharacteristicValue( + String service, + String characteristic, + Uint8List value, + ) => UniversalBle._platform.updatePeripheralCharacteristicValue( + BleUuidParser.string(service), + BleUuidParser.string(characteristic), + value, + ); + + static Future notify( + String service, + String characteristic, + Uint8List value, { + bool indicate = false, + }) => UniversalBle._platform.notifyPeripheralCharacteristic( + BleUuidParser.string(service), + BleUuidParser.string(characteristic), + value, + indicate: indicate, + ); +} diff --git a/lib/src/universal_ble_linux/universal_ble_linux.dart b/lib/src/universal_ble_linux/universal_ble_linux.dart index aa94693..0850432 100644 --- a/lib/src/universal_ble_linux/universal_ble_linux.dart +++ b/lib/src/universal_ble_linux/universal_ble_linux.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'package:bluez/bluez.dart'; +import 'package:dbus/dbus.dart'; import 'package:flutter/services.dart'; import 'package:universal_ble/src/models/model_exports.dart'; import 'package:universal_ble/src/utils/universal_ble_error_parser.dart'; @@ -27,8 +28,15 @@ class UniversalBleLinux extends UniversalBlePlatform { final Map _deviceUpdateStreamSubscriptions = {}; final Map _deviceAdvertisementSubscriptions = {}; - final Map _characteristicPropertiesSubscriptions = - {}; + final Map _characteristicPropertiesSubscriptions = {}; + + DBusClient? _peripheralBus; + _BlueZPeripheralApplication? _peripheralApplication; + final List _peripheralObjects = []; + final Map _peripheralCharacteristics = {}; + BlueZAdvertisement? _peripheralAdvertisement; + bool _peripheralApplicationRegistered = false; + DBusObjectPath? _peripheralAdapterPath; @override Future getBluetoothAvailabilityState() async { @@ -38,9 +46,7 @@ class UniversalBleLinux extends UniversalBlePlatform { if (adapter == null) { return AvailabilityState.unsupported; } - return adapter.powered - ? AvailabilityState.poweredOn - : AvailabilityState.poweredOff; + return adapter.powered ? AvailabilityState.poweredOn : AvailabilityState.poweredOff; } @override @@ -74,10 +80,7 @@ class UniversalBleLinux extends UniversalBlePlatform { } @override - Future startScan({ - ScanFilter? scanFilter, - PlatformConfig? platformConfig, - }) async { + Future startScan({ScanFilter? scanFilter, PlatformConfig? platformConfig}) async { await _ensureInitialized(); var adapter = _activeAdapter; if (adapter == null) { @@ -135,9 +138,7 @@ class UniversalBleLinux extends UniversalBlePlatform { Future getConnectionState(String deviceId) async { BlueZDevice? device = _getDeviceById(deviceId); bool connected = device?.connected ?? false; - return connected - ? BleConnectionState.connected - : BleConnectionState.disconnected; + return connected ? BleConnectionState.connected : BleConnectionState.disconnected; } @override @@ -165,19 +166,14 @@ class UniversalBleLinux extends UniversalBlePlatform { } @override - Future> discoverServices( - String deviceId, - bool withDescriptors, - ) async { + Future> discoverServices(String deviceId, bool withDescriptors) 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", - ); + UniversalLogger.logInfo("DiscoverServicesFailed: Device disconnected"); return true; } } @@ -219,9 +215,7 @@ class UniversalBleLinux extends UniversalBlePlatform { uuid: e.uuid.toString(), properties: properties, descriptors: withDescriptors - ? e.descriptors - .map((e) => BleDescriptor(e.uuid.toString())) - .toList() + ? e.descriptors.map((e) => BleDescriptor(e.uuid.toString())).toList() : [], ); }).toList(); @@ -281,35 +275,31 @@ 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 - .remove(characteristicKey) - ?.cancel(); + _characteristicPropertiesSubscriptions.remove(characteristicKey)?.cancel(); } } @@ -320,10 +310,7 @@ class UniversalBleLinux extends UniversalBlePlatform { String characteristic, { final Duration? timeout, }) async { - UniversalLogger.logDebug( - "READ -> $deviceId $service $characteristic", - withTimestamp: true, - ); + UniversalLogger.logDebug("READ -> $deviceId $service $characteristic", withTimestamp: true); try { final c = _getCharacteristic(deviceId, service, characteristic); final data = await c.readValue(); @@ -333,9 +320,7 @@ class UniversalBleLinux extends UniversalBlePlatform { "READ_FAILED <- $deviceId $service $characteristic ${e.message}", withTimestamp: true, ); - throw e.toUniversalBleException( - defaultCode: UniversalBleErrorCode.readFailed, - ); + throw e.toUniversalBleException(defaultCode: UniversalBleErrorCode.readFailed); } } @@ -354,24 +339,16 @@ class UniversalBleLinux extends UniversalBlePlatform { try { final c = _getCharacteristic(deviceId, service, characteristic); if (bleOutputProperty == BleOutputProperty.withResponse) { - await c.writeValue( - value, - type: BlueZGattCharacteristicWriteType.request, - ); + await c.writeValue(value, type: BlueZGattCharacteristicWriteType.request); } else { - await c.writeValue( - value, - type: BlueZGattCharacteristicWriteType.command, - ); + await c.writeValue(value, type: BlueZGattCharacteristicWriteType.command); } } on BlueZFailedException catch (e) { UniversalLogger.logError( "WRITE_FAILED <- $deviceId $service $characteristic ${e.message}", withTimestamp: true, ); - throw e.toUniversalBleException( - defaultCode: UniversalBleErrorCode.writeFailed, - ); + throw e.toUniversalBleException(defaultCode: UniversalBleErrorCode.writeFailed); } } @@ -398,16 +375,142 @@ class UniversalBleLinux extends UniversalBlePlatform { } @override - Future requestConnectionPriority( - String deviceId, - BleConnectionPriority priority, - ) { + Future requestConnectionPriority(String deviceId, BleConnectionPriority priority) { throw UniversalBleException( code: UniversalBleErrorCode.notSupported, message: "requestConnectionPriority is not supported on Linux platform", ); } + @override + Future isPeripheralSupported() async { + await _ensureInitialized(); + return _isLinuxPeripheralSupported(); + } + + @override + Future startPeripheral(BlePeripheralConfig config) async { + await _ensureInitialized(); + + final adapter = _activeAdapter; + if (adapter == null) { + throw UniversalBleException( + code: UniversalBleErrorCode.bluetoothNotAvailable, + message: 'Bluetooth adapter unavailable', + ); + } + if (!adapter.powered) { + throw UniversalBleException( + code: UniversalBleErrorCode.bluetoothNotEnabled, + message: 'Bluetooth not enabled', + ); + } + if (!await _isLinuxPeripheralSupported()) { + throw UniversalBleException( + code: UniversalBleErrorCode.notSupported, + message: 'BLE peripheral mode is not supported by this Linux adapter', + ); + } + + await _stopPeripheralInternal(); + + final bus = DBusClient.system(); + final application = _BlueZPeripheralApplication( + DBusObjectPath('/com/navideck/universal_ble/peripheral'), + ); + late final DBusObjectPath? adapterPath; + try { + adapterPath = await _resolveActiveAdapterPath(bus); + } catch (error) { + await bus.close(); + throw UniversalBleException( + code: UniversalBleErrorCode.bluetoothNotAvailable, + message: 'Bluetooth adapter unavailable', + details: error, + ); + } + if (adapterPath == null) { + await bus.close(); + throw UniversalBleException( + code: UniversalBleErrorCode.bluetoothNotAvailable, + message: 'Bluetooth adapter unavailable', + ); + } + + _peripheralBus = bus; + _peripheralApplication = application; + _peripheralAdapterPath = adapterPath; + _peripheralObjects.clear(); + _peripheralCharacteristics.clear(); + + try { + await bus.registerObject(application); + await _registerPeripheralObjects(bus, application.path, config); + + await _blueZAdapterObject(bus, adapterPath).callMethod( + 'org.bluez.GattManager1', + 'RegisterApplication', + [application.path, DBusDict.stringVariant({})], + replySignature: DBusSignature(''), + ); + _peripheralApplicationRegistered = true; + + _peripheralAdvertisement = await adapter.advertisingManager.registerAdvertisement( + type: BlueZAdvertisementType.peripheral, + serviceUuids: config.services.map((service) => service.uuid).toList(), + localName: config.advertisedName, + ); + } catch (error) { + await _stopPeripheralInternal(); + if (error is UniversalBleException) rethrow; + throw UniversalBleException( + code: UniversalBleErrorCode.failed, + message: 'Failed to start Linux BLE peripheral', + details: error, + ); + } + } + + @override + Future stopPeripheral() async { + await _stopPeripheralInternal(); + } + + @override + Future updatePeripheralCharacteristicValue( + String service, + String characteristic, + Uint8List value, + ) async { + final localCharacteristic = + _peripheralCharacteristics[_peripheralCharacteristicKey(service, characteristic)]; + if (localCharacteristic == null) { + throw UniversalBleException( + code: UniversalBleErrorCode.characteristicNotFound, + message: 'Unknown peripheral characteristic $characteristic', + ); + } + localCharacteristic.updateValue(value); + } + + @override + Future notifyPeripheralCharacteristic( + String service, + String characteristic, + Uint8List value, { + bool indicate = false, + }) async { + final localCharacteristic = + _peripheralCharacteristics[_peripheralCharacteristicKey(service, characteristic)]; + if (localCharacteristic == null) { + throw UniversalBleException( + code: UniversalBleErrorCode.characteristicNotFound, + message: 'Unknown peripheral characteristic $characteristic', + ); + } + await localCharacteristic.notifyValue(value); + } + @override Future readRssi(String deviceId) async { throw UniversalBleException( @@ -446,9 +549,7 @@ 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) { @@ -456,16 +557,12 @@ class UniversalBleLinux extends UniversalBlePlatform { .map((e) => e.uuid.toString()) .any((service) => withServices.contains(service)); } else { - UniversalLogger.logInfo( - 'Skipping: ${device.address}: Services not resolved yet.', - ); + UniversalLogger.logInfo('Skipping: ${device.address}: Services not resolved yet.'); return false; } }).toList(); } - return devices - .map((device) => device.toBleDevice(isSystemDevice: true)) - .toList(); + return devices.map((device) => device.toBleDevice(isSystemDevice: true)).toList(); } AvailabilityState get _availabilityState { @@ -511,9 +608,7 @@ class UniversalBleLinux extends UniversalBlePlatform { _activeAdapter ??= _client.adapters.first; - UniversalLogger.logInfo( - 'BleAdapter: ${_activeAdapter?.name} - ${_activeAdapter?.address}', - ); + UniversalLogger.logInfo('BleAdapter: ${_activeAdapter?.name} - ${_activeAdapter?.address}'); _activeAdapter?.propertiesChanged.listen((List properties) { // Handle pairing state change @@ -562,6 +657,174 @@ class UniversalBleLinux extends UniversalBlePlatform { } } + Future _isLinuxPeripheralSupported() async { + final adapter = _activeAdapter; + if (adapter == null || !adapter.powered) return false; + if (adapter.roles.isNotEmpty && !adapter.roles.contains('peripheral')) { + return false; + } + + final bus = DBusClient.system(); + try { + final adapterPath = await _resolveActiveAdapterPath(bus); + if (adapterPath == null) return false; + + final adapterObject = _blueZAdapterObject(bus, adapterPath); + final introspection = await adapterObject.introspect(); + final interfaces = introspection.interfaces.map((interface) => interface.name).toSet(); + return interfaces.contains('org.bluez.GattManager1') && + interfaces.contains('org.bluez.LEAdvertisingManager1'); + } catch (error) { + UniversalLogger.logInfo('Linux peripheral support check failed: $error'); + return false; + } finally { + await bus.close(); + } + } + + Future _resolveActiveAdapterPath(DBusClient bus) async { + final adapter = _activeAdapter; + if (adapter == null) return null; + + final root = DBusRemoteObjectManager(bus, name: 'org.bluez', path: DBusObjectPath('/')); + final managedObjects = await root.getManagedObjects(); + final adapterAddress = adapter.address.toUpperCase(); + + for (final entry in managedObjects.entries) { + final adapterProperties = entry.value['org.bluez.Adapter1']; + if (adapterProperties == null) continue; + try { + final candidateAddress = adapterProperties['Address']?.asString().toUpperCase(); + if (candidateAddress == adapterAddress) return entry.key; + } catch (_) {} + } + + final adapterName = adapter.name; + if (RegExp(r'^hci\d+$').hasMatch(adapterName)) { + return DBusObjectPath('/org/bluez/$adapterName'); + } + return null; + } + + DBusRemoteObject _blueZAdapterObject(DBusClient bus, DBusObjectPath adapterPath) { + return DBusRemoteObject(bus, name: 'org.bluez', path: adapterPath); + } + + Future _registerPeripheralObjects( + DBusClient bus, + DBusObjectPath applicationPath, + BlePeripheralConfig config, + ) async { + for (var serviceIndex = 0; serviceIndex < config.services.length; serviceIndex++) { + final serviceConfig = config.services[serviceIndex]; + final servicePath = DBusObjectPath('${applicationPath.value}/service$serviceIndex'); + final service = _BlueZPeripheralService(servicePath, uuid: serviceConfig.uuid); + await bus.registerObject(service); + _peripheralObjects.add(service); + + for ( + var characteristicIndex = 0; + characteristicIndex < serviceConfig.characteristics.length; + characteristicIndex++ + ) { + final characteristicConfig = serviceConfig.characteristics[characteristicIndex]; + final characteristicPath = DBusObjectPath('${servicePath.value}/char$characteristicIndex'); + final characteristic = _BlueZPeripheralCharacteristic( + characteristicPath, + servicePath: servicePath, + serviceUuid: serviceConfig.uuid, + uuid: characteristicConfig.uuid, + properties: characteristicConfig.properties, + initialValue: characteristicConfig.initialValue, + onWrite: updatePeripheralWrite, + onSubscription: updatePeripheralSubscription, + ); + await bus.registerObject(characteristic); + _peripheralObjects.add(characteristic); + _peripheralCharacteristics[_peripheralCharacteristicKey( + serviceConfig.uuid, + characteristicConfig.uuid, + )] = + characteristic; + + for ( + var descriptorIndex = 0; + descriptorIndex < characteristicConfig.descriptors.length; + descriptorIndex++ + ) { + final descriptorConfig = characteristicConfig.descriptors[descriptorIndex]; + final descriptorPath = DBusObjectPath('${characteristicPath.value}/desc$descriptorIndex'); + final descriptor = _BlueZPeripheralDescriptor( + descriptorPath, + characteristicPath: characteristicPath, + uuid: descriptorConfig.uuid, + permissions: descriptorConfig.permissions, + initialValue: descriptorConfig.initialValue, + ); + await bus.registerObject(descriptor); + _peripheralObjects.add(descriptor); + } + } + } + } + + Future _stopPeripheralInternal() async { + final adapter = _activeAdapter; + final advertisement = _peripheralAdvertisement; + _peripheralAdvertisement = null; + if (advertisement != null && adapter != null) { + try { + await adapter.advertisingManager.unregisterAdvertisement(advertisement); + } catch (error) { + UniversalLogger.logInfo('Linux peripheral advertisement cleanup failed: $error'); + } + } + + final bus = _peripheralBus; + final application = _peripheralApplication; + final adapterPath = _peripheralAdapterPath; + if (bus != null && + application != null && + adapterPath != null && + _peripheralApplicationRegistered) { + try { + await _blueZAdapterObject(bus, adapterPath).callMethod( + 'org.bluez.GattManager1', + 'UnregisterApplication', + [application.path], + replySignature: DBusSignature(''), + ); + } catch (error) { + UniversalLogger.logInfo('Linux peripheral GATT cleanup failed: $error'); + } + } + _peripheralApplicationRegistered = false; + + if (bus != null) { + for (final object in _peripheralObjects.reversed) { + try { + await bus.unregisterObject(object); + } catch (_) {} + } + _peripheralObjects.clear(); + + if (application != null) { + try { + await bus.unregisterObject(application); + } catch (_) {} + } + await bus.close(); + } + + _peripheralBus = null; + _peripheralApplication = null; + _peripheralAdapterPath = null; + _peripheralCharacteristics.clear(); + } + + String _peripheralCharacteristicKey(String service, String characteristic) => + '${BleUuidParser.string(service)}|${BleUuidParser.string(characteristic)}'; + void _onDeviceAdd(BlueZDevice device) { BleDevice bleDevice = device.toBleDevice(); if (!_bleFilter.shouldAcceptDevice(bleDevice)) { @@ -577,8 +840,7 @@ class UniversalBleLinux extends UniversalBlePlatform { _devices[device.address] = device; // Setup advertisements Listener - _deviceAdvertisementSubscriptions[device.address] ??= device - .propertiesChanged + _deviceAdvertisementSubscriptions[device.address] ??= device.propertiesChanged .where((e) { return e.contains(BluezProperty.rssi) || e.contains(BluezProperty.manufacturerData) || @@ -592,8 +854,9 @@ class UniversalBleLinux extends UniversalBlePlatform { }); // 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 @@ -646,6 +909,440 @@ class UniversalBleLinux extends UniversalBlePlatform { } } +class _BlueZPeripheralApplication extends DBusObject { + _BlueZPeripheralApplication(super.path) : super(isObjectManager: true); +} + +class _BlueZPeripheralService extends DBusObject { + _BlueZPeripheralService(super.path, {required this.uuid}); + + static const _interfaceName = 'org.bluez.GattService1'; + + final String uuid; + + Map _properties() => {'UUID': DBusString(uuid), 'Primary': DBusBoolean(true)}; + + @override + Map> get interfacesAndProperties => { + _interfaceName: _properties(), + }; + + @override + Future getProperty(String interface, String name) async { + if (interface != _interfaceName) return DBusMethodErrorResponse.unknownInterface(); + final value = _properties()[name]; + if (value == null) return DBusMethodErrorResponse.unknownProperty(); + return DBusGetPropertyResponse(value); + } + + @override + Future getAllProperties(String interface) async { + return DBusGetAllPropertiesResponse( + interface == _interfaceName ? _properties() : {}, + ); + } + + @override + Future setProperty(String interface, String name, DBusValue value) async { + if (interface != _interfaceName) return DBusMethodErrorResponse.unknownInterface(); + return _properties().containsKey(name) + ? DBusMethodErrorResponse.propertyReadOnly() + : DBusMethodErrorResponse.unknownProperty(); + } + + @override + List introspect() => [ + DBusIntrospectInterface( + _interfaceName, + properties: [ + DBusIntrospectProperty('UUID', DBusSignature('s'), access: DBusPropertyAccess.read), + DBusIntrospectProperty('Primary', DBusSignature('b'), access: DBusPropertyAccess.read), + ], + ), + ]; +} + +class _BlueZPeripheralCharacteristic extends DBusObject { + _BlueZPeripheralCharacteristic( + super.path, { + required this.servicePath, + required this.serviceUuid, + required this.uuid, + required this.properties, + required Uint8List? initialValue, + required this.onWrite, + required this.onSubscription, + }) : _value = Uint8List.fromList(initialValue ?? Uint8List(0)); + + static const _interfaceName = 'org.bluez.GattCharacteristic1'; + + final DBusObjectPath servicePath; + final String serviceUuid; + final String uuid; + final List properties; + final void Function(BlePeripheralWriteEvent event) onWrite; + final void Function(BlePeripheralSubscriptionEvent event) onSubscription; + + Uint8List _value; + bool _notifying = false; + + Map _properties() => { + 'UUID': DBusString(uuid), + 'Service': servicePath, + 'Value': DBusArray.byte(_value), + 'Notifying': DBusBoolean(_notifying), + 'Flags': DBusArray.string(properties.map((property) => property.bluezFlag)), + }; + + @override + Map> get interfacesAndProperties => { + _interfaceName: _properties(), + }; + + void updateValue(Uint8List value) { + _value = Uint8List.fromList(value); + } + + Future notifyValue(Uint8List value) async { + updateValue(value); + await emitPropertiesChanged( + _interfaceName, + changedProperties: {'Value': DBusArray.byte(_value)}, + ); + } + + @override + Future handleMethodCall(DBusMethodCall methodCall) async { + if (methodCall.interface != _interfaceName) { + return DBusMethodErrorResponse.unknownInterface(); + } + + switch (methodCall.name) { + case 'ReadValue': + if (methodCall.values.length != 1) return DBusMethodErrorResponse.invalidArgs(); + final options = methodCall.values[0].asStringVariantDict(); + final offset = _offsetFromOptions(options); + if (offset > _value.length) { + return DBusMethodErrorResponse('org.bluez.Error.InvalidOffset', [ + DBusString('Invalid offset'), + ]); + } + return DBusMethodSuccessResponse([DBusArray.byte(_value.sublist(offset))]); + case 'WriteValue': + if (methodCall.values.length != 2) return DBusMethodErrorResponse.invalidArgs(); + final incoming = Uint8List.fromList(methodCall.values[0].asByteArray().toList()); + final options = methodCall.values[1].asStringVariantDict(); + final offset = _offsetFromOptions(options); + if (offset > _value.length) { + return DBusMethodErrorResponse('org.bluez.Error.InvalidOffset', [ + DBusString('Invalid offset'), + ]); + } + _writeValue(offset, incoming); + onWrite( + BlePeripheralWriteEvent( + deviceId: _deviceIdFromOptions(options, methodCall), + service: serviceUuid, + characteristic: uuid, + value: incoming, + ), + ); + return DBusMethodSuccessResponse(); + case 'StartNotify': + if (methodCall.values.isNotEmpty) return DBusMethodErrorResponse.invalidArgs(); + _notifying = true; + await emitPropertiesChanged( + _interfaceName, + changedProperties: {'Notifying': DBusBoolean(true)}, + ); + onSubscription( + BlePeripheralSubscriptionEvent( + deviceId: methodCall.sender ?? '', + service: serviceUuid, + characteristic: uuid, + subscribed: true, + ), + ); + return DBusMethodSuccessResponse(); + case 'StopNotify': + if (methodCall.values.isNotEmpty) return DBusMethodErrorResponse.invalidArgs(); + _notifying = false; + await emitPropertiesChanged( + _interfaceName, + changedProperties: {'Notifying': DBusBoolean(false)}, + ); + onSubscription( + BlePeripheralSubscriptionEvent( + deviceId: methodCall.sender ?? '', + service: serviceUuid, + characteristic: uuid, + subscribed: false, + ), + ); + return DBusMethodSuccessResponse(); + default: + return DBusMethodErrorResponse.unknownMethod(); + } + } + + @override + Future getProperty(String interface, String name) async { + if (interface != _interfaceName) return DBusMethodErrorResponse.unknownInterface(); + final value = _properties()[name]; + if (value == null) return DBusMethodErrorResponse.unknownProperty(); + return DBusGetPropertyResponse(value); + } + + @override + Future getAllProperties(String interface) async { + return DBusGetAllPropertiesResponse( + interface == _interfaceName ? _properties() : {}, + ); + } + + @override + Future setProperty(String interface, String name, DBusValue value) async { + if (interface != _interfaceName) return DBusMethodErrorResponse.unknownInterface(); + return _properties().containsKey(name) + ? DBusMethodErrorResponse.propertyReadOnly() + : DBusMethodErrorResponse.unknownProperty(); + } + + @override + List introspect() => [ + DBusIntrospectInterface( + _interfaceName, + methods: [ + DBusIntrospectMethod( + 'ReadValue', + args: [ + DBusIntrospectArgument( + DBusSignature('a{sv}'), + DBusArgumentDirection.in_, + name: 'options', + ), + DBusIntrospectArgument(DBusSignature('ay'), DBusArgumentDirection.out, name: 'value'), + ], + ), + DBusIntrospectMethod( + 'WriteValue', + args: [ + DBusIntrospectArgument(DBusSignature('ay'), DBusArgumentDirection.in_, name: 'value'), + DBusIntrospectArgument( + DBusSignature('a{sv}'), + DBusArgumentDirection.in_, + name: 'options', + ), + ], + ), + DBusIntrospectMethod('StartNotify'), + DBusIntrospectMethod('StopNotify'), + ], + properties: [ + DBusIntrospectProperty('UUID', DBusSignature('s'), access: DBusPropertyAccess.read), + DBusIntrospectProperty('Service', DBusSignature('o'), access: DBusPropertyAccess.read), + DBusIntrospectProperty('Value', DBusSignature('ay'), access: DBusPropertyAccess.read), + DBusIntrospectProperty('Notifying', DBusSignature('b'), access: DBusPropertyAccess.read), + DBusIntrospectProperty('Flags', DBusSignature('as'), access: DBusPropertyAccess.read), + ], + ), + ]; + + void _writeValue(int offset, Uint8List incoming) { + if (offset == 0) { + _value = Uint8List.fromList(incoming); + return; + } + + final nextLength = offset + incoming.length > _value.length + ? offset + incoming.length + : _value.length; + final nextValue = Uint8List(nextLength); + nextValue.setRange(0, _value.length, _value); + nextValue.setRange(offset, offset + incoming.length, incoming); + _value = nextValue; + } +} + +class _BlueZPeripheralDescriptor extends DBusObject { + _BlueZPeripheralDescriptor( + super.path, { + required this.characteristicPath, + required this.uuid, + required this.permissions, + required Uint8List? initialValue, + }) : _value = Uint8List.fromList(initialValue ?? Uint8List(0)); + + static const _interfaceName = 'org.bluez.GattDescriptor1'; + + final DBusObjectPath characteristicPath; + final String uuid; + final List permissions; + Uint8List _value; + + Map _properties() => { + 'UUID': DBusString(uuid), + 'Characteristic': characteristicPath, + 'Value': DBusArray.byte(_value), + 'Flags': DBusArray.string(permissions.map((permission) => permission.bluezFlag)), + }; + + @override + Map> get interfacesAndProperties => { + _interfaceName: _properties(), + }; + + @override + Future handleMethodCall(DBusMethodCall methodCall) async { + if (methodCall.interface != _interfaceName) { + return DBusMethodErrorResponse.unknownInterface(); + } + + switch (methodCall.name) { + case 'ReadValue': + if (methodCall.values.length != 1) return DBusMethodErrorResponse.invalidArgs(); + final offset = _offsetFromOptions(methodCall.values[0].asStringVariantDict()); + if (offset > _value.length) { + return DBusMethodErrorResponse('org.bluez.Error.InvalidOffset', [ + DBusString('Invalid offset'), + ]); + } + return DBusMethodSuccessResponse([DBusArray.byte(_value.sublist(offset))]); + case 'WriteValue': + if (methodCall.values.length != 2) return DBusMethodErrorResponse.invalidArgs(); + final incoming = Uint8List.fromList(methodCall.values[0].asByteArray().toList()); + final offset = _offsetFromOptions(methodCall.values[1].asStringVariantDict()); + if (offset > _value.length) { + return DBusMethodErrorResponse('org.bluez.Error.InvalidOffset', [ + DBusString('Invalid offset'), + ]); + } + if (offset == 0) { + _value = incoming; + } else { + final nextLength = offset + incoming.length > _value.length + ? offset + incoming.length + : _value.length; + final nextValue = Uint8List(nextLength); + nextValue.setRange(0, _value.length, _value); + nextValue.setRange(offset, offset + incoming.length, incoming); + _value = nextValue; + } + return DBusMethodSuccessResponse(); + default: + return DBusMethodErrorResponse.unknownMethod(); + } + } + + @override + Future getProperty(String interface, String name) async { + if (interface != _interfaceName) return DBusMethodErrorResponse.unknownInterface(); + final value = _properties()[name]; + if (value == null) return DBusMethodErrorResponse.unknownProperty(); + return DBusGetPropertyResponse(value); + } + + @override + Future getAllProperties(String interface) async { + return DBusGetAllPropertiesResponse( + interface == _interfaceName ? _properties() : {}, + ); + } + + @override + Future setProperty(String interface, String name, DBusValue value) async { + if (interface != _interfaceName) return DBusMethodErrorResponse.unknownInterface(); + return _properties().containsKey(name) + ? DBusMethodErrorResponse.propertyReadOnly() + : DBusMethodErrorResponse.unknownProperty(); + } + + @override + List introspect() => [ + DBusIntrospectInterface( + _interfaceName, + methods: [ + DBusIntrospectMethod( + 'ReadValue', + args: [ + DBusIntrospectArgument( + DBusSignature('a{sv}'), + DBusArgumentDirection.in_, + name: 'options', + ), + DBusIntrospectArgument(DBusSignature('ay'), DBusArgumentDirection.out, name: 'value'), + ], + ), + DBusIntrospectMethod( + 'WriteValue', + args: [ + DBusIntrospectArgument(DBusSignature('ay'), DBusArgumentDirection.in_, name: 'value'), + DBusIntrospectArgument( + DBusSignature('a{sv}'), + DBusArgumentDirection.in_, + name: 'options', + ), + ], + ), + ], + properties: [ + DBusIntrospectProperty('UUID', DBusSignature('s'), access: DBusPropertyAccess.read), + DBusIntrospectProperty( + 'Characteristic', + DBusSignature('o'), + access: DBusPropertyAccess.read, + ), + DBusIntrospectProperty('Value', DBusSignature('ay'), access: DBusPropertyAccess.read), + DBusIntrospectProperty('Flags', DBusSignature('as'), access: DBusPropertyAccess.read), + ], + ), + ]; +} + +int _offsetFromOptions(Map options) { + final offset = options['offset']; + if (offset == null) return 0; + try { + return offset.asUint16(); + } catch (_) { + return 0; + } +} + +String _deviceIdFromOptions(Map options, DBusMethodCall methodCall) { + try { + final devicePath = options['device']?.asObjectPath().value; + if (devicePath != null) { + final markerIndex = devicePath.lastIndexOf('/dev_'); + if (markerIndex >= 0) { + return devicePath.substring(markerIndex + 5).replaceAll('_', ':').toUpperCase(); + } + return devicePath; + } + } catch (_) {} + return methodCall.sender ?? ''; +} + +extension on CharacteristicProperty { + String get bluezFlag => switch (this) { + CharacteristicProperty.broadcast => 'broadcast', + CharacteristicProperty.read => 'read', + CharacteristicProperty.writeWithoutResponse => 'write-without-response', + CharacteristicProperty.write => 'write', + CharacteristicProperty.notify => 'notify', + CharacteristicProperty.indicate => 'indicate', + CharacteristicProperty.authenticatedSignedWrites => 'authenticated-signed-writes', + CharacteristicProperty.extendedProperties => 'extended-properties', + }; +} + +extension on BlePeripheralCharacteristicPermission { + String get bluezFlag => switch (this) { + BlePeripheralCharacteristicPermission.read => 'read', + BlePeripheralCharacteristicPermission.write => 'write', + }; +} + class BluezProperty { static const String rssi = 'RSSI'; static const String connected = 'Connected'; @@ -679,27 +1376,20 @@ extension on BlueZGattCharacteristicFlag { BlueZGattCharacteristicFlag.indicate => CharacteristicProperty.indicate, BlueZGattCharacteristicFlag.authenticatedSignedWrites => CharacteristicProperty.authenticatedSignedWrites, - BlueZGattCharacteristicFlag.extendedProperties => - CharacteristicProperty.extendedProperties, + BlueZGattCharacteristicFlag.extendedProperties => CharacteristicProperty.extendedProperties, _ => null, }; } } extension on BlueZFailedException { - UniversalBleException toUniversalBleException({ - required UniversalBleErrorCode defaultCode, - }) { + UniversalBleException toUniversalBleException({required UniversalBleErrorCode defaultCode}) { // Map BlueZ error code to UniversalBleErrorCode UniversalBleErrorCode code = UniversalBleErrorParser.getCode(errorCode); if (code == UniversalBleErrorCode.unknownError) { code = defaultCode; } - throw UniversalBleException( - code: code, - message: message, - details: errorCode, - ); + throw UniversalBleException(code: code, message: message, details: errorCode); } /// Extract error code from message and parse into decimal diff --git a/lib/src/universal_ble_pigeon/universal_ble.g.dart b/lib/src/universal_ble_pigeon/universal_ble.g.dart index 9e023e0..22768b9 100644 --- a/lib/src/universal_ble_pigeon/universal_ble.g.dart +++ b/lib/src/universal_ble_pigeon/universal_ble.g.dart @@ -1,18 +1,37 @@ -// Autogenerated from Pigeon (v26.1.4), do not edit directly. +// Autogenerated from Pigeon (v26.3.4), 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({ @@ -30,6 +49,15 @@ 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( @@ -37,16 +65,52 @@ bool _deepEquals(Object? a, Object? b) { ); } 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; } +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 @@ -187,12 +251,19 @@ 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 { @@ -228,12 +299,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 { @@ -261,9 +333,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,12 +348,14 @@ 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 { @@ -312,12 +385,270 @@ 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()]); +} + +class UniversalBlePeripheralConfig { + UniversalBlePeripheralConfig({ + required this.advertisedName, + required this.services, + }); + + String advertisedName; + + List services; + + List _toList() { + return [advertisedName, services]; + } + + Object encode() { + return _toList(); + } + + static UniversalBlePeripheralConfig decode(Object result) { + result as List; + return UniversalBlePeripheralConfig( + advertisedName: result[0]! as String, + services: (result[1]! as List) + .cast(), + ); + } + + @override + // ignore: avoid_equals_and_hash_code_on_mutable_classes + bool operator ==(Object other) { + if (other is! UniversalBlePeripheralConfig || + other.runtimeType != runtimeType) { + return false; + } + if (identical(this, other)) { + return true; + } + return _deepEquals(advertisedName, other.advertisedName) && + _deepEquals(services, other.services); + } + + @override + // ignore: avoid_equals_and_hash_code_on_mutable_classes + int get hashCode => _deepHash([runtimeType, ..._toList()]); +} + +class UniversalBlePeripheralService { + UniversalBlePeripheralService({ + required this.uuid, + required this.characteristics, + }); + + String uuid; + + List characteristics; + + List _toList() { + return [uuid, characteristics]; + } + + Object encode() { + return _toList(); + } + + static UniversalBlePeripheralService decode(Object result) { + result as List; + return UniversalBlePeripheralService( + uuid: result[0]! as String, + characteristics: (result[1]! as List) + .cast(), + ); + } + + @override + // ignore: avoid_equals_and_hash_code_on_mutable_classes + bool operator ==(Object other) { + if (other is! UniversalBlePeripheralService || + other.runtimeType != runtimeType) { + return false; + } + if (identical(this, other)) { + return true; + } + return _deepEquals(uuid, other.uuid) && + _deepEquals(characteristics, other.characteristics); + } + + @override + // ignore: avoid_equals_and_hash_code_on_mutable_classes + int get hashCode => _deepHash([runtimeType, ..._toList()]); +} + +class UniversalBlePeripheralCharacteristic { + UniversalBlePeripheralCharacteristic({ + required this.uuid, + required this.properties, + required this.permissions, + required this.descriptors, + this.initialValue, + }); + + String uuid; + + List properties; + + List permissions; + + List descriptors; + + Uint8List? initialValue; + + List _toList() { + return [uuid, properties, permissions, descriptors, initialValue]; + } + + Object encode() { + return _toList(); + } + + static UniversalBlePeripheralCharacteristic decode(Object result) { + result as List; + return UniversalBlePeripheralCharacteristic( + uuid: result[0]! as String, + properties: (result[1]! as List).cast(), + permissions: (result[2]! as List).cast(), + descriptors: (result[3]! as List) + .cast(), + initialValue: result[4] as Uint8List?, + ); + } + + @override + // ignore: avoid_equals_and_hash_code_on_mutable_classes + bool operator ==(Object other) { + if (other is! UniversalBlePeripheralCharacteristic || + 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(initialValue, other.initialValue); + } + + @override + // ignore: avoid_equals_and_hash_code_on_mutable_classes + int get hashCode => _deepHash([runtimeType, ..._toList()]); +} + +class UniversalBlePeripheralDescriptor { + UniversalBlePeripheralDescriptor({ + required this.uuid, + required this.permissions, + this.initialValue, + }); + + String uuid; + + List permissions; + + Uint8List? initialValue; + + List _toList() { + return [uuid, permissions, initialValue]; + } + + Object encode() { + return _toList(); + } + + static UniversalBlePeripheralDescriptor decode(Object result) { + result as List; + return UniversalBlePeripheralDescriptor( + uuid: result[0]! as String, + permissions: (result[1]! as List).cast(), + initialValue: result[2] as Uint8List?, + ); + } + + @override + // ignore: avoid_equals_and_hash_code_on_mutable_classes + bool operator ==(Object other) { + if (other is! UniversalBlePeripheralDescriptor || + other.runtimeType != runtimeType) { + return false; + } + if (identical(this, other)) { + return true; + } + return _deepEquals(uuid, other.uuid) && + _deepEquals(permissions, other.permissions) && + _deepEquals(initialValue, other.initialValue); + } + + @override + // ignore: avoid_equals_and_hash_code_on_mutable_classes + int get hashCode => _deepHash([runtimeType, ..._toList()]); +} + +class UniversalBlePeripheralWriteEvent { + UniversalBlePeripheralWriteEvent({ + required this.deviceId, + required this.service, + required this.characteristic, + required this.value, + }); + + String deviceId; + + String service; + + String characteristic; + + Uint8List value; + + List _toList() { + return [deviceId, service, characteristic, value]; + } + + Object encode() { + return _toList(); + } + + static UniversalBlePeripheralWriteEvent decode(Object result) { + result as List; + return UniversalBlePeripheralWriteEvent( + deviceId: result[0]! as String, + service: result[1]! as String, + characteristic: result[2]! as String, + value: result[3]! as Uint8List, + ); + } + + @override + // ignore: avoid_equals_and_hash_code_on_mutable_classes + bool operator ==(Object other) { + if (other is! UniversalBlePeripheralWriteEvent || + other.runtimeType != runtimeType) { + return false; + } + if (identical(this, other)) { + return true; + } + return _deepEquals(deviceId, other.deviceId) && + _deepEquals(service, other.service) && + _deepEquals(characteristic, other.characteristic) && + _deepEquals(value, other.value); + } + + @override + // ignore: avoid_equals_and_hash_code_on_mutable_classes + int get hashCode => _deepHash([runtimeType, ..._toList()]); } /// Android options to scan devices @@ -365,12 +696,17 @@ 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 { @@ -400,12 +736,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 @@ -433,9 +769,9 @@ 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?)! + withServices: (result[0]! as List).cast(), + withNamePrefix: (result[1]! as List).cast(), + withManufacturerData: (result[2]! as List) .cast(), ); } @@ -449,12 +785,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 { @@ -497,12 +835,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 { @@ -541,12 +881,13 @@ 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 { @@ -577,21 +918,36 @@ class _PigeonCodec extends StandardMessageCodec { } else if (value is UniversalBleDescriptor) { buffer.putUint8(135); writeValue(buffer, value.encode()); - } else if (value is AndroidOptions) { + } else if (value is UniversalBlePeripheralConfig) { buffer.putUint8(136); writeValue(buffer, value.encode()); - } else if (value is UniversalScanConfig) { + } else if (value is UniversalBlePeripheralService) { buffer.putUint8(137); writeValue(buffer, value.encode()); - } else if (value is UniversalScanFilter) { + } else if (value is UniversalBlePeripheralCharacteristic) { buffer.putUint8(138); writeValue(buffer, value.encode()); - } else if (value is UniversalManufacturerDataFilter) { + } else if (value is UniversalBlePeripheralDescriptor) { buffer.putUint8(139); writeValue(buffer, value.encode()); - } else if (value is UniversalManufacturerData) { + } else if (value is UniversalBlePeripheralWriteEvent) { buffer.putUint8(140); writeValue(buffer, value.encode()); + } else if (value is AndroidOptions) { + buffer.putUint8(141); + writeValue(buffer, value.encode()); + } else if (value is UniversalScanConfig) { + buffer.putUint8(142); + writeValue(buffer, value.encode()); + } else if (value is UniversalScanFilter) { + buffer.putUint8(143); + writeValue(buffer, value.encode()); + } else if (value is UniversalManufacturerDataFilter) { + buffer.putUint8(144); + writeValue(buffer, value.encode()); + } else if (value is UniversalManufacturerData) { + buffer.putUint8(145); + writeValue(buffer, value.encode()); } else { super.writeValue(buffer, value); } @@ -618,14 +974,24 @@ class _PigeonCodec extends StandardMessageCodec { case 135: return UniversalBleDescriptor.decode(readValue(buffer)!); case 136: - return AndroidOptions.decode(readValue(buffer)!); + return UniversalBlePeripheralConfig.decode(readValue(buffer)!); case 137: - return UniversalScanConfig.decode(readValue(buffer)!); + return UniversalBlePeripheralService.decode(readValue(buffer)!); case 138: - return UniversalScanFilter.decode(readValue(buffer)!); + return UniversalBlePeripheralCharacteristic.decode(readValue(buffer)!); case 139: - return UniversalManufacturerDataFilter.decode(readValue(buffer)!); + return UniversalBlePeripheralDescriptor.decode(readValue(buffer)!); case 140: + return UniversalBlePeripheralWriteEvent.decode(readValue(buffer)!); + case 141: + return AndroidOptions.decode(readValue(buffer)!); + case 142: + return UniversalScanConfig.decode(readValue(buffer)!); + case 143: + return UniversalScanFilter.decode(readValue(buffer)!); + case 144: + return UniversalManufacturerDataFilter.decode(readValue(buffer)!); + case 145: return UniversalManufacturerData.decode(readValue(buffer)!); default: return super.readValueOfType(type, buffer); @@ -661,25 +1027,19 @@ 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 int?)!; - } + + final Object? pigeonVar_replyValue = _extractReplyValueOrThrow( + pigeonVar_replyList, + pigeonVar_channelName, + isNullValid: false, + ); + return pigeonVar_replyValue! as int; } - Future hasPermissions(bool withAndroidFineLocation) async { + Future hasPermissions( + bool withAndroidFineLocation, + bool withAndroidBluetoothAdvertise, + ) async { final pigeonVar_channelName = 'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.hasPermissions$pigeonVar_messageChannelSuffix'; final pigeonVar_channel = BasicMessageChannel( @@ -688,28 +1048,22 @@ class UniversalBlePlatformChannel { binaryMessenger: pigeonVar_binaryMessenger, ); final Future pigeonVar_sendFuture = pigeonVar_channel.send( - [withAndroidFineLocation], + [withAndroidFineLocation, withAndroidBluetoothAdvertise], ); 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?)!; - } + + final Object? pigeonVar_replyValue = _extractReplyValueOrThrow( + pigeonVar_replyList, + pigeonVar_channelName, + isNullValid: false, + ); + return pigeonVar_replyValue! as bool; } - Future requestPermissions(bool withAndroidFineLocation) async { + Future requestPermissions( + bool withAndroidFineLocation, + bool withAndroidBluetoothAdvertise, + ) async { final pigeonVar_channelName = 'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.requestPermissions$pigeonVar_messageChannelSuffix'; final pigeonVar_channel = BasicMessageChannel( @@ -718,20 +1072,15 @@ class UniversalBlePlatformChannel { binaryMessenger: pigeonVar_binaryMessenger, ); final Future pigeonVar_sendFuture = pigeonVar_channel.send( - [withAndroidFineLocation], + [withAndroidFineLocation, withAndroidBluetoothAdvertise], ); 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; - } + + _extractReplyValueOrThrow( + pigeonVar_replyList, + pigeonVar_channelName, + isNullValid: true, + ); } Future enableBluetooth() async { @@ -744,22 +1093,13 @@ 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?)!; - } + + final Object? pigeonVar_replyValue = _extractReplyValueOrThrow( + pigeonVar_replyList, + pigeonVar_channelName, + isNullValid: false, + ); + return pigeonVar_replyValue! as bool; } Future disableBluetooth() async { @@ -772,22 +1112,13 @@ 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?)!; - } + + final Object? pigeonVar_replyValue = _extractReplyValueOrThrow( + pigeonVar_replyList, + pigeonVar_channelName, + isNullValid: false, + ); + return pigeonVar_replyValue! as bool; } Future startScan( @@ -805,17 +1136,12 @@ class UniversalBlePlatformChannel { [filter, config], ); 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; - } + + _extractReplyValueOrThrow( + pigeonVar_replyList, + pigeonVar_channelName, + isNullValid: true, + ); } Future stopScan() async { @@ -828,17 +1154,12 @@ 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; - } + + _extractReplyValueOrThrow( + pigeonVar_replyList, + pigeonVar_channelName, + isNullValid: true, + ); } Future isScanning() async { @@ -851,22 +1172,13 @@ 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?)!; - } + + final Object? pigeonVar_replyValue = _extractReplyValueOrThrow( + pigeonVar_replyList, + pigeonVar_channelName, + isNullValid: false, + ); + return pigeonVar_replyValue! as bool; } Future connect(String deviceId, {bool? autoConnect}) async { @@ -881,17 +1193,12 @@ class UniversalBlePlatformChannel { [deviceId, autoConnect], ); 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; - } + + _extractReplyValueOrThrow( + pigeonVar_replyList, + pigeonVar_channelName, + isNullValid: true, + ); } Future disconnect(String deviceId) async { @@ -906,17 +1213,12 @@ class UniversalBlePlatformChannel { [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; - } + + _extractReplyValueOrThrow( + pigeonVar_replyList, + pigeonVar_channelName, + isNullValid: true, + ); } Future setNotifiable( @@ -936,17 +1238,12 @@ class UniversalBlePlatformChannel { [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; - } + + _extractReplyValueOrThrow( + pigeonVar_replyList, + pigeonVar_channelName, + isNullValid: true, + ); } Future> discoverServices( @@ -964,23 +1261,13 @@ class UniversalBlePlatformChannel { [deviceId, withDescriptors], ); 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(); - } + + final Object? pigeonVar_replyValue = _extractReplyValueOrThrow( + pigeonVar_replyList, + pigeonVar_channelName, + isNullValid: false, + ); + return (pigeonVar_replyValue! as List).cast(); } Future readValue( @@ -999,22 +1286,13 @@ class UniversalBlePlatformChannel { [deviceId, service, characteristic], ); 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?)!; - } + + final Object? pigeonVar_replyValue = _extractReplyValueOrThrow( + pigeonVar_replyList, + pigeonVar_channelName, + isNullValid: false, + ); + return pigeonVar_replyValue! as Uint8List; } Future requestMtu(String deviceId, int expectedMtu) async { @@ -1029,22 +1307,13 @@ class UniversalBlePlatformChannel { [deviceId, expectedMtu], ); 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?)!; - } + + final Object? pigeonVar_replyValue = _extractReplyValueOrThrow( + pigeonVar_replyList, + pigeonVar_channelName, + isNullValid: false, + ); + return pigeonVar_replyValue! as int; } Future writeValue( @@ -1065,17 +1334,12 @@ class UniversalBlePlatformChannel { [deviceId, service, characteristic, value, bleOutputProperty], ); 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; - } + + _extractReplyValueOrThrow( + pigeonVar_replyList, + pigeonVar_channelName, + isNullValid: true, + ); } Future isPaired(String deviceId) async { @@ -1090,22 +1354,13 @@ class UniversalBlePlatformChannel { [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?)!; - } + + final Object? pigeonVar_replyValue = _extractReplyValueOrThrow( + pigeonVar_replyList, + pigeonVar_channelName, + isNullValid: false, + ); + return pigeonVar_replyValue! as bool; } Future pair(String deviceId) async { @@ -1120,22 +1375,13 @@ class UniversalBlePlatformChannel { [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?)!; - } + + final Object? pigeonVar_replyValue = _extractReplyValueOrThrow( + pigeonVar_replyList, + pigeonVar_channelName, + isNullValid: false, + ); + return pigeonVar_replyValue! as bool; } Future unPair(String deviceId) async { @@ -1150,17 +1396,12 @@ class UniversalBlePlatformChannel { [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; - } + + _extractReplyValueOrThrow( + pigeonVar_replyList, + pigeonVar_channelName, + isNullValid: true, + ); } Future> getSystemDevices( @@ -1177,23 +1418,14 @@ class UniversalBlePlatformChannel { [withServices], ); 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(); - } + + final Object? pigeonVar_replyValue = _extractReplyValueOrThrow( + pigeonVar_replyList, + pigeonVar_channelName, + isNullValid: false, + ); + return (pigeonVar_replyValue! as List) + .cast(); } Future getConnectionState(String deviceId) async { @@ -1208,22 +1440,13 @@ class UniversalBlePlatformChannel { [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?)!; - } + + final Object? pigeonVar_replyValue = _extractReplyValueOrThrow( + pigeonVar_replyList, + pigeonVar_channelName, + isNullValid: false, + ); + return pigeonVar_replyValue! as int; } Future readRssi(String deviceId) async { @@ -1238,46 +1461,139 @@ class UniversalBlePlatformChannel { [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?)!; - } + + 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?; - 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; - } + 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 isPeripheralSupported() async { + final pigeonVar_channelName = + 'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.isPeripheralSupported$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 startPeripheral(UniversalBlePeripheralConfig config) async { + final pigeonVar_channelName = + 'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.startPeripheral$pigeonVar_messageChannelSuffix'; + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [config], + ); + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + + _extractReplyValueOrThrow( + pigeonVar_replyList, + pigeonVar_channelName, + isNullValid: true, + ); + } + + Future stopPeripheral() async { + final pigeonVar_channelName = + 'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.stopPeripheral$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 updatePeripheralCharacteristicValue( + String service, + String characteristic, + Uint8List value, + ) async { + final pigeonVar_channelName = + 'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.updatePeripheralCharacteristicValue$pigeonVar_messageChannelSuffix'; + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [service, characteristic, value], + ); + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + + _extractReplyValueOrThrow( + pigeonVar_replyList, + pigeonVar_channelName, + isNullValid: true, + ); + } + + Future notifyPeripheralCharacteristic( + String service, + String characteristic, + Uint8List value, + bool indicate, + ) async { + final pigeonVar_channelName = + 'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.notifyPeripheralCharacteristic$pigeonVar_messageChannelSuffix'; + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [service, characteristic, value, indicate], + ); + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + + _extractReplyValueOrThrow( + pigeonVar_replyList, + pigeonVar_channelName, + isNullValid: true, + ); } Future setLogLevel(UniversalBleLogLevel logLevel) async { @@ -1292,17 +1608,12 @@ class UniversalBlePlatformChannel { [logLevel], ); 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; - } + + _extractReplyValueOrThrow( + pigeonVar_replyList, + pigeonVar_channelName, + isNullValid: true, + ); } } @@ -1325,6 +1636,17 @@ abstract class UniversalBleCallbackChannel { void onConnectionChanged(String deviceId, bool connected, String? error); + void onPeripheralConnectionChanged(String deviceId, bool connected); + + void onPeripheralWrite(UniversalBlePeripheralWriteEvent event); + + void onPeripheralSubscriptionChanged( + String deviceId, + String service, + String characteristic, + bool subscribed, + ); + static void setUp( UniversalBleCallbackChannel? api, { BinaryMessenger? binaryMessenger, @@ -1343,18 +1665,10 @@ abstract class UniversalBleCallbackChannel { 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 int arg_state = args[0]! as int; try { - api.onAvailabilityChanged(arg_state!); + api.onAvailabilityChanged(arg_state); return wrapResponse(empty: true); } on PlatformException catch (e) { return wrapResponse(error: e); @@ -1376,24 +1690,12 @@ abstract class UniversalBleCallbackChannel { 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 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); + api.onPairStateChange(arg_deviceId, arg_isPaired, arg_error); return wrapResponse(empty: true); } on PlatformException catch (e) { return wrapResponse(error: e); @@ -1415,19 +1717,11 @@ abstract class UniversalBleCallbackChannel { 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 UniversalBleScanResult arg_result = + args[0]! as UniversalBleScanResult; try { - api.onScanResult(arg_result!); + api.onScanResult(arg_result); return wrapResponse(empty: true); } on PlatformException catch (e) { return wrapResponse(error: e); @@ -1449,32 +1743,16 @@ abstract class UniversalBleCallbackChannel { 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 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_deviceId, + arg_characteristicId, + arg_value, arg_timestamp, ); return wrapResponse(empty: true); @@ -1498,24 +1776,97 @@ abstract class UniversalBleCallbackChannel { 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; + final String? arg_error = args[2] as String?; try { - api.onConnectionChanged(arg_deviceId!, arg_connected!, arg_error); + 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()), + ); + } + }); + } + } + { + final pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.universal_ble.UniversalBleCallbackChannel.onPeripheralConnectionChanged$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; + try { + api.onPeripheralConnectionChanged(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()), + ); + } + }); + } + } + { + final pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.universal_ble.UniversalBleCallbackChannel.onPeripheralWrite$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 UniversalBlePeripheralWriteEvent arg_event = + args[0]! as UniversalBlePeripheralWriteEvent; + try { + api.onPeripheralWrite(arg_event); + 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.onPeripheralSubscriptionChanged$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_service = args[1]! as String; + final String arg_characteristic = args[2]! as String; + final bool arg_subscribed = args[3]! as bool; + try { + api.onPeripheralSubscriptionChanged( + arg_deviceId, + arg_service, + arg_characteristic, + arg_subscribed, + ); return wrapResponse(empty: true); } on PlatformException catch (e) { return wrapResponse(error: e); 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 eb46419..d864a99 100644 --- a/lib/src/universal_ble_pigeon/universal_ble_pigeon_channel.dart +++ b/lib/src/universal_ble_pigeon/universal_ble_pigeon_channel.dart @@ -5,8 +5,7 @@ import 'package:universal_ble/universal_ble.dart'; class UniversalBlePigeonChannel extends UniversalBlePlatform { static UniversalBlePigeonChannel? _instance; - static UniversalBlePigeonChannel get instance => - _instance ??= UniversalBlePigeonChannel._(); + static UniversalBlePigeonChannel get instance => _instance ??= UniversalBlePigeonChannel._(); late final UniversalBleFilterUtil _bleFilter = UniversalBleFilterUtil(); UniversalBlePigeonChannel._() { @@ -17,9 +16,7 @@ class UniversalBlePigeonChannel extends UniversalBlePlatform { @override Future getBluetoothAvailabilityState() async { - int state = await _executeWithErrorHandling( - () => _channel.getBluetoothAvailabilityState(), - ); + int state = await _executeWithErrorHandling(() => _channel.getBluetoothAvailabilityState()); return AvailabilityState.parse(state); } @@ -40,10 +37,7 @@ class UniversalBlePigeonChannel extends UniversalBlePlatform { } @override - Future startScan({ - ScanFilter? scanFilter, - PlatformConfig? platformConfig, - }) async { + Future startScan({ScanFilter? scanFilter, PlatformConfig? platformConfig}) async { await _ensureInitialized(platformConfig); _bleFilter.scanFilter = scanFilter; await _executeWithErrorHandling( @@ -55,48 +49,32 @@ class UniversalBlePigeonChannel extends UniversalBlePlatform { } @override - Future stopScan() => - _executeWithErrorHandling(() => _channel.stopScan()); + Future stopScan() => _executeWithErrorHandling(() => _channel.stopScan()); @override - Future isScanning() => - _executeWithErrorHandling(() => _channel.isScanning()); + Future isScanning() => _executeWithErrorHandling(() => _channel.isScanning()); @override Future getConnectionState(String deviceId) async { - int state = await _executeWithErrorHandling( - () => _channel.getConnectionState(deviceId), - ); + int state = await _executeWithErrorHandling(() => _channel.getConnectionState(deviceId)); return BleConnectionState.parse(state); } @override - Future connect( - String deviceId, { - Duration? connectionTimeout, - bool autoConnect = false, - }) => _executeWithErrorHandling( - () => _channel.connect(deviceId, autoConnect: autoConnect), - ); + Future connect(String deviceId, {Duration? connectionTimeout, bool autoConnect = false}) => + _executeWithErrorHandling(() => _channel.connect(deviceId, autoConnect: autoConnect)); @override Future disconnect(String deviceId) => _executeWithErrorHandling(() => _channel.disconnect(deviceId)); @override - Future> discoverServices( - String deviceId, - bool withDescriptors, - ) async { - List universalBleServices = - await _executeWithErrorHandling( - () => _channel.discoverServices(deviceId, withDescriptors), - ); + Future> discoverServices(String deviceId, bool withDescriptors) async { + List universalBleServices = await _executeWithErrorHandling( + () => _channel.discoverServices(deviceId, withDescriptors), + ); return List.from( - universalBleServices - .where((e) => e != null) - .map((e) => e!.toBleService(deviceId)) - .toList(), + universalBleServices.where((e) => e != null).map((e) => e!.toBleService(deviceId)).toList(), ); } @@ -108,12 +86,7 @@ class UniversalBlePigeonChannel extends UniversalBlePlatform { BleInputProperty bleInputProperty, ) { return _executeWithErrorHandling( - () => _channel.setNotifiable( - deviceId, - service, - characteristic, - bleInputProperty.index, - ), + () => _channel.setNotifiable(deviceId, service, characteristic, bleInputProperty.index), ); } @@ -124,9 +97,7 @@ class UniversalBlePigeonChannel extends UniversalBlePlatform { String characteristic, { final Duration? timeout, }) { - return _executeWithErrorHandling( - () => _channel.readValue(deviceId, service, characteristic), - ); + return _executeWithErrorHandling(() => _channel.readValue(deviceId, service, characteristic)); } @override @@ -138,32 +109,51 @@ class UniversalBlePigeonChannel extends UniversalBlePlatform { BleOutputProperty bleOutputProperty, ) { return _executeWithErrorHandling( - () => _channel.writeValue( - deviceId, - service, - characteristic, - value, - bleOutputProperty.index, - ), + () => _channel.writeValue(deviceId, service, characteristic, value, bleOutputProperty.index), ); } @override Future requestMtu(String deviceId, int expectedMtu) => - _executeWithErrorHandling( - () => _channel.requestMtu(deviceId, expectedMtu), - ); + _executeWithErrorHandling(() => _channel.requestMtu(deviceId, expectedMtu)); @override Future readRssi(String deviceId) => _executeWithErrorHandling(() => _channel.readRssi(deviceId)); @override - Future requestConnectionPriority( - String deviceId, - BleConnectionPriority priority, + Future requestConnectionPriority(String deviceId, BleConnectionPriority priority) => + _executeWithErrorHandling(() => _channel.requestConnectionPriority(deviceId, priority.index)); + + @override + Future isPeripheralSupported() => + _executeWithErrorHandling(() => _channel.isPeripheralSupported()); + + @override + Future startPeripheral(BlePeripheralConfig config) => _executeWithErrorHandling( + () => _channel.startPeripheral(config.toUniversalPeripheralConfig()), + ); + + @override + Future stopPeripheral() => _executeWithErrorHandling(() => _channel.stopPeripheral()); + + @override + Future updatePeripheralCharacteristicValue( + String service, + String characteristic, + Uint8List value, ) => _executeWithErrorHandling( - () => _channel.requestConnectionPriority(deviceId, priority.index), + () => _channel.updatePeripheralCharacteristicValue(service, characteristic, value), + ); + + @override + Future notifyPeripheralCharacteristic( + String service, + String characteristic, + Uint8List value, { + bool indicate = false, + }) => _executeWithErrorHandling( + () => _channel.notifyPeripheralCharacteristic(service, characteristic, value, indicate), ); @override @@ -171,26 +161,29 @@ class UniversalBlePigeonChannel extends UniversalBlePlatform { _executeWithErrorHandling(() => _channel.isPaired(deviceId)); @override - Future pair(String deviceId) => - _executeWithErrorHandling(() => _channel.pair(deviceId)); + Future pair(String deviceId) => _executeWithErrorHandling(() => _channel.pair(deviceId)); @override Future unpair(String deviceId) => _executeWithErrorHandling(() => _channel.unPair(deviceId)); @override - Future hasPermissions({bool withAndroidFineLocation = false}) async { + Future hasPermissions({ + bool withAndroidFineLocation = false, + bool withAndroidBluetoothAdvertise = false, + }) async { return await _executeWithErrorHandling( - () => _channel.hasPermissions(withAndroidFineLocation), + () => _channel.hasPermissions(withAndroidFineLocation, withAndroidBluetoothAdvertise), ); } @override Future requestPermissions({ bool withAndroidFineLocation = false, + bool withAndroidBluetoothAdvertise = false, }) async { await _executeWithErrorHandling( - () => _channel.requestPermissions(withAndroidFineLocation), + () => _channel.requestPermissions(withAndroidFineLocation, withAndroidBluetoothAdvertise), ); } @@ -199,15 +192,12 @@ class UniversalBlePigeonChannel extends UniversalBlePlatform { var devices = await _executeWithErrorHandling( () => _channel.getSystemDevices(withServices ?? []), ); - return List.from( - devices.map((e) => e.toBleDevice(isSystemDevice: true)).toList(), - ); + return List.from(devices.map((e) => e.toBleDevice(isSystemDevice: true)).toList()); } @override - Future setLogLevel(BleLogLevel logLevel) => _executeWithErrorHandling( - () => _channel.setLogLevel(logLevel.toUniversalBleLogLevel()), - ); + Future setLogLevel(BleLogLevel logLevel) => + _executeWithErrorHandling(() => _channel.setLogLevel(logLevel.toUniversalBleLogLevel())); /// To set listeners void _setupListeners() { @@ -223,6 +213,9 @@ class UniversalBlePigeonChannel extends UniversalBlePlatform { connectionChanged: updateConnection, valueChanged: updateCharacteristicValue, pairStateChange: updatePairingState, + peripheralConnectionChanged: updatePeripheralConnection, + peripheralWrite: updatePeripheralWrite, + peripheralSubscriptionChanged: updatePeripheralSubscription, ), ); } @@ -246,8 +239,7 @@ class UniversalBlePigeonChannel extends UniversalBlePlatform { defaultTargetPlatform == TargetPlatform.iOS || defaultTargetPlatform == TargetPlatform.macOS) { await requestPermissions( - withAndroidFineLocation: - platformConfig?.android?.requestLocationPermission ?? false, + withAndroidFineLocation: platformConfig?.android?.requestLocationPermission ?? false, ); } } @@ -277,12 +269,70 @@ extension _BleServiceExtension on UniversalBleService { } } +extension _BlePeripheralConfigExtension on BlePeripheralConfig { + UniversalBlePeripheralConfig toUniversalPeripheralConfig() { + return UniversalBlePeripheralConfig( + advertisedName: advertisedName, + services: services.map((service) => service.toUniversalPeripheralService()).toList(), + ); + } +} + +extension _BlePeripheralServiceExtension on BlePeripheralService { + UniversalBlePeripheralService toUniversalPeripheralService() { + return UniversalBlePeripheralService( + uuid: uuid, + characteristics: characteristics + .map((characteristic) => characteristic.toUniversalPeripheralCharacteristic()) + .toList(), + ); + } +} + +extension _BlePeripheralCharacteristicExtension on BlePeripheralCharacteristic { + UniversalBlePeripheralCharacteristic toUniversalPeripheralCharacteristic() { + return UniversalBlePeripheralCharacteristic( + uuid: uuid, + properties: properties.map((property) => property.index).toList(), + permissions: permissions.map((permission) => permission.index).toList(), + descriptors: descriptors + .map((descriptor) => descriptor.toUniversalPeripheralDescriptor()) + .toList(), + initialValue: initialValue, + ); + } +} + +extension _BlePeripheralDescriptorExtension on BlePeripheralDescriptor { + UniversalBlePeripheralDescriptor toUniversalPeripheralDescriptor() { + return UniversalBlePeripheralDescriptor( + uuid: uuid, + permissions: permissions.map((permission) => permission.index).toList(), + initialValue: initialValue, + ); + } +} + +extension _UniversalBlePeripheralWriteEventExtension on UniversalBlePeripheralWriteEvent { + BlePeripheralWriteEvent toBlePeripheralWriteEvent() { + return BlePeripheralWriteEvent( + deviceId: deviceId, + service: service, + characteristic: characteristic, + value: value, + ); + } +} + class _UniversalBleCallbackHandler extends UniversalBleCallbackChannel { OnAvailabilityChange availabilityChange; OnScanResult scanResult; OnConnectionChange connectionChanged; OnValueChange valueChanged; OnPairingStateChange pairStateChange; + void Function(String deviceId, bool connected) peripheralConnectionChanged; + void Function(BlePeripheralWriteEvent event) peripheralWrite; + void Function(BlePeripheralSubscriptionEvent event) peripheralSubscriptionChanged; _UniversalBleCallbackHandler({ required this.availabilityChange, @@ -290,31 +340,51 @@ class _UniversalBleCallbackHandler extends UniversalBleCallbackChannel { required this.connectionChanged, required this.valueChanged, required this.pairStateChange, + required this.peripheralConnectionChanged, + required this.peripheralWrite, + required this.peripheralSubscriptionChanged, }); @override - void onAvailabilityChanged(int state) => - availabilityChange(AvailabilityState.parse(state)); + void onAvailabilityChanged(int state) => availabilityChange(AvailabilityState.parse(state)); @override void onConnectionChanged(String deviceId, bool connected, String? error) => connectionChanged(deviceId, connected, error); @override - void onScanResult(UniversalBleScanResult result) => - scanResult(result.toBleDevice()); + void onScanResult(UniversalBleScanResult result) => scanResult(result.toBleDevice()); @override - void onValueChanged( - String deviceId, - String characteristicId, - Uint8List value, - int? timestamp, - ) => valueChanged(deviceId, characteristicId, value, timestamp); + void onValueChanged(String deviceId, String characteristicId, Uint8List value, int? timestamp) => + valueChanged(deviceId, characteristicId, value, timestamp); @override void onPairStateChange(String deviceId, bool isPaired, String? error) => pairStateChange(deviceId, isPaired); + + @override + void onPeripheralConnectionChanged(String deviceId, bool connected) => + peripheralConnectionChanged(deviceId, connected); + + @override + void onPeripheralWrite(UniversalBlePeripheralWriteEvent event) => + peripheralWrite(event.toBlePeripheralWriteEvent()); + + @override + void onPeripheralSubscriptionChanged( + String deviceId, + String service, + String characteristic, + bool subscribed, + ) => peripheralSubscriptionChanged( + BlePeripheralSubscriptionEvent( + deviceId: deviceId, + service: service, + characteristic: characteristic, + subscribed: subscribed, + ), + ); } extension _UniversalBleScanResultExtension on UniversalBleScanResult { @@ -339,8 +409,7 @@ extension _UniversalBleScanResultExtension on UniversalBleScanResult { extension _ScanFilterExtension on ScanFilter? { UniversalScanFilter? toUniversalScanFilter() { - List? manufacturerDataFilters = this - ?.withManufacturerData + List? manufacturerDataFilters = this?.withManufacturerData .map( (e) => UniversalManufacturerDataFilter( companyIdentifier: e.companyIdentifier, diff --git a/lib/src/universal_ble_platform_interface.dart b/lib/src/universal_ble_platform_interface.dart index d2c75b9..971c967 100644 --- a/lib/src/universal_ble_platform_interface.dart +++ b/lib/src/universal_ble_platform_interface.dart @@ -17,23 +17,26 @@ abstract class UniversalBlePlatform { final _scanStreamController = UniversalBleStreamController(); final bleConnectionUpdateStreamController = - UniversalBleStreamController< - ({String deviceId, bool isConnected, String? error}) - >(); + UniversalBleStreamController<({String deviceId, bool isConnected, String? error})>(); final _valueStreamController = - UniversalBleStreamController< - ({String deviceId, String characteristicId, Uint8List value}) - >(); + UniversalBleStreamController<({String deviceId, String characteristicId, Uint8List value})>(); final _pairStateStreamController = UniversalBleStreamController<({String deviceId, bool isPaired})>(); + final _peripheralConnectionStreamController = + UniversalBleStreamController(); + + final _peripheralWriteStreamController = UniversalBleStreamController(); + + final _peripheralSubscriptionStreamController = + UniversalBleStreamController(); + /// Send latest availability state upon subscribing - late final _availabilityStreamController = - UniversalBleStreamController( - initialEvent: getBluetoothAvailabilityState, - ); + late final _availabilityStreamController = UniversalBleStreamController( + initialEvent: getBluetoothAvailabilityState, + ); Future getBluetoothAvailabilityState(); @@ -41,35 +44,29 @@ abstract class UniversalBlePlatform { Future disableBluetooth(); - Future hasPermissions({bool withAndroidFineLocation = false}) async { + Future hasPermissions({ + bool withAndroidFineLocation = false, + bool withAndroidBluetoothAdvertise = false, + }) async { return true; } Future requestPermissions({ bool withAndroidFineLocation = false, + bool withAndroidBluetoothAdvertise = false, }) async {} - Future startScan({ - ScanFilter? scanFilter, - PlatformConfig? platformConfig, - }); + Future startScan({ScanFilter? scanFilter, PlatformConfig? platformConfig}); Future stopScan(); Future isScanning(); - Future connect( - String deviceId, { - Duration? connectionTimeout, - bool autoConnect = false, - }); + Future connect(String deviceId, {Duration? connectionTimeout, bool autoConnect = false}); Future disconnect(String deviceId); - Future> discoverServices( - String deviceId, - bool withDescriptors, - ); + Future> discoverServices(String deviceId, bool withDescriptors); Future setNotifiable( String deviceId, @@ -97,10 +94,34 @@ abstract class UniversalBlePlatform { Future readRssi(String deviceId); - Future requestConnectionPriority( - String deviceId, - BleConnectionPriority priority, - ); + Future requestConnectionPriority(String deviceId, BleConnectionPriority priority); + + Future isPeripheralSupported() async => false; + + Future startPeripheral(BlePeripheralConfig config) { + throw UnsupportedError('BLE peripheral mode is not supported'); + } + + Future stopPeripheral() { + throw UnsupportedError('BLE peripheral mode is not supported'); + } + + Future updatePeripheralCharacteristicValue( + String service, + String characteristic, + Uint8List value, + ) { + throw UnsupportedError('BLE peripheral mode is not supported'); + } + + Future notifyPeripheralCharacteristic( + String service, + String characteristic, + Uint8List value, { + bool indicate = false, + }) { + throw UnsupportedError('BLE peripheral mode is not supported'); + } Future isPaired(String deviceId); @@ -112,39 +133,39 @@ abstract class UniversalBlePlatform { Future> getSystemDevices(List? withServices); - Future setLogLevel(BleLogLevel logLevel) async => - UniversalLogger.setLogLevel(logLevel); + Future setLogLevel(BleLogLevel logLevel) async => UniversalLogger.setLogLevel(logLevel); bool receivesAdvertisements(String deviceId) => true; /// Streams Stream get scanStream => _scanStreamController.stream; - Stream get availabilityStream => - _availabilityStreamController.stream; + Stream get availabilityStream => _availabilityStreamController.stream; - Stream connectionStream(String deviceId) => - bleConnectionUpdateStreamController.stream - .where((e) => e.deviceId == deviceId) - .map((e) => e.isConnected); + Stream connectionStream(String deviceId) => bleConnectionUpdateStreamController.stream + .where((e) => e.deviceId == deviceId) + .map((e) => e.isConnected); - Stream characteristicValueStream( - String deviceId, - String characteristicId, - ) { + Stream characteristicValueStream(String deviceId, String characteristicId) { characteristicId = BleUuidParser.string(characteristicId); return _valueStreamController.stream .where((e) { - return e.deviceId == deviceId && - e.characteristicId == characteristicId; + 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); + + Stream get peripheralConnectionStream => + _peripheralConnectionStreamController.stream; + + Stream get peripheralWriteStream => + _peripheralWriteStreamController.stream; + + Stream get peripheralSubscriptionStream => + _peripheralSubscriptionStreamController.stream; /// Update Handlers void updateScanResult(BleDevice bleDevice) { @@ -206,19 +227,27 @@ abstract class UniversalBlePlatform { onPairingStateChange?.call(deviceId, isPaired); } catch (_) {} } + + void updatePeripheralConnection(String deviceId, bool connected) { + _peripheralConnectionStreamController.add( + BlePeripheralConnectionEvent(deviceId: deviceId, connected: connected), + ); + } + + void updatePeripheralWrite(BlePeripheralWriteEvent event) { + _peripheralWriteStreamController.add(event); + } + + void updatePeripheralSubscription(BlePeripheralSubscriptionEvent event) { + _peripheralSubscriptionStreamController.add(event); + } } // Callback types -typedef OnConnectionChange = - void Function(String deviceId, bool isConnected, String? error); +typedef OnConnectionChange = void Function(String deviceId, bool isConnected, String? error); typedef OnValueChange = - void Function( - String deviceId, - String characteristicId, - Uint8List value, - int? timestamp, - ); + void Function(String deviceId, String characteristicId, Uint8List value, int? timestamp); typedef OnScanResult = void Function(BleDevice scanResult); diff --git a/pigeon/universal_ble.dart b/pigeon/universal_ble.dart index aa92a36..78068ff 100644 --- a/pigeon/universal_ble.dart +++ b/pigeon/universal_ble.dart @@ -6,8 +6,7 @@ import 'package:pigeon/pigeon.dart'; dartPackageName: 'universal_ble', dartOut: 'lib/src/universal_ble_pigeon/universal_ble.g.dart', dartOptions: DartOptions(), - kotlinOut: - 'android/src/main/kotlin/com/navideck/universal_ble/UniversalBle.g.kt', + kotlinOut: 'android/src/main/kotlin/com/navideck/universal_ble/UniversalBle.g.kt', swiftOut: 'darwin/universal_ble/Sources/universal_ble/UniversalBle.g.swift', kotlinOptions: KotlinOptions(package: 'com.navideck.universal_ble'), swiftOptions: SwiftOptions(), @@ -17,17 +16,16 @@ import 'package:pigeon/pigeon.dart'; debugGenerators: true, ), ) - /// Flutter -> Native @HostApi() abstract class UniversalBlePlatformChannel { @async int getBluetoothAvailabilityState(); - bool hasPermissions(bool withAndroidFineLocation); + bool hasPermissions(bool withAndroidFineLocation, bool withAndroidBluetoothAdvertise); @async - void requestPermissions(bool withAndroidFineLocation); + void requestPermissions(bool withAndroidFineLocation, bool withAndroidBluetoothAdvertise); @async bool enableBluetooth(); @@ -46,25 +44,13 @@ abstract class UniversalBlePlatformChannel { void disconnect(String deviceId); @async - void setNotifiable( - String deviceId, - String service, - String characteristic, - int bleInputProperty, - ); + void setNotifiable(String deviceId, String service, String characteristic, int bleInputProperty); @async - List discoverServices( - String deviceId, - bool withDescriptors, - ); + List discoverServices(String deviceId, bool withDescriptors); @async - Uint8List readValue( - String deviceId, - String service, - String characteristic, - ); + Uint8List readValue(String deviceId, String service, String characteristic); @async int requestMtu(String deviceId, int expectedMtu); @@ -87,9 +73,7 @@ abstract class UniversalBlePlatformChannel { void unPair(String deviceId); @async - List getSystemDevices( - List withServices, - ); + List getSystemDevices(List withServices); int getConnectionState(String deviceId); @@ -99,6 +83,25 @@ abstract class UniversalBlePlatformChannel { @async void requestConnectionPriority(String deviceId, int priority); + bool isPeripheralSupported(); + + @async + void startPeripheral(UniversalBlePeripheralConfig config); + + @async + void stopPeripheral(); + + @async + void updatePeripheralCharacteristicValue(String service, String characteristic, Uint8List value); + + @async + void notifyPeripheralCharacteristic( + String service, + String characteristic, + Uint8List value, + bool indicate, + ); + void setLogLevel(UniversalBleLogLevel logLevel); } @@ -111,17 +114,19 @@ abstract class UniversalBleCallbackChannel { void onScanResult(UniversalBleScanResult result); - void onValueChanged( - String deviceId, - String characteristicId, - Uint8List value, - int? timestamp, - ); + void onValueChanged(String deviceId, String characteristicId, Uint8List value, int? timestamp); - void onConnectionChanged( + void onConnectionChanged(String deviceId, bool connected, String? error); + + void onPeripheralConnectionChanged(String deviceId, bool connected); + + void onPeripheralWrite(UniversalBlePeripheralWriteEvent event); + + void onPeripheralSubscriptionChanged( String deviceId, - bool connected, - String? error, + String service, + String characteristic, + bool subscribed, ); } @@ -147,14 +152,7 @@ class UniversalBleScanResult { }); } -enum UniversalBleLogLevel { - none, - error, - warning, - info, - debug, - verbose, -} +enum UniversalBleLogLevel { none, error, warning, info, debug, verbose } class UniversalBleService { String uuid; @@ -174,14 +172,56 @@ class UniversalBleDescriptor { UniversalBleDescriptor(this.uuid); } +class UniversalBlePeripheralConfig { + String advertisedName; + List services; + + UniversalBlePeripheralConfig(this.advertisedName, this.services); +} + +class UniversalBlePeripheralService { + String uuid; + List characteristics; + + UniversalBlePeripheralService(this.uuid, this.characteristics); +} + +class UniversalBlePeripheralCharacteristic { + String uuid; + List properties; + List permissions; + List descriptors; + Uint8List? initialValue; + + UniversalBlePeripheralCharacteristic( + this.uuid, + this.properties, + this.permissions, + this.descriptors, + this.initialValue, + ); +} + +class UniversalBlePeripheralDescriptor { + String uuid; + List permissions; + Uint8List? initialValue; + + UniversalBlePeripheralDescriptor(this.uuid, this.permissions, this.initialValue); +} + +class UniversalBlePeripheralWriteEvent { + String deviceId; + String service; + String characteristic; + Uint8List value; + + UniversalBlePeripheralWriteEvent(this.deviceId, this.service, this.characteristic, this.value); +} + /// Scan config -enum AndroidScanMode { - balanced, - lowLatency, - lowPower, - opportunistic, -} +enum AndroidScanMode { balanced, lowLatency, lowPower, opportunistic } /// Android options to scan devices /// [requestLocationPermission] is used to request location permission on Android 12+ (API 31+). @@ -193,11 +233,7 @@ class AndroidOptions { bool? requestLocationPermission; AndroidScanMode? scanMode; int? reportDelayMillis; - AndroidOptions({ - this.requestLocationPermission, - this.scanMode, - this.reportDelayMillis, - }); + AndroidOptions({this.requestLocationPermission, this.scanMode, this.reportDelayMillis}); } class UniversalScanConfig { @@ -211,32 +247,21 @@ class UniversalScanFilter { final List withNamePrefix; final List withManufacturerData; - UniversalScanFilter( - this.withServices, - this.withNamePrefix, - this.withManufacturerData, - ); + UniversalScanFilter(this.withServices, this.withNamePrefix, this.withManufacturerData); } class UniversalManufacturerDataFilter { int companyIdentifier; Uint8List? data; Uint8List? mask; - UniversalManufacturerDataFilter({ - required this.companyIdentifier, - this.data, - this.mask, - }); + UniversalManufacturerDataFilter({required this.companyIdentifier, this.data, this.mask}); } class UniversalManufacturerData { final int companyIdentifier; final Uint8List data; - UniversalManufacturerData({ - required this.companyIdentifier, - required this.data, - }); + UniversalManufacturerData({required this.companyIdentifier, required this.data}); } /// Unified error codes for all platforms diff --git a/pubspec.yaml b/pubspec.yaml index 487daff..8c28927 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -33,6 +33,7 @@ dependencies: plugin_platform_interface: ^2.1.8 flutter_web_bluetooth: ^1.1.0 bluez: ^0.8.3 + dbus: ^0.7.11 dev_dependencies: flutter_test: diff --git a/test/ble_characteristic_test.dart b/test/ble_characteristic_test.dart index 409c855..ec276b5 100644 --- a/test/ble_characteristic_test.dart +++ b/test/ble_characteristic_test.dart @@ -26,8 +26,7 @@ void main() { group('BleCharacteristic Tests', () { test("DiscoverServices test", () async { debugPrint("Discovering services"); - List services = - await UniversalBle.discoverServices(mockDeviceId); + List services = await UniversalBle.discoverServices(mockDeviceId); expect(services.length, 1); BleService service = services.first; @@ -35,18 +34,9 @@ void main() { expect(service.characteristics.length, 1); BleCharacteristic characteristic = service.characteristics.first; - expect( - BleUuidParser.compareStrings(characteristic.uuid, characteristicId), - true, - ); + expect(BleUuidParser.compareStrings(characteristic.uuid, characteristicId), true); expect(characteristic.metaData?.deviceId, mockDeviceId); - expect( - BleUuidParser.compareStrings( - characteristic.metaData!.serviceId, - serviceId, - ), - true, - ); + expect(BleUuidParser.compareStrings(characteristic.metaData!.serviceId, serviceId), true); }); test("Subscription Test", () async { @@ -87,14 +77,17 @@ class _UniversalBleMock extends UniversalBlePlatformMock { Uint8List? charValue; @override - Future> discoverServices( - String deviceId, bool withDescriptors) async { + Future> discoverServices(String deviceId, bool withDescriptors) async { return [mockBleService]; } @override - Future setNotifiable(String deviceId, String service, - String characteristic, BleInputProperty bleInputProperty) async { + Future setNotifiable( + String deviceId, + String service, + String characteristic, + BleInputProperty bleInputProperty, + ) async { if (bleInputProperty == BleInputProperty.disabled) { notifierTimer?.cancel(); notifierTimer = null; @@ -113,23 +106,30 @@ class _UniversalBleMock extends UniversalBlePlatformMock { @override Future writeValue( - String deviceId, - String service, - String characteristic, - Uint8List value, - BleOutputProperty bleOutputProperty) async { + String deviceId, + String service, + String characteristic, + Uint8List value, + BleOutputProperty bleOutputProperty, + ) async { charValue = value; } @override Future readValue( - String deviceId, String service, String characteristic, - {Duration? timeout}) async { + String deviceId, + String service, + String characteristic, { + Duration? timeout, + }) async { return charValue ?? Uint8List(0); } @override - Future requestPermissions({bool withAndroidFineLocation = false}) { + Future requestPermissions({ + bool withAndroidFineLocation = false, + bool withAndroidBluetoothAdvertise = false, + }) { throw UnimplementedError(); } diff --git a/test/ble_peripheral_test.dart b/test/ble_peripheral_test.dart new file mode 100644 index 0000000..dd481ee --- /dev/null +++ b/test/ble_peripheral_test.dart @@ -0,0 +1,45 @@ +import 'dart:typed_data'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:universal_ble/universal_ble.dart'; + +void main() { + group('BlePeripheral models', () { + test('normalizes UUIDs and defensively copies characteristic values', () { + final initialValue = Uint8List.fromList([1, 2, 3]); + + final characteristic = BlePeripheralCharacteristic( + uuid: 'fff1', + properties: const [CharacteristicProperty.read], + permissions: const [BlePeripheralCharacteristicPermission.read], + initialValue: initialValue, + ); + + initialValue[0] = 9; + + expect( + BleUuidParser.compareStrings(characteristic.uuid, '0000fff1-0000-1000-8000-00805f9b34fb'), + isTrue, + ); + expect(characteristic.initialValue, [1, 2, 3]); + }); + + test('normalizes write event UUIDs and defensively copies payloads', () { + final value = Uint8List.fromList([4, 5, 6]); + + final event = BlePeripheralWriteEvent( + deviceId: 'client-1', + service: 'fff0', + characteristic: 'fff1', + value: value, + ); + + value[0] = 8; + + expect(event.deviceId, 'client-1'); + expect(BleUuidParser.compareStrings(event.service, 'fff0'), isTrue); + expect(BleUuidParser.compareStrings(event.characteristic, 'fff1'), isTrue); + expect(event.value, [4, 5, 6]); + }); + }); +} diff --git a/windows/src/generated/universal_ble.g.cpp b/windows/src/generated/universal_ble.g.cpp index 90171ad..8c056d6 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.4), 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,438 @@ 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(); +} + +// UniversalBlePeripheralConfig + +UniversalBlePeripheralConfig::UniversalBlePeripheralConfig( + const std::string& advertised_name, + const EncodableList& services) + : advertised_name_(advertised_name), + services_(services) {} + +const std::string& UniversalBlePeripheralConfig::advertised_name() const { + return advertised_name_; +} + +void UniversalBlePeripheralConfig::set_advertised_name(std::string_view value_arg) { + advertised_name_ = value_arg; +} + + +const EncodableList& UniversalBlePeripheralConfig::services() const { + return services_; +} + +void UniversalBlePeripheralConfig::set_services(const EncodableList& value_arg) { + services_ = value_arg; +} + + +EncodableList UniversalBlePeripheralConfig::ToEncodableList() const { + EncodableList list; + list.reserve(2); + list.push_back(EncodableValue(advertised_name_)); + list.push_back(EncodableValue(services_)); + return list; +} + +UniversalBlePeripheralConfig UniversalBlePeripheralConfig::FromEncodableList(const EncodableList& list) { + UniversalBlePeripheralConfig decoded( + std::get(list[0]), + std::get(list[1])); + return decoded; +} + +bool UniversalBlePeripheralConfig::operator==(const UniversalBlePeripheralConfig& other) const { + return PigeonInternalDeepEquals(advertised_name_, other.advertised_name_) && PigeonInternalDeepEquals(services_, other.services_); +} + +bool UniversalBlePeripheralConfig::operator!=(const UniversalBlePeripheralConfig& other) const { + return !(*this == other); +} + +size_t UniversalBlePeripheralConfig::Hash() const { + size_t result = 1; + result = result * 31 + PigeonInternalDeepHash(advertised_name_); + result = result * 31 + PigeonInternalDeepHash(services_); + return result; +} + +size_t PigeonInternalDeepHash(const UniversalBlePeripheralConfig& v) { + return v.Hash(); +} + +// UniversalBlePeripheralService + +UniversalBlePeripheralService::UniversalBlePeripheralService( + const std::string& uuid, + const EncodableList& characteristics) + : uuid_(uuid), + characteristics_(characteristics) {} + +const std::string& UniversalBlePeripheralService::uuid() const { + return uuid_; +} + +void UniversalBlePeripheralService::set_uuid(std::string_view value_arg) { + uuid_ = value_arg; +} + + +const EncodableList& UniversalBlePeripheralService::characteristics() const { + return characteristics_; +} + +void UniversalBlePeripheralService::set_characteristics(const EncodableList& value_arg) { + characteristics_ = value_arg; +} + + +EncodableList UniversalBlePeripheralService::ToEncodableList() const { + EncodableList list; + list.reserve(2); + list.push_back(EncodableValue(uuid_)); + list.push_back(EncodableValue(characteristics_)); + return list; +} + +UniversalBlePeripheralService UniversalBlePeripheralService::FromEncodableList(const EncodableList& list) { + UniversalBlePeripheralService decoded( + std::get(list[0]), + std::get(list[1])); + return decoded; +} + +bool UniversalBlePeripheralService::operator==(const UniversalBlePeripheralService& other) const { + return PigeonInternalDeepEquals(uuid_, other.uuid_) && PigeonInternalDeepEquals(characteristics_, other.characteristics_); +} + +bool UniversalBlePeripheralService::operator!=(const UniversalBlePeripheralService& other) const { + return !(*this == other); +} + +size_t UniversalBlePeripheralService::Hash() const { + size_t result = 1; + result = result * 31 + PigeonInternalDeepHash(uuid_); + result = result * 31 + PigeonInternalDeepHash(characteristics_); + return result; +} + +size_t PigeonInternalDeepHash(const UniversalBlePeripheralService& v) { + return v.Hash(); +} + +// UniversalBlePeripheralCharacteristic + +UniversalBlePeripheralCharacteristic::UniversalBlePeripheralCharacteristic( + const std::string& uuid, + const EncodableList& properties, + const EncodableList& permissions, + const EncodableList& descriptors) + : uuid_(uuid), + properties_(properties), + permissions_(permissions), + descriptors_(descriptors) {} + +UniversalBlePeripheralCharacteristic::UniversalBlePeripheralCharacteristic( + const std::string& uuid, + const EncodableList& properties, + const EncodableList& permissions, + const EncodableList& descriptors, + const std::vector* initial_value) + : uuid_(uuid), + properties_(properties), + permissions_(permissions), + descriptors_(descriptors), + initial_value_(initial_value ? std::optional>(*initial_value) : std::nullopt) {} + +const std::string& UniversalBlePeripheralCharacteristic::uuid() const { + return uuid_; +} + +void UniversalBlePeripheralCharacteristic::set_uuid(std::string_view value_arg) { + uuid_ = value_arg; +} + + +const EncodableList& UniversalBlePeripheralCharacteristic::properties() const { + return properties_; +} + +void UniversalBlePeripheralCharacteristic::set_properties(const EncodableList& value_arg) { + properties_ = value_arg; +} + + +const EncodableList& UniversalBlePeripheralCharacteristic::permissions() const { + return permissions_; +} + +void UniversalBlePeripheralCharacteristic::set_permissions(const EncodableList& value_arg) { + permissions_ = value_arg; +} + + +const EncodableList& UniversalBlePeripheralCharacteristic::descriptors() const { + return descriptors_; +} + +void UniversalBlePeripheralCharacteristic::set_descriptors(const EncodableList& value_arg) { + descriptors_ = value_arg; +} + + +const std::vector* UniversalBlePeripheralCharacteristic::initial_value() const { + return initial_value_ ? &(*initial_value_) : nullptr; +} + +void UniversalBlePeripheralCharacteristic::set_initial_value(const std::vector* value_arg) { + initial_value_ = value_arg ? std::optional>(*value_arg) : std::nullopt; +} + +void UniversalBlePeripheralCharacteristic::set_initial_value(const std::vector& value_arg) { + initial_value_ = value_arg; +} + + +EncodableList UniversalBlePeripheralCharacteristic::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(EncodableValue(descriptors_)); + list.push_back(initial_value_ ? EncodableValue(*initial_value_) : EncodableValue()); + return list; +} + +UniversalBlePeripheralCharacteristic UniversalBlePeripheralCharacteristic::FromEncodableList(const EncodableList& list) { + UniversalBlePeripheralCharacteristic decoded( + std::get(list[0]), + std::get(list[1]), + std::get(list[2]), + std::get(list[3])); + auto& encodable_initial_value = list[4]; + if (!encodable_initial_value.IsNull()) { + decoded.set_initial_value(std::get>(encodable_initial_value)); + } + return decoded; +} + +bool UniversalBlePeripheralCharacteristic::operator==(const UniversalBlePeripheralCharacteristic& other) const { + return PigeonInternalDeepEquals(uuid_, other.uuid_) && PigeonInternalDeepEquals(properties_, other.properties_) && PigeonInternalDeepEquals(permissions_, other.permissions_) && PigeonInternalDeepEquals(descriptors_, other.descriptors_) && PigeonInternalDeepEquals(initial_value_, other.initial_value_); +} + +bool UniversalBlePeripheralCharacteristic::operator!=(const UniversalBlePeripheralCharacteristic& other) const { + return !(*this == other); +} + +size_t UniversalBlePeripheralCharacteristic::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(initial_value_); + return result; +} + +size_t PigeonInternalDeepHash(const UniversalBlePeripheralCharacteristic& v) { + return v.Hash(); +} + +// UniversalBlePeripheralDescriptor + +UniversalBlePeripheralDescriptor::UniversalBlePeripheralDescriptor( + const std::string& uuid, + const EncodableList& permissions) + : uuid_(uuid), + permissions_(permissions) {} + +UniversalBlePeripheralDescriptor::UniversalBlePeripheralDescriptor( + const std::string& uuid, + const EncodableList& permissions, + const std::vector* initial_value) + : uuid_(uuid), + permissions_(permissions), + initial_value_(initial_value ? std::optional>(*initial_value) : std::nullopt) {} + +const std::string& UniversalBlePeripheralDescriptor::uuid() const { + return uuid_; +} + +void UniversalBlePeripheralDescriptor::set_uuid(std::string_view value_arg) { + uuid_ = value_arg; +} + + +const EncodableList& UniversalBlePeripheralDescriptor::permissions() const { + return permissions_; +} + +void UniversalBlePeripheralDescriptor::set_permissions(const EncodableList& value_arg) { + permissions_ = value_arg; +} + + +const std::vector* UniversalBlePeripheralDescriptor::initial_value() const { + return initial_value_ ? &(*initial_value_) : nullptr; +} + +void UniversalBlePeripheralDescriptor::set_initial_value(const std::vector* value_arg) { + initial_value_ = value_arg ? std::optional>(*value_arg) : std::nullopt; +} + +void UniversalBlePeripheralDescriptor::set_initial_value(const std::vector& value_arg) { + initial_value_ = value_arg; +} + + +EncodableList UniversalBlePeripheralDescriptor::ToEncodableList() const { + EncodableList list; + list.reserve(3); + list.push_back(EncodableValue(uuid_)); + list.push_back(EncodableValue(permissions_)); + list.push_back(initial_value_ ? EncodableValue(*initial_value_) : EncodableValue()); + return list; +} + +UniversalBlePeripheralDescriptor UniversalBlePeripheralDescriptor::FromEncodableList(const EncodableList& list) { + UniversalBlePeripheralDescriptor decoded( + std::get(list[0]), + std::get(list[1])); + auto& encodable_initial_value = list[2]; + if (!encodable_initial_value.IsNull()) { + decoded.set_initial_value(std::get>(encodable_initial_value)); + } + return decoded; +} + +bool UniversalBlePeripheralDescriptor::operator==(const UniversalBlePeripheralDescriptor& other) const { + return PigeonInternalDeepEquals(uuid_, other.uuid_) && PigeonInternalDeepEquals(permissions_, other.permissions_) && PigeonInternalDeepEquals(initial_value_, other.initial_value_); +} + +bool UniversalBlePeripheralDescriptor::operator!=(const UniversalBlePeripheralDescriptor& other) const { + return !(*this == other); +} + +size_t UniversalBlePeripheralDescriptor::Hash() const { + size_t result = 1; + result = result * 31 + PigeonInternalDeepHash(uuid_); + result = result * 31 + PigeonInternalDeepHash(permissions_); + result = result * 31 + PigeonInternalDeepHash(initial_value_); + return result; +} + +size_t PigeonInternalDeepHash(const UniversalBlePeripheralDescriptor& v) { + return v.Hash(); +} + +// UniversalBlePeripheralWriteEvent + +UniversalBlePeripheralWriteEvent::UniversalBlePeripheralWriteEvent( + const std::string& device_id, + const std::string& service, + const std::string& characteristic, + const std::vector& value) + : device_id_(device_id), + service_(service), + characteristic_(characteristic), + value_(value) {} + +const std::string& UniversalBlePeripheralWriteEvent::device_id() const { + return device_id_; +} + +void UniversalBlePeripheralWriteEvent::set_device_id(std::string_view value_arg) { + device_id_ = value_arg; +} + + +const std::string& UniversalBlePeripheralWriteEvent::service() const { + return service_; +} + +void UniversalBlePeripheralWriteEvent::set_service(std::string_view value_arg) { + service_ = value_arg; +} + + +const std::string& UniversalBlePeripheralWriteEvent::characteristic() const { + return characteristic_; +} + +void UniversalBlePeripheralWriteEvent::set_characteristic(std::string_view value_arg) { + characteristic_ = value_arg; +} + + +const std::vector& UniversalBlePeripheralWriteEvent::value() const { + return value_; +} + +void UniversalBlePeripheralWriteEvent::set_value(const std::vector& value_arg) { + value_ = value_arg; +} + + +EncodableList UniversalBlePeripheralWriteEvent::ToEncodableList() const { + EncodableList list; + list.reserve(4); + list.push_back(EncodableValue(device_id_)); + list.push_back(EncodableValue(service_)); + list.push_back(EncodableValue(characteristic_)); + list.push_back(EncodableValue(value_)); + return list; +} + +UniversalBlePeripheralWriteEvent UniversalBlePeripheralWriteEvent::FromEncodableList(const EncodableList& list) { + UniversalBlePeripheralWriteEvent decoded( + std::get(list[0]), + std::get(list[1]), + std::get(list[2]), + std::get>(list[3])); + return decoded; +} + +bool UniversalBlePeripheralWriteEvent::operator==(const UniversalBlePeripheralWriteEvent& other) const { + return PigeonInternalDeepEquals(device_id_, other.device_id_) && PigeonInternalDeepEquals(service_, other.service_) && PigeonInternalDeepEquals(characteristic_, other.characteristic_) && PigeonInternalDeepEquals(value_, other.value_); +} + +bool UniversalBlePeripheralWriteEvent::operator!=(const UniversalBlePeripheralWriteEvent& other) const { + return !(*this == other); +} + +size_t UniversalBlePeripheralWriteEvent::Hash() const { + size_t result = 1; + result = result * 31 + PigeonInternalDeepHash(device_id_); + result = result * 31 + PigeonInternalDeepHash(service_); + result = result * 31 + PigeonInternalDeepHash(characteristic_); + result = result * 31 + PigeonInternalDeepHash(value_); + return result; +} + +size_t PigeonInternalDeepHash(const UniversalBlePeripheralWriteEvent& v) { + return v.Hash(); +} + // AndroidOptions AndroidOptions::AndroidOptions() {} @@ -408,6 +1101,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 +1165,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 +1237,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 +1328,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,12 +1389,31 @@ 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_); +} + +bool UniversalManufacturerData::operator!=(const UniversalManufacturerData& other) const { + return !(*this == other); +} + +size_t UniversalManufacturerData::Hash() const { + size_t result = 1; + result = result * 31 + PigeonInternalDeepHash(company_identifier_); + result = result * 31 + PigeonInternalDeepHash(data_); + return result; +} + +size_t PigeonInternalDeepHash(const UniversalManufacturerData& v) { + return v.Hash(); +} + PigeonInternalCodecSerializer::PigeonInternalCodecSerializer() {} EncodableValue PigeonInternalCodecSerializer::ReadValueOfType( uint8_t type, - flutter::ByteStreamReader* stream) const { + ::flutter::ByteStreamReader* stream) const { switch (type) { case 129: { const auto& encodable_enum_arg = ReadValue(stream); @@ -653,28 +1443,43 @@ EncodableValue PigeonInternalCodecSerializer::ReadValueOfType( return CustomEncodableValue(UniversalBleDescriptor::FromEncodableList(std::get(ReadValue(stream)))); } case 136: { - return CustomEncodableValue(AndroidOptions::FromEncodableList(std::get(ReadValue(stream)))); + return CustomEncodableValue(UniversalBlePeripheralConfig::FromEncodableList(std::get(ReadValue(stream)))); } case 137: { - return CustomEncodableValue(UniversalScanConfig::FromEncodableList(std::get(ReadValue(stream)))); + return CustomEncodableValue(UniversalBlePeripheralService::FromEncodableList(std::get(ReadValue(stream)))); } case 138: { - return CustomEncodableValue(UniversalScanFilter::FromEncodableList(std::get(ReadValue(stream)))); + return CustomEncodableValue(UniversalBlePeripheralCharacteristic::FromEncodableList(std::get(ReadValue(stream)))); } case 139: { - return CustomEncodableValue(UniversalManufacturerDataFilter::FromEncodableList(std::get(ReadValue(stream)))); + return CustomEncodableValue(UniversalBlePeripheralDescriptor::FromEncodableList(std::get(ReadValue(stream)))); } case 140: { + return CustomEncodableValue(UniversalBlePeripheralWriteEvent::FromEncodableList(std::get(ReadValue(stream)))); + } + case 141: { + return CustomEncodableValue(AndroidOptions::FromEncodableList(std::get(ReadValue(stream)))); + } + case 142: { + return CustomEncodableValue(UniversalScanConfig::FromEncodableList(std::get(ReadValue(stream)))); + } + case 143: { + return CustomEncodableValue(UniversalScanFilter::FromEncodableList(std::get(ReadValue(stream)))); + } + case 144: { + return CustomEncodableValue(UniversalManufacturerDataFilter::FromEncodableList(std::get(ReadValue(stream)))); + } + case 145: { return CustomEncodableValue(UniversalManufacturerData::FromEncodableList(std::get(ReadValue(stream)))); } default: - return flutter::StandardCodecSerializer::ReadValueOfType(type, stream); + return ::flutter::StandardCodecSerializer::ReadValueOfType(type, stream); } } void PigeonInternalCodecSerializer::WriteValue( const EncodableValue& value, - flutter::ByteStreamWriter* stream) const { + ::flutter::ByteStreamWriter* stream) const { if (const CustomEncodableValue* custom_value = std::get_if(&value)) { if (custom_value->type() == typeid(UniversalBleLogLevel)) { stream->WriteByte(129); @@ -711,56 +1516,81 @@ void PigeonInternalCodecSerializer::WriteValue( WriteValue(EncodableValue(std::any_cast(*custom_value).ToEncodableList()), stream); return; } - if (custom_value->type() == typeid(AndroidOptions)) { + if (custom_value->type() == typeid(UniversalBlePeripheralConfig)) { stream->WriteByte(136); + WriteValue(EncodableValue(std::any_cast(*custom_value).ToEncodableList()), stream); + return; + } + if (custom_value->type() == typeid(UniversalBlePeripheralService)) { + stream->WriteByte(137); + WriteValue(EncodableValue(std::any_cast(*custom_value).ToEncodableList()), stream); + return; + } + if (custom_value->type() == typeid(UniversalBlePeripheralCharacteristic)) { + stream->WriteByte(138); + WriteValue(EncodableValue(std::any_cast(*custom_value).ToEncodableList()), stream); + return; + } + if (custom_value->type() == typeid(UniversalBlePeripheralDescriptor)) { + stream->WriteByte(139); + WriteValue(EncodableValue(std::any_cast(*custom_value).ToEncodableList()), stream); + return; + } + if (custom_value->type() == typeid(UniversalBlePeripheralWriteEvent)) { + stream->WriteByte(140); + WriteValue(EncodableValue(std::any_cast(*custom_value).ToEncodableList()), stream); + return; + } + if (custom_value->type() == typeid(AndroidOptions)) { + stream->WriteByte(141); WriteValue(EncodableValue(std::any_cast(*custom_value).ToEncodableList()), stream); return; } if (custom_value->type() == typeid(UniversalScanConfig)) { - stream->WriteByte(137); + stream->WriteByte(142); WriteValue(EncodableValue(std::any_cast(*custom_value).ToEncodableList()), stream); return; } if (custom_value->type() == typeid(UniversalScanFilter)) { - stream->WriteByte(138); + stream->WriteByte(143); WriteValue(EncodableValue(std::any_cast(*custom_value).ToEncodableList()), stream); return; } if (custom_value->type() == typeid(UniversalManufacturerDataFilter)) { - stream->WriteByte(139); + stream->WriteByte(144); WriteValue(EncodableValue(std::any_cast(*custom_value).ToEncodableList()), stream); return; } if (custom_value->type() == typeid(UniversalManufacturerData)) { - stream->WriteByte(140); + stream->WriteByte(145); WriteValue(EncodableValue(std::any_cast(*custom_value).ToEncodableList()), stream); return; } } - flutter::StandardCodecSerializer::WriteValue(value, stream); + ::flutter::StandardCodecSerializer::WriteValue(value, stream); } /// The codec used by UniversalBlePlatformChannel. -const flutter::StandardMessageCodec& UniversalBlePlatformChannel::GetCodec() { - return flutter::StandardMessageCodec::GetInstance(&PigeonInternalCodecSerializer::GetInstance()); +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, + ::flutter::BinaryMessenger* binary_messenger, UniversalBlePlatformChannel* api) { UniversalBlePlatformChannel::SetUp(binary_messenger, api, ""); } void UniversalBlePlatformChannel::SetUp( - flutter::BinaryMessenger* binary_messenger, + ::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) { + channel.SetMessageHandler([api](const EncodableValue& message, const ::flutter::MessageReply& reply) { try { api->GetBluetoothAvailabilityState([reply](ErrorOr&& output) { if (output.has_error()) { @@ -782,7 +1612,7 @@ void UniversalBlePlatformChannel::SetUp( { 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) { + 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); @@ -791,7 +1621,13 @@ void UniversalBlePlatformChannel::SetUp( 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); + const auto& encodable_with_android_bluetooth_advertise_arg = args.at(1); + if (encodable_with_android_bluetooth_advertise_arg.IsNull()) { + reply(WrapError("with_android_bluetooth_advertise_arg unexpectedly null.")); + return; + } + const auto& with_android_bluetooth_advertise_arg = std::get(encodable_with_android_bluetooth_advertise_arg); + ErrorOr output = api->HasPermissions(with_android_fine_location_arg, with_android_bluetooth_advertise_arg); if (output.has_error()) { reply(WrapError(output.error())); return; @@ -810,7 +1646,7 @@ void UniversalBlePlatformChannel::SetUp( { 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) { + 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); @@ -819,7 +1655,13 @@ void UniversalBlePlatformChannel::SetUp( 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& encodable_with_android_bluetooth_advertise_arg = args.at(1); + if (encodable_with_android_bluetooth_advertise_arg.IsNull()) { + reply(WrapError("with_android_bluetooth_advertise_arg unexpectedly null.")); + return; + } + const auto& with_android_bluetooth_advertise_arg = std::get(encodable_with_android_bluetooth_advertise_arg); + api->RequestPermissions(with_android_fine_location_arg, with_android_bluetooth_advertise_arg, [reply](std::optional&& output) { if (output.has_value()) { reply(WrapError(output.value())); return; @@ -839,7 +1681,7 @@ void UniversalBlePlatformChannel::SetUp( { 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) { + channel.SetMessageHandler([api](const EncodableValue& message, const ::flutter::MessageReply& reply) { try { api->EnableBluetooth([reply](ErrorOr&& output) { if (output.has_error()) { @@ -861,7 +1703,7 @@ void UniversalBlePlatformChannel::SetUp( { 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) { + channel.SetMessageHandler([api](const EncodableValue& message, const ::flutter::MessageReply& reply) { try { api->DisableBluetooth([reply](ErrorOr&& output) { if (output.has_error()) { @@ -883,7 +1725,7 @@ void UniversalBlePlatformChannel::SetUp( { 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) { + 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); @@ -909,7 +1751,7 @@ void UniversalBlePlatformChannel::SetUp( { 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) { + channel.SetMessageHandler([api](const EncodableValue& message, const ::flutter::MessageReply& reply) { try { std::optional output = api->StopScan(); if (output.has_value()) { @@ -930,7 +1772,7 @@ void UniversalBlePlatformChannel::SetUp( { 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) { + channel.SetMessageHandler([api](const EncodableValue& message, const ::flutter::MessageReply& reply) { try { ErrorOr output = api->IsScanning(); if (output.has_error()) { @@ -951,7 +1793,7 @@ void UniversalBlePlatformChannel::SetUp( { 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) { + 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); @@ -981,7 +1823,7 @@ void UniversalBlePlatformChannel::SetUp( { 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) { + 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); @@ -1009,7 +1851,7 @@ 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) { + 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); @@ -1056,7 +1898,7 @@ void UniversalBlePlatformChannel::SetUp( { 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) { + 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); @@ -1091,7 +1933,7 @@ void UniversalBlePlatformChannel::SetUp( { 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_device_id_arg = args.at(0); @@ -1132,7 +1974,7 @@ void UniversalBlePlatformChannel::SetUp( { 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_device_id_arg = args.at(0); @@ -1167,7 +2009,7 @@ void UniversalBlePlatformChannel::SetUp( { 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) { + 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); @@ -1220,7 +2062,7 @@ void UniversalBlePlatformChannel::SetUp( { 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) { + 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); @@ -1249,7 +2091,7 @@ void UniversalBlePlatformChannel::SetUp( { 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) { + 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); @@ -1278,7 +2120,7 @@ void UniversalBlePlatformChannel::SetUp( { 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) { + 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); @@ -1306,7 +2148,7 @@ void UniversalBlePlatformChannel::SetUp( { 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) { + 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); @@ -1335,7 +2177,7 @@ void UniversalBlePlatformChannel::SetUp( { 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 { const auto& args = std::get(message); const auto& encodable_device_id_arg = args.at(0); @@ -1363,7 +2205,7 @@ void UniversalBlePlatformChannel::SetUp( { 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 { const auto& args = std::get(message); const auto& encodable_device_id_arg = args.at(0); @@ -1392,7 +2234,7 @@ void UniversalBlePlatformChannel::SetUp( { 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_device_id_arg = args.at(0); @@ -1424,10 +2266,170 @@ void UniversalBlePlatformChannel::SetUp( channel.SetMessageHandler(nullptr); } } + { + BasicMessageChannel<> channel(binary_messenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.isPeripheralSupported" + prepended_suffix, &GetCodec()); + if (api != nullptr) { + channel.SetMessageHandler([api](const EncodableValue& message, const ::flutter::MessageReply& reply) { + try { + ErrorOr output = api->IsPeripheralSupported(); + 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.startPeripheral" + 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_config_arg = args.at(0); + if (encodable_config_arg.IsNull()) { + reply(WrapError("config_arg unexpectedly null.")); + return; + } + const auto& config_arg = std::any_cast(std::get(encodable_config_arg)); + api->StartPeripheral(config_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.stopPeripheral" + prepended_suffix, &GetCodec()); + if (api != nullptr) { + channel.SetMessageHandler([api](const EncodableValue& message, const ::flutter::MessageReply& reply) { + try { + api->StopPeripheral([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.updatePeripheralCharacteristicValue" + 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_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(1); + 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(2); + if (encodable_value_arg.IsNull()) { + reply(WrapError("value_arg unexpectedly null.")); + return; + } + const auto& value_arg = std::get>(encodable_value_arg); + api->UpdatePeripheralCharacteristicValue(service_arg, characteristic_arg, value_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.notifyPeripheralCharacteristic" + 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_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(1); + 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(2); + if (encodable_value_arg.IsNull()) { + reply(WrapError("value_arg unexpectedly null.")); + return; + } + const auto& value_arg = std::get>(encodable_value_arg); + const auto& encodable_indicate_arg = args.at(3); + if (encodable_indicate_arg.IsNull()) { + reply(WrapError("indicate_arg unexpectedly null.")); + return; + } + const auto& indicate_arg = std::get(encodable_indicate_arg); + api->NotifyPeripheralCharacteristic(service_arg, characteristic_arg, value_arg, indicate_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.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 { const auto& args = std::get(message); const auto& encodable_log_level_arg = args.at(0); @@ -1471,18 +2473,18 @@ 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) +UniversalBleCallbackChannel::UniversalBleCallbackChannel(::flutter::BinaryMessenger* binary_messenger) : binary_messenger_(binary_messenger), message_channel_suffix_("") {} UniversalBleCallbackChannel::UniversalBleCallbackChannel( - flutter::BinaryMessenger* binary_messenger, + ::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& UniversalBleCallbackChannel::GetCodec() { + return ::flutter::StandardMessageCodec::GetInstance(&PigeonInternalCodecSerializer::GetInstance()); } void UniversalBleCallbackChannel::OnAvailabilityChanged( @@ -1506,7 +2508,7 @@ void UniversalBleCallbackChannel::OnAvailabilityChanged( } } else { on_error(CreateConnectionError(channel_name)); - } + } }); } @@ -1535,7 +2537,7 @@ void UniversalBleCallbackChannel::OnPairStateChange( } } else { on_error(CreateConnectionError(channel_name)); - } + } }); } @@ -1560,7 +2562,7 @@ void UniversalBleCallbackChannel::OnScanResult( } } else { on_error(CreateConnectionError(channel_name)); - } + } }); } @@ -1591,7 +2593,7 @@ void UniversalBleCallbackChannel::OnValueChanged( } } else { on_error(CreateConnectionError(channel_name)); - } + } }); } @@ -1620,7 +2622,90 @@ void UniversalBleCallbackChannel::OnConnectionChanged( } } else { on_error(CreateConnectionError(channel_name)); - } + } + }); +} + +void UniversalBleCallbackChannel::OnPeripheralConnectionChanged( + const std::string& device_id_arg, + bool connected_arg, + std::function&& on_success, + std::function&& on_error) { + const std::string channel_name = "dev.flutter.pigeon.universal_ble.UniversalBleCallbackChannel.onPeripheralConnectionChanged" + message_channel_suffix_; + BasicMessageChannel<> channel(binary_messenger_, channel_name, &GetCodec()); + EncodableValue encoded_api_arguments = EncodableValue(EncodableList{ + EncodableValue(device_id_arg), + EncodableValue(connected_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::OnPeripheralWrite( + const UniversalBlePeripheralWriteEvent& event_arg, + std::function&& on_success, + std::function&& on_error) { + const std::string channel_name = "dev.flutter.pigeon.universal_ble.UniversalBleCallbackChannel.onPeripheralWrite" + message_channel_suffix_; + BasicMessageChannel<> channel(binary_messenger_, channel_name, &GetCodec()); + EncodableValue encoded_api_arguments = EncodableValue(EncodableList{ + CustomEncodableValue(event_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::OnPeripheralSubscriptionChanged( + const std::string& device_id_arg, + const std::string& service_arg, + const std::string& characteristic_arg, + bool subscribed_arg, + std::function&& on_success, + std::function&& on_error) { + const std::string channel_name = "dev.flutter.pigeon.universal_ble.UniversalBleCallbackChannel.onPeripheralSubscriptionChanged" + message_channel_suffix_; + BasicMessageChannel<> channel(binary_messenger_, channel_name, &GetCodec()); + EncodableValue encoded_api_arguments = EncodableValue(EncodableList{ + EncodableValue(device_id_arg), + EncodableValue(service_arg), + EncodableValue(characteristic_arg), + EncodableValue(subscribed_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)); + } }); } diff --git a/windows/src/generated/universal_ble.g.h b/windows/src/generated/universal_ble.g.h index 88cb517..d5507c3 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.4), 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 { @@ -152,9 +152,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,25 +172,29 @@ 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 PigeonInternalCodecSerializer; @@ -198,9 +202,9 @@ class UniversalBleScanResult { 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 +218,27 @@ 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 PigeonInternalCodecSerializer; std::string uuid_; - std::optional characteristics_; + std::optional<::flutter::EncodableList> characteristics_; }; @@ -240,27 +248,31 @@ 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 PigeonInternalCodecSerializer; std::string uuid_; - flutter::EncodableList properties_; - flutter::EncodableList descriptors_; + ::flutter::EncodableList properties_; + ::flutter::EncodableList descriptors_; }; @@ -273,9 +285,13 @@ 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 PigeonInternalCodecSerializer; @@ -283,6 +299,195 @@ class UniversalBleDescriptor { }; +// Generated class from Pigeon that represents data sent in messages. +class UniversalBlePeripheralConfig { + public: + // Constructs an object setting all fields. + explicit UniversalBlePeripheralConfig( + const std::string& advertised_name, + const ::flutter::EncodableList& services); + + const std::string& advertised_name() const; + void set_advertised_name(std::string_view value_arg); + + const ::flutter::EncodableList& services() const; + void set_services(const ::flutter::EncodableList& value_arg); + + bool operator==(const UniversalBlePeripheralConfig& other) const; + bool operator!=(const UniversalBlePeripheralConfig& 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 UniversalBlePeripheralConfig FromEncodableList(const ::flutter::EncodableList& list); + ::flutter::EncodableList ToEncodableList() const; + friend class UniversalBlePlatformChannel; + friend class UniversalBleCallbackChannel; + friend class PigeonInternalCodecSerializer; + std::string advertised_name_; + ::flutter::EncodableList services_; +}; + + +// Generated class from Pigeon that represents data sent in messages. +class UniversalBlePeripheralService { + public: + // Constructs an object setting all fields. + explicit UniversalBlePeripheralService( + const std::string& uuid, + 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); + + bool operator==(const UniversalBlePeripheralService& other) const; + bool operator!=(const UniversalBlePeripheralService& 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 UniversalBlePeripheralService FromEncodableList(const ::flutter::EncodableList& list); + ::flutter::EncodableList ToEncodableList() const; + friend class UniversalBlePlatformChannel; + friend class UniversalBleCallbackChannel; + friend class PigeonInternalCodecSerializer; + std::string uuid_; + ::flutter::EncodableList characteristics_; +}; + + +// Generated class from Pigeon that represents data sent in messages. +class UniversalBlePeripheralCharacteristic { + public: + // Constructs an object setting all non-nullable fields. + explicit UniversalBlePeripheralCharacteristic( + const std::string& uuid, + const ::flutter::EncodableList& properties, + const ::flutter::EncodableList& permissions, + const ::flutter::EncodableList& descriptors); + + // Constructs an object setting all fields. + explicit UniversalBlePeripheralCharacteristic( + const std::string& uuid, + const ::flutter::EncodableList& properties, + const ::flutter::EncodableList& permissions, + const ::flutter::EncodableList& descriptors, + const std::vector* initial_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); + + const std::vector* initial_value() const; + void set_initial_value(const std::vector* value_arg); + void set_initial_value(const std::vector& value_arg); + + bool operator==(const UniversalBlePeripheralCharacteristic& other) const; + bool operator!=(const UniversalBlePeripheralCharacteristic& 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 UniversalBlePeripheralCharacteristic FromEncodableList(const ::flutter::EncodableList& list); + ::flutter::EncodableList ToEncodableList() const; + friend class UniversalBlePlatformChannel; + friend class UniversalBleCallbackChannel; + friend class PigeonInternalCodecSerializer; + std::string uuid_; + ::flutter::EncodableList properties_; + ::flutter::EncodableList permissions_; + ::flutter::EncodableList descriptors_; + std::optional> initial_value_; +}; + + +// Generated class from Pigeon that represents data sent in messages. +class UniversalBlePeripheralDescriptor { + public: + // Constructs an object setting all non-nullable fields. + explicit UniversalBlePeripheralDescriptor( + const std::string& uuid, + const ::flutter::EncodableList& permissions); + + // Constructs an object setting all fields. + explicit UniversalBlePeripheralDescriptor( + const std::string& uuid, + const ::flutter::EncodableList& permissions, + const std::vector* initial_value); + + const std::string& uuid() const; + void set_uuid(std::string_view value_arg); + + const ::flutter::EncodableList& permissions() const; + void set_permissions(const ::flutter::EncodableList& value_arg); + + const std::vector* initial_value() const; + void set_initial_value(const std::vector* value_arg); + void set_initial_value(const std::vector& value_arg); + + bool operator==(const UniversalBlePeripheralDescriptor& other) const; + bool operator!=(const UniversalBlePeripheralDescriptor& 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 UniversalBlePeripheralDescriptor FromEncodableList(const ::flutter::EncodableList& list); + ::flutter::EncodableList ToEncodableList() const; + friend class UniversalBlePlatformChannel; + friend class UniversalBleCallbackChannel; + friend class PigeonInternalCodecSerializer; + std::string uuid_; + ::flutter::EncodableList permissions_; + std::optional> initial_value_; +}; + + +// Generated class from Pigeon that represents data sent in messages. +class UniversalBlePeripheralWriteEvent { + public: + // Constructs an object setting all fields. + explicit UniversalBlePeripheralWriteEvent( + const std::string& device_id, + const std::string& service, + const std::string& characteristic, + const std::vector& value); + + const std::string& device_id() const; + void set_device_id(std::string_view value_arg); + + const std::string& service() const; + void set_service(std::string_view value_arg); + + const std::string& characteristic() const; + void set_characteristic(std::string_view value_arg); + + const std::vector& value() const; + void set_value(const std::vector& value_arg); + + bool operator==(const UniversalBlePeripheralWriteEvent& other) const; + bool operator!=(const UniversalBlePeripheralWriteEvent& 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 UniversalBlePeripheralWriteEvent FromEncodableList(const ::flutter::EncodableList& list); + ::flutter::EncodableList ToEncodableList() const; + friend class UniversalBlePlatformChannel; + friend class UniversalBleCallbackChannel; + friend class PigeonInternalCodecSerializer; + std::string device_id_; + std::string service_; + std::string characteristic_; + std::vector value_; +}; + + // Android options to scan devices // [requestLocationPermission] is used to request location permission on Android 12+ (API 31+). // [scanMode] is used to set the scan mode for for Bluetooth LE scan. @@ -314,9 +519,13 @@ 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; @@ -345,9 +554,13 @@ 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 PigeonInternalCodecSerializer; @@ -362,28 +575,32 @@ 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 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,9 +627,13 @@ 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 PigeonInternalCodecSerializer; @@ -436,9 +657,13 @@ 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 PigeonInternalCodecSerializer; @@ -447,7 +672,7 @@ class UniversalManufacturerData { }; -class PigeonInternalCodecSerializer : public flutter::StandardCodecSerializer { +class PigeonInternalCodecSerializer : public ::flutter::StandardCodecSerializer { public: PigeonInternalCodecSerializer(); inline static PigeonInternalCodecSerializer& GetInstance() { @@ -456,12 +681,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 @@ -473,9 +698,12 @@ class UniversalBlePlatformChannel { UniversalBlePlatformChannel& operator=(const UniversalBlePlatformChannel&) = delete; virtual ~UniversalBlePlatformChannel() {} virtual void GetBluetoothAvailabilityState(std::function reply)> result) = 0; - virtual ErrorOr HasPermissions(bool with_android_fine_location) = 0; + virtual ErrorOr HasPermissions( + bool with_android_fine_location, + bool with_android_bluetooth_advertise) = 0; virtual void RequestPermissions( bool with_android_fine_location, + bool with_android_bluetooth_advertise, std::function reply)> result) = 0; virtual void EnableBluetooth(std::function reply)> result) = 0; virtual void DisableBluetooth(std::function reply)> result) = 0; @@ -497,7 +725,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 +750,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, @@ -532,20 +760,36 @@ class UniversalBlePlatformChannel { const std::string& device_id, int64_t priority, std::function reply)> result) = 0; + virtual ErrorOr IsPeripheralSupported() = 0; + virtual void StartPeripheral( + const UniversalBlePeripheralConfig& config, + std::function reply)> result) = 0; + virtual void StopPeripheral(std::function reply)> result) = 0; + virtual void UpdatePeripheralCharacteristicValue( + const std::string& service, + const std::string& characteristic, + const std::vector& value, + std::function reply)> result) = 0; + virtual void NotifyPeripheralCharacteristic( + const std::string& service, + const std::string& characteristic, + const std::vector& value, + bool indicate, + std::function reply)> result) = 0; 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 +798,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, @@ -586,8 +830,24 @@ class UniversalBleCallbackChannel { const std::string* error, std::function&& on_success, std::function&& on_error); + void OnPeripheralConnectionChanged( + const std::string& device_id, + bool connected, + std::function&& on_success, + std::function&& on_error); + void OnPeripheralWrite( + const UniversalBlePeripheralWriteEvent& event, + std::function&& on_success, + std::function&& on_error); + void OnPeripheralSubscriptionChanged( + const std::string& device_id, + const std::string& service, + const std::string& characteristic, + bool subscribed, + std::function&& on_success, + std::function&& on_error); private: - flutter::BinaryMessenger* binary_messenger_; + ::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 3bf1c2e..6598e44 100644 --- a/windows/src/universal_ble_plugin.cpp +++ b/windows/src/universal_ble_plugin.cpp @@ -120,13 +120,14 @@ void UniversalBlePlugin::DisableBluetooth( } ErrorOr -UniversalBlePlugin::HasPermissions(bool with_android_fine_location) { +UniversalBlePlugin::HasPermissions(bool with_android_fine_location, + bool with_android_bluetooth_advertise) { // Windows does not require runtime permissions for Bluetooth return true; } void UniversalBlePlugin::RequestPermissions( - bool with_android_fine_location, + bool with_android_fine_location, bool with_android_bluetooth_advertise, std::function reply)> result) { // Windows does not require runtime permissions for Bluetooth result(std::nullopt); @@ -444,6 +445,39 @@ void UniversalBlePlugin::RequestConnectionPriority( "requestConnectionPriority is not supported on Windows platform")); } +ErrorOr UniversalBlePlugin::IsPeripheralSupported() { return false; } + +void UniversalBlePlugin::StartPeripheral( + const UniversalBlePeripheralConfig &config, + std::function reply)> result) { + result(create_flutter_error( + UniversalBleErrorCode::kNotSupported, + "BLE peripheral mode is not implemented on Windows platform yet")); +} + +void UniversalBlePlugin::StopPeripheral( + std::function reply)> result) { + result(std::nullopt); +} + +void UniversalBlePlugin::UpdatePeripheralCharacteristicValue( + const std::string &service, const std::string &characteristic, + const std::vector &value, + std::function reply)> result) { + result(create_flutter_error( + UniversalBleErrorCode::kNotSupported, + "BLE peripheral mode is not implemented on Windows platform yet")); +} + +void UniversalBlePlugin::NotifyPeripheralCharacteristic( + const std::string &service, const std::string &characteristic, + const std::vector &value, bool indicate, + std::function reply)> result) { + result(create_flutter_error( + UniversalBleErrorCode::kNotSupported, + "BLE peripheral mode is not implemented on Windows platform yet")); +} + void UniversalBlePlugin::ReadRssi( const std::string &device_id, std::function reply)> result) { diff --git a/windows/src/universal_ble_plugin.h b/windows/src/universal_ble_plugin.h index 867ed41..0806af2 100644 --- a/windows/src/universal_ble_plugin.h +++ b/windows/src/universal_ble_plugin.h @@ -184,9 +184,10 @@ private: ErrorOr IsScanning() override; std::optional Connect(const std::string &device_id, const bool *auto_connect) override; std::optional Disconnect(const std::string &device_id) override; - ErrorOr HasPermissions(bool with_android_fine_location) override; + ErrorOr HasPermissions(bool with_android_fine_location, + bool with_android_bluetooth_advertise) override; void RequestPermissions( - bool with_android_fine_location, + bool with_android_fine_location, bool with_android_bluetooth_advertise, std::function reply)> result) override; void DiscoverServices(const std::string &device_id, bool with_descriptors, @@ -210,6 +211,20 @@ private: void RequestConnectionPriority( const std::string &device_id, int64_t priority, std::function reply)> result) override; + ErrorOr IsPeripheralSupported() override; + void StartPeripheral( + const UniversalBlePeripheralConfig &config, + std::function reply)> result) override; + void StopPeripheral( + std::function reply)> result) override; + void UpdatePeripheralCharacteristicValue( + const std::string &service, const std::string &characteristic, + const std::vector &value, + std::function reply)> result) override; + void NotifyPeripheralCharacteristic( + const std::string &service, const std::string &characteristic, + const std::vector &value, bool indicate, + std::function reply)> result) override; void ReadRssi(const std::string &device_id, std::function reply)> result) override; void IsPaired(const std::string &device_id,