فهرست منبع

add IR and "raw" button mode - release

Philippe G 5 سال پیش
والد
کامیت
5b6ddf0b02

+ 17 - 9
components/driver_bt/bt_app_sink.c

@@ -87,41 +87,49 @@ static EXT_RAM_ATTR struct {
 	bool updated;
 } 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_set_by_local_host(s_volume < 127-3 ? s_volume + 3 : 127);
 	(*bt_app_a2d_cmd_cb)(BT_SINK_VOLUME, 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_set_by_local_host(s_volume > 3 ? s_volume - 3 : 0);
 	(*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);
 	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);
 }
 
-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);
 }
 
-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);
 }
 
-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);
 }
 
-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);
 }
 
@@ -297,7 +305,7 @@ static void bt_av_hdl_a2d_evt(uint16_t event, void *p_param)
 				// force metadata update
 				update_metadata(true);
 				
-				actrls_set(controls, NULL);
+				actrls_set(controls, false, NULL, actrls_ir_action);
 			} else {
 				// 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);	

+ 17 - 9
components/raop/raop_sink.c

@@ -31,42 +31,50 @@ static log_level *loglevel = &raop_loglevel;
 static struct raop_ctx_s *raop;
 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);
 	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);
 	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);
 	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);
 	LOG_INFO("AirPlay pause");
 }
 
