Events, Queues, Tasks - mriksman/esp-idf-homekit GitHub Wiki

Events, Queues, Tasks

Event Loops

Legacy Event Loop

In the legacy event loop, all possible event types and event data structures had to be defined in system_event_id_t enumeration and system_event_info_t union, which made it impossible to send custom events to the event loop, and use the event loop for other kinds of events.

This event loop implementation is started using esp_event_loop_init() function. Application typically supplies an event handler.

esp_event_loop_init(event_handler, NULL);

Event handler receives a pointer to the event structure system_event_t which describes current event. This structure follows a tagged union pattern: event_id member indicates the type of event, and event_info member is a union of description structures. Application event handler will typically use switch(event->event_id) to handle different kinds of events.

static esp_err_t event_handler(void *ctx, system_event_t *event) {
    switch(event->event_id) {
    case SYSTEM_EVENT_STA_START:

New Event Loop

New event loop implementation uses esp_event library. In the examples, the default event loop is used, which is a special type of loop used for system events (WiFi events, for example). The handle for this loop is hidden from the user.

ESP_ERROR_CHECK(esp_event_loop_create_default());
ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL));
ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &event_handler, NULL));

static void event_handler(void* arg, esp_event_base_t event_base, 
int32_t event_id, void* event_data) {
    if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
        esp_wifi_connect();

System events are only posted to the default event loop. You cannot try and create your own event loop and register System events to it. You can, however, register multiple callback handlers in response to a System Event – each handler will be called in the order they were registered.

Issues with mDNS

In ESP-IDF, the mDNS component has been updated to automatically register a handler with the new event loop. In the old mDNS code, the user was required to call the following function from within the legacy event loop;

esp_err_t legacy_event_handler(void *ctx, system_event_t *event) {
    mdns_handle_system_event(ctx, event);
    return ESP_OK;
}

Task Events

Events can be sent to a task using an intermediary object. Examples of such objects are queues, semaphores, mutexes and event groups. Task notifications are a method of sending an event directly to a task without the need for such an intermediary object.

static void retry_connect_task(void * arg)
{
    uint32_t ul_notifiction_value = 0;
    while(!ul_notifiction_value) {
        esp_wifi_connect();

        // Wait here (like vTaskDelay) and check for an external event notification.
        // If set exit while() loop and delete task
        ul_notifiction_value = ulTaskNotifyTake(pdTRUE, 10000/portTICK_PERIOD_MS); 
    }
    vTaskDelete(NULL);
}

pdTRUE will clear the incremented notification value, thus making it act like a binary semaphore.

In another task, you send the command

// This sends a notification to the task which will increment the 'notification value'
xTaskNotifyGive(retry_connect_task_handle);

Event Groups

Event Groups can be used to indicate events. Tasks can optionally wait with xEventGroupWaitBits() or read back the bits they are interested in and decide from there.

#define WIFI_SCAN_OR_CONNECT_IN_PROGRESS_BIT       BIT0
EventGroupHandle_t s_wifi_event_group;

static void _wifi_connect(void) {
    EventBits_t uxBits = 0;
    uxBits = xEventGroupGetBits(s_wifi_event_group);
    if(!(uxBits & WIFI_SCAN_OR_CONNECT_IN_PROGRESS_BIT)){
        esp_wifi_connect();
        xEventGroupSetBits(s_wifi_event_group, WIFI_SCAN_OR_CONNECT_IN_PROGRESS_BIT);
    } else {
        ESP_LOGI(TAG, "Scan or connect in progress");
    }
}

In the WIFI_EVENT_STA_DISCONNECTED you clear the WIFI_SCAN_OR_CONNECT_IN_PROGRESS_BIT with xEventGroupClearBits(s_wifi_event_group, WIFI_SCAN_OR_CONNECT_IN_PROGRESS_BIT) to indicate the connection attempt has finished.

Event Groups are useful for synchronising tasks (like a Semaphore).

Mutexes

Another way to manage access to resources is via Mutex.

SemaphoreHandle_t s_wifi_mutex = NULL;

static void _wifi_connect(void* arg) {
        if( xSemaphoreTake(s_wifi_mutex, 100/portTICK_PERIOD_MS) == pdTRUE) {
            esp_wifi_connect();
        }
        else {
            ESP_LOGI(TAG, "Scan or connect in progress");
        }
}

Initialise the mutex before using it;

    s_wifi_mutex = xSemaphoreCreateMutex();

In the WIFI_EVENT_STA_DISCONNECTED or IP_EVENT_STA_GOT_IP events, you give back the Mutex xSemaphoreGive(s_wifi_mutex) to indicate the connection attempt has finished (failed or successful).

Tasks

The main tasks and their attributes are as following:

Name    Stack Size    Priority
uiT        3584(C)    14
ppT        2048(C)    13
tiT        2048(C)    8
Tmr Svc    2048(C)    2
IDLE       768        0
sys_evt    2048(C)    10
pmT        1024       11
rtT        2048       12

Note: (C) means it is configurable by menuconfig.

  • uiT. This task initializes the system, including peripherals, file system, user entry function and so on. This task will delete itself and free the resources after calling app_main.
  • ppT. This task is to process Wi-Fi hardware driver and stack. It posts messages from the logic link layer to the upper layer TCP/IP stack after transforming them into ethernet packets.
  • tiT. The task is the main task of TCP-IP stack (LwIP), it is to deal with TCP-IP packets.
  • Tmr Svc. This task is the processor of FreeRTOS internal software timer.
  • IDLE. This task is FreeRTOS internal idle callback task, it is created when starting the FreeRTOS. Its hook function is vApplicationIdleHook. The system's function of sleep and function of feeding task watch dog are called in the vApplicationIdleHook.
  • sys_evt. The task processes system events, for example, Wi-Fi and TCP-IP stack events.
  • pmT. The task is for system power management. It will check if the system can sleep right now, and if it is, it will start preparing for system sleep.
  • rtT. The task is the processor of high priority hardware timer. It mainly processes Wi-Fi real time events. It is suggested that functions based on this component should not be called in application, because it may block other low layer Wi-Fi functions.

In order to see the stats, you need to configure menuconfig with the following;

Component Config | FreeRTOS
[*] Enable FreeRTOS trace facility
[*]   Enable FreeRTOS stats formatting function
[ ] Enable FreeRTOS to collect run time stats
[ ] Set a debug watchpoint as a stack overflow check

The last option needed to be turned off, as otherwise you would get a Stack Canary panic. Even with the task running with a stack heap of >32KB.

Then, run the following code;

char buffer[400];
vTaskList(buffer);
ESP_LOGI(TAG, buffer);

The size of the buffer should be 40 bytes * number of tasks running.

After starting up with Wi-Fi, System Events and HTTPD services;

Name    State  Priority  High Water  Stack Number
uiT       X      14         1600          1
ppT       R      13         1776          6
tiT       R       8         2300          5
httpd     R       5         4016          8
Tmr Svc   R       2         1968          3
IDLE      R       0          944          2
sys_evt   B      10         1104          4
pmT       B      11          800          7

No rrT. uiT exits once app_main has finished.

High Water Mark is the minimum amount of stack space that has remained for the task since the task was created. The closer this value is to zero the closer the task has come to overflowing its stack.

States are;

  • R -- Ready
  • X -- Running (the calling task is querying its own priority)
  • D -- Deleted (waiting clean up)
  • B -- Blocked
  • S -- Suspended, or Blocked without a timeout