Browse Source

add sleep option + potentially fix led_vu issue

philippe44 1 year ago
parent
commit
a83f14113e

+ 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

+ 3 - 1
components/led_strip/led_vu.c

@@ -33,6 +33,8 @@ static const char *TAG = "led_vu";
 #define LED_VU_PEAK_HOLD 6U
 
 #define LED_VU_DEFAULT_GPIO 22
+#define LED_VU_RMT_INTR_NUM 20
+
 #define LED_VU_DEFAULT_LENGTH 19
 #define LED_VU_MAX_LENGTH 255
 
@@ -90,7 +92,7 @@ void led_vu_init()
     strip.vu_odd = strip.length - 1;
 
     // create driver configuration
-    struct led_strip_t led_strip_config = { .rgb_led_type = RGB_LED_TYPE_WS2812 };
+    struct led_strip_t led_strip_config = { .rgb_led_type = RGB_LED_TYPE_WS2812, .rmt_interrupt_num = LED_VU_RMT_INTR_NUM };
     led_strip_config.access_semaphore = xSemaphoreCreateBinary();
     led_strip_config.led_strip_length = strip.length;
     led_strip_config.led_strip_working = heap_caps_malloc(strip.length * sizeof(struct led_color_t), MALLOC_CAP_8BIT);

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