Программирование ARM ESP-IDF Event Loop Library Fri, June 06 2025  

Поделиться

Нашли опечатку?

Пожалуйста, сообщите об этом - просто выделите ошибочное слово или фразу и нажмите Shift Enter.


ESP-IDF Event Loop Library Печать
Добавил(а) microsin   

Библиотека цикла событий (Event Loop Library) позволяет компонентам декларировать события, чтобы другие компоненты могли регистрировать обработчики - функции, которые выполняются, когда происходят эти события. Это дает возможность слабо связанным функционально компонентам присоединять требуемое поведение к изменениям состояния других компонентов без участия приложения. Это также упрощает обработку событий путем сериализации и переноса выполнения кода обработки событий в другой контекст.

Один из общих случаев использования, это когда библиотека высокого уровня использует библиотеку Wi-Fi: она может напрямую подписаться на модель программирования библиотеки Wi-Fi ESP32 (ESP32 Wi-Fi Programming Model) и действовать в соответствии с её событиями.

Замечание: различные модули стека Bluetooth поставляют события приложениям через выделенные функции обратного вызова (callback) вместо использования Event Loop Library.

[Использование esp_event API]

Для пользователей этой библиотеки есть два объекта, с которыми нужно будет иметь дело: events (события) и event loops (циклы событий).

Event показывает важное событие, такое как успешное подключение Wi-Fi к точке доступа. При ссылке на события следует использовать двухкомпонентный идентификатор, подробнее см. далее секцию "Декларация и определение событий". Цикл событий (event loop) является мостом между событиями и обработчиками событий (event handlers). Источник событий публикует события в event loop, используя для этого специальные вызовы API библиотеки Event Loop (esp_event), и обработчики событий, зарегистрированные для event loop, отвечают за действия по определенным типам событий.

Использование этой библиотеки примерно представляет собой следующую последовательность действий:

1. Пользователь определяет функцию, которая должна запуститься, когда событие публикуется в цикл событий. Эта функция называется обработчиком события (event handler), у неё должна быть такая же сигнатура, как у типа esp_event_handler_t.

2. Цикл событий (event loop) создается с использованием функции esp_event_loop_create(), которая возвратит дескриптор цикла событий типа esp_event_loop_handle_t. Циклы событий, созданные с помощью этой API-функции, называются пользовательскими циклами событий (user event loops). Однако существует специальный тип цикла событий, называемый циклом событий по умолчанию (default event loop), который обсуждается далее в секции "Default Event Loop".

3. Компоненты регистрируют обработчики событий (event handlers) для цикла событий, используя функцию esp_event_handler_register_with(). Обработчики могут быть зарегистрированы в нескольких циклах событий, см. далее секцию "Замечания по регистрации обработчика события".

4. Источники событий помещают событие в цикл, используя функцию esp_event_post_to().

5. Компоненты, которые хотят удалить свои обработчики, чтобы они больше не вызывались, могут отменить регистрацию обработчиков в цикле событий путем вызова функции esp_event_handler_unregister_with().

6. Циклы событий, которые больше не нужны, могут быть удалены вызовом esp_event_loop_delete().

В коде это может выглядеть следующим образом:

