add startupDelay and connectionPriority params to BtBench snippets

This commit is contained in:
Gilles Boccon-Gibod
2025-02-03 18:00:46 -05:00
parent dedc0aca54
commit 3c7b5df7c5
7 changed files with 137 additions and 40 deletions
+10 -2
View File
@@ -28,7 +28,7 @@ class OneDeviceBenchTest(base_test.BaseTestClass):
def test_l2cap_client_ping(self): def test_l2cap_client_ping(self):
runner = self.dut.bench.runL2capClient( runner = self.dut.bench.runL2capClient(
"ping", "4B:2A:67:76:2B:E3", 128, True, 100, 970, 100 "ping", "4B:2A:67:76:2B:E3", 128, True, 100, 970, 100, "HIGH"
) )
print("### Initial status:", runner) print("### Initial status:", runner)
final_status = self.dut.bench.waitForRunnerCompletion(runner["id"]) final_status = self.dut.bench.waitForRunnerCompletion(runner["id"])
@@ -36,7 +36,15 @@ class OneDeviceBenchTest(base_test.BaseTestClass):
def test_l2cap_client_send(self): def test_l2cap_client_send(self):
runner = self.dut.bench.runL2capClient( runner = self.dut.bench.runL2capClient(
"send", "7E:90:D0:F2:7A:11", 131, True, 100, 970, 0 "send",
"F1:F1:F1:F1:F1:F1",
128,
True,
100,
970,
0,
"HIGH",
10000,
) )
print("### Initial status:", runner) print("### Initial status:", runner)
final_status = self.dut.bench.waitForRunnerCompletion(runner["id"]) final_status = self.dut.bench.waitForRunnerCompletion(runner["id"])
@@ -22,11 +22,13 @@ import androidx.test.core.app.ApplicationProvider;
import com.google.android.mobly.snippet.Snippet; import com.google.android.mobly.snippet.Snippet;
import com.google.android.mobly.snippet.rpc.Rpc; import com.google.android.mobly.snippet.rpc.Rpc;
import com.google.android.mobly.snippet.rpc.RpcOptional;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONException; import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
import java.io.IOException;
import java.security.InvalidParameterException; import java.security.InvalidParameterException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.UUID; import java.util.UUID;
@@ -71,12 +73,15 @@ public class AutomationSnippet implements Snippet {
private final Context mContext; private final Context mContext;
private final ArrayList<Runner> mRunners = new ArrayList<>(); private final ArrayList<Runner> mRunners = new ArrayList<>();
public AutomationSnippet() { public AutomationSnippet() throws IOException {
mContext = ApplicationProvider.getApplicationContext(); mContext = ApplicationProvider.getApplicationContext();
BluetoothManager bluetoothManager = mContext.getSystemService(BluetoothManager.class); BluetoothManager bluetoothManager = mContext.getSystemService(BluetoothManager.class);
mBluetoothAdapter = bluetoothManager.getAdapter(); mBluetoothAdapter = bluetoothManager.getAdapter();
if (mBluetoothAdapter == null) { if (mBluetoothAdapter == null) {
throw new RuntimeException("bluetooth not supported"); throw new IOException("bluetooth not supported");
}
if (!mBluetoothAdapter.isEnabled()) {
throw new IOException("bluetooth not enabled");
} }
} }
@@ -85,32 +90,34 @@ public class AutomationSnippet implements Snippet {
switch (mode) { switch (mode) {
case "rfcomm-client": case "rfcomm-client":
runnable = new RfcommClient(model, mBluetoothAdapter, runnable = new RfcommClient(model, mBluetoothAdapter,
(PacketIO packetIO) -> createIoClient(model, scenario, (PacketIO packetIO) -> createIoClient(model, scenario,
packetIO)); packetIO));
break; break;
case "rfcomm-server": case "rfcomm-server":
runnable = new RfcommServer(model, mBluetoothAdapter, runnable = new RfcommServer(model, mBluetoothAdapter,
(PacketIO packetIO) -> createIoClient(model, scenario, (PacketIO packetIO) -> createIoClient(model, scenario,
packetIO)); packetIO));
break; break;
case "l2cap-client": case "l2cap-client":
runnable = new L2capClient(model, mBluetoothAdapter, mContext, runnable = new L2capClient(model, mBluetoothAdapter, mContext,
(PacketIO packetIO) -> createIoClient(model, scenario, (PacketIO packetIO) -> createIoClient(model, scenario,
packetIO)); packetIO));
break; break;
case "l2cap-server": case "l2cap-server":
runnable = new L2capServer(model, mBluetoothAdapter, runnable = new L2capServer(model, mBluetoothAdapter,
(PacketIO packetIO) -> createIoClient(model, scenario, (PacketIO packetIO) -> createIoClient(model, scenario,
packetIO)); packetIO));
break; break;
default: default:
return null; return null;
} }
model.setMode(mode);
model.setScenario(scenario);
runnable.run(); runnable.run();
Runner runner = new Runner(runnable, mode, scenario, model); Runner runner = new Runner(runnable, mode, scenario, model);
mRunners.add(runner); mRunners.add(runner);
@@ -140,7 +147,21 @@ public class AutomationSnippet implements Snippet {
JSONObject result = new JSONObject(); JSONObject result = new JSONObject();
result.put("status", model.getStatus()); result.put("status", model.getStatus());
result.put("running", model.getRunning()); result.put("running", model.getRunning());
result.put("peer_bluetooth_address", model.getPeerBluetoothAddress());
result.put("mode", model.getMode());
result.put("scenario", model.getScenario());
result.put("sender_packet_size", model.getSenderPacketSize());
result.put("sender_packet_count", model.getSenderPacketCount());
result.put("sender_packet_interval", model.getSenderPacketInterval());
result.put("packets_sent", model.getPacketsSent());
result.put("packets_received", model.getPacketsReceived());
result.put("l2cap_psm", model.getL2capPsm()); result.put("l2cap_psm", model.getL2capPsm());
result.put("use_2m_phy", model.getUse2mPhy());
result.put("connection_priority", model.getConnectionPriority());
result.put("mtu", model.getMtu());
result.put("rx_phy", model.getRxPhy());
result.put("tx_phy", model.getTxPhy());
result.put("startup_delay", model.getStartupDelay());
if (model.getStatus().equals("OK")) { if (model.getStatus().equals("OK")) {
JSONObject stats = new JSONObject(); JSONObject stats = new JSONObject();
result.put("stats", stats); result.put("stats", stats);
@@ -167,12 +188,12 @@ public class AutomationSnippet implements Snippet {
@Rpc(description = "Run a scenario in RFComm Client mode") @Rpc(description = "Run a scenario in RFComm Client mode")
public JSONObject runRfcommClient(String scenario, String peerBluetoothAddress, int packetCount, public JSONObject runRfcommClient(String scenario, String peerBluetoothAddress, int packetCount,
int packetSize, int packetInterval) throws JSONException { int packetSize, int packetInterval,
assert (mBluetoothAdapter != null); @RpcOptional Integer startupDelay) throws JSONException {
// We only support "send" and "ping" for this mode for now // We only support "send" and "ping" for this mode for now
if (!(scenario.equals("send") || scenario.equals("ping"))) { if (!(scenario.equals("send") || scenario.equals("ping"))) {
throw new InvalidParameterException("only 'send' and 'ping' are supported for this mode"); throw new InvalidParameterException(
"only 'send' and 'ping' are supported for this mode");
} }
AppViewModel model = new AppViewModel(); AppViewModel model = new AppViewModel();
@@ -180,6 +201,9 @@ public class AutomationSnippet implements Snippet {
model.setSenderPacketCount(packetCount); model.setSenderPacketCount(packetCount);
model.setSenderPacketSize(packetSize); model.setSenderPacketSize(packetSize);
model.setSenderPacketInterval(packetInterval); model.setSenderPacketInterval(packetInterval);
if (startupDelay != null) {
model.setStartupDelay(startupDelay);
}
Runner runner = runScenario(model, "rfcomm-client", scenario); Runner runner = runScenario(model, "rfcomm-client", scenario);
assert runner != null; assert runner != null;
@@ -187,15 +211,18 @@ public class AutomationSnippet implements Snippet {
} }
@Rpc(description = "Run a scenario in RFComm Server mode") @Rpc(description = "Run a scenario in RFComm Server mode")
public JSONObject runRfcommServer(String scenario) throws JSONException { public JSONObject runRfcommServer(String scenario,
assert (mBluetoothAdapter != null); @RpcOptional Integer startupDelay) throws JSONException {
// We only support "receive" and "pong" for this mode for now // We only support "receive" and "pong" for this mode for now
if (!(scenario.equals("receive") || scenario.equals("pong"))) { if (!(scenario.equals("receive") || scenario.equals("pong"))) {
throw new InvalidParameterException("only 'receive' and 'pong' are supported for this mode"); throw new InvalidParameterException(
"only 'receive' and 'pong' are supported for this mode");
} }
AppViewModel model = new AppViewModel(); AppViewModel model = new AppViewModel();
if (startupDelay != null) {
model.setStartupDelay(startupDelay);
}
Runner runner = runScenario(model, "rfcomm-server", scenario); Runner runner = runScenario(model, "rfcomm-server", scenario);
assert runner != null; assert runner != null;
@@ -205,12 +232,12 @@ public class AutomationSnippet implements Snippet {
@Rpc(description = "Run a scenario in L2CAP Client mode") @Rpc(description = "Run a scenario in L2CAP Client mode")
public JSONObject runL2capClient(String scenario, String peerBluetoothAddress, int psm, public JSONObject runL2capClient(String scenario, String peerBluetoothAddress, int psm,
boolean use_2m_phy, int packetCount, int packetSize, boolean use_2m_phy, int packetCount, int packetSize,
int packetInterval) throws JSONException { int packetInterval, @RpcOptional String connectionPriority,
assert (mBluetoothAdapter != null); @RpcOptional Integer startupDelay) throws JSONException {
// We only support "send" and "ping" for this mode for now // We only support "send" and "ping" for this mode for now
if (!(scenario.equals("send") || scenario.equals("ping"))) { if (!(scenario.equals("send") || scenario.equals("ping"))) {
throw new InvalidParameterException("only 'send' and 'ping' are supported for this mode"); throw new InvalidParameterException(
"only 'send' and 'ping' are supported for this mode");
} }
AppViewModel model = new AppViewModel(); AppViewModel model = new AppViewModel();
@@ -220,22 +247,30 @@ public class AutomationSnippet implements Snippet {
model.setSenderPacketCount(packetCount); model.setSenderPacketCount(packetCount);
model.setSenderPacketSize(packetSize); model.setSenderPacketSize(packetSize);
model.setSenderPacketInterval(packetInterval); model.setSenderPacketInterval(packetInterval);
if (connectionPriority != null) {
model.setConnectionPriority(connectionPriority);
}
if (startupDelay != null) {
model.setStartupDelay(startupDelay);
}
Runner runner = runScenario(model, "l2cap-client", scenario); Runner runner = runScenario(model, "l2cap-client", scenario);
assert runner != null; assert runner != null;
return runner.toJson(); return runner.toJson();
} }
@Rpc(description = "Run a scenario in L2CAP Server mode") @Rpc(description = "Run a scenario in L2CAP Server mode")
public JSONObject runL2capServer(String scenario) throws JSONException { public JSONObject runL2capServer(String scenario,
assert (mBluetoothAdapter != null); @RpcOptional Integer startupDelay) throws JSONException {
// We only support "receive" and "pong" for this mode for now // We only support "receive" and "pong" for this mode for now
if (!(scenario.equals("receive") || scenario.equals("pong"))) { if (!(scenario.equals("receive") || scenario.equals("pong"))) {
throw new InvalidParameterException("only 'receive' and 'pong' are supported for this mode"); throw new InvalidParameterException(
"only 'receive' and 'pong' are supported for this mode");
} }
AppViewModel model = new AppViewModel(); AppViewModel model = new AppViewModel();
if (startupDelay != null) {
model.setStartupDelay(startupDelay);
}
Runner runner = runScenario(model, "l2cap-server", scenario); Runner runner = runScenario(model, "l2cap-server", scenario);
assert runner != null; assert runner != null;
@@ -276,7 +311,7 @@ public class AutomationSnippet implements Snippet {
JSONObject result = new JSONObject(); JSONObject result = new JSONObject();
JSONArray runners = new JSONArray(); JSONArray runners = new JSONArray();
result.put("runners", runners); result.put("runners", runners);
for (Runner runner: mRunners) { for (Runner runner : mRunners) {
runners.put(runner.toJson()); runners.put(runner.toJson());
} }
@@ -90,6 +90,18 @@ class L2capClient(
// Request an MTU update, even though we don't use GATT, because Android // Request an MTU update, even though we don't use GATT, because Android
// won't request a larger link layer maximum data length otherwise. // won't request a larger link layer maximum data length otherwise.
gatt.requestMtu(517) gatt.requestMtu(517)
// Request a specific connection priority
val connectionPriority = when (viewModel.connectionPriority) {
"BALANCED" -> BluetoothGatt.CONNECTION_PRIORITY_BALANCED
"LOW_POWER" -> BluetoothGatt.CONNECTION_PRIORITY_LOW_POWER
"HIGH" -> BluetoothGatt.CONNECTION_PRIORITY_HIGH
"DCK" -> BluetoothGatt.CONNECTION_PRIORITY_DCK
else -> 0
}
if (!gatt.requestConnectionPriority(connectionPriority)) {
Log.warning("requestConnectionPriority failed")
}
} }
} }
}, },
@@ -66,6 +66,7 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import com.github.google.bumble.btbench.ui.theme.BTBenchTheme import com.github.google.bumble.btbench.ui.theme.BTBenchTheme
import java.io.IOException
import java.util.logging.Logger import java.util.logging.Logger
private val Log = Logger.getLogger("bumble.main-activity") private val Log = Logger.getLogger("bumble.main-activity")
@@ -76,6 +77,7 @@ const val SENDER_PACKET_SIZE_PREF_KEY = "sender_packet_size"
const val SENDER_PACKET_INTERVAL_PREF_KEY = "sender_packet_interval" const val SENDER_PACKET_INTERVAL_PREF_KEY = "sender_packet_interval"
const val SCENARIO_PREF_KEY = "scenario" const val SCENARIO_PREF_KEY = "scenario"
const val MODE_PREF_KEY = "mode" const val MODE_PREF_KEY = "mode"
const val CONNECTION_PRIORITY_PREF_KEY = "connection_priority"
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
private val appViewModel = AppViewModel() private val appViewModel = AppViewModel()
@@ -195,7 +197,7 @@ class MainActivity : ComponentActivity() {
private fun runScenario() { private fun runScenario() {
if (bluetoothAdapter == null) { if (bluetoothAdapter == null) {
return throw IOException("bluetooth not enabled")
} }
val runner = when (appViewModel.mode) { val runner = when (appViewModel.mode) {
@@ -366,7 +368,35 @@ fun MainView(
checked = appViewModel.use2mPhy, checked = appViewModel.use2mPhy,
onCheckedChange = { appViewModel.use2mPhy = it } onCheckedChange = { appViewModel.use2mPhy = it }
) )
Column(Modifier.selectableGroup()) {
listOf(
"BALANCED",
"LOW",
"HIGH",
"DCK"
).forEach { text ->
Row(
Modifier
.selectable(
selected = (text == appViewModel.connectionPriority),
onClick = { appViewModel.updateConnectionPriority(text) },
role = Role.RadioButton
)
.padding(horizontal = 16.dp),
verticalAlignment = Alignment.CenterVertically
) {
RadioButton(
selected = (text == appViewModel.connectionPriority),
onClick = null
)
Text(
text = text,
style = MaterialTheme.typography.bodyLarge,
modifier = Modifier.padding(start = 16.dp)
)
}
}
}
} }
Row { Row {
Column(Modifier.selectableGroup()) { Column(Modifier.selectableGroup()) {
@@ -25,6 +25,7 @@ import java.util.UUID
val DEFAULT_RFCOMM_UUID: UUID = UUID.fromString("E6D55659-C8B4-4B85-96BB-B1143AF6D3AE") val DEFAULT_RFCOMM_UUID: UUID = UUID.fromString("E6D55659-C8B4-4B85-96BB-B1143AF6D3AE")
const val DEFAULT_PEER_BLUETOOTH_ADDRESS = "AA:BB:CC:DD:EE:FF" const val DEFAULT_PEER_BLUETOOTH_ADDRESS = "AA:BB:CC:DD:EE:FF"
const val DEFAULT_STARTUP_DELAY = 3000
const val DEFAULT_SENDER_PACKET_COUNT = 100 const val DEFAULT_SENDER_PACKET_COUNT = 100
const val DEFAULT_SENDER_PACKET_SIZE = 1024 const val DEFAULT_SENDER_PACKET_SIZE = 1024
const val DEFAULT_SENDER_PACKET_INTERVAL = 100 const val DEFAULT_SENDER_PACKET_INTERVAL = 100
@@ -47,8 +48,10 @@ class AppViewModel : ViewModel() {
var mode by mutableStateOf(RFCOMM_SERVER_MODE) var mode by mutableStateOf(RFCOMM_SERVER_MODE)
var scenario by mutableStateOf(RECEIVE_SCENARIO) var scenario by mutableStateOf(RECEIVE_SCENARIO)
var peerBluetoothAddress by mutableStateOf(DEFAULT_PEER_BLUETOOTH_ADDRESS) var peerBluetoothAddress by mutableStateOf(DEFAULT_PEER_BLUETOOTH_ADDRESS)
var startupDelay by mutableIntStateOf(DEFAULT_STARTUP_DELAY)
var l2capPsm by mutableIntStateOf(DEFAULT_PSM) var l2capPsm by mutableIntStateOf(DEFAULT_PSM)
var use2mPhy by mutableStateOf(true) var use2mPhy by mutableStateOf(true)
var connectionPriority by mutableStateOf("BALANCED")
var mtu by mutableIntStateOf(0) var mtu by mutableIntStateOf(0)
var rxPhy by mutableIntStateOf(0) var rxPhy by mutableIntStateOf(0)
var txPhy by mutableIntStateOf(0) var txPhy by mutableIntStateOf(0)
@@ -98,6 +101,11 @@ class AppViewModel : ViewModel() {
if (savedScenario != null) { if (savedScenario != null) {
scenario = savedScenario scenario = savedScenario
} }
val savedConnectionPriority = preferences.getString(CONNECTION_PRIORITY_PREF_KEY, null)
if (savedConnectionPriority != null) {
connectionPriority = savedConnectionPriority
}
} }
fun updatePeerBluetoothAddress(peerBluetoothAddress: String) { fun updatePeerBluetoothAddress(peerBluetoothAddress: String) {
@@ -220,6 +228,14 @@ class AppViewModel : ViewModel() {
} }
} }
fun updateConnectionPriority(connectionPriority: String) {
this.connectionPriority = connectionPriority
with(preferences!!.edit()) {
putString(CONNECTION_PRIORITY_PREF_KEY, connectionPriority)
apply()
}
}
fun clear() { fun clear() {
status = "" status = ""
lastError = "" lastError = ""
@@ -19,8 +19,6 @@ import java.util.logging.Logger
import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.TimeSource import kotlin.time.TimeSource
private const val DEFAULT_STARTUP_DELAY = 3000
private val Log = Logger.getLogger("btbench.pinger") private val Log = Logger.getLogger("btbench.pinger")
class Pinger(private val viewModel: AppViewModel, private val packetIO: PacketIO) : IoClient, class Pinger(private val viewModel: AppViewModel, private val packetIO: PacketIO) : IoClient,
@@ -36,8 +34,8 @@ class Pinger(private val viewModel: AppViewModel, private val packetIO: PacketIO
override fun run() { override fun run() {
viewModel.clear() viewModel.clear()
Log.info("startup delay: $DEFAULT_STARTUP_DELAY") Log.info("startup delay: ${viewModel.startupDelay}")
Thread.sleep(DEFAULT_STARTUP_DELAY.toLong()); Thread.sleep(viewModel.startupDelay.toLong());
Log.info("running") Log.info("running")
Log.info("sending reset") Log.info("sending reset")
@@ -19,8 +19,6 @@ import java.util.logging.Logger
import kotlin.time.DurationUnit import kotlin.time.DurationUnit
import kotlin.time.TimeSource import kotlin.time.TimeSource
private const val DEFAULT_STARTUP_DELAY = 3000
private val Log = Logger.getLogger("btbench.sender") private val Log = Logger.getLogger("btbench.sender")
class Sender(private val viewModel: AppViewModel, private val packetIO: PacketIO) : IoClient, class Sender(private val viewModel: AppViewModel, private val packetIO: PacketIO) : IoClient,
@@ -36,8 +34,8 @@ class Sender(private val viewModel: AppViewModel, private val packetIO: PacketIO
override fun run() { override fun run() {
viewModel.clear() viewModel.clear()
Log.info("startup delay: $DEFAULT_STARTUP_DELAY") Log.info("startup delay: ${viewModel.startupDelay}")
Thread.sleep(DEFAULT_STARTUP_DELAY.toLong()); Thread.sleep(viewModel.startupDelay.toLong());
Log.info("running") Log.info("running")
Log.info("sending reset") Log.info("sending reset")