1 /*
2 * Copyright (c) 2023 Demant A/S
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7 #include <stdbool.h>
8 #include <stddef.h>
9 #include <stdint.h>
10
11 #include <zephyr/bluetooth/audio/audio.h>
12 #include <zephyr/bluetooth/audio/lc3.h>
13 #include <zephyr/bluetooth/audio/pacs.h>
14 #include <zephyr/bluetooth/bluetooth.h>
15 #include <zephyr/bluetooth/gap.h>
16 #include <zephyr/bluetooth/gatt.h>
17 #include <zephyr/bluetooth/uuid.h>
18 #include <zephyr/kernel.h>
19 #include <zephyr/logging/log.h>
20 #include <zephyr/logging/log_core.h>
21 #include <zephyr/sys/__assert.h>
22 #include <zephyr/sys/util.h>
23
24 #include "bstests.h"
25 #include "common.h"
26
27 LOG_MODULE_REGISTER(pacs_notify_server_test, LOG_LEVEL_DBG);
28
29 extern enum bst_result_t bst_result;
30
31 static struct bt_audio_codec_cap lc3_codec_1 =
32 BT_AUDIO_CODEC_CAP_LC3(BT_AUDIO_CODEC_CAP_FREQ_16KHZ | BT_AUDIO_CODEC_CAP_FREQ_24KHZ,
33 BT_AUDIO_CODEC_CAP_DURATION_10,
34 BT_AUDIO_CODEC_CAP_CHAN_COUNT_SUPPORT(1), 40u, 60u, 1u,
35 BT_AUDIO_CONTEXT_TYPE_ANY);
36 static struct bt_audio_codec_cap lc3_codec_2 =
37 BT_AUDIO_CODEC_CAP_LC3(BT_AUDIO_CODEC_CAP_FREQ_16KHZ,
38 BT_AUDIO_CODEC_CAP_DURATION_10,
39 BT_AUDIO_CODEC_CAP_CHAN_COUNT_SUPPORT(1), 40u, 60u, 1u,
40 BT_AUDIO_CONTEXT_TYPE_ANY);
41 static struct bt_pacs_cap caps_1 = {
42 .codec_cap = &lc3_codec_1,
43 };
44 static struct bt_pacs_cap caps_2 = {
45 .codec_cap = &lc3_codec_2,
46 };
47
is_peer_subscribed(struct bt_conn * conn)48 static bool is_peer_subscribed(struct bt_conn *conn)
49 {
50 struct bt_gatt_attr *attr;
51 uint8_t nbr_subscribed = 0;
52
53 attr = bt_gatt_find_by_uuid(NULL, 0, BT_UUID_PACS_SNK);
54 if (!attr) {
55 LOG_DBG("No BT_UUID_PACS_SNK attribute found");
56 }
57 if (bt_gatt_is_subscribed(conn, attr, BT_GATT_CCC_NOTIFY)) {
58 nbr_subscribed++;
59 }
60
61 attr = bt_gatt_find_by_uuid(NULL, 0, BT_UUID_PACS_SNK_LOC);
62 if (!attr) {
63 LOG_DBG("No BT_UUID_PACS_SNK_LOC attribute found");
64 }
65 if (bt_gatt_is_subscribed(conn, attr, BT_GATT_CCC_NOTIFY)) {
66 nbr_subscribed++;
67 }
68
69 attr = bt_gatt_find_by_uuid(NULL, 0, BT_UUID_PACS_SRC);
70 if (!attr) {
71 LOG_DBG("No BT_UUID_PACS_SRC attribute found");
72 }
73 if (bt_gatt_is_subscribed(conn, attr, BT_GATT_CCC_NOTIFY)) {
74 nbr_subscribed++;
75 }
76
77 attr = bt_gatt_find_by_uuid(NULL, 0, BT_UUID_PACS_SRC_LOC);
78 if (!attr) {
79 LOG_DBG("No BT_UUID_PACS_SRC_LOC attribute found");
80 }
81 if (bt_gatt_is_subscribed(conn, attr, BT_GATT_CCC_NOTIFY)) {
82 nbr_subscribed++;
83 }
84
85 attr = bt_gatt_find_by_uuid(NULL, 0, BT_UUID_PACS_AVAILABLE_CONTEXT);
86 if (!attr) {
87 LOG_DBG("No BT_UUID_PACS_AVAILABLE_CONTEXT attribute found");
88 }
89 if (bt_gatt_is_subscribed(conn, attr, BT_GATT_CCC_NOTIFY)) {
90 nbr_subscribed++;
91 }
92
93 attr = bt_gatt_find_by_uuid(NULL, 0, BT_UUID_PACS_SUPPORTED_CONTEXT);
94 if (!attr) {
95 LOG_DBG("No BT_UUID_PACS_SUPPORTED_CONTEXT attribute found");
96 }
97 if (bt_gatt_is_subscribed(conn, attr, BT_GATT_CCC_NOTIFY)) {
98 nbr_subscribed++;
99 }
100
101 if (nbr_subscribed != 6) {
102 return false;
103 }
104
105 return true;
106 }
107
trigger_notifications(void)108 static void trigger_notifications(void)
109 {
110 static enum bt_audio_context available = BT_AUDIO_CONTEXT_TYPE_ANY;
111 static enum bt_audio_context supported = BT_AUDIO_CONTEXT_TYPE_ANY;
112 static int i;
113 int err;
114 struct bt_pacs_cap *caps;
115 enum bt_audio_location snk_loc;
116 enum bt_audio_location src_loc;
117
118 LOG_DBG("Triggering Notifications");
119
120 if (i) {
121 caps = &caps_1;
122 snk_loc = BT_AUDIO_LOCATION_FRONT_LEFT;
123 src_loc = BT_AUDIO_LOCATION_FRONT_RIGHT;
124 i = 0;
125 } else {
126 caps = &caps_2;
127 snk_loc = BT_AUDIO_LOCATION_FRONT_RIGHT;
128 src_loc = BT_AUDIO_LOCATION_FRONT_LEFT;
129 i++;
130 }
131
132 LOG_DBG("Changing Sink PACs");
133 bt_pacs_cap_register(BT_AUDIO_DIR_SINK, caps);
134 bt_pacs_cap_register(BT_AUDIO_DIR_SOURCE, caps);
135
136 LOG_DBG("Changing Sink Location");
137 err = bt_pacs_set_location(BT_AUDIO_DIR_SINK, snk_loc);
138 if (err != 0) {
139 LOG_DBG("Failed to set device sink location");
140 }
141
142 LOG_DBG("Changing Source Location");
143 err = bt_pacs_set_location(BT_AUDIO_DIR_SOURCE, src_loc);
144 if (err != 0) {
145 LOG_DBG("Failed to set device source location");
146 }
147
148 LOG_DBG("Changing Supported Contexts Location");
149 supported = supported ^ BT_AUDIO_CONTEXT_TYPE_MEDIA;
150 bt_pacs_set_supported_contexts(BT_AUDIO_DIR_SINK, supported);
151
152 LOG_DBG("Changing Available Contexts");
153 available = available ^ BT_AUDIO_CONTEXT_TYPE_MEDIA;
154 bt_pacs_set_available_contexts(BT_AUDIO_DIR_SINK, available);
155 }
156
test_main(void)157 static void test_main(void)
158 {
159 int err;
160 enum bt_audio_context available, available_for_conn;
161 const struct bt_data ad[] = {
162 BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)),
163 };
164
165 LOG_DBG("Enabling Bluetooth");
166 err = bt_enable(NULL);
167 if (err != 0) {
168 FAIL("Bluetooth enable failed (err %d)", err);
169 return;
170 }
171
172 bt_pacs_set_supported_contexts(BT_AUDIO_DIR_SINK, BT_AUDIO_CONTEXT_TYPE_ANY);
173 bt_pacs_set_supported_contexts(BT_AUDIO_DIR_SOURCE, BT_AUDIO_CONTEXT_TYPE_ANY);
174 bt_pacs_set_available_contexts(BT_AUDIO_DIR_SINK, BT_AUDIO_CONTEXT_TYPE_ANY);
175 bt_pacs_set_available_contexts(BT_AUDIO_DIR_SOURCE, BT_AUDIO_CONTEXT_TYPE_ANY);
176
177 LOG_DBG("Registereding PACS");
178 bt_pacs_cap_register(BT_AUDIO_DIR_SINK, &caps_1);
179 bt_pacs_cap_register(BT_AUDIO_DIR_SOURCE, &caps_1);
180
181 err = bt_pacs_set_location(BT_AUDIO_DIR_SINK, BT_AUDIO_LOCATION_FRONT_LEFT);
182 if (err != 0) {
183 LOG_DBG("Failed to set device sink location");
184 return;
185 }
186
187 err = bt_pacs_set_location(BT_AUDIO_DIR_SOURCE, BT_AUDIO_LOCATION_FRONT_RIGHT);
188 if (err != 0) {
189 LOG_DBG("Failed to set device source location");
190 return;
191 }
192
193 LOG_DBG("Start Advertising");
194 err = bt_le_adv_start(BT_LE_ADV_CONN_ONE_TIME, ad, ARRAY_SIZE(ad), NULL, 0);
195 if (err != 0) {
196 FAIL("Advertising failed to start (err %d)", err);
197 return;
198 }
199
200 LOG_DBG("Waiting to be connected");
201 WAIT_FOR_FLAG(flag_connected);
202 LOG_DBG("Connected");
203 LOG_DBG("Waiting to be subscribed");
204
205 while (!is_peer_subscribed(default_conn)) {
206 (void)k_sleep(K_MSEC(10));
207 }
208 LOG_DBG("Subscribed");
209
210 LOG_INF("Trigger changes while device is connected");
211 trigger_notifications();
212
213 /* Now wait for client to disconnect, then stop adv so it does not reconnect */
214 LOG_DBG("Wait for client disconnect");
215 WAIT_FOR_UNSET_FLAG(flag_connected);
216 LOG_DBG("Client disconnected");
217
218 err = bt_le_adv_stop();
219 if (err != 0) {
220 FAIL("Advertising failed to stop (err %d)", err);
221 return;
222 }
223
224 LOG_INF("Trigger changes while device is disconnected");
225 trigger_notifications();
226
227 LOG_DBG("Start Advertising");
228 err = bt_le_adv_start(BT_LE_ADV_CONN_ONE_TIME, ad, ARRAY_SIZE(ad), NULL, 0);
229 if (err != 0) {
230 FAIL("Advertising failed to start (err %d)", err);
231 return;
232 }
233
234 WAIT_FOR_FLAG(flag_connected);
235 WAIT_FOR_UNSET_FLAG(flag_connected);
236 LOG_DBG("Client disconnected");
237
238 err = bt_le_adv_stop();
239 if (err != 0) {
240 FAIL("Advertising failed to stop (err %d)", err);
241 return;
242 }
243
244 LOG_DBG("Start Advertising");
245 err = bt_le_adv_start(BT_LE_ADV_CONN_ONE_TIME, ad, ARRAY_SIZE(ad), NULL, 0);
246 if (err != 0) {
247 FAIL("Advertising failed to start (err %d)", err);
248 return;
249 }
250
251 WAIT_FOR_FLAG(flag_connected);
252 LOG_DBG("Connected");
253
254 available = bt_pacs_get_available_contexts(BT_AUDIO_DIR_SINK);
255 __ASSERT_NO_MSG(bt_pacs_get_available_contexts_for_conn(default_conn, BT_AUDIO_DIR_SINK) ==
256 available);
257
258 available_for_conn = BT_AUDIO_CONTEXT_TYPE_UNSPECIFIED;
259
260 LOG_INF("Override available contexts");
261 err = bt_pacs_conn_set_available_contexts_for_conn(default_conn, BT_AUDIO_DIR_SINK,
262 &available_for_conn);
263 __ASSERT_NO_MSG(err == 0);
264
265 __ASSERT_NO_MSG(bt_pacs_get_available_contexts(BT_AUDIO_DIR_SINK) == available);
266 __ASSERT_NO_MSG(bt_pacs_get_available_contexts_for_conn(default_conn, BT_AUDIO_DIR_SINK) ==
267 available_for_conn);
268
269 WAIT_FOR_UNSET_FLAG(flag_connected);
270 LOG_DBG("Client disconnected");
271
272 err = bt_le_adv_stop();
273 if (err != 0) {
274 FAIL("Advertising failed to stop (err %d)", err);
275 return;
276 }
277
278 LOG_DBG("Start Advertising");
279 err = bt_le_adv_start(BT_LE_ADV_CONN_ONE_TIME, ad, ARRAY_SIZE(ad), NULL, 0);
280 if (err != 0) {
281 FAIL("Advertising failed to start (err %d)", err);
282 return;
283 }
284
285 WAIT_FOR_FLAG(flag_connected);
286 LOG_DBG("Connected");
287
288 __ASSERT_NO_MSG(bt_pacs_get_available_contexts(BT_AUDIO_DIR_SINK) == available);
289 __ASSERT_NO_MSG(bt_pacs_get_available_contexts_for_conn(default_conn, BT_AUDIO_DIR_SINK) ==
290 available);
291
292 WAIT_FOR_UNSET_FLAG(flag_connected);
293
294 PASS("PACS Notify Server passed\n");
295 }
296
297 static const struct bst_test_instance test_pacs_notify_server[] = {
298 {
299 .test_id = "pacs_notify_server",
300 .test_pre_init_f = test_init,
301 .test_tick_f = test_tick,
302 .test_main_f = test_main,
303 },
304 BSTEST_END_MARKER,
305 };
306
test_pacs_notify_server_install(struct bst_test_list * tests)307 struct bst_test_list *test_pacs_notify_server_install(struct bst_test_list *tests)
308 {
309 return bst_add_tests(tests, test_pacs_notify_server);
310 }
311