Browse Source

add IR and "raw" button mode - release

Philippe G 5 years ago
parent
commit
5b6ddf0b02

+ 17 - 9
components/driver_bt/bt_app_sink.c

@@ -87,41 +87,49 @@ static EXT_RAM_ATTR struct {
 	bool updated;
 	bool updated;
 } s_metadata;	
 } s_metadata;	
 
 
-static void bt_volume_up(void) {
+static void bt_volume_up(bool pressed) {
+	if (!pressed) return;
 	// volume UP/DOWN buttons are not supported by iPhone/Android
 	// volume UP/DOWN buttons are not supported by iPhone/Android
 	volume_set_by_local_host(s_volume < 127-3 ? s_volume + 3 : 127);
 	volume_set_by_local_host(s_volume < 127-3 ? s_volume + 3 : 127);
 	(*bt_app_a2d_cmd_cb)(BT_SINK_VOLUME, s_volume);
 	(*bt_app_a2d_cmd_cb)(BT_SINK_VOLUME, s_volume);
 	ESP_LOGI(BT_AV_TAG, "BT volume up %u", s_volume);
 	ESP_LOGI(BT_AV_TAG, "BT volume up %u", s_volume);
 }
 }
 
 
-static void bt_volume_down(void) {
+static void bt_volume_down(bool pressed) {
+	if (!pressed) return;
 	// volume UP/DOWN buttons are not supported by iPhone/Android
 	// volume UP/DOWN buttons are not supported by iPhone/Android
 	volume_set_by_local_host(s_volume > 3 ? s_volume - 3 : 0);
 	volume_set_by_local_host(s_volume > 3 ? s_volume - 3 : 0);
 	(*bt_app_a2d_cmd_cb)(BT_SINK_VOLUME, s_volume);
 	(*bt_app_a2d_cmd_cb)(BT_SINK_VOLUME, s_volume);
 }
 }
 
 
-static void bt_toggle(void) {
+static void bt_toggle(bool pressed) {
+	if (!pressed) return;
 	if (s_audio == AUDIO_PLAYING) esp_avrc_ct_send_passthrough_cmd(tl++ & 0x0f, ESP_AVRC_PT_CMD_STOP, ESP_AVRC_PT_CMD_STATE_PRESSED);
 	if (s_audio == AUDIO_PLAYING) esp_avrc_ct_send_passthrough_cmd(tl++ & 0x0f, ESP_AVRC_PT_CMD_STOP, ESP_AVRC_PT_CMD_STATE_PRESSED);
 	else esp_avrc_ct_send_passthrough_cmd(tl++, ESP_AVRC_PT_CMD_PLAY, ESP_AVRC_PT_CMD_STATE_PRESSED);
 	else esp_avrc_ct_send_passthrough_cmd(tl++, ESP_AVRC_PT_CMD_PLAY, ESP_AVRC_PT_CMD_STATE_PRESSED);
 }
 }
 
 
-static void bt_play(void) {
+static void bt_play(bool pressed) {
+	if (!pressed) return;
 	esp_avrc_ct_send_passthrough_cmd(tl++ & 0x0f, ESP_AVRC_PT_CMD_PLAY, ESP_AVRC_PT_CMD_STATE_PRESSED);
 	esp_avrc_ct_send_passthrough_cmd(tl++ & 0x0f, ESP_AVRC_PT_CMD_PLAY, ESP_AVRC_PT_CMD_STATE_PRESSED);
 }
 }
 
 
-static void bt_pause(void) {
+static void bt_pause(bool pressed) {
+	if (!pressed) return;
 	esp_avrc_ct_send_passthrough_cmd(tl++ & 0x0f, ESP_AVRC_PT_CMD_PAUSE, ESP_AVRC_PT_CMD_STATE_PRESSED);
 	esp_avrc_ct_send_passthrough_cmd(tl++ & 0x0f, ESP_AVRC_PT_CMD_PAUSE, ESP_AVRC_PT_CMD_STATE_PRESSED);
 }
 }
 
 
-static void bt_stop(void) {
+static void bt_stop(bool pressed) {
+	if (!pressed) return;
 	esp_avrc_ct_send_passthrough_cmd(tl++ & 0x0f, ESP_AVRC_PT_CMD_STOP, ESP_AVRC_PT_CMD_STATE_PRESSED);
 	esp_avrc_ct_send_passthrough_cmd(tl++ & 0x0f, ESP_AVRC_PT_CMD_STOP, ESP_AVRC_PT_CMD_STATE_PRESSED);
 }
 }
 
 
-static void bt_prev(void) {
+static void bt_prev(bool pressed) {
+	if (!pressed) return;
 	esp_avrc_ct_send_passthrough_cmd(tl++ & 0x0f, ESP_AVRC_PT_CMD_BACKWARD, ESP_AVRC_PT_CMD_STATE_PRESSED);
 	esp_avrc_ct_send_passthrough_cmd(tl++ & 0x0f, ESP_AVRC_PT_CMD_BACKWARD, ESP_AVRC_PT_CMD_STATE_PRESSED);
 }
 }
 
 
-static void bt_next(void) {
+static void bt_next(bool pressed) {
+	if (!pressed) return;
 	esp_avrc_ct_send_passthrough_cmd(tl++ & 0x0f, ESP_AVRC_PT_CMD_FORWARD, ESP_AVRC_PT_CMD_STATE_PRESSED);
 	esp_avrc_ct_send_passthrough_cmd(tl++ & 0x0f, ESP_AVRC_PT_CMD_FORWARD, ESP_AVRC_PT_CMD_STATE_PRESSED);
 }
 }
 
 
@@ -297,7 +305,7 @@ static void bt_av_hdl_a2d_evt(uint16_t event, void *p_param)
 				// force metadata update
 				// force metadata update
 				update_metadata(true);
 				update_metadata(true);
 				
 				
