From 5a83347bec398b202570332061d6100f26abb51d Mon Sep 17 00:00:00 2001 From: Renz Christian Bagaporo Date: Fri, 26 Oct 2018 13:14:19 +0800 Subject: [PATCH] event: Implement event loop library --- .gitlab-ci.yml | 20 +- components/esp32/CMakeLists.txt | 2 +- components/esp32/event_loop.c | 2 +- .../{esp_event.h => esp_event_legacy.h} | 0 components/esp_event/CMakeLists.txt | 9 + components/esp_event/Kconfig | 11 + components/esp_event/component.mk | 6 + components/esp_event/default_event_loop.c | 101 ++ components/esp_event/esp_event.c | 856 +++++++++++++ components/esp_event/esp_event_private.c | 44 + components/esp_event/include/esp_event.h | 336 +++++ components/esp_event/include/esp_event_base.h | 43 + .../private_include/esp_event_internal.h | 100 ++ .../private_include/esp_event_private.h | 54 + components/esp_event/test/CMakeLists.txt | 5 + components/esp_event/test/component.mk | 5 + components/esp_event/test/test_event.c | 1077 +++++++++++++++++ components/ethernet/CMakeLists.txt | 2 +- components/idf_test/include/idf_performance.h | 2 + docs/Doxyfile | 3 + docs/en/api-reference/system/esp_event.rst | 206 ++++ docs/en/api-reference/system/index.rst | 1 + docs/zh_CN/api-reference/system/esp_event.rst | 1 + examples/system/README.md | 2 +- .../default_event_loop/CMakeLists.txt | 6 + .../esp_event/default_event_loop/Makefile | 9 + .../esp_event/default_event_loop/README.md | 166 +++ .../default_event_loop/example_test.py | 100 ++ .../default_event_loop/main/CMakeLists.txt | 4 + .../default_event_loop/main/component.mk | 8 + .../default_event_loop/main/event_source.h | 53 + .../esp_event/default_event_loop/main/main.c | 179 +++ .../esp_event/user_event_loops/CMakeLists.txt | 6 + .../esp_event/user_event_loops/Makefile | 9 + .../esp_event/user_event_loops/README.md | 117 ++ .../user_event_loops/example_test.py | 50 + .../user_event_loops/main/CMakeLists.txt | 4 + .../user_event_loops/main/component.mk | 8 + .../user_event_loops/main/event_source.h | 35 + .../esp_event/user_event_loops/main/main.c | 123 ++ 40 files changed, 3760 insertions(+), 5 deletions(-) rename components/esp32/include/{esp_event.h => esp_event_legacy.h} (100%) create mode 100644 components/esp_event/CMakeLists.txt create mode 100644 components/esp_event/Kconfig create mode 100644 components/esp_event/component.mk create mode 100644 components/esp_event/default_event_loop.c create mode 100644 components/esp_event/esp_event.c create mode 100644 components/esp_event/esp_event_private.c create mode 100644 components/esp_event/include/esp_event.h create mode 100644 components/esp_event/include/esp_event_base.h create mode 100644 components/esp_event/private_include/esp_event_internal.h create mode 100644 components/esp_event/private_include/esp_event_private.h create mode 100644 components/esp_event/test/CMakeLists.txt create mode 100644 components/esp_event/test/component.mk create mode 100644 components/esp_event/test/test_event.c create mode 100644 docs/en/api-reference/system/esp_event.rst create mode 100644 docs/zh_CN/api-reference/system/esp_event.rst create mode 100644 examples/system/esp_event/default_event_loop/CMakeLists.txt create mode 100644 examples/system/esp_event/default_event_loop/Makefile create mode 100644 examples/system/esp_event/default_event_loop/README.md create mode 100644 examples/system/esp_event/default_event_loop/example_test.py create mode 100644 examples/system/esp_event/default_event_loop/main/CMakeLists.txt create mode 100644 examples/system/esp_event/default_event_loop/main/component.mk create mode 100644 examples/system/esp_event/default_event_loop/main/event_source.h create mode 100644 examples/system/esp_event/default_event_loop/main/main.c create mode 100644 examples/system/esp_event/user_event_loops/CMakeLists.txt create mode 100644 examples/system/esp_event/user_event_loops/Makefile create mode 100644 examples/system/esp_event/user_event_loops/README.md create mode 100644 examples/system/esp_event/user_event_loops/example_test.py create mode 100644 examples/system/esp_event/user_event_loops/main/CMakeLists.txt create mode 100644 examples/system/esp_event/user_event_loops/main/component.mk create mode 100644 examples/system/esp_event/user_event_loops/main/event_source.h create mode 100644 examples/system/esp_event/user_event_loops/main/main.c diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 124bc7228..897e61217 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -928,6 +928,12 @@ example_test_001_01: - ESP32 - Example_WIFI +example_test_001_02: + <<: *example_test_template + tags: + - ESP32 + - Example_WIFI + example_test_002_01: <<: *example_test_template image: $CI_DOCKER_REGISTRY/ubuntu-test-env$BOT_DOCKER_IMAGE_TAG @@ -1168,7 +1174,19 @@ UT_001_36: tags: - ESP32_IDF - UT_T1_1 - + +UT_001_37: + <<: *unit_test_template + tags: + - ESP32_IDF + - UT_T1_1 + +UT_001_38: + <<: *unit_test_template + tags: + - ESP32_IDF + - UT_T1_1 + UT_002_01: <<: *unit_test_template tags: diff --git a/components/esp32/CMakeLists.txt b/components/esp32/CMakeLists.txt index efb675102..92b75a4ce 100644 --- a/components/esp32/CMakeLists.txt +++ b/components/esp32/CMakeLists.txt @@ -62,7 +62,7 @@ else() "hwcrypto/sha.c") set(COMPONENT_ADD_INCLUDEDIRS "include") - set(COMPONENT_REQUIRES driver tcpip_adapter) + set(COMPONENT_REQUIRES driver tcpip_adapter esp_event) # driver is a public requirement because esp_sleep.h uses gpio_num_t & touch_pad_t # tcpip_adapter is a public requirement because esp_event.h uses tcpip_adapter types set(COMPONENT_PRIV_REQUIRES diff --git a/components/esp32/event_loop.c b/components/esp32/event_loop.c index d8e6a5bd2..9dbefb45d 100644 --- a/components/esp32/event_loop.c +++ b/components/esp32/event_loop.c @@ -18,8 +18,8 @@ #include "esp_err.h" #include "esp_wifi.h" -#include "esp_event.h" #include "esp_event_loop.h" +#include "esp_event_legacy.h" #include "esp_task.h" #include "esp_mesh.h" diff --git a/components/esp32/include/esp_event.h b/components/esp32/include/esp_event_legacy.h similarity index 100% rename from components/esp32/include/esp_event.h rename to components/esp32/include/esp_event_legacy.h diff --git a/components/esp_event/CMakeLists.txt b/components/esp_event/CMakeLists.txt new file mode 100644 index 000000000..2e791df3b --- /dev/null +++ b/components/esp_event/CMakeLists.txt @@ -0,0 +1,9 @@ +set(COMPONENT_SRCS "default_event_loop.c" + "esp_event.c" + "esp_event_private.c") +set(COMPONENT_ADD_INCLUDEDIRS "include") +set(COMPONENT_PRIV_INCLUDEDIRS "private_include") + +set(COMPONENT_REQUIRES log) + +register_component() diff --git a/components/esp_event/Kconfig b/components/esp_event/Kconfig new file mode 100644 index 000000000..2ae488988 --- /dev/null +++ b/components/esp_event/Kconfig @@ -0,0 +1,11 @@ +menu "Event Loop Library" + +config EVENT_LOOP_PROFILING + bool "Enable event loop profiling" + default n + help + Enables collections of statistics in the event loop library such as the number of events posted to/recieved by an event loop, number of + callbacks involved, number of events dropped to to a full event loop queue, run time of event handlers, and number of times/run + time of each event handler. + +endmenu \ No newline at end of file diff --git a/components/esp_event/component.mk b/components/esp_event/component.mk new file mode 100644 index 000000000..05135a275 --- /dev/null +++ b/components/esp_event/component.mk @@ -0,0 +1,6 @@ +# +# Component Makefile +# +COMPONENT_ADD_INCLUDEDIRS := include +COMPONENT_PRIV_INCLUDEDIRS := private_include +COMPONENT_SRCDIRS := . \ No newline at end of file diff --git a/components/esp_event/default_event_loop.c b/components/esp_event/default_event_loop.c new file mode 100644 index 000000000..91fb78927 --- /dev/null +++ b/components/esp_event/default_event_loop.c @@ -0,0 +1,101 @@ +// Copyright 2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "esp_event.h" +#include "esp_event_internal.h" + +/* ------------------------- Static Variables ------------------------------- */ + +static esp_event_loop_handle_t s_default_loop = NULL; + +/* ---------------------------- Public API ---------------------------------- */ + +esp_err_t esp_event_handler_register(esp_event_base_t event_base, int32_t event_id, + esp_event_handler_t event_handler, void* event_handler_arg) +{ + if (s_default_loop == NULL) { + return ESP_ERR_INVALID_STATE; + } + + return esp_event_handler_register_with(s_default_loop, event_base, event_id, + event_handler, event_handler_arg); +} + +esp_err_t esp_event_handler_unregister(esp_event_base_t event_base, int32_t event_id, + esp_event_handler_t event_handler) +{ + if (s_default_loop == NULL) { + return ESP_ERR_INVALID_STATE; + } + + return esp_event_handler_unregister_with(s_default_loop, event_base, event_id, + event_handler); +} + +esp_err_t esp_event_post(esp_event_base_t event_base, int32_t event_id, + void* event_data, size_t event_data_size, TickType_t ticks_to_wait) +{ + if (s_default_loop == NULL) { + return ESP_ERR_INVALID_STATE; + } + + return esp_event_post_to(s_default_loop, event_base, event_id, + event_data, event_data_size, ticks_to_wait); +} + + +esp_err_t esp_event_loop_create_default() +{ + if (s_default_loop) { + return ESP_ERR_INVALID_STATE; + } + + esp_event_loop_args_t loop_args = { + .queue_size = CONFIG_SYSTEM_EVENT_QUEUE_SIZE, + .task_name = "sys_evt", + .task_stack_size = ESP_TASKD_EVENT_STACK, + .task_priority = ESP_TASKD_EVENT_PRIO, + .task_core_id = 0 + }; + + esp_err_t err; + + err = esp_event_loop_create(&loop_args, &s_default_loop); + if (err != ESP_OK) { + return err; + } + + return ESP_OK; +} + +esp_err_t esp_event_loop_delete_default() +{ + if (!s_default_loop) { + return ESP_ERR_INVALID_STATE; + } + + esp_err_t err; + + err = esp_event_loop_delete(s_default_loop); + + if (err != ESP_OK) { + return err; + } + + s_default_loop = NULL; + + return ESP_OK; +} + + diff --git a/components/esp_event/esp_event.c b/components/esp_event/esp_event.c new file mode 100644 index 000000000..8c5c0570e --- /dev/null +++ b/components/esp_event/esp_event.c @@ -0,0 +1,856 @@ +// Copyright 2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include + +#include "esp_log.h" + +#include "esp_event.h" +#include "esp_event_internal.h" +#include "esp_event_private.h" + +#ifdef CONFIG_EVENT_LOOP_PROFILING +#include "esp_timer.h" +#endif + +/* ---------------------------- Definitions --------------------------------- */ + +#ifdef CONFIG_EVENT_LOOP_PROFILING +// loop@ rx: dr: inv: run: +#define LOOP_DUMP_FORMAT "loop@%p,%s rx:%u dr:%u inv:%u run:%lld us\n" +// event@ proc: run: +#define EVENT_DUMP_FORMAT "\tevent@%s:%d proc:%u run:%lld us\n" + // handler@
inv: run: +#define HANDLER_DUMP_FORMAT "\t\thandler@%p inv:%u run:%lld us\n" + +#define PRINT_DUMP_INFO(dst, sz, ...) do { \ + int cb = snprintf(dst, sz, __VA_ARGS__); \ + dst += cb; \ + sz -= cb; \ + } while(0); +#endif + +/* ------------------------- Static Variables ------------------------------- */ + +static const char* TAG = "event"; +static const char* esp_event_any_base = "any"; + +#ifdef CONFIG_EVENT_LOOP_PROFILING +static SLIST_HEAD(esp_event_loop_instance_list_t, esp_event_loop_instance) s_event_loops = + SLIST_HEAD_INITIALIZER(s_event_loops); + +static portMUX_TYPE s_event_loops_spinlock = portMUX_INITIALIZER_UNLOCKED; +#endif + + +/* ------------------------- Static Functions ------------------------------- */ + +#ifdef CONFIG_EVENT_LOOP_PROFILING +static int esp_event_dump_prepare() +{ + esp_event_loop_instance_t* loop_it; + esp_event_base_instance_t* base_it; + esp_event_id_instance_t* id_it; + esp_event_handler_instance_t* handler_it; + + // Count the number of items to be printed. This is needed to compute how much memory to reserve. + int loops = 0, events = 0, handlers = 0; + + portENTER_CRITICAL(&s_event_loops_spinlock); + + SLIST_FOREACH(loop_it, &s_event_loops, loop_entry) { + SLIST_FOREACH(handler_it, &(loop_it->loop_handlers), handler_entry) { + handlers++; + } + SLIST_FOREACH(base_it, &(loop_it->event_bases), event_base_entry) { + SLIST_FOREACH(handler_it, &(base_it->base_handlers), handler_entry) { + handlers++; + } + // Print event-level handlers + SLIST_FOREACH(id_it, &(base_it->event_ids), event_id_entry) { + SLIST_FOREACH(handler_it, &(id_it->handlers), handler_entry) { + handlers++; + } + events++; + } + events++; + } + events++; + loops++; + } + + portEXIT_CRITICAL(&s_event_loops_spinlock); + + // Reserve slightly more memory than computed + int allowance = 3; + int size = (((loops + allowance) * (sizeof(LOOP_DUMP_FORMAT) + 10 + 20 + 3 * 11 + 20 )) + + ((events + allowance) * (sizeof(EVENT_DUMP_FORMAT) + 10 + 20 + 11 + 20)) + + ((handlers + allowance) * (sizeof(HANDLER_DUMP_FORMAT) + 10 + 11 + 20))); + + return size; +} +#endif + +static void esp_event_loop_run_task(void* args) +{ + esp_err_t err; + esp_event_loop_handle_t event_loop = (esp_event_loop_handle_t) args; + + ESP_LOGD(TAG, "running task for loop %p", event_loop); + + while(1) { + err = esp_event_loop_run(event_loop, portMAX_DELAY); + if (err != ESP_OK) { + break; + } + } + + ESP_LOGE(TAG, "suspended task for loop %p", event_loop); + vTaskSuspend(NULL); +} + +// Functions that operate on handler instance +static esp_event_handler_instance_t* handler_instance_create(esp_event_handler_t event_handler, void* event_handler_arg) +{ + esp_event_handler_instance_t* handler_instance = calloc(1, sizeof(*handler_instance)); + + if (handler_instance != NULL) { + handler_instance->handler = event_handler; + handler_instance->arg = event_handler_arg; + } + + return handler_instance; +} + +static void handler_instance_delete(esp_event_handler_instance_t* handler_instance) +{ + free(handler_instance); +} + +// Functions that operate on handler instance list +static esp_event_handler_instance_t* handler_instances_find(esp_event_handler_instances_t* handlers, esp_event_handler_t handler) +{ + esp_event_handler_instance_t* it; + + SLIST_FOREACH(it, handlers, handler_entry) { + if (it->handler == handler) { + break; + } + } + + return it; +} + +static void handler_instances_add(esp_event_handler_instances_t* handlers, esp_event_handler_instance_t* handler_instance) +{ + SLIST_INSERT_HEAD(handlers, handler_instance, handler_entry); +} + +static void handler_instances_remove(esp_event_handler_instances_t* handlers, esp_event_handler_instance_t* handler_instance) +{ + SLIST_REMOVE(handlers, handler_instance, esp_event_handler_instance, handler_entry); + handler_instance_delete(handler_instance); +} + +static void handler_instances_remove_all(esp_event_handler_instances_t* handlers) +{ + esp_event_handler_instance_t* it; + esp_event_handler_instance_t* temp; + + SLIST_FOREACH_SAFE(it, handlers, handler_entry, temp) { + handler_instances_remove(handlers, it); + } +} + +// Functions that operate on event id instance +static void* event_id_instance_create(int32_t event_id) +{ + esp_event_id_instance_t* event_id_instance = calloc(1, sizeof(*event_id_instance)); + + if (event_id_instance != NULL) { + event_id_instance->id = event_id; + SLIST_INIT(&(event_id_instance->handlers)); + } + + return event_id_instance; +} + +static void event_id_instance_delete(esp_event_id_instance_t* event_id_instance) +{ + handler_instances_remove_all(&(event_id_instance->handlers)); + free(event_id_instance); +} + +// Functions that operate on event id instance list +static void event_id_instances_remove(esp_event_id_instances_t* head, esp_event_id_instance_t* event_id_instance) +{ + SLIST_REMOVE(head, event_id_instance, esp_event_id_instance, event_id_entry); + event_id_instance_delete(event_id_instance); +} + +// Functions that operate on event base instance +static esp_event_base_instance_t* event_base_instance_create(esp_event_base_t event_base) +{ + esp_event_base_instance_t* event_base_instance = calloc(1, sizeof(*event_base_instance)); + + if (event_base_instance != NULL) { + event_base_instance->base = event_base; + SLIST_INIT(&(event_base_instance->base_handlers)); + SLIST_INIT(&(event_base_instance->event_ids)); + } + + return event_base_instance; +} + +static void event_base_instance_delete(esp_event_base_instance_t* event_base_instance) +{ + esp_event_id_instance_t* it; + esp_event_id_instance_t* temp; + + handler_instances_remove_all(&(event_base_instance->base_handlers)); + + SLIST_FOREACH_SAFE(it, &(event_base_instance->event_ids), event_id_entry, temp) { + event_id_instances_remove(&(event_base_instance->event_ids), it); + } + + free(event_base_instance); +} + +static void event_base_instance_add_event_id_instance(esp_event_base_instance_t* event_base_instance, esp_event_id_instance_t* event_id_instance) +{ + SLIST_INSERT_HEAD(&(event_base_instance->event_ids), event_id_instance, event_id_entry); +} + +static esp_event_id_instance_t* event_base_instance_find_event_id_instance(esp_event_base_instance_t* event_base_instance, int32_t event_id) +{ + esp_event_id_instance_t* it; + + SLIST_FOREACH(it, &(event_base_instance->event_ids), event_id_entry) { + if (it->id == event_id) { + break; + } + } + + return it; +} + +// Functions that operate on event base instances list +static void event_base_instances_remove(esp_event_base_instances_t* head, esp_event_base_instance_t* event_base_instance) +{ + SLIST_REMOVE(head, event_base_instance, esp_event_base_instance, event_base_entry); + event_base_instance_delete(event_base_instance); +} + +// Functions that operate on loop instances +static void loop_add_event_base_instance(esp_event_loop_instance_t* loop, esp_event_base_instance_t* event_base_instance) { + SLIST_INSERT_HEAD(&(loop->event_bases), event_base_instance, event_base_entry); +} + +static void loop_remove_all_event_base_instance(esp_event_loop_instance_t* loop) +{ + esp_event_base_instance_t* it; + esp_event_base_instance_t* temp; + + SLIST_FOREACH_SAFE(it, &(loop->event_bases), event_base_entry, temp) { + event_base_instances_remove(&(loop->event_bases), it); + } +} + +static esp_event_base_instance_t* loop_find_event_base_instance(esp_event_loop_instance_t* loop, esp_event_base_t event_base) +{ + esp_event_base_instance_t* it; + + SLIST_FOREACH(it, &(loop->event_bases), event_base_entry) { + if (it->base == event_base) { + break; + } + } + + return it; +} + +// Functions that operate on post instance +static esp_err_t post_instance_create(esp_event_base_t event_base, int32_t event_id, void* event_data, int32_t event_data_size, esp_event_post_instance_t* post) +{ + void** event_data_copy = NULL; + + // Make persistent copy of event data on heap. + if (event_data != NULL && event_data_size != 0) { + event_data_copy = calloc(1, event_data_size); + + if (event_data_copy == NULL) { + ESP_LOGE(TAG, "alloc for post data to event %s:%d failed", event_base, event_id); + return ESP_ERR_NO_MEM; + } + + memcpy(event_data_copy, event_data, event_data_size); + } + + post->base = event_base; + post->id = event_id; + post->data = event_data_copy; + + ESP_LOGD(TAG, "created post for event %s:%d", event_base, event_id); + + return ESP_OK; +} + +static void post_instance_delete(esp_event_post_instance_t* post) +{ + free(post->data); +} + +static esp_event_handler_instances_t* find_handlers_list(esp_event_loop_instance_t* loop, esp_event_base_t event_base, + int32_t event_id) +{ + esp_event_handler_instances_t* handlers = NULL; + + esp_event_base_instance_t* base = NULL; + esp_event_id_instance_t* event = NULL; + + if (event_base == esp_event_any_base && event_id == ESP_EVENT_ANY_ID) { + handlers = &(loop->loop_handlers); + } else { + base = loop_find_event_base_instance(loop, event_base); + if (base != NULL) { + if (event_id == ESP_EVENT_ANY_ID) { + handlers = &(base->base_handlers); + } else { + event = event_base_instance_find_event_id_instance(base, event_id); + if (event != NULL) { + handlers = &(event->handlers); + } + } + } + } + + return handlers; +} + +/* ---------------------------- Public API --------------------------------- */ + +esp_err_t esp_event_loop_create(const esp_event_loop_args_t* event_loop_args, esp_event_loop_handle_t* event_loop) +{ + assert(event_loop_args); + + esp_event_loop_instance_t* loop; + esp_err_t err = ESP_ERR_NO_MEM; // most likely error + + loop = calloc(1, sizeof(*loop)); + if (loop == NULL) { + ESP_LOGE(TAG, "alloc for event loop failed"); + goto on_err; + } + + loop->queue = xQueueCreate(event_loop_args->queue_size , sizeof(esp_event_post_instance_t)); + if (loop->queue == NULL) { + ESP_LOGE(TAG, "create event loop queue failed"); + goto on_err; + } + + loop->mutex = xSemaphoreCreateRecursiveMutex(); + if (loop->mutex == NULL) { + ESP_LOGE(TAG, "create event loop mutex failed"); + goto on_err; + } + +#ifdef CONFIG_EVENT_LOOP_PROFILING + loop->profiling_mutex = xSemaphoreCreateMutex(); + if (loop->profiling_mutex == NULL) { + ESP_LOGE(TAG, "create event loop profiling mutex failed"); + goto on_err; + } +#endif + + SLIST_INIT(&(loop->loop_handlers)); + SLIST_INIT(&(loop->event_bases)); + + // Create the loop task if requested + if (event_loop_args->task_name != NULL) { + BaseType_t task_created = xTaskCreatePinnedToCore(esp_event_loop_run_task, event_loop_args->task_name, + event_loop_args->task_stack_size, (void*) loop, + event_loop_args->task_priority, &(loop->task), event_loop_args->task_core_id); + + if (task_created != pdPASS) { + ESP_LOGE(TAG, "create task for loop failed"); + err = ESP_FAIL; + goto on_err; + } + + loop->name = event_loop_args->task_name; + + ESP_LOGD(TAG, "created task for loop %p", loop); + } else { + loop->name = ""; + loop->task = NULL; + } + + loop->running_task = NULL; + +#ifdef CONFIG_EVENT_LOOP_PROFILING + portENTER_CRITICAL(&s_event_loops_spinlock); + SLIST_INSERT_HEAD(&s_event_loops, loop, loop_entry); + portEXIT_CRITICAL(&s_event_loops_spinlock); +#endif + + *event_loop = (esp_event_loop_handle_t) loop; + + ESP_LOGD(TAG, "created event loop %p", loop); + + return ESP_OK; + +on_err: + if(loop->queue != NULL) { + vQueueDelete(loop->queue); + } + + if(loop->mutex != NULL) { + vSemaphoreDelete(loop->mutex); + } + +#ifdef CONFIG_EVENT_LOOP_PROFILING + if(loop->profiling_mutex != NULL) { + vSemaphoreDelete(loop->profiling_mutex); + } +#endif + + free(loop); + + return err; +} + +// On event lookup performance: The library implements the event list as a linked list, which results to O(n) +// lookup time. The test comparing this implementation to the O(lg n) performance of rbtrees +// (https://github.com/freebsd/freebsd/blob/master/sys/sys/tree.h) +// indicate that the difference is not that substantial, especially considering the additional +// pointers per node of rbtrees. Code for the rbtree implementation of the event loop library is archived +// in feature/esp_event_loop_library_rbtrees if needed. +esp_err_t esp_event_loop_run(esp_event_loop_handle_t event_loop, TickType_t ticks_to_run) +{ + assert(event_loop); + + esp_event_loop_instance_t* loop = (esp_event_loop_instance_t*) event_loop; + esp_event_post_instance_t post; + TickType_t marker = xTaskGetTickCount(); + TickType_t end = 0; + esp_event_handler_instance_t* temp; + +#if( configUSE_16_BIT_TICKS == 1 ) + int32_t remaining_ticks = ticks_to_run; +#else + int64_t remaining_ticks = ticks_to_run; +#endif + + while(xQueueReceive(loop->queue, &post, ticks_to_run) == pdTRUE) { + esp_event_base_instance_t* base = NULL; + esp_event_id_instance_t* event = NULL; + + // Reserve space for three possible matches: (1) the entry for handlers registered to all events in the loop, the + // (2) entry matching events with a specified base and (3) the entry matching both base and id. + #define LOOP_LEVEL_HANDLER 0 + #define BASE_LEVEL_HANDLER 1 + #define EVENT_LEVEL_HANDLER 2 + + esp_event_handler_instances_t* handlers_list[EVENT_LEVEL_HANDLER + 1] = {0}; + + // The event has already been unqueued, so ensure it gets executed. + xSemaphoreTakeRecursive(loop->mutex, portMAX_DELAY); + + loop->running_task = xTaskGetCurrentTaskHandle(); + + handlers_list[LOOP_LEVEL_HANDLER] = &(loop->loop_handlers); + + base = loop_find_event_base_instance(loop, post.base); + if (base) { + event = event_base_instance_find_event_id_instance(base, post.id); + handlers_list[BASE_LEVEL_HANDLER] = &(base->base_handlers); + if (event) { + handlers_list[EVENT_LEVEL_HANDLER] = &(event->handlers); + } + } + + bool exec = false; + + for (int i = LOOP_LEVEL_HANDLER; i <= EVENT_LEVEL_HANDLER; i++) { + if (handlers_list[i] != NULL) { + esp_event_handler_instance_t* it; + + SLIST_FOREACH_SAFE(it, handlers_list[i], handler_entry, temp) { + ESP_LOGD(TAG, "running post %s:%d with handler %p on loop %p", post.base, post.id, it->handler, event_loop); + +#ifdef CONFIG_EVENT_LOOP_PROFILING + int64_t start, diff; + start = esp_timer_get_time(); +#endif + // Execute the handler + (*(it->handler))(it->arg, post.base, post.id, post.data); + +#ifdef CONFIG_EVENT_LOOP_PROFILING + diff = esp_timer_get_time() - start; + + xSemaphoreTake(loop->profiling_mutex, portMAX_DELAY); + + it->total_times_invoked++; + it->total_runtime += diff; + + if (i == LOOP_LEVEL_HANDLER) { + loop->loop_handlers_invoked++; + loop->loop_handlers_runtime += diff; + } else if (i == BASE_LEVEL_HANDLER) { + base->base_handlers_invoked++; + base->base_handlers_runtime += diff; + } else { + event->handlers_invoked++; + event->handlers_runtime += diff; + } + + loop->total_handlers_invoked++; + loop->total_handlers_runtime += diff; + + xSemaphoreGive(loop->profiling_mutex); +#endif + } + } + exec |= true; + } + + if (ticks_to_run != portMAX_DELAY) { + end = xTaskGetTickCount(); + remaining_ticks -= end - marker; + // If the ticks to run expired, return to the caller + if (remaining_ticks <= 0) { + xSemaphoreGiveRecursive(loop->mutex); + break; + } else { + marker = end; + } + } + + loop->running_task = NULL; + + xSemaphoreGiveRecursive(loop->mutex); + + if (!exec) { + // No handlers were registered, not even loop/base level handlers + ESP_LOGW(TAG, "no handlers have been registered for event %s:%d posted to loop %p", post.base, post.id, event_loop); + } + } + + return ESP_OK; +} + +esp_err_t esp_event_loop_delete(esp_event_loop_handle_t event_loop) +{ + assert(event_loop); + + esp_event_loop_instance_t* loop = (esp_event_loop_instance_t*) event_loop; + SemaphoreHandle_t loop_mutex = loop->mutex; + + xSemaphoreTakeRecursive(loop->mutex, portMAX_DELAY); + +#ifdef CONFIG_EVENT_LOOP_PROFILING + portENTER_CRITICAL(&s_event_loops_spinlock); + SLIST_REMOVE(&s_event_loops, loop, esp_event_loop_instance, loop_entry); + portEXIT_CRITICAL(&s_event_loops_spinlock); +#endif + + // Delete the task if it was created + if (loop->task != NULL) { + vTaskDelete(loop->task); + } + + // Remove all registered events in the loop + handler_instances_remove_all(&(loop->loop_handlers)); + loop_remove_all_event_base_instance(loop); + + // Drop existing posts on the queue + esp_event_post_instance_t post; + while(xQueueReceive(loop->queue, &post, 0) == pdTRUE) { + free(post.data); + } + + // Cleanup loop + vQueueDelete(loop->queue); + free(loop); + // Free loop mutex before deleting + xSemaphoreGiveRecursive(loop_mutex); + vSemaphoreDelete(loop_mutex); + + ESP_LOGD(TAG, "deleted loop %p", (void*) event_loop); + + return ESP_OK; +} + +esp_err_t esp_event_handler_register_with(esp_event_loop_handle_t event_loop, esp_event_base_t event_base, + int32_t event_id, esp_event_handler_t event_handler, void* event_handler_arg) +{ + assert(event_loop); + assert(event_handler); + + if (event_base == ESP_EVENT_ANY_BASE && event_id != ESP_EVENT_ANY_ID) { + ESP_LOGE(TAG, "registering to any event base with specific id unsupported"); + return ESP_ERR_INVALID_ARG; + } + + esp_event_loop_instance_t* loop = (esp_event_loop_instance_t*) event_loop; + + esp_event_base_instance_t* base = NULL; + esp_event_id_instance_t* event = NULL; + esp_event_handler_instance_t* handler = NULL; + esp_event_handler_instances_t* handlers = NULL; + + bool base_created = false; + bool event_created = false; + + if (event_base == ESP_EVENT_ANY_BASE) { + event_base = esp_event_any_base; + } + + xSemaphoreTakeRecursive(loop->mutex, portMAX_DELAY); + + if (event_base == esp_event_any_base && event_id == ESP_EVENT_ANY_ID) { + // Add to the loop-level handlers + handlers = &(loop->loop_handlers); + } else { + // If base instance does not exist, create one + if ((base = loop_find_event_base_instance(loop, event_base)) == NULL) { + base = event_base_instance_create(event_base); + if (base == NULL) { + xSemaphoreGiveRecursive(loop->mutex); + return ESP_ERR_NO_MEM; + } + base_created = true; + } + // Add to the event base instance level handlers + if (event_id == ESP_EVENT_ANY_ID) { + handlers = &(base->base_handlers); + } else { + if (base_created || + (event = event_base_instance_find_event_id_instance(base, event_id)) == NULL) { + event = event_id_instance_create(event_id); + // If it does not exist, create one + if (event == NULL) { + if (base_created) { + event_base_instance_delete(base); + } + xSemaphoreGiveRecursive(loop->mutex); + return ESP_ERR_NO_MEM; + } + event_created = true; + } + // Add to the event id instance level handlers + handlers = &(event->handlers); + } + } + + // Add handler to the list + if (base_created || event_created || + (handler = handler_instances_find(handlers, event_handler)) == NULL) { + handler = handler_instance_create(event_handler, event_handler_arg); + if (handler == NULL) { + if (event_created) { + event_id_instance_delete(event); + } + if (base_created) { + event_base_instance_delete(base); + } + xSemaphoreGiveRecursive(loop->mutex); + return ESP_ERR_NO_MEM; + } + handler_instances_add(handlers, handler); + // If a new event base/ event id instance was created, add them to the appropriate list + if (event_created) { + event_base_instance_add_event_id_instance(base, event); + } + if (base_created) { + loop_add_event_base_instance(loop, base); + } + ESP_LOGD(TAG, "registered handler %p for event %s:%d", event_handler, event_base, event_id); + } else { + handler->arg = event_handler_arg; + ESP_LOGW(TAG, "handler %p for event %s:%d already registered, overwriting", event_handler, event_base, event_id); + } + + xSemaphoreGiveRecursive(loop->mutex); + + return ESP_OK; +} + + +esp_err_t esp_event_handler_unregister_with(esp_event_loop_handle_t event_loop, esp_event_base_t event_base, + int32_t event_id, esp_event_handler_t event_handler) +{ + assert(event_loop); + assert(event_handler); + + if (event_base == ESP_EVENT_ANY_BASE && event_id != ESP_EVENT_ANY_ID) { + ESP_LOGE(TAG, "unregistering to any event base with specific id unsupported"); + return ESP_FAIL; + } + + if (event_base == ESP_EVENT_ANY_BASE) { + event_base = esp_event_any_base; + } + + esp_event_loop_instance_t* loop = (esp_event_loop_instance_t*) event_loop; + + esp_event_handler_instance_t* handler = NULL; + esp_event_handler_instances_t* handlers = find_handlers_list(loop, event_base, event_id); + + xSemaphoreTakeRecursive(loop->mutex, portMAX_DELAY); + + if (handlers != NULL && + (handler = handler_instances_find(handlers, event_handler)) != NULL) { + handler_instances_remove(handlers, handler); + ESP_LOGD(TAG, "unregistered handler %p from event %s:%d", event_handler, event_base, event_id); + } else { + ESP_LOGW(TAG, "handler %p for event %s:%d not registered, ignoring", event_handler, event_base, event_id); + } + + xSemaphoreGiveRecursive(loop->mutex); + + return ESP_OK; +} + + +esp_err_t esp_event_post_to(esp_event_loop_handle_t event_loop, esp_event_base_t event_base, int32_t event_id, + void* event_data, size_t event_data_size, TickType_t ticks_to_wait) +{ + assert(event_loop); + + if (event_base == ESP_EVENT_ANY_BASE || event_id == ESP_EVENT_ANY_ID) { + ESP_LOGE(TAG, "posting nonspecific event base or id unsupported"); + return ESP_ERR_INVALID_ARG; + } + + esp_event_loop_instance_t* loop = (esp_event_loop_instance_t*) event_loop; + + esp_event_post_instance_t post; + esp_err_t err = post_instance_create(event_base, event_id, event_data, event_data_size, &post); + + if (err != ESP_OK) { + return err; + } + + BaseType_t result = pdFALSE; + + // Find the task that currently executes the loop. It is safe to query loop->task since it is + // not mutated since loop creation. ENSURE THIS REMAINS TRUE. + if (loop->task == NULL) { + // The loop has no dedicated task. Find out what task is currently running it. + result = xSemaphoreTakeRecursive(loop->mutex, ticks_to_wait); + + if (result == pdTRUE) { + if (loop->running_task != xTaskGetCurrentTaskHandle()) { + xSemaphoreGiveRecursive(loop->mutex); + result = xQueueSendToBack(loop->queue, &post, ticks_to_wait); + } else { + xSemaphoreGiveRecursive(loop->mutex); + result = xQueueSendToBack(loop->queue, &post, 0); + } + } + } else { + // The loop has a dedicated task. + if (loop->task != xTaskGetCurrentTaskHandle()) { + result = xQueueSendToBack(loop->queue, &post, ticks_to_wait); + } else { + result = xQueueSendToBack(loop->queue, &post, 0); + } + } + + if (result != pdTRUE) { + post_instance_delete(&post); + +#ifdef CONFIG_EVENT_LOOP_PROFILING + xSemaphoreTake(loop->profiling_mutex, portMAX_DELAY); + loop->events_dropped++; + xSemaphoreGive(loop->profiling_mutex); +#endif + return ESP_ERR_TIMEOUT; + } + +#ifdef CONFIG_EVENT_LOOP_PROFILING + xSemaphoreTake(loop->profiling_mutex, portMAX_DELAY); + loop->events_recieved++; + xSemaphoreGive(loop->profiling_mutex); +#endif + + ESP_LOGD(TAG, "posted %s:%d to loop %p", post.base, post.id, event_loop); + + return ESP_OK; +} + +esp_err_t esp_event_dump(FILE* file) +{ +#ifdef CONFIG_EVENT_LOOP_PROFILING + assert(file); + + esp_event_loop_instance_t* loop_it; + esp_event_base_instance_t* base_it; + esp_event_id_instance_t* id_it; + esp_event_handler_instance_t* handler_it; + + // Allocate memory for printing + int sz = esp_event_dump_prepare(); + char* buf = calloc(sz, sizeof(char)); + char* dst = buf; + + // Print info to buffer + portENTER_CRITICAL(&s_event_loops_spinlock); + SLIST_FOREACH(loop_it, &s_event_loops, loop_entry) { + PRINT_DUMP_INFO(dst, sz, LOOP_DUMP_FORMAT, loop_it, loop_it->name, loop_it->events_recieved, + loop_it->events_dropped, loop_it->total_handlers_invoked, loop_it->total_handlers_runtime); + + // Print loop-level handler + PRINT_DUMP_INFO(dst, sz, esp_event_any_base, ESP_EVENT_ANY_ID, loop_it->loop_handlers_invoked, + loop_it->loop_handlers_runtime); + SLIST_FOREACH(handler_it, &(loop_it->loop_handlers), handler_entry) { + PRINT_DUMP_INFO(dst, sz, HANDLER_DUMP_FORMAT, handler_it->handler, handler_it->total_times_invoked, + handler_it->total_runtime); + } + + SLIST_FOREACH(base_it, &(loop_it->event_bases), event_base_entry) { + // Print base-level handler + PRINT_DUMP_INFO(dst, sz, EVENT_DUMP_FORMAT, base_it->base, ESP_EVENT_ANY_ID, + base_it->base_handlers_invoked, base_it->base_handlers_runtime); + SLIST_FOREACH(handler_it, &(base_it->base_handlers), handler_entry) { + PRINT_DUMP_INFO(dst, sz, HANDLER_DUMP_FORMAT, handler_it->handler, + handler_it->total_times_invoked, handler_it->total_runtime); + } + + // Print event-level handlers + SLIST_FOREACH(id_it, &(base_it->event_ids), event_id_entry) { + PRINT_DUMP_INFO(dst, sz, EVENT_DUMP_FORMAT, base_it->base, id_it->id, + id_it->handlers_invoked, id_it->handlers_runtime); + + SLIST_FOREACH(handler_it, &(id_it->handlers), handler_entry) { + PRINT_DUMP_INFO(dst, sz, HANDLER_DUMP_FORMAT, handler_it->handler, + handler_it->total_times_invoked, handler_it->total_runtime); + } + } + } + } + portEXIT_CRITICAL(&s_event_loops_spinlock); + + // Print the contents of the buffer to the file + fprintf(file, buf); + + // Free the allocated buffer + free(buf); +#endif + return ESP_OK; +} diff --git a/components/esp_event/esp_event_private.c b/components/esp_event/esp_event_private.c new file mode 100644 index 000000000..8e14284c7 --- /dev/null +++ b/components/esp_event/esp_event_private.c @@ -0,0 +1,44 @@ +// Copyright 2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "esp_event_private.h" +#include "esp_event_internal.h" + +#include "esp_log.h" + +bool esp_event_is_handler_registered(esp_event_loop_handle_t event_loop, esp_event_base_t event_base, int32_t event_id, esp_event_handler_t event_handler) +{ + esp_event_loop_instance_t* loop = (esp_event_loop_instance_t*) event_loop; + + bool result = false; + xSemaphoreTake(loop->mutex, portMAX_DELAY); + + esp_event_base_instance_t* base_it; + SLIST_FOREACH(base_it, &(loop->event_bases), event_base_entry) { + esp_event_id_instance_t* event_it; + SLIST_FOREACH(event_it, &(base_it->event_ids), event_id_entry) { + esp_event_handler_instance_t* handler_it; + SLIST_FOREACH(handler_it, &(event_it->handlers), handler_entry) { + if (base_it->base == event_base && event_it->id == event_id && handler_it->handler == event_handler) { + result = true; + goto out; + } + } + } + } + +out: + xSemaphoreGive(loop->mutex); + return result; +} \ No newline at end of file diff --git a/components/esp_event/include/esp_event.h b/components/esp_event/include/esp_event.h new file mode 100644 index 000000000..f095844a7 --- /dev/null +++ b/components/esp_event/include/esp_event.h @@ -0,0 +1,336 @@ +// Copyright 2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef ESP_EVENT_H_ +#define ESP_EVENT_H_ + +#include "esp_err.h" + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/queue.h" +#include "freertos/semphr.h" + +#include "esp_event_base.h" +#include "esp_event_legacy.h" + +#ifdef __cplusplus +extern "C" { +#endif + + +/// Configuration for creating event loops +typedef struct { + int32_t queue_size; /**< size of the event loop queue */ + const char* task_name; /**< name of the event loop task; if NULL, + a dedicated task is not created for event loop*/ + UBaseType_t task_priority; /**< priority of the event loop task, ignored if task name is NULL */ + uint32_t task_stack_size; /**< stack size of the event loop task, ignored if task name is NULL */ + BaseType_t task_core_id; /**< core to which the event loop task is pinned to, + ignored if task name is NULL */ +} esp_event_loop_args_t; + +/** + * @brief Create a new event loop. + * + * @param[in] event_loop_args configuration structure for the event loop to create + * @param[out] event_loop handle to the created event loop + * + * @return + * - ESP_OK: Success + * - ESP_ERR_NO_MEM: Cannot allocate memory for event loops list + * - ESP_FAIL: Failed to create task loop + * - Others: Fail + */ +esp_err_t esp_event_loop_create(const esp_event_loop_args_t* event_loop_args, esp_event_loop_handle_t* event_loop); + +/** + * @brief Delete an existing event loop. + * + * @param[in] event_loop event loop to delete + * + * @return + * - ESP_OK: Success + * - Others: Fail + */ +esp_err_t esp_event_loop_delete(esp_event_loop_handle_t event_loop); + +/** + * @brief Create default event loop + * + * @return + * - ESP_OK: Success + * - ESP_ERR_NO_MEM: Cannot allocate memory for event loops list + * - ESP_FAIL: Failed to create task loop + * - Others: Fail + */ +esp_err_t esp_event_loop_create_default(); + +/** + * @brief Delete the default event loop + * + * @return + * - ESP_OK: Success + * - Others: Fail + */ +esp_err_t esp_event_loop_delete_default(); + +/** + * @brief Dispatch events posted to an event loop. + * + * This function is used to dispatch events posted to a loop with no dedicated task, i.e task name was set to NULL + * in event_loop_args argument during loop creation. This function includes an argument to limit the amount of time + * it runs, returning control to the caller when that time expires (or some time afterwards). There is no guarantee + * that a call to this function will exit at exactly the time of expiry. There is also no guarantee that events have + * been dispatched during the call, as the function might have spent all of the alloted time waiting on the event queue. + * Once an event has been unqueued, however, it is guaranteed to be dispatched. This guarantee contributes to not being + * able to exit exactly at time of expiry as (1) blocking on internal mutexes is necessary for dispatching the unqueued + * event, and (2) during dispatch of the unqueued event there is no way to control the time occupied by handler code + * execution. The guaranteed time of exit is therefore the alloted time + amount of time required to dispatch + * the last unqueued event. + * + * In cases where waiting on the queue times out, ESP_OK is returned and not ESP_ERR_TIMEOUT, since it is + * normal behavior. + * + * @param[in] event_loop event loop to dispatch posted events from + * @param[in] ticks_to_run number of ticks to run the loop + * + * @note encountering an unknown event that has been posted to the loop will only generate a warning, not an error. + * + * @return + * - ESP_OK: Success + * - Others: Fail + */ +esp_err_t esp_event_loop_run(esp_event_loop_handle_t event_loop, TickType_t ticks_to_run); + +/** + * @brief Register an event handler to the system event loop. + * + * This function can be used to register a handler for either: (1) specific events, + * (2) all events of a certain event base, or (3) all events known by the system event loop. + * + * - specific events: specify exact event_base and event_id + * - all events of a certain base: specify exact event_base and use ESP_EVENT_ANY_ID as the event_id + * - all events known by the loop: use ESP_EVENT_ANY_BASE for event_base and ESP_EVENT_ANY_ID as the event_id + * + * Registering multiple handlers to events is possible. Registering a single handler to multiple events is + * also possible. However, registering the same handler to the same event multiple times would cause the + * previous registrations to be overwritten. + * + * @param[in] event_base the base id of the event to register the handler for + * @param[in] event_id the id of the event to register the handler for + * @param[in] event_handler the handler function which gets called when the event is dispatched + * @param[in] event_handler_arg data, aside from event data, that is passed to the handler when it is called + * + * @note the event loop library does not maintain a copy of event_handler_arg, therefore the user should + * ensure that event_handler_arg still points to a valid location by the time the handler gets called + * + * @return + * - ESP_OK: Success + * - ESP_ERR_NO_MEM: Cannot allocate memory for the handler + * - ESP_ERR_INVALIG_ARG: Invalid combination of event base and event id + * - Others: Fail + */ +esp_err_t esp_event_handler_register(esp_event_base_t event_base, + int32_t event_id, + esp_event_handler_t event_handler, + void* event_handler_arg); + +/** + * @brief Register an event handler to a specific loop. + * + * This function behaves in the same manner as esp_event_handler_register, except the additional + * specification of the event loop to register the handler to. + * + * @param[in] event_loop the event loop to register this handler function to + * @param[in] event_base the base id of the event to register the handler for + * @param[in] event_id the id of the event to register the handler for + * @param[in] event_handler the handler function which gets called when the event is dispatched + * @param[in] event_handler_arg data, aside from event data, that is passed to the handler when it is called + * + * @return + * - ESP_OK: Success + * - ESP_ERR_NO_MEM: Cannot allocate memory for the handler + * - ESP_ERR_INVALIG_ARG: Invalid combination of event base and event id + * - Others: Fail + */ +esp_err_t esp_event_handler_register_with(esp_event_loop_handle_t event_loop, + esp_event_base_t event_base, + int32_t event_id, + esp_event_handler_t event_handler, + void* event_handler_arg); + +/** + * @brief Unregister a handler with the system event loop. + * + * This function can be used to unregister a handler so that it no longer gets called during dispatch. + * Handlers can be unregistered for either: (1) specific events, (2) all events of a certain event base, + * or (3) all events known by the system event loop + * + * - specific events: specify exact event_base and event_id + * - all events of a certain base: specify exact event_base and use ESP_EVENT_ANY_ID as the event_id + * - all events known by the loop: use ESP_EVENT_ANY_BASE for event_base and ESP_EVENT_ANY_ID as the event_id + * + * This function ignores unregistration of handlers that has not been previously registered. + * + * @param[in] event_base the base of the event with which to unregister the handler + * @param[in] event_id the id of the event with which to unregister the handler + * @param[in] event_handler the handler to unregister + * + * @return ESP_OK success + * @return ESP_ERR_INVALIG_ARG invalid combination of event base and event id + * @return others fail + */ +esp_err_t esp_event_handler_unregister(esp_event_base_t event_base, int32_t event_id, esp_event_handler_t event_handler); + +/** + * @brief Unregister a handler with the system event loop. + * + * This function behaves in the same manner as esp_event_handler_unregister, except the additional specification of + * the event loop to unregister the handler with. + * + * @param[in] event_loop the event loop with which to unregister this handler function + * @param[in] event_base the base of the event with which to unregister the handler + * @param[in] event_id the id of the event with which to unregister the handler + * @param[in] event_handler the handler to unregister + * + * @return + * - ESP_OK: Success + * - ESP_ERR_INVALIG_ARG: Invalid combination of event base and event id + * - Others: Fail + */ +esp_err_t esp_event_handler_unregister_with(esp_event_loop_handle_t event_loop, + esp_event_base_t event_base, + int32_t event_id, + esp_event_handler_t event_handler); + +/** + * @brief Posts an event to the system default event loop. The event loop library keeps a copy of event_data and manages + * the copy's lifetime automatically (allocation + deletion); this ensures that the data the + * handler recieves is always valid. + * + * @param[in] event_base the event base that identifies the event + * @param[in] event_id the the event id that identifies the event + * @param[in] event_data the data, specific to the event occurence, that gets passed to the handler + * @param[in] event_data_size the size of the event data + * @param[in] ticks_to_wait number of ticks to block on a full event queue + * + * @note posting events from an ISR is not supported + * + * @return + * - ESP_OK: Success + * - ESP_ERR_TIMEOUT: Time to wait for event queue to unblock expired + * - ESP_ERR_INVALIG_ARG: Invalid combination of event base and event id + * - Others: Fail + */ +esp_err_t esp_event_post(esp_event_base_t event_base, + int32_t event_id, + void* event_data, + size_t event_data_size, + TickType_t ticks_to_wait); + +/** + * @brief Posts an event to the specified event loop. The event loop library keeps a copy of event_data and manages + * the copy's lifetime automatically (allocation + deletion); this ensures that the data the + * handler recieves is always valid. + * + * This function behaves in the same manner as esp_event_post_to, except the additional specification of the event loop + * to post the event to. + * + * @param[in] event_loop the event loop to post to + * @param[in] event_base the event base that identifies the event + * @param[in] event_id the the event id that identifies the event + * @param[in] event_data the data, specific to the event occurence, that gets passed to the handler + * @param[in] event_data_size the size of the event data + * @param[in] ticks_to_wait number of ticks to block on a full event queue + * + * @note posting events from an ISR is not supported + * + * @return + * - ESP_OK: Success + * - ESP_ERR_TIMEOUT: Time to wait for event queue to unblock expired + * - ESP_ERR_INVALIG_ARG: Invalid combination of event base and event id + * - Others: Fail + */ +esp_err_t esp_event_post_to(esp_event_loop_handle_t event_loop, + esp_event_base_t event_base, + int32_t event_id, + void* event_data, + size_t event_data_size, + TickType_t ticks_to_wait); + +/** + * @brief Dumps statistics of all event loops. + * + * Dumps event loop info in the format: + * + @verbatim + event loop + event + handler + handler + event + handler + handler + event loop + event + handler + ... + ... + ... + + where: + + event loop + format: address,name rx:total_recieved dr:total_dropped inv:total_number_of_invocations run:total_runtime + where: + address - memory address of the event loop + name - name of the event loop + total_recieved - number of successfully posted events + total_number_of_invocations - total number of handler invocations performed so far + total_runtime - total runtime of all invocations so far + + event + format: base:id proc:total_processed run:total_runtime + where: + base - event base + id - event id + total_processed - number of instances of this event that has been processed + total_runtime - total amount of time in microseconds used for invoking handlers of this event + + handler + format: address inv:total_invoked run:total_runtime + where: + address - address of the handler function + total_invoked - number of times this handler has been invoked + total_runtime - total amount of time used for invoking this handler + + @endverbatim + * + * @param[in] file the file stream to output to + * + * @note this function is a noop when CONFIG_EVENT_LOOP_PROFILING is disabled + * + * @return + * - ESP_OK: Success + * - ESP_ERR_NO_MEM: Cannot allocate memory for event loops list + * - Others: Fail + */ +esp_err_t esp_event_dump(FILE* file); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // #ifndef ESP_EVENT_H_ \ No newline at end of file diff --git a/components/esp_event/include/esp_event_base.h b/components/esp_event/include/esp_event_base.h new file mode 100644 index 000000000..d2fd38042 --- /dev/null +++ b/components/esp_event/include/esp_event_base.h @@ -0,0 +1,43 @@ +// Copyright 2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef ESP_EVENT_BASE_H_ +#define ESP_EVENT_BASE_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +// Defines for declaring and defining event base +#define ESP_EVENT_DECLARE_BASE(id) extern esp_event_base_t id; +#define ESP_EVENT_DEFINE_BASE(id) esp_event_base_t id = #id; + +// Event loop library types +typedef const char* esp_event_base_t; /**< unique pointer to a subsystem that exposes events */ +typedef void* esp_event_loop_handle_t; /**< a number that identifies an event with respect to a base */ +typedef void (*esp_event_handler_t)(void* event_handler_arg, + esp_event_base_t event_base, + int32_t event_id, + void* event_data); /**< function called when an event is posted to the queue */ + + +// Defines for registering/unregistering event handlers +#define ESP_EVENT_ANY_BASE NULL /**< register handler for any event base */ +#define ESP_EVENT_ANY_ID -1 /**< register handler for any event id */ + +#ifdef __cplusplus +} +#endif + +#endif // #ifndef ESP_EVENT_BASE_H_ diff --git a/components/esp_event/private_include/esp_event_internal.h b/components/esp_event/private_include/esp_event_internal.h new file mode 100644 index 000000000..28cd88203 --- /dev/null +++ b/components/esp_event/private_include/esp_event_internal.h @@ -0,0 +1,100 @@ +// Copyright 2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef ESP_EVENT_INTERNAL_H_ +#define ESP_EVENT_INTERNAL_H_ + +#include "esp_event.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/// Event handler +typedef struct esp_event_handler_instance { + esp_event_handler_t handler; /**< event handler function*/ + void* arg; /**< event handler argument */ +#ifdef CONFIG_EVENT_LOOP_PROFILING + uint32_t total_times_invoked; /**< number of times this handler has been invoked */ + int64_t total_runtime; /**< total runtime of this handler across all calls */ +#endif + SLIST_ENTRY(esp_event_handler_instance) handler_entry; /**< next event handler in the list */ +} esp_event_handler_instance_t; + +typedef SLIST_HEAD(esp_event_handler_instances, esp_event_handler_instance) esp_event_handler_instances_t; + +typedef struct esp_event_id_instance { + int32_t id; + esp_event_handler_instances_t handlers; /**< list of handlers to be executed when + this event is raised */ + SLIST_ENTRY(esp_event_id_instance) event_id_entry; /**< pointer to the next event node on the linked list */ +#ifdef CONFIG_EVENT_LOOP_PROFILING + uint32_t handlers_invoked; /**< total number of times the event has been + raised and processed in the loop */ + int64_t handlers_runtime; /**< total time spent in executing handlers */ +#endif +} esp_event_id_instance_t; + +typedef SLIST_HEAD(esp_event_id_instances, esp_event_id_instance) esp_event_id_instances_t; + +/// Event +typedef struct esp_event_base_instance { + esp_event_base_t base; /**< base identifier of the event */ + esp_event_handler_instances_t base_handlers; /**< event base level handlers, handlers for + all events with this base */ + esp_event_id_instances_t event_ids; /**< list of event ids with this base */ + SLIST_ENTRY(esp_event_base_instance) event_base_entry; /**< pointer to the next event node on the linked list */ +#ifdef CONFIG_EVENT_LOOP_PROFILING + uint32_t base_handlers_invoked; /**< total number of base-level handlers invoked */ + int64_t base_handlers_runtime; /**< amount of time processing base-level handlers */ +#endif +} esp_event_base_instance_t; + +typedef SLIST_HEAD(esp_event_base_instances, esp_event_base_instance) esp_event_base_instances_t; + +/// Event loop +typedef struct esp_event_loop_instance { + const char* name; /**< name of this event loop */ + QueueHandle_t queue; /**< event queue */ + TaskHandle_t task; /**< task that consumes the event queue */ + TaskHandle_t running_task; /**< for loops with no dedicated task, the + task that consumes the queue */ + SemaphoreHandle_t mutex; /**< mutex for updating the events linked list */ + esp_event_handler_instances_t loop_handlers; /**< loop level handlers, handlers for all events + registered in the loop */ + esp_event_base_instances_t event_bases; /**< events linked list head pointer */ +#ifdef CONFIG_EVENT_LOOP_PROFILING + uint32_t events_recieved; /**< number of events successfully posted to the loop */ + uint32_t events_dropped; /**< number of events dropped due to queue being full */ + uint32_t loop_handlers_invoked; /**< total number of loop-level handlers invoked */ + int64_t loop_handlers_runtime; /**< amount of time processing loop-level handlers */ + uint32_t total_handlers_invoked; /**< total number of handlers invoked */ + int64_t total_handlers_runtime; /**< total amount of time dedicated to processing this loop */ + SLIST_ENTRY(esp_event_loop_instance) loop_entry; /**< next event loop in the list */ + SemaphoreHandle_t profiling_mutex; +#endif +} esp_event_loop_instance_t; + +/// Event posted to the event queue +typedef struct esp_event_post_instance { + esp_event_base_t base; /**< the event base */ + int32_t id; /**< the event id */ + void** data; /**< data associated with the event */ +} esp_event_post_instance_t; + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // #ifndef ESP_EVENT_INTERNAL_H_ \ No newline at end of file diff --git a/components/esp_event/private_include/esp_event_private.h b/components/esp_event/private_include/esp_event_private.h new file mode 100644 index 000000000..990d51872 --- /dev/null +++ b/components/esp_event/private_include/esp_event_private.h @@ -0,0 +1,54 @@ +// Copyright 2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef ESP_EVENT_PRIVATE_H_ +#define ESP_EVENT_PRIVATE_H_ + +#include "esp_event.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Searches handlers registered with an event loop to see if it has been registered. + * + * @param[in] event_loop the loop to search + * @param[in] event_base the event base to search + * @param[in] event_id the event id to search + * @param[in] event_handler the event handler to look for + * + * @return true handler registered + * @return false handler not registered + * + * @return + * - true: Handler registered + * - false: Handler not registered + */ +bool esp_event_is_handler_registered(esp_event_loop_handle_t event_loop, esp_event_base_t event_base, int32_t event_id, esp_event_handler_t event_handler); + +/** + * @brief Deinitializes the event loop library + * + * @return + * - ESP_OK: Success + * - Others: Fail + */ +esp_err_t esp_event_loop_deinit(); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // #ifndef ESP_EVENT_PRIVATE_H_ \ No newline at end of file diff --git a/components/esp_event/test/CMakeLists.txt b/components/esp_event/test/CMakeLists.txt new file mode 100644 index 000000000..5e302bed4 --- /dev/null +++ b/components/esp_event/test/CMakeLists.txt @@ -0,0 +1,5 @@ +set(COMPONENT_SRCDIRS ".") +set(COMPONENT_PRIV_INCLUDEDIRS "../private_include" ".") +set(COMPONENT_PRIV_REQUIRES unity esp_event) + +register_component() \ No newline at end of file diff --git a/components/esp_event/test/component.mk b/components/esp_event/test/component.mk new file mode 100644 index 000000000..22e49eddd --- /dev/null +++ b/components/esp_event/test/component.mk @@ -0,0 +1,5 @@ +# +#Component Makefile +# +COMPONENT_PRIV_INCLUDEDIRS := ../private_include . +COMPONENT_ADD_LDFLAGS = -Wl,--whole-archive -l$(COMPONENT_NAME) -Wl,--no-whole-archive \ No newline at end of file diff --git a/components/esp_event/test/test_event.c b/components/esp_event/test/test_event.c new file mode 100644 index 000000000..afad5b1ea --- /dev/null +++ b/components/esp_event/test/test_event.c @@ -0,0 +1,1077 @@ +#include +#include + +#include "esp_event.h" +#include "sdkconfig.h" + +#include "freertos/FreeRTOS.h" +#include "esp_event_loop.h" +#include "freertos/task.h" +#include "esp_log.h" + +#include "esp_event.h" +#include "esp_event_private.h" +#include "esp_event_internal.h" + +#include "esp_heap_caps.h" + +#include "sdkconfig.h" +#include "unity.h" + +#include "idf_performance.h" + +static const char* TAG = "test_event"; + +#define TEST_CONFIG_ITEMS_TO_REGISTER 5 +#define TEST_CONFIG_TASKS_TO_SPAWN 2 + +#define TEST_CONFIG_WAIT_MULTIPLIER 5 + +#define TEST_SETUP() \ + test_setup(); \ + s_test_core_id = xPortGetCoreID(); \ + s_test_priority = uxTaskPriorityGet(NULL); \ + +#define TEST_TEARDOWN() \ + test_teardown(); \ + vTaskDelay(pdMS_TO_TICKS(CONFIG_INT_WDT_TIMEOUT_MS * TEST_CONFIG_WAIT_MULTIPLIER)); + +typedef struct { + void* data; + SemaphoreHandle_t start; + SemaphoreHandle_t done; +} task_arg_t; + +typedef struct { + esp_event_base_t base; + int32_t id; + esp_event_handler_t* handles; + int32_t num; + esp_event_loop_handle_t loop; + bool is_registration; +} handler_registration_data_t; + +typedef struct { + esp_event_base_t base; + int32_t id; + esp_event_loop_handle_t loop; + int32_t num; +} post_event_data_t; + +typedef struct { + int performed; + int expected; + SemaphoreHandle_t done; +} performance_data_t; + +typedef struct { + void* data; + SemaphoreHandle_t mutex; +} simple_arg_t; + +static BaseType_t s_test_core_id; +static UBaseType_t s_test_priority; + +ESP_EVENT_DECLARE_BASE(s_test_base1); +ESP_EVENT_DECLARE_BASE(s_test_base2); + +ESP_EVENT_DEFINE_BASE(s_test_base1); +ESP_EVENT_DEFINE_BASE(s_test_base2); + +enum { + TEST_EVENT_BASE1_EV1, + TEST_EVENT_BASE1_EV2, + TEST_EVENT_BASE1_MAX +}; + +enum { + TEST_EVENT_BASE2_EV1, + TEST_EVENT_BASE2_EV2, + TEST_EVENT_BASE2_MAX +}; + +static BaseType_t test_event_get_core() +{ + static int calls = 0; + + if (portNUM_PROCESSORS > 1) { + return (s_test_core_id + calls++) % portNUM_PROCESSORS; + } else { + return s_test_core_id; + } +} + +static esp_event_loop_args_t test_event_get_default_loop_args() +{ + esp_event_loop_args_t loop_config = { + .queue_size = CONFIG_SYSTEM_EVENT_QUEUE_SIZE, + .task_name = "loop", + .task_priority = s_test_priority, + .task_stack_size = 2048, + .task_core_id = test_event_get_core() + }; + + return loop_config; +} + +static void test_event_simple_handler(void* event_handler_arg, esp_event_base_t event_base, int32_t event_id, void* event_data) +{ + simple_arg_t* arg = (simple_arg_t*) event_handler_arg; + xSemaphoreTake(arg->mutex, portMAX_DELAY); + + int* count = (int*) arg->data; + + if (event_data == NULL) { + (*count)++; + } else { + (*count) += *((int*) event_data); + } + + xSemaphoreGive(arg->mutex); +} + + +static void test_event_performance_handler(void* event_handler_arg, esp_event_base_t event_base, int32_t event_id, void* event_data) +{ + performance_data_t* data = (performance_data_t*) event_handler_arg; + + data->performed++; + + if (data->performed >= data->expected) { + xSemaphoreGive(data->done); + } +} + +static void test_event_post_task(void* args) +{ + task_arg_t* arg = (task_arg_t*) args; + post_event_data_t* data = arg->data; + + xSemaphoreTake(arg->start, portMAX_DELAY); + + for (int i = 0; i < data->num; i++) { + TEST_ASSERT_EQUAL(ESP_OK, esp_event_post_to(data->loop, data->base, data->id, NULL, 0, portMAX_DELAY)); + vTaskDelay(1); + } + + xSemaphoreGive(arg->done); + + vTaskDelete(NULL); +} + +static void test_event_simple_handler_registration_task(void* args) +{ + task_arg_t* arg = (task_arg_t*) args; + handler_registration_data_t* data = (handler_registration_data_t*) arg->data; + + xSemaphoreTake(arg->start, portMAX_DELAY); + + for(int i = 0; i < data->num; i++) { + if (data->is_registration) { + TEST_ASSERT_EQUAL(ESP_OK, esp_event_handler_register_with(data->loop, data->base, data->id, data->handles[i], NULL)); + } else { + TEST_ASSERT_EQUAL(ESP_OK, esp_event_handler_unregister_with(data->loop, data->base, data->id, data->handles[i])); + } + vTaskDelay(1); + } + + xSemaphoreGive(arg->done); + + vTaskDelete(NULL); +} + +static void test_handler_post_w_task(void* event_handler_arg, esp_event_base_t event_base, int32_t event_id, void* event_data) +{ + simple_arg_t* arg = (simple_arg_t*) event_handler_arg; + + esp_event_loop_handle_t* loop = (esp_event_loop_handle_t*) event_data; + int* count = (int*) arg->data; + + (*count)++; + + if (*count <= 2) { + if (event_base == s_test_base1 && event_id == TEST_EVENT_BASE1_EV1) { + TEST_ASSERT_EQUAL(ESP_OK, esp_event_post_to(*loop, s_test_base1, TEST_EVENT_BASE1_EV2, NULL, 0, portMAX_DELAY)); + } else{ + xSemaphoreGive((SemaphoreHandle_t) arg->mutex); + } + } else { + // Test that once the queue is full and the handler attempts to post to the same loop, + // posting does not block indefinitely. + if (event_base == s_test_base1 && event_id == TEST_EVENT_BASE1_EV1) { + xSemaphoreTake((SemaphoreHandle_t) arg->mutex, portMAX_DELAY); + TEST_ASSERT_EQUAL(ESP_ERR_TIMEOUT, esp_event_post_to(*loop, s_test_base1, TEST_EVENT_BASE1_EV2, NULL, 0, portMAX_DELAY)); + } + } +} + +static void test_handler_post_wo_task(void* event_handler_arg, esp_event_base_t event_base, int32_t event_id, void* event_data) +{ + simple_arg_t* arg = (simple_arg_t*) event_handler_arg; + + esp_event_loop_handle_t* loop = (esp_event_loop_handle_t*) event_data; + int* count = (int*) arg->data; + + (*count)++; + + if (*count <= 2) { + if (event_base == s_test_base1 && event_id == TEST_EVENT_BASE1_EV1) { + TEST_ASSERT_EQUAL(ESP_OK, esp_event_post_to(*loop, s_test_base1, TEST_EVENT_BASE1_EV2, NULL, 0, portMAX_DELAY)); + } else{ + xSemaphoreGive((SemaphoreHandle_t) arg->mutex); + } + } else { + // Test that once the queue is full and the handler attempts to post to the same loop, + // posting does not block indefinitely. + if (event_base == s_test_base1 && event_id == TEST_EVENT_BASE1_EV1) { + xSemaphoreTake((SemaphoreHandle_t) arg->mutex, portMAX_DELAY); + TEST_ASSERT_EQUAL(ESP_OK, esp_event_post_to(*loop, s_test_base1, TEST_EVENT_BASE1_EV2, NULL, 0, portMAX_DELAY)); + TEST_ASSERT_EQUAL(ESP_ERR_TIMEOUT, esp_event_post_to(*loop, s_test_base1, TEST_EVENT_BASE1_EV2, NULL, 0, portMAX_DELAY)); + } + } +} + +static void test_post_from_handler_loop_task(void* args) +{ + esp_event_loop_handle_t event_loop = (esp_event_loop_handle_t) args; + + while(1) { + TEST_ASSERT_EQUAL(ESP_OK, esp_event_loop_run(event_loop, portMAX_DELAY)); + } +} + +static void test_setup() +{ + TEST_ASSERT_TRUE(TEST_CONFIG_TASKS_TO_SPAWN >= 2); + TEST_ASSERT_EQUAL(ESP_OK, esp_event_loop_create_default()); +} + +static void test_teardown() +{ + TEST_ASSERT_EQUAL(ESP_OK, esp_event_loop_delete_default()); +} + + +TEST_CASE("can create and delete event loops", "[event]") +{ + /* this test aims to verify that: + * - creating loops with and without a task succeeds + * - event queue can accomodate the set queue size, and drops the post when exceeded + * - deleting loops with unconsumed posts and unregistered handlers (when unregistration is enabled) does not leak memory */ + + TEST_SETUP(); + + esp_event_loop_handle_t loop1; // with dedicated task + esp_event_loop_handle_t loop2; // without dedicated task + esp_event_loop_handle_t loop3; // with leftover post and handlers + + esp_event_loop_args_t loop_args = test_event_get_default_loop_args(); + + TEST_ASSERT_EQUAL(ESP_OK, esp_event_loop_create(&loop_args, &loop1)); + + loop_args.task_name = NULL; + TEST_ASSERT_EQUAL(ESP_OK, esp_event_loop_create(&loop_args, &loop2)); + TEST_ASSERT_EQUAL(ESP_OK, esp_event_loop_create(&loop_args, &loop3)); + + TEST_ASSERT_EQUAL(ESP_OK, esp_event_handler_register_with(loop3, s_test_base1, TEST_EVENT_BASE1_EV1, (void*) 0x00000001, NULL)); + TEST_ASSERT_EQUAL(ESP_OK, esp_event_handler_register_with(loop3, s_test_base1, TEST_EVENT_BASE1_EV2, (void*) 0x00000002, NULL)); + TEST_ASSERT_EQUAL(ESP_OK, esp_event_handler_register_with(loop3, s_test_base2, TEST_EVENT_BASE1_EV1, (void*) 0x00000003, NULL)); + + for (int i = 0; i < loop_args.queue_size; i++) { + int mod = i % 4; + + switch(mod) { + case 0: + TEST_ASSERT_EQUAL(ESP_OK, esp_event_post_to(loop3, s_test_base1, TEST_EVENT_BASE1_EV1, NULL, 0, portMAX_DELAY)); + break; + case 1: + TEST_ASSERT_EQUAL(ESP_OK, esp_event_post_to(loop3, s_test_base2, TEST_EVENT_BASE1_EV1, NULL, 0, portMAX_DELAY)); + break; + case 2: + TEST_ASSERT_EQUAL(ESP_OK, esp_event_post_to(loop3, s_test_base1, TEST_EVENT_BASE1_EV2, NULL, 0, portMAX_DELAY)); + break; + case 3: + TEST_ASSERT_EQUAL(ESP_OK, esp_event_post_to(loop3, s_test_base2, TEST_EVENT_BASE1_EV2, NULL, 0, portMAX_DELAY)); + break; + default: + break; + } + } + + TEST_ASSERT_EQUAL(ESP_ERR_TIMEOUT, esp_event_post_to(loop3, s_test_base1, TEST_EVENT_BASE1_EV1, NULL, 0, pdMS_TO_TICKS(10))); + + TEST_ASSERT_EQUAL(ESP_OK, esp_event_loop_delete(loop1)); + TEST_ASSERT_EQUAL(ESP_OK, esp_event_loop_delete(loop2)); + TEST_ASSERT_EQUAL(ESP_OK, esp_event_loop_delete(loop3)); + + TEST_TEARDOWN(); +} + +TEST_CASE("can register/unregister handlers for all events/all events for a specific base", "[event]") +{ + /* this test aims to verify that handlers can be registered to be called on all events + * or for all events with specific bases */ + + TEST_SETUP(); + + esp_event_loop_handle_t loop; + + int count = 0; + + simple_arg_t arg = { + .data = &count, + .mutex = xSemaphoreCreateMutex() + }; + + esp_event_loop_args_t loop_args = test_event_get_default_loop_args(); + + loop_args.task_name = NULL; + TEST_ASSERT_EQUAL(ESP_OK, esp_event_loop_create(&loop_args, &loop)); + + TEST_ASSERT_EQUAL(ESP_OK, esp_event_handler_register_with(loop, ESP_EVENT_ANY_BASE, ESP_EVENT_ANY_ID, test_event_simple_handler, &arg)); + TEST_ASSERT_EQUAL(ESP_OK, esp_event_handler_register_with(loop, s_test_base1, ESP_EVENT_ANY_ID, test_event_simple_handler, &arg)); + TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, esp_event_handler_register_with(loop, ESP_EVENT_ANY_BASE, TEST_EVENT_BASE1_EV1, test_event_simple_handler, &arg)); + TEST_ASSERT_EQUAL(ESP_OK, esp_event_handler_register_with(loop, s_test_base1, TEST_EVENT_BASE1_EV1, test_event_simple_handler, &arg)); + TEST_ASSERT_EQUAL(ESP_OK, esp_event_handler_register_with(loop, s_test_base1, TEST_EVENT_BASE1_EV2, test_event_simple_handler, &arg)); + TEST_ASSERT_EQUAL(ESP_OK, esp_event_handler_register_with(loop, s_test_base2, TEST_EVENT_BASE1_EV1, test_event_simple_handler, &arg)); + + TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, esp_event_post_to(loop, ESP_EVENT_ANY_BASE, ESP_EVENT_ANY_ID, NULL, 0, portMAX_DELAY)); + TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, esp_event_post_to(loop, s_test_base1, ESP_EVENT_ANY_ID, NULL, 0, portMAX_DELAY)); + TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, esp_event_post_to(loop, ESP_EVENT_ANY_BASE, TEST_EVENT_BASE1_EV1, NULL, 0, portMAX_DELAY)); + TEST_ASSERT_EQUAL(ESP_OK, esp_event_post_to(loop, s_test_base1, TEST_EVENT_BASE1_EV1, NULL, 0, portMAX_DELAY)); // exec loop, base and id level (+3) + TEST_ASSERT_EQUAL(ESP_OK, esp_event_post_to(loop, s_test_base1, TEST_EVENT_BASE1_EV2, NULL, 0, portMAX_DELAY)); // exec loop, base and id level (+3) + + // Post unknown events. Respective loop level and base level handlers should still execute. + TEST_ASSERT_EQUAL(ESP_OK, esp_event_post_to(loop, s_test_base1, TEST_EVENT_BASE1_MAX, NULL, 0, portMAX_DELAY)); // exec loop and base level (+2) + TEST_ASSERT_EQUAL(ESP_OK, esp_event_post_to(loop, s_test_base2, TEST_EVENT_BASE2_MAX, NULL, 0, portMAX_DELAY)); // exec loop level (+1) + + TEST_ASSERT_EQUAL(ESP_OK, esp_event_loop_run(loop, pdMS_TO_TICKS(10))); + + TEST_ASSERT_EQUAL(9, count); // 3 + 3 + 2 + 1 + + TEST_ASSERT_EQUAL(ESP_OK, esp_event_loop_delete(loop)); + + vSemaphoreDelete(arg.mutex); + + TEST_TEARDOWN(); +} + +TEST_CASE("can unregister handler", "[event]") +{ + /* this test aims to verify that unregistered handlers no longer execute when events are raised */ + + TEST_SETUP(); + + esp_event_loop_handle_t loop; + esp_event_loop_args_t loop_args = test_event_get_default_loop_args(); + + loop_args.task_name = NULL; + TEST_ASSERT_EQUAL(ESP_OK, esp_event_loop_create(&loop_args, &loop)); + + int count = 0; + + simple_arg_t arg = { + .data = &count, + .mutex = xSemaphoreCreateMutex() + }; + + TEST_ASSERT_EQUAL(ESP_OK, esp_event_handler_register_with(loop, s_test_base1, TEST_EVENT_BASE1_EV1, test_event_simple_handler, &arg)); + TEST_ASSERT_EQUAL(ESP_OK, esp_event_handler_register_with(loop, s_test_base2, TEST_EVENT_BASE1_EV1, test_event_simple_handler, &arg)); + + TEST_ASSERT_EQUAL(ESP_OK, esp_event_post_to(loop, s_test_base1, TEST_EVENT_BASE1_EV1, NULL, 0, portMAX_DELAY)); + TEST_ASSERT_EQUAL(ESP_OK, esp_event_post_to(loop, s_test_base2, TEST_EVENT_BASE1_EV1, NULL, 0, portMAX_DELAY)); + + TEST_ASSERT_EQUAL(ESP_OK, esp_event_loop_run(loop, pdMS_TO_TICKS(10))); + + TEST_ASSERT_EQUAL(2, count); + + TEST_ASSERT_EQUAL(ESP_OK, esp_event_handler_unregister_with(loop, s_test_base1, TEST_EVENT_BASE1_EV1, test_event_simple_handler)); + + TEST_ASSERT_EQUAL(ESP_OK, esp_event_post_to(loop, s_test_base1, TEST_EVENT_BASE1_EV1, NULL, 0, portMAX_DELAY)); + TEST_ASSERT_EQUAL(ESP_OK, esp_event_post_to(loop, s_test_base2, TEST_EVENT_BASE1_EV1, NULL, 0, portMAX_DELAY)); + + TEST_ASSERT_EQUAL(ESP_OK, esp_event_loop_run(loop, pdMS_TO_TICKS(10))); + + TEST_ASSERT_EQUAL(3, count); + + TEST_ASSERT_EQUAL(ESP_OK, esp_event_loop_delete(loop)); + + vSemaphoreDelete(arg.mutex); + + TEST_TEARDOWN(); +} + +TEST_CASE("can exit running loop at approximately the set amount of time", "[event]") +{ + /* this test aims to verify that running loop does not block indefinitely in cases where + * events are posted frequently */ + + TEST_SETUP(); + + esp_event_loop_handle_t loop; + esp_event_loop_args_t loop_args = test_event_get_default_loop_args(); + + loop_args.task_name = NULL; + TEST_ASSERT_EQUAL(ESP_OK, esp_event_loop_create(&loop_args, &loop)); + + performance_data_t handler_data = { + .performed = 0, + .expected = INT32_MAX, + .done = xSemaphoreCreateBinary() + }; + + TEST_ASSERT_EQUAL(ESP_OK, esp_event_handler_register_with(loop, s_test_base1, TEST_EVENT_BASE1_EV1, test_event_performance_handler, &handler_data)); + + post_event_data_t post_event_data = { + .base = s_test_base1, + .id = TEST_EVENT_BASE1_EV1, + .loop = loop, + .num = INT32_MAX + }; + + task_arg_t post_event_arg = { + .data = &post_event_data, + .done = xSemaphoreCreateBinary(), + .start = xSemaphoreCreateBinary() + }; + + TaskHandle_t post_task; + + xTaskCreatePinnedToCore(test_event_post_task, "post", 2048, &post_event_arg, s_test_priority, &post_task, test_event_get_core()); + + int runtime_ms = 10; + int runtime_us = runtime_ms * 1000; + + int64_t start, diff; + start = esp_timer_get_time(); + + xSemaphoreGive(post_event_arg.start); + + // Run the loop for the runtime_ms set amount of time, regardless of whether events + // are still being posted to the loop. + TEST_ASSERT_EQUAL(ESP_OK, esp_event_loop_run(loop, pdMS_TO_TICKS(runtime_ms))); + + diff = (esp_timer_get_time() - start); + + // Threshold is 25 percent. + TEST_ASSERT(diff < runtime_us * 1.25f); + + // Verify that the post task still continues + TEST_ASSERT_NOT_EQUAL(pdTRUE, xSemaphoreTake(post_event_arg.done, pdMS_TO_TICKS(10))); + + vSemaphoreDelete(post_event_arg.done); + vSemaphoreDelete(post_event_arg.start); + vSemaphoreDelete(handler_data.done); + vTaskDelete(post_task); + + TEST_ASSERT_EQUAL(ESP_OK, esp_event_loop_delete(loop)); + + TEST_TEARDOWN(); +} + +TEST_CASE("can register/unregister handlers simultaneously", "[event]") +{ + /* this test aims to verify that the event handlers list remains consistent despite + * simultaneous access by differenct tasks */ + + TEST_SETUP(); + + const char* base = "base"; + int32_t id = 0; + + esp_event_loop_handle_t loop; + esp_event_loop_args_t loop_args = test_event_get_default_loop_args(); + + TEST_ASSERT_EQUAL(ESP_OK, esp_event_loop_create(&loop_args, &loop)); + + ESP_LOGI(TAG, "registering handlers"); + + handler_registration_data_t* registration_data = calloc(TEST_CONFIG_TASKS_TO_SPAWN, sizeof(*registration_data)); + task_arg_t* registration_arg = calloc(TEST_CONFIG_TASKS_TO_SPAWN, sizeof(*registration_arg)); + + for (int i = 0; i < TEST_CONFIG_TASKS_TO_SPAWN; i++) { + registration_data[i].base = base; + registration_data[i].id = id; + registration_data[i].loop = loop; + registration_data[i].handles = calloc(TEST_CONFIG_ITEMS_TO_REGISTER, sizeof(esp_event_handler_t)); + registration_data[i].num = TEST_CONFIG_ITEMS_TO_REGISTER; + registration_data[i].is_registration = true; + + for (int j = 0; j < TEST_CONFIG_ITEMS_TO_REGISTER; j++) { + registration_data[i].handles[j] = (void*) (i * TEST_CONFIG_ITEMS_TO_REGISTER) + (j + TEST_CONFIG_ITEMS_TO_REGISTER); + } + + registration_arg[i].start = xSemaphoreCreateBinary(); + registration_arg[i].done = xSemaphoreCreateBinary(); + registration_arg[i].data = ®istration_data[i]; + + xTaskCreatePinnedToCore(test_event_simple_handler_registration_task, "register", 2048, ®istration_arg[i], s_test_priority, NULL, test_event_get_core()); + } + + // Give the semaphores to the spawned registration task + for (int i = 0; i < TEST_CONFIG_TASKS_TO_SPAWN; i++) { + xSemaphoreGive(registration_arg[i].start); + } + + // Take the same semaphores in order to proceed + for (int i = 0; i < TEST_CONFIG_TASKS_TO_SPAWN; i++) { + xSemaphoreTake(registration_arg[i].done, portMAX_DELAY); + } + + ESP_LOGI(TAG, "checking consistency of handlers list"); + + // Check consistency of events list + for (int i = 0; i < TEST_CONFIG_TASKS_TO_SPAWN; i++) { + for (int j = 0; j < TEST_CONFIG_ITEMS_TO_REGISTER; j++) { + TEST_ASSERT_TRUE(esp_event_is_handler_registered(loop, base, id, registration_data[i].handles[j])); + } + } + + ESP_LOGI(TAG, "unregistering handlers"); + + /* Test if tasks can unregister simultaneously */ + + // Unregister registered events + handler_registration_data_t* unregistration_data = calloc(TEST_CONFIG_TASKS_TO_SPAWN, sizeof(*unregistration_data)); + task_arg_t* unregistration_arg = calloc(TEST_CONFIG_TASKS_TO_SPAWN, sizeof(*unregistration_arg)); + + for (int i = 0; i < TEST_CONFIG_TASKS_TO_SPAWN; i++) { + unregistration_data[i].base = base; + unregistration_data[i].id = id; + unregistration_data[i].loop = loop; + unregistration_data[i].handles = calloc(TEST_CONFIG_ITEMS_TO_REGISTER, sizeof(esp_event_handler_t)); + unregistration_data[i].num = TEST_CONFIG_ITEMS_TO_REGISTER; + unregistration_data[i].is_registration = false; + + memcpy(unregistration_data[i].handles, registration_data[i].handles, TEST_CONFIG_ITEMS_TO_REGISTER * sizeof(esp_event_handler_t)); + + unregistration_arg[i].data = &unregistration_data[i]; + unregistration_arg[i].start = xSemaphoreCreateBinary(); + unregistration_arg[i].done = xSemaphoreCreateBinary(); + + xTaskCreatePinnedToCore(test_event_simple_handler_registration_task, "unregister", 2048, &unregistration_arg[i], s_test_priority, NULL, test_event_get_core()); + } + + // Give the semaphores to the spawned unregistration task + for (int i = 0; i < TEST_CONFIG_TASKS_TO_SPAWN; i++) { + xSemaphoreGive(unregistration_arg[i].start); + } + + // Take the same semaphores in order to proceed + for (int i = 0; i < TEST_CONFIG_TASKS_TO_SPAWN; i++) { + xSemaphoreTake(unregistration_arg[i].done, portMAX_DELAY); + } + + ESP_LOGI(TAG, "checking consistency of handlers list"); + + // Check consistency of events list + for (int i = 0; i < TEST_CONFIG_TASKS_TO_SPAWN; i++) { + for (int j = 0; j < TEST_CONFIG_ITEMS_TO_REGISTER; j++) { + TEST_ASSERT_FALSE(esp_event_is_handler_registered(loop, base, id, registration_data[i].handles[j])); + } + } + + // Do cleanup + for (int i = 0; i < TEST_CONFIG_TASKS_TO_SPAWN; i++) { + free(registration_data[i].handles); + vSemaphoreDelete(registration_arg[i].start); + vSemaphoreDelete(registration_arg[i].done); + + free(unregistration_data[i].handles); + vSemaphoreDelete(unregistration_arg[i].start); + vSemaphoreDelete(unregistration_arg[i].done); + } + + free(registration_data); + free(unregistration_data); + free(registration_arg); + free(unregistration_arg); + + TEST_ASSERT_EQUAL(ESP_OK, esp_event_loop_delete(loop)); + + TEST_TEARDOWN(); +} + +TEST_CASE("can post and run events", "[event]") +{ + /* this test aims to verify that: + * - multiple tasks can post to the queue simultaneously + * - handlers recieve the appropriate handler arg and associated event data */ + + TEST_SETUP(); + + esp_event_loop_handle_t loop; + + esp_event_loop_args_t loop_args = test_event_get_default_loop_args(); + + loop_args.task_name = NULL; + loop_args.queue_size = TEST_CONFIG_TASKS_TO_SPAWN * TEST_CONFIG_ITEMS_TO_REGISTER; + TEST_ASSERT_EQUAL(ESP_OK, esp_event_loop_create(&loop_args, &loop)); + + int count = 0; + + simple_arg_t arg = { + .data = &count, + .mutex = xSemaphoreCreateMutex() + }; + + TEST_ASSERT_EQUAL(ESP_OK, esp_event_handler_register_with(loop, s_test_base1, TEST_EVENT_BASE1_EV1, test_event_simple_handler, &arg)); + + post_event_data_t* post_event_data = calloc(TEST_CONFIG_TASKS_TO_SPAWN, sizeof(*post_event_data)); + task_arg_t* post_event_arg = calloc(TEST_CONFIG_TASKS_TO_SPAWN, sizeof(*post_event_arg)); + + for (int i = 0; i < TEST_CONFIG_TASKS_TO_SPAWN; i++) + { + post_event_data[i].base = s_test_base1; + post_event_data[i].id = TEST_EVENT_BASE1_EV1; + post_event_data[i].loop = loop; + post_event_data[i].num = TEST_CONFIG_ITEMS_TO_REGISTER; + + post_event_arg[i].data = &post_event_data[i]; + post_event_arg[i].start = xSemaphoreCreateBinary(); + post_event_arg[i].done = xSemaphoreCreateBinary(); + + xTaskCreatePinnedToCore(test_event_post_task, "post", 2048, &post_event_arg[i], s_test_priority, NULL, test_event_get_core()); + } + + for (int i = 0; i < TEST_CONFIG_TASKS_TO_SPAWN; i++) { + xSemaphoreGive(post_event_arg[i].start); + } + + // Execute some events as they are posted + for (int i = 0; i < (TEST_CONFIG_TASKS_TO_SPAWN * TEST_CONFIG_ITEMS_TO_REGISTER) / 2; i++) { + TEST_ASSERT_EQUAL(ESP_OK, esp_event_loop_run(loop, pdMS_TO_TICKS(10))); + } + + for (int i = 0; i < TEST_CONFIG_TASKS_TO_SPAWN; i++) { + xSemaphoreTake(post_event_arg[i].done, portMAX_DELAY); + } + + // Execute the rest + TEST_ASSERT_EQUAL(ESP_OK, esp_event_loop_run(loop, pdMS_TO_TICKS(10))); + + TEST_ASSERT_EQUAL(TEST_CONFIG_TASKS_TO_SPAWN * TEST_CONFIG_ITEMS_TO_REGISTER, count); + + // Cleanup + for (int i = 0; i < TEST_CONFIG_TASKS_TO_SPAWN; i++) { + vSemaphoreDelete(post_event_arg[i].start); + vSemaphoreDelete(post_event_arg[i].done); + } + + free(post_event_data); + free(post_event_arg); + + TEST_ASSERT_EQUAL(ESP_OK, esp_event_loop_delete(loop)); + + vSemaphoreDelete(arg.mutex); + + TEST_TEARDOWN(); +} + +static void loop_run_task(void* args) +{ + esp_event_loop_handle_t event_loop = (esp_event_loop_handle_t) args; + + while(1) { + esp_event_loop_run(event_loop, portMAX_DELAY); + } +} + +static void performance_test(bool dedicated_task) +{ + TEST_SETUP(); + + const char test_base[] = "qwertyuiopasdfghjklzxvbnmmnbvcxzqwertyuiopasdfghjklzxvbnmmnbvcxz"; + + #define TEST_CONFIG_BASES (sizeof(test_base) - 1) + #define TEST_CONFIG_IDS (TEST_CONFIG_BASES / 2) + + // Create loop + esp_event_loop_args_t loop_args = test_event_get_default_loop_args(); + esp_event_loop_handle_t loop; + + if (!dedicated_task) { + loop_args.task_name = NULL; + } + + TEST_ASSERT_EQUAL(ESP_OK, esp_event_loop_create(&loop_args, &loop)); + + performance_data_t data; + + // Register the handlers + for (int base = 0; base < TEST_CONFIG_BASES; base++) { + for (int id = 0; id < TEST_CONFIG_IDS; id++) { + TEST_ASSERT_EQUAL(ESP_OK, esp_event_handler_register_with(loop, test_base + base, id, test_event_performance_handler, &data)); + } + } + + TaskHandle_t mtask = NULL; + + if (!dedicated_task) { + xTaskCreate(loop_run_task, "loop_run", loop_args.task_stack_size, (void*) loop, loop_args.task_priority, &mtask); + } + + // Perform performance test + float running_sum = 0; + float running_count = 0; + + for (int bases = 1; bases <= TEST_CONFIG_BASES; bases *= 2) { + for (int ids = 1; ids <= TEST_CONFIG_IDS; ids *= 2) { + + data.performed = 0; + data.expected = bases * ids; + data.done = xSemaphoreCreateBinary(); + + // Generate randomized list of posts + int post_bases[TEST_CONFIG_BASES]; + int post_ids[TEST_CONFIG_IDS]; + + for (int i = 0; i < bases; i++) { + post_bases[i] = i; + } + + for (int i = 0; i < ids; i++) { + post_ids[i] = i; + } + + for (int i = 0; i < bases; i++) { + int rand_a = rand() % bases; + int rand_b = rand() % bases; + + int temp = post_bases[rand_a]; + post_bases[rand_a]= post_bases[rand_b]; + post_bases[rand_b] = temp; + } + + for (int i = 0; i < ids; i++) { + int rand_a = rand() % ids; + int rand_b = rand() % ids; + + int temp = post_ids[rand_a]; + post_ids[rand_a]= post_ids[rand_b]; + post_ids[rand_b] = temp; + } + + // Post the events + int64_t start = esp_timer_get_time(); + for (int base = 0; base < bases; base++) { + for (int id = 0; id < ids; id++) { + TEST_ASSERT_EQUAL(ESP_OK, esp_event_post_to(loop, test_base + post_bases[base], post_ids[id], NULL, 0, portMAX_DELAY)); + } + } + + xSemaphoreTake(data.done, portMAX_DELAY); + int64_t elapsed = esp_timer_get_time() - start; + + // Record data + TEST_ASSERT_EQUAL(data.expected, data.performed); + + running_count++; + running_sum += data.performed / (elapsed / (1000000.0)); + + vSemaphoreDelete(data.done); + } + } + + int average = (int) (running_sum / (running_count)); + +#ifdef CONFIG_EVENT_LOOP_PROFILING + ESP_LOGI(TAG, "events dispatched/second with profiling enabled: %d", average); + // Enabling profiling will slow down event dispatch, so the set threshold + // is not valid when it is enabled. +#else + TEST_PERFORMANCE_GREATER_THAN(EVENT_DISPATCH, "%d", average); +#endif + + if (!dedicated_task) { + ((esp_event_loop_instance_t*) loop)->task = mtask; + } + + TEST_ASSERT_EQUAL(ESP_OK, esp_event_loop_delete(loop)); + + TEST_TEARDOWN(); +} + +TEST_CASE("performance test - dedicated task", "[event]") +{ + performance_test(true); +} + +TEST_CASE("performance test - no dedicated task", "[event]") +{ + performance_test(false); +} + +TEST_CASE("can post to loop from handler - dedicated task", "[event]") +{ + TEST_SETUP(); + + esp_event_loop_handle_t loop_w_task; + + esp_event_loop_args_t loop_args = test_event_get_default_loop_args(); + + int count; + + simple_arg_t arg = { + .data = &count, + .mutex = xSemaphoreCreateBinary() + }; + + TEST_ASSERT_EQUAL(ESP_OK, esp_event_loop_create(&loop_args, &loop_w_task)); + + count = 0; + + // Test that a handler can post to a different loop while there is still slots on the queue + TEST_ASSERT_EQUAL(ESP_OK, esp_event_handler_register_with(loop_w_task, s_test_base1, TEST_EVENT_BASE1_EV1, test_handler_post_w_task, &arg)); + TEST_ASSERT_EQUAL(ESP_OK, esp_event_handler_register_with(loop_w_task, s_test_base1, TEST_EVENT_BASE1_EV2, test_handler_post_w_task, &arg)); + + TEST_ASSERT_EQUAL(ESP_OK, esp_event_post_to(loop_w_task, s_test_base1, TEST_EVENT_BASE1_EV1, &loop_w_task, sizeof(&loop_w_task), portMAX_DELAY)); + + xSemaphoreTake(arg.mutex, portMAX_DELAY); + + TEST_ASSERT_EQUAL(2, count); + + // Test that other tasks can still post while there is still slots in the queue, while handler is executing + count = 100; + + TEST_ASSERT_EQUAL(ESP_OK, esp_event_post_to(loop_w_task, s_test_base1, TEST_EVENT_BASE1_EV1, &loop_w_task, sizeof(&loop_w_task), portMAX_DELAY)); + + for (int i = 0; i < loop_args.queue_size; i++) { + TEST_ASSERT_EQUAL(ESP_OK, esp_event_post_to(loop_w_task, s_test_base1, TEST_EVENT_BASE1_EV2, NULL, 0, portMAX_DELAY)); + } + + TEST_ASSERT_EQUAL(ESP_ERR_TIMEOUT, esp_event_post_to(loop_w_task, s_test_base1, TEST_EVENT_BASE1_EV2, NULL, 0, + pdMS_TO_TICKS(CONFIG_INT_WDT_TIMEOUT_MS * TEST_CONFIG_WAIT_MULTIPLIER))); + + xSemaphoreGive(arg.mutex); + + vTaskDelay(pdMS_TO_TICKS(CONFIG_INT_WDT_TIMEOUT_MS * TEST_CONFIG_WAIT_MULTIPLIER)); + + TEST_ASSERT_EQUAL(ESP_OK, esp_event_loop_delete(loop_w_task)); + + vSemaphoreDelete(arg.mutex); + + TEST_TEARDOWN(); +} + +TEST_CASE("can post to loop from handler - no dedicated task", "[event]") +{ + TEST_SETUP(); + + esp_event_loop_handle_t loop_wo_task; + + esp_event_loop_args_t loop_args = test_event_get_default_loop_args(); + + int count; + + simple_arg_t arg = { + .data = &count, + .mutex = xSemaphoreCreateBinary() + }; + + count = 0; + + loop_args.queue_size = 1; + loop_args.task_name = NULL; + TEST_ASSERT_EQUAL(ESP_OK, esp_event_loop_create(&loop_args, &loop_wo_task)); + + TaskHandle_t mtask; + + xTaskCreate(test_post_from_handler_loop_task, "task", 2584, (void*) loop_wo_task, s_test_priority, &mtask); + + // Test that a handler can post to a different loop while there is still slots on the queue + TEST_ASSERT_EQUAL(ESP_OK, esp_event_handler_register_with(loop_wo_task, s_test_base1, TEST_EVENT_BASE1_EV1, test_handler_post_wo_task, &arg)); + TEST_ASSERT_EQUAL(ESP_OK, esp_event_handler_register_with(loop_wo_task, s_test_base1, TEST_EVENT_BASE1_EV2, test_handler_post_wo_task, &arg)); + + TEST_ASSERT_EQUAL(ESP_OK, esp_event_post_to(loop_wo_task, s_test_base1, TEST_EVENT_BASE1_EV1, &loop_wo_task, sizeof(&loop_wo_task), portMAX_DELAY)); + + xSemaphoreTake(arg.mutex, portMAX_DELAY); + + TEST_ASSERT_EQUAL(2, count); + + count = 100; + + TEST_ASSERT_EQUAL(ESP_OK, esp_event_post_to(loop_wo_task, s_test_base1, TEST_EVENT_BASE1_EV1, &loop_wo_task, sizeof(&loop_wo_task), portMAX_DELAY)); + + vTaskDelay(pdMS_TO_TICKS(CONFIG_INT_WDT_TIMEOUT_MS * TEST_CONFIG_WAIT_MULTIPLIER)); + + // For loop without tasks, posting is more restrictive. Posting should wait until execution of handler finishes + TEST_ASSERT_EQUAL(ESP_ERR_TIMEOUT, esp_event_post_to(loop_wo_task, s_test_base1, TEST_EVENT_BASE1_EV2, NULL, 0, + pdMS_TO_TICKS(CONFIG_INT_WDT_TIMEOUT_MS * TEST_CONFIG_WAIT_MULTIPLIER))); + + xSemaphoreGive(arg.mutex); + + vTaskDelay(pdMS_TO_TICKS(CONFIG_INT_WDT_TIMEOUT_MS * TEST_CONFIG_WAIT_MULTIPLIER)); + + vTaskDelete(mtask); + + TEST_ASSERT_EQUAL(ESP_OK, esp_event_loop_delete(loop_wo_task)); + + vSemaphoreDelete(arg.mutex); + + TEST_TEARDOWN(); +} + +static void test_event_simple_handler_template(void* handler_arg, esp_event_base_t base, int32_t id, void* event_arg) +{ + int* count = (int*) handler_arg; + (*count)++; +} + +static void test_event_simple_handler_1(void* handler_arg, esp_event_base_t base, int32_t id, void* event_arg) +{ + test_event_simple_handler_template(handler_arg, base, id, event_arg); +} + +static void test_event_simple_handler_3(void* handler_arg, esp_event_base_t base, int32_t id, void* event_arg) +{ + test_event_simple_handler_template(handler_arg, base, id, event_arg); +} + +static void test_event_simple_handler_2(void* handler_arg, esp_event_base_t base, int32_t id, void* event_arg) +{ + test_event_simple_handler_template(handler_arg, base, id, event_arg); +} + +static void test_registration_from_handler_hdlr(void* handler_arg, esp_event_base_t base, int32_t id, void* event_arg) +{ + esp_event_loop_handle_t* loop = (esp_event_loop_handle_t*) event_arg; + TEST_ASSERT_EQUAL(ESP_OK, esp_event_handler_register_with(*loop, s_test_base1, TEST_EVENT_BASE1_EV2, test_event_simple_handler_1, handler_arg)); + TEST_ASSERT_EQUAL(ESP_OK, esp_event_handler_register_with(*loop, s_test_base1, TEST_EVENT_BASE1_EV2, test_event_simple_handler_2, handler_arg)); + TEST_ASSERT_EQUAL(ESP_OK, esp_event_handler_register_with(*loop, s_test_base1, TEST_EVENT_BASE1_EV2, test_event_simple_handler_3, handler_arg)); +} + +static void test_unregistration_from_handler_hdlr(void* handler_arg, esp_event_base_t base, int32_t id, void* event_arg) +{ + esp_event_loop_handle_t* loop = (esp_event_loop_handle_t*) event_arg; + TEST_ASSERT_EQUAL(ESP_OK, esp_event_handler_unregister_with(*loop, s_test_base1, TEST_EVENT_BASE1_EV2, test_event_simple_handler_1)); + TEST_ASSERT_EQUAL(ESP_OK, esp_event_handler_unregister_with(*loop, s_test_base1, TEST_EVENT_BASE1_EV2, test_event_simple_handler_2)); + TEST_ASSERT_EQUAL(ESP_OK, esp_event_handler_unregister_with(*loop, s_test_base1, TEST_EVENT_BASE1_EV2, test_event_simple_handler_3)); +} + +TEST_CASE("can register from handler", "[event]") +{ + TEST_SETUP(); + + esp_event_loop_handle_t loop; + + esp_event_loop_args_t loop_args = test_event_get_default_loop_args(); + + loop_args.task_name = NULL; + TEST_ASSERT_EQUAL(ESP_OK, esp_event_loop_create(&loop_args, &loop)); + + int count = 0; + + TEST_ASSERT_EQUAL(ESP_OK, esp_event_handler_register_with(loop, s_test_base1, TEST_EVENT_BASE1_EV1, test_registration_from_handler_hdlr, &count)); + TEST_ASSERT_EQUAL(ESP_OK, esp_event_handler_register_with(loop, s_test_base2, TEST_EVENT_BASE2_EV1, test_unregistration_from_handler_hdlr, &count)); + + TEST_ASSERT_EQUAL(ESP_OK, esp_event_post_to(loop, s_test_base1, TEST_EVENT_BASE1_EV1, &loop, sizeof(&loop), portMAX_DELAY)); + TEST_ASSERT_EQUAL(ESP_OK, esp_event_loop_run(loop, pdMS_TO_TICKS(10))); + + TEST_ASSERT_EQUAL(ESP_OK, esp_event_post_to(loop, s_test_base1, TEST_EVENT_BASE1_EV2, NULL, 0, portMAX_DELAY)); + TEST_ASSERT_EQUAL(ESP_OK, esp_event_loop_run(loop, pdMS_TO_TICKS(10))); + + TEST_ASSERT_EQUAL(3, count); + + TEST_ASSERT_EQUAL(ESP_OK, esp_event_post_to(loop, s_test_base2, TEST_EVENT_BASE2_EV1, &loop, sizeof(&loop), portMAX_DELAY)); + TEST_ASSERT_EQUAL(ESP_OK, esp_event_loop_run(loop, pdMS_TO_TICKS(10))); + + TEST_ASSERT_EQUAL(ESP_OK, esp_event_post_to(loop, s_test_base1, TEST_EVENT_BASE1_EV2, NULL, 0, portMAX_DELAY)); + TEST_ASSERT_EQUAL(ESP_OK, esp_event_loop_run(loop, pdMS_TO_TICKS(10))); + + TEST_ASSERT_EQUAL(3, count); + + TEST_ASSERT_EQUAL(ESP_OK, esp_event_loop_delete(loop)); + + TEST_TEARDOWN(); +} + +static void test_create_loop_handler(void* handler_args, esp_event_base_t base, int32_t id, void* event_data) +{ + esp_event_loop_args_t loop_args = test_event_get_default_loop_args(); + + if (id == TEST_EVENT_BASE1_EV1) { + TEST_ASSERT_EQUAL(ESP_OK, esp_event_loop_create(&loop_args, (esp_event_loop_handle_t*) handler_args)); + } else { + TEST_ASSERT_EQUAL(ESP_OK, esp_event_loop_delete(*((esp_event_loop_handle_t*) handler_args))); + } +} + +TEST_CASE("can create and delete loop from handler", "[event]") +{ + TEST_SETUP(); + + esp_event_loop_handle_t loop; + esp_event_loop_handle_t test_loop; + + esp_event_loop_args_t loop_args = test_event_get_default_loop_args(); + + loop_args.task_name = NULL; + TEST_ASSERT_EQUAL(ESP_OK, esp_event_loop_create(&loop_args, &loop)); + + TEST_ASSERT_EQUAL(ESP_OK, esp_event_handler_register_with(loop, s_test_base1, TEST_EVENT_BASE1_EV1, test_create_loop_handler, &test_loop)); + TEST_ASSERT_EQUAL(ESP_OK, esp_event_handler_register_with(loop, s_test_base1, TEST_EVENT_BASE1_EV2, test_create_loop_handler, &test_loop)); + + TEST_ASSERT_EQUAL(ESP_OK, esp_event_post_to(loop, s_test_base1, TEST_EVENT_BASE1_EV1, NULL, 0, portMAX_DELAY)); + TEST_ASSERT_EQUAL(ESP_OK, esp_event_loop_run(loop, pdMS_TO_TICKS(10))); + + TEST_ASSERT_EQUAL(ESP_OK, esp_event_post_to(loop, s_test_base1, TEST_EVENT_BASE1_EV2, NULL, 0, portMAX_DELAY)); + TEST_ASSERT_EQUAL(ESP_OK, esp_event_loop_run(loop, pdMS_TO_TICKS(10))); + + TEST_ASSERT_EQUAL(ESP_OK, esp_event_loop_delete(loop)); + + TEST_TEARDOWN(); +} + +#ifdef CONFIG_EVENT_LOOP_PROFILING +TEST_CASE("can dump event loop profile", "[event]") +{ + /* this test aims to verify that dumping event loop statistics succeed */ + + TEST_SETUP(); + + esp_event_loop_handle_t loop; + + esp_event_loop_args_t loop_args = test_event_get_default_loop_args(); + + loop_args.task_name = NULL; + loop_args.queue_size = 5; + TEST_ASSERT_EQUAL(ESP_OK, esp_event_loop_create(&loop_args, &loop)); + + int count = 0; + + simple_arg_t arg = { + .data = &count, + .mutex = xSemaphoreCreateMutex() + }; + + TEST_ASSERT_EQUAL(ESP_OK, esp_event_handler_register_with(loop, ESP_EVENT_ANY_BASE, ESP_EVENT_ANY_ID, test_event_simple_handler, &arg)); + TEST_ASSERT_EQUAL(ESP_OK, esp_event_handler_register_with(loop, s_test_base1, ESP_EVENT_ANY_ID, test_event_simple_handler, &arg)); + TEST_ASSERT_EQUAL(ESP_OK, esp_event_handler_register_with(loop, s_test_base1, TEST_EVENT_BASE1_EV1, test_event_simple_handler, &arg)); + TEST_ASSERT_EQUAL(ESP_OK, esp_event_handler_register_with(loop, s_test_base1, TEST_EVENT_BASE1_EV2, test_event_simple_handler, &arg)); + + TEST_ASSERT_EQUAL(ESP_OK, esp_event_handler_register_with(loop, s_test_base2, TEST_EVENT_BASE1_EV1, test_event_simple_handler, &arg)); + TEST_ASSERT_EQUAL(ESP_OK, esp_event_handler_register_with(loop, s_test_base2, TEST_EVENT_BASE1_EV2, test_event_simple_handler, &arg)); + + TEST_ASSERT_EQUAL(ESP_OK, esp_event_post_to(loop, s_test_base1, TEST_EVENT_BASE1_EV1, NULL, 0, 1)); + TEST_ASSERT_EQUAL(ESP_OK, esp_event_post_to(loop, s_test_base2, TEST_EVENT_BASE1_EV1, NULL, 0, 1)); + TEST_ASSERT_EQUAL(ESP_OK, esp_event_post_to(loop, s_test_base1, TEST_EVENT_BASE1_EV2, NULL, 0, 1)); + TEST_ASSERT_EQUAL(ESP_OK, esp_event_post_to(loop, s_test_base2, TEST_EVENT_BASE1_EV2, NULL, 0, 1)); + TEST_ASSERT_EQUAL(ESP_OK, esp_event_post_to(loop, s_test_base1, TEST_EVENT_BASE1_EV1, NULL, 0, 1)); + TEST_ASSERT_EQUAL(ESP_ERR_TIMEOUT, esp_event_post_to(loop, s_test_base2, TEST_EVENT_BASE1_EV1, NULL, 0, 1)); + + TEST_ASSERT_EQUAL(ESP_OK, esp_event_loop_run(loop, pdMS_TO_TICKS(10))); + + // 5 invocations of loop-levlel handlers + 3 invocation of base-level handlers (s_test_base1) + + // 5 invocations of respective event-level handlers + TEST_ASSERT_EQUAL(13, count); + + TEST_ASSERT_EQUAL(ESP_OK, esp_event_dump(stdout)); + + TEST_ASSERT_EQUAL(ESP_OK, esp_event_loop_delete(loop)); + + vSemaphoreDelete(arg.mutex); + + TEST_TEARDOWN(); +} +#endif \ No newline at end of file diff --git a/components/ethernet/CMakeLists.txt b/components/ethernet/CMakeLists.txt index ef7c9a0f9..03f45c06d 100644 --- a/components/ethernet/CMakeLists.txt +++ b/components/ethernet/CMakeLists.txt @@ -6,6 +6,6 @@ set(COMPONENT_SRCS "emac_dev.c" set(COMPONENT_ADD_INCLUDEDIRS "include") set(COMPONENT_REQUIRES) -set(COMPONENT_PRIV_REQUIRES tcpip_adapter) +set(COMPONENT_PRIV_REQUIRES tcpip_adapter esp_event) register_component() diff --git a/components/idf_test/include/idf_performance.h b/components/idf_test/include/idf_performance.h index 8a36935c8..8ba1be753 100644 --- a/components/idf_test/include/idf_performance.h +++ b/components/idf_test/include/idf_performance.h @@ -27,3 +27,5 @@ #define IDF_PERFORMANCE_MIN_TCP_TX_THROUGHPUT 40 #define IDF_PERFORMANCE_MIN_UDP_RX_THROUGHPUT 80 #define IDF_PERFORMANCE_MIN_UDP_TX_THROUGHPUT 50 +// events dispatched per second by event loop library +#define IDF_PERFORMANCE_MIN_EVENT_DISPATCH 25000 \ No newline at end of file diff --git a/docs/Doxyfile b/docs/Doxyfile index aaee15e0d..3306fbc07 100644 --- a/docs/Doxyfile +++ b/docs/Doxyfile @@ -184,6 +184,9 @@ INPUT = \ ../../components/esp32/include/esp32/pm.h \ ### esp_timer, High Resolution Timer ../../components/esp32/include/esp_timer.h \ + ### esp_event, Event Loop Library + ../../components/esp_event/include/esp_event.h \ + ../../components/esp_event/include/esp_event_base.h \ ### ESP Pthread parameters ../../components/pthread/include/esp_pthread.h \ ### diff --git a/docs/en/api-reference/system/esp_event.rst b/docs/en/api-reference/system/esp_event.rst new file mode 100644 index 000000000..f1aa25dd9 --- /dev/null +++ b/docs/en/api-reference/system/esp_event.rst @@ -0,0 +1,206 @@ +Event Loop Library +================== + +Overview +-------- + +The event loop library allows components to declare events to which other components can register handlers -- code which will +execute when those events occur. This allows loosely coupled components to attach desired behavior to changes in state of other components +without application involvement. For instance, a high level connection handling library may subscribe to events produced +by the wifi subsystem directly and act on those events. This also simplifies event processing by serializing and deferring +code execution to another context. + +Using ``esp_event`` APIs +------------------------ + +There are two objects of concern for users of this library: events and event loops. + +Events are occurences of note. For example, for WiFi, a successful connection to the access point may be an event. +Events are referenced using a two part identifier which are discussed more :ref:`here `. +Event loops are the vehicle by which events get posted by event sources and handled by event handler functions. +These two appear prominently in the event loop library APIs. + +Using this library roughly entails the following flow: + +1. A user defines a function that should run when an event is posted to a loop. This function is referred to as the event handler. It should have the same signature as :cpp:type:`esp_event_handler_t`. +2. An event loop is created using :cpp:func:`esp_event_loop_create`, which outputs a handle to the loop of type :cpp:type:`esp_event_loop_handle_t`. Event loops created using this API are referred to as user event loops. There is, however, a special type of event loop called the default event loop which are discussed :ref:`here `. +3. Components register event handlers to the loop using :cpp:func:`esp_event_handler_register_with`. Handlers can be registered with multiple loops, more on that :ref:`here `. +4. Event sources post an event to the loop using :cpp:func:`esp_event_post_to`. +5. Components wanting to remove their handlers from being called can do so by unregistering from the loop using :cpp:func:`esp_event_handler_unregister_with`. +6. Event loops which are no longer needed can be deleted using :cpp:func:`esp_event_loop_delete`. + +In code, the flow above may look like as follows: + +.. code-block:: c + + // 1. Define the event handler + void run_on_event(void* handler_arg, esp_event_base_t base, int32_t id, void* event_data) + { + // Event handler logic + } + + void app_main() + { + // 2. A configuration structure of type esp_event_loop_args_t is needed to specify the properties of the loop to be + // created. A handle of type esp_event_loop_handle_t is obtained, which is needed by the other APIs to reference the loop + // to perform their operations on. + esp_event_loop_args_t loop_args = { + .queue_size = ..., + .task_name = ... + .task_priority = ..., + .task_stack_size = ..., + .task_core_id = ... + }; + + esp_event_loop_handle_t loop_handle; + + esp_event_loop_create(&loop_args, &loop_handle) + + // 3. Register event handler defined in (1). MY_EVENT_BASE and MY_EVENT_ID specifies a hypothetical + // event that handler run_on_event should execute on when it gets posted to the loop. + esp_event_handler_register_with(loop_handle, MY_EVENT_BASE, MY_EVENT_ID, run_on_event, ...); + + ... + + // 4. Post events to the loop. This queues the event on the event loop. At some point in time + // the event loop executes the event handler registered to the posted event, in this case run_on_event. + // For simplicity sake this example calls esp_event_post_to from app_main, but posting can be done from + // any other tasks (which is the more interesting use case). + esp_event_post_to(loop_handle, MY_EVENT_BASE, MY_EVENT_ID, ...); + + ... + + // 5. Unregistering an unneeded handler + esp_event_handler_unregister_with(loop_handle, MY_EVENT_BASE, MY_EVENT_ID, run_on_event); + + ... + + // 6. Deleting an unneeded event loop + esp_event_loop_delete(loop_handle); + } + +.. _esp-event-declaring-defining-events: + +Declaring and defining events +----------------------------- + +As mentioned previously, events consists of two-part identifers: the event base and the event ID. The event base identifies an independent group +of events; the event ID identifies the event within that group. Think of the event base and event ID as a +person's last name and first name, respectively. A last name identifies a family, and the first name identifies a person within that family. + +The event loop library provides macros to declare and define the event base easily. + +Event base declaration: + +.. code-block:: c + + ESP_EVENT_DECLARE_BASE(EVENT_BASE) + +Event base definition: + +.. code-block:: c + + ESP_EVENT_DEFINE_BASE(EVENT_BASE) + +.. note:: + + In IDF, the base identifiers for system events are uppercase and are postfixed with ``_EVENT``. For example, the base for wifi events is declared and defined + as ``WIFI_EVENT``, the ethernet event base ``ETHERNET_EVENT``, and so on. The purpose is to have event bases look like constants (although + they are global variables considering the defintions of macros ``ESP_EVENT_DECLARE_BASE`` and ``ESP_EVENT_DEFINE_BASE``). + +For event ID's, declaring them as enumerations is recommended. Once again, for visibility, these are typically placed in public header files. + +Event ID: + +.. code-block:: c + + enum { + EVENT_ID_1, + EVENT_ID_2, + EVENT_ID_3, + ... + } + +.. _esp-event-default-loops: + +Default Event Loop +------------------ + +The default event loop is a special type of loop used for system events (WiFi events, for example). The handle for this +loop is hidden from the user. The creation, deletion, handler registration/unregistration and posting of events is done +through a variant of the APIs for user event loops. The table below enumerates those variants, and the user event +loops equivalent. + ++---------------------------------------------------+---------------------------------------------------+ +| User Event Loops | Default Event Loops | ++===================================================+===================================================+ +| :cpp:func:`esp_event_loop_create` | :cpp:func:`esp_event_loop_create_default` | ++---------------------------------------------------+---------------------------------------------------+ +| :cpp:func:`esp_event_loop_delete` | :cpp:func:`esp_event_loop_delete_default` | ++---------------------------------------------------+---------------------------------------------------+ +| :cpp:func:`esp_event_handler_register_with` | :cpp:func:`esp_event_handler_register` | ++---------------------------------------------------+---------------------------------------------------+ +| :cpp:func:`esp_event_handler_unregister_with` | :cpp:func:`esp_event_handler_unregister` | ++---------------------------------------------------+---------------------------------------------------+ +| :cpp:func:`esp_event_post_to` | :cpp:func:`esp_event_post` | ++---------------------------------------------------+---------------------------------------------------+ + +If you compare the signatures for both, they are mostly similar except the for the lack of loop handle +specification for the default event loop APIs. + +Other than the API difference and the special designation to which system events are posted to, there is no difference +to how default event loops and user event loops behave. It is even possible for users to post their own events +to the default event loop, should the user opt to not create their own loops to save memory. + +.. _esp-event-handler-registration: + +Notes on Handler Registration +----------------------------- + +It is possible to register a single handler to multiple events individually, i.e. using multiple calls to :cpp:func:`esp_event_handler_register_with`. +For those multiple calls, the specific event base and event ID can be specified with which the handler should execute. + +However, in some cases it is desirable for a handler to execute on (1) all events that get posted to a loop or (2) all events +of a particular base identifier. This is possible using the special event base identifier ``ESP_EVENT_ANY_BASE`` and +special event ID ``ESP_EVENT_ANY_ID``. These special identifiers may be passed as the event base and event ID arguments +for :cpp:func:`esp_event_handler_register_with`. + +Therefore, the valid arguments to :cpp:func:`esp_event_handler_register_with` are: + +1. , - handler executes when the event with base and event ID gets posted to the loop +2. , ESP_EVENT_ANY_ID - handler executes when any event with base gets posted to the loop +3. ESP_EVENT_ANY_BASE, ESP_EVENT_ANY_ID - handler executes when any event gets posted to the loop + +As an example, suppose the following handler registrations were performed: + +.. code-block:: c + + esp_event_handler_register_with(loop_handle, MY_EVENT_BASE, MY_EVENT_ID, run_on_event_1, ...); + esp_event_handler_register_with(loop_handle, MY_EVENT_BASE, ESP_EVENT_ANY_ID, run_on_event_2, ...); + esp_event_handler_register_with(loop_handle, ESP_EVENT_ANY_BASE, ESP_EVENT_ANY_ID, run_on_event_3, ...); + +If the hypothetical event ``MY_EVENT_BASE``, ``MY_EVENT_ID`` is posted, all three handlers ``run_on_event_1``, ``run_on_event_2``, +and ``run_on_event_3`` would execute. + +If the hypothetical event ``MY_EVENT_BASE``, ``MY_OTHER_EVENT_ID`` is posted, only ``run_on_event_2`` and ``run_on_event_3`` would execute. + +If the hypothetical event ``MY_OTHER_EVENT_BASE``, ``MY_OTHER_EVENT_ID`` is posted, only ``run_on_event_3`` would execute. + +Event loop profiling +-------------------- + +A configuration option :envvar:`CONFIG_EVENT_LOOP_PROFILING` can be enabled in order to activate statistics collection for all event loops created. +The function :cpp:func:`esp_event_dump` can be used to output the collected statistics to a file stream. More details on the information included in the dump +can be found in the :cpp:func:`esp_event_dump` API Reference. + +Application Example +------------------- + +Examples on using the ``esp_event`` library can be found on :example:`system/esp_event`. The examples cover event declaration, loop creation, handler registration +and unregistration and event posting. + +API Reference +------------- + +.. include:: /_build/inc/esp_event.inc +.. include:: /_build/inc/esp_event_base.inc diff --git a/docs/en/api-reference/system/index.rst b/docs/en/api-reference/system/index.rst index c85b238eb..0d24fcc94 100644 --- a/docs/en/api-reference/system/index.rst +++ b/docs/en/api-reference/system/index.rst @@ -14,6 +14,7 @@ System API Inter-Processor Call High Resolution Timer Logging + Event Loop Library Application Level Tracing Power Management Sleep Modes diff --git a/docs/zh_CN/api-reference/system/esp_event.rst b/docs/zh_CN/api-reference/system/esp_event.rst new file mode 100644 index 000000000..5a57e6f32 --- /dev/null +++ b/docs/zh_CN/api-reference/system/esp_event.rst @@ -0,0 +1 @@ +.. include:: ../../../en/api-reference/system/esp_event.rst diff --git a/examples/system/README.md b/examples/system/README.md index ecdc5518c..2426b5e1b 100644 --- a/examples/system/README.md +++ b/examples/system/README.md @@ -1,5 +1,5 @@ # System Examples -Configuration and management of memory, interrupts, WDT (watchdog timer), OTA (over the air updates), deep sleep and logging. +Configuration and management of memory, interrupts, WDT (watchdog timer), OTA (over the air updates), deep sleep logging, and event loops. See the [README.md](../README.md) file in the upper level [examples](../) directory for more information about examples. diff --git a/examples/system/esp_event/default_event_loop/CMakeLists.txt b/examples/system/esp_event/default_event_loop/CMakeLists.txt new file mode 100644 index 000000000..d50c7c603 --- /dev/null +++ b/examples/system/esp_event/default_event_loop/CMakeLists.txt @@ -0,0 +1,6 @@ +# The following lines of boilerplate have to be in your project's CMakeLists +# in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.5) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(default_event_loop) diff --git a/examples/system/esp_event/default_event_loop/Makefile b/examples/system/esp_event/default_event_loop/Makefile new file mode 100644 index 000000000..09722e215 --- /dev/null +++ b/examples/system/esp_event/default_event_loop/Makefile @@ -0,0 +1,9 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# + +PROJECT_NAME := default_event_loop + +include $(IDF_PATH)/make/project.mk + diff --git a/examples/system/esp_event/default_event_loop/README.md b/examples/system/esp_event/default_event_loop/README.md new file mode 100644 index 000000000..bd6ee8805 --- /dev/null +++ b/examples/system/esp_event/default_event_loop/README.md @@ -0,0 +1,166 @@ +# Event Loop Library ('esp_event') Default Event Loop Example + +This example illustrates the basics of the event loop library. To keep the example simple, demonstration is limited to the use of the default event loop. +The default event loop is the event loop the system uses for its events. For some use cases, this could be sufficient. +However, should the user need to create their own event loops, the example `user_event_loops` should be able to provide guidance. + +Here are the things this example illustrates: + +### Declaring and Defining Events + +This example shows the typical setup of having the event base and event IDs declared in a header +file and then having the definitions in a source file. + +Declaration of the event base makes use of the macro `ESP_EVENT_DECLARE_BASE`, while the event IDs are declared as an `enum`; see +``event_source.h``. The source file ``main.c`` holds the definition of the event base using the macro `ESP_EVENT_DEFINE_BASE`. + +### Creating the Default Event Loop + +This example illustrates the creation of the default event loop using the API function `esp_event_loop_create_default`. + +### Posting Events to the Default Event Loop + +Simply put, posting an event to a loop is the act of queueing its handlers for execution. For the default loop, this is done using the API `esp_event_post`. The ability to pass event-specific data to the handler is also illustrated. + +### Handler Registration/Unregistration + +This example illustrates handler registration to the default event loop using `esp_event_handler_register` for (1) specific events, (2) *any* event under a certain base, and (3) *any* event. This also shows the possbility of registering multiple handlers to the same event. + +Unregistering a handler is done using `esp_event_handler_register`. Unregistering a handler means that it no longer executes even when the event it was previously registered to gets posted to the loop. + +## Example Flow Explained + +The example flow is best explained using a sample log output. + +``` +I (297) default_event_loop: setting up +I (297) default_event_loop: starting event sources +I (297) default_event_loop: TIMER_EVENTS:TIMER_EVENT_STARTED: posting to default loop +I (297) default_event_loop: TASK_EVENTS:TASK_ITERATION_EVENT: posting to default loop, 1 out of 5 +I (317) default_event_loop: TIMER_EVENTS:TIMER_EVENT_STARTED: all_event_handler +I (327) default_event_loop: TIMER_EVENTS:TIMER_EVENT_STARTED: timer_any_handler +I (337) default_event_loop: TIMER_EVENTS:TIMER_EVENT_STARTED: timer_started_handler +I (347) default_event_loop: TASK_EVENTS:TASK_ITERATION_EVENT: all_event_handler +I (347) default_event_loop: TASK_EVENTS:TASK_ITERATION_EVENT: task_iteration_handler, executed 1 times +I (827) default_event_loop: TASK_EVENTS:TASK_ITERATION_EVENT: posting to default loop, 2 out of 5 +I (827) default_event_loop: TASK_EVENTS:TASK_ITERATION_EVENT: all_event_handler +I (827) default_event_loop: TASK_EVENTS:TASK_ITERATION_EVENT: task_iteration_handler, executed 2 times +I (1297) default_event_loop: TIMER_EVENTS:TIMER_EVENT_EXPIRY: posting to default loop +I (1297) default_event_loop: TIMER_EVENTS:TIMER_EVENT_EXPIRY: all_event_handler +I (1307) default_event_loop: TIMER_EVENTS:TIMER_EVENT_EXPIRY: timer_any_handler +I (1317) default_event_loop: TIMER_EVENTS:TIMER_EVENT_EXPIRY: timer_expiry_handler, executed 1 out of 3 times +I (1327) default_event_loop: TASK_EVENTS:TASK_ITERATION_EVENT: posting to default loop, 3 out of 5 +I (1337) default_event_loop: TASK_EVENTS:TASK_ITERATION_EVENT: unregistering task_iteration_handler +I (1337) default_event_loop: TASK_EVENTS:TASK_ITERATION_EVENT: all_event_handler +I (1357) default_event_loop: TASK_EVENTS:TASK_ITERATION_EVENT: task_iteration_handler, executed 3 times +I (1867) default_event_loop: TASK_EVENTS:TASK_ITERATION_EVENT: posting to default loop, 4 out of 5 +I (1867) default_event_loop: TASK_EVENTS:TASK_ITERATION_EVENT: all_event_handler +I (2297) default_event_loop: TIMER_EVENTS:TIMER_EVENT_EXPIRY: posting to default loop +I (2297) default_event_loop: TIMER_EVENTS:TIMER_EVENT_EXPIRY: all_event_handler +I (2307) default_event_loop: TIMER_EVENTS:TIMER_EVENT_EXPIRY: timer_any_handler +I (2317) default_event_loop: TIMER_EVENTS:TIMER_EVENT_EXPIRY: timer_expiry_handler, executed 2 out of 3 times +I (2367) default_event_loop: TASK_EVENTS:TASK_ITERATION_EVENT: posting to default loop, 5 out of 5 +I (2367) default_event_loop: TASK_EVENTS:TASK_ITERATION_EVENT: all_event_handler +I (3297) default_event_loop: TIMER_EVENTS:TIMER_EVENT_EXPIRY: posting to default loop +I (3297) default_event_loop: TIMER_EVENTS:TIMER_EVENT_EXPIRY: all_event_handler +I (3307) default_event_loop: TIMER_EVENTS:TIMER_EVENT_EXPIRY: timer_any_handler +I (3317) default_event_loop: TIMER_EVENTS:TIMER_EVENT_STOPPED: posting to default loop +I (3327) default_event_loop: TIMER_EVENTS:TIMER_EVENT_EXPIRY: timer_expiry_handler, executed 3 out of 3 times +I (3337) default_event_loop: TIMER_EVENTS:TIMER_EVENT_STOPPED: all_event_handler +I (3337) default_event_loop: TIMER_EVENTS:TIMER_EVENT_STOPPED: timer_any_handler +I (3347) default_event_loop: TIMER_EVENTS:TIMER_EVENT_STOPPED: timer_stopped_handler +I (3357) default_event_loop: TIMER_EVENTS:TIMER_EVENT_STOPPED: deleted timer event source +I (3367) default_event_loop: TASK_EVENTS:TASK_ITERATION_EVENT: deleting task event source +``` + +### Setting + +This example uses two event sources: a periodic timer and a task with a loop inside. Events are raised for the periodic timer when (1) the timer is started (2) the timer period expires and (3) the timer is stopped. Events are raised for the when (1) the loop iterates. + +All of the events mentioned above has their own specific handler. There are additional handlers, however. One handler executes when *any* event under the periodic timer event is posted; while the other executes if *any* event is posted. + +The number of periodic timer expiries and loop iterations are limited. When the limit for the timer expiry is reached, +the timer is stopped. When the limit for the iterarations is reached, the task is deleted. In the case of the loop iteration, there is another limit: the number of iterations for when its handler will be unregistered. + +### Step-by-Step Explanation + +The following text explains the important points of the sample log output. + +a. + +``` +I (297) default_event_loop: setting up +``` +At this stage the default event loop is created, as well as the handlers for the different events registered. + +b. +``` +I (297) default_event_loop: starting event sources +I (297) default_event_loop: TIMER_EVENTS:TIMER_EVENT_STARTED: posting to default loop +I (297) default_event_loop: TASK_EVENTS:TASK_ITERATION_EVENT: posting to default loop, 1 out of 5 +``` + +The two event sources are started. The respective events are posted to the default event loop. + +c. +``` +I (317) default_event_loop: TIMER_EVENTS:TIMER_EVENT_STARTED: all_event_handler +I (327) default_event_loop: TIMER_EVENTS:TIMER_EVENT_STARTED: timer_any_handler +I (337) default_event_loop: TIMER_EVENTS:TIMER_EVENT_STARTED: timer_started_handler +``` +``` +I (347) default_event_loop: TASK_EVENTS:TASK_ITERATION_EVENT: all_event_handler +I (347) default_event_loop: TASK_EVENTS:TASK_ITERATION_EVENT: task_iteration_handler, executed 1 times +``` +The handlers are executed for the events posted in **(b)**. In addition to event-specific handlers `timer_started_handler` +and `task_iteration_handler`, the handlers `timer_any_handler` and `all_event_handler` also executed. + +The handler `timer_any_handler` executes for *any* timer event. It can be seen executing for the timer expiry and timer stopped events in the upcoming parts of the log. + +On the other hand, `all_event_handler` executes for *any* event. This is the reason why it executes for both ``TIMER_EVENTS:TIMER_EVENT_STARTED`` and ``TASK_EVENTS:TASK_ITERATION_EVENT``. + +The proceeding lines of the log follows the same pattern: the event is posted to the loop and the handlers are executed. + +d. + +``` +... +I (1337) default_event_loop: TASK_EVENTS:TASK_ITERATION_EVENT: unregistering task_iteration_handler +``` +At this point in the execution the handler `task_iteration_handler` is unregistered, therefore it no longer executes +when the event ``TASK_EVENTS:TASK_ITERATION_EVENT`` is posted. +``` +I (1867) default_event_loop: TASK_EVENTS:TASK_ITERATION_EVENT: posting to default loop, 4 out of 5 +I (1867) default_event_loop: TASK_EVENTS:TASK_ITERATION_EVENT: all_event_handler + +... + +I (2367) default_event_loop: TASK_EVENTS:TASK_ITERATION_EVENT: posting to default loop, 5 out of 5 +I (2367) default_event_loop: TASK_EVENTS:TASK_ITERATION_EVENT: all_event_handler +``` +The iteration event continues to get posted, but only `all_event_handler` gets executed. + +e. +``` +... +I (3297) default_event_loop: TIMER_EVENTS:TIMER_EVENT_EXPIRY: posting to default loop +I (3297) default_event_loop: TIMER_EVENTS:TIMER_EVENT_EXPIRY: all_event_handler +I (3307) default_event_loop: TIMER_EVENTS:TIMER_EVENT_EXPIRY: timer_any_handler +I (3317) default_event_loop: TIMER_EVENTS:TIMER_EVENT_STOPPED: posting to default loop +I (3327) default_event_loop: TIMER_EVENTS:TIMER_EVENT_EXPIRY: timer_expiry_handler, executed 3 out of 3 times +... +I (3347) default_event_loop: TIMER_EVENTS:TIMER_EVENT_STOPPED: timer_stopped_handler +I (3357) default_event_loop: TIMER_EVENTS:TIMER_EVENT_STOPPED: deleted timer event source +``` +When the periodic timer expiry limit is reached, the event ``TIMER_EVENTS:TIMER_EVENT_STOPPED`` is posted to the loop. The periodic timer is consequently deleted in the handler `timer_stopped_handler`. + +``` +... +I (3367) default_event_loop: TASK_EVENTS:TASK_ITERATION_EVENT: deleting task event source +... +``` +The task containing the loop that posts iteration events also gets deleted. The example ends at this point. + +--- + +See the README.md file in the upper level 'examples' directory for more information about examples. diff --git a/examples/system/esp_event/default_event_loop/example_test.py b/examples/system/esp_event/default_event_loop/example_test.py new file mode 100644 index 000000000..1d5f2d9fa --- /dev/null +++ b/examples/system/esp_event/default_event_loop/example_test.py @@ -0,0 +1,100 @@ +from __future__ import print_function +import re +import os +import sys + +# this is a test case write with tiny-test-fw. +# to run test cases outside tiny-test-fw, +# we need to set environment variable `TEST_FW_PATH`, +# then get and insert `TEST_FW_PATH` to sys path before import FW module +test_fw_path = os.getenv('TEST_FW_PATH') +if test_fw_path and test_fw_path not in sys.path: + sys.path.insert(0, test_fw_path) + +import TinyFW +import IDF + +# Timer events +TIMER_EVENT_LIMIT = 3 + +TIMER_EXPIRY_HANDLING = "TIMER_EVENTS:TIMER_EVENT_EXPIRY: timer_expiry_handler, executed {} out of " + str(TIMER_EVENT_LIMIT) + " times" + +# Task events +TASK_ITERATION_LIMIT = 5 +TASK_UNREGISTRATION_LIMIT = 3 + +TASK_ITERATION_POST = "TASK_EVENTS:TASK_ITERATION_EVENT: posting to default loop, {} out of " + str(TASK_ITERATION_LIMIT) +TASK_ITERATION_HANDLING = "TASK_EVENTS:TASK_ITERATION_EVENT: task_iteration_handler, executed {} times" + +def _test_timer_events(dut): + dut.start_app() + + print("Checking timer events posting and handling") + + dut.expect("setting up") + dut.expect("starting event sources") + + print("Finished setup") + + dut.expect("TIMER_EVENTS:TIMER_EVENT_STARTED: posting to default loop") + print("Posted timer started event") + dut.expect("TIMER_EVENTS:TIMER_EVENT_STARTED: all_event_handler") + dut.expect("TIMER_EVENTS:TIMER_EVENT_STARTED: timer_any_handler") + dut.expect("TIMER_EVENTS:TIMER_EVENT_STARTED: timer_started_handler") + print("Handled timer started event") + + for expiries in range(1, TIMER_EVENT_LIMIT + 1): + dut.expect("TIMER_EVENTS:TIMER_EVENT_EXPIRY: posting to default loop") + print("Posted timer expiry event {} out of {}".format(expiries, TIMER_EVENT_LIMIT)) + + if expiries < TIMER_EVENT_LIMIT: + dut.expect_all("TIMER_EVENTS:TIMER_EVENT_EXPIRY: all_event_handler", + "TIMER_EVENTS:TIMER_EVENT_EXPIRY: timer_any_handler", + TIMER_EXPIRY_HANDLING.format(expiries)) + else: + dut.expect_all("TIMER_EVENTS:TIMER_EVENT_STOPPED: posting to default loop", + "TIMER_EVENTS:TIMER_EVENT_EXPIRY: all_event_handler", + "TIMER_EVENTS:TIMER_EVENT_EXPIRY: timer_any_handler", + TIMER_EXPIRY_HANDLING.format(expiries)) + print("Posted timer stopped event") + + print("Handled timer expiry event {} out of {}".format(expiries, TIMER_EVENT_LIMIT)) + + dut.expect("TIMER_EVENTS:TIMER_EVENT_STOPPED: timer_stopped_handler") + dut.expect("TIMER_EVENTS:TIMER_EVENT_STOPPED: deleted timer event source") + print("Handled timer stopped event") + +def _test_iteration_events(dut): + dut.start_app() + + print("Checking iteration events posting and handling") + dut.expect("setting up") + dut.expect("starting event sources") + print("Finished setup") + + for iteration in range(1, TASK_ITERATION_LIMIT + 1): + dut.expect(TASK_ITERATION_POST.format(iteration)) + print("Posted iteration {} out of {}".format(iteration, TASK_ITERATION_LIMIT)) + + if iteration < TASK_UNREGISTRATION_LIMIT: + dut.expect_all("TASK_EVENTS:TASK_ITERATION_EVENT: all_event_handler", TASK_ITERATION_HANDLING.format(iteration)) + elif iteration == TASK_UNREGISTRATION_LIMIT: + dut.expect_all("TASK_EVENTS:TASK_ITERATION_EVENT: unregistering task_iteration_handler", "TASK_EVENTS:TASK_ITERATION_EVENT: all_event_handler") + print("Unregistered handler at iteration {} out of {}".format(iteration, TASK_ITERATION_LIMIT)) + else: + dut.expect_all("TASK_EVENTS:TASK_ITERATION_EVENT: all_event_handler") + + print("Handled iteration {} out of {}".format(iteration, TASK_ITERATION_LIMIT)) + + dut.expect("TASK_EVENTS:TASK_ITERATION_EVENT: deleting task event source") + print("Deleted task event source") + +@IDF.idf_example_test(env_tag='Example_WIFI') +def test_default_event_loop_example(env, extra_data): + dut = env.get_dut('default_event_loop', 'examples/system/event/default_event_loop') + + _test_iteration_events(dut) + _test_timer_events(dut) + +if __name__ == '__main__': + test_default_event_loop_example() diff --git a/examples/system/esp_event/default_event_loop/main/CMakeLists.txt b/examples/system/esp_event/default_event_loop/main/CMakeLists.txt new file mode 100644 index 000000000..85970762a --- /dev/null +++ b/examples/system/esp_event/default_event_loop/main/CMakeLists.txt @@ -0,0 +1,4 @@ +set(COMPONENT_SRCS "main.c") +set(COMPONENT_ADD_INCLUDEDIRS ".") + +register_component() diff --git a/examples/system/esp_event/default_event_loop/main/component.mk b/examples/system/esp_event/default_event_loop/main/component.mk new file mode 100644 index 000000000..e19e22a53 --- /dev/null +++ b/examples/system/esp_event/default_event_loop/main/component.mk @@ -0,0 +1,8 @@ +# +# Main component makefile. +# +# This Makefile can be left empty. By default, it will take the sources in the +# src/ directory, compile them and link them into lib(subdirectory_name).a +# in the build directory. This behaviour is entirely configurable, +# please read the ESP-IDF documents if you need to do this. +# \ No newline at end of file diff --git a/examples/system/esp_event/default_event_loop/main/event_source.h b/examples/system/esp_event/default_event_loop/main/event_source.h new file mode 100644 index 000000000..91ca4f10c --- /dev/null +++ b/examples/system/esp_event/default_event_loop/main/event_source.h @@ -0,0 +1,53 @@ +/* esp_event (event loop library) basic example + + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ + +#ifndef EVENT_SOURCE_H_ +#define EVENT_SOURCE_H_ + +#include "esp_event.h" +#include "esp_event_loop.h" +#include "esp_timer.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// This example makes use of two event sources: a periodic timer, and a task. + +// Declarations for event source 1: periodic timer +#define TIMER_EXPIRIES_COUNT 3 // number of times the periodic timer expires before being stopped +#define TIMER_PERIOD 1000000 // period of the timer event source in microseconds + +extern esp_timer_handle_t g_timer; // the periodic timer object + +// Declare an event base +ESP_EVENT_DECLARE_BASE(TIMER_EVENTS); // declaration of the timer events family + +enum { // declaration of the specific events under the timer event family + TIMER_EVENT_STARTED, // raised when the timer is first started + TIMER_EVENT_EXPIRY, // raised when a period of the timer has elapsed + TIMER_EVENT_STOPPED // raised when the timer has been stopped +}; + +// Declarations for event source 2: task +#define TASK_ITERATIONS_COUNT 5 // number of times the task iterates +#define TASK_ITERATIONS_UNREGISTER 3 // count at which the task event handler is unregistered +#define TASK_PERIOD 500 // period of the task loop in milliseconds + +ESP_EVENT_DECLARE_BASE(TASK_EVENTS); // declaration of the task events family + +enum { + TASK_ITERATION_EVENT, // raised during an iteration of the loop within the task +}; + +#ifdef __cplusplus +} +#endif + +#endif // #ifndef EVENT_SOURCE_H_ \ No newline at end of file diff --git a/examples/system/esp_event/default_event_loop/main/main.c b/examples/system/esp_event/default_event_loop/main/main.c new file mode 100644 index 000000000..5af9e52c9 --- /dev/null +++ b/examples/system/esp_event/default_event_loop/main/main.c @@ -0,0 +1,179 @@ +/* esp_event (event loop library) basic example + + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ + +#include "esp_log.h" + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +#include "event_source.h" + +static const char* TAG = "default_event_loop"; + +static char* get_id_string(esp_event_base_t base, int32_t id) { + char* event = ""; + if (base == TIMER_EVENTS) { + switch(id) { + case TIMER_EVENT_STARTED: + event = "TIMER_EVENT_STARTED"; + break; + case TIMER_EVENT_EXPIRY: + event = "TIMER_EVENT_EXPIRY"; + break; + case TIMER_EVENT_STOPPED: + event = "TIMER_EVENT_STOPPED"; + break; + } + } else { + event = "TASK_ITERATION_EVENT"; + } + return event; +} + +/* Event source periodic timer related definitions */ +ESP_EVENT_DEFINE_BASE(TIMER_EVENTS); + +esp_timer_handle_t TIMER; + +// Callback that will be executed when the timer period lapses. Posts the timer expiry event +// to the default event loop. +static void timer_callback(void* arg) +{ + ESP_LOGI(TAG, "%s:%s: posting to default loop", TIMER_EVENTS, get_id_string(TIMER_EVENTS, TIMER_EVENT_EXPIRY)); + ESP_ERROR_CHECK(esp_event_post(TIMER_EVENTS, TIMER_EVENT_EXPIRY, NULL, 0, portMAX_DELAY)); +} + +// Handler which executes when the timer started event gets executed by the loop. +static void timer_started_handler(void* handler_args, esp_event_base_t base, int32_t id, void* event_data) +{ + ESP_LOGI(TAG, "%s:%s: timer_started_handler", base, get_id_string(base, id)); +} + +// Handler which executes when the timer expiry event gets executed by the loop. This handler keeps track of +// how many times the timer expired. When a set number of expiry is reached, the handler stops the timer +// and sends a timer stopped event. +static void timer_expiry_handler(void* handler_args, esp_event_base_t base, int32_t id, void* event_data) +{ + static int count = 0; + + count++; + + if (count >= TIMER_EXPIRIES_COUNT) { + // Stop the timer + ESP_ERROR_CHECK(esp_timer_stop(TIMER)); + + ESP_LOGI(TAG, "%s:%s: posting to default loop", base, get_id_string(base, TIMER_EVENT_STOPPED)); + + // Post the event that the timer has been stopped + ESP_ERROR_CHECK(esp_event_post(TIMER_EVENTS, TIMER_EVENT_STOPPED, NULL, 0, portMAX_DELAY)); + } + + ESP_LOGI(TAG, "%s:%s: timer_expiry_handler, executed %d out of %d times", base, get_id_string(base, id), count, TIMER_EXPIRIES_COUNT); +} + +// Handler which executes when any timer event (started, expiry, stopped) get executed by the loop +static void timer_any_handler(void* handler_args, esp_event_base_t base, int32_t id, void* event_data) +{ + ESP_LOGI(TAG, "%s:%s: timer_any_handler", base, get_id_string(base, id)); +} + +// Handler which executes when the timer stopped event gets executed by the loop. Since the timer has been +// stopped, it is safe to delete it. +static void timer_stopped_handler(void* handler_args, esp_event_base_t base, int32_t id, void* event_data) +{ + ESP_LOGI(TAG, "%s:%s: timer_stopped_handler", base, get_id_string(base, id)); + + // Delete the timer + esp_timer_delete(TIMER); + + ESP_LOGI(TAG, "%s:%s: deleted timer event source", base, get_id_string(base, id)); +} + +/* Event source task related definitions */ +ESP_EVENT_DEFINE_BASE(TASK_EVENTS) + +static void task_iteration_handler(void* handler_args, esp_event_base_t base, int32_t id, void* event_data) +{ + int iteration = *((int*) event_data); + ESP_LOGI(TAG, "%s:%s: task_iteration_handler, executed %d times", base, get_id_string(base, id), iteration); +} + +static void task_event_source(void* args) +{ + for(int iteration = 1; iteration <= TASK_ITERATIONS_COUNT; iteration++) { + + ESP_LOGI(TAG, "%s:%s: posting to default loop, %d out of %d", TASK_EVENTS, + get_id_string(TASK_EVENTS, TASK_ITERATION_EVENT), iteration, TASK_ITERATIONS_COUNT); + + // Post that the loop has iterated. Notice that the iteration count is passed to the handler. Take note + // that data passed during event posting is a deep copy of the original data. + ESP_ERROR_CHECK(esp_event_post(TASK_EVENTS, TASK_ITERATION_EVENT, &iteration, sizeof(iteration), portMAX_DELAY)); + + if (iteration == TASK_ITERATIONS_UNREGISTER){ + ESP_LOGI(TAG, "%s:%s: unregistering task_iteration_handler", TASK_EVENTS, get_id_string(TASK_EVENTS, TASK_ITERATION_EVENT)); + ESP_ERROR_CHECK(esp_event_handler_unregister(TASK_EVENTS, TASK_ITERATION_EVENT, task_iteration_handler)); + } + + vTaskDelay(pdMS_TO_TICKS(TASK_PERIOD)); + } + + vTaskDelay(pdMS_TO_TICKS(TASK_PERIOD)); + + ESP_LOGI(TAG, "%s:%s: deleting task event source", TASK_EVENTS, get_id_string(TASK_EVENTS, TASK_ITERATION_EVENT)); + + vTaskDelete(NULL); +} + +/* Handler for all events */ +static void all_event_handler(void* handler_args, esp_event_base_t base, int32_t id, void* event_data) +{ + ESP_LOGI(TAG, "%s:%s: all_event_handler", base, get_id_string(base, id)); +} + +/* Example main */ +void app_main(void) +{ + ESP_LOGI(TAG, "setting up"); + + // Create the default event loop + ESP_ERROR_CHECK(esp_event_loop_create_default()); + + // Register the specific timer event handlers. + ESP_ERROR_CHECK(esp_event_handler_register(TIMER_EVENTS, TIMER_EVENT_STARTED, timer_started_handler, NULL)); + ESP_ERROR_CHECK(esp_event_handler_register(TIMER_EVENTS, TIMER_EVENT_EXPIRY, timer_expiry_handler, NULL)); + ESP_ERROR_CHECK(esp_event_handler_register(TIMER_EVENTS, TIMER_EVENT_STOPPED, timer_stopped_handler, NULL)); + + // Register the handler for all timer family events. This will execute if the timer is started, expired or is stopped. + ESP_ERROR_CHECK(esp_event_handler_register(TIMER_EVENTS, ESP_EVENT_ANY_ID, timer_any_handler, NULL)); + + // Register the handler for task iteration event. + ESP_ERROR_CHECK(esp_event_handler_register(TASK_EVENTS, TASK_ITERATION_EVENT, task_iteration_handler, NULL)); + + // Register the handler for all event. This will execute if either the timer events or the task iteration event + // is posted to the default loop. + ESP_ERROR_CHECK(esp_event_handler_register(ESP_EVENT_ANY_BASE, ESP_EVENT_ANY_ID, all_event_handler, NULL)); + + // Create and start the event sources + esp_timer_create_args_t timer_args = { + .callback = &timer_callback, + }; + + ESP_ERROR_CHECK(esp_timer_create(&timer_args, &TIMER)); + + ESP_LOGI(TAG, "starting event sources"); + + // Create the event source task with the same priority as the current task + xTaskCreate(task_event_source, "task_event_source", 2048, NULL, uxTaskPriorityGet(NULL), NULL); + + ESP_ERROR_CHECK(esp_timer_start_periodic(TIMER, TIMER_PERIOD)); + + // Post the timer started event + ESP_LOGI(TAG, "%s:%s: posting to default loop", TIMER_EVENTS, get_id_string(TIMER_EVENTS, TIMER_EVENT_STARTED)); + ESP_ERROR_CHECK(esp_event_post(TIMER_EVENTS, TIMER_EVENT_STARTED, NULL, 0, portMAX_DELAY)); +} \ No newline at end of file diff --git a/examples/system/esp_event/user_event_loops/CMakeLists.txt b/examples/system/esp_event/user_event_loops/CMakeLists.txt new file mode 100644 index 000000000..88dd1ff56 --- /dev/null +++ b/examples/system/esp_event/user_event_loops/CMakeLists.txt @@ -0,0 +1,6 @@ +# The following lines of boilerplate have to be in your project's CMakeLists +# in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.5) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(user_event_loops) diff --git a/examples/system/esp_event/user_event_loops/Makefile b/examples/system/esp_event/user_event_loops/Makefile new file mode 100644 index 000000000..7ff7b64f0 --- /dev/null +++ b/examples/system/esp_event/user_event_loops/Makefile @@ -0,0 +1,9 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# + +PROJECT_NAME := user_event_loops + +include $(IDF_PATH)/make/project.mk + diff --git a/examples/system/esp_event/user_event_loops/README.md b/examples/system/esp_event/user_event_loops/README.md new file mode 100644 index 000000000..0baf163a0 --- /dev/null +++ b/examples/system/esp_event/user_event_loops/README.md @@ -0,0 +1,117 @@ +# Event Loop Library ('esp_event') User Event Loops Example + +This example demonstrates the creation and use of user event loops. This example is a supplement +to the `default_event_loop` example, if the default event loop is not sufficient for the user's use case. + +Here are the things this example illustrates: + +### Creating Event Loops + +Creating a loop entails populating the structure `esp_event_loop_args_t` with the desired parameters and calling `esp_event_loop_create`. The call to `esp_event_loop_create` produces a handle to the loop, which is used to perform actions on that loop such as handler registration/unregistration and posting events. + +### Running Event Loops + +Depending on the parameters, the user can create either a loop with a dedicated task or one without. The purpose of the dedicated task is to unqueue events from the loop and execute its handlers. For loops without the dedicated task, the user should make a call to `esp_event_loop_run` in an application task. + +### Handler Registration/Unregistration, + +Handler registration and unregistration works the same way as the default event loop, just with a different API, `esp_event_handler_register_with` and `esp_event_handler_register_with` respectively. There are two things this example highlights: (1) the possibility of registering the same handler for different loops and (2) the ability to pass static data to handlers. + +### Posting Events to the Default Event Loop + +Posting events also works the same way as the default event loop, except with a different API, `esp_event_post_to`. + +## Example Flow Explained + +The example flow is best explained using the sample log output. + +``` +I (296) user_event_loops: setting up +I (296) user_event_loops: starting event source +I (296) user_event_loops: starting application task +I (296) user_event_loops: posting TASK_EVENTS:TASK_ITERATION_EVENT to loop_without_task, iteration 1 out of 10 +I (316) user_event_loops: application_task: running application task +I (326) user_event_loops: handling TASK_EVENTS:TASK_ITERATION_EVENT from loop_without_task, iteration 1 +I (826) user_event_loops: posting TASK_EVENTS:TASK_ITERATION_EVENT to loop_with_task, iteration 2 out of 10 +I (826) user_event_loops: handling TASK_EVENTS:TASK_ITERATION_EVENT from loop_with_task, iteration 2 +I (1326) user_event_loops: posting TASK_EVENTS:TASK_ITERATION_EVENT to loop_without_task, iteration 3 out of 10 +I (1326) user_event_loops: handling TASK_EVENTS:TASK_ITERATION_EVENT from loop_without_task, iteration 3 +I (1426) user_event_loops: application_task: running application task +I (1826) user_event_loops: posting TASK_EVENTS:TASK_ITERATION_EVENT to loop_with_task, iteration 4 out of 10 +I (1826) user_event_loops: handling TASK_EVENTS:TASK_ITERATION_EVENT from loop_with_task, iteration 4 +I (2326) user_event_loops: posting TASK_EVENTS:TASK_ITERATION_EVENT to loop_without_task, iteration 5 out of 10 +I (2326) user_event_loops: handling TASK_EVENTS:TASK_ITERATION_EVENT from loop_without_task, iteration 5 +I (2526) user_event_loops: application_task: running application task +I (2826) user_event_loops: posting TASK_EVENTS:TASK_ITERATION_EVENT to loop_with_task, iteration 6 out of 10 +I (2826) user_event_loops: handling TASK_EVENTS:TASK_ITERATION_EVENT from loop_with_task, iteration 6 +I (3326) user_event_loops: posting TASK_EVENTS:TASK_ITERATION_EVENT to loop_without_task, iteration 7 out of 10 +I (3326) user_event_loops: handling TASK_EVENTS:TASK_ITERATION_EVENT from loop_without_task, iteration 7 +I (3626) user_event_loops: application_task: running application task +I (3826) user_event_loops: posting TASK_EVENTS:TASK_ITERATION_EVENT to loop_with_task, iteration 8 out of 10 +I (3826) user_event_loops: handling TASK_EVENTS:TASK_ITERATION_EVENT from loop_with_task, iteration 8 +I (4326) user_event_loops: posting TASK_EVENTS:TASK_ITERATION_EVENT to loop_without_task, iteration 9 out of 10 +I (4326) user_event_loops: handling TASK_EVENTS:TASK_ITERATION_EVENT from loop_without_task, iteration 9 +I (4726) user_event_loops: application_task: running application task +I (4826) user_event_loops: posting TASK_EVENTS:TASK_ITERATION_EVENT to loop_with_task, iteration 10 out of 10 +I (4826) user_event_loops: handling TASK_EVENTS:TASK_ITERATION_EVENT from loop_with_task, iteration 10 +I (5826) user_event_loops: application_task: running application task +I (5826) user_event_loops: deleting task event source +I (6926) user_event_loops: application_task: running application task +I (8026) user_event_loops: application_task: running application task +I (9126) user_event_loops: application_task: running application task +... +``` + +### Setting + +This example has a single event source: a task with a loop inside. Events are raised for the task event source when the loop iterates. + +Two loops are created, one with a dedicated task and one without. Events are posted to either loops,depending on whether the iteration is odd or even. For the loop with a dedicated task, event handlers are automatically executed. However, for the loop without the dedicated task, a call to run the loop is made in one of the application tasks. As a result, the execution of the event handlers for this loop is interspersed with the execution of application task code. + +### Step-by-Step Explanation + +The following text explains the important points of this example's sample log output. + +a. + +``` +I (296) user_event_loops: setting up +I (296) user_event_loops: starting event source +I (296) user_event_loops: starting application task +``` +At this stage the two event loops are created, as well as the handlers for the iteration event registered. The event source is started, which will post the event to the appropriate loop. The application task which makes the call to run the loop without dedicated task, is also created and started. + +b. +``` +I (296) user_event_loops: posting TASK_EVENTS:TASK_ITERATION_EVENT to loop_without_task, iteration 1 out of 10 +I (316) user_event_loops: application_task: running application task +I (326) user_event_loops: handling TASK_EVENTS:TASK_ITERATION_EVENT from loop_without_task, iteration 1 +I (826) user_event_loops: posting TASK_EVENTS:TASK_ITERATION_EVENT to loop_with_task, iteration 2 out of 10 +I (826) user_event_loops: handling TASK_EVENTS:TASK_ITERATION_EVENT from loop_with_task, iteration 2 +I (1326) user_event_loops: posting TASK_EVENTS:TASK_ITERATION_EVENT to loop_without_task, iteration 3 out of 10 +I (1326) user_event_loops: handling TASK_EVENTS:TASK_ITERATION_EVENT from loop_without_task, iteration 3 +I (1426) user_event_loops: application_task: running application task +I (1826) user_event_loops: posting TASK_EVENTS:TASK_ITERATION_EVENT to loop_with_task, iteration 4 out of 10 +... +``` +In this section of the log we see the odd iterations posted to the loop without dedicated task, and the even iterations to the loop with a dedicated task. For the event with dedicated task, event handlers are executed automatically. The loop without a dedicated task, on the other hand, runs in the context of the application task. + +c. + +``` +... +I (4826) user_event_loops: posting TASK_EVENTS:TASK_ITERATION_EVENT to loop_with_task, iteration 10 out of 10 +I (4826) user_event_loops: handling TASK_EVENTS:TASK_ITERATION_EVENT from loop_with_task, iteration 10 +I (5826) user_event_loops: application_task: running application task +I (5826) user_event_loops: deleting task event source +I (6926) user_event_loops: application_task: running application task +I (8026) user_event_loops: application_task: running application task +I (9126) user_event_loops: application_task: running application task +... +``` + +The last of the iteration event is posted, and the event source is deleted. Because the loop without the task no longer receive events to execute, only the application task code executes. + +--- + +See the README.md file in the upper level 'examples' directory for more information about examples. diff --git a/examples/system/esp_event/user_event_loops/example_test.py b/examples/system/esp_event/user_event_loops/example_test.py new file mode 100644 index 000000000..f4c716430 --- /dev/null +++ b/examples/system/esp_event/user_event_loops/example_test.py @@ -0,0 +1,50 @@ +from __future__ import print_function +import re +import os +import sys + +# this is a test case write with tiny-test-fw. +# to run test cases outside tiny-test-fw, +# we need to set environment variable `TEST_FW_PATH`, +# then get and insert `TEST_FW_PATH` to sys path before import FW module +test_fw_path = os.getenv('TEST_FW_PATH') +if test_fw_path and test_fw_path not in sys.path: + sys.path.insert(0, test_fw_path) + +import TinyFW +import IDF + +TASK_ITERATION_LIMIT = 10 + +TASK_ITERATION_POSTING = "posting TASK_EVENTS:TASK_ITERATION_EVENT to {}, iteration {} out of " + str(TASK_ITERATION_LIMIT) +TASK_ITERATION_HANDLING = "handling TASK_EVENTS:TASK_ITERATION_EVENT from {}, iteration {}" + +@IDF.idf_example_test(env_tag='Example_WIFI') +def test_user_event_loops_example(env, extra_data): + dut = env.get_dut('user_event_loops', 'examples/system/event/user_event_loops') + + dut.start_app() + + dut.expect("setting up") + dut.expect("starting event source") + dut.expect("starting application task") + print("Finished setup") + + for iteration in range(1, TASK_ITERATION_LIMIT + 1): + loop = None + + if (iteration % 2 == 0): + loop = "loop_with_task" + else: + loop = "loop_without_task" + + dut.expect(TASK_ITERATION_POSTING.format(loop, iteration)) + print("Posted iteration {} to {}".format(iteration, loop)) + dut.expect(TASK_ITERATION_HANDLING.format(loop, iteration)) + print("Handled iteration {} from {}".format(iteration, loop)) + + dut.expect("deleting task event source") + print("Deleted task event source") + +if __name__ == '__main__': + test_user_event_loops_example() diff --git a/examples/system/esp_event/user_event_loops/main/CMakeLists.txt b/examples/system/esp_event/user_event_loops/main/CMakeLists.txt new file mode 100644 index 000000000..85970762a --- /dev/null +++ b/examples/system/esp_event/user_event_loops/main/CMakeLists.txt @@ -0,0 +1,4 @@ +set(COMPONENT_SRCS "main.c") +set(COMPONENT_ADD_INCLUDEDIRS ".") + +register_component() diff --git a/examples/system/esp_event/user_event_loops/main/component.mk b/examples/system/esp_event/user_event_loops/main/component.mk new file mode 100644 index 000000000..e19e22a53 --- /dev/null +++ b/examples/system/esp_event/user_event_loops/main/component.mk @@ -0,0 +1,8 @@ +# +# Main component makefile. +# +# This Makefile can be left empty. By default, it will take the sources in the +# src/ directory, compile them and link them into lib(subdirectory_name).a +# in the build directory. This behaviour is entirely configurable, +# please read the ESP-IDF documents if you need to do this. +# \ No newline at end of file diff --git a/examples/system/esp_event/user_event_loops/main/event_source.h b/examples/system/esp_event/user_event_loops/main/event_source.h new file mode 100644 index 000000000..73a3b69c1 --- /dev/null +++ b/examples/system/esp_event/user_event_loops/main/event_source.h @@ -0,0 +1,35 @@ +/* esp_event (event loop library) basic example + + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ + +#ifndef EVENT_SOURCE_H_ +#define EVENT_SOURCE_H_ + +#include "esp_event.h" +#include "esp_event_loop.h" +#include "esp_timer.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// Declarations for the event source +#define TASK_ITERATIONS_COUNT 10 // number of times the task iterates +#define TASK_PERIOD 500 // period of the task loop in milliseconds + +ESP_EVENT_DECLARE_BASE(TASK_EVENTS); // declaration of the task events family + +enum { + TASK_ITERATION_EVENT // raised during an iteration of the loop within the task +}; + +#ifdef __cplusplus +} +#endif + +#endif // #ifndef EVENT_SOURCE_H_ \ No newline at end of file diff --git a/examples/system/esp_event/user_event_loops/main/main.c b/examples/system/esp_event/user_event_loops/main/main.c new file mode 100644 index 000000000..a604ff051 --- /dev/null +++ b/examples/system/esp_event/user_event_loops/main/main.c @@ -0,0 +1,123 @@ +/* esp_event (event loop library) basic example + + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ + +#include "esp_log.h" + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +#include "event_source.h" +#include "esp_event_base.h" + +static const char* TAG = "user_event_loops"; + +// Event loops +esp_event_loop_handle_t loop_with_task; +esp_event_loop_handle_t loop_without_task; + +static void application_task(void* args) +{ + while(1) { + ESP_LOGI(TAG, "application_task: running application task"); + esp_event_loop_run(loop_without_task, 100); + vTaskDelay(10); + } +} + +/* Event source task related definitions */ +ESP_EVENT_DEFINE_BASE(TASK_EVENTS) + +TaskHandle_t g_task; + +static void task_iteration_handler(void* handler_args, esp_event_base_t base, int32_t id, void* event_data) +{ + // Two types of data can be passed in to the event handler: the handler specific data and the event-specific data. + // + // The handler specific data (handler_args) is a pointer to the original data, therefore, the user should ensure that + // the memory location it points to is still valid when the handler executes. + // + // The event-specific data (event_data) is a pointer to a deep copy of the original data, and is managed automatically. + int iteration = *((int*) event_data); + + char* loop; + + if (handler_args == loop_with_task) { + loop = "loop_with_task"; + } else { + loop = "loop_without_task"; + } + + ESP_LOGI(TAG, "handling %s:%s from %s, iteration %d", base, "TASK_ITERATION_EVENT", loop, iteration); +} + +static void task_event_source(void* args) +{ + for(int iteration = 1; iteration <= TASK_ITERATIONS_COUNT; iteration++) { + esp_event_loop_handle_t loop_to_post_to; + + if (iteration % 2 == 0) { + // if even, post to the event loop with dedicated task + loop_to_post_to = loop_with_task; + } else { + // if odd, post to the event loop without a dedicated task + loop_to_post_to = loop_without_task; + } + + ESP_LOGI(TAG, "posting %s:%s to %s, iteration %d out of %d", TASK_EVENTS, "TASK_ITERATION_EVENT", + loop_to_post_to == loop_with_task ? "loop_with_task" : "loop_without_task", + iteration, TASK_ITERATIONS_COUNT); + + ESP_ERROR_CHECK(esp_event_post_to(loop_to_post_to, TASK_EVENTS, TASK_ITERATION_EVENT, &iteration, sizeof(iteration), portMAX_DELAY)); + + vTaskDelay(pdMS_TO_TICKS(TASK_PERIOD)); + } + + vTaskDelay(pdMS_TO_TICKS(TASK_PERIOD)); + + ESP_LOGI(TAG, "deleting task event source"); + + vTaskDelete(NULL); +} + +/* Example main */ +void app_main(void) +{ + ESP_LOGI(TAG, "setting up"); + + esp_event_loop_args_t loop_with_task_args = { + .queue_size = 5, + .task_name = "loop_task", // task will be created + .task_priority = uxTaskPriorityGet(NULL), + .task_stack_size = 2048, + .task_core_id = tskNO_AFFINITY + }; + + esp_event_loop_args_t loop_without_task_args = { + .queue_size = 5, + .task_name = NULL // no task will be created + }; + + // Create the event loops + ESP_ERROR_CHECK(esp_event_loop_create(&loop_with_task_args, &loop_with_task)); + ESP_ERROR_CHECK(esp_event_loop_create(&loop_without_task_args, &loop_without_task)); + + // Register the handler for task iteration event. Notice that the same handler is used for handling event on different loops. + // The loop handle is provided as an argument in order for this example to display the loop the handler is being run on. + ESP_ERROR_CHECK(esp_event_handler_register_with(loop_with_task, TASK_EVENTS, TASK_ITERATION_EVENT, task_iteration_handler, loop_with_task)); + ESP_ERROR_CHECK(esp_event_handler_register_with(loop_without_task, TASK_EVENTS, TASK_ITERATION_EVENT, task_iteration_handler, loop_without_task)); + + ESP_LOGI(TAG, "starting event source"); + + // Create the event source task with the same priority as the current task + xTaskCreate(task_event_source, "task_event_source", 2048, NULL, uxTaskPriorityGet(NULL), NULL); + + ESP_LOGI(TAG, "starting application task"); + // Create the application task with the same priority as the current task + xTaskCreate(application_task, "application_task", 2048, NULL, uxTaskPriorityGet(NULL), NULL); +} \ No newline at end of file