Mike Thielvoldt
Mike Thielvoldt

Reputation: 301

In the context of embedded development on an RTOS, when should I use an event handler and when should I use a task?

I am getting acquainted with development on the ESP32 using the ESP-IDF framework, and this is also my first time developing atop freeRTOS.

My simple exploratory application will involve responding to input from two different sources: the serial connection to my dev computer, and a connection to an MQTT client (for example).

Reading introductory materials on freeRTOS seem to suggest that I should create separate tasks to monitor and handle each input source.

However, the project examples in ESP-IDF (which are built atop freeRTOS) do not contain calls to xTaskCreate or vTaskStartScheduler(). Instead I see event handles and callbacks being registered in an event loop:

static esp_err_t mqtt_event_handler_cb(esp_mqtt_event_handle_t event)
{
    esp_mqtt_client_handle_t client = event->client;
    int msg_id;
    static int cnt = 0;
    // your_context_t *context = event->context;
    switch (event->event_id) {
        case MQTT_EVENT_CONNECTED:
            ESP_LOGI(TAG, "MQTT_EVENT_CONNECTED");
            //... Some other actions ...
            break;
        case MQTT_EVENT_DISCONNECTED:
            ESP_LOGI(TAG, "MQTT_EVENT_DISCONNECTED");
            break;
        case MQTT_EVENT_SUBSCRIBED:
            ESP_LOGI(TAG, "MQTT_EVENT_SUBSCRIBED, msg_id=%d", event->msg_id);
            //... Some other actions ...
            break;
        default:
            ESP_LOGI(TAG, "Other event id:%d", event->event_id);
            break;
    }
    return ESP_OK;
}

static void mqtt_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data) {
    ESP_LOGD(TAG, "Event dispatched from event loop base=%s, event_id=%d", base, event_id);
    mqtt_event_handler_cb(event_data);
}

static void mqtt_app_start(void)
{
    const esp_mqtt_client_config_t mqtt_cfg = {
        .uri = CONFIG_BROKER_URI,
        .cert_pem = (const char *)mqtt_eclipse_org_pem_start,
    };

    esp_mqtt_client_handle_t client = esp_mqtt_client_init(&mqtt_cfg);
    esp_mqtt_client_register_event(client, ESP_EVENT_ANY_ID, mqtt_event_handler, client);
    esp_mqtt_client_start(client);
}
void app_main(void)
{
    ESP_ERROR_CHECK(nvs_flash_init());
    ESP_ERROR_CHECK(esp_netif_init());
    ESP_ERROR_CHECK(esp_event_loop_create_default());

    /* This helper function configures Wi-Fi or Ethernet, as selected in menuconfig.
     * Read "Establishing Wi-Fi or Ethernet Connection" section in
     * examples/protocols/README.md for more information about this function.
     */
    ESP_ERROR_CHECK(example_connect());
    mqtt_app_start();
}

I do not understand why ESP-IDF would impose an event loop structure on top of what seems like a fairly purpose-built way to manage sharing processor time between code blocks in response to external events. I suspect I am missing a key piece of understanding that makes it clear what scope/context each tool (event loops and task managers) applies to.

Ultimately my next step will be to either create my own task or create my own event to handle input from my dev machine over serial. Which one?

Upvotes: 1

Views: 1717

Answers (1)

Tarmo
Tarmo

Reputation: 4770

Keep in mind that the purpose of ESP IDF examples is to demonstrate the usage of a specific subsystem API in the simplest possible way. For that end they ignore unnecessary details which may become important when building a real software architecture :)

In this case the MQTT library uses the Event Loop Library (ELL) which is an abstracted event handler service provided by Espressif to simplify your life. It's executing in a dedicated task. Any event callbacks issued by ELL run in this dedicated task, shared by all modules which receive callbacks from ELL.

This is a compromise. If you have lots of light-weight (i.e. not blocking the task they run in) event handlers which are not real-time critical, then executing them in a single task is a great way to conserve resources. It also allows for a fairly simple architecture.

If you need to do anything "heavy" (i.e. block the task) as a response to an event, then you should not execute this in the ELL task. Create your own processing thread and have the ELL event handler simply post a message there to start processing.

It's mostly the same with UART - Espressif's driver creates a task and the "data received" handler is executed there. So the answer to your question is: if you need to block a task, create your own task for this purpose and defer processing from an event handler to there. If not, feel free to piggyback on the event handler of the UART or ELL task.

Upvotes: 4

Related Questions