diff --git a/apps/bench.py b/apps/bench.py index b86a38fb..6e6df3a0 100644 --- a/apps/bench.py +++ b/apps/bench.py @@ -1109,7 +1109,7 @@ class Central(Connection.Listener): transport, peripheral_address, classic, - role_factory, + scenario_factory, mode_factory, connection_interval, phy, @@ -1122,7 +1122,7 @@ class Central(Connection.Listener): self.transport = transport self.peripheral_address = peripheral_address self.classic = classic - self.role_factory = role_factory + self.scenario_factory = scenario_factory self.mode_factory = mode_factory self.authenticate = authenticate self.encrypt = encrypt or authenticate @@ -1175,7 +1175,7 @@ class Central(Connection.Listener): DEFAULT_CENTRAL_NAME, central_address, hci_source, hci_sink ) mode = self.mode_factory(self.device) - role = self.role_factory(mode) + scenario = self.scenario_factory(mode) self.device.classic_enabled = self.classic # Set up a pairing config factory with minimal requirements. @@ -1256,7 +1256,7 @@ class Central(Connection.Listener): await mode.on_connection(self.connection) - await role.run() + await scenario.run() await asyncio.sleep(DEFAULT_LINGER_TIME) await self.connection.disconnect() @@ -1287,7 +1287,7 @@ class Peripheral(Device.Listener, Connection.Listener): def __init__( self, transport, - role_factory, + scenario_factory, mode_factory, classic, extended_data_length, @@ -1295,11 +1295,11 @@ class Peripheral(Device.Listener, Connection.Listener): ): self.transport = transport self.classic = classic - self.role_factory = role_factory + self.scenario_factory = scenario_factory self.mode_factory = mode_factory self.extended_data_length = extended_data_length self.role_switch = role_switch - self.role = None + self.scenario = None self.mode = None self.device = None self.connection = None @@ -1319,7 +1319,7 @@ class Peripheral(Device.Listener, Connection.Listener): ) self.device.listener = self self.mode = self.mode_factory(self.device) - self.role = self.role_factory(self.mode) + self.scenario = self.scenario_factory(self.mode) self.device.classic_enabled = self.classic # Set up a pairing config factory with minimal requirements. @@ -1356,7 +1356,7 @@ class Peripheral(Device.Listener, Connection.Listener): print_connection(self.connection) await self.mode.on_connection(self.connection) - await self.role.run() + await self.scenario.run() await asyncio.sleep(DEFAULT_LINGER_TIME) def on_connection(self, connection): @@ -1385,7 +1385,7 @@ class Peripheral(Device.Listener, Connection.Listener): def on_disconnection(self, reason): logging.info(color(f'!!! Disconnection: reason={reason}', 'red')) self.connection = None - self.role.reset() + self.scenario.reset() if self.classic: AsyncRunner.spawn(self.device.set_discoverable(True)) @@ -1467,13 +1467,13 @@ def create_mode_factory(ctx, default_mode): # ----------------------------------------------------------------------------- -def create_role_factory(ctx, default_role): - role = ctx.obj['role'] - if role is None: - role = default_role +def create_scenario_factory(ctx, default_scenario): + scenario = ctx.obj['scenario'] + if scenario is None: + scenarion = default_scenario - def create_role(packet_io): - if role == 'sender': + def create_scenario(packet_io): + if scenario == 'send': return Sender( packet_io, start_delay=ctx.obj['start_delay'], @@ -1484,10 +1484,10 @@ def create_role_factory(ctx, default_role): packet_count=ctx.obj['packet_count'], ) - if role == 'receiver': + if scenario == 'receive': return Receiver(packet_io, ctx.obj['linger']) - if role == 'ping': + if scenario == 'ping': return Ping( packet_io, start_delay=ctx.obj['start_delay'], @@ -1498,12 +1498,12 @@ def create_role_factory(ctx, default_role): packet_count=ctx.obj['packet_count'], ) - if role == 'pong': + if scenario == 'pong': return Pong(packet_io, ctx.obj['linger']) - raise ValueError('invalid role') + raise ValueError('invalid scenario') - return create_role + return create_scenario # ----------------------------------------------------------------------------- @@ -1511,7 +1511,7 @@ def create_role_factory(ctx, default_role): # ----------------------------------------------------------------------------- @click.group() @click.option('--device-config', metavar='FILENAME', help='Device configuration file') -@click.option('--role', type=click.Choice(['sender', 'receiver', 'ping', 'pong'])) +@click.option('--scenario', type=click.Choice(['send', 'receive', 'ping', 'pong'])) @click.option( '--mode', type=click.Choice( @@ -1606,7 +1606,7 @@ def create_role_factory(ctx, default_role): metavar='SIZE', type=click.IntRange(8, 8192), default=500, - help='Packet size (client or ping role)', + help='Packet size (send or ping scenario)', ) @click.option( '--packet-count', @@ -1614,7 +1614,7 @@ def create_role_factory(ctx, default_role): metavar='COUNT', type=int, default=10, - help='Packet count (client or ping role)', + help='Packet count (send or ping scenario)', ) @click.option( '--start-delay', @@ -1622,7 +1622,7 @@ def create_role_factory(ctx, default_role): metavar='SECONDS', type=int, default=1, - help='Start delay (client or ping role)', + help='Start delay (send or ping scenario)', ) @click.option( '--repeat', @@ -1630,7 +1630,7 @@ def create_role_factory(ctx, default_role): type=int, default=0, help=( - 'Repeat the run N times (client and ping roles)' + 'Repeat the run N times (send and ping scenario)' '(0, which is the fault, to run just once) ' ), ) @@ -1654,13 +1654,13 @@ def create_role_factory(ctx, default_role): @click.option( '--linger', is_flag=True, - help="Don't exit at the end of a run (server and pong roles)", + help="Don't exit at the end of a run (receive and pong scenarios)", ) @click.pass_context def bench( ctx, device_config, - role, + scenario, mode, att_mtu, extended_data_length, @@ -1686,7 +1686,7 @@ def bench( ): ctx.ensure_object(dict) ctx.obj['device_config'] = device_config - ctx.obj['role'] = role + ctx.obj['scenario'] = scenario ctx.obj['mode'] = mode ctx.obj['att_mtu'] = att_mtu ctx.obj['rfcomm_channel'] = rfcomm_channel @@ -1740,7 +1740,7 @@ def central( ctx, transport, peripheral_address, connection_interval, phy, authenticate, encrypt ): """Run as a central (initiates the connection)""" - role_factory = create_role_factory(ctx, 'sender') + scenario_factory = create_scenario_factory(ctx, 'send') mode_factory = create_mode_factory(ctx, 'gatt-client') classic = ctx.obj['classic'] @@ -1749,7 +1749,7 @@ def central( transport, peripheral_address, classic, - role_factory, + scenario_factory, mode_factory, connection_interval, phy, @@ -1767,13 +1767,13 @@ def central( @click.pass_context def peripheral(ctx, transport): """Run as a peripheral (waits for a connection)""" - role_factory = create_role_factory(ctx, 'receiver') + scenario_factory = create_scenario_factory(ctx, 'receive') mode_factory = create_mode_factory(ctx, 'gatt-server') async def run_peripheral(): await Peripheral( transport, - role_factory, + scenario_factory, mode_factory, ctx.obj['classic'], ctx.obj['extended_data_length'], diff --git a/docs/mkdocs/src/apps_and_tools/bench.md b/docs/mkdocs/src/apps_and_tools/bench.md index be68161a..84a9c936 100644 --- a/docs/mkdocs/src/apps_and_tools/bench.md +++ b/docs/mkdocs/src/apps_and_tools/bench.md @@ -11,32 +11,44 @@ Usage: bumble-bench [OPTIONS] COMMAND [ARGS]... Options: --device-config FILENAME Device configuration file - --role [sender|receiver|ping|pong] + --scenario [send|receive|ping|pong] --mode [gatt-client|gatt-server|l2cap-client|l2cap-server|rfcomm-client|rfcomm-server] --att-mtu MTU GATT MTU (gatt-client mode) [23<=x<=517] --extended-data-length TEXT Request a data length upon connection, specified as tx_octets/tx_time - --rfcomm-channel INTEGER RFComm channel to use + --role-switch [central|peripheral] + Request role switch upon connection (central + or peripheral) + --rfcomm-channel INTEGER RFComm channel to use (specify 0 for channel + discovery via SDP) --rfcomm-uuid TEXT RFComm service UUID to use (ignored if --rfcomm-channel is not 0) + --rfcomm-l2cap-mtu INTEGER RFComm L2CAP MTU + --rfcomm-max-frame-size INTEGER + RFComm maximum frame size + --rfcomm-initial-credits INTEGER + RFComm initial credits + --rfcomm-max-credits INTEGER RFComm max credits + --rfcomm-credits-threshold INTEGER + RFComm credits threshold --l2cap-psm INTEGER L2CAP PSM to use --l2cap-mtu INTEGER L2CAP MTU to use --l2cap-mps INTEGER L2CAP MPS to use --l2cap-max-credits INTEGER L2CAP maximum number of credits allowed for the peer - -s, --packet-size SIZE Packet size (client or ping role) - [8<=x<=4096] - -c, --packet-count COUNT Packet count (client or ping role) - -sd, --start-delay SECONDS Start delay (client or ping role) - --repeat N Repeat the run N times (client and ping - roles)(0, which is the fault, to run just + -s, --packet-size SIZE Packet size (send or ping scenario) + [8<=x<=8192] + -c, --packet-count COUNT Packet count (send or ping scenario) + -sd, --start-delay SECONDS Start delay (send or ping scenario) + --repeat N Repeat the run N times (send and ping + scenario)(0, which is the fault, to run just once) --repeat-delay SECONDS Delay, in seconds, between repeats --pace MILLISECONDS Wait N milliseconds between packets (0, which is the fault, to send as fast as possible) - --linger Don't exit at the end of a run (server and - pong roles) + --linger Don't exit at the end of a run (receive and + pong scenarios) --help Show this message and exit. Commands: @@ -82,7 +94,7 @@ Device 1 mode | Device 2 mode Device 1 role | Device 2 role --------------|-------------- -``sender`` | ``receiver`` +``send`` | ``receive`` ``ping`` | ``pong`` @@ -137,12 +149,12 @@ the other on `usb:1`, and two consoles/terminals. We will run a command in each. !!! example "Ping/Pong Latency" In the first console/terminal: ``` - $ bumble-bench --role pong peripheral usb:0 + $ bumble-bench --scenario pong peripheral usb:0 ``` In the second console/terminal: ``` - $ bumble-bench --role ping central usb:1 + $ bumble-bench --scenario ping central usb:1 ``` !!! example "Reversed modes with GATT and custom connection interval" @@ -170,10 +182,10 @@ the other on `usb:1`, and two consoles/terminals. We will run a command in each. !!! example "Reversed roles with L2CAP" In the first console/terminal: ``` - $ bumble-bench --mode l2cap-client --role sender peripheral usb:0 + $ bumble-bench --mode l2cap-client --scenario send peripheral usb:0 ``` In the second console/terminal: ``` - $ bumble-bench --mode l2cap-server --role receiver central usb:1 + $ bumble-bench --mode l2cap-server --scenario receive central usb:1 ``` 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 765cc9ed..21c66e58 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 @@ -17,86 +17,113 @@ package com.github.google.bumble.btbench; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothManager; import android.content.Context; -import android.renderscript.RSInvalidStateException; -import android.util.Log; import com.google.android.mobly.snippet.Snippet; import com.google.android.mobly.snippet.rpc.Rpc; + import androidx.test.core.app.ApplicationProvider; import org.json.JSONException; import org.json.JSONObject; - public class AutomationSnippet implements Snippet { private static final String TAG = "btbench.snippet"; private final BluetoothAdapter mBluetoothAdapter; - private AppViewModel rfcommServerModel; - private RfcommServer rfcommServer; - private AppViewModel l2capServerModel; - private L2capServer l2capServer; + private final Context mContext; public AutomationSnippet() { - Context context = ApplicationProvider.getApplicationContext(); - BluetoothManager bluetoothManager = context.getSystemService(BluetoothManager.class); + mContext = ApplicationProvider.getApplicationContext(); + BluetoothManager bluetoothManager = mContext.getSystemService(BluetoothManager.class); mBluetoothAdapter = bluetoothManager.getAdapter(); if (mBluetoothAdapter == null) { throw new RuntimeException("bluetooth not supported"); } } - private static JSONObject throughputStats(AppViewModel model) throws JSONException { + private void runScenario(AppViewModel model, String mode, String scenario) { + Mode runner; + switch (mode) { + case "rfcomm-client": + runner = new RfcommClient(model, mBluetoothAdapter, (PacketIO packetIO) -> createIoClient(model, scenario, packetIO)); + break; + + case "rfcomm-server": + runner = new RfcommServer(model, mBluetoothAdapter, (PacketIO packetIO) -> createIoClient(model, scenario, packetIO)); + break; + + case "l2cap-client": + runner = new L2capClient(model, mBluetoothAdapter, mContext, (PacketIO packetIO) -> createIoClient(model, scenario, packetIO)); + break; + + case "l2cap-server": + runner = new L2capServer(model, mBluetoothAdapter, (PacketIO packetIO) -> createIoClient(model, scenario, packetIO)); + break; + + default: + return; + } + + runner.run(true); + } + + private IoClient createIoClient(AppViewModel model, String scenario, PacketIO packetIO) { + switch (scenario) { + case "send": + return new Sender(model, packetIO); + + case "receive": + return new Receiver(model, packetIO); + + case "ping": + return new Pinger(model, packetIO); + + case "pong": + return new Ponger(model, packetIO); + + default: + return null; + } + } + + private static JSONObject resultFromModel(AppViewModel model) throws JSONException { JSONObject result = new JSONObject(); JSONObject stats = new JSONObject(); result.put("stats", stats); JSONObject throughputStats = new JSONObject(); stats.put("throughput", throughputStats); throughputStats.put("average", model.getThroughput()); + JSONObject rttStats = new JSONObject(); + stats.put("rtt", rttStats); + rttStats.put("compound", model.getStats()); return result; } - @Rpc(description = "Run an RFComm client throughput test") - public JSONObject runRfcommClient(String peerBluetoothAddress, int packetCount, int packetSize) throws JSONException { - assert(mBluetoothAdapter != null); + @Rpc(description = "Run a scenario in RFComm Client mode") + public JSONObject runRfcommClient(String scenario, String peerBluetoothAddress, int packetCount, int packetSize, int packetInterval) throws JSONException { + assert (mBluetoothAdapter != null); AppViewModel model = new AppViewModel(); model.setPeerBluetoothAddress(peerBluetoothAddress); model.setSenderPacketCount(packetCount); model.setSenderPacketSize(packetSize); + model.setSenderPacketInterval(packetInterval); - //RfcommClient rfCommClient = new RfcommClient(model, mBluetoothAdapter); - //rfCommClient.run(true); - return throughputStats(model); + runScenario(model, "rfcomm-client", scenario); + return resultFromModel(model); } - @Rpc(description = "Run an L2CAP client throughput test") - public JSONObject runL2capClient(String peerBluetoothAddress, int psm, boolean use_2m_phy, int packetCount, int packetSize) throws JSONException { - assert(mBluetoothAdapter != null); + @Rpc(description = "Run a scenario in L2CAP Client mode") + public JSONObject runL2capClient(String scenario, String peerBluetoothAddress, int psm, boolean use_2m_phy, int packetCount, int packetSize, int packetInterval) throws JSONException { + assert (mBluetoothAdapter != null); AppViewModel model = new AppViewModel(); model.setPeerBluetoothAddress(peerBluetoothAddress); model.setL2capPsm(psm); model.setUse2mPhy(use_2m_phy); model.setSenderPacketCount(packetCount); model.setSenderPacketSize(packetSize); + model.setSenderPacketInterval(packetInterval); - Context context = ApplicationProvider.getApplicationContext(); - //L2capClient l2capClient = new L2capClient(model, mBluetoothAdapter, context); - //l2capClient.run(true); - return throughputStats(model); - } - - @Rpc(description = "Run an RFComm server") - public JSONObject runRfcommServer() throws JSONException { - assert(mBluetoothAdapter != null); - if (rfcommServerModel != null) { - rfcommServerModel.abort(); - rfcommServerModel = null; - rfcommServer = null; - } - rfcommServerModel = new AppViewModel(); - //rfcommServer = new RfcommServer(rfcommServerModel, mBluetoothAdapter); - //rfcommServer.run(true); - - return new JSONObject(); + runScenario(model, "l2cap-client", scenario); + return resultFromModel(model); } @Override 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 d284ef5c..1fed7bc6 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 @@ -168,12 +168,25 @@ class MainActivity : ComponentActivity() { appViewModel.senderPacketInterval = packetInterval } appViewModel.updateSenderPacketSizeSlider() + intent.getStringExtra("scenario")?.let { + when (it) { + "send" -> appViewModel.scenario = SEND_SCENARIO + "receive" -> appViewModel.scenario = RECEIVE_SCENARIO + "ping" -> appViewModel.scenario = PING_SCENARIO + "pong" -> appViewModel.scenario = PONG_SCENARIO + } + } + intent.getStringExtra("mode")?.let { + when (it) { + "rfcomm-client" -> appViewModel.mode = RFCOMM_CLIENT_MODE + "rfcomm-server" -> appViewModel.mode = RFCOMM_SERVER_MODE + "l2cap-client" -> appViewModel.mode = L2CAP_CLIENT_MODE + "l2cap-server" -> appViewModel.mode = L2CAP_SERVER_MODE + } + } intent.getStringExtra("autostart")?.let { when (it) { - "rfcomm-client" -> runRfcommClient() - "rfcomm-server" -> runRfcommServer() - "l2cap-client" -> runL2capClient() - "l2cap-server" -> runL2capServer() + "run-scenario" -> runScenario() "scan-start" -> runScan(true) "stop-start" -> runScan(false) } @@ -200,6 +213,11 @@ class MainActivity : ComponentActivity() { runner.run(false) } + private fun runScan(startScan: Boolean) { + val scan = bluetoothAdapter?.let { Scan(it) } + scan?.run(startScan) + } + private fun createIoClient(packetIo: PacketIO): IoClient { return when (appViewModel.scenario) { SEND_SCENARIO -> Sender(appViewModel, packetIo) @@ -210,30 +228,6 @@ class MainActivity : ComponentActivity() { } } - private fun runRfcommClient() { -// val rfcommClient = bluetoothAdapter?.let { RfcommClient(appViewModel, it) } -// rfcommClient?.run() - } - - private fun runRfcommServer() { -// val rfcommServer = bluetoothAdapter?.let { RfcommServer(appViewModel, it) } -// rfcommServer?.run() - } - - private fun runL2capClient() { -// val l2capClient = bluetoothAdapter?.let { L2capClient(appViewModel, it, baseContext) } -// l2capClient?.run() - } - - private fun runL2capServer() { -// val l2capServer = bluetoothAdapter?.let { L2capServer(appViewModel, it) } -// l2capServer?.run() - } - - private fun runScan(startScan: Boolean) { - val scan = bluetoothAdapter?.let { Scan(it) } - scan?.run(startScan) - } @SuppressLint("MissingPermission") fun becomeDiscoverable() { diff --git a/extras/android/BtBench/app/src/main/java/com/github/google/bumble/btbench/SocketClient.kt b/extras/android/BtBench/app/src/main/java/com/github/google/bumble/btbench/SocketClient.kt index 2a0f1111..4a41b2e0 100644 --- a/extras/android/BtBench/app/src/main/java/com/github/google/bumble/btbench/SocketClient.kt +++ b/extras/android/BtBench/app/src/main/java/com/github/google/bumble/btbench/SocketClient.kt @@ -64,6 +64,7 @@ class SocketClient( try { ioClient.run() + socket.close() } catch (error: IOException) { Log.info("run ended abruptly") } diff --git a/extras/android/RemoteHCI/app/src/main/java/com/github/google/bumble/remotehci/HciProxy.java b/extras/android/RemoteHCI/app/src/main/java/com/github/google/bumble/remotehci/HciProxy.java index 1949a636..0b177f2f 100644 --- a/extras/android/RemoteHCI/app/src/main/java/com/github/google/bumble/remotehci/HciProxy.java +++ b/extras/android/RemoteHCI/app/src/main/java/com/github/google/bumble/remotehci/HciProxy.java @@ -23,6 +23,7 @@ public class HciProxy { HciHal hciHal = HciHal.create(new HciHalCallback() { @Override public void onPacket(HciPacket.Type type, byte[] packet) { + Log.d(TAG, String.format("CONTROLLER->HOST: type=%s, size=%d", type, packet.length)); mServer.sendPacket(type, packet); switch (type) { @@ -83,7 +84,7 @@ public class HciProxy { @Override public void onPacket(HciPacket.Type type, byte[] packet) { - Log.d(TAG, String.format("onPacket: type=%s, size=%d", type, packet.length)); + Log.d(TAG, String.format("HOST->CONTROLLER: type=%s, size=%d", type, packet.length)); hciHal.sendPacket(type, packet); switch (type) {