Prechádzať zdrojové kódy

Merge remote-tracking branch 'origin/master' into master-cmake

Conflicts:
	README.md
	components/config/config.c
	components/driver_bt/bt_app_sink.c
	components/raop/raop.c
	components/services/audio_controls.c
	main/platform_esp32.h
Sebastien 4 rokov pred
rodič
commit
1a2de11e92
60 zmenil súbory, kde vykonal 1929 pridanie a 845 odobranie
  1. 22 3
      README.md
  2. 6 15
      components/codecs/inc/alac/alac_wrapper.h
  3. BIN
      components/codecs/lib/libhelix-aac.a
  4. 711 0
      components/config/config.c
  5. 8 0
      components/display/core/gds_draw.h
  6. 8 0
      components/display/core/gds_image.c
  7. 8 0
      components/display/core/gds_image.h
  8. 8 0
      components/display/core/gds_private.h
  9. 3 14
      components/display/core/gds_text.c
  10. 3 13
      components/display/core/gds_text.h
  11. 4 0
      components/display/core/ifaces/default_if_i2c.c
  12. 2 13
      components/display/display.c
  13. 2 12
      components/display/display.h
  14. 53 33
      components/driver_bt/bt_app_sink.c
  15. 1 1
      components/driver_bt/bt_app_source.c
  16. 2 12
      components/raop/platform.h
  17. 5 14
      components/raop/raop.c
  18. 3 13
      components/raop/raop.h
  19. 17 9
      components/raop/raop_sink.c
  20. 4 14
      components/raop/util.c
  21. 2 13
      components/raop/util.h
  22. 115 48
      components/services/audio_controls.c
  23. 10 17
      components/services/audio_controls.h
  24. 47 24
      components/services/buttons.c
  25. 6 12
      components/services/buttons.h
  26. 2 12
      components/services/globdefs.h
  27. 177 0
      components/services/infrared.c
  28. 17 0
      components/services/infrared.h
  29. 2 12
      components/services/led.h
  30. 6 2
      components/services/monitor.c
  31. 2 12
      components/services/monitor.h
  32. 2 12
      components/squeezelite/adac.h
  33. 1 1
      components/squeezelite/alac.c
  34. 194 47
      components/squeezelite/controls.c
  35. 30 50
      components/squeezelite/decode_external.c
  36. 40 63
      components/squeezelite/display.c
  37. 5 12
      components/squeezelite/embedded.c
  38. 5 1
      components/squeezelite/embedded.h
  39. 2 12
      components/squeezelite/equalizer.c
  40. 2 12
      components/squeezelite/equalizer.h
  41. 2 12
      components/squeezelite/external/dac_external.c
  42. 30 24
      components/squeezelite/helix-aac.c
  43. 2 12
      components/squeezelite/output_bt.c
  44. 2 12
      components/squeezelite/output_embedded.c
  45. 2 12
      components/squeezelite/output_i2s.c
  46. 19 3
      components/squeezelite/slimproto.c
  47. 2 12
      components/squeezelite/tas57xx/dac_57xx.c
  48. 2 12
      components/tools/perf_trace.h
  49. 2 12
      components/tools/tools.h
  50. 2 12
      components/tools/trace.h
  51. 6 13
      main/esp_app_main.c
  52. 29 0
      main/platform_esp32.h
  53. BIN
      plugin/SqueezeESP32.zip
  54. 55 27
      plugin/SqueezeESP32/HTML/EN/plugins/SqueezeESP32/settings/player.html
  55. 84 10
      plugin/SqueezeESP32/Player.pm
  56. 33 36
      plugin/SqueezeESP32/PlayerSettings.pm
  57. 69 84
      plugin/SqueezeESP32/Plugin.pm
  58. 1 1
      plugin/SqueezeESP32/install.xml
  59. 48 21
      plugin/SqueezeESP32/strings.txt
  60. 2 2
      plugin/repo.xml

+ 22 - 3
README.md

@@ -92,6 +92,15 @@ The NVS parameter "metadata_config" sets how metadata is displayed for AirPlay a
 
 You can install the excellent plugin "Music Information Screen" which is super useful to tweak the layout for these small displays.
 
+### Infrared
+You can use any IR receiver compatible with NEC protocol (38KHz). Vcc, GND and output are the only pins that need to be connected, no pullup, no filtering capacitor, it's a straight connection.
+
+The IR codes are send "as is" to LMS, so only a Logitech SB remote from Boom, Classic or Touch will work. I think the file Slim_Devices_Remote.ir in the "server" directory of LMS can be modified to adapt to other codes, but I've not tried that.
+
+In AirPlay and Bluetooth mode, only these native remotes are supported, I've not added the option to make your own mapping
+
+See "set GPIO" below to set the GPIO associated to infrared receiver (option "ir"). 
+
 ### Set GPIO
 The parameter "set_GPIO" is use to assign GPIO to various functions.
 
@@ -103,10 +112,12 @@ If you have an audio jack that supports insertion (use :0 or :1 to set the level
 
 You can set the Green and Red status led as well with their respective active state (:0 or :1)
 
+The \<ir\> parameter set the GPIO associated to an IR receiver. No need to add pullup or capacitor
+
 Syntax is:
 
 ```
-<gpio>=Vcc|GND|amp|jack[:0|1]|green[:0|1]|red[:0|1]|spkfault[:0|1][,<repeated sequence for next GPIO>]
+<gpio>=Vcc|GND|amp|ir|jack[:0|1]|green[:0|1]|red[:0|1]|spkfault[:0|1][,<repeated sequence for next GPIO>]
 ```
 You can define the defaults for jack, spkfault leds at compile time but nvs parameter takes precedence except for SqueezeAMP where these are forced at runtime.
 ### Rotary Encoder
@@ -122,6 +133,8 @@ A=<gpio>,B=<gpio>[,SW=gpio>[,volume][,longpress]]
 
 HW note: all gpio used for rotary have internal pull-up so normally there is no need to provide Vcc to the encoder. Nevertheless if the encoder board you're using also has its own pull-up that are stronger than ESP32's ones (which is likely the case), then there will be crosstalk between gpio, so you must bring Vcc. Look at your board schematic and you'll understand that these board pull-up create a "winning" pull-down when any other pin is grounded. 
 
+See also the "IMPORTANT NOTE" on the "Buttons" section
+
 ### Buttons
 Buttons are described using a JSON string with the following syntax
 ```
@@ -158,7 +171,7 @@ Where \<action\> is either the name of another configuration to load (remap) or
 ```
 ACTRLS_NONE, ACTRLS_VOLUP, ACTRLS_VOLDOWN, ACTRLS_TOGGLE, ACTRLS_PLAY, 
 ACTRLS_PAUSE, ACTRLS_STOP, ACTRLS_REW, ACTRLS_FWD, ACTRLS_PREV, ACTRLS_NEXT, 
-BCTRLS_PUSH, BCTRLS_UP, BCTRLS_DOWN, BCTRLS_LEFT, BCTRLS_RIGHT,
+BCTRLS_UP, BCTRLS_DOWN, BCTRLS_LEFT, BCTRLS_RIGHT,
 KNOB_LEFT, KNOB_RIGHT, KNOB_PUSH
 ```
 				
@@ -179,7 +192,7 @@ While the config named "buttons_remap"
  {"gpio":5,"type":"BUTTON_LOW","pull":true,"shifter_gpio":4,"normal":{"pressed":"BCTRLS_UP"}}]
 ``` 
 Defines two buttons
-- first on GPIO 4, active low. When pressed, it triggers a navigation down command. When pressed more than 1000ms, it changes the button configuration for the one descrobed above
+- first on GPIO 4, active low. When pressed, it triggers a navigation down command. When pressed more than 1000ms, it changes the button configuration for the one described above
 - second on GPIO 5, active low. When pressed it triggers a navigation up command. That button, in that configuration, has no shift option
 
 Below is a difficult but functional 2-buttons interface for your decoding pleasure
@@ -207,6 +220,12 @@ Below is a difficult but functional 2-buttons interface for your decoding pleasu
  "longshifted":{"pressed":"BCTRLS_LEFT"}}
 ]
 ```
+<strong>IMPORTANT NOTE</strong>: LMS also supports the possibility to send 'raw' button codes. It's a bit complicated, so bear with me. Buttons can either be processed by SqueezeESP32 and mapped to a "function" like play/pause or they can be just sent to LMS as plain (raw) code and the full logic of press/release/longpress is handled by LMS, you don't have any control on that.
+
+The benefit of the "raw" mode is that you can build a player which is as close as possible to a Boom (e.g.) but you can't use the remapping function nor longress or shift logics to do your own mapping when you have a limited set of buttons. In 'raw' mode, all you really need to define is the mapping between the gpio and the button. As far as LMS is concerned, any other option in these JSON payloads does not matter. Now, when you use BT or AirPlay, the full JSON construct described above fully applies, so the shift, longpress, remapping options still work. 
+
+There is no good or bad option, it's your choice. Use the NVS parameter "lms_ctrls_raw" to change that option
+
 ### Battery / ADC
 The NVS parameter "bat_config" sets the ADC1 channel used to measure battery/DC voltage. Scale is a float ratio applied to every sample of the 12 bits ADC. A measure is taken every 10s and an average is made every 5 minutes (not a sliding window). Syntax is
 ```

+ 6 - 15
components/codecs/inc/alac/alac_wrapper.h

@@ -1,22 +1,13 @@
 /*****************************************************************************
  * alac_wrapper.h: ALAC coder wrapper
  *
- * Copyright (C) 2016 Philippe <philippe44@outlook.com>
+ * (c) Philippe G. 2019, philippe_44@outlook.com
  *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
- *****************************************************************************/
+ * This software is released under the MIT License.
+ * https://opensource.org/licenses/MIT
+ * 
+ */
+ 
 #ifndef __ALAC_WRAPPER_H_
 #define __ALAC_WRAPPER_H_
 

BIN
components/codecs/lib/libhelix-aac.a


+ 711 - 0
components/config/config.c

