mirror of
https://github.com/bluekitchen/btstack.git
synced 2025-03-15 22:20:59 +00:00
gatt-service/vcs: implement notifications
This commit is contained in:
parent
5e9859c731
commit
4cb846c806
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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 <volume_control_service.gatt>` 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 */
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user