// (1) Определение обработчика события
void run_on_event(void* handler_arg, esp_event_base_t base, int32_t id, void* event_data) { // Логика обработки события }

void app_main() { // (2) Структура конфигурации типа esp_event_loop_args_t, которая нужна для указания // свойств создаваемого цикла событий. Результатом создания цикла будет получение // дескриптора типа esp_event_loop_handle_t, который необходим для других API-функций // в качестве ссылки на цикл событий при выполнении их операций. 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) Регистрация обработчика, определенного на шаге (1). MY_EVENT_BASE и MY_EVENT_ID // указывают гипотетическое событие, которое получит и должен обработать run_on_event, // когда событие будет опубликовано в event loop. esp_event_handler_register_with(loop_handle, MY_EVENT_BASE, MY_EVENT_ID, run_on_event, ...); ...
// (4) Публикация событий в event loop. Это ставит событие в очередь цикла событий. // В какой-то момент цикл событий выполнит зарегистрированный обработчик события // для этого опубликованного события. Для нашего примера запустится обработчик // run_on_event. Для упрощения этот пример вызывает esp_event_post_to из app_main, // однако публикация события может быть произведена из любой другой задачи // (что более интересный случай использования). esp_event_post_to(loop_handle, MY_EVENT_BASE, MY_EVENT_ID, ...); ...
// (5) Отмена регистрации обработчика, который больше не нужен: esp_event_handler_unregister_with(loop_handle, MY_EVENT_BASE, MY_EVENT_ID, run_on_event); ...
// (6) Удаление цикла событий, если он больше не нужен: esp_event_loop_delete(loop_handle); }

[Декларация и определение событий]

Как упоминалось ранее, события состоят из двухкомпонентных идентификаторов: event base и event ID. Здесь event base идентифицирует независимую группу событий, а event ID идентифицирует событие в этой группе. Об event base и event ID можно думать как об фамилии и имени соответственно. Фамилия идентифицирует семью, а имя идентифицирует персону в пределах этого семейства.

Event Loop Library предоставляет макросы для упрощения декларации и определения event base. Декларация event base:

ESP_EVENT_DECLARE_BASE(EVENT_BASE);

Определение event base:

ESP_EVENT_DEFINE_BASE(EVENT_BASE);

Замечание: в ESP-IDF идентификаторы base для системных событий записываются в верхнем регистре, и снабжаются пост-суффиксом _EVENT. Например, base для событий Wi-Fi декларируется и определяется как WIFI_EVENT, base события Ethernet как ETHERNET_EVENT, и так далее. Цель состоит в том, чтобы базы событий выглядели как константы (хотя они являются глобальными переменными с учетом определений макросов ESP_EVENT_DECLARE_BASE и ESP_EVENT_DEFINE_BASE).

Для идентификаторов событий (event ID) рекомендуется использовать декларацию в виде перечисления. И опять-таки, для видимости они обычно помещаются в публичных заголовочных файлах.

Пример определения Event ID:

enum {
    EVENT_ID_1,
    EVENT_ID_2,
    EVENT_ID_3,
    ...
}

[Default Event Loop]

Цикл событий по умолчанию (Default Event Loop) это специальный тип цикла, используемый для системных событий (например событий Wi-Fi). Дескриптор для этого цикла скрыт от пользователя, и создание, удаление цикла событий, регистрация обработчика, отмена регистрации обработчика и публикация событий осуществляется через отдельный набор API-функций, очень похожих на аналогичные функции для пользовательских циклов событий. В следующей таблице перечислены эти функции, и их эквивалент для пользовательских циклов событий.

User Event Loops (пользовательские циклы событий) Default Event Loop (цикл событий по умолчанию)
esp_event_loop_create() esp_event_loop_create_default()
esp_event_loop_delete() esp_event_loop_delete_default()
esp_event_handler_register_with() esp_event_handler_register()
esp_event_handler_unregister_with() esp_event_handler_unregister()
esp_event_post_to() esp_event_post()

Если вы сравните их сигнатуры, то увидите, что они почти одинаковые, за исключением того, что отсутствует спецификация для переменной дескриптора цикла у API-функций Default Event Loop.

За исключением отличий в API и специального назначения, куда публикуются системные события, не существует отличий от циклов событий по умолчанию и циклов событий пользователя. Пользователь даже может публиковать свои собственные события в цикл событий по умолчанию, если пользователь примет решение не создавать свои собственные циклы событий (например из соображений экономии памяти).

[Замечания по регистрации обработчика события]

Можно индивидуально зарегистрировать один обработчик для нескольких событий, путем нескольких вызовов esp_event_handler_register_with(). Для этих нескольких вызовов должны быть указаны отдельные варианты комбинации event base и event ID, и для этих вызовов может быть указан один и тот же обработчик.

Однако в некоторых случаях желательно, чтобы обработчик вызывался в следующих ситуациях:

1. На всех событиях, которые были опубликованы в цикл событий.

2. На всех событиях с определенным base-идентификатором.

Это возможно, если указать специальный base-идентификатор ESP_EVENT_ANY_BASE и специальный event ID идентификатор ESP_EVENT_ANY_ID. Эти специальные идентификаторы могут быть переданы как аргументы для event base и event ID arguments функции esp_event_handler_register_with().

Таким образом, для функции esp_event_handler_register_with() допустимы следующие варианты аргументов идентификаторов:

1. < event base>, - обработчик выполнится, когда в цикл событий будет опубликовано событие, у которого base-идентификатор совпадает с < event base>, и идентификатор события совпадает с < event ID>.

2. < event base>, ESP_EVENT_ANY_ID - обработчик выполнится, когда в цикл событий опубликовано любое событие, когда base-идентификатор совпадает с < event base>.

3. ESP_EVENT_ANY_BASE, ESP_EVENT_ANY_ID - обработчик выполнится, когда в цикл событий опубликовано любое событие (с любым базовым идентификатором и любым идентификатором события).

Для примера предположим, что были выполнены следующие регистрации обработчиков:

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, ...);

Если было опубликовано гипотетическое событие MY_EVENT_BASE, MY_EVENT_ID, то выполнятся все три обработчика: run_on_event_1, run_on_event_2 и run_on_event_3.

Если было опубликовано гипотетическое событие MY_EVENT_BASE, MY_OTHER_EVENT_ID, то запустятся только обработчики run_on_event_2 и run_on_event_3.

Если было опубликовано гипотетическое событие MY_OTHER_EVENT_BASE, MY_OTHER_EVENT_ID, то запустится только run_on_event_3.

Отмена регистрации обработчика самим собой. Обычно обработчику, выполняемому циклом событий, не разрешено выполнять какую-либо активность по регистрации или отмене регистрации в этом цикле событий. Однако есть некоторое исключение: обработчик может отменить регистрацию самого себя. Например, можно сделать следующее:

void run_on_event(void* handler_arg, esp_event_base_t base, int32_t id, void* event_data)
{
   esp_event_loop_handle_t *loop_handle = (esp_event_loop_handle_t*) handler_arg;
   esp_event_handler_unregister_with(*loop_handle, MY_EVENT_BASE, MY_EVENT_ID, run_on_event);
}

void app_main(void) { esp_event_loop_handle_t loop_handle; esp_event_loop_create(&loop_args, &loop_handle); esp_event_handler_register_with(loop_handle, MY_EVENT_BASE, MY_EVENT_ID, run_on_event, &loop_handle); // ... в какой-то момент произойдут публикация события MY_EVENT_BASE, MY_EVENT_ID и запуск // цикла событий ... }

Регистрация обработчика и порядок вызова обработчиков. Общее правило заключается в том, что из обработчиков, которые соответствуют определенному объявленному событию, первыми запустятся те, которые были зарегистрированы первыми. Таким образом, пользователь может управлять порядком, в каком запускаются обработчики, если он выполнит регистрацию в указанном порядке при условии, что все регистрации будут выполнены в одной задаче. Если пользователь планирует воспользоваться преимуществами такого поведения, следует быть внимательным к ситуациям, когда несколько задач регистрируют обработчики. Хотя поведение по правилу 'первым зарегистрировался, первым выполнился' все еще сохраняется, задача, которая выполнилась первой, также и первой зарегистрирует свои обработчики. Обработчики, которые регистрируются в другой задаче, все еще сохранят свой порядок запуска относительно друг друга, но если более приоритетная задача выполнила переключение контекста на себя и выполнила свою регистрацию, то она может вставить свои зарегистрированные обработчики в список запуска; таким образом, общий порядок запуска может поменяться.

[Профайлинг цикла событий]

Можно разрешить опцию конфигурации CONFIG_ESP_EVENT_LOOP_PROFILING, чтобы активировать накопление статистики для всех созданных циклов событий. Для вывода накопленной статистики в файловый поток может использоваться функция esp_event_dump(). Описание информации, которая включается в дамп, можно найти в описании esp_event_dump().

[Примеры приложений]

Проект system/esp_event/default_event_loop демонстрирует, как использовать системный цикл по умолчанию ESP32-C3 для публикации и обработки событий, включая декларацию и определение событий, создание default event loop, публикацию событий в цикл, регистрацию обработчиков событий и отмену их регистрации.

Проект system/esp_event/user_event_loops демонстрирует, как создавать и использовать пользовательские циклы событий на ESP32-C3, включая создание и запуск циклов событий, регистрацию обработчиков и отмену их регистрации, публикацию событий, с возможностью обработать различные случаи использования за пределами цикла событий по умолчанию.

Примечание: вышеупомянутые примеры можно найти в папке examples каталога установки ESP-IDF (например ~/esp/v5.4.1/esp-idf/examples).

[Справочник по API библиотеки цикла событий]

Заголовочные файлы библиотеки: components/esp_event/include/esp_event.h, и components/esp_event/include/esp_event_base.h, они подключается директивой #include:

#include "esp_event.h"
#include "esp_event_base.h"

Этот заголовочный файл представляет декларацию публичного API, предоставляемого компонентом esp_event. Чтобы декларировать, что ваш компонент (например приложение) зависит от компоненты esp_event, добавьте следующее в свой CMakeLists.txt:

    REQUIRES esp_event

.. или:

    PRIV_REQUIRES esp_event

В следующей таблице приведено общее описание API-функций библиотеки цикла событий esp_event. Полное описание функций, их сигнатур, используемых структур, макросов и типов см. в документации [1].

Функция Описание
esp_event_loop_create Создает новый цикл событий.
esp_event_loop_delete Удалит существующий цикл событий.
esp_event_loop_create_default Создаст цикл событий по умолчанию.
esp_event_loop_delete_default Удалит существующий цикл событий по умолчанию.
esp_event_loop_run Диспетчеризирует события, опубликованные в цикл событий (т. е. запускает его обработчики). Эта функция используется для диспетчеризации событий, отправленных в цикл без выделенной задачи, то есть имя задачи было установлено в NULL в event_loop_args аргументе во время создания цикла. Эта функция включает в себя аргумент для ограничения количества времени, которое она запускает, возвращая управление вызывающий код по истечении этого времени (или через некоторое время после этого). Нет гарантии, что вызов этой функции завершится именно в момент истечения этого времени. Также нет гарантии, что события были отправлены во время вызова, поскольку функция могла потратить все выделенное время на ожидание в очереди событий. Однако после исключения события из очереди оно гарантированно диспетчеризируется. Эта гарантия способствует невозможности выхода точно во время истечения срока действия, так как (1) блокировка на внутренних мьютексах необходима для диспетчеризации извлеченного из очереди события, и (2) во время диспетчеризации извлеченного из очереди события события нет способа контролировать время, занятое выполнением кода обработчика. Поэтому гарантированное время выхода - это выделенное время + количество времени, необходимое для диспетчеризации последнего извлеченного события из очереди.

В случаях, когда произошел таймаут в ожидании на очереди, функция возвратит ESP_OK, и не ESP_ERR_TIMEOUT, поскольку это нормальное поведение. Замечание: когда попадается неизвестное событие в цикле, будет сгенерировано только предупреждение, не ошибка.
esp_event_handler_register(1) Регистрирует обработчик события для системного цикла событий (legacy). Эта функция может использоваться для регистрации обработчика: (a) определенных событий, (b) всех событий на определенной event base, или (c) всех событий, известных системному циклу событий (system event loop).

• Обработчик определенных событий: указываются определенные event_base и event_id
• Обработчик всех событий определенной base: указывается event_base и в качестве event_id используется ESP_EVENT_ANY_ID
• Обработчик всех событий, известных циклу событий: используется ESP_EVENT_ANY_BASE для event_base и ESP_EVENT_ANY_ID в качестве event_id.

Можно зарегистрировать несколько обработчиков для событий. Также можно зарегистрировать один обработчик для нескольких событий. Однако регистрация одного обработчика для одного и того же события несколько раз приведет к тому, что предыдущие регистрации будут перезаписаны.
esp_event_handler_register_with(1) Регистрирует обработчик для определенного цикла событий (legacy). Эта функция ведет себя аналогично esp_event_handler_register, за исключением дополнительного указания цикла событий для регистрации обработчика.
esp_event_handler_instance_register_with(1,2) Регистрирует экземпляр обработчика события для определенного цикла события. Эта функция может использоваться для регистрации обработчика: (a) определенных событий, (b) всех событий на определенной event base, или (c) всех событий, известных системному циклу событий (system event loop).

• Обработчик определенных событий: указываются определенные event_base и event_id
• Обработчик всех событий определенной base: указывается event_base и в качестве event_id используется ESP_EVENT_ANY_ID
• Обработчик всех событий, известных циклу событий: используется ESP_EVENT_ANY_BASE для event_base и ESP_EVENT_ANY_ID в качестве event_id.

Помимо ошибки функция возвращает объект экземпляра в качестве выходного параметра для идентификации каждой регистрации. Это необходимо для удаления (дерегистрации) регистрации перед удалением цикла событий. Возможна регистрация нескольких обработчиков для событий, регистрация одного обработчика для нескольких событий, а также регистрация одного и того же обработчика для одного и того же события несколько раз. Каждая регистрация дает отдельный объект экземпляра, который идентифицирует его в течение срока действия регистрации.
esp_event_handler_instance_register(1,2) Регистрирует экземпляр обработчика для цикла событий по умолчанию. Эта функция делает то же самое, что и esp_event_handler_instance_register_with, за исключением того, что регистрируется обработчик для default event loop.
esp_event_handler_unregister(3) Отменит регистрацию обработчика system event loop (legacy). Отмененный обработчик больше не будет вызываться во время диспетчеризации событий. Регистрация обработчиков может быть отменена для любой комбинации event_base и event_id, с которыми они были ранее зарегистрированы. Для отмены регистрации обработчика аргументы event_base и event_id должны точно совпадать с аргументами, ранее переданными в esp_event_handler_register(), когда обработчик был зарегистрирован. Передача ESP_EVENT_ANY_BASE и/или ESP_EVENT_ANY_ID будет отменять только обработчики, которые были зарегистрированы с такими же wildcard-аргументами.
esp_event_handler_unregister_with Отменит регистрацию обработчика из определенного цикла событий (legacy). Эта функция ведет себя аналогично esp_event_handler_unregister, за исключением дополнительного указания цикла событий, в котором должна пройти отмена регистрации обработчика.
esp_event_handler_instance_unregister_with(3) Отменит регистрацию обработчика из определенного цикла событий. Отменит регистрацию экземпляра обработчика, так что он больше не будет вызван при диспетчеризации событий. Может быть отменена регистрация экземпляров обработчика для любой комбинации event_base и event_id, с которой была ранее произведена регистрация. Для отмены экземпляра обработчика аргументы event_base и event_id должны точно совпадать аргументам, которые были переданы в esp_event_handler_instance_register(), когда регистрировался экземпляр обработчика. Передача ESP_EVENT_ANY_BASE и/или ESP_EVENT_ANY_ID будет отменять только экземпляры обработчиков, которые были зарегистрированы с такими же wildcard-аргументами.
esp_event_handler_instance_unregister Отменит регистрацию обработчика из system event loop. Эта функция делает то же самое, что и esp_event_handler_instance_unregister_with, за исключением того, что она отменит регистрацию экземпляра обработчика из default event loop.
esp_event_post Публикует событие в системный цикл событий (system default event loop). Библиотека цикла событий хранит копию event_data, и автоматически управляет временем жизни этой копии (выделением и освобождением памяти для неё); это гарантирует, что данные, которые получит обработчик, будут всегда достоверны.
esp_event_post_to Публикует событие в определенный цикл событий. Библиотека цикла событий хранит копию event_data, и автоматически управляет временем жизни этой копии (выделением и освобождением памяти для неё); это гарантирует, что данные, которые получит обработчик, будут всегда достоверны. Эта функция ведет себя аналогично esp_event_post, за исключением дополнительного указания цикла событий, куда публикуется событие.
esp_event_isr_post(4) Специальный вариант esp_event_post для публикации событий из обработчиков прерывания.
esp_event_isr_post_to(4) Специальный вариант esp_event_post_to для публикации событий из обработчиков прерывания.
esp_event_dump Выводит дамп статистики всех циклов событий (описание формата дампа см. в [1]). Эта функция заменяется на noop, когда запрещена опция конфигурации CONFIG_ESP_EVENT_LOOP_PROFILING.

Примечания:

(1) Библиотека цикла событий не хранит копию event_handler_arg, поэтому пользователь должен гарантировать, что event_handler_arg все еще указывает на допустимое местоположение в момент вызова обработчика.
(2) Вызов этой функции с параметром instance, установленным в NULL, эквивалентен вызову esp_event_handler_register_with.
(3) При использовании ESP_EVENT_ANY_ID обработчики, зарегистрированные по определенным идентификаторам событий с использованием одной базы, не будут отменены. При использовании ESP_EVENT_ANY_BASE события, зарегистрированные на определенных базах, также не будут отменены. Это позволяет избежать случайной отмены регистрации обработчиков, зарегистрированных другими пользователями или компонентами.
(4) Эта функция доступна только когда разрешена опция CONFIG_ESP_EVENT_POST_FROM_ISR времени компиляции. Когда эта функция вызывается из обработчика прерывания, размещенного в IRAM, эта функция также должна быть размещена в IRAM путем разрешения опции CONFIG_ESP_EVENT_POST_FROM_IRAM_ISR.

[Ссылки]

1. ESP32-C3 Event Loop Library site:espressif.com.

 

Добавить комментарий


Защитный код
Обновить

Top of Page