Skip to content

Commit 6104953

Browse files
authored
Add BLE throughput E2E test (#2264)
1 parent 2d411b5 commit 6104953

File tree

1 file changed

+336
-0
lines changed

1 file changed

+336
-0
lines changed
Lines changed: 336 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,336 @@
1+
#include <zephyr/kernel.h>
2+
#include <zephyr/bluetooth/bluetooth.h>
3+
#include <zephyr/bluetooth/uuid.h>
4+
#include <zephyr/bluetooth/gatt.h>
5+
#include <zephyr/logging/log.h>
6+
#include <zephyr/sys/ring_buffer.h>
7+
#include <zephyr/sys/atomic.h> // Include for atomic operations
8+
#include <stdint.h>
9+
#include <zephyr/timing/timing.h> // Include for timing
10+
11+
LOG_MODULE_REGISTER(transport_ble_test, CONFIG_LOG_DEFAULT_LEVEL);
12+
13+
#define TEST_PACKET_SIZE 50
14+
#define WRITE_INTERVAL_MS 10
15+
#define TEST_RING_BUF_SIZE (TEST_PACKET_SIZE * 150) // Store 100 packets
16+
#define WRITER_STACK_SIZE 1024*10
17+
#define READER_STACK_SIZE 2048*10
18+
#define WRITER_PRIORITY K_PRIO_PREEMPT(7)
19+
#define READER_PRIORITY K_PRIO_PREEMPT(6)
20+
21+
// --- Test-specific Globals ---
22+
static struct ring_buf test_ring_buf;
23+
static uint8_t test_tx_queue[TEST_RING_BUF_SIZE];
24+
static struct bt_conn *test_conn = NULL;
25+
static volatile bool test_subscribed = false;
26+
static atomic_t test_write_count = ATOMIC_INIT(0);
27+
static atomic_t test_gatt_notify_count = ATOMIC_INIT(0);
28+
static atomic_t test_write_failed_count = ATOMIC_INIT(0);
29+
static atomic_t test_notify_failed_count = ATOMIC_INIT(0);
30+
31+
K_THREAD_STACK_DEFINE(writer_stack_area, WRITER_STACK_SIZE);
32+
static struct k_thread writer_thread_data;
33+
34+
K_THREAD_STACK_DEFINE(reader_stack_area, READER_STACK_SIZE);
35+
static struct k_thread reader_thread_data;
36+
37+
#define LOGGER_STACK_SIZE 1024
38+
#define LOGGER_PRIORITY K_PRIO_PREEMPT(8) // Lower priority than reader/writer
39+
K_THREAD_STACK_DEFINE(logger_stack_area, LOGGER_STACK_SIZE);
40+
static struct k_thread logger_thread_data;
41+
42+
43+
// --- Test-specific BLE Definitions ---
44+
45+
// Use the same UUIDs as transport.c for compatibility with clients
46+
static struct bt_uuid_128 test_audio_service_uuid = BT_UUID_INIT_128(BT_UUID_128_ENCODE(0x19B10000, 0xE8F2, 0x537E, 0x4F6C, 0xD104768A1214));
47+
static struct bt_uuid_128 test_audio_characteristic_data_uuid = BT_UUID_INIT_128(BT_UUID_128_ENCODE(0x19B10001, 0xE8F2, 0x537E, 0x4F6C, 0xD104768A1214));
48+
49+
static void test_audio_ccc_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value)
50+
{
51+
test_subscribed = (value == BT_GATT_CCC_NOTIFY);
52+
LOG_INF("Client %s", test_subscribed ? "subscribed" : "unsubscribed");
53+
}
54+
55+
// Minimal GATT service definition for the test
56+
static struct bt_gatt_attr test_audio_service_attrs[] = {
57+
BT_GATT_PRIMARY_SERVICE(&test_audio_service_uuid),
58+
BT_GATT_CHARACTERISTIC(&test_audio_characteristic_data_uuid.uuid, BT_GATT_CHRC_NOTIFY, BT_GATT_PERM_READ, NULL, NULL, NULL),
59+
BT_GATT_CCC(test_audio_ccc_cfg_changed, BT_GATT_PERM_READ | BT_GATT_PERM_WRITE),
60+
};
61+
62+
static struct bt_gatt_service test_audio_service = BT_GATT_SERVICE(test_audio_service_attrs);
63+
64+
// Minimal Advertisement data
65+
static const struct bt_data test_ad[] = {
66+
BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)),
67+
BT_DATA(BT_DATA_UUID128_ALL, test_audio_service_uuid.val, sizeof(test_audio_service_uuid.val)),
68+
BT_DATA(BT_DATA_NAME_COMPLETE, "OMI-BLE-TEST", sizeof("OMI-BLE-TEST") - 1),
69+
};
70+
71+
// --- Test-specific Connection Callbacks ---
72+
73+
static void test_connected(struct bt_conn *conn, uint8_t err)
74+
{
75+
if (err) {
76+
LOG_ERR("Connection failed (err 0x%02x)", err);
77+
if (test_conn) {
78+
bt_conn_unref(test_conn);
79+
test_conn = NULL;
80+
}
81+
test_subscribed = false;
82+
} else {
83+
LOG_INF("Connected");
84+
test_conn = bt_conn_ref(conn);
85+
test_subscribed = false; // Require subscription after connection
86+
}
87+
}
88+
89+
static void test_disconnected(struct bt_conn *conn, uint8_t reason)
90+
{
91+
LOG_INF("Disconnected (reason 0x%02x)", reason);
92+
if (test_conn == conn) {
93+
bt_conn_unref(test_conn);
94+
test_conn = NULL;
95+
test_subscribed = false;
96+
}
97+
}
98+
99+
static struct bt_conn_cb test_conn_callbacks = {
100+
.connected = test_connected,
101+
.disconnected = test_disconnected,
102+
};
103+
104+
// --- Test Threads ---
105+
106+
static void writer_thread_entry(void *p1, void *p2, void *p3)
107+
{
108+
uint8_t dummy_data[TEST_PACKET_SIZE];
109+
uint8_t counter = 0;
110+
111+
// Fill with initial pattern
112+
for (int i = 0; i < TEST_PACKET_SIZE; i++) {
113+
dummy_data[i] = i;
114+
}
115+
116+
LOG_INF("Writer thread started");
117+
118+
while (1) {
119+
k_msleep(WRITE_INTERVAL_MS);
120+
121+
// Modify data slightly each time
122+
dummy_data[0] = counter++;
123+
124+
int written = ring_buf_put(&test_ring_buf, dummy_data, TEST_PACKET_SIZE);
125+
if (written != TEST_PACKET_SIZE) {
126+
// LOG_WRN("Ring buffer full, discarding data!");
127+
atomic_inc(&test_write_failed_count); // Increment write failed counter
128+
// Optional: Add a small sleep here if buffer is often full
129+
}
130+
else {
131+
atomic_inc(&test_write_count); // Increment write counter on success
132+
}
133+
}
134+
}
135+
136+
static void reader_notifier_thread_entry(void *p1, void *p2, void *p3)
137+
{
138+
uint8_t data_buffer[TEST_PACKET_SIZE];
139+
int err;
140+
141+
LOG_INF("Reader/Notifier thread started");
142+
143+
while (1) {
144+
// Wait until connected and subscribed
145+
while (!test_conn || !test_subscribed) {
146+
k_msleep(100); // Check periodically
147+
}
148+
149+
// Read data from ring buffer
150+
int read = ring_buf_get(&test_ring_buf, data_buffer, TEST_PACKET_SIZE);
151+
152+
if (read == TEST_PACKET_SIZE) {
153+
// Send notification
154+
err = bt_gatt_notify(test_conn, &test_audio_service.attrs[1], data_buffer, TEST_PACKET_SIZE);
155+
if (err == -EAGAIN || err == -ENOMEM) {
156+
// Queue is full, retry after a short delay
157+
LOG_WRN("bt_gatt_notify failed (%d), retrying...", err);
158+
// Put data back into ring buffer (might fail if buffer became full)
159+
if (ring_buf_put(&test_ring_buf, data_buffer, TEST_PACKET_SIZE) != TEST_PACKET_SIZE) {
160+
LOG_ERR("Failed to put data back into ring buffer after notify failure!");
161+
}
162+
k_msleep(5); // Small delay before retrying
163+
continue; // Skip yield at the end, try again immediately
164+
} else if (err) {
165+
LOG_ERR("bt_gatt_notify failed unexpectedly (err %d)", err);
166+
atomic_inc(&test_notify_failed_count); // Increment notify failed counter
167+
// Consider what to do on other errors - maybe stop test?
168+
// For now, just log and continue trying.
169+
} else {
170+
// LOG_DBG("Sent %d bytes", TEST_PACKET_SIZE);
171+
atomic_inc(&test_gatt_notify_count); // Increment notify counter on success
172+
}
173+
} else if (read == 0) {
174+
// Buffer is empty, wait a bit
175+
k_msleep(5);
176+
} else {
177+
// Should not happen if writes are always TEST_PACKET_SIZE
178+
LOG_ERR("Ring buffer read unexpected size: %d", read);
179+
}
180+
181+
// Yield to other threads
182+
k_yield();
183+
}
184+
}
185+
186+
// --- Logger Thread ---
187+
188+
static void logger_thread_entry(void *p1, void *p2, void *p3)
189+
{
190+
uint32_t last_write_count = 0;
191+
uint32_t last_notify_count = 0;
192+
uint32_t current_write_count;
193+
uint32_t current_notify_count;
194+
uint32_t write_rate;
195+
uint32_t notify_rate;
196+
uint32_t last_write_failed_count = 0;
197+
uint32_t last_notify_failed_count = 0;
198+
uint32_t current_write_failed_count;
199+
uint32_t current_notify_failed_count;
200+
uint32_t write_failed_rate;
201+
uint32_t notify_failed_rate;
202+
int64_t last_time = k_uptime_get();
203+
int64_t current_time;
204+
int64_t delta_time;
205+
206+
LOG_INF("Logger thread started");
207+
208+
while (1) {
209+
k_msleep(1000); // Log every second
210+
211+
current_time = k_uptime_get();
212+
delta_time = current_time - last_time;
213+
214+
if (delta_time <= 0) {
215+
// Avoid division by zero or negative time delta if clock wraps or resolution is low
216+
continue;
217+
}
218+
219+
current_write_count = atomic_get(&test_write_count);
220+
current_notify_count = atomic_get(&test_gatt_notify_count);
221+
222+
current_write_failed_count = atomic_get(&test_write_failed_count);
223+
current_notify_failed_count = atomic_get(&test_notify_failed_count);
224+
225+
// Calculate rate per second
226+
write_rate = ((current_write_count - last_write_count) * 1000) / delta_time;
227+
notify_rate = ((current_notify_count - last_notify_count) * 1000) / delta_time;
228+
write_failed_rate = ((current_write_failed_count - last_write_failed_count) * 1000) / delta_time;
229+
notify_failed_rate = ((current_notify_failed_count - last_notify_failed_count) * 1000) / delta_time;
230+
231+
LOG_INF("BLE Test Rate -> Writes/s: %u (Fail: %u), Notifies/s: %u (Fail: %u)",
232+
write_rate, write_failed_rate, notify_rate, notify_failed_rate);
233+
234+
last_write_count = current_write_count;
235+
last_notify_count = current_notify_count;
236+
last_write_failed_count = current_write_failed_count;
237+
last_notify_failed_count = current_notify_failed_count;
238+
last_time = current_time;
239+
}
240+
}
241+
242+
243+
// --- Main Test Function ---
244+
245+
int transport_ble_test(void)
246+
{
247+
int err;
248+
249+
LOG_INF("Starting BLE Transport Test...");
250+
251+
// 0. Initialize test state
252+
test_conn = NULL;
253+
test_subscribed = false;
254+
atomic_set(&test_write_count, 0); // Ensure counters start at 0
255+
atomic_set(&test_gatt_notify_count, 0);
256+
atomic_set(&test_write_failed_count, 0);
257+
atomic_set(&test_notify_failed_count, 0);
258+
ring_buf_init(&test_ring_buf, sizeof(test_tx_queue), test_tx_queue);
259+
260+
// 1. Turn Bluetooth On
261+
err = bt_enable(NULL);
262+
if (err) {
263+
LOG_ERR("Bluetooth init failed (err %d)", err);
264+
return err;
265+
}
266+
LOG_INF("Bluetooth initialized");
267+
268+
bt_conn_cb_register(&test_conn_callbacks);
269+
270+
// 2. Register GATT Service
271+
err = bt_gatt_service_register(&test_audio_service);
272+
if (err) {
273+
LOG_ERR("Failed to register test GATT service (err %d)", err);
274+
return err;
275+
}
276+
LOG_INF("Test GATT service registered");
277+
278+
279+
// 3. Advertise
280+
err = bt_le_adv_start(BT_LE_ADV_CONN, test_ad, ARRAY_SIZE(test_ad), NULL, 0);
281+
if (err) {
282+
LOG_ERR("Advertising failed to start (err %d)", err);
283+
return err;
284+
}
285+
LOG_INF("Advertising successfully started. Waiting for connection and subscription...");
286+
287+
// 4. Wait for subscriber (handled within reader thread)
288+
// The reader thread will wait until test_conn and test_subscribed are set.
289+
290+
// 5. Start Threads
291+
// Writer Thread
292+
k_tid_t writer_tid = k_thread_create(&writer_thread_data, writer_stack_area,
293+
K_THREAD_STACK_SIZEOF(writer_stack_area),
294+
writer_thread_entry,
295+
NULL, NULL, NULL,
296+
WRITER_PRIORITY, 0, K_NO_WAIT);
297+
if (!writer_tid) {
298+
LOG_ERR("Failed to create writer thread");
299+
return -1; // Or appropriate error code
300+
}
301+
k_thread_name_set(writer_tid, "ble_test_writer");
302+
303+
304+
// Reader/Notifier Thread
305+
k_tid_t reader_tid = k_thread_create(&reader_thread_data, reader_stack_area,
306+
K_THREAD_STACK_SIZEOF(reader_stack_area),
307+
reader_notifier_thread_entry,
308+
NULL, NULL, NULL,
309+
READER_PRIORITY, 0, K_NO_WAIT);
310+
if (!reader_tid) {
311+
LOG_ERR("Failed to create reader thread");
312+
// Consider stopping the writer thread here
313+
return -1; // Or appropriate error code
314+
}
315+
k_thread_name_set(reader_tid, "ble_test_reader");
316+
317+
// Logger Thread
318+
k_tid_t logger_tid = k_thread_create(&logger_thread_data, logger_stack_area,
319+
K_THREAD_STACK_SIZEOF(logger_stack_area),
320+
logger_thread_entry,
321+
NULL, NULL, NULL,
322+
LOGGER_PRIORITY, 0, K_NO_WAIT);
323+
if (!logger_tid) {
324+
LOG_ERR("Failed to create logger thread");
325+
// Consider stopping other threads here
326+
return -1; // Or appropriate error code
327+
}
328+
k_thread_name_set(logger_tid, "ble_test_logger");
329+
330+
331+
LOG_INF("Test threads started. Running indefinitely.");
332+
333+
// The function returns, but the threads continue running.
334+
// You might want to add a mechanism to stop the test later.
335+
return 0;
336+
}

0 commit comments

Comments
 (0)