Lvgl Gateway Thread example

Description

Is there any good practice example of how to write a thread gateway for the lvgl lib ?
https://docs.lvgl.io/9.3/details/integration/adding-lvgl-to-your-project/threading.html#gateway-thread

I am already running “lv_task_handler()” in a separate task, but i just need to use a mutex almost every time I access functions from the lvgl lib to work.
It works well using mutex obviously, but I would like to explore this gateway thread design pattern.

Task to run only lv_task_handler() each 5 ms, in my code:

static void littleVgl_task_handler( void* pvParameters )
{    
    TickType_t* freq = (TickType_t*) pvParameters;        
    TickType_t xFrequency = pdMS_TO_TICKS( *freq );  // The macro pdMS_TO_TICKS() can be used to convert milliseconds into ticks. 
 
	while(1)  // execulta a cada 5 ms. 
	{        
        my_app_lvgl_semaphore_take();
        lv_task_handler();            
        my_app_lvgl_semaphore_give();

        // Wait for the next cycle.
  		vTaskDelay( xFrequency );  // Se quiser usar vTaskSuspend e VtaskResume da mherda. 
    }
}

lv_tick_inc( 1 ) i am executing in a timer interrupt each 1 ms.

What MCU/Processor/Board and compiler are you using?

ESP32-S3

What do you want to achieve?

I better and organized way to write a ihm code.

What have you tried so far?

I used mutex.

Thank’s.

I think it would look something like this:

// --- The LVGL Gateway Task ---
void lv_gateway_task(void *pvParameter)
{
    (void)pvParameter; // Cast to void to suppress unused parameter warning

    // Ensure the command queue is valid. If not, something went wrong during system init.
    if (xGuiCommandQueue == NULL)
    {
        printf("ERROR: GUI command queue is not initialized. Deleting LVGL gateway task.\n");
        vTaskDelete(NULL); // Self-delete if critical resource is missing
    }

    gui_command_t received_command;   // Buffer to hold commands received from the queue

    // Main loop of the LVGL Gateway Task
    while (1)
    {
        // 1. Process all pending GUI commands from the queue
        // We use a timeout of 0 (pdMS_TO_TICKS(0)) to make it non-blocking.
        // This ensures lv_task_handler() is called regularly even if the queue is empty.
        while (xQueueReceive(xGuiCommandQueue, &received_command, (TickType_t)0) == pdTRUE) 
        {
            switch (received_command.type)
            {
                case CMD_CREATE_BUTTON:
                {
                    lv_obj_t *btn = lv_btn_create(lv_scr_act()); // Create button on the active screen
                    lv_obj_set_pos(btn, received_command.data.create_button_data.x, received_command.data.create_button_data.y);
                    lv_obj_set_size(btn, 100, 50); // Fixed size for example

                    lv_obj_t *label = lv_label_create(btn); // Create a label inside the button
                    lv_label_set_text(label, received_command.data.create_button_data.text);
                    lv_obj_center(label); // Center the label within the button
                    printf("LVGL_TASK: Button '%s' created at (%d,%d)\n", received_command.data.create_button_data.text, received_command.data.create_button_data.x, received_command.data.create_button_data.y);
                    // Optional: Attach an event callback. This callback will execute within this task's context.
                    // lv_obj_add_event_cb(btn, my_button_event_cb, LV_EVENT_CLICKED, NULL);
                    break;
                }
                case CMD_UPDATE_LABEL:
                {
                    // Always check if the target object is still valid before operating on it.
                    if (received_command.data.update_label_data.target_label != NULL &&
                        lv_obj_get_parent(received_command.data.update_label_data.target_label) != NULL)
                    {
                        lv_label_set_text(received_command.data.update_label_data.target_label, received_command.data.update_label_data.new_text);
                        printf("LVGL_TASK: Label updated to '%s'\n", received_command.data.update_label_data.new_text);
                    }
                    else
                    {
                        printf("LVGL_TASK: Attempted to update NULL/invalid label.\n");
                    }
                    break;
                }
                case CMD_SET_SCREEN: 
                {
                    if (received_command.data.set_screen_data.screen_obj != NULL)
                    {
                        lv_disp_load_scr(received_command.data.set_screen_data.screen_obj);
                        printf("LVGL_TASK: Switched to new screen.\n");
                    } 
                    else 
                    {
                        printf("LVGL_TASK: Attempted to load NULL screen.\n");
                    }
                    break;
                }
                default:
                    printf("LVGL_TASK: Unknown GUI command received.\n");
                    break;
            }
        }

        // 2. Run the main LVGL task handler
        // This function handles display refreshing, input device polling,
        // animation advancements, and timer executions. It must be called regularly.
        lv_task_handler();

        // 3. Yield CPU time
        // A short delay to allow other FreeRTOS tasks to run.
        // The ideal delay depends on desired UI responsiveness and system load.
        vTaskDelay(pdMS_TO_TICKS(5)); // Delay for 5 milliseconds
    }
}