概述:
ESP32 SDK事件循环库允许声明一类事件,当事件发生后,注册了事件循环库的函数将被执行。这样可以将松散耦合的程序集中到一个事件循环库函数中去处理事件完成情况,而无需应用程序的参与,这样可以逻辑顺序一步一步的处理一套复杂的运行逻辑。类似基于状态机的编程,简化了事件处理流程。
一个常见的例子就是使用WiFi库:它将WIFI建立过程分解成一个个小的事件(状态),每一事件执行完成后回调,在回调函数中检查事件完成情况并开启下一步事件。
备注:
蓝牙堆栈的各模块是通过回调函数而不是通过事件循环库向应用程序传递事件。
如何使用事件库的esp_event APIs:
在这个库中,我们只需留意:事件和事件循环(events 、event loops).
事件是任务可能产生的一个个事件或状态。如,成功连接到Wi-Fi接入点后是一个事件。事件是用两部分标识符来引用,后面会讨论。
事件循环是事件发生后调用事件处理函数处理的一个过程。
在使用过程中要按下面的方式建立一个事件和循环:
1,建立一个事件循环处理函数,函数类型如下esp_event_handler_t 这个跟程序中常用的回调函数类似
void (*esp_event_handler_t)(void* event_handler_arg,
esp_event_base_t event_base,
int32_t event_id,
void* event_data);
2,使用esp_event_loop_create()创建一个事件循环,它将输出一个esp_event_loop_handle_t句柄。这个API创建的是用户事件循环。库中有一个默认事件循环,可以直接使用。
3,使用esp_event_handler_register_with()将事件处理函数注册到循环中。一个处理函数可以在多个循环事件中使用,也可以不同循环事件使用同一个处理函数,还可以一个循环事件中不同事件ID对应不同处理函数。
4,可以使用esp_event_post_to()将一个事件发布到循环中,之后事件处理函数就会被执行。
5,移除事件处理函数可以通过使用esp_event_handler_unregister_with()实现。
6,可以用esp_event_loop_delete()来删除事件循环。
上面流程程序如下:
// 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);
}
声明和定义事件:
事件由两部分组成:事件类和事件ID。事件类就是一个独立的事件;事件ID是这个事件类中的子类。事件类就像一个数组,事件ID就是对应的数据第几个数。
事件循环库提供了声明和定义事件类的宏如下。
事件类声明:
ESP_EVENT_DECLARE_BASE(EVENT_BASE)
事件定义:
ESP_EVENT_DEFINE_BASE(EVENT_BASE)
备注:
SDK中,系统事件的基类是大写的,后缀为_EVENT。例如,Wi-Fi事件被声明和定义为WIFI_EVENT,以太网事件的为ETHERNET_EVENT,等等。目的是让事件基类看起来像常量(如ESP_EVENT_DECLARE_BASE和ESP_EVENT_DEFINE_BASE的 它们是全局变量)。
事件ID,最好用枚举来声明,放在公共头文件里,这样具有全局性,方便使用。
如事件ID:
enum {
EVENT_ID_1,
EVENT_ID_2,
EVENT_ID_3,
...
}
默认事件循环:
默认事件循环是一个处理系统事件(例如,Wi-Fi事件)的循环。这个循环的处理程序对用户是隐藏的。
用户可能使用创建、删除、处理程序的注册/注销和发布这些API操作,但与用户循环事件API不同。
基中用户事件和默认事件API不同之处如下:
通过比较API发现,除了后缀不一样,其实大部分是一样的。
两者除了调用的API的不同和被指定为系统事件之外,默认事件循环和用户事件循环的功能没有区别。为了节约内存甚至可以将用户的事件发布到默认的事件循环中去使用。
事件处理函数注册:
可以多次调用esp_event_handler_register_with()注册一个处理函数,同时可以指定不同的处理函数执行事件类和事件ID。
有些情况下一个处理函数最好是在(1)所有被发布到一个循环中的事件或(2)所有特定基础标识符的事件上执行。
使用特殊事件基本标识符ESP_EVENT_ANY_BASE和特殊事件标识符ESP_EVENT_ANY_ID可以做到这一点。这些特殊的标识符可以作为esp_event_handler_register_with()的事件基础和事件ID参数被传递。
esp_event_handler_register_with()的有效参数是:
1, <event base>, <event ID> :当<event base>和<event ID>的事件发布时,处理函数会被执行。
2,<event base>, ESP_EVENT_ANY_ID:当 <event base>的任意事件id发布时,处理函数会被执行。
3,ESP_EVENT_ANY_BASE, ESP_EVENT_ANY_ID:当任何事件类和任何事件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);
// ... post event MY_EVENT_BASE, MY_EVENT_ID and run loop at some point
}
注册和注册函数的运行顺序:
一般规则是,对于在调度期间匹配某个发布事件的处理程序,首先注册的处理程序也会首先执行。用户可以根据执行哪些处理函数的先后顺序,在按先后进行注册。这样的前提是所有注册都是使用单个任务执行的。如果在多个任务中注册处理函数时必须保证任务执行顺序可控。因为执行原则是“先注册,先执行”,但由于任务被抢占,导致本应先注册的任务未能运行,而后注册的任务被先执行;
后续:
ESP32事件循环库与Threadx RTOS的Event chaining相似,也有不同之处,总之是解决了很多应用上的不便。相同之处是,可以注册一个函数处理事件。不同之处是ESP32事件库是直接针对事件调用处理,而Event chaining是原生OS组件,可以针对队列、事件组等组件发生操作时行回函数,由用户确切处理,通常包括恢复相应的线程以处理新事件等。
扩展:
ESP32事件循环库也可以通过事件标志组和一个任务来达到类似效果。