From f368b5e518e7cb6e5c66301091ae73a46281e272 Mon Sep 17 00:00:00 2001 From: Gilles Boccon-Gibod Date: Mon, 3 Feb 2025 18:02:14 -0500 Subject: [PATCH] wip --- apps/bench.py | 1 + examples/mobly/bench/one_device_bench_test.py | 14 + examples/mobly/bench/sample_config.yml | 6 +- .../BtBench/app/src/main/AndroidManifest.xml | 2 +- .../google/bumble/btbench/Advertiser.kt | 39 +++ .../bumble/btbench/AutomationSnippet.java | 59 +++++ .../google/bumble/btbench/Connection.kt | 2 +- .../com/github/google/bumble/btbench/Gatt.kt | 23 ++ .../google/bumble/btbench/GattClient.kt | 27 +- .../google/bumble/btbench/GattServer.kt | 243 ++++++++++++++++++ .../google/bumble/btbench/L2capServer.kt | 25 +- .../google/bumble/btbench/MainActivity.kt | 3 + .../github/google/bumble/btbench/Ponger.kt | 8 + .../github/google/bumble/btbench/Receiver.kt | 4 + 14 files changed, 418 insertions(+), 38 deletions(-) create mode 100644 extras/android/BtBench/app/src/main/java/com/github/google/bumble/btbench/Advertiser.kt create mode 100644 extras/android/BtBench/app/src/main/java/com/github/google/bumble/btbench/Gatt.kt create mode 100644 extras/android/BtBench/app/src/main/java/com/github/google/bumble/btbench/GattServer.kt diff --git a/apps/bench.py b/apps/bench.py index 7d934ced..7bbba770 100644 --- a/apps/bench.py +++ b/apps/bench.py @@ -1607,6 +1607,7 @@ def create_scenario(packet_io): '--att-mtu', metavar='MTU', type=click.IntRange(23, 517), + default=517, help='GATT MTU (gatt-client mode)', ) @click.option( diff --git a/examples/mobly/bench/one_device_bench_test.py b/examples/mobly/bench/one_device_bench_test.py index adec626b..c7158854 100644 --- a/examples/mobly/bench/one_device_bench_test.py +++ b/examples/mobly/bench/one_device_bench_test.py @@ -50,6 +50,20 @@ def test_l2cap_client_send(self): final_status = self.dut.bench.waitForRunnerCompletion(runner["id"]) print("### Final status:", final_status) + def test_gatt_client_send(self): + runner = self.dut.bench.runGattClient( + "send", "F1:F1:F1:F1:F1:F1", 128, True, 100, 970, 100, "HIGH" + ) + print("### Initial status:", runner) + final_status = self.dut.bench.waitForRunnerCompletion(runner["id"]) + print("### Final status:", final_status) + + def test_gatt_server_receive(self): + runner = self.dut.bench.runGattServer("receive") + print("### Initial status:", runner) + final_status = self.dut.bench.waitForRunnerCompletion(runner["id"]) + print("### Final status:", final_status) + if __name__ == "__main__": test_runner.main() diff --git a/examples/mobly/bench/sample_config.yml b/examples/mobly/bench/sample_config.yml index 7a1a0d46..50823a48 100644 --- a/examples/mobly/bench/sample_config.yml +++ b/examples/mobly/bench/sample_config.yml @@ -2,8 +2,8 @@ TestBeds: - Name: BenchTestBed Controllers: AndroidDevice: - - serial: 37211FDJG000DJ + - serial: emulator-5554 local_bt_address: 94:45:60:5E:03:B0 - - serial: 23071FDEE001F7 - local_bt_address: DC:E5:5B:E5:51:2C + #- serial: 23071FDEE001F7 + # local_bt_address: DC:E5:5B:E5:51:2C diff --git a/extras/android/BtBench/app/src/main/AndroidManifest.xml b/extras/android/BtBench/app/src/main/AndroidManifest.xml index 6be34786..f522fb86 100644 --- a/extras/android/BtBench/app/src/main/AndroidManifest.xml +++ b/extras/android/BtBench/app/src/main/AndroidManifest.xml @@ -1,7 +1,7 @@ - + diff --git a/extras/android/BtBench/app/src/main/java/com/github/google/bumble/btbench/Advertiser.kt b/extras/android/BtBench/app/src/main/java/com/github/google/bumble/btbench/Advertiser.kt new file mode 100644 index 00000000..b7370a80 --- /dev/null +++ b/extras/android/BtBench/app/src/main/java/com/github/google/bumble/btbench/Advertiser.kt @@ -0,0 +1,39 @@ +package com.github.google.bumble.btbench + +import android.annotation.SuppressLint +import android.bluetooth.BluetoothAdapter +import android.bluetooth.le.AdvertiseCallback +import android.bluetooth.le.AdvertiseData +import android.bluetooth.le.AdvertiseSettings +import android.bluetooth.le.AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY +import android.os.Build +import java.util.logging.Logger + +private val Log = Logger.getLogger("btbench.advertiser") + +class Advertiser(private val bluetoothAdapter: BluetoothAdapter) : AdvertiseCallback() { + @SuppressLint("MissingPermission") + fun start() { + val advertiseSettingsBuilder = AdvertiseSettings.Builder() + .setAdvertiseMode(ADVERTISE_MODE_LOW_LATENCY) + .setConnectable(true) + advertiseSettingsBuilder.setDiscoverable(true) + val advertiseSettings = advertiseSettingsBuilder.build() + val advertiseData = AdvertiseData.Builder().build() + val scanData = AdvertiseData.Builder().setIncludeDeviceName(true).build() + bluetoothAdapter.bluetoothLeAdvertiser.startAdvertising(advertiseSettings, advertiseData, scanData, this) + } + + @SuppressLint("MissingPermission") + fun stop() { + bluetoothAdapter.bluetoothLeAdvertiser.stopAdvertising(this) + } + + override fun onStartFailure(errorCode: Int) { + Log.warning("failed to start advertising: $errorCode") + } + + override fun onStartSuccess(settingsInEffect: AdvertiseSettings) { + Log.info("advertising started: $settingsInEffect") + } +} \ No newline at end of file diff --git a/extras/android/BtBench/app/src/main/java/com/github/google/bumble/btbench/AutomationSnippet.java b/extras/android/BtBench/app/src/main/java/com/github/google/bumble/btbench/AutomationSnippet.java index 6cdbc5f1..ca7d41dc 100644 --- a/extras/android/BtBench/app/src/main/java/com/github/google/bumble/btbench/AutomationSnippet.java +++ b/extras/android/BtBench/app/src/main/java/com/github/google/bumble/btbench/AutomationSnippet.java @@ -112,6 +112,18 @@ private Runner runScenario(AppViewModel model, String mode, String scenario) { packetIO)); break; + case "gatt-client": + runnable = new GattClient(model, mBluetoothAdapter, mContext, + (PacketIO packetIO) -> createIoClient(model, scenario, + packetIO)); + break; + + case "gatt-server": + runnable = new GattServer(model, mBluetoothAdapter, mContext, + (PacketIO packetIO) -> createIoClient(model, scenario, + packetIO)); + break; + default: return null; } @@ -277,6 +289,53 @@ public JSONObject runL2capServer(String scenario, return runner.toJson(); } + @Rpc(description = "Run a scenario in GATT Client mode") + public JSONObject runGattClient(String scenario, String peerBluetoothAddress, + boolean use_2m_phy, int packetCount, int packetSize, + int packetInterval, @RpcOptional String connectionPriority, + @RpcOptional Integer startupDelay) throws JSONException { + // We only support "send" and "ping" for this mode for now + if (!(scenario.equals("send") || scenario.equals("ping"))) { + throw new InvalidParameterException( + "only 'send' and 'ping' are supported for this mode"); + } + + AppViewModel model = new AppViewModel(); + model.setPeerBluetoothAddress(peerBluetoothAddress); + model.setUse2mPhy(use_2m_phy); + model.setSenderPacketCount(packetCount); + model.setSenderPacketSize(packetSize); + model.setSenderPacketInterval(packetInterval); + if (connectionPriority != null) { + model.setConnectionPriority(connectionPriority); + } + if (startupDelay != null) { + model.setStartupDelay(startupDelay); + } + Runner runner = runScenario(model, "gatt-client", scenario); + assert runner != null; + return runner.toJson(); + } + + @Rpc(description = "Run a scenario in GATT Server mode") + public JSONObject runGattServer(String scenario, + @RpcOptional Integer startupDelay) throws JSONException { + // We only support "receive" and "pong" for this mode for now + if (!(scenario.equals("receive") || scenario.equals("pong"))) { + throw new InvalidParameterException( + "only 'receive' and 'pong' are supported for this mode"); + } + + AppViewModel model = new AppViewModel(); + if (startupDelay != null) { + model.setStartupDelay(startupDelay); + } + + Runner runner = runScenario(model, "gatt-server", scenario); + assert runner != null; + return runner.toJson(); + } + @Rpc(description = "Stop a Runner") public JSONObject stopRunner(String runnerId) throws JSONException { Runner runner = findRunner(runnerId); diff --git a/extras/android/BtBench/app/src/main/java/com/github/google/bumble/btbench/Connection.kt b/extras/android/BtBench/app/src/main/java/com/github/google/bumble/btbench/Connection.kt index 7f27c831..df465213 100644 --- a/extras/android/BtBench/app/src/main/java/com/github/google/bumble/btbench/Connection.kt +++ b/extras/android/BtBench/app/src/main/java/com/github/google/bumble/btbench/Connection.kt @@ -49,7 +49,7 @@ open class Connection( } @SuppressLint("MissingPermission") - fun disconnect() { + open fun disconnect() { gatt?.disconnect() } diff --git a/extras/android/BtBench/app/src/main/java/com/github/google/bumble/btbench/Gatt.kt b/extras/android/BtBench/app/src/main/java/com/github/google/bumble/btbench/Gatt.kt new file mode 100644 index 00000000..126c5bc0 --- /dev/null +++ b/extras/android/BtBench/app/src/main/java/com/github/google/bumble/btbench/Gatt.kt @@ -0,0 +1,23 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.github.google.bumble.btbench + +import java.util.UUID + +var CCCD_UUID = UUID.fromString("00002902-0000-1000-8000-00805F9B34FB") + +val BENCH_SERVICE_UUID = UUID.fromString("50DB505C-8AC4-4738-8448-3B1D9CC09CC5") +val BENCH_TX_UUID = UUID.fromString("E789C754-41A1-45F4-A948-A0A1A90DBA53") +val BENCH_RX_UUID = UUID.fromString("016A2CC7-E14B-4819-935F-1F56EAE4098D") diff --git a/extras/android/BtBench/app/src/main/java/com/github/google/bumble/btbench/GattClient.kt b/extras/android/BtBench/app/src/main/java/com/github/google/bumble/btbench/GattClient.kt index 5d60f6b5..fe7080e2 100644 --- a/extras/android/BtBench/app/src/main/java/com/github/google/bumble/btbench/GattClient.kt +++ b/extras/android/BtBench/app/src/main/java/com/github/google/bumble/btbench/GattClient.kt @@ -30,12 +30,6 @@ import kotlin.concurrent.thread private val Log = Logger.getLogger("btbench.gatt-client") -private var CCCD_UUID = UUID.fromString("00002902-0000-1000-8000-00805F9B34FB") - -private val SPEED_SERVICE_UUID = UUID.fromString("50DB505C-8AC4-4738-8448-3B1D9CC09CC5") -private val SPEED_TX_UUID = UUID.fromString("E789C754-41A1-45F4-A948-A0A1A90DBA53") -private val SPEED_RX_UUID = UUID.fromString("016A2CC7-E14B-4819-935F-1F56EAE4098D") - class GattClientConnection( viewModel: AppViewModel, @@ -52,7 +46,8 @@ class GattClientConnection( super.connect() // Check if we're already connected and have discovered the services - if (gatt?.getService(SPEED_SERVICE_UUID) != null) { + if (gatt?.getService(BENCH_SERVICE_UUID) != null) { + Log.fine("already connected") onServicesDiscovered(gatt, BluetoothGatt.GATT_SUCCESS) } } @@ -63,6 +58,7 @@ class GattClientConnection( ) { super.onConnectionStateChange(gatt, status, newState) if (status != BluetoothGatt.GATT_SUCCESS) { + Log.warning("onConnectionStateChange status=$status") discoveryDone.countDown() return } @@ -76,6 +72,8 @@ class GattClientConnection( @SuppressLint("MissingPermission") override fun onServicesDiscovered(gatt: BluetoothGatt?, status: Int) { + Log.fine("onServicesDiscovered") + if (status != BluetoothGatt.GATT_SUCCESS) { Log.warning("failed to discover services: ${status}") discoveryDone.countDown() @@ -83,7 +81,7 @@ class GattClientConnection( } // Find the service - val service = gatt!!.getService(SPEED_SERVICE_UUID) + val service = gatt!!.getService(BENCH_SERVICE_UUID) if (service == null) { Log.warning("GATT Service not found") discoveryDone.countDown() @@ -91,13 +89,13 @@ class GattClientConnection( } // Find the RX and TX characteristics - rxCharacteristic = service.getCharacteristic(SPEED_RX_UUID) + rxCharacteristic = service.getCharacteristic(BENCH_RX_UUID) if (rxCharacteristic == null) { Log.warning("GATT RX Characteristics not found") discoveryDone.countDown() return } - txCharacteristic = service.getCharacteristic(SPEED_TX_UUID) + txCharacteristic = service.getCharacteristic(BENCH_TX_UUID) if (txCharacteristic == null) { Log.warning("GATT TX Characteristics not found") discoveryDone.countDown() @@ -105,6 +103,7 @@ class GattClientConnection( } // Subscribe to the RX characteristic + Log.fine("subscribing to RX") gatt.setCharacteristicNotification(rxCharacteristic, true) val cccdDescriptor = rxCharacteristic!!.getDescriptor(CCCD_UUID) gatt.writeDescriptor(cccdDescriptor, BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE); @@ -132,7 +131,7 @@ class GattClientConnection( characteristic: BluetoothGattCharacteristic, value: ByteArray ) { - if (characteristic.uuid == SPEED_RX_UUID && packetSink != null) { + if (characteristic.uuid == BENCH_RX_UUID && packetSink != null) { val packet = Packet.from(value) packetSink!!.onPacket(packet) } @@ -163,6 +162,12 @@ class GattClientConnection( ) } + override + fun disconnect() { + super.disconnect() + discoveryDone.countDown() + } + fun waitForDiscoveryCompletion() { discoveryDone.await() } diff --git a/extras/android/BtBench/app/src/main/java/com/github/google/bumble/btbench/GattServer.kt b/extras/android/BtBench/app/src/main/java/com/github/google/bumble/btbench/GattServer.kt new file mode 100644 index 00000000..8a36ecc3 --- /dev/null +++ b/extras/android/BtBench/app/src/main/java/com/github/google/bumble/btbench/GattServer.kt @@ -0,0 +1,243 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.github.google.bumble.btbench + +import android.annotation.SuppressLint +import android.bluetooth.BluetoothAdapter +import android.bluetooth.BluetoothDevice +import android.bluetooth.BluetoothGatt +import android.bluetooth.BluetoothGattCharacteristic +import android.bluetooth.BluetoothGattDescriptor +import android.bluetooth.BluetoothGattServer +import android.bluetooth.BluetoothGattServerCallback +import android.bluetooth.BluetoothGattService +import android.bluetooth.BluetoothManager +import android.bluetooth.BluetoothStatusCodes +import android.content.Context +import androidx.core.content.ContextCompat +import java.io.IOException +import java.util.concurrent.CountDownLatch +import java.util.concurrent.LinkedBlockingQueue +import java.util.concurrent.Semaphore +import java.util.logging.Logger +import kotlin.concurrent.thread +import kotlin.experimental.and + +private val Log = Logger.getLogger("btbench.gatt-server") + +@SuppressLint("MissingPermission") +class GattServer( + private val viewModel: AppViewModel, + private val bluetoothAdapter: BluetoothAdapter, + context: Context, + private val createIoClient: (packetIo: PacketIO) -> IoClient +) : Mode, PacketIO, BluetoothGattServerCallback() { + override var packetSink: PacketSink? = null + private val gattServer: BluetoothGattServer + private val rxCharacteristic: BluetoothGattCharacteristic? + private val txCharacteristic: BluetoothGattCharacteristic? + private val notifySemaphore: Semaphore = Semaphore(1) + private val ready: CountDownLatch = CountDownLatch(1) + private var peerDevice: BluetoothDevice? = null + private var clientThread: Thread? = null + private var sinkQueue: LinkedBlockingQueue? = null + + init { + val bluetoothManager = ContextCompat.getSystemService(context, BluetoothManager::class.java) + gattServer = bluetoothManager!!.openGattServer(context, this) + val benchService = gattServer.getService(BENCH_SERVICE_UUID) + if (benchService == null) { + rxCharacteristic = BluetoothGattCharacteristic( + BENCH_RX_UUID, + BluetoothGattCharacteristic.PROPERTY_NOTIFY, + 0 + ) + txCharacteristic = BluetoothGattCharacteristic( + BENCH_TX_UUID, + BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE, + BluetoothGattCharacteristic.PERMISSION_WRITE + ) + val rxCCCD = BluetoothGattDescriptor( + CCCD_UUID, + BluetoothGattDescriptor.PERMISSION_READ or BluetoothGattDescriptor.PERMISSION_WRITE + ) + rxCharacteristic.addDescriptor(rxCCCD) + + val service = + BluetoothGattService(BENCH_SERVICE_UUID, BluetoothGattService.SERVICE_TYPE_PRIMARY) + service.addCharacteristic(rxCharacteristic) + service.addCharacteristic(txCharacteristic) + + gattServer.addService(service) + } else { + rxCharacteristic = benchService.getCharacteristic(BENCH_RX_UUID) + txCharacteristic = benchService.getCharacteristic(BENCH_TX_UUID) + } + } + + override fun onCharacteristicWriteRequest( + device: BluetoothDevice?, + requestId: Int, + characteristic: BluetoothGattCharacteristic?, + preparedWrite: Boolean, + responseNeeded: Boolean, + offset: Int, + value: ByteArray? + ) { + Log.info("onCharacteristicWriteRequest") + if (characteristic != null && characteristic.uuid == BENCH_TX_UUID) { + if (packetSink == null) { + Log.warning("no sink, dropping") + } else if (offset != 0) { + Log.warning("offset != 0") + } else if (value == null) { + Log.warning("no value") + } else { + // Deliver the packet in a separate thread so that we don't block this + // callback. + sinkQueue?.put(Packet.from(value)) + } + } + + if (responseNeeded) { + gattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, value) + } + } + + override fun onNotificationSent(device: BluetoothDevice?, status: Int) { + if (status == BluetoothGatt.GATT_SUCCESS) { + notifySemaphore.release() + } + } + + override fun onDescriptorWriteRequest( + device: BluetoothDevice?, + requestId: Int, + descriptor: BluetoothGattDescriptor?, + preparedWrite: Boolean, + responseNeeded: Boolean, + offset: Int, + value: ByteArray? + ) { + if (descriptor?.uuid == CCCD_UUID && descriptor?.characteristic?.uuid == BENCH_RX_UUID) { + if (offset == 0 && value?.size == 2) { + if (value[0].and(1).toInt() != 0) { + // Subscription + Log.fine("peer subscribed to RX") + peerDevice = device + ready.countDown() + } + } + } + + if (responseNeeded) { + gattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, value) + } + } + + @SuppressLint("MissingPermission") + override fun sendPacket(packet: Packet) { + if (peerDevice == null) { + Log.warning("no peer device, cannot send") + return + } + if (rxCharacteristic == null) { + Log.warning("no RX characteristic, cannot send") + return + } + + // Wait until we can notify + notifySemaphore.acquire() + + // Send the packet via a notification + val result = gattServer.notifyCharacteristicChanged( + peerDevice!!, + rxCharacteristic, + false, + packet.toBytes() + ) + if (result != BluetoothStatusCodes.SUCCESS) { + Log.warning("notifyCharacteristicChanged failed: $result") + notifySemaphore.release() + } + } + + override fun run() { + viewModel.running = true + + // Start advertising + Log.fine("starting advertiser") + val advertiser = Advertiser(bluetoothAdapter) + advertiser.start() + + clientThread = thread(name = "GattServer") { + // Wait for a subscriber + Log.info("waiting for RX subscriber") + viewModel.aborter = { + ready.countDown() + } + ready.await() + if (peerDevice == null) { + Log.warning("server interrupted") + viewModel.running = false + gattServer.close() + return@thread + } + Log.info("RX subscriber accepted") + + // Stop advertising + Log.info("stopping advertiser") + advertiser.stop() + + sinkQueue = LinkedBlockingQueue() + val sinkWriterThread = thread(name = "SinkWriter") { + while (true) { + try { + val packet = sinkQueue!!.take() + if (packetSink == null) { + Log.warning("no sink, dropping packet") + continue + } + packetSink!!.onPacket(packet) + } catch (error: InterruptedException) { + Log.warning("sink writer interrupted") + break + } + } + } + + val ioClient = createIoClient(this) + + try { + ioClient.run() + viewModel.status = "OK" + } catch (error: IOException) { + Log.info("run ended abruptly") + viewModel.status = "ABORTED" + viewModel.lastError = "IO_ERROR" + } finally { + sinkWriterThread.interrupt() + sinkWriterThread.join() + gattServer.close() + viewModel.running = false + } + } + } + + override fun waitForCompletion() { + clientThread?.join() + Log.info("server thread completed") + } +} \ No newline at end of file diff --git a/extras/android/BtBench/app/src/main/java/com/github/google/bumble/btbench/L2capServer.kt b/extras/android/BtBench/app/src/main/java/com/github/google/bumble/btbench/L2capServer.kt index bb989c62..69b31dc4 100644 --- a/extras/android/BtBench/app/src/main/java/com/github/google/bumble/btbench/L2capServer.kt +++ b/extras/android/BtBench/app/src/main/java/com/github/google/bumble/btbench/L2capServer.kt @@ -37,34 +37,15 @@ class L2capServer( @SuppressLint("MissingPermission") override fun run() { // Advertise so that the peer can find us and connect. - val callback = object : AdvertiseCallback() { - override fun onStartFailure(errorCode: Int) { - Log.warning("failed to start advertising: $errorCode") - } - - override fun onStartSuccess(settingsInEffect: AdvertiseSettings) { - Log.info("advertising started: $settingsInEffect") - } - } - val advertiseSettingsBuilder = AdvertiseSettings.Builder() - .setAdvertiseMode(ADVERTISE_MODE_LOW_LATENCY) - .setConnectable(true) - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { - advertiseSettingsBuilder.setDiscoverable(true) - } - val advertiseSettings = advertiseSettingsBuilder.build() - val advertiseData = AdvertiseData.Builder().build() - val scanData = AdvertiseData.Builder().setIncludeDeviceName(true).build() - val advertiser = bluetoothAdapter.bluetoothLeAdvertiser - + val advertiser = Advertiser(bluetoothAdapter) val serverSocket = bluetoothAdapter.listenUsingInsecureL2capChannel() viewModel.l2capPsm = serverSocket.psm Log.info("psm = $serverSocket.psm") socketServer = SocketServer(viewModel, serverSocket, createIoClient) socketServer!!.run( - { advertiser.stopAdvertising(callback) }, - { advertiser.startAdvertising(advertiseSettings, advertiseData, scanData, callback) } + { advertiser.stop() }, + { advertiser.start() } ) } diff --git a/extras/android/BtBench/app/src/main/java/com/github/google/bumble/btbench/MainActivity.kt b/extras/android/BtBench/app/src/main/java/com/github/google/bumble/btbench/MainActivity.kt index 131a26fc..94eddeab 100644 --- a/extras/android/BtBench/app/src/main/java/com/github/google/bumble/btbench/MainActivity.kt +++ b/extras/android/BtBench/app/src/main/java/com/github/google/bumble/btbench/MainActivity.kt @@ -211,6 +211,9 @@ class MainActivity : ComponentActivity() { GATT_CLIENT_MODE -> GattClient( appViewModel, bluetoothAdapter!!, baseContext, ::createIoClient ) + GATT_SERVER_MODE -> GattServer( + appViewModel, bluetoothAdapter!!, baseContext, ::createIoClient + ) else -> throw IllegalStateException() } diff --git a/extras/android/BtBench/app/src/main/java/com/github/google/bumble/btbench/Ponger.kt b/extras/android/BtBench/app/src/main/java/com/github/google/bumble/btbench/Ponger.kt index b2ddb7ee..16c366bb 100644 --- a/extras/android/BtBench/app/src/main/java/com/github/google/bumble/btbench/Ponger.kt +++ b/extras/android/BtBench/app/src/main/java/com/github/google/bumble/btbench/Ponger.kt @@ -14,6 +14,7 @@ package com.github.google.bumble.btbench +import java.util.concurrent.CountDownLatch import java.util.logging.Logger import kotlin.time.TimeSource @@ -23,6 +24,7 @@ class Ponger(private val viewModel: AppViewModel, private val packetIO: PacketIO private var startTime: TimeSource.Monotonic.ValueTimeMark = TimeSource.Monotonic.markNow() private var lastPacketTime: TimeSource.Monotonic.ValueTimeMark = TimeSource.Monotonic.markNow() private var expectedSequenceNumber: Int = 0 + private val done = CountDownLatch(1) init { packetIO.packetSink = this @@ -30,6 +32,7 @@ class Ponger(private val viewModel: AppViewModel, private val packetIO: PacketIO override fun run() { viewModel.clear() + done.await() } override fun abort() {} @@ -58,5 +61,10 @@ class Ponger(private val viewModel: AppViewModel, private val packetIO: PacketIO packetIO.sendPacket(AckPacket(packet.flags, packet.sequenceNumber)) viewModel.packetsSent += 1 + + if (packet.flags and Packet.LAST_FLAG != 0) { + Log.info("received last packet") + done.countDown() + } } } diff --git a/extras/android/BtBench/app/src/main/java/com/github/google/bumble/btbench/Receiver.kt b/extras/android/BtBench/app/src/main/java/com/github/google/bumble/btbench/Receiver.kt index 71055c1f..1dc17504 100644 --- a/extras/android/BtBench/app/src/main/java/com/github/google/bumble/btbench/Receiver.kt +++ b/extras/android/BtBench/app/src/main/java/com/github/google/bumble/btbench/Receiver.kt @@ -14,6 +14,7 @@ package com.github.google.bumble.btbench +import java.util.concurrent.CountDownLatch import java.util.logging.Logger import kotlin.time.DurationUnit import kotlin.time.TimeSource @@ -24,6 +25,7 @@ class Receiver(private val viewModel: AppViewModel, private val packetIO: Packet private var startTime: TimeSource.Monotonic.ValueTimeMark = TimeSource.Monotonic.markNow() private var lastPacketTime: TimeSource.Monotonic.ValueTimeMark = TimeSource.Monotonic.markNow() private var bytesReceived = 0 + private val done = CountDownLatch(1) init { packetIO.packetSink = this @@ -31,6 +33,7 @@ class Receiver(private val viewModel: AppViewModel, private val packetIO: Packet override fun run() { viewModel.clear() + done.await() } override fun abort() {} @@ -62,6 +65,7 @@ class Receiver(private val viewModel: AppViewModel, private val packetIO: Packet Log.info("throughput: $throughput") viewModel.throughput = throughput packetIO.sendPacket(AckPacket(packet.flags, packet.sequenceNumber)) + done.countDown() } } }