@@ -0,0 +1,711 @@
+/*
+ *  Squeezelite for esp32
+ *
+ *  (c) Sebastien 2019
+ *  (c) Philippe G. 2019, philippe_44@outlook.com
+ * 
+ *  This software is released under the MIT License.
+ *  https://opensource.org/licenses/MIT
+ * 
+ */
+//#define LOG_LOCAL_LEVEL ESP_LOG_VERBOSE
+#include "config.h"
+#include "nvs_utilities.h"
+
+#include <stdio.h>
+#include <string.h>
+#include "esp_system.h"
+#include "esp_log.h"
+#include "esp_console.h"
+#include "esp_vfs_dev.h"
+#include "driver/uart.h"
+#include "linenoise/linenoise.h"
+#include "argtable3/argtable3.h"
+#include "esp_vfs_fat.h"
+#include "nvs.h"
+#include "nvs_flash.h"
+#include "nvs_utilities.h"
+#include "cJSON.h"
+#include "freertos/timers.h"
+#include "freertos/event_groups.h"
+
+
+#define CONFIG_COMMIT_DELAY 1000
+#define LOCK_MAX_WAIT 20*CONFIG_COMMIT_DELAY
+static const char * TAG = "config";
+static cJSON * nvs_json=NULL;
+static TimerHandle_t timer;
+static SemaphoreHandle_t config_mutex = NULL;
+static EventGroupHandle_t config_group;
+/* @brief indicate that the ESP32 is currently connected. */
+static const int CONFIG_NO_COMMIT_PENDING = BIT0;
+static const int CONFIG_LOAD_BIT = BIT1;
+
+bool config_lock(TickType_t xTicksToWait);
+void config_unlock();
+extern esp_err_t nvs_load_config();
+void config_raise_change(bool flag);
+cJSON_bool config_is_entry_changed(cJSON * entry);
+bool config_set_group_bit(int bit_num,bool flag);
+cJSON * config_set_value_safe(nvs_type_t nvs_type, const char *key, void * value);
+static void vCallbackFunction( TimerHandle_t xTimer );
+void config_set_entry_changed_flag(cJSON * entry, cJSON_bool flag);
+#define IMPLEMENT_SET_DEFAULT(t,nt) void config_set_default_## t (const char *key, t  value){\
+	void * pval = malloc(sizeof(value));\
+	*((t *) pval) = value;\
+	config_set_default(nt, key,pval,0);\
+	free(pval); }
+#define IMPLEMENT_GET_NUM(t,nt) esp_err_t config_get_## t (const char *key, t *  value){\
+		void * pval = config_alloc_get(nt, key);\
+		if(pval!=NULL){ *value = *(t * )pval; free(pval); return ESP_OK; }\
+		return ESP_FAIL;}
+#if RECOVERY_APPLICATION==0
+static void * malloc_fn(size_t sz){
+
+	void * ptr = heap_caps_malloc(sz, MALLOC_CAP_SPIRAM);
+	if(ptr==NULL){
+		ESP_LOGE(TAG,"malloc_fn:  unable to allocate memory!");
+	}
+	return ptr;
+}
+/*
+static void * free_fn(void * ptr){
+	if(ptr!=NULL){
+		heap_caps_free(ptr);
+	}
+	else {
+		ESP_LOGW(TAG,"free_fn: Cannot free null pointer!");
+	}
+	return NULL;
+}
+*/
+#endif
+void init_cJSON(){
+	static cJSON_Hooks hooks;
+	// initialize cJSON hooks it uses SPIRAM memory
+	// as opposed to IRAM
+#if RECOVERY_APPLICATION==0
+	// In squeezelite mode, allocate memory from PSRAM.  Otherwise allocate from internal RAM
+	// as recovery will lock flash access when erasing FLASH or writing to OTA partition.
+	hooks.malloc_fn=&malloc_fn;
+    //hooks.free_fn=&free_fn;
+	cJSON_InitHooks(&hooks);
+#endif
+}
+void config_init(){
+	ESP_LOGD(TAG, "Creating mutex for Config");
+	config_mutex = xSemaphoreCreateMutex();
+	ESP_LOGD(TAG, "Creating event group");
+	config_group = xEventGroupCreate();
+	ESP_LOGD(TAG, "Loading config from nvs");
+
+	init_cJSON();
+	if(nvs_json !=NULL){
+		cJSON_Delete(nvs_json);
+	}
+	nvs_json = cJSON_CreateObject();
+
+	config_set_group_bit(CONFIG_LOAD_BIT,true);
+	nvs_load_config();
+	config_set_group_bit(CONFIG_LOAD_BIT,false);
+	config_start_timer();
+}
+
+void config_start_timer(){
+	ESP_LOGD(TAG, "Starting config timer");
+	timer = xTimerCreate("configTimer", CONFIG_COMMIT_DELAY / portTICK_RATE_MS, pdFALSE, NULL, vCallbackFunction);
+    if( xTimerStart( timer , CONFIG_COMMIT_DELAY/ portTICK_RATE_MS ) != pdPASS )    {
+        ESP_LOGE(TAG, "config commitment timer failed to start.");
+    }
+
+}
+
+nvs_type_t  config_get_item_type(cJSON * entry){
+	if(entry==NULL){
+		ESP_LOGE(TAG,"null pointer received!");
+		return true;
+	}
+	cJSON * item_type = cJSON_GetObjectItemCaseSensitive(entry, "type");
+	if(item_type ==NULL ) {
+		ESP_LOGE(TAG, "Item type not found! ");
+		return 0;
+	}
+	ESP_LOGD(TAG,"Found item type %f",item_type->valuedouble);
+	return item_type->valuedouble;
+}
+
+
+cJSON * config_set_value_safe(nvs_type_t nvs_type, const char *key, void * value){
+	cJSON * entry = cJSON_CreateObject();
+
+	double numvalue = 0;
+	if(entry == NULL) {
+		ESP_LOGE(TAG, "Unable to allocate memory for entry %s",key);
+		return NULL;
+	}
+
+	cJSON * existing = cJSON_GetObjectItemCaseSensitive(nvs_json, key);
+	if(existing !=NULL && nvs_type == NVS_TYPE_STR && config_get_item_type(existing) != NVS_TYPE_STR  ) {
+		ESP_LOGW(TAG, "Storing numeric value from string");
+		numvalue = atof((char *)value);
+		cJSON_AddNumberToObject(entry,"value", numvalue	);
+		nvs_type_t exist_type = config_get_item_type(existing);
+		ESP_LOGW(TAG, "Stored  value %f from string %s as type %d",numvalue, (char *)value,exist_type);
+		cJSON_AddNumberToObject(entry,"type", exist_type);
+	}
+	else {
+		cJSON_AddNumberToObject(entry,"type", nvs_type	);
+		switch (nvs_type) {
+			case NVS_TYPE_I8:
+				cJSON_AddNumberToObject(entry,"value", *(int8_t*)value	);
+				break;
+			case NVS_TYPE_I16:
+				cJSON_AddNumberToObject(entry,"value", *(int16_t*)value	);
+				break;
+			case NVS_TYPE_I32:
+				cJSON_AddNumberToObject(entry,"value", *(int32_t*)value	);
+				break;
+			case NVS_TYPE_U8:
+				cJSON_AddNumberToObject(entry,"value", *(uint8_t*)value	);
+				break;
+			case NVS_TYPE_U16:
+				cJSON_AddNumberToObject(entry,"value", *(uint16_t*)value	);
+				break;
+			case NVS_TYPE_U32:
+				cJSON_AddNumberToObject(entry,"value", *(uint32_t*)value	);
+				break;
+			case NVS_TYPE_STR:
+				cJSON_AddStringToObject(entry, "value", (char *)value);
+				break;
+			case NVS_TYPE_I64:
+			case NVS_TYPE_U64:
+			default:
+				ESP_LOGE(TAG, "nvs type %u not supported", nvs_type);
+				break;
+		}
+	}
+	if(existing!=NULL ) {
+		ESP_LOGV(TAG, "Changing existing entry [%s].", key);
+		char * exist_str = cJSON_PrintUnformatted(existing);
+		if(exist_str!=NULL){
+			ESP_LOGV(TAG,"Existing entry: %s", exist_str);
+			free(exist_str);
+		}
+		else {
+			ESP_LOGV(TAG,"Failed to print existing entry");
+		}
+		// set commit flag as equal so we can compare
+		cJSON_AddBoolToObject(entry,"chg",config_is_entry_changed(existing));
+		if(!cJSON_Compare(entry,existing,false)){
+			char * entry_str = cJSON_PrintUnformatted(entry);
+			if(entry_str!=NULL){
+				ESP_LOGD(TAG,"New config object: \n%s", entry_str );
+				free(entry_str);
+			}
+			else {
+				ESP_LOGD(TAG,"Failed to print entry");
+			}
+			ESP_LOGI(TAG, "Setting changed flag config [%s]", key);
+			config_set_entry_changed_flag(entry,true);
+			ESP_LOGI(TAG, "Updating config [%s]", key);
+			cJSON_ReplaceItemInObject(nvs_json,key, entry);
+			entry_str = cJSON_PrintUnformatted(entry);
+			if(entry_str!=NULL){
+				ESP_LOGD(TAG,"New config: %s", entry_str );
+				free(entry_str);
+			}
+			else {
+				ESP_LOGD(TAG,"Failed to print entry");
+			}
+		}
+		else {
+			ESP_LOGD(TAG, "Config not changed. ");
+			cJSON_Delete(entry);
+			entry = existing;
+		}
+	}
+	else {
+		// This is a new entry.
+		config_set_entry_changed_flag(entry,true);
+		cJSON_AddItemToObject(nvs_json, key, entry);
+	}
+
+	return entry;
+}
+
+nvs_type_t config_get_entry_type(cJSON * entry){
+	if(entry==NULL){
+		ESP_LOGE(TAG,"null pointer received!");
+		return 0;
+	}
+	cJSON * entry_type = cJSON_GetObjectItemCaseSensitive(entry, "type");
+	if(entry_type ==NULL ) {
+		ESP_LOGE(TAG, "Entry type not found in nvs cache for existing setting.");
+		return 0;
+	}
+	ESP_LOGV(TAG,"Found type %s",type_to_str(entry_type->valuedouble));
+	return entry_type->valuedouble;
+}
+void config_set_entry_changed_flag(cJSON * entry, cJSON_bool flag){
+	ESP_LOGV(TAG, "config_set_entry_changed_flag: begin");
+	if(entry==NULL){
+		ESP_LOGE(TAG,"null pointer received!");
+		return;
+	}
+	bool bIsConfigLoading=((xEventGroupGetBits(config_group) & CONFIG_LOAD_BIT)!=0);
+	bool changedFlag=bIsConfigLoading?false:flag;
+	ESP_LOGV(TAG, "config_set_entry_changed_flag: retrieving chg flag from entry");
+	cJSON * changed = cJSON_GetObjectItemCaseSensitive(entry, "chg");
+	if(changed ==NULL ) {
+		ESP_LOGV(TAG, "config_set_entry_changed_flag: chg flag not found. Adding. ");
+		cJSON_AddBoolToObject(entry,"chg",changedFlag);
+	}
+	else {
+		ESP_LOGV(TAG, "config_set_entry_changed_flag: Existing change flag found. ");
+		if(cJSON_IsTrue(changed) && changedFlag){
+			ESP_LOGW(TAG, "Commit flag not changed!");
+		}
+		else{
+			ESP_LOGV(TAG, "config_set_entry_changed_flag: Updating change flag to %s",changedFlag?"TRUE":"FALSE");
+			changed->type = changedFlag?cJSON_True:cJSON_False ;
+		}
+	}
+
+	if(changedFlag) {
+		ESP_LOGV(TAG, "config_set_entry_changed_flag: Calling config_raise_change. ");
+		config_raise_change(true);
+	}
+	ESP_LOGV(TAG, "config_set_entry_changed_flag: done. ");
+}
+cJSON_bool config_is_entry_changed(cJSON * entry){
+	if(entry==NULL){
+		ESP_LOGE(TAG,"null pointer received!");
+		return true;
+	}
+	cJSON * changed = cJSON_GetObjectItemCaseSensitive(entry, "chg");
+	if(changed ==NULL ) {
+		ESP_LOGE(TAG, "Change flag not found! ");
+		return true;
+	}
+	return cJSON_IsTrue(changed);
+}
+
+
+
+
+void * config_safe_alloc_get_entry_value(nvs_type_t nvs_type, cJSON * entry){
+	void * value=NULL;
+	if(entry==NULL){
+		ESP_LOGE(TAG,"null pointer received!");
+	}
+	ESP_LOGV(TAG, "getting config value type %s", type_to_str(nvs_type));
+	cJSON * entry_value = cJSON_GetObjectItemCaseSensitive(entry, "value");
+	if(entry_value==NULL ) {
+		char * entry_str = cJSON_PrintUnformatted(entry);
+		if(entry_str!=NULL){
+			ESP_LOGE(TAG, "Missing config value!. Object: \n%s", entry_str);
+			free(entry_str);
+		}
+		else{
+			ESP_LOGE(TAG, "Missing config value");
+		}
+		return NULL;
+	}
+
+	nvs_type_t type = config_get_entry_type(entry);
+	if(nvs_type != type){
+		// requested value type different than the stored type
+		char * entry_str = cJSON_PrintUnformatted(entry);
+		if(entry_str!=NULL){
+			ESP_LOGE(TAG, "Requested value type %s, found value type %s instead, Object: \n%s", type_to_str(nvs_type), type_to_str(type),entry_str);
+			free(entry_str);
+		}
+		else{
+			ESP_LOGE(TAG, "Requested value type %s, found value type %s instead", type_to_str(nvs_type), type_to_str(type));
+		}
+
+		return NULL;
+	}
+	if (nvs_type == NVS_TYPE_I8) {
+		value=malloc(sizeof(int8_t));
+		*(int8_t *)value = (int8_t)entry_value->valuedouble;
+	} else if (nvs_type == NVS_TYPE_U8) {
+		value=malloc(sizeof(uint8_t));
+		*(uint8_t *)value = (uint8_t)entry_value->valuedouble;
+	} else if (nvs_type == NVS_TYPE_I16) {
+		value=malloc(sizeof(int16_t));
+		*(int16_t *)value = (int16_t)entry_value->valuedouble;
+	} else if (nvs_type == NVS_TYPE_U16) {
+		value=malloc(sizeof(uint16_t));
+		*(uint16_t *)value = (uint16_t)entry_value->valuedouble;
+	} else if (nvs_type == NVS_TYPE_I32) {
+		value=malloc(sizeof(int32_t));
+		*(int32_t *)value = (int32_t)entry_value->valuedouble;
+	} else if (nvs_type == NVS_TYPE_U32) {
+		value=malloc(sizeof(uint32_t));
+		*(uint32_t *)value = (uint32_t)entry_value->valuedouble;
+	} else if (nvs_type == NVS_TYPE_I64) {
+		value=malloc(sizeof(int64_t));
+		*(int64_t *)value = (int64_t)entry_value->valuedouble;
+	} else if (nvs_type == NVS_TYPE_U64) {
+		value=malloc(sizeof(uint64_t));
+		*(uint64_t *)value = (uint64_t)entry_value->valuedouble;
+	} else if (nvs_type == NVS_TYPE_STR) {
+		if(!cJSON_IsString(entry_value)){
+			char * entry_str = cJSON_PrintUnformatted(entry);
+			if(entry_str!=NULL){
+				ESP_LOGE(TAG, "requested value type string, config type is different. key: %s, value: %s, type %d, Object: \n%s",
+					entry_value->string,
+					entry_value->valuestring,
+					entry_value->type,
+					entry_str);
+				free(entry_str);
+			}
+			else {
+				ESP_LOGE(TAG, "requested value type string, config type is different. key: %s, value: %s, type %d",
+					entry_value->string,
+					entry_value->valuestring,
+					entry_value->type);
+			}
+		}
+		else {
+			value=(void *)strdup(cJSON_GetStringValue(entry_value));
+			if(value==NULL){
+				char * entry_str = cJSON_PrintUnformatted(entry);
+				if(entry_str!=NULL){
+					ESP_LOGE(TAG, "strdup failed on value for object \n%s",entry_str);
+					free(entry_str);
+				}
+				else {
+					ESP_LOGE(TAG, "strdup failed on value");
+				}
+			}
+		}
+	} else if (nvs_type == NVS_TYPE_BLOB) {
+		ESP_LOGE(TAG, "Unsupported type NVS_TYPE_BLOB");
+	}
+	return value;
+}
+
+void config_commit_to_nvs(){
+	ESP_LOGI(TAG,"Committing configuration to nvs. Locking config object.");
+	ESP_LOGV(TAG,"config_commit_to_nvs. Locking config object.");
+	if(!config_lock(LOCK_MAX_WAIT/portTICK_PERIOD_MS)){
+		ESP_LOGE(TAG, "config_commit_to_nvs: Unable to lock config for commit ");
+		return ;
+	}
+	if(nvs_json==NULL){
+		ESP_LOGE(TAG, ": cJSON nvs cache object not set.");
+		return;
+	}
+	ESP_LOGV(TAG,"config_commit_to_nvs. Config Locked!");
+	cJSON * entry=nvs_json->child;
+	while(entry!= NULL){
+		char * entry_str = cJSON_PrintUnformatted(entry);
+		if(entry_str!=NULL){
+			ESP_LOGV(TAG,"config_commit_to_nvs processing item %s",entry_str);
+			free(entry_str);
+		}
+
+		if(config_is_entry_changed(entry)){
+			ESP_LOGD(TAG, "Committing entry %s value to nvs.",(entry->string==NULL)?"UNKNOWN":entry->string);
+			nvs_type_t type = config_get_entry_type(entry);
+			void * value = config_safe_alloc_get_entry_value(type, entry);
+			if(value!=NULL){
+				esp_err_t err = store_nvs_value(type,entry->string,value);
+				free(value);
+				if(err!=ESP_OK){
+					char * entry_str = cJSON_PrintUnformatted(entry);
+					if(entry_str!=NULL){
+						ESP_LOGE(TAG, "Error comitting value to nvs for key %s, Object: \n%s",entry->string,entry_str);
+						free(entry_str);
+					}
+					else {
+						ESP_LOGE(TAG, "Error comitting value to nvs for key %s",entry->string);
+					}
+				}
+				else {
+					config_set_entry_changed_flag(entry, false);
+				}
+			}
+			else {
+				char * entry_str = cJSON_PrintUnformatted(entry);
+				if(entry_str!=NULL){
+					ESP_LOGE(TAG, "Unable to retrieve value. Error comitting value to nvs for key %s, Object: \n%s",entry->string,entry_str);
+					free(entry_str);
+				}
+				else {
+					ESP_LOGE(TAG, "Unable to retrieve value. Error comitting value to nvs for key %s",entry->string);
+				}
+			}
+		}
+		else {
+			ESP_LOGV(TAG,"config_commit_to_nvs. Item already committed.  Ignoring.");
+		}
+		taskYIELD();  /* allows the freeRTOS scheduler to take over if needed. */
+		entry = entry->next;
+	}
+	ESP_LOGV(TAG,"config_commit_to_nvs. Resetting the global commit flag.");
+	config_raise_change(false);
+	ESP_LOGV(TAG,"config_commit_to_nvs. Releasing the lock object.");
+	config_unlock();
+}
+bool config_has_changes(){
+	return  (xEventGroupGetBits(config_group) & CONFIG_NO_COMMIT_PENDING)==0;
+}
+
+
+bool wait_for_commit(){
+	bool commit_pending=(xEventGroupGetBits(config_group) & CONFIG_NO_COMMIT_PENDING)==0;
+	while (commit_pending){
+		ESP_LOGW(TAG,"Waiting for config commit ...");
+		commit_pending = (xEventGroupWaitBits(config_group, CONFIG_NO_COMMIT_PENDING,pdFALSE, pdTRUE, (CONFIG_COMMIT_DELAY*2) / portTICK_PERIOD_MS) & CONFIG_NO_COMMIT_PENDING)==0;
+		if(commit_pending){
+			ESP_LOGW(TAG,"Timeout waiting for config commit.");
+	    }
+	    else {
+	    	ESP_LOGI(TAG,"Config committed!");
+	    }
+	}
+	return !commit_pending;
+}
+
+bool config_lock(TickType_t xTicksToWait) {
+	ESP_LOGV(TAG, "Locking config json object");
+	if( xSemaphoreTake( config_mutex, xTicksToWait ) == pdTRUE ) {
+		ESP_LOGV(TAG, "config Json object locked!");
+		return true;
+	}
+	else {
+		ESP_LOGE(TAG, "Semaphore take failed. Unable to lock config Json object mutex");
+		return false;
+	}
+}
+
+void config_unlock() {
+	ESP_LOGV(TAG, "Unlocking json buffer!");
+	xSemaphoreGive( config_mutex );
+}
+
+static void vCallbackFunction( TimerHandle_t xTimer ) {
+	static int cnt=0;
+	if(config_has_changes()){
+		ESP_LOGI(TAG, "configuration has some uncommitted entries");
+		config_commit_to_nvs();
+	}
+	else{
+		if(++cnt>=15){
+			ESP_LOGV(TAG,"commit timer: commit flag not set");
+			cnt=0;
+		}
+	}
+	xTimerReset( xTimer, 10 );
+}
+void config_raise_change(bool change_found){
+	if(config_set_group_bit(CONFIG_NO_COMMIT_PENDING,!change_found))
+	{
+		ESP_LOGD(TAG,"Config commit set to %s",change_found?"Pending Commit":"Committed");
+	}
+}
+bool config_set_group_bit(int bit_num,bool flag){
+	bool result = true;
+	int curFlags=xEventGroupGetBits(config_group);
+	if((curFlags & CONFIG_LOAD_BIT) && bit_num == CONFIG_NO_COMMIT_PENDING ){
+		ESP_LOGD(TAG,"Loading config, ignoring changes");
+		result = false;
+	}
+	if(result){
+		bool curBit=(xEventGroupGetBits(config_group) & bit_num);
+		if(curBit == flag){
+			ESP_LOGV(TAG,"Flag %d already %s", bit_num, flag?"Set":"Cleared");
+			result = false;
+		}
+	}
+	if(result){
+		ESP_LOGV(TAG,"%s Flag %d ", flag?"Setting":"Clearing",bit_num);
+		if(!flag){
+			xEventGroupClearBits(config_group, bit_num);
+		}
+		else {
+			xEventGroupSetBits(config_group, bit_num);
+		}
+	}
+	return result;
+}
+
+void config_set_default(nvs_type_t type, const char *key, void * default_value, size_t blob_size) {
+	if(!config_lock(LOCK_MAX_WAIT/portTICK_PERIOD_MS)){
+		ESP_LOGE(TAG, "Unable to lock config");
+		return;
+	}
+
+	ESP_LOGV(TAG, "Checking if key %s exists in nvs cache for type %s.", key,type_to_str(type));
+	cJSON * entry = cJSON_GetObjectItemCaseSensitive(nvs_json, key);
+
+	if(entry !=NULL){
+		ESP_LOGV(TAG, "Entry found.");
+	}
+	else {
+		// Value was not found
+		ESP_LOGW(TAG, "Adding default value for [%s].", key);
+		entry=config_set_value_safe(type, key, default_value);
+		if(entry == NULL){
+			ESP_LOGE(TAG, "Failed to add value to cache!");
+		}
+		char * entry_str = cJSON_PrintUnformatted(entry);
+		if(entry_str!=NULL){
+			ESP_LOGD(TAG, "Value added to default for object: \n%s",entry_str);
+			free(entry_str);
+		}
+	}
+
+	config_unlock();
+
+}
+
+void config_delete_key(const char *key){
+	nvs_handle nvs;
+	ESP_LOGD(TAG, "Deleting nvs entry for [%s]", key);
+	if(!config_lock(LOCK_MAX_WAIT/portTICK_PERIOD_MS)){
+		ESP_LOGE(TAG, "Unable to lock config for delete");
+		return false;
+	}
+	esp_err_t err = nvs_open_from_partition(settings_partition, current_namespace, NVS_READWRITE, &nvs);
+	if (err == ESP_OK) {
+		err = nvs_erase_key(nvs, key);
+		if (err == ESP_OK) {
+			ESP_LOGD(TAG, "key [%s] erased from nvs.",key);
+			err = nvs_commit(nvs);
+			if (err == ESP_OK) {
+				ESP_LOGD(TAG, "nvs erase committed.");
+			}
+			else {
+				ESP_LOGE(TAG, "Unable to commit nvs erase operation for key [%s]. %s.",key,esp_err_to_name(err));
+			}
+		}
+		else {
+			ESP_LOGE(TAG, "Unable to delete nvs key [%s]. %s. ",key, esp_err_to_name(err));
+		}
+		nvs_close(nvs);
+	}
+	else {
+		ESP_LOGE(TAG, "Error opening nvs: %s. Unable to delete nvs key [%s].",esp_err_to_name(err),key);
+	}
+	char * struc_str = cJSON_PrintUnformatted(nvs_json);
+	if(struc_str!=NULL){
+		ESP_LOGV(TAG, "Structure before delete \n%s", struc_str);
+		free(struc_str);
+	}
+	cJSON * entry = cJSON_DetachItemFromObjectCaseSensitive(nvs_json, key);
+	if(entry !=NULL){
+		ESP_LOGI(TAG, "Removing config key [%s]", entry->string);
+		cJSON_Delete(entry);
+		struc_str = cJSON_PrintUnformatted(nvs_json);
+		if(struc_str!=NULL){
+			ESP_LOGV(TAG, "Structure after delete \n%s", struc_str);
+			free(struc_str);
+		}
+	}
+	else {
+		ESP_LOGW(TAG, "Unable to remove config key [%s]: not found.", key);
+	}
+	config_unlock();
+}
+void * config_alloc_get(nvs_type_t nvs_type, const char *key) {
+	return config_alloc_get_default(nvs_type, key, NULL, 0);
+}
+void * config_alloc_get_default(nvs_type_t nvs_type, const char *key, void * default_value, size_t blob_size) {
+
+	void * value = NULL;
+	ESP_LOGV(TAG, "Retrieving key %s from nvs cache for type %s.", key,type_to_str(nvs_type));
+	if(nvs_json==NULL){
+		ESP_LOGE(TAG,"configuration not loaded!");
+		return value;
+	}
+	if(!config_lock(LOCK_MAX_WAIT/portTICK_PERIOD_MS)){
+		ESP_LOGE(TAG, "Unable to lock config");
+		return value;
+	}
+	ESP_LOGD(TAG,"Getting config entry for key %s",key);
+	cJSON * entry = cJSON_GetObjectItemCaseSensitive(nvs_json, key);
+	if(entry !=NULL){
+		ESP_LOGV(TAG, "Entry found, getting value.");
+		value = config_safe_alloc_get_entry_value(nvs_type, entry);
+	}
+	else if(default_value!=NULL){
+		// Value was not found
+		ESP_LOGW(TAG, "Adding new config value for key [%s]",key);
+		entry=config_set_value_safe(nvs_type, key, default_value);
+		if(entry == NULL){
+			ESP_LOGE(TAG, "Failed to add value to cache");
+		}
+		else {
+			char * entry_str = cJSON_PrintUnformatted(entry);
+			if(entry_str!=NULL){
+				ESP_LOGV(TAG, "Value added configuration object for key [%s]: \n%s", entry->string,entry_str);
+				free(entry_str);
+			}
+			else {
+				ESP_LOGV(TAG, "Value added configuration object for key [%s]", entry->string);
+			}
+			value = config_safe_alloc_get_entry_value(nvs_type, entry);
+		}
+	}
+	else{
+		ESP_LOGW(TAG,"Value not found for key %s",key);
+	}
+	config_unlock();
+	return value;
+}
+char * config_alloc_get_json(bool bFormatted){
+	char * json_buffer = NULL;
+	if(!config_lock(LOCK_MAX_WAIT/portTICK_PERIOD_MS)){
+		ESP_LOGE(TAG, "Unable to lock config after %d ms",LOCK_MAX_WAIT);
+		return strdup("{\"error\":\"Unable to lock configuration object.\"}");
+	}
+	if(bFormatted){
+		json_buffer= cJSON_Print(nvs_json);
+	}
+	else {
+		json_buffer= cJSON_PrintUnformatted(nvs_json);
+	}
+	config_unlock();
+	return json_buffer;
+}
+esp_err_t config_set_value(nvs_type_t nvs_type, const char *key, void * value){
+	esp_err_t result = ESP_OK;
+	if(!config_lock(LOCK_MAX_WAIT/portTICK_PERIOD_MS)){
+			ESP_LOGE(TAG, "Unable to lock config after %d ms",LOCK_MAX_WAIT);
+			result = ESP_FAIL;
+	}
+	cJSON * entry = config_set_value_safe(nvs_type, key, value);
+	if(entry == NULL){
+		result = ESP_FAIL;
+	}
+	else{
+		char * entry_str = cJSON_PrintUnformatted(entry);
+		if(entry_str!=NULL){
+			ESP_LOGV(TAG,"config_set_value result: \n%s",entry_str);
+			free(entry_str);
+		}
+		else {
+			ESP_LOGV(TAG,"config_set_value completed");
+		}
+	}
+	config_unlock();
+	return result;
+}
+
+IMPLEMENT_SET_DEFAULT(uint8_t,NVS_TYPE_U8);
+IMPLEMENT_SET_DEFAULT(int8_t,NVS_TYPE_I8);
+IMPLEMENT_SET_DEFAULT(uint16_t,NVS_TYPE_U16);
+IMPLEMENT_SET_DEFAULT(int16_t,NVS_TYPE_I16);
+IMPLEMENT_SET_DEFAULT(uint32_t,NVS_TYPE_U32);
+IMPLEMENT_SET_DEFAULT(int32_t,NVS_TYPE_I32);
+
+IMPLEMENT_GET_NUM(uint8_t,NVS_TYPE_U8);
+IMPLEMENT_GET_NUM(int8_t,NVS_TYPE_I8);
+IMPLEMENT_GET_NUM(uint16_t,NVS_TYPE_U16);
+IMPLEMENT_GET_NUM(int16_t,NVS_TYPE_I16);
+IMPLEMENT_GET_NUM(uint32_t,NVS_TYPE_U32);
+IMPLEMENT_GET_NUM(int32_t,NVS_TYPE_I32);