-				actrls_set(controls, NULL);
+				actrls_set(controls, false, NULL, actrls_ir_action);
 			} else {
 			} else {
 				// if decoder is busy, stop it (would be better to not ACK this command, but don't know how)
 				// if decoder is busy, stop it (would be better to not ACK this command, but don't know how)
 				esp_avrc_ct_send_passthrough_cmd(tl++ & 0x0f, ESP_AVRC_PT_CMD_STOP, ESP_AVRC_PT_CMD_STATE_PRESSED);	
 				esp_avrc_ct_send_passthrough_cmd(tl++ & 0x0f, ESP_AVRC_PT_CMD_STOP, ESP_AVRC_PT_CMD_STATE_PRESSED);	

+ 17 - 9
components/raop/raop_sink.c

@@ -31,42 +31,50 @@ static log_level *loglevel = &raop_loglevel;
 static struct raop_ctx_s *raop;
 static struct raop_ctx_s *raop;
 static raop_cmd_vcb_t cmd_handler_chain;
 static raop_cmd_vcb_t cmd_handler_chain;
 
 
-static void raop_volume_up(void) {
+static void raop_volume_up(bool pressed) {
+	if (!pressed) return;
 	raop_cmd(raop, RAOP_VOLUME_UP, NULL);
 	raop_cmd(raop, RAOP_VOLUME_UP, NULL);
 	LOG_INFO("AirPlay volume up");
 	LOG_INFO("AirPlay volume up");
 }
 }
 
 
-static void raop_volume_down(void) {
+static void raop_volume_down(bool pressed) {
+	if (!pressed) return;
 	raop_cmd(raop, RAOP_VOLUME_DOWN, NULL);
 	raop_cmd(raop, RAOP_VOLUME_DOWN, NULL);
 	LOG_INFO("AirPlay volume down");
 	LOG_INFO("AirPlay volume down");
 }
 }
 
 
-static void raop_toggle(void) {
+static void raop_toggle(bool pressed) {
+	if (!pressed) return;
 	raop_cmd(raop, RAOP_TOGGLE, NULL);
 	raop_cmd(raop, RAOP_TOGGLE, NULL);
 	LOG_INFO("AirPlay play/pause");
 	LOG_INFO("AirPlay play/pause");
 }
 }
 
 
-static void raop_pause(void) {
+static void raop_pause(bool pressed) {
+	if (!pressed) return;
 	raop_cmd(raop, RAOP_PAUSE, NULL);
 	raop_cmd(raop, RAOP_PAUSE, NULL);
 	LOG_INFO("AirPlay pause");
 	LOG_INFO("AirPlay pause");
 }
 }
 
 
-static void raop_play(void) {
+static void raop_play(bool pressed) {
+	if (!pressed) return;
 	raop_cmd(raop, RAOP_PLAY, NULL);
 	raop_cmd(raop, RAOP_PLAY, NULL);
 	LOG_INFO("AirPlay play");
 	LOG_INFO("AirPlay play");
 }
 }
 
 
-static void raop_stop(void) {
+static void raop_stop(bool pressed) {
+	if (!pressed) return;
 	raop_cmd(raop, RAOP_STOP, NULL);
 	raop_cmd(raop, RAOP_STOP, NULL);
 	LOG_INFO("AirPlay stop");
 	LOG_INFO("AirPlay stop");
 }
 }
 
 
-static void raop_prev(void) {
+static void raop_prev(bool pressed) {
+	if (!pressed) return;
 	raop_cmd(raop, RAOP_PREV, NULL);
 	raop_cmd(raop, RAOP_PREV, NULL);
 	LOG_INFO("AirPlay previous");
 	LOG_INFO("AirPlay previous");
 }
 }
 
 
-static void raop_next(void) {
+static void raop_next(bool pressed) {
+	if (!pressed) return;
 	raop_cmd(raop, RAOP_NEXT, NULL);
 	raop_cmd(raop, RAOP_NEXT, NULL);
 	LOG_INFO("AirPlay next");
 	LOG_INFO("AirPlay next");
 }
 }
@@ -98,7 +106,7 @@ static bool cmd_handler(raop_event_t event, ...) {
 	// now handle events for display
 	// now handle events for display
 	switch(event) {
 	switch(event) {
 	case RAOP_SETUP:
 	case RAOP_SETUP:
-		actrls_set(controls, NULL);
+		actrls_set(controls, false, NULL, actrls_ir_action);
 		displayer_control(DISPLAYER_ACTIVATE, "AIRPLAY");
 		displayer_control(DISPLAYER_ACTIVATE, "AIRPLAY");
 		break;
 		break;
 	case RAOP_PLAY:
 	case RAOP_PLAY:

+ 113 - 36
components/services/audio_controls.c

@@ -17,6 +17,7 @@
 #include "cJSON.h"
 #include "cJSON.h"
 #include "buttons.h"
 #include "buttons.h"
 #include "config.h"
 #include "config.h"
+#include "accessors.h"
 #include "audio_controls.h"
 #include "audio_controls.h"
 
 
 typedef esp_err_t (actrls_config_map_handler) (const cJSON * member, actrls_config_t *cur_config,uint32_t offset);
 typedef esp_err_t (actrls_config_map_handler) (const cJSON * member, actrls_config_t *cur_config,uint32_t offset);
@@ -33,6 +34,9 @@ static esp_err_t actrls_process_type (const cJSON * member, actrls_config_t *cur
 static esp_err_t actrls_process_bool (const cJSON * member, actrls_config_t *cur_config, uint32_t offset);
 static esp_err_t actrls_process_bool (const cJSON * member, actrls_config_t *cur_config, uint32_t offset);
 static esp_err_t actrls_process_action (const cJSON * member, actrls_config_t *cur_config, uint32_t offset);
 static esp_err_t actrls_process_action (const cJSON * member, actrls_config_t *cur_config, uint32_t offset);
 
 
+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 const actrls_config_map_t actrls_config_map[] =
 static const actrls_config_map_t actrls_config_map[] =
 		{
 		{
 			{"gpio", offsetof(actrls_config_t,gpio), actrls_process_int},
 			{"gpio", offsetof(actrls_config_t,gpio), actrls_process_int},
@@ -61,11 +65,88 @@ static actrls_config_t *json_config;
 cJSON * control_profiles = NULL;
 cJSON * control_profiles = NULL;
 static actrls_t default_controls, current_controls;
 static actrls_t default_controls, current_controls;
 static actrls_hook_t *default_hook, *current_hook;
 static actrls_hook_t *default_hook, *current_hook;
+static bool default_raw_controls, current_raw_controls;
+static actrls_ir_handler_t *default_ir_handler, *current_ir_handler;
+
 static struct {
 static struct {
 	bool long_state;
 	bool long_state;
 	bool volume_lock;
 	bool volume_lock;
 } rotary;
 } rotary;
 
 
+static const struct ir_action_map_s{
+		uint32_t code;
+		actrls_action_e action;
+} ir_action_map[] = {	
+	{0x7689b04f, BCTRLS_DOWN}, {0x7689906f, BCTRLS_LEFT}, {0x7689d02f, BCTRLS_RIGHT}, {0x7689e01f, BCTRLS_UP},
+	{0x768900ff, ACTRLS_VOLDOWN}, {0x7689807f, ACTRLS_VOLUP}, 
+	{0x7689c03f, ACTRLS_PREV}, {0x7689a05f, ACTRLS_NEXT},
+	{0x768920df, ACTRLS_PAUSE}, {0x768910ef, ACTRLS_PLAY},
+	{0x00, 0x00},
+};
+
+/****************************************************************************************
+ * This function can be called to map IR codes to default actions
+ */
+bool actrls_ir_action(uint16_t addr, uint16_t cmd) {
+	uint32_t code = (addr << 16) | cmd;
+	struct ir_action_map_s const *map = ir_action_map;
+	
+	while (map->code && map->code != code) map++;
+	
+	if (map->code && current_controls[map->action]) {
+		current_controls[map->action](true);
+		return true;
+	} else {
+		return false;	
+	}	
+}
+
+/****************************************************************************************
+ * 
+ */
+static void ir_handler(uint16_t addr, uint16_t cmd) {
+	ESP_LOGD(TAG, "recaived IR %04hx:%04hx", addr, cmd);
+	if (current_ir_handler) current_ir_handler(addr, cmd);
+}
+
+/****************************************************************************************
+ * 
+ */
+static void set_ir_gpio(int gpio, char *value) {
+	if (!strcasecmp(value, "ir") ) {
+		create_infrared(gpio, ir_handler);
+	}	
+}	
+ 
+/****************************************************************************************
+ * 
+ */
+esp_err_t actrls_init(const char *profile_name) {
+	esp_err_t err = ESP_OK;
+	char *config = config_alloc_get_default(NVS_TYPE_STR, "rotary_config", NULL, 0);
+	
+	if (config && *config) {
+		char *p;
+		int A = -1, B = -1, SW = -1, longpress = 0;
+		
+		// parse config
+		if ((p = strcasestr(config, "A")) != NULL) A = atoi(strchr(p, '=') + 1);
+		if ((p = strcasestr(config, "B")) != NULL) B = atoi(strchr(p, '=') + 1);
+		if ((p = strcasestr(config, "SW")) != NULL) SW = atoi(strchr(p, '=') + 1);
+		if ((p = strcasestr(config, "volume")) != NULL) rotary.volume_lock = true;
+		if ((p = strcasestr(config, "longpress")) != NULL) longpress = 1000;
+				
+		// create rotary (no handling of long press)
+		err = create_rotary(NULL, A, B, SW, longpress, control_rotary_handler) ? ESP_OK : ESP_FAIL;
+	}
+	
+	// set infrared GPIO if any
+	parse_set_GPIO(set_ir_gpio);
+	
+	if (!err) return actrls_init_json(profile_name, true);
+	else return err;
+}
+
 /****************************************************************************************
 /****************************************************************************************
  * 
  * 
  */
  */
@@ -73,6 +154,13 @@ static void control_handler(void *client, button_event_e event, button_press_e p
 	actrls_config_t *key = (actrls_config_t*) client;
 	actrls_config_t *key = (actrls_config_t*) client;
 	actrls_action_detail_t  action_detail;
 	actrls_action_detail_t  action_detail;
 
 
+	// in raw mode, we just do normal action press *and* release, there is no longpress nor shift
+	if (current_raw_controls) {
+		ESP_LOGD(TAG, "calling action %u in raw mode", key->normal[0].action);
+		if (current_controls[key->normal[0].action]) (*current_controls[key->normal[0].action])(event == BUTTON_PRESSED);
+		return;
+	}
+	
 	switch(press) {
 	switch(press) {
 	case BUTTON_NORMAL:
 	case BUTTON_NORMAL:
 		if (long_press) action_detail = key->longpress[event == BUTTON_PRESSED ? 0 : 1];
 		if (long_press) action_detail = key->longpress[event == BUTTON_PRESSED ? 0 : 1];
@@ -87,7 +175,7 @@ static void control_handler(void *client, button_event_e event, button_press_e p
 		break;
 		break;
 	}
 	}
 	
 	
-	ESP_LOGD(TAG, "control gpio:%u press:%u long:%u event:%u action:%u", key->gpio, press, long_press, event,action_detail.action);
+	ESP_LOGD(TAG, "control gpio:%u press:%u long:%u event:%u action:%u", key->gpio, press, long_press, event, action_detail.action);
 
 
 	// stop here if control hook served the request
 	// stop here if control hook served the request
 	if (current_hook && (*current_hook)(key->gpio, action_detail.action, event, press, long_press)) return;
 	if (current_hook && (*current_hook)(key->gpio, action_detail.action, event, press, long_press)) return;
@@ -114,7 +202,7 @@ static void control_handler(void *client, button_event_e event, button_press_e p
 		}	
 		}	
 	} else if (action_detail.action != ACTRLS_NONE) {
 	} else if (action_detail.action != ACTRLS_NONE) {
 		ESP_LOGD(TAG, "calling action %u", action_detail.action);
 		ESP_LOGD(TAG, "calling action %u", action_detail.action);
-		if (current_controls[action_detail.action]) (*current_controls[action_detail.action])();
+		if (current_controls[action_detail.action]) (*current_controls[action_detail.action])(event == BUTTON_PRESSED);
 	}	
 	}	
 }
 }
 
 
@@ -123,6 +211,15 @@ static void control_handler(void *client, button_event_e event, button_press_e p
  */
  */
 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) {
 	actrls_action_e action = ACTRLS_NONE;
 	actrls_action_e action = ACTRLS_NONE;
+	bool pressed = true;
+	
+	// in raw mode, we just pass rotary events
+	if (current_raw_controls) {
+		if (event == ROTARY_LEFT) (*current_controls[KNOB_LEFT])(true);
+		else if (event == ROTARY_RIGHT) (*current_controls[KNOB_RIGHT])(true);
+		else (*current_controls[KNOB_PUSH])(event == ROTARY_PRESSED);
+		return;
+	}
 	
 	
 	switch(event) {
 	switch(event) {
 	case ROTARY_LEFT:
 	case ROTARY_LEFT:
@@ -144,17 +241,7 @@ static void control_rotary_handler(void *client, rotary_event_e event, bool long
 		break;
 		break;
 	}
 	}
 	
 	
-	if (action != ACTRLS_NONE) (*current_controls[action])();
-}
-
-/****************************************************************************************
- * 
- */
-esp_err_t actrls_init(int n, const actrls_config_t *config) {
-	for (int i = 0; i < n; i++) {
-		button_create((void*) (config + i), config[i].gpio, config[i].type, config[i].pull, config[i].debounce, control_handler, config[i].long_press, config[i].shifter_gpio);
-	}
-	return ESP_OK;
+	if (action != ACTRLS_NONE) (*current_controls[action])(pressed);
 }
 }
 
 
 /****************************************************************************************
 /****************************************************************************************
@@ -361,30 +448,13 @@ static void actrls_defaults(actrls_config_t *config) {
 /****************************************************************************************
 /****************************************************************************************
  * 
  * 
  */
  */
-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) {
 	esp_err_t err = ESP_OK;
 	esp_err_t err = ESP_OK;
 	actrls_config_t *cur_config = NULL;
 	actrls_config_t *cur_config = NULL;
 	actrls_config_t *config_root = NULL;
 	actrls_config_t *config_root = NULL;
+	char *config;
 	const cJSON *button;
 	const cJSON *button;
 	
 	
-	char *config = config_alloc_get_default(NVS_TYPE_STR, "rotary_config", NULL, 0);
-	if (config && *config) {
-		char *p;
-		int A = -1, B = -1, SW = -1, longpress = 0;
-		
-		// parse config
-		if ((p = strcasestr(config, "A")) != NULL) A = atoi(strchr(p, '=') + 1);
-		if ((p = strcasestr(config, "B")) != NULL) B = atoi(strchr(p, '=') + 1);
-		if ((p = strcasestr(config, "SW")) != NULL) SW = atoi(strchr(p, '=') + 1);
-		if ((p = strcasestr(config, "volume")) != NULL) rotary.volume_lock = true;
-		if ((p = strcasestr(config, "longpress")) != NULL) longpress = 1000;
-				
-		// create rotary (no handling of long press)
-		err = create_rotary(NULL, A, B, SW, longpress, control_rotary_handler) ? ESP_OK : ESP_FAIL;
-	}
-			
-	if (config) free(config);	
-		
 	if (!profile_name || !*profile_name) return ESP_OK;
 	if (!profile_name || !*profile_name) return ESP_OK;
 	
 	
 	config = config_alloc_get_default(NVS_TYPE_STR, profile_name, NULL, 0);
 	config = config_alloc_get_default(NVS_TYPE_STR, profile_name, NULL, 0);
@@ -416,8 +486,9 @@ esp_err_t actrls_init_json(const char *profile_name, bool create) {
 				esp_err_t loc_err = actrls_process_button(button, cur_config);
 				esp_err_t loc_err = actrls_process_button(button, cur_config);
 				err = (err == ESP_OK) ? loc_err : err;
 				err = (err == ESP_OK) ? loc_err : err;
 				if (loc_err == ESP_OK) {
 				if (loc_err == ESP_OK) {
-					if (create) button_create((void*) cur_config, cur_config->gpio,cur_config->type, cur_config->pull,cur_config->debounce,
-									control_handler, cur_config->long_press, cur_config->shifter_gpio);
+					if (create) button_create((void*) cur_config, cur_config->gpio,cur_config->type, 
+												cur_config->pull,cur_config->debounce, control_handler, 
+												cur_config->long_press, cur_config->shifter_gpio);
 				} else {
 				} else {
 					ESP_LOGE(TAG,"Error parsing button structure.  Button will not be registered.");
 					ESP_LOGE(TAG,"Error parsing button structure.  Button will not be registered.");
 				}
 				}
@@ -439,18 +510,22 @@ esp_err_t actrls_init_json(const char *profile_name, bool create) {
 /****************************************************************************************
 /****************************************************************************************
  *
  *
  */
  */
-void actrls_set_default(const actrls_t controls, actrls_hook_t *hook) {
+void actrls_set_default(const actrls_t controls, bool raw_controls, actrls_hook_t *hook, actrls_ir_handler_t *ir_handler) {
 	memcpy(default_controls, controls, sizeof(actrls_t));
 	memcpy(default_controls, controls, sizeof(actrls_t));
 	memcpy(current_controls, default_controls, sizeof(actrls_t));
 	memcpy(current_controls, default_controls, sizeof(actrls_t));
 	default_hook = current_hook = hook;
 	default_hook = current_hook = hook;
+	default_raw_controls = current_raw_controls = raw_controls;
+	default_ir_handler = current_ir_handler = ir_handler;
 }
 }
 
 
 /****************************************************************************************
 /****************************************************************************************
  * 
  * 
  */
  */
-void actrls_set(const actrls_t controls, actrls_hook_t *hook) {
+void actrls_set(const actrls_t controls, bool raw_controls, actrls_hook_t *hook, actrls_ir_handler_t *ir_handler) {
 	memcpy(current_controls, controls, sizeof(actrls_t));
 	memcpy(current_controls, controls, sizeof(actrls_t));
 	current_hook = hook;
 	current_hook = hook;
+	current_raw_controls = raw_controls;
+	current_ir_handler = ir_handler;
 }
 }
 
 
 /****************************************************************************************
 /****************************************************************************************
@@ -459,4 +534,6 @@ void actrls_set(const actrls_t controls, actrls_hook_t *hook) {
 void actrls_unset(void) {
 void actrls_unset(void) {
 	memcpy(current_controls, default_controls, sizeof(actrls_t));
 	memcpy(current_controls, default_controls, sizeof(actrls_t));
 	current_hook = default_hook;
 	current_hook = default_hook;
+	current_raw_controls = default_raw_controls;
+	current_ir_handler = default_ir_handler;
 }
 }

+ 8 - 5
components/services/audio_controls.h

@@ -18,9 +18,10 @@ typedef enum { 	ACTRLS_NONE = -1, ACTRLS_VOLUP, ACTRLS_VOLDOWN, ACTRLS_TOGGLE, A
 				ACTRLS_REMAP, ACTRLS_MAX 
 				ACTRLS_REMAP, ACTRLS_MAX 
 		} actrls_action_e;
 		} actrls_action_e;
 
 
-typedef void (*actrls_handler)(void);
+typedef void (*actrls_handler)(bool pressed);
 typedef actrls_handler actrls_t[ACTRLS_MAX - ACTRLS_NONE - 1];
 typedef actrls_handler actrls_t[ACTRLS_MAX - ACTRLS_NONE - 1];
 typedef bool actrls_hook_t(int gpio, actrls_action_e action, button_event_e event, button_press_e press, bool long_press);
 typedef bool actrls_hook_t(int gpio, actrls_action_e action, button_event_e event, button_press_e press, bool long_press);
+typedef bool actrls_ir_handler_t(uint16_t addr, uint16_t cmd);
 
 
 // BEWARE any change to struct below must be mapped to actrls_config_map
 // BEWARE any change to struct below must be mapped to actrls_config_map
 typedef struct {
 typedef struct {
@@ -37,14 +38,16 @@ typedef struct actrl_config_s {
 	actrls_action_detail_t normal[2], longpress[2], shifted[2], longshifted[2];	// [0] keypressed, [1] keyreleased
 	actrls_action_detail_t normal[2], longpress[2], shifted[2], longshifted[2];	// [0] keypressed, [1] keyreleased
 } actrls_config_t;
 } actrls_config_t;
 
 
-esp_err_t actrls_init(int n, const actrls_config_t *config);
-esp_err_t actrls_init_json(const char *profile_name, bool create);
+esp_err_t actrls_init(const char *profile_name);
 
 
 /* 
 /* 
 Set hook function to non-null to be set your own direct managemet function, 
 Set hook function to non-null to be set your own direct managemet function, 
 which should return true if it managed the control request, false if the
 which should return true if it managed the control request, false if the
 normal handling should be done
 normal handling should be done
+The add_release boolean forces a release event to be sent if a press action has been 
+set, whether a release action has been set or not
 */
 */
-void actrls_set_default(const actrls_t controls, actrls_hook_t *hook);
-void actrls_set(const actrls_t controls, actrls_hook_t *hook);
+void actrls_set_default(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);

+ 45 - 12
components/services/buttons.c

@@ -20,6 +20,7 @@
 #include "esp_log.h"
 #include "esp_log.h"
 #include "esp_task.h"
 #include "esp_task.h"
 #include "driver/gpio.h"
 #include "driver/gpio.h"
+#include "driver/rmt.h"
 #include "buttons.h"
 #include "buttons.h"
 #include "rotary_encoder.h"
 #include "rotary_encoder.h"
 #include "globdefs.h"
 #include "globdefs.h"
@@ -56,11 +57,29 @@ static struct {
 	rotary_handler handler;
 	rotary_handler handler;
 } rotary;
 } rotary;
 
 
+static struct {
+	RingbufHandle_t rb;
+	infrared_handler handler;
+} infrared;
+
 static xQueueHandle button_evt_queue;
 static xQueueHandle button_evt_queue;
-static QueueSetHandle_t button_queue_set;
+static QueueSetHandle_t common_queue_set;
 
 
 static void buttons_task(void* arg);
 static void buttons_task(void* arg);
 
 
+/****************************************************************************************
+ * Start task needed by button,s rotaty and infrared
+ */
+static void common_task_init(void) {
+	static DRAM_ATTR StaticTask_t xTaskBuffer __attribute__ ((aligned (4)));
+	static EXT_RAM_ATTR StackType_t xStack[BUTTON_STACK_SIZE] __attribute__ ((aligned (4)));
+	
+	if (!common_queue_set) {
+		common_queue_set = xQueueCreateSet(BUTTON_QUEUE_LEN + 1);
+		xTaskCreateStatic( (TaskFunction_t) buttons_task, "buttons_thread", BUTTON_STACK_SIZE, NULL, ESP_TASK_PRIO_MIN + 1, xStack, &xTaskBuffer);
+	}
+ }	
+
 /****************************************************************************************
 /****************************************************************************************
  * GPIO low-level handler
  * GPIO low-level handler
  */
  */
@@ -107,8 +126,8 @@ static void buttons_task(void* arg) {
     while (1) {
     while (1) {
 		QueueSetMemberHandle_t xActivatedMember;
 		QueueSetMemberHandle_t xActivatedMember;
 
 
-		// wait on button and rotary queues
-		if ((xActivatedMember = xQueueSelectFromSet( button_queue_set, portMAX_DELAY )) == NULL) continue;
+		// wait on button, rotary and infrared queues 
+		if ((xActivatedMember = xQueueSelectFromSet( common_queue_set, portMAX_DELAY )) == NULL) continue;
 		
 		
 		if (xActivatedMember == button_evt_queue) {
 		if (xActivatedMember == button_evt_queue) {
 			struct button_s button;
 			struct button_s button;
@@ -150,7 +169,7 @@ static void buttons_task(void* arg) {
 				// button is a copy, so need to go to real context
 				// button is a copy, so need to go to real context
 				button.self->shifting = false;
 				button.self->shifting = false;
 			}
 			}
-		} else {
+		} else if (xActivatedMember == rotary.queue) {
 			rotary_encoder_event_t event = { 0 };
 			rotary_encoder_event_t event = { 0 };
 			
 			
 			// received a rotary event
 			// received a rotary event
@@ -161,6 +180,9 @@ static void buttons_task(void* arg) {
 			
 			
 			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 {
+			// this is IR
+			infrared_receive(infrared.rb, infrared.handler);
 		}	
 		}	
     }
     }
 }	
 }	
@@ -176,18 +198,14 @@ void dummy_handler(void *id, button_event_e event, button_press_e press) {
  * Create buttons 
  * Create buttons 
  */
  */
 void button_create(void *client, int gpio, int type, bool pull, int debounce, button_handler handler, int long_press, int shifter_gpio) { 
 void button_create(void *client, int gpio, int type, bool pull, int debounce, button_handler handler, int long_press, int shifter_gpio) { 
-	static DRAM_ATTR StaticTask_t xTaskBuffer __attribute__ ((aligned (4)));
-	static EXT_RAM_ATTR StackType_t xStack[BUTTON_STACK_SIZE] __attribute__ ((aligned (4)));
-
 	if (n_buttons >= MAX_BUTTONS) return;
 	if (n_buttons >= MAX_BUTTONS) return;
 
 
 	ESP_LOGI(TAG, "Creating button using GPIO %u, type %u, pull-up/down %u, long press %u shifter %d", gpio, type, pull, long_press, shifter_gpio);
 	ESP_LOGI(TAG, "Creating button using GPIO %u, type %u, pull-up/down %u, long press %u shifter %d", gpio, type, pull, long_press, shifter_gpio);
 
 
 	if (!n_buttons) {
 	if (!n_buttons) {
 		button_evt_queue = xQueueCreate(BUTTON_QUEUE_LEN, sizeof(struct button_s));
 		button_evt_queue = xQueueCreate(BUTTON_QUEUE_LEN, sizeof(struct button_s));
-		if (!button_queue_set) button_queue_set = xQueueCreateSet(BUTTON_QUEUE_LEN + 1);
-		xQueueAddToSet( button_evt_queue, button_queue_set );
-		xTaskCreateStatic( (TaskFunction_t) buttons_task, "buttons_thread", BUTTON_STACK_SIZE, NULL, ESP_TASK_PRIO_MIN + 1, xStack, &xTaskBuffer);
+		common_task_init();
+		xQueueAddToSet( button_evt_queue, common_queue_set );
 	}
 	}
 	
 	
 	// just in case this structure is allocated in a future release
 	// just in case this structure is allocated in a future release
@@ -340,8 +358,8 @@ bool create_rotary(void *id, int A, int B, int SW, int long_press, rotary_handle
     rotary.queue = rotary_encoder_create_queue();
     rotary.queue = rotary_encoder_create_queue();
     rotary_encoder_set_queue(&rotary.info, rotary.queue);
     rotary_encoder_set_queue(&rotary.info, rotary.queue);
 	
 	
-	if (!button_queue_set) button_queue_set = xQueueCreateSet(BUTTON_QUEUE_LEN + 1);
-	xQueueAddToSet( rotary.queue, button_queue_set );
+	common_task_init();
+	xQueueAddToSet( rotary.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);
 	if (SW != -1) button_create(id, SW, BUTTON_LOW, true, 0, rotary_button_handler, long_press, -1);
@@ -350,3 +368,18 @@ bool create_rotary(void *id, int A, int B, int SW, int long_press, rotary_handle
 	
 	
 	return true;
 	return true;
 }	
 }	
+
+/****************************************************************************************
+ * Create Infrared
+ */
+bool create_infrared(int gpio, infrared_handler handler) {
+	// initialize IR infrastructure
+	infrared_init(&infrared.rb, gpio);
+	infrared.handler = handler;
+	
+	// join the queue set
+	common_task_init();
+	xRingbufferAddToQueueSetRead(infrared.rb, common_queue_set);
+	
+	return (infrared.rb != NULL);
+}	

+ 4 - 0
components/services/buttons.h

@@ -7,6 +7,8 @@
  */
  */
 
 
 #pragma once
 #pragma once
+
+#include "infrared.h"
  
  
 // button type (pressed = LOW or HIGH, matches GPIO level)
 // button type (pressed = LOW or HIGH, matches GPIO level)
 #define BUTTON_LOW 		0
 #define BUTTON_LOW 		0
@@ -32,3 +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_infrared(int gpio, infrared_handler handler);

+ 177 - 0
components/services/infrared.c

@@ -0,0 +1,177 @@
+/* 
+ *  infrared receiver (using espressif's example)
+ *
+ *  (c) Philippe G. 2020, philippe_44@outlook.com
+ *
+ *  This software is released under the MIT License.
+ *  https://opensource.org/licenses/MIT
+ *
+ */
+#include <stdio.h>
+#include <string.h>
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+#include "esp_err.h"
+#include "esp_log.h"
+#include "driver/rmt.h"
+#include "infrared.h"
+
+static const char* TAG = "IR";
+
+#define RMT_RX_ACTIVE_LEVEL  0   /*!< If we connect with a IR receiver, the data is active low */
+
+#define RMT_RX_CHANNEL    0     /*!< RMT channel for receiver */
+#define RMT_CLK_DIV      100    /*!< RMT counter clock divider */
+#define RMT_TICK_10_US    (80000000/RMT_CLK_DIV/100000)   /*!< RMT counter value for 10 us.(Source clock is APB clock) */
+
+#define NEC_HEADER_HIGH_US    9000                         /*!< NEC protocol header: positive 9ms */
+#define NEC_HEADER_LOW_US     4500                         /*!< NEC protocol header: negative 4.5ms*/
+#define NEC_BIT_ONE_HIGH_US    560                         /*!< NEC protocol data bit 1: positive 0.56ms */
+#define NEC_BIT_ONE_LOW_US    (2250-NEC_BIT_ONE_HIGH_US)   /*!< NEC protocol data bit 1: negative 1.69ms */
+#define NEC_BIT_ZERO_HIGH_US   560                         /*!< NEC protocol data bit 0: positive 0.56ms */
+#define NEC_BIT_ZERO_LOW_US   (1120-NEC_BIT_ZERO_HIGH_US)  /*!< NEC protocol data bit 0: negative 0.56ms */
+#define NEC_BIT_MARGIN         150                          /*!< NEC parse margin time */
+
+#define NEC_ITEM_DURATION(d)  ((d & 0x7fff)*10/RMT_TICK_10_US)  /*!< Parse duration time from memory register value */
+#define NEC_DATA_ITEM_NUM   34  /*!< NEC code item number: header + 32bit data + end */
+#define rmt_item32_tIMEOUT_US  9500   /*!< RMT receiver timeout value(us) */
+
+/****************************************************************************************
+ * 
+ */
+inline bool nec_check_in_range(int duration_ticks, int target_us, int margin_us) {
+    if(( NEC_ITEM_DURATION(duration_ticks) < (target_us + margin_us))
+        && ( NEC_ITEM_DURATION(duration_ticks) > (target_us - margin_us))) {
+        return true;
+    } else {
+        return false;
+    }
+}
+
+/****************************************************************************************
+ * 
+ */
+static bool nec_header_if(rmt_item32_t* item) {
+    if((item->level0 == RMT_RX_ACTIVE_LEVEL && item->level1 != RMT_RX_ACTIVE_LEVEL)
+        && nec_check_in_range(item->duration0, NEC_HEADER_HIGH_US, NEC_BIT_MARGIN)
+        && nec_check_in_range(item->duration1, NEC_HEADER_LOW_US, NEC_BIT_MARGIN)) {
+        return true;
+    }
+    return false;
+}
+
+/****************************************************************************************
+ * 
+ */
+static bool nec_bit_one_if(rmt_item32_t* item) {
+    if((item->level0 == RMT_RX_ACTIVE_LEVEL && item->level1 != RMT_RX_ACTIVE_LEVEL)
+        && nec_check_in_range(item->duration0, NEC_BIT_ONE_HIGH_US, NEC_BIT_MARGIN)
+        && nec_check_in_range(item->duration1, NEC_BIT_ONE_LOW_US, NEC_BIT_MARGIN)) {
+        return true;
+    }
+    return false;
+}
+
+/****************************************************************************************
+ * 
+ */
+static bool nec_bit_zero_if(rmt_item32_t* item) {
+    if((item->level0 == RMT_RX_ACTIVE_LEVEL && item->level1 != RMT_RX_ACTIVE_LEVEL)
+        && nec_check_in_range(item->duration0, NEC_BIT_ZERO_HIGH_US, NEC_BIT_MARGIN)
+        && nec_check_in_range(item->duration1, NEC_BIT_ZERO_LOW_US, NEC_BIT_MARGIN)) {
+        return true;
+    }
+    return false;
+}
+
+/****************************************************************************************
+ * 
+ */
+static int nec_parse_items(rmt_item32_t* item, int item_num, uint16_t* addr, uint16_t* data) {
+    int w_len = item_num;
+    if(w_len < NEC_DATA_ITEM_NUM) {
+        return -1;
+    }
+    int i = 0, j = 0;
+    if(!nec_header_if(item++)) {
+        return -1;
+    }
+    uint16_t addr_t = 0;
+    for(j = 15; j >= 0; j--) {
+        if(nec_bit_one_if(item)) {
+            addr_t |= (1 << j);
+        } else if(nec_bit_zero_if(item)) {
+            addr_t |= (0 << j);
+        } else {
+            return -1;
+        }
+        item++;
+        i++;
+    }
+    uint16_t data_t = 0;
+    for(j = 15; j >= 0; j--) {
+        if(nec_bit_one_if(item)) {
+            data_t |= (1 << j);
+        } else if(nec_bit_zero_if(item)) {
+            data_t |= (0 << j);
+        } else {
+            return -1;
+        }
+        item++;
+        i++;
+    }
+    *addr = addr_t;
+    *data = data_t;
+    return i;
+}
+
+/****************************************************************************************
+ * 
+ */
+void infrared_receive(RingbufHandle_t rb, infrared_handler handler) {
+	size_t rx_size = 0;
+	rmt_item32_t* item = (rmt_item32_t*) xRingbufferReceive(rb, &rx_size, 10 / portTICK_RATE_MS);
+	
+	if (item) {
+		uint16_t addr, cmd;
+		int offset = 0;
+		
+		while (1) {
+			// parse data value from ringbuffer.
+			int res = nec_parse_items(item + offset, rx_size / 4 - offset, &addr, &cmd);
+			if (res > 0) {
+				offset += res + 1;
+				handler(addr, cmd);
+				ESP_LOGD(TAG, "RMT RCV --- addr: 0x%04x cmd: 0x%04x", addr, cmd);
+			} else break;
+        }
+		
+		// after parsing the data, return spaces to ringbuffer.
+        vRingbufferReturnItem(rb, (void*) item);
+    }
+}
+
+/****************************************************************************************
+ * 
+ */
+void infrared_init(RingbufHandle_t *rb, int gpio) {
+	rmt_config_t rmt_rx;
+	
+	ESP_LOGI(TAG, "Starting Infrared Receiver on gpio %d", gpio);
+	
+	// initialize RMT driver
+    rmt_rx.channel = RMT_RX_CHANNEL;
+    rmt_rx.gpio_num = gpio;
+    rmt_rx.clk_div = RMT_CLK_DIV;
+    rmt_rx.mem_block_num = 1;
+    rmt_rx.rmt_mode = RMT_MODE_RX;
+    rmt_rx.rx_config.filter_en = true;
+    rmt_rx.rx_config.filter_ticks_thresh = 100;
+    rmt_rx.rx_config.idle_threshold = rmt_item32_tIMEOUT_US / 10 * (RMT_TICK_10_US);
+    rmt_config(&rmt_rx);
+    rmt_driver_install(rmt_rx.channel, 1000, 0);
+	
+	// get RMT RX ringbuffer
+    rmt_get_ringbuf_handle(RMT_RX_CHANNEL, rb);
+    rmt_rx_start(RMT_RX_CHANNEL, 1);
+}

+ 17 - 0
components/services/infrared.h

@@ -0,0 +1,17 @@
+/* 
+ *  (c) Philippe G. 2019, philippe_44@outlook.com
+ *
+ *  This software is released under the MIT License.
+ *  https://opensource.org/licenses/MIT
+ *
+ */
+
+#pragma once
+
+#include <stdint.h>
+#include "freertos/FreeRTOS.h"
+#include "freertos/ringbuf.h"
+ 
+typedef void (*infrared_handler)(uint16_t addr, uint16_t cmd);
+void infrared_receive(RingbufHandle_t rb, infrared_handler handler);
+void infrared_init(RingbufHandle_t *rb, int gpio);

+ 6 - 2
components/services/monitor.c

@@ -140,24 +140,28 @@ bool spkfault_svc (void) {
 /****************************************************************************************
 /****************************************************************************************
  * 
  * 
  */
  */
-void set_jack_gpio(int gpio, char *value) {
+#ifndef CONFIG_JACK_LOCKED
+static void set_jack_gpio(int gpio, char *value) {
 	if (strcasestr(value, "jack")) {
 	if (strcasestr(value, "jack")) {
 		char *p;
 		char *p;
 		jack.gpio = gpio;	
 		jack.gpio = gpio;	
 		if ((p = strchr(value, ':')) != NULL) jack.active = atoi(p + 1);
 		if ((p = strchr(value, ':')) != NULL) jack.active = atoi(p + 1);
 	}	
 	}	
 }
 }
+#endif
 
 
 /****************************************************************************************
 /****************************************************************************************
  * 
  * 
  */
  */
-void set_spkfault_gpio(int gpio, char *value) {
+#ifndef CONFIG_SPKFAULT_LOCKED
+static void set_spkfault_gpio(int gpio, char *value) {
 	if (strcasestr(value, "spkfault")) {
 	if (strcasestr(value, "spkfault")) {
 		char *p;
 		char *p;
 		spkfault.gpio = gpio;	
 		spkfault.gpio = gpio;	
 		if ((p = strchr(value, ':')) != NULL) spkfault.active = atoi(p + 1);
 		if ((p = strchr(value, ':')) != NULL) spkfault.active = atoi(p + 1);
 	}	
 	}	
 }
 }
+#endif
 
 
 /****************************************************************************************
 /****************************************************************************************
  * 
  * 

+ 190 - 35
components/squeezelite/controls.c

@@ -7,84 +7,225 @@
  */
  */
 
 
 #include "squeezelite.h"
 #include "squeezelite.h"
+#include "config.h"
 #include "audio_controls.h"
 #include "audio_controls.h"
 
 
 static log_level loglevel = lINFO;
 static log_level loglevel = lINFO;
 
 
+#define DOWN_OFS	0x10000
+#define UP_OFS		0x20000
+
+// numbers are simply 0..9
+
+// arrow_right.down	= 0001000e seems to be missing ...
+enum { 	BUTN_POWER_FRONT = 0X0A, BUTN_ARROW_UP, BUTN_ARROW_DOWN, BUTN_ARROW_LEFT, BUTN_KNOB_PUSH, BUTN_SEARCH,
+		BUTN_REW, BUTN_FWD, BUTN_PLAY, BUTN_ADD, BUTN_BRIGHTNESS, BUTN_NOW_PLAYING,
+		BUTN_PAUSE = 0X17, BUTN_BROWSE, BUTN_VOLUP_FRONT, BUTN_VOLDOWN_FRONT, BUTN_SIZE, BUTN_VISUAL, BUTN_VOLUMEMODE,
+		BUTN_PRESET_1 = 0X23, BUTN_PRESET_2, BUTN_PRESET_3, BUTN_PRESET_4, BUTN_PRESET_5, BUTN_PRESET_6, BUTN_SNOOZE,
+		BUTN_KNOB_LEFT = 0X5A, BUTN_KNOB_RIGHT };
+
+#define BUTN_ARROW_RIGHT BUTN_KNOB_PUSH
+		
+#pragma pack(push, 1)
+
+struct BUTN_header {
+	char  opcode[4];
+	u32_t length;
+	u32_t jiffies;
+	u32_t button;
+};
+
+struct IR_header {
+	char  opcode[4];
+	u32_t length;
+	u32_t jiffies;
+	u8_t format; // unused
+	u8_t bits;	// unused
+	u32_t code;
+};
+
+#pragma pack(pop)
+
 static in_addr_t server_ip;
 static in_addr_t server_ip;
 static u16_t server_hport;
 static u16_t server_hport;
 static u16_t server_cport;
 static u16_t server_cport;
 static u8_t mac[6];
 static u8_t mac[6];
 static void	(*chained_notify)(in_addr_t, u16_t, u16_t);
 static void	(*chained_notify)(in_addr_t, u16_t, u16_t);
+static bool raw_mode;
 
 
 static void cli_send_cmd(char *cmd);
 static void cli_send_cmd(char *cmd);
 
 
-static void lms_volume_up(void) {
-	cli_send_cmd("button volup");
+/****************************************************************************************
+ * Send BUTN
+ */
+static void sendBUTN(int code, bool pressed) {
+	struct BUTN_header pkt_header;
+		
+	memset(&pkt_header, 0, sizeof(pkt_header));
+	memcpy(&pkt_header.opcode, "BUTN", 4);
+
+	pkt_header.length = htonl(sizeof(pkt_header) - 8);
+	pkt_header.jiffies = htonl(gettime_ms());
+	pkt_header.button = htonl(code + (pressed ? DOWN_OFS : UP_OFS));
+		
+	LOG_INFO("sending BUTN code %04x %s", code, pressed ? "down" : "up");	
+
+	LOCK_P;
+	send_packet((uint8_t *) &pkt_header, sizeof(pkt_header));
+	UNLOCK_P;
+}
+
+/****************************************************************************************
+ * Send IR
+ */
+static void sendIR(u16_t addr, u16_t cmd) {
+	struct IR_header pkt_header;
+		
+	memset(&pkt_header, 0, sizeof(pkt_header));
+	memcpy(&pkt_header.opcode, "IR  ", 4);
+
+	pkt_header.length = htonl(sizeof(pkt_header) - 8);
+	pkt_header.jiffies = htonl(gettime_ms());
+	pkt_header.format = pkt_header.bits = 0;
+	pkt_header.code = htonl((addr << 16) | cmd);
+		
+	LOG_INFO("sending IR code %04x", (addr << 16) | cmd);	
+
+	LOCK_P;
+	send_packet((uint8_t *) &pkt_header, sizeof(pkt_header));
+	UNLOCK_P;
+}
+
+static void lms_volume_up(bool pressed) {
+	if (raw_mode) {
+		sendBUTN(BUTN_VOLUP_FRONT, pressed);
+	} else {
+		cli_send_cmd("button volup");
+	}	
 }
 }
 
 
-static void lms_volume_down(void) {
-	cli_send_cmd("button voldown");
+static void lms_volume_down(bool pressed) {
+	if (raw_mode) {
+		sendBUTN(BUTN_VOLDOWN_FRONT, pressed);
+	} else {
+		cli_send_cmd("button voldown");
+	}	
 }
 }
 
 
-static void lms_toggle(void) {
-	cli_send_cmd("pause");
+static void lms_toggle(bool pressed) {
+	if (raw_mode) {
+		sendBUTN(BUTN_PAUSE, pressed);
+	} else {
+		cli_send_cmd("pause");
+	}	
 }
 }
 
 
-static void lms_pause(void) {
-	cli_send_cmd("pause 1");
+static void lms_pause(bool pressed) {
+	if (raw_mode) {
+		sendBUTN(BUTN_PAUSE, pressed);
+	} else {
+		cli_send_cmd("pause 1");
+	}	
 }
 }
 
 
-static void lms_play(void) {
-	cli_send_cmd("button play.single");
+static void lms_play(bool pressed) {
+	if (raw_mode) {
+		sendBUTN(BUTN_PLAY, pressed);
+	} else {
+		cli_send_cmd("button play.single");
+	}	
 }
 }
 
 
-static void lms_stop(void) {
+static void lms_stop(bool pressed) {
 	cli_send_cmd("button stop");
 	cli_send_cmd("button stop");
 }
 }
 
 
-static void lms_rew(void) {
-	cli_send_cmd("button rew.repeat");
+static void lms_rew(bool pressed) {
+	if (raw_mode) {
+		sendBUTN(BUTN_REW, pressed);
+	} else {
+		cli_send_cmd("button rew.repeat");
+	}	
 }
 }
 
 
-static void lms_fwd(void) {
-	cli_send_cmd("button fwd.repeat");
+static void lms_fwd(bool pressed) {
+	if (raw_mode) {
+		sendBUTN(BUTN_FWD, pressed);
+	} else {
+		cli_send_cmd("button fwd.repeat");
+	}	
 }
 }
 
 
-static void lms_prev(void) {
-	cli_send_cmd("button rew");
+static void lms_prev(bool pressed) {
+	if (raw_mode) {
+		sendBUTN(BUTN_REW, pressed);
+	} else {
+		cli_send_cmd("button rew");
+	}	
 }
 }
 
 
-static void lms_next(void) {
-	cli_send_cmd("button fwd");
+static void lms_next(bool pressed) {
+	if (raw_mode) {
+		sendBUTN(BUTN_FWD, pressed);
+	} else {
+		cli_send_cmd("button fwd");
+	}
 }
 }
 
 
-static void lms_up(void) {
-	cli_send_cmd("button arrow_up");
+static void lms_up(bool pressed) {
+	if (raw_mode) {
+		sendBUTN(BUTN_ARROW_UP, pressed);
+	} else {
+		cli_send_cmd("button arrow_up");
+	}	
 }
 }
 
 
-static void lms_down(void) {
-	cli_send_cmd("button arrow_down");
+static void lms_down(bool pressed) {
+	if (raw_mode) {
+		sendBUTN(BUTN_ARROW_DOWN, pressed);
+	} else {
+		cli_send_cmd("button arrow_down");
+	}	
 }
 }
 
 
-static void lms_left(void) {
-	cli_send_cmd("button arrow_left");
+static void lms_left(bool pressed) {
+	if (raw_mode) {
+		sendBUTN(BUTN_ARROW_LEFT, pressed);
+	} else {
+		cli_send_cmd("button arrow_left");
+	}	
 }
 }
 
 
-static void lms_right(void) {
-	cli_send_cmd("button arrow_right");
+static void lms_right(bool pressed) {
+	if (raw_mode) {
+		sendBUTN(BUTN_ARROW_RIGHT, pressed);
+	} else {
+		cli_send_cmd("button arrow_right");
+	}	
 }
 }
 
 
-static void lms_knob_left(void) {
-	cli_send_cmd("button knob_left");
+static void lms_knob_left(bool pressed) {
+	if (raw_mode) {
+		sendBUTN(BUTN_KNOB_LEFT, pressed);
+	} else {
+		cli_send_cmd("button knob_left");
+	}	
 }
 }
 
 
-static void lms_knob_right(void) {
-	cli_send_cmd("button knob_right");
+static void lms_knob_right(bool pressed) {
+	if (raw_mode) {
+		sendBUTN(BUTN_KNOB_RIGHT, pressed);
+	} else {
+		cli_send_cmd("button knob_right");
+	}	
 }
 }
 
 
-static void lms_knob_push(void) {
-	cli_send_cmd("button knob_push");
+static void lms_knob_push(bool pressed) {
+	if (raw_mode) {
+		sendBUTN(BUTN_KNOB_PUSH, pressed);
+	} else {
+		cli_send_cmd("button knob_push");
+	}	
 }
 }
 
 
 const actrls_t LMS_controls = {
 const actrls_t LMS_controls = {
@@ -137,13 +278,27 @@ static void notify(in_addr_t ip, u16_t hport, u16_t cport) {
 	if (chained_notify) (*chained_notify)(ip, hport, cport);
 	if (chained_notify) (*chained_notify)(ip, hport, cport);
 }
 }
 
 
+/****************************************************************************************
+ * IR handler
+ */
+static bool ir_handler(u16_t addr, u16_t cmd) {
+	sendIR(addr, cmd);
+	return true;
+}
+
 /****************************************************************************************
 /****************************************************************************************
  * Initialize controls - shall be called once from output_init_embedded
  * Initialize controls - shall be called once from output_init_embedded
  */
  */
 void sb_controls_init(void) {
 void sb_controls_init(void) {
-	LOG_INFO("initializing CLI controls");
+	char *p = config_alloc_get_default(NVS_TYPE_STR, "lms_ctrls_raw", "n", 0);
+	raw_mode = p && (*p == '1' || *p == 'Y' || *p == 'y');
+	free(p);
+	
+	LOG_INFO("initializing audio (buttons/rotary/ir) controls (raw:%u)", raw_mode);
+	
 	get_mac(mac);
 	get_mac(mac);
-	actrls_set_default(LMS_controls, NULL);
+	actrls_set_default(LMS_controls, raw_mode, NULL, ir_handler);
+	
 	chained_notify = server_notify;
 	chained_notify = server_notify;
 	server_notify = notify;
 	server_notify = notify;
 }
 }

+ 38 - 50
components/squeezelite/display.c

@@ -197,23 +197,20 @@ extern const uint8_t vu_bitmap[]   asm("_binary_vu_data_start");
 #define ANIM_SCREEN_1     0x04 
 #define ANIM_SCREEN_1     0x04 
 #define ANIM_SCREEN_2     0x08 
 #define ANIM_SCREEN_2     0x08 
 
 
-static u8_t ANIC_resp = ANIM_NONE;
-static uint16_t SETD_width;
-
 #define SCROLL_STACK_SIZE	(3*1024)
 #define SCROLL_STACK_SIZE	(3*1024)
 #define LINELEN				40
 #define LINELEN				40
 
 
 static log_level loglevel = lINFO;
 static log_level loglevel = lINFO;
 
 
 static bool (*slimp_handler_chain)(u8_t *data, int len);
 static bool (*slimp_handler_chain)(u8_t *data, int len);
-static void (*slimp_loop_chain)(void);
 static void (*notify_chain)(in_addr_t ip, u16_t hport, u16_t cport);
 static void (*notify_chain)(in_addr_t ip, u16_t hport, u16_t cport);
 static bool (*display_bus_chain)(void *from, enum display_bus_cmd_e cmd);
 static bool (*display_bus_chain)(void *from, enum display_bus_cmd_e cmd);
 
 
 #define max(a,b) (((a) > (b)) ? (a) : (b))
 #define max(a,b) (((a) > (b)) ? (a) : (b))
 
 
 static void server(in_addr_t ip, u16_t hport, u16_t cport);
 static void server(in_addr_t ip, u16_t hport, u16_t cport);
-static void send_server(void);
+static void sendSETD(u16_t width, u16_t height);
+static void sendANIC(u8_t code);
 static bool handler(u8_t *data, int len);
 static bool handler(u8_t *data, int len);
 static bool display_bus_handler(void *from, enum display_bus_cmd_e cmd);
 static bool display_bus_handler(void *from, enum display_bus_cmd_e cmd);
 static void vfdc_handler( u8_t *_data, int bytes_read);
 static void vfdc_handler( u8_t *_data, int bytes_read);
@@ -223,7 +220,6 @@ static void grfs_handler(u8_t *data, int len);
 static void grfg_handler(u8_t *data, int len);
 static void grfg_handler(u8_t *data, int len);
 static void grfa_handler(u8_t *data, int len);
 static void grfa_handler(u8_t *data, int len);
 static void visu_handler(u8_t *data, int len);
 static void visu_handler(u8_t *data, int len);
-
 static void displayer_task(void* arg);
 static void displayer_task(void* arg);
 
 
 /* scrolling undocumented information
 /* scrolling undocumented information
@@ -293,11 +289,13 @@ bool sb_display_init(void) {
 		return false;
 		return false;
 	}	
 	}	
 	
 	
+	// inform LMS of our screen dimensions
+	sendSETD(GDS_GetWidth(display), GDS_GetHeight(display));
+	
 	// need to force height to 32 maximum
 	// need to force height to 32 maximum
 	displayer.width = GDS_GetWidth(display);
 	displayer.width = GDS_GetWidth(display);
 	displayer.height = min(GDS_GetHeight(display), SB_HEIGHT);
 	displayer.height = min(GDS_GetHeight(display), SB_HEIGHT);
-	SETD_width = displayer.width;
-
+	
 	// create visu configuration
 	// create visu configuration
 	visu.bar_gap = 1;
 	visu.bar_gap = 1;
 	visu.speed = 100;
 	visu.speed = 100;
@@ -319,9 +317,6 @@ bool sb_display_init(void) {
 	slimp_handler_chain = slimp_handler;
 	slimp_handler_chain = slimp_handler;
 	slimp_handler = handler;
 	slimp_handler = handler;
 	
 	
-	slimp_loop_chain = slimp_loop;
-	slimp_loop = send_server;
-	
 	notify_chain = server_notify;
 	notify_chain = server_notify;
 	server_notify = server;
 	server_notify = server;
 	
 	
@@ -358,52 +353,44 @@ static bool display_bus_handler(void *from, enum display_bus_cmd_e cmd) {
 	else return true;
 	else return true;
 }
 }
 
 
-
 /****************************************************************************************
 /****************************************************************************************
- * Send message to server (ANIC at that time)
+ * Send ANImation Complete
  */
  */
-static void send_server(void) {
-	/* 
-	 This complication is needed as we cannot send direclty to LMS, because 
-	 send_packet is not thread safe. So must subscribe to slimproto busy loop
-	 end send from there
-	*/ 
-	if (ANIC_resp != ANIM_NONE) {
-		struct ANIC_header pkt_header;
+static void sendANIC(u8_t code) {
+	struct ANIC_header pkt_header;
 
 
-		memset(&pkt_header, 0, sizeof(pkt_header));
-		memcpy(&pkt_header.opcode, "ANIC", 4);
-		pkt_header.length = htonl(sizeof(pkt_header) - 8);
-		pkt_header.mode = ANIC_resp;
+	memset(&pkt_header, 0, sizeof(pkt_header));
+	memcpy(&pkt_header.opcode, "ANIC", 4);
+	pkt_header.length = htonl(sizeof(pkt_header) - 8);
+	pkt_header.mode = code;
 
 
-		send_packet((uint8_t *) &pkt_header, sizeof(pkt_header));
+	LOCK_P;
+	send_packet((uint8_t *) &pkt_header, sizeof(pkt_header));
+	UNLOCK_P;
+}	
 		
 		
-		ANIC_resp = ANIM_NONE;
-	}	
-	
-	if (SETD_width) {
-		struct SETD_header pkt_header;
+/****************************************************************************************
+ * Send SETD for width
+ */
+static void sendSETD(u16_t width, u16_t height) {
+	struct SETD_header pkt_header;
 		
 		
-		memset(&pkt_header, 0, sizeof(pkt_header));
-		memcpy(&pkt_header.opcode, "SETD", 4);
+	memset(&pkt_header, 0, sizeof(pkt_header));
+	memcpy(&pkt_header.opcode, "SETD", 4);
 
 
-		pkt_header.id = 0xfe; // id 0xfe is width S:P:Squeezebox2
-		pkt_header.length = htonl(sizeof(pkt_header) +  4 - 8);
+	pkt_header.id = 0xfe; // id 0xfe is width S:P:Squeezebox2
+	pkt_header.length = htonl(sizeof(pkt_header) +  4 - 8);
 		
 		
-		u16_t height = GDS_GetHeight(display);
-		LOG_INFO("sending dimension %ux%u", SETD_width, height);	
+	LOG_INFO("sending dimension %ux%u", width, height);	
 
 
-		SETD_width = htons(SETD_width);
-		height = htons(height);
+	width = htons(width);
+	height = htons(height);
 		
 		
-		send_packet((uint8_t *) &pkt_header, sizeof(pkt_header));
-		send_packet((uint8_t *) &SETD_width, 2);
-		send_packet((uint8_t *) &height, 2);
-		
-		SETD_width = 0;
-	}	
-	
-	if (slimp_loop_chain) (*slimp_loop_chain)();
+	LOCK_P;
+	send_packet((uint8_t *) &pkt_header, sizeof(pkt_header));
+	send_packet((uint8_t *) &width, 2);
+	send_packet((uint8_t *) &height, 2);
+	UNLOCK_P;
 }
 }
 
 
 /****************************************************************************************
 /****************************************************************************************
@@ -416,11 +403,13 @@ static void server(in_addr_t ip, u16_t hport, u16_t cport) {
 	
 	
 	sprintf(msg, "%s:%hu", inet_ntoa(ip), hport);
 	sprintf(msg, "%s:%hu", inet_ntoa(ip), hport);
 	if (displayer.owned) GDS_TextPos(display, GDS_FONT_DEFAULT, GDS_TEXT_CENTERED, GDS_TEXT_CLEAR | GDS_TEXT_UPDATE, msg);
 	if (displayer.owned) GDS_TextPos(display, GDS_FONT_DEFAULT, GDS_TEXT_CENTERED, GDS_TEXT_CLEAR | GDS_TEXT_UPDATE, msg);
-	SETD_width = displayer.width;
 	displayer.dirty = true;
 	displayer.dirty = true;
 	
 	
 	xSemaphoreGive(displayer.mutex);
 	xSemaphoreGive(displayer.mutex);
 	
 	
+	// inform new LMS server of our capabilities
+	sendSETD(displayer.width, GDS_GetHeight(display));
+	
 	if (notify_chain) (*notify_chain)(ip, hport, cport);
 	if (notify_chain) (*notify_chain)(ip, hport, cport);
 }
 }
 
 
@@ -1141,8 +1130,7 @@ static void displayer_task(void *args) {
 				
 				
 				// see if we need to pause or if we are done 				
 				// see if we need to pause or if we are done 				
 				if (scroller.mode) {
 				if (scroller.mode) {
-					// can't call directly send_packet from slimproto as it's not re-entrant
-					ANIC_resp = ANIM_SCROLL_ONCE | ANIM_SCREEN_1;
+					sendANIC(ANIM_SCROLL_ONCE | ANIM_SCREEN_1);
 					LOG_INFO("scroll-once terminated");
 					LOG_INFO("scroll-once terminated");
 				} else {
 				} else {
 					scroller.wake = scroller.pause;
 					scroller.wake = scroller.pause;

+ 3 - 0
components/squeezelite/embedded.c

@@ -15,6 +15,8 @@
 #include "esp_timer.h"
 #include "esp_timer.h"
 #include "esp_wifi.h"
 #include "esp_wifi.h"
 
 
+mutex_type slimp_mutex;
+
 void get_mac(u8_t mac[]) {
 void get_mac(u8_t mac[]) {
     esp_read_mac(mac, ESP_MAC_WIFI_STA);
     esp_read_mac(mac, ESP_MAC_WIFI_STA);
 }
 }
@@ -46,6 +48,7 @@ extern bool sb_display_init(void);
 u8_t custom_player_id = 12;
 u8_t custom_player_id = 12;
 
 
 void embedded_init(void) {
 void embedded_init(void) {
+	mutex_create(slimp_mutex);
 	sb_controls_init();
 	sb_controls_init();
 	if (sb_display_init()) custom_player_id = 100;
 	if (sb_display_init()) custom_player_id = 100;
 }
 }

+ 4 - 1
components/squeezelite/embedded.h

@@ -58,6 +58,9 @@ void		embedded_init(void);
 void 		register_external(void);
 void 		register_external(void);
 void 		deregister_external(void);
 void 		deregister_external(void);
 void 		decode_restore(int external);
 void 		decode_restore(int external);
+extern mutex_type slimp_mutex;
+#define LOCK_P   mutex_lock(slimp_mutex)
+#define UNLOCK_P mutex_unlock(slimp_mutex)
 
 
 // must provide or define as 0xffff
 // must provide or define as 0xffff
 u16_t		get_RSSI(void);
 u16_t		get_RSSI(void);
@@ -77,5 +80,5 @@ void 		output_visu_close(void);
 bool		(*slimp_handler)(u8_t *data, int len);
 bool		(*slimp_handler)(u8_t *data, int len);
 void 		(*slimp_loop)(void);
 void 		(*slimp_loop)(void);
 void 		(*server_notify)(in_addr_t ip, u16_t hport, u16_t cport);
 void 		(*server_notify)(in_addr_t ip, u16_t hport, u16_t cport);
-				   
+	   
 #endif // EMBEDDED_H
 #endif // EMBEDDED_H

+ 19 - 3
components/squeezelite/slimproto.c

@@ -67,6 +67,10 @@ event_event wake_e;
 #define UNLOCK_O mutex_unlock(outputbuf->mutex)
 #define UNLOCK_O mutex_unlock(outputbuf->mutex)
 #define LOCK_D   mutex_lock(decode.mutex)
 #define LOCK_D   mutex_lock(decode.mutex)
 #define UNLOCK_D mutex_unlock(decode.mutex)
 #define UNLOCK_D mutex_unlock(decode.mutex)
+#if !EMBEDDED
+#define LOCK_P
+#define UNLOCK_P
+#endif 
 #if IR
 #if IR
 #define LOCK_I   mutex_lock(ir.mutex)
 #define LOCK_I   mutex_lock(ir.mutex)
 #define UNLOCK_I mutex_unlock(ir.mutex)
 #define UNLOCK_I mutex_unlock(ir.mutex)
@@ -149,11 +153,12 @@ static void sendHELO(bool reconnect, const char *fixed_cap, const char *var_cap,
 	LOG_INFO("mac: %02x:%02x:%02x:%02x:%02x:%02x", pkt.mac[0], pkt.mac[1], pkt.mac[2], pkt.mac[3], pkt.mac[4], pkt.mac[5]);
 	LOG_INFO("mac: %02x:%02x:%02x:%02x:%02x:%02x", pkt.mac[0], pkt.mac[1], pkt.mac[2], pkt.mac[3], pkt.mac[4], pkt.mac[5]);
 
 
 	LOG_INFO("cap: %s%s%s", base_cap, fixed_cap, var_cap);
 	LOG_INFO("cap: %s%s%s", base_cap, fixed_cap, var_cap);
-
+	LOCK_P;
 	send_packet((u8_t *)&pkt, sizeof(pkt));
 	send_packet((u8_t *)&pkt, sizeof(pkt));
 	send_packet((u8_t *)base_cap, strlen(base_cap));
 	send_packet((u8_t *)base_cap, strlen(base_cap));
 	send_packet((u8_t *)fixed_cap, strlen(fixed_cap));
 	send_packet((u8_t *)fixed_cap, strlen(fixed_cap));
 	send_packet((u8_t *)var_cap, strlen(var_cap));
 	send_packet((u8_t *)var_cap, strlen(var_cap));
+	UNLOCK_P;
 }
 }
 
 
 static void sendSTAT(const char *event, u32_t server_timestamp) {
 static void sendSTAT(const char *event, u32_t server_timestamp) {
@@ -205,7 +210,9 @@ static void sendSTAT(const char *event, u32_t server_timestamp) {
 				   ms_played - now + status.stream_start, status.device_frames * 1000 / status.current_sample_rate, now - status.updated);
 				   ms_played - now + status.stream_start, status.device_frames * 1000 / status.current_sample_rate, now - status.updated);
 	}
 	}
 
 
+	LOCK_P;
 	send_packet((u8_t *)&pkt, sizeof(pkt));
 	send_packet((u8_t *)&pkt, sizeof(pkt));
+	UNLOCK_P;
 }
 }
 
 
 static void sendDSCO(disconnect_code disconnect) {
 static void sendDSCO(disconnect_code disconnect) {
@@ -218,7 +225,9 @@ static void sendDSCO(disconnect_code disconnect) {
 
 
 	LOG_DEBUG("DSCO: %d", disconnect);
 	LOG_DEBUG("DSCO: %d", disconnect);
 
 
+	LOCK_P;
 	send_packet((u8_t *)&pkt, sizeof(pkt));
 	send_packet((u8_t *)&pkt, sizeof(pkt));
+	UNLOCK_P;
 }
 }
 
 
 static void sendRESP(const char *header, size_t len) {
 static void sendRESP(const char *header, size_t len) {
@@ -229,9 +238,11 @@ static void sendRESP(const char *header, size_t len) {
 	pkt_header.length = htonl(sizeof(pkt_header) + len - 8);
 	pkt_header.length = htonl(sizeof(pkt_header) + len - 8);
 
 
 	LOG_DEBUG("RESP");
 	LOG_DEBUG("RESP");
-
+	
+	LOCK_P;
 	send_packet((u8_t *)&pkt_header, sizeof(pkt_header));
 	send_packet((u8_t *)&pkt_header, sizeof(pkt_header));
 	send_packet((u8_t *)header, len);
 	send_packet((u8_t *)header, len);
+	UNLOCK_P;
 }
 }
 
 
 static void sendMETA(const char *meta, size_t len) {
 static void sendMETA(const char *meta, size_t len) {
@@ -243,8 +254,10 @@ static void sendMETA(const char *meta, size_t len) {
 
 
 	LOG_DEBUG("META");
 	LOG_DEBUG("META");
 
 
+	LOCK_P;
 	send_packet((u8_t *)&pkt_header, sizeof(pkt_header));
 	send_packet((u8_t *)&pkt_header, sizeof(pkt_header));
 	send_packet((u8_t *)meta, len);
 	send_packet((u8_t *)meta, len);
+	UNLOCK_P;
 }
 }
 
 
 static void sendSETDName(const char *name) {
 static void sendSETDName(const char *name) {
@@ -258,8 +271,10 @@ static void sendSETDName(const char *name) {
 
 
 	LOG_DEBUG("set playername: %s", name);
 	LOG_DEBUG("set playername: %s", name);
 
 
+	LOCK_P;
 	send_packet((u8_t *)&pkt_header, sizeof(pkt_header));
 	send_packet((u8_t *)&pkt_header, sizeof(pkt_header));
 	send_packet((u8_t *)name, strlen(name) + 1);
 	send_packet((u8_t *)name, strlen(name) + 1);
+	UNLOCK_P;
 }
 }
 
 
 #if IR
 #if IR
@@ -274,8 +289,9 @@ void sendIR(u32_t code, u32_t ts) {
 	pkt.ir_code = htonl(code);
 	pkt.ir_code = htonl(code);
 
 
 	LOG_DEBUG("IR: ir code: 0x%x ts: %u", code, ts);
 	LOG_DEBUG("IR: ir code: 0x%x ts: %u", code, ts);
-
+	LOCK_P;
 	send_packet((u8_t *)&pkt, sizeof(pkt));
 	send_packet((u8_t *)&pkt, sizeof(pkt));
+	UNLOCK_P;
 }
 }
 #endif
 #endif
 
 

+ 4 - 1
main/esp_app_main.c

@@ -284,6 +284,9 @@ void register_default_nvs(){
 	ESP_LOGD(TAG,"Registering default Audio control board type %s, value ","actrls_config");
 	ESP_LOGD(TAG,"Registering default Audio control board type %s, value ","actrls_config");
 	config_set_default(NVS_TYPE_STR, "actrls_config", "", 0);
 	config_set_default(NVS_TYPE_STR, "actrls_config", "", 0);
 	
 	
+	ESP_LOGD(TAG,"Registering default value for key %s", "lms_ctrls_raw");
+	config_set_default(NVS_TYPE_STR, "lms_ctrls_raw", "n", 0);
+	
 	ESP_LOGD(TAG,"Registering default Audio control board type %s, value %s", "rotary_config", CONFIG_ROTARY_ENCODER);
 	ESP_LOGD(TAG,"Registering default Audio control board type %s, value %s", "rotary_config", CONFIG_ROTARY_ENCODER);
 	config_set_default(NVS_TYPE_STR, "rotary_config", CONFIG_ROTARY_ENCODER, 0);
 	config_set_default(NVS_TYPE_STR, "rotary_config", CONFIG_ROTARY_ENCODER, 0);
 
 
@@ -392,7 +395,7 @@ void app_main()
 
 
 	ESP_LOGD(TAG,"Getting audio control mapping ");
 	ESP_LOGD(TAG,"Getting audio control mapping ");
 	char *actrls_config = config_alloc_get_default(NVS_TYPE_STR, "actrls_config", NULL, 0);
 	char *actrls_config = config_alloc_get_default(NVS_TYPE_STR, "actrls_config", NULL, 0);
-	if (actrls_init_json(actrls_config, true) == ESP_OK) {
+	if (actrls_init(actrls_config) == ESP_OK) {
 		ESP_LOGD(TAG,"Initializing audio control buttons type %s", actrls_config);	
 		ESP_LOGD(TAG,"Initializing audio control buttons type %s", actrls_config);	
 	} else {
 	} else {
 		ESP_LOGD(TAG,"No audio control buttons");
 		ESP_LOGD(TAG,"No audio control buttons");