ソースを参照

add 2nd encoder for volume only

philippe44 6 ヶ月 前
コミット
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
  - 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

+ 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 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];	
+}
+
 /****************************************************************************************
  *
  */

+ 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_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);

+ 57 - 24
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);
@@ -394,46 +405,68 @@ void *button_remap(void *client, int gpio, button_handler handler, int long_pres
 	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
  */
-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
 	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;
+	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(&rotary.info, A, B);
+    rotary_encoder_init(&encoder->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);
+    encoder->queue = rotary_encoder_create_queue();
+    rotary_encoder_set_queue(&encoder->info, encoder->queue);
 	
 	common_task_init();
-	xQueueAddToSet( rotary.queue, common_queue_set );
+	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, 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;
+}
+
+/****************************************************************************************
+ * 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);
 
 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);

+ 6 - 0
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=<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
 			string "GPIO expander configuration"
 			help

+ 1 - 0
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},