mirror of
https://github.com/bluekitchen/btstack.git
synced 2025-04-07 16:20:19 +00:00
gatt-service: add broadcast_audio_scan_service_[server|client|util]
This commit is contained in:
parent
841e7c8278
commit
b661adb482
@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
|
||||
## Unreleased
|
||||
|
||||
### Added
|
||||
- GATT Service: Broadcast Audio Scamn Service Server and Client (BASS 1.0)
|
||||
|
||||
### Fixed
|
||||
### Changed
|
||||
|
||||
|
@ -6,6 +6,9 @@ SRC_BLE_GATT_SERVICE_FILES = \
|
||||
battery_service_client.c \
|
||||
battery_service_server.c \
|
||||
bond_management_service_server.c \
|
||||
broadcast_audio_scan_service_client.c \
|
||||
broadcast_audio_scan_service_util.c \
|
||||
broadcast_audio_scan_service_server.c \
|
||||
cycling_power_service_server.c \
|
||||
cycling_speed_and_cadence_service_server.c \
|
||||
device_information_service_client.c \
|
||||
|
4
src/ble/gatt-service/broadcast_audio_scan_service.gatt
Normal file
4
src/ble/gatt-service/broadcast_audio_scan_service.gatt
Normal file
@ -0,0 +1,4 @@
|
||||
// Broadcast Audio Scan Service 0x184F
|
||||
PRIMARY_SERVICE, ORG_BLUETOOTH_SERVICE_BROADCAST_AUDIO_SCAN_SERVICE
|
||||
CHARACTERISTIC, ORG_BLUETOOTH_CHARACTERISTIC_BROADCAST_AUDIO_SCAN_CONTROL_POINT , DYNAMIC | WRITE | WRITE_WITHOUT_RESPONSE | ENCRYPTION_KEY_SIZE_16,
|
||||
CHARACTERISTIC, ORG_BLUETOOTH_CHARACTERISTIC_BROADCAST_RECEIVE_STATE , DYNAMIC | READ | NOTIFY | ENCRYPTION_KEY_SIZE_16,
|
865
src/ble/gatt-service/broadcast_audio_scan_service_client.c
Normal file
865
src/ble/gatt-service/broadcast_audio_scan_service_client.c
Normal file
@ -0,0 +1,865 @@
|
||||
/*
|
||||
* Copyright (C) 2022 BlueKitchen GmbH
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of the copyright holders nor the names of
|
||||
* contributors may be used to endorse or promote products derived
|
||||
* from this software without specific prior written permission.
|
||||
* 4. Any redistribution, use, or modification is done solely for
|
||||
* personal benefit and not for any commercial purpose or for
|
||||
* monetary gain.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY BLUEKITCHEN GMBH AND CONTRIBUTORS
|
||||
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BLUEKITCHEN
|
||||
* GMBH OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
|
||||
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
|
||||
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
|
||||
* THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
*
|
||||
* Please inquire about commercial licensing options at
|
||||
* contact@bluekitchen-gmbh.com
|
||||
*
|
||||
*/
|
||||
|
||||
#define BTSTACK_FILE__ "broadcast_audio_scan_service_client.c"
|
||||
|
||||
#include "ble/att_db.h"
|
||||
#include "ble/att_server.h"
|
||||
#include "bluetooth_gatt.h"
|
||||
#include "btstack_debug.h"
|
||||
#include "btstack_defines.h"
|
||||
#include "btstack_event.h"
|
||||
#include "btstack_util.h"
|
||||
#include "btstack_memory.h"
|
||||
|
||||
#include "ble/gatt-service/le_audio_util.h"
|
||||
#include "ble/gatt-service/broadcast_audio_scan_service_client.h"
|
||||
|
||||
#ifdef ENABLE_TESTING_SUPPORT
|
||||
#include <stdio.h>
|
||||
#endif
|
||||
|
||||
static btstack_linked_list_t bass_connections;
|
||||
|
||||
static uint16_t bass_client_cid_counter = 0;
|
||||
static btstack_packet_handler_t bass_event_callback;
|
||||
|
||||
static void handle_gatt_client_event(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size);
|
||||
|
||||
static uint16_t bass_client_get_next_cid(void){
|
||||
bass_client_cid_counter = btstack_next_cid_ignoring_zero(bass_client_cid_counter);
|
||||
return bass_client_cid_counter;
|
||||
}
|
||||
|
||||
static void bass_client_finalize_connection(bass_client_connection_t * connection){
|
||||
btstack_linked_list_remove(&bass_connections, (btstack_linked_item_t*) connection);
|
||||
}
|
||||
|
||||
static bass_client_connection_t * bass_client_get_connection_for_con_handle(hci_con_handle_t con_handle){
|
||||
btstack_linked_list_iterator_t it;
|
||||
btstack_linked_list_iterator_init(&it, (btstack_linked_list_t *) &bass_connections);
|
||||
while (btstack_linked_list_iterator_has_next(&it)){
|
||||
bass_client_connection_t * connection = (bass_client_connection_t *)btstack_linked_list_iterator_next(&it);
|
||||
if (connection->con_handle != con_handle) continue;
|
||||
return connection;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static bass_client_connection_t * bass_get_client_for_cid(uint16_t bass_cid){
|
||||
btstack_linked_list_iterator_t it;
|
||||
btstack_linked_list_iterator_init(&it, (btstack_linked_list_t *) &bass_connections);
|
||||
while (btstack_linked_list_iterator_has_next(&it)){
|
||||
bass_client_connection_t * connection = (bass_client_connection_t *)btstack_linked_list_iterator_next(&it);
|
||||
if (connection->cid != bass_cid) continue;
|
||||
return connection;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static bass_client_source_t * bass_get_receive_state_for_value_handle(bass_client_connection_t * connection, uint16_t value_handle){
|
||||
uint8_t i;
|
||||
for (i = 0; i < connection->receive_states_instances_num; i++){
|
||||
if (connection->receive_states[i].receive_state_value_handle == value_handle){
|
||||
return &connection->receive_states[i];
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static bass_client_source_t * bass_get_source_for_source_id(bass_client_connection_t * connection, uint8_t source_id){
|
||||
uint8_t i;
|
||||
for (i = 0; i < connection->receive_states_instances_num; i++){
|
||||
if (connection->receive_states[i].source_id == source_id){
|
||||
return &connection->receive_states[i];
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void bass_client_reset_source(bass_client_source_t * source){
|
||||
if (source == NULL){
|
||||
return;
|
||||
}
|
||||
source->source_id = BASS_INVALID_SOURCE_INDEX;
|
||||
source->in_use = false;
|
||||
memset(&source->data, 0, sizeof(bass_source_data_t));
|
||||
}
|
||||
|
||||
static void bass_client_emit_connection_established(bass_client_connection_t * connection, uint8_t status){
|
||||
uint8_t event[8];
|
||||
uint16_t pos = 0;
|
||||
event[pos++] = HCI_EVENT_GATTSERVICE_META;
|
||||
event[pos++] = sizeof(event) - 2;
|
||||
event[pos++] = GATTSERVICE_SUBEVENT_BASS_CONNECTED;
|
||||
little_endian_store_16(event, pos, connection->con_handle);
|
||||
pos += 2;
|
||||
little_endian_store_16(event, pos, connection->cid);
|
||||
pos += 2;
|
||||
event[pos++] = status;
|
||||
(*bass_event_callback)(HCI_EVENT_PACKET, 0, event, sizeof(event));
|
||||
}
|
||||
|
||||
static void bass_client_emit_scan_operation_complete(bass_client_connection_t * connection, uint8_t status, bass_opcode_t opcode){
|
||||
uint8_t event[7];
|
||||
uint16_t pos = 0;
|
||||
event[pos++] = HCI_EVENT_GATTSERVICE_META;
|
||||
event[pos++] = sizeof(event) - 2;
|
||||
event[pos++] = GATTSERVICE_SUBEVENT_BASS_SCAN_OPERATION_COMPLETE;
|
||||
little_endian_store_16(event, pos, connection->cid);
|
||||
pos += 2;
|
||||
event[pos++] = status;
|
||||
event[pos++] = (uint8_t)opcode;
|
||||
(*bass_event_callback)(HCI_EVENT_PACKET, 0, event, sizeof(event));
|
||||
}
|
||||
|
||||
static void bass_client_emit_source_operation_complete(bass_client_connection_t * connection, uint8_t status, bass_opcode_t opcode, uint8_t source_id){
|
||||
uint8_t event[8];
|
||||
uint16_t pos = 0;
|
||||
event[pos++] = HCI_EVENT_GATTSERVICE_META;
|
||||
event[pos++] = sizeof(event) - 2;
|
||||
event[pos++] = GATTSERVICE_SUBEVENT_BASS_SOURCE_OPERATION_COMPLETE;
|
||||
little_endian_store_16(event, pos, connection->cid);
|
||||
pos += 2;
|
||||
event[pos++] = status;
|
||||
event[pos++] = (uint8_t)opcode;
|
||||
event[pos++] = source_id;
|
||||
(*bass_event_callback)(HCI_EVENT_PACKET, 0, event, sizeof(event));
|
||||
}
|
||||
|
||||
static void bass_client_emit_receive_state(bass_client_connection_t * connection, uint8_t source_id){
|
||||
uint8_t pos = 0;
|
||||
uint8_t event[7];
|
||||
event[pos++] = HCI_EVENT_GATTSERVICE_META;
|
||||
event[pos++] = sizeof(event) - 2;
|
||||
event[pos++] = GATTSERVICE_SUBEVENT_BASS_NOTIFICATION_COMPLETE;
|
||||
little_endian_store_16(event, pos, connection->cid);
|
||||
pos += 2;
|
||||
event[pos++] = source_id;
|
||||
(*bass_event_callback)(HCI_EVENT_PACKET, 0, event, sizeof(event));
|
||||
}
|
||||
|
||||
static bool bass_client_remote_broadcast_receive_state_buffer_valid(uint8_t *buffer, uint16_t buffer_size){
|
||||
if (buffer_size < 1){
|
||||
log_info("Add Source opcode, buffer too small");
|
||||
return false;
|
||||
}
|
||||
uint8_t pos = 0;
|
||||
// source_id
|
||||
pos++;
|
||||
return bass_util_add_source_buffer_in_valid_range(buffer+pos, buffer_size-pos);
|
||||
}
|
||||
|
||||
static void handle_gatt_server_notification(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){
|
||||
UNUSED(packet_type);
|
||||
UNUSED(channel);
|
||||
UNUSED(size);
|
||||
|
||||
if (hci_event_packet_get_type(packet) != GATT_EVENT_NOTIFICATION){
|
||||
return;
|
||||
}
|
||||
|
||||
bass_client_connection_t * connection = bass_client_get_connection_for_con_handle(channel);
|
||||
if (connection == NULL){
|
||||
return;
|
||||
}
|
||||
|
||||
uint16_t value_handle = gatt_event_notification_get_value_handle(packet);
|
||||
uint16_t value_length = gatt_event_notification_get_value_length(packet);
|
||||
uint8_t * value = (uint8_t *)gatt_event_notification_get_value(packet);
|
||||
|
||||
if (bass_client_remote_broadcast_receive_state_buffer_valid(value, value_length)){
|
||||
bass_client_source_t * source = bass_get_receive_state_for_value_handle(connection, value_handle);
|
||||
if (source == NULL){
|
||||
return;
|
||||
}
|
||||
source->source_id = value[0];
|
||||
bass_util_get_source_from_buffer(value + 1, value_length - 1, &source->data);
|
||||
bass_client_emit_receive_state(connection, source->source_id);
|
||||
}
|
||||
}
|
||||
|
||||
static bool bass_client_register_notification(bass_client_connection_t * connection){
|
||||
bass_client_source_t * receive_state = &connection->receive_states[connection->receive_states_index];
|
||||
if (receive_state == NULL){
|
||||
return false;
|
||||
}
|
||||
|
||||
gatt_client_characteristic_t characteristic;
|
||||
characteristic.value_handle = receive_state->receive_state_value_handle;
|
||||
characteristic.properties = receive_state->receive_state_properties;
|
||||
characteristic.end_handle = connection->end_handle;
|
||||
|
||||
uint8_t status = gatt_client_write_client_characteristic_configuration(
|
||||
&handle_gatt_client_event,
|
||||
connection->con_handle,
|
||||
&characteristic,
|
||||
GATT_CLIENT_CHARACTERISTICS_CONFIGURATION_NOTIFICATION);
|
||||
|
||||
// notification supported, register for value updates
|
||||
if (status == ERROR_CODE_SUCCESS){
|
||||
gatt_client_listen_for_characteristic_value_updates(
|
||||
&connection->notification_listener,
|
||||
&handle_gatt_server_notification,
|
||||
connection->con_handle, &characteristic);
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
static uint16_t bass_client_prepare_add_source_buffer(bass_client_connection_t * connection){
|
||||
bass_source_data_t * receive_state = connection->receive_state_data;
|
||||
|
||||
uint16_t buffer_offset = connection->buffer_offset;
|
||||
uint8_t * buffer = connection->buffer;
|
||||
uint16_t buffer_size = btstack_min(sizeof(connection->buffer), connection->mtu);
|
||||
|
||||
btstack_assert(buffer_offset == 0);
|
||||
|
||||
uint8_t field_data[6];
|
||||
uint16_t source_offset = 0;
|
||||
uint16_t stored_bytes = 0;
|
||||
memset(buffer, 0, buffer_size);
|
||||
|
||||
field_data[0] = (uint8_t)BASS_OPCODE_ADD_SOURCE;
|
||||
stored_bytes += le_audio_virtual_memcpy_helper(field_data, 1, source_offset, buffer, buffer_size, buffer_offset);
|
||||
source_offset++;
|
||||
|
||||
stored_bytes += bass_util_copy_source_common_data_to_buffer(receive_state, &source_offset, buffer_offset, buffer, buffer_size);
|
||||
|
||||
field_data[0] = (uint8_t)receive_state->pa_sync;
|
||||
stored_bytes += le_audio_virtual_memcpy_helper(field_data, 1, source_offset, buffer, buffer_size, buffer_offset);
|
||||
source_offset++;
|
||||
|
||||
little_endian_store_16(field_data, 0, receive_state->pa_interval);
|
||||
stored_bytes += le_audio_virtual_memcpy_helper(field_data, 2, source_offset, buffer, buffer_size, buffer_offset);
|
||||
source_offset += 2;
|
||||
|
||||
stored_bytes += bass_util_copy_source_metadata_to_buffer(receive_state, &source_offset, buffer_offset, buffer, buffer_size);
|
||||
|
||||
return stored_bytes;
|
||||
}
|
||||
|
||||
static uint16_t bass_client_prepare_modify_source_buffer(bass_client_connection_t * connection){
|
||||
bass_source_data_t * receive_state = connection->receive_state_data;
|
||||
|
||||
uint16_t buffer_offset = connection->buffer_offset;
|
||||
uint8_t * buffer = connection->buffer;
|
||||
uint16_t buffer_size = btstack_min(sizeof(connection->buffer), connection->mtu);
|
||||
|
||||
btstack_assert(buffer_offset == 0);
|
||||
|
||||
uint8_t field_data[6];
|
||||
uint16_t source_offset = 0;
|
||||
uint16_t stored_bytes = 0;
|
||||
memset(buffer, 0, buffer_size);
|
||||
|
||||
field_data[0] = (uint8_t)BASS_OPCODE_MODIFY_SOURCE;
|
||||
stored_bytes += le_audio_virtual_memcpy_helper(field_data, 1, source_offset, buffer, buffer_size, buffer_offset);
|
||||
source_offset++;
|
||||
|
||||
field_data[0] = connection->receive_state_source_id;
|
||||
stored_bytes += le_audio_virtual_memcpy_helper(field_data, 1, source_offset, buffer, buffer_size, buffer_offset);
|
||||
source_offset++;
|
||||
|
||||
field_data[0] = (uint8_t)receive_state->pa_sync;
|
||||
stored_bytes += le_audio_virtual_memcpy_helper(field_data, 1, source_offset, buffer, buffer_size, buffer_offset);
|
||||
source_offset++;
|
||||
|
||||
little_endian_store_16(field_data, 0, receive_state->pa_interval);
|
||||
stored_bytes += le_audio_virtual_memcpy_helper(field_data, 2, source_offset, buffer, buffer_size, buffer_offset);
|
||||
source_offset += 2;
|
||||
|
||||
stored_bytes += bass_util_copy_source_metadata_to_buffer(receive_state, &source_offset, buffer_offset, buffer, buffer_size);
|
||||
return stored_bytes;
|
||||
}
|
||||
|
||||
static uint16_t bass_client_receive_state_len(bass_source_data_t * source_data){
|
||||
uint16_t source_len = 0;
|
||||
// opcode(1), address_type(1), address(6), adv_sid(1), broadcast_id(3), pa_sync(1), subgroups_num(1)
|
||||
source_len = 1 + 1 + 6 + 1 + 3 + 1 + 1;
|
||||
|
||||
uint8_t i;
|
||||
for (i = 0; i < source_data->subgroups_num; i++){
|
||||
bass_subgroup_t subgroup = source_data->subgroups[i];
|
||||
// bis_sync(4), metadata_length(1), metadata_length
|
||||
source_len += 4 + 1 + subgroup.metadata_length;
|
||||
}
|
||||
return source_len;
|
||||
}
|
||||
|
||||
static void bass_client_run_for_connection(bass_client_connection_t * connection){
|
||||
uint8_t status;
|
||||
gatt_client_characteristic_t characteristic;
|
||||
gatt_client_service_t service;
|
||||
|
||||
uint8_t value[18];
|
||||
uint16_t stored_bytes;
|
||||
|
||||
switch (connection->state){
|
||||
case BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_CONNECTED:
|
||||
break;
|
||||
|
||||
case BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W2_WRITE_CONTROL_POINT_START_SCAN:
|
||||
#ifdef ENABLE_TESTING_SUPPORT
|
||||
printf(" Write START SCAN [0x%04X]:\n",
|
||||
connection->control_point_value_handle);
|
||||
#endif
|
||||
|
||||
connection->state = BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W4_WRITE_CONTROL_POINT_START_SCAN;
|
||||
value[0] = BASS_OPCODE_REMOTE_SCAN_STARTED;
|
||||
// see GATT_EVENT_QUERY_COMPLETE for end of write
|
||||
status = gatt_client_write_value_of_characteristic(
|
||||
&handle_gatt_client_event, connection->con_handle,
|
||||
connection->control_point_value_handle, 1, &value[0]);
|
||||
UNUSED(status);
|
||||
break;
|
||||
|
||||
case BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W2_WRITE_CONTROL_POINT_STOP_SCAN:
|
||||
#ifdef ENABLE_TESTING_SUPPORT
|
||||
printf(" Write START SCAN [0x%04X]:\n",
|
||||
connection->control_point_value_handle);
|
||||
#endif
|
||||
|
||||
connection->state = BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W4_WRITE_CONTROL_POINT_STOP_SCAN;
|
||||
value[0] = BASS_OPCODE_REMOTE_SCAN_STOPPED;
|
||||
// see GATT_EVENT_QUERY_COMPLETE for end of write
|
||||
status = gatt_client_write_value_of_characteristic(
|
||||
&handle_gatt_client_event, connection->con_handle,
|
||||
connection->control_point_value_handle, 1, &value[0]);
|
||||
UNUSED(status);
|
||||
break;
|
||||
|
||||
case BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W2_WRITE_CONTROL_POINT_ADD_SOURCE:
|
||||
connection->state = BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W4_WRITE_CONTROL_POINT_ADD_SOURCE;
|
||||
#ifdef ENABLE_TESTING_SUPPORT
|
||||
printf(" ADD SOURCE [0x%04X]:\n", connection->control_point_value_handle);
|
||||
#endif
|
||||
connection->state = BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W4_WRITE_CONTROL_POINT_ADD_SOURCE;
|
||||
stored_bytes = bass_client_prepare_add_source_buffer(connection);
|
||||
connection->buffer_offset += stored_bytes;
|
||||
|
||||
gatt_client_write_long_value_of_characteristic(&handle_gatt_client_event, connection->con_handle,
|
||||
connection->control_point_value_handle, stored_bytes, connection->buffer);
|
||||
break;
|
||||
|
||||
case BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W2_WRITE_CONTROL_POINT_MODIFY_SOURCE:
|
||||
#ifdef ENABLE_TESTING_SUPPORT
|
||||
printf(" MODIFY SOURCE [%d]:\n", connection->receive_state_source_id);
|
||||
#endif
|
||||
connection->state = BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W4_WRITE_CONTROL_POINT_MODIFY_SOURCE;
|
||||
stored_bytes = bass_client_prepare_modify_source_buffer(connection);
|
||||
connection->buffer_offset += stored_bytes;
|
||||
|
||||
gatt_client_write_long_value_of_characteristic(&handle_gatt_client_event, connection->con_handle,
|
||||
connection->control_point_value_handle, stored_bytes, connection->buffer);
|
||||
|
||||
break;
|
||||
|
||||
case BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W2_WRITE_CONTROL_POINT_REMOVE_SOURCE:
|
||||
#ifdef ENABLE_TESTING_SUPPORT
|
||||
printf(" REMOVE SOURCE [%d]:\n", connection->receive_state_source_id);
|
||||
#endif
|
||||
connection->state = BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W4_WRITE_CONTROL_POINT_REMOVE_SOURCE;
|
||||
value[0] = BASS_OPCODE_REMOVE_SOURCE;
|
||||
value[1] = connection->receive_state_source_id;
|
||||
// see GATT_EVENT_QUERY_COMPLETE for end of write
|
||||
status = gatt_client_write_value_of_characteristic(
|
||||
&handle_gatt_client_event, connection->con_handle,
|
||||
connection->control_point_value_handle, 2, &value[0]);
|
||||
UNUSED(status);
|
||||
break;
|
||||
|
||||
case BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W2_WRITE_CONTROL_POINT_SET_BROADCAST_CODE:
|
||||
#ifdef ENABLE_TESTING_SUPPORT
|
||||
printf(" SET BROADCAST CODE [%d]:\n", connection->receive_state_source_id);
|
||||
#endif
|
||||
connection->state = BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W4_WRITE_CONTROL_POINT_SET_BROADCAST_CODE;
|
||||
value[0] = BASS_OPCODE_SET_BROADCAST_CODE;
|
||||
value[1] = connection->receive_state_source_id;
|
||||
reverse_128(&value[2], connection->broadcast_code);
|
||||
|
||||
// see GATT_EVENT_QUERY_COMPLETE for end of write
|
||||
status = gatt_client_write_value_of_characteristic(
|
||||
&handle_gatt_client_event, connection->con_handle,
|
||||
connection->control_point_value_handle, 18, &value[0]);
|
||||
UNUSED(status);
|
||||
break;
|
||||
|
||||
case BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_W2_QUERY_SERVICE:
|
||||
connection->state = BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_W4_SERVICE_RESULT;
|
||||
gatt_client_discover_primary_services_by_uuid16(&handle_gatt_client_event, connection->con_handle, ORG_BLUETOOTH_SERVICE_BROADCAST_AUDIO_SCAN_SERVICE);
|
||||
break;
|
||||
|
||||
case BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_W2_QUERY_CHARACTERISTICS:
|
||||
connection->state = BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_W4_CHARACTERISTIC_RESULT;
|
||||
|
||||
service.start_group_handle = connection->start_handle;
|
||||
service.end_group_handle = connection->end_handle;
|
||||
service.uuid16 = ORG_BLUETOOTH_SERVICE_BROADCAST_AUDIO_SCAN_SERVICE;
|
||||
|
||||
gatt_client_discover_characteristics_for_service(
|
||||
&handle_gatt_client_event,
|
||||
connection->con_handle,
|
||||
&service);
|
||||
|
||||
break;
|
||||
|
||||
case BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_W2_QUERY_CHARACTERISTIC_DESCRIPTORS:
|
||||
#ifdef ENABLE_TESTING_SUPPORT
|
||||
printf("Read client characteristic descriptors [index %d]:\n", connection->receive_states_index);
|
||||
#endif
|
||||
connection->state = BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_W4_CHARACTERISTIC_DESCRIPTORS_RESULT;
|
||||
characteristic.value_handle = connection->receive_states[connection->receive_states_index].receive_state_value_handle;
|
||||
characteristic.properties = connection->receive_states[connection->receive_states_index].receive_state_properties;
|
||||
characteristic.end_handle = connection->end_handle;
|
||||
|
||||
(void) gatt_client_discover_characteristic_descriptors(&handle_gatt_client_event, connection->con_handle, &characteristic);
|
||||
break;
|
||||
|
||||
case BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W2_READ_CHARACTERISTIC_CONFIGURATION:
|
||||
#ifdef ENABLE_TESTING_SUPPORT
|
||||
printf("Read client characteristic value [index %d, handle 0x%04X]:\n", connection->receive_states_index, connection->receive_states[connection->receive_states_index].receive_state_ccc_handle);
|
||||
#endif
|
||||
connection->state = BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W4_CHARACTERISTIC_CONFIGURATION_RESULT;
|
||||
|
||||
// result in GATT_EVENT_CHARACTERISTIC_VALUE_QUERY_RESULT
|
||||
(void) gatt_client_read_characteristic_descriptor_using_descriptor_handle(
|
||||
&handle_gatt_client_event,
|
||||
connection->con_handle,
|
||||
connection->receive_states[connection->receive_states_index].receive_state_ccc_handle);
|
||||
break;
|
||||
|
||||
case BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_W2_REGISTER_NOTIFICATION:
|
||||
#ifdef ENABLE_TESTING_SUPPORT
|
||||
printf("Register notification [index %d, handle 0x%04X]:\n", connection->receive_states_index, connection->receive_states[connection->receive_states_index].receive_state_ccc_handle);
|
||||
#endif
|
||||
|
||||
connection->state = BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_W4_NOTIFICATION_REGISTERED;
|
||||
|
||||
status = bass_client_register_notification(connection);
|
||||
if (status == ERROR_CODE_SUCCESS) return;
|
||||
|
||||
if (connection->receive_states[connection->receive_states_index].receive_state_ccc_handle != 0){
|
||||
connection->state = BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W2_READ_CHARACTERISTIC_CONFIGURATION;
|
||||
break;
|
||||
}
|
||||
|
||||
connection->state = BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_CONNECTED;
|
||||
bass_client_emit_connection_established(connection, ERROR_CODE_SUCCESS);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
// @return true if client valid / run function should be called
|
||||
static bool bass_client_handle_query_complete(bass_client_connection_t * connection, uint8_t status){
|
||||
switch (connection->state){
|
||||
case BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_W4_SERVICE_RESULT:
|
||||
if (status != ATT_ERROR_SUCCESS){
|
||||
bass_client_emit_connection_established(connection, status);
|
||||
bass_client_finalize_connection(connection);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (connection->service_instances_num == 0){
|
||||
bass_client_emit_connection_established(connection, ERROR_CODE_UNSUPPORTED_FEATURE_OR_PARAMETER_VALUE);
|
||||
bass_client_finalize_connection(connection);
|
||||
return false;
|
||||
}
|
||||
connection->receive_states_index = 0;
|
||||
connection->receive_states_instances_num = 0;
|
||||
connection->state = BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_W2_QUERY_CHARACTERISTICS;
|
||||
break;
|
||||
|
||||
case BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_W4_CHARACTERISTIC_RESULT:
|
||||
if (status != ATT_ERROR_SUCCESS){
|
||||
bass_client_emit_connection_established(connection, status);
|
||||
bass_client_finalize_connection(connection);
|
||||
return false;
|
||||
}
|
||||
|
||||
connection->receive_states_index = 0;
|
||||
connection->state = BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_W2_QUERY_CHARACTERISTIC_DESCRIPTORS;
|
||||
break;
|
||||
|
||||
case BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_W4_CHARACTERISTIC_DESCRIPTORS_RESULT:
|
||||
if (connection->receive_states_index < (connection->receive_states_instances_num - 1)){
|
||||
connection->receive_states_index++;
|
||||
connection->state = BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_W2_QUERY_CHARACTERISTIC_DESCRIPTORS;
|
||||
break;
|
||||
}
|
||||
connection->receive_states_index = 0;
|
||||
connection->state = BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_W2_REGISTER_NOTIFICATION;
|
||||
break;
|
||||
|
||||
case BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_W4_NOTIFICATION_REGISTERED:
|
||||
if (connection->receive_states_index < (connection->receive_states_instances_num - 1)){
|
||||
connection->receive_states_index++;
|
||||
connection->state = BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_W2_REGISTER_NOTIFICATION;
|
||||
break;
|
||||
}
|
||||
|
||||
connection->state = BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_CONNECTED;
|
||||
bass_client_emit_connection_established(connection, ERROR_CODE_SUCCESS);
|
||||
break;
|
||||
|
||||
case BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W4_CHARACTERISTIC_CONFIGURATION_RESULT:
|
||||
connection->state = BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_CONNECTED;
|
||||
bass_client_emit_connection_established(connection, ERROR_CODE_SUCCESS);
|
||||
break;
|
||||
|
||||
case BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W4_WRITE_CONTROL_POINT_START_SCAN:
|
||||
connection->state = BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_CONNECTED;
|
||||
bass_client_emit_scan_operation_complete(connection, status, BASS_OPCODE_REMOTE_SCAN_STARTED);
|
||||
break;
|
||||
|
||||
case BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W4_WRITE_CONTROL_POINT_STOP_SCAN:
|
||||
connection->state = BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_CONNECTED;
|
||||
bass_client_emit_scan_operation_complete(connection, status, BASS_OPCODE_REMOTE_SCAN_STOPPED);
|
||||
break;
|
||||
|
||||
case BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W4_WRITE_CONTROL_POINT_ADD_SOURCE:
|
||||
connection->state = BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_CONNECTED;
|
||||
bass_client_emit_source_operation_complete(connection, status, BASS_OPCODE_ADD_SOURCE, BASS_INVALID_SOURCE_INDEX);
|
||||
break;
|
||||
|
||||
case BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W4_WRITE_CONTROL_POINT_MODIFY_SOURCE:
|
||||
connection->state = BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_CONNECTED;
|
||||
bass_client_emit_source_operation_complete(connection, status, BASS_OPCODE_MODIFY_SOURCE, connection->receive_state_source_id);
|
||||
break;
|
||||
|
||||
case BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W4_WRITE_CONTROL_POINT_REMOVE_SOURCE:
|
||||
connection->state = BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_CONNECTED;
|
||||
bass_client_reset_source(bass_get_source_for_source_id(connection, connection->receive_state_source_id));
|
||||
bass_client_emit_source_operation_complete(connection, status, BASS_OPCODE_REMOVE_SOURCE, connection->receive_state_source_id);
|
||||
break;
|
||||
|
||||
case BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W4_WRITE_CONTROL_POINT_SET_BROADCAST_CODE:
|
||||
connection->state = BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_CONNECTED;
|
||||
bass_client_emit_source_operation_complete(connection, status, BASS_OPCODE_SET_BROADCAST_CODE, connection->receive_state_source_id);
|
||||
break;
|
||||
|
||||
case BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_CONNECTED:
|
||||
if (status != ATT_ERROR_SUCCESS){
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void handle_gatt_client_event(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){
|
||||
UNUSED(packet_type);
|
||||
UNUSED(channel);
|
||||
UNUSED(size);
|
||||
|
||||
bass_client_connection_t * connection = NULL;
|
||||
gatt_client_service_t service;
|
||||
gatt_client_characteristic_t characteristic;
|
||||
gatt_client_characteristic_descriptor_t characteristic_descriptor;
|
||||
|
||||
bool call_run = true;
|
||||
|
||||
switch(hci_event_packet_get_type(packet)){
|
||||
case GATT_EVENT_MTU:
|
||||
connection = bass_client_get_connection_for_con_handle(gatt_event_mtu_get_handle(packet));
|
||||
btstack_assert(connection != NULL);
|
||||
connection->mtu = gatt_event_mtu_get_MTU(packet);
|
||||
break;
|
||||
|
||||
case GATT_EVENT_SERVICE_QUERY_RESULT:
|
||||
connection = bass_client_get_connection_for_con_handle(gatt_event_service_query_result_get_handle(packet));
|
||||
btstack_assert(connection != NULL);
|
||||
|
||||
if (connection->service_instances_num < 1){
|
||||
gatt_event_service_query_result_get_service(packet, &service);
|
||||
connection->start_handle = service.start_group_handle;
|
||||
connection->end_handle = service.end_group_handle;
|
||||
|
||||
#ifdef ENABLE_TESTING_SUPPORT
|
||||
printf("BASS Service: start handle 0x%04X, end handle 0x%04X\n", connection->start_handle, connection->end_handle);
|
||||
#endif
|
||||
connection->service_instances_num++;
|
||||
} else {
|
||||
log_info("Found more then one BASS Service instance. ");
|
||||
}
|
||||
break;
|
||||
|
||||
case GATT_EVENT_CHARACTERISTIC_QUERY_RESULT:
|
||||
connection = bass_client_get_connection_for_con_handle(gatt_event_characteristic_query_result_get_handle(packet));
|
||||
btstack_assert(connection != NULL);
|
||||
|
||||
gatt_event_characteristic_query_result_get_characteristic(packet, &characteristic);
|
||||
|
||||
switch (characteristic.uuid16){
|
||||
case ORG_BLUETOOTH_CHARACTERISTIC_BROADCAST_AUDIO_SCAN_CONTROL_POINT:
|
||||
connection->control_point_value_handle = characteristic.value_handle;
|
||||
break;
|
||||
case ORG_BLUETOOTH_CHARACTERISTIC_BROADCAST_RECEIVE_STATE:
|
||||
if (connection->receive_states_instances_num < connection->max_receive_states_num){
|
||||
connection->receive_states[connection->receive_states_instances_num].receive_state_value_handle = characteristic.value_handle;
|
||||
connection->receive_states[connection->receive_states_instances_num].receive_state_properties = characteristic.properties;
|
||||
} else {
|
||||
log_info("Found more BASS receive_states chrs then it can be stored. ");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
btstack_assert(false);
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef ENABLE_TESTING_SUPPORT
|
||||
printf("BASS Characteristic:\n Attribute Handle 0x%04X, Properties 0x%02X, Handle 0x%04X, UUID 0x%04X, %s\n",
|
||||
characteristic.start_handle,
|
||||
characteristic.properties,
|
||||
characteristic.value_handle, characteristic.uuid16,
|
||||
characteristic.uuid16 == ORG_BLUETOOTH_CHARACTERISTIC_BROADCAST_RECEIVE_STATE ? "RECEIVE_STATE" : "CONTROL_POINT");
|
||||
#endif
|
||||
break;
|
||||
|
||||
|
||||
case GATT_EVENT_ALL_CHARACTERISTIC_DESCRIPTORS_QUERY_RESULT:
|
||||
connection = bass_client_get_connection_for_con_handle(gatt_event_all_characteristic_descriptors_query_result_get_handle(packet));
|
||||
btstack_assert(connection != NULL);
|
||||
gatt_event_all_characteristic_descriptors_query_result_get_characteristic_descriptor(packet, &characteristic_descriptor);
|
||||
|
||||
if (characteristic_descriptor.uuid16 == ORG_BLUETOOTH_DESCRIPTOR_GATT_CLIENT_CHARACTERISTIC_CONFIGURATION){
|
||||
connection->receive_states[connection->receive_states_index].receive_state_ccc_handle = characteristic_descriptor.handle;
|
||||
|
||||
#ifdef ENABLE_TESTING_SUPPORT
|
||||
printf(" BASS Characteristic Configuration Descriptor: Handle 0x%04X, UUID 0x%04X\n",
|
||||
characteristic_descriptor.handle,
|
||||
characteristic_descriptor.uuid16);
|
||||
#endif
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
case GATT_EVENT_CHARACTERISTIC_VALUE_QUERY_RESULT:
|
||||
connection = bass_client_get_connection_for_con_handle(gatt_event_characteristic_value_query_result_get_handle(packet));
|
||||
btstack_assert(connection != NULL);
|
||||
|
||||
if (connection->state == BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W4_CHARACTERISTIC_CONFIGURATION_RESULT){
|
||||
#ifdef ENABLE_TESTING_SUPPORT
|
||||
printf(" Received CCC value: ");
|
||||
printf_hexdump(gatt_event_characteristic_value_query_result_get_value(packet), gatt_event_characteristic_value_query_result_get_value_length(packet));
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
|
||||
if (gatt_event_characteristic_value_query_result_get_value_length(packet) == 0 ){
|
||||
break;
|
||||
}
|
||||
|
||||
// TODO
|
||||
// bass_client_emit_receive_state(connection,
|
||||
// gatt_event_characteristic_value_query_result_get_value_handle(packet),
|
||||
// ATT_ERROR_SUCCESS,
|
||||
// gatt_event_notification_get_value(packet),
|
||||
// gatt_event_notification_get_value_length(packet));
|
||||
break;
|
||||
|
||||
case GATT_EVENT_QUERY_COMPLETE:
|
||||
connection = bass_client_get_connection_for_con_handle(gatt_event_query_complete_get_handle(packet));
|
||||
btstack_assert(connection != NULL);
|
||||
call_run = bass_client_handle_query_complete(connection, gatt_event_query_complete_get_att_status(packet));
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (call_run && (connection != NULL)){
|
||||
bass_client_run_for_connection(connection);
|
||||
}
|
||||
}
|
||||
|
||||
void broadcast_audio_scan_service_client_init(btstack_packet_handler_t packet_handler){
|
||||
btstack_assert(packet_handler != NULL);
|
||||
bass_event_callback = packet_handler;
|
||||
}
|
||||
|
||||
uint8_t broadcast_audio_scan_service_client_connect(bass_client_connection_t * connection,
|
||||
bass_client_source_t * receive_states, uint8_t receive_states_num,
|
||||
hci_con_handle_t con_handle, uint16_t * bass_cid){
|
||||
|
||||
btstack_assert(receive_states != NULL);
|
||||
btstack_assert(receive_states_num > 0);
|
||||
|
||||
if (bass_client_get_connection_for_con_handle(con_handle) != NULL){
|
||||
return ERROR_CODE_COMMAND_DISALLOWED;
|
||||
}
|
||||
|
||||
uint16_t cid = bass_client_get_next_cid();
|
||||
if (bass_cid != NULL) {
|
||||
*bass_cid = cid;
|
||||
}
|
||||
|
||||
connection->cid = cid;
|
||||
connection->con_handle = con_handle;
|
||||
connection->mtu = ATT_DEFAULT_MTU;
|
||||
|
||||
connection->max_receive_states_num = receive_states_num;
|
||||
connection->receive_states = receive_states;
|
||||
|
||||
uint8_t i;
|
||||
for (i = 0; i < connection->max_receive_states_num; i++){
|
||||
connection->receive_states[i].in_use = false;
|
||||
connection->receive_states[i].source_id = BASS_INVALID_SOURCE_INDEX;
|
||||
}
|
||||
|
||||
connection->service_instances_num = 0;
|
||||
connection->receive_states_instances_num = 0;
|
||||
connection->receive_states_index = 0;
|
||||
|
||||
connection->state = BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_W2_QUERY_SERVICE;
|
||||
btstack_linked_list_add(&bass_connections, (btstack_linked_item_t *) connection);
|
||||
|
||||
bass_client_run_for_connection(connection);
|
||||
return ERROR_CODE_SUCCESS;
|
||||
}
|
||||
|
||||
uint8_t broadcast_audio_scan_service_client_scanning_started(uint16_t bass_cid){
|
||||
bass_client_connection_t * connection = bass_get_client_for_cid(bass_cid);
|
||||
if (connection == NULL){
|
||||
return ERROR_CODE_UNKNOWN_CONNECTION_IDENTIFIER;
|
||||
}
|
||||
if (connection->state != BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_CONNECTED){
|
||||
return ERROR_CODE_COMMAND_DISALLOWED;
|
||||
}
|
||||
connection->state = BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W2_WRITE_CONTROL_POINT_START_SCAN;
|
||||
bass_client_run_for_connection(connection);
|
||||
return ERROR_CODE_SUCCESS;
|
||||
}
|
||||
|
||||
uint8_t broadcast_audio_scan_service_client_scanning_stopped(uint16_t bass_cid){
|
||||
bass_client_connection_t * connection = bass_get_client_for_cid(bass_cid);
|
||||
if (connection == NULL){
|
||||
return ERROR_CODE_UNKNOWN_CONNECTION_IDENTIFIER;
|
||||
}
|
||||
if (connection->state != BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_CONNECTED){
|
||||
return ERROR_CODE_COMMAND_DISALLOWED;
|
||||
}
|
||||
connection->state = BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W2_WRITE_CONTROL_POINT_STOP_SCAN;
|
||||
bass_client_run_for_connection(connection);
|
||||
return ERROR_CODE_SUCCESS;
|
||||
}
|
||||
|
||||
uint8_t broadcast_audio_scan_service_client_add_source(uint16_t bass_cid, bass_source_data_t * receive_state){
|
||||
bass_client_connection_t * connection = bass_get_client_for_cid(bass_cid);
|
||||
if (connection == NULL){
|
||||
return ERROR_CODE_UNKNOWN_CONNECTION_IDENTIFIER;
|
||||
}
|
||||
if (connection->state != BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_CONNECTED){
|
||||
return ERROR_CODE_COMMAND_DISALLOWED;
|
||||
}
|
||||
|
||||
connection->state = BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W2_WRITE_CONTROL_POINT_ADD_SOURCE;
|
||||
connection->receive_state_data = receive_state;
|
||||
connection->buffer_offset = 0;
|
||||
connection->data_size = bass_client_receive_state_len(receive_state);
|
||||
|
||||
bass_client_run_for_connection(connection);
|
||||
return ERROR_CODE_SUCCESS;
|
||||
}
|
||||
|
||||
uint8_t broadcast_audio_scan_service_client_modify_source(uint16_t bass_cid, uint8_t source_id, bass_source_data_t * source_data){
|
||||
bass_client_connection_t * connection = bass_get_client_for_cid(bass_cid);
|
||||
if (connection == NULL){
|
||||
return ERROR_CODE_UNKNOWN_CONNECTION_IDENTIFIER;
|
||||
}
|
||||
if (connection->state != BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_CONNECTED){
|
||||
return ERROR_CODE_COMMAND_DISALLOWED;
|
||||
}
|
||||
|
||||
connection->state = BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W2_WRITE_CONTROL_POINT_MODIFY_SOURCE;
|
||||
connection->receive_state_data = source_data;
|
||||
connection->receive_state_source_id = source_id;
|
||||
connection->buffer_offset = 0;
|
||||
connection->data_size = bass_client_receive_state_len(source_data);
|
||||
|
||||
bass_client_run_for_connection(connection);
|
||||
return ERROR_CODE_SUCCESS;
|
||||
}
|
||||
|
||||
uint8_t broadcast_audio_scan_service_client_delete_source(uint16_t bass_cid, uint8_t source_id){
|
||||
bass_client_connection_t * connection = bass_get_client_for_cid(bass_cid);
|
||||
if (connection == NULL){
|
||||
return ERROR_CODE_UNKNOWN_CONNECTION_IDENTIFIER;
|
||||
}
|
||||
if (connection->state != BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_CONNECTED){
|
||||
return ERROR_CODE_COMMAND_DISALLOWED;
|
||||
}
|
||||
|
||||
connection->state = BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W2_WRITE_CONTROL_POINT_REMOVE_SOURCE;
|
||||
connection->receive_state_source_id = source_id;
|
||||
|
||||
bass_client_run_for_connection(connection);
|
||||
return ERROR_CODE_SUCCESS;
|
||||
}
|
||||
|
||||
uint8_t broadcast_audio_scan_service_client_set_broadcast_code(uint16_t bass_cid, uint8_t source_id, uint8_t * broadcast_code){
|
||||
bass_client_connection_t * connection = bass_get_client_for_cid(bass_cid);
|
||||
if (connection == NULL){
|
||||
return ERROR_CODE_UNKNOWN_CONNECTION_IDENTIFIER;
|
||||
}
|
||||
if (connection->state != BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_CONNECTED){
|
||||
return ERROR_CODE_COMMAND_DISALLOWED;
|
||||
}
|
||||
|
||||
connection->state = BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W2_WRITE_CONTROL_POINT_SET_BROADCAST_CODE;
|
||||
connection->receive_state_source_id = source_id;
|
||||
connection->broadcast_code = broadcast_code;
|
||||
|
||||
bass_client_run_for_connection(connection);
|
||||
return ERROR_CODE_SUCCESS;
|
||||
}
|
||||
|
||||
void broadcast_audio_scan_service_client_deinit(uint16_t bass_cid){
|
||||
bass_client_connection_t * connection = bass_get_client_for_cid(bass_cid);
|
||||
if (connection == NULL){
|
||||
return;
|
||||
}
|
||||
// finalize connections
|
||||
bass_client_finalize_connection(connection);
|
||||
}
|
||||
|
181
src/ble/gatt-service/broadcast_audio_scan_service_client.h
Normal file
181
src/ble/gatt-service/broadcast_audio_scan_service_client.h
Normal file
@ -0,0 +1,181 @@
|
||||
/*
|
||||
* Copyright (C) 2022 BlueKitchen GmbH
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of the copyright holders nor the names of
|
||||
* contributors may be used to endorse or promote products derived
|
||||
* from this software without specific prior written permission.
|
||||
* 4. Any redistribution, use, or modification is done solely for
|
||||
* personal benefit and not for any commercial purpose or for
|
||||
* monetary gain.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY BLUEKITCHEN GMBH AND CONTRIBUTORS
|
||||
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BLUEKITCHEN
|
||||
* GMBH OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
|
||||
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
|
||||
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
|
||||
* THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
*
|
||||
* Please inquire about commercial licensing options at
|
||||
* contact@bluekitchen-gmbh.com
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* @title Audio Stream Control Service Client
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_H
|
||||
#define BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include "le_audio.h"
|
||||
#include "broadcast_audio_scan_service_util.h"
|
||||
|
||||
#if defined __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define BASS_CLIENT_MAX_ATT_BUFFER_SIZE 512
|
||||
/* API_START */
|
||||
|
||||
typedef enum {
|
||||
BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_IDLE,
|
||||
BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_W2_QUERY_SERVICE,
|
||||
BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_W4_SERVICE_RESULT,
|
||||
BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_W2_QUERY_CHARACTERISTICS,
|
||||
BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_W4_CHARACTERISTIC_RESULT,
|
||||
|
||||
BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_W2_QUERY_CHARACTERISTIC_DESCRIPTORS,
|
||||
BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_W4_CHARACTERISTIC_DESCRIPTORS_RESULT,
|
||||
|
||||
BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_W2_REGISTER_NOTIFICATION,
|
||||
BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_W4_NOTIFICATION_REGISTERED,
|
||||
BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_STATE_CONNECTED,
|
||||
|
||||
BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W2_READ_CHARACTERISTIC_CONFIGURATION,
|
||||
BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W4_CHARACTERISTIC_CONFIGURATION_RESULT,
|
||||
|
||||
BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W2_WRITE_CONTROL_POINT_START_SCAN,
|
||||
BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W4_WRITE_CONTROL_POINT_START_SCAN,
|
||||
|
||||
BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W2_WRITE_CONTROL_POINT_STOP_SCAN,
|
||||
BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W4_WRITE_CONTROL_POINT_STOP_SCAN,
|
||||
|
||||
BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W2_WRITE_CONTROL_POINT_ADD_SOURCE,
|
||||
BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W4_WRITE_CONTROL_POINT_ADD_SOURCE,
|
||||
|
||||
BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W2_WRITE_CONTROL_POINT_MODIFY_SOURCE,
|
||||
BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W4_WRITE_CONTROL_POINT_MODIFY_SOURCE,
|
||||
|
||||
BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W2_WRITE_CONTROL_POINT_SET_BROADCAST_CODE,
|
||||
BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W4_WRITE_CONTROL_POINT_SET_BROADCAST_CODE,
|
||||
|
||||
BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W2_WRITE_CONTROL_POINT_REMOVE_SOURCE,
|
||||
BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W4_WRITE_CONTROL_POINT_REMOVE_SOURCE,
|
||||
|
||||
BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W2_READE_RECEIVE_STATE,
|
||||
BROADCAST_AUDIO_SCAN_SERVICE_CLIENT_W4_READE_RECEIVE_STATE,
|
||||
|
||||
} broadcast_audio_scan_service_client_state_t;
|
||||
|
||||
typedef struct {
|
||||
// used for add source command
|
||||
bass_source_data_t data;
|
||||
|
||||
// received via notification
|
||||
uint8_t source_id;
|
||||
bool in_use;
|
||||
le_audio_pa_sync_t pa_sync;
|
||||
|
||||
// characteristic
|
||||
uint16_t receive_state_value_handle;
|
||||
uint16_t receive_state_ccc_handle;
|
||||
uint16_t receive_state_properties;
|
||||
} bass_client_source_t;
|
||||
|
||||
|
||||
typedef struct {
|
||||
btstack_linked_item_t item;
|
||||
|
||||
hci_con_handle_t con_handle;
|
||||
uint16_t cid;
|
||||
uint16_t mtu;
|
||||
broadcast_audio_scan_service_client_state_t state;
|
||||
|
||||
// service
|
||||
uint16_t start_handle;
|
||||
uint16_t end_handle;
|
||||
uint16_t control_point_value_handle;
|
||||
|
||||
// used for memory capacity checking
|
||||
uint8_t service_instances_num;
|
||||
uint8_t receive_states_instances_num;
|
||||
// used for notification registration
|
||||
uint8_t receive_states_index;
|
||||
|
||||
uint8_t max_receive_states_num;
|
||||
bass_client_source_t * receive_states;
|
||||
|
||||
// used for write segmentation
|
||||
uint8_t buffer[BASS_CLIENT_MAX_ATT_BUFFER_SIZE];
|
||||
uint16_t buffer_offset;
|
||||
uint16_t data_size;
|
||||
|
||||
gatt_client_notification_t notification_listener;
|
||||
|
||||
// used for adding and modifying source
|
||||
bass_source_data_t * receive_state_data;
|
||||
uint8_t receive_state_source_id;
|
||||
// used for setting the broadcast code
|
||||
uint8_t * broadcast_code;
|
||||
} bass_client_connection_t;
|
||||
|
||||
/**
|
||||
* @brief Init Broadcast Audio Scan Service Client
|
||||
*/
|
||||
void broadcast_audio_scan_service_client_init(btstack_packet_handler_t packet_handler);
|
||||
|
||||
uint8_t broadcast_audio_scan_service_client_connect(bass_client_connection_t * connection, bass_client_source_t * sources, uint8_t num_sources, hci_con_handle_t con_handle, uint16_t * bass_cid);
|
||||
|
||||
uint8_t broadcast_audio_scan_service_client_scanning_started(uint16_t bass_cid);
|
||||
|
||||
uint8_t broadcast_audio_scan_service_client_scanning_stopped(uint16_t bass_cid);
|
||||
|
||||
uint8_t broadcast_audio_scan_service_client_add_source(uint16_t bass_cid, bass_source_data_t * source_data);
|
||||
|
||||
uint8_t broadcast_audio_scan_service_client_modify_source(uint16_t bass_cid, uint8_t source_id, bass_source_data_t * source_data);
|
||||
|
||||
uint8_t broadcast_audio_scan_service_client_set_broadcast_code(uint16_t bass_cid, uint8_t source_id, uint8_t * broadcast_code);
|
||||
|
||||
uint8_t broadcast_audio_scan_service_client_delete_source(uint16_t bass_cid, uint8_t source_id);
|
||||
|
||||
|
||||
/**
|
||||
* @brief Deinit Broadcast Audio Scan Service Client
|
||||
*/
|
||||
void broadcast_audio_scan_service_client_deinit(uint16_t bass_cid);
|
||||
|
||||
/* API_END */
|
||||
|
||||
#if defined __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
679
src/ble/gatt-service/broadcast_audio_scan_service_server.c
Normal file
679
src/ble/gatt-service/broadcast_audio_scan_service_server.c
Normal file
@ -0,0 +1,679 @@
|
||||
/*
|
||||
* Copyright (C) 2022 BlueKitchen GmbH
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of the copyright holders nor the names of
|
||||
* contributors may be used to endorse or promote products derived
|
||||
* from this software without specific prior written permission.
|
||||
* 4. Any redistribution, use, or modification is done solely for
|
||||
* personal benefit and not for any commercial purpose or for
|
||||
* monetary gain.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY BLUEKITCHEN GMBH AND CONTRIBUTORS
|
||||
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BLUEKITCHEN
|
||||
* GMBH OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
|
||||
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
|
||||
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
|
||||
* THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
*
|
||||
* Please inquire about commercial licensing options at
|
||||
* contact@bluekitchen-gmbh.com
|
||||
*
|
||||
*/
|
||||
|
||||
#define BTSTACK_FILE__ "broadcast_audio_scan_service_server.c"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include "ble/att_db.h"
|
||||
#include "ble/att_server.h"
|
||||
#include "bluetooth_gatt.h"
|
||||
#include "btstack_debug.h"
|
||||
#include "btstack_defines.h"
|
||||
#include "btstack_event.h"
|
||||
#include "btstack_util.h"
|
||||
|
||||
#include "ble/gatt-service/broadcast_audio_scan_service_server.h"
|
||||
#include "ble/gatt-service/le_audio_util.h"
|
||||
|
||||
#ifdef ENABLE_TESTING_SUPPORT
|
||||
#include <stdio.h>
|
||||
#endif
|
||||
|
||||
#define BASS_MAX_NOTIFY_BUFFER_SIZE 200
|
||||
#define BASS_INVALID_SOURCE_INDEX 0xFF
|
||||
|
||||
static att_service_handler_t broadcast_audio_scan_service;
|
||||
static btstack_packet_handler_t bass_event_callback;
|
||||
|
||||
// characteristic: AUDIO_SCAN_CONTROL_POINT
|
||||
static uint16_t bass_audio_scan_control_point_handle;
|
||||
|
||||
static uint8_t bass_logic_time = 0;
|
||||
|
||||
static bass_server_source_t * bass_sources;
|
||||
static uint8_t bass_sources_num = 0;
|
||||
static bass_remote_client_t * bass_clients;
|
||||
static uint8_t bass_clients_num = 0;
|
||||
static btstack_context_callback_registration_t scheduled_tasks_callback;
|
||||
|
||||
static uint8_t bass_get_next_update_counter(void){
|
||||
uint8_t next_update_counter;
|
||||
if (bass_logic_time == 0xff) {
|
||||
next_update_counter = 0;
|
||||
} else {
|
||||
next_update_counter = bass_logic_time + 1;
|
||||
}
|
||||
bass_logic_time = next_update_counter;
|
||||
return next_update_counter;
|
||||
}
|
||||
|
||||
// returns positive number if counter a > b
|
||||
static int8_t bass_counter_delta(uint8_t counter_a, uint8_t counter_b){
|
||||
return (int8_t)(counter_a - counter_b);
|
||||
}
|
||||
|
||||
static uint8_t bass_find_empty_or_last_used_source_index(void){
|
||||
bass_server_source_t * last_used_source = NULL;
|
||||
uint8_t last_used_source_index = BASS_INVALID_SOURCE_INDEX;
|
||||
|
||||
uint8_t i;
|
||||
for (i = 0; i < bass_sources_num; i++){
|
||||
if (!bass_sources[i].in_use){
|
||||
return i;
|
||||
}
|
||||
if (last_used_source == NULL){
|
||||
last_used_source = &bass_sources[i];
|
||||
last_used_source_index = i;
|
||||
continue;
|
||||
}
|
||||
if (bass_counter_delta(bass_sources[i].update_counter, last_used_source->update_counter) < 0 ){
|
||||
last_used_source = &bass_sources[i];
|
||||
last_used_source_index = i;
|
||||
}
|
||||
}
|
||||
return last_used_source_index;
|
||||
}
|
||||
|
||||
static bass_server_source_t * bass_find_empty_or_last_used_source(void){
|
||||
uint8_t last_used_source_index = bass_find_empty_or_last_used_source_index();
|
||||
if (last_used_source_index == BASS_INVALID_SOURCE_INDEX){
|
||||
return NULL;
|
||||
}
|
||||
return &bass_sources[last_used_source_index];
|
||||
}
|
||||
|
||||
static bass_server_source_t * bass_find_receive_state_for_value_handle(uint16_t attribute_handle){
|
||||
uint16_t i;
|
||||
for (i = 0; i < bass_sources_num; i++){
|
||||
if (attribute_handle == bass_sources[i].bass_receive_state_handle){
|
||||
return &bass_sources[i];
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static bass_server_source_t * bass_find_receive_state_for_client_configuration_handle(uint16_t attribute_handle){
|
||||
if (attribute_handle == 0){
|
||||
return NULL;
|
||||
}
|
||||
uint8_t i;
|
||||
for (i = 0; i < bass_sources_num; i++){
|
||||
if (bass_sources[i].bass_receive_state_client_configuration_handle == attribute_handle){
|
||||
return &bass_sources[i];
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static bass_server_source_t * bass_find_source_for_source_id(uint8_t source_id){
|
||||
if (source_id < bass_sources_num){
|
||||
return &bass_sources[source_id];
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static bass_remote_client_t * bass_find_client_for_con_handle(hci_con_handle_t con_handle){
|
||||
uint16_t i;
|
||||
for (i = 0; i < bass_clients_num; i++){
|
||||
if (bass_clients[i].con_handle == con_handle) {
|
||||
return &bass_clients[i];
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void bass_register_con_handle(hci_con_handle_t con_handle, uint16_t client_configuration){
|
||||
bass_remote_client_t * client = bass_find_client_for_con_handle(con_handle);
|
||||
if (client == NULL){
|
||||
client = bass_find_client_for_con_handle(HCI_CON_HANDLE_INVALID);
|
||||
if (client == NULL){
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
client->con_handle = (client_configuration == 0) ? HCI_CON_HANDLE_INVALID : con_handle;
|
||||
}
|
||||
|
||||
static void bass_emit_remote_scan_stoped(hci_con_handle_t con_handle){
|
||||
btstack_assert(bass_event_callback != NULL);
|
||||
|
||||
uint8_t event[5];
|
||||
uint8_t pos = 0;
|
||||
event[pos++] = HCI_EVENT_GATTSERVICE_META;
|
||||
event[pos++] = sizeof(event) - 2;
|
||||
event[pos++] = GATTSERVICE_SUBEVENT_BASS_REMOTE_SCAN_STOPPED;
|
||||
little_endian_store_16(event, pos, con_handle);
|
||||
pos += 2;
|
||||
(*bass_event_callback)(HCI_EVENT_PACKET, 0, event, sizeof(event));
|
||||
}
|
||||
|
||||
static void bass_emit_remote_scan_started(hci_con_handle_t con_handle){
|
||||
btstack_assert(bass_event_callback != NULL);
|
||||
|
||||
uint8_t event[5];
|
||||
uint8_t pos = 0;
|
||||
event[pos++] = HCI_EVENT_GATTSERVICE_META;
|
||||
event[pos++] = sizeof(event) - 2;
|
||||
event[pos++] = GATTSERVICE_SUBEVENT_BASS_REMOTE_SCAN_STARTED;
|
||||
little_endian_store_16(event, pos, con_handle);
|
||||
pos += 2;
|
||||
(*bass_event_callback)(HCI_EVENT_PACKET, 0, event, sizeof(event));
|
||||
}
|
||||
|
||||
|
||||
static void bass_emit_source_state_changed(uint8_t subevent_id, hci_con_handle_t con_handle, uint8_t source_id, le_audio_pa_sync_t pa_sync){
|
||||
btstack_assert(bass_event_callback != NULL);
|
||||
|
||||
uint8_t event[7];
|
||||
uint8_t pos = 0;
|
||||
event[pos++] = HCI_EVENT_GATTSERVICE_META;
|
||||
event[pos++] = sizeof(event) - 2;
|
||||
event[pos++] = subevent_id;
|
||||
little_endian_store_16(event, pos, con_handle);
|
||||
pos += 2;
|
||||
event[pos++] = source_id;
|
||||
event[pos++] = (uint8_t)pa_sync;
|
||||
(*bass_event_callback)(HCI_EVENT_PACKET, 0, event, sizeof(event));
|
||||
}
|
||||
|
||||
static void bass_emit_source_added(hci_con_handle_t con_handle, bass_server_source_t * source){
|
||||
bass_emit_source_state_changed(GATTSERVICE_SUBEVENT_BASS_SOURCE_ADDED, con_handle, source->source_id, source->pa_sync);
|
||||
}
|
||||
|
||||
static void bass_emit_source_modified(hci_con_handle_t con_handle, bass_server_source_t * source){
|
||||
bass_emit_source_state_changed(GATTSERVICE_SUBEVENT_BASS_SOURCE_MODIFIED, con_handle, source->source_id, source->pa_sync);
|
||||
}
|
||||
|
||||
static void bass_emit_source_deleted(hci_con_handle_t con_handle, bass_server_source_t * source){
|
||||
bass_emit_source_state_changed(GATTSERVICE_SUBEVENT_BASS_SOURCE_DELETED, con_handle, source->source_id, LE_AUDIO_PA_SYNC_DO_NOT_SYNCHRONIZE_TO_PA);
|
||||
}
|
||||
|
||||
static void bass_emit_broadcast_code(hci_con_handle_t con_handle, uint8_t source_id, uint8_t * broadcast_code){
|
||||
btstack_assert(bass_event_callback != NULL);
|
||||
|
||||
uint8_t event[22];
|
||||
uint8_t pos = 0;
|
||||
event[pos++] = HCI_EVENT_GATTSERVICE_META;
|
||||
event[pos++] = sizeof(event) - 2;
|
||||
event[pos++] = GATTSERVICE_SUBEVENT_BASS_BROADCAST_CODE;
|
||||
little_endian_store_16(event, pos, con_handle);
|
||||
pos += 2;
|
||||
event[pos++] = source_id;
|
||||
reverse_bytes(&broadcast_code[1], &event[pos], 16);
|
||||
pos += 16;
|
||||
(*bass_event_callback)(HCI_EVENT_PACKET, 0, event, sizeof(event));
|
||||
}
|
||||
|
||||
// offset gives position into fully serialized bass record
|
||||
static uint16_t bass_server_copy_source_to_buffer(bass_server_source_t * source, uint16_t buffer_offset, uint8_t * buffer, uint16_t buffer_size){
|
||||
uint8_t field_data[16];
|
||||
uint16_t source_offset = 0;
|
||||
uint16_t stored_bytes = 0;
|
||||
memset(buffer, 0, buffer_size);
|
||||
|
||||
if (!source->in_use){
|
||||
return 0;
|
||||
}
|
||||
field_data[0] = source->source_id;
|
||||
stored_bytes += le_audio_virtual_memcpy_helper(field_data, 1, source_offset, buffer, buffer_size, buffer_offset);
|
||||
source_offset++;
|
||||
|
||||
stored_bytes += bass_util_copy_source_common_data_to_buffer(&source->data, &source_offset, buffer_offset, buffer, buffer_size);
|
||||
|
||||
field_data[0] = (uint8_t)source->data.pa_sync_state;
|
||||
stored_bytes += le_audio_virtual_memcpy_helper(field_data, 1, source_offset, buffer, buffer_size, buffer_offset);
|
||||
source_offset++;
|
||||
|
||||
field_data[0] = (uint8_t)source->big_encryption;
|
||||
stored_bytes += le_audio_virtual_memcpy_helper(field_data, 1, source_offset, buffer, buffer_size, buffer_offset);
|
||||
source_offset++;
|
||||
|
||||
if (source->big_encryption == LE_AUDIO_BIG_ENCRYPTION_BAD_CODE){
|
||||
reverse_128(source->bad_code, &field_data[0]);
|
||||
stored_bytes += le_audio_virtual_memcpy_helper(field_data, 16, source_offset, buffer, buffer_size, buffer_offset);
|
||||
source_offset += 16;
|
||||
}
|
||||
|
||||
stored_bytes += bass_util_copy_source_metadata_to_buffer(&source->data, &source_offset, buffer_offset, buffer, buffer_size);
|
||||
return stored_bytes;
|
||||
}
|
||||
|
||||
static uint16_t broadcast_audio_scan_service_read_callback(hci_con_handle_t con_handle, uint16_t attribute_handle, uint16_t offset, uint8_t * buffer, uint16_t buffer_size){
|
||||
UNUSED(con_handle);
|
||||
|
||||
bass_server_source_t * source;
|
||||
source = bass_find_receive_state_for_value_handle(attribute_handle);
|
||||
if (source){
|
||||
return bass_server_copy_source_to_buffer(source, offset, buffer, buffer_size);
|
||||
}
|
||||
|
||||
source = bass_find_receive_state_for_client_configuration_handle(attribute_handle);
|
||||
if (source){
|
||||
return att_read_callback_handle_little_endian_16(source->bass_receive_state_client_configuration, offset, buffer, buffer_size);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool bass_source_remote_modify_source_buffer_valid(uint8_t *buffer, uint16_t buffer_size){
|
||||
if (buffer_size < 10){
|
||||
log_info("Modify Source opcode, buffer too small");
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t pos = 1; // source_id
|
||||
return bass_util_pa_sync_state_and_subgroups_in_valid_range(buffer+pos, buffer_size-pos);
|
||||
}
|
||||
|
||||
static void bass_server_add_source_from_buffer(uint8_t *buffer, uint16_t buffer_size, bass_server_source_t * source){
|
||||
UNUSED(buffer_size);
|
||||
|
||||
source->update_counter = bass_get_next_update_counter();
|
||||
source->in_use = true;
|
||||
|
||||
bass_util_get_source_from_buffer(buffer, buffer_size, &source->data);
|
||||
}
|
||||
|
||||
static bool bass_pa_synchronized(bass_server_source_t * source){
|
||||
return source->data.pa_sync_state == LE_AUDIO_PA_SYNC_STATE_SYNCHRONIZED_TO_PA;
|
||||
}
|
||||
|
||||
|
||||
static bool bass_bis_synchronized(bass_server_source_t * source){
|
||||
uint8_t i;
|
||||
for (i = 0; i < source->data.subgroups_num; i++){
|
||||
if ((source->data.subgroups[i].bis_sync_state > 0) && (source->data.subgroups[i].bis_sync_state < 0xFFFFFFFF)){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
static void bass_reset_source(bass_server_source_t * source){
|
||||
source->in_use = false;
|
||||
source->data.address_type = BD_ADDR_TYPE_LE_PUBLIC;
|
||||
memset(source->data.address, 0, sizeof(source->data.address));
|
||||
source->data.adv_sid = 0;
|
||||
source->data.broadcast_id = 0;
|
||||
source->pa_sync = LE_AUDIO_PA_SYNC_DO_NOT_SYNCHRONIZE_TO_PA;
|
||||
source->data.pa_sync_state = LE_AUDIO_PA_SYNC_STATE_NOT_SYNCHRONIZED_TO_PA;
|
||||
source->big_encryption = LE_AUDIO_BIG_ENCRYPTION_NOT_ENCRYPTED;
|
||||
memset(source->bad_code, 0, sizeof(source->bad_code));
|
||||
source->data.pa_interval = 0;
|
||||
source->data.subgroups_num = 0;
|
||||
memset(source->data.subgroups, 0, sizeof(source->data.subgroups));
|
||||
}
|
||||
|
||||
static void bass_reset_client_long_write_buffer(bass_remote_client_t * client){
|
||||
memset(client->long_write_buffer, 0, sizeof(client->long_write_buffer));
|
||||
client->long_write_value_size = 0;
|
||||
}
|
||||
|
||||
static int broadcast_audio_scan_service_write_callback(hci_con_handle_t con_handle, uint16_t attribute_handle, uint16_t transaction_mode, uint16_t offset, uint8_t *buffer, uint16_t buffer_size){
|
||||
// printf("broadcast_audio_scan_service_write_callback con_handle 0x%02x, attr_handle 0x%02x \n", con_handle, attribute_handle);
|
||||
if (attribute_handle != 0 && attribute_handle != bass_audio_scan_control_point_handle){
|
||||
|
||||
bass_server_source_t * source = bass_find_receive_state_for_client_configuration_handle(attribute_handle);
|
||||
if (source){
|
||||
source->bass_receive_state_client_configuration = little_endian_read_16(buffer, 0);
|
||||
bass_register_con_handle(con_handle, source->bass_receive_state_client_configuration);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
bass_remote_client_t * client = bass_find_client_for_con_handle(con_handle);
|
||||
if (client == NULL){
|
||||
return ATT_ERROR_WRITE_REQUEST_REJECTED;
|
||||
}
|
||||
|
||||
uint16_t total_value_len = buffer_size + offset;
|
||||
|
||||
switch (transaction_mode){
|
||||
case ATT_TRANSACTION_MODE_NONE:
|
||||
if (buffer_size > sizeof(client->long_write_buffer)){
|
||||
bass_reset_client_long_write_buffer(client);
|
||||
return ATT_ERROR_WRITE_REQUEST_REJECTED;
|
||||
}
|
||||
memcpy(&client->long_write_buffer[0], buffer, buffer_size);
|
||||
client->long_write_value_size = total_value_len;
|
||||
break;
|
||||
|
||||
case ATT_TRANSACTION_MODE_ACTIVE:
|
||||
if (total_value_len > sizeof(client->long_write_buffer)){
|
||||
bass_reset_client_long_write_buffer(client);
|
||||
return ATT_ERROR_WRITE_REQUEST_REJECTED;
|
||||
}
|
||||
// allow overlapped and/or mixed order chunks
|
||||
client->long_write_attribute_handle = attribute_handle;
|
||||
|
||||
memcpy(&client->long_write_buffer[offset], buffer, buffer_size);
|
||||
if (total_value_len > client->long_write_value_size){
|
||||
client->long_write_value_size = total_value_len;
|
||||
}
|
||||
return 0;
|
||||
|
||||
case ATT_TRANSACTION_MODE_VALIDATE:
|
||||
return 0;
|
||||
|
||||
case ATT_TRANSACTION_MODE_EXECUTE:
|
||||
attribute_handle = client->long_write_attribute_handle;
|
||||
break;
|
||||
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (attribute_handle == bass_audio_scan_control_point_handle){
|
||||
if (client->long_write_value_size < 2){
|
||||
return ATT_ERROR_WRITE_REQUEST_REJECTED;
|
||||
}
|
||||
|
||||
bass_opcode_t opcode = (bass_opcode_t)client->long_write_buffer[0];
|
||||
uint8_t *remote_data = &client->long_write_buffer[1];
|
||||
uint16_t remote_data_size = client->long_write_value_size - 1;
|
||||
|
||||
bass_server_source_t * source;
|
||||
switch (opcode){
|
||||
case BASS_OPCODE_REMOTE_SCAN_STOPPED:
|
||||
if (remote_data_size != 1){
|
||||
return ATT_ERROR_WRITE_REQUEST_REJECTED;
|
||||
}
|
||||
bass_emit_remote_scan_stoped(con_handle);
|
||||
break;
|
||||
|
||||
case BASS_OPCODE_REMOTE_SCAN_STARTED:
|
||||
if (remote_data_size != 1){
|
||||
return ATT_ERROR_WRITE_REQUEST_REJECTED;
|
||||
}
|
||||
bass_emit_remote_scan_started(con_handle);
|
||||
break;
|
||||
|
||||
case BASS_OPCODE_ADD_SOURCE:
|
||||
if (!bass_util_add_source_buffer_in_valid_range(remote_data, remote_data_size)){
|
||||
return ATT_ERROR_WRITE_REQUEST_REJECTED;
|
||||
}
|
||||
source = bass_find_empty_or_last_used_source();
|
||||
btstack_assert(source != NULL);
|
||||
log_info("add source %d", source->source_id);
|
||||
bass_server_add_source_from_buffer(remote_data, remote_data_size, source);
|
||||
bass_emit_source_added(con_handle, source);
|
||||
// server needs to trigger notification
|
||||
break;
|
||||
|
||||
case BASS_OPCODE_MODIFY_SOURCE:
|
||||
if (!bass_source_remote_modify_source_buffer_valid(remote_data, remote_data_size)){
|
||||
return ATT_ERROR_WRITE_REQUEST_REJECTED;
|
||||
}
|
||||
|
||||
source = bass_find_source_for_source_id(remote_data[0]);
|
||||
if (source == NULL){
|
||||
return BASS_ERROR_CODE_INVALID_SOURCE_ID;
|
||||
}
|
||||
bass_util_get_pa_info_and_subgroups_from_buffer(remote_data+1, remote_data_size-1, &source->data);
|
||||
bass_emit_source_modified(con_handle, source);
|
||||
// server needs to trigger notification
|
||||
break;
|
||||
|
||||
case BASS_OPCODE_SET_BROADCAST_CODE:
|
||||
if (remote_data_size != 17){
|
||||
return ATT_ERROR_WRITE_REQUEST_REJECTED;
|
||||
}
|
||||
|
||||
source = bass_find_source_for_source_id(remote_data[0]);
|
||||
if (source == NULL){
|
||||
return BASS_ERROR_CODE_INVALID_SOURCE_ID;
|
||||
}
|
||||
bass_emit_broadcast_code(con_handle, source->source_id, &remote_data[1]);
|
||||
break;
|
||||
|
||||
case BASS_OPCODE_REMOVE_SOURCE:
|
||||
if (remote_data_size != 1){
|
||||
return ATT_ERROR_WRITE_REQUEST_REJECTED;
|
||||
}
|
||||
source = bass_find_source_for_source_id(remote_data[0]);
|
||||
if (source == NULL){
|
||||
return BASS_ERROR_CODE_INVALID_SOURCE_ID;
|
||||
}
|
||||
|
||||
if (bass_pa_synchronized(source)){
|
||||
log_info("remove source %d rejected, PA synchronised", source->source_id);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (bass_bis_synchronized(source)){
|
||||
log_info("remove source %d rejected, BIS synchronised", source->source_id);
|
||||
return 0;
|
||||
}
|
||||
|
||||
bass_reset_source(source);
|
||||
broadcast_audio_scan_service_server_set_pa_sync_state(source->source_id, LE_AUDIO_PA_SYNC_STATE_NOT_SYNCHRONIZED_TO_PA);
|
||||
bass_emit_source_deleted(con_handle, source);
|
||||
break;
|
||||
|
||||
default:
|
||||
bass_reset_client_long_write_buffer(client);
|
||||
return BASS_ERROR_CODE_OPCODE_NOT_SUPPORTED;
|
||||
}
|
||||
bass_reset_client_long_write_buffer(client);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void broadcast_audio_scan_service_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){
|
||||
UNUSED(channel);
|
||||
UNUSED(packet);
|
||||
UNUSED(size);
|
||||
|
||||
if (packet_type != HCI_EVENT_PACKET){
|
||||
return;
|
||||
}
|
||||
|
||||
hci_con_handle_t con_handle;
|
||||
bass_remote_client_t * client;
|
||||
|
||||
switch (hci_event_packet_get_type(packet)) {
|
||||
case HCI_EVENT_DISCONNECTION_COMPLETE:
|
||||
con_handle = hci_event_disconnection_complete_get_connection_handle(packet);
|
||||
|
||||
client = bass_find_client_for_con_handle(con_handle);
|
||||
if (client == NULL){
|
||||
break;
|
||||
}
|
||||
|
||||
memset(client, 0, sizeof(bass_remote_client_t));
|
||||
client->con_handle = HCI_CON_HANDLE_INVALID;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void broadcast_audio_scan_service_server_init(const uint8_t sources_num, bass_server_source_t * sources, const uint8_t clients_num, bass_remote_client_t * clients){
|
||||
// get service handle range
|
||||
btstack_assert(sources_num != 0);
|
||||
btstack_assert(clients_num != 0);
|
||||
|
||||
uint16_t start_handle = 0;
|
||||
uint16_t end_handle = 0xffff;
|
||||
int service_found = gatt_server_get_handle_range_for_service_with_uuid16(ORG_BLUETOOTH_SERVICE_BROADCAST_AUDIO_SCAN_SERVICE, &start_handle, &end_handle);
|
||||
btstack_assert(service_found != 0);
|
||||
|
||||
UNUSED(service_found);
|
||||
|
||||
bass_audio_scan_control_point_handle = gatt_server_get_value_handle_for_characteristic_with_uuid16(start_handle, end_handle, ORG_BLUETOOTH_CHARACTERISTIC_BROADCAST_AUDIO_SCAN_CONTROL_POINT);
|
||||
bass_sources_num = 0;
|
||||
bass_logic_time = 0;
|
||||
bass_sources = sources;
|
||||
|
||||
#ifdef ENABLE_TESTING_SUPPORT
|
||||
printf("BASS 0x%02x - 0x%02x \n", start_handle, end_handle);
|
||||
#endif
|
||||
uint16_t start_chr_handle = start_handle;
|
||||
while ( (start_chr_handle < end_handle) && (bass_sources_num < sources_num )) {
|
||||
uint16_t chr_value_handle = gatt_server_get_value_handle_for_characteristic_with_uuid16(start_chr_handle, end_handle, ORG_BLUETOOTH_CHARACTERISTIC_BROADCAST_RECEIVE_STATE);
|
||||
uint16_t chr_client_configuration_handle = gatt_server_get_client_configuration_handle_for_characteristic_with_uuid16(start_chr_handle, end_handle, ORG_BLUETOOTH_CHARACTERISTIC_BROADCAST_RECEIVE_STATE);
|
||||
|
||||
if (chr_value_handle == 0){
|
||||
break;
|
||||
}
|
||||
bass_server_source_t * source = &bass_sources[bass_sources_num];
|
||||
bass_reset_source(source);
|
||||
|
||||
source->source_id = bass_sources_num;
|
||||
source->update_counter = bass_get_next_update_counter();
|
||||
source->bass_receive_state_client_configuration = 0;
|
||||
|
||||
source->bass_receive_state_handle = chr_value_handle;
|
||||
source->bass_receive_state_client_configuration_handle = chr_client_configuration_handle;
|
||||
|
||||
#ifdef ENABLE_TESTING_SUPPORT
|
||||
printf(" bass_receive_state_%d 0x%02x \n", bass_sources_num, source->bass_receive_state_handle);
|
||||
printf(" bass_receive_state_%d CCC 0x%02x \n", bass_sources_num, source->bass_receive_state_client_configuration_handle);
|
||||
#endif
|
||||
|
||||
start_chr_handle = chr_client_configuration_handle + 1;
|
||||
bass_sources_num++;
|
||||
}
|
||||
|
||||
bass_clients_num = clients_num;
|
||||
bass_clients = clients;
|
||||
memset(bass_clients, 0, sizeof(bass_remote_client_t) * bass_clients_num);
|
||||
uint8_t i;
|
||||
for (i = 0; i < bass_clients_num; i++){
|
||||
bass_clients[i].con_handle = HCI_CON_HANDLE_INVALID;
|
||||
}
|
||||
|
||||
log_info("Found BASS service 0x%02x-0x%02x (num sources %d)", start_handle, end_handle, bass_sources_num);
|
||||
|
||||
// register service with ATT Server
|
||||
broadcast_audio_scan_service.start_handle = start_handle;
|
||||
broadcast_audio_scan_service.end_handle = end_handle;
|
||||
broadcast_audio_scan_service.read_callback = &broadcast_audio_scan_service_read_callback;
|
||||
broadcast_audio_scan_service.write_callback = &broadcast_audio_scan_service_write_callback;
|
||||
broadcast_audio_scan_service.packet_handler = broadcast_audio_scan_service_packet_handler;
|
||||
att_server_register_service_handler(&broadcast_audio_scan_service);
|
||||
}
|
||||
|
||||
void broadcast_audio_scan_service_server_register_packet_handler(btstack_packet_handler_t callback){
|
||||
btstack_assert(callback != NULL);
|
||||
bass_event_callback = callback;
|
||||
}
|
||||
|
||||
static void bass_service_can_send_now(void * context){
|
||||
bass_remote_client_t * client = (bass_remote_client_t *) context;
|
||||
btstack_assert(client != NULL);
|
||||
|
||||
uint8_t source_index;
|
||||
for (source_index = 0; source_index < bass_sources_num; source_index++){
|
||||
uint8_t task = (1 << source_index);
|
||||
if ((client->sources_to_notify & task) != 0){
|
||||
client->sources_to_notify &= ~task;
|
||||
uint8_t buffer[BASS_MAX_NOTIFY_BUFFER_SIZE];
|
||||
uint16_t bytes_copied = bass_server_copy_source_to_buffer(&bass_sources[source_index], 0, buffer, sizeof(buffer));
|
||||
att_server_notify(client->con_handle, bass_sources[source_index].bass_receive_state_handle, &buffer[0], bytes_copied);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t i;
|
||||
for (i = 0; i < bass_clients_num; i++){
|
||||
client = &bass_clients[i];
|
||||
|
||||
if (client->sources_to_notify != 0){
|
||||
scheduled_tasks_callback.callback = &bass_service_can_send_now;
|
||||
scheduled_tasks_callback.context = (void*) client;
|
||||
att_server_register_can_send_now_callback(&scheduled_tasks_callback, client->con_handle);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void bass_set_callback(uint8_t source_index){
|
||||
// there is only one type of task: notify on source state change
|
||||
// as task we register which source is changed, and the change will be propagated to all clients
|
||||
uint8_t i;
|
||||
uint8_t task = (1 << source_index);
|
||||
|
||||
uint8_t scheduled_tasks = 0;
|
||||
|
||||
for (i = 0; i < bass_clients_num; i++){
|
||||
bass_remote_client_t * client = &bass_clients[i];
|
||||
|
||||
if (client->con_handle == HCI_CON_HANDLE_INVALID){
|
||||
client->sources_to_notify &= ~task;
|
||||
return;
|
||||
}
|
||||
|
||||
scheduled_tasks |= client->sources_to_notify;
|
||||
client->sources_to_notify |= task;
|
||||
|
||||
if (scheduled_tasks == 0){
|
||||
scheduled_tasks_callback.callback = &bass_service_can_send_now;
|
||||
scheduled_tasks_callback.context = (void*) client;
|
||||
att_server_register_can_send_now_callback(&scheduled_tasks_callback, client->con_handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void broadcast_audio_scan_service_server_set_pa_sync_state(uint8_t source_index, le_audio_pa_sync_state_t sync_state){
|
||||
btstack_assert(source_index < bass_sources_num);
|
||||
|
||||
bass_server_source_t * source = &bass_sources[source_index];
|
||||
source->data.pa_sync_state = sync_state;
|
||||
|
||||
if (source->bass_receive_state_client_configuration != 0){
|
||||
bass_set_callback(source_index);
|
||||
}
|
||||
}
|
||||
|
||||
void broadcast_audio_scan_service_server_add_source(bass_source_data_t source_data, uint8_t * source_index){
|
||||
*source_index = bass_find_empty_or_last_used_source_index();
|
||||
if (*source_index == BASS_INVALID_SOURCE_INDEX){
|
||||
return;
|
||||
}
|
||||
bass_server_source_t * last_used_source = &bass_sources[*source_index];
|
||||
last_used_source->update_counter = bass_get_next_update_counter();
|
||||
last_used_source->in_use = true;
|
||||
last_used_source->source_id = *source_index;
|
||||
memcpy(&last_used_source->data, &source_data, sizeof(bass_source_data_t));
|
||||
}
|
||||
|
||||
void broadcast_audio_scan_service_server_deinit(void){
|
||||
}
|
130
src/ble/gatt-service/broadcast_audio_scan_service_server.h
Normal file
130
src/ble/gatt-service/broadcast_audio_scan_service_server.h
Normal file
@ -0,0 +1,130 @@
|
||||
/*
|
||||
* Copyright (C) 2022 BlueKitchen GmbH
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of the copyright holders nor the names of
|
||||
* contributors may be used to endorse or promote products derived
|
||||
* from this software without specific prior written permission.
|
||||
* 4. Any redistribution, use, or modification is done solely for
|
||||
* personal benefit and not for any commercial purpose or for
|
||||
* monetary gain.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY BLUEKITCHEN GMBH AND CONTRIBUTORS
|
||||
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BLUEKITCHEN
|
||||
* GMBH OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
|
||||
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
|
||||
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
|
||||
* THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
*
|
||||
* Please inquire about commercial licensing options at
|
||||
* contact@bluekitchen-gmbh.com
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* @title Broadcast Audio Scan Service Server (BASS)
|
||||
*
|
||||
* @text The Broadcast Audio Scan Service is used by servers to expose their status with respect
|
||||
* to synchronization to broadcast Audio Streams and associated data, including Broadcast_Codes
|
||||
* used to decrypt encrypted broadcast Audio Streams. Clients can use the attributes exposed by
|
||||
* servers to observe and/or request changes in server behavior.
|
||||
*
|
||||
* To use with your application, add `#import <broadcast_audio_scan_service.gatt>` to your .gatt file.
|
||||
*/
|
||||
|
||||
#ifndef BROADCAST_AUDIO_SCAN_SERVICE_SERVER_H
|
||||
#define BROADCAST_AUDIO_SCAN_SERVICE_SERVER_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include "le_audio.h"
|
||||
#include "broadcast_audio_scan_service_util.h"
|
||||
|
||||
#if defined __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* API_START */
|
||||
// memory for list of these structs is allocated by the application
|
||||
typedef struct {
|
||||
// assigned by client via control point
|
||||
bass_source_data_t data;
|
||||
|
||||
uint8_t update_counter;
|
||||
uint8_t source_id;
|
||||
bool in_use;
|
||||
|
||||
le_audio_pa_sync_t pa_sync;
|
||||
le_audio_big_encryption_t big_encryption;
|
||||
uint8_t bad_code[16];
|
||||
|
||||
uint16_t bass_receive_state_handle;
|
||||
uint16_t bass_receive_state_client_configuration_handle;
|
||||
uint16_t bass_receive_state_client_configuration;
|
||||
} bass_server_source_t;
|
||||
|
||||
typedef struct {
|
||||
hci_con_handle_t con_handle;
|
||||
uint16_t sources_to_notify;
|
||||
|
||||
// used for caching long write
|
||||
uint8_t long_write_buffer[512];
|
||||
uint16_t long_write_value_size;
|
||||
uint16_t long_write_attribute_handle;
|
||||
} bass_remote_client_t;
|
||||
|
||||
/**
|
||||
* @brief Init Broadcast Audio Scan Service Server with ATT DB
|
||||
* @param sources_num
|
||||
* @param sources
|
||||
* @param clients_num
|
||||
* @param clients
|
||||
*/
|
||||
void broadcast_audio_scan_service_server_init(uint8_t const sources_num, bass_server_source_t * sources, uint8_t const clients_num, bass_remote_client_t * clients);
|
||||
|
||||
/**
|
||||
* @brief Register callback.
|
||||
* @param callback
|
||||
*/
|
||||
void broadcast_audio_scan_service_server_register_packet_handler(btstack_packet_handler_t callback);
|
||||
|
||||
/**
|
||||
* @brief Set PA state of source.
|
||||
* @param source_index
|
||||
* @param sync_state
|
||||
*/
|
||||
void broadcast_audio_scan_service_server_set_pa_sync_state(uint8_t source_index, le_audio_pa_sync_state_t sync_state);
|
||||
|
||||
/**
|
||||
* @brief Register callback.
|
||||
* @param source_data
|
||||
* @param source_index
|
||||
*/
|
||||
void broadcast_audio_scan_service_server_add_source(bass_source_data_t source_data, uint8_t * source_index);
|
||||
|
||||
/**
|
||||
* @brief Deinit Broadcast Audio Scan Service Server
|
||||
*/
|
||||
void broadcast_audio_scan_service_server_deinit(void);
|
||||
|
||||
/* API_END */
|
||||
|
||||
#if defined __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
228
src/ble/gatt-service/broadcast_audio_scan_service_util.c
Normal file
228
src/ble/gatt-service/broadcast_audio_scan_service_util.c
Normal file
@ -0,0 +1,228 @@
|
||||
/*
|
||||
* Copyright (C) 2022 BlueKitchen GmbH
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of the copyright holders nor the names of
|
||||
* contributors may be used to endorse or promote products derived
|
||||
* from this software without specific prior written permission.
|
||||
* 4. Any redistribution, use, or modification is done solely for
|
||||
* personal benefit and not for any commercial purpose or for
|
||||
* monetary gain.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY BLUEKITCHEN GMBH AND CONTRIBUTORS
|
||||
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BLUEKITCHEN
|
||||
* GMBH OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
|
||||
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
|
||||
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
|
||||
* THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
*
|
||||
* Please inquire about commercial licensing options at
|
||||
* contact@bluekitchen-gmbh.com
|
||||
*
|
||||
*/
|
||||
|
||||
#define BTSTACK_FILE__ "broadcast_audio_scan_service_server.c"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include "ble/att_db.h"
|
||||
#include "ble/att_server.h"
|
||||
#include "bluetooth_gatt.h"
|
||||
#include "btstack_debug.h"
|
||||
#include "btstack_defines.h"
|
||||
#include "btstack_event.h"
|
||||
#include "btstack_util.h"
|
||||
|
||||
#include "ble/gatt-service/broadcast_audio_scan_service_util.h"
|
||||
#include "ble/gatt-service/le_audio.h"
|
||||
#include "ble/gatt-service/le_audio_util.h"
|
||||
|
||||
#ifdef ENABLE_TESTING_SUPPORT
|
||||
#include <stdio.h>
|
||||
#endif
|
||||
|
||||
// offset gives position into fully serialized bass record
|
||||
uint16_t bass_util_copy_source_common_data_to_buffer(bass_source_data_t * data, uint16_t *source_offset, uint16_t buffer_offset, uint8_t * buffer, uint16_t buffer_size){
|
||||
uint16_t stored_bytes = 0;
|
||||
uint8_t field_data[16];
|
||||
|
||||
field_data[0] = (uint8_t)data->address_type;
|
||||
stored_bytes += le_audio_virtual_memcpy_helper(field_data, 1, *source_offset, buffer, buffer_size, buffer_offset);
|
||||
(*source_offset)++;
|
||||
|
||||
reverse_bd_addr(data->address, &field_data[0]);
|
||||
stored_bytes += le_audio_virtual_memcpy_helper(field_data, 6, *source_offset, buffer, buffer_size, buffer_offset);
|
||||
(*source_offset) += 6;
|
||||
|
||||
field_data[0] = data->adv_sid;
|
||||
stored_bytes += le_audio_virtual_memcpy_helper(field_data, 1, *source_offset, buffer, buffer_size, buffer_offset);
|
||||
(*source_offset)++;
|
||||
|
||||
little_endian_store_24(field_data, 0, data->broadcast_id);
|
||||
stored_bytes += le_audio_virtual_memcpy_helper(field_data, 3, *source_offset, buffer, buffer_size, buffer_offset);
|
||||
(*source_offset) += 3;
|
||||
|
||||
return stored_bytes;
|
||||
}
|
||||
|
||||
// offset gives position into fully serialized bass record
|
||||
uint16_t bass_util_copy_source_metadata_to_buffer(bass_source_data_t * data, uint16_t *source_offset, uint16_t buffer_offset, uint8_t * buffer, uint16_t buffer_size){
|
||||
uint16_t stored_bytes = 0;
|
||||
uint8_t field_data[16];
|
||||
|
||||
field_data[0] = (uint8_t)data->subgroups_num;
|
||||
stored_bytes += le_audio_virtual_memcpy_helper(field_data, 1, *source_offset, buffer, buffer_size, buffer_offset);
|
||||
(*source_offset)++;
|
||||
|
||||
if (data->subgroups_num == 0){
|
||||
return stored_bytes;
|
||||
}
|
||||
|
||||
uint8_t i;
|
||||
for (i = 0; i < data->subgroups_num; i++){
|
||||
bass_subgroup_t subgroup = data->subgroups[i];
|
||||
|
||||
little_endian_store_32(field_data, 0, subgroup.bis_sync_state);
|
||||
|
||||
stored_bytes += le_audio_virtual_memcpy_helper(field_data, 4, *source_offset, buffer, buffer_size, buffer_offset);
|
||||
(*source_offset) += 4;
|
||||
stored_bytes += le_audio_virtual_memcpy_metadata(&subgroup.metadata, subgroup.metadata_length, source_offset, buffer, buffer_size, buffer_offset);
|
||||
}
|
||||
return stored_bytes;
|
||||
}
|
||||
|
||||
bool bass_util_pa_sync_state_and_subgroups_in_valid_range(uint8_t *buffer, uint16_t buffer_size){
|
||||
uint8_t pos = 0;
|
||||
// pa_sync_state
|
||||
uint8_t pa_sync_state = buffer[pos++];
|
||||
if (pa_sync_state >= (uint8_t)LE_AUDIO_PA_SYNC_STATE_RFU){
|
||||
log_info("Unexpected pa_sync_state 0x%02X", pa_sync_state);
|
||||
return false;
|
||||
}
|
||||
|
||||
// pa_interval
|
||||
pos += 2;
|
||||
uint8_t num_subgroups = buffer[pos++];
|
||||
if (num_subgroups > BASS_SUBGROUPS_MAX_NUM){
|
||||
log_info("Number of subgroups %u exceedes maximum %u", num_subgroups, BASS_SUBGROUPS_MAX_NUM);
|
||||
return false;
|
||||
}
|
||||
|
||||
// If a BIS_Sync parameter value is not 0xFFFFFFFF for a subgroup,
|
||||
// and if a BIS_index value written by a client is set to a value of 0b1 in more than one subgroup,
|
||||
// the server shall ignore the operation.
|
||||
uint8_t i;
|
||||
uint32_t mask_total = 0;
|
||||
for (i = 0; i < num_subgroups; i++){
|
||||
// bis_sync
|
||||
uint32_t bis_sync = little_endian_read_32(buffer, pos);
|
||||
pos += 4;
|
||||
|
||||
if (bis_sync != 0xFFFFFFFF){
|
||||
uint32_t mask_add = mask_total + ~bis_sync;
|
||||
uint32_t mask_or = mask_total | ~bis_sync;
|
||||
if (mask_add != mask_or){
|
||||
// not disjunct
|
||||
return false;
|
||||
}
|
||||
mask_total = mask_add;
|
||||
}
|
||||
|
||||
// check if we can read metadata_length
|
||||
if (pos >= buffer_size){
|
||||
log_info("Metadata length not specified, subgroup %u", i);
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t metadata_length = buffer[pos++];
|
||||
|
||||
if ((buffer_size - pos) < metadata_length){
|
||||
log_info("Metadata length %u exceedes remaining buffer %u", metadata_length, buffer_size - pos);
|
||||
return false;
|
||||
}
|
||||
// metadata
|
||||
pos += metadata_length;
|
||||
}
|
||||
|
||||
return (pos == buffer_size);
|
||||
}
|
||||
|
||||
bool bass_util_add_source_buffer_in_valid_range(uint8_t *buffer, uint16_t buffer_size){
|
||||
if (buffer_size < 15){
|
||||
log_info("Add Source opcode, buffer too small");
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t pos = 0;
|
||||
// addr type
|
||||
uint8_t adv_type = buffer[pos++];
|
||||
if (adv_type > (uint8_t)BD_ADDR_TYPE_LE_RANDOM){
|
||||
log_info("Unexpected adv_type 0x%02X", adv_type);
|
||||
return false;
|
||||
}
|
||||
|
||||
// address
|
||||
pos += 6;
|
||||
|
||||
// advertising_sid Range: 0x00-0x0F
|
||||
uint8_t advertising_sid = buffer[pos++];
|
||||
if (advertising_sid > 0x0F){
|
||||
log_info("Advertising sid out of range 0x%02X", advertising_sid);
|
||||
return false;
|
||||
}
|
||||
|
||||
// broadcast_id
|
||||
pos += 3;
|
||||
return bass_util_pa_sync_state_and_subgroups_in_valid_range(buffer+pos, buffer_size-pos);
|
||||
}
|
||||
|
||||
void bass_util_get_pa_info_and_subgroups_from_buffer(uint8_t *buffer, uint16_t buffer_size, bass_source_data_t * source_data){
|
||||
UNUSED(buffer_size);
|
||||
uint8_t pos = 0;
|
||||
source_data->pa_sync_state = (le_audio_pa_sync_state_t)buffer[pos++];
|
||||
source_data->pa_interval = little_endian_read_16(buffer, pos);
|
||||
pos += 2;
|
||||
|
||||
source_data->subgroups_num = buffer[pos++];
|
||||
uint8_t i;
|
||||
for (i = 0; i < source_data->subgroups_num; i++){
|
||||
// bis_sync
|
||||
source_data->subgroups[i].bis_sync_state = little_endian_read_32(buffer, pos);
|
||||
pos += 4;
|
||||
|
||||
uint8_t metadata_bytes_read = le_audio_metadata_parse_tlv(&buffer[pos], buffer_size - pos, &source_data->subgroups[i].metadata);
|
||||
source_data->subgroups[i].metadata_length = metadata_bytes_read - 1;
|
||||
pos += metadata_bytes_read;
|
||||
}
|
||||
}
|
||||
|
||||
void bass_util_get_source_from_buffer(uint8_t *buffer, uint16_t buffer_size, bass_source_data_t * source_data){
|
||||
UNUSED(buffer_size);
|
||||
uint8_t pos = 0;
|
||||
|
||||
source_data->address_type = (bd_addr_type_t)buffer[pos++];
|
||||
|
||||
reverse_bd_addr(&buffer[pos], source_data->address);
|
||||
pos += 6;
|
||||
|
||||
source_data->adv_sid = buffer[pos++];
|
||||
|
||||
source_data->broadcast_id = little_endian_read_24(buffer, pos);
|
||||
pos += 3;
|
||||
|
||||
bass_util_get_pa_info_and_subgroups_from_buffer(buffer+pos, buffer_size-pos, source_data);
|
||||
}
|
120
src/ble/gatt-service/broadcast_audio_scan_service_util.h
Normal file
120
src/ble/gatt-service/broadcast_audio_scan_service_util.h
Normal file
@ -0,0 +1,120 @@
|
||||
/*
|
||||
* Copyright (C) 2022 BlueKitchen GmbH
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of the copyright holders nor the names of
|
||||
* contributors may be used to endorse or promote products derived
|
||||
* from this software without specific prior written permission.
|
||||
* 4. Any redistribution, use, or modification is done solely for
|
||||
* personal benefit and not for any commercial purpose or for
|
||||
* monetary gain.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY BLUEKITCHEN GMBH AND CONTRIBUTORS
|
||||
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BLUEKITCHEN
|
||||
* GMBH OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
|
||||
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
|
||||
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
|
||||
* THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
*
|
||||
* Please inquire about commercial licensing options at
|
||||
* contact@bluekitchen-gmbh.com
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#ifndef BROADCAST_AUDIO_SCAN_SERVICE_UTIL_H
|
||||
#define BROADCAST_AUDIO_SCAN_SERVICE_UTIL_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include "le_audio.h"
|
||||
|
||||
#if defined __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define BASS_ERROR_CODE_OPCODE_NOT_SUPPORTED 0x80
|
||||
#define BASS_ERROR_CODE_INVALID_SOURCE_ID 0x81
|
||||
#define BASS_SUBGROUPS_MAX_NUM 10
|
||||
|
||||
#define BASS_MAX_NOTIFY_BUFFER_SIZE 200
|
||||
#define BASS_INVALID_SOURCE_INDEX 0xFF
|
||||
|
||||
/* API_START */
|
||||
|
||||
typedef enum {
|
||||
BASS_OPCODE_REMOTE_SCAN_STOPPED = 0x00,
|
||||
BASS_OPCODE_REMOTE_SCAN_STARTED,
|
||||
BASS_OPCODE_ADD_SOURCE,
|
||||
BASS_OPCODE_MODIFY_SOURCE,
|
||||
BASS_OPCODE_SET_BROADCAST_CODE,
|
||||
BASS_OPCODE_REMOVE_SOURCE,
|
||||
BASS_OPCODE_RFU
|
||||
} bass_opcode_t;
|
||||
|
||||
typedef enum {
|
||||
BASS_ROLE_CLIENT = 0,
|
||||
BASS_ROLE_SERVER
|
||||
} bass_role_t;
|
||||
|
||||
typedef struct {
|
||||
// 4-octet bitfield. Shall not exist if num_subgroups = 0
|
||||
// Bit 0-30 = BIS_index[1-31]
|
||||
// 0x00000000: 0 = Not synchronized to BIS_index[x]
|
||||
// 0xxxxxxxxx: 1 = Synchronized to BIS_index[x]
|
||||
// 0xFFFFFFFF: Failed to sync to BIG
|
||||
uint32_t bis_sync_state;
|
||||
|
||||
uint8_t metadata_length;
|
||||
le_audio_metadata_t metadata;
|
||||
} bass_subgroup_t;
|
||||
|
||||
typedef struct {
|
||||
// assigned by client via control point
|
||||
bd_addr_type_t address_type;
|
||||
bd_addr_t address;
|
||||
uint8_t adv_sid;
|
||||
uint32_t broadcast_id;
|
||||
le_audio_pa_sync_state_t pa_sync_state;
|
||||
le_audio_pa_sync_t pa_sync;
|
||||
uint16_t pa_interval;
|
||||
|
||||
uint8_t subgroups_num;
|
||||
// Shall not exist if num_subgroups = 0
|
||||
bass_subgroup_t subgroups[BASS_SUBGROUPS_MAX_NUM];
|
||||
} bass_source_data_t;
|
||||
|
||||
// offset gives position into fully serialized BASS record
|
||||
uint16_t bass_util_copy_source_common_data_to_buffer(bass_source_data_t * data, uint16_t *source_offset, uint16_t buffer_offset, uint8_t * buffer, uint16_t buffer_size);
|
||||
|
||||
uint16_t bass_util_copy_source_metadata_to_buffer(bass_source_data_t * data, uint16_t *source_offset, uint16_t buffer_offset, uint8_t * buffer, uint16_t buffer_size);
|
||||
|
||||
bool bass_util_pa_sync_state_and_subgroups_in_valid_range(uint8_t *buffer, uint16_t buffer_size);
|
||||
|
||||
bool bass_util_add_source_buffer_in_valid_range(uint8_t *buffer, uint16_t buffer_size);
|
||||
|
||||
void bass_util_get_source_from_buffer(uint8_t *buffer, uint16_t buffer_size, bass_source_data_t * source_data);
|
||||
|
||||
void bass_util_get_pa_info_and_subgroups_from_buffer(uint8_t *buffer, uint16_t buffer_size, bass_source_data_t * source_data);
|
||||
|
||||
/* API_END */
|
||||
|
||||
#if defined __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
@ -87,6 +87,8 @@
|
||||
#include "ble/gatt-service/battery_service_client.h"
|
||||
#include "ble/gatt-service/battery_service_server.h"
|
||||
#include "ble/gatt-service/bond_management_service_server.h"
|
||||
#include "ble/gatt-service/broadcast_audio_scan_service_client.h"
|
||||
#include "ble/gatt-service/broadcast_audio_scan_service_server.h"
|
||||
#include "ble/gatt-service/cycling_power_service_server.h"
|
||||
#include "ble/gatt-service/cycling_speed_and_cadence_service_server.h"
|
||||
#include "ble/gatt-service/device_information_service_client.h"
|
||||
|
Loading…
x
Reference in New Issue
Block a user