feat: initial

This commit is contained in:
aZholtikov committed 2026-09-20 22:38:05 +03:00
1 parent 53b3a102d0
commit 3522c5b950
9 files changed
+622 -3

No files matched your search

+1
View File
@@ -0,0 +1 @@
.DS_Store
+1 -1
View File
@@ -1 +1 @@
idf_component_register(SRCS "main.c" INCLUDE_DIRS "include")
idf_component_register(SRCS "zh_menu.c" INCLUDE_DIRS "include")
+201 -2
View File
@@ -1,3 +1,202 @@
# esp_component_template
# ESP32 ESP-IDF component for hierarchical menu
esp_component_template
## Origin
This component is a derivative work based on the original project [menu_manager](https://github.com/MarcioBulla/menu_manager) by Marcio Bulla,
which is distributed under the Apache License, Version 2.0. The original code has been adapted, modified and extended for use with ESP-IDF as the `zh_menu` component.
## Features
1. Multi-level hierarchical menu with support for nested submenus up to a configurable depth limit.
2. Abstract navigation commands (`ZH_MENU_ENTER`, `ZH_MENU_SHOW`, `ZH_MENU_UP`, `ZH_MENU_DOWN`, `ZH_MENU_SELECT`, `ZH_MENU_BACK`) independent of any input device.
3. Configurable callbacks for menu enter, menu exit and current position change.
4. Support for selectable items that can enter a dedicated adjustment mode with repeated `ZH_MENU_SELECT` handling.
5. Support for simple action items that trigger a one-shot callback on selection.
6. Built-in navigation stack that allows returning to the previous menu level with `ZH_MENU_BACK`.
7. Optional cyclic navigation mode for scrolling through menu items in a loop.
8. Per-item selectable flag that separates stateful items from plain action items.
9. Minimal memory footprint with a single dynamically allocated handle and no per-node heap usage.
## Using
In an existing project, run the following command to install the component:
```bash
cd ../your_project/components
git clone https://github.com/aZholtikov/zh_menu
```
In the application, add the component:
```c
#include "zh_menu.h"
```
## Example
```c
#include "zh_menu.h"
#include "zh_encoder.h" // https://github.com/aZholtikov/zh_encoder
#define ENCODER_NUMBER 0x01
zh_encoder_handle_t *encoder_handle = NULL;
zh_menu_handle_t *menu_handle = NULL;
void zh_encoder_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data);
void zh_menu_enter_main_menu_callback(void *arg);
void zh_menu_exit_main_menu_callback(void *arg);
void zh_menu_change_menu_callback(zh_menu_node_t *current_node, uint8_t current_index);
void zh_menu_selectable_item_callback(void *arg);
void zh_menu_simple_item_callback(void *args);
void zh_menu_back_command_callback(void *arg);
zh_menu_node_t main_menu = {
.label = "Main menu",
.submenu = (zh_menu_node_t[]){
{.label = "Submenu 1", .submenu = (zh_menu_node_t[]){
// After select this item (ZH_MENU_SELECT) `zh_menu_selectable_item_callback` will called. All commands except `ZH_MENU_BACK` and 'ZH_MENU_SELECT' will inactive.
// For example, in this mode you can use an encoder to change the value of this menu item. You can send anytime 'ZH_MENU_SELECT' for call `zh_menu_selectable_item_callback` again (optional).
// Command `ZH_MENU_BACK` will call `zh_menu_change_menu_callback' and activates the other commands.
{.label = "Submenu 1 Sel 1", .item_cb = &zh_menu_selectable_item_callback, .selectable = true},
// After select this item (ZH_MENU_SELECT) `zh_menu_simple_item_callback` will called.
{.label = "Submenu 1 Item 2", .item_cb = &zh_menu_simple_item_callback},
{.label = "Submenu 1 Item 3", .item_cb = &zh_menu_simple_item_callback},
// After select this item (ZH_MENU_SELECT) `zh_menu_back_command_callback` will called for send 'ZH_MENU_BACK' command.
{.label = "Back", .item_cb = &zh_menu_back_command_callback},
},
.item_numbers = 4},
{.label = "Submenu 2", .submenu = (zh_menu_node_t[]){
// After select this item (ZH_MENU_SELECT) all commands except `ZH_MENU_BACK` and 'ZH_MENU_SELECT' will inactive.
// Command `ZH_MENU_BACK` will call `zh_menu_change_menu_callback' and activates the other commands.
{.label = "Submenu 2 Sel 1", .selectable = true},
{.label = "Submenu 2 Item 2", .item_cb = &zh_menu_simple_item_callback},
{.label = "Back", .item_cb = &zh_menu_back_command_callback},
},
.item_numbers = 3},
{.label = "Submenu 3", .submenu = (zh_menu_node_t[]){
{.label = "Subsubmenu 3", .submenu = (zh_menu_node_t[]){
{.label = "Subsubmenu 3 Sel 1", .item_cb = &zh_menu_selectable_item_callback, .selectable = true},
{.label = "Subsubmenu 3 Item 2", .item_cb = &zh_menu_simple_item_callback},
{.label = "Subsubmenu 3 Item 3", .item_cb = &zh_menu_simple_item_callback},
{.label = "Back", .item_cb = &zh_menu_back_command_callback},
},
.item_numbers = 4},
{.label = "Back", .item_cb = &zh_menu_back_command_callback},
},
.item_numbers = 2},
// After select this item (ZH_MENU_SELECT) `zh_menu_back_command_callback` will called for send 'ZH_MENU_BACK' command.
// After 'ZH_MENU_BACK' command menu will inactive mode. Current menu position will reset and `zh_menu_exit_main_menu_callback` will called.
// For activate again send 'ZH_MENU_ENTER' command.
{.label = "Exit", .item_cb = &zh_menu_back_command_callback},
},
.item_numbers = 4,
};
void app_main(void)
{
esp_log_level_set("zh_menu", ESP_LOG_ERROR);
esp_log_level_set("zh_encoder", ESP_LOG_ERROR);
esp_event_loop_create_default();
esp_event_handler_instance_register(ZH_ENCODER, ESP_EVENT_ANY_ID, &zh_encoder_event_handler, NULL, NULL);
zh_encoder_init_config_t encoder_config = ZH_ENCODER_INIT_CONFIG_DEFAULT();
encoder_config.task_priority = 5;
encoder_config.stack_size = configMINIMAL_STACK_SIZE;
encoder_config.queue_size = 5;
encoder_config.a_gpio_number = GPIO_NUM_4;
encoder_config.b_gpio_number = GPIO_NUM_16;
encoder_config.s_gpio_number = GPIO_NUM_15;
encoder_config.encoder_min_value = -1000000;
encoder_config.encoder_max_value = 1000000;
encoder_config.encoder_step = 1;
encoder_config.encoder_number = ENCODER_NUMBER;
zh_encoder_init(&encoder_config, &encoder_handle);
zh_menu_init_config_t menu_config = {.root = &main_menu,
.loop = true,
.enter_cb = &zh_menu_enter_main_menu_callback, // Optional.
.exit_cb = &zh_menu_exit_main_menu_callback, // Optional.
.change_cb = &zh_menu_change_menu_callback};
zh_menu_init(&menu_config, &menu_handle);
}
void zh_encoder_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data)
{
(void)arg;
zh_menu_status_t menu_status = {0};
zh_menu_get(&menu_handle, &menu_status);
switch (event_id)
{
case ZH_BUTTON_EVENT:
zh_encoder_button_event_on_isr_t *button_event = event_data;
if (button_event->button_status == true)
{
menu_status.is_active == false ? zh_menu_set(&menu_handle, ZH_MENU_ENTER, NULL) : menu_status.is_selected == false ? zh_menu_set(&menu_handle, ZH_MENU_SELECT, NULL)
: zh_menu_set(&menu_handle, ZH_MENU_BACK, NULL);
}
break;
case ZH_ENCODER_EVENT:
zh_encoder_event_on_isr_t *encoder_event = event_data;
if (menu_status.is_active == true)
{
if (menu_status.is_selected == true)
{
zh_menu_set(&menu_handle, ZH_MENU_SELECT, encoder_event);
}
else
{
encoder_event->encoder_status == true ? zh_menu_set(&menu_handle, ZH_MENU_UP, NULL) : zh_menu_set(&menu_handle, ZH_MENU_DOWN, NULL);
}
}
default:
break;
}
}
void zh_menu_enter_main_menu_callback(void *arg)
{
(void)arg;
printf("Main menu activated.\n");
}
void zh_menu_exit_main_menu_callback(void *arg)
{
(void)arg;
printf("Main menu deactivated.\n");
}
void zh_menu_change_menu_callback(zh_menu_node_t *current_node, uint8_t current_index)
{
printf("\n========== MENU ==========\n");
printf("Menu: %s\n", current_node->label);
printf("--------------------------\n");
for (uint8_t i = 0; i < current_node->item_numbers; ++i)
{
printf("%c %s\n", i == current_index ? '>' : ' ', current_node->submenu[i].label);
}
printf("==========================\n\n");
fflush(stdout);
}
void zh_menu_selectable_item_callback(void *arg)
{
printf("Selectable item callback performed.\n");
if (arg != NULL)
{
zh_encoder_event_on_isr_t *encoder_event = arg;
printf("Encoder number %d position %0.0f was %s.\n", encoder_event->encoder_number, encoder_event->encoder_position, (encoder_event->encoder_status == true) ? "increased" : "reduced");
}
}
void zh_menu_simple_item_callback(void *arg)
{
(void)arg;
printf("Simple item callback performed.\n");
}
void zh_menu_back_command_callback(void *arg)
{
(void)arg;
zh_menu_set(&menu_handle, ZH_MENU_BACK, NULL);
}
```
+8
View File
@@ -0,0 +1,8 @@
description: ESP32 ESP-IDF component for hierarchical menu
url: https://github.com/aZholtikov/zh_menu
version: "1.0.0"
license: "Apache-2.0"
maintainers:
- "Alexey Zholtikov <alexey.zholtikov@yandex.ru>"
dependencies:
idf: ">=5.0"
View File
Whitespace-only changes.
+174
View File
@@ -0,0 +1,174 @@
/**
* @file zh_menu.h
*
* @brief Hierarchical menu component for ESP-IDF.
*
* This header provides types and functions for building a multi-level
* menu with navigation, selection handling and user callback support.
* The component is independent of any particular input method (buttons,
* encoder, interface commands) and operates with abstract navigation
* commands.
*
* This component is a derivative work based on the original project
* "menu_manager" by Marcio Bulla, available at:
* https://github.com/MarcioBulla/menu_manager
* The original project is distributed under the Apache License,
* Version 2.0. The original code was adapted, modified and extended
* for use with ESP-IDF as the zh_menu component.
*
* Key features:
* - Support for nested submenus with a depth limit
* - Configurable callbacks for position change, enter and exit
* - Cyclic navigation mode over menu items
* - Flexible handling of selectable and non-selectable items
* - Low memory footprint due to dynamic allocation of the handle
*
* @note The component functions are thread-safe only to the extent
* provided by the caller. External synchronization is required
* when accessed from multiple tasks.
*/
#pragma once
#include "esp_err.h"
#include "esp_log.h"
#include "esp_heap_caps.h"
#ifdef __cplusplus
extern "C"
{
#endif
/**
* @brief Opaque menu handle.
*
* The internal structure is hidden from the user and is defined
* in the implementation file. All operations are performed through
* a pointer to this type.
*/
typedef struct _zh_menu_handle_t zh_menu_handle_t;
/**
* @brief Menu navigation commands.
*/
typedef enum
{
ZH_MENU_ENTER, /*!< Activate the menu and go to the root level */
ZH_MENU_SHOW, /*!< Force refresh of the current state via callback */
ZH_MENU_UP, /*!< Move selection to the previous item */
ZH_MENU_DOWN, /*!< Move selection to the next item */
ZH_MENU_SELECT, /*!< Select the current item or enter a submenu */
ZH_MENU_BACK, /*!< Return to the previous level or exit the menu */
ZH_MENU_MAX /*!< Upper bound of valid commands, not a command itself */
} zh_menu_navigate_t;
/**
* @brief Menu tree node.
*
* Describes a single menu item: its displayed label, nested submenu,
* number of items in the submenu, user callback and selectable flag.
*
* @warning The submenu array must exist during the entire lifetime
* of the menu. The component does not copy nodes.
*/
typedef struct zh_menu_node_t
{
const char *label; /*!< Displayed label of the menu item */
struct zh_menu_node_t *submenu; /*!< Pointer to an array of nested items or NULL */
uint8_t item_numbers; /*!< Number of items in the nested submenu array */
void (*item_cb)(void *args); /*!< Callback invoked when the item is selected */
bool selectable; /*!< Flag indicating that the item can be selected */
} zh_menu_node_t;
/**
* @brief Current menu state.
*
* Used to pass activity and selection status to external code
* through the zh_menu_get() function.
*/
typedef struct
{
bool is_active; /*!< Flag indicating that the menu is activated */
bool is_selected; /*!< Flag indicating that the current item is in the selection state */
} zh_menu_status_t;
/**
* @brief Menu initialization configuration.
*
* Contains the root node, a set of user callbacks and the cyclic navigation flag.
*
* @note The change_cb, enter_cb and exit_cb callbacks may be NULL
* if the corresponding handling is not required.
*/
typedef struct
{
zh_menu_node_t *root; /*!< Root node of the menu tree */
void (*change_cb)(zh_menu_node_t *current_path, uint8_t current_index); /*!< Callback for the current position change */
void (*enter_cb)(void *args); /*!< Callback for entering the menu */
void (*exit_cb)(void *args); /*!< Callback for exiting the menu */
bool loop; /*!< Cyclic navigation flag over items */
} zh_menu_init_config_t;
/**
* @brief Initialize the menu.
*
* Creates the menu handle, stores the configuration and prepares
* the internal state. After a successful call, the menu is in
* an inactive state until the ZH_MENU_ENTER command.
*
* @param[in] config Initialization configuration (must not be NULL)
* @param[out] handle Pointer to the handle pointer (must be NULL)
*
* @return ESP_OK on success
* @return ESP_ERR_INVALID_ARG if config, config->root or handle is NULL
* @return ESP_ERR_INVALID_STATE if *handle is already initialized
* @return ESP_ERR_NO_MEM if memory allocation for the handle failed
*/
esp_err_t zh_menu_init(const zh_menu_init_config_t *config, zh_menu_handle_t **handle);
/**
* @brief Deinitialize the menu.
*
* Frees the dynamic memory occupied by the handle and resets the pointer to it.
*
* @param[in,out] handle Pointer to the handle pointer (must not be NULL)
*
* @return ESP_OK on success
* @return ESP_ERR_INVALID_ARG if handle or *handle is NULL
*/
esp_err_t zh_menu_deinit(zh_menu_handle_t **handle);
/**
* @brief Execute a menu navigation command.
*
* Processes one of the zh_menu_navigate_t commands, changing
* the internal menu state and invoking the corresponding callbacks.
* The exact behavior depends on the current activity and selection state.
*
* @param[in,out] handle Pointer to the handle pointer (must not be NULL)
* @param[in] command Navigation command. Must be less than ZH_MENU_MAX
* @param[in] arg Arbitrary argument passed to the user callbacks (enter_cb, exit_cb, item_cb) (may be NULL)
*
* @return ESP_OK on success
* @return ESP_ERR_INVALID_ARG if handle, *handle is NULL or command >= ZH_MENU_MAX
*
* @note Commands that are invalid in the current state are ignored and do not cause an error.
*/
esp_err_t zh_menu_set(zh_menu_handle_t **handle, zh_menu_navigate_t command, void *arg);
/**
* @brief Get the current menu state.
*
* Fills the status structure with the activity and selection flags.
*
* @param[in] handle Pointer to the handle pointer (must not be NULL)
* @param[out] status Pointer to the status structure to be filled (must not be NULL)
*
* @return ESP_OK on success
* @return ESP_ERR_INVALID_ARG if handle, *handle or status is NULL
*/
esp_err_t zh_menu_get(zh_menu_handle_t **handle, zh_menu_status_t *status);
#ifdef __cplusplus
}
#endif
View File
Whitespace-only changes.
+1
View File
@@ -0,0 +1 @@
1.0.0
+236
View File
@@ -0,0 +1,236 @@
/*
* This file is part of the zh_menu component, which is a derivative work
* based on the original project "menu_manager" by Marcio Bulla, available at:
* https://github.com/MarcioBulla/menu_manager
* The original project is distributed under the Apache License, Version 2.0.
* The original code was adapted, modified and extended for use with ESP-IDF
* as the zh_menu component.
*/
#include "zh_menu.h"
static const char *TAG = "zh_menu";
#define ZH_LOGI(msg, ...) ESP_LOGI(TAG, msg, ##__VA_ARGS__)
#define ZH_LOGE(msg, err, ...) ESP_LOGE(TAG, "[%s:%d:%s] " msg, __FILE__, __LINE__, esp_err_to_name(err), ##__VA_ARGS__)
#define ZH_ERROR_CHECK(cond, err, cleanup, msg, ...) \
if (!(cond)) \
{ \
ZH_LOGE(msg, err, ##__VA_ARGS__); \
cleanup; \
return err; \
}
#define ZH_MENU_MAX_DEPTH 8 /*!< Maximum depth of nested submenus. Limits the navigation stack size. */
/**
* @brief Internal representation of the menu handle.
*
* Stores the stack of traversed levels, the current and root nodes,
* user callbacks, state flags and the current index of the highlighted
* item.
*
* @note The menu_stack is used to return to the previous level on the ZH_MENU_BACK command.
*
* @warning When ZH_MENU_MAX_DEPTH is reached, further descent is not performed to avoid stack overflow.
*/
struct _zh_menu_handle_t
{
zh_menu_node_t *menu_stack[ZH_MENU_MAX_DEPTH]; /*!< Stack of traversed menu levels */
zh_menu_node_t *current_menu; /*!< Current active menu level */
zh_menu_node_t *root; /*!< Root node of the menu tree */
void (*change_cb)(zh_menu_node_t *current_path, uint8_t current_index); /*!< Callback for the current position change */
void (*enter_cb)(void *args); /*!< Callback for entering the menu */
void (*exit_cb)(void *args); /*!< Callback for exiting the menu */
bool loop; /*!< Cyclic navigation flag */
uint8_t menu_depth; /*!< Current nesting depth */
uint8_t current_index; /*!< Index of the highlighted item on the current level */
bool is_activated; /*!< Menu activity flag */
bool is_selected; /*!< Selection state flag of the current item */
};
esp_err_t zh_menu_init(const zh_menu_init_config_t *config, zh_menu_handle_t **handle)
{
ZH_LOGI("Menu initialization started.");
ZH_ERROR_CHECK(config != NULL && config->root != NULL && handle != NULL, ESP_ERR_INVALID_ARG, NULL, "Menu initialization failed. Invalid argument.");
ZH_ERROR_CHECK(*handle == NULL, ESP_ERR_INVALID_STATE, NULL, "Menu initialization failed. Menu is already initialized.");
*handle = heap_caps_calloc(1, sizeof(zh_menu_handle_t), MALLOC_CAP_8BIT);
ZH_ERROR_CHECK(*handle != NULL, ESP_ERR_NO_MEM, NULL, "Menu initialization failed. Failed to allocate menu handle.");
(*handle)->change_cb = config->change_cb;
(*handle)->enter_cb = config->enter_cb;
(*handle)->exit_cb = config->exit_cb;
(*handle)->current_menu = config->root;
(*handle)->root = config->root;
(*handle)->loop = config->loop;
ZH_LOGI("Menu initialization completed successfully.");
return ESP_OK;
}
esp_err_t zh_menu_deinit(zh_menu_handle_t **handle)
{
ZH_LOGI("Menu deinitialization started.");
ZH_ERROR_CHECK(handle != NULL && *handle != NULL, ESP_ERR_INVALID_ARG, NULL, "Menu deinitialization failed. Invalid argument.");
heap_caps_free(*handle);
*handle = NULL;
ZH_LOGI("Menu deinitialization completed successfully.");
return ESP_OK;
}
esp_err_t zh_menu_set(zh_menu_handle_t **handle, zh_menu_navigate_t command, void *arg)
{
ZH_LOGI("Menu set position started.");
ZH_ERROR_CHECK(handle != NULL && *handle != NULL && command < ZH_MENU_MAX, ESP_ERR_INVALID_ARG, NULL, "Menu set position failed. Invalid argument.");
switch (command)
{
case ZH_MENU_ENTER:
if ((*handle)->is_activated == false)
{
(*handle)->is_activated = true;
(*handle)->is_selected = false;
(*handle)->current_menu = (*handle)->root;
(*handle)->current_index = 0;
(*handle)->menu_depth = 0;
if ((*handle)->enter_cb != NULL)
{
(*handle)->enter_cb(arg);
}
if ((*handle)->change_cb != NULL)
{
(*handle)->change_cb((*handle)->current_menu, (*handle)->current_index);
}
}
break;
case ZH_MENU_SHOW:
if ((*handle)->is_activated == true)
{
if ((*handle)->change_cb != NULL)
{
(*handle)->change_cb((*handle)->current_menu, (*handle)->current_index);
}
}
break;
case ZH_MENU_UP:
if ((*handle)->is_activated == true && (*handle)->is_selected == false)
{
if ((*handle)->current_index > 0)
{
--(*handle)->current_index;
}
else if ((*handle)->loop == true)
{
(*handle)->current_index = (*handle)->current_menu->item_numbers - 1;
}
if ((*handle)->change_cb != NULL)
{
(*handle)->change_cb((*handle)->current_menu, (*handle)->current_index);
}
}
break;
case ZH_MENU_DOWN:
if ((*handle)->is_activated == true && (*handle)->is_selected == false)
{
if ((*handle)->current_index < (*handle)->current_menu->item_numbers - 1)
{
++(*handle)->current_index;
}
else if ((*handle)->loop == true)
{
(*handle)->current_index = 0;
}
if ((*handle)->change_cb != NULL)
{
(*handle)->change_cb((*handle)->current_menu, (*handle)->current_index);
}
}
break;
case ZH_MENU_SELECT:
if ((*handle)->is_activated == true)
{
if ((*handle)->is_selected == true)
{
if ((*handle)->current_menu->submenu[(*handle)->current_index].item_cb != NULL)
{
(*handle)->current_menu->submenu[(*handle)->current_index].item_cb(arg);
}
}
else if ((*handle)->current_menu->submenu[(*handle)->current_index].selectable == true)
{
(*handle)->is_selected = true;
if ((*handle)->current_menu->submenu[(*handle)->current_index].item_cb != NULL)
{
(*handle)->current_menu->submenu[(*handle)->current_index].item_cb(arg);
}
}
else if ((*handle)->current_menu->submenu[(*handle)->current_index].item_cb != NULL)
{
(*handle)->current_menu->submenu[(*handle)->current_index].item_cb(arg);
}
else if ((*handle)->current_menu->submenu[(*handle)->current_index].submenu != NULL && (*handle)->current_menu->submenu[(*handle)->current_index].item_numbers > 0)
{
if ((*handle)->menu_depth < ZH_MENU_MAX_DEPTH)
{
(*handle)->menu_stack[(*handle)->menu_depth] = (*handle)->current_menu;
++(*handle)->menu_depth;
}
(*handle)->current_menu = &(*handle)->current_menu->submenu[(*handle)->current_index];
(*handle)->current_index = 0;
if ((*handle)->change_cb != NULL)
{
(*handle)->change_cb((*handle)->current_menu, (*handle)->current_index);
}
}
}
break;
case ZH_MENU_BACK:
if ((*handle)->is_activated == true)
{
if ((*handle)->is_selected == true)
{
(*handle)->is_selected = false;
if ((*handle)->change_cb != NULL)
{
(*handle)->change_cb((*handle)->current_menu, (*handle)->current_index);
}
break;
}
if ((*handle)->menu_depth > 0)
{
--(*handle)->menu_depth;
(*handle)->current_menu = (*handle)->menu_stack[(*handle)->menu_depth];
(*handle)->current_index = 0;
if ((*handle)->change_cb != NULL)
{
(*handle)->change_cb((*handle)->current_menu, (*handle)->current_index);
}
}
else
{
(*handle)->is_activated = false;
(*handle)->is_selected = false;
(*handle)->current_menu = (*handle)->root;
(*handle)->current_index = 0;
(*handle)->menu_depth = 0;
if ((*handle)->exit_cb != NULL)
{
(*handle)->exit_cb(arg);
}
}
}
break;
default:
break;
}
ZH_LOGI("Menu set position completed successfully.");
return ESP_OK;
}
esp_err_t zh_menu_get(zh_menu_handle_t **handle, zh_menu_status_t *status)
{
ZH_LOGI("Menu get status started.");
ZH_ERROR_CHECK(handle != NULL && *handle != NULL && status != NULL, ESP_ERR_INVALID_ARG, NULL, "Menu get status failed. Invalid argument.");
status->is_active = (*handle)->is_activated;
status->is_selected = (*handle)->is_selected;
ZH_LOGI("Menu get status completed successfully.");
return ESP_OK;
}