Browse Source

add 2nd encoder for volume only

philippe44 6 months ago
parent
commit
424fb93ec4

+ 4 - 0
CHANGELOG

@@ -1,3 +1,7 @@
+2024-09-28
+ - add dedicated volume encoder
+ - fix memory leak in rotary config creation
+ 
 2024-09-28
 2024-09-28
  - create autoexec NVS entry at the right place (not only whne BT is enabled!
  - 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
  - try to make i2s panic mode work for all esp versions

+ 49 - 0
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 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 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 void rotary_timer( TimerHandle_t xTimer );
 
 
 static const actrls_config_map_t actrls_config_map[] =
 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;
 		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
 	// set infrared GPIO if any
 	parse_set_GPIO(set_ir_gpio);
 	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);
 	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;
 	return err;
 }
 }
 
 
+/****************************************************************************************
+ *
+ */
+actrls_handler get_ctrl_handler(actrls_action_e action) {
+	return current_controls[action];	
+}
+
 /****************************************************************************************
 /****************************************************************************************
  *
  *
  */
  */

+ 6 - 0
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_set(const actrls_t controls, bool raw_controls, actrls_hook_t *hook, actrls_ir_handler_t *ir_handler);
 void actrls_unset(void);
 void actrls_unset(void);
 bool actrls_ir_action(uint16_t addr, uint16_t code);
 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);

+ 57 - 24
components/services/buttons.c

@@ -58,13 +58,13 @@ static struct {
 
 
 static TimerHandle_t polled_timer;
 static TimerHandle_t polled_timer;
 
 
-static EXT_RAM_ATTR struct {
+static EXT_RAM_ATTR struct encoder {
 	QueueHandle_t queue;
 	QueueHandle_t queue;
 	void *client;
 	void *client;
 	rotary_encoder_info_t info;
 	rotary_encoder_info_t info;
 	int A, B, SW;
 	int A, B, SW;
 	rotary_handler handler;
 	rotary_handler handler;
-} rotary;
+} rotary, volume;
 
 
 static EXT_RAM_ATTR struct {
 static EXT_RAM_ATTR struct {
 	RingbufHandle_t rb;
 	RingbufHandle_t rb;
@@ -227,11 +227,22 @@ static void buttons_task(void* arg) {
 			// received a rotary event
 			// received a rotary event
 		    xQueueReceive(rotary.queue, &event, 0);
 		    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");
 					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.handler(rotary.client, event.state.direction == ROTARY_ENCODER_DIRECTION_CLOCKWISE ? 
 											ROTARY_RIGHT : ROTARY_LEFT, false);   
 											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 {
 		} else {
 			// this is IR
 			// this is IR
 			active = infrared_receive(infrared.rb, infrared.handler);
 			active = infrared_receive(infrared.rb, infrared.handler);
@@ -394,46 +405,68 @@ void *button_remap(void *client, int gpio, button_handler handler, int long_pres
 	return prev_client;
 	return prev_client;
 }
 }
 
 
-/****************************************************************************************
- * Rotary 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);
-	rotary.handler(id, event == BUTTON_PRESSED ? ROTARY_PRESSED : ROTARY_RELEASED, long_press);
-}
-
 /****************************************************************************************
 /****************************************************************************************
  * Create rotary encoder
  * Create rotary encoder
  */
  */
-bool create_rotary(void *id, int A, int B, int SW, int long_press, rotary_handler handler) {
+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
 	// 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) {
 	if (A == -1 || B == -1 || A == 36 || A == 39 || B == 36 || B == 39) {
 		ESP_LOGI(TAG, "Cannot create rotary %d %d", A, B);
 		ESP_LOGI(TAG, "Cannot create rotary %d %d", A, B);
 		return false;
 		return false;
 	}
 	}
 
 
-	rotary.A = A;
-	rotary.B = B;
-	rotary.SW = SW;
-	rotary.client = id;
-	rotary.handler = handler;
+	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
     // Initialise the rotary encoder device with the GPIOs for A and B signals
-    rotary_encoder_init(&rotary.info, A, B);
+    rotary_encoder_init(&encoder->info, A, B);
 		
 		
     // Create a queue for events from the rotary encoder driver.
     // Create a queue for events from the rotary encoder driver.
-    rotary.queue = rotary_encoder_create_queue();
-    rotary_encoder_set_queue(&rotary.info, rotary.queue);
+    encoder->queue = rotary_encoder_create_queue();
+    rotary_encoder_set_queue(&encoder->info, encoder->queue);
 	
 	
 	common_task_init();
 	common_task_init();
-	xQueueAddToSet( rotary.queue, common_queue_set );
+	xQueueAddToSet( encoder->queue, common_queue_set );
 
 
 	// create companion button if rotary has a switch
 	// 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);
+	if (SW != -1) button_create(id, SW, BUTTON_LOW, true, 0, button, long_press, -1);
 	
 	
 	return true;
 	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);
+	rotary.handler(id, event == BUTTON_PRESSED ? ROTARY_PRESSED : ROTARY_RELEASED, long_press);
+}
+
+/****************************************************************************************
+ * Create rotary encoder
+ */
+bool create_rotary(void *id, int A, int B, int SW, int long_press, rotary_handler handler) {
+	ESP_LOGI(TAG, "Created rotary encoder A:%d B:%d, SW:%d", A, B, SW);
+	return create_rotary_encoder(&rotary, id, A, B, SW, long_press, handler, rotary_button_handler);
 }	
 }	
 
 
 /****************************************************************************************
 /****************************************************************************************

+ 1 - 1
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);
 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_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);
 bool create_infrared(int gpio, infrared_handler handler, infrared_mode_t mode);

+ 6 - 0
main/Kconfig.projbuild

@@ -369,6 +369,12 @@ menu "Squeezelite-ESP32"
 			help
 			help
 				Set GPIO for rotary encoder (quadrature phase). See README on SqueezeESP32 project's GitHub for more details
 				Set GPIO for rotary encoder (quadrature phase). See README on SqueezeESP32 project's GitHub for more details
 				A=<gpio>,B=<gpio>[,SW=gpio>[[,knobonly[=<ms>]|[,volume][,longpress]]			
 				A=<gpio>,B=<gpio>[,SW=gpio>[[,knobonly[=<ms>]|[,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=<gpio>,B=<gpio>[,SW=gpio>]
 		config GPIO_EXP_CONFIG
 		config GPIO_EXP_CONFIG
 			string "GPIO expander configuration"
 			string "GPIO expander configuration"
 			help
 			help

+ 1 - 0
main/esp_app_main.c

@@ -89,6 +89,7 @@ const DefaultStringVal defaultStringVals[] = {
     {"actrls_config", ""},
     {"actrls_config", ""},
     {"lms_ctrls_raw", "n"},
     {"lms_ctrls_raw", "n"},
     {"rotary_config", CONFIG_ROTARY_ENCODER},
     {"rotary_config", CONFIG_ROTARY_ENCODER},
+	{"volume_rotary", CONFIG_VOLUME_ROTARY_ENCODER},
     {"display_config", CONFIG_DISPLAY_CONFIG},
     {"display_config", CONFIG_DISPLAY_CONFIG},
     {"eth_config", CONFIG_ETH_CONFIG},
     {"eth_config", CONFIG_ETH_CONFIG},
     {"i2c_config", CONFIG_I2C_CONFIG},
     {"i2c_config", CONFIG_I2C_CONFIG},