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

View File

@@ -28,7 +28,7 @@ class OneDeviceBenchTest(base_test.BaseTestClass):
def test_l2cap_client_ping(self):
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)
final_status = self.dut.bench.waitForRunnerCompletion(runner["id"])
@@ -36,7 +36,15 @@ class OneDeviceBenchTest(base_test.BaseTestClass):
def test_l2cap_client_send(self):
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)
final_status = self.dut.bench.waitForRunnerCompletion(runner["id"])

View File

@@ -22,11 +22,13 @@ import androidx.test.core.app.ApplicationProvider;
import com.google.android.mobly.snippet.Snippet;
import com.google.android.mobly.snippet.rpc.Rpc;
import com.google.android.mobly.snippet.rpc.RpcOptional;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
import java.security.InvalidParameterException;
import java.util.ArrayList;
import java.util.UUID;
@@ -71,12 +73,15 @@ public class AutomationSnippet implements Snippet {
private final Context mContext;
private final ArrayList<Runner> mRunners = new ArrayList<>();
public AutomationSnippet() {
public AutomationSnippet() throws IOException {
mContext = ApplicationProvider.getApplicationContext();
BluetoothManager bluetoothManager = mContext.getSystemService(BluetoothManager.class);
mBluetoothAdapter = bluetoothManager.getAdapter();
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) {
case "rfcomm-client":
runnable = new RfcommClient(model, mBluetoothAdapter,
(PacketIO packetIO) -> createIoClient(model, scenario,
packetIO));
(PacketIO packetIO) -> createIoClient(model, scenario,
packetIO));
break;
case "rfcomm-server":
runnable = new RfcommServer(model, mBluetoothAdapter,
(PacketIO packetIO) -> createIoClient(model, scenario,
packetIO));
(PacketIO packetIO) -> createIoClient(model, scenario,
packetIO));
break;
case "l2cap-client":
runnable = new L2capClient(model, mBluetoothAdapter, mContext,
(PacketIO packetIO) -> createIoClient(model, scenario,
packetIO));
(PacketIO packetIO) -> createIoClient(model, scenario,
packetIO));
break;
case "l2cap-server":
runnable = new L2capServer(model, mBluetoothAdapter,
(PacketIO packetIO) -> createIoClient(model, scenario,
packetIO));
(PacketIO packetIO) -> createIoClient(model, scenario,
packetIO));
break;
default:
return null;
}
model.setMode(mode);
model.setScenario(scenario);
runnable.run();
Runner runner = new Runner(runnable, mode, scenario, model);
mRunners.add(runner);
@@ -140,7 +147,21 @@ public class AutomationSnippet implements Snippet {
JSONObject result = new JSONObject();
result.put("status", model.getStatus());
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("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")) {
JSONObject stats = new JSONObject();
result.put("stats", stats);
@@ -167,12 +188,12 @@ public class AutomationSnippet implements Snippet {
@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);
int packetSize, int packetInterval,
@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");
throw new InvalidParameterException(
"only 'send' and 'ping' are supported for this mode");
}
AppViewModel model = new AppViewModel();
@@ -180,6 +201,9 @@ public class AutomationSnippet implements Snippet {
model.setSenderPacketCount(packetCount);
model.setSenderPacketSize(packetSize);
model.setSenderPacketInterval(packetInterval);
if (startupDelay != null) {
model.setStartupDelay(startupDelay);
}
Runner runner = runScenario(model, "rfcomm-client", scenario);
assert runner != null;
@@ -187,15 +211,18 @@ public class AutomationSnippet implements Snippet {
}
@Rpc(description = "Run a scenario in RFComm Server mode")
public JSONObject runRfcommServer(String scenario) throws JSONException {
assert (mBluetoothAdapter != null);
public JSONObject runRfcommServer(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");
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, "rfcomm-server", scenario);
assert runner != null;
@@ -205,12 +232,12 @@ public class AutomationSnippet implements Snippet {
@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);
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");
throw new InvalidParameterException(
"only 'send' and 'ping' are supported for this mode");
}
AppViewModel model = new AppViewModel();
@@ -220,22 +247,30 @@ public class AutomationSnippet implements Snippet {
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, "l2cap-client", scenario);
assert runner != null;
return runner.toJson();
}
@Rpc(description = "Run a scenario in L2CAP Server mode")
public JSONObject runL2capServer(String scenario) throws JSONException {
assert (mBluetoothAdapter != null);
public JSONObject runL2capServer(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");
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, "l2cap-server", scenario);
assert runner != null;
@@ -276,7 +311,7 @@ public class AutomationSnippet implements Snippet {
JSONObject result = new JSONObject();
JSONArray runners = new JSONArray();
result.put("runners", runners);
for (Runner runner: mRunners) {
for (Runner runner : mRunners) {
runners.put(runner.toJson());
}

View File

@@ -90,6 +90,18 @@ class L2capClient(
// Request an MTU update, even though we don't use GATT, because Android
// won't request a larger link layer maximum data length otherwise.
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")
}
}
}
},

