|
| 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