+ 8 - 0
components/display/core/gds_draw.h

@@ -1,3 +1,11 @@
+/* 
+ * (c) Philippe G. 2019, philippe_44@outlook.com
+ *
+ * This software is released under the MIT License.
+ * https://opensource.org/licenses/MIT
+ * 
+ */
+ 
 #ifndef _GDS_DRAW_H_
 #define _GDS_DRAW_H_
 

+ 8 - 0
components/display/core/gds_image.c

@@ -1,3 +1,11 @@
+/* 
+ * (c) Philippe G. 2019, philippe_44@outlook.com
+ *
+ * This software is released under the MIT License.
+ * https://opensource.org/licenses/MIT
+ * 
+ */
+ 
 #include <string.h>
 #include "math.h"
 #include "esp32/rom/tjpgd.h"

+ 8 - 0
components/display/core/gds_image.h

@@ -1,3 +1,11 @@
+/* 
+ * (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>

+ 8 - 0
components/display/core/gds_private.h

@@ -1,3 +1,11 @@
+/* 
+ * (c) Philippe G. 2019, philippe_44@outlook.com
+ *
+ * This software is released under the MIT License.
+ * https://opensource.org/licenses/MIT
+ * 
+ */
+ 
 #ifndef _GDS_PRIVATE_H_
 #define _GDS_PRIVATE_H_
 

+ 3 - 14
components/display/core/gds_text.c

@@ -1,20 +1,9 @@
 /* 
- *  (c) 2004,2006 Richard Titmuss for SlimProtoLib 
- *  (c) Philippe G. 2019, philippe_44@outlook.com
+ * (c) Philippe G. 2019, philippe_44@outlook.com
  *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
+ * This software is released under the MIT License.
+ * https://opensource.org/licenses/MIT
  * 
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- *
  */
 
 #include <string.h>

+ 3 - 13
components/display/core/gds_text.h

@@ -1,19 +1,9 @@
 /* 
- *  (c) Philippe G. 2019, philippe_44@outlook.com
+ * (c) Philippe G. 2019, philippe_44@outlook.com
  *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
+ * This software is released under the MIT License.
+ * https://opensource.org/licenses/MIT
  * 
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- *
  */
 
 #pragma once

+ 4 - 0
components/display/core/ifaces/default_if_i2c.c

@@ -21,6 +21,7 @@ static int I2CWait;
 
 static const int GDS_I2C_COMMAND_MODE = 0x80;
 static const int GDS_I2C_DATA_MODE = 0x40;
+static const int i2c_timeout_value=2000;
 
 static bool I2CDefaultWriteBytes( int Address, bool IsCommand, const uint8_t* Data, size_t DataLength );
 static bool I2CDefaultWriteCommand( struct GDS_Device* Device, uint8_t Command );
@@ -49,6 +50,9 @@ bool GDS_I2CInit( int PortNumber, int SDA, int SCL, int Speed ) {
 
 		ESP_ERROR_CHECK_NONFATAL( i2c_param_config( I2CPortNumber, &Config ), return false );
 		ESP_ERROR_CHECK_NONFATAL( i2c_driver_install( I2CPortNumber, Config.mode, 0, 0, 0 ), return false );
+		printf("Setting timeout value to %d",i2c_timeout_value);
+		i2c_set_timeout(I2CPortNumber,  (I2C_APB_CLK_FREQ /(2500000))* i2c_timeout_value);
+
 	}	
 
     return true;

+ 2 - 13
components/display/display.c

@@ -1,19 +1,8 @@
 /* 
- *  (c) 2004,2006 Richard Titmuss for SlimProtoLib 
  *  (c) Philippe G. 2019, philippe_44@outlook.com
  *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- * 
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *  This software is released under the MIT License.
+ *  https://opensource.org/licenses/MIT
  *
  */
 

+ 2 - 12
components/display/display.h

@@ -1,18 +1,8 @@
 /* 
  *  (c) Philippe G. 2019, philippe_44@outlook.com
  *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- * 
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *  This software is released under the MIT License.
+ *  https://opensource.org/licenses/MIT
  *
  */
 

+ 53 - 33
components/driver_bt/bt_app_sink.c

@@ -71,8 +71,7 @@ static esp_avrc_rn_evt_cap_mask_t s_avrc_peer_rn_cap;
 static _lock_t s_volume_lock;
 static uint8_t s_volume = 0;
 static bool s_volume_notify;
-static bool s_playing = false; 
-static enum { AUDIO_IDLE, AUDIO_CONNECTED, AUDIO_ACTIVATED } s_audio = AUDIO_IDLE;
+static enum { AUDIO_IDLE, AUDIO_CONNECTED, AUDIO_PLAYING } s_audio = AUDIO_IDLE;
 
 static int s_sample_rate;
 static int tl;
@@ -88,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) {
-	if (s_playing) esp_avrc_ct_send_passthrough_cmd(tl++ & 0x0f, ESP_AVRC_PT_CMD_STOP, ESP_AVRC_PT_CMD_STATE_PRESSED);
+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);
 }
 
@@ -139,14 +146,14 @@ const static actrls_t controls = {
 /* disconnection */
 void bt_disconnect(void) {
 	displayer_control(DISPLAYER_SHUTDOWN);
-	if (s_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);
 	actrls_unset();
-	ESP_LOGI(BT_AV_TAG, "forced disconnection");
+	ESP_LOGI(BT_AV_TAG, "forced disconnection %d", s_audio);
 }
 
 /* update metadata if any */
 void update_metadata(bool force) {
-	if ((s_metadata.updated || force) && s_audio == AUDIO_ACTIVATED) {
+	if ((s_metadata.updated || force) && s_audio == AUDIO_PLAYING) {
 		(*bt_app_a2d_cmd_cb)(BT_SINK_PROGRESS, -1, s_metadata.duration);
 		(*bt_app_a2d_cmd_cb)(BT_SINK_METADATA, s_metadata.artist, s_metadata.album, s_metadata.title);
 		s_metadata.updated = false;
@@ -290,18 +297,17 @@ static void bt_av_hdl_a2d_evt(uint16_t event, void *p_param)
 			
 			// verify that we can take control
 			if ((*bt_app_a2d_cmd_cb)(BT_SINK_AUDIO_STARTED, s_sample_rate)) {
-				// resynchronize events as¨PLAY might be sent before STARTED ...
-				s_audio = AUDIO_ACTIVATED;
 				
-				// send PLAY there, in case it was sent before AUDIO_STATE
-				if (s_playing) (*bt_app_a2d_cmd_cb)(BT_SINK_PLAY);
+				// if PLAY is sent before AUDIO_STARTED, generate the event here
+				s_audio = AUDIO_PLAYING;
+				(*bt_app_a2d_cmd_cb)(BT_SINK_PLAY);
 				
 				// force metadata update
 				update_metadata(true);
 				
-				actrls_set(controls, NULL);
-			} else if (s_playing) {
-				// if decoder is busy but BT is playing, stop it (would be better to not ACK this command, but don't know how)
+				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);	
 			}	
 		} else if (ESP_A2D_AUDIO_STATE_STOPPED == a2d->audio_stat.state ||
@@ -382,19 +388,33 @@ void bt_av_notify_evt_handler(uint8_t event_id, esp_avrc_rn_param_t *event_param
         break;
     case ESP_AVRC_RN_PLAY_STATUS_CHANGE:
         ESP_LOGI(BT_AV_TAG, "Playback status changed: 0x%x", event_parameter->playback);
-		// re-synchronize events
-		s_playing = (event_parameter->playback == ESP_AVRC_PLAYBACK_PLAYING);
-		if (event_parameter->playback == ESP_AVRC_PLAYBACK_PLAYING && s_audio != AUDIO_IDLE) {
-			// if decoder is busy then stop (would be better to not ACK this command, but don't know how)
-			if (s_audio == AUDIO_CONNECTED || !(*bt_app_a2d_cmd_cb)(BT_SINK_PLAY)) {
-				esp_avrc_ct_send_passthrough_cmd(tl++ & 0x0f, ESP_AVRC_PT_CMD_STOP, ESP_AVRC_PT_CMD_STATE_PRESSED);
-			} else {
-				update_metadata(false);
+		if (s_audio != AUDIO_IDLE) {
+			switch (event_parameter->playback) {
+			case ESP_AVRC_PLAYBACK_PLAYING:
+				// if decoder is busy then stop (would be better to not ACK this command, but don't know how)
+				if (s_audio != AUDIO_PLAYING && !(*bt_app_a2d_cmd_cb)(BT_SINK_PLAY)) {
+					ESP_LOGW(BT_AV_TAG, "Player busy with another controller");					
+					esp_avrc_ct_send_passthrough_cmd(tl++ & 0x0f, ESP_AVRC_PT_CMD_STOP, ESP_AVRC_PT_CMD_STATE_PRESSED);
+				} else {
+					s_audio = AUDIO_PLAYING;
+					update_metadata(false);
+				}
+				break;		
+			case ESP_AVRC_PLAYBACK_PAUSED:
+				s_audio = AUDIO_CONNECTED;
+				(*bt_app_a2d_cmd_cb)(BT_SINK_PAUSE);
+				break;
+			case ESP_AVRC_PLAYBACK_STOPPED:
+				s_audio = AUDIO_CONNECTED;
+				(*bt_app_a2d_cmd_cb)(BT_SINK_PROGRESS, 0, -1);			
+				(*bt_app_a2d_cmd_cb)(BT_SINK_STOP);
+				break;
+			default:
+				ESP_LOGI(BT_AV_TAG, "Un-handled event");
+				break;
 			}	
-		} else if (event_parameter->playback == ESP_AVRC_PLAYBACK_PAUSED) (*bt_app_a2d_cmd_cb)(BT_SINK_PAUSE);
-		else if (event_parameter->playback == ESP_AVRC_PLAYBACK_STOPPED) {
-			(*bt_app_a2d_cmd_cb)(BT_SINK_PROGRESS, 0, -1);			
-			(*bt_app_a2d_cmd_cb)(BT_SINK_STOP);
+		} else {
+			ESP_LOGW(BT_AV_TAG, "Not yet in BT connected mode: 0x%x", event_parameter->playback);
 		}	
         bt_av_playback_changed();
         break;

+ 1 - 1
components/driver_bt/bt_app_source.c

@@ -19,7 +19,7 @@
 #include "platform_config.h"
 #include "trace.h"
 
-static const char * TAG = "platform";
+static const char * TAG = "bt_app_source";
 
 extern int32_t 	output_bt_data(uint8_t *data, int32_t len);
 extern void 	output_bt_tick(void);

+ 2 - 12
components/raop/platform.h

@@ -3,18 +3,8 @@
  *
  *  (c) Philippe, philippe_44@outlook.com
  *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ * This software is released under the MIT License.
+ * https://opensource.org/licenses/MIT
  *
  */
 

+ 5 - 14
components/raop/raop.c

@@ -1,19 +1,9 @@
 /*
  *
- *  (c) Philippe 2019, philippe_44@outlook.com
+ * (c) Philippe 2019, philippe_44@outlook.com
  *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ * This software is released under the MIT License.
+ * https://opensource.org/licenses/MIT
  *
  */
 
@@ -957,4 +947,5 @@ static void on_dmap_string(void *ctx, const char *code, const char *name, const
 	if (!strcasecmp(code, "asar")) metadata->artist = strndup(buf, len);
 	else if (!strcasecmp(code, "asal")) metadata->album = strndup(buf, len);
 	else if (!strcasecmp(code, "minm")) metadata->title = strndup(buf, len);
-}
+}
+

+ 3 - 13
components/raop/raop.h

@@ -1,20 +1,10 @@
 /*
  *  AirCast: Chromecast to AirPlay
  *
- *  (c) Philippe 2016-2017, philippe_44@outlook.com
+ * (c) Philippe 2016-2017, philippe_44@outlook.com
  *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ * This software is released under the MIT License.
+ * https://opensource.org/licenses/MIT
  *
  */
 

+ 17 - 9
components/raop/raop_sink.c

@@ -32,42 +32,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");
 }
@@ -99,7 +107,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:

+ 4 - 14
components/raop/util.c

@@ -1,20 +1,10 @@
 /*
- *  AirConnect: Chromecast & UPnP to AirPlay
+ * AirConnect: Chromecast & UPnP to AirPlay
  *
- *  (c) Philippe 2016-2017, philippe_44@outlook.com
+ * (c) Philippe 2016-2017, philippe_44@outlook.com
  *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ * This software is released under the MIT License.
+ * https://opensource.org/licenses/MIT
  *
  */
 

+ 2 - 13
components/raop/util.h

@@ -1,21 +1,10 @@
 /*
  *  Misc utilities
  *
- *  (c) Adrian Smith 2012-2014, triode1@btinternet.com
  *  (c) Philippe 2016-2017, philippe_44@outlook.com
  *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *  This software is released under the MIT License.
+ *  https://opensource.org/licenses/MIT
  *
  */
 

+ 115 - 48
components/services/audio_controls.c

@@ -4,18 +4,8 @@
  *  (c) Sebastien 2019
  *      Philippe G. 2019, philippe_44@outlook.com
  *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- * 
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *  This software is released under the MIT License.
+ *  https://opensource.org/licenses/MIT
  *
  */
 #include <stdlib.h>
@@ -26,6 +16,7 @@
 #include "cJSON.h"
 #include "buttons.h"
 #include "platform_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);
@@ -42,6 +33,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},
@@ -70,11 +64,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;
+}
+
 /****************************************************************************************
  * 
  */
@@ -82,6 +153,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];
@@ -96,7 +174,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;
@@ -123,7 +201,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);
 	}	
 }
 
@@ -132,6 +210,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:
@@ -153,17 +240,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);
 }
 
 /****************************************************************************************
@@ -370,30 +447,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);
@@ -425,8 +485,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.");
 				}
@@ -448,18 +509,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;
 }
 
 /****************************************************************************************
@@ -468,4 +533,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;
 }

+ 10 - 17
components/services/audio_controls.h

@@ -1,18 +1,8 @@
 /* 
  *  (c) Philippe G. 2019, philippe_44@outlook.com
  *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- * 
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *  This software is released under the MIT License.
+ *  https://opensource.org/licenses/MIT
  *
  */
  
@@ -28,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 {
@@ -47,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);

+ 47 - 24
components/services/buttons.c

@@ -3,18 +3,8 @@
  *
  *  (c) Philippe G. 2019, philippe_44@outlook.com
  *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- * 
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *  This software is released under the MIT License.
+ *  https://opensource.org/licenses/MIT
  *
  */
  
@@ -30,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"
@@ -66,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
  */
@@ -117,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;
@@ -160,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
@@ -171,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);
 		}	
     }
 }	
@@ -186,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
@@ -350,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);
@@ -360,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);
+}	

+ 6 - 12
components/services/buttons.h

@@ -1,22 +1,14 @@
 /* 
  *  (c) Philippe G. 2019, philippe_44@outlook.com
  *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- * 
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *  This software is released under the MIT License.
+ *  https://opensource.org/licenses/MIT
  *
  */
 
 #pragma once
+
+#include "infrared.h"
  
 // button type (pressed = LOW or HIGH, matches GPIO level)
 #define BUTTON_LOW 		0
@@ -42,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);

+ 2 - 12
components/services/globdefs.h

@@ -3,18 +3,8 @@
  *
  *  (c) Philippe G. 2019, philippe_44@outlook.com
  *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- * 
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *  This software is released under the MIT License.
+ *  https://opensource.org/licenses/MIT
  *
  */
  

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

+ 2 - 12
components/services/led.h

@@ -4,18 +4,8 @@
  *  (c) Sebastien 2019
  *      Philippe G. 2019, philippe_44@outlook.com
  *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- * 
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *  This software is released under the MIT License.
+ *  https://opensource.org/licenses/MIT
  *
  */
  

+ 6 - 2
components/services/monitor.c

@@ -175,24 +175,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
 
 /****************************************************************************************
  * 

+ 2 - 12
components/services/monitor.h

@@ -3,18 +3,8 @@
  *
  *  (c) Philippe G. 2019, philippe_44@outlook.com
  *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- * 
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *  This software is released under the MIT License.
+ *  https://opensource.org/licenses/MIT
  *
  */
  

+ 2 - 12
components/squeezelite/adac.h

@@ -4,18 +4,8 @@
  *  (c) Sebastien 2019
  *      Philippe G. 2019, philippe_44@outlook.com
  *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- * 
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *  This software is released under the MIT License.
+ *  https://opensource.org/licenses/MIT
  *
  */
 

+ 1 - 1
components/squeezelite/alac.c

@@ -358,7 +358,7 @@ static decode_state alac_decode(void) {
 		return DECODE_COMPLETE;
 	}
 
-	// enough data for coding
+	// is there enough data for decoding
 	if (bytes < block_size) {
 		UNLOCK_S;
 		return DECODE_RUNNING;

+ 194 - 47
components/squeezelite/controls.c

@@ -1,100 +1,231 @@
 /* 
  *  (c) Philippe G. 2019, philippe_44@outlook.com
  *
-  * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- * 
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *  This software is released under the MIT License.
+ *  https://opensource.org/licenses/MIT
  *
  */
 
 #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 but are not used
+
+// 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_down(void) {
-	cli_send_cmd("button voldown");
+static void lms_volume_up(bool pressed) {
+	if (raw_mode) {
+		sendBUTN(BUTN_VOLUP_FRONT, pressed);
+	} else {
+		cli_send_cmd("button volup");
+	}	
 }
 
-static void lms_toggle(void) {
-	cli_send_cmd("pause");
+static void lms_volume_down(bool pressed) {
+	if (raw_mode) {
+		sendBUTN(BUTN_VOLDOWN_FRONT, pressed);
+	} else {
+		cli_send_cmd("button voldown");
+	}	
 }
 
-static void lms_pause(void) {
-	cli_send_cmd("pause 1");
+static void lms_toggle(bool pressed) {
+	if (raw_mode) {
+		sendBUTN(BUTN_PAUSE, pressed);
+	} else {
+		cli_send_cmd("pause");
+	}	
 }
 
-static void lms_play(void) {
-	cli_send_cmd("button play.single");
+static void lms_pause(bool pressed) {
+	if (raw_mode) {
+		sendBUTN(BUTN_PAUSE, pressed);
+	} else {
+		cli_send_cmd("pause 1");
+	}	
 }
 
-static void lms_stop(void) {
+static void lms_play(bool pressed) {
+	if (raw_mode) {
+		sendBUTN(BUTN_PLAY, pressed);
+	} else {
+		cli_send_cmd("button play.single");
+	}	
+}
+
+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 = {
@@ -143,17 +274,33 @@ static void notify(in_addr_t ip, u16_t hport, u16_t cport) {
 	server_ip = ip;
 	server_hport = hport;
 	server_cport = cport;
+	
 	LOG_INFO("notified server %s hport %hu cport %hu", inet_ntoa(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
  */
 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;
 }

+ 30 - 50
components/squeezelite/decode_external.c

@@ -4,18 +4,8 @@
  *  (c) Sebastien 2019
  *      Philippe G. 2019, philippe_44@outlook.com
  *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- * 
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *  This software is released under the MIT License.
+ *  https://opensource.org/licenses/MIT
  *
  */
 
@@ -44,15 +34,15 @@ static bool enable_bt_sink;
 static bool enable_airplay;
 
 #define RAOP_OUTPUT_SIZE 	(RAOP_SAMPLE_RATE * 2 * 2 * 2 * 1.2)
-#define SYNC_WIN_RUN	32
+#define SYNC_WIN_SLOW	32
 #define SYNC_WIN_CHECK	8
-#define SYNC_WIN_START	2
+#define SYNC_WIN_FAST	2
 
 static raop_event_t	raop_state;
 
 static EXT_RAM_ATTR struct {
 	bool enabled;
-	int sum, count, win, errors[SYNC_WIN_RUN];
+	int sum, count, win, errors[SYNC_WIN_SLOW];
 	s32_t len;
 	u32_t start_time, playtime;
 } raop_sync;
@@ -200,48 +190,31 @@ static bool raop_sink_cmd_handler(raop_event_t event, va_list args)
 	// this is async, so player might have been deleted
 	switch (event) {
 		case RAOP_TIMING: {
+									
+			if (!raop_sync.enabled || output.state != OUTPUT_RUNNING || output.frames_played_dmp < output.device_frames) break;
+
 			u32_t ms, now = gettime_ms();
+			u32_t level = _buf_used(outputbuf);
 			int error;
-									
-			if (!raop_sync.enabled || output.state < OUTPUT_RUNNING || output.frames_played_dmp < output.device_frames) break;
-			
-			// first must make sure we started on time
-			if (raop_sync.win == SYNC_WIN_START) {
-				// how many ms have we really played
-				ms = now - output.updated + ((output.frames_played_dmp - output.device_frames) * 10) / (RAOP_SAMPLE_RATE / 100);
-				error = ms - (now - raop_sync.start_time);
-				
-				LOG_INFO("backend played %u, desired %u, (delta:%d)", ms, now - raop_sync.start_time, error);
-			} else {	
-				u32_t level = _buf_used(outputbuf);
 				
-				// in how many ms will the most recent block play 
-				ms = (((s32_t)(level - raop_sync.len) / BYTES_PER_FRAME + output.device_frames + output.frames_in_process) * 10) / (RAOP_SAMPLE_RATE / 100) - (s32_t) (now - output.updated);
+			// in how many ms will the most recent block play 
+			ms = (((s32_t)(level - raop_sync.len) / BYTES_PER_FRAME + output.device_frames + output.frames_in_process) * 10) / (RAOP_SAMPLE_RATE / 100) - (s32_t) (now - output.updated);
 				
-				// when outputbuf is empty, it means we have a network black-out or something
-				error = level ? (raop_sync.playtime - now) - ms : 0;
+			// when outputbuf is empty, it means we have a network black-out or something
+			error = level ? (raop_sync.playtime - now) - ms : 0;
 				
-				if (loglevel == lDEBUG || !level) {
-					LOG_INFO("head local:%d, remote:%d (delta:%d)", ms, raop_sync.playtime - now, error);
-					LOG_INFO("obuf:%u, sync_len:%u, devframes:%u, inproc:%u", _buf_used(outputbuf), raop_sync.len, output.device_frames, output.frames_in_process);
-				}	
+			if (loglevel == lDEBUG || !level) {
+				LOG_INFO("head local:%d, remote:%d (delta:%d)", ms, raop_sync.playtime - now, error);
+				LOG_INFO("obuf:%u, sync_len:%u, devframes:%u, inproc:%u", _buf_used(outputbuf), raop_sync.len, output.device_frames, output.frames_in_process);
 			}	
 			
 			// calculate sum, error and update sliding window
 			raop_sync.errors[raop_sync.count++ % raop_sync.win] = error;
 			raop_sync.sum += error;
 			error = raop_sync.sum / min(raop_sync.count, raop_sync.win);
-			
-			// move to normal mode if possible
-			if (raop_sync.win == SYNC_WIN_START && raop_sync.count >= SYNC_WIN_START && abs(error) < 10) {
-				raop_sync.win = SYNC_WIN_RUN;
-				LOG_INFO("switching to slow sync mode %u", raop_sync.win);
-			}	
 
-			// wait till e have enough data or there is a strong deviation
-			if ((raop_sync.count >= raop_sync.win && abs(error) > 10) || (raop_sync.count >= SYNC_WIN_CHECK && abs(error) > 100)) { 
-			
-				// correct if needed
+			// wait till we have enough data or there is a strong deviation
+			if ((raop_sync.count >= raop_sync.win && abs(error) > 10) || (raop_sync.count >= SYNC_WIN_CHECK && abs(error) > 100)) {
 				if (error < 0) {
 					output.skip_frames = -(error * RAOP_SAMPLE_RATE) / 1000;
 					output.state = OUTPUT_SKIP_FRAMES;					
@@ -251,11 +224,18 @@ static bool raop_sink_cmd_handler(raop_event_t event, va_list args)
 					output.state = OUTPUT_PAUSE_FRAMES;
 					LOG_INFO("pausing for %u frames (count: %d)", output.pause_frames, raop_sync.count);
 				}
-
-				// reset sliding window		
+				
 				raop_sync.sum = raop_sync.count = 0;
 				memset(raop_sync.errors, 0, sizeof(raop_sync.errors));
-											
+			}	
+			
+			// move to normal mode if possible			
+			if (raop_sync.win == 1) {
+				raop_sync.win = SYNC_WIN_FAST;
+				LOG_INFO("backend played %u, desired %u, (delta:%d)", ms, raop_sync.playtime - now, error);
+			} else if (raop_sync.win == SYNC_WIN_FAST && raop_sync.count >= SYNC_WIN_FAST && abs(error) < 10) {
+				raop_sync.win = SYNC_WIN_SLOW;
+				LOG_INFO("switching to slow sync mode %u", raop_sync.win);
 			}	
 
 			break;
@@ -272,8 +252,8 @@ static bool raop_sink_cmd_handler(raop_event_t event, va_list args)
 		case RAOP_STREAM:
 			LOG_INFO("Stream", NULL);
 			raop_state = event;
-			raop_sync.win = SYNC_WIN_START;
-			raop_sync.sum = raop_sync.count = 0 ;
+			raop_sync.win = 1;
+			raop_sync.sum = raop_sync.count = 0;
 			memset(raop_sync.errors, 0, sizeof(raop_sync.errors));
 			raop_sync.enabled = !strcasestr(output.device, "BT");
 			output.next_sample_rate = output.current_sample_rate = RAOP_SAMPLE_RATE;

+ 40 - 63
components/squeezelite/display.c

@@ -1,19 +1,8 @@
 /* 
- *  (c) 2004,2006 Richard Titmuss for SlimProtoLib 
  *  (c) Philippe G. 2019, philippe_44@outlook.com
  *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- * 
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *  This software is released under the MIT License.
+ *  https://opensource.org/licenses/MIT
  *
  */
 
@@ -208,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);
@@ -234,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
@@ -304,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;
@@ -330,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;
 	
@@ -369,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);
-		
-		send_packet((uint8_t *) &pkt_header, sizeof(pkt_header));
-		send_packet((uint8_t *) &SETD_width, 2);
-		send_packet((uint8_t *) &height, 2);
+	width = htons(width);
+	height = htons(height);
 		
-		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;
 }
 
 /****************************************************************************************
@@ -427,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);
 }
 
@@ -1152,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;

+ 5 - 12
components/squeezelite/embedded.c

@@ -4,18 +4,8 @@
  *  (c) Sebastien 2019
  *      Philippe G. 2019, philippe_44@outlook.com
  *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- * 
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *  This software is released under the MIT License.
+ *  https://opensource.org/licenses/MIT
  *
  */
 #include "squeezelite.h"
@@ -25,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);
 }
@@ -56,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;
 }

+ 5 - 1
components/squeezelite/embedded.h

@@ -61,6 +61,10 @@ void		embedded_init(void);
 void 		register_external(void);
 void 		deregister_external(void);
 void 		decode_restore(int external);
+// used when other client wants to use slimproto socket to send messages
+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);
@@ -80,5 +84,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

+ 2 - 12
components/squeezelite/equalizer.c

@@ -3,18 +3,8 @@
  *
  *  (c) Philippe G. 2020, philippe_44@outlook.com
  *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- * 
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *  This software is released under the MIT License.
+ *  https://opensource.org/licenses/MIT
  *
  */
  

+ 2 - 12
components/squeezelite/equalizer.h

@@ -3,18 +3,8 @@
  *
  *  (c) Philippe G. 2020, philippe_44@outlook.com
  *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- * 
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *  This software is released under the MIT License.
+ *  https://opensource.org/licenses/MIT
  *
  */
  

+ 2 - 12
components/squeezelite/external/dac_external.c

@@ -4,18 +4,8 @@
  *  (c) Sebastien 2019
  *      Philippe G. 2019, philippe_44@outlook.com
  *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- * 
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *  This software is released under the MIT License.
+ *  https://opensource.org/licenses/MIT
  *
  */
  

+ 30 - 24
components/squeezelite/helix-aac.c

@@ -330,7 +330,7 @@ static decode_state helixaac_decode(void) {
 	u8_t *sptr;
 	bool endstream;
 	frames_t frames;
-
+	
 	LOCK_S;
 	bytes_total = _buf_used(streambuf);
 	bytes_wrap  = min(bytes_total, _buf_cont_read(streambuf));
@@ -389,19 +389,25 @@ static decode_state helixaac_decode(void) {
 		}
 
 		if (found == 1) {
-
-			LOG_INFO("samplerate: %u channels: %u", samplerate, channels);
-			bytes_total = _buf_used(streambuf);
-			bytes_wrap  = min(bytes_total, _buf_cont_read(streambuf));
-
 			LOCK_O;
-			LOG_INFO("setting track_start");
 			output.next_sample_rate = decode_newstream(samplerate, output.supported_rates);
 			IF_DSD( output.next_fmt = PCM; )
 			output.track_start = outputbuf->writep;
 			if (output.fade_mode) _checkfade(true);
 			decode.new_stream = false;
 			UNLOCK_O;
+			
+			LOG_INFO("setting track start, samplerate: %u channels: %u", samplerate, channels);
+			
+			bytes_total = _buf_used(streambuf);
+			bytes_wrap  = min(bytes_total, _buf_cont_read(streambuf));
+
+			// come back later if we don' thave enough data			
+			if (bytes_total < WRAPBUF_LEN) {
+				UNLOCK_S;
+				LOG_INFO("need more audio data");
+				return DECODE_RUNNING;
+			}
 
 		} else if (found == -1) {
 
@@ -418,15 +424,15 @@ static decode_state helixaac_decode(void) {
 		}
 	}
 
-	if (bytes_wrap < WRAPBUF_LEN && bytes_total > WRAPBUF_LEN) {
-		// make a local copy of frames which may have wrapped round the end of streambuf
+	// we always have at least WRAPBUF_LEN unless it's the end of a stream
+	if (bytes_wrap < WRAPBUF_LEN) {
+		// build a linear buffer if we are crossing the end of streambuf
 		memcpy(a->wrap_buf, streambuf->readp, bytes_wrap);
-		memcpy(a->wrap_buf + bytes_wrap, streambuf->buf, WRAPBUF_LEN - bytes_wrap);
+		memcpy(a->wrap_buf + bytes_wrap, streambuf->buf, min(WRAPBUF_LEN, bytes_total) - bytes_wrap);
 		
 		sptr = a->wrap_buf;
-		bytes = bytes_wrap = WRAPBUF_LEN;
+		bytes = bytes_wrap = min(WRAPBUF_LEN, bytes_total);
 	} else {
-
 		sptr = streambuf->readp;
 		bytes = bytes_wrap;
 	}
@@ -442,9 +448,8 @@ static decode_state helixaac_decode(void) {
 	bytes = bytes_wrap - bytes;
 	endstream = false;
 
-	// mp4 end of chunk - skip to next offset
 	if (a->chunkinfo && a->chunkinfo[a->nextchunk].offset && a->sample++ == a->chunkinfo[a->nextchunk].sample) {
-
+		// mp4 end of chunk - skip to next offset
 		if (a->chunkinfo[a->nextchunk].offset > a->pos) {
 			u32_t skip = a->chunkinfo[a->nextchunk].offset - a->pos;
 			if (skip != bytes) {
@@ -461,15 +466,12 @@ static decode_state helixaac_decode(void) {
 			LOG_ERROR("error: need to skip backwards!");
 			endstream = true;
 		}
-
-	// adts and mp4 when not at end of chunk 
 	} else if (bytes > 0) {
-
+		// adts and mp4 when not at end of chunk 
 		_buf_inc_readp(streambuf, bytes);
 		a->pos += bytes;
-
-	// error which doesn't advance streambuf - end
 	} else {
+		// error which doesn't advance streambuf - end
 		endstream = true;
 	}
 
@@ -517,7 +519,7 @@ static decode_state helixaac_decode(void) {
 		frames_t f;
 		frames_t count;
 		ISAMPLE_T *optr;
-
+		
 		IF_DIRECT(
 			f = _buf_cont_write(outputbuf) / BYTES_PER_FRAME;
 			optr = (ISAMPLE_T *)outputbuf->writep;
@@ -559,7 +561,7 @@ static decode_state helixaac_decode(void) {
 			if (frames) LOG_ERROR("unhandled case");
 		);
 	}
-
+	
 	UNLOCK_O_direct;
 
 	return DECODE_RUNNING;
@@ -570,13 +572,15 @@ static void helixaac_open(u8_t size, u8_t rate, u8_t chan, u8_t endianness) {
 
 	a->type = size;
 	a->pos = a->consume = a->sample = a->nextchunk = 0;
-
+	
 	if (a->chunkinfo) {
 		free(a->chunkinfo);
 	}
+	
 	if (a->stsc) {
 		free(a->stsc);
 	}
+	
 	a->chunkinfo = NULL;
 	a->stsc = NULL;
 	a->skip = 0;
@@ -585,12 +589,14 @@ static void helixaac_open(u8_t size, u8_t rate, u8_t chan, u8_t endianness) {
 	a->empty = false;
 
 	if (a->hAac) {
-		HAAC(a, FlushCodec, a->hAac);
+		// always free decoder as flush only works when no parameter has changed
+		HAAC(a, FreeDecoder, a->hAac);			
 	} else {
-		a->hAac = HAAC(a, InitDecoder);	
 		a->write_buf = malloc(FRAME_BUF * BYTES_PER_FRAME);
 		a->wrap_buf = malloc(WRAPBUF_LEN);
 	}
+	
+	a->hAac = HAAC(a, InitDecoder);	
 }
 
 static void helixaac_close(void) {

+ 2 - 12
components/squeezelite/output_bt.c

@@ -4,18 +4,8 @@
  *  (c) Sebastien 2019
  *      Philippe G. 2019, philippe_44@outlook.com
  *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- * 
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *  This software is released under the MIT License.
+ *  https://opensource.org/licenses/MIT
  *
  */
 

+ 2 - 12
components/squeezelite/output_embedded.c

@@ -4,18 +4,8 @@
  *  (c) Sebastien 2019
  *      Philippe G. 2019, philippe_44@outlook.com
  *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- * 
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *  This software is released under the MIT License.
+ *  https://opensource.org/licenses/MIT
  *
  */
 #include "squeezelite.h"

+ 2 - 12
components/squeezelite/output_i2s.c

@@ -4,18 +4,8 @@
  *  (c) Sebastien 2019
  *      Philippe G. 2019, philippe_44@outlook.com
  *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- * 
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *  This software is released under the MIT License.
+ *  https://opensource.org/licenses/MIT
  *
  */
  

+ 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
 

+ 2 - 12
components/squeezelite/tas57xx/dac_57xx.c

@@ -4,18 +4,8 @@
  *  (c) Sebastien 2019
  *      Philippe G. 2019, philippe_44@outlook.com
  *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- * 
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *  This software is released under the MIT License.
+ *  https://opensource.org/licenses/MIT
  *
  */
  

+ 2 - 12
components/tools/perf_trace.h

@@ -4,18 +4,8 @@
  *  (c) Sebastien 2019
  *      Philippe G. 2019, philippe_44@outlook.com
  *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- * 
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *  This software is released under the MIT License.
+ *  https://opensource.org/licenses/MIT
  *
  */
  

+ 2 - 12
components/tools/tools.h

@@ -3,18 +3,8 @@
  *
  *      Philippe G. 2019, philippe_44@outlook.com
  *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- * 
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *  This software is released under the MIT License.
+ *  https://opensource.org/licenses/MIT
  *
  */
  

+ 2 - 12
components/tools/trace.h

@@ -4,18 +4,8 @@
  *  (c) Sebastien 2019
  *      Philippe G. 2019, philippe_44@outlook.com
  *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- * 
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *  This software is released under the MIT License.
+ *  https://opensource.org/licenses/MIT
  *
  */
  

+ 6 - 13
main/esp_app_main.c

@@ -4,18 +4,8 @@
  *  (c) Sebastien 2019
  *      Philippe G. 2019, philippe_44@outlook.com
  *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- * 
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *  This software is released under the MIT License.
+ *  https://opensource.org/licenses/MIT
  *
  */
 
@@ -323,6 +313,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);
 
@@ -441,7 +434,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");

+ 29 - 0
main/platform_esp32.h

@@ -0,0 +1,29 @@
+/* 
+ *  Squeezelite for esp32
+ *
+ *  (c) Sebastien 2019
+ *      Philippe G. 2019, philippe_44@outlook.com
+ *
+ *  This software is released under the MIT License.
+ *  https://opensource.org/licenses/MIT
+ *
+ */
+ 
+#pragma once
+
+#include "esp_pthread.h"
+#ifndef SQUEEZELITE_ESP32_RELEASE_URL
+#define SQUEEZELITE_ESP32_RELEASE_URL "https://github.com/sle118/squeezelite-esp32/releases"
+#endif
+
+extern void run_command(char * line);
+extern  bool wait_for_wifi();
+extern void console_start();
+extern pthread_cond_t wifi_connect_suspend_cond;
+extern pthread_t wifi_connect_suspend_mutex;
+typedef enum {
+	INFO,
+	WARNING,
+	ERROR
+} message_severity_t;
+extern void set_status_message(message_severity_t severity, const char * message);

BIN
plugin/SqueezeESP32.zip


+ 55 - 27
plugin/SqueezeESP32/HTML/EN/plugins/SqueezeESP32/settings/player.html

@@ -3,74 +3,102 @@
 	[% IF prefs.pref_width %]
 		[% WRAPPER setting title="PLUGIN_SQUEEZEESP32_WIDTH" desc="PLUGIN_SQUEEZEESP32_WIDTH_DESC" %]
 			<!--<input type="text" readonly class="stdedit" name="pref_width" id="width" value="[% prefs.pref_width %]" size="3">-->
+			<input type="hidden" name="pref_width" value="[% prefs.pref_width %]">
 			[% prefs.pref_width %]
 		[% END %]
-	
+
 		[% WRAPPER setting title="PLUGIN_SQUEEZEESP32_SMALL_VU" desc="PLUGIN_SQUEEZEESP32_SMALL_VU_DESC" %]
 			<input type="number" min="10" max= "50" step="5"class="stdedit" name="pref_small_VU" id="small_VU" value="[% prefs.pref_small_VU %]" size="3">
 		[% END %]
-	
+
 		[% WRAPPER setting title="PLUGIN_SQUEEZEESP32_SPECTRUM_SCALE" desc="PLUGIN_SQUEEZEESP32_SPECTRUM_SCALE_DESC" %]
-			<input type="number" min="10" max= "50" step="5" class="stdedit" name="pref_spectrum_scale" id="spectrum_scale" value="[% prefs.pref_spectrum.scale %]" size="3">
+			<input type="number" min="10" max= "50" step="5" class="stdedit" name="pref_spectrum_scale" id="spectrum_scale" value="[% pref_spectrum.scale %]" size="3">
 		[% END %]
-	
+
 		[% WRAPPER setting title="PLUGIN_SQUEEZEESP32_SMALL_SPECTRUM" desc="PLUGIN_SQUEEZEESP32_SMALL_SPECTRUM_DESC" %]
 			[% "PLUGIN_SQUEEZEESP32_SMALL_SPECTRUM_SIZE" | string %]&nbsp
-			<input type="number" min="10" max= "50" step="5"class="stdedit" name="pref_spectrum_small_size" id="spectrum_small_size" value="[% prefs.pref_spectrum.small.size %]" size="3">
+			<input type="number" min="10" max= "50" step="5"class="stdedit" name="pref_spectrum_small_size" id="spectrum_small_size" value="[% pref_spectrum.small.size %]" size="3">
 			[% "PLUGIN_SQUEEZEESP32_SMALL_SPECTRUM_BAND" | string %]&nbsp
-			<input type="text" class="stdedit" name="pref_spectrum_small_band" id="spectrum_small_band" value="[% prefs.pref_spectrum.small.band %]" size="3">
+			<input type="text" class="stdedit" name="pref_spectrum_small_band" id="spectrum_small_band" value="[% pref_spectrum.small.band %]" size="3">
 		[% END %]
-	
+
 		[% WRAPPER setting title="PLUGIN_SQUEEZEESP32_FULL_SPECTRUM_BAND" desc="PLUGIN_SQUEEZEESP32_FULL_SPECTRUM_BAND_DESC" %]
-			<input type="text" class="stdedit" name="pref_spectrum_full_band" id="spectrum_full_band" value="[% prefs.pref_spectrum.full.band %]" size="3">
+			<input type="text" class="stdedit" name="pref_spectrum_full_band" id="spectrum_full_band" value="[% pref_spectrum.full.band %]" size="3">
 		[% END %]
-	
+
 		[% WRAPPER setting title="PLUGIN_SQUEEZEESP32_ARTWORK" desc="PLUGIN_SQUEEZEESP32_ARTWORK_DESC" %]
 			[% "PLUGIN_SQUEEZEESP32_ARTWORK_ENABLE" | string %]&nbsp
-			<input type="checkbox" name="pref_artwork_enable" [% IF prefs.pref_artwork.enable %] checked [% END %]>
+			<input type="checkbox" name="pref_artwork_enable" [% IF pref_artwork.enable %] checked [% END %]>&nbsp;
 			[% "PLUGIN_SQUEEZEESP32_ARTWORK_X" | string %]&nbsp
-			<input type="text" class="stdedit" name="pref_artwork_x" id="artwork_x" value="[% prefs.pref_artwork.x %]" size="2">
+			<input type="text" class="stdedit" name="pref_artwork_x" id="artwork_x" value="[% pref_artwork.x %]" size="2">
 			[% "PLUGIN_SQUEEZEESP32_ARTWORK_Y" | string %]&nbsp
-			<input type="text" class="stdedit" name="pref_artwork_y" id="artwork_y" value="[% prefs.pref_artwork.y %]" size="2">
+			<input type="text" class="stdedit" name="pref_artwork_y" id="artwork_y" value="[% pref_artwork.y %]" size="2">
 		[% END %]
 
-		<hr>	
-	[% END %]	
-	
+		<hr>
+	[% END %]
+
 	[% WRAPPER setting title="PLUGIN_SQUEEZEESP32_EQUALIZER" desc="" %]
+		<div>[% "PLUGIN_SQUEEZEESP32_EQUALIZER_SAVE" | string %]</div>
 	[% END %]
-	
+
+	<script TYPE="text/javascript">
+		if (Ext) {
+			Ext.onReady(function () {
+				new Ext.util.TaskRunner().start({
+					run: checkEq,
+					interval: 1000
+				});
+			});
+
+			function checkEq() {
+				var eqValues = [];
+				this.lastValues = this.lastValues || [];
+
+				for (var x = 0; x < 10; x++) {
+					eqValues[x] = Ext.get('pref_equalizer.' + x).dom.value || 0;
+				}
+
+				if (eqValues.join() != this.lastValues.join()) {
+					this.lastValues = eqValues;
+					SqueezeJS.Controller.request({
+						params: ['[% playerid %]', ['squeezeesp32', 'seteq', eqValues.join()]]
+					});
+				}
+			}
+		}
+	</script>
 	[% WRAPPER settingSection %]
 		[% WRAPPER settingGroup title='31Hz' desc="" %]
-			<input type="text" class="stdedit sliderInput_-13_20" name="pref_eq.0" id="pref_eq.0" value="[% prefs.pref_eq.0 %]" size="2"">
+			<input type="text" class="stdedit sliderInput_-13_20" name="pref_equalizer.0" id="pref_equalizer.0" value="[% pref_equalizer.0 %]" size="2"">
 		[% END %]
 		[% WRAPPER settingGroup title='62Hz' desc="" %]
-			<input type="text" class="stdedit sliderInput_-13_20" name="pref_eq.1" id="pref_eq.1" value="[% prefs.pref_eq.1 %]" size="2">
+			<input type="text" class="stdedit sliderInput_-13_20" name="pref_equalizer.1" id="pref_equalizer.1" value="[% pref_equalizer.1 %]" size="2">
 		[% END %]
 		[% WRAPPER settingGroup title='125Hz' desc="" %]
-			<input type="text" class="stdedit sliderInput_-13_20" name="pref_eq.2" id="pref_eq.2" value="[% prefs.pref_eq.2 %]" size="2">
+			<input type="text" class="stdedit sliderInput_-13_20" name="pref_equalizer.2" id="pref_equalizer.2" value="[% pref_equalizer.2 %]" size="2">
 		[% END %]
 		[% WRAPPER settingGroup title='250Hz' desc="" %]
-			<input type="text" class="stdedit sliderInput_-13_20" name="pref_eq.3" id="pref_eq.3" value="[% prefs.pref_eq.3 %]" size="2">
+			<input type="text" class="stdedit sliderInput_-13_20" name="pref_equalizer.3" id="pref_equalizer.3" value="[% pref_equalizer.3 %]" size="2">
 		[% END %]
 		[% WRAPPER settingGroup title='500Hz' desc="" %]
-			<input type="text" class="stdedit sliderInput_-13_20" name="pref_eq.4" id="pref_eq.4" value="[% prefs.pref_eq.4 %]" size="2">
+			<input type="text" class="stdedit sliderInput_-13_20" name="pref_equalizer.4" id="pref_equalizer.4" value="[% pref_equalizer.4 %]" size="2">
 		[% END %]
 		[% WRAPPER settingGroup title='1kHz' desc="" %]
-			<input type="text" class="stdedit sliderInput_-13_20" name="pref_eq.5" id="pref_eq.5" value="[% prefs.pref_eq.5 %]" size="2">
+			<input type="text" class="stdedit sliderInput_-13_20" name="pref_equalizer.5" id="pref_equalizer.5" value="[% pref_equalizer.5 %]" size="2">
 		[% END %]
 		[% WRAPPER settingGroup title='2kHz' desc="" %]
-			<input type="text" class="stdedit sliderInput_-13_20" name="pref_eq.6" id="pref_eq.6" value="[% prefs.pref_eq.6 %]" size="2">
+			<input type="text" class="stdedit sliderInput_-13_20" name="pref_equalizer.6" id="pref_equalizer.6" value="[% pref_equalizer.6 %]" size="2">
 		[% END %]
 		[% WRAPPER settingGroup title='4kHz' desc="" %]
-			<input type="text" class="stdedit sliderInput_-13_20" name="pref_eq.7" id="pref_eq.7" value="[% prefs.pref_eq.7 %]" size="2">
+			<input type="text" class="stdedit sliderInput_-13_20" name="pref_equalizer.7" id="pref_equalizer.7" value="[% pref_equalizer.7 %]" size="2">
 		[% END %]
 		[% WRAPPER settingGroup title='8kHz' desc="" %]
-			<input type="text" class="stdedit sliderInput_-13_20" name="pref_eq.8" id="pref_eq.8" value="[% prefs.pref_eq.8 %]" size="2">
+			<input type="text" class="stdedit sliderInput_-13_20" name="pref_equalizer.8" id="pref_equalizer.8" value="[% pref_equalizer.8 %]" size="2">
 		[% END %]
 		[% WRAPPER settingGroup title='16kHz' desc="" %]
-			<input type="text" class="stdedit sliderInput_-13_20" name="pref_eq.9" id="pref_eq.9" value="[% prefs.pref_eq.9 %]" size="2">
+			<input type="text" class="stdedit sliderInput_-13_20" name="pref_equalizer.9" id="pref_equalizer.9" value="[% pref_equalizer.9 %]" size="2">
 		[% END %]
 	[% END %]
-	
+
 [% PROCESS settings/footer.html %]

+ 84 - 10
plugin/SqueezeESP32/Player.pm

@@ -3,6 +3,9 @@ package Plugins::SqueezeESP32::Player;
 use strict;
 use base qw(Slim::Player::SqueezePlay);
 
+use Digest::MD5 qw(md5);
+use List::Util qw(min);
+
 use Slim::Utils::Log;
 use Slim::Utils::Prefs;
 
@@ -16,34 +19,34 @@ sub hasIR { 0 }
 sub init {
 	my $client = shift;
 	$client->SUPER::init(@_);
-	Plugins::SqueezeESP32::Plugin::config_artwork($client);
+	$client->config_artwork();
 }
 
 # Allow the player to define it's display width (and probably more)
 sub playerSettingsFrame {
 	my $client   = shift;
 	my $data_ref = shift;
-	
+
 	my $value;
 	my $id = unpack('C', $$data_ref);
-	
+
 	# New SETD command 0xfe for display width & height
-	if ($id == 0xfe) { 
+	if ($id == 0xfe) {
 		$value = (unpack('Cn', $$data_ref))[1];
 		if ($value > 100 && $value < 400) {
 			$prefs->client($client)->set('width', $value);
-			
+
 			my $height = (unpack('Cnn', $$data_ref))[2];
 			$prefs->client($client)->set('height', $height || 0);
 
 			$client->display->modes($client->display->build_modes);
 			$client->display->widthOverride(1, $value);
 			$client->update;
-			
-			$log->info("Setting player $value" . "x" . "$height for ", $client->name);
-		} 
+
+			main::INFOLOG && $log->is_info && $log->info("Setting player $value" . "x" . "$height for ", $client->name);
+		}
 	}
-	
+
 	$client->SUPER::playerSettingsFrame($data_ref);
 }
 
@@ -51,10 +54,81 @@ sub hasScrolling {
 	return 1;
 }
 
+sub hasIR { 
+	return 1; 
+}
+
+sub update_artwork {
+	my $client = shift;
+	my $cprefs = $prefs->client($client);
+
+	my $artwork = $cprefs->get('artwork') || return;
+
+	return unless $artwork->{'enable'};
+
+	my $s = min($cprefs->get('height') - $artwork->{'y'}, $cprefs->get('width') - $artwork->{'x'});
+
+	my $params = { force => shift || 0 };
+	my $path = 'music/current/cover_' . $s . 'x' . $s . '_o.jpg';
+	my $body = Slim::Web::Graphics::artworkRequest($client, $path, $params, \&send_artwork, undef, HTTP::Response->new);
+
+	send_artwork($client, undef, \$body) if $body;
+}
+
+sub send_artwork {
+	my ($client, $params, $dataref) = @_;
+
+	# I'm not sure why we are called so often, so only send when needed
+	my $md5 = md5($$dataref);
+	return if $client->pluginData('artwork_md5') eq $md5 && !$params->{'force'};
+
+	$client->pluginData('artwork', $dataref);
+	$client->pluginData('artwork_md5', $md5);
+
+	my $artwork = $prefs->client($client)->get('artwork') || {};
+	my $length = length $$dataref;
+	my $offset = 0;
+
+	$log->info("got resized artwork (length: ", length $$dataref, ")");
+
+	my $header = pack('Nnn', $length, $artwork->{'x'}, $artwork->{'y'});
+
+	while ($length > 0) {
+		$length = 1280 if $length > 1280;
+		$log->info("sending grfa $length");
+
+		my $data = $header . pack('N', $offset) . substr( $$dataref, 0, $length, '' );
+
+		$client->sendFrame( grfa => \$data );
+		$offset += $length;
+		$length = length $$dataref;
+	}
+}
+
+sub clear_artwork {
+	my ($client, $request) = @_;
+
+	my $artwork = $prefs->client($client)->get('artwork');
+
+	if ($artwork && $artwork->{'enable'}) {
+		main::INFOLOG && $log->is_info && $log->info("artwork stop/clear " . $request->getRequestString());
+		$client->pluginData('artwork_md5', '');
+	}
+}
+
+sub config_artwork {
+	my ($client) = @_;
+
+	if ( my $artwork = $prefs->client($client)->get('artwork') ) {
+		my $header = pack('Nnn', $artwork->{'enable'}, $artwork->{'x'}, $artwork->{'y'});
+		$client->sendFrame( grfa => \$header );
+	}
+}
+
 sub reconnect {
 	my $client = shift;
 	$client->pluginData('artwork_md5', '');
 	$client->SUPER::reconnect(@_);
-}	
+}
 
 1;

+ 33 - 36
plugin/SqueezeESP32/PlayerSettings.pm

@@ -30,63 +30,60 @@ sub page {
 
 sub prefs {
 	my ($class, $client) = @_;
-	my @prefs = qw(width small_VU spectrum artwork eq);
+	my @prefs;
+	push @prefs, qw(width small_VU) if $client->displayWidth;
 	return ($prefs->client($client), @prefs);
 }
 
 sub handler {
 	my ($class, $client, $paramRef) = @_;
-	
+
 	my ($cprefs, @prefs) = $class->prefs($client);
-	
+
 	if ($paramRef->{'saveSettings'}) {
 		if ($client->displayWidth) {
 			$cprefs->set('small_VU', $paramRef->{'pref_small_VU'} || 15);
-			my $spectrum =	{	scale => $paramRef->{'pref_spectrum_scale'} || 25,
-								small => { 	size => $paramRef->{'pref_spectrum_small_size'} || 25, 
-											band => $paramRef->{'pref_spectrum_small_band'} || 5.33 },
-								full  => { 	band => $paramRef->{'pref_spectrum_full_band'} } || 8,
-					};
+			my $spectrum = {
+				scale => $paramRef->{'pref_spectrum_scale'} || 25,
+				small => { 	size => $paramRef->{'pref_spectrum_small_size'} || 25,
+				band  => $paramRef->{'pref_spectrum_small_band'} || 5.33 },
+				full  => { 	band => $paramRef->{'pref_spectrum_full_band'} } || 8,
+			};
 			$cprefs->set('spectrum', $spectrum);
-			
-			my $artwork =	{	enable => $paramRef->{'pref_artwork_enable'},
-								x => $paramRef->{'pref_artwork_x'} || 0, 
-								y => $paramRef->{'pref_artwork_y'} || 0,
-					};
-			$cprefs->set('artwork', $artwork);				
+
+			my $artwork = {
+				enable => $paramRef->{'pref_artwork_enable'},
+				x => $paramRef->{'pref_artwork_x'} || 0,
+				y => $paramRef->{'pref_artwork_y'} || 0,
+			};
+			$cprefs->set('artwork', $artwork);
 			$client->display->modes($client->display->build_modes);
 			$client->display->update;
-		
+
 			# force update or disable artwork
 			if ($artwork->{'enable'}) {
-				Plugins::SqueezeESP32::Plugin::update_artwork($client, 1);
+				$client->update_artwork(1);
 			} else {
-				Plugins::SqueezeESP32::Plugin::config_artwork($client);
-			}	
-		}	
-		
-		my $eq = $cprefs->get('eq');
-		for my $i (0 .. $#{$eq}) {
-			$eq->[$i] = $paramRef->{"pref_eq.$i"};
+				$client->config_artwork();
+			}
 		}
-		$cprefs->set('eq', $eq);
-		Plugins::SqueezeESP32::Plugin::send_equalizer($client);		
+
+		my $equalizer = $cprefs->get('equalizer');
+		for my $i (0 .. $#{$equalizer}) {
+			$equalizer->[$i] = $paramRef->{"pref_equalizer.$i"} || 0;
+		}
+		$cprefs->set('equalizer', $equalizer);
 	}
-	
+
 	if ($client->displayWidth) {
-		# as there is nothing captured, we need to re-set these variables
-		$paramRef->{'pref_width'} = $cprefs->get('width'); 
-	
-		# here I don't know why you need to set again spectrum which is a reference
-		# to a hash. Using $paramRef->{prefs} does not work either. It seems that 
-		# some are copies of value, some are references, can't figure out. This whole
-		# logic of "Settings" is beyond me and I really hate it
+		# the Settings super class can't handle anything but scalar values
+		# we need to populate the $paramRef for the other prefs manually
 		$paramRef->{'pref_spectrum'} = $cprefs->get('spectrum');
 		$paramRef->{'pref_artwork'} = $cprefs->get('artwork');
 	}
-	
-	$paramRef->{'pref_eq'} = $cprefs->get('eq');
-	
+
+	$paramRef->{'pref_equalizer'} = $cprefs->get('equalizer');
+
 	return $class->SUPER::handler($client, $paramRef);
 }
 

+ 69 - 84
plugin/SqueezeESP32/Plugin.pm

@@ -4,8 +4,6 @@ use strict;
 
 use base qw(Slim::Plugin::Base);
 
-use Digest::MD5 qw(md5);
-use List::Util qw(min);
 use Slim::Utils::Prefs;
 use Slim::Utils::Log;
 use Slim::Web::ImageProxy;
@@ -16,121 +14,108 @@ my $log = Slim::Utils::Log->addLogCategory({
 	'category'     => 'plugin.squeezeesp32',
 	'defaultLevel' => 'INFO',
 	'description'  => Slim::Utils::Strings::string('SqueezeESP32'),
-}); 
+});
+
+# migrate 'eq' pref, as that's a reserved word and could cause problems in the future
+$prefs->migrateClient(1, sub {
+	my ($cprefs, $client) = @_;
+	$cprefs->set('equalizer', $cprefs->get('eq'));
+	$cprefs->remove('eq');
+	1;
+});
+
+$prefs->setChange(sub {
+	send_equalizer($_[2]);
+}, 'equalizer');
 
 sub initPlugin {
 	my $class = shift;
-	
+
 	if ( main::WEBUI ) {
 		require Plugins::SqueezeESP32::PlayerSettings;
 		Plugins::SqueezeESP32::PlayerSettings->new;
-		
+
 		# require Plugins::SqueezeESP32::Settings;
 		# Plugins::SqueezeESP32::Settings->new;
 	}
-	
+
 	$class->SUPER::initPlugin(@_);
 	Slim::Networking::Slimproto::addPlayerClass($class, 100, 'squeezeesp32', { client => 'Plugins::SqueezeESP32::Player', display => 'Plugins::SqueezeESP32::Graphics' });
-	$log->info("Added class 100 for SqueezeESP32");
-	
+	main::INFOLOG && $log->is_info && $log->info("Added class 100 for SqueezeESP32");
+
+	# register a command to set the EQ - without saving the values! Send params as single comma separated list of values
+	Slim::Control::Request::addDispatch(['squeezeesp32', 'seteq', '_eq'], [1, 0, 0, \&setEQ]);
+
+	# Note for some forgetful know-it-all: we need to wrap the callback to make it unique. Otherwise subscriptions would overwrite each other.
 	Slim::Control::Request::subscribe( sub { onNotification(@_) }, [ ['newmetadata'] ] );
 	Slim::Control::Request::subscribe( sub { onNotification(@_) }, [ ['playlist'], ['open', 'newsong'] ]);
 	Slim::Control::Request::subscribe( \&onStopClear, [ ['playlist'], ['stop', 'clear'] ]);
+
+	# the custom player class is only initialized if it has a display - thus we need to listen to connect events in order to initializes other player prefs
 	Slim::Control::Request::subscribe( \&onPlayer,[ ['client'], [ 'new', 'reconnect' ] ] );
 }
 
 sub onStopClear {
-    my $request = shift;
-    my $client  = $request->client;
-	my $artwork = $prefs->client($client)->get('artwork');
-	
-	if ($client->model eq 'squeezeesp32' && $artwork->{'enable'}) {
-		my $reqstr = $request->getRequestString();
-		$log->info("artwork stop/clear $reqstr");
-		$client->pluginData('artwork_md5', '')
-	}	
+	my $request = shift;
+	my $client  = $request->client || return;
+
+	if ($client->isa('Plugins::SqueezeESP32::Player')) {
+		$client->clear_artwork($request);
+	}
 }
 
 sub onPlayer {
-    my $request = shift;
-    my $client  = $request->client;
-
-    if ($client->model eq 'squeezeesp32') {
-		$prefs->client($client)->init( { 
-					eq => [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
-				} );
-		Plugins::SqueezeESP32::Plugin::send_equalizer($client);
+	my $request = shift;
+	my $client  = $request->client || return;
+
+	if ($client->model eq 'squeezeesp32') {
+		main::INFOLOG && $log->is_info && $log->info("SqueezeESP player connected: " . $client->id);
+
+		$prefs->client($client)->init( {
+			equalizer => [(0) x 10],
+		} );
+		send_equalizer($client);
 	}
 }
 
 sub onNotification {
-    my $request = shift;
-    my $client  = $request->client;
-	
-	my $reqstr     = $request->getRequestString();
+	my $request = shift;
+	my $client  = $request->client || return;
 
-	update_artwork($client);
+	if ($client->isa('Plugins::SqueezeESP32::Player')) {
+		$client->update_artwork();
+	}
 }
 
-sub update_artwork {
-    my $client  = shift;
-	my $params = { force => shift || 0 };
-	my $cprefs = $prefs->client($client);
-	my $artwork = $cprefs->get('artwork');
-		
-	return unless $client->model eq 'squeezeesp32' && $artwork->{'enable'};
-
-	my $s = min($cprefs->get('height') - $artwork->{'y'}, $cprefs->get('width') - $artwork->{'x'});
-	
-	my $path = 'music/current/cover_' . $s . 'x' . $s . '_o.jpg';
-	my $body = Slim::Web::Graphics::artworkRequest($client, $path, $params, \&send_artwork, undef, HTTP::Response->new);
-	
-	send_artwork($client, undef, \$body) if $body;
-}
+sub setEQ {
+	my $request = shift;
 
-sub send_artwork {
-	my ($client, $params, $dataref) = @_;
-	
-	# I'm not sure why we are called so often, so only send when needed
-	my $md5 = md5($$dataref);
-	return if $client->pluginData('artwork_md5') eq $md5 && !$params->{'force'};
-	
-	$client->pluginData('artwork', $dataref);
-	$client->pluginData('artwork_md5', $md5);
-	
-	my $artwork = $prefs->client($client)->get('artwork');
-	my $length = length $$dataref;
-	my $offset = 0;
-	
-	$log->info("got resized artwork (length: ", length $$dataref, ")");
-	
-	my $header = pack('Nnn', $length, $artwork->{'x'}, $artwork->{'y'});
-	
-	while ($length > 0) {
-		$length = 1280 if $length > 1280;
-		$log->info("sending grfa $length");
-			
-		my $data = $header . pack('N', $offset) . substr( $$dataref, 0, $length, '' );
-			
-		$client->sendFrame( grfa => \$data );
-		$offset += $length;			
-		$length = length $$dataref;
+	# check this is the correct command.
+	if ($request->isNotCommand([['squeezeesp32'],['seteq']])) {
+		$request->setStatusBadDispatch();
+		return;
 	}
-}	
 
-sub send_equalizer {
-	my ($client) = @_;
-	my $equalizer = $prefs->client($client)->get('eq');
-	my $size = @$equalizer;
-	my $data = pack("c[$size]", @{$equalizer});
-	$client->sendFrame( eqlz => \$data );
+	# get our parameters
+	my $client   = $request->client();
+	my @eqParams = split(/,/, $request->getParam('_eq') || '');
+
+	for (my $x = 0; $x < 10; $x++) {
+		$eqParams[$x] ||= 0;
+	}
+
+	send_equalizer($client, \@eqParams);
 }
 
-sub config_artwork {
-	my ($client) = @_;
-	my $artwork = $prefs->client($client)->get('artwork');
-	my $header = pack('Nnn', $artwork->{'enable'}, $artwork->{'x'}, $artwork->{'y'});
-	$client->sendFrame( grfa => \$header );
+sub send_equalizer {
+	my ($client, $equalizer) = @_;
+
+	if ($client->model eq 'squeezeesp32') {
+		$equalizer ||= $prefs->client($client)->get('equalizer') || [(0) x 10];
+		my $size = @$equalizer;
+		my $data = pack("c[$size]", @{$equalizer});
+		$client->sendFrame( eqlz => \$data );
+	}
 }
 
 1;

+ 1 - 1
plugin/SqueezeESP32/install.xml

@@ -10,6 +10,6 @@
   <name>PLUGIN_SQUEEZEESP32</name>
   <description>PLUGIN_SQUEEZEESP32_DESC</description>
   <module>Plugins::SqueezeESP32::Plugin</module>
-    <version>0.83</version>
+    <version>0.92</version>
   <creator>Philippe</creator>
 </extensions>

+ 48 - 21
plugin/SqueezeESP32/strings.txt

@@ -1,75 +1,102 @@
 WELCOME_TO_SQUEEZEESP32
+	DE	Willkommen bei SqueezeESP32!
 	EN	Welcome to SqueezeESP32
-	
+
 PLUGIN_SQUEEZEESP32
 	EN	SqueezeESP32
-	
+
 PLUGIN_SQUEEZEESP32_BANNER
+	DE	WARNUNG
 	EN	WARNING
-	
+
 PLUGIN_SQUEEZEESP32_BANNER_TEXT
+	DE	Sie müssen LMS neu starten, damit diese Einstellungen aktiv werden
 	EN	You need to restart LMS for these parameters to be taken into account
-	
+
 PLUGIN_SQUEEZEESP32_DESC
+	DE	Konfiguriert eine neue Player ID (100), um Displays an SqueezeESP32 zu unterstützen
 	EN	Adds a new player id (100) to enable display with SqueezeESP32
-	
+
 PLUGIN_SQUEEZEESP32_PLAYERSETTINGS
+	DE	ESP32 Einstellungen
 	EN	ESP32 settings
-	
+
 PLUGIN_SQUEEZEESP32_WIDTH
+	DE	Displaybreite
 	EN	Screen width
 
 PLUGIN_SQUEEZEESP32_WIDTH_DESC
+	DE	Breite des Displays in Pixeln, wie es vom Player angegeben wird
 	EN	Width of the display in pixel as reported by the player
-	
+
 PLUGIN_SQUEEZEESP32_SMALL_VU
+	DE	Kleine VU Grösse
 	EN	Small VU size
-	
+
 PLUGIN_SQUEEZEESP32_SMALL_VU_DESC
+	DE	Prozentsatz des Displays, das für den kleinen VU verwendet wird (rechts ausgerichtet)
 	EN	% of the display used for small VU (right-justified)
-	
+
 PLUGIN_SQUEEZEESP32_SPECTRUM_SCALE
+	DE	Spektrum-Skalierung
 	EN	Spectrum scaling
-	
+
 PLUGIN_SQUEEZEESP32_SPECTRUM_SCALE_DESC
+	DE	Prozentsatz des Spektrums, das in der ersten Hälfte des Bildschirms angezeigt wird. Z.B. 50 bedeutet 50% des Spektrums wird auf dem halben Bildschirm angezeigt.
+	DE	Aber 25 bedeutet, dass nur 25% des Spektrums auf dem halben Bildschirm angezeigt wird.
 	EN	% of Spectrum displayed in first half of the screen. For example, 50 means that 50% of spectrum is displayed in 1/2 of the screen
 	EN	But 25 means that only 25% of spectrum is displayed in 1/2 of the screen, so it's a sort of log
-	
+
 PLUGIN_SQUEEZEESP32_SMALL_SPECTRUM
+	DE	Kleines Spektrum
 	EN	Small spectrum options
-	
+
 PLUGIN_SQUEEZEESP32_SMALL_SPECTRUM_DESC
+	DE	<i>Grösse</i>: Prozentsatz des Displays, das für das kleine Spektrum verwendet wird.
+	DE	<br><i>Band-Faktor</i>: die Anzahl Bänder ist die Breite der <b>Spektrumsanzeige</b> dividiert durch diesen Faktor.
 	EN	<i>Size</i>: % of the screen used by small spectrum
 	EN	<br><i>Band factor</i>: number of bands is the width of the <b>spectrum</b> screen  divided by this factor
-	
+
 PLUGIN_SQUEEZEESP32_SMALL_SPECTRUM_SIZE
+	DE	Grösse
 	EN	Size
-	
+
 PLUGIN_SQUEEZEESP32_SMALL_SPECTRUM_BAND
+	DE	Band-Faktor
 	EN	Band factor
-	
+
 PLUGIN_SQUEEZEESP32_FULL_SPECTRUM_BAND
+	DE	Band-Faktor für ganzes Spektrum
 	EN	Full spectrum band factor
-		
+
 PLUGIN_SQUEEZEESP32_FULL_SPECTRUM_BAND_DESC
+	DE	Die Anzahl Bänder ist die Breite der Anzeige dividiert durch diesen Faktor.
 	EN	The number of bands is the width of the screen divided by this factor
-	
+
 PLUGIN_SQUEEZEESP32_ARTWORK
+	DE	Plattenhüllen
 	EN	Artwork
-	
+
 PLUGIN_SQUEEZEESP32_ARTWORK_DESC
-	EN	When Y position is less than 32, then artwork is display at the right of the main screen and x defines the starting position
+	DE	Wenn die Y Position kleiner als 32 ist, dann werden Plattenhüllen auf der rechten Seite angezeigt, und x definiert die Startposition.
+	DE	Plattenhüllen werden auf Displays mit weniger als 16 Graustufen in sehr geringer Qualität angezeigt.
+	EN	When Y position is less than 32, then artwork is displayed at the right of the main screen and x defines the starting position
 	EN	Using artwork on less than 16-levels grayscale display if really poor quality
 
 PLUGIN_SQUEEZEESP32_ARTWORK_ENABLE
+	DE	Aktivieren
 	EN	Enable
-	
+
 PLUGIN_SQUEEZEESP32_ARTWORK_X
 	EN	X
 
 PLUGIN_SQUEEZEESP32_ARTWORK_Y
 	EN	Y
-	
+
 PLUGIN_SQUEEZEESP32_EQUALIZER
+	DE	Parametrischer Equalizer
 	EN	Parametric equalizer
 
+PLUGIN_SQUEEZEESP32_EQUALIZER_SAVE
+	DE	Bitte speichern Sie die Equalizer Einstellungen, falls das Gerät diese dauerhaft verwenden soll. Ansonsten werden sie beim nächsten Start zurückgesetzt.
+	EN	Don't forget to save the Equalizer settings if you want them to stick. Otherwise they'll be reset next time you restart the device.

+ 2 - 2
plugin/repo.xml

@@ -1,10 +1,10 @@
 <?xml version='1.0' standalone='yes'?>
 <extensions>
   <plugins>
-    <plugin version="0.83" name="SqueezeESP32" minTarget="7.5" maxTarget="*">
+    <plugin version="0.92" name="SqueezeESP32" minTarget="7.5" maxTarget="*">
       <link>https://github.com/sle118/squeezelite-esp32</link>
       <creator>Philippe</creator>
-      <sha>3bbf5a40ff368472c0de6d43fa61134f8f34558e</sha>
+      <sha>274c9278af12e79edb8c3273f19f3fc7a9703795</sha>
       <email>philippe_44@outlook.com</email>
       <desc lang="EN">SqueezeESP32 additional player id (100)</desc>
       <url>http://github.com/sle118/squeezelite-esp32/raw/master/plugin/SqueezeESP32.zip</url>