From e4481a95f93da6297a13970f9bb0f73e172d06f6 Mon Sep 17 00:00:00 2001 From: philippe44 Date: Thu, 14 Sep 2023 19:21:17 -0700 Subject: [PATCH 1/6] Update README.md --- README.md | 47 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ff43acd3..05e46f77 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,27 @@ [![Platform Build](https://github.com/sle118/squeezelite-esp32/actions/workflows/Platform_build.yml/badge.svg)](https://github.com/sle118/squeezelite-esp32/actions/workflows/Platform_build.yml) # Squeezelite-esp32 +### 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. I wakes up only on GPIO levels. + +The NVS parameter `sleep_config` is mostly used for setting sleep conditions +``` +[delay=][,sleep=[:0|1]][,wake=[:0|1][|[: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) but also wake up upon another press (0 level applied). + +Please see [buttons](###buttons) for detailed syntax. + +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. + ## What is this? Squeezelite-esp32 is an audio software suite made to run on espressif's esp32 and esp32-s3 wifi (b/g/n) and bluetooth chipsets. It offers the following capabilities @@ -389,13 +410,14 @@ Where (all parameters are optionals except gpio) Where `` 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 [Sleepin](###sleepin)). + 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 +526,27 @@ channel=0..7,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. I wakes up only on GPIO levels. + +The NVS parameter `sleep_config` is mostly used for setting sleep conditions +``` +[delay=][,sleep=[:0|1]][,wake=[:0|1][|[: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) but also wake up upon another press (0 level applied). + +Please see [buttons](###buttons) for detailed syntax. + +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 From d8bd588982523b742319ea63434556c683191774 Mon Sep 17 00:00:00 2001 From: philippe44 Date: Thu, 14 Sep 2023 19:21:30 -0700 Subject: [PATCH 2/6] Update README.md --- README.md | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/README.md b/README.md index 05e46f77..8109f43d 100644 --- a/README.md +++ b/README.md @@ -1,27 +1,6 @@ [![Platform Build](https://github.com/sle118/squeezelite-esp32/actions/workflows/Platform_build.yml/badge.svg)](https://github.com/sle118/squeezelite-esp32/actions/workflows/Platform_build.yml) # Squeezelite-esp32 -### 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. I wakes up only on GPIO levels. - -The NVS parameter `sleep_config` is mostly used for setting sleep conditions -``` -[delay=][,sleep=[:0|1]][,wake=[:0|1][|[: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) but also wake up upon another press (0 level applied). - -Please see [buttons](###buttons) for detailed syntax. - -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. - ## What is this? Squeezelite-esp32 is an audio software suite made to run on espressif's esp32 and esp32-s3 wifi (b/g/n) and bluetooth chipsets. It offers the following capabilities From 5ed2f6d03e85a7592981fdfed486493ce8d2e3c3 Mon Sep 17 00:00:00 2001 From: philippe44 Date: Thu, 14 Sep 2023 19:23:56 -0700 Subject: [PATCH 3/6] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8109f43d..226046c9 100644 --- a/README.md +++ b/README.md @@ -395,7 +395,7 @@ 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 [Sleepin](###sleepin)). +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 From 65ff5f7c2a627b0e842767a035b38f0525f2dbde Mon Sep 17 00:00:00 2001 From: philippe44 Date: Thu, 14 Sep 2023 19:26:53 -0700 Subject: [PATCH 4/6] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 226046c9..e150264d 100644 --- a/README.md +++ b/README.md @@ -395,7 +395,7 @@ 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)). +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 @@ -520,7 +520,7 @@ The NVS parameter `sleep_config` is mostly used for setting sleep conditions 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) but also wake up upon another press (0 level applied). -Please see [buttons](###buttons) for detailed syntax. +Please see [buttons](#buttons) for detailed syntax. Note that not all GPIOs can be used to wake-up the esp32 - ESP32: 0, 2, 4, 12-15, 25-27, 32-39; From a2eddb5411069dc4b83852d02dbf08057b1a4696 Mon Sep 17 00:00:00 2001 From: philippe44 Date: Fri, 15 Sep 2023 16:50:20 -0700 Subject: [PATCH 5/6] Update README.md --- README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index e150264d..f4ffbcce 100644 --- a/README.md +++ b/README.md @@ -508,7 +508,7 @@ NB: Set parameter to empty to disable battery reading. For named configurations ### 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. I wakes up only on GPIO levels. +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 ``` @@ -518,11 +518,13 @@ The NVS parameter `sleep_config` is mostly used for setting sleep conditions - 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) but also wake up upon another press (0 level applied). +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. -Note that not all GPIOs can be used to wake-up the esp32 +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. From a83f14113e72f34e50527371ec7ef15bc079aaf7 Mon Sep 17 00:00:00 2001 From: philippe44 Date: Fri, 15 Sep 2023 17:18:06 -0700 Subject: [PATCH 6/6] add sleep option + potentially fix led_vu issue --- components/display/display.c | 13 +++ components/led_strip/led_vu.c | 4 +- components/services/audio_controls.c | 24 +++-- components/services/audio_controls.h | 2 +- components/services/services.c | 131 ++++++++++++++++++++++++++- components/services/services.h | 16 ++++ components/squeezelite/output_i2s.c | 37 +++++--- main/esp_app_main.c | 1 + 8 files changed, 198 insertions(+), 30 deletions(-) create mode 100644 components/services/services.h diff --git a/components/display/display.c b/components/display/display.c index 3a963af4..d91df159 100644 --- a/components/display/display.c +++ b/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 diff --git a/components/led_strip/led_vu.c b/components/led_strip/led_vu.c index a0dd2d38..643ecdaf 100644 --- a/components/led_strip/led_vu.c +++ b/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); diff --git a/components/services/audio_controls.c b/components/services/audio_controls.c index bd3c56ed..60792ccf 100644 --- a/components/services/audio_controls.c +++ b/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); } diff --git a/components/services/audio_controls.h b/components/services/audio_controls.h index fd45784e..137bda7e 100644 --- a/components/services/audio_controls.h +++ b/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, diff --git a/components/services/services.c b/components/services/services.c index b1c1dbc4..6ace648b 100644 --- a/components/services/services.c +++ b/components/services/services.c @@ -7,7 +7,11 @@ */ #include +#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(); } diff --git a/components/services/services.h b/components/services/services.h new file mode 100644 index 00000000..62394143 --- /dev/null +++ b/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); diff --git a/components/squeezelite/output_i2s.c b/components/squeezelite/output_i2s.c index 022c9aaf..d9645ffd 100644 --- a/components/squeezelite/output_i2s.c +++ b/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); diff --git a/main/esp_app_main.c b/main/esp_app_main.c index bb817941..38754428 100644 --- a/main/esp_app_main.c +++ b/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", "");