-static void raop_play(void) {
+static void raop_play(bool pressed) {
+	if (!pressed) return;
 	raop_cmd(raop, RAOP_PLAY, NULL);
 	LOG_INFO("AirPlay play");
 }
 
-static void raop_stop(void) {
+static void raop_stop(bool pressed) {
+	if (!pressed) return;
 	raop_cmd(raop, RAOP_STOP, NULL);
 	LOG_INFO("AirPlay stop");
 }
 
-static void raop_prev(void) {
+static void raop_prev(bool pressed) {
+	if (!pressed) return;
 	raop_cmd(raop, RAOP_PREV, NULL);
 	LOG_INFO("AirPlay previous");
 }
 
-static void raop_next(void) {
+static void raop_next(bool pressed) {
+	if (!pressed) return;
 	raop_cmd(raop, RAOP_NEXT, NULL);
 	LOG_INFO("AirPlay next");
 }
@@ -98,7 +106,7 @@ static bool cmd_handler(raop_event_t event, ...) {
 	// now handle events for display
 	switch(event) {
 	case RAOP_SETUP:
-		actrls_set(controls, NULL);
+		actrls_set(controls, false, NULL, actrls_ir_action);
 		displayer_control(DISPLAYER_ACTIVATE, "AIRPLAY");
 		break;
 	case RAOP_PLAY:

+ 113 - 36
components/services/audio_controls.c

@@ -17,6 +17,7 @@
 #include "cJSON.h"
 #include "buttons.h"
 #include "config.h"
+#include "accessors.h"
 #include "audio_controls.h"
 
 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_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[] =
 		{
 			{"gpio", offsetof(actrls_config_t,gpio), actrls_process_int},
@@ -61,11 +65,88 @@ static actrls_config_t *json_config;
 cJSON * control_profiles = NULL;
 static actrls_t default_controls, current_controls;
 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 {
 	bool long_state;
 	bool volume_lock;
 } 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_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) {
 	case BUTTON_NORMAL:
 		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;
 	}
 	
-	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
 	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) {
 		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) {
 	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) {
 	case ROTARY_LEFT:
@@ -144,17 +241,7 @@ static void control_rotary_handler(void *client, rotary_event_e event, bool long
 		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;
 	actrls_config_t *cur_config = NULL;
 	actrls_config_t *config_root = NULL;
+	char *config;
 	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;
 	
 	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);
 				err = (err == ESP_OK) ? loc_err : err;
 				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 {
 					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(current_controls, default_controls, sizeof(actrls_t));
 	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));
 	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) {
 	memcpy(current_controls, default_controls, sizeof(actrls_t));
 	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_action_e;
 
-typedef void (*actrls_handler)(void);
+typedef void (*actrls_handler)(bool pressed);
 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_ir_handler_t(uint16_t addr, uint16_t cmd);
 
 // BEWARE any change to struct below must be mapped to actrls_config_map
 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_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, 
 which should return true if it managed the control request, false if the
 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);
+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_task.h"
 #include "driver/gpio.h"
+#include "driver/rmt.h"
 #include "buttons.h"
 #include "rotary_encoder.h"
 #include "globdefs.h"
@@ -56,11 +57,29 @@ static struct {
 	rotary_handler handler;
 } rotary;
 
+static struct {
+	RingbufHandle_t rb;
+	infrared_handler handler;
+} infrared;
+
 static xQueueHandle button_evt_queue;
-static QueueSetHandle_t button_queue_set;
+static QueueSetHandle_t common_queue_set;
 
 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
  */
@@ -107,8 +126,8 @@ static void buttons_task(void* arg) {
     while (1) {
 		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) {
 			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.self->shifting = false;
 			}
-		} else {
+		} else if (xActivatedMember == rotary.queue) {
 			rotary_encoder_event_t event = { 0 };
 			
 			// 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_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 
  */
 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;
 
 	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) {
 		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
@@ -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_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
 	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;
 }	
+
+/****************************************************************************************
+ * 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
+
+#include "infrared.h"
  
 // button type (pressed = LOW or HIGH, matches GPIO level)
 #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);
 
 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")) {
 		char *p;
 		jack.gpio = gpio;	
 		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")) {
 		char *p;
 		spkfault.gpio = gpio;	
 		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 "config.h"
 #include "audio_controls.h"
 
 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 u16_t server_hport;
 static u16_t server_cport;
 static u8_t mac[6];
 static void	(*chained_notify)(in_addr_t, u16_t, u16_t);
+static bool raw_mode;
 
 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");
 }
 
-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 = {
@@ -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);
 }
 
+/****************************************************************************************
+ * 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
  */
 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);
-	actrls_set_default(LMS_controls, NULL);
+	actrls_set_default(LMS_controls, raw_mode, NULL, ir_handler);
+	
 	chained_notify = server_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_2     0x08 
 
-static u8_t ANIC_resp = ANIM_NONE;
-static uint16_t SETD_width;
-
 #define SCROLL_STACK_SIZE	(3*1024)
 #define LINELEN				40
 
 static log_level loglevel = lINFO;
 
 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 bool (*display_bus_chain)(void *from, enum display_bus_cmd_e cmd);
 
 #define max(a,b) (((a) > (b)) ? (a) : (b))
 
 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 display_bus_handler(void *from, enum display_bus_cmd_e cmd);
 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 grfa_handler(u8_t *data, int len);
 static void visu_handler(u8_t *data, int len);
-
 static void displayer_task(void* arg);
 
 /* scrolling undocumented information
@@ -293,11 +289,13 @@ bool sb_display_init(void) {
 		return false;
 	}	
 	
+	// inform LMS of our screen dimensions
+	sendSETD(GDS_GetWidth(display), GDS_GetHeight(display));
+	
 	// need to force height to 32 maximum
 	displayer.width = GDS_GetWidth(display);
 	displayer.height = min(GDS_GetHeight(display), SB_HEIGHT);
-	SETD_width = displayer.width;
-
+	
 	// create visu configuration
 	visu.bar_gap = 1;
 	visu.speed = 100;
@@ -319,9 +317,6 @@ bool sb_display_init(void) {
 	slimp_handler_chain = slimp_handler;
 	slimp_handler = handler;
 	
-	slimp_loop_chain = slimp_loop;
-	slimp_loop = send_server;
-	
 	notify_chain = server_notify;
 	server_notify = server;
 	
@@ -358,52 +353,44 @@ static bool display_bus_handler(void *from, enum display_bus_cmd_e cmd) {
 	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);
 	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;
 	
 	xSemaphoreGive(displayer.mutex);
 	
+	// inform new LMS server of our capabilities
+	sendSETD(displayer.width, GDS_GetHeight(display));
+	
 	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 				
 				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");
 				} else {
 					scroller.wake = scroller.pause;

+ 3 - 0
components/squeezelite/embedded.c

@@ -15,6 +15,8 @@
 #include "esp_timer.h"
 #include "esp_wifi.h"
 
+mutex_type slimp_mutex;
+
 void get_mac(u8_t mac[]) {
     esp_read_mac(mac, ESP_MAC_WIFI_STA);
 }
@@ -46,6 +48,7 @@ extern bool sb_display_init(void);
 u8_t custom_player_id = 12;
 
 void embedded_init(void) {
+	mutex_create(slimp_mutex);
 	sb_controls_init();
 	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 		deregister_external(void);
 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
 u16_t		get_RSSI(void);
@@ -77,5 +80,5 @@ void 		output_visu_close(void);
 bool		(*slimp_handler)(u8_t *data, int len);
 void 		(*slimp_loop)(void);
 void 		(*server_notify)(in_addr_t ip, u16_t hport, u16_t cport);
-				   
+	   
 #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 LOCK_D   mutex_lock(decode.mutex)
 #define UNLOCK_D mutex_unlock(decode.mutex)
+#if !EMBEDDED
+#define LOCK_P
+#define UNLOCK_P
+#endif 
 #if IR
 #define LOCK_I   mutex_lock(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("cap: %s%s%s", base_cap, fixed_cap, var_cap);
-
+	LOCK_P;
 	send_packet((u8_t *)&pkt, sizeof(pkt));
 	send_packet((u8_t *)base_cap, strlen(base_cap));
 	send_packet((u8_t *)fixed_cap, strlen(fixed_cap));
 	send_packet((u8_t *)var_cap, strlen(var_cap));
+	UNLOCK_P;
 }
 
 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);
 	}
 
+	LOCK_P;
 	send_packet((u8_t *)&pkt, sizeof(pkt));
+	UNLOCK_P;
 }
 
 static void sendDSCO(disconnect_code disconnect) {
@@ -218,7 +225,9 @@ static void sendDSCO(disconnect_code disconnect) {
 
 	LOG_DEBUG("DSCO: %d", disconnect);
 
+	LOCK_P;
 	send_packet((u8_t *)&pkt, sizeof(pkt));
+	UNLOCK_P;
 }
 
 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);
 
 	LOG_DEBUG("RESP");
-
+	
+	LOCK_P;
 	send_packet((u8_t *)&pkt_header, sizeof(pkt_header));
 	send_packet((u8_t *)header, len);
+	UNLOCK_P;
 }
 
 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");
 
+	LOCK_P;
 	send_packet((u8_t *)&pkt_header, sizeof(pkt_header));
 	send_packet((u8_t *)meta, len);
+	UNLOCK_P;
 }
 
 static void sendSETDName(const char *name) {
@@ -258,8 +271,10 @@ static void sendSETDName(const char *name) {
 
 	LOG_DEBUG("set playername: %s", name);
 
+	LOCK_P;
 	send_packet((u8_t *)&pkt_header, sizeof(pkt_header));
 	send_packet((u8_t *)name, strlen(name) + 1);
+	UNLOCK_P;
 }
 
 #if IR
@@ -274,8 +289,9 @@ void sendIR(u32_t code, u32_t ts) {
 	pkt.ir_code = htonl(code);
 
 	LOG_DEBUG("IR: ir code: 0x%x ts: %u", code, ts);
-
+	LOCK_P;
 	send_packet((u8_t *)&pkt, sizeof(pkt));
+	UNLOCK_P;
 }
 #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");
 	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);
 	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 ");
 	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);	
 	} else {
 		ESP_LOGD(TAG,"No audio control buttons");