diff --git a/src/ble/gatt-service/volume_control_service_server.c b/src/ble/gatt-service/volume_control_service_server.c index ab544ea59..85d5241bb 100644 --- a/src/ble/gatt-service/volume_control_service_server.c +++ b/src/ble/gatt-service/volume_control_service_server.c @@ -49,56 +49,265 @@ #include "bluetooth_gatt.h" #include "btstack_debug.h" - #include "ble/gatt-service/volume_control_service_server.h" -static att_service_handler_t volume_control_service; +#define VCS_CMD_RELATIVE_VOLUME_DOWN 0x00 +#define VCS_CMD_RELATIVE_VOLUME_UP 0x01 +#define VCS_CMD_UNMUTE_RELATIVE_VOLUME_DOWN 0x02 +#define VCS_CMD_UNMUTE_RELATIVE_VOLUME_UP 0x03 +#define VCS_CMD_SET_ABSOLUTE_VOLUME 0x04 +#define VCS_CMD_UNMUTE 0x05 +#define VCS_CMD_MUTE 0x06 -static uint8_t volume_value; -static uint16_t volume_value_client_configuration; -static hci_con_handle_t volume_value_client_configuration_connection; -static uint16_t volume_value_handle; -static uint16_t volume_value_client_configuration_handle; +#define VCS_TASK_SEND_VOLUME_SETTING 0x01 +#define VCS_TASK_SEND_VOLUME_FLAGS 0x02 +static att_service_handler_t volume_control_service; + +static btstack_context_callback_registration_t vcs_callback; +static hci_con_handle_t vcs_con_handle; +static uint8_t vcs_tasks; + +static uint8_t vcs_volume_change_step_size; + +// characteristic: VOLUME_STATE +static uint16_t vcs_volume_state_handle; + +static uint16_t vcs_volume_state_client_configuration_handle; +static uint16_t vcs_volume_state_client_configuration; + +static vcs_mute_t vcs_volume_state_mute; // 0 - not_muted, 1 - muted +static uint8_t vcs_volume_state_volume_setting; // range [0,255] +static uint8_t vcs_volume_state_change_counter; // range [0,255], ounter is increased for every change on mute and volume setting + +// characteristic: VOLUME_FLAGS +static uint16_t vcs_volume_flags_handle; + +static uint16_t vcs_volume_flags_client_configuration_handle; +static uint16_t vcs_volume_flags_client_configuration; + +static vcs_flag_t vcs_volume_flags; + +// characteristic: CONTROL_POINT +static uint16_t vcs_control_point_value_handle; + + +static void volume_control_service_update_change_counter(void){ + vcs_volume_state_change_counter++; +} + +static void volume_control_service_volume_up(void){ + if (vcs_volume_state_volume_setting < (255 - vcs_volume_change_step_size)){ + vcs_volume_state_volume_setting += vcs_volume_change_step_size; + } else { + vcs_volume_state_volume_setting = 255; + } +} + +static void volume_control_service_volume_down(void){ + if (vcs_volume_state_volume_setting > vcs_volume_change_step_size){ + vcs_volume_state_volume_setting -= vcs_volume_change_step_size; + } else { + vcs_volume_state_volume_setting = 0; + } +} + +static void volume_control_service_mute(void){ + vcs_volume_state_mute = VCS_MUTE_ON; +} + +static void volume_control_service_unmute(void){ + vcs_volume_state_mute = VCS_MUTE_OFF; +} static uint16_t volume_control_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); + UNUSED(con_handle); - if (attribute_handle == volume_value_handle){ - return att_read_callback_handle_byte(volume_value, offset, buffer, buffer_size); - } - if (attribute_handle == volume_value_client_configuration_handle){ - return att_read_callback_handle_little_endian_16(volume_value_client_configuration, offset, buffer, buffer_size); - } - return 0; + if (attribute_handle == vcs_volume_state_handle){ + uint8_t value[3]; + value[0] = vcs_volume_state_volume_setting; + value[1] = (uint8_t) vcs_volume_state_mute; + value[2] = vcs_volume_state_change_counter; + return att_read_callback_handle_blob(value, sizeof(value), offset, buffer, buffer_size); + } + + if (attribute_handle == vcs_volume_flags_handle){ + return att_read_callback_handle_byte((uint8_t)vcs_volume_flags, offset, buffer, buffer_size); + } + + return 0; +} + + +static void volume_control_service_can_send_now(void * context){ + UNUSED(context); + + // checks task + if ((vcs_tasks & VCS_TASK_SEND_VOLUME_SETTING) != 0){ + vcs_tasks &= ~VCS_TASK_SEND_VOLUME_SETTING; + + volume_control_service_update_change_counter(); + + uint8_t buffer[3]; + buffer[0] = vcs_volume_state_volume_setting; + buffer[1] = (uint8_t)vcs_volume_state_mute; + buffer[2] = vcs_volume_state_change_counter; + att_server_notify(vcs_con_handle, vcs_volume_state_handle, &buffer[0], sizeof(buffer)); + + } else if ((vcs_tasks & VCS_TASK_SEND_VOLUME_FLAGS) != 0){ + vcs_tasks &= ~VCS_TASK_SEND_VOLUME_FLAGS; + + uint8_t value = (uint8_t)vcs_volume_flags; + att_server_notify(vcs_con_handle, vcs_volume_flags_handle, &value, 1); + } + + if (vcs_tasks != 0){ + att_server_register_can_send_now_callback(&vcs_callback, vcs_con_handle); + } +} + +static void volume_control_service_server_set_callback(uint8_t task){ + uint8_t scheduled_tasks = vcs_tasks; + vcs_tasks |= task; + if (scheduled_tasks == 0){ + vcs_callback.callback = &volume_control_service_can_send_now; + att_server_register_can_send_now_callback(&vcs_callback, vcs_con_handle); + } +} + +static void volume_control_service_server_enable_user_set_volume_setting_flag(void){ + vcs_volume_flags = VCS_FLAG_USER_SET_VOLUME_SETTING; + if (vcs_volume_flags_client_configuration != 0){ + volume_control_service_server_set_callback(VCS_TASK_SEND_VOLUME_FLAGS); + } } static int volume_control_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){ - UNUSED(transaction_mode); - UNUSED(offset); - UNUSED(buffer_size); + UNUSED(transaction_mode); + UNUSED(offset); - if (attribute_handle == volume_value_client_configuration_handle){ - volume_value_client_configuration = little_endian_read_16(buffer, 0); - volume_value_client_configuration_connection = con_handle; - } - return 0; + if (attribute_handle == vcs_control_point_value_handle){ + if (buffer_size < 2){ + return VOLUME_CONTROL_OPCODE_NOT_SUPPORTED; + } + + uint8_t cmd = buffer[0]; + uint8_t change_counter = buffer[1]; + + if (change_counter != vcs_volume_state_change_counter){ + return VOLUME_CONTROL_INVALID_CHANGE_COUNTER; + } + + uint8_t old_volume_setting = vcs_volume_state_volume_setting; + vcs_mute_t old_mute = vcs_volume_state_mute; + + switch (cmd){ + case VCS_CMD_RELATIVE_VOLUME_DOWN: + volume_control_service_volume_down(); + break; + + case VCS_CMD_RELATIVE_VOLUME_UP: + volume_control_service_volume_up(); + break; + + case VCS_CMD_UNMUTE_RELATIVE_VOLUME_DOWN: + volume_control_service_volume_down(); + volume_control_service_unmute(); + break; + + case VCS_CMD_UNMUTE_RELATIVE_VOLUME_UP: + volume_control_service_volume_up(); + volume_control_service_unmute(); + break; + + case VCS_CMD_SET_ABSOLUTE_VOLUME: + if (buffer_size != 3){ + return VOLUME_CONTROL_OPCODE_NOT_SUPPORTED; + } + vcs_volume_state_volume_setting = buffer[2]; + break; + + case VCS_CMD_UNMUTE: + volume_control_service_unmute(); + break; + + case VCS_CMD_MUTE: + volume_control_service_mute(); + break; + + default: + return VOLUME_CONTROL_OPCODE_NOT_SUPPORTED; + } + + if ((old_volume_setting != vcs_volume_state_volume_setting) || (old_mute != vcs_volume_state_mute)){ + volume_control_service_update_change_counter(); + + if (vcs_volume_flags == VCS_FLAG_RESET_VOLUME_SETTING){ + volume_control_service_server_enable_user_set_volume_setting_flag(); + } + } + } + + else if (attribute_handle == vcs_volume_state_client_configuration_handle){ + vcs_volume_state_client_configuration = little_endian_read_16(buffer, 0); + vcs_con_handle = con_handle; + } + + else if (attribute_handle == vcs_volume_flags_client_configuration_handle){ + vcs_volume_flags_client_configuration = little_endian_read_16(buffer, 0); + vcs_con_handle = con_handle; + } + + return 0; } -void volume_control_service_server_init(){ +void volume_control_service_server_init(uint8_t volume_setting, vcs_mute_t mute, uint8_t volume_change_step){ + vcs_volume_state_volume_setting = volume_setting; + vcs_volume_state_mute = mute; + vcs_volume_flags = VCS_FLAG_RESET_VOLUME_SETTING; + vcs_volume_change_step_size = volume_change_step; + + vcs_tasks = 0; + + // get service handle range + uint16_t start_handle = 0; + uint16_t end_handle = 0xfff; + int service_found = gatt_server_get_handle_range_for_service_with_uuid16(ORG_BLUETOOTH_SERVICE_VOLUME_CONTROL, &start_handle, &end_handle); + btstack_assert(service_found != 0); + UNUSED(service_found); - // get service handle range - uint16_t start_handle = 0; - uint16_t end_handle = 0xfff; - int service_found = gatt_server_get_handle_range_for_service_with_uuid16(ORG_BLUETOOTH_SERVICE_VOLUME_CONTROL, &start_handle, &end_handle); - btstack_assert(service_found != 0); - UNUSED(service_found); + // get characteristic value handle and client configuration handle + vcs_control_point_value_handle = gatt_server_get_value_handle_for_characteristic_with_uuid16(start_handle, end_handle, ORG_BLUETOOTH_CHARACTERISTIC_VOLUME_CONTROL_POINT); + + vcs_volume_state_handle = gatt_server_get_value_handle_for_characteristic_with_uuid16(start_handle, end_handle, ORG_BLUETOOTH_CHARACTERISTIC_VOLUME_STATE); + vcs_volume_state_client_configuration_handle = gatt_server_get_client_configuration_handle_for_characteristic_with_uuid16(start_handle, end_handle, ORG_BLUETOOTH_CHARACTERISTIC_VOLUME_STATE); - // register service with ATT Server - volume_control_service.start_handle = start_handle; - volume_control_service.end_handle = end_handle; - volume_control_service.read_callback = &volume_control_service_read_callback; - volume_control_service.write_callback = &volume_control_service_write_callback; - att_server_register_service_handler(&volume_control_service); + vcs_volume_flags_handle = gatt_server_get_value_handle_for_characteristic_with_uuid16(start_handle, end_handle, ORG_BLUETOOTH_CHARACTERISTIC_VOLUME_FLAGS); + vcs_volume_flags_client_configuration_handle = gatt_server_get_client_configuration_handle_for_characteristic_with_uuid16(start_handle, end_handle, ORG_BLUETOOTH_CHARACTERISTIC_VOLUME_FLAGS); + + // register service with ATT Server + volume_control_service.start_handle = start_handle; + volume_control_service.end_handle = end_handle; + volume_control_service.read_callback = &volume_control_service_read_callback; + volume_control_service.write_callback = &volume_control_service_write_callback; + att_server_register_service_handler(&volume_control_service); +} + +void volume_control_service_server_set_volume_state(uint8_t volume_setting, vcs_mute_t mute){ + uint8_t update_value = false; + + if (vcs_volume_state_volume_setting != volume_setting){ + vcs_volume_state_volume_setting = volume_setting; + update_value = true; + } + + if (vcs_volume_state_mute != mute){ + vcs_volume_state_mute = mute; + update_value = true; + } + + if (update_value && (vcs_volume_state_client_configuration != 0)){ + volume_control_service_server_set_callback(VCS_TASK_SEND_VOLUME_SETTING); + } } diff --git a/src/ble/gatt-service/volume_control_service_server.h b/src/ble/gatt-service/volume_control_service_server.h index 9cabe74f8..24f6979f1 100644 --- a/src/ble/gatt-service/volume_control_service_server.h +++ b/src/ble/gatt-service/volume_control_service_server.h @@ -49,21 +49,45 @@ extern "C" { #endif +#define VOLUME_CONTROL_INVALID_CHANGE_COUNTER 0x80 +#define VOLUME_CONTROL_OPCODE_NOT_SUPPORTED 0x81 + +typedef enum { + VCS_MUTE_OFF = 0, + VCS_MUTE_ON +} vcs_mute_t; + +typedef enum { + VCS_FLAG_RESET_VOLUME_SETTING = 0, + VCS_FLAG_USER_SET_VOLUME_SETTING +} vcs_flag_t; + /** - * @text The Volume Control Service enables a device to expose the controls and state of its audio volume. + * @text The Volume Control Service (VCS) enables a device to expose the controls and state of its audio volume. * * To use with your application, add `#import ` to your .gatt file. * After adding it to your .gatt file, you call *volume_control_service_server_init()* * + * VCS may include zero or more instances of VOCS and zero or more instances of AICS + * */ /* API_START */ /** * @brief Init Volume Control Service Server with ATT DB + * @param volume_setting range [0,255] + * @param mute see vcs_mute_t + * @param volume_change_step */ -void volume_control_service_server_init(void); +void volume_control_service_server_init(uint8_t volume_setting, vcs_mute_t mute, uint8_t volume_change_step); +/** + * @brief Set volume state. + * @param volume_setting range [0,255] + * @param mute see vcs_mute_t + */ +void volume_control_service_server_set_volume_state(uint8_t volume_setting, vcs_mute_t mute); /* API_END */