From e0e749fb5b5a51392ab48e592f458407cfde2fb2 Mon Sep 17 00:00:00 2001 From: philippe44 Date: Mon, 18 Sep 2023 18:34:03 -0700 Subject: [PATCH] add (some) spurious IR detection capability --- components/services/infrared.c | 9 ++ components/services/infrared.h | 1 + components/services/services.c | 215 ++++++++++++++++++++------------- 3 files changed, 142 insertions(+), 83 deletions(-) diff --git a/components/services/infrared.c b/components/services/infrared.c index 6ff6f108..8b22ee7a 100644 --- a/components/services/infrared.c +++ b/components/services/infrared.c @@ -22,6 +22,8 @@ static const char* TAG = "IR"; #define IR_TOOLS_FLAGS_PROTO_EXT (1 << 0) /*!< Enable Extended IR protocol */ #define IR_TOOLS_FLAGS_INVERSE (1 << 1) /*!< Inverse the IR signal, i.e. take high level as low, and vice versa */ +static int8_t ir_gpio = -1; + /** * @brief IR device type * @@ -478,6 +480,12 @@ bool infrared_receive(RingbufHandle_t rb, infrared_handler handler) { return decoded; } +/**************************************************************************************** + * + */ +int8_t infrared_gpio(void) { + return ir_gpio; +}; /**************************************************************************************** * @@ -491,6 +499,7 @@ void infrared_init(RingbufHandle_t *rb, int gpio, infrared_mode_t mode) { ir_parser_config.flags |= IR_TOOLS_FLAGS_PROTO_EXT; // Using extended IR protocols (both NEC and RC5 have extended version) ir_parser = (mode == IR_NEC) ? ir_parser_rmt_new_nec(&ir_parser_config) : ir_parser_rmt_new_rc5(&ir_parser_config); + ir_gpio = gpio; // get RMT RX ringbuffer rmt_get_ringbuf_handle(rmt_channel, rb); diff --git a/components/services/infrared.h b/components/services/infrared.h index 3dcbf82d..40bb3d76 100644 --- a/components/services/infrared.h +++ b/components/services/infrared.h @@ -17,4 +17,5 @@ typedef void (*infrared_handler)(uint16_t addr, uint16_t cmd); bool infrared_receive(RingbufHandle_t rb, infrared_handler handler); void infrared_init(RingbufHandle_t *rb, int gpio, infrared_mode_t mode); +int8_t infrared_gpio(void); diff --git a/components/services/services.c b/components/services/services.c index 142688de..f389fe84 100644 --- a/components/services/services.c +++ b/components/services/services.c @@ -37,35 +37,35 @@ 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 = { +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; uint64_t rtc_gpio, rtc_level; - uint32_t delay; + uint32_t delay, spurious; 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); + uint32_t (*sleeper[10])(void); } sleep_context; - + static const char *TAG = "services"; /**************************************************************************************** - * + * */ void set_chip_power_gpio(int gpio, char *value) { bool parsed = true; // we only parse on-chip GPIOs if (gpio >= GPIO_NUM_MAX) return; - + if (!strcasecmp(value, "vcc") ) { gpio_pad_select_gpio(gpio); gpio_set_direction(gpio, GPIO_MODE_OUTPUT); @@ -75,19 +75,19 @@ void set_chip_power_gpio(int gpio, char *value) { gpio_set_direction(gpio, GPIO_MODE_OUTPUT); gpio_set_level(gpio, 0); } else parsed = false; - + if (parsed) ESP_LOGI(TAG, "set GPIO %u to %s", gpio, value); -} +} /**************************************************************************************** - * + * */ void set_exp_power_gpio(int gpio, char *value) { bool parsed = true; // we only parse on-chip GPIOs if (gpio < GPIO_NUM_MAX) return; - + if (!strcasecmp(value, "vcc") ) { gpio_exp_set_direction(gpio, GPIO_MODE_OUTPUT, NULL); gpio_exp_set_level(gpio, 1, true, NULL); @@ -95,78 +95,88 @@ void set_exp_power_gpio(int gpio, char *value) { gpio_exp_set_direction(gpio, GPIO_MODE_OUTPUT, NULL); gpio_exp_set_level(gpio, 0, true, NULL); } 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_timer(uint32_t now) { - static uint32_t last; - - // first chain the calls to psudo_idle function + static EXT_RAM_ATTR uint32_t last, first; + + // first chain the calls to pseudo_idle function if (sleep_context.idle_chain) sleep_context.idle_chain(now); - + + // we need boot time for spurious timeout calculation + if (!first) first = 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; + + // time to evaluate if we had spurious wake-up + if (sleep_context.spurious && now > sleep_context.spurious + first) { + bool spurious = true; + + // see if at least one sleeper has been awake since we started + for (uint32_t (**sleeper)(void) = sleep_context.sleeper; *sleeper && spurious; sleeper++) { + spurious &= (*sleeper)() >= now - first; + } + + // no activity since we woke-up, this was a spurious one + if (spurious) { + ESP_LOGI(TAG, "spurious wake of %d sec, going back to sleep", (now - first) / 1000); + services_sleep_activate(SLEEP_ONTIMER); + } + + // resume normal work but we might have no "regular" inactivity delay + sleep_context.spurious = 0; + if (!sleep_context.delay) *sleep_context.sleeper = NULL; + ESP_LOGI(TAG, "wake-up was not spurious after %d sec", (now - first) / 1000); } - - // if we are here, we are ready to sleep; - services_sleep_activate(SLEEP_ONTIMER); -} + + // we might be here because we are waiting for spurious + if (sleep_context.delay) { + // call all sleepers to know how long for how long they have been inactive + for (uint32_t (**sleeper)(void) = sleep_context.sleeper; sleep_context.delay && *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; } -} +} /**************************************************************************************** - * + * */ 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_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 if ((p = strcasestr(config, "wake"))) { char list[32] = "", item[8]; @@ -182,13 +192,22 @@ static void sleep_init(void) { 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_context.wake_gpio) { - ESP_LOGI(TAG, "Sleep wake-up gpio bitmap 0x%llx (active 0x%llx)", sleep_context.wake_gpio, sleep_context.wake_level); + ESP_LOGI(TAG, "Sleep wake-up gpio bitmap 0x%llx (active 0x%llx)", sleep_context.wake_gpio, sleep_context.wake_level); } } - + + // 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 rtc-pull criteria if ((p = strcasestr(config, "rtc"))) { char list[32] = "", item[8]; @@ -204,42 +223,72 @@ static void sleep_init(void) { 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_context.rtc_gpio) { - ESP_LOGI(TAG, "RTC forced gpio bitmap 0x%llx (active 0x%llx)", sleep_context.rtc_gpio, sleep_context.rtc_level); + ESP_LOGI(TAG, "RTC forced gpio bitmap 0x%llx (active 0x%llx)", sleep_context.rtc_gpio, sleep_context.rtc_level); } } - - // then get the gpio that activate sleep (we could check that we have a valid wake) + + // get the GPIOs 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); + 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); } + + // do we want delay sleep + PARSE_PARAM(config, "delay", '=', sleep_context.delay); + sleep_context.delay *= 60*1000; + + // now check why we woke-up + esp_sleep_wakeup_cause_t cause = esp_sleep_get_wakeup_cause(); + if (cause == ESP_SLEEP_WAKEUP_EXT0 || cause == ESP_SLEEP_WAKEUP_EXT1) { + ESP_LOGI(TAG, "waking-up from deep sleep with cause %d", cause); + + // find the type of wake-up + uint64_t wake_gpio; + if (cause == ESP_SLEEP_WAKEUP_EXT0) wake_gpio = sleep_context.wake_gpio; + else wake_gpio = esp_sleep_get_ext1_wakeup_status(); + + // we might be woken up by infrared in which case we want a short sleep + if (infrared_gpio() >= 0 && ((1LL << infrared_gpio()) & wake_gpio)) { + sleep_context.spurious = 1; + PARSE_PARAM(config, "spurious", '=', sleep_context.spurious); + sleep_context.spurious *= 60*1000; + ESP_LOGI(TAG, "spurious wake-up detection during %d sec", sleep_context.spurious / 1000); + } + } + + // if we have inactivity timer (user-set or because of IR wake) then active counters + if (sleep_context.delay || sleep_context.spurious) { + sleep_context.idle_chain = pseudo_idle_svc; + pseudo_idle_svc = sleep_timer; + if (sleep_context.delay) ESP_LOGI(TAG, "inactivity timer of %d minute(s)", sleep_context.delay / (60*1000)); + } } /**************************************************************************************** - * + * */ -void services_sleep_activate(sleep_cause_e cause) { +void services_sleep_activate(sleep_cause_e cause) { // call all sleep hooks that might want to do something 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_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_context.rtc_gpio) { if ((sleep_context.rtc_level >> i) & 0x01) rtc_gpio_pullup_en(i); @@ -249,7 +298,7 @@ void services_sleep_activate(sleep_cause_e cause) { rtc_gpio_isolate(i); } } - + // is there just one GPIO 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); @@ -268,13 +317,13 @@ void services_sleep_activate(sleep_cause_e cause) { } // 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); + 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(); } /**************************************************************************************** - * + * */ static void register_method(void **store, size_t size, void *method) { for (int i = 0; i < size; i++, *store++) if (!*store) { @@ -284,26 +333,26 @@ static void register_method(void **store, size_t size, void *method) { } /**************************************************************************************** - * + * */ 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); } /**************************************************************************************** - * + * */ void services_init(void) { messaging_service_init(); gpio_install_isr_service(0); - + #ifdef CONFIG_I2C_LOCKED if (i2c_system_port == 0) { i2c_system_port = 1; @@ -314,7 +363,7 @@ void services_init(void) { // set potential power GPIO on chip first in case expanders are power using these parse_set_GPIO(set_chip_power_gpio); - // shared I2C bus + // shared I2C bus const i2c_config_t * i2c_config = config_i2c_get(&i2c_system_port); ESP_LOGI(TAG,"Configuring I2C sda:%d scl:%d port:%u speed:%u", i2c_config->sda_io_num, i2c_config->scl_io_num, i2c_system_port, i2c_config->master.clk_speed); @@ -324,11 +373,11 @@ void services_init(void) { } else { i2c_system_port = -1; ESP_LOGW(TAG, "no I2C configured"); - } + } const spi_bus_config_t * spi_config = config_spi_get((spi_host_device_t*) &spi_system_host); ESP_LOGI(TAG,"Configuring SPI mosi:%d miso:%d clk:%d host:%u dc:%d", spi_config->mosi_io_num, spi_config->miso_io_num, spi_config->sclk_io_num, spi_system_host, spi_system_dc_gpio); - + if (spi_config->mosi_io_num != -1 && spi_config->sclk_io_num != -1) { spi_bus_initialize( spi_system_host, spi_config, 1 ); if (spi_system_dc_gpio != -1) { @@ -337,35 +386,35 @@ void services_init(void) { gpio_set_level( spi_system_dc_gpio, 0 ); } else { ESP_LOGW(TAG, "No DC GPIO set, SPI display will not work"); - } + } } else { spi_system_host = -1; ESP_LOGW(TAG, "no SPI configured"); - } + } // create GPIO expanders const gpio_exp_config_t* gpio_exp_config; for (int count = 0; (gpio_exp_config = config_gpio_exp_get(count)); count++) gpio_exp_create(gpio_exp_config); - // now set potential power GPIO on expander + // now set potential power GPIO on expander parse_set_GPIO(set_exp_power_gpio); // system-wide PWM timer configuration ledc_timer_config_t pwm_timer = { - .duty_resolution = LEDC_TIMER_13_BIT, - .freq_hz = 5000, + .duty_resolution = LEDC_TIMER_13_BIT, + .freq_hz = 5000, #ifdef CONFIG_IDF_TARGET_ESP32S3 .speed_mode = LEDC_LOW_SPEED_MODE, #else - .speed_mode = LEDC_HIGH_SPEED_MODE, -#endif + .speed_mode = LEDC_HIGH_SPEED_MODE, +#endif .timer_num = pwm_system.timer, }; - + ledc_timer_config(&pwm_timer); led_svc_init(); battery_svc_init(); - monitor_svc_init(); + monitor_svc_init(); sleep_init(); }