View File

@@ -66,6 +66,7 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.core.content.ContextCompat
import com.github.google.bumble.btbench.ui.theme.BTBenchTheme
import java.io.IOException
import java.util.logging.Logger
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 SCENARIO_PREF_KEY = "scenario"
const val MODE_PREF_KEY = "mode"
const val CONNECTION_PRIORITY_PREF_KEY = "connection_priority"
class MainActivity : ComponentActivity() {
private val appViewModel = AppViewModel()
@@ -195,7 +197,7 @@ class MainActivity : ComponentActivity() {
private fun runScenario() {
if (bluetoothAdapter == null) {
return
throw IOException("bluetooth not enabled")
}
val runner = when (appViewModel.mode) {
@@ -366,7 +368,35 @@ fun MainView(
checked = appViewModel.use2mPhy,
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 {
Column(Modifier.selectableGroup()) {

View File

@@ -25,6 +25,7 @@ import java.util.UUID
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_STARTUP_DELAY = 3000
const val DEFAULT_SENDER_PACKET_COUNT = 100
const val DEFAULT_SENDER_PACKET_SIZE = 1024
const val DEFAULT_SENDER_PACKET_INTERVAL = 100
@@ -47,8 +48,10 @@ class AppViewModel : ViewModel() {
var mode by mutableStateOf(RFCOMM_SERVER_MODE)
var scenario by mutableStateOf(RECEIVE_SCENARIO)
var peerBluetoothAddress by mutableStateOf(DEFAULT_PEER_BLUETOOTH_ADDRESS)
var startupDelay by mutableIntStateOf(DEFAULT_STARTUP_DELAY)
var l2capPsm by mutableIntStateOf(DEFAULT_PSM)
var use2mPhy by mutableStateOf(true)
var connectionPriority by mutableStateOf("BALANCED")
var mtu by mutableIntStateOf(0)
var rxPhy by mutableIntStateOf(0)
var txPhy by mutableIntStateOf(0)
@@ -98,6 +101,11 @@ class AppViewModel : ViewModel() {
if (savedScenario != null) {
scenario = savedScenario
}
val savedConnectionPriority = preferences.getString(CONNECTION_PRIORITY_PREF_KEY, null)
if (savedConnectionPriority != null) {
connectionPriority = savedConnectionPriority
}
}
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() {
status = ""
lastError = ""

View File

@@ -19,8 +19,6 @@ import java.util.logging.Logger
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.TimeSource
private const val DEFAULT_STARTUP_DELAY = 3000
private val Log = Logger.getLogger("btbench.pinger")
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() {
viewModel.clear()
Log.info("startup delay: $DEFAULT_STARTUP_DELAY")
Thread.sleep(DEFAULT_STARTUP_DELAY.toLong());
Log.info("startup delay: ${viewModel.startupDelay}")
Thread.sleep(viewModel.startupDelay.toLong());
Log.info("running")
Log.info("sending reset")

View File

@@ -19,8 +19,6 @@ import java.util.logging.Logger
import kotlin.time.DurationUnit
import kotlin.time.TimeSource
private const val DEFAULT_STARTUP_DELAY = 3000
private val Log = Logger.getLogger("btbench.sender")
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() {
viewModel.clear()
Log.info("startup delay: $DEFAULT_STARTUP_DELAY")
Thread.sleep(DEFAULT_STARTUP_DELAY.toLong());
Log.info("startup delay: ${viewModel.startupDelay}")
Thread.sleep(viewModel.startupDelay.toLong());
Log.info("running")
Log.info("sending reset")