From 0147a200b3563866f1df2d164be44c4246e451dd Mon Sep 17 00:00:00 2001 From: Alon Bar-Lev Date: Fri, 20 Oct 2023 20:48:52 +0300 Subject: [PATCH] feature(console): add user context support current implementation implicitly forces the developer to use global variables to enter its context during the command invocation. this implementation adds the option to add a user context with minimum API implication and no breakage. when running via repl the context is available in the repl config, when running directly from esp_context a new method is introduced. Fields added: esp_console_cmd_t::func_context - (*)(int argc, char **argv, void *context) esp_console_repl_config_t::context - void* Functions added: esp_err_t esp_console_run_context(const char *cmdline, void *context, int *cmd_ret) Signed-off-by: Alon Bar-Lev --- components/console/commands.c | 20 ++++++++--- components/console/esp_console.h | 28 +++++++++++++++ components/console/esp_console_repl.c | 15 ++++---- .../test_apps/console/main/test_console.c | 35 +++++++++++++++++++ 4 files changed, 88 insertions(+), 10 deletions(-) diff --git a/components/console/commands.c b/components/console/commands.c index 5e63db87b4c1..aceeab037faa 100644 --- a/components/console/commands.c +++ b/components/console/commands.c @@ -32,9 +32,10 @@ typedef struct cmd_item_ { * May be NULL. */ char *hint; - esp_console_cmd_func_t func; //!< pointer to the command handler - void *argtable; //!< optional pointer to arg table - SLIST_ENTRY(cmd_item_) next; //!< next command in the list + esp_console_cmd_func_t func; //!< pointer to the command handler + esp_console_cmd_func_context_t func_context; //!< pointer to the command handler + void *argtable; //!< optional pointer to arg table + SLIST_ENTRY(cmd_item_) next; //!< next command in the list } cmd_item_t; /** linked list of command structures */ @@ -130,6 +131,7 @@ esp_err_t esp_console_cmd_register(const esp_console_cmd_t *cmd) } item->argtable = cmd->argtable; item->func = cmd->func; + item->func_context = cmd->func_context; cmd_item_t *last = SLIST_FIRST(&s_cmd_list); if (last == NULL) { SLIST_INSERT_HEAD(&s_cmd_list, item, next); @@ -189,6 +191,11 @@ static const cmd_item_t *find_command_by_name(const char *name) } esp_err_t esp_console_run(const char *cmdline, int *cmd_ret) +{ + return esp_console_run_context(NULL, cmdline, cmd_ret); +} + +esp_err_t esp_console_run_context(void *context, const char *cmdline, int *cmd_ret) { if (s_tmp_line_buf == NULL) { return ESP_ERR_INVALID_STATE; @@ -210,7 +217,12 @@ esp_err_t esp_console_run(const char *cmdline, int *cmd_ret) free(argv); return ESP_ERR_NOT_FOUND; } - *cmd_ret = (*cmd->func)(argc, argv); + if (cmd->func) { + *cmd_ret = (*cmd->func)(argc, argv); + } + if (cmd->func_context) { + *cmd_ret = (*cmd->func_context)(context, argc, argv); + } free(argv); return ESP_OK; } diff --git a/components/console/esp_console.h b/components/console/esp_console.h index 304dc423b1a3..f04248cb3f65 100644 --- a/components/console/esp_console.h +++ b/components/console/esp_console.h @@ -52,6 +52,7 @@ typedef struct { uint32_t task_priority; //!< repl task priority const char *prompt; //!< prompt (NULL represents default: "esp> ") size_t max_cmdline_length; //!< maximum length of a command line. If 0, default value will be used + void *context; //!< user context } esp_console_repl_config_t; /** @@ -156,6 +157,15 @@ esp_err_t esp_console_deinit(void); */ typedef int (*esp_console_cmd_func_t)(int argc, char **argv); +/** + * @brief Console command main function + * @param context a user context given at invocation. + * @param argc number of arguments + * @param argv array with argc entries, each pointing to a zero-terminated string argument + * @return console command return code, 0 indicates "success" + */ +typedef int (*esp_console_cmd_func_context_t)(void *context, int argc, char **argv); + /** * @brief Console command description */ @@ -181,6 +191,10 @@ typedef struct { * Pointer to a function which implements the command. */ esp_console_cmd_func_t func; + /** + * Pointer to a context aware function which implements the command. + */ + esp_console_cmd_func_context_t func_context; /** * Array or structure of pointers to arg_xxx structures, may be NULL. * Used to generate hint text if 'hint' is set to NULL. @@ -213,6 +227,20 @@ esp_err_t esp_console_cmd_register(const esp_console_cmd_t *cmd); */ esp_err_t esp_console_run(const char *cmdline, int *cmd_ret); +/** + * @brief Run command line with context + * @param context a user context. + * @param cmdline command line (command name followed by a number of arguments) + * @param[out] cmd_ret return code from the command (set if command was run) + * @return + * - ESP_OK, if command was run + * - ESP_ERR_INVALID_ARG, if the command line is empty, or only contained + * whitespace + * - ESP_ERR_NOT_FOUND, if command with given name wasn't registered + * - ESP_ERR_INVALID_STATE, if esp_console_init wasn't called + */ +esp_err_t esp_console_run_context(void *context, const char *cmdline, int *cmd_ret); + /** * @brief Split command line into arguments in place * @verbatim diff --git a/components/console/esp_console_repl.c b/components/console/esp_console_repl.c index 123572d62f2a..618793b3cb20 100644 --- a/components/console/esp_console_repl.c +++ b/components/console/esp_console_repl.c @@ -39,6 +39,7 @@ typedef struct { const char *history_save_path; TaskHandle_t task_hdl; // REPL task handle size_t max_cmdline_length; // Maximum length of a command line. If 0, default value will be used. + void *context; // User context } esp_console_repl_com_t; typedef struct { @@ -56,7 +57,7 @@ static esp_err_t esp_console_repl_usb_cdc_delete(esp_console_repl_t *repl); #if CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG static esp_err_t esp_console_repl_usb_serial_jtag_delete(esp_console_repl_t *repl); #endif //CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG -static esp_err_t esp_console_common_init(size_t max_cmdline_length, esp_console_repl_com_t *repl_com); +static esp_err_t esp_console_common_init(void *context, size_t max_cmdline_length, esp_console_repl_com_t *repl_com); static esp_err_t esp_console_setup_prompt(const char *prompt, esp_console_repl_com_t *repl_com); static esp_err_t esp_console_setup_history(const char *history_path, uint32_t max_history_len, esp_console_repl_com_t *repl_com); @@ -86,7 +87,7 @@ esp_err_t esp_console_new_repl_usb_cdc(const esp_console_dev_usb_cdc_config_t *d fcntl(fileno(stdin), F_SETFL, 0); // initialize console, common part - ret = esp_console_common_init(repl_config->max_cmdline_length, &cdc_repl->repl_com); + ret = esp_console_common_init(repl_config->context, repl_config->max_cmdline_length, &cdc_repl->repl_com); if (ret != ESP_OK) { goto _exit; } @@ -160,7 +161,7 @@ esp_err_t esp_console_new_repl_usb_serial_jtag(const esp_console_dev_usb_serial_ } // initialize console, common part - ret = esp_console_common_init(repl_config->max_cmdline_length, &usb_serial_jtag_repl->repl_com); + ret = esp_console_common_init(repl_config->context, repl_config->max_cmdline_length, &usb_serial_jtag_repl->repl_com); if (ret != ESP_OK) { goto _exit; } @@ -264,7 +265,7 @@ esp_err_t esp_console_new_repl_uart(const esp_console_dev_uart_config_t *dev_con esp_vfs_dev_uart_use_driver(dev_config->channel); // initialize console, common part - ret = esp_console_common_init(repl_config->max_cmdline_length, &uart_repl->repl_com); + ret = esp_console_common_init(repl_config->context, repl_config->max_cmdline_length, &uart_repl->repl_com); if (ret != ESP_OK) { goto _exit; } @@ -369,7 +370,7 @@ static esp_err_t esp_console_setup_history(const char *history_path, uint32_t ma return ret; } -static esp_err_t esp_console_common_init(size_t max_cmdline_length, esp_console_repl_com_t *repl_com) +static esp_err_t esp_console_common_init(void *context, size_t max_cmdline_length, esp_console_repl_com_t *repl_com) { esp_err_t ret = ESP_OK; /* Initialize the console */ @@ -381,6 +382,8 @@ static esp_err_t esp_console_common_init(size_t max_cmdline_length, esp_console_ repl_com->max_cmdline_length = max_cmdline_length; } + repl_com->context = context; + #if CONFIG_LOG_COLORS console_config.hint_color = atoi(LOG_COLOR_CYAN); #else @@ -531,7 +534,7 @@ static void esp_console_repl_task(void *args) /* Try to run the command */ int ret; - esp_err_t err = esp_console_run(line, &ret); + esp_err_t err = esp_console_run_context(repl_com->context, line, &ret); if (err == ESP_ERR_NOT_FOUND) { printf("Unrecognized command\n"); } else if (err == ESP_ERR_INVALID_ARG) { diff --git a/components/console/test_apps/console/main/test_console.c b/components/console/test_apps/console/main/test_console.c index db7685027a00..ded9ea335e63 100644 --- a/components/console/test_apps/console/main/test_console.c +++ b/components/console/test_apps/console/main/test_console.c @@ -102,3 +102,38 @@ TEST_CASE("esp console init/deinit test, minimal config", "[console]") TEST_ESP_OK(esp_console_cmd_register(&cmd)); TEST_ESP_OK(esp_console_deinit()); } + +static const char *context_test_context = "context"; + +/* handle 'quit' command */ +static int do_cmd_quit_with_context(void *context, int argc, char **argv) +{ + printf("ByeBye %s\r\n", (char *)context); + s_repl->del(s_repl); + + linenoiseHistoryFree(); // Free up memory + + return context != context_test_context; +} + +static esp_console_cmd_t s_quit_cmd_with_context = { + .command = "quit", + .help = "Quit REPL environment", + .func_context = &do_cmd_quit_with_context +}; + +// Enter "quit" to exit REPL environment +/* Marked as ignore since it cannot run as a normal unity test case + ran separately in test_console_repl */ +TEST_CASE("esp console repl test with context", "[console][ignore]") +{ + esp_console_repl_config_t repl_config = ESP_CONSOLE_REPL_CONFIG_DEFAULT(); + repl_config.context = (void *)context_test_context; + esp_console_dev_uart_config_t uart_config = ESP_CONSOLE_DEV_UART_CONFIG_DEFAULT(); + TEST_ESP_OK(esp_console_new_repl_uart(&uart_config, &repl_config, &s_repl)); + + TEST_ESP_OK(esp_console_cmd_register(&s_quit_cmd_with_context)); + + TEST_ESP_OK(esp_console_start_repl(s_repl)); + vTaskDelay(pdMS_TO_TICKS(2000)); +}