From 5bc9d41478bb6a8db5266c6a515a7c12110821e9 Mon Sep 17 00:00:00 2001 From: Guillaume Souchere Date: Fri, 25 Jul 2025 16:07:42 +0200 Subject: [PATCH 1/9] feat(esp_commands): add esp_commands code --- .github/ISSUE_TEMPLATE/bug-report.yml | 1 + .github/workflows/upload_component.yml | 1 + .idf_build_apps.toml | 1 + esp_commands/.build-test-rules.yml | 6 + esp_commands/CMakeLists.txt | 11 + esp_commands/LICENSE | 202 ++++++ esp_commands/README.md | 0 esp_commands/esp_commands.c | 672 ++++++++++++++++++ esp_commands/esp_commands_helpers.c | 115 +++ esp_commands/esp_dynamic_commands.c | 121 ++++ esp_commands/idf_component.yml | 9 + esp_commands/include/esp_commands.h | 303 ++++++++ esp_commands/linker.lf | 13 + .../private_include/esp_commands_helpers.h | 27 + .../private_include/esp_dynamic_commands.h | 139 ++++ esp_commands/sbom_esp_commands.yml | 6 + 16 files changed, 1627 insertions(+) create mode 100644 esp_commands/.build-test-rules.yml create mode 100644 esp_commands/CMakeLists.txt create mode 100644 esp_commands/LICENSE create mode 100644 esp_commands/README.md create mode 100644 esp_commands/esp_commands.c create mode 100644 esp_commands/esp_commands_helpers.c create mode 100644 esp_commands/esp_dynamic_commands.c create mode 100644 esp_commands/idf_component.yml create mode 100644 esp_commands/include/esp_commands.h create mode 100644 esp_commands/linker.lf create mode 100644 esp_commands/private_include/esp_commands_helpers.h create mode 100644 esp_commands/private_include/esp_dynamic_commands.h create mode 100644 esp_commands/sbom_esp_commands.yml diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml index 53729b89f9..4439619617 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.yml +++ b/.github/ISSUE_TEMPLATE/bug-report.yml @@ -32,6 +32,7 @@ body: - eigen - esp_daylight - esp_delta_ota + - esp_commands - esp_encrypted_img - esp_flash_dispatcher - esp_gcov diff --git a/.github/workflows/upload_component.yml b/.github/workflows/upload_component.yml index 11de419fab..4603810cfe 100644 --- a/.github/workflows/upload_component.yml +++ b/.github/workflows/upload_component.yml @@ -34,6 +34,7 @@ jobs: dhara eigen esp_daylight + esp_commands esp_delta_ota esp_encrypted_img esp_flash_dispatcher diff --git a/.idf_build_apps.toml b/.idf_build_apps.toml index 2aa60e4e9a..34fbbb6a10 100644 --- a/.idf_build_apps.toml +++ b/.idf_build_apps.toml @@ -8,6 +8,7 @@ manifest_file = [ "ccomp_timer/.build-test-rules.yml", "coremark/.build-test-rules.yml", "esp_daylight/.build-test-rules.yml", + "esp_commands/.build-test-rules.yml", "esp_encrypted_img/.build-test-rules.yml", "esp_flash_dispatcher/.build-test-rules.yml", "esp_gcov/.build-test-rules.yml", diff --git a/esp_commands/.build-test-rules.yml b/esp_commands/.build-test-rules.yml new file mode 100644 index 0000000000..89638d6281 --- /dev/null +++ b/esp_commands/.build-test-rules.yml @@ -0,0 +1,6 @@ +esp_commands/test_apps: + disable: + - if: IDF_VERSION_MAJOR < 6 + reason: "esp_commands is created based on commands.c in esp-idf version < 6.0" + - if: IDF_TARGET not in ["esp32", "esp32c3"] + reason: "Sufficient to test on one Xtensa and one RISC-V target" \ No newline at end of file diff --git a/esp_commands/CMakeLists.txt b/esp_commands/CMakeLists.txt new file mode 100644 index 0000000000..202fa7b933 --- /dev/null +++ b/esp_commands/CMakeLists.txt @@ -0,0 +1,11 @@ +idf_build_get_property(target IDF_TARGET) + +set(srcs "esp_commands.c" + "esp_dynamic_commands.c" + "esp_commands_helpers.c") + +idf_component_register( + SRCS ${srcs} + INCLUDE_DIRS include + PRIV_INCLUDE_DIRS private_include + LDFRAGMENTS linker.lf) diff --git a/esp_commands/LICENSE b/esp_commands/LICENSE new file mode 100644 index 0000000000..efca5e67a3 --- /dev/null +++ b/esp_commands/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright Espressif Systems + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/esp_commands/README.md b/esp_commands/README.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/esp_commands/esp_commands.c b/esp_commands/esp_commands.c new file mode 100644 index 0000000000..b52f650cbe --- /dev/null +++ b/esp_commands/esp_commands.c @@ -0,0 +1,672 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include "esp_commands.h" +#include "esp_dynamic_commands.h" +#include "esp_commands_helpers.h" +#include "esp_err.h" + +/* Default foreground color */ +#define ANSI_COLOR_DEFAULT 39 + +/* Pointers to the first and last command in the dedicated section. + * See linker.lf for detailed information about the section */ +extern esp_command_t _esp_commands_start; +extern esp_command_t _esp_commands_end; + +typedef struct esp_command_sets { + esp_command_set_t static_set; + esp_command_set_t dynamic_set; +} esp_command_sets_t; + +/** run-time configuration options */ +static esp_commands_config_t s_config = { + .hint_bold = false, + .hint_color = ANSI_COLOR_DEFAULT, + .max_cmdline_args = 32, + .max_cmdline_length = 256 +}; + +/** + * @brief go through all commands registered in the + * memory section starting at _esp_commands_start + * and ending at _esp_commands_end OR go through all + * the commands listed in cmd_set if not NULL + */ +#define FOR_EACH_STATIC_COMMAND(cmd_set, cmd) \ + for (size_t _i = 0; \ + ((cmd_set) == NULL \ + ? (((cmd) = &_esp_commands_start + _i), \ + (&_esp_commands_start + _i) < &_esp_commands_end) \ + : (((cmd) = (cmd_set)->cmd_ptr_set[_i]), \ + _i < (cmd_set)->cmd_set_size)); \ + ++_i) + +/** + * @brief returns the number of commands registered + * in the .esp_commands section + */ +#define ESP_COMMANDS_COUNT (size_t)(&_esp_commands_end - &_esp_commands_start) + +/** + * @brief check the location of the pointer to esp_command_t + * + * @param cmd the pointer to the command to check + * @return true if the command was registered statically + * false if the command was registered dynamically + */ +static inline __attribute__((always_inline)) bool command_is_static(esp_command_t *cmd) +{ + if (cmd >= &_esp_commands_start && cmd <= &_esp_commands_end) { + return true; + } + return false; +} + +typedef bool (*walker_t)(void *walker_ctx, esp_command_t *cmd); +static inline __attribute__((always_inline)) +void go_through_commands(esp_command_sets_t *cmd_sets, void *cmd_walker_ctx, walker_t cmd_walker) +{ + if (!cmd_walker) { + return; + } + + esp_command_t *cmd = NULL; + bool continue_walk = false; + + /* cmd_sets is composed of 2 sets (static and dynamic). + * - If cmd_sets is NULL, go through all the statically AND dynamically registered commands. + * - If cmd_sets is not NULL and either the static or the dynamic set is empty, then the macros + * FOR_EACH_XX_COMMAND will not go through the whole list of static (resp. dynamic) commands but + * through the empty set, so no command will be walked. + */ + + esp_command_set_t *static_set = cmd_sets ? &cmd_sets->static_set : NULL; + /* it is possible that the set is empty, in which case set static_set to NULL + * to prevent the for loop to try to access a list of commands pointer set to NULL */ + if (static_set && !static_set->cmd_ptr_set) { + static_set = NULL; + } + FOR_EACH_STATIC_COMMAND(static_set, cmd) { + continue_walk = cmd_walker(cmd_walker_ctx, cmd); + if (!continue_walk) { + return; + } + } + + esp_command_set_t *dynamic_set = cmd_sets ? &cmd_sets->dynamic_set : NULL; + /* it is possible that the set is empty, in which case set dynamic_set to NULL + * to prevent the for loop to try to access a list of commands pointer set to NULL */ + if (dynamic_set && !dynamic_set->cmd_ptr_set) { + dynamic_set = NULL; + } + esp_dynamic_commands_lock(); + FOR_EACH_DYNAMIC_COMMAND(dynamic_set, cmd) { + continue_walk = cmd_walker(cmd_walker_ctx, cmd); + if (!continue_walk) { + esp_dynamic_commands_unlock(); + return; + } + } + esp_dynamic_commands_unlock(); +} + +typedef struct find_cmd_ctx { + const char *name; /*!< the name to check commands against */ + esp_command_t *cmd; /*!< the command matching the name */ +} find_cmd_ctx_t; + +static inline __attribute__((always_inline)) +bool compare_command_name(void *ctx, esp_command_t *cmd) +{ + /* called by esp_commands_find_command through go_through_commands, + * ctx cannot be NULL */ + find_cmd_ctx_t *cmd_ctx = (find_cmd_ctx_t *)ctx; + + /* called by go_through_commands, thus cmd cannot be NULL */ + if (strcmp(cmd->name, cmd_ctx->name) == 0) { + /* command found, store it in the ctx so esp_commands_find_command + * can process it. Notify go_through_commands to stop the walk by + * returning false */ + cmd_ctx->cmd = cmd; + return false; + } + + /* command not matching with the name from the ctx, continue the walk */ + return true; +} + +esp_err_t esp_commands_update_config(const esp_commands_config_t *config) +{ + if (!config || + (config->max_cmdline_args == 0) || + (config->max_cmdline_length == 0)) { + return ESP_ERR_INVALID_ARG; + } + + memcpy(&s_config, config, sizeof(s_config)); + + return ESP_OK; +} + +esp_err_t esp_commands_register_cmd(esp_command_t *cmd) +{ + if (cmd == NULL || + // (cmd->name == NULL || strchr(cmd->name, ' ') != NULL) || + cmd->func == NULL ) { + printf("this should not happen\n"); + return ESP_ERR_INVALID_ARG; + } + + /* try to find the command in the static and dynamic lists. + * if the dynamic list is empty, the mutex locking will fail + * in esp_commands_find_command and the function will return after + * checking the static list only. */ + esp_command_t *list_item_cmd = esp_commands_find_command((esp_command_sets_t *)NULL, cmd->name); + esp_err_t ret_val = ESP_FAIL; + if (!list_item_cmd) { + /* command with given name not found, it is a new command, we can allocate + * the list item and the command itself */ + ret_val = esp_dynamic_commands_add(cmd); + } else if (command_is_static(list_item_cmd)) { + /* a command with matching name is found in the list of commands + * that were registered at runtime, in which case it cannot be + * replaced with the new command */ + ret_val = ESP_FAIL; + } else { + /* an item with matching name was found in the list of dynamically + * registered commands. Replace the command on spot with the new esp_command_t. */ + ret_val = esp_dynamic_commands_replace(cmd); + } + + return ret_val; +} + +esp_err_t esp_commands_unregister_cmd(const char *cmd_name) +{ + /* only items dynamically registered can be unregistered. + * try to remove the item with the given name from the list + * of dynamically registered commands */ + esp_command_t *cmd = esp_commands_find_command((esp_command_sets_t *)NULL, cmd_name); + if (!cmd) { + return ESP_ERR_NOT_FOUND; + } else if (command_is_static(cmd)) { + return ESP_ERR_INVALID_ARG; + } else { + return esp_dynamic_commands_remove(cmd); + } +} + +esp_err_t esp_commands_execute(esp_command_set_handle_t cmd_set, const char *cmdline, int *cmd_ret) +{ + char **argv = (char **) calloc(s_config.max_cmdline_args, sizeof(char *)); + if (argv == NULL) { + return ESP_ERR_NO_MEM; + } + char *tmp_line_buf = (char *) calloc(1, s_config.max_cmdline_length); + if (!tmp_line_buf) { + free(argv); + return ESP_ERR_NO_MEM; + } + + strlcpy(tmp_line_buf, cmdline, s_config.max_cmdline_length); + + size_t argc = esp_commands_split_argv(tmp_line_buf, argv, s_config.max_cmdline_args); + if (argc == 0) { + free(argv); + free(tmp_line_buf); + return ESP_ERR_INVALID_ARG; + } + + /* help should always be executed, if cmd_sets is set or not */ + const esp_command_t *cmd = NULL; + bool is_cmd_help = false; + if (strcmp("help", argv[0]) == 0) { + /* find the help command in the list in .esp_commands section */ + cmd = esp_commands_find_command((esp_command_sets_t *)NULL, "help"); + is_cmd_help = true; + } else { + cmd = esp_commands_find_command(cmd_set, argv[0]); + } + + if (cmd == NULL) { + free(argv); + free(tmp_line_buf); + return ESP_ERR_NOT_FOUND; + } + if (cmd->func) { + if (is_cmd_help) { + // executing help command, pass the cmd_set as context + *cmd_ret = (*cmd->func)(cmd_set, argc, argv); + } else { + *cmd_ret = (*cmd->func)(cmd->func_ctx, argc, argv); + } + } + free(argv); + free(tmp_line_buf); + return ESP_OK; +} + +esp_command_t *esp_commands_find_command(esp_command_set_handle_t cmd_set, const char *name) +{ + /* no need to check that cmd_set is NULL, if it is, then FOR_EACH_XX_COMMAND + * will go through all registered commands */ + if (!name) { + return NULL; + } + + find_cmd_ctx_t ctx = { .cmd = NULL, .name = name }; + go_through_commands(cmd_set, &ctx, compare_command_name); + + /* if command was found during the walk, cmd field will be populated with + * the command matching the name given in parameter, otherwise it will still + * be NULL (value set as default value above) */ + return ctx.cmd; +} +typedef struct create_cmd_set_ctx { + esp_commands_get_field_t get_field; + const char *cmd_set_name; + esp_command_t **static_cmd_ptrs; + size_t static_cmd_count; + esp_command_t **dynamic_cmd_ptrs; + size_t dynamic_cmd_count; +} create_cmd_set_ctx_t; + +static inline __attribute__((always_inline)) +bool fill_temp_set_info(void *caller_ctx, esp_command_t *cmd) +{ + /* called by esp_commands_find_command through go_through_commands, + * ctx cannot be NULL */ + create_cmd_set_ctx_t *ctx = (create_cmd_set_ctx_t *)caller_ctx; + + /* called by go_through_commands, thus cmd cannot be NULL */ + if (strcmp(ctx->get_field(cmd), ctx->cmd_set_name) == 0) { + // it's a match, add the pointer to command to the cmd ptr set + if (command_is_static(cmd)) { + ctx->static_cmd_ptrs[ctx->static_cmd_count] = cmd; + ctx->static_cmd_count++; + } else { + ctx->dynamic_cmd_ptrs[ctx->dynamic_cmd_count] = cmd; + ctx->dynamic_cmd_count++; + } + } + + /* command not matching with the name from the ctx, continue the walk */ + return true; +} + +static inline __attribute__((always_inline)) +esp_err_t update_cmd_set_with_temp_info(esp_command_set_t *cmd_set, size_t cmd_count, esp_command_t **cmd_ptrs) +{ + if (cmd_count == 0) { + cmd_set->cmd_ptr_set = NULL; + cmd_set->cmd_set_size = 0; + } else { + const size_t alloc_cmd_ptrs_size = sizeof(esp_command_t *) * cmd_count; + cmd_set->cmd_ptr_set = malloc(alloc_cmd_ptrs_size); + if (!cmd_set->cmd_ptr_set) { + return ESP_ERR_NO_MEM; + } else { + /* copy the temp set of pointer in to the final destination */ + memcpy(cmd_set->cmd_ptr_set, cmd_ptrs, alloc_cmd_ptrs_size); + cmd_set->cmd_set_size = cmd_count; + } + } + return ESP_OK; +} + +esp_command_set_handle_t esp_commands_create_cmd_set(const char **cmd_set, const size_t cmd_set_size, esp_commands_get_field_t get_field) +{ + if (!cmd_set || cmd_set_size == 0) { + return NULL; + } + + esp_command_sets_t *cmd_ptr_sets = malloc(sizeof(esp_command_sets_t)); + if (!cmd_ptr_sets) { + return NULL; + } + + + esp_command_t *static_cmd_ptrs_temp[ESP_COMMANDS_COUNT]; + esp_command_t *dynamic_cmd_ptrs_temp[esp_dynamic_commands_get_number_of_cmd()]; + create_cmd_set_ctx_t ctx = { + .cmd_set_name = NULL, + .get_field = get_field, + .static_cmd_ptrs = static_cmd_ptrs_temp, + .static_cmd_count = 0, + .dynamic_cmd_ptrs = dynamic_cmd_ptrs_temp, + .dynamic_cmd_count = 0 + }; + + /* populate the temporary cmd pointer sets */ + for (size_t i = 0; i < cmd_set_size; i++) { + ctx.cmd_set_name = cmd_set[i]; + go_through_commands(NULL, &ctx, fill_temp_set_info); + } + + /* if no static command was found, return a static set with 0 items in it */ + esp_err_t ret_val = update_cmd_set_with_temp_info(&cmd_ptr_sets->static_set, + ctx.static_cmd_count, + ctx.static_cmd_ptrs); + if (ret_val == ESP_ERR_NO_MEM) { + free(cmd_ptr_sets); + return NULL; + } + + /* if no dynamic command was found, return a dynamic set with 0 items in it */ + ret_val = update_cmd_set_with_temp_info(&cmd_ptr_sets->dynamic_set, + ctx.dynamic_cmd_count, + ctx.dynamic_cmd_ptrs); + if (ret_val == ESP_ERR_NO_MEM) { + free(cmd_ptr_sets->static_set.cmd_ptr_set); + free(cmd_ptr_sets); + return NULL; + } + + return (esp_command_set_handle_t)cmd_ptr_sets; +} + +esp_command_set_handle_t esp_commands_concat_cmd_set(esp_command_set_handle_t cmd_set_a, esp_command_set_handle_t cmd_set_b) +{ + if (!cmd_set_a && !cmd_set_b) { + return NULL; + } else if (cmd_set_a && !cmd_set_b) { + return cmd_set_a; + } else if (!cmd_set_a && cmd_set_b) { + return cmd_set_b; + } + + /* Reaching this point, both cmd_set_a and cmd_set_b are set. + * Create a new cmd_set that can host the items from both sets, + * assign the items to the new set and free the input sets */ + esp_command_sets_t *concat_cmd_sets = malloc(sizeof(esp_command_sets_t)); + if (!concat_cmd_sets) { + return NULL; + } + const size_t new_static_set_size = cmd_set_a->static_set.cmd_set_size + cmd_set_b->static_set.cmd_set_size; + concat_cmd_sets->static_set.cmd_ptr_set = calloc(new_static_set_size, sizeof(esp_command_t *)); + if (!concat_cmd_sets->static_set.cmd_ptr_set) { + free(concat_cmd_sets); + return NULL; + } + + const size_t new_dynamic_set_size = cmd_set_a->dynamic_set.cmd_set_size + cmd_set_b->dynamic_set.cmd_set_size; + concat_cmd_sets->dynamic_set.cmd_ptr_set = calloc(new_dynamic_set_size, sizeof(esp_command_t *)); + if (!concat_cmd_sets->static_set.cmd_ptr_set) { + free(concat_cmd_sets->static_set.cmd_ptr_set); + free(concat_cmd_sets); + return NULL; + } + + /* update the new cmd set sizes */ + concat_cmd_sets->static_set.cmd_set_size = new_static_set_size; + concat_cmd_sets->dynamic_set.cmd_set_size = new_dynamic_set_size; + + /* fill the list of command pointers */ + memcpy(concat_cmd_sets->static_set.cmd_ptr_set, + cmd_set_a->static_set.cmd_ptr_set, + sizeof(esp_command_t *) * cmd_set_a->static_set.cmd_set_size); + memcpy(concat_cmd_sets->static_set.cmd_ptr_set + cmd_set_a->static_set.cmd_set_size, + cmd_set_b->static_set.cmd_ptr_set, + sizeof(esp_command_t *) * cmd_set_b->static_set.cmd_set_size); + + memcpy(concat_cmd_sets->dynamic_set.cmd_ptr_set, + cmd_set_a->dynamic_set.cmd_ptr_set, + sizeof(esp_command_t *) * cmd_set_a->dynamic_set.cmd_set_size); + memcpy(concat_cmd_sets->dynamic_set.cmd_ptr_set + cmd_set_a->dynamic_set.cmd_set_size, + cmd_set_b->dynamic_set.cmd_ptr_set, + sizeof(esp_command_t *) * cmd_set_b->dynamic_set.cmd_set_size); + + esp_commands_destroy_cmd_set(&cmd_set_a); + esp_commands_destroy_cmd_set(&cmd_set_b); + + return (esp_command_set_handle_t)concat_cmd_sets; +} + +void esp_commands_destroy_cmd_set(esp_command_set_handle_t *cmd_set) +{ + if (!cmd_set || !*cmd_set) { + return; + } + + if ((*cmd_set)->static_set.cmd_ptr_set) { + free((*cmd_set)->static_set.cmd_ptr_set); + } + + if ((*cmd_set)->dynamic_set.cmd_ptr_set) { + free((*cmd_set)->dynamic_set.cmd_ptr_set); + } + + free(*cmd_set); + *cmd_set = NULL; +} + +typedef struct call_completion_cb_ctx { + const char *buf; + const size_t buf_len; + esp_command_get_completion_t completion_cb; +} call_completion_cb_ctx_t; + +static bool call_completion_cb(void *caller_ctx, esp_command_t *cmd) +{ + call_completion_cb_ctx_t *ctx = (call_completion_cb_ctx_t *)caller_ctx; + + /* Check if command starts with buf */ + if (strncmp(ctx->buf, cmd->name, ctx->buf_len) == 0) { + ctx->completion_cb(cmd->name); + } + return true; +} + +void esp_commands_get_completion(esp_command_set_handle_t cmd_set, const char *buf, esp_command_get_completion_t completion_cb) +{ + size_t len = strlen(buf); + if (len == 0) { + return; + } + + call_completion_cb_ctx_t ctx = { + .buf = buf, + .buf_len = len, + .completion_cb = completion_cb + }; + go_through_commands(cmd_set, &ctx, call_completion_cb); +} + +const char *esp_commands_get_hint(esp_command_set_handle_t cmd_set, const char *buf, int *color, bool *bold) +{ + *color = s_config.hint_color; + *bold = s_config.hint_bold; + + esp_command_t *cmd = esp_commands_find_command(cmd_set, buf); + if (cmd && cmd->hint_cb != NULL) { + return cmd->hint_cb(cmd->func_ctx); + } + + return NULL; +} + +const char *esp_commands_get_glossary(esp_command_set_handle_t cmd_set, const char *buf) +{ + esp_command_t *cmd = esp_commands_find_command(cmd_set, buf); + if (cmd && cmd->glossary_cb != NULL) { + return cmd->glossary_cb(cmd->func_ctx); + } + + return NULL; +} + +/* -------------------------------------------------------------- */ +/* help command related code */ +/* -------------------------------------------------------------- */ + +static void print_arg_help(esp_command_t *it) +{ + /* First line: command name and hint + * Pad all the hints to the same column + */ + printf("%-s", it->name); + if (it->hint_cb) { + printf(" %s\n", it->hint_cb(it->func_ctx)); + } else { + printf("\n"); + } + + /* Second line: print help */ + /* TODO: replace the simple print with a function that + * replaces arg_print_formatted */ + if (it->help) { + printf(" %s\n", it->help); + } else { + printf(" -\n"); + } + + /* Third line: print the glossary*/ + if (it->glossary_cb) { + printf("%s\n", it->glossary_cb(it->func_ctx)); + } else { + printf(" -\n"); + } + + printf("\n"); +} + +static void print_arg_command(esp_command_t *it) +{ + printf("%-s", it->name); + if (it->hint_cb) { + printf(" %s\n", it->hint_cb(it->func_ctx)); + } +} + +typedef enum { + HELP_VERBOSE_LEVEL_0 = 0, + HELP_VERBOSE_LEVEL_1 = 1, + HELP_VERBOSE_LEVEL_MAX_NUM = 2 +} help_verbose_level_e; + +typedef void (*const fn_print_arg_t)(esp_command_t *); + +static fn_print_arg_t print_verbose_level_arr[HELP_VERBOSE_LEVEL_MAX_NUM] = { + print_arg_command, + print_arg_help, +}; + +typedef struct call_cmd_ctx { + help_verbose_level_e verbose_level; + const char *command_name; + bool command_found; +} call_cmd_ctx_t; + +static inline __attribute__((always_inline)) +bool call_command_funcs(void *caller_ctx, esp_command_t *cmd) +{ + call_cmd_ctx_t *ctx = (call_cmd_ctx_t *)caller_ctx; + + if (!ctx->command_name) { + /* ctx->command_name is empty, print all commands */ + print_verbose_level_arr[ctx->verbose_level](cmd); + } else if (ctx->command_name && + (strcmp(ctx->command_name, cmd->name) == 0)) { + /* we found the command name, print the help and return */ + print_verbose_level_arr[ctx->verbose_level](cmd); + ctx->command_found = true; + return false; + } + + return true; +} + +static int help_command(void *context, int argc, char **argv) +{ + char *command_name = NULL; + help_verbose_level_e verbose_level = HELP_VERBOSE_LEVEL_1; + + /* argc can never be superior to 4 given than the format is: + * help cmd_name -v 0 */ + if (argc <= 0 || argc > 4) { + /* unknown issue, return error */ + printf("help: invalid number of arguments %d\n", argc); + return 1; + } + + esp_command_sets_t *cmd_sets = (esp_command_sets_t *)context; + + if (argc > 1) { + /* more than 1 arg, figure out if only verbose level argument + * was passed and if a specific command was passed. + * start from the second argument since the first one is "help" */ + for (int i = 1; i < argc; i++) { + if ((strcmp(argv[i], "-v") == 0) || + (strcmp(argv[i], "--verbose") == 0)) { + /* check if the following argument is either 0, or 1 */ + if (i + 1 >= argc) { + /* format error, return with error */ + printf("help: arguments not provided in the right format\n"); + return 1; + } else if (strcmp(argv[i + 1], "0") == 0) { + verbose_level = 0; + } else if (strcmp(argv[i + 1], "1") == 0) { + verbose_level = 1; + } else { + /* wrong command format, return error */ + printf("help: invalid verbose level %s\n", argv[i + 1]); + return 1; + } + + /* we found the -v / --verbose, bump i to skip the value of + * the verbose argument since it was just parsed */ + i++; + } else { + /* the argument is not -v or --verbose, it is then the command name + * of which we should print the hint, store it for latter */ + command_name = argv[i]; + } + } + } + + /* at this point we should have figured out all the arguments of the help + * command. if command_name is NULL, then print all commands. if command_name + * is not NULL, find the command and only print the help for this command. if the + * command is not found, return with error */ + call_cmd_ctx_t ctx = { + .verbose_level = verbose_level, + .command_name = command_name, + .command_found = false + }; + go_through_commands(cmd_sets, &ctx, call_command_funcs); + + if (command_name && !ctx.command_found) { + printf("help: invalid command name %s\n", command_name); + return 1; + } + + return 0; +} + +static const char *get_help_hint(void *context) +{ + (void)context; + return "[] [-v <0|1>]"; +} + +static const char *get_help_glossary(void *context) +{ + (void)context; + return " Name of command\n" + " -v, --verbose <0|1> If specified, list console commands with given verbose level";; +} + +static const char help_str[] = "Print the summary of all registered commands if no arguments " + "are given, otherwise print summary of given command."; + +ESP_COMMAND_REGISTER(help, /* name of the heap command */ + help, /* group of the help command */ + help_str, /* help string of the help command */ + help_command, /* func */ + NULL, /* the context is null here, it will provided by the exec function */ + get_help_hint, /* hint callback */ + get_help_glossary); /* glossary callback */ diff --git a/esp_commands/esp_commands_helpers.c b/esp_commands/esp_commands_helpers.c new file mode 100644 index 0000000000..30947f32d5 --- /dev/null +++ b/esp_commands/esp_commands_helpers.c @@ -0,0 +1,115 @@ +/* + * SPDX-FileCopyrightText: 2016-2021 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include "esp_commands_helpers.h" +#include "esp_commands.h" + +#define SS_FLAG_ESCAPE 0x8 + +typedef enum { + /* parsing the space between arguments */ + SS_SPACE = 0x0, + /* parsing an argument which isn't quoted */ + SS_ARG = 0x1, + /* parsing a quoted argument */ + SS_QUOTED_ARG = 0x2, + /* parsing an escape sequence within unquoted argument */ + SS_ARG_ESCAPED = SS_ARG | SS_FLAG_ESCAPE, + /* parsing an escape sequence within a quoted argument */ + SS_QUOTED_ARG_ESCAPED = SS_QUOTED_ARG | SS_FLAG_ESCAPE, +} split_state_t; + +/* helper macro, called when done with an argument */ +#define END_ARG() do { \ + char_out = 0; \ + argv[argc++] = next_arg_start; \ + state = SS_SPACE; \ +} while(0) + +size_t esp_commands_split_argv(char *line, char **argv, size_t argv_size) +{ + const int QUOTE = '"'; + const int ESCAPE = '\\'; + const int SPACE = ' '; + split_state_t state = SS_SPACE; + size_t argc = 0; + char *next_arg_start = line; + char *out_ptr = line; + for (char *in_ptr = line; argc < argv_size - 1; ++in_ptr) { + int char_in = (unsigned char) * in_ptr; + if (char_in == 0) { + break; + } + int char_out = -1; + + switch (state) { + case SS_SPACE: + if (char_in == SPACE) { + /* skip space */ + } else if (char_in == QUOTE) { + next_arg_start = out_ptr; + state = SS_QUOTED_ARG; + } else if (char_in == ESCAPE) { + next_arg_start = out_ptr; + state = SS_ARG_ESCAPED; + } else { + next_arg_start = out_ptr; + state = SS_ARG; + char_out = char_in; + } + break; + + case SS_QUOTED_ARG: + if (char_in == QUOTE) { + END_ARG(); + } else if (char_in == ESCAPE) { + state = SS_QUOTED_ARG_ESCAPED; + } else { + char_out = char_in; + } + break; + + case SS_ARG_ESCAPED: + case SS_QUOTED_ARG_ESCAPED: + if (char_in == ESCAPE || char_in == QUOTE || char_in == SPACE) { + char_out = char_in; + } else { + /* unrecognized escape character, skip */ + } + state = (split_state_t)(state & (~SS_FLAG_ESCAPE)); + break; + + case SS_ARG: + if (char_in == SPACE) { + END_ARG(); + } else if (char_in == ESCAPE) { + state = SS_ARG_ESCAPED; + } else { + char_out = char_in; + } + break; + } + /* need to output anything? */ + if (char_out >= 0) { + *out_ptr = char_out; + ++out_ptr; + } + } + /* make sure the final argument is terminated */ + *out_ptr = 0; + /* finalize the last argument */ + if (state != SS_SPACE && argc < argv_size - 1) { + argv[argc++] = next_arg_start; + } + /* add a NULL at the end of argv */ + argv[argc] = NULL; + + return argc; +} diff --git a/esp_commands/esp_dynamic_commands.c b/esp_commands/esp_dynamic_commands.c new file mode 100644 index 0000000000..10f50d7258 --- /dev/null +++ b/esp_commands/esp_dynamic_commands.c @@ -0,0 +1,121 @@ +/* + * SPDX-FileCopyrightText: 2016-2021 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include "esp_dynamic_commands.h" +#include "esp_commands.h" + +#define CONTAINER_OF(ptr, type, member) \ + ((type *)((char *)(ptr) - offsetof(type, member))) + +static esp_command_internal_ll_t s_dynamic_cmd_list = SLIST_HEAD_INITIALIZER(esp_command_internal); +static size_t s_number_of_registered_commands = 0; +static SemaphoreHandle_t s_esp_commands_mutex = NULL; +static StaticSemaphore_t s_esp_commands_mutex_buf; + +void esp_dynamic_commands_lock(void) +{ + /* check if the mutex needs to be initialized and initialized it only + * is requested in by the state of the create parameter */ + if (s_esp_commands_mutex == NULL) { + s_esp_commands_mutex = xSemaphoreCreateMutexStatic(&s_esp_commands_mutex_buf); + assert(s_esp_commands_mutex != NULL); + } + + xSemaphoreTake(s_esp_commands_mutex, portMAX_DELAY); +} + +void esp_dynamic_commands_unlock(void) +{ + if (s_esp_commands_mutex == NULL) { + return; + } + xSemaphoreGive(s_esp_commands_mutex); +} + +const esp_command_internal_ll_t *esp_dynamic_commands_get_list(void) +{ + return &s_dynamic_cmd_list; +} + +esp_err_t esp_dynamic_commands_add(esp_command_t *cmd) +{ + if (!cmd) { + return ESP_ERR_INVALID_ARG; + } + + esp_command_internal_t *list_item = malloc(sizeof(esp_command_internal_t)); + if (!list_item) { + return ESP_ERR_NO_MEM; + } + + memcpy(&list_item->cmd, cmd, sizeof(esp_command_t)); + + esp_command_internal_t *last = NULL; + esp_command_internal_t *it = NULL; + + /* this could be called on an empty list, make sure the + * mutex is initialized */ + esp_dynamic_commands_lock(); + + SLIST_FOREACH(it, &s_dynamic_cmd_list, next_item) { + if (strcmp(it->cmd.name, list_item->cmd.name) > 0) { + break; + } + last = it; + } + + if (last == NULL) { + SLIST_INSERT_HEAD(&s_dynamic_cmd_list, list_item, next_item); + } else { + SLIST_INSERT_AFTER(last, list_item, next_item); + } + + s_number_of_registered_commands++; + + esp_dynamic_commands_unlock(); + + return ESP_OK; +} + +esp_err_t esp_dynamic_commands_replace(esp_command_t *item_cmd) +{ + esp_dynamic_commands_lock(); + + esp_command_internal_t *list_item = CONTAINER_OF(item_cmd, esp_command_internal_t, cmd); + memcpy(&list_item->cmd, item_cmd, sizeof(esp_command_t)); + + esp_dynamic_commands_unlock(); + + return ESP_OK; +} + +esp_err_t esp_dynamic_commands_remove(esp_command_t *item_cmd) +{ + esp_dynamic_commands_lock(); + + esp_command_internal_t *list_item = CONTAINER_OF(item_cmd, esp_command_internal_t, cmd); + SLIST_REMOVE(&s_dynamic_cmd_list, list_item, esp_command_internal, next_item); + + s_number_of_registered_commands--; + + esp_dynamic_commands_unlock(); + + free(list_item); + + return ESP_OK; +} + +size_t esp_dynamic_commands_get_number_of_cmd(void) +{ + esp_dynamic_commands_lock(); + size_t nb_of_registered_cmd = s_number_of_registered_commands; + esp_dynamic_commands_unlock(); + return nb_of_registered_cmd; +} diff --git a/esp_commands/idf_component.yml b/esp_commands/idf_component.yml new file mode 100644 index 0000000000..c58bc23f5d --- /dev/null +++ b/esp_commands/idf_component.yml @@ -0,0 +1,9 @@ +version: "1.0.0" +description: "esp_commands - Command handling component" +url: https://github.com/espressif/idf-extra-components/tree/master/esp_commands +dependencies: + idf: ">=6.0" +sbom: + manifests: + - path: sbom_esp_commands.yml + dest: . \ No newline at end of file diff --git a/esp_commands/include/esp_commands.h b/esp_commands/include/esp_commands.h new file mode 100644 index 0000000000..f8761dfa3a --- /dev/null +++ b/esp_commands/include/esp_commands.h @@ -0,0 +1,303 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include "esp_err.h" + +/** + * @brief Console command main function type with user context + * + * This function type is used to implement a console command. + * + * @param context User-defined context passed at invocation + * @param argc Number of arguments + * @param argv Array of argc entries, each pointing to a null-terminated string argument + * @return Return code of the console command; 0 indicates success + */ +typedef int (*esp_command_func_t)(void *context, int argc, char **argv); + +/** + * @brief Callback to generate a command hint + * + * This function is called to retrieve a short hint for a command, + * typically used for auto-completion or UI help. + * + * @param context Context registered when the command was registered + * @return Persistent string containing the generated hint + */ +typedef const char *(*esp_command_hint_t)(void *context); + +/** + * @brief Callback to generate a command glossary entry + * + * This function is called to retrieve detailed description or glossary + * information for a command. + * + * @param context Context registered when the command was registered + * @return Persistent string containing the generated glossary + */ +typedef const char *(*esp_command_glossary_t)(void *context); + +/** + * @brief Structure describing a console command + * + * @note Only one of `func` or `func_ctx` should be set. + * @note The `group` field allows categorizing commands into groups, + * which can simplify filtering or listing commands. + */ +typedef struct esp_command { + const char *name; /*!< Name of the command */ + const char *group; /*!< Command group to which this command belongs */ + const char *help; /*!< Short help text for the command */ + esp_command_func_t func; /*!< Function implementing the command */ + void *func_ctx; /*!< User-defined context for the command function */ + esp_command_hint_t hint_cb; /*!< Callback returning the hint for the command */ + esp_command_glossary_t glossary_cb; /*!< Callback returning the glossary for the command */ +} esp_command_t; + +/** + * @brief Macro to define a forced inline accessor for a string field of esp_command_t + * + * @param NAME Field name of the esp_command_t structure + */ +#define DEFINE_FIELD_ACCESSOR(NAME) \ + static inline __attribute__((always_inline)) \ + const char *get_##NAME(const esp_command_t *cmd) { \ + if (!cmd) { \ + return NULL; \ + } \ + return cmd->NAME; \ + } + +/** + * @brief Macro expanding to + * static inline __attribute__((always_inline)) const char *get_name(esp_command_t *cmd) { + * if (!cmd) { + * return NULL; + * } + * return cmd->name; + * } + */ +DEFINE_FIELD_ACCESSOR(name) + +/** + * @brief Macro expanding to + * static inline __attribute__((always_inline)) const char *get_group(esp_command_t *cmd) { + * if (!cmd) { + * return NULL; + * } + * return cmd->group; + * } + */ +DEFINE_FIELD_ACCESSOR(group) + +/** + * @brief Macro expanding to + * static inline __attribute__((always_inline)) const char *get_help(esp_command_t *cmd) { + * if (!cmd) { + * return NULL; + * } + * return cmd->help; + * } + */ +DEFINE_FIELD_ACCESSOR(help) + +/** + * @brief Macro to create the accessor function name for a field of esp_command_t + * + * @param NAME Field name of esp_command_t + */ +#define FIELD_ACCESSOR(NAME) get_##NAME + +/** + * @brief Configuration parameters for esp_commands_manager initialization + */ +typedef struct esp_commands_config { + size_t max_cmdline_length; /*!< Maximum length of the command line buffer, in bytes */ + size_t max_cmdline_args; /*!< Maximum number of command line arguments to parse */ + int hint_color; /*!< ANSI color code used for hint text */ + bool hint_bold; /*!< If true, display hint text in bold */ +} esp_commands_config_t; + +/** + * @brief Default configuration for esp_commands_manager + */ +#define ESP_COMMANDS_CONFIG_DEFAULT() \ +{ \ + .max_cmdline_length = 256, \ + .max_cmdline_args = 32, \ + .hint_color = 39, \ + .hint_bold = false \ +} + +/** + * @brief Callback for a completed command name + * + * This callback is called when a command is successfully completed. + * + * @param completed_cmd_name Completed command name + */ +typedef void (*esp_command_get_completion_t)(const char *completed_cmd_name); + +/** + * @brief Callback to retrieve a string field of esp_command_t + * + * @param cmd Command object + * @return Value of the requested string field + */ +typedef const char *(*esp_commands_get_field_t)(const esp_command_t *cmd); + +/** + * @brief Opaque handle to a set of commands + */ +typedef struct esp_command_sets *esp_command_set_handle_t; + +/** + * @brief Update the component configuration + * + * @param config Configuration data to update + * @return ESP_OK if successful + * ESP_ERR_INVALID_ARG if config pointer is NULL + */ +esp_err_t esp_commands_update_config(const esp_commands_config_t *config); + +/** + * @brief macro registering a command and placing it in a specific section of flash.rodata + * @note see the linker.lf file for more information concerning the section characteristics + */ +#define ESP_COMMAND_REGISTER(cmd_name, cmd_group, cmd_help, cmd_func, cmd_func_ctx, cmd_hint_cb, cmd_glossary_cb) \ + static_assert((cmd_func) != NULL); \ + static const esp_command_t cmd_name __attribute__((used, section(".esp_commands"))) = { \ + .name = #cmd_name, \ + .group = #cmd_group, \ + .help = cmd_help, \ + .func = cmd_func, \ + .func_ctx = cmd_func_ctx, \ + .hint_cb = cmd_hint_cb, \ + .glossary_cb = cmd_glossary_cb \ + }; + +/** + * @brief Register a command + * + * @param cmd Pointer to the command structure + * @return ESP_OK if successful + * Other esp_err_t on error + */ +esp_err_t esp_commands_register_cmd(esp_command_t *cmd); + +/** + * @brief Unregister a command by name or group + * + * @param cmd_name Name or group of the command to unregister + * @return ESP_OK if successful + * Other esp_err_t on error + */ +esp_err_t esp_commands_unregister_cmd(const char *cmd_name); + +/** + * @brief Execute a command line + * + * @param cmd_set Set of commands allowed to execute. If NULL, all registered commands are allowed + * @param cmd_line Command line string to execute + * @param cmd_ret Return value from the command function + * @return ESP_OK on success + * ESP_ERR_INVALID_ARG if the command line is empty or only whitespace + * ESP_ERR_NOT_FOUND if command is not found in cmd_set + * ESP_ERR_NO_MEM if internal memory allocation fails + */ +esp_err_t esp_commands_execute(esp_command_set_handle_t cmd_set, const char *cmdline, int *cmd_ret); + +/** + * @brief Find a command by name within a specific command set. + * + * This function searches a command whose name matches the provided string. + * + * @param cmd_set Handle to the command set to search in. Must be a valid + * `esp_command_set_handle_t` or `NULL` if the search should be performed + * on all statically adn dynamically registered commands. + * @param name String containing the name of the command to search for. + * + * @return pointer to the matching command or NULL if no command is found. + */ +esp_command_t *esp_commands_find_command(esp_command_set_handle_t cmd_set, const char *name); + +/** + * @brief Provide command completion for linenoise library + * + * @param cmd_set Set of commands allowed for completion. If NULL, all registered commands are used + * @param buf Input string typed by the user + * @param completion_cb Callback to return completed command names + */ +void esp_commands_get_completion(esp_command_set_handle_t cmd_set, const char *buf, esp_command_get_completion_t completion_cb); + +/** + * @brief Provide command hint for linenoise library + * + * @param cmd_set Set of commands allowed for hinting. If NULL, all registered commands are used + * @param buf Input string typed by the user + * @param[out] color ANSI color code for hint text + * @param[out] bold True if hint should be displayed in bold + * @return Persistent string containing the hint; must not be freed + */ +const char *esp_commands_get_hint(esp_command_set_handle_t cmd_set, const char *buf, int *color, bool *bold); + +/** + * @brief Retrieve glossary for a command line + * + * @param cmd_set Set of commands allowed + * @param buf Command line typed by the user + * @return Persistent string containing the glossary; must not be freed + */ +const char *esp_commands_get_glossary(esp_command_set_handle_t cmd_set, const char *buf); + +/** + * @brief Create a command set from an array of command names + * + * @param cmd_set Array of command names + * @param cmd_set_size Number of entries in cmd_set + * @param get_field Function to retrieve the field from esp_command_t for comparison + * @return Handle to the created command set + */ +esp_command_set_handle_t esp_commands_create_cmd_set(const char **cmd_set, const size_t cmd_set_size, esp_commands_get_field_t get_field); + +/** + * @brief Convenience macro to create a command set + * + * @param cmd_set Array of command names + * @param accessor Field accessor function + */ +#define ESP_COMMANDS_CREATE_CMD_SET(cmd_set, accessor) \ + esp_commands_create_cmd_set(cmd_set, sizeof(cmd_set) / sizeof((cmd_set)[0]), accessor) + +/** + * @brief Concatenate two command sets + * + * @note If one set is NULL, the other is returned + * @note If both are NULL, returns NULL + * @note Duplicates are not removed + * + * @param cmd_set_a First command set + * @param cmd_set_b Second command set + * @return New command set containing all commands from both sets + */ +esp_command_set_handle_t esp_commands_concat_cmd_set(esp_command_set_handle_t cmd_set_a, esp_command_set_handle_t cmd_set_b); + +/** + * @brief Destroy a command set + * + * @param cmd_set Pointer to the handle of the command set to destroy + */ +void esp_commands_destroy_cmd_set(esp_command_set_handle_t *cmd_set); + +#ifdef __cplusplus +} +#endif diff --git a/esp_commands/linker.lf b/esp_commands/linker.lf new file mode 100644 index 0000000000..c33eef3399 --- /dev/null +++ b/esp_commands/linker.lf @@ -0,0 +1,13 @@ +[sections:esp_commands] +entries: + .esp_commands + +[scheme:esp_commands_default] +entries: + esp_commands -> flash_rodata + +[mapping:esp_commands] +archive: * +entries: + * (esp_commands_default); + esp_commands -> flash_rodata KEEP() SORT(name) SURROUND(esp_commands) diff --git a/esp_commands/private_include/esp_commands_helpers.h b/esp_commands/private_include/esp_commands_helpers.h new file mode 100644 index 0000000000..4ee67164ac --- /dev/null +++ b/esp_commands/private_include/esp_commands_helpers.h @@ -0,0 +1,27 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +/** + * @brief Split a command line and populate argc and argv parameters + * + * @param line the line that has to be split into arguments + * @param argv array of arguments created from the line + * @param argv_size size of the argument array + * @return size_t number of arguments found in the line and stored + * in argv + */ +size_t esp_commands_split_argv(char *line, char **argv, size_t argv_size); + +#ifdef __cplusplus +} +#endif diff --git a/esp_commands/private_include/esp_dynamic_commands.h b/esp_commands/private_include/esp_dynamic_commands.h new file mode 100644 index 0000000000..1b9547ac47 --- /dev/null +++ b/esp_commands/private_include/esp_dynamic_commands.h @@ -0,0 +1,139 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include "sys/queue.h" +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" +#include "esp_commands.h" + +/** + * @brief Structure representing a fixed set of commands. + * + * This is typically used for static or predefined command lists. + */ +typedef struct esp_command_set { + esp_command_t **cmd_ptr_set; /*!< Array of pointers to commands. */ + size_t cmd_set_size; /*!< Number of commands in the set. */ +} esp_command_set_t; + +/** + * @brief Internal structure for a dynamically registered command. + * + * Each dynamic command is stored as an `esp_command_t` plus + * linked list metadata for insertion/removal. + */ +typedef struct esp_command_internal { + esp_command_t cmd; /*!< Command instance. */ + SLIST_ENTRY(esp_command_internal) next_item; /*!< Linked list entry metadata. */ +} esp_command_internal_t; + +/** + * @brief Linked list head type for dynamic command storage. + */ +typedef SLIST_HEAD(esp_command_internal_ll, esp_command_internal) esp_command_internal_ll_t; + +/** + * @brief Iterate over a set of commands, either from a static set or dynamic list. + * + * This macro supports iterating over: + * - A provided `esp_command_set_t` (static set), OR + * - The global dynamic command list if `cmd_set` is `NULL`. + * + * @param cmd_set Pointer to a command set (`esp_command_set_t`) or `NULL` for dynamic commands. + * @param item_cmd Iterator variable of type `esp_command_t *` that will point to each command. + * + * @note Internally, the macro uses `_node` and `_i` as hidden variables. + */ +#define FOR_EACH_DYNAMIC_COMMAND(cmd_set, item_cmd) \ + __attribute__((unused)) esp_command_internal_t *_node = \ + ((cmd_set) == NULL ? SLIST_FIRST(esp_dynamic_commands_get_list()) \ + : NULL); \ + __attribute__((unused)) size_t _i = 0; \ + for (; \ + ((cmd_set) == NULL \ + ? ((_node != NULL) && ((item_cmd) = &_node->cmd)) \ + : (_i < (cmd_set)->cmd_set_size && \ + ((item_cmd) = (cmd_set)->cmd_ptr_set[_i]))); \ + ((cmd_set) == NULL \ + ? (_node = SLIST_NEXT(_node, next_item)) \ + : (void)++_i)) + +/** + * @brief Acquire the dynamic commands lock. + * + * This function must be called before modifying or iterating over + * the dynamic command list to ensure thread safety. + */ +void esp_dynamic_commands_lock(void); + +/** + * @brief Release the dynamic commands lock. + * + * Call this after operations on the dynamic command list are complete. + */ +void esp_dynamic_commands_unlock(void); + +/** + * @brief Get the internal linked list of dynamic commands. + * + * @return Pointer to the dynamic command linked list head. + * + * @warning The returned list is internal; do not modify it directly. + * Use provided API functions to modify dynamic commands. + */ +const esp_command_internal_ll_t *esp_dynamic_commands_get_list(void); + +/** + * @brief Add a new command to the dynamic command list. + * + * @param cmd Pointer to the command to add. + * @return + * - `ESP_OK` on success. + * - Appropriate error code on failure. + * + * @note The function acquires the lock internally. + */ +esp_err_t esp_dynamic_commands_add(esp_command_t *cmd); + +/** + * @brief Replace an existing command in the dynamic command list. + * + * If a command with the same name exists, it will be replaced. + * + * @param item_cmd Pointer to the new command data. + * @return + * - `ESP_OK` on success. + * - Appropriate error code on failure. + */ +esp_err_t esp_dynamic_commands_replace(esp_command_t *item_cmd); + +/** + * @brief Remove a command from the dynamic command list. + * + * @param item_cmd Pointer to the command to remove. + * @return + * - `ESP_OK` on success. + * - Appropriate error code on failure. + */ +esp_err_t esp_dynamic_commands_remove(esp_command_t *item_cmd); + +/** + * @brief Get the number of registered dynamic commands. + * + * @return The total number of dynamic commands currently registered. + */ +size_t esp_dynamic_commands_get_number_of_cmd(void); + +#ifdef __cplusplus +} +#endif diff --git a/esp_commands/sbom_esp_commands.yml b/esp_commands/sbom_esp_commands.yml new file mode 100644 index 0000000000..f666a01477 --- /dev/null +++ b/esp_commands/sbom_esp_commands.yml @@ -0,0 +1,6 @@ +name: esp_commands +description: Command handling component +url: https://github.com/espressif/idf-extra-components/tree/master/esp_commands +version: 1.0.0 +cpe: cpe:2.3:a:espressif:esp_commands:{}:*:*:*:*:*:*:* +supplier: 'Organization: Espressif Systems' \ No newline at end of file From 13cb90dbfd9cdce2819d1cccf5d1025f5a1f56ad Mon Sep 17 00:00:00 2001 From: Guillaume Souchere Date: Thu, 7 Aug 2025 14:16:23 +0200 Subject: [PATCH 2/9] feat(esp_commands): Add tests to the esp_commands component --- esp_commands/README.md | 181 +++++++ esp_commands/include/esp_commands.h | 2 +- esp_commands/test_apps/CMakeLists.txt | 5 + esp_commands/test_apps/main/CMakeLists.txt | 4 + esp_commands/test_apps/main/idf_component.yml | 4 + .../main/include/test_esp_commands_utils.h | 72 +++ .../test_apps/main/test_esp_commands.c | 452 ++++++++++++++++++ esp_commands/test_apps/main/test_main.c | 26 + esp_commands/test_apps/pytest_esp_commands.py | 9 + esp_commands/test_apps/sdkconfig.defaults | 1 + 10 files changed, 755 insertions(+), 1 deletion(-) create mode 100644 esp_commands/test_apps/CMakeLists.txt create mode 100644 esp_commands/test_apps/main/CMakeLists.txt create mode 100644 esp_commands/test_apps/main/idf_component.yml create mode 100644 esp_commands/test_apps/main/include/test_esp_commands_utils.h create mode 100644 esp_commands/test_apps/main/test_esp_commands.c create mode 100644 esp_commands/test_apps/main/test_main.c create mode 100644 esp_commands/test_apps/pytest_esp_commands.py create mode 100644 esp_commands/test_apps/sdkconfig.defaults diff --git a/esp_commands/README.md b/esp_commands/README.md index e69de29bb2..422b6b4b5f 100644 --- a/esp_commands/README.md +++ b/esp_commands/README.md @@ -0,0 +1,181 @@ +# ESP Commands + +The `esp_commands` component provides a flexible command registration and execution framework for ESP-IDF applications. +It allows applications to define console-like commands with metadata (help text, hints, glossary entries) and register them dynamically or statically. + +--- + +## Features + +- Define commands with: + - Command name + - Group categorization + - Help text + - Optional hints and glossary callbacks +- Register commands at runtime or at compile-time (via section placement macros). +- Execute commands from command line strings. +- Provide command completion, hints, and glossary callback registration mechanism. +- Create and manage subsets of commands (command sets). +- Customizable configuration for command parsing and hint display. + +--- + +## Configuration + +The component is initialized with a configuration struct: + +```c +esp_commands_config_t config = ESP_COMMANDS_CONFIG_DEFAULT(); +esp_commands_update_config(&config); +``` + +- `max_cmdline_length`: Maximum command line buffer length (bytes). +- `max_cmdline_args`: Maximum number of arguments parsed. +- `hint_color`: ANSI color code used for hints. +- `hint_bold`: Whether hints are displayed in bold. + +--- + +## Defining Commands + +### Command Structure + +A command is described by the `esp_command_t` struct: + +```c +typedef struct esp_command { + const char *name; /*!< Command name */ + const char *group; /*!< Group/category */ + const char *help; /*!< Short help text */ + esp_command_func_t func; /*!< Command implementation */ + void *func_ctx; /*!< User context */ + esp_command_hint_t hint_cb; /*!< Hint callback */ + esp_command_glossary_t glossary_cb; /*!< Glossary callback */ +} esp_command_t; +``` + +### Static Registration + +Use the `ESP_COMMAND_REGISTER` macro to register a command at compile time: + +```c +static int my_cmd(void *ctx, int argc, char **argv) { + printf("Hello from my_cmd!\n"); + return 0; +} + +ESP_COMMAND_REGISTER(my_cmd, tools, "Prints hello", my_cmd, NULL, NULL, NULL); +``` + +This places the command into the `.esp_commands` section. + +### Dynamic Registration + +Commands can also be registered/unregistered at runtime: + +```c +esp_command_t cmd = { + .name = "echo", + .group = "utils", + .help = "Echoes arguments back", + .func = echo_func, +}; + +esp_commands_register_cmd(&cmd); +esp_commands_unregister_cmd("echo"); +``` + +--- + +## Executing Commands + +Commands can be executed from a command line string: + +```c +int cmd_ret; +esp_err_t ret = esp_commands_execute(NULL, "my_cmd arg1 arg2", &cmd_ret); +``` + +- `cmd_set`: Limits execution to a set of commands (or `NULL` for all commands). +- `cmd_line`: String containing the command and arguments. +- `cmd_ret`: Receives the command function return value. + +--- + +## Command Completion, Hints, and Glossary + +Completion & Help APIs: + +```c +esp_commands_get_completion(NULL, "ec", completion_cb); +const char *hint = esp_commands_get_hint(NULL, "echo", &color, &bold); +const char *glossary = esp_commands_get_glossary(NULL, "echo"); +``` + +- **Completion**: Suggests matching commands. +- **Hint**: Provides a short usage hint. +- **Glossary**: Provides detailed command description. + +--- + +## Command Sets + +Command sets allow grouping subsets of commands for filtering: + +```c +const char *cmd_names[] = {"echo", "my_cmd"}; +esp_command_set_handle_t set = + ESP_COMMANDS_CREATE_CMD_SET(cmd_names, FIELD_ACCESSOR(name)); + +esp_commands_execute(set, "echo Hello!", NULL); +esp_commands_destroy_cmd_set(&set); +``` + +- Create sets by name, group, or other fields. +- Concatenate sets with `esp_commands_concat_cmd_set()`. +- Destroy sets when no longer needed. + +--- + +## Quick Start Example + +```c +#include +#include "esp_commands.h" + +// Example command function +static int hello_cmd(void *ctx, int argc, char **argv) { + printf("Hello, ESP Commands!\n"); + return 0; +} + +// Register command statically +ESP_COMMAND_REGISTER(hello_cmd, demo, "Prints a hello message", hello_cmd, NULL, NULL, NULL); + +void app_main(void) { + // Update configuration (optional) + esp_commands_config_t config = ESP_COMMANDS_CONFIG_DEFAULT(); + esp_commands_update_config(&config); + + // Execute command + int ret_val; + esp_err_t ret = esp_commands_execute(NULL, "hello_cmd", &ret_val); + if (ret == ESP_OK) { + printf("Command executed successfully, return value: %d\n", ret_val); + } else { + printf("Failed to execute command, error: %d\n", ret); + } +} +``` + +--- + +## API Reference + +- **Configuration**: `esp_commands_update_config()` +- **Registration**: `esp_commands_register_cmd()`, `esp_commands_unregister_cmd()` +- **Execution**: `esp_commands_execute()`, `esp_commands_find_command()` +- **Completion & Help APIs**: `esp_commands_get_completion()`, `esp_commands_get_hint()`, `esp_commands_get_glossary()` +- **Command Sets**: `esp_commands_create_cmd_set()`, `esp_commands_concat_cmd_set()`, `esp_commands_destroy_cmd_set()` + +--- diff --git a/esp_commands/include/esp_commands.h b/esp_commands/include/esp_commands.h index f8761dfa3a..56ebb501a7 100644 --- a/esp_commands/include/esp_commands.h +++ b/esp_commands/include/esp_commands.h @@ -223,7 +223,7 @@ esp_err_t esp_commands_execute(esp_command_set_handle_t cmd_set, const char *cmd * * @param cmd_set Handle to the command set to search in. Must be a valid * `esp_command_set_handle_t` or `NULL` if the search should be performed - * on all statically adn dynamically registered commands. + * on all statically and dynamically registered commands. * @param name String containing the name of the command to search for. * * @return pointer to the matching command or NULL if no command is found. diff --git a/esp_commands/test_apps/CMakeLists.txt b/esp_commands/test_apps/CMakeLists.txt new file mode 100644 index 0000000000..16e55d1d1d --- /dev/null +++ b/esp_commands/test_apps/CMakeLists.txt @@ -0,0 +1,5 @@ +cmake_minimum_required(VERSION 3.16) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +set(COMPONENTS main) +project(esp_commands_test) diff --git a/esp_commands/test_apps/main/CMakeLists.txt b/esp_commands/test_apps/main/CMakeLists.txt new file mode 100644 index 0000000000..de2402ea64 --- /dev/null +++ b/esp_commands/test_apps/main/CMakeLists.txt @@ -0,0 +1,4 @@ +idf_component_register(SRCS "test_esp_commands.c" "test_main.c" + PRIV_INCLUDE_DIRS "." "include" + PRIV_REQUIRES unity + WHOLE_ARCHIVE) diff --git a/esp_commands/test_apps/main/idf_component.yml b/esp_commands/test_apps/main/idf_component.yml new file mode 100644 index 0000000000..1abe4b08c4 --- /dev/null +++ b/esp_commands/test_apps/main/idf_component.yml @@ -0,0 +1,4 @@ +dependencies: + espressif/esp_commands: + version: "*" + override_path: "../.." diff --git a/esp_commands/test_apps/main/include/test_esp_commands_utils.h b/esp_commands/test_apps/main/include/test_esp_commands_utils.h new file mode 100644 index 0000000000..eab21e6a72 --- /dev/null +++ b/esp_commands/test_apps/main/include/test_esp_commands_utils.h @@ -0,0 +1,72 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#define NB_OF_REGISTERED_CMD 8 + +#define GET_NAME(NAME, SUFFIX) NAME##SUFFIX +#define GET_STR(STR) #STR + +#define CREATE_CMD_FUNC(NAME) \ + static int GET_NAME(NAME, _func)(void *ctx, int argc, char **argv) { \ + printf(GET_STR(NAME) GET_STR(_func)); \ + printf("\n"); \ + return 0; \ + } + +#define CREATE_FUNC(NAME, SUFFIX) \ + static const char *GET_NAME(NAME, SUFFIX)(void *context) { \ + return #NAME #SUFFIX; \ + } + +/* static command functions*/ +CREATE_CMD_FUNC(cmd_a) +CREATE_CMD_FUNC(cmd_b) +CREATE_CMD_FUNC(cmd_c) +CREATE_CMD_FUNC(cmd_d) +CREATE_CMD_FUNC(cmd_e) +CREATE_CMD_FUNC(cmd_f) +CREATE_CMD_FUNC(cmd_g) +CREATE_CMD_FUNC(cmd_h) + +/* static hint functions*/ +CREATE_FUNC(cmd_a, _hint) +CREATE_FUNC(cmd_b, _hint) +CREATE_FUNC(cmd_c, _hint) +CREATE_FUNC(cmd_d, _hint) +CREATE_FUNC(cmd_e, _hint) +CREATE_FUNC(cmd_f, _hint) +CREATE_FUNC(cmd_g, _hint) +CREATE_FUNC(cmd_h, _hint) + +/* static glossary functions*/ +CREATE_FUNC(cmd_a, _glossary) +CREATE_FUNC(cmd_b, _glossary) +CREATE_FUNC(cmd_c, _glossary) +CREATE_FUNC(cmd_d, _glossary) +CREATE_FUNC(cmd_e, _glossary) +CREATE_FUNC(cmd_f, _glossary) +CREATE_FUNC(cmd_g, _glossary) +CREATE_FUNC(cmd_h, _glossary) + +/* command registration */ +ESP_COMMAND_REGISTER(cmd_a, group_1, GET_STR(cmd_a_help), cmd_a_func, NULL, cmd_a_hint, cmd_a_glossary); +ESP_COMMAND_REGISTER(cmd_b, group_1, GET_STR(cmd_b_help), cmd_b_func, NULL, cmd_b_hint, cmd_b_glossary); +ESP_COMMAND_REGISTER(cmd_c, group_2, GET_STR(cmd_c_help), cmd_c_func, NULL, cmd_c_hint, cmd_c_glossary); +ESP_COMMAND_REGISTER(cmd_d, group_2, GET_STR(cmd_d_help), cmd_d_func, NULL, cmd_d_hint, cmd_d_glossary); +ESP_COMMAND_REGISTER(cmd_e, group_3, GET_STR(cmd_e_help), cmd_e_func, NULL, cmd_e_hint, cmd_e_glossary); +ESP_COMMAND_REGISTER(cmd_f, group_3, GET_STR(cmd_f_help), cmd_f_func, NULL, cmd_f_hint, cmd_f_glossary); +ESP_COMMAND_REGISTER(cmd_g, group_4, GET_STR(cmd_g_help), cmd_g_func, NULL, cmd_g_hint, cmd_g_glossary); +ESP_COMMAND_REGISTER(cmd_h, group_4, GET_STR(cmd_h_help), cmd_h_func, NULL, cmd_h_hint, cmd_h_glossary); + +#ifdef __cplusplus +} +#endif diff --git a/esp_commands/test_apps/main/test_esp_commands.c b/esp_commands/test_apps/main/test_esp_commands.c new file mode 100644 index 0000000000..ab1369540f --- /dev/null +++ b/esp_commands/test_apps/main/test_esp_commands.c @@ -0,0 +1,452 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include "unity.h" +#include "esp_commands.h" +#include "test_esp_commands_utils.h" + +/* + * IMPORTANT: + * - 8 commands are created in test_esp_commands_utils.h (cmd_a - cmd_h) + * - the commands are divided in 4 groups (group_1 - group_4) + * - each group contains 2 commands. + * - group_1 contains cmd_a and cmd_b, + * [...] + * - group_4 contains cmd_g and cmd_h + */ + +static void test_setup(void) +{ + const esp_commands_config_t config = ESP_COMMANDS_CONFIG_DEFAULT(); + TEST_ASSERT_EQUAL(ESP_OK, esp_commands_update_config(&config)); +} + +TEST_CASE("help command - called without command set", "[esp_commands]") +{ + test_setup(); + + /* call esp_commands_execute to run help command with verbosity 0 */ + int cmd_ret = -1; + TEST_ASSERT_EQUAL(ESP_OK, esp_commands_execute(NULL, "help -v 0", &cmd_ret)); + TEST_ASSERT_EQUAL(0, cmd_ret); + + /* call esp_commands_execute to run help command with verbosity 1 */ + cmd_ret = -1; + TEST_ASSERT_EQUAL(ESP_OK, esp_commands_execute(NULL, "help -v 1", &cmd_ret)); + TEST_ASSERT_EQUAL(0, cmd_ret); + + /* call esp_commands_execute to run help command on a registered command */ + cmd_ret = -1; + TEST_ASSERT_EQUAL(ESP_OK, esp_commands_execute(NULL, "help cmd_a -v 0", &cmd_ret)); + TEST_ASSERT_EQUAL(0, cmd_ret); + TEST_ASSERT_EQUAL(ESP_OK, esp_commands_execute(NULL, "help cmd_a -v 1", &cmd_ret)); + TEST_ASSERT_EQUAL(0, cmd_ret); + + /* call esp_commands_execute to run help command on an unregistered command */ + cmd_ret = -1; + TEST_ASSERT_EQUAL(ESP_OK, esp_commands_execute(NULL, "help cmd_w", &cmd_ret)); + TEST_ASSERT_EQUAL(1, cmd_ret); + + /* call esp_commands_execute to run help command on a registered command with wrong + * verbosity syntax */ + cmd_ret = -1; + TEST_ASSERT_EQUAL(ESP_OK, esp_commands_execute(NULL, "help cmd_a -v=1", &cmd_ret)); + TEST_ASSERT_EQUAL(1, cmd_ret); + + /* call esp_commands_execute to run help command with too many command names */ + cmd_ret = -1; + TEST_ASSERT_EQUAL(ESP_OK, esp_commands_execute(NULL, "help cmd_a cmd_b -v 1", &cmd_ret)); + TEST_ASSERT_EQUAL(1, cmd_ret); +} + +TEST_CASE("test command set error handling", "[esp_commands]") +{ + test_setup(); + + /* create a command set with NULL passed as list of command id */ + TEST_ASSERT_NULL(esp_commands_create_cmd_set(NULL, 2, FIELD_ACCESSOR(group))); + + /* create a command set with 0 as size of list of command id */ + const char *group_set_a[] = {"b", "group_4"}; + TEST_ASSERT_NULL(esp_commands_create_cmd_set(group_set_a, 0, FIELD_ACCESSOR(group))); + + /* concatenate 2 NULL sets */ + TEST_ASSERT_NULL(esp_commands_concat_cmd_set(NULL, NULL)); + + /* redefinition of esp_command_set_t so we can access the fields + * and test their values */ + typedef struct cmd_set { + esp_command_t **cmd_ptr_set; + size_t cmd_set_size; + } cmd_set_t; + + /* pass wrong command name in array, expect a non null command set handle with 0 items in it*/ + const char *group_set_b[] = {"group2", "group4"}; + esp_command_set_handle_t group_set_handle_b = esp_commands_create_cmd_set(group_set_b, 2, FIELD_ACCESSOR(group)); + cmd_set_t *cmd_set = (cmd_set_t *)group_set_handle_b; + TEST_ASSERT_NOT_NULL(group_set_handle_b); + TEST_ASSERT_NULL(cmd_set->cmd_ptr_set); + TEST_ASSERT_EQUAL(0, cmd_set->cmd_set_size); + + esp_commands_destroy_cmd_set(&group_set_handle_b); +} + +typedef struct cmd_test_sequence { + const char *cmd_list[NB_OF_REGISTERED_CMD]; + int expected_ret_val[NB_OF_REGISTERED_CMD]; +} cmd_test_sequence_t; + +static void run_cmd_test(esp_command_set_handle_t handle, const char **cmd_list, const int *expected_ret_val, size_t nb_cmds) +{ + for (size_t i = 0; i < nb_cmds; i++) { + int cmd_ret = -1; + esp_err_t expected = expected_ret_val[i] == 0 ? ESP_OK : ESP_ERR_NOT_FOUND; + TEST_ASSERT_EQUAL(expected, esp_commands_execute(handle, cmd_list[i], &cmd_ret)); + TEST_ASSERT_EQUAL(expected_ret_val[i], cmd_ret); + } +} + +TEST_CASE("test static command set", "[esp_commands]") +{ + test_setup(); + + const char *cmd_list[] = {"cmd_a", "cmd_b", "cmd_c", "cmd_d", "cmd_e", "cmd_f", "cmd_g", "cmd_h"}; + const size_t nb_cmds = sizeof(cmd_list) / sizeof(cmd_list[0]); + int expected_ret_val[nb_cmds]; + + /* create sets by group */ + const char *group_set_a[] = {"group_1", "group_3"}; + esp_command_set_handle_t handle_set_a = ESP_COMMANDS_CREATE_CMD_SET(group_set_a, FIELD_ACCESSOR(group)); + TEST_ASSERT_NOT_NULL(handle_set_a); + + const char *group_set_b[] = {"group_2", "group_4"}; + esp_command_set_handle_t handle_set_b = ESP_COMMANDS_CREATE_CMD_SET(group_set_b, FIELD_ACCESSOR(group)); + TEST_ASSERT_NOT_NULL(handle_set_b); + + /* test set_a by group */ + int tmp_ret[] = {0, 0, -1, -1, 0, 0, -1, -1}; + memcpy(expected_ret_val, tmp_ret, sizeof(tmp_ret)); + run_cmd_test(handle_set_a, cmd_list, expected_ret_val, nb_cmds); + + /* test set_b by group */ + int tmp_ret_b[] = {-1, -1, 0, 0, -1, -1, 0, 0}; + memcpy(expected_ret_val, tmp_ret_b, sizeof(tmp_ret_b)); + run_cmd_test(handle_set_b, cmd_list, expected_ret_val, nb_cmds); + + /* test help command with set of static commands */ + int cmd_ret; + TEST_ASSERT_EQUAL(ESP_OK, esp_commands_execute(handle_set_a, "help", &cmd_ret)); + TEST_ASSERT_EQUAL(0, cmd_ret); + TEST_ASSERT_EQUAL(ESP_OK, esp_commands_execute(handle_set_b, "help", &cmd_ret)); + TEST_ASSERT_EQUAL(0, cmd_ret); + + /* destroy sets */ + esp_commands_destroy_cmd_set(&handle_set_a); + esp_commands_destroy_cmd_set(&handle_set_b); + + /* create sets by name */ + const char *cmd_name_set_a[] = {"cmd_a", "cmd_b", "cmd_c"}; + handle_set_a = esp_commands_create_cmd_set(cmd_name_set_a, 3, FIELD_ACCESSOR(name)); + TEST_ASSERT_NOT_NULL(handle_set_a); + + const char *cmd_name_set_b[] = {"cmd_f", "cmd_g", "cmd_h"}; + handle_set_b = esp_commands_create_cmd_set(cmd_name_set_b, 3, FIELD_ACCESSOR(name)); + TEST_ASSERT_NOT_NULL(handle_set_b); + + int tmp_ret2[] = {0, 0, 0, -1, -1, -1, -1, -1}; + memcpy(expected_ret_val, tmp_ret2, sizeof(tmp_ret2)); + run_cmd_test(handle_set_a, cmd_list, expected_ret_val, nb_cmds); + + int tmp_ret3[] = {-1, -1, -1, -1, -1, 0, 0, 0}; + memcpy(expected_ret_val, tmp_ret3, sizeof(tmp_ret3)); + run_cmd_test(handle_set_b, cmd_list, expected_ret_val, nb_cmds); + + /* concatenate sets */ + esp_command_set_handle_t handle_set_c = esp_commands_concat_cmd_set(handle_set_a, handle_set_b); + TEST_ASSERT_NOT_NULL(handle_set_c); + + int tmp_ret4[] = {0, 0, 0, -1, -1, 0, 0, 0}; + memcpy(expected_ret_val, tmp_ret4, sizeof(tmp_ret4)); + run_cmd_test(handle_set_c, cmd_list, expected_ret_val, nb_cmds); + + esp_commands_destroy_cmd_set(&handle_set_c); +} + +static int dummy_cmd_func(void *context, int argc, char **argv) +{ + printf("dynamic command called\n"); + return 0; // always return success +} + +TEST_CASE("test dynamic command set", "[esp_commands]") +{ + test_setup(); + + const char *cmd_list[] = {"cmd_1", "cmd_2", "cmd_3", "cmd_4", "cmd_5", "cmd_6", "cmd_7", "cmd_8"}; + const size_t nb_cmds = sizeof(cmd_list) / sizeof(cmd_list[0]); + int expected_ret_val[nb_cmds]; + + /* dynamically register commands */ + for (size_t i = 0; i < nb_cmds; i++) { + esp_command_t cmd = { + .name = cmd_list[i], + .group = (i % 2 == 0) ? "group_a" : "group_b", + .help = "dummy help", + .func = dummy_cmd_func, // implement a simple dummy function returning i%2 + .func_ctx = NULL, + .hint_cb = NULL, + .glossary_cb = NULL + }; + TEST_ASSERT_EQUAL(ESP_OK, esp_commands_register_cmd(&cmd)); + } + + /* test execution by group_a */ + const char *group_set[] = {"group_a"}; + esp_command_set_handle_t handle_set_1 = ESP_COMMANDS_CREATE_CMD_SET(group_set, FIELD_ACCESSOR(group)); + TEST_ASSERT_NOT_NULL(handle_set_1); + + int tmp_ret[] = {0, -1, 0, -1, 0, -1, 0, -1}; + memcpy(expected_ret_val, tmp_ret, sizeof(tmp_ret)); + run_cmd_test(handle_set_1, cmd_list, expected_ret_val, nb_cmds); + + /* test execution by command name */ + const char *cmd_name_set[] = {"cmd_1", "cmd_2", "cmd_3"}; + esp_command_set_handle_t handle_set_2 = esp_commands_create_cmd_set(cmd_name_set, 3, FIELD_ACCESSOR(name)); + TEST_ASSERT_NOT_NULL(handle_set_2); + + int tmp_ret2[] = {0, 0, 0, -1, -1, -1, -1, -1}; + memcpy(expected_ret_val, tmp_ret2, sizeof(tmp_ret2)); + run_cmd_test(handle_set_2, cmd_list, expected_ret_val, nb_cmds); + + /* test help command with set of dynamic commands */ + int cmd_ret; + TEST_ASSERT_EQUAL(ESP_OK, esp_commands_execute(handle_set_1, "help", &cmd_ret)); + TEST_ASSERT_EQUAL(0, cmd_ret); + TEST_ASSERT_EQUAL(ESP_OK, esp_commands_execute(handle_set_2, "help", &cmd_ret)); + TEST_ASSERT_EQUAL(0, cmd_ret); + + + /* unregister dynamically registered commands */ + for (size_t i = 0; i < nb_cmds; i++) { + TEST_ASSERT_EQUAL(ESP_OK, esp_commands_unregister_cmd(cmd_list[i])); + } + + esp_commands_destroy_cmd_set(&handle_set_1); + esp_commands_destroy_cmd_set(&handle_set_2); +} + +TEST_CASE("test static and dynamic command sets", "[esp_commands]") +{ + test_setup(); + + // --- dynamic commands --- + const char *dyn_cmd_list[] = {"cmd_1", "cmd_2", "cmd_3", "cmd_4", "cmd_5", "cmd_6", "cmd_7", "cmd_8"}; + const size_t nb_dyn_cmds = sizeof(dyn_cmd_list) / sizeof(dyn_cmd_list[0]); + + for (size_t i = 0; i < nb_dyn_cmds; i++) { + esp_command_t cmd = { + .name = dyn_cmd_list[i], + .group = (i % 2 == 0) ? "group_a" : "group_b", + .help = "dummy help", + .func = dummy_cmd_func, + .func_ctx = NULL, + .hint_cb = NULL, + .glossary_cb = NULL + }; + TEST_ASSERT_EQUAL(ESP_OK, esp_commands_register_cmd(&cmd)); + } + + // --- create static command sets (already registered statically) --- + const char *static_groups[] = {"group_1", "group_3"}; + esp_command_set_handle_t handle_static_set = ESP_COMMANDS_CREATE_CMD_SET(static_groups, FIELD_ACCESSOR(group)); + TEST_ASSERT_NOT_NULL(handle_static_set); + + // --- create dynamic command sets --- + const char *dyn_groups[] = {"group_a"}; + esp_command_set_handle_t handle_dynamic_set = ESP_COMMANDS_CREATE_CMD_SET(dyn_groups, FIELD_ACCESSOR(group)); + TEST_ASSERT_NOT_NULL(handle_dynamic_set); + + // --- combine static and dynamic sets --- + esp_command_set_handle_t handle_combined_set = esp_commands_concat_cmd_set(handle_static_set, handle_dynamic_set); + TEST_ASSERT_NOT_NULL(handle_combined_set); + + // --- run tests for combined set --- + const char *all_cmds[] = {"cmd_a", "cmd_b", "cmd_c", "cmd_d", "cmd_e", "cmd_f", "cmd_g", "cmd_h", + "cmd_1", "cmd_2", "cmd_3", "cmd_4", "cmd_5", "cmd_6", "cmd_7", "cmd_8" + }; + int expected_ret[] = {0, 0, -1, -1, 0, 0, -1, -1, + 0, -1, 0, -1, 0, -1, 0, -1 + }; + + run_cmd_test(handle_combined_set, all_cmds, expected_ret, 16); + + /* test help command with set of dynamic commands */ + int cmd_ret; + TEST_ASSERT_EQUAL(ESP_OK, esp_commands_execute(handle_combined_set, "help", &cmd_ret)); + TEST_ASSERT_EQUAL(0, cmd_ret); + + // --- cleanup --- + esp_commands_destroy_cmd_set(&handle_combined_set); + + for (size_t i = 0; i < nb_dyn_cmds; i++) { + TEST_ASSERT_EQUAL(ESP_OK, esp_commands_unregister_cmd(dyn_cmd_list[i])); + } +} + +static size_t completion_nb_of_calls = 0; + +static void test_completion_cb(const char *completed_cmd_name) +{ + completion_nb_of_calls++; +} + +TEST_CASE("test completion callback", "[esp_commands]") +{ + test_setup(); + + /* create sets by group */ + const char *set_a[] = {"group_1", "group_3"}; + esp_command_set_handle_t handle_set_a = ESP_COMMANDS_CREATE_CMD_SET(set_a, FIELD_ACCESSOR(group)); + TEST_ASSERT_NOT_NULL(handle_set_a); + + /* register a command dynamically and add it to the set */ + esp_command_t cmd = { + .name = "dyn_cmd", + .group = "dyn_cmd_group", + .help = "dummy help", + .func = dummy_cmd_func, + .func_ctx = NULL, + .hint_cb = NULL, + .glossary_cb = NULL + }; + TEST_ASSERT_EQUAL(ESP_OK, esp_commands_register_cmd(&cmd)); + + const char *set_b[] = {"dyn_cmd"}; + esp_command_set_handle_t handle_set_b = ESP_COMMANDS_CREATE_CMD_SET(set_b, FIELD_ACCESSOR(name)); + TEST_ASSERT_NOT_NULL(handle_set_b); + esp_command_set_handle_t handle_concat_set = esp_commands_concat_cmd_set(handle_set_a, handle_set_b); + TEST_ASSERT_NOT_NULL(handle_concat_set); + + esp_commands_get_completion(NULL, "a", test_completion_cb); + TEST_ASSERT_EQUAL(0, completion_nb_of_calls); + + esp_commands_get_completion(handle_concat_set, "cmd_", test_completion_cb); + TEST_ASSERT_EQUAL(4, completion_nb_of_calls); + + /* reset the cb counter */ + completion_nb_of_calls = 0; + + esp_commands_get_completion(NULL, "cmd_", test_completion_cb); + TEST_ASSERT_EQUAL(8, completion_nb_of_calls); + + /* reset the cb counter */ + completion_nb_of_calls = 0; + + esp_commands_get_completion(NULL, "dyn", test_completion_cb); + TEST_ASSERT_EQUAL(1, completion_nb_of_calls); + + /* reset the cb counter */ + completion_nb_of_calls = 0; + + esp_commands_get_completion(handle_concat_set, "dyn", test_completion_cb); + TEST_ASSERT_EQUAL(1, completion_nb_of_calls); + + /* reset the cb counter */ + completion_nb_of_calls = 0; + + esp_commands_destroy_cmd_set(&handle_concat_set); + TEST_ASSERT_NULL(handle_concat_set); + + esp_commands_unregister_cmd("dyn_cmd"); +} + +typedef struct hint_cb_ctx { + const char *message; +} hint_cb_ctx_t; + +static const char *test_hint_cb(void *context) +{ + hint_cb_ctx_t *ctx = (hint_cb_ctx_t *)context; + return ctx->message; +} + +static const char *test_glossary_cb(void *context) +{ + hint_cb_ctx_t *ctx = (hint_cb_ctx_t *)context; + return ctx->message; +} + +TEST_CASE("test hint and glossary callbacks", "[esp_commands]") +{ + test_setup(); + + hint_cb_ctx_t ctx_a = { .message = "msg_a" }; + hint_cb_ctx_t ctx_b = { .message = "msg_b" }; + + esp_command_t cmd_a = { + .name = "dyn_cmd_a", + .group = "dyn_cmd_group", + .help = "dummy help", + .func = dummy_cmd_func, + .func_ctx = &ctx_a, + .hint_cb = test_hint_cb, + .glossary_cb = test_glossary_cb + }; + TEST_ASSERT_EQUAL(ESP_OK, esp_commands_register_cmd(&cmd_a)); + + esp_command_t cmd_b = { + .name = "dyn_cmd_b", + .group = "dyn_cmd_group", + .help = "dummy help", + .func = dummy_cmd_func, + .func_ctx = &ctx_b, + .hint_cb = test_hint_cb, + .glossary_cb = test_glossary_cb + }; + TEST_ASSERT_EQUAL(ESP_OK, esp_commands_register_cmd(&cmd_b)); + + bool bold = true; + int color = 0; + const char *dyn_cmd_a_msg_hint = esp_commands_get_hint(NULL, "dyn_cmd_a", &color, &bold); + TEST_ASSERT_EQUAL(0, strcmp(dyn_cmd_a_msg_hint, ctx_a.message)); + TEST_ASSERT_EQUAL(false, bold); /* bold set a false by default in the component config */ + TEST_ASSERT_EQUAL(39, color); /* color set to 39 by default in the component config */ + + const char *dyn_cmd_b_msg_hint = esp_commands_get_hint(NULL, "dyn_cmd_b", &color, &bold); + TEST_ASSERT_EQUAL(0, strcmp(dyn_cmd_b_msg_hint, ctx_b.message)); + + const char *dyn_cmd_a_msg_glossary = esp_commands_get_glossary(NULL, "dyn_cmd_a"); + TEST_ASSERT_EQUAL(0, strcmp(dyn_cmd_a_msg_glossary, ctx_a.message)); + + const char *dyn_cmd_b_msg_glossary = esp_commands_get_glossary(NULL, "dyn_cmd_b"); + TEST_ASSERT_EQUAL(0, strcmp(dyn_cmd_b_msg_glossary, ctx_b.message)); + + /* create a set with only dyn_cmd_a and check that the hint cb is called for + * dyn_cmd_a but not for dyn_cmd_b */ + const char *set[] = {"dyn_cmd_a"}; + esp_command_set_handle_t handle_set = ESP_COMMANDS_CREATE_CMD_SET(set, FIELD_ACCESSOR(name)); + TEST_ASSERT_NOT_NULL(handle_set); + + const char *dyn_cmd_a_msg_hint_bis = esp_commands_get_hint(handle_set, "dyn_cmd_a", &color, &bold); + TEST_ASSERT_EQUAL(0, strcmp(dyn_cmd_a_msg_hint_bis, ctx_a.message)); + + const char *dyn_cmd_b_msg_hint_bis = esp_commands_get_hint(handle_set, "dyn_cmd_b", &color, &bold); + TEST_ASSERT_NULL(dyn_cmd_b_msg_hint_bis); + + const char *dyn_cmd_a_msg_glossary_bis = esp_commands_get_glossary(handle_set, "dyn_cmd_a"); + TEST_ASSERT_EQUAL(0, strcmp(dyn_cmd_a_msg_glossary_bis, ctx_a.message)); + + const char *dyn_cmd_b_msg_glossary_bis = esp_commands_get_glossary(handle_set, "dyn_cmd_b"); + TEST_ASSERT_NULL(dyn_cmd_b_msg_glossary_bis); + + TEST_ASSERT_EQUAL(ESP_OK, esp_commands_unregister_cmd("dyn_cmd_a")); + TEST_ASSERT_EQUAL(ESP_OK, esp_commands_unregister_cmd("dyn_cmd_b")); + + esp_commands_destroy_cmd_set(&handle_set); +} diff --git a/esp_commands/test_apps/main/test_main.c b/esp_commands/test_apps/main/test_main.c new file mode 100644 index 0000000000..01c2785d05 --- /dev/null +++ b/esp_commands/test_apps/main/test_main.c @@ -0,0 +1,26 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "unity.h" +#include "unity_test_runner.h" +#include "esp_heap_caps.h" +#include "unity_test_utils_memory.h" + +void setUp(void) +{ + unity_utils_record_free_mem(); +} + +void tearDown(void) +{ + unity_utils_evaluate_leaks_direct(0); +} + +void app_main(void) +{ + printf("Running esp_commands component tests\n"); + unity_run_menu(); +} diff --git a/esp_commands/test_apps/pytest_esp_commands.py b/esp_commands/test_apps/pytest_esp_commands.py new file mode 100644 index 0000000000..862e20b907 --- /dev/null +++ b/esp_commands/test_apps/pytest_esp_commands.py @@ -0,0 +1,9 @@ +import pytest +from pytest_embedded import Dut +from pytest_embedded_idf.utils import idf_parametrize + + +@pytest.mark.generic +@pytest.mark.skip_if_soc("IDF_VERSION_MAJOR < 6") +def test_esp_commands(dut) -> None: + dut.run_all_single_board_cases() diff --git a/esp_commands/test_apps/sdkconfig.defaults b/esp_commands/test_apps/sdkconfig.defaults new file mode 100644 index 0000000000..5e7cb391c2 --- /dev/null +++ b/esp_commands/test_apps/sdkconfig.defaults @@ -0,0 +1 @@ +CONFIG_ESP_TASK_WDT_EN=n \ No newline at end of file From 6aed6259031e6c408a9ecb4e8e8e03ece6feb942 Mon Sep 17 00:00:00 2001 From: Guillaume Souchere Date: Wed, 1 Oct 2025 12:54:48 +0200 Subject: [PATCH 3/9] feat(esp_commands): Add a way to output data on specified file descriptor --- esp_commands/esp_commands.c | 97 +++++++++++++------ esp_commands/esp_commands_helpers.c | 1 - esp_commands/include/esp_commands.h | 44 +++++++-- .../private_include/esp_commands_helpers.h | 27 ------ .../main/include/test_esp_commands_utils.h | 2 +- .../test_apps/main/test_esp_commands.c | 43 ++++---- 6 files changed, 127 insertions(+), 87 deletions(-) delete mode 100644 esp_commands/private_include/esp_commands_helpers.h diff --git a/esp_commands/esp_commands.c b/esp_commands/esp_commands.c index b52f650cbe..099a3eebbc 100644 --- a/esp_commands/esp_commands.c +++ b/esp_commands/esp_commands.c @@ -4,9 +4,10 @@ * SPDX-License-Identifier: Apache-2.0 */ #include +#include +#include #include "esp_commands.h" #include "esp_dynamic_commands.h" -#include "esp_commands_helpers.h" #include "esp_err.h" /* Default foreground color */ @@ -24,6 +25,7 @@ typedef struct esp_command_sets { /** run-time configuration options */ static esp_commands_config_t s_config = { + .write_func = write, .hint_bold = false, .hint_color = ANSI_COLOR_DEFAULT, .max_cmdline_args = 32, @@ -149,15 +151,20 @@ esp_err_t esp_commands_update_config(const esp_commands_config_t *config) memcpy(&s_config, config, sizeof(s_config)); + /* if no write function was passed in parameter, + * default it to the posix write */ + if (s_config.write_func == NULL) { + s_config.write_func = write; + } + return ESP_OK; } esp_err_t esp_commands_register_cmd(esp_command_t *cmd) { if (cmd == NULL || - // (cmd->name == NULL || strchr(cmd->name, ' ') != NULL) || - cmd->func == NULL ) { - printf("this should not happen\n"); + (cmd->name == NULL || strchr(cmd->name, ' ') != NULL) || + (cmd->func == NULL)) { return ESP_ERR_INVALID_ARG; } @@ -200,7 +207,7 @@ esp_err_t esp_commands_unregister_cmd(const char *cmd_name) } } -esp_err_t esp_commands_execute(esp_command_set_handle_t cmd_set, const char *cmdline, int *cmd_ret) +esp_err_t esp_commands_execute(esp_command_set_handle_t cmd_set, const int cmd_fd, const char *cmdline, int *cmd_ret) { char **argv = (char **) calloc(s_config.max_cmdline_args, sizeof(char *)); if (argv == NULL) { @@ -237,12 +244,14 @@ esp_err_t esp_commands_execute(esp_command_set_handle_t cmd_set, const char *cmd free(tmp_line_buf); return ESP_ERR_NOT_FOUND; } + + const int fd_out = cmd_fd == -1 ? STDOUT_FILENO : cmd_fd; if (cmd->func) { if (is_cmd_help) { // executing help command, pass the cmd_set as context - *cmd_ret = (*cmd->func)(cmd_set, argc, argv); + *cmd_ret = (*cmd->func)(cmd_set, fd_out, argc, argv); } else { - *cmd_ret = (*cmd->func)(cmd->func_ctx, argc, argv); + *cmd_ret = (*cmd->func)(cmd->func_ctx, fd_out, argc, argv); } } free(argv); @@ -447,6 +456,7 @@ void esp_commands_destroy_cmd_set(esp_command_set_handle_t *cmd_set) typedef struct call_completion_cb_ctx { const char *buf; const size_t buf_len; + void *cb_ctx; esp_command_get_completion_t completion_cb; } call_completion_cb_ctx_t; @@ -456,12 +466,12 @@ static bool call_completion_cb(void *caller_ctx, esp_command_t *cmd) /* Check if command starts with buf */ if (strncmp(ctx->buf, cmd->name, ctx->buf_len) == 0) { - ctx->completion_cb(cmd->name); + ctx->completion_cb(ctx->cb_ctx, cmd->name); } return true; } -void esp_commands_get_completion(esp_command_set_handle_t cmd_set, const char *buf, esp_command_get_completion_t completion_cb) +void esp_commands_get_completion(esp_command_set_handle_t cmd_set, const char *buf, void *cb_ctx, esp_command_get_completion_t completion_cb) { size_t len = strlen(buf); if (len == 0) { @@ -471,6 +481,7 @@ void esp_commands_get_completion(esp_command_set_handle_t cmd_set, const char *b call_completion_cb_ctx_t ctx = { .buf = buf, .buf_len = len, + .cb_ctx = cb_ctx, .completion_cb = completion_cb }; go_through_commands(cmd_set, &ctx, call_completion_cb); @@ -503,43 +514,69 @@ const char *esp_commands_get_glossary(esp_command_set_handle_t cmd_set, const ch /* help command related code */ /* -------------------------------------------------------------- */ -static void print_arg_help(esp_command_t *it) +#define FDPRINTF(fd, fmt, ...) do { \ + char _buf[s_config.max_cmdline_length]; \ + int _len = snprintf(_buf, sizeof(_buf), fmt, ##__VA_ARGS__); \ + if (_len > 0) { \ + ssize_t _ignored __attribute__((unused)); \ + _ignored = write(fd, _buf, \ + _len < (int)sizeof(_buf) ? _len : (int)sizeof(_buf) - 1); \ + } \ +} while (0) + +static void print_arg_help(const int fd_out, esp_command_t *it) { /* First line: command name and hint * Pad all the hints to the same column */ - printf("%-s", it->name); + FDPRINTF(fd_out, "%-s", it->name); + + const char *hint = NULL; if (it->hint_cb) { - printf(" %s\n", it->hint_cb(it->func_ctx)); + hint = it->hint_cb(it->func_ctx); + } + + if (hint) { + FDPRINTF(fd_out, "%s\n", it->hint_cb(it->func_ctx)); } else { - printf("\n"); + FDPRINTF(fd_out, "\n"); } /* Second line: print help */ /* TODO: replace the simple print with a function that * replaces arg_print_formatted */ if (it->help) { - printf(" %s\n", it->help); + FDPRINTF(fd_out, " %s\n", it->help); } else { - printf(" -\n"); + FDPRINTF(fd_out, " -\n"); } /* Third line: print the glossary*/ + const char *glossary = NULL; if (it->glossary_cb) { - printf("%s\n", it->glossary_cb(it->func_ctx)); + glossary = it->glossary_cb(it->func_ctx); + } + + if (glossary) { + FDPRINTF(fd_out, " %s\n", it->glossary_cb(it->func_ctx)); } else { - printf(" -\n"); + FDPRINTF(fd_out, " -\n"); } - printf("\n"); + FDPRINTF(fd_out, "\n"); } -static void print_arg_command(esp_command_t *it) +static void print_arg_command(const int fd_out, esp_command_t *it) { - printf("%-s", it->name); + FDPRINTF(fd_out, "%-s", it->name); if (it->hint_cb) { - printf(" %s\n", it->hint_cb(it->func_ctx)); + const char *hint = it->hint_cb(it->func_ctx); + if (hint) { + FDPRINTF(fd_out, " %s", it->hint_cb(it->func_ctx)); + } } + + FDPRINTF(fd_out, "\n"); } typedef enum { @@ -548,7 +585,7 @@ typedef enum { HELP_VERBOSE_LEVEL_MAX_NUM = 2 } help_verbose_level_e; -typedef void (*const fn_print_arg_t)(esp_command_t *); +typedef void (*const fn_print_arg_t)(const int fd_out, esp_command_t *); static fn_print_arg_t print_verbose_level_arr[HELP_VERBOSE_LEVEL_MAX_NUM] = { print_arg_command, @@ -556,6 +593,7 @@ static fn_print_arg_t print_verbose_level_arr[HELP_VERBOSE_LEVEL_MAX_NUM] = { }; typedef struct call_cmd_ctx { + const int fd_out; help_verbose_level_e verbose_level; const char *command_name; bool command_found; @@ -568,11 +606,11 @@ bool call_command_funcs(void *caller_ctx, esp_command_t *cmd) if (!ctx->command_name) { /* ctx->command_name is empty, print all commands */ - print_verbose_level_arr[ctx->verbose_level](cmd); + print_verbose_level_arr[ctx->verbose_level](ctx->fd_out, cmd); } else if (ctx->command_name && (strcmp(ctx->command_name, cmd->name) == 0)) { /* we found the command name, print the help and return */ - print_verbose_level_arr[ctx->verbose_level](cmd); + print_verbose_level_arr[ctx->verbose_level](ctx->fd_out, cmd); ctx->command_found = true; return false; } @@ -580,7 +618,7 @@ bool call_command_funcs(void *caller_ctx, esp_command_t *cmd) return true; } -static int help_command(void *context, int argc, char **argv) +static int help_command(void *context, const int fd_out, int argc, char **argv) { char *command_name = NULL; help_verbose_level_e verbose_level = HELP_VERBOSE_LEVEL_1; @@ -589,7 +627,7 @@ static int help_command(void *context, int argc, char **argv) * help cmd_name -v 0 */ if (argc <= 0 || argc > 4) { /* unknown issue, return error */ - printf("help: invalid number of arguments %d\n", argc); + FDPRINTF(fd_out, "help: invalid number of arguments %d\n", argc); return 1; } @@ -605,7 +643,7 @@ static int help_command(void *context, int argc, char **argv) /* check if the following argument is either 0, or 1 */ if (i + 1 >= argc) { /* format error, return with error */ - printf("help: arguments not provided in the right format\n"); + FDPRINTF(fd_out, "help: arguments not provided in the right format\n"); return 1; } else if (strcmp(argv[i + 1], "0") == 0) { verbose_level = 0; @@ -613,7 +651,7 @@ static int help_command(void *context, int argc, char **argv) verbose_level = 1; } else { /* wrong command format, return error */ - printf("help: invalid verbose level %s\n", argv[i + 1]); + FDPRINTF(fd_out, "help: invalid verbose level %s\n", argv[i + 1]); return 1; } @@ -633,6 +671,7 @@ static int help_command(void *context, int argc, char **argv) * is not NULL, find the command and only print the help for this command. if the * command is not found, return with error */ call_cmd_ctx_t ctx = { + .fd_out = fd_out, .verbose_level = verbose_level, .command_name = command_name, .command_found = false @@ -640,7 +679,7 @@ static int help_command(void *context, int argc, char **argv) go_through_commands(cmd_sets, &ctx, call_command_funcs); if (command_name && !ctx.command_found) { - printf("help: invalid command name %s\n", command_name); + FDPRINTF(fd_out, "help: invalid command name %s\n", command_name); return 1; } diff --git a/esp_commands/esp_commands_helpers.c b/esp_commands/esp_commands_helpers.c index 30947f32d5..b2ebf875eb 100644 --- a/esp_commands/esp_commands_helpers.c +++ b/esp_commands/esp_commands_helpers.c @@ -8,7 +8,6 @@ #include #include #include -#include "esp_commands_helpers.h" #include "esp_commands.h" #define SS_FLAG_ESCAPE 0x8 diff --git a/esp_commands/include/esp_commands.h b/esp_commands/include/esp_commands.h index 56ebb501a7..3329bf1688 100644 --- a/esp_commands/include/esp_commands.h +++ b/esp_commands/include/esp_commands.h @@ -18,11 +18,12 @@ extern "C" { * This function type is used to implement a console command. * * @param context User-defined context passed at invocation + * @param fd_out The file descriptor to use to output data * @param argc Number of arguments * @param argv Array of argc entries, each pointing to a null-terminated string argument * @return Return code of the console command; 0 indicates success */ -typedef int (*esp_command_func_t)(void *context, int argc, char **argv); +typedef int (*esp_command_func_t)(void *context, const int fd_out, int argc, char **argv); /** * @brief Callback to generate a command hint @@ -117,14 +118,25 @@ DEFINE_FIELD_ACCESSOR(help) */ #define FIELD_ACCESSOR(NAME) get_##NAME +/** + * @brief Function pointer type for writing bytes. + * + * @param fd File descriptor. + * @param buf Buffer containing bytes to write. + * @param count Number of bytes to write. + * @return Number of bytes written, or -1 on error. + */ +typedef ssize_t (*esp_commands_write_t)(int fd, const void *buf, size_t count); + /** * @brief Configuration parameters for esp_commands_manager initialization */ typedef struct esp_commands_config { - size_t max_cmdline_length; /*!< Maximum length of the command line buffer, in bytes */ - size_t max_cmdline_args; /*!< Maximum number of command line arguments to parse */ - int hint_color; /*!< ANSI color code used for hint text */ - bool hint_bold; /*!< If true, display hint text in bold */ + esp_commands_write_t write_func; /*!< Write function to call when executing a command */ + size_t max_cmdline_length; /*!< Maximum length of the command line buffer, in bytes */ + size_t max_cmdline_args; /*!< Maximum number of command line arguments to parse */ + int hint_color; /*!< ANSI color code used for hint text */ + bool hint_bold; /*!< If true, display hint text in bold */ } esp_commands_config_t; /** @@ -143,9 +155,10 @@ typedef struct esp_commands_config { * * This callback is called when a command is successfully completed. * + * @param cb_ctx Opaque pointer pointing at the context passed to the callback * @param completed_cmd_name Completed command name */ -typedef void (*esp_command_get_completion_t)(const char *completed_cmd_name); +typedef void (*esp_command_get_completion_t)(void *cb_ctx, const char *completed_cmd_name); /** * @brief Callback to retrieve a string field of esp_command_t @@ -207,14 +220,15 @@ esp_err_t esp_commands_unregister_cmd(const char *cmd_name); * @brief Execute a command line * * @param cmd_set Set of commands allowed to execute. If NULL, all registered commands are allowed + * @param cmd_fd File descriptor used to output data * @param cmd_line Command line string to execute - * @param cmd_ret Return value from the command function + * @param cmd_ret Return value from the command function. If -1, standard output will be used. * @return ESP_OK on success * ESP_ERR_INVALID_ARG if the command line is empty or only whitespace * ESP_ERR_NOT_FOUND if command is not found in cmd_set * ESP_ERR_NO_MEM if internal memory allocation fails */ -esp_err_t esp_commands_execute(esp_command_set_handle_t cmd_set, const char *cmdline, int *cmd_ret); +esp_err_t esp_commands_execute(esp_command_set_handle_t cmd_set, const int cmd_fd, const char *cmdline, int *cmd_ret); /** * @brief Find a command by name within a specific command set. @@ -235,9 +249,10 @@ esp_command_t *esp_commands_find_command(esp_command_set_handle_t cmd_set, const * * @param cmd_set Set of commands allowed for completion. If NULL, all registered commands are used * @param buf Input string typed by the user + * @param cb_ctx context passed to the completion callback * @param completion_cb Callback to return completed command names */ -void esp_commands_get_completion(esp_command_set_handle_t cmd_set, const char *buf, esp_command_get_completion_t completion_cb); +void esp_commands_get_completion(esp_command_set_handle_t cmd_set, const char *buf, void *cb_ctx, esp_command_get_completion_t completion_cb); /** * @brief Provide command hint for linenoise library @@ -298,6 +313,17 @@ esp_command_set_handle_t esp_commands_concat_cmd_set(esp_command_set_handle_t cm */ void esp_commands_destroy_cmd_set(esp_command_set_handle_t *cmd_set); +/** + * @brief Split a command line and populate argc and argv parameters + * + * @param line the line that has to be split into arguments + * @param argv array of arguments created from the line + * @param argv_size size of the argument array + * @return size_t number of arguments found in the line and stored + * in argv + */ +size_t esp_commands_split_argv(char *line, char **argv, size_t argv_size); + #ifdef __cplusplus } #endif diff --git a/esp_commands/private_include/esp_commands_helpers.h b/esp_commands/private_include/esp_commands_helpers.h deleted file mode 100644 index 4ee67164ac..0000000000 --- a/esp_commands/private_include/esp_commands_helpers.h +++ /dev/null @@ -1,27 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD - * - * SPDX-License-Identifier: Apache-2.0 - */ -#pragma once - -#ifdef __cplusplus -extern "C" { -#endif - -#include - -/** - * @brief Split a command line and populate argc and argv parameters - * - * @param line the line that has to be split into arguments - * @param argv array of arguments created from the line - * @param argv_size size of the argument array - * @return size_t number of arguments found in the line and stored - * in argv - */ -size_t esp_commands_split_argv(char *line, char **argv, size_t argv_size); - -#ifdef __cplusplus -} -#endif diff --git a/esp_commands/test_apps/main/include/test_esp_commands_utils.h b/esp_commands/test_apps/main/include/test_esp_commands_utils.h index eab21e6a72..a2426eb5b6 100644 --- a/esp_commands/test_apps/main/include/test_esp_commands_utils.h +++ b/esp_commands/test_apps/main/include/test_esp_commands_utils.h @@ -16,7 +16,7 @@ extern "C" { #define GET_STR(STR) #STR #define CREATE_CMD_FUNC(NAME) \ - static int GET_NAME(NAME, _func)(void *ctx, int argc, char **argv) { \ + static int GET_NAME(NAME, _func)(void *ctx, const int out_fd, int argc, char **argv) { \ printf(GET_STR(NAME) GET_STR(_func)); \ printf("\n"); \ return 0; \ diff --git a/esp_commands/test_apps/main/test_esp_commands.c b/esp_commands/test_apps/main/test_esp_commands.c index ab1369540f..f3e402683c 100644 --- a/esp_commands/test_apps/main/test_esp_commands.c +++ b/esp_commands/test_apps/main/test_esp_commands.c @@ -6,6 +6,7 @@ #include #include +#include #include #include "unity.h" #include "esp_commands.h" @@ -33,35 +34,35 @@ TEST_CASE("help command - called without command set", "[esp_commands]") /* call esp_commands_execute to run help command with verbosity 0 */ int cmd_ret = -1; - TEST_ASSERT_EQUAL(ESP_OK, esp_commands_execute(NULL, "help -v 0", &cmd_ret)); + TEST_ASSERT_EQUAL(ESP_OK, esp_commands_execute(NULL, -1, "help -v 0", &cmd_ret)); TEST_ASSERT_EQUAL(0, cmd_ret); /* call esp_commands_execute to run help command with verbosity 1 */ cmd_ret = -1; - TEST_ASSERT_EQUAL(ESP_OK, esp_commands_execute(NULL, "help -v 1", &cmd_ret)); + TEST_ASSERT_EQUAL(ESP_OK, esp_commands_execute(NULL, STDOUT_FILENO, "help -v 1", &cmd_ret)); TEST_ASSERT_EQUAL(0, cmd_ret); /* call esp_commands_execute to run help command on a registered command */ cmd_ret = -1; - TEST_ASSERT_EQUAL(ESP_OK, esp_commands_execute(NULL, "help cmd_a -v 0", &cmd_ret)); + TEST_ASSERT_EQUAL(ESP_OK, esp_commands_execute(NULL, -1, "help cmd_a -v 0", &cmd_ret)); TEST_ASSERT_EQUAL(0, cmd_ret); - TEST_ASSERT_EQUAL(ESP_OK, esp_commands_execute(NULL, "help cmd_a -v 1", &cmd_ret)); + TEST_ASSERT_EQUAL(ESP_OK, esp_commands_execute(NULL, STDOUT_FILENO, "help cmd_a -v 1", &cmd_ret)); TEST_ASSERT_EQUAL(0, cmd_ret); /* call esp_commands_execute to run help command on an unregistered command */ cmd_ret = -1; - TEST_ASSERT_EQUAL(ESP_OK, esp_commands_execute(NULL, "help cmd_w", &cmd_ret)); + TEST_ASSERT_EQUAL(ESP_OK, esp_commands_execute(NULL, -1, "help cmd_w", &cmd_ret)); TEST_ASSERT_EQUAL(1, cmd_ret); /* call esp_commands_execute to run help command on a registered command with wrong * verbosity syntax */ cmd_ret = -1; - TEST_ASSERT_EQUAL(ESP_OK, esp_commands_execute(NULL, "help cmd_a -v=1", &cmd_ret)); + TEST_ASSERT_EQUAL(ESP_OK, esp_commands_execute(NULL, -1, "help cmd_a -v=1", &cmd_ret)); TEST_ASSERT_EQUAL(1, cmd_ret); /* call esp_commands_execute to run help command with too many command names */ cmd_ret = -1; - TEST_ASSERT_EQUAL(ESP_OK, esp_commands_execute(NULL, "help cmd_a cmd_b -v 1", &cmd_ret)); + TEST_ASSERT_EQUAL(ESP_OK, esp_commands_execute(NULL, -1, "help cmd_a cmd_b -v 1", &cmd_ret)); TEST_ASSERT_EQUAL(1, cmd_ret); } @@ -107,7 +108,7 @@ static void run_cmd_test(esp_command_set_handle_t handle, const char **cmd_list, for (size_t i = 0; i < nb_cmds; i++) { int cmd_ret = -1; esp_err_t expected = expected_ret_val[i] == 0 ? ESP_OK : ESP_ERR_NOT_FOUND; - TEST_ASSERT_EQUAL(expected, esp_commands_execute(handle, cmd_list[i], &cmd_ret)); + TEST_ASSERT_EQUAL(expected, esp_commands_execute(handle, -1, cmd_list[i], &cmd_ret)); TEST_ASSERT_EQUAL(expected_ret_val[i], cmd_ret); } } @@ -141,9 +142,9 @@ TEST_CASE("test static command set", "[esp_commands]") /* test help command with set of static commands */ int cmd_ret; - TEST_ASSERT_EQUAL(ESP_OK, esp_commands_execute(handle_set_a, "help", &cmd_ret)); + TEST_ASSERT_EQUAL(ESP_OK, esp_commands_execute(handle_set_a, -1, "help", &cmd_ret)); TEST_ASSERT_EQUAL(0, cmd_ret); - TEST_ASSERT_EQUAL(ESP_OK, esp_commands_execute(handle_set_b, "help", &cmd_ret)); + TEST_ASSERT_EQUAL(ESP_OK, esp_commands_execute(handle_set_b, -1, "help", &cmd_ret)); TEST_ASSERT_EQUAL(0, cmd_ret); /* destroy sets */ @@ -178,8 +179,10 @@ TEST_CASE("test static command set", "[esp_commands]") esp_commands_destroy_cmd_set(&handle_set_c); } -static int dummy_cmd_func(void *context, int argc, char **argv) +static int dummy_cmd_func(void *context, const int fd_out, int argc, char **argv) { + (void)fd_out; + (void)context; printf("dynamic command called\n"); return 0; // always return success } @@ -226,9 +229,9 @@ TEST_CASE("test dynamic command set", "[esp_commands]") /* test help command with set of dynamic commands */ int cmd_ret; - TEST_ASSERT_EQUAL(ESP_OK, esp_commands_execute(handle_set_1, "help", &cmd_ret)); + TEST_ASSERT_EQUAL(ESP_OK, esp_commands_execute(handle_set_1, -1, "help", &cmd_ret)); TEST_ASSERT_EQUAL(0, cmd_ret); - TEST_ASSERT_EQUAL(ESP_OK, esp_commands_execute(handle_set_2, "help", &cmd_ret)); + TEST_ASSERT_EQUAL(ESP_OK, esp_commands_execute(handle_set_2, -1, "help", &cmd_ret)); TEST_ASSERT_EQUAL(0, cmd_ret); @@ -288,7 +291,7 @@ TEST_CASE("test static and dynamic command sets", "[esp_commands]") /* test help command with set of dynamic commands */ int cmd_ret; - TEST_ASSERT_EQUAL(ESP_OK, esp_commands_execute(handle_combined_set, "help", &cmd_ret)); + TEST_ASSERT_EQUAL(ESP_OK, esp_commands_execute(handle_combined_set, -1, "help", &cmd_ret)); TEST_ASSERT_EQUAL(0, cmd_ret); // --- cleanup --- @@ -301,7 +304,7 @@ TEST_CASE("test static and dynamic command sets", "[esp_commands]") static size_t completion_nb_of_calls = 0; -static void test_completion_cb(const char *completed_cmd_name) +static void test_completion_cb(void *cb_ctx, const char *completed_cmd_name) { completion_nb_of_calls++; } @@ -333,28 +336,28 @@ TEST_CASE("test completion callback", "[esp_commands]") esp_command_set_handle_t handle_concat_set = esp_commands_concat_cmd_set(handle_set_a, handle_set_b); TEST_ASSERT_NOT_NULL(handle_concat_set); - esp_commands_get_completion(NULL, "a", test_completion_cb); + esp_commands_get_completion(NULL, "a", NULL, test_completion_cb); TEST_ASSERT_EQUAL(0, completion_nb_of_calls); - esp_commands_get_completion(handle_concat_set, "cmd_", test_completion_cb); + esp_commands_get_completion(handle_concat_set, "cmd_", NULL, test_completion_cb); TEST_ASSERT_EQUAL(4, completion_nb_of_calls); /* reset the cb counter */ completion_nb_of_calls = 0; - esp_commands_get_completion(NULL, "cmd_", test_completion_cb); + esp_commands_get_completion(NULL, "cmd_", NULL, test_completion_cb); TEST_ASSERT_EQUAL(8, completion_nb_of_calls); /* reset the cb counter */ completion_nb_of_calls = 0; - esp_commands_get_completion(NULL, "dyn", test_completion_cb); + esp_commands_get_completion(NULL, "dyn", NULL, test_completion_cb); TEST_ASSERT_EQUAL(1, completion_nb_of_calls); /* reset the cb counter */ completion_nb_of_calls = 0; - esp_commands_get_completion(handle_concat_set, "dyn", test_completion_cb); + esp_commands_get_completion(handle_concat_set, "dyn", NULL, test_completion_cb); TEST_ASSERT_EQUAL(1, completion_nb_of_calls); /* reset the cb counter */ From ca09438f35ce8b58393933024691678adaa12576 Mon Sep 17 00:00:00 2001 From: Guillaume Souchere Date: Tue, 7 Oct 2025 11:15:17 +0200 Subject: [PATCH 4/9] feat(esp_commands): Add support for linux target --- esp_commands/.build-test-rules.yml | 8 ++---- esp_commands/CMakeLists.txt | 16 +++++++---- esp_commands/README.md | 6 ++-- esp_commands/esp_commands.c | 20 +++++++++++-- esp_commands/esp_dynamic_commands.c | 3 +- esp_commands/idf_component.yml | 2 +- esp_commands/include/esp_commands.h | 18 ++++-------- esp_commands/linux/esp_commands.ld | 10 +++++++ .../private_include/esp_commands_internal.h | 28 +++++++++++++++++++ .../test_apps/main/test_esp_commands.c | 10 ++++++- esp_commands/test_apps/pytest_esp_commands.py | 10 +++++-- 11 files changed, 99 insertions(+), 32 deletions(-) create mode 100644 esp_commands/linux/esp_commands.ld create mode 100644 esp_commands/private_include/esp_commands_internal.h diff --git a/esp_commands/.build-test-rules.yml b/esp_commands/.build-test-rules.yml index 89638d6281..006c519513 100644 --- a/esp_commands/.build-test-rules.yml +++ b/esp_commands/.build-test-rules.yml @@ -1,6 +1,4 @@ esp_commands/test_apps: - disable: - - if: IDF_VERSION_MAJOR < 6 - reason: "esp_commands is created based on commands.c in esp-idf version < 6.0" - - if: IDF_TARGET not in ["esp32", "esp32c3"] - reason: "Sufficient to test on one Xtensa and one RISC-V target" \ No newline at end of file + enable: + - if: (IDF_TARGET == "linux") and (IDF_VERSION_MAJOR == 5 and IDF_VERSION_MINOR >= 3) + reason: "Sufficient to test on Linux target" \ No newline at end of file diff --git a/esp_commands/CMakeLists.txt b/esp_commands/CMakeLists.txt index 202fa7b933..e8375badcd 100644 --- a/esp_commands/CMakeLists.txt +++ b/esp_commands/CMakeLists.txt @@ -1,11 +1,17 @@ -idf_build_get_property(target IDF_TARGET) +idf_build_get_property(idf_target IDF_TARGET) set(srcs "esp_commands.c" "esp_dynamic_commands.c" "esp_commands_helpers.c") idf_component_register( - SRCS ${srcs} - INCLUDE_DIRS include - PRIV_INCLUDE_DIRS private_include - LDFRAGMENTS linker.lf) + SRCS ${srcs} + INCLUDE_DIRS include + PRIV_INCLUDE_DIRS private_include + LDFRAGMENTS linker.lf +) + +if(${idf_target} STREQUAL "linux") + # Add custom ld file + target_link_options(${COMPONENT_TARGET} INTERFACE "-Wl,-T,${CMAKE_CURRENT_SOURCE_DIR}/linux/esp_commands.ld") +endif() diff --git a/esp_commands/README.md b/esp_commands/README.md index 422b6b4b5f..9542167053 100644 --- a/esp_commands/README.md +++ b/esp_commands/README.md @@ -29,6 +29,7 @@ esp_commands_config_t config = ESP_COMMANDS_CONFIG_DEFAULT(); esp_commands_update_config(&config); ``` +- `write_func`: The custom write function used by esp_commands to output data (default to posix write is not specified) - `max_cmdline_length`: Maximum command line buffer length (bytes). - `max_cmdline_args`: Maximum number of arguments parsed. - `hint_color`: ANSI color code used for hints. @@ -93,10 +94,11 @@ Commands can be executed from a command line string: ```c int cmd_ret; -esp_err_t ret = esp_commands_execute(NULL, "my_cmd arg1 arg2", &cmd_ret); +esp_err_t ret = esp_commands_execute(NULL, STDOUT_FILENO, "my_cmd arg1 arg2", &cmd_ret); ``` - `cmd_set`: Limits execution to a set of commands (or `NULL` for all commands). +- `cmd_fd`: the file descriptor on which the output of the command is directed - `cmd_line`: String containing the command and arguments. - `cmd_ret`: Receives the command function return value. @@ -114,7 +116,7 @@ const char *glossary = esp_commands_get_glossary(NULL, "echo"); - **Completion**: Suggests matching commands. - **Hint**: Provides a short usage hint. -- **Glossary**: Provides detailed command description. +- **Glossary**: Provides detailed command argument description. --- diff --git a/esp_commands/esp_commands.c b/esp_commands/esp_commands.c index 099a3eebbc..086346c8a6 100644 --- a/esp_commands/esp_commands.c +++ b/esp_commands/esp_commands.c @@ -6,7 +6,9 @@ #include #include #include +#include "esp_heap_caps.h" #include "esp_commands.h" +#include "esp_commands_internal.h" #include "esp_dynamic_commands.h" #include "esp_err.h" @@ -26,6 +28,7 @@ typedef struct esp_command_sets { /** run-time configuration options */ static esp_commands_config_t s_config = { .write_func = write, + .heap_caps_used = MALLOC_CAP_DEFAULT, .hint_bold = false, .hint_color = ANSI_COLOR_DEFAULT, .max_cmdline_args = 32, @@ -141,6 +144,11 @@ bool compare_command_name(void *ctx, esp_command_t *cmd) return true; } +void *esp_commands_malloc(const size_t malloc_size) +{ + return heap_caps_malloc(malloc_size, s_config.heap_caps_used); +} + esp_err_t esp_commands_update_config(const esp_commands_config_t *config) { if (!config || @@ -157,6 +165,12 @@ esp_err_t esp_commands_update_config(const esp_commands_config_t *config) s_config.write_func = write; } + /* if the heap_caps_used field is set to 0, set + * it to MALLOC_CAP_DEFAULT */ + if (s_config.heap_caps_used == 0) { + s_config.heap_caps_used = MALLOC_CAP_DEFAULT; + } + return ESP_OK; } @@ -315,7 +329,7 @@ esp_err_t update_cmd_set_with_temp_info(esp_command_set_t *cmd_set, size_t cmd_c cmd_set->cmd_set_size = 0; } else { const size_t alloc_cmd_ptrs_size = sizeof(esp_command_t *) * cmd_count; - cmd_set->cmd_ptr_set = malloc(alloc_cmd_ptrs_size); + cmd_set->cmd_ptr_set = heap_caps_malloc(alloc_cmd_ptrs_size, s_config.heap_caps_used); if (!cmd_set->cmd_ptr_set) { return ESP_ERR_NO_MEM; } else { @@ -333,7 +347,7 @@ esp_command_set_handle_t esp_commands_create_cmd_set(const char **cmd_set, const return NULL; } - esp_command_sets_t *cmd_ptr_sets = malloc(sizeof(esp_command_sets_t)); + esp_command_sets_t *cmd_ptr_sets = heap_caps_malloc(sizeof(esp_command_sets_t), s_config.heap_caps_used); if (!cmd_ptr_sets) { return NULL; } @@ -391,7 +405,7 @@ esp_command_set_handle_t esp_commands_concat_cmd_set(esp_command_set_handle_t cm /* Reaching this point, both cmd_set_a and cmd_set_b are set. * Create a new cmd_set that can host the items from both sets, * assign the items to the new set and free the input sets */ - esp_command_sets_t *concat_cmd_sets = malloc(sizeof(esp_command_sets_t)); + esp_command_sets_t *concat_cmd_sets = heap_caps_malloc(sizeof(esp_command_sets_t), s_config.heap_caps_used); if (!concat_cmd_sets) { return NULL; } diff --git a/esp_commands/esp_dynamic_commands.c b/esp_commands/esp_dynamic_commands.c index 10f50d7258..55808e60b2 100644 --- a/esp_commands/esp_dynamic_commands.c +++ b/esp_commands/esp_dynamic_commands.c @@ -8,6 +8,7 @@ #include #include #include +#include "esp_commands_internal.h" #include "esp_dynamic_commands.h" #include "esp_commands.h" @@ -50,7 +51,7 @@ esp_err_t esp_dynamic_commands_add(esp_command_t *cmd) return ESP_ERR_INVALID_ARG; } - esp_command_internal_t *list_item = malloc(sizeof(esp_command_internal_t)); + esp_command_internal_t *list_item = esp_commands_malloc(sizeof(esp_command_internal_t)); if (!list_item) { return ESP_ERR_NO_MEM; } diff --git a/esp_commands/idf_component.yml b/esp_commands/idf_component.yml index c58bc23f5d..108257eda9 100644 --- a/esp_commands/idf_component.yml +++ b/esp_commands/idf_component.yml @@ -2,7 +2,7 @@ version: "1.0.0" description: "esp_commands - Command handling component" url: https://github.com/espressif/idf-extra-components/tree/master/esp_commands dependencies: - idf: ">=6.0" + idf: ">=5.3" sbom: manifests: - path: sbom_esp_commands.yml diff --git a/esp_commands/include/esp_commands.h b/esp_commands/include/esp_commands.h index 3329bf1688..c48e39b3a4 100644 --- a/esp_commands/include/esp_commands.h +++ b/esp_commands/include/esp_commands.h @@ -10,6 +10,7 @@ extern "C" { #endif #include +#include "esp_heap_caps.h" #include "esp_err.h" /** @@ -133,23 +134,13 @@ typedef ssize_t (*esp_commands_write_t)(int fd, const void *buf, size_t count); */ typedef struct esp_commands_config { esp_commands_write_t write_func; /*!< Write function to call when executing a command */ + uint32_t heap_caps_used; /*!< Set of heap capabilities to be used to perform internal allocations */ size_t max_cmdline_length; /*!< Maximum length of the command line buffer, in bytes */ size_t max_cmdline_args; /*!< Maximum number of command line arguments to parse */ int hint_color; /*!< ANSI color code used for hint text */ bool hint_bold; /*!< If true, display hint text in bold */ } esp_commands_config_t; -/** - * @brief Default configuration for esp_commands_manager - */ -#define ESP_COMMANDS_CONFIG_DEFAULT() \ -{ \ - .max_cmdline_length = 256, \ - .max_cmdline_args = 32, \ - .hint_color = 39, \ - .hint_bold = false \ -} - /** * @brief Callback for a completed command name * @@ -188,7 +179,10 @@ esp_err_t esp_commands_update_config(const esp_commands_config_t *config); */ #define ESP_COMMAND_REGISTER(cmd_name, cmd_group, cmd_help, cmd_func, cmd_func_ctx, cmd_hint_cb, cmd_glossary_cb) \ static_assert((cmd_func) != NULL); \ - static const esp_command_t cmd_name __attribute__((used, section(".esp_commands"))) = { \ + /* Alignment attribute is required when building on linux target to prevent each input section */ \ + /* from inheriting its alignment from the object's file default one thus preventing gaps between */ \ + /* commands in the section. */ \ + static const esp_command_t cmd_name __attribute__((used, section(".esp_commands"), aligned(4))) = { \ .name = #cmd_name, \ .group = #cmd_group, \ .help = cmd_help, \ diff --git a/esp_commands/linux/esp_commands.ld b/esp_commands/linux/esp_commands.ld new file mode 100644 index 0000000000..d61d347254 --- /dev/null +++ b/esp_commands/linux/esp_commands.ld @@ -0,0 +1,10 @@ +SECTIONS +{ + .esp_commands : + { + PROVIDE(_esp_commands_start = .); + KEEP(*(SORT(.esp_commands*))) /* Concatenate all .esp_commands */ + PROVIDE(_esp_commands_end = .); + } +} +INSERT AFTER .rodata; diff --git a/esp_commands/private_include/esp_commands_internal.h b/esp_commands/private_include/esp_commands_internal.h new file mode 100644 index 0000000000..641a065a64 --- /dev/null +++ b/esp_commands/private_include/esp_commands_internal.h @@ -0,0 +1,28 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include "esp_heap_caps.h" + +/** + * @brief Component specific implementation of malloc + * + * @note This function uses heap_caps_malloc together + * with the set of capabilities provided by the user in the + * config structure. Implemented in esp_commands.c + * + * @param malloc_size + * @return void* + */ +void *esp_commands_malloc(const size_t malloc_size); + +#ifdef __cplusplus +} +#endif diff --git a/esp_commands/test_apps/main/test_esp_commands.c b/esp_commands/test_apps/main/test_esp_commands.c index f3e402683c..c0895eacc4 100644 --- a/esp_commands/test_apps/main/test_esp_commands.c +++ b/esp_commands/test_apps/main/test_esp_commands.c @@ -9,6 +9,7 @@ #include #include #include "unity.h" +#include "esp_heap_caps.h" #include "esp_commands.h" #include "test_esp_commands_utils.h" @@ -24,7 +25,14 @@ static void test_setup(void) { - const esp_commands_config_t config = ESP_COMMANDS_CONFIG_DEFAULT(); + const esp_commands_config_t config = { + .write_func = write, + .heap_caps_used = MALLOC_CAP_DEFAULT, + .hint_bold = false, + .hint_color = 39, + .max_cmdline_args = 32, + .max_cmdline_length = 256 + }; TEST_ASSERT_EQUAL(ESP_OK, esp_commands_update_config(&config)); } diff --git a/esp_commands/test_apps/pytest_esp_commands.py b/esp_commands/test_apps/pytest_esp_commands.py index 862e20b907..6308398a9d 100644 --- a/esp_commands/test_apps/pytest_esp_commands.py +++ b/esp_commands/test_apps/pytest_esp_commands.py @@ -1,9 +1,15 @@ import pytest from pytest_embedded import Dut from pytest_embedded_idf.utils import idf_parametrize +import glob +from pathlib import Path -@pytest.mark.generic -@pytest.mark.skip_if_soc("IDF_VERSION_MAJOR < 6") +@pytest.mark.host_test +@pytest.mark.skipif( + not bool(glob.glob(f'{Path(__file__).parent.absolute()}/build*/')), + reason="Skip the idf version that did not build" +) +@idf_parametrize('target', ['linux'], indirect=['target']) def test_esp_commands(dut) -> None: dut.run_all_single_board_cases() From 6382001bcfb407290f8c43244b9f9c09d16cb69c Mon Sep 17 00:00:00 2001 From: Guillaume Souchere Date: Tue, 28 Oct 2025 06:44:07 +0100 Subject: [PATCH 5/9] feat(esp_commands): Sort commands in esp_commands section --- esp_commands/include/esp_commands.h | 5 ++++- esp_commands/linker.lf | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/esp_commands/include/esp_commands.h b/esp_commands/include/esp_commands.h index c48e39b3a4..d796d8aee5 100644 --- a/esp_commands/include/esp_commands.h +++ b/esp_commands/include/esp_commands.h @@ -13,6 +13,9 @@ extern "C" { #include "esp_heap_caps.h" #include "esp_err.h" +#define ESP_COMMAND_STRINGIFY(name) #name +#define ESP_COMMAND_ + /** * @brief Console command main function type with user context * @@ -182,7 +185,7 @@ esp_err_t esp_commands_update_config(const esp_commands_config_t *config); /* Alignment attribute is required when building on linux target to prevent each input section */ \ /* from inheriting its alignment from the object's file default one thus preventing gaps between */ \ /* commands in the section. */ \ - static const esp_command_t cmd_name __attribute__((used, section(".esp_commands"), aligned(4))) = { \ + static const esp_command_t cmd_name __attribute__((used, section(".esp_commands" "." ESP_COMMAND_STRINGIFY(cmd_name)), aligned(4))) = { \ .name = #cmd_name, \ .group = #cmd_group, \ .help = cmd_help, \ diff --git a/esp_commands/linker.lf b/esp_commands/linker.lf index c33eef3399..7c238164ef 100644 --- a/esp_commands/linker.lf +++ b/esp_commands/linker.lf @@ -1,6 +1,6 @@ [sections:esp_commands] entries: - .esp_commands + .esp_command+ [scheme:esp_commands_default] entries: From 753705b6da1b504a9caf921b83332e2be4943d4e Mon Sep 17 00:00:00 2001 From: Guillaume Souchere Date: Tue, 28 Oct 2025 11:03:11 +0100 Subject: [PATCH 6/9] feat(esp_commands): Update esp_commands_execute to take command related arguments These arguments include: - fd to use to output data - write function to use to output data - opaque pointer used to pass dynamic context to the command fnuction --- esp_commands/esp_commands.c | 71 +++++++++---------- esp_commands/include/esp_commands.h | 42 ++++++----- .../main/include/test_esp_commands_utils.h | 2 +- .../test_apps/main/test_esp_commands.c | 31 ++++---- 4 files changed, 74 insertions(+), 72 deletions(-) diff --git a/esp_commands/esp_commands.c b/esp_commands/esp_commands.c index 086346c8a6..9bc5292efc 100644 --- a/esp_commands/esp_commands.c +++ b/esp_commands/esp_commands.c @@ -27,7 +27,6 @@ typedef struct esp_command_sets { /** run-time configuration options */ static esp_commands_config_t s_config = { - .write_func = write, .heap_caps_used = MALLOC_CAP_DEFAULT, .hint_bold = false, .hint_color = ANSI_COLOR_DEFAULT, @@ -159,12 +158,6 @@ esp_err_t esp_commands_update_config(const esp_commands_config_t *config) memcpy(&s_config, config, sizeof(s_config)); - /* if no write function was passed in parameter, - * default it to the posix write */ - if (s_config.write_func == NULL) { - s_config.write_func = write; - } - /* if the heap_caps_used field is set to 0, set * it to MALLOC_CAP_DEFAULT */ if (s_config.heap_caps_used == 0) { @@ -221,7 +214,7 @@ esp_err_t esp_commands_unregister_cmd(const char *cmd_name) } } -esp_err_t esp_commands_execute(esp_command_set_handle_t cmd_set, const int cmd_fd, const char *cmdline, int *cmd_ret) +esp_err_t esp_commands_execute(esp_command_set_handle_t cmd_set, esp_commands_exec_arg_t *cmd_args, const char *cmdline, int *cmd_ret) { char **argv = (char **) calloc(s_config.max_cmdline_args, sizeof(char *)); if (argv == NULL) { @@ -259,14 +252,14 @@ esp_err_t esp_commands_execute(esp_command_set_handle_t cmd_set, const int cmd_f return ESP_ERR_NOT_FOUND; } - const int fd_out = cmd_fd == -1 ? STDOUT_FILENO : cmd_fd; if (cmd->func) { + esp_commands_exec_arg_t help_args; if (is_cmd_help) { - // executing help command, pass the cmd_set as context - *cmd_ret = (*cmd->func)(cmd_set, fd_out, argc, argv); - } else { - *cmd_ret = (*cmd->func)(cmd->func_ctx, fd_out, argc, argv); + help_args.out_fd = (cmd_args && cmd_args->out_fd) ? cmd_args->out_fd : STDOUT_FILENO; + help_args.write_func = (cmd_args && cmd_args->write_func) ? cmd_args->write_func : write; + help_args.dynamic_ctx = cmd_set; } + *cmd_ret = (*cmd->func)(cmd->func_ctx, &help_args, argc, argv); } free(argv); free(tmp_line_buf); @@ -528,7 +521,7 @@ const char *esp_commands_get_glossary(esp_command_set_handle_t cmd_set, const ch /* help command related code */ /* -------------------------------------------------------------- */ -#define FDPRINTF(fd, fmt, ...) do { \ +#define FDPRINTF(fd, write, fmt, ...) do { \ char _buf[s_config.max_cmdline_length]; \ int _len = snprintf(_buf, sizeof(_buf), fmt, ##__VA_ARGS__); \ if (_len > 0) { \ @@ -538,12 +531,12 @@ const char *esp_commands_get_glossary(esp_command_set_handle_t cmd_set, const ch } \ } while (0) -static void print_arg_help(const int fd_out, esp_command_t *it) +static void print_arg_help(esp_commands_exec_arg_t *cmd_args, esp_command_t *it) { /* First line: command name and hint * Pad all the hints to the same column */ - FDPRINTF(fd_out, "%-s", it->name); + FDPRINTF(cmd_args->out_fd, cmd_args->write_func, "%-s", it->name); const char *hint = NULL; if (it->hint_cb) { @@ -551,18 +544,18 @@ static void print_arg_help(const int fd_out, esp_command_t *it) } if (hint) { - FDPRINTF(fd_out, "%s\n", it->hint_cb(it->func_ctx)); + FDPRINTF(cmd_args->out_fd, cmd_args->write_func, "%s\n", it->hint_cb(it->func_ctx)); } else { - FDPRINTF(fd_out, "\n"); + FDPRINTF(cmd_args->out_fd, cmd_args->write_func, "\n"); } /* Second line: print help */ /* TODO: replace the simple print with a function that * replaces arg_print_formatted */ if (it->help) { - FDPRINTF(fd_out, " %s\n", it->help); + FDPRINTF(cmd_args->out_fd, cmd_args->write_func, " %s\n", it->help); } else { - FDPRINTF(fd_out, " -\n"); + FDPRINTF(cmd_args->out_fd, cmd_args->write_func, " -\n"); } /* Third line: print the glossary*/ @@ -572,25 +565,25 @@ static void print_arg_help(const int fd_out, esp_command_t *it) } if (glossary) { - FDPRINTF(fd_out, " %s\n", it->glossary_cb(it->func_ctx)); + FDPRINTF(cmd_args->out_fd, cmd_args->write_func, " %s\n", it->glossary_cb(it->func_ctx)); } else { - FDPRINTF(fd_out, " -\n"); + FDPRINTF(cmd_args->out_fd, cmd_args->write_func, " -\n"); } - FDPRINTF(fd_out, "\n"); + FDPRINTF(cmd_args->out_fd, cmd_args->write_func, "\n"); } -static void print_arg_command(const int fd_out, esp_command_t *it) +static void print_arg_command(esp_commands_exec_arg_t *cmd_args, esp_command_t *it) { - FDPRINTF(fd_out, "%-s", it->name); + FDPRINTF(cmd_args->out_fd, cmd_args->write_func, "%-s", it->name); if (it->hint_cb) { const char *hint = it->hint_cb(it->func_ctx); if (hint) { - FDPRINTF(fd_out, " %s", it->hint_cb(it->func_ctx)); + FDPRINTF(cmd_args->out_fd, cmd_args->write_func, " %s", it->hint_cb(it->func_ctx)); } } - FDPRINTF(fd_out, "\n"); + FDPRINTF(cmd_args->out_fd, cmd_args->write_func, "\n"); } typedef enum { @@ -599,7 +592,7 @@ typedef enum { HELP_VERBOSE_LEVEL_MAX_NUM = 2 } help_verbose_level_e; -typedef void (*const fn_print_arg_t)(const int fd_out, esp_command_t *); +typedef void (*const fn_print_arg_t)(esp_commands_exec_arg_t *cmd_args, esp_command_t *); static fn_print_arg_t print_verbose_level_arr[HELP_VERBOSE_LEVEL_MAX_NUM] = { print_arg_command, @@ -607,7 +600,7 @@ static fn_print_arg_t print_verbose_level_arr[HELP_VERBOSE_LEVEL_MAX_NUM] = { }; typedef struct call_cmd_ctx { - const int fd_out; + esp_commands_exec_arg_t *cmd_args; help_verbose_level_e verbose_level; const char *command_name; bool command_found; @@ -620,11 +613,11 @@ bool call_command_funcs(void *caller_ctx, esp_command_t *cmd) if (!ctx->command_name) { /* ctx->command_name is empty, print all commands */ - print_verbose_level_arr[ctx->verbose_level](ctx->fd_out, cmd); + print_verbose_level_arr[ctx->verbose_level](ctx->cmd_args, cmd); } else if (ctx->command_name && (strcmp(ctx->command_name, cmd->name) == 0)) { /* we found the command name, print the help and return */ - print_verbose_level_arr[ctx->verbose_level](ctx->fd_out, cmd); + print_verbose_level_arr[ctx->verbose_level](ctx->cmd_args, cmd); ctx->command_found = true; return false; } @@ -632,8 +625,10 @@ bool call_command_funcs(void *caller_ctx, esp_command_t *cmd) return true; } -static int help_command(void *context, const int fd_out, int argc, char **argv) +static int help_command(void *context, esp_commands_exec_arg_t *cmd_args, int argc, char **argv) { + (void)context; /* this is NULL and useless for the help command */ + char *command_name = NULL; help_verbose_level_e verbose_level = HELP_VERBOSE_LEVEL_1; @@ -641,11 +636,11 @@ static int help_command(void *context, const int fd_out, int argc, char **argv) * help cmd_name -v 0 */ if (argc <= 0 || argc > 4) { /* unknown issue, return error */ - FDPRINTF(fd_out, "help: invalid number of arguments %d\n", argc); + FDPRINTF(cmd_args->out_fd, cmd_args->write_func, "help: invalid number of arguments %d\n", argc); return 1; } - esp_command_sets_t *cmd_sets = (esp_command_sets_t *)context; + esp_command_sets_t *cmd_sets = (esp_command_sets_t *)cmd_args->dynamic_ctx; if (argc > 1) { /* more than 1 arg, figure out if only verbose level argument @@ -657,7 +652,7 @@ static int help_command(void *context, const int fd_out, int argc, char **argv) /* check if the following argument is either 0, or 1 */ if (i + 1 >= argc) { /* format error, return with error */ - FDPRINTF(fd_out, "help: arguments not provided in the right format\n"); + FDPRINTF(cmd_args->out_fd, cmd_args->write_func, "help: arguments not provided in the right format\n"); return 1; } else if (strcmp(argv[i + 1], "0") == 0) { verbose_level = 0; @@ -665,7 +660,7 @@ static int help_command(void *context, const int fd_out, int argc, char **argv) verbose_level = 1; } else { /* wrong command format, return error */ - FDPRINTF(fd_out, "help: invalid verbose level %s\n", argv[i + 1]); + FDPRINTF(cmd_args->out_fd, cmd_args->write_func, "help: invalid verbose level %s\n", argv[i + 1]); return 1; } @@ -685,7 +680,7 @@ static int help_command(void *context, const int fd_out, int argc, char **argv) * is not NULL, find the command and only print the help for this command. if the * command is not found, return with error */ call_cmd_ctx_t ctx = { - .fd_out = fd_out, + .cmd_args = cmd_args, .verbose_level = verbose_level, .command_name = command_name, .command_found = false @@ -693,7 +688,7 @@ static int help_command(void *context, const int fd_out, int argc, char **argv) go_through_commands(cmd_sets, &ctx, call_command_funcs); if (command_name && !ctx.command_found) { - FDPRINTF(fd_out, "help: invalid command name %s\n", command_name); + FDPRINTF(cmd_args->out_fd, cmd_args->write_func, "help: invalid command name %s\n", command_name); return 1; } diff --git a/esp_commands/include/esp_commands.h b/esp_commands/include/esp_commands.h index d796d8aee5..d4511af1c5 100644 --- a/esp_commands/include/esp_commands.h +++ b/esp_commands/include/esp_commands.h @@ -14,7 +14,26 @@ extern "C" { #include "esp_err.h" #define ESP_COMMAND_STRINGIFY(name) #name -#define ESP_COMMAND_ + +/** + * @brief Function pointer type for writing bytes. + * + * @param fd File descriptor. + * @param buf Buffer containing bytes to write. + * @param count Number of bytes to write. + * @return Number of bytes written, or -1 on error. + */ +typedef ssize_t (*esp_commands_write_t)(int fd, const void *buf, size_t count); + +/** + * @brief Structure containing dynamic argument necessary for the\ + * command callback to execute properly + */ +typedef struct esp_commands_exec_arg { + int out_fd; /*!< file descriptor that the command function has to use to output data */ + esp_commands_write_t write_func; /*!< write function the command function has to use to output datga */ + void *dynamic_ctx; /*!< dynamic context passed to the command function */ +} esp_commands_exec_arg_t; /** * @brief Console command main function type with user context @@ -22,12 +41,12 @@ extern "C" { * This function type is used to implement a console command. * * @param context User-defined context passed at invocation - * @param fd_out The file descriptor to use to output data + * @param cmd_arg Structure containing dynamic arguments necessary for the command * @param argc Number of arguments * @param argv Array of argc entries, each pointing to a null-terminated string argument * @return Return code of the console command; 0 indicates success */ -typedef int (*esp_command_func_t)(void *context, const int fd_out, int argc, char **argv); +typedef int (*esp_command_func_t)(void *context, esp_commands_exec_arg_t *cmd_arg, int argc, char **argv); /** * @brief Callback to generate a command hint @@ -54,7 +73,6 @@ typedef const char *(*esp_command_glossary_t)(void *context); /** * @brief Structure describing a console command * - * @note Only one of `func` or `func_ctx` should be set. * @note The `group` field allows categorizing commands into groups, * which can simplify filtering or listing commands. */ @@ -122,21 +140,10 @@ DEFINE_FIELD_ACCESSOR(help) */ #define FIELD_ACCESSOR(NAME) get_##NAME -/** - * @brief Function pointer type for writing bytes. - * - * @param fd File descriptor. - * @param buf Buffer containing bytes to write. - * @param count Number of bytes to write. - * @return Number of bytes written, or -1 on error. - */ -typedef ssize_t (*esp_commands_write_t)(int fd, const void *buf, size_t count); - /** * @brief Configuration parameters for esp_commands_manager initialization */ typedef struct esp_commands_config { - esp_commands_write_t write_func; /*!< Write function to call when executing a command */ uint32_t heap_caps_used; /*!< Set of heap capabilities to be used to perform internal allocations */ size_t max_cmdline_length; /*!< Maximum length of the command line buffer, in bytes */ size_t max_cmdline_args; /*!< Maximum number of command line arguments to parse */ @@ -217,7 +224,8 @@ esp_err_t esp_commands_unregister_cmd(const char *cmd_name); * @brief Execute a command line * * @param cmd_set Set of commands allowed to execute. If NULL, all registered commands are allowed - * @param cmd_fd File descriptor used to output data + * @param cmd_arg Structure containing dynamic arguments necessary for the command + * callback to execute properly * @param cmd_line Command line string to execute * @param cmd_ret Return value from the command function. If -1, standard output will be used. * @return ESP_OK on success @@ -225,7 +233,7 @@ esp_err_t esp_commands_unregister_cmd(const char *cmd_name); * ESP_ERR_NOT_FOUND if command is not found in cmd_set * ESP_ERR_NO_MEM if internal memory allocation fails */ -esp_err_t esp_commands_execute(esp_command_set_handle_t cmd_set, const int cmd_fd, const char *cmdline, int *cmd_ret); +esp_err_t esp_commands_execute(esp_command_set_handle_t cmd_set, esp_commands_exec_arg_t *cmd_args, const char *cmdline, int *cmd_ret); /** * @brief Find a command by name within a specific command set. diff --git a/esp_commands/test_apps/main/include/test_esp_commands_utils.h b/esp_commands/test_apps/main/include/test_esp_commands_utils.h index a2426eb5b6..624459d0de 100644 --- a/esp_commands/test_apps/main/include/test_esp_commands_utils.h +++ b/esp_commands/test_apps/main/include/test_esp_commands_utils.h @@ -16,7 +16,7 @@ extern "C" { #define GET_STR(STR) #STR #define CREATE_CMD_FUNC(NAME) \ - static int GET_NAME(NAME, _func)(void *ctx, const int out_fd, int argc, char **argv) { \ + static int GET_NAME(NAME, _func)(void *ctx, esp_commands_exec_arg_t *cmd_args, int argc, char **argv) { \ printf(GET_STR(NAME) GET_STR(_func)); \ printf("\n"); \ return 0; \ diff --git a/esp_commands/test_apps/main/test_esp_commands.c b/esp_commands/test_apps/main/test_esp_commands.c index c0895eacc4..4025ea8d9a 100644 --- a/esp_commands/test_apps/main/test_esp_commands.c +++ b/esp_commands/test_apps/main/test_esp_commands.c @@ -26,7 +26,6 @@ static void test_setup(void) { const esp_commands_config_t config = { - .write_func = write, .heap_caps_used = MALLOC_CAP_DEFAULT, .hint_bold = false, .hint_color = 39, @@ -42,35 +41,35 @@ TEST_CASE("help command - called without command set", "[esp_commands]") /* call esp_commands_execute to run help command with verbosity 0 */ int cmd_ret = -1; - TEST_ASSERT_EQUAL(ESP_OK, esp_commands_execute(NULL, -1, "help -v 0", &cmd_ret)); + TEST_ASSERT_EQUAL(ESP_OK, esp_commands_execute(NULL, NULL, "help -v 0", &cmd_ret)); TEST_ASSERT_EQUAL(0, cmd_ret); /* call esp_commands_execute to run help command with verbosity 1 */ cmd_ret = -1; - TEST_ASSERT_EQUAL(ESP_OK, esp_commands_execute(NULL, STDOUT_FILENO, "help -v 1", &cmd_ret)); + TEST_ASSERT_EQUAL(ESP_OK, esp_commands_execute(NULL, NULL, "help -v 1", &cmd_ret)); TEST_ASSERT_EQUAL(0, cmd_ret); /* call esp_commands_execute to run help command on a registered command */ cmd_ret = -1; - TEST_ASSERT_EQUAL(ESP_OK, esp_commands_execute(NULL, -1, "help cmd_a -v 0", &cmd_ret)); + TEST_ASSERT_EQUAL(ESP_OK, esp_commands_execute(NULL, NULL, "help cmd_a -v 0", &cmd_ret)); TEST_ASSERT_EQUAL(0, cmd_ret); - TEST_ASSERT_EQUAL(ESP_OK, esp_commands_execute(NULL, STDOUT_FILENO, "help cmd_a -v 1", &cmd_ret)); + TEST_ASSERT_EQUAL(ESP_OK, esp_commands_execute(NULL, NULL, "help cmd_a -v 1", &cmd_ret)); TEST_ASSERT_EQUAL(0, cmd_ret); /* call esp_commands_execute to run help command on an unregistered command */ cmd_ret = -1; - TEST_ASSERT_EQUAL(ESP_OK, esp_commands_execute(NULL, -1, "help cmd_w", &cmd_ret)); + TEST_ASSERT_EQUAL(ESP_OK, esp_commands_execute(NULL, NULL, "help cmd_w", &cmd_ret)); TEST_ASSERT_EQUAL(1, cmd_ret); /* call esp_commands_execute to run help command on a registered command with wrong * verbosity syntax */ cmd_ret = -1; - TEST_ASSERT_EQUAL(ESP_OK, esp_commands_execute(NULL, -1, "help cmd_a -v=1", &cmd_ret)); + TEST_ASSERT_EQUAL(ESP_OK, esp_commands_execute(NULL, NULL, "help cmd_a -v=1", &cmd_ret)); TEST_ASSERT_EQUAL(1, cmd_ret); /* call esp_commands_execute to run help command with too many command names */ cmd_ret = -1; - TEST_ASSERT_EQUAL(ESP_OK, esp_commands_execute(NULL, -1, "help cmd_a cmd_b -v 1", &cmd_ret)); + TEST_ASSERT_EQUAL(ESP_OK, esp_commands_execute(NULL, NULL, "help cmd_a cmd_b -v 1", &cmd_ret)); TEST_ASSERT_EQUAL(1, cmd_ret); } @@ -116,7 +115,7 @@ static void run_cmd_test(esp_command_set_handle_t handle, const char **cmd_list, for (size_t i = 0; i < nb_cmds; i++) { int cmd_ret = -1; esp_err_t expected = expected_ret_val[i] == 0 ? ESP_OK : ESP_ERR_NOT_FOUND; - TEST_ASSERT_EQUAL(expected, esp_commands_execute(handle, -1, cmd_list[i], &cmd_ret)); + TEST_ASSERT_EQUAL(expected, esp_commands_execute(handle, NULL, cmd_list[i], &cmd_ret)); TEST_ASSERT_EQUAL(expected_ret_val[i], cmd_ret); } } @@ -150,9 +149,9 @@ TEST_CASE("test static command set", "[esp_commands]") /* test help command with set of static commands */ int cmd_ret; - TEST_ASSERT_EQUAL(ESP_OK, esp_commands_execute(handle_set_a, -1, "help", &cmd_ret)); + TEST_ASSERT_EQUAL(ESP_OK, esp_commands_execute(handle_set_a, NULL, "help", &cmd_ret)); TEST_ASSERT_EQUAL(0, cmd_ret); - TEST_ASSERT_EQUAL(ESP_OK, esp_commands_execute(handle_set_b, -1, "help", &cmd_ret)); + TEST_ASSERT_EQUAL(ESP_OK, esp_commands_execute(handle_set_b, NULL, "help", &cmd_ret)); TEST_ASSERT_EQUAL(0, cmd_ret); /* destroy sets */ @@ -187,9 +186,9 @@ TEST_CASE("test static command set", "[esp_commands]") esp_commands_destroy_cmd_set(&handle_set_c); } -static int dummy_cmd_func(void *context, const int fd_out, int argc, char **argv) +static int dummy_cmd_func(void *context, esp_commands_exec_arg_t *cmd_args, int argc, char **argv) { - (void)fd_out; + (void)cmd_args; (void)context; printf("dynamic command called\n"); return 0; // always return success @@ -237,9 +236,9 @@ TEST_CASE("test dynamic command set", "[esp_commands]") /* test help command with set of dynamic commands */ int cmd_ret; - TEST_ASSERT_EQUAL(ESP_OK, esp_commands_execute(handle_set_1, -1, "help", &cmd_ret)); + TEST_ASSERT_EQUAL(ESP_OK, esp_commands_execute(handle_set_1, NULL, "help", &cmd_ret)); TEST_ASSERT_EQUAL(0, cmd_ret); - TEST_ASSERT_EQUAL(ESP_OK, esp_commands_execute(handle_set_2, -1, "help", &cmd_ret)); + TEST_ASSERT_EQUAL(ESP_OK, esp_commands_execute(handle_set_2, NULL, "help", &cmd_ret)); TEST_ASSERT_EQUAL(0, cmd_ret); @@ -299,7 +298,7 @@ TEST_CASE("test static and dynamic command sets", "[esp_commands]") /* test help command with set of dynamic commands */ int cmd_ret; - TEST_ASSERT_EQUAL(ESP_OK, esp_commands_execute(handle_combined_set, -1, "help", &cmd_ret)); + TEST_ASSERT_EQUAL(ESP_OK, esp_commands_execute(handle_combined_set, NULL, "help", &cmd_ret)); TEST_ASSERT_EQUAL(0, cmd_ret); // --- cleanup --- From a8199e190529b9ddf0147efe8a93840369a769cc Mon Sep 17 00:00:00 2001 From: Guillaume Souchere Date: Thu, 30 Oct 2025 03:03:17 +0100 Subject: [PATCH 7/9] feat(esp_commands): Sort static commands for all targets --- esp_commands/.build-test-rules.yml | 4 +++- esp_commands/CMakeLists.txt | 8 ++++---- esp_commands/include/esp_commands.h | 19 +++++++++++-------- esp_commands/linker.lf | 2 +- esp_commands/{ => src}/esp_commands.c | 12 ++++++------ esp_commands/{ => src}/esp_commands_helpers.c | 0 esp_commands/{ => src}/esp_dynamic_commands.c | 0 7 files changed, 25 insertions(+), 20 deletions(-) rename esp_commands/{ => src}/esp_commands.c (98%) rename esp_commands/{ => src}/esp_commands_helpers.c (100%) rename esp_commands/{ => src}/esp_dynamic_commands.c (100%) diff --git a/esp_commands/.build-test-rules.yml b/esp_commands/.build-test-rules.yml index 006c519513..1215fb5732 100644 --- a/esp_commands/.build-test-rules.yml +++ b/esp_commands/.build-test-rules.yml @@ -1,4 +1,6 @@ esp_commands/test_apps: enable: - if: (IDF_TARGET == "linux") and (IDF_VERSION_MAJOR == 5 and IDF_VERSION_MINOR >= 3) - reason: "Sufficient to test on Linux target" \ No newline at end of file + reason: "Test the main logic of the component (not target dependent)" + - if: IDF_TARGET == "esp32" + reason: "Test the placement of statically registered commands in dedicated flash section" \ No newline at end of file diff --git a/esp_commands/CMakeLists.txt b/esp_commands/CMakeLists.txt index e8375badcd..9031c3ac75 100644 --- a/esp_commands/CMakeLists.txt +++ b/esp_commands/CMakeLists.txt @@ -1,15 +1,15 @@ idf_build_get_property(idf_target IDF_TARGET) -set(srcs "esp_commands.c" - "esp_dynamic_commands.c" - "esp_commands_helpers.c") +set(srcs "src/esp_commands.c" + "src/esp_dynamic_commands.c" + "src/esp_commands_helpers.c") idf_component_register( SRCS ${srcs} INCLUDE_DIRS include PRIV_INCLUDE_DIRS private_include LDFRAGMENTS linker.lf -) + WHOLE_ARCHIVE) if(${idf_target} STREQUAL "linux") # Add custom ld file diff --git a/esp_commands/include/esp_commands.h b/esp_commands/include/esp_commands.h index d4511af1c5..8522115795 100644 --- a/esp_commands/include/esp_commands.h +++ b/esp_commands/include/esp_commands.h @@ -13,7 +13,7 @@ extern "C" { #include "esp_heap_caps.h" #include "esp_err.h" -#define ESP_COMMAND_STRINGIFY(name) #name +#define _STRINGIFY(name) #name /** * @brief Function pointer type for writing bytes. @@ -187,14 +187,14 @@ esp_err_t esp_commands_update_config(const esp_commands_config_t *config); * @brief macro registering a command and placing it in a specific section of flash.rodata * @note see the linker.lf file for more information concerning the section characteristics */ -#define ESP_COMMAND_REGISTER(cmd_name, cmd_group, cmd_help, cmd_func, cmd_func_ctx, cmd_hint_cb, cmd_glossary_cb) \ +#define _ESP_REPL_STRINGIFY(x) #x +#define ESP_REPL_STRINGIFY(x) _ESP_REPL_STRINGIFY(x) + +#define _ESP_COMMAND_REGISTER(cmd_name, cmd_group, cmd_help, cmd_func, cmd_func_ctx, cmd_hint_cb, cmd_glossary_cb) \ static_assert((cmd_func) != NULL); \ - /* Alignment attribute is required when building on linux target to prevent each input section */ \ - /* from inheriting its alignment from the object's file default one thus preventing gaps between */ \ - /* commands in the section. */ \ - static const esp_command_t cmd_name __attribute__((used, section(".esp_commands" "." ESP_COMMAND_STRINGIFY(cmd_name)), aligned(4))) = { \ - .name = #cmd_name, \ - .group = #cmd_group, \ + static const esp_command_t cmd_name __attribute__((used, section(".esp_commands" "." _ESP_REPL_STRINGIFY(cmd_name)), aligned(4))) = { \ + .name = _ESP_REPL_STRINGIFY(cmd_name), \ + .group = _ESP_REPL_STRINGIFY(cmd_group), \ .help = cmd_help, \ .func = cmd_func, \ .func_ctx = cmd_func_ctx, \ @@ -202,6 +202,9 @@ esp_err_t esp_commands_update_config(const esp_commands_config_t *config); .glossary_cb = cmd_glossary_cb \ }; +#define ESP_COMMAND_REGISTER(cmd_name, cmd_group, cmd_help, cmd_func, cmd_func_ctx, cmd_hint_cb, cmd_glossary_cb) \ + _ESP_COMMAND_REGISTER(cmd_name, cmd_group, cmd_help, cmd_func, cmd_func_ctx, cmd_hint_cb, cmd_glossary_cb) + /** * @brief Register a command * diff --git a/esp_commands/linker.lf b/esp_commands/linker.lf index 7c238164ef..d18a3bca76 100644 --- a/esp_commands/linker.lf +++ b/esp_commands/linker.lf @@ -1,6 +1,6 @@ [sections:esp_commands] entries: - .esp_command+ + .esp_commands+ [scheme:esp_commands_default] entries: diff --git a/esp_commands/esp_commands.c b/esp_commands/src/esp_commands.c similarity index 98% rename from esp_commands/esp_commands.c rename to esp_commands/src/esp_commands.c index 9bc5292efc..29abe37740 100644 --- a/esp_commands/esp_commands.c +++ b/esp_commands/src/esp_commands.c @@ -536,7 +536,7 @@ static void print_arg_help(esp_commands_exec_arg_t *cmd_args, esp_command_t *it) /* First line: command name and hint * Pad all the hints to the same column */ - FDPRINTF(cmd_args->out_fd, cmd_args->write_func, "%-s", it->name); + FDPRINTF(cmd_args->out_fd, cmd_args->write_func, "%s", it->name); const char *hint = NULL; if (it->hint_cb) { @@ -544,18 +544,18 @@ static void print_arg_help(esp_commands_exec_arg_t *cmd_args, esp_command_t *it) } if (hint) { - FDPRINTF(cmd_args->out_fd, cmd_args->write_func, "%s\n", it->hint_cb(it->func_ctx)); + FDPRINTF(cmd_args->out_fd, cmd_args->write_func, " %s\n", it->hint_cb(it->func_ctx)); } else { - FDPRINTF(cmd_args->out_fd, cmd_args->write_func, "\n"); + FDPRINTF(cmd_args->out_fd, cmd_args->write_func, " -\n"); } /* Second line: print help */ /* TODO: replace the simple print with a function that * replaces arg_print_formatted */ if (it->help) { - FDPRINTF(cmd_args->out_fd, cmd_args->write_func, " %s\n", it->help); + FDPRINTF(cmd_args->out_fd, cmd_args->write_func, " %s\n", it->help); } else { - FDPRINTF(cmd_args->out_fd, cmd_args->write_func, " -\n"); + FDPRINTF(cmd_args->out_fd, cmd_args->write_func, " -\n"); } /* Third line: print the glossary*/ @@ -567,7 +567,7 @@ static void print_arg_help(esp_commands_exec_arg_t *cmd_args, esp_command_t *it) if (glossary) { FDPRINTF(cmd_args->out_fd, cmd_args->write_func, " %s\n", it->glossary_cb(it->func_ctx)); } else { - FDPRINTF(cmd_args->out_fd, cmd_args->write_func, " -\n"); + FDPRINTF(cmd_args->out_fd, cmd_args->write_func, " -\n"); } FDPRINTF(cmd_args->out_fd, cmd_args->write_func, "\n"); diff --git a/esp_commands/esp_commands_helpers.c b/esp_commands/src/esp_commands_helpers.c similarity index 100% rename from esp_commands/esp_commands_helpers.c rename to esp_commands/src/esp_commands_helpers.c diff --git a/esp_commands/esp_dynamic_commands.c b/esp_commands/src/esp_dynamic_commands.c similarity index 100% rename from esp_commands/esp_dynamic_commands.c rename to esp_commands/src/esp_dynamic_commands.c From 8c0e035a7735a1a576d88e6aacd2684248e4e4d6 Mon Sep 17 00:00:00 2001 From: Guillaume Souchere Date: Thu, 6 Nov 2025 12:25:21 +0100 Subject: [PATCH 8/9] fix(esp_commands): Incorporate review comments --- esp_commands/.build-test-rules.yml | 5 +- esp_commands/README.md | 36 ++-- esp_commands/idf_component.yml | 2 +- esp_commands/include/esp_commands.h | 171 +--------------- esp_commands/include/esp_commands_utils.h | 186 ++++++++++++++++++ esp_commands/src/esp_commands.c | 111 ++++++++--- esp_commands/src/esp_dynamic_commands.c | 18 +- .../test_apps/main/test_esp_commands.c | 26 +-- esp_commands/test_apps/pytest_esp_commands.py | 2 +- 9 files changed, 321 insertions(+), 236 deletions(-) create mode 100644 esp_commands/include/esp_commands_utils.h diff --git a/esp_commands/.build-test-rules.yml b/esp_commands/.build-test-rules.yml index 1215fb5732..33653bfcec 100644 --- a/esp_commands/.build-test-rules.yml +++ b/esp_commands/.build-test-rules.yml @@ -1,6 +1,3 @@ esp_commands/test_apps: enable: - - if: (IDF_TARGET == "linux") and (IDF_VERSION_MAJOR == 5 and IDF_VERSION_MINOR >= 3) - reason: "Test the main logic of the component (not target dependent)" - - if: IDF_TARGET == "esp32" - reason: "Test the placement of statically registered commands in dedicated flash section" \ No newline at end of file + - if: (IDF_TARGET in ["esp32", "linux"]) and (IDF_VERSION_MAJOR == 5 and IDF_VERSION_MINOR >= 3) \ No newline at end of file diff --git a/esp_commands/README.md b/esp_commands/README.md index 9542167053..95213fbcd4 100644 --- a/esp_commands/README.md +++ b/esp_commands/README.md @@ -22,10 +22,16 @@ It allows applications to define console-like commands with metadata (help text, ## Configuration -The component is initialized with a configuration struct: +By default, the component is initialized with a default configuration. It is however possible for the user to update this configuration with the call of the following API: ```c -esp_commands_config_t config = ESP_COMMANDS_CONFIG_DEFAULT(); +esp_commands_config_t config = { + .heap_caps_used = , + .max_cmdline_length = , + .max_cmdline_args = , + .hint_color = , + .hint_bold = +}; esp_commands_update_config(&config); ``` @@ -60,7 +66,7 @@ typedef struct esp_command { Use the `ESP_COMMAND_REGISTER` macro to register a command at compile time: ```c -static int my_cmd(void *ctx, int argc, char **argv) { +static int my_cmd(void *context, esp_commands_exec_arg_t *cmd_arg, int argc, char **argv) { printf("Hello from my_cmd!\n"); return 0; } @@ -68,7 +74,7 @@ static int my_cmd(void *ctx, int argc, char **argv) { ESP_COMMAND_REGISTER(my_cmd, tools, "Prints hello", my_cmd, NULL, NULL, NULL); ``` -This places the command into the `.esp_commands` section. +This places the command into the `.esp_commands` section in flash. ### Dynamic Registration @@ -76,10 +82,10 @@ Commands can also be registered/unregistered at runtime: ```c esp_command_t cmd = { - .name = "echo", - .group = "utils", - .help = "Echoes arguments back", - .func = echo_func, + .name = "my_cmd", + .group = "tools", + .help = "Prints hello", + .func = my_cmd, }; esp_commands_register_cmd(&cmd); @@ -94,7 +100,7 @@ Commands can be executed from a command line string: ```c int cmd_ret; -esp_err_t ret = esp_commands_execute(NULL, STDOUT_FILENO, "my_cmd arg1 arg2", &cmd_ret); +esp_err_t ret = esp_commands_execute("my_cmd arg1 arg2", &cmd_ret, NULL, STDOUT_FILENO); ``` - `cmd_set`: Limits execution to a set of commands (or `NULL` for all commands). @@ -129,7 +135,7 @@ const char *cmd_names[] = {"echo", "my_cmd"}; esp_command_set_handle_t set = ESP_COMMANDS_CREATE_CMD_SET(cmd_names, FIELD_ACCESSOR(name)); -esp_commands_execute(set, "echo Hello!", NULL); +esp_commands_execute("my_cmd", NULL, set, NULL); esp_commands_destroy_cmd_set(&set); ``` @@ -156,12 +162,18 @@ ESP_COMMAND_REGISTER(hello_cmd, demo, "Prints a hello message", hello_cmd, NULL, void app_main(void) { // Update configuration (optional) - esp_commands_config_t config = ESP_COMMANDS_CONFIG_DEFAULT(); + esp_commands_config_t config = { + .heap_caps_used = MALLOC_CAP_INTERNAL, + .max_cmdline_length = 64, + .max_cmdline_args = 4, + .hint_color = 31, // Red foreground + .hint_bold = true + }; esp_commands_update_config(&config); // Execute command int ret_val; - esp_err_t ret = esp_commands_execute(NULL, "hello_cmd", &ret_val); + esp_err_t ret = esp_commands_execute("hello_cmd", &ret_val, NULL, NULL); if (ret == ESP_OK) { printf("Command executed successfully, return value: %d\n", ret_val); } else { diff --git a/esp_commands/idf_component.yml b/esp_commands/idf_component.yml index 108257eda9..6c79c44a35 100644 --- a/esp_commands/idf_component.yml +++ b/esp_commands/idf_component.yml @@ -1,4 +1,4 @@ -version: "1.0.0" +version: "0.1.0" description: "esp_commands - Command handling component" url: https://github.com/espressif/idf-extra-components/tree/master/esp_commands dependencies: diff --git a/esp_commands/include/esp_commands.h b/esp_commands/include/esp_commands.h index 8522115795..e94e12ea1e 100644 --- a/esp_commands/include/esp_commands.h +++ b/esp_commands/include/esp_commands.h @@ -10,170 +10,10 @@ extern "C" { #endif #include +#include "esp_commands_utils.h" #include "esp_heap_caps.h" #include "esp_err.h" -#define _STRINGIFY(name) #name - -/** - * @brief Function pointer type for writing bytes. - * - * @param fd File descriptor. - * @param buf Buffer containing bytes to write. - * @param count Number of bytes to write. - * @return Number of bytes written, or -1 on error. - */ -typedef ssize_t (*esp_commands_write_t)(int fd, const void *buf, size_t count); - -/** - * @brief Structure containing dynamic argument necessary for the\ - * command callback to execute properly - */ -typedef struct esp_commands_exec_arg { - int out_fd; /*!< file descriptor that the command function has to use to output data */ - esp_commands_write_t write_func; /*!< write function the command function has to use to output datga */ - void *dynamic_ctx; /*!< dynamic context passed to the command function */ -} esp_commands_exec_arg_t; - -/** - * @brief Console command main function type with user context - * - * This function type is used to implement a console command. - * - * @param context User-defined context passed at invocation - * @param cmd_arg Structure containing dynamic arguments necessary for the command - * @param argc Number of arguments - * @param argv Array of argc entries, each pointing to a null-terminated string argument - * @return Return code of the console command; 0 indicates success - */ -typedef int (*esp_command_func_t)(void *context, esp_commands_exec_arg_t *cmd_arg, int argc, char **argv); - -/** - * @brief Callback to generate a command hint - * - * This function is called to retrieve a short hint for a command, - * typically used for auto-completion or UI help. - * - * @param context Context registered when the command was registered - * @return Persistent string containing the generated hint - */ -typedef const char *(*esp_command_hint_t)(void *context); - -/** - * @brief Callback to generate a command glossary entry - * - * This function is called to retrieve detailed description or glossary - * information for a command. - * - * @param context Context registered when the command was registered - * @return Persistent string containing the generated glossary - */ -typedef const char *(*esp_command_glossary_t)(void *context); - -/** - * @brief Structure describing a console command - * - * @note The `group` field allows categorizing commands into groups, - * which can simplify filtering or listing commands. - */ -typedef struct esp_command { - const char *name; /*!< Name of the command */ - const char *group; /*!< Command group to which this command belongs */ - const char *help; /*!< Short help text for the command */ - esp_command_func_t func; /*!< Function implementing the command */ - void *func_ctx; /*!< User-defined context for the command function */ - esp_command_hint_t hint_cb; /*!< Callback returning the hint for the command */ - esp_command_glossary_t glossary_cb; /*!< Callback returning the glossary for the command */ -} esp_command_t; - -/** - * @brief Macro to define a forced inline accessor for a string field of esp_command_t - * - * @param NAME Field name of the esp_command_t structure - */ -#define DEFINE_FIELD_ACCESSOR(NAME) \ - static inline __attribute__((always_inline)) \ - const char *get_##NAME(const esp_command_t *cmd) { \ - if (!cmd) { \ - return NULL; \ - } \ - return cmd->NAME; \ - } - -/** - * @brief Macro expanding to - * static inline __attribute__((always_inline)) const char *get_name(esp_command_t *cmd) { - * if (!cmd) { - * return NULL; - * } - * return cmd->name; - * } - */ -DEFINE_FIELD_ACCESSOR(name) - -/** - * @brief Macro expanding to - * static inline __attribute__((always_inline)) const char *get_group(esp_command_t *cmd) { - * if (!cmd) { - * return NULL; - * } - * return cmd->group; - * } - */ -DEFINE_FIELD_ACCESSOR(group) - -/** - * @brief Macro expanding to - * static inline __attribute__((always_inline)) const char *get_help(esp_command_t *cmd) { - * if (!cmd) { - * return NULL; - * } - * return cmd->help; - * } - */ -DEFINE_FIELD_ACCESSOR(help) - -/** - * @brief Macro to create the accessor function name for a field of esp_command_t - * - * @param NAME Field name of esp_command_t - */ -#define FIELD_ACCESSOR(NAME) get_##NAME - -/** - * @brief Configuration parameters for esp_commands_manager initialization - */ -typedef struct esp_commands_config { - uint32_t heap_caps_used; /*!< Set of heap capabilities to be used to perform internal allocations */ - size_t max_cmdline_length; /*!< Maximum length of the command line buffer, in bytes */ - size_t max_cmdline_args; /*!< Maximum number of command line arguments to parse */ - int hint_color; /*!< ANSI color code used for hint text */ - bool hint_bold; /*!< If true, display hint text in bold */ -} esp_commands_config_t; - -/** - * @brief Callback for a completed command name - * - * This callback is called when a command is successfully completed. - * - * @param cb_ctx Opaque pointer pointing at the context passed to the callback - * @param completed_cmd_name Completed command name - */ -typedef void (*esp_command_get_completion_t)(void *cb_ctx, const char *completed_cmd_name); - -/** - * @brief Callback to retrieve a string field of esp_command_t - * - * @param cmd Command object - * @return Value of the requested string field - */ -typedef const char *(*esp_commands_get_field_t)(const esp_command_t *cmd); - -/** - * @brief Opaque handle to a set of commands - */ -typedef struct esp_command_sets *esp_command_set_handle_t; - /** * @brief Update the component configuration * @@ -187,9 +27,6 @@ esp_err_t esp_commands_update_config(const esp_commands_config_t *config); * @brief macro registering a command and placing it in a specific section of flash.rodata * @note see the linker.lf file for more information concerning the section characteristics */ -#define _ESP_REPL_STRINGIFY(x) #x -#define ESP_REPL_STRINGIFY(x) _ESP_REPL_STRINGIFY(x) - #define _ESP_COMMAND_REGISTER(cmd_name, cmd_group, cmd_help, cmd_func, cmd_func_ctx, cmd_hint_cb, cmd_glossary_cb) \ static_assert((cmd_func) != NULL); \ static const esp_command_t cmd_name __attribute__((used, section(".esp_commands" "." _ESP_REPL_STRINGIFY(cmd_name)), aligned(4))) = { \ @@ -226,17 +63,17 @@ esp_err_t esp_commands_unregister_cmd(const char *cmd_name); /** * @brief Execute a command line * + * @param cmd_line Command line string to execute + * @param cmd_ret Return value from the command function. If -1, standard output will be used. * @param cmd_set Set of commands allowed to execute. If NULL, all registered commands are allowed * @param cmd_arg Structure containing dynamic arguments necessary for the command * callback to execute properly - * @param cmd_line Command line string to execute - * @param cmd_ret Return value from the command function. If -1, standard output will be used. * @return ESP_OK on success * ESP_ERR_INVALID_ARG if the command line is empty or only whitespace * ESP_ERR_NOT_FOUND if command is not found in cmd_set * ESP_ERR_NO_MEM if internal memory allocation fails */ -esp_err_t esp_commands_execute(esp_command_set_handle_t cmd_set, esp_commands_exec_arg_t *cmd_args, const char *cmdline, int *cmd_ret); +esp_err_t esp_commands_execute(const char *cmdline, int *cmd_ret, esp_command_set_handle_t cmd_set, esp_commands_exec_arg_t *cmd_args); /** * @brief Find a command by name within a specific command set. diff --git a/esp_commands/include/esp_commands_utils.h b/esp_commands/include/esp_commands_utils.h new file mode 100644 index 0000000000..eca94db285 --- /dev/null +++ b/esp_commands/include/esp_commands_utils.h @@ -0,0 +1,186 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +#define _ESP_REPL_STRINGIFY(x) #x +#define ESP_REPL_STRINGIFY(x) _ESP_REPL_STRINGIFY(x) + +/** + * @brief Function pointer type for writing bytes. + * + * @param fd File descriptor. + * @param buf Buffer containing bytes to write. + * @param count Number of bytes to write. + * @return Number of bytes written, or -1 on error. + */ +typedef ssize_t (*esp_commands_write_t)(int fd, const void *buf, size_t count); + +/** + * @brief Structure containing dynamic argument necessary for the + * command callback to execute properly. + * + * @note Since a command function callback can be executed from + * a random context, the callback has to be aware of what file descriptor + * and what write function to use in order to print data to the expected + * destination. + */ +typedef struct esp_commands_exec_arg { + int out_fd; /*!< file descriptor that the command function has to use to print data in the environment it was called from */ + esp_commands_write_t write_func; /*!< write function the command function has to use to print data in the environment it was called from */ + void *dynamic_ctx; /*!< dynamic context passed to the command function */ +} esp_commands_exec_arg_t; + +/** + * @brief Console command main function type with user context + * + * This function type is used to implement a console command. + * + * @param context User-defined context passed at invocation + * @param cmd_arg Structure containing dynamic arguments necessary for the command + * @param argc Number of arguments + * @param argv Array of argc entries, each pointing to a null-terminated string argument + * @return Return code of the console command; 0 indicates success + */ +typedef int (*esp_command_func_t)(void *context, esp_commands_exec_arg_t *cmd_arg, int argc, char **argv); + +/** + * @brief Callback to generate a command hint + * + * This function is called to retrieve a short hint for a command, + * typically used for auto-completion or UI help. + * + * @param context Context registered when the command was registered + * @return Persistent string containing the generated hint + */ +typedef const char *(*esp_command_hint_t)(void *context); + +/** + * @brief Callback to generate a command glossary entry + * + * This function is called to retrieve detailed description or glossary + * information for a command. + * + * @param context Context registered when the command was registered + * @return Persistent string containing the generated glossary + */ +typedef const char *(*esp_command_glossary_t)(void *context); + +/** + * @brief Structure describing a console command + * + * @note The `group` field allows categorizing commands into groups, + * which can simplify filtering or listing commands. + */ +typedef struct esp_command { + const char *name; /*!< Name of the command */ + const char *group; /*!< Command group to which this command belongs */ + const char *help; /*!< Short help text for the command */ + esp_command_func_t func; /*!< Function implementing the command */ + void *func_ctx; /*!< User-defined context for the command function */ + esp_command_hint_t hint_cb; /*!< Callback returning the hint for the command */ + esp_command_glossary_t glossary_cb; /*!< Callback returning the glossary for the command */ +} esp_command_t; + +/** + * @brief Configuration parameters for esp_commands_manager initialization + */ +typedef struct esp_commands_config { + uint32_t heap_caps_used; /*!< Set of heap capabilities to be used to perform internal allocations */ + size_t max_cmdline_length; /*!< Maximum length of the command line buffer, in bytes */ + size_t max_cmdline_args; /*!< Maximum number of command line arguments to parse */ + int hint_color; /*!< ANSI color code used for hint text */ + bool hint_bold; /*!< If true, display hint text in bold */ +} esp_commands_config_t; + +/** + * @brief Callback for a completed command name + * + * This callback is called when a command is successfully completed. + * + * @param cb_ctx Opaque pointer pointing at the context passed to the callback + * @param completed_cmd_name Completed command name + */ +typedef void (*esp_command_get_completion_t)(void *cb_ctx, const char *completed_cmd_name); + +/** + * @brief Callback to retrieve a string field of esp_command_t + * + * @param cmd Command object + * @return Value of the requested string field + */ +typedef const char *(*esp_commands_get_field_t)(const esp_command_t *cmd); + +/** + * @brief Opaque handle to a set of commands + */ +typedef struct esp_command_sets *esp_command_set_handle_t; + +/** + * @brief Macro to define a forced inline accessor for a string field of esp_command_t + * + * @param NAME Field name of the esp_command_t structure + */ +#define DEFINE_FIELD_ACCESSOR(NAME) \ + static inline __attribute__((always_inline)) \ + const char *get_##NAME(const esp_command_t *cmd) { \ + if (!cmd) { \ + return NULL; \ + } \ + return cmd->NAME; \ + } + +/** + * @brief Macro expanding to + * static inline __attribute__((always_inline)) const char *get_name(esp_command_t *cmd) { + * if (!cmd) { + * return NULL; + * } + * return cmd->name; + * } + */ +DEFINE_FIELD_ACCESSOR(name) + +/** + * @brief Macro expanding to + * static inline __attribute__((always_inline)) const char *get_group(esp_command_t *cmd) { + * if (!cmd) { + * return NULL; + * } + * return cmd->group; + * } + */ +DEFINE_FIELD_ACCESSOR(group) + +/** + * @brief Macro expanding to + * static inline __attribute__((always_inline)) const char *get_help(esp_command_t *cmd) { + * if (!cmd) { + * return NULL; + * } + * return cmd->help; + * } + */ +DEFINE_FIELD_ACCESSOR(help) + +/** + * @brief Macro to create the accessor function name for a field of esp_command_t + * + * @note Those accessor functions are defined in esp_commands_internal.h + * + * @param NAME Field name of esp_command_t + */ +#define FIELD_ACCESSOR(NAME) get_##NAME + +#ifdef __cplusplus +} +#endif diff --git a/esp_commands/src/esp_commands.c b/esp_commands/src/esp_commands.c index 29abe37740..a2ffd04c09 100644 --- a/esp_commands/src/esp_commands.c +++ b/esp_commands/src/esp_commands.c @@ -6,6 +6,8 @@ #include #include #include +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" #include "esp_heap_caps.h" #include "esp_commands.h" #include "esp_commands_internal.h" @@ -15,6 +17,10 @@ /* Default foreground color */ #define ANSI_COLOR_DEFAULT 39 +/* static mutex used to protect access to the static configuration */ +static SemaphoreHandle_t s_esp_commands_mutex = NULL; +static StaticSemaphore_t s_esp_commands_mutex_buf; + /* Pointers to the first and last command in the dedicated section. * See linker.lf for detailed information about the section */ extern esp_command_t _esp_commands_start; @@ -55,6 +61,26 @@ static esp_commands_config_t s_config = { */ #define ESP_COMMANDS_COUNT (size_t)(&_esp_commands_end - &_esp_commands_start) +/** + * @brief Lock access to the s_config static structure + */ +static void esp_commands_lock(void) +{ + if (s_esp_commands_mutex == NULL) { + s_esp_commands_mutex = xSemaphoreCreateMutexStatic(&s_esp_commands_mutex_buf); + assert(s_esp_commands_mutex != NULL); + } + xSemaphoreTake(s_esp_commands_mutex, portMAX_DELAY); +} + +/** + * @brief Unlock access to the s_config static structure + */ +static void esp_commands_unlock(void) +{ + xSemaphoreGive(s_esp_commands_mutex); +} + /** * @brief check the location of the pointer to esp_command_t * @@ -145,7 +171,11 @@ bool compare_command_name(void *ctx, esp_command_t *cmd) void *esp_commands_malloc(const size_t malloc_size) { - return heap_caps_malloc(malloc_size, s_config.heap_caps_used); + esp_commands_lock(); + const uint32_t caps = s_config.heap_caps_used; + esp_commands_unlock(); + + return heap_caps_malloc(malloc_size, caps); } esp_err_t esp_commands_update_config(const esp_commands_config_t *config) @@ -156,6 +186,7 @@ esp_err_t esp_commands_update_config(const esp_commands_config_t *config) return ESP_ERR_INVALID_ARG; } + esp_commands_lock(); memcpy(&s_config, config, sizeof(s_config)); /* if the heap_caps_used field is set to 0, set @@ -163,6 +194,7 @@ esp_err_t esp_commands_update_config(const esp_commands_config_t *config) if (s_config.heap_caps_used == 0) { s_config.heap_caps_used = MALLOC_CAP_DEFAULT; } + esp_commands_unlock(); return ESP_OK; } @@ -214,55 +246,70 @@ esp_err_t esp_commands_unregister_cmd(const char *cmd_name) } } -esp_err_t esp_commands_execute(esp_command_set_handle_t cmd_set, esp_commands_exec_arg_t *cmd_args, const char *cmdline, int *cmd_ret) +esp_err_t esp_commands_execute(const char *cmdline, int *cmd_ret, esp_command_set_handle_t cmd_set, esp_commands_exec_arg_t *cmd_args) { - char **argv = (char **) calloc(s_config.max_cmdline_args, sizeof(char *)); - if (argv == NULL) { - return ESP_ERR_NO_MEM; - } - char *tmp_line_buf = (char *) calloc(1, s_config.max_cmdline_length); - if (!tmp_line_buf) { - free(argv); - return ESP_ERR_NO_MEM; - } + esp_commands_lock(); + const size_t copy_max_cmdline_args = s_config.max_cmdline_args; + const size_t opy_max_cmdline_length = s_config.max_cmdline_length; + esp_commands_unlock(); - strlcpy(tmp_line_buf, cmdline, s_config.max_cmdline_length); + /* the life time of those variables is not exceeding the scope of this function. Use the stack. */ + char *argv[copy_max_cmdline_args]; + memset(argv, 0x00, sizeof(argv)); + char tmp_line_buf[opy_max_cmdline_length]; + memset(tmp_line_buf, 0x00, sizeof(tmp_line_buf)); + + /* copy the raw command line into the temp buffer */ + strlcpy(tmp_line_buf, cmdline, opy_max_cmdline_length); + + /* parse and split the raw command line */ + size_t argc = esp_commands_split_argv(tmp_line_buf, argv, copy_max_cmdline_args); - size_t argc = esp_commands_split_argv(tmp_line_buf, argv, s_config.max_cmdline_args); if (argc == 0) { - free(argv); - free(tmp_line_buf); return ESP_ERR_INVALID_ARG; } - /* help should always be executed, if cmd_sets is set or not */ + /* try to find the command from the first argument in the command line */ const esp_command_t *cmd = NULL; + esp_command_sets_t *temp_set = cmd_set; bool is_cmd_help = false; if (strcmp("help", argv[0]) == 0) { - /* find the help command in the list in .esp_commands section */ - cmd = esp_commands_find_command((esp_command_sets_t *)NULL, "help"); + /* set the set to NULL because the help is not in the set passed by the user + * since this command is registered by esp_commands itself */ + temp_set = (esp_command_sets_t *)NULL; + + /* keep in mind that the command being executed is the help. This is needed + * when calling the help command function, to pass a specific dynamic context */ is_cmd_help = true; - } else { - cmd = esp_commands_find_command(cmd_set, argv[0]); } + cmd = esp_commands_find_command(temp_set, argv[0]); if (cmd == NULL) { - free(argv); - free(tmp_line_buf); return ESP_ERR_NOT_FOUND; } if (cmd->func) { - esp_commands_exec_arg_t help_args; if (is_cmd_help) { - help_args.out_fd = (cmd_args && cmd_args->out_fd) ? cmd_args->out_fd : STDOUT_FILENO; + esp_commands_exec_arg_t help_args; + + /* reuse the out_fd and write_func received as parameter by esp_commands_execute + * to allow the help command function to print information on the correct IO. Use + * default values in case the parameters provided are not set */ + help_args.out_fd = (cmd_args && cmd_args->out_fd != -1) ? cmd_args->out_fd : STDOUT_FILENO; help_args.write_func = (cmd_args && cmd_args->write_func) ? cmd_args->write_func : write; + + /* the help command needs the cmd_set to be able to only print the help for commands + * in the user set of commands */ help_args.dynamic_ctx = cmd_set; + + /* call the help command function with the specific dynamic context */ + *cmd_ret = (*cmd->func)(cmd->func_ctx, &help_args, argc, argv); + } else { + /* regular command function has to be called, just passed the cmd_args as provided + * to the esp_commands_execute function */ + *cmd_ret = (*cmd->func)(cmd->func_ctx, cmd_args, argc, argv); } - *cmd_ret = (*cmd->func)(cmd->func_ctx, &help_args, argc, argv); } - free(argv); - free(tmp_line_buf); return ESP_OK; } @@ -322,7 +369,7 @@ esp_err_t update_cmd_set_with_temp_info(esp_command_set_t *cmd_set, size_t cmd_c cmd_set->cmd_set_size = 0; } else { const size_t alloc_cmd_ptrs_size = sizeof(esp_command_t *) * cmd_count; - cmd_set->cmd_ptr_set = heap_caps_malloc(alloc_cmd_ptrs_size, s_config.heap_caps_used); + cmd_set->cmd_ptr_set = esp_commands_malloc(alloc_cmd_ptrs_size); if (!cmd_set->cmd_ptr_set) { return ESP_ERR_NO_MEM; } else { @@ -340,7 +387,7 @@ esp_command_set_handle_t esp_commands_create_cmd_set(const char **cmd_set, const return NULL; } - esp_command_sets_t *cmd_ptr_sets = heap_caps_malloc(sizeof(esp_command_sets_t), s_config.heap_caps_used); + esp_command_sets_t *cmd_ptr_sets = esp_commands_malloc(sizeof(esp_command_sets_t)); if (!cmd_ptr_sets) { return NULL; } @@ -398,7 +445,7 @@ esp_command_set_handle_t esp_commands_concat_cmd_set(esp_command_set_handle_t cm /* Reaching this point, both cmd_set_a and cmd_set_b are set. * Create a new cmd_set that can host the items from both sets, * assign the items to the new set and free the input sets */ - esp_command_sets_t *concat_cmd_sets = heap_caps_malloc(sizeof(esp_command_sets_t), s_config.heap_caps_used); + esp_command_sets_t *concat_cmd_sets = esp_commands_malloc(sizeof(esp_command_sets_t)); if (!concat_cmd_sets) { return NULL; } @@ -496,8 +543,10 @@ void esp_commands_get_completion(esp_command_set_handle_t cmd_set, const char *b const char *esp_commands_get_hint(esp_command_set_handle_t cmd_set, const char *buf, int *color, bool *bold) { + esp_commands_lock(); *color = s_config.hint_color; *bold = s_config.hint_bold; + esp_commands_unlock(); esp_command_t *cmd = esp_commands_find_command(cmd_set, buf); if (cmd && cmd->hint_cb != NULL) { @@ -522,7 +571,9 @@ const char *esp_commands_get_glossary(esp_command_set_handle_t cmd_set, const ch /* -------------------------------------------------------------- */ #define FDPRINTF(fd, write, fmt, ...) do { \ + esp_commands_lock(); \ char _buf[s_config.max_cmdline_length]; \ + esp_commands_unlock(); \ int _len = snprintf(_buf, sizeof(_buf), fmt, ##__VA_ARGS__); \ if (_len > 0) { \ ssize_t _ignored __attribute__((unused)); \ diff --git a/esp_commands/src/esp_dynamic_commands.c b/esp_commands/src/esp_dynamic_commands.c index 55808e60b2..b868a88b52 100644 --- a/esp_commands/src/esp_dynamic_commands.c +++ b/esp_commands/src/esp_dynamic_commands.c @@ -8,6 +8,8 @@ #include #include #include +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" #include "esp_commands_internal.h" #include "esp_dynamic_commands.h" #include "esp_commands.h" @@ -17,27 +19,27 @@ static esp_command_internal_ll_t s_dynamic_cmd_list = SLIST_HEAD_INITIALIZER(esp_command_internal); static size_t s_number_of_registered_commands = 0; -static SemaphoreHandle_t s_esp_commands_mutex = NULL; -static StaticSemaphore_t s_esp_commands_mutex_buf; +static SemaphoreHandle_t s_esp_commands_dyn_mutex = NULL; +static StaticSemaphore_t s_esp_commands_dyn_mutex_buf; void esp_dynamic_commands_lock(void) { /* check if the mutex needs to be initialized and initialized it only * is requested in by the state of the create parameter */ - if (s_esp_commands_mutex == NULL) { - s_esp_commands_mutex = xSemaphoreCreateMutexStatic(&s_esp_commands_mutex_buf); - assert(s_esp_commands_mutex != NULL); + if (s_esp_commands_dyn_mutex == NULL) { + s_esp_commands_dyn_mutex = xSemaphoreCreateMutexStatic(&s_esp_commands_dyn_mutex_buf); + assert(s_esp_commands_dyn_mutex != NULL); } - xSemaphoreTake(s_esp_commands_mutex, portMAX_DELAY); + xSemaphoreTake(s_esp_commands_dyn_mutex, portMAX_DELAY); } void esp_dynamic_commands_unlock(void) { - if (s_esp_commands_mutex == NULL) { + if (s_esp_commands_dyn_mutex == NULL) { return; } - xSemaphoreGive(s_esp_commands_mutex); + xSemaphoreGive(s_esp_commands_dyn_mutex); } const esp_command_internal_ll_t *esp_dynamic_commands_get_list(void) diff --git a/esp_commands/test_apps/main/test_esp_commands.c b/esp_commands/test_apps/main/test_esp_commands.c index 4025ea8d9a..d0f5ee6858 100644 --- a/esp_commands/test_apps/main/test_esp_commands.c +++ b/esp_commands/test_apps/main/test_esp_commands.c @@ -41,35 +41,35 @@ TEST_CASE("help command - called without command set", "[esp_commands]") /* call esp_commands_execute to run help command with verbosity 0 */ int cmd_ret = -1; - TEST_ASSERT_EQUAL(ESP_OK, esp_commands_execute(NULL, NULL, "help -v 0", &cmd_ret)); + TEST_ASSERT_EQUAL(ESP_OK, esp_commands_execute("help -v 0", &cmd_ret, NULL, NULL)); TEST_ASSERT_EQUAL(0, cmd_ret); /* call esp_commands_execute to run help command with verbosity 1 */ cmd_ret = -1; - TEST_ASSERT_EQUAL(ESP_OK, esp_commands_execute(NULL, NULL, "help -v 1", &cmd_ret)); + TEST_ASSERT_EQUAL(ESP_OK, esp_commands_execute("help -v 1", &cmd_ret, NULL, NULL)); TEST_ASSERT_EQUAL(0, cmd_ret); /* call esp_commands_execute to run help command on a registered command */ cmd_ret = -1; - TEST_ASSERT_EQUAL(ESP_OK, esp_commands_execute(NULL, NULL, "help cmd_a -v 0", &cmd_ret)); + TEST_ASSERT_EQUAL(ESP_OK, esp_commands_execute("help cmd_a -v 0", &cmd_ret, NULL, NULL)); TEST_ASSERT_EQUAL(0, cmd_ret); - TEST_ASSERT_EQUAL(ESP_OK, esp_commands_execute(NULL, NULL, "help cmd_a -v 1", &cmd_ret)); + TEST_ASSERT_EQUAL(ESP_OK, esp_commands_execute("help cmd_a -v 1", &cmd_ret, NULL, NULL)); TEST_ASSERT_EQUAL(0, cmd_ret); /* call esp_commands_execute to run help command on an unregistered command */ cmd_ret = -1; - TEST_ASSERT_EQUAL(ESP_OK, esp_commands_execute(NULL, NULL, "help cmd_w", &cmd_ret)); + TEST_ASSERT_EQUAL(ESP_OK, esp_commands_execute("help cmd_w", &cmd_ret, NULL, NULL)); TEST_ASSERT_EQUAL(1, cmd_ret); /* call esp_commands_execute to run help command on a registered command with wrong * verbosity syntax */ cmd_ret = -1; - TEST_ASSERT_EQUAL(ESP_OK, esp_commands_execute(NULL, NULL, "help cmd_a -v=1", &cmd_ret)); + TEST_ASSERT_EQUAL(ESP_OK, esp_commands_execute("help cmd_a -v=1", &cmd_ret, NULL, NULL)); TEST_ASSERT_EQUAL(1, cmd_ret); /* call esp_commands_execute to run help command with too many command names */ cmd_ret = -1; - TEST_ASSERT_EQUAL(ESP_OK, esp_commands_execute(NULL, NULL, "help cmd_a cmd_b -v 1", &cmd_ret)); + TEST_ASSERT_EQUAL(ESP_OK, esp_commands_execute("help cmd_a cmd_b -v 1", &cmd_ret, NULL, NULL)); TEST_ASSERT_EQUAL(1, cmd_ret); } @@ -115,7 +115,7 @@ static void run_cmd_test(esp_command_set_handle_t handle, const char **cmd_list, for (size_t i = 0; i < nb_cmds; i++) { int cmd_ret = -1; esp_err_t expected = expected_ret_val[i] == 0 ? ESP_OK : ESP_ERR_NOT_FOUND; - TEST_ASSERT_EQUAL(expected, esp_commands_execute(handle, NULL, cmd_list[i], &cmd_ret)); + TEST_ASSERT_EQUAL(expected, esp_commands_execute(cmd_list[i], &cmd_ret, handle, NULL)); TEST_ASSERT_EQUAL(expected_ret_val[i], cmd_ret); } } @@ -149,9 +149,9 @@ TEST_CASE("test static command set", "[esp_commands]") /* test help command with set of static commands */ int cmd_ret; - TEST_ASSERT_EQUAL(ESP_OK, esp_commands_execute(handle_set_a, NULL, "help", &cmd_ret)); + TEST_ASSERT_EQUAL(ESP_OK, esp_commands_execute("help", &cmd_ret, handle_set_a, NULL)); TEST_ASSERT_EQUAL(0, cmd_ret); - TEST_ASSERT_EQUAL(ESP_OK, esp_commands_execute(handle_set_b, NULL, "help", &cmd_ret)); + TEST_ASSERT_EQUAL(ESP_OK, esp_commands_execute("help", &cmd_ret, handle_set_b, NULL)); TEST_ASSERT_EQUAL(0, cmd_ret); /* destroy sets */ @@ -236,9 +236,9 @@ TEST_CASE("test dynamic command set", "[esp_commands]") /* test help command with set of dynamic commands */ int cmd_ret; - TEST_ASSERT_EQUAL(ESP_OK, esp_commands_execute(handle_set_1, NULL, "help", &cmd_ret)); + TEST_ASSERT_EQUAL(ESP_OK, esp_commands_execute("help", &cmd_ret, handle_set_1, NULL)); TEST_ASSERT_EQUAL(0, cmd_ret); - TEST_ASSERT_EQUAL(ESP_OK, esp_commands_execute(handle_set_2, NULL, "help", &cmd_ret)); + TEST_ASSERT_EQUAL(ESP_OK, esp_commands_execute("help", &cmd_ret, handle_set_2, NULL)); TEST_ASSERT_EQUAL(0, cmd_ret); @@ -298,7 +298,7 @@ TEST_CASE("test static and dynamic command sets", "[esp_commands]") /* test help command with set of dynamic commands */ int cmd_ret; - TEST_ASSERT_EQUAL(ESP_OK, esp_commands_execute(handle_combined_set, NULL, "help", &cmd_ret)); + TEST_ASSERT_EQUAL(ESP_OK, esp_commands_execute("help", &cmd_ret, handle_combined_set, NULL)); TEST_ASSERT_EQUAL(0, cmd_ret); // --- cleanup --- diff --git a/esp_commands/test_apps/pytest_esp_commands.py b/esp_commands/test_apps/pytest_esp_commands.py index 6308398a9d..eba4d4ffd7 100644 --- a/esp_commands/test_apps/pytest_esp_commands.py +++ b/esp_commands/test_apps/pytest_esp_commands.py @@ -10,6 +10,6 @@ not bool(glob.glob(f'{Path(__file__).parent.absolute()}/build*/')), reason="Skip the idf version that did not build" ) -@idf_parametrize('target', ['linux'], indirect=['target']) +@idf_parametrize('target', ['linux', 'esp32'], indirect=['target']) def test_esp_commands(dut) -> None: dut.run_all_single_board_cases() From b47ee2d23769c868bb3073da4fdbba12bd57f0ba Mon Sep 17 00:00:00 2001 From: Guillaume Souchere Date: Tue, 11 Nov 2025 09:16:50 +0100 Subject: [PATCH 9/9] feat(esp_cli_commands): Rename the component to esp_cli_commands --- .github/ISSUE_TEMPLATE/bug-report.yml | 2 +- .github/workflows/upload_component.yml | 2 +- .idf_build_apps.toml | 2 +- .../.build-test-rules.yml | 2 +- .../CMakeLists.txt | 8 +- {esp_commands => esp_cli_commands}/LICENSE | 0 {esp_commands => esp_cli_commands}/README.md | 72 +- .../idf_component.yml | 6 +- .../include/esp_cli_commands.h | 42 +- .../include/esp_cli_commands_utils.h | 58 +- esp_cli_commands/linker.lf | 13 + esp_cli_commands/linux/esp_cli_commands.ld | 10 + .../esp_cli_commands_internal.h | 4 +- .../esp_cli_dynamic_commands.h | 44 +- .../sbom_esp_cli_commands.yml | 6 +- esp_cli_commands/src/esp_cli_commands.c | 771 ++++++++++++++++++ .../src/esp_cli_commands_helpers.c | 4 +- .../src/esp_cli_dynamic_commands.c | 124 +++ .../test_apps/CMakeLists.txt | 4 +- .../test_apps/main/CMakeLists.txt | 2 +- .../test_apps/main/idf_component.yml | 2 +- .../include/test_esp_cli_commands_utils.h | 18 +- .../test_apps/main/test_esp_cli_commands.c | 180 ++-- .../test_apps/main/test_main.c | 2 +- .../test_apps/pytest_esp_cli_commands.py | 2 +- .../test_apps/sdkconfig.defaults | 0 esp_commands/linker.lf | 13 - esp_commands/linux/esp_commands.ld | 10 - .../esp_cli_dynamic_commands.h | 139 ++++ .../{esp_commands.c => esp_cli_commands.c} | 236 +++--- esp_commands/src/esp_cli_dynamic_commands.c | 124 +++ esp_commands/src/esp_dynamic_commands.c | 124 --- 32 files changed, 1530 insertions(+), 496 deletions(-) rename {esp_commands => esp_cli_commands}/.build-test-rules.yml (79%) rename {esp_commands => esp_cli_commands}/CMakeLists.txt (65%) rename {esp_commands => esp_cli_commands}/LICENSE (100%) rename {esp_commands => esp_cli_commands}/README.md (58%) rename {esp_commands => esp_cli_commands}/idf_component.yml (54%) rename esp_commands/include/esp_commands.h => esp_cli_commands/include/esp_cli_commands.h (67%) rename esp_commands/include/esp_commands_utils.h => esp_cli_commands/include/esp_cli_commands_utils.h (71%) create mode 100644 esp_cli_commands/linker.lf create mode 100644 esp_cli_commands/linux/esp_cli_commands.ld rename esp_commands/private_include/esp_commands_internal.h => esp_cli_commands/private_include/esp_cli_commands_internal.h (80%) rename esp_commands/private_include/esp_dynamic_commands.h => esp_cli_commands/private_include/esp_cli_dynamic_commands.h (70%) rename esp_commands/sbom_esp_commands.yml => esp_cli_commands/sbom_esp_cli_commands.yml (61%) create mode 100644 esp_cli_commands/src/esp_cli_commands.c rename esp_commands/src/esp_commands_helpers.c => esp_cli_commands/src/esp_cli_commands_helpers.c (96%) create mode 100644 esp_cli_commands/src/esp_cli_dynamic_commands.c rename {esp_commands => esp_cli_commands}/test_apps/CMakeLists.txt (51%) rename {esp_commands => esp_cli_commands}/test_apps/main/CMakeLists.txt (65%) rename {esp_commands => esp_cli_commands}/test_apps/main/idf_component.yml (65%) rename esp_commands/test_apps/main/include/test_esp_commands_utils.h => esp_cli_commands/test_apps/main/include/test_esp_cli_commands_utils.h (57%) rename esp_commands/test_apps/main/test_esp_commands.c => esp_cli_commands/test_apps/main/test_esp_cli_commands.c (58%) rename {esp_commands => esp_cli_commands}/test_apps/main/test_main.c (87%) rename esp_commands/test_apps/pytest_esp_commands.py => esp_cli_commands/test_apps/pytest_esp_cli_commands.py (91%) rename {esp_commands => esp_cli_commands}/test_apps/sdkconfig.defaults (100%) delete mode 100644 esp_commands/linker.lf delete mode 100644 esp_commands/linux/esp_commands.ld create mode 100644 esp_commands/private_include/esp_cli_dynamic_commands.h rename esp_commands/src/{esp_commands.c => esp_cli_commands.c} (72%) create mode 100644 esp_commands/src/esp_cli_dynamic_commands.c delete mode 100644 esp_commands/src/esp_dynamic_commands.c diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml index 4439619617..1036cbb78b 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.yml +++ b/.github/ISSUE_TEMPLATE/bug-report.yml @@ -32,7 +32,7 @@ body: - eigen - esp_daylight - esp_delta_ota - - esp_commands + - esp_cli_commands - esp_encrypted_img - esp_flash_dispatcher - esp_gcov diff --git a/.github/workflows/upload_component.yml b/.github/workflows/upload_component.yml index 4603810cfe..9d223298a3 100644 --- a/.github/workflows/upload_component.yml +++ b/.github/workflows/upload_component.yml @@ -34,7 +34,7 @@ jobs: dhara eigen esp_daylight - esp_commands + esp_cli_commands esp_delta_ota esp_encrypted_img esp_flash_dispatcher diff --git a/.idf_build_apps.toml b/.idf_build_apps.toml index 34fbbb6a10..217dfe7bb6 100644 --- a/.idf_build_apps.toml +++ b/.idf_build_apps.toml @@ -8,7 +8,7 @@ manifest_file = [ "ccomp_timer/.build-test-rules.yml", "coremark/.build-test-rules.yml", "esp_daylight/.build-test-rules.yml", - "esp_commands/.build-test-rules.yml", + "esp_cli_commands/.build-test-rules.yml", "esp_encrypted_img/.build-test-rules.yml", "esp_flash_dispatcher/.build-test-rules.yml", "esp_gcov/.build-test-rules.yml", diff --git a/esp_commands/.build-test-rules.yml b/esp_cli_commands/.build-test-rules.yml similarity index 79% rename from esp_commands/.build-test-rules.yml rename to esp_cli_commands/.build-test-rules.yml index 33653bfcec..eca6705b9c 100644 --- a/esp_commands/.build-test-rules.yml +++ b/esp_cli_commands/.build-test-rules.yml @@ -1,3 +1,3 @@ -esp_commands/test_apps: +esp_cli_commands/test_apps: enable: - if: (IDF_TARGET in ["esp32", "linux"]) and (IDF_VERSION_MAJOR == 5 and IDF_VERSION_MINOR >= 3) \ No newline at end of file diff --git a/esp_commands/CMakeLists.txt b/esp_cli_commands/CMakeLists.txt similarity index 65% rename from esp_commands/CMakeLists.txt rename to esp_cli_commands/CMakeLists.txt index 9031c3ac75..4782b49eb7 100644 --- a/esp_commands/CMakeLists.txt +++ b/esp_cli_commands/CMakeLists.txt @@ -1,8 +1,8 @@ idf_build_get_property(idf_target IDF_TARGET) -set(srcs "src/esp_commands.c" - "src/esp_dynamic_commands.c" - "src/esp_commands_helpers.c") +set(srcs "src/esp_cli_commands.c" + "src/esp_cli_dynamic_commands.c" + "src/esp_cli_commands_helpers.c") idf_component_register( SRCS ${srcs} @@ -13,5 +13,5 @@ idf_component_register( if(${idf_target} STREQUAL "linux") # Add custom ld file - target_link_options(${COMPONENT_TARGET} INTERFACE "-Wl,-T,${CMAKE_CURRENT_SOURCE_DIR}/linux/esp_commands.ld") + target_link_options(${COMPONENT_TARGET} INTERFACE "-Wl,-T,${CMAKE_CURRENT_SOURCE_DIR}/linux/esp_cli_commands.ld") endif() diff --git a/esp_commands/LICENSE b/esp_cli_commands/LICENSE similarity index 100% rename from esp_commands/LICENSE rename to esp_cli_commands/LICENSE diff --git a/esp_commands/README.md b/esp_cli_commands/README.md similarity index 58% rename from esp_commands/README.md rename to esp_cli_commands/README.md index 95213fbcd4..1d46cce3b8 100644 --- a/esp_commands/README.md +++ b/esp_cli_commands/README.md @@ -1,6 +1,6 @@ # ESP Commands -The `esp_commands` component provides a flexible command registration and execution framework for ESP-IDF applications. +The `esp_cli_commands` component provides a flexible command registration and execution framework for ESP-IDF applications. It allows applications to define console-like commands with metadata (help text, hints, glossary entries) and register them dynamically or statically. --- @@ -25,17 +25,17 @@ It allows applications to define console-like commands with metadata (help text, By default, the component is initialized with a default configuration. It is however possible for the user to update this configuration with the call of the following API: ```c -esp_commands_config_t config = { +esp_cli_commands_config_t config = { .heap_caps_used = , .max_cmdline_length = , .max_cmdline_args = , .hint_color = , .hint_bold = }; -esp_commands_update_config(&config); +esp_cli_commands_update_config(&config); ``` -- `write_func`: The custom write function used by esp_commands to output data (default to posix write is not specified) +- `write_func`: The custom write function used by esp_cli_commands to output data (default to posix write is not specified) - `max_cmdline_length`: Maximum command line buffer length (bytes). - `max_cmdline_args`: Maximum number of arguments parsed. - `hint_color`: ANSI color code used for hints. @@ -47,49 +47,49 @@ esp_commands_update_config(&config); ### Command Structure -A command is described by the `esp_command_t` struct: +A command is described by the `esp_cli_command_t` struct: ```c -typedef struct esp_command { +typedef struct esp_cli_command { const char *name; /*!< Command name */ const char *group; /*!< Group/category */ const char *help; /*!< Short help text */ - esp_command_func_t func; /*!< Command implementation */ + esp_cli_command_func_t func; /*!< Command implementation */ void *func_ctx; /*!< User context */ - esp_command_hint_t hint_cb; /*!< Hint callback */ - esp_command_glossary_t glossary_cb; /*!< Glossary callback */ -} esp_command_t; + esp_cli_command_hint_t hint_cb; /*!< Hint callback */ + esp_cli_command_glossary_t glossary_cb; /*!< Glossary callback */ +} esp_cli_command_t; ``` ### Static Registration -Use the `ESP_COMMAND_REGISTER` macro to register a command at compile time: +Use the `ESP_CLI_COMMAND_REGISTER` macro to register a command at compile time: ```c -static int my_cmd(void *context, esp_commands_exec_arg_t *cmd_arg, int argc, char **argv) { +static int my_cmd(void *context, esp_cli_commands_exec_arg_t *cmd_arg, int argc, char **argv) { printf("Hello from my_cmd!\n"); return 0; } -ESP_COMMAND_REGISTER(my_cmd, tools, "Prints hello", my_cmd, NULL, NULL, NULL); +ESP_CLI_COMMAND_REGISTER(my_cmd, tools, "Prints hello", my_cmd, NULL, NULL, NULL); ``` -This places the command into the `.esp_commands` section in flash. +This places the command into the `.esp_cli_commands` section in flash. ### Dynamic Registration Commands can also be registered/unregistered at runtime: ```c -esp_command_t cmd = { +esp_cli_command_t cmd = { .name = "my_cmd", .group = "tools", .help = "Prints hello", .func = my_cmd, }; -esp_commands_register_cmd(&cmd); -esp_commands_unregister_cmd("echo"); +esp_cli_commands_register_cmd(&cmd); +esp_cli_commands_unregister_cmd("echo"); ``` --- @@ -100,7 +100,7 @@ Commands can be executed from a command line string: ```c int cmd_ret; -esp_err_t ret = esp_commands_execute("my_cmd arg1 arg2", &cmd_ret, NULL, STDOUT_FILENO); +esp_err_t ret = esp_cli_commands_execute("my_cmd arg1 arg2", &cmd_ret, NULL, STDOUT_FILENO); ``` - `cmd_set`: Limits execution to a set of commands (or `NULL` for all commands). @@ -115,9 +115,9 @@ esp_err_t ret = esp_commands_execute("my_cmd arg1 arg2", &cmd_ret, NULL, STDOUT_ Completion & Help APIs: ```c -esp_commands_get_completion(NULL, "ec", completion_cb); -const char *hint = esp_commands_get_hint(NULL, "echo", &color, &bold); -const char *glossary = esp_commands_get_glossary(NULL, "echo"); +esp_cli_commands_get_completion(NULL, "ec", completion_cb); +const char *hint = esp_cli_commands_get_hint(NULL, "echo", &color, &bold); +const char *glossary = esp_cli_commands_get_glossary(NULL, "echo"); ``` - **Completion**: Suggests matching commands. @@ -132,15 +132,15 @@ Command sets allow grouping subsets of commands for filtering: ```c const char *cmd_names[] = {"echo", "my_cmd"}; -esp_command_set_handle_t set = - ESP_COMMANDS_CREATE_CMD_SET(cmd_names, FIELD_ACCESSOR(name)); +esp_cli_command_set_handle_t set = + ESP_CLI_COMMANDS_CREATE_CMD_SET(cmd_names, ESP_CLI_COMMAND_FIELD_ACCESSOR(name)); -esp_commands_execute("my_cmd", NULL, set, NULL); -esp_commands_destroy_cmd_set(&set); +esp_cli_commands_execute("my_cmd", NULL, set, NULL); +esp_cli_commands_destroy_cmd_set(&set); ``` - Create sets by name, group, or other fields. -- Concatenate sets with `esp_commands_concat_cmd_set()`. +- Concatenate sets with `esp_cli_commands_concat_cmd_set()`. - Destroy sets when no longer needed. --- @@ -149,7 +149,7 @@ esp_commands_destroy_cmd_set(&set); ```c #include -#include "esp_commands.h" +#include "esp_cli_commands.h" // Example command function static int hello_cmd(void *ctx, int argc, char **argv) { @@ -158,22 +158,22 @@ static int hello_cmd(void *ctx, int argc, char **argv) { } // Register command statically -ESP_COMMAND_REGISTER(hello_cmd, demo, "Prints a hello message", hello_cmd, NULL, NULL, NULL); +ESP_CLI_COMMAND_REGISTER(hello_cmd, demo, "Prints a hello message", hello_cmd, NULL, NULL, NULL); void app_main(void) { // Update configuration (optional) - esp_commands_config_t config = { + esp_cli_commands_config_t config = { .heap_caps_used = MALLOC_CAP_INTERNAL, .max_cmdline_length = 64, .max_cmdline_args = 4, .hint_color = 31, // Red foreground .hint_bold = true }; - esp_commands_update_config(&config); + esp_cli_commands_update_config(&config); // Execute command int ret_val; - esp_err_t ret = esp_commands_execute("hello_cmd", &ret_val, NULL, NULL); + esp_err_t ret = esp_cli_commands_execute("hello_cmd", &ret_val, NULL, NULL); if (ret == ESP_OK) { printf("Command executed successfully, return value: %d\n", ret_val); } else { @@ -186,10 +186,10 @@ void app_main(void) { ## API Reference -- **Configuration**: `esp_commands_update_config()` -- **Registration**: `esp_commands_register_cmd()`, `esp_commands_unregister_cmd()` -- **Execution**: `esp_commands_execute()`, `esp_commands_find_command()` -- **Completion & Help APIs**: `esp_commands_get_completion()`, `esp_commands_get_hint()`, `esp_commands_get_glossary()` -- **Command Sets**: `esp_commands_create_cmd_set()`, `esp_commands_concat_cmd_set()`, `esp_commands_destroy_cmd_set()` +- **Configuration**: `esp_cli_commands_update_config()` +- **Registration**: `esp_cli_commands_register_cmd()`, `esp_cli_commands_unregister_cmd()` +- **Execution**: `esp_cli_commands_execute()`, `esp_cli_commands_find_command()` +- **Completion & Help APIs**: `esp_cli_commands_get_completion()`, `esp_cli_commands_get_hint()`, `esp_cli_commands_get_glossary()` +- **Command Sets**: `esp_cli_commands_create_cmd_set()`, `esp_cli_commands_concat_cmd_set()`, `esp_cli_commands_destroy_cmd_set()` --- diff --git a/esp_commands/idf_component.yml b/esp_cli_commands/idf_component.yml similarity index 54% rename from esp_commands/idf_component.yml rename to esp_cli_commands/idf_component.yml index 6c79c44a35..8ee3cfb839 100644 --- a/esp_commands/idf_component.yml +++ b/esp_cli_commands/idf_component.yml @@ -1,9 +1,9 @@ version: "0.1.0" -description: "esp_commands - Command handling component" -url: https://github.com/espressif/idf-extra-components/tree/master/esp_commands +description: "esp_cli_commands - Command handling component" +url: https://github.com/espressif/idf-extra-components/tree/master/esp_cli_commands dependencies: idf: ">=5.3" sbom: manifests: - - path: sbom_esp_commands.yml + - path: sbom_esp_cli_commands.yml dest: . \ No newline at end of file diff --git a/esp_commands/include/esp_commands.h b/esp_cli_commands/include/esp_cli_commands.h similarity index 67% rename from esp_commands/include/esp_commands.h rename to esp_cli_commands/include/esp_cli_commands.h index e94e12ea1e..47bf8452b3 100644 --- a/esp_commands/include/esp_commands.h +++ b/esp_cli_commands/include/esp_cli_commands.h @@ -10,7 +10,7 @@ extern "C" { #endif #include -#include "esp_commands_utils.h" +#include "esp_cli_commands_utils.h" #include "esp_heap_caps.h" #include "esp_err.h" @@ -21,15 +21,15 @@ extern "C" { * @return ESP_OK if successful * ESP_ERR_INVALID_ARG if config pointer is NULL */ -esp_err_t esp_commands_update_config(const esp_commands_config_t *config); +esp_err_t esp_cli_commands_update_config(const esp_cli_commands_config_t *config); /** * @brief macro registering a command and placing it in a specific section of flash.rodata * @note see the linker.lf file for more information concerning the section characteristics */ -#define _ESP_COMMAND_REGISTER(cmd_name, cmd_group, cmd_help, cmd_func, cmd_func_ctx, cmd_hint_cb, cmd_glossary_cb) \ +#define _ESP_CLI_COMMAND_REGISTER(cmd_name, cmd_group, cmd_help, cmd_func, cmd_func_ctx, cmd_hint_cb, cmd_glossary_cb) \ static_assert((cmd_func) != NULL); \ - static const esp_command_t cmd_name __attribute__((used, section(".esp_commands" "." _ESP_REPL_STRINGIFY(cmd_name)), aligned(4))) = { \ + static const esp_cli_command_t cmd_name __attribute__((used, section(".esp_cli_commands" "." _ESP_REPL_STRINGIFY(cmd_name)), aligned(4))) = { \ .name = _ESP_REPL_STRINGIFY(cmd_name), \ .group = _ESP_REPL_STRINGIFY(cmd_group), \ .help = cmd_help, \ @@ -39,8 +39,8 @@ esp_err_t esp_commands_update_config(const esp_commands_config_t *config); .glossary_cb = cmd_glossary_cb \ }; -#define ESP_COMMAND_REGISTER(cmd_name, cmd_group, cmd_help, cmd_func, cmd_func_ctx, cmd_hint_cb, cmd_glossary_cb) \ - _ESP_COMMAND_REGISTER(cmd_name, cmd_group, cmd_help, cmd_func, cmd_func_ctx, cmd_hint_cb, cmd_glossary_cb) +#define ESP_CLI_COMMAND_REGISTER(cmd_name, cmd_group, cmd_help, cmd_func, cmd_func_ctx, cmd_hint_cb, cmd_glossary_cb) \ + _ESP_CLI_COMMAND_REGISTER(cmd_name, cmd_group, cmd_help, cmd_func, cmd_func_ctx, cmd_hint_cb, cmd_glossary_cb) /** * @brief Register a command @@ -49,7 +49,7 @@ esp_err_t esp_commands_update_config(const esp_commands_config_t *config); * @return ESP_OK if successful * Other esp_err_t on error */ -esp_err_t esp_commands_register_cmd(esp_command_t *cmd); +esp_err_t esp_cli_commands_register_cmd(esp_cli_command_t *cmd); /** * @brief Unregister a command by name or group @@ -58,7 +58,7 @@ esp_err_t esp_commands_register_cmd(esp_command_t *cmd); * @return ESP_OK if successful * Other esp_err_t on error */ -esp_err_t esp_commands_unregister_cmd(const char *cmd_name); +esp_err_t esp_cli_commands_unregister_cmd(const char *cmd_name); /** * @brief Execute a command line @@ -73,7 +73,7 @@ esp_err_t esp_commands_unregister_cmd(const char *cmd_name); * ESP_ERR_NOT_FOUND if command is not found in cmd_set * ESP_ERR_NO_MEM if internal memory allocation fails */ -esp_err_t esp_commands_execute(const char *cmdline, int *cmd_ret, esp_command_set_handle_t cmd_set, esp_commands_exec_arg_t *cmd_args); +esp_err_t esp_cli_commands_execute(const char *cmdline, int *cmd_ret, esp_cli_command_set_handle_t cmd_set, esp_cli_commands_exec_arg_t *cmd_args); /** * @brief Find a command by name within a specific command set. @@ -81,13 +81,13 @@ esp_err_t esp_commands_execute(const char *cmdline, int *cmd_ret, esp_command_se * This function searches a command whose name matches the provided string. * * @param cmd_set Handle to the command set to search in. Must be a valid - * `esp_command_set_handle_t` or `NULL` if the search should be performed + * `esp_cli_command_set_handle_t` or `NULL` if the search should be performed * on all statically and dynamically registered commands. * @param name String containing the name of the command to search for. * * @return pointer to the matching command or NULL if no command is found. */ -esp_command_t *esp_commands_find_command(esp_command_set_handle_t cmd_set, const char *name); +esp_cli_command_t *esp_cli_commands_find_command(esp_cli_command_set_handle_t cmd_set, const char *name); /** * @brief Provide command completion for linenoise library @@ -97,7 +97,7 @@ esp_command_t *esp_commands_find_command(esp_command_set_handle_t cmd_set, const * @param cb_ctx context passed to the completion callback * @param completion_cb Callback to return completed command names */ -void esp_commands_get_completion(esp_command_set_handle_t cmd_set, const char *buf, void *cb_ctx, esp_command_get_completion_t completion_cb); +void esp_cli_commands_get_completion(esp_cli_command_set_handle_t cmd_set, const char *buf, void *cb_ctx, esp_cli_command_get_completion_t completion_cb); /** * @brief Provide command hint for linenoise library @@ -108,7 +108,7 @@ void esp_commands_get_completion(esp_command_set_handle_t cmd_set, const char *b * @param[out] bold True if hint should be displayed in bold * @return Persistent string containing the hint; must not be freed */ -const char *esp_commands_get_hint(esp_command_set_handle_t cmd_set, const char *buf, int *color, bool *bold); +const char *esp_cli_commands_get_hint(esp_cli_command_set_handle_t cmd_set, const char *buf, int *color, bool *bold); /** * @brief Retrieve glossary for a command line @@ -117,17 +117,17 @@ const char *esp_commands_get_hint(esp_command_set_handle_t cmd_set, const char * * @param buf Command line typed by the user * @return Persistent string containing the glossary; must not be freed */ -const char *esp_commands_get_glossary(esp_command_set_handle_t cmd_set, const char *buf); +const char *esp_cli_commands_get_glossary(esp_cli_command_set_handle_t cmd_set, const char *buf); /** * @brief Create a command set from an array of command names * * @param cmd_set Array of command names * @param cmd_set_size Number of entries in cmd_set - * @param get_field Function to retrieve the field from esp_command_t for comparison + * @param get_field Function to retrieve the field from esp_cli_command_t for comparison * @return Handle to the created command set */ -esp_command_set_handle_t esp_commands_create_cmd_set(const char **cmd_set, const size_t cmd_set_size, esp_commands_get_field_t get_field); +esp_cli_command_set_handle_t esp_cli_commands_create_cmd_set(const char **cmd_set, const size_t cmd_set_size, esp_cli_commands_get_field_t get_field); /** * @brief Convenience macro to create a command set @@ -135,8 +135,8 @@ esp_command_set_handle_t esp_commands_create_cmd_set(const char **cmd_set, const * @param cmd_set Array of command names * @param accessor Field accessor function */ -#define ESP_COMMANDS_CREATE_CMD_SET(cmd_set, accessor) \ - esp_commands_create_cmd_set(cmd_set, sizeof(cmd_set) / sizeof((cmd_set)[0]), accessor) +#define ESP_CLI_COMMANDS_CREATE_CMD_SET(cmd_set, accessor) \ + esp_cli_commands_create_cmd_set(cmd_set, sizeof(cmd_set) / sizeof((cmd_set)[0]), accessor) /** * @brief Concatenate two command sets @@ -149,14 +149,14 @@ esp_command_set_handle_t esp_commands_create_cmd_set(const char **cmd_set, const * @param cmd_set_b Second command set * @return New command set containing all commands from both sets */ -esp_command_set_handle_t esp_commands_concat_cmd_set(esp_command_set_handle_t cmd_set_a, esp_command_set_handle_t cmd_set_b); +esp_cli_command_set_handle_t esp_cli_commands_concat_cmd_set(esp_cli_command_set_handle_t cmd_set_a, esp_cli_command_set_handle_t cmd_set_b); /** * @brief Destroy a command set * * @param cmd_set Pointer to the handle of the command set to destroy */ -void esp_commands_destroy_cmd_set(esp_command_set_handle_t *cmd_set); +void esp_cli_commands_destroy_cmd_set(esp_cli_command_set_handle_t *cmd_set); /** * @brief Split a command line and populate argc and argv parameters @@ -167,7 +167,7 @@ void esp_commands_destroy_cmd_set(esp_command_set_handle_t *cmd_set); * @return size_t number of arguments found in the line and stored * in argv */ -size_t esp_commands_split_argv(char *line, char **argv, size_t argv_size); +size_t esp_cli_commands_split_argv(char *line, char **argv, size_t argv_size); #ifdef __cplusplus } diff --git a/esp_commands/include/esp_commands_utils.h b/esp_cli_commands/include/esp_cli_commands_utils.h similarity index 71% rename from esp_commands/include/esp_commands_utils.h rename to esp_cli_commands/include/esp_cli_commands_utils.h index eca94db285..c831b4ebd8 100644 --- a/esp_commands/include/esp_commands_utils.h +++ b/esp_cli_commands/include/esp_cli_commands_utils.h @@ -23,7 +23,7 @@ extern "C" { * @param count Number of bytes to write. * @return Number of bytes written, or -1 on error. */ -typedef ssize_t (*esp_commands_write_t)(int fd, const void *buf, size_t count); +typedef ssize_t (*esp_cli_commands_write_t)(int fd, const void *buf, size_t count); /** * @brief Structure containing dynamic argument necessary for the @@ -34,11 +34,11 @@ typedef ssize_t (*esp_commands_write_t)(int fd, const void *buf, size_t count); * and what write function to use in order to print data to the expected * destination. */ -typedef struct esp_commands_exec_arg { +typedef struct esp_cli_commands_exec_arg { int out_fd; /*!< file descriptor that the command function has to use to print data in the environment it was called from */ - esp_commands_write_t write_func; /*!< write function the command function has to use to print data in the environment it was called from */ + esp_cli_commands_write_t write_func; /*!< write function the command function has to use to print data in the environment it was called from */ void *dynamic_ctx; /*!< dynamic context passed to the command function */ -} esp_commands_exec_arg_t; +} esp_cli_commands_exec_arg_t; /** * @brief Console command main function type with user context @@ -51,7 +51,7 @@ typedef struct esp_commands_exec_arg { * @param argv Array of argc entries, each pointing to a null-terminated string argument * @return Return code of the console command; 0 indicates success */ -typedef int (*esp_command_func_t)(void *context, esp_commands_exec_arg_t *cmd_arg, int argc, char **argv); +typedef int (*esp_cli_command_func_t)(void *context, esp_cli_commands_exec_arg_t *cmd_arg, int argc, char **argv); /** * @brief Callback to generate a command hint @@ -62,7 +62,7 @@ typedef int (*esp_command_func_t)(void *context, esp_commands_exec_arg_t *cmd_ar * @param context Context registered when the command was registered * @return Persistent string containing the generated hint */ -typedef const char *(*esp_command_hint_t)(void *context); +typedef const char *(*esp_cli_command_hint_t)(void *context); /** * @brief Callback to generate a command glossary entry @@ -73,7 +73,7 @@ typedef const char *(*esp_command_hint_t)(void *context); * @param context Context registered when the command was registered * @return Persistent string containing the generated glossary */ -typedef const char *(*esp_command_glossary_t)(void *context); +typedef const char *(*esp_cli_command_glossary_t)(void *context); /** * @brief Structure describing a console command @@ -81,26 +81,26 @@ typedef const char *(*esp_command_glossary_t)(void *context); * @note The `group` field allows categorizing commands into groups, * which can simplify filtering or listing commands. */ -typedef struct esp_command { +typedef struct esp_cli_command { const char *name; /*!< Name of the command */ const char *group; /*!< Command group to which this command belongs */ const char *help; /*!< Short help text for the command */ - esp_command_func_t func; /*!< Function implementing the command */ + esp_cli_command_func_t func; /*!< Function implementing the command */ void *func_ctx; /*!< User-defined context for the command function */ - esp_command_hint_t hint_cb; /*!< Callback returning the hint for the command */ - esp_command_glossary_t glossary_cb; /*!< Callback returning the glossary for the command */ -} esp_command_t; + esp_cli_command_hint_t hint_cb; /*!< Callback returning the hint for the command */ + esp_cli_command_glossary_t glossary_cb; /*!< Callback returning the glossary for the command */ +} esp_cli_command_t; /** - * @brief Configuration parameters for esp_commands_manager initialization + * @brief Configuration parameters for esp_cli_commands_manager initialization */ -typedef struct esp_commands_config { +typedef struct esp_cli_commands_config { uint32_t heap_caps_used; /*!< Set of heap capabilities to be used to perform internal allocations */ size_t max_cmdline_length; /*!< Maximum length of the command line buffer, in bytes */ size_t max_cmdline_args; /*!< Maximum number of command line arguments to parse */ int hint_color; /*!< ANSI color code used for hint text */ bool hint_bold; /*!< If true, display hint text in bold */ -} esp_commands_config_t; +} esp_cli_commands_config_t; /** * @brief Callback for a completed command name @@ -110,29 +110,29 @@ typedef struct esp_commands_config { * @param cb_ctx Opaque pointer pointing at the context passed to the callback * @param completed_cmd_name Completed command name */ -typedef void (*esp_command_get_completion_t)(void *cb_ctx, const char *completed_cmd_name); +typedef void (*esp_cli_command_get_completion_t)(void *cb_ctx, const char *completed_cmd_name); /** - * @brief Callback to retrieve a string field of esp_command_t + * @brief Callback to retrieve a string field of esp_cli_command_t * * @param cmd Command object * @return Value of the requested string field */ -typedef const char *(*esp_commands_get_field_t)(const esp_command_t *cmd); +typedef const char *(*esp_cli_commands_get_field_t)(const esp_cli_command_t *cmd); /** * @brief Opaque handle to a set of commands */ -typedef struct esp_command_sets *esp_command_set_handle_t; +typedef struct esp_cli_command_sets *esp_cli_command_set_handle_t; /** - * @brief Macro to define a forced inline accessor for a string field of esp_command_t + * @brief Macro to define a forced inline accessor for a string field of esp_cli_command_t * - * @param NAME Field name of the esp_command_t structure + * @param NAME Field name of the esp_cli_command_t structure */ #define DEFINE_FIELD_ACCESSOR(NAME) \ static inline __attribute__((always_inline)) \ - const char *get_##NAME(const esp_command_t *cmd) { \ + const char *get_##NAME(const esp_cli_command_t *cmd) { \ if (!cmd) { \ return NULL; \ } \ @@ -141,7 +141,7 @@ typedef struct esp_command_sets *esp_command_set_handle_t; /** * @brief Macro expanding to - * static inline __attribute__((always_inline)) const char *get_name(esp_command_t *cmd) { + * static inline __attribute__((always_inline)) const char *get_name(esp_cli_command_t *cmd) { * if (!cmd) { * return NULL; * } @@ -152,7 +152,7 @@ DEFINE_FIELD_ACCESSOR(name) /** * @brief Macro expanding to - * static inline __attribute__((always_inline)) const char *get_group(esp_command_t *cmd) { + * static inline __attribute__((always_inline)) const char *get_group(esp_cli_command_t *cmd) { * if (!cmd) { * return NULL; * } @@ -163,7 +163,7 @@ DEFINE_FIELD_ACCESSOR(group) /** * @brief Macro expanding to - * static inline __attribute__((always_inline)) const char *get_help(esp_command_t *cmd) { + * static inline __attribute__((always_inline)) const char *get_help(esp_cli_command_t *cmd) { * if (!cmd) { * return NULL; * } @@ -173,13 +173,13 @@ DEFINE_FIELD_ACCESSOR(group) DEFINE_FIELD_ACCESSOR(help) /** - * @brief Macro to create the accessor function name for a field of esp_command_t + * @brief Macro to create the accessor function name for a field of esp_cli_command_t * - * @note Those accessor functions are defined in esp_commands_internal.h + * @note Those accessor functions are defined in esp_cli_commands_internal.h * - * @param NAME Field name of esp_command_t + * @param NAME Field name of esp_cli_command_t */ -#define FIELD_ACCESSOR(NAME) get_##NAME +#define ESP_CLI_COMMAND_FIELD_ACCESSOR(NAME) get_##NAME #ifdef __cplusplus } diff --git a/esp_cli_commands/linker.lf b/esp_cli_commands/linker.lf new file mode 100644 index 0000000000..428c872a33 --- /dev/null +++ b/esp_cli_commands/linker.lf @@ -0,0 +1,13 @@ +[sections:esp_cli_commands] +entries: + .esp_cli_commands+ + +[scheme:esp_cli_commands_default] +entries: + esp_cli_commands -> flash_rodata + +[mapping:esp_cli_commands] +archive: * +entries: + * (esp_cli_commands_default); + esp_cli_commands -> flash_rodata KEEP() SORT(name) SURROUND(esp_cli_commands) diff --git a/esp_cli_commands/linux/esp_cli_commands.ld b/esp_cli_commands/linux/esp_cli_commands.ld new file mode 100644 index 0000000000..324abb009c --- /dev/null +++ b/esp_cli_commands/linux/esp_cli_commands.ld @@ -0,0 +1,10 @@ +SECTIONS +{ + .esp_cli_commands : + { + PROVIDE(_esp_cli_commands_start = .); + KEEP(*(SORT(.esp_cli_commands*))) /* Concatenate all .esp_cli_commands */ + PROVIDE(_esp_cli_commands_end = .); + } +} +INSERT AFTER .rodata; diff --git a/esp_commands/private_include/esp_commands_internal.h b/esp_cli_commands/private_include/esp_cli_commands_internal.h similarity index 80% rename from esp_commands/private_include/esp_commands_internal.h rename to esp_cli_commands/private_include/esp_cli_commands_internal.h index 641a065a64..a544071467 100644 --- a/esp_commands/private_include/esp_commands_internal.h +++ b/esp_cli_commands/private_include/esp_cli_commands_internal.h @@ -16,12 +16,12 @@ extern "C" { * * @note This function uses heap_caps_malloc together * with the set of capabilities provided by the user in the - * config structure. Implemented in esp_commands.c + * config structure. Implemented in esp_cli_commands.c * * @param malloc_size * @return void* */ -void *esp_commands_malloc(const size_t malloc_size); +void *esp_cli_commands_malloc(const size_t malloc_size); #ifdef __cplusplus } diff --git a/esp_commands/private_include/esp_dynamic_commands.h b/esp_cli_commands/private_include/esp_cli_dynamic_commands.h similarity index 70% rename from esp_commands/private_include/esp_dynamic_commands.h rename to esp_cli_commands/private_include/esp_cli_dynamic_commands.h index 1b9547ac47..3c3e0f44c6 100644 --- a/esp_commands/private_include/esp_dynamic_commands.h +++ b/esp_cli_commands/private_include/esp_cli_dynamic_commands.h @@ -14,49 +14,49 @@ extern "C" { #include "sys/queue.h" #include "freertos/FreeRTOS.h" #include "freertos/semphr.h" -#include "esp_commands.h" +#include "esp_cli_commands.h" /** * @brief Structure representing a fixed set of commands. * * This is typically used for static or predefined command lists. */ -typedef struct esp_command_set { - esp_command_t **cmd_ptr_set; /*!< Array of pointers to commands. */ +typedef struct esp_cli_command_set { + esp_cli_command_t **cmd_ptr_set; /*!< Array of pointers to commands. */ size_t cmd_set_size; /*!< Number of commands in the set. */ -} esp_command_set_t; +} esp_cli_command_set_t; /** * @brief Internal structure for a dynamically registered command. * - * Each dynamic command is stored as an `esp_command_t` plus + * Each dynamic command is stored as an `esp_cli_command_t` plus * linked list metadata for insertion/removal. */ -typedef struct esp_command_internal { - esp_command_t cmd; /*!< Command instance. */ - SLIST_ENTRY(esp_command_internal) next_item; /*!< Linked list entry metadata. */ -} esp_command_internal_t; +typedef struct esp_cli_command_internal { + esp_cli_command_t cmd; /*!< Command instance. */ + SLIST_ENTRY(esp_cli_command_internal) next_item; /*!< Linked list entry metadata. */ +} esp_cli_command_internal_t; /** * @brief Linked list head type for dynamic command storage. */ -typedef SLIST_HEAD(esp_command_internal_ll, esp_command_internal) esp_command_internal_ll_t; +typedef SLIST_HEAD(esp_cli_command_internal_ll, esp_cli_command_internal) esp_cli_command_internal_ll_t; /** * @brief Iterate over a set of commands, either from a static set or dynamic list. * * This macro supports iterating over: - * - A provided `esp_command_set_t` (static set), OR + * - A provided `esp_cli_command_set_t` (static set), OR * - The global dynamic command list if `cmd_set` is `NULL`. * - * @param cmd_set Pointer to a command set (`esp_command_set_t`) or `NULL` for dynamic commands. - * @param item_cmd Iterator variable of type `esp_command_t *` that will point to each command. + * @param cmd_set Pointer to a command set (`esp_cli_command_set_t`) or `NULL` for dynamic commands. + * @param item_cmd Iterator variable of type `esp_cli_command_t *` that will point to each command. * * @note Internally, the macro uses `_node` and `_i` as hidden variables. */ #define FOR_EACH_DYNAMIC_COMMAND(cmd_set, item_cmd) \ - __attribute__((unused)) esp_command_internal_t *_node = \ - ((cmd_set) == NULL ? SLIST_FIRST(esp_dynamic_commands_get_list()) \ + __attribute__((unused)) esp_cli_command_internal_t *_node = \ + ((cmd_set) == NULL ? SLIST_FIRST(esp_cli_dynamic_commands_get_list()) \ : NULL); \ __attribute__((unused)) size_t _i = 0; \ for (; \ @@ -74,14 +74,14 @@ typedef SLIST_HEAD(esp_command_internal_ll, esp_command_internal) esp_command_in * This function must be called before modifying or iterating over * the dynamic command list to ensure thread safety. */ -void esp_dynamic_commands_lock(void); +void esp_cli_dynamic_commands_lock(void); /** * @brief Release the dynamic commands lock. * * Call this after operations on the dynamic command list are complete. */ -void esp_dynamic_commands_unlock(void); +void esp_cli_dynamic_commands_unlock(void); /** * @brief Get the internal linked list of dynamic commands. @@ -91,7 +91,7 @@ void esp_dynamic_commands_unlock(void); * @warning The returned list is internal; do not modify it directly. * Use provided API functions to modify dynamic commands. */ -const esp_command_internal_ll_t *esp_dynamic_commands_get_list(void); +const esp_cli_command_internal_ll_t *esp_cli_dynamic_commands_get_list(void); /** * @brief Add a new command to the dynamic command list. @@ -103,7 +103,7 @@ const esp_command_internal_ll_t *esp_dynamic_commands_get_list(void); * * @note The function acquires the lock internally. */ -esp_err_t esp_dynamic_commands_add(esp_command_t *cmd); +esp_err_t esp_cli_dynamic_commands_add(esp_cli_command_t *cmd); /** * @brief Replace an existing command in the dynamic command list. @@ -115,7 +115,7 @@ esp_err_t esp_dynamic_commands_add(esp_command_t *cmd); * - `ESP_OK` on success. * - Appropriate error code on failure. */ -esp_err_t esp_dynamic_commands_replace(esp_command_t *item_cmd); +esp_err_t esp_cli_dynamic_commands_replace(esp_cli_command_t *item_cmd); /** * @brief Remove a command from the dynamic command list. @@ -125,14 +125,14 @@ esp_err_t esp_dynamic_commands_replace(esp_command_t *item_cmd); * - `ESP_OK` on success. * - Appropriate error code on failure. */ -esp_err_t esp_dynamic_commands_remove(esp_command_t *item_cmd); +esp_err_t esp_cli_dynamic_commands_remove(esp_cli_command_t *item_cmd); /** * @brief Get the number of registered dynamic commands. * * @return The total number of dynamic commands currently registered. */ -size_t esp_dynamic_commands_get_number_of_cmd(void); +size_t esp_cli_dynamic_commands_get_number_of_cmd(void); #ifdef __cplusplus } diff --git a/esp_commands/sbom_esp_commands.yml b/esp_cli_commands/sbom_esp_cli_commands.yml similarity index 61% rename from esp_commands/sbom_esp_commands.yml rename to esp_cli_commands/sbom_esp_cli_commands.yml index f666a01477..b8e67b5858 100644 --- a/esp_commands/sbom_esp_commands.yml +++ b/esp_cli_commands/sbom_esp_cli_commands.yml @@ -1,6 +1,6 @@ -name: esp_commands +name: esp_cli_commands description: Command handling component -url: https://github.com/espressif/idf-extra-components/tree/master/esp_commands +url: https://github.com/espressif/idf-extra-components/tree/master/esp_cli_commands version: 1.0.0 -cpe: cpe:2.3:a:espressif:esp_commands:{}:*:*:*:*:*:*:* +cpe: cpe:2.3:a:espressif:esp_cli_commands:{}:*:*:*:*:*:*:* supplier: 'Organization: Espressif Systems' \ No newline at end of file diff --git a/esp_cli_commands/src/esp_cli_commands.c b/esp_cli_commands/src/esp_cli_commands.c new file mode 100644 index 0000000000..9940994e1e --- /dev/null +++ b/esp_cli_commands/src/esp_cli_commands.c @@ -0,0 +1,771 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" +#include "esp_heap_caps.h" +#include "esp_cli_commands.h" +#include "esp_cli_commands_internal.h" +#include "esp_cli_dynamic_commands.h" +#include "esp_err.h" + +/* Default foreground color */ +#define ANSI_COLOR_DEFAULT 39 + +/* static mutex used to protect access to the static configuration */ +static SemaphoreHandle_t s_esp_cli_commands_mutex = NULL; +static StaticSemaphore_t s_esp_cli_commands_mutex_buf; + +/* Pointers to the first and last command in the dedicated section. + * See linker.lf for detailed information about the section */ +extern esp_cli_command_t _esp_cli_commands_start; +extern esp_cli_command_t _esp_cli_commands_end; + +typedef struct esp_cli_command_sets { + esp_cli_command_set_t static_set; + esp_cli_command_set_t dynamic_set; +} esp_cli_command_sets_t; + +/** run-time configuration options */ +static esp_cli_commands_config_t s_config = { + .heap_caps_used = MALLOC_CAP_DEFAULT, + .hint_bold = false, + .hint_color = ANSI_COLOR_DEFAULT, + .max_cmdline_args = 32, + .max_cmdline_length = 256 +}; + +/** + * @brief go through all commands registered in the + * memory section starting at _esp_cli_commands_start + * and ending at _esp_cli_commands_end OR go through all + * the commands listed in cmd_set if not NULL + */ +#define FOR_EACH_STATIC_COMMAND(cmd_set, cmd) \ + for (size_t _i = 0; \ + ((cmd_set) == NULL \ + ? (((cmd) = &_esp_cli_commands_start + _i), \ + (&_esp_cli_commands_start + _i) < &_esp_cli_commands_end) \ + : (((cmd) = (cmd_set)->cmd_ptr_set[_i]), \ + _i < (cmd_set)->cmd_set_size)); \ + ++_i) + +/** + * @brief returns the number of commands registered + * in the .esp_cli_commands section + */ +#define ESP_CLI_COMMANDS_COUNT (size_t)(&_esp_cli_commands_end - &_esp_cli_commands_start) + +/** + * @brief Lock access to the s_config static structure + */ +static void esp_cli_commands_lock(void) +{ + if (s_esp_cli_commands_mutex == NULL) { + s_esp_cli_commands_mutex = xSemaphoreCreateMutexStatic(&s_esp_cli_commands_mutex_buf); + assert(s_esp_cli_commands_mutex != NULL); + } + xSemaphoreTake(s_esp_cli_commands_mutex, portMAX_DELAY); +} + +/** + * @brief Unlock access to the s_config static structure + */ +static void esp_cli_commands_unlock(void) +{ + xSemaphoreGive(s_esp_cli_commands_mutex); +} + +/** + * @brief check the location of the pointer to esp_cli_command_t + * + * @param cmd the pointer to the command to check + * @return true if the command was registered statically + * false if the command was registered dynamically + */ +static inline __attribute__((always_inline)) bool command_is_static(esp_cli_command_t *cmd) +{ + if (cmd >= &_esp_cli_commands_start && cmd <= &_esp_cli_commands_end) { + return true; + } + return false; +} + +typedef bool (*walker_t)(void *walker_ctx, esp_cli_command_t *cmd); +static inline __attribute__((always_inline)) +void go_through_commands(esp_cli_command_sets_t *cmd_sets, void *cmd_walker_ctx, walker_t cmd_walker) +{ + if (!cmd_walker) { + return; + } + + esp_cli_command_t *cmd = NULL; + bool continue_walk = false; + + /* cmd_sets is composed of 2 sets (static and dynamic). + * - If cmd_sets is NULL, go through all the statically AND dynamically registered commands. + * - If cmd_sets is not NULL and either the static or the dynamic set is empty, then the macros + * FOR_EACH_XX_COMMAND will not go through the whole list of static (resp. dynamic) commands but + * through the empty set, so no command will be walked. + */ + + esp_cli_command_set_t *static_set = cmd_sets ? &cmd_sets->static_set : NULL; + /* it is possible that the set is empty, in which case set static_set to NULL + * to prevent the for loop to try to access a list of commands pointer set to NULL */ + if (static_set && !static_set->cmd_ptr_set) { + static_set = NULL; + } + FOR_EACH_STATIC_COMMAND(static_set, cmd) { + continue_walk = cmd_walker(cmd_walker_ctx, cmd); + if (!continue_walk) { + return; + } + } + + esp_cli_command_set_t *dynamic_set = cmd_sets ? &cmd_sets->dynamic_set : NULL; + /* it is possible that the set is empty, in which case set dynamic_set to NULL + * to prevent the for loop to try to access a list of commands pointer set to NULL */ + if (dynamic_set && !dynamic_set->cmd_ptr_set) { + dynamic_set = NULL; + } + esp_cli_dynamic_commands_lock(); + FOR_EACH_DYNAMIC_COMMAND(dynamic_set, cmd) { + continue_walk = cmd_walker(cmd_walker_ctx, cmd); + if (!continue_walk) { + esp_cli_dynamic_commands_unlock(); + return; + } + } + esp_cli_dynamic_commands_unlock(); +} + +typedef struct find_cmd_ctx { + const char *name; /*!< the name to check commands against */ + esp_cli_command_t *cmd; /*!< the command matching the name */ +} find_cmd_ctx_t; + +static inline __attribute__((always_inline)) +bool compare_command_name(void *ctx, esp_cli_command_t *cmd) +{ + /* called by esp_cli_commands_find_command through go_through_commands, + * ctx cannot be NULL */ + find_cmd_ctx_t *cmd_ctx = (find_cmd_ctx_t *)ctx; + + /* called by go_through_commands, thus cmd cannot be NULL */ + if (strcmp(cmd->name, cmd_ctx->name) == 0) { + /* command found, store it in the ctx so esp_cli_commands_find_command + * can process it. Notify go_through_commands to stop the walk by + * returning false */ + cmd_ctx->cmd = cmd; + return false; + } + + /* command not matching with the name from the ctx, continue the walk */ + return true; +} + +void *esp_cli_commands_malloc(const size_t malloc_size) +{ + esp_cli_commands_lock(); + const uint32_t caps = s_config.heap_caps_used; + esp_cli_commands_unlock(); + + return heap_caps_malloc(malloc_size, caps); +} + +esp_err_t esp_cli_commands_update_config(const esp_cli_commands_config_t *config) +{ + if (!config || + (config->max_cmdline_args == 0) || + (config->max_cmdline_length == 0)) { + return ESP_ERR_INVALID_ARG; + } + + esp_cli_commands_lock(); + memcpy(&s_config, config, sizeof(s_config)); + + /* if the heap_caps_used field is set to 0, set + * it to MALLOC_CAP_DEFAULT */ + if (s_config.heap_caps_used == 0) { + s_config.heap_caps_used = MALLOC_CAP_DEFAULT; + } + esp_cli_commands_unlock(); + + return ESP_OK; +} + +esp_err_t esp_cli_commands_register_cmd(esp_cli_command_t *cmd) +{ + if (cmd == NULL || + (cmd->name == NULL || strchr(cmd->name, ' ') != NULL) || + (cmd->func == NULL)) { + return ESP_ERR_INVALID_ARG; + } + + /* try to find the command in the static and dynamic lists. + * if the dynamic list is empty, the mutex locking will fail + * in esp_cli_commands_find_command and the function will return after + * checking the static list only. */ + esp_cli_command_t *list_item_cmd = esp_cli_commands_find_command((esp_cli_command_sets_t *)NULL, cmd->name); + esp_err_t ret_val = ESP_FAIL; + if (!list_item_cmd) { + /* command with given name not found, it is a new command, we can allocate + * the list item and the command itself */ + ret_val = esp_cli_dynamic_commands_add(cmd); + } else if (command_is_static(list_item_cmd)) { + /* a command with matching name is found in the list of commands + * that were registered at runtime, in which case it cannot be + * replaced with the new command */ + ret_val = ESP_FAIL; + } else { + /* an item with matching name was found in the list of dynamically + * registered commands. Replace the command on spot with the new esp_cli_command_t. */ + ret_val = esp_cli_dynamic_commands_replace(cmd); + } + + return ret_val; +} + +esp_err_t esp_cli_commands_unregister_cmd(const char *cmd_name) +{ + /* only items dynamically registered can be unregistered. + * try to remove the item with the given name from the list + * of dynamically registered commands */ + esp_cli_command_t *cmd = esp_cli_commands_find_command((esp_cli_command_sets_t *)NULL, cmd_name); + if (!cmd) { + return ESP_ERR_NOT_FOUND; + } else if (command_is_static(cmd)) { + return ESP_ERR_INVALID_ARG; + } else { + return esp_cli_dynamic_commands_remove(cmd); + } +} + +esp_err_t esp_cli_commands_execute(const char *cmdline, int *cmd_ret, esp_cli_command_set_handle_t cmd_set, esp_cli_commands_exec_arg_t *cmd_args) +{ + esp_cli_commands_lock(); + const size_t copy_max_cmdline_args = s_config.max_cmdline_args; + const size_t opy_max_cmdline_length = s_config.max_cmdline_length; + esp_cli_commands_unlock(); + + /* the life time of those variables is not exceeding the scope of this function. Use the stack. */ + char *argv[copy_max_cmdline_args]; + memset(argv, 0x00, sizeof(argv)); + char tmp_line_buf[opy_max_cmdline_length]; + memset(tmp_line_buf, 0x00, sizeof(tmp_line_buf)); + + /* copy the raw command line into the temp buffer */ + strlcpy(tmp_line_buf, cmdline, opy_max_cmdline_length); + + /* parse and split the raw command line */ + size_t argc = esp_cli_commands_split_argv(tmp_line_buf, argv, copy_max_cmdline_args); + + if (argc == 0) { + return ESP_ERR_INVALID_ARG; + } + + /* try to find the command from the first argument in the command line */ + const esp_cli_command_t *cmd = NULL; + esp_cli_command_sets_t *temp_set = cmd_set; + bool is_cmd_help = false; + if (strcmp("help", argv[0]) == 0) { + /* set the set to NULL because the help is not in the set passed by the user + * since this command is registered by esp_cli_commands itself */ + temp_set = (esp_cli_command_sets_t *)NULL; + + /* keep in mind that the command being executed is the help. This is needed + * when calling the help command function, to pass a specific dynamic context */ + is_cmd_help = true; + } + cmd = esp_cli_commands_find_command(temp_set, argv[0]); + + if (cmd == NULL) { + return ESP_ERR_NOT_FOUND; + } + + if (cmd->func) { + if (is_cmd_help) { + esp_cli_commands_exec_arg_t help_args; + + /* reuse the out_fd and write_func received as parameter by esp_cli_commands_execute + * to allow the help command function to print information on the correct IO. Use + * default values in case the parameters provided are not set */ + help_args.out_fd = (cmd_args && cmd_args->out_fd != -1) ? cmd_args->out_fd : STDOUT_FILENO; + help_args.write_func = (cmd_args && cmd_args->write_func) ? cmd_args->write_func : write; + + /* the help command needs the cmd_set to be able to only print the help for commands + * in the user set of commands */ + help_args.dynamic_ctx = cmd_set; + + /* call the help command function with the specific dynamic context */ + *cmd_ret = (*cmd->func)(cmd->func_ctx, &help_args, argc, argv); + } else { + /* regular command function has to be called, just passed the cmd_args as provided + * to the esp_cli_commands_execute function */ + *cmd_ret = (*cmd->func)(cmd->func_ctx, cmd_args, argc, argv); + } + } + return ESP_OK; +} + +esp_cli_command_t *esp_cli_commands_find_command(esp_cli_command_set_handle_t cmd_set, const char *name) +{ + /* no need to check that cmd_set is NULL, if it is, then FOR_EACH_XX_COMMAND + * will go through all registered commands */ + if (!name) { + return NULL; + } + + find_cmd_ctx_t ctx = { .cmd = NULL, .name = name }; + go_through_commands(cmd_set, &ctx, compare_command_name); + + /* if command was found during the walk, cmd field will be populated with + * the command matching the name given in parameter, otherwise it will still + * be NULL (value set as default value above) */ + return ctx.cmd; +} +typedef struct create_cmd_set_ctx { + esp_cli_commands_get_field_t get_field; + const char *cmd_set_name; + esp_cli_command_t **static_cmd_ptrs; + size_t static_cmd_count; + esp_cli_command_t **dynamic_cmd_ptrs; + size_t dynamic_cmd_count; +} create_cmd_set_ctx_t; + +static inline __attribute__((always_inline)) +bool fill_temp_set_info(void *caller_ctx, esp_cli_command_t *cmd) +{ + /* called by esp_cli_commands_find_command through go_through_commands, + * ctx cannot be NULL */ + create_cmd_set_ctx_t *ctx = (create_cmd_set_ctx_t *)caller_ctx; + + /* called by go_through_commands, thus cmd cannot be NULL */ + if (strcmp(ctx->get_field(cmd), ctx->cmd_set_name) == 0) { + // it's a match, add the pointer to command to the cmd ptr set + if (command_is_static(cmd)) { + ctx->static_cmd_ptrs[ctx->static_cmd_count] = cmd; + ctx->static_cmd_count++; + } else { + ctx->dynamic_cmd_ptrs[ctx->dynamic_cmd_count] = cmd; + ctx->dynamic_cmd_count++; + } + } + + /* command not matching with the name from the ctx, continue the walk */ + return true; +} + +static inline __attribute__((always_inline)) +esp_err_t update_cmd_set_with_temp_info(esp_cli_command_set_t *cmd_set, size_t cmd_count, esp_cli_command_t **cmd_ptrs) +{ + if (cmd_count == 0) { + cmd_set->cmd_ptr_set = NULL; + cmd_set->cmd_set_size = 0; + } else { + const size_t alloc_cmd_ptrs_size = sizeof(esp_cli_command_t *) * cmd_count; + cmd_set->cmd_ptr_set = esp_cli_commands_malloc(alloc_cmd_ptrs_size); + if (!cmd_set->cmd_ptr_set) { + return ESP_ERR_NO_MEM; + } else { + /* copy the temp set of pointer in to the final destination */ + memcpy(cmd_set->cmd_ptr_set, cmd_ptrs, alloc_cmd_ptrs_size); + cmd_set->cmd_set_size = cmd_count; + } + } + return ESP_OK; +} + +esp_cli_command_set_handle_t esp_cli_commands_create_cmd_set(const char **cmd_set, const size_t cmd_set_size, esp_cli_commands_get_field_t get_field) +{ + if (!cmd_set || cmd_set_size == 0) { + return NULL; + } + + esp_cli_command_sets_t *cmd_ptr_sets = esp_cli_commands_malloc(sizeof(esp_cli_command_sets_t)); + if (!cmd_ptr_sets) { + return NULL; + } + + + esp_cli_command_t *static_cmd_ptrs_temp[ESP_CLI_COMMANDS_COUNT]; + esp_cli_command_t *dynamic_cmd_ptrs_temp[esp_cli_dynamic_commands_get_number_of_cmd()]; + create_cmd_set_ctx_t ctx = { + .cmd_set_name = NULL, + .get_field = get_field, + .static_cmd_ptrs = static_cmd_ptrs_temp, + .static_cmd_count = 0, + .dynamic_cmd_ptrs = dynamic_cmd_ptrs_temp, + .dynamic_cmd_count = 0 + }; + + /* populate the temporary cmd pointer sets */ + for (size_t i = 0; i < cmd_set_size; i++) { + ctx.cmd_set_name = cmd_set[i]; + go_through_commands(NULL, &ctx, fill_temp_set_info); + } + + /* if no static command was found, return a static set with 0 items in it */ + esp_err_t ret_val = update_cmd_set_with_temp_info(&cmd_ptr_sets->static_set, + ctx.static_cmd_count, + ctx.static_cmd_ptrs); + if (ret_val == ESP_ERR_NO_MEM) { + free(cmd_ptr_sets); + return NULL; + } + + /* if no dynamic command was found, return a dynamic set with 0 items in it */ + ret_val = update_cmd_set_with_temp_info(&cmd_ptr_sets->dynamic_set, + ctx.dynamic_cmd_count, + ctx.dynamic_cmd_ptrs); + if (ret_val == ESP_ERR_NO_MEM) { + free(cmd_ptr_sets->static_set.cmd_ptr_set); + free(cmd_ptr_sets); + return NULL; + } + + return (esp_cli_command_set_handle_t)cmd_ptr_sets; +} + +esp_cli_command_set_handle_t esp_cli_commands_concat_cmd_set(esp_cli_command_set_handle_t cmd_set_a, esp_cli_command_set_handle_t cmd_set_b) +{ + if (!cmd_set_a && !cmd_set_b) { + return NULL; + } else if (cmd_set_a && !cmd_set_b) { + return cmd_set_a; + } else if (!cmd_set_a && cmd_set_b) { + return cmd_set_b; + } + + /* Reaching this point, both cmd_set_a and cmd_set_b are set. + * Create a new cmd_set that can host the items from both sets, + * assign the items to the new set and free the input sets */ + esp_cli_command_sets_t *concat_cmd_sets = esp_cli_commands_malloc(sizeof(esp_cli_command_sets_t)); + if (!concat_cmd_sets) { + return NULL; + } + const size_t new_static_set_size = cmd_set_a->static_set.cmd_set_size + cmd_set_b->static_set.cmd_set_size; + concat_cmd_sets->static_set.cmd_ptr_set = calloc(new_static_set_size, sizeof(esp_cli_command_t *)); + if (!concat_cmd_sets->static_set.cmd_ptr_set) { + free(concat_cmd_sets); + return NULL; + } + + const size_t new_dynamic_set_size = cmd_set_a->dynamic_set.cmd_set_size + cmd_set_b->dynamic_set.cmd_set_size; + concat_cmd_sets->dynamic_set.cmd_ptr_set = calloc(new_dynamic_set_size, sizeof(esp_cli_command_t *)); + if (!concat_cmd_sets->static_set.cmd_ptr_set) { + free(concat_cmd_sets->static_set.cmd_ptr_set); + free(concat_cmd_sets); + return NULL; + } + + /* update the new cmd set sizes */ + concat_cmd_sets->static_set.cmd_set_size = new_static_set_size; + concat_cmd_sets->dynamic_set.cmd_set_size = new_dynamic_set_size; + + /* fill the list of command pointers */ + memcpy(concat_cmd_sets->static_set.cmd_ptr_set, + cmd_set_a->static_set.cmd_ptr_set, + sizeof(esp_cli_command_t *) * cmd_set_a->static_set.cmd_set_size); + memcpy(concat_cmd_sets->static_set.cmd_ptr_set + cmd_set_a->static_set.cmd_set_size, + cmd_set_b->static_set.cmd_ptr_set, + sizeof(esp_cli_command_t *) * cmd_set_b->static_set.cmd_set_size); + + memcpy(concat_cmd_sets->dynamic_set.cmd_ptr_set, + cmd_set_a->dynamic_set.cmd_ptr_set, + sizeof(esp_cli_command_t *) * cmd_set_a->dynamic_set.cmd_set_size); + memcpy(concat_cmd_sets->dynamic_set.cmd_ptr_set + cmd_set_a->dynamic_set.cmd_set_size, + cmd_set_b->dynamic_set.cmd_ptr_set, + sizeof(esp_cli_command_t *) * cmd_set_b->dynamic_set.cmd_set_size); + + esp_cli_commands_destroy_cmd_set(&cmd_set_a); + esp_cli_commands_destroy_cmd_set(&cmd_set_b); + + return (esp_cli_command_set_handle_t)concat_cmd_sets; +} + +void esp_cli_commands_destroy_cmd_set(esp_cli_command_set_handle_t *cmd_set) +{ + if (!cmd_set || !*cmd_set) { + return; + } + + if ((*cmd_set)->static_set.cmd_ptr_set) { + free((*cmd_set)->static_set.cmd_ptr_set); + } + + if ((*cmd_set)->dynamic_set.cmd_ptr_set) { + free((*cmd_set)->dynamic_set.cmd_ptr_set); + } + + free(*cmd_set); + *cmd_set = NULL; +} + +typedef struct call_completion_cb_ctx { + const char *buf; + const size_t buf_len; + void *cb_ctx; + esp_cli_command_get_completion_t completion_cb; +} call_completion_cb_ctx_t; + +static bool call_completion_cb(void *caller_ctx, esp_cli_command_t *cmd) +{ + call_completion_cb_ctx_t *ctx = (call_completion_cb_ctx_t *)caller_ctx; + + /* Check if command starts with buf */ + if (strncmp(ctx->buf, cmd->name, ctx->buf_len) == 0) { + ctx->completion_cb(ctx->cb_ctx, cmd->name); + } + return true; +} + +void esp_cli_commands_get_completion(esp_cli_command_set_handle_t cmd_set, const char *buf, void *cb_ctx, esp_cli_command_get_completion_t completion_cb) +{ + size_t len = strlen(buf); + if (len == 0) { + return; + } + + call_completion_cb_ctx_t ctx = { + .buf = buf, + .buf_len = len, + .cb_ctx = cb_ctx, + .completion_cb = completion_cb + }; + go_through_commands(cmd_set, &ctx, call_completion_cb); +} + +const char *esp_cli_commands_get_hint(esp_cli_command_set_handle_t cmd_set, const char *buf, int *color, bool *bold) +{ + esp_cli_commands_lock(); + *color = s_config.hint_color; + *bold = s_config.hint_bold; + esp_cli_commands_unlock(); + + esp_cli_command_t *cmd = esp_cli_commands_find_command(cmd_set, buf); + if (cmd && cmd->hint_cb != NULL) { + return cmd->hint_cb(cmd->func_ctx); + } + + return NULL; +} + +const char *esp_cli_commands_get_glossary(esp_cli_command_set_handle_t cmd_set, const char *buf) +{ + esp_cli_command_t *cmd = esp_cli_commands_find_command(cmd_set, buf); + if (cmd && cmd->glossary_cb != NULL) { + return cmd->glossary_cb(cmd->func_ctx); + } + + return NULL; +} + +/* -------------------------------------------------------------- */ +/* help command related code */ +/* -------------------------------------------------------------- */ + +#define ESP_CLI_COMMANDS_FD_PRINT(fd, write, fmt, ...) do { \ + esp_cli_commands_lock(); \ + char _buf[s_config.max_cmdline_length]; \ + esp_cli_commands_unlock(); \ + int _len = snprintf(_buf, sizeof(_buf), fmt, ##__VA_ARGS__); \ + if (_len > 0) { \ + ssize_t _ignored __attribute__((unused)); \ + _ignored = write(fd, _buf, \ + _len < (int)sizeof(_buf) ? _len : (int)sizeof(_buf) - 1); \ + } \ +} while (0) + +static void print_arg_help(esp_cli_commands_exec_arg_t *cmd_args, esp_cli_command_t *it) +{ + /* First line: command name and hint + * Pad all the hints to the same column + */ + ESP_CLI_COMMANDS_FD_PRINT(cmd_args->out_fd, cmd_args->write_func, "%s", it->name); + + const char *hint = NULL; + if (it->hint_cb) { + hint = it->hint_cb(it->func_ctx); + } + + if (hint) { + ESP_CLI_COMMANDS_FD_PRINT(cmd_args->out_fd, cmd_args->write_func, " %s\n", it->hint_cb(it->func_ctx)); + } else { + ESP_CLI_COMMANDS_FD_PRINT(cmd_args->out_fd, cmd_args->write_func, " -\n"); + } + + /* Second line: print help */ + /* TODO: replace the simple print with a function that + * replaces arg_print_formatted */ + if (it->help) { + ESP_CLI_COMMANDS_FD_PRINT(cmd_args->out_fd, cmd_args->write_func, " %s\n", it->help); + } else { + ESP_CLI_COMMANDS_FD_PRINT(cmd_args->out_fd, cmd_args->write_func, " -\n"); + } + + /* Third line: print the glossary*/ + const char *glossary = NULL; + if (it->glossary_cb) { + glossary = it->glossary_cb(it->func_ctx); + } + + if (glossary) { + ESP_CLI_COMMANDS_FD_PRINT(cmd_args->out_fd, cmd_args->write_func, " %s\n", it->glossary_cb(it->func_ctx)); + } else { + ESP_CLI_COMMANDS_FD_PRINT(cmd_args->out_fd, cmd_args->write_func, " -\n"); + } + + ESP_CLI_COMMANDS_FD_PRINT(cmd_args->out_fd, cmd_args->write_func, "\n"); +} + +static void print_arg_command(esp_cli_commands_exec_arg_t *cmd_args, esp_cli_command_t *it) +{ + ESP_CLI_COMMANDS_FD_PRINT(cmd_args->out_fd, cmd_args->write_func, "%-s", it->name); + if (it->hint_cb) { + const char *hint = it->hint_cb(it->func_ctx); + if (hint) { + ESP_CLI_COMMANDS_FD_PRINT(cmd_args->out_fd, cmd_args->write_func, " %s", it->hint_cb(it->func_ctx)); + } + } + + ESP_CLI_COMMANDS_FD_PRINT(cmd_args->out_fd, cmd_args->write_func, "\n"); +} + +typedef enum { + HELP_VERBOSE_LEVEL_0 = 0, + HELP_VERBOSE_LEVEL_1 = 1, + HELP_VERBOSE_LEVEL_MAX_NUM = 2 +} help_verbose_level_e; + +typedef void (*const fn_print_arg_t)(esp_cli_commands_exec_arg_t *cmd_args, esp_cli_command_t *); + +static fn_print_arg_t print_verbose_level_arr[HELP_VERBOSE_LEVEL_MAX_NUM] = { + print_arg_command, + print_arg_help, +}; + +typedef struct call_cmd_ctx { + esp_cli_commands_exec_arg_t *cmd_args; + help_verbose_level_e verbose_level; + const char *command_name; + bool command_found; +} call_cmd_ctx_t; + +static inline __attribute__((always_inline)) +bool call_command_funcs(void *caller_ctx, esp_cli_command_t *cmd) +{ + call_cmd_ctx_t *ctx = (call_cmd_ctx_t *)caller_ctx; + + if (!ctx->command_name) { + /* ctx->command_name is empty, print all commands */ + print_verbose_level_arr[ctx->verbose_level](ctx->cmd_args, cmd); + } else if (ctx->command_name && + (strcmp(ctx->command_name, cmd->name) == 0)) { + /* we found the command name, print the help and return */ + print_verbose_level_arr[ctx->verbose_level](ctx->cmd_args, cmd); + ctx->command_found = true; + return false; + } + + return true; +} + +static int help_command(void *context, esp_cli_commands_exec_arg_t *cmd_args, int argc, char **argv) +{ + (void)context; /* this is NULL and useless for the help command */ + + char *command_name = NULL; + help_verbose_level_e verbose_level = HELP_VERBOSE_LEVEL_1; + + /* argc can never be superior to 4 given than the format is: + * help cmd_name -v 0 */ + if (argc <= 0 || argc > 4) { + /* unknown issue, return error */ + ESP_CLI_COMMANDS_FD_PRINT(cmd_args->out_fd, cmd_args->write_func, "help: invalid number of arguments %d\n", argc); + return 1; + } + + esp_cli_command_sets_t *cmd_sets = (esp_cli_command_sets_t *)cmd_args->dynamic_ctx; + + if (argc > 1) { + /* more than 1 arg, figure out if only verbose level argument + * was passed and if a specific command was passed. + * start from the second argument since the first one is "help" */ + for (int i = 1; i < argc; i++) { + if ((strcmp(argv[i], "-v") == 0) || + (strcmp(argv[i], "--verbose") == 0)) { + /* check if the following argument is either 0, or 1 */ + if (i + 1 >= argc) { + /* format error, return with error */ + ESP_CLI_COMMANDS_FD_PRINT(cmd_args->out_fd, cmd_args->write_func, "help: arguments not provided in the right format\n"); + return 1; + } else if (strcmp(argv[i + 1], "0") == 0) { + verbose_level = 0; + } else if (strcmp(argv[i + 1], "1") == 0) { + verbose_level = 1; + } else { + /* wrong command format, return error */ + ESP_CLI_COMMANDS_FD_PRINT(cmd_args->out_fd, cmd_args->write_func, "help: invalid verbose level %s\n", argv[i + 1]); + return 1; + } + + /* we found the -v / --verbose, bump i to skip the value of + * the verbose argument since it was just parsed */ + i++; + } else { + /* the argument is not -v or --verbose, it is then the command name + * of which we should print the hint, store it for latter */ + command_name = argv[i]; + } + } + } + + /* at this point we should have figured out all the arguments of the help + * command. if command_name is NULL, then print all commands. if command_name + * is not NULL, find the command and only print the help for this command. if the + * command is not found, return with error */ + call_cmd_ctx_t ctx = { + .cmd_args = cmd_args, + .verbose_level = verbose_level, + .command_name = command_name, + .command_found = false + }; + go_through_commands(cmd_sets, &ctx, call_command_funcs); + + if (command_name && !ctx.command_found) { + ESP_CLI_COMMANDS_FD_PRINT(cmd_args->out_fd, cmd_args->write_func, "help: invalid command name %s\n", command_name); + return 1; + } + + return 0; +} + +static const char *get_help_hint(void *context) +{ + (void)context; + return "[] [-v <0|1>]"; +} + +static const char *get_help_glossary(void *context) +{ + (void)context; + return " Name of command\n" + " -v, --verbose <0|1> If specified, list console commands with given verbose level";; +} + +static const char help_str[] = "Print the summary of all registered commands if no arguments " + "are given, otherwise print summary of given command."; + +ESP_CLI_COMMAND_REGISTER(help, /* name of the heap command */ + help, /* group of the help command */ + help_str, /* help string of the help command */ + help_command, /* func */ + NULL, /* the context is null here, it will provided by the exec function */ + get_help_hint, /* hint callback */ + get_help_glossary); /* glossary callback */ diff --git a/esp_commands/src/esp_commands_helpers.c b/esp_cli_commands/src/esp_cli_commands_helpers.c similarity index 96% rename from esp_commands/src/esp_commands_helpers.c rename to esp_cli_commands/src/esp_cli_commands_helpers.c index b2ebf875eb..56abc4b8c4 100644 --- a/esp_commands/src/esp_commands_helpers.c +++ b/esp_cli_commands/src/esp_cli_commands_helpers.c @@ -8,7 +8,7 @@ #include #include #include -#include "esp_commands.h" +#include "esp_cli_commands.h" #define SS_FLAG_ESCAPE 0x8 @@ -32,7 +32,7 @@ typedef enum { state = SS_SPACE; \ } while(0) -size_t esp_commands_split_argv(char *line, char **argv, size_t argv_size) +size_t esp_cli_commands_split_argv(char *line, char **argv, size_t argv_size) { const int QUOTE = '"'; const int ESCAPE = '\\'; diff --git a/esp_cli_commands/src/esp_cli_dynamic_commands.c b/esp_cli_commands/src/esp_cli_dynamic_commands.c new file mode 100644 index 0000000000..b2bb55984c --- /dev/null +++ b/esp_cli_commands/src/esp_cli_dynamic_commands.c @@ -0,0 +1,124 @@ +/* + * SPDX-FileCopyrightText: 2016-2021 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" +#include "esp_cli_commands_internal.h" +#include "esp_cli_dynamic_commands.h" +#include "esp_cli_commands.h" + +#define CONTAINER_OF(ptr, type, member) \ + ((type *)((char *)(ptr) - offsetof(type, member))) + +static esp_cli_command_internal_ll_t s_dynamic_cmd_list = SLIST_HEAD_INITIALIZER(esp_cli_command_internal); +static size_t s_number_of_registered_commands = 0; +static SemaphoreHandle_t s_esp_cli_commands_dyn_mutex = NULL; +static StaticSemaphore_t s_esp_cli_commands_dyn_mutex_buf; + +void esp_cli_dynamic_commands_lock(void) +{ + /* check if the mutex needs to be initialized and initialized it only + * is requested in by the state of the create parameter */ + if (s_esp_cli_commands_dyn_mutex == NULL) { + s_esp_cli_commands_dyn_mutex = xSemaphoreCreateMutexStatic(&s_esp_cli_commands_dyn_mutex_buf); + assert(s_esp_cli_commands_dyn_mutex != NULL); + } + + xSemaphoreTake(s_esp_cli_commands_dyn_mutex, portMAX_DELAY); +} + +void esp_cli_dynamic_commands_unlock(void) +{ + if (s_esp_cli_commands_dyn_mutex == NULL) { + return; + } + xSemaphoreGive(s_esp_cli_commands_dyn_mutex); +} + +const esp_cli_command_internal_ll_t *esp_cli_dynamic_commands_get_list(void) +{ + return &s_dynamic_cmd_list; +} + +esp_err_t esp_cli_dynamic_commands_add(esp_cli_command_t *cmd) +{ + if (!cmd) { + return ESP_ERR_INVALID_ARG; + } + + esp_cli_command_internal_t *list_item = esp_cli_commands_malloc(sizeof(esp_cli_command_internal_t)); + if (!list_item) { + return ESP_ERR_NO_MEM; + } + + memcpy(&list_item->cmd, cmd, sizeof(esp_cli_command_t)); + + esp_cli_command_internal_t *last = NULL; + esp_cli_command_internal_t *it = NULL; + + /* this could be called on an empty list, make sure the + * mutex is initialized */ + esp_cli_dynamic_commands_lock(); + + SLIST_FOREACH(it, &s_dynamic_cmd_list, next_item) { + if (strcmp(it->cmd.name, list_item->cmd.name) > 0) { + break; + } + last = it; + } + + if (last == NULL) { + SLIST_INSERT_HEAD(&s_dynamic_cmd_list, list_item, next_item); + } else { + SLIST_INSERT_AFTER(last, list_item, next_item); + } + + s_number_of_registered_commands++; + + esp_cli_dynamic_commands_unlock(); + + return ESP_OK; +} + +esp_err_t esp_cli_dynamic_commands_replace(esp_cli_command_t *item_cmd) +{ + esp_cli_dynamic_commands_lock(); + + esp_cli_command_internal_t *list_item = CONTAINER_OF(item_cmd, esp_cli_command_internal_t, cmd); + memcpy(&list_item->cmd, item_cmd, sizeof(esp_cli_command_t)); + + esp_cli_dynamic_commands_unlock(); + + return ESP_OK; +} + +esp_err_t esp_cli_dynamic_commands_remove(esp_cli_command_t *item_cmd) +{ + esp_cli_dynamic_commands_lock(); + + esp_cli_command_internal_t *list_item = CONTAINER_OF(item_cmd, esp_cli_command_internal_t, cmd); + SLIST_REMOVE(&s_dynamic_cmd_list, list_item, esp_cli_command_internal, next_item); + + s_number_of_registered_commands--; + + esp_cli_dynamic_commands_unlock(); + + free(list_item); + + return ESP_OK; +} + +size_t esp_cli_dynamic_commands_get_number_of_cmd(void) +{ + esp_cli_dynamic_commands_lock(); + size_t nb_of_registered_cmd = s_number_of_registered_commands; + esp_cli_dynamic_commands_unlock(); + return nb_of_registered_cmd; +} diff --git a/esp_commands/test_apps/CMakeLists.txt b/esp_cli_commands/test_apps/CMakeLists.txt similarity index 51% rename from esp_commands/test_apps/CMakeLists.txt rename to esp_cli_commands/test_apps/CMakeLists.txt index 16e55d1d1d..20f9170ffb 100644 --- a/esp_commands/test_apps/CMakeLists.txt +++ b/esp_cli_commands/test_apps/CMakeLists.txt @@ -1,5 +1,5 @@ -cmake_minimum_required(VERSION 3.16) +cmake_minimum_required(VERSION 3.22) include($ENV{IDF_PATH}/tools/cmake/project.cmake) set(COMPONENTS main) -project(esp_commands_test) +project(esp_cli_commands_test) diff --git a/esp_commands/test_apps/main/CMakeLists.txt b/esp_cli_commands/test_apps/main/CMakeLists.txt similarity index 65% rename from esp_commands/test_apps/main/CMakeLists.txt rename to esp_cli_commands/test_apps/main/CMakeLists.txt index de2402ea64..056e7a6bc7 100644 --- a/esp_commands/test_apps/main/CMakeLists.txt +++ b/esp_cli_commands/test_apps/main/CMakeLists.txt @@ -1,4 +1,4 @@ -idf_component_register(SRCS "test_esp_commands.c" "test_main.c" +idf_component_register(SRCS "test_esp_cli_commands.c" "test_main.c" PRIV_INCLUDE_DIRS "." "include" PRIV_REQUIRES unity WHOLE_ARCHIVE) diff --git a/esp_commands/test_apps/main/idf_component.yml b/esp_cli_commands/test_apps/main/idf_component.yml similarity index 65% rename from esp_commands/test_apps/main/idf_component.yml rename to esp_cli_commands/test_apps/main/idf_component.yml index 1abe4b08c4..a3e8b33462 100644 --- a/esp_commands/test_apps/main/idf_component.yml +++ b/esp_cli_commands/test_apps/main/idf_component.yml @@ -1,4 +1,4 @@ dependencies: - espressif/esp_commands: + espressif/esp_cli_commands: version: "*" override_path: "../.." diff --git a/esp_commands/test_apps/main/include/test_esp_commands_utils.h b/esp_cli_commands/test_apps/main/include/test_esp_cli_commands_utils.h similarity index 57% rename from esp_commands/test_apps/main/include/test_esp_commands_utils.h rename to esp_cli_commands/test_apps/main/include/test_esp_cli_commands_utils.h index 624459d0de..e5ebab30e9 100644 --- a/esp_commands/test_apps/main/include/test_esp_commands_utils.h +++ b/esp_cli_commands/test_apps/main/include/test_esp_cli_commands_utils.h @@ -16,7 +16,7 @@ extern "C" { #define GET_STR(STR) #STR #define CREATE_CMD_FUNC(NAME) \ - static int GET_NAME(NAME, _func)(void *ctx, esp_commands_exec_arg_t *cmd_args, int argc, char **argv) { \ + static int GET_NAME(NAME, _func)(void *ctx, esp_cli_commands_exec_arg_t *cmd_args, int argc, char **argv) { \ printf(GET_STR(NAME) GET_STR(_func)); \ printf("\n"); \ return 0; \ @@ -58,14 +58,14 @@ CREATE_FUNC(cmd_g, _glossary) CREATE_FUNC(cmd_h, _glossary) /* command registration */ -ESP_COMMAND_REGISTER(cmd_a, group_1, GET_STR(cmd_a_help), cmd_a_func, NULL, cmd_a_hint, cmd_a_glossary); -ESP_COMMAND_REGISTER(cmd_b, group_1, GET_STR(cmd_b_help), cmd_b_func, NULL, cmd_b_hint, cmd_b_glossary); -ESP_COMMAND_REGISTER(cmd_c, group_2, GET_STR(cmd_c_help), cmd_c_func, NULL, cmd_c_hint, cmd_c_glossary); -ESP_COMMAND_REGISTER(cmd_d, group_2, GET_STR(cmd_d_help), cmd_d_func, NULL, cmd_d_hint, cmd_d_glossary); -ESP_COMMAND_REGISTER(cmd_e, group_3, GET_STR(cmd_e_help), cmd_e_func, NULL, cmd_e_hint, cmd_e_glossary); -ESP_COMMAND_REGISTER(cmd_f, group_3, GET_STR(cmd_f_help), cmd_f_func, NULL, cmd_f_hint, cmd_f_glossary); -ESP_COMMAND_REGISTER(cmd_g, group_4, GET_STR(cmd_g_help), cmd_g_func, NULL, cmd_g_hint, cmd_g_glossary); -ESP_COMMAND_REGISTER(cmd_h, group_4, GET_STR(cmd_h_help), cmd_h_func, NULL, cmd_h_hint, cmd_h_glossary); +ESP_CLI_COMMAND_REGISTER(cmd_a, group_1, GET_STR(cmd_a_help), cmd_a_func, NULL, cmd_a_hint, cmd_a_glossary); +ESP_CLI_COMMAND_REGISTER(cmd_b, group_1, GET_STR(cmd_b_help), cmd_b_func, NULL, cmd_b_hint, cmd_b_glossary); +ESP_CLI_COMMAND_REGISTER(cmd_c, group_2, GET_STR(cmd_c_help), cmd_c_func, NULL, cmd_c_hint, cmd_c_glossary); +ESP_CLI_COMMAND_REGISTER(cmd_d, group_2, GET_STR(cmd_d_help), cmd_d_func, NULL, cmd_d_hint, cmd_d_glossary); +ESP_CLI_COMMAND_REGISTER(cmd_e, group_3, GET_STR(cmd_e_help), cmd_e_func, NULL, cmd_e_hint, cmd_e_glossary); +ESP_CLI_COMMAND_REGISTER(cmd_f, group_3, GET_STR(cmd_f_help), cmd_f_func, NULL, cmd_f_hint, cmd_f_glossary); +ESP_CLI_COMMAND_REGISTER(cmd_g, group_4, GET_STR(cmd_g_help), cmd_g_func, NULL, cmd_g_hint, cmd_g_glossary); +ESP_CLI_COMMAND_REGISTER(cmd_h, group_4, GET_STR(cmd_h_help), cmd_h_func, NULL, cmd_h_hint, cmd_h_glossary); #ifdef __cplusplus } diff --git a/esp_commands/test_apps/main/test_esp_commands.c b/esp_cli_commands/test_apps/main/test_esp_cli_commands.c similarity index 58% rename from esp_commands/test_apps/main/test_esp_commands.c rename to esp_cli_commands/test_apps/main/test_esp_cli_commands.c index d0f5ee6858..b6f9ff1930 100644 --- a/esp_commands/test_apps/main/test_esp_commands.c +++ b/esp_cli_commands/test_apps/main/test_esp_cli_commands.c @@ -10,12 +10,12 @@ #include #include "unity.h" #include "esp_heap_caps.h" -#include "esp_commands.h" -#include "test_esp_commands_utils.h" +#include "esp_cli_commands.h" +#include "test_esp_cli_commands_utils.h" /* * IMPORTANT: - * - 8 commands are created in test_esp_commands_utils.h (cmd_a - cmd_h) + * - 8 commands are created in test_esp_cli_commands_utils.h (cmd_a - cmd_h) * - the commands are divided in 4 groups (group_1 - group_4) * - each group contains 2 commands. * - group_1 contains cmd_a and cmd_b, @@ -25,84 +25,84 @@ static void test_setup(void) { - const esp_commands_config_t config = { + const esp_cli_commands_config_t config = { .heap_caps_used = MALLOC_CAP_DEFAULT, .hint_bold = false, .hint_color = 39, .max_cmdline_args = 32, .max_cmdline_length = 256 }; - TEST_ASSERT_EQUAL(ESP_OK, esp_commands_update_config(&config)); + TEST_ASSERT_EQUAL(ESP_OK, esp_cli_commands_update_config(&config)); } -TEST_CASE("help command - called without command set", "[esp_commands]") +TEST_CASE("help command - called without command set", "[esp_cli_commands]") { test_setup(); - /* call esp_commands_execute to run help command with verbosity 0 */ + /* call esp_cli_commands_execute to run help command with verbosity 0 */ int cmd_ret = -1; - TEST_ASSERT_EQUAL(ESP_OK, esp_commands_execute("help -v 0", &cmd_ret, NULL, NULL)); + TEST_ASSERT_EQUAL(ESP_OK, esp_cli_commands_execute("help -v 0", &cmd_ret, NULL, NULL)); TEST_ASSERT_EQUAL(0, cmd_ret); - /* call esp_commands_execute to run help command with verbosity 1 */ + /* call esp_cli_commands_execute to run help command with verbosity 1 */ cmd_ret = -1; - TEST_ASSERT_EQUAL(ESP_OK, esp_commands_execute("help -v 1", &cmd_ret, NULL, NULL)); + TEST_ASSERT_EQUAL(ESP_OK, esp_cli_commands_execute("help -v 1", &cmd_ret, NULL, NULL)); TEST_ASSERT_EQUAL(0, cmd_ret); - /* call esp_commands_execute to run help command on a registered command */ + /* call esp_cli_commands_execute to run help command on a registered command */ cmd_ret = -1; - TEST_ASSERT_EQUAL(ESP_OK, esp_commands_execute("help cmd_a -v 0", &cmd_ret, NULL, NULL)); + TEST_ASSERT_EQUAL(ESP_OK, esp_cli_commands_execute("help cmd_a -v 0", &cmd_ret, NULL, NULL)); TEST_ASSERT_EQUAL(0, cmd_ret); - TEST_ASSERT_EQUAL(ESP_OK, esp_commands_execute("help cmd_a -v 1", &cmd_ret, NULL, NULL)); + TEST_ASSERT_EQUAL(ESP_OK, esp_cli_commands_execute("help cmd_a -v 1", &cmd_ret, NULL, NULL)); TEST_ASSERT_EQUAL(0, cmd_ret); - /* call esp_commands_execute to run help command on an unregistered command */ + /* call esp_cli_commands_execute to run help command on an unregistered command */ cmd_ret = -1; - TEST_ASSERT_EQUAL(ESP_OK, esp_commands_execute("help cmd_w", &cmd_ret, NULL, NULL)); + TEST_ASSERT_EQUAL(ESP_OK, esp_cli_commands_execute("help cmd_w", &cmd_ret, NULL, NULL)); TEST_ASSERT_EQUAL(1, cmd_ret); - /* call esp_commands_execute to run help command on a registered command with wrong + /* call esp_cli_commands_execute to run help command on a registered command with wrong * verbosity syntax */ cmd_ret = -1; - TEST_ASSERT_EQUAL(ESP_OK, esp_commands_execute("help cmd_a -v=1", &cmd_ret, NULL, NULL)); + TEST_ASSERT_EQUAL(ESP_OK, esp_cli_commands_execute("help cmd_a -v=1", &cmd_ret, NULL, NULL)); TEST_ASSERT_EQUAL(1, cmd_ret); - /* call esp_commands_execute to run help command with too many command names */ + /* call esp_cli_commands_execute to run help command with too many command names */ cmd_ret = -1; - TEST_ASSERT_EQUAL(ESP_OK, esp_commands_execute("help cmd_a cmd_b -v 1", &cmd_ret, NULL, NULL)); + TEST_ASSERT_EQUAL(ESP_OK, esp_cli_commands_execute("help cmd_a cmd_b -v 1", &cmd_ret, NULL, NULL)); TEST_ASSERT_EQUAL(1, cmd_ret); } -TEST_CASE("test command set error handling", "[esp_commands]") +TEST_CASE("test command set error handling", "[esp_cli_commands]") { test_setup(); /* create a command set with NULL passed as list of command id */ - TEST_ASSERT_NULL(esp_commands_create_cmd_set(NULL, 2, FIELD_ACCESSOR(group))); + TEST_ASSERT_NULL(esp_cli_commands_create_cmd_set(NULL, 2, ESP_CLI_COMMAND_FIELD_ACCESSOR(group))); /* create a command set with 0 as size of list of command id */ const char *group_set_a[] = {"b", "group_4"}; - TEST_ASSERT_NULL(esp_commands_create_cmd_set(group_set_a, 0, FIELD_ACCESSOR(group))); + TEST_ASSERT_NULL(esp_cli_commands_create_cmd_set(group_set_a, 0, ESP_CLI_COMMAND_FIELD_ACCESSOR(group))); /* concatenate 2 NULL sets */ - TEST_ASSERT_NULL(esp_commands_concat_cmd_set(NULL, NULL)); + TEST_ASSERT_NULL(esp_cli_commands_concat_cmd_set(NULL, NULL)); - /* redefinition of esp_command_set_t so we can access the fields + /* redefinition of esp_cli_command_set_t so we can access the fields * and test their values */ typedef struct cmd_set { - esp_command_t **cmd_ptr_set; + esp_cli_command_t **cmd_ptr_set; size_t cmd_set_size; } cmd_set_t; /* pass wrong command name in array, expect a non null command set handle with 0 items in it*/ const char *group_set_b[] = {"group2", "group4"}; - esp_command_set_handle_t group_set_handle_b = esp_commands_create_cmd_set(group_set_b, 2, FIELD_ACCESSOR(group)); + esp_cli_command_set_handle_t group_set_handle_b = esp_cli_commands_create_cmd_set(group_set_b, 2, ESP_CLI_COMMAND_FIELD_ACCESSOR(group)); cmd_set_t *cmd_set = (cmd_set_t *)group_set_handle_b; TEST_ASSERT_NOT_NULL(group_set_handle_b); TEST_ASSERT_NULL(cmd_set->cmd_ptr_set); TEST_ASSERT_EQUAL(0, cmd_set->cmd_set_size); - esp_commands_destroy_cmd_set(&group_set_handle_b); + esp_cli_commands_destroy_cmd_set(&group_set_handle_b); } typedef struct cmd_test_sequence { @@ -110,17 +110,17 @@ typedef struct cmd_test_sequence { int expected_ret_val[NB_OF_REGISTERED_CMD]; } cmd_test_sequence_t; -static void run_cmd_test(esp_command_set_handle_t handle, const char **cmd_list, const int *expected_ret_val, size_t nb_cmds) +static void run_cmd_test(esp_cli_command_set_handle_t handle, const char **cmd_list, const int *expected_ret_val, size_t nb_cmds) { for (size_t i = 0; i < nb_cmds; i++) { int cmd_ret = -1; esp_err_t expected = expected_ret_val[i] == 0 ? ESP_OK : ESP_ERR_NOT_FOUND; - TEST_ASSERT_EQUAL(expected, esp_commands_execute(cmd_list[i], &cmd_ret, handle, NULL)); + TEST_ASSERT_EQUAL(expected, esp_cli_commands_execute(cmd_list[i], &cmd_ret, handle, NULL)); TEST_ASSERT_EQUAL(expected_ret_val[i], cmd_ret); } } -TEST_CASE("test static command set", "[esp_commands]") +TEST_CASE("test static command set", "[esp_cli_commands]") { test_setup(); @@ -130,11 +130,11 @@ TEST_CASE("test static command set", "[esp_commands]") /* create sets by group */ const char *group_set_a[] = {"group_1", "group_3"}; - esp_command_set_handle_t handle_set_a = ESP_COMMANDS_CREATE_CMD_SET(group_set_a, FIELD_ACCESSOR(group)); + esp_cli_command_set_handle_t handle_set_a = ESP_CLI_COMMANDS_CREATE_CMD_SET(group_set_a, ESP_CLI_COMMAND_FIELD_ACCESSOR(group)); TEST_ASSERT_NOT_NULL(handle_set_a); const char *group_set_b[] = {"group_2", "group_4"}; - esp_command_set_handle_t handle_set_b = ESP_COMMANDS_CREATE_CMD_SET(group_set_b, FIELD_ACCESSOR(group)); + esp_cli_command_set_handle_t handle_set_b = ESP_CLI_COMMANDS_CREATE_CMD_SET(group_set_b, ESP_CLI_COMMAND_FIELD_ACCESSOR(group)); TEST_ASSERT_NOT_NULL(handle_set_b); /* test set_a by group */ @@ -149,22 +149,22 @@ TEST_CASE("test static command set", "[esp_commands]") /* test help command with set of static commands */ int cmd_ret; - TEST_ASSERT_EQUAL(ESP_OK, esp_commands_execute("help", &cmd_ret, handle_set_a, NULL)); + TEST_ASSERT_EQUAL(ESP_OK, esp_cli_commands_execute("help", &cmd_ret, handle_set_a, NULL)); TEST_ASSERT_EQUAL(0, cmd_ret); - TEST_ASSERT_EQUAL(ESP_OK, esp_commands_execute("help", &cmd_ret, handle_set_b, NULL)); + TEST_ASSERT_EQUAL(ESP_OK, esp_cli_commands_execute("help", &cmd_ret, handle_set_b, NULL)); TEST_ASSERT_EQUAL(0, cmd_ret); /* destroy sets */ - esp_commands_destroy_cmd_set(&handle_set_a); - esp_commands_destroy_cmd_set(&handle_set_b); + esp_cli_commands_destroy_cmd_set(&handle_set_a); + esp_cli_commands_destroy_cmd_set(&handle_set_b); /* create sets by name */ const char *cmd_name_set_a[] = {"cmd_a", "cmd_b", "cmd_c"}; - handle_set_a = esp_commands_create_cmd_set(cmd_name_set_a, 3, FIELD_ACCESSOR(name)); + handle_set_a = esp_cli_commands_create_cmd_set(cmd_name_set_a, 3, ESP_CLI_COMMAND_FIELD_ACCESSOR(name)); TEST_ASSERT_NOT_NULL(handle_set_a); const char *cmd_name_set_b[] = {"cmd_f", "cmd_g", "cmd_h"}; - handle_set_b = esp_commands_create_cmd_set(cmd_name_set_b, 3, FIELD_ACCESSOR(name)); + handle_set_b = esp_cli_commands_create_cmd_set(cmd_name_set_b, 3, ESP_CLI_COMMAND_FIELD_ACCESSOR(name)); TEST_ASSERT_NOT_NULL(handle_set_b); int tmp_ret2[] = {0, 0, 0, -1, -1, -1, -1, -1}; @@ -176,17 +176,17 @@ TEST_CASE("test static command set", "[esp_commands]") run_cmd_test(handle_set_b, cmd_list, expected_ret_val, nb_cmds); /* concatenate sets */ - esp_command_set_handle_t handle_set_c = esp_commands_concat_cmd_set(handle_set_a, handle_set_b); + esp_cli_command_set_handle_t handle_set_c = esp_cli_commands_concat_cmd_set(handle_set_a, handle_set_b); TEST_ASSERT_NOT_NULL(handle_set_c); int tmp_ret4[] = {0, 0, 0, -1, -1, 0, 0, 0}; memcpy(expected_ret_val, tmp_ret4, sizeof(tmp_ret4)); run_cmd_test(handle_set_c, cmd_list, expected_ret_val, nb_cmds); - esp_commands_destroy_cmd_set(&handle_set_c); + esp_cli_commands_destroy_cmd_set(&handle_set_c); } -static int dummy_cmd_func(void *context, esp_commands_exec_arg_t *cmd_args, int argc, char **argv) +static int dummy_cmd_func(void *context, esp_cli_commands_exec_arg_t *cmd_args, int argc, char **argv) { (void)cmd_args; (void)context; @@ -194,7 +194,7 @@ static int dummy_cmd_func(void *context, esp_commands_exec_arg_t *cmd_args, int return 0; // always return success } -TEST_CASE("test dynamic command set", "[esp_commands]") +TEST_CASE("test dynamic command set", "[esp_cli_commands]") { test_setup(); @@ -204,7 +204,7 @@ TEST_CASE("test dynamic command set", "[esp_commands]") /* dynamically register commands */ for (size_t i = 0; i < nb_cmds; i++) { - esp_command_t cmd = { + esp_cli_command_t cmd = { .name = cmd_list[i], .group = (i % 2 == 0) ? "group_a" : "group_b", .help = "dummy help", @@ -213,12 +213,12 @@ TEST_CASE("test dynamic command set", "[esp_commands]") .hint_cb = NULL, .glossary_cb = NULL }; - TEST_ASSERT_EQUAL(ESP_OK, esp_commands_register_cmd(&cmd)); + TEST_ASSERT_EQUAL(ESP_OK, esp_cli_commands_register_cmd(&cmd)); } /* test execution by group_a */ const char *group_set[] = {"group_a"}; - esp_command_set_handle_t handle_set_1 = ESP_COMMANDS_CREATE_CMD_SET(group_set, FIELD_ACCESSOR(group)); + esp_cli_command_set_handle_t handle_set_1 = ESP_CLI_COMMANDS_CREATE_CMD_SET(group_set, ESP_CLI_COMMAND_FIELD_ACCESSOR(group)); TEST_ASSERT_NOT_NULL(handle_set_1); int tmp_ret[] = {0, -1, 0, -1, 0, -1, 0, -1}; @@ -227,7 +227,7 @@ TEST_CASE("test dynamic command set", "[esp_commands]") /* test execution by command name */ const char *cmd_name_set[] = {"cmd_1", "cmd_2", "cmd_3"}; - esp_command_set_handle_t handle_set_2 = esp_commands_create_cmd_set(cmd_name_set, 3, FIELD_ACCESSOR(name)); + esp_cli_command_set_handle_t handle_set_2 = esp_cli_commands_create_cmd_set(cmd_name_set, 3, ESP_CLI_COMMAND_FIELD_ACCESSOR(name)); TEST_ASSERT_NOT_NULL(handle_set_2); int tmp_ret2[] = {0, 0, 0, -1, -1, -1, -1, -1}; @@ -236,22 +236,22 @@ TEST_CASE("test dynamic command set", "[esp_commands]") /* test help command with set of dynamic commands */ int cmd_ret; - TEST_ASSERT_EQUAL(ESP_OK, esp_commands_execute("help", &cmd_ret, handle_set_1, NULL)); + TEST_ASSERT_EQUAL(ESP_OK, esp_cli_commands_execute("help", &cmd_ret, handle_set_1, NULL)); TEST_ASSERT_EQUAL(0, cmd_ret); - TEST_ASSERT_EQUAL(ESP_OK, esp_commands_execute("help", &cmd_ret, handle_set_2, NULL)); + TEST_ASSERT_EQUAL(ESP_OK, esp_cli_commands_execute("help", &cmd_ret, handle_set_2, NULL)); TEST_ASSERT_EQUAL(0, cmd_ret); /* unregister dynamically registered commands */ for (size_t i = 0; i < nb_cmds; i++) { - TEST_ASSERT_EQUAL(ESP_OK, esp_commands_unregister_cmd(cmd_list[i])); + TEST_ASSERT_EQUAL(ESP_OK, esp_cli_commands_unregister_cmd(cmd_list[i])); } - esp_commands_destroy_cmd_set(&handle_set_1); - esp_commands_destroy_cmd_set(&handle_set_2); + esp_cli_commands_destroy_cmd_set(&handle_set_1); + esp_cli_commands_destroy_cmd_set(&handle_set_2); } -TEST_CASE("test static and dynamic command sets", "[esp_commands]") +TEST_CASE("test static and dynamic command sets", "[esp_cli_commands]") { test_setup(); @@ -260,7 +260,7 @@ TEST_CASE("test static and dynamic command sets", "[esp_commands]") const size_t nb_dyn_cmds = sizeof(dyn_cmd_list) / sizeof(dyn_cmd_list[0]); for (size_t i = 0; i < nb_dyn_cmds; i++) { - esp_command_t cmd = { + esp_cli_command_t cmd = { .name = dyn_cmd_list[i], .group = (i % 2 == 0) ? "group_a" : "group_b", .help = "dummy help", @@ -269,21 +269,21 @@ TEST_CASE("test static and dynamic command sets", "[esp_commands]") .hint_cb = NULL, .glossary_cb = NULL }; - TEST_ASSERT_EQUAL(ESP_OK, esp_commands_register_cmd(&cmd)); + TEST_ASSERT_EQUAL(ESP_OK, esp_cli_commands_register_cmd(&cmd)); } // --- create static command sets (already registered statically) --- const char *static_groups[] = {"group_1", "group_3"}; - esp_command_set_handle_t handle_static_set = ESP_COMMANDS_CREATE_CMD_SET(static_groups, FIELD_ACCESSOR(group)); + esp_cli_command_set_handle_t handle_static_set = ESP_CLI_COMMANDS_CREATE_CMD_SET(static_groups, ESP_CLI_COMMAND_FIELD_ACCESSOR(group)); TEST_ASSERT_NOT_NULL(handle_static_set); // --- create dynamic command sets --- const char *dyn_groups[] = {"group_a"}; - esp_command_set_handle_t handle_dynamic_set = ESP_COMMANDS_CREATE_CMD_SET(dyn_groups, FIELD_ACCESSOR(group)); + esp_cli_command_set_handle_t handle_dynamic_set = ESP_CLI_COMMANDS_CREATE_CMD_SET(dyn_groups, ESP_CLI_COMMAND_FIELD_ACCESSOR(group)); TEST_ASSERT_NOT_NULL(handle_dynamic_set); // --- combine static and dynamic sets --- - esp_command_set_handle_t handle_combined_set = esp_commands_concat_cmd_set(handle_static_set, handle_dynamic_set); + esp_cli_command_set_handle_t handle_combined_set = esp_cli_commands_concat_cmd_set(handle_static_set, handle_dynamic_set); TEST_ASSERT_NOT_NULL(handle_combined_set); // --- run tests for combined set --- @@ -298,14 +298,14 @@ TEST_CASE("test static and dynamic command sets", "[esp_commands]") /* test help command with set of dynamic commands */ int cmd_ret; - TEST_ASSERT_EQUAL(ESP_OK, esp_commands_execute("help", &cmd_ret, handle_combined_set, NULL)); + TEST_ASSERT_EQUAL(ESP_OK, esp_cli_commands_execute("help", &cmd_ret, handle_combined_set, NULL)); TEST_ASSERT_EQUAL(0, cmd_ret); // --- cleanup --- - esp_commands_destroy_cmd_set(&handle_combined_set); + esp_cli_commands_destroy_cmd_set(&handle_combined_set); for (size_t i = 0; i < nb_dyn_cmds; i++) { - TEST_ASSERT_EQUAL(ESP_OK, esp_commands_unregister_cmd(dyn_cmd_list[i])); + TEST_ASSERT_EQUAL(ESP_OK, esp_cli_commands_unregister_cmd(dyn_cmd_list[i])); } } @@ -316,17 +316,17 @@ static void test_completion_cb(void *cb_ctx, const char *completed_cmd_name) completion_nb_of_calls++; } -TEST_CASE("test completion callback", "[esp_commands]") +TEST_CASE("test completion callback", "[esp_cli_commands]") { test_setup(); /* create sets by group */ const char *set_a[] = {"group_1", "group_3"}; - esp_command_set_handle_t handle_set_a = ESP_COMMANDS_CREATE_CMD_SET(set_a, FIELD_ACCESSOR(group)); + esp_cli_command_set_handle_t handle_set_a = ESP_CLI_COMMANDS_CREATE_CMD_SET(set_a, ESP_CLI_COMMAND_FIELD_ACCESSOR(group)); TEST_ASSERT_NOT_NULL(handle_set_a); /* register a command dynamically and add it to the set */ - esp_command_t cmd = { + esp_cli_command_t cmd = { .name = "dyn_cmd", .group = "dyn_cmd_group", .help = "dummy help", @@ -335,45 +335,45 @@ TEST_CASE("test completion callback", "[esp_commands]") .hint_cb = NULL, .glossary_cb = NULL }; - TEST_ASSERT_EQUAL(ESP_OK, esp_commands_register_cmd(&cmd)); + TEST_ASSERT_EQUAL(ESP_OK, esp_cli_commands_register_cmd(&cmd)); const char *set_b[] = {"dyn_cmd"}; - esp_command_set_handle_t handle_set_b = ESP_COMMANDS_CREATE_CMD_SET(set_b, FIELD_ACCESSOR(name)); + esp_cli_command_set_handle_t handle_set_b = ESP_CLI_COMMANDS_CREATE_CMD_SET(set_b, ESP_CLI_COMMAND_FIELD_ACCESSOR(name)); TEST_ASSERT_NOT_NULL(handle_set_b); - esp_command_set_handle_t handle_concat_set = esp_commands_concat_cmd_set(handle_set_a, handle_set_b); + esp_cli_command_set_handle_t handle_concat_set = esp_cli_commands_concat_cmd_set(handle_set_a, handle_set_b); TEST_ASSERT_NOT_NULL(handle_concat_set); - esp_commands_get_completion(NULL, "a", NULL, test_completion_cb); + esp_cli_commands_get_completion(NULL, "a", NULL, test_completion_cb); TEST_ASSERT_EQUAL(0, completion_nb_of_calls); - esp_commands_get_completion(handle_concat_set, "cmd_", NULL, test_completion_cb); + esp_cli_commands_get_completion(handle_concat_set, "cmd_", NULL, test_completion_cb); TEST_ASSERT_EQUAL(4, completion_nb_of_calls); /* reset the cb counter */ completion_nb_of_calls = 0; - esp_commands_get_completion(NULL, "cmd_", NULL, test_completion_cb); + esp_cli_commands_get_completion(NULL, "cmd_", NULL, test_completion_cb); TEST_ASSERT_EQUAL(8, completion_nb_of_calls); /* reset the cb counter */ completion_nb_of_calls = 0; - esp_commands_get_completion(NULL, "dyn", NULL, test_completion_cb); + esp_cli_commands_get_completion(NULL, "dyn", NULL, test_completion_cb); TEST_ASSERT_EQUAL(1, completion_nb_of_calls); /* reset the cb counter */ completion_nb_of_calls = 0; - esp_commands_get_completion(handle_concat_set, "dyn", NULL, test_completion_cb); + esp_cli_commands_get_completion(handle_concat_set, "dyn", NULL, test_completion_cb); TEST_ASSERT_EQUAL(1, completion_nb_of_calls); /* reset the cb counter */ completion_nb_of_calls = 0; - esp_commands_destroy_cmd_set(&handle_concat_set); + esp_cli_commands_destroy_cmd_set(&handle_concat_set); TEST_ASSERT_NULL(handle_concat_set); - esp_commands_unregister_cmd("dyn_cmd"); + esp_cli_commands_unregister_cmd("dyn_cmd"); } typedef struct hint_cb_ctx { @@ -392,14 +392,14 @@ static const char *test_glossary_cb(void *context) return ctx->message; } -TEST_CASE("test hint and glossary callbacks", "[esp_commands]") +TEST_CASE("test hint and glossary callbacks", "[esp_cli_commands]") { test_setup(); hint_cb_ctx_t ctx_a = { .message = "msg_a" }; hint_cb_ctx_t ctx_b = { .message = "msg_b" }; - esp_command_t cmd_a = { + esp_cli_command_t cmd_a = { .name = "dyn_cmd_a", .group = "dyn_cmd_group", .help = "dummy help", @@ -408,9 +408,9 @@ TEST_CASE("test hint and glossary callbacks", "[esp_commands]") .hint_cb = test_hint_cb, .glossary_cb = test_glossary_cb }; - TEST_ASSERT_EQUAL(ESP_OK, esp_commands_register_cmd(&cmd_a)); + TEST_ASSERT_EQUAL(ESP_OK, esp_cli_commands_register_cmd(&cmd_a)); - esp_command_t cmd_b = { + esp_cli_command_t cmd_b = { .name = "dyn_cmd_b", .group = "dyn_cmd_group", .help = "dummy help", @@ -419,44 +419,44 @@ TEST_CASE("test hint and glossary callbacks", "[esp_commands]") .hint_cb = test_hint_cb, .glossary_cb = test_glossary_cb }; - TEST_ASSERT_EQUAL(ESP_OK, esp_commands_register_cmd(&cmd_b)); + TEST_ASSERT_EQUAL(ESP_OK, esp_cli_commands_register_cmd(&cmd_b)); bool bold = true; int color = 0; - const char *dyn_cmd_a_msg_hint = esp_commands_get_hint(NULL, "dyn_cmd_a", &color, &bold); + const char *dyn_cmd_a_msg_hint = esp_cli_commands_get_hint(NULL, "dyn_cmd_a", &color, &bold); TEST_ASSERT_EQUAL(0, strcmp(dyn_cmd_a_msg_hint, ctx_a.message)); TEST_ASSERT_EQUAL(false, bold); /* bold set a false by default in the component config */ TEST_ASSERT_EQUAL(39, color); /* color set to 39 by default in the component config */ - const char *dyn_cmd_b_msg_hint = esp_commands_get_hint(NULL, "dyn_cmd_b", &color, &bold); + const char *dyn_cmd_b_msg_hint = esp_cli_commands_get_hint(NULL, "dyn_cmd_b", &color, &bold); TEST_ASSERT_EQUAL(0, strcmp(dyn_cmd_b_msg_hint, ctx_b.message)); - const char *dyn_cmd_a_msg_glossary = esp_commands_get_glossary(NULL, "dyn_cmd_a"); + const char *dyn_cmd_a_msg_glossary = esp_cli_commands_get_glossary(NULL, "dyn_cmd_a"); TEST_ASSERT_EQUAL(0, strcmp(dyn_cmd_a_msg_glossary, ctx_a.message)); - const char *dyn_cmd_b_msg_glossary = esp_commands_get_glossary(NULL, "dyn_cmd_b"); + const char *dyn_cmd_b_msg_glossary = esp_cli_commands_get_glossary(NULL, "dyn_cmd_b"); TEST_ASSERT_EQUAL(0, strcmp(dyn_cmd_b_msg_glossary, ctx_b.message)); /* create a set with only dyn_cmd_a and check that the hint cb is called for * dyn_cmd_a but not for dyn_cmd_b */ const char *set[] = {"dyn_cmd_a"}; - esp_command_set_handle_t handle_set = ESP_COMMANDS_CREATE_CMD_SET(set, FIELD_ACCESSOR(name)); + esp_cli_command_set_handle_t handle_set = ESP_CLI_COMMANDS_CREATE_CMD_SET(set, ESP_CLI_COMMAND_FIELD_ACCESSOR(name)); TEST_ASSERT_NOT_NULL(handle_set); - const char *dyn_cmd_a_msg_hint_bis = esp_commands_get_hint(handle_set, "dyn_cmd_a", &color, &bold); + const char *dyn_cmd_a_msg_hint_bis = esp_cli_commands_get_hint(handle_set, "dyn_cmd_a", &color, &bold); TEST_ASSERT_EQUAL(0, strcmp(dyn_cmd_a_msg_hint_bis, ctx_a.message)); - const char *dyn_cmd_b_msg_hint_bis = esp_commands_get_hint(handle_set, "dyn_cmd_b", &color, &bold); + const char *dyn_cmd_b_msg_hint_bis = esp_cli_commands_get_hint(handle_set, "dyn_cmd_b", &color, &bold); TEST_ASSERT_NULL(dyn_cmd_b_msg_hint_bis); - const char *dyn_cmd_a_msg_glossary_bis = esp_commands_get_glossary(handle_set, "dyn_cmd_a"); + const char *dyn_cmd_a_msg_glossary_bis = esp_cli_commands_get_glossary(handle_set, "dyn_cmd_a"); TEST_ASSERT_EQUAL(0, strcmp(dyn_cmd_a_msg_glossary_bis, ctx_a.message)); - const char *dyn_cmd_b_msg_glossary_bis = esp_commands_get_glossary(handle_set, "dyn_cmd_b"); + const char *dyn_cmd_b_msg_glossary_bis = esp_cli_commands_get_glossary(handle_set, "dyn_cmd_b"); TEST_ASSERT_NULL(dyn_cmd_b_msg_glossary_bis); - TEST_ASSERT_EQUAL(ESP_OK, esp_commands_unregister_cmd("dyn_cmd_a")); - TEST_ASSERT_EQUAL(ESP_OK, esp_commands_unregister_cmd("dyn_cmd_b")); + TEST_ASSERT_EQUAL(ESP_OK, esp_cli_commands_unregister_cmd("dyn_cmd_a")); + TEST_ASSERT_EQUAL(ESP_OK, esp_cli_commands_unregister_cmd("dyn_cmd_b")); - esp_commands_destroy_cmd_set(&handle_set); + esp_cli_commands_destroy_cmd_set(&handle_set); } diff --git a/esp_commands/test_apps/main/test_main.c b/esp_cli_commands/test_apps/main/test_main.c similarity index 87% rename from esp_commands/test_apps/main/test_main.c rename to esp_cli_commands/test_apps/main/test_main.c index 01c2785d05..b53563861b 100644 --- a/esp_commands/test_apps/main/test_main.c +++ b/esp_cli_commands/test_apps/main/test_main.c @@ -21,6 +21,6 @@ void tearDown(void) void app_main(void) { - printf("Running esp_commands component tests\n"); + printf("Running esp_cli_commands component tests\n"); unity_run_menu(); } diff --git a/esp_commands/test_apps/pytest_esp_commands.py b/esp_cli_commands/test_apps/pytest_esp_cli_commands.py similarity index 91% rename from esp_commands/test_apps/pytest_esp_commands.py rename to esp_cli_commands/test_apps/pytest_esp_cli_commands.py index eba4d4ffd7..0744b77a48 100644 --- a/esp_commands/test_apps/pytest_esp_commands.py +++ b/esp_cli_commands/test_apps/pytest_esp_cli_commands.py @@ -11,5 +11,5 @@ reason="Skip the idf version that did not build" ) @idf_parametrize('target', ['linux', 'esp32'], indirect=['target']) -def test_esp_commands(dut) -> None: +def test_esp_cli_commands(dut) -> None: dut.run_all_single_board_cases() diff --git a/esp_commands/test_apps/sdkconfig.defaults b/esp_cli_commands/test_apps/sdkconfig.defaults similarity index 100% rename from esp_commands/test_apps/sdkconfig.defaults rename to esp_cli_commands/test_apps/sdkconfig.defaults diff --git a/esp_commands/linker.lf b/esp_commands/linker.lf deleted file mode 100644 index d18a3bca76..0000000000 --- a/esp_commands/linker.lf +++ /dev/null @@ -1,13 +0,0 @@ -[sections:esp_commands] -entries: - .esp_commands+ - -[scheme:esp_commands_default] -entries: - esp_commands -> flash_rodata - -[mapping:esp_commands] -archive: * -entries: - * (esp_commands_default); - esp_commands -> flash_rodata KEEP() SORT(name) SURROUND(esp_commands) diff --git a/esp_commands/linux/esp_commands.ld b/esp_commands/linux/esp_commands.ld deleted file mode 100644 index d61d347254..0000000000 --- a/esp_commands/linux/esp_commands.ld +++ /dev/null @@ -1,10 +0,0 @@ -SECTIONS -{ - .esp_commands : - { - PROVIDE(_esp_commands_start = .); - KEEP(*(SORT(.esp_commands*))) /* Concatenate all .esp_commands */ - PROVIDE(_esp_commands_end = .); - } -} -INSERT AFTER .rodata; diff --git a/esp_commands/private_include/esp_cli_dynamic_commands.h b/esp_commands/private_include/esp_cli_dynamic_commands.h new file mode 100644 index 0000000000..3c3e0f44c6 --- /dev/null +++ b/esp_commands/private_include/esp_cli_dynamic_commands.h @@ -0,0 +1,139 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include "sys/queue.h" +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" +#include "esp_cli_commands.h" + +/** + * @brief Structure representing a fixed set of commands. + * + * This is typically used for static or predefined command lists. + */ +typedef struct esp_cli_command_set { + esp_cli_command_t **cmd_ptr_set; /*!< Array of pointers to commands. */ + size_t cmd_set_size; /*!< Number of commands in the set. */ +} esp_cli_command_set_t; + +/** + * @brief Internal structure for a dynamically registered command. + * + * Each dynamic command is stored as an `esp_cli_command_t` plus + * linked list metadata for insertion/removal. + */ +typedef struct esp_cli_command_internal { + esp_cli_command_t cmd; /*!< Command instance. */ + SLIST_ENTRY(esp_cli_command_internal) next_item; /*!< Linked list entry metadata. */ +} esp_cli_command_internal_t; + +/** + * @brief Linked list head type for dynamic command storage. + */ +typedef SLIST_HEAD(esp_cli_command_internal_ll, esp_cli_command_internal) esp_cli_command_internal_ll_t; + +/** + * @brief Iterate over a set of commands, either from a static set or dynamic list. + * + * This macro supports iterating over: + * - A provided `esp_cli_command_set_t` (static set), OR + * - The global dynamic command list if `cmd_set` is `NULL`. + * + * @param cmd_set Pointer to a command set (`esp_cli_command_set_t`) or `NULL` for dynamic commands. + * @param item_cmd Iterator variable of type `esp_cli_command_t *` that will point to each command. + * + * @note Internally, the macro uses `_node` and `_i` as hidden variables. + */ +#define FOR_EACH_DYNAMIC_COMMAND(cmd_set, item_cmd) \ + __attribute__((unused)) esp_cli_command_internal_t *_node = \ + ((cmd_set) == NULL ? SLIST_FIRST(esp_cli_dynamic_commands_get_list()) \ + : NULL); \ + __attribute__((unused)) size_t _i = 0; \ + for (; \ + ((cmd_set) == NULL \ + ? ((_node != NULL) && ((item_cmd) = &_node->cmd)) \ + : (_i < (cmd_set)->cmd_set_size && \ + ((item_cmd) = (cmd_set)->cmd_ptr_set[_i]))); \ + ((cmd_set) == NULL \ + ? (_node = SLIST_NEXT(_node, next_item)) \ + : (void)++_i)) + +/** + * @brief Acquire the dynamic commands lock. + * + * This function must be called before modifying or iterating over + * the dynamic command list to ensure thread safety. + */ +void esp_cli_dynamic_commands_lock(void); + +/** + * @brief Release the dynamic commands lock. + * + * Call this after operations on the dynamic command list are complete. + */ +void esp_cli_dynamic_commands_unlock(void); + +/** + * @brief Get the internal linked list of dynamic commands. + * + * @return Pointer to the dynamic command linked list head. + * + * @warning The returned list is internal; do not modify it directly. + * Use provided API functions to modify dynamic commands. + */ +const esp_cli_command_internal_ll_t *esp_cli_dynamic_commands_get_list(void); + +/** + * @brief Add a new command to the dynamic command list. + * + * @param cmd Pointer to the command to add. + * @return + * - `ESP_OK` on success. + * - Appropriate error code on failure. + * + * @note The function acquires the lock internally. + */ +esp_err_t esp_cli_dynamic_commands_add(esp_cli_command_t *cmd); + +/** + * @brief Replace an existing command in the dynamic command list. + * + * If a command with the same name exists, it will be replaced. + * + * @param item_cmd Pointer to the new command data. + * @return + * - `ESP_OK` on success. + * - Appropriate error code on failure. + */ +esp_err_t esp_cli_dynamic_commands_replace(esp_cli_command_t *item_cmd); + +/** + * @brief Remove a command from the dynamic command list. + * + * @param item_cmd Pointer to the command to remove. + * @return + * - `ESP_OK` on success. + * - Appropriate error code on failure. + */ +esp_err_t esp_cli_dynamic_commands_remove(esp_cli_command_t *item_cmd); + +/** + * @brief Get the number of registered dynamic commands. + * + * @return The total number of dynamic commands currently registered. + */ +size_t esp_cli_dynamic_commands_get_number_of_cmd(void); + +#ifdef __cplusplus +} +#endif diff --git a/esp_commands/src/esp_commands.c b/esp_commands/src/esp_cli_commands.c similarity index 72% rename from esp_commands/src/esp_commands.c rename to esp_commands/src/esp_cli_commands.c index a2ffd04c09..4774aebf21 100644 --- a/esp_commands/src/esp_commands.c +++ b/esp_commands/src/esp_cli_commands.c @@ -9,30 +9,30 @@ #include "freertos/FreeRTOS.h" #include "freertos/semphr.h" #include "esp_heap_caps.h" -#include "esp_commands.h" -#include "esp_commands_internal.h" -#include "esp_dynamic_commands.h" +#include "esp_cli_commands.h" +#include "esp_cli_commands_internal.h" +#include "esp_cli_dynamic_commands.h" #include "esp_err.h" /* Default foreground color */ #define ANSI_COLOR_DEFAULT 39 /* static mutex used to protect access to the static configuration */ -static SemaphoreHandle_t s_esp_commands_mutex = NULL; -static StaticSemaphore_t s_esp_commands_mutex_buf; +static SemaphoreHandle_t s_esp_cli_commands_mutex = NULL; +static StaticSemaphore_t s_esp_cli_commands_mutex_buf; /* Pointers to the first and last command in the dedicated section. * See linker.lf for detailed information about the section */ -extern esp_command_t _esp_commands_start; -extern esp_command_t _esp_commands_end; +extern esp_cli_command_t _esp_cli_commands_start; +extern esp_cli_command_t _esp_cli_commands_end; -typedef struct esp_command_sets { - esp_command_set_t static_set; - esp_command_set_t dynamic_set; -} esp_command_sets_t; +typedef struct esp_cli_command_sets { + esp_cli_command_set_t static_set; + esp_cli_command_set_t dynamic_set; +} esp_cli_command_sets_t; /** run-time configuration options */ -static esp_commands_config_t s_config = { +static esp_cli_commands_config_t s_config = { .heap_caps_used = MALLOC_CAP_DEFAULT, .hint_bold = false, .hint_color = ANSI_COLOR_DEFAULT, @@ -42,69 +42,69 @@ static esp_commands_config_t s_config = { /** * @brief go through all commands registered in the - * memory section starting at _esp_commands_start - * and ending at _esp_commands_end OR go through all + * memory section starting at _esp_cli_commands_start + * and ending at _esp_cli_commands_end OR go through all * the commands listed in cmd_set if not NULL */ #define FOR_EACH_STATIC_COMMAND(cmd_set, cmd) \ for (size_t _i = 0; \ ((cmd_set) == NULL \ - ? (((cmd) = &_esp_commands_start + _i), \ - (&_esp_commands_start + _i) < &_esp_commands_end) \ + ? (((cmd) = &_esp_cli_commands_start + _i), \ + (&_esp_cli_commands_start + _i) < &_esp_cli_commands_end) \ : (((cmd) = (cmd_set)->cmd_ptr_set[_i]), \ _i < (cmd_set)->cmd_set_size)); \ ++_i) /** * @brief returns the number of commands registered - * in the .esp_commands section + * in the .esp_cli_commands section */ -#define ESP_COMMANDS_COUNT (size_t)(&_esp_commands_end - &_esp_commands_start) +#define esp_cli_commands_COUNT (size_t)(&_esp_cli_commands_end - &_esp_cli_commands_start) /** * @brief Lock access to the s_config static structure */ -static void esp_commands_lock(void) +static void esp_cli_commands_lock(void) { - if (s_esp_commands_mutex == NULL) { - s_esp_commands_mutex = xSemaphoreCreateMutexStatic(&s_esp_commands_mutex_buf); - assert(s_esp_commands_mutex != NULL); + if (s_esp_cli_commands_mutex == NULL) { + s_esp_cli_commands_mutex = xSemaphoreCreateMutexStatic(&s_esp_cli_commands_mutex_buf); + assert(s_esp_cli_commands_mutex != NULL); } - xSemaphoreTake(s_esp_commands_mutex, portMAX_DELAY); + xSemaphoreTake(s_esp_cli_commands_mutex, portMAX_DELAY); } /** * @brief Unlock access to the s_config static structure */ -static void esp_commands_unlock(void) +static void esp_cli_commands_unlock(void) { - xSemaphoreGive(s_esp_commands_mutex); + xSemaphoreGive(s_esp_cli_commands_mutex); } /** - * @brief check the location of the pointer to esp_command_t + * @brief check the location of the pointer to esp_cli_command_t * * @param cmd the pointer to the command to check * @return true if the command was registered statically * false if the command was registered dynamically */ -static inline __attribute__((always_inline)) bool command_is_static(esp_command_t *cmd) +static inline __attribute__((always_inline)) bool command_is_static(esp_cli_command_t *cmd) { - if (cmd >= &_esp_commands_start && cmd <= &_esp_commands_end) { + if (cmd >= &_esp_cli_commands_start && cmd <= &_esp_cli_commands_end) { return true; } return false; } -typedef bool (*walker_t)(void *walker_ctx, esp_command_t *cmd); +typedef bool (*walker_t)(void *walker_ctx, esp_cli_command_t *cmd); static inline __attribute__((always_inline)) -void go_through_commands(esp_command_sets_t *cmd_sets, void *cmd_walker_ctx, walker_t cmd_walker) +void go_through_commands(esp_cli_command_sets_t *cmd_sets, void *cmd_walker_ctx, walker_t cmd_walker) { if (!cmd_walker) { return; } - esp_command_t *cmd = NULL; + esp_cli_command_t *cmd = NULL; bool continue_walk = false; /* cmd_sets is composed of 2 sets (static and dynamic). @@ -114,7 +114,7 @@ void go_through_commands(esp_command_sets_t *cmd_sets, void *cmd_walker_ctx, wal * through the empty set, so no command will be walked. */ - esp_command_set_t *static_set = cmd_sets ? &cmd_sets->static_set : NULL; + esp_cli_command_set_t *static_set = cmd_sets ? &cmd_sets->static_set : NULL; /* it is possible that the set is empty, in which case set static_set to NULL * to prevent the for loop to try to access a list of commands pointer set to NULL */ if (static_set && !static_set->cmd_ptr_set) { @@ -127,38 +127,38 @@ void go_through_commands(esp_command_sets_t *cmd_sets, void *cmd_walker_ctx, wal } } - esp_command_set_t *dynamic_set = cmd_sets ? &cmd_sets->dynamic_set : NULL; + esp_cli_command_set_t *dynamic_set = cmd_sets ? &cmd_sets->dynamic_set : NULL; /* it is possible that the set is empty, in which case set dynamic_set to NULL * to prevent the for loop to try to access a list of commands pointer set to NULL */ if (dynamic_set && !dynamic_set->cmd_ptr_set) { dynamic_set = NULL; } - esp_dynamic_commands_lock(); + esp_cli_dynamic_commands_lock(); FOR_EACH_DYNAMIC_COMMAND(dynamic_set, cmd) { continue_walk = cmd_walker(cmd_walker_ctx, cmd); if (!continue_walk) { - esp_dynamic_commands_unlock(); + esp_cli_dynamic_commands_unlock(); return; } } - esp_dynamic_commands_unlock(); + esp_cli_dynamic_commands_unlock(); } typedef struct find_cmd_ctx { const char *name; /*!< the name to check commands against */ - esp_command_t *cmd; /*!< the command matching the name */ + esp_cli_command_t *cmd; /*!< the command matching the name */ } find_cmd_ctx_t; static inline __attribute__((always_inline)) -bool compare_command_name(void *ctx, esp_command_t *cmd) +bool compare_command_name(void *ctx, esp_cli_command_t *cmd) { - /* called by esp_commands_find_command through go_through_commands, + /* called by esp_cli_commands_find_command through go_through_commands, * ctx cannot be NULL */ find_cmd_ctx_t *cmd_ctx = (find_cmd_ctx_t *)ctx; /* called by go_through_commands, thus cmd cannot be NULL */ if (strcmp(cmd->name, cmd_ctx->name) == 0) { - /* command found, store it in the ctx so esp_commands_find_command + /* command found, store it in the ctx so esp_cli_commands_find_command * can process it. Notify go_through_commands to stop the walk by * returning false */ cmd_ctx->cmd = cmd; @@ -169,16 +169,16 @@ bool compare_command_name(void *ctx, esp_command_t *cmd) return true; } -void *esp_commands_malloc(const size_t malloc_size) +void *esp_cli_commands_malloc(const size_t malloc_size) { - esp_commands_lock(); + esp_cli_commands_lock(); const uint32_t caps = s_config.heap_caps_used; - esp_commands_unlock(); + esp_cli_commands_unlock(); return heap_caps_malloc(malloc_size, caps); } -esp_err_t esp_commands_update_config(const esp_commands_config_t *config) +esp_err_t esp_cli_commands_update_config(const esp_cli_commands_config_t *config) { if (!config || (config->max_cmdline_args == 0) || @@ -186,7 +186,7 @@ esp_err_t esp_commands_update_config(const esp_commands_config_t *config) return ESP_ERR_INVALID_ARG; } - esp_commands_lock(); + esp_cli_commands_lock(); memcpy(&s_config, config, sizeof(s_config)); /* if the heap_caps_used field is set to 0, set @@ -194,12 +194,12 @@ esp_err_t esp_commands_update_config(const esp_commands_config_t *config) if (s_config.heap_caps_used == 0) { s_config.heap_caps_used = MALLOC_CAP_DEFAULT; } - esp_commands_unlock(); + esp_cli_commands_unlock(); return ESP_OK; } -esp_err_t esp_commands_register_cmd(esp_command_t *cmd) +esp_err_t esp_cli_commands_register_cmd(esp_cli_command_t *cmd) { if (cmd == NULL || (cmd->name == NULL || strchr(cmd->name, ' ') != NULL) || @@ -209,14 +209,14 @@ esp_err_t esp_commands_register_cmd(esp_command_t *cmd) /* try to find the command in the static and dynamic lists. * if the dynamic list is empty, the mutex locking will fail - * in esp_commands_find_command and the function will return after + * in esp_cli_commands_find_command and the function will return after * checking the static list only. */ - esp_command_t *list_item_cmd = esp_commands_find_command((esp_command_sets_t *)NULL, cmd->name); + esp_cli_command_t *list_item_cmd = esp_cli_commands_find_command((esp_cli_command_sets_t *)NULL, cmd->name); esp_err_t ret_val = ESP_FAIL; if (!list_item_cmd) { /* command with given name not found, it is a new command, we can allocate * the list item and the command itself */ - ret_val = esp_dynamic_commands_add(cmd); + ret_val = esp_cli_dynamic_commands_add(cmd); } else if (command_is_static(list_item_cmd)) { /* a command with matching name is found in the list of commands * that were registered at runtime, in which case it cannot be @@ -224,34 +224,34 @@ esp_err_t esp_commands_register_cmd(esp_command_t *cmd) ret_val = ESP_FAIL; } else { /* an item with matching name was found in the list of dynamically - * registered commands. Replace the command on spot with the new esp_command_t. */ - ret_val = esp_dynamic_commands_replace(cmd); + * registered commands. Replace the command on spot with the new esp_cli_command_t. */ + ret_val = esp_cli_dynamic_commands_replace(cmd); } return ret_val; } -esp_err_t esp_commands_unregister_cmd(const char *cmd_name) +esp_err_t esp_cli_commands_unregister_cmd(const char *cmd_name) { /* only items dynamically registered can be unregistered. * try to remove the item with the given name from the list * of dynamically registered commands */ - esp_command_t *cmd = esp_commands_find_command((esp_command_sets_t *)NULL, cmd_name); + esp_cli_command_t *cmd = esp_cli_commands_find_command((esp_cli_command_sets_t *)NULL, cmd_name); if (!cmd) { return ESP_ERR_NOT_FOUND; } else if (command_is_static(cmd)) { return ESP_ERR_INVALID_ARG; } else { - return esp_dynamic_commands_remove(cmd); + return esp_cli_dynamic_commands_remove(cmd); } } -esp_err_t esp_commands_execute(const char *cmdline, int *cmd_ret, esp_command_set_handle_t cmd_set, esp_commands_exec_arg_t *cmd_args) +esp_err_t esp_cli_commands_execute(const char *cmdline, int *cmd_ret, esp_cli_command_set_handle_t cmd_set, esp_cli_commands_exec_arg_t *cmd_args) { - esp_commands_lock(); + esp_cli_commands_lock(); const size_t copy_max_cmdline_args = s_config.max_cmdline_args; const size_t opy_max_cmdline_length = s_config.max_cmdline_length; - esp_commands_unlock(); + esp_cli_commands_unlock(); /* the life time of those variables is not exceeding the scope of this function. Use the stack. */ char *argv[copy_max_cmdline_args]; @@ -263,26 +263,26 @@ esp_err_t esp_commands_execute(const char *cmdline, int *cmd_ret, esp_command_se strlcpy(tmp_line_buf, cmdline, opy_max_cmdline_length); /* parse and split the raw command line */ - size_t argc = esp_commands_split_argv(tmp_line_buf, argv, copy_max_cmdline_args); + size_t argc = esp_cli_commands_split_argv(tmp_line_buf, argv, copy_max_cmdline_args); if (argc == 0) { return ESP_ERR_INVALID_ARG; } /* try to find the command from the first argument in the command line */ - const esp_command_t *cmd = NULL; - esp_command_sets_t *temp_set = cmd_set; + const esp_cli_command_t *cmd = NULL; + esp_cli_command_sets_t *temp_set = cmd_set; bool is_cmd_help = false; if (strcmp("help", argv[0]) == 0) { /* set the set to NULL because the help is not in the set passed by the user - * since this command is registered by esp_commands itself */ - temp_set = (esp_command_sets_t *)NULL; + * since this command is registered by esp_cli_commands itself */ + temp_set = (esp_cli_command_sets_t *)NULL; /* keep in mind that the command being executed is the help. This is needed * when calling the help command function, to pass a specific dynamic context */ is_cmd_help = true; } - cmd = esp_commands_find_command(temp_set, argv[0]); + cmd = esp_cli_commands_find_command(temp_set, argv[0]); if (cmd == NULL) { return ESP_ERR_NOT_FOUND; @@ -290,9 +290,9 @@ esp_err_t esp_commands_execute(const char *cmdline, int *cmd_ret, esp_command_se if (cmd->func) { if (is_cmd_help) { - esp_commands_exec_arg_t help_args; + esp_cli_commands_exec_arg_t help_args; - /* reuse the out_fd and write_func received as parameter by esp_commands_execute + /* reuse the out_fd and write_func received as parameter by esp_cli_commands_execute * to allow the help command function to print information on the correct IO. Use * default values in case the parameters provided are not set */ help_args.out_fd = (cmd_args && cmd_args->out_fd != -1) ? cmd_args->out_fd : STDOUT_FILENO; @@ -306,14 +306,14 @@ esp_err_t esp_commands_execute(const char *cmdline, int *cmd_ret, esp_command_se *cmd_ret = (*cmd->func)(cmd->func_ctx, &help_args, argc, argv); } else { /* regular command function has to be called, just passed the cmd_args as provided - * to the esp_commands_execute function */ + * to the esp_cli_commands_execute function */ *cmd_ret = (*cmd->func)(cmd->func_ctx, cmd_args, argc, argv); } } return ESP_OK; } -esp_command_t *esp_commands_find_command(esp_command_set_handle_t cmd_set, const char *name) +esp_cli_command_t *esp_cli_commands_find_command(esp_cli_command_set_handle_t cmd_set, const char *name) { /* no need to check that cmd_set is NULL, if it is, then FOR_EACH_XX_COMMAND * will go through all registered commands */ @@ -330,18 +330,18 @@ esp_command_t *esp_commands_find_command(esp_command_set_handle_t cmd_set, const return ctx.cmd; } typedef struct create_cmd_set_ctx { - esp_commands_get_field_t get_field; + esp_cli_commands_get_field_t get_field; const char *cmd_set_name; - esp_command_t **static_cmd_ptrs; + esp_cli_command_t **static_cmd_ptrs; size_t static_cmd_count; - esp_command_t **dynamic_cmd_ptrs; + esp_cli_command_t **dynamic_cmd_ptrs; size_t dynamic_cmd_count; } create_cmd_set_ctx_t; static inline __attribute__((always_inline)) -bool fill_temp_set_info(void *caller_ctx, esp_command_t *cmd) +bool fill_temp_set_info(void *caller_ctx, esp_cli_command_t *cmd) { - /* called by esp_commands_find_command through go_through_commands, + /* called by esp_cli_commands_find_command through go_through_commands, * ctx cannot be NULL */ create_cmd_set_ctx_t *ctx = (create_cmd_set_ctx_t *)caller_ctx; @@ -362,14 +362,14 @@ bool fill_temp_set_info(void *caller_ctx, esp_command_t *cmd) } static inline __attribute__((always_inline)) -esp_err_t update_cmd_set_with_temp_info(esp_command_set_t *cmd_set, size_t cmd_count, esp_command_t **cmd_ptrs) +esp_err_t update_cmd_set_with_temp_info(esp_cli_command_set_t *cmd_set, size_t cmd_count, esp_cli_command_t **cmd_ptrs) { if (cmd_count == 0) { cmd_set->cmd_ptr_set = NULL; cmd_set->cmd_set_size = 0; } else { - const size_t alloc_cmd_ptrs_size = sizeof(esp_command_t *) * cmd_count; - cmd_set->cmd_ptr_set = esp_commands_malloc(alloc_cmd_ptrs_size); + const size_t alloc_cmd_ptrs_size = sizeof(esp_cli_command_t *) * cmd_count; + cmd_set->cmd_ptr_set = esp_cli_commands_malloc(alloc_cmd_ptrs_size); if (!cmd_set->cmd_ptr_set) { return ESP_ERR_NO_MEM; } else { @@ -381,20 +381,20 @@ esp_err_t update_cmd_set_with_temp_info(esp_command_set_t *cmd_set, size_t cmd_c return ESP_OK; } -esp_command_set_handle_t esp_commands_create_cmd_set(const char **cmd_set, const size_t cmd_set_size, esp_commands_get_field_t get_field) +esp_cli_command_set_handle_t esp_cli_commands_create_cmd_set(const char **cmd_set, const size_t cmd_set_size, esp_cli_commands_get_field_t get_field) { if (!cmd_set || cmd_set_size == 0) { return NULL; } - esp_command_sets_t *cmd_ptr_sets = esp_commands_malloc(sizeof(esp_command_sets_t)); + esp_cli_command_sets_t *cmd_ptr_sets = esp_cli_commands_malloc(sizeof(esp_cli_command_sets_t)); if (!cmd_ptr_sets) { return NULL; } - esp_command_t *static_cmd_ptrs_temp[ESP_COMMANDS_COUNT]; - esp_command_t *dynamic_cmd_ptrs_temp[esp_dynamic_commands_get_number_of_cmd()]; + esp_cli_command_t *static_cmd_ptrs_temp[esp_cli_commands_COUNT]; + esp_cli_command_t *dynamic_cmd_ptrs_temp[esp_cli_dynamic_commands_get_number_of_cmd()]; create_cmd_set_ctx_t ctx = { .cmd_set_name = NULL, .get_field = get_field, @@ -429,10 +429,10 @@ esp_command_set_handle_t esp_commands_create_cmd_set(const char **cmd_set, const return NULL; } - return (esp_command_set_handle_t)cmd_ptr_sets; + return (esp_cli_command_set_handle_t)cmd_ptr_sets; } -esp_command_set_handle_t esp_commands_concat_cmd_set(esp_command_set_handle_t cmd_set_a, esp_command_set_handle_t cmd_set_b) +esp_cli_command_set_handle_t esp_cli_commands_concat_cmd_set(esp_cli_command_set_handle_t cmd_set_a, esp_cli_command_set_handle_t cmd_set_b) { if (!cmd_set_a && !cmd_set_b) { return NULL; @@ -445,19 +445,19 @@ esp_command_set_handle_t esp_commands_concat_cmd_set(esp_command_set_handle_t cm /* Reaching this point, both cmd_set_a and cmd_set_b are set. * Create a new cmd_set that can host the items from both sets, * assign the items to the new set and free the input sets */ - esp_command_sets_t *concat_cmd_sets = esp_commands_malloc(sizeof(esp_command_sets_t)); + esp_cli_command_sets_t *concat_cmd_sets = esp_cli_commands_malloc(sizeof(esp_cli_command_sets_t)); if (!concat_cmd_sets) { return NULL; } const size_t new_static_set_size = cmd_set_a->static_set.cmd_set_size + cmd_set_b->static_set.cmd_set_size; - concat_cmd_sets->static_set.cmd_ptr_set = calloc(new_static_set_size, sizeof(esp_command_t *)); + concat_cmd_sets->static_set.cmd_ptr_set = calloc(new_static_set_size, sizeof(esp_cli_command_t *)); if (!concat_cmd_sets->static_set.cmd_ptr_set) { free(concat_cmd_sets); return NULL; } const size_t new_dynamic_set_size = cmd_set_a->dynamic_set.cmd_set_size + cmd_set_b->dynamic_set.cmd_set_size; - concat_cmd_sets->dynamic_set.cmd_ptr_set = calloc(new_dynamic_set_size, sizeof(esp_command_t *)); + concat_cmd_sets->dynamic_set.cmd_ptr_set = calloc(new_dynamic_set_size, sizeof(esp_cli_command_t *)); if (!concat_cmd_sets->static_set.cmd_ptr_set) { free(concat_cmd_sets->static_set.cmd_ptr_set); free(concat_cmd_sets); @@ -471,25 +471,25 @@ esp_command_set_handle_t esp_commands_concat_cmd_set(esp_command_set_handle_t cm /* fill the list of command pointers */ memcpy(concat_cmd_sets->static_set.cmd_ptr_set, cmd_set_a->static_set.cmd_ptr_set, - sizeof(esp_command_t *) * cmd_set_a->static_set.cmd_set_size); + sizeof(esp_cli_command_t *) * cmd_set_a->static_set.cmd_set_size); memcpy(concat_cmd_sets->static_set.cmd_ptr_set + cmd_set_a->static_set.cmd_set_size, cmd_set_b->static_set.cmd_ptr_set, - sizeof(esp_command_t *) * cmd_set_b->static_set.cmd_set_size); + sizeof(esp_cli_command_t *) * cmd_set_b->static_set.cmd_set_size); memcpy(concat_cmd_sets->dynamic_set.cmd_ptr_set, cmd_set_a->dynamic_set.cmd_ptr_set, - sizeof(esp_command_t *) * cmd_set_a->dynamic_set.cmd_set_size); + sizeof(esp_cli_command_t *) * cmd_set_a->dynamic_set.cmd_set_size); memcpy(concat_cmd_sets->dynamic_set.cmd_ptr_set + cmd_set_a->dynamic_set.cmd_set_size, cmd_set_b->dynamic_set.cmd_ptr_set, - sizeof(esp_command_t *) * cmd_set_b->dynamic_set.cmd_set_size); + sizeof(esp_cli_command_t *) * cmd_set_b->dynamic_set.cmd_set_size); - esp_commands_destroy_cmd_set(&cmd_set_a); - esp_commands_destroy_cmd_set(&cmd_set_b); + esp_cli_commands_destroy_cmd_set(&cmd_set_a); + esp_cli_commands_destroy_cmd_set(&cmd_set_b); - return (esp_command_set_handle_t)concat_cmd_sets; + return (esp_cli_command_set_handle_t)concat_cmd_sets; } -void esp_commands_destroy_cmd_set(esp_command_set_handle_t *cmd_set) +void esp_cli_commands_destroy_cmd_set(esp_cli_command_set_handle_t *cmd_set) { if (!cmd_set || !*cmd_set) { return; @@ -511,10 +511,10 @@ typedef struct call_completion_cb_ctx { const char *buf; const size_t buf_len; void *cb_ctx; - esp_command_get_completion_t completion_cb; + esp_cli_command_get_completion_t completion_cb; } call_completion_cb_ctx_t; -static bool call_completion_cb(void *caller_ctx, esp_command_t *cmd) +static bool call_completion_cb(void *caller_ctx, esp_cli_command_t *cmd) { call_completion_cb_ctx_t *ctx = (call_completion_cb_ctx_t *)caller_ctx; @@ -525,7 +525,7 @@ static bool call_completion_cb(void *caller_ctx, esp_command_t *cmd) return true; } -void esp_commands_get_completion(esp_command_set_handle_t cmd_set, const char *buf, void *cb_ctx, esp_command_get_completion_t completion_cb) +void esp_cli_commands_get_completion(esp_cli_command_set_handle_t cmd_set, const char *buf, void *cb_ctx, esp_cli_command_get_completion_t completion_cb) { size_t len = strlen(buf); if (len == 0) { @@ -541,14 +541,14 @@ void esp_commands_get_completion(esp_command_set_handle_t cmd_set, const char *b go_through_commands(cmd_set, &ctx, call_completion_cb); } -const char *esp_commands_get_hint(esp_command_set_handle_t cmd_set, const char *buf, int *color, bool *bold) +const char *esp_cli_commands_get_hint(esp_cli_command_set_handle_t cmd_set, const char *buf, int *color, bool *bold) { - esp_commands_lock(); + esp_cli_commands_lock(); *color = s_config.hint_color; *bold = s_config.hint_bold; - esp_commands_unlock(); + esp_cli_commands_unlock(); - esp_command_t *cmd = esp_commands_find_command(cmd_set, buf); + esp_cli_command_t *cmd = esp_cli_commands_find_command(cmd_set, buf); if (cmd && cmd->hint_cb != NULL) { return cmd->hint_cb(cmd->func_ctx); } @@ -556,9 +556,9 @@ const char *esp_commands_get_hint(esp_command_set_handle_t cmd_set, const char * return NULL; } -const char *esp_commands_get_glossary(esp_command_set_handle_t cmd_set, const char *buf) +const char *esp_cli_commands_get_glossary(esp_cli_command_set_handle_t cmd_set, const char *buf) { - esp_command_t *cmd = esp_commands_find_command(cmd_set, buf); + esp_cli_command_t *cmd = esp_cli_commands_find_command(cmd_set, buf); if (cmd && cmd->glossary_cb != NULL) { return cmd->glossary_cb(cmd->func_ctx); } @@ -571,9 +571,9 @@ const char *esp_commands_get_glossary(esp_command_set_handle_t cmd_set, const ch /* -------------------------------------------------------------- */ #define FDPRINTF(fd, write, fmt, ...) do { \ - esp_commands_lock(); \ + esp_cli_commands_lock(); \ char _buf[s_config.max_cmdline_length]; \ - esp_commands_unlock(); \ + esp_cli_commands_unlock(); \ int _len = snprintf(_buf, sizeof(_buf), fmt, ##__VA_ARGS__); \ if (_len > 0) { \ ssize_t _ignored __attribute__((unused)); \ @@ -582,7 +582,7 @@ const char *esp_commands_get_glossary(esp_command_set_handle_t cmd_set, const ch } \ } while (0) -static void print_arg_help(esp_commands_exec_arg_t *cmd_args, esp_command_t *it) +static void print_arg_help(esp_cli_commands_exec_arg_t *cmd_args, esp_cli_command_t *it) { /* First line: command name and hint * Pad all the hints to the same column @@ -624,7 +624,7 @@ static void print_arg_help(esp_commands_exec_arg_t *cmd_args, esp_command_t *it) FDPRINTF(cmd_args->out_fd, cmd_args->write_func, "\n"); } -static void print_arg_command(esp_commands_exec_arg_t *cmd_args, esp_command_t *it) +static void print_arg_command(esp_cli_commands_exec_arg_t *cmd_args, esp_cli_command_t *it) { FDPRINTF(cmd_args->out_fd, cmd_args->write_func, "%-s", it->name); if (it->hint_cb) { @@ -643,7 +643,7 @@ typedef enum { HELP_VERBOSE_LEVEL_MAX_NUM = 2 } help_verbose_level_e; -typedef void (*const fn_print_arg_t)(esp_commands_exec_arg_t *cmd_args, esp_command_t *); +typedef void (*const fn_print_arg_t)(esp_cli_commands_exec_arg_t *cmd_args, esp_cli_command_t *); static fn_print_arg_t print_verbose_level_arr[HELP_VERBOSE_LEVEL_MAX_NUM] = { print_arg_command, @@ -651,14 +651,14 @@ static fn_print_arg_t print_verbose_level_arr[HELP_VERBOSE_LEVEL_MAX_NUM] = { }; typedef struct call_cmd_ctx { - esp_commands_exec_arg_t *cmd_args; + esp_cli_commands_exec_arg_t *cmd_args; help_verbose_level_e verbose_level; const char *command_name; bool command_found; } call_cmd_ctx_t; static inline __attribute__((always_inline)) -bool call_command_funcs(void *caller_ctx, esp_command_t *cmd) +bool call_command_funcs(void *caller_ctx, esp_cli_command_t *cmd) { call_cmd_ctx_t *ctx = (call_cmd_ctx_t *)caller_ctx; @@ -676,7 +676,7 @@ bool call_command_funcs(void *caller_ctx, esp_command_t *cmd) return true; } -static int help_command(void *context, esp_commands_exec_arg_t *cmd_args, int argc, char **argv) +static int help_command(void *context, esp_cli_commands_exec_arg_t *cmd_args, int argc, char **argv) { (void)context; /* this is NULL and useless for the help command */ @@ -691,7 +691,7 @@ static int help_command(void *context, esp_commands_exec_arg_t *cmd_args, int ar return 1; } - esp_command_sets_t *cmd_sets = (esp_command_sets_t *)cmd_args->dynamic_ctx; + esp_cli_command_sets_t *cmd_sets = (esp_cli_command_sets_t *)cmd_args->dynamic_ctx; if (argc > 1) { /* more than 1 arg, figure out if only verbose level argument @@ -762,10 +762,10 @@ static const char *get_help_glossary(void *context) static const char help_str[] = "Print the summary of all registered commands if no arguments " "are given, otherwise print summary of given command."; -ESP_COMMAND_REGISTER(help, /* name of the heap command */ - help, /* group of the help command */ - help_str, /* help string of the help command */ - help_command, /* func */ - NULL, /* the context is null here, it will provided by the exec function */ - get_help_hint, /* hint callback */ - get_help_glossary); /* glossary callback */ +ESP_CLI_COMMAND_REGISTER(help, /* name of the heap command */ + help, /* group of the help command */ + help_str, /* help string of the help command */ + help_command, /* func */ + NULL, /* the context is null here, it will provided by the exec function */ + get_help_hint, /* hint callback */ + get_help_glossary); /* glossary callback */ diff --git a/esp_commands/src/esp_cli_dynamic_commands.c b/esp_commands/src/esp_cli_dynamic_commands.c new file mode 100644 index 0000000000..b2bb55984c --- /dev/null +++ b/esp_commands/src/esp_cli_dynamic_commands.c @@ -0,0 +1,124 @@ +/* + * SPDX-FileCopyrightText: 2016-2021 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" +#include "esp_cli_commands_internal.h" +#include "esp_cli_dynamic_commands.h" +#include "esp_cli_commands.h" + +#define CONTAINER_OF(ptr, type, member) \ + ((type *)((char *)(ptr) - offsetof(type, member))) + +static esp_cli_command_internal_ll_t s_dynamic_cmd_list = SLIST_HEAD_INITIALIZER(esp_cli_command_internal); +static size_t s_number_of_registered_commands = 0; +static SemaphoreHandle_t s_esp_cli_commands_dyn_mutex = NULL; +static StaticSemaphore_t s_esp_cli_commands_dyn_mutex_buf; + +void esp_cli_dynamic_commands_lock(void) +{ + /* check if the mutex needs to be initialized and initialized it only + * is requested in by the state of the create parameter */ + if (s_esp_cli_commands_dyn_mutex == NULL) { + s_esp_cli_commands_dyn_mutex = xSemaphoreCreateMutexStatic(&s_esp_cli_commands_dyn_mutex_buf); + assert(s_esp_cli_commands_dyn_mutex != NULL); + } + + xSemaphoreTake(s_esp_cli_commands_dyn_mutex, portMAX_DELAY); +} + +void esp_cli_dynamic_commands_unlock(void) +{ + if (s_esp_cli_commands_dyn_mutex == NULL) { + return; + } + xSemaphoreGive(s_esp_cli_commands_dyn_mutex); +} + +const esp_cli_command_internal_ll_t *esp_cli_dynamic_commands_get_list(void) +{ + return &s_dynamic_cmd_list; +} + +esp_err_t esp_cli_dynamic_commands_add(esp_cli_command_t *cmd) +{ + if (!cmd) { + return ESP_ERR_INVALID_ARG; + } + + esp_cli_command_internal_t *list_item = esp_cli_commands_malloc(sizeof(esp_cli_command_internal_t)); + if (!list_item) { + return ESP_ERR_NO_MEM; + } + + memcpy(&list_item->cmd, cmd, sizeof(esp_cli_command_t)); + + esp_cli_command_internal_t *last = NULL; + esp_cli_command_internal_t *it = NULL; + + /* this could be called on an empty list, make sure the + * mutex is initialized */ + esp_cli_dynamic_commands_lock(); + + SLIST_FOREACH(it, &s_dynamic_cmd_list, next_item) { + if (strcmp(it->cmd.name, list_item->cmd.name) > 0) { + break; + } + last = it; + } + + if (last == NULL) { + SLIST_INSERT_HEAD(&s_dynamic_cmd_list, list_item, next_item); + } else { + SLIST_INSERT_AFTER(last, list_item, next_item); + } + + s_number_of_registered_commands++; + + esp_cli_dynamic_commands_unlock(); + + return ESP_OK; +} + +esp_err_t esp_cli_dynamic_commands_replace(esp_cli_command_t *item_cmd) +{ + esp_cli_dynamic_commands_lock(); + + esp_cli_command_internal_t *list_item = CONTAINER_OF(item_cmd, esp_cli_command_internal_t, cmd); + memcpy(&list_item->cmd, item_cmd, sizeof(esp_cli_command_t)); + + esp_cli_dynamic_commands_unlock(); + + return ESP_OK; +} + +esp_err_t esp_cli_dynamic_commands_remove(esp_cli_command_t *item_cmd) +{ + esp_cli_dynamic_commands_lock(); + + esp_cli_command_internal_t *list_item = CONTAINER_OF(item_cmd, esp_cli_command_internal_t, cmd); + SLIST_REMOVE(&s_dynamic_cmd_list, list_item, esp_cli_command_internal, next_item); + + s_number_of_registered_commands--; + + esp_cli_dynamic_commands_unlock(); + + free(list_item); + + return ESP_OK; +} + +size_t esp_cli_dynamic_commands_get_number_of_cmd(void) +{ + esp_cli_dynamic_commands_lock(); + size_t nb_of_registered_cmd = s_number_of_registered_commands; + esp_cli_dynamic_commands_unlock(); + return nb_of_registered_cmd; +} diff --git a/esp_commands/src/esp_dynamic_commands.c b/esp_commands/src/esp_dynamic_commands.c deleted file mode 100644 index b868a88b52..0000000000 --- a/esp_commands/src/esp_dynamic_commands.c +++ /dev/null @@ -1,124 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2016-2021 Espressif Systems (Shanghai) CO LTD - * - * SPDX-License-Identifier: Apache-2.0 - */ - -#include -#include -#include -#include -#include "freertos/FreeRTOS.h" -#include "freertos/semphr.h" -#include "esp_commands_internal.h" -#include "esp_dynamic_commands.h" -#include "esp_commands.h" - -#define CONTAINER_OF(ptr, type, member) \ - ((type *)((char *)(ptr) - offsetof(type, member))) - -static esp_command_internal_ll_t s_dynamic_cmd_list = SLIST_HEAD_INITIALIZER(esp_command_internal); -static size_t s_number_of_registered_commands = 0; -static SemaphoreHandle_t s_esp_commands_dyn_mutex = NULL; -static StaticSemaphore_t s_esp_commands_dyn_mutex_buf; - -void esp_dynamic_commands_lock(void) -{ - /* check if the mutex needs to be initialized and initialized it only - * is requested in by the state of the create parameter */ - if (s_esp_commands_dyn_mutex == NULL) { - s_esp_commands_dyn_mutex = xSemaphoreCreateMutexStatic(&s_esp_commands_dyn_mutex_buf); - assert(s_esp_commands_dyn_mutex != NULL); - } - - xSemaphoreTake(s_esp_commands_dyn_mutex, portMAX_DELAY); -} - -void esp_dynamic_commands_unlock(void) -{ - if (s_esp_commands_dyn_mutex == NULL) { - return; - } - xSemaphoreGive(s_esp_commands_dyn_mutex); -} - -const esp_command_internal_ll_t *esp_dynamic_commands_get_list(void) -{ - return &s_dynamic_cmd_list; -} - -esp_err_t esp_dynamic_commands_add(esp_command_t *cmd) -{ - if (!cmd) { - return ESP_ERR_INVALID_ARG; - } - - esp_command_internal_t *list_item = esp_commands_malloc(sizeof(esp_command_internal_t)); - if (!list_item) { - return ESP_ERR_NO_MEM; - } - - memcpy(&list_item->cmd, cmd, sizeof(esp_command_t)); - - esp_command_internal_t *last = NULL; - esp_command_internal_t *it = NULL; - - /* this could be called on an empty list, make sure the - * mutex is initialized */ - esp_dynamic_commands_lock(); - - SLIST_FOREACH(it, &s_dynamic_cmd_list, next_item) { - if (strcmp(it->cmd.name, list_item->cmd.name) > 0) { - break; - } - last = it; - } - - if (last == NULL) { - SLIST_INSERT_HEAD(&s_dynamic_cmd_list, list_item, next_item); - } else { - SLIST_INSERT_AFTER(last, list_item, next_item); - } - - s_number_of_registered_commands++; - - esp_dynamic_commands_unlock(); - - return ESP_OK; -} - -esp_err_t esp_dynamic_commands_replace(esp_command_t *item_cmd) -{ - esp_dynamic_commands_lock(); - - esp_command_internal_t *list_item = CONTAINER_OF(item_cmd, esp_command_internal_t, cmd); - memcpy(&list_item->cmd, item_cmd, sizeof(esp_command_t)); - - esp_dynamic_commands_unlock(); - - return ESP_OK; -} - -esp_err_t esp_dynamic_commands_remove(esp_command_t *item_cmd) -{ - esp_dynamic_commands_lock(); - - esp_command_internal_t *list_item = CONTAINER_OF(item_cmd, esp_command_internal_t, cmd); - SLIST_REMOVE(&s_dynamic_cmd_list, list_item, esp_command_internal, next_item); - - s_number_of_registered_commands--; - - esp_dynamic_commands_unlock(); - - free(list_item); - - return ESP_OK; -} - -size_t esp_dynamic_commands_get_number_of_cmd(void) -{ - esp_dynamic_commands_lock(); - size_t nb_of_registered_cmd = s_number_of_registered_commands; - esp_dynamic_commands_unlock(); - return nb_of_registered_cmd; -}