Browse Source

Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into fix_led_vu

Wizmo2 1 year ago
parent
commit
fd0c38c49f

+ 26 - 2
README.md

@@ -389,13 +389,14 @@ Where (all parameters are optionals except gpio)
 Where `<action>` is either the name of another configuration to load (remap) or one amongst
 
 ```
-ACTRLS_NONE, ACTRLS_POWER, ACTRLS_VOLUP, ACTRLS_VOLDOWN, ACTRLS_TOGGLE, ACTRLS_PLAY, 
+ACTRLS_NONE, ACTRLS_SLEEP, ACTRLS_POWER, ACTRLS_VOLUP, ACTRLS_VOLDOWN, ACTRLS_TOGGLE, ACTRLS_PLAY, 
 ACTRLS_PAUSE, ACTRLS_STOP, ACTRLS_REW, ACTRLS_FWD, ACTRLS_PREV, ACTRLS_NEXT, 
 BCTRLS_UP, BCTRLS_DOWN, BCTRLS_LEFT, BCTRLS_RIGHT, 
 BCTRLS_PS1, BCTRLS_PS2, BCTRLS_PS3, BCTRLS_PS4, BCTRLS_PS5, BCTRLS_PS6, BCTRLS_PS7, BCTRLS_PS8, BCTRLS_PS9, BCTRLS_PS10,
 KNOB_LEFT, KNOB_RIGHT, KNOB_PUSH,	
 ```
-				
+Note that ACTRLS_SLEEP is not an actual button that can be sent to LMS, but it's a hook to activate deep sleep mode (see [Sleeping](#sleeping)).
+
 One you've created such a string, use it to fill a new NVS parameter with any name below 16(?) characters. You can have as many of these configs as you can. Then set the config parameter "actrls_config" with the name of your default config
 
 For example a config named "buttons" :
@@ -504,6 +505,29 @@ channel=0..7,scale=<scale>,cells=<1..3>[,atten=<0|1|2|3>]
 ```
 NB: Set parameter to empty to disable battery reading. For named configurations (SqueezeAMP, Muse ...), this is ignored (except for SqueezeAMP where number of cells is required)
 
+### Sleeping
+The esp32 can be put in deep sleep mode to save some power. How much really depends on the connected periperals, so best is to do your own measures. Waking-up from deep sleep is the equivalent of a reboot, but as the chip takes a few seconds to connect, it's still an efficient process.
+
+The esp32 can enter deep sleep after an audio inactivity timeout, after a button has been pressed or after a GPIO is set to a given level. It wakes up only on GPIO events 
+
+The NVS parameter `sleep_config` is mostly used for setting sleep conditions
+```
+[delay=<mins>][,sleep=<gpio>[:0|1]][,wake=<gpio>[:0|1][|<gpio>[:0|1]...]
+```
+- delay is in **minutes**
+- sleep is the GPIO that will put the system into sleep and it can be a level 0 or 1
+- wake is a **list** of GPIOs that with cause it to wake up (reboot) with their respective values. In such list, GPIO's are separated by an actual '|'
+
+Be mindful that if the same GPIO is used to go to sleep and wakeup with the *same* level, in other word it's a transition/edge that triggers the action, the above will not work and the esp32 will immediately restart. In such case, you case use a button definition. The benefit of buttons is that not only can you re-use one actual button (e.g. 'stop') to make it the sleep trigger (using a long-press or a shift-press) but by selecting the ACTRLS_SLEEP action upon 'release', you can got to sleep upon release (1-0-1 transition) but also wake up upon another press (0 level applied on GPIO) because you only go to sleep *after* the GPIO returned to 1.
+
+Please see [buttons](#buttons) for detailed syntax.
+
+The option to use multiple GPIOs is very limited on esp32 and the esp-idf 4.3.x we are using: it is only possible to wake-up when **any** of the defined GPIO is set to 1. The fact that you can specify different levels in the wake list is irrelevant for now, it's just a provision for future upgrades to more recent versions of esp-idf.
+
+**Note that not all GPIOs can be used to wake-up the esp32**
+- ESP32: 0, 2, 4, 12-15, 25-27, 32-39;
+- ESP32-S3: 0-21.
+
 # Configuration
 
 ## Setup WiFi

+ 13 - 0
components/display/display.c

@@ -15,6 +15,7 @@
 #include "platform_config.h"
 #include "tools.h"
 #include "display.h"
+#include "services.h"
 #include "gds.h"
 #include "gds_default_if.h"
 #include "gds_draw.h"
@@ -73,7 +74,9 @@ static const char *known_drivers[] = {"SH1106",
 		"ILI9341",
 		NULL
 	};
+    
 static void displayer_task(void *args);
+static void display_sleep(void);
 
 struct GDS_Device *display;   
 extern GDS_DetectFunc SSD1306_Detect, SSD132x_Detect, SH1106_Detect, SSD1675_Detect, SSD1322_Detect, SSD1351_Detect, ST77xx_Detect, ILI9341_Detect;
@@ -174,11 +177,21 @@ void display_init(char *welcome) {
 			if (height <= 64 && width > height * 2) displayer.artwork.offset = width - height - ARTWORK_BORDER;
 			PARSE_PARAM(displayer.metadata_config, "artwork", ':', displayer.artwork.fit);
 		}	
+        
+        // and finally register ourselves to power off upon deep sleep
+        services_sleep_sethook(display_sleep);
 	}
 	
 	free(config);
 }
 
+/****************************************************************************************
+ * 
+ */
+static void display_sleep(void) {
+    GDS_DisplayOff(display);
+}
+
 /****************************************************************************************
  * This is not thread-safe as displayer_task might be in the middle of line drawing
  * but it won't crash (I think) and making it thread-safe would be complicated for a

+ 1 - 1
components/led_strip/led_vu.c

@@ -100,7 +100,7 @@ void led_vu_init()
     led_strip_config.led_strip_showing = heap_caps_malloc(strip.length * sizeof(struct led_color_t), MALLOC_CAP_8BIT);
     led_strip_config.gpio = strip.gpio;
     led_strip_config.rmt_channel = rmt_system_base_channel++;
-    
+
     // initialize driver 
     bool led_init_ok = led_strip_init(&led_strip_config);
     if (led_init_ok) {

+ 14 - 10
components/services/audio_controls.c

@@ -19,6 +19,7 @@
 #include "buttons.h"
 #include "platform_config.h"
 #include "accessors.h"
+#include "services.h"
 #include "audio_controls.h"
 
 typedef esp_err_t (actrls_config_map_handler) (const cJSON * member, actrls_config_t *cur_config,uint32_t offset);
@@ -57,7 +58,7 @@ static const actrls_config_map_t actrls_config_map[] =
 // BEWARE: the actions below need to stay aligned with the corresponding enum to properly support json parsing
 //   along with the actrls_t controls in LMS_controls, bt_sink and raop_sink
 #define EP(x) [x] = #x  /* ENUM PRINT */
-static const char * actrls_action_s[ ] = { EP(ACTRLS_POWER),EP(ACTRLS_VOLUP),EP(ACTRLS_VOLDOWN),EP(ACTRLS_TOGGLE),EP(ACTRLS_PLAY),
+static const char * actrls_action_s[ ] = { EP(ACTRLS_SLEEP),EP(ACTRLS_POWER),EP(ACTRLS_VOLUP),EP(ACTRLS_VOLDOWN),EP(ACTRLS_TOGGLE),EP(ACTRLS_PLAY),
 									EP(ACTRLS_PAUSE),EP(ACTRLS_STOP),EP(ACTRLS_REW),EP(ACTRLS_FWD),EP(ACTRLS_PREV),EP(ACTRLS_NEXT),
 									EP(BCTRLS_UP),EP(BCTRLS_DOWN),EP(BCTRLS_LEFT),EP(BCTRLS_RIGHT), 
 									EP(BCTRLS_PS0),EP(BCTRLS_PS1),EP(BCTRLS_PS2),EP(BCTRLS_PS3),EP(BCTRLS_PS4),EP(BCTRLS_PS5),EP(BCTRLS_PS6),EP(BCTRLS_PS7),EP(BCTRLS_PS8),EP(BCTRLS_PS9),
@@ -170,13 +171,6 @@ 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];
@@ -195,7 +189,14 @@ static void control_handler(void *client, button_event_e event, button_press_e p
 
 	// stop here if control hook served the request
 	if (current_hook && (*current_hook)(key->gpio, action_detail.action, event, press, long_press)) return;
-	
+    
+   	// in raw mode, we just do normal action press *and* release, there is no longpress nor shift
+	if (current_raw_controls && action_detail.action != ACTRLS_SLEEP) {
+		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;
+	}
+
 	// otherwise process using configuration
 	if (action_detail.action == ACTRLS_REMAP) {
 		// remap requested
@@ -216,7 +217,10 @@ static void control_handler(void *client, button_event_e event, button_press_e p
 		} else {
 			ESP_LOGE(TAG,"Invalid profile name %s. Cannot remap buttons",action_detail.name);
 		}	
-	} else if (action_detail.action != ACTRLS_NONE) {
+	} else if (action_detail.action == ACTRLS_SLEEP) {
+        ESP_LOGI(TAG, "Sleep button pressed");
+        services_sleep_activate(SLEEP_ONKEY);
+    } 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])(event == BUTTON_PRESSED);
 	}	

+ 1 - 1
components/services/audio_controls.h

@@ -11,7 +11,7 @@
 #include "buttons.h"
 
 // BEWARE: this is the index of the array of action below (change actrls_action_s as well!)
-typedef enum { 	ACTRLS_NONE = -1, ACTRLS_POWER,ACTRLS_VOLUP, ACTRLS_VOLDOWN, ACTRLS_TOGGLE, ACTRLS_PLAY, 
+typedef enum { 	ACTRLS_NONE = -1, ACTRLS_SLEEP, ACTRLS_POWER, ACTRLS_VOLUP, ACTRLS_VOLDOWN, ACTRLS_TOGGLE, ACTRLS_PLAY, 
 				ACTRLS_PAUSE, ACTRLS_STOP, ACTRLS_REW, ACTRLS_FWD, ACTRLS_PREV, ACTRLS_NEXT, 
 				BCTRLS_UP, BCTRLS_DOWN, BCTRLS_LEFT, BCTRLS_RIGHT, 
 				BCTRLS_PS0,BCTRLS_PS1,BCTRLS_PS2,BCTRLS_PS3,BCTRLS_PS4,BCTRLS_PS5,BCTRLS_PS6,BCTRLS_PS7,BCTRLS_PS8,BCTRLS_PS9,

+ 127 - 4
components/services/services.c

@@ -7,7 +7,11 @@
 */
 
 #include <stdio.h>
+#include "freertos/FreeRTOS.h"
+#include "freertos/timers.h"
 #include "esp_log.h"
+#include "esp_sleep.h"
+#include "driver/rtc_io.h"
 #include "driver/gpio.h"
 #include "driver/ledc.h"
 #include "driver/i2c.h"
@@ -20,6 +24,8 @@
 #include "globdefs.h"
 #include "accessors.h"
 #include "messaging.h"
+#include "buttons.h"
+#include "services.h"
 
 extern void battery_svc_init(void);
 extern void monitor_svc_init(void);
@@ -30,11 +36,19 @@ int i2c_system_speed = 400000;
 int spi_system_host = SPI_SYSTEM_HOST;
 int spi_system_dc_gpio = -1;
 int rmt_system_base_channel = RMT_CHANNEL_0;
+
 pwm_system_t pwm_system = { 
 		.timer = LEDC_TIMER_0,
 		.base_channel = LEDC_CHANNEL_0,
 		.max = (1 << LEDC_TIMER_13_BIT),
-	};		
+};		
+    
+static EXT_RAM_ATTR struct {
+    uint64_t wake_gpio, wake_level;
+    uint32_t delay;
+} sleep_config;
+    
+static EXT_RAM_ATTR void (*sleep_hooks[16])(void);    
 
 static const char *TAG = "services";
 
@@ -60,6 +74,9 @@ void set_chip_power_gpio(int gpio, char *value) {
 	if (parsed) ESP_LOGI(TAG, "set GPIO %u to %s", gpio, value);
 }	
 
+/****************************************************************************************
+ * 
+ */
 void set_exp_power_gpio(int gpio, char *value) {
 	bool parsed = true;
 
@@ -75,8 +92,113 @@ void set_exp_power_gpio(int gpio, char *value) {
 	} else parsed = false;
 	
 	if (parsed) ESP_LOGI(TAG, "set expanded GPIO %u to %s", gpio, value);
- }	
- 
+}
+
+/****************************************************************************************
+ * 
+ */
+static void sleep_gpio_handler(void *id, button_event_e event, button_press_e mode, bool long_press) {
+    if (event == BUTTON_PRESSED) services_sleep_activate(SLEEP_ONGPIO);
+}  
+
+/****************************************************************************************
+ * 
+ */
+static void sleep_init(void) {
+    char *config = config_alloc_get(NVS_TYPE_STR, "sleep_config");
+    char *p;
+    
+    // do we want delay sleep
+    PARSE_PARAM(config, "delay", '=', sleep_config.delay);
+    sleep_config.delay *= 60*1000;
+    if (sleep_config.delay) {
+        ESP_LOGI(TAG, "Sleep inactivity of %d minute(s)", sleep_config.delay / (60*1000));
+    }
+           
+    // get the wake criteria
+    if ((p = strcasestr(config, "wake"))) {
+        char list[32] = "", item[8];
+		sscanf(p, "%*[^=]=%31[^,]", list);
+        p = list - 1;
+        while (p++ && sscanf(p, "%7[^|]", item)) {
+            int level = 0, gpio = atoi(item);
+            if (!rtc_gpio_is_valid_gpio(gpio)) {
+                ESP_LOGE(TAG, "invalid wake GPIO %d (not in RTC domain)", gpio);
+            } else {
+                sleep_config.wake_gpio |= 1LL << gpio;
+            }
+            if (sscanf(item, "%*[^:]:%d", &level)) sleep_config.wake_level |= level << gpio;
+            p = strchr(p, '|');
+        }
+        
+        // when moving to esp-idf more recent than 4.4.x, multiple gpio wake-up with level specific can be done
+        if (sleep_config.wake_gpio) {
+            ESP_LOGI(TAG, "Sleep wake-up gpio bitmap 0x%llx (active 0x%llx)", sleep_config.wake_gpio, sleep_config.wake_level);    
+        }
+    }
+          
+    // then get the gpio that activate sleep (we could check that we have a valid wake)
+    if ((p = strcasestr(config, "sleep"))) {
+        int gpio, level = 0;
+		char sleep[8] = "";
+		sscanf(p, "%*[^=]=%7[^,]", sleep);
+		gpio = atoi(sleep);
+        if ((p = strchr(sleep, ':')) != NULL) level = atoi(p + 1);
+        ESP_LOGI(TAG, "Sleep activation gpio %d (active %d)", gpio, level);        
+        button_create(NULL, gpio, level ? BUTTON_HIGH : BUTTON_LOW, true, 0, sleep_gpio_handler, 0, -1);
+    }
+}
+
+/****************************************************************************************
+ * 
+ */
+void services_sleep_callback(uint32_t elapsed) {
+    if (sleep_config.delay && elapsed >= sleep_config.delay) {
+        services_sleep_activate(SLEEP_ONTIMER);
+    }
+}    
+
+/****************************************************************************************
+ * 
+ */
+void services_sleep_activate(sleep_cause_e cause) {   
+    // call all sleep hooks that might want to do something
+    for (void (**hook)(void) = sleep_hooks; *hook; hook++) (*hook)();
+           
+    // isolate all possible GPIOs, except the wake-up ones
+    esp_sleep_config_gpio_isolate();
+    for (int i = 0; i < GPIO_NUM_MAX; i++) {
+        if (!rtc_gpio_is_valid_gpio(i) || ((1LL << i) & sleep_config.wake_gpio)) continue;
+        rtc_gpio_isolate(i);
+    }
+    
+    // is there just one GPIO
+    if (sleep_config.wake_gpio & (sleep_config.wake_gpio - 1)) {
+        ESP_LOGI(TAG, "going to sleep cause %d, wake-up on multiple GPIO, any '1' wakes up 0x%llx", cause, sleep_config.wake_gpio);
+        esp_sleep_enable_ext1_wakeup(sleep_config.wake_gpio, ESP_EXT1_WAKEUP_ANY_HIGH);
+    } else {
+        int gpio = __builtin_ctz(sleep_config.wake_gpio);
+        int level = (sleep_config.wake_level >> gpio) & 0x01;
+        ESP_LOGI(TAG, "going to sleep cause %d, wake-up on GPIO %d level %d", cause, gpio, level);
+        esp_sleep_enable_ext0_wakeup(gpio, level);
+    }
+
+    // we need to use a timer in case the same button is used for sleep and wake-up and it's "pressed" vs "released" selected
+    if (cause == SLEEP_ONKEY) xTimerStart(xTimerCreate("sleepTimer", pdMS_TO_TICKS(1000), pdFALSE, NULL, (void (*)(void*)) esp_deep_sleep_start), 0);		
+    else esp_deep_sleep_start();
+}
+
+/****************************************************************************************
+ * 
+ */
+void services_sleep_sethook(void (*hook)(void)) {
+    for (int i = 0; i < sizeof(sleep_hooks)/sizeof(void(*)(void)); i++) {
+        if (!sleep_hooks[i]) {
+            sleep_hooks[i] = hook;
+            return;
+        }
+    }
+}
 
 /****************************************************************************************
  * 
@@ -147,5 +269,6 @@ void services_init(void) {
 
 	led_svc_init();
 	battery_svc_init();
-	monitor_svc_init();
+	monitor_svc_init(); 
+    sleep_init();
 }

+ 16 - 0
components/services/services.h

@@ -0,0 +1,16 @@
+/* 
+ *  Squeezelite for esp32
+ *
+ *  (c) Philippe G. 2019, philippe_44@outlook.com
+ *
+ *  This software is released under the MIT License.
+ *  https://opensource.org/licenses/MIT
+ *
+ */
+ 
+#pragma once
+
+typedef enum { SLEEP_ONTIMER, SLEEP_ONKEY, SLEEP_ONGPIO, SLEEP_ONIR } sleep_cause_e;
+void services_sleep_activate(sleep_cause_e cause);
+void services_sleep_sethook(void (*hook)(void));
+void services_sleep_callback(uint32_t elapsed);

+ 23 - 14
components/squeezelite/output_i2s.c

@@ -41,6 +41,7 @@ sure that using rate_delay would fix that
 #include "adac.h"
 #include "time.h"
 #include "led.h"
+#include "services.h"
 #include "monitor.h"
 #include "platform_config.h"
 #include "gpio_exp.h"
@@ -102,9 +103,11 @@ const struct adac_s *adac = &dac_external;
 
 static log_level loglevel;
 
+static uint32_t stopped_time;        
+static void (*pseudo_idle_chain)(uint32_t);
 static bool (*slimp_handler_chain)(u8_t *data, int len);
 static bool jack_mutes_amp;
-static bool running, isI2SStarted, ended;
+static bool running, isI2SStarted, ended, i2s_stats;
 static i2s_config_t i2s_config;
 static u8_t *obuf;
 static frames_t oframes;
@@ -125,7 +128,8 @@ DECLARE_ALL_MIN_MAX;
 static int _i2s_write_frames(frames_t out_frames, bool silence, s32_t gainL, s32_t gainR, u8_t flags,
 								s32_t cross_gain_in, s32_t cross_gain_out, ISAMPLE_T **cross_ptr);
 static void output_thread_i2s(void *arg);
-static void i2s_stats(uint32_t now);
+static void i2s_idle(uint32_t now);
+
 static void spdif_convert(ISAMPLE_T *src, size_t frames, u32_t *dst, size_t *count);
 static void (*jack_handler_chain)(bool inserted);
 
@@ -411,7 +415,15 @@ void output_init_i2s(log_level level, char *device, unsigned output_buf_size, ch
 	else adac->speaker(true);
 	
 	adac->headset(jack_inserted_svc());	
-
+    
+    // do we want stats
+	p = config_alloc_get_default(NVS_TYPE_STR, "stats", "n", 0);
+	i2s_stats = p && (*p == '1' || *p == 'Y' || *p == 'y');
+    free(p);
+       
+    pseudo_idle_chain = pseudo_idle_svc;
+    pseudo_idle_svc = i2s_idle;
+    
 	// create task as a FreeRTOS task but uses stack in internal RAM
 	{
 		static DRAM_ATTR StaticTask_t xTaskBuffer __attribute__ ((aligned (4)));
@@ -419,14 +431,6 @@ void output_init_i2s(log_level level, char *device, unsigned output_buf_size, ch
 		output_i2s_task = xTaskCreateStaticPinnedToCore( (TaskFunction_t) output_thread_i2s, "output_i2s", OUTPUT_THREAD_STACK_SIZE, 
 											  NULL, CONFIG_ESP32_PTHREAD_TASK_PRIO_DEFAULT + 1, xStack, &xTaskBuffer, 0 );
 	}
-	
-	// do we want stats
-	p = config_alloc_get_default(NVS_TYPE_STR, "stats", "n", 0);
-	if (p && (*p == '1' || *p == 'Y' || *p == 'y')) {
-        pseudo_idle_chain = pseudo_idle_svc;
-        pseudo_idle_svc = i2s_stats;
-	}
-    free(p);
 }
 
 
@@ -493,6 +497,7 @@ static void output_thread_i2s(void *arg) {
 	uint32_t fullness = gettime_ms();
 	bool synced;
 	output_state state = OUTPUT_OFF - 1;
+    stopped_time = pdMS_TO_TICKS(xTaskGetTickCount());
     
 	while (running) {
 			
@@ -508,6 +513,7 @@ static void output_thread_i2s(void *arg) {
 				if (amp_control.gpio != -1) gpio_set_level_x(amp_control.gpio, !amp_control.active);
 				LOG_INFO("switching off amp GPIO %d", amp_control.gpio);
 			} else if (output.state == OUTPUT_STOPPED) {
+                stopped_time = pdMS_TO_TICKS(xTaskGetTickCount());
 				adac->speaker(false);
 				led_blink(LED_GREEN, 200, 1000);
 			} else if (output.state == OUTPUT_RUNNING) {
@@ -632,16 +638,19 @@ static void output_thread_i2s(void *arg) {
 }
 
 /****************************************************************************************
- * Stats output thread
+ * stats output callback
  */
-static void i2s_stats(uint32_t now) {
+static void i2s_idle(uint32_t now) {
     static uint32_t last;
     
     // first chain to next handler
     if (pseudo_idle_chain) pseudo_idle_chain(now);
     
+    // call the sleep mamanger 
+    if (output.state <= OUTPUT_STOPPED) services_sleep_callback(now - stopped_time);
+    
     // then see if we need to act
-    if (output.state <= OUTPUT_STOPPED || now < last + STATS_PERIOD_MS) return;  
+    if (!i2s_stats || output.state <= OUTPUT_STOPPED || now < last + STATS_PERIOD_MS) return;  
     last = now;
     
 	LOG_INFO( "Output State: %d, current sample rate: %d, bytes per frame: %d", output.state, output.current_sample_rate, BYTES_PER_FRAME);

+ 1 - 0
main/esp_app_main.c

@@ -270,6 +270,7 @@ void register_default_nvs(){
 	register_default_string_val( "i2c_config", CONFIG_I2C_CONFIG);
 	register_default_string_val( "spi_config", CONFIG_SPI_CONFIG);
 	register_default_string_val( "set_GPIO", CONFIG_SET_GPIO);
+	register_default_string_val( "sleep_config", "");    
 	register_default_string_val( "led_brightness", "");
 	register_default_string_val( "spdif_config", "");
 	register_default_string_val( "dac_config", "");