diff --git a/kable-core/api/android/kable-core.api b/kable-core/api/android/kable-core.api index 95069e6be..a71277c56 100644 --- a/kable-core/api/android/kable-core.api +++ b/kable-core/api/android/kable-core.api @@ -319,6 +319,7 @@ public abstract interface class com/juul/kable/Peripheral : kotlinx/coroutines/C public abstract fun getName ()Ljava/lang/String; public abstract fun getServices ()Lkotlinx/coroutines/flow/StateFlow; public abstract fun getState ()Lkotlinx/coroutines/flow/StateFlow; + public abstract fun maximumWriteValueLengthForType (Lcom/juul/kable/WriteType;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public abstract fun observe (Lcom/juul/kable/Characteristic;Lkotlin/jvm/functions/Function1;)Lkotlinx/coroutines/flow/Flow; public abstract fun read (Lcom/juul/kable/Characteristic;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public abstract fun read (Lcom/juul/kable/Descriptor;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; diff --git a/kable-core/api/jvm/kable-core.api b/kable-core/api/jvm/kable-core.api index c6cfa7420..193cfe5bd 100644 --- a/kable-core/api/jvm/kable-core.api +++ b/kable-core/api/jvm/kable-core.api @@ -235,6 +235,7 @@ public abstract interface class com/juul/kable/Peripheral : kotlinx/coroutines/C public abstract fun getName ()Ljava/lang/String; public abstract fun getServices ()Lkotlinx/coroutines/flow/StateFlow; public abstract fun getState ()Lkotlinx/coroutines/flow/StateFlow; + public abstract fun maximumWriteValueLengthForType (Lcom/juul/kable/WriteType;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public abstract fun observe (Lcom/juul/kable/Characteristic;Lkotlin/jvm/functions/Function1;)Lkotlinx/coroutines/flow/Flow; public abstract fun read (Lcom/juul/kable/Characteristic;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public abstract fun read (Lcom/juul/kable/Descriptor;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; diff --git a/kable-core/src/androidMain/kotlin/BluetoothDeviceAndroidPeripheral.kt b/kable-core/src/androidMain/kotlin/BluetoothDeviceAndroidPeripheral.kt index 7ab5bd4dc..a0fcf8eec 100644 --- a/kable-core/src/androidMain/kotlin/BluetoothDeviceAndroidPeripheral.kt +++ b/kable-core/src/androidMain/kotlin/BluetoothDeviceAndroidPeripheral.kt @@ -47,6 +47,9 @@ import kotlin.time.Duration // https://github.com/JuulLabs/kable/issues/295 private const val DISCOVER_SERVICES_RETRIES = 5 +private const val DEFAULT_ATT_MTU = 23 +private const val ATT_MTU_HEADER_SIZE = 3 + internal class BluetoothDeviceAndroidPeripheral( private val bluetoothDevice: BluetoothDevice, private val autoConnectPredicate: () -> Boolean, @@ -163,6 +166,9 @@ internal class BluetoothDeviceAndroidPeripheral( .requestConnectionPriority(priority.intValue) } + override suspend fun maximumWriteValueLengthForType(writeType: WriteType): Int = + (mtu.value ?: DEFAULT_ATT_MTU) - ATT_MTU_HEADER_SIZE + @ExperimentalApi // Experimental until Web Bluetooth advertisements APIs are stable. override suspend fun rssi(): Int = connectionOrThrow().execute { diff --git a/kable-core/src/appleMain/kotlin/CBPeripheralCoreBluetoothPeripheral.kt b/kable-core/src/appleMain/kotlin/CBPeripheralCoreBluetoothPeripheral.kt index 74b86e7bb..0c12abab3 100644 --- a/kable-core/src/appleMain/kotlin/CBPeripheralCoreBluetoothPeripheral.kt +++ b/kable-core/src/appleMain/kotlin/CBPeripheralCoreBluetoothPeripheral.kt @@ -27,6 +27,8 @@ import kotlinx.coroutines.flow.onSubscription import kotlinx.coroutines.flow.updateAndGet import kotlinx.coroutines.sync.withLock import kotlinx.io.IOException +import platform.CoreBluetooth.CBCharacteristicWriteWithResponse +import platform.CoreBluetooth.CBCharacteristicWriteWithoutResponse import platform.CoreBluetooth.CBDescriptor import platform.CoreBluetooth.CBManagerState import platform.CoreBluetooth.CBManagerStatePoweredOn @@ -144,6 +146,14 @@ internal class CBPeripheralCoreBluetoothPeripheral( ) } + override suspend fun maximumWriteValueLengthForType(writeType: WriteType): Int { + val type = when (writeType) { + WithResponse -> CBCharacteristicWriteWithResponse + WithoutResponse -> CBCharacteristicWriteWithoutResponse + } + return cbPeripheral.maximumWriteValueLengthForType(type).toInt() + } + @ExperimentalApi // Experimental until Web Bluetooth advertisements APIs are stable. @Throws(CancellationException::class, IOException::class) override suspend fun rssi(): Int = connectionOrThrow().execute { diff --git a/kable-core/src/commonMain/kotlin/Peripheral.kt b/kable-core/src/commonMain/kotlin/Peripheral.kt index 6bfe4866a..42184438e 100644 --- a/kable-core/src/commonMain/kotlin/Peripheral.kt +++ b/kable-core/src/commonMain/kotlin/Peripheral.kt @@ -153,6 +153,15 @@ public interface Peripheral : CoroutineScope { */ public val services: StateFlow?> + /** + * Return the current ATT MTU size, minus the size of the ATT headers (3 bytes). + * + * On Android, this will be the default (23 - 3) unless you called [requestMtu] when connecting. + * For iOS, this is automatically negotiated, and can also vary depending on the writeType. + * On JavaScript, this will return the default (23 - 3) every time as there is no ATT MTU property available. + */ + public suspend fun maximumWriteValueLengthForType(writeType: WriteType): Int + /** * On JavaScript, requires Chrome 79+ with the * `chrome://flags/#enable-experimental-web-platform-features` flag enabled. diff --git a/kable-core/src/jsMain/kotlin/BluetoothDeviceWebBluetoothPeripheral.kt b/kable-core/src/jsMain/kotlin/BluetoothDeviceWebBluetoothPeripheral.kt index 4358914b7..f56064cdf 100644 --- a/kable-core/src/jsMain/kotlin/BluetoothDeviceWebBluetoothPeripheral.kt +++ b/kable-core/src/jsMain/kotlin/BluetoothDeviceWebBluetoothPeripheral.kt @@ -32,6 +32,8 @@ import kotlin.coroutines.resumeWithException import kotlin.time.Duration private const val ADVERTISEMENT_RECEIVED = "advertisementreceived" +private const val DEFAULT_ATT_MTU = 23 +private const val ATT_MTU_HEADER_SIZE = 3 internal class BluetoothDeviceWebBluetoothPeripheral( private val bluetoothDevice: BluetoothDevice, @@ -114,6 +116,9 @@ internal class BluetoothDeviceWebBluetoothPeripheral( ) } + override suspend fun maximumWriteValueLengthForType(writeType: WriteType): Int = + DEFAULT_ATT_MTU - ATT_MTU_HEADER_SIZE + /** * Per [Web Bluetooth / Scanning Sample][https://googlechrome.github.io/samples/web-bluetooth/scan.html]: *