diff --git a/components/display/display.c b/components/display/display.c index d91df159e..e5d0747d5 100644 --- a/components/display/display.c +++ b/components/display/display.c @@ -179,7 +179,7 @@ void display_init(char *welcome) { } // and finally register ourselves to power off upon deep sleep - services_sleep_sethook(display_sleep); + services_sleep_setsuspend(display_sleep); } free(config); diff --git a/components/driver_bt/bt_app_source.c b/components/driver_bt/bt_app_source.c index 9c5aad6f7..503e0593c 100644 --- a/components/driver_bt/bt_app_source.c +++ b/components/driver_bt/bt_app_source.c @@ -25,6 +25,8 @@ static const char * TAG = "bt_app_source"; static const char * BT_RC_CT_TAG="RCCT"; extern int32_t output_bt_data(uint8_t *data, int32_t len); extern void output_bt_tick(void); +extern void output_bt_stop(void); +extern void output_bt_start(void); extern char* output_state_str(void); extern bool output_stopped(void); extern bool is_recovery_running; @@ -803,6 +805,7 @@ static void bt_app_av_media_proc(uint16_t event, void *param) if (a2d->media_ctrl_stat.cmd == ESP_A2D_MEDIA_CTRL_START && a2d->media_ctrl_stat.status == ESP_A2D_MEDIA_CTRL_ACK_SUCCESS) { ESP_LOGI(TAG,"a2dp media started successfully."); + output_bt_start(); set_a2dp_media_state(APP_AV_MEDIA_STATE_STARTED); } else { // not started succesfully, transfer to idle state @@ -831,6 +834,7 @@ static void bt_app_av_media_proc(uint16_t event, void *param) if (a2d->media_ctrl_stat.cmd == ESP_A2D_MEDIA_CTRL_STOP && a2d->media_ctrl_stat.status == ESP_A2D_MEDIA_CTRL_ACK_SUCCESS) { ESP_LOGI(TAG,"a2dp media stopped successfully..."); + output_bt_stop(); set_a2dp_media_state(APP_AV_MEDIA_STATE_IDLE); } else { ESP_LOGI(TAG,"a2dp media stopping..."); diff --git a/components/services/battery.c b/components/services/battery.c index 09efe79eb..2827c2647 100644 --- a/components/services/battery.c +++ b/components/services/battery.c @@ -40,7 +40,7 @@ static struct { .cells = 2, }; -void (*battery_handler_svc)(float value); +void (*battery_handler_svc)(float value, int cells); /**************************************************************************************** * @@ -66,7 +66,7 @@ static void battery_callback(TimerHandle_t xTimer) { if (++battery.count == 30) { battery.avg = battery.sum / battery.count; battery.sum = battery.count = 0; - if (battery_handler_svc) (battery_handler_svc)(battery.avg); + if (battery_handler_svc) (battery_handler_svc)(battery.avg, battery.cells); ESP_LOGI(TAG, "Voltage %.2fV", battery.avg); } } diff --git a/components/services/buttons.c b/components/services/buttons.c index e90ffc500..c1a3a7345 100644 --- a/components/services/buttons.c +++ b/components/services/buttons.c @@ -23,12 +23,14 @@ #include "driver/rmt.h" #include "gpio_exp.h" #include "buttons.h" +#include "services.h" #include "rotary_encoder.h" #include "globdefs.h" static const char * TAG = "buttons"; static EXT_RAM_ATTR int n_buttons; +static EXT_RAM_ATTR uint32_t buttons_idle_since; #define BUTTON_STACK_SIZE 4096 #define MAX_BUTTONS 32 @@ -156,17 +158,30 @@ static void buttons_handler(struct button_s *button, int level) { } } +/**************************************************************************************** + * Get inactivity callback + */ +static uint32_t buttons_idle_callback(void) { + return pdTICKS_TO_MS(xTaskGetTickCount()) - buttons_idle_since; +} + /**************************************************************************************** * Tasks that calls the appropriate functions when buttons are pressed */ static void buttons_task(void* arg) { - ESP_LOGI(TAG, "starting button tasks"); + ESP_LOGI(TAG, "starting button tasks"); + + buttons_idle_since = pdTICKS_TO_MS(xTaskGetTickCount()); + services_sleep_setsleeper(buttons_idle_callback); while (1) { QueueSetMemberHandle_t xActivatedMember; // wait on button, rotary and infrared queues if ((xActivatedMember = xQueueSelectFromSet( common_queue_set, portMAX_DELAY )) == NULL) continue; + + // mark the last activity + buttons_idle_since = pdTICKS_TO_MS(xTaskGetTickCount()); if (xActivatedMember == button_queue) { struct button_s button; diff --git a/components/services/monitor.h b/components/services/monitor.h index b2f1e3da1..fffd7d43d 100644 --- a/components/services/monitor.h +++ b/components/services/monitor.h @@ -22,7 +22,7 @@ extern bool jack_inserted_svc(void); extern void (*spkfault_handler_svc)(bool inserted); extern bool spkfault_svc(void); -extern void (*battery_handler_svc)(float value); +extern void (*battery_handler_svc)(float value, int cells); extern float battery_value_svc(void); extern uint16_t battery_level_svc(void); diff --git a/components/services/services.c b/components/services/services.c index b301d8d3f..e3f7ed15f 100644 --- a/components/services/services.c +++ b/components/services/services.c @@ -47,10 +47,14 @@ static EXT_RAM_ATTR struct { uint64_t wake_gpio, wake_level; uint64_t rtc_gpio, rtc_level; uint32_t delay; -} sleep_config; + float battery_level; + int battery_count; + void (*idle_chain)(uint32_t now); + void (*battery_chain)(float level, int cells); + void (*suspend[10])(void); + uint32_t (*sleeper[10])(void); +} sleep_context; -static EXT_RAM_ATTR void (*sleep_hooks[16])(void); - static const char *TAG = "services"; /**************************************************************************************** @@ -102,6 +106,43 @@ static void sleep_gpio_handler(void *id, button_event_e event, button_press_e mo if (event == BUTTON_PRESSED) services_sleep_activate(SLEEP_ONGPIO); } +/**************************************************************************************** + * + */ +static void sleep_timer(uint32_t now) { + static uint32_t last; + + // first chain the calls to psudo_idle function + if (sleep_context.idle_chain) sleep_context.idle_chain(now); + + // only query callbacks every 30s if we have at least one sleeper + if (!*sleep_context.sleeper || now < last + 30*1000) return; + last = now; + + // call all sleep hooks that might want to do something + for (uint32_t (**sleeper)(void) = sleep_context.sleeper; *sleeper; sleeper++) { + if ((*sleeper)() < sleep_context.delay) return; + } + + // if we are here, we are ready to sleep; + services_sleep_activate(SLEEP_ONTIMER); +} + +/**************************************************************************************** + * + */ +static void sleep_battery(float level, int cells) { + // chain if any + if (sleep_context.battery_chain) sleep_context.battery_chain(level, cells); + + // then assess if we have to stop because of low batt + if (level < sleep_context.battery_level) { + if (sleep_context.battery_count++ == 2) services_sleep_activate(SLEEP_ONBATTERY); + } else { + sleep_context.battery_count = 0; + } +} + /**************************************************************************************** * */ @@ -110,10 +151,20 @@ static void sleep_init(void) { 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)); + PARSE_PARAM(config, "delay", '=', sleep_context.delay); + sleep_context.delay *= 60*1000; + if (sleep_context.delay) { + sleep_context.idle_chain = pseudo_idle_svc; + pseudo_idle_svc = sleep_timer; + ESP_LOGI(TAG, "Sleep inactivity of %d minute(s)", sleep_context.delay / (60*1000)); + } + + // do we want battery safety + PARSE_PARAM_FLOAT(config, "batt", '=', sleep_context.battery_level); + if (sleep_context.battery_level != 0.0) { + sleep_context.battery_chain = battery_handler_svc; + battery_handler_svc = sleep_battery; + ESP_LOGI(TAG, "Sleep on battery level of %.2f", sleep_context.battery_level); } // get the wake criteria @@ -126,15 +177,15 @@ static void sleep_init(void) { 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; + sleep_context.wake_gpio |= 1LL << gpio; } - if (sscanf(item, "%*[^:]:%d", &level)) sleep_config.wake_level |= level << gpio; + if (sscanf(item, "%*[^:]:%d", &level)) sleep_context.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); + if (sleep_context.wake_gpio) { + ESP_LOGI(TAG, "Sleep wake-up gpio bitmap 0x%llx (active 0x%llx)", sleep_context.wake_gpio, sleep_context.wake_level); } } @@ -148,15 +199,15 @@ static void sleep_init(void) { if (!rtc_gpio_is_valid_gpio(gpio)) { ESP_LOGE(TAG, "invalid rtc GPIO %d", gpio); } else { - sleep_config.rtc_gpio |= 1LL << gpio; + sleep_context.rtc_gpio |= 1LL << gpio; } - if (sscanf(item, "%*[^:]:%d", &level)) sleep_config.rtc_level |= level << gpio; + if (sscanf(item, "%*[^:]:%d", &level)) sleep_context.rtc_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.rtc_gpio) { - ESP_LOGI(TAG, "RTC forced gpio bitmap 0x%llx (active 0x%llx)", sleep_config.rtc_gpio, sleep_config.rtc_level); + if (sleep_context.rtc_gpio) { + ESP_LOGI(TAG, "RTC forced gpio bitmap 0x%llx (active 0x%llx)", sleep_context.rtc_gpio, sleep_context.rtc_level); } } @@ -172,49 +223,40 @@ static void sleep_init(void) { } } -/**************************************************************************************** - * - */ -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)(); + for (void (**suspend)(void) = sleep_context.suspend; *suspend; suspend++) (*suspend)(); // isolate all possible GPIOs, except the wake-up and RTC-maintaines ones esp_sleep_config_gpio_isolate(); // keep RTC domain up if we need to maintain pull-up/down of some GPIO from RTC - if (sleep_config.rtc_gpio) esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON); + if (sleep_context.rtc_gpio) esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON); for (int i = 0; i < GPIO_NUM_MAX; i++) { // must be a RTC GPIO if (!rtc_gpio_is_valid_gpio(i)) continue; // do we need to maintain a pull-up or down of that GPIO - if ((1LL << i) & sleep_config.rtc_gpio) { - if ((sleep_config.rtc_level >> i) & 0x01) rtc_gpio_pullup_en(i); + if ((1LL << i) & sleep_context.rtc_gpio) { + if ((sleep_context.rtc_level >> i) & 0x01) rtc_gpio_pullup_en(i); else rtc_gpio_pulldown_en(i); // or is this not wake-up GPIO, just isolate it - } else if (!((1LL << i) & sleep_config.wake_gpio)) { + } else if (!((1LL << i) & sleep_context.wake_gpio)) { 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 if (sleep_config.wake_gpio) { - int gpio = __builtin_ctz(sleep_config.wake_gpio); - int level = (sleep_config.wake_level >> gpio) & 0x01; + if (sleep_context.wake_gpio & (sleep_context.wake_gpio - 1)) { + ESP_LOGI(TAG, "going to sleep cause %d, wake-up on multiple GPIO, any '1' wakes up 0x%llx", cause, sleep_context.wake_gpio); + esp_sleep_enable_ext1_wakeup(sleep_context.wake_gpio, ESP_EXT1_WAKEUP_ANY_HIGH); + } else if (sleep_context.wake_gpio) { + int gpio = __builtin_ctz(sleep_context.wake_gpio); + int level = (sleep_context.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); } else { @@ -226,18 +268,31 @@ void services_sleep_activate(sleep_cause_e cause) { 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; - } +static void register_method(void **store, size_t size, void *method) { + for (int i = 0; i < size; i++, *store++) if (!*store) { + *store = method; + return; } } +/**************************************************************************************** + * + */ +void services_sleep_setsuspend(void (*hook)(void)) { + register_method((void**) sleep_context.suspend, sizeof(sleep_context.suspend)/sizeof(*sleep_context.suspend), (void*) hook); +} + +/**************************************************************************************** + * + */ +void services_sleep_setsleeper(uint32_t (*sleeper)(void)) { + register_method((void**) sleep_context.sleeper, sizeof(sleep_context.sleeper)/sizeof(*sleep_context.sleeper), (void*) sleeper); +} + /**************************************************************************************** * */ diff --git a/components/services/services.h b/components/services/services.h index 62394143c..42a0bfddc 100644 --- a/components/services/services.h +++ b/components/services/services.h @@ -10,7 +10,7 @@ #pragma once -typedef enum { SLEEP_ONTIMER, SLEEP_ONKEY, SLEEP_ONGPIO, SLEEP_ONIR } sleep_cause_e; +typedef enum { SLEEP_ONTIMER, SLEEP_ONKEY, SLEEP_ONGPIO, SLEEP_ONIR, SLEEP_ONBATTERY } 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); +void services_sleep_setsuspend(void (*hook)(void)); +void services_sleep_setsleeper(uint32_t (*sleeper)(void)); diff --git a/components/squeezelite/output_bt.c b/components/squeezelite/output_bt.c index c37f9ad24..c062c0abc 100644 --- a/components/squeezelite/output_bt.c +++ b/components/squeezelite/output_bt.c @@ -9,12 +9,14 @@ * */ +#include #include "driver/gpio.h" #include "squeezelite.h" #include "equalizer.h" #include "perf_trace.h" #include "platform_config.h" -#include +#include "services.h" +#include "led.h" extern struct outputstate output; extern struct buffer *outputbuf; @@ -39,6 +41,7 @@ static bool running = false; static uint8_t *btout; static frames_t oframes; static bool stats; +static uint32_t bt_idle_since; static int _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); @@ -60,10 +63,28 @@ static int _write_frames(frames_t out_frames, bool silence, s32_t gainL, s32_t g RESET_MIN_MAX_DURATION(lock_out_time) DECLARE_ALL_MIN_MAX; + +/**************************************************************************************** + * Get inactivity callback + */ +static uint32_t bt_idle_callback(void) { + return output.state <= OUTPUT_STOPPED ? pdTICKS_TO_MS(xTaskGetTickCount()) - bt_idle_since : 0; +} +/**************************************************************************************** + * Init BT sink + */ void output_init_bt(log_level level, char *device, unsigned output_buf_size, char *params, unsigned rates[], unsigned rate_delay, unsigned idle) { loglevel = level; - running = true; + + // idle counter + bt_idle_since = pdTICKS_TO_MS(xTaskGetTickCount()); + services_sleep_setsleeper(bt_idle_callback); + + // even BT has a right to use led :-) + led_blink(LED_GREEN, 200, 1000); + + running = true; output.write_cb = &_write_frames; hal_bluetooth_init(device); char *p = config_alloc_get_default(NVS_TYPE_STR, "stats", "n", 0); @@ -72,6 +93,9 @@ void output_init_bt(log_level level, char *device, unsigned output_buf_size, cha equalizer_set_samplerate(output.current_sample_rate); } +/**************************************************************************************** + * Close BT sink + */ void output_close_bt(void) { LOCK; running = false; @@ -80,6 +104,9 @@ void output_close_bt(void) { equalizer_close(); } +/**************************************************************************************** + * Data framing callback + */ static int _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) { @@ -120,6 +147,9 @@ static int _write_frames(frames_t out_frames, bool silence, s32_t gainL, s32_t g return (int)out_frames; } +/**************************************************************************************** + * Data callback for BT stack + */ int32_t output_bt_data(uint8_t *data, int32_t len) { int32_t iframes = len / BYTES_PER_FRAME, start_timer = 0; @@ -153,6 +183,9 @@ int32_t output_bt_data(uint8_t *data, int32_t len) { return oframes * BYTES_PER_FRAME; } +/**************************************************************************************** + * Tick for BT + */ void output_bt_tick(void) { static time_t lastTime=0; @@ -186,3 +219,18 @@ void output_bt_tick(void) { } } +/**************************************************************************************** + * BT playback stop + */ +void output_bt_stop(void) { + led_blink(LED_GREEN, 200, 1000); + bt_idle_since = pdTICKS_TO_MS(xTaskGetTickCount()); +} + +/**************************************************************************************** + * BT playback start + */ +void output_bt_start(void) { + led_on(LED_GREEN); + bt_idle_since = pdTICKS_TO_MS(xTaskGetTickCount()); +} diff --git a/components/squeezelite/output_i2s.c b/components/squeezelite/output_i2s.c index d9645ffd4..f8398af2d 100644 --- a/components/squeezelite/output_i2s.c +++ b/components/squeezelite/output_i2s.c @@ -103,11 +103,11 @@ const struct adac_s *adac = &dac_external; static log_level loglevel; -static uint32_t stopped_time; +static uint32_t i2s_idle_since; 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, i2s_stats; +static bool running, isI2SStarted, ended; static i2s_config_t i2s_config; static u8_t *obuf; static frames_t oframes; @@ -128,7 +128,7 @@ 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_idle(uint32_t now); +static void i2s_stats(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); @@ -202,6 +202,13 @@ static void set_amp_gpio(int gpio, char *value) { } #endif +/**************************************************************************************** + * Get inactivity callback + */ +static uint32_t i2s_idle_callback(void) { + return output.state <= OUTPUT_STOPPED ? pdTICKS_TO_MS(xTaskGetTickCount()) - i2s_idle_since : 0; +} + /**************************************************************************************** * Set pin from config string */ @@ -418,11 +425,15 @@ void output_init_i2s(log_level level, char *device, unsigned output_buf_size, ch // do we want stats p = config_alloc_get_default(NVS_TYPE_STR, "stats", "n", 0); - i2s_stats = p && (*p == '1' || *p == 'Y' || *p == 'y'); + if (p && (*p == '1' || *p == 'Y' || *p == 'y')) { + pseudo_idle_chain = pseudo_idle_svc; + pseudo_idle_svc = i2s_stats; + } free(p); - - pseudo_idle_chain = pseudo_idle_svc; - pseudo_idle_svc = i2s_idle; + + // register a callback for inactivity + i2s_idle_since = pdTICKS_TO_MS(xTaskGetTickCount()); + services_sleep_setsleeper(i2s_idle_callback); // create task as a FreeRTOS task but uses stack in internal RAM { @@ -433,7 +444,6 @@ void output_init_i2s(log_level level, char *device, unsigned output_buf_size, ch } } - /**************************************************************************************** * Terminate DAC output */ @@ -497,8 +507,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) { TIME_MEASUREMENT_START(timer_start); @@ -513,7 +522,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()); + i2s_idle_since = pdTICKS_TO_MS(xTaskGetTickCount()); adac->speaker(false); led_blink(LED_GREEN, 200, 1000); } else if (output.state == OUTPUT_RUNNING) { @@ -640,17 +649,14 @@ static void output_thread_i2s(void *arg) { /**************************************************************************************** * stats output callback */ -static void i2s_idle(uint32_t now) { +static void i2s_stats(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 (!i2s_stats || output.state <= OUTPUT_STOPPED || now < last + STATS_PERIOD_MS) return; + if (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/components/targets/muse/muse.c b/components/targets/muse/muse.c index 8f757c7a3..4ee2fc12c 100644 --- a/components/targets/muse/muse.c +++ b/components/targets/muse/muse.c @@ -48,8 +48,8 @@ void ws2812_write_leds(struct led_state new_state); static const char TAG[] = "muse"; -static void (*battery_handler_chain)(float value); -static void battery_svc(float value); +static void (*battery_handler_chain)(float value, int cells); +static void battery_svc(float value, int cells); static bool init(void); static void set_battery_led(float value); @@ -81,11 +81,11 @@ static void set_battery_led(float value) { ws2812_write_leds(new_state); } -static void battery_svc(float value) { +static void battery_svc(float value, int cells) { set_battery_led(value); ESP_LOGI(TAG, "Called for battery service with %f", value); - if (battery_handler_chain) battery_handler_chain(value); + if (battery_handler_chain) battery_handler_chain(value, cells); } // This is the buffer which the hw peripheral will access while pulsing the output pin