From e42dd3b91b66f84b7fdde6d11c53eb802fa67c74 Mon Sep 17 00:00:00 2001 From: Alexey Zholtikov Date: Sun, 25 Jan 2026 20:53:24 +0300 Subject: [PATCH] wip: --- CMakeLists.txt | 2 +- Kconfig | 12 ++ README.md | 68 +++++++++- component.mk | 0 examples/default/CMakeLists.txt | 8 ++ examples/default/main/CMakeLists.txt | 2 + examples/default/main/idf_component.yml | 6 + examples/default/main/main.c | 88 +++++++++++++ include/main.h | 0 include/menu_manager.h | 113 +++++++++++++++++ main.c | 0 menu_manager.c | 159 ++++++++++++++++++++++++ 12 files changed, 455 insertions(+), 3 deletions(-) create mode 100644 Kconfig delete mode 100644 component.mk create mode 100644 examples/default/CMakeLists.txt create mode 100644 examples/default/main/CMakeLists.txt create mode 100644 examples/default/main/idf_component.yml create mode 100644 examples/default/main/main.c delete mode 100644 include/main.h create mode 100644 include/menu_manager.h delete mode 100644 main.c create mode 100644 menu_manager.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 9ab5c51..25e660b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1 +1 @@ -idf_component_register(SRCS "main.c" INCLUDE_DIRS "include") \ No newline at end of file +idf_component_register(SRCS "menu_manager.c" INCLUDE_DIRS "include") \ No newline at end of file diff --git a/Kconfig b/Kconfig new file mode 100644 index 0000000..5835a18 --- /dev/null +++ b/Kconfig @@ -0,0 +1,12 @@ +menu "Menu Menager" + +config MAX_DEPTH_PATH + int "Set max deth of submenus" + default 5 + +config SALVE_INDEX + bool "Savel last menu index selected" + default y + +endmenu + diff --git a/README.md b/README.md index 3df3e4d..3dde50b 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,67 @@ -# esp_component_template +# Menu Manager +Easy and flexible menu manager for ESP-IDF + + +```mermaid +--- +config: + title: Menu Manager +--- +flowchart TD + start((menu init)) + createQueue[Create Queue qCommand] + waitQueue[Wait Queue qCommand] + showDisplay[Show menu] + + funtionInExecution{Is there a funtion in execution?} + isCommandBack{Is command back?} + exitFuntion[Exit funtion in execution] + + swithCasesInput{Command} + navigateUP[Increments the index] + navigateDOWN[Decrements the index] + navigateBACK[return to parent menu] + + navigateSELECTION{Is there a funtion?} + selectSubMenu[Select sub menu] + executeFuntion[Execute funtion] + + start --> createQueue + createQueue --> waitQueue + waitQueue --> funtionInExecution + funtionInExecution --"Yes"--> isCommandBack + isCommandBack --"Yes"--> exitFuntion + funtionInExecution --"No"--> swithCasesInput + + swithCasesInput --"UP"--> navigateUP + swithCasesInput --"DOWN"--> navigateDOWN + swithCasesInput --"BACK"--> navigateBACK + swithCasesInput --"SELECT"--> navigateSELECTION + + exitFuntion --> showDisplay + navigateUP --> showDisplay + navigateDOWN --> showDisplay + navigateBACK --> showDisplay + + navigateSELECTION --"No"--> selectSubMenu --> showDisplay + navigateSELECTION --"Yes"--> executeFuntion --> waitQueue + + showDisplay --> waitQueue + + %% Estilos e cores + classDef startEnd fill:#e1f5fe,stroke:#01579b,stroke-width:3px,color:#000 + classDef queue fill:#f3e5f5,stroke:#4a148c,stroke-width:2px,color:#000 + classDef display fill:#e8f5e8,stroke:#2e7d32,stroke-width:2px,color:#000 + classDef decision fill:#fff3e0,stroke:#e65100,stroke-width:2px,color:#000 + classDef navigation fill:#fce4ec,stroke:#ad1457,stroke-width:2px,color:#000 + classDef execution fill:#ffebee,stroke:#c62828,stroke-width:2px,color:#000 + classDef selection fill:#e0f2f1,stroke:#00695c,stroke-width:2px,color:#000 + + class start startEnd + class createQueue,waitQueue queue + class showDisplay display + class funtionInExecution,isCommandBack,swithCasesInput,navigateSELECTION decision + class navigateUP,navigateDOWN,navigateBACK navigation + class exitFuntion,executeFuntion execution + class selectSubMenu selection``` -esp_component_template \ No newline at end of file diff --git a/component.mk b/component.mk deleted file mode 100644 index e69de29..0000000 diff --git a/examples/default/CMakeLists.txt b/examples/default/CMakeLists.txt new file mode 100644 index 0000000..b74a515 --- /dev/null +++ b/examples/default/CMakeLists.txt @@ -0,0 +1,8 @@ + +# https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html +# The following five lines of boilerplate have to be in your project's +# CMakeLists in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.16) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(main) diff --git a/examples/default/main/CMakeLists.txt b/examples/default/main/CMakeLists.txt new file mode 100644 index 0000000..cf2c455 --- /dev/null +++ b/examples/default/main/CMakeLists.txt @@ -0,0 +1,2 @@ +idf_component_register(SRCS "main.c" + INCLUDE_DIRS ".") diff --git a/examples/default/main/idf_component.yml b/examples/default/main/idf_component.yml new file mode 100644 index 0000000..33a160a --- /dev/null +++ b/examples/default/main/idf_component.yml @@ -0,0 +1,6 @@ +dependencies: + idf: + version: '>=4.1.0' + MarcioBulla/menu_manager: + git: https://github.com/MarcioBulla/menu_manager + version: main diff --git a/examples/default/main/main.c b/examples/default/main/main.c new file mode 100644 index 0000000..7c85e62 --- /dev/null +++ b/examples/default/main/main.c @@ -0,0 +1,88 @@ +#include "menu_manager.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static const char *TAG = "main"; + +void dumb(void *args) { + ESP_LOGI(TAG, "I am dumb"); + while (true) { + vTaskDelay(10000 / portTICK_PERIOD_MS); + } +} + +menu_node_t submenu[3] = { + {.label = "funcA", .function = &dumb}, + {.label = "funcB", .function = &dumb}, + {.label = "funcC", .function = &dumb}, +}; + +menu_node_t root = { + .label = "root", + .num_options = 3, + .submenus = (menu_node_t[3]){ + {.label = "submenu1", .submenus = submenu, .num_options = 3}, + {.label = "submenu2", .submenus = submenu, .num_options = 3}, + {.label = "submenu3", .submenus = submenu, .num_options = 3}, + }}; + +void simula_input(void *args) { + Navigate_t teste = NAVIGATE_UP; + vTaskDelay(6000 / portTICK_PERIOD_MS); + ESP_LOGI(TAG, "NEXT"); + xQueueSend(qCommands, &teste, portMAX_DELAY); + vTaskDelay(5000 / portTICK_PERIOD_MS); + teste = NAVIGATE_SELECT; + ESP_LOGI(TAG, "SELECT"); + xQueueSend(qCommands, &teste, portMAX_DELAY); + vTaskDelay(5000 / portTICK_PERIOD_MS); + teste = NAVIGATE_DOWN; + ESP_LOGI(TAG, "DOWN"); + xQueueSend(qCommands, &teste, portMAX_DELAY); + vTaskDelay(5000 / portTICK_PERIOD_MS); + teste = NAVIGATE_SELECT; + ESP_LOGI(TAG, "SELECT"); + xQueueSend(qCommands, &teste, portMAX_DELAY); + vTaskDelay(5000 / portTICK_PERIOD_MS); + teste = NAVIGATE_BACK; + ESP_LOGI(TAG, "BACK"); + xQueueSend(qCommands, &teste, portMAX_DELAY); + vTaskDelay(5000 / portTICK_PERIOD_MS); + ESP_LOGI(TAG, "BACK"); + xQueueSend(qCommands, &teste, portMAX_DELAY); + vTaskDelay(10000 / portTICK_PERIOD_MS); + + ESP_LOGI(TAG, "Finalizada"); + vTaskDelete(NULL); +} + +void display(menu_path_t *current_path) { + + ESP_LOGI(TAG, "title: %s, index_select: %d", + current_path->current_menu->label, current_path->current_index); + + ESP_LOGI( + TAG, "Option Selected %s", + current_path->current_menu->submenus[current_path->current_index].label); +} + +void app_main(void) { + + menu_config_t config = { + .root = root, + .loop = true, + .display = &display, + }; + + xTaskCreatePinnedToCore(&menu_init, "menu_init", 2048, &config, 3, NULL, 0); + vTaskDelay(5000 / portMAX_DELAY); + xTaskCreatePinnedToCore(&simula_input, "simula", 2048, NULL, 1, NULL, 0); + vTaskDelete(NULL); +} diff --git a/include/main.h b/include/main.h deleted file mode 100644 index e69de29..0000000 diff --git a/include/menu_manager.h b/include/menu_manager.h new file mode 100644 index 0000000..4f0dce7 --- /dev/null +++ b/include/menu_manager.h @@ -0,0 +1,113 @@ +/** + * @file menu_manager.h + * @brief This file defines structures and functions related to the Menu System. + */ + +#ifndef __MENU_MANAGER_H__ +#define __MENU_MANAGER_H__ +#pragma once +#include "freertos/idf_additions.h" +#include "sdkconfig.h" +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define END_MENU_FUNCTION end_menuFunction() +#define SET_QUICK_FUNCTION setQuick_menuFunction() + +extern TaskHandle_t tMenuFunction; +extern QueueHandle_t qCommands; + +/** + * Type with possibles of action in menu system. + * + */ +typedef enum { + NAVIGATE_UP, + /**< Action up (index++). */ + NAVIGATE_DOWN, + /**< Action DOWN (index--). */ + NAVIGATE_SELECT, + /**< Action SELECT (depth++). */ + NAVIGATE_BACK, + /**< Action BACH (depth--). */ + NAVIGATE_NOTHING, + /**< Nothing. */ +} Navigate_t; + +/** + * Menu System is based in nodes all menus are nodes. Submenus are nodes with + * array of more submenus and function (leafs) are nodes with function. + * + */ +typedef struct menu_node { + char *label; + /**< Title of node. */ + struct menu_node *submenus; + /**< Point of array submenus or one submenu. */ + size_t num_options; + /**< number of option on submenu. */ + void (*function)(void *args); + /**< function that defined this node as leaf. */ +} menu_node_t; + +/** + * Struct for save location and index. + * + */ +typedef struct { + menu_node_t *current_menu; + /**< Point if current menu. */ + uint8_t current_index; + /**< current Index of selected option. */ +} menu_path_t; + +/** + * This struct is all essential args that menu_init will need. + */ +typedef struct { + menu_node_t root; + /**< Menu_node_t: origin of Menu System. */ + void (*display)(menu_path_t *current_path); + /**< Function that receive menu_path_t and index for display current + * selection. */ + bool loop; + /**< Loop menu. */ +} menu_config_t; + +/** + * Start menu system that receive generic input function and generic + * display function. + * + * @param params The struct that there are args. + */ +void menu_init(void *params); + +/** + * @brief Use this function all option menus when finish + */ +void exitFunction(void); + +/** + * @brief Exec especific funtioon + * + * @param Function addres of function + */ +void execFunction(void (*function)(void *args)); + +/** + * @brief Use this function when your function is a wuick function before + * end_menuFunction() + */ +void setQuick_menuFunction(void); + +#ifdef __cplusplus +} +#endif + +#endif //__MENU_MANAGER_H__ diff --git a/main.c b/main.c deleted file mode 100644 index e69de29..0000000 diff --git a/menu_manager.c b/menu_manager.c new file mode 100644 index 0000000..28cb4bf --- /dev/null +++ b/menu_manager.c @@ -0,0 +1,159 @@ +// TODO: Add comments +#include "menu_manager.h" +#include "esp_err.h" +#include "freertos/idf_additions.h" +#include "freertos/portmacro.h" +#include "freertos/projdefs.h" +#include "sdkconfig.h" +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif +const static char *TAG = "menu_menager"; + +TaskHandle_t tMenuFunction = NULL; +QueueHandle_t qCommands = NULL; + +menu_path_t steckPath[CONFIG_MAX_DEPTH_PATH]; +menu_path_t path; +uint8_t depth = 0; +Navigate_t inputCommand = NAVIGATE_NOTHING; +void (**show)(menu_path_t *current_menu); + +static void NavigationUp(bool loop) { + ESP_LOGI(TAG, "Command UP"); + + if (path.current_index < path.current_menu->num_options - 1 || loop) { + path.current_index++; + path.current_index %= path.current_menu->num_options; + } +} + +static void NavigationDown(bool loop) { + ESP_LOGI(TAG, "Command DOWN"); + + if (path.current_index > 0 || loop) { + path.current_index = + (path.current_menu->num_options + path.current_index - 1) % + path.current_menu->num_options; + } +} + +static void ExecFunction() { + ESP_LOGI(TAG, "Execute Function: %s", + path.current_menu->submenus[path.current_index].label); + + xTaskCreatePinnedToCore( + path.current_menu->submenus[path.current_index].function, + path.current_menu->submenus[path.current_index].label, 10240, NULL, 10, + &tMenuFunction, 1); +} + +static void SelectionOption() { + ESP_LOGI(TAG, "Open Submenu"); + + path.current_menu = &path.current_menu->submenus[path.current_index]; + path.current_index = 0; + depth++; + steckPath[depth] = path; +} + +static void NavigationBack() { + ESP_LOGI(TAG, "Command BACK"); + + depth--; + path = steckPath[depth]; +#if !CONFIG_SALVE_INDEX + path.current_index = 0; +#endif +} + +void menu_init(void *args) { + ESP_LOGI(TAG, "Start menu"); + menu_config_t *params = (menu_config_t *)args; + + qCommands = xQueueCreate(10, sizeof(Navigate_t)); + + path.current_index = 0; + path.current_menu = ¶ms->root; + steckPath[depth] = path; + + show = ¶ms->display; + (*show)(&path); + + ESP_LOGI(TAG, "Root Title: %s", path.current_menu->label); + + while (true) { + + xQueueReceive(qCommands, &inputCommand, portMAX_DELAY); + + if (tMenuFunction == NULL) { + + switch (inputCommand) { + + case NAVIGATE_UP: + NavigationUp(params->loop); + break; + + case NAVIGATE_DOWN: + NavigationDown(params->loop); + break; + + case NAVIGATE_SELECT: + if (path.current_menu->submenus[path.current_index].function) { + ExecFunction(); + } else { + SelectionOption(); + } + break; + + case NAVIGATE_BACK: + if (depth) { + NavigationBack(); + } + break; + + default: + ESP_LOGW(TAG, "Undenfined Input"); + break; + } + + (*show)(&path); + } else if (inputCommand == NAVIGATE_BACK) { + vTaskDelete(tMenuFunction); + tMenuFunction = NULL; + (*show)(&path); + } + } +} + +void exitFunction(void) { + if (tMenuFunction != NULL) { + ESP_LOGI(TAG, "Exit Function"); + (*show)(&path); + tMenuFunction = NULL; + vTaskDelete(tMenuFunction); + } +} + +void execFunction(void (*function)(void *args)) { + ESP_LOGI(TAG, "Execute Function"); + + xTaskCreatePinnedToCore(function, "Function_by_menu", 10240, NULL, 10, + &tMenuFunction, 1); + vTaskDelay(1000 / portTICK_PERIOD_MS); +} + +void setQuick_menuFunction(void) { (*show)(&path); } + +#ifdef __cplusplus +} +#endif