From 424fb93ec41514d160b85965ef4e15361750bb26 Mon Sep 17 00:00:00 2001 From: philippe44 Date: Sat, 28 Sep 2024 23:17:09 +0200 Subject: [PATCH] add 2nd encoder for volume only --- CHANGELOG | 4 ++ components/services/audio_controls.c | 49 ++++++++++++++ components/services/audio_controls.h | 6 ++ components/services/buttons.c | 95 +++++++++++++++++++--------- components/services/buttons.h | 2 +- main/Kconfig.projbuild | 6 ++ main/esp_app_main.c | 1 + 7 files changed, 131 insertions(+), 32 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index cdc8a9d2..aa96c81d 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,7 @@ +2024-09-28 + - add dedicated volume encoder + - fix memory leak in rotary config creation + 2024-09-28 - create autoexec NVS entry at the right place (not only whne BT is enabled! - try to make i2s panic mode work for all esp versions diff --git a/components/services/audio_controls.c b/components/services/audio_controls.c index 1850236d..dff7cda5 100644 --- a/components/services/audio_controls.c +++ b/components/services/audio_controls.c @@ -38,6 +38,7 @@ static esp_err_t actrls_process_action (const cJSON * member, actrls_config_t *c static esp_err_t actrls_init_json(const char *profile_name, bool create); static void control_rotary_handler(void *client, rotary_event_e event, bool long_press); +static void volume_rotary_handler(void *client, rotary_event_e event, bool long_press); static void rotary_timer( TimerHandle_t xTimer ); static const actrls_config_map_t actrls_config_map[] = @@ -157,6 +158,24 @@ esp_err_t actrls_init(const char *profile_name) { err = create_rotary(NULL, A, B, SW, longpress, control_rotary_handler) ? ESP_OK : ESP_FAIL; } + free(config); + config = config_alloc_get_default(NVS_TYPE_STR, "volume_rotary", NULL, 0); + + // now see if we have a dedicated volume rotary + if (config && *config) { + int A = -1, B = -1, SW = -1; + + // parse config + PARSE_PARAM(config, "A", '=', A); + PARSE_PARAM(config, "B", '=', B); + PARSE_PARAM(config, "SW", '=', SW); + + // create rotary (no handling of long press) + err |= create_volume_rotary(NULL, A, B, SW, volume_rotary_handler) ? ESP_OK : ESP_FAIL; + } + + free(config); + // set infrared GPIO if any parse_set_GPIO(set_ir_gpio); @@ -290,6 +309,29 @@ static void control_rotary_handler(void *client, rotary_event_e event, bool long if (action != ACTRLS_NONE) (*current_controls[action])(pressed); } +/**************************************************************************************** + * + */ +static void volume_rotary_handler(void *client, rotary_event_e event, bool long_press) { + actrls_action_e action = ACTRLS_NONE; + bool pressed = true; + + switch(event) { + case ROTARY_LEFT: + action = ACTRLS_VOLDOWN; + break; + case ROTARY_RIGHT: + action = ACTRLS_VOLUP; + break; + case ROTARY_PRESSED: + action = ACTRLS_TOGGLE; + default: + break; + } + + if (action != ACTRLS_NONE) (*current_controls[action])(pressed); +} + /**************************************************************************************** * */ @@ -568,6 +610,13 @@ exit: return err; } +/**************************************************************************************** + * + */ +actrls_handler get_ctrl_handler(actrls_action_e action) { + return current_controls[action]; +} + /**************************************************************************************** * */ diff --git a/components/services/audio_controls.h b/components/services/audio_controls.h index 2e45c20d..ebc8422a 100644 --- a/components/services/audio_controls.h +++ b/components/services/audio_controls.h @@ -53,3 +53,9 @@ void actrls_set_default(const actrls_t controls, bool raw_controls, actrls_hook_ void actrls_set(const actrls_t controls, bool raw_controls, actrls_hook_t *hook, actrls_ir_handler_t *ir_handler); void actrls_unset(void); bool actrls_ir_action(uint16_t addr, uint16_t code); + +/* Call this to get the handler for any of the audio actions. It will map to the control specific +to the current mode (LMS, AirPlay, Spotify). This is useful if you have a custom way to create +buttons (like analogue buttons) +*/ +actrls_handler get_ctrl_handler(actrls_action_e); diff --git a/components/services/buttons.c b/components/services/buttons.c index 1c2a406e..5cbcee49 100644 --- a/components/services/buttons.c +++ b/components/services/buttons.c @@ -58,13 +58,13 @@ static struct { static TimerHandle_t polled_timer; -static EXT_RAM_ATTR struct { +static EXT_RAM_ATTR struct encoder { QueueHandle_t queue; void *client; rotary_encoder_info_t info; int A, B, SW; rotary_handler handler; -} rotary; +} rotary, volume; static EXT_RAM_ATTR struct { RingbufHandle_t rb; @@ -227,11 +227,22 @@ static void buttons_task(void* arg) { // received a rotary event xQueueReceive(rotary.queue, &event, 0); - ESP_LOGD(TAG, "Event: position %d, direction %s", event.state.position, + ESP_LOGD(TAG, "Rotary event: position %d, direction %s", event.state.position, event.state.direction ? (event.state.direction == ROTARY_ENCODER_DIRECTION_CLOCKWISE ? "CW" : "CCW") : "NOT_SET"); rotary.handler(rotary.client, event.state.direction == ROTARY_ENCODER_DIRECTION_CLOCKWISE ? ROTARY_RIGHT : ROTARY_LEFT, false); + } else if (xActivatedMember == volume.queue) { + rotary_encoder_event_t event = { 0 }; + + // received a volume rotary event + xQueueReceive(volume.queue, &event, 0); + + ESP_LOGD(TAG, "Volume event: position %d, direction %s", event.state.position, + event.state.direction ? (event.state.direction == ROTARY_ENCODER_DIRECTION_CLOCKWISE ? "CW" : "CCW") : "NOT_SET"); + + volume.handler(volume.client, event.state.direction == ROTARY_ENCODER_DIRECTION_CLOCKWISE ? + ROTARY_RIGHT : ROTARY_LEFT, false); } else { // this is IR active = infrared_receive(infrared.rb, infrared.handler); @@ -395,7 +406,55 @@ void *button_remap(void *client, int gpio, button_handler handler, int long_pres } /**************************************************************************************** - * Rotary encoder handler + * Create rotary encoder + */ +static bool create_rotary_encoder(struct encoder *encoder, void *id, int A, int B, int SW, int long_press, rotary_handler handler, button_handler button) { + // nasty ESP32 bug: fire-up constantly INT on GPIO 36/39 if ADC1, AMP, Hall used which WiFi does when PS is activated + if (A == -1 || B == -1 || A == 36 || A == 39 || B == 36 || B == 39) { + ESP_LOGI(TAG, "Cannot create rotary %d %d", A, B); + return false; + } + + encoder->A = A; + encoder->B = B; + encoder->SW = SW; + encoder->client = id; + encoder->handler = handler; + + // Initialise the rotary encoder device with the GPIOs for A and B signals + rotary_encoder_init(&encoder->info, A, B); + + // Create a queue for events from the rotary encoder driver. + encoder->queue = rotary_encoder_create_queue(); + rotary_encoder_set_queue(&encoder->info, encoder->queue); + + common_task_init(); + xQueueAddToSet( encoder->queue, common_queue_set ); + + // create companion button if rotary has a switch + if (SW != -1) button_create(id, SW, BUTTON_LOW, true, 0, button, long_press, -1); + + return true; +} + +/**************************************************************************************** + * Volume button encoder handler + */ +static void volume_button_handler(void *id, button_event_e event, button_press_e mode, bool long_press) { + ESP_LOGI(TAG, "Volume encoder push-button %d", event); + volume.handler(id, event == BUTTON_PRESSED ? ROTARY_PRESSED : ROTARY_RELEASED, long_press); +} + +/**************************************************************************************** + * Create volume encoder + */ +bool create_volume_rotary(void *id, int A, int B, int SW, rotary_handler handler) { + ESP_LOGI(TAG, "Created volume encoder A:%d B:%d, SW:%d", A, B, SW); + return create_rotary_encoder(&volume, id, A, B, SW, false, handler, volume_button_handler); +} + +/**************************************************************************************** + * Rotary button encoder handler */ static void rotary_button_handler(void *id, button_event_e event, button_press_e mode, bool long_press) { ESP_LOGI(TAG, "Rotary push-button %d", event); @@ -406,34 +465,8 @@ static void rotary_button_handler(void *id, button_event_e event, button_press_e * Create rotary encoder */ bool create_rotary(void *id, int A, int B, int SW, int long_press, rotary_handler handler) { - // nasty ESP32 bug: fire-up constantly INT on GPIO 36/39 if ADC1, AMP, Hall used which WiFi does when PS is activated - if (A == -1 || B == -1 || A == 36 || A == 39 || B == 36 || B == 39) { - ESP_LOGI(TAG, "Cannot create rotary %d %d", A, B); - return false; - } - - rotary.A = A; - rotary.B = B; - rotary.SW = SW; - rotary.client = id; - rotary.handler = handler; - - // Initialise the rotary encoder device with the GPIOs for A and B signals - rotary_encoder_init(&rotary.info, A, B); - - // Create a queue for events from the rotary encoder driver. - rotary.queue = rotary_encoder_create_queue(); - rotary_encoder_set_queue(&rotary.info, rotary.queue); - - common_task_init(); - xQueueAddToSet( rotary.queue, common_queue_set ); - - // create companion button if rotary has a switch - if (SW != -1) button_create(id, SW, BUTTON_LOW, true, 0, rotary_button_handler, long_press, -1); - ESP_LOGI(TAG, "Created rotary encoder A:%d B:%d, SW:%d", A, B, SW); - - return true; + return create_rotary_encoder(&rotary, id, A, B, SW, long_press, handler, rotary_button_handler); } /**************************************************************************************** diff --git a/components/services/buttons.h b/components/services/buttons.h index 042991d6..d85faef1 100644 --- a/components/services/buttons.h +++ b/components/services/buttons.h @@ -34,5 +34,5 @@ typedef enum { ROTARY_LEFT, ROTARY_RIGHT, ROTARY_PRESSED, ROTARY_RELEASED } rota typedef void (*rotary_handler)(void *id, rotary_event_e event, bool long_press); bool create_rotary(void *id, int A, int B, int SW, int long_press, rotary_handler handler); - +bool create_volume_rotary(void *id, int A, int B, int SW, rotary_handler handler); bool create_infrared(int gpio, infrared_handler handler, infrared_mode_t mode); diff --git a/main/Kconfig.projbuild b/main/Kconfig.projbuild index 2e4f6990..1b42ba06 100644 --- a/main/Kconfig.projbuild +++ b/main/Kconfig.projbuild @@ -369,6 +369,12 @@ menu "Squeezelite-ESP32" help Set GPIO for rotary encoder (quadrature phase). See README on SqueezeESP32 project's GitHub for more details A=,B=[,SW=gpio>[[,knobonly[=]|[,volume][,longpress]] + config VOLUME_ROTARY_ENCODER + string "Volume Rotary Encoder configuration" + default "" + help + Set GPIO for volume rotary encoder (quadrature phase). See README on SqueezeESP32 project's GitHub for more details + A=,B=[,SW=gpio>] config GPIO_EXP_CONFIG string "GPIO expander configuration" help diff --git a/main/esp_app_main.c b/main/esp_app_main.c index 67a9f0c7..37ffe966 100644 --- a/main/esp_app_main.c +++ b/main/esp_app_main.c @@ -89,6 +89,7 @@ const DefaultStringVal defaultStringVals[] = { {"actrls_config", ""}, {"lms_ctrls_raw", "n"}, {"rotary_config", CONFIG_ROTARY_ENCODER}, + {"volume_rotary", CONFIG_VOLUME_ROTARY_ENCODER}, {"display_config", CONFIG_DISPLAY_CONFIG}, {"eth_config", CONFIG_ETH_CONFIG}, {"i2c_config", CONFIG_I2C_CONFIG},