From 110157900faee6a100e3c840c868fb675f25ae4c Mon Sep 17 00:00:00 2001 From: xBill Date: Thu, 27 Jan 2022 16:06:08 +0900 Subject: [PATCH] add CLI tab-to-autocomplete ability and implement for nrfconnect (#13630) * implement cli autocompletion for ESP32 and nRFConnect * avoid STL;revert implementation for esp32 * update to register with nullptr prefix * cleanup comment * revert an unnecessary change * backfill two new commands; address CI failures --- .../src/binding-handler.cpp | 2 +- .../esp32/main/OnOffCommands.cpp | 4 +- .../cyw30739/src/AppShellCommands.cpp | 4 +- .../esp32/main/OTAProviderCommands.cpp | 4 +- .../esp32/shell_extension/heap_trace.cpp | 4 +- .../linux/CommissioneeShellCommands.cpp | 2 +- .../linux/ControllerShellCommands.cpp | 2 +- examples/shell/shell_common/cmd_misc.cpp | 2 +- examples/shell/shell_common/cmd_otcli.cpp | 2 +- examples/shell/shell_common/cmd_ping.cpp | 2 +- examples/shell/shell_common/cmd_send.cpp | 2 +- examples/shell/shell_common/cmd_server.cpp | 4 +- .../tv-app/linux/AppPlatformShellCommands.cpp | 2 +- src/lib/shell/Engine.cpp | 187 +++++++++++++++--- src/lib/shell/Engine.h | 78 +++++++- src/lib/shell/MainLoopZephyr.cpp | 95 ++++++++- src/lib/shell/commands/BLE.cpp | 4 +- src/lib/shell/commands/Base64.cpp | 4 +- src/lib/shell/commands/Config.cpp | 6 +- src/lib/shell/commands/Device.cpp | 4 +- src/lib/shell/commands/Dns.cpp | 6 +- src/lib/shell/commands/Meta.cpp | 2 +- src/lib/shell/commands/NFC.cpp | 2 +- src/lib/shell/commands/OnboardingCodes.cpp | 2 +- src/lib/shell/commands/Ota.cpp | 4 +- src/lib/shell/commands/WiFi.cpp | 4 +- 26 files changed, 362 insertions(+), 72 deletions(-) diff --git a/examples/all-clusters-app/all-clusters-common/src/binding-handler.cpp b/examples/all-clusters-app/all-clusters-common/src/binding-handler.cpp index 15ec992bdd7789..8979b3add9b301 100644 --- a/examples/all-clusters-app/all-clusters-common/src/binding-handler.cpp +++ b/examples/all-clusters-app/all-clusters-common/src/binding-handler.cpp @@ -61,7 +61,7 @@ static CHIP_ERROR SwitchCommandHandler(int argc, char ** argv) static void RegisterSwitchCommands() { static const shell_command_t sSwitchCommand = { SwitchCommandHandler, "switch", "Switch commands. Usage: switch [on|off]" }; - Engine::Root().RegisterCommands(&sSwitchCommand, 1); + Engine::Root().RegisterCommands(&sSwitchCommand, 1, nullptr); return; } #endif // defined(ENABLE_CHIP_SHELL) diff --git a/examples/all-clusters-app/esp32/main/OnOffCommands.cpp b/examples/all-clusters-app/esp32/main/OnOffCommands.cpp index 45a393548fcb3e..ff436de4861735 100644 --- a/examples/all-clusters-app/esp32/main/OnOffCommands.cpp +++ b/examples/all-clusters-app/esp32/main/OnOffCommands.cpp @@ -90,12 +90,12 @@ void OnOffCommands::Register() static const shell_command_t subCommands[] = { { &OnLightHandler, "on", "Usage: OnOff on endpoint-id" }, { &OffLightHandler, "off", "Usage: OnOff off endpoint-id" }, { &ToggleLightHandler, "toggle", "Usage: OnOff toggle endpoint-id" } }; - sSubShell.RegisterCommands(subCommands, ArraySize(subCommands)); + sSubShell.RegisterCommands(subCommands, ArraySize(subCommands), "OnOff"); // Register the root `OnOff` command in the top-level shell. static const shell_command_t onOffCommand = { &OnOffHandler, "OnOff", "OnOff commands" }; - Engine::Root().RegisterCommands(&onOffCommand, 1); + Engine::Root().RegisterCommands(&onOffCommand, 1, nullptr); } } // namespace Shell diff --git a/examples/lighting-app/cyw30739/src/AppShellCommands.cpp b/examples/lighting-app/cyw30739/src/AppShellCommands.cpp index 84f7b74adefc94..94f2b5e848faf2 100644 --- a/examples/lighting-app/cyw30739/src/AppShellCommands.cpp +++ b/examples/lighting-app/cyw30739/src/AppShellCommands.cpp @@ -50,9 +50,9 @@ void RegisterAppShellCommands(void) .cmd_help = "App commands", }; - sAppSubcommands.RegisterCommands(sAppSubCommands, ArraySize(sAppSubCommands)); + sAppSubcommands.RegisterCommands(sAppSubCommands, ArraySize(sAppSubCommands), "app"); - Engine::Root().RegisterCommands(&sAppCommand, 1); + Engine::Root().RegisterCommands(&sAppCommand, 1, nullptr); } CHIP_ERROR AppCommandHelpHandler(int argc, char * argv[]) diff --git a/examples/ota-provider-app/esp32/main/OTAProviderCommands.cpp b/examples/ota-provider-app/esp32/main/OTAProviderCommands.cpp index 58bf1ee1320f16..b209eff22c6179 100644 --- a/examples/ota-provider-app/esp32/main/OTAProviderCommands.cpp +++ b/examples/ota-provider-app/esp32/main/OTAProviderCommands.cpp @@ -69,12 +69,12 @@ void OTAProviderCommands::Register() "Usage: OTAProvider delay " }, }; - sSubShell.RegisterCommands(subCommands, ArraySize(subCommands)); + sSubShell.RegisterCommands(subCommands, ArraySize(subCommands), "OTAProvider"); // Register the root `OTA Provider` command in the top-level shell. static const shell_command_t otaProviderCommand = { &OTAProviderHandler, "OTAProvider", "OTA Provider commands" }; - Engine::Root().RegisterCommands(&otaProviderCommand, 1); + Engine::Root().RegisterCommands(&otaProviderCommand, 1, nullptr); } // Set Example OTA provider diff --git a/examples/platform/esp32/shell_extension/heap_trace.cpp b/examples/platform/esp32/shell_extension/heap_trace.cpp index a2c508a370aea7..b052088ac19e4b 100644 --- a/examples/platform/esp32/shell_extension/heap_trace.cpp +++ b/examples/platform/esp32/shell_extension/heap_trace.cpp @@ -137,7 +137,7 @@ void RegisterHeapTraceCommands() { &HeapTraceTaskHandler, "task", "Dump heap usage of each task" }, #endif // CONFIG_HEAP_TASK_TRACKING }; - sShellHeapSubCommands.RegisterCommands(sHeapSubCommands, ArraySize(sHeapSubCommands)); + sShellHeapSubCommands.RegisterCommands(sHeapSubCommands, ArraySize(sHeapSubCommands), "heap-trace"); #if CONFIG_HEAP_TRACING_STANDALONE ESP_ERROR_CHECK(heap_trace_init_standalone(sTraceRecords, kNumHeapTraceRecords)); @@ -145,7 +145,7 @@ void RegisterHeapTraceCommands() #endif // CONFIG_HEAP_TRACING_STANDALONE static const shell_command_t sHeapCommand = { &HeapTraceDispatch, "heap-trace", "Heap debug tracing" }; - Engine::Root().RegisterCommands(&sHeapCommand, 1); + Engine::Root().RegisterCommands(&sHeapCommand, 1, nullptr); } } // namespace chip diff --git a/examples/platform/linux/CommissioneeShellCommands.cpp b/examples/platform/linux/CommissioneeShellCommands.cpp index e256cb0fbecb72..fe10c7b56dd085 100644 --- a/examples/platform/linux/CommissioneeShellCommands.cpp +++ b/examples/platform/linux/CommissioneeShellCommands.cpp @@ -148,7 +148,7 @@ void RegisterCommissioneeCommands() "Commissionee commands. Usage: commissionee [command_name]" }; // Register the root `device` command with the top-level shell. - Engine::Root().RegisterCommands(&sDeviceComand, 1); + Engine::Root().RegisterCommands(&sDeviceComand, 1, nullptr); return; } diff --git a/examples/platform/linux/ControllerShellCommands.cpp b/examples/platform/linux/ControllerShellCommands.cpp index ff231eb5341c50..6c5604eada24ce 100644 --- a/examples/platform/linux/ControllerShellCommands.cpp +++ b/examples/platform/linux/ControllerShellCommands.cpp @@ -273,7 +273,7 @@ void RegisterControllerCommands() "Controller commands. Usage: controller [command_name]" }; // Register the root `device` command with the top-level shell. - Engine::Root().RegisterCommands(&sDeviceComand, 1); + Engine::Root().RegisterCommands(&sDeviceComand, 1, nullptr); return; } diff --git a/examples/shell/shell_common/cmd_misc.cpp b/examples/shell/shell_common/cmd_misc.cpp index ff8a5ad9739656..6fb98833b02955 100644 --- a/examples/shell/shell_common/cmd_misc.cpp +++ b/examples/shell/shell_common/cmd_misc.cpp @@ -68,5 +68,5 @@ static shell_command_t cmds_misc[] = { void cmd_misc_init() { - Engine::Root().RegisterCommands(cmds_misc, ArraySize(cmds_misc)); + Engine::Root().RegisterCommands(cmds_misc, ArraySize(cmds_misc), nullptr); } diff --git a/examples/shell/shell_common/cmd_otcli.cpp b/examples/shell/shell_common/cmd_otcli.cpp index dbb8128d265b6e..296d238167ec78 100644 --- a/examples/shell/shell_common/cmd_otcli.cpp +++ b/examples/shell/shell_common/cmd_otcli.cpp @@ -193,6 +193,6 @@ void cmd_otcli_init() #endif // Register the root otcli command with the top-level shell. - Engine::Root().RegisterCommands(&cmds_otcli_root, 1); + Engine::Root().RegisterCommands(&cmds_otcli_root, 1, nullptr); #endif // CHIP_ENABLE_OPENTHREAD } diff --git a/examples/shell/shell_common/cmd_ping.cpp b/examples/shell/shell_common/cmd_ping.cpp index 6a8183d3de80b2..b8740e59b183bb 100644 --- a/examples/shell/shell_common/cmd_ping.cpp +++ b/examples/shell/shell_common/cmd_ping.cpp @@ -483,5 +483,5 @@ static shell_command_t cmds_ping[] = { void cmd_ping_init() { - Engine::Root().RegisterCommands(cmds_ping, ArraySize(cmds_ping)); + Engine::Root().RegisterCommands(cmds_ping, ArraySize(cmds_ping), nullptr); } diff --git a/examples/shell/shell_common/cmd_send.cpp b/examples/shell/shell_common/cmd_send.cpp index c216c57578297a..a48518102a364e 100644 --- a/examples/shell/shell_common/cmd_send.cpp +++ b/examples/shell/shell_common/cmd_send.cpp @@ -406,5 +406,5 @@ static shell_command_t cmds_send[] = { void cmd_send_init() { - Engine::Root().RegisterCommands(cmds_send, ArraySize(cmds_send)); + Engine::Root().RegisterCommands(cmds_send, ArraySize(cmds_send), nullptr); } diff --git a/examples/shell/shell_common/cmd_server.cpp b/examples/shell/shell_common/cmd_server.cpp index 45a5b2460ada05..4455d16fc4e99c 100644 --- a/examples/shell/shell_common/cmd_server.cpp +++ b/examples/shell/shell_common/cmd_server.cpp @@ -228,8 +228,8 @@ void cmd_app_server_init() std::atexit(CmdAppServerAtExit); // Register `server` subcommands with the local shell dispatcher. - sShellServerSubcommands.RegisterCommands(sServerSubCommands, ArraySize(sServerSubCommands)); + sShellServerSubcommands.RegisterCommands(sServerSubCommands, ArraySize(sServerSubCommands), "server"); // Register the root `server` command with the top-level shell. - Engine::Root().RegisterCommands(&sServerComand, 1); + Engine::Root().RegisterCommands(&sServerComand, 1, nullptr); } diff --git a/examples/tv-app/linux/AppPlatformShellCommands.cpp b/examples/tv-app/linux/AppPlatformShellCommands.cpp index 96acfed6270709..2b03bbd42d2284 100644 --- a/examples/tv-app/linux/AppPlatformShellCommands.cpp +++ b/examples/tv-app/linux/AppPlatformShellCommands.cpp @@ -192,7 +192,7 @@ void RegisterAppPlatformCommands() static const shell_command_t sDeviceComand = { &AppPlatformHandler, "app", "App commands. Usage: app [command_name]" }; // Register the root `device` command with the top-level shell. - Engine::Root().RegisterCommands(&sDeviceComand, 1); + Engine::Root().RegisterCommands(&sDeviceComand, 1, nullptr); return; } diff --git a/src/lib/shell/Engine.cpp b/src/lib/shell/Engine.cpp index 10bb2dcfa6723a..8723ecc051a895 100644 --- a/src/lib/shell/Engine.cpp +++ b/src/lib/shell/Engine.cpp @@ -21,10 +21,9 @@ * Source implementation for a generic shell API for CHIP examples. */ -#include - #include #include +#include #include #include #include @@ -35,7 +34,6 @@ #include #include #include -#include using namespace chip::Logging; @@ -43,6 +41,7 @@ namespace chip { namespace Shell { Engine Engine::theEngineRoot; +shell_map * Engine::theShellMapListHead = NULL; int Engine::Init() { @@ -56,47 +55,189 @@ int Engine::Init() void Engine::ForEachCommand(shell_command_iterator_t * on_command, void * arg) { - for (unsigned i = 0; i < _commandSetCount; i++) + for (unsigned i = 0; i < _commandCount; i++) { - for (unsigned j = 0; j < _commandSetSize[i]; j++) + if (on_command(_commands[i], arg) != CHIP_NO_ERROR) { - if (on_command(&_commandSet[i][j], arg) != CHIP_NO_ERROR) + return; + } + } +} + +void Engine::RegisterCommands(shell_command_t * command_set, unsigned count, const char * prefix) +{ + if (this == &Engine::Root() || prefix == nullptr) + { + prefix = ""; + } + + if (_commandCount + count > CHIP_SHELL_MAX_MODULES) + { + ChipLogError(Shell, "Max number of commands registered to this shell"); + assert(0); + } + + for (unsigned i = 0; i < count; i++) + { + _commands[_commandCount + i] = &command_set[i]; + } + _commandCount += count; + + Engine::InsertShellMap(prefix, this); +} + +void Engine::InsertShellMap(char const * prefix, Engine * shell) +{ + shell_map_t * map = Engine::theShellMapListHead; + while (map != NULL) + { + if (strcmp(prefix, map->prefix) == 0) + { + for (size_t i = 0; i < map->enginec; i++) { - return; + if (map->enginev[i] == shell) + { + return; + } } + if (map->enginec == sizeof(map->enginev)) + { + ChipLogError(Shell, "Max number of shells registered under this prefix"); + assert(0); + } + map->enginev[map->enginec] = shell; + map->enginec++; + return; } + map = map->next; } + shell_map_t * new_map = new shell_map_t; + new_map->prefix = prefix; + new_map->enginev[0] = shell; + new_map->enginec = 1; + new_map->next = Engine::theShellMapListHead; + Engine::theShellMapListHead = new_map; } -void Engine::RegisterCommands(shell_command_t * command_set, unsigned count) +CHIP_ERROR Engine::GetCommandCompletions(cmd_completion_context * context) { - if (_commandSetCount >= CHIP_SHELL_MAX_MODULES) + + if (Engine::theShellMapListHead == NULL) { - ChipLogError(Shell, "Max number of modules reached\n"); - assert(0); + ChipLogDetail(Shell, "There isn't any command registered yet."); + return CHIP_NO_ERROR; } + const char * buf = context->line_buf; + if (buf == nullptr) + { + buf = ""; + } + + int last_space_idx = -1; + int buf_len = strlen(buf); - _commandSet[_commandSetCount] = command_set; - _commandSetSize[_commandSetCount] = count; - ++_commandSetCount; + // find space in buf + for (int i = buf_len - 1; i > -1; i--) + { + if (buf[i] == ' ') + { + last_space_idx = i; + break; + } + } + + // check whether the buf perfectly matches a prefix + bool perfect_match = false; + shell_map_t * map = Engine::theShellMapListHead; + + while (map != NULL) + { + + if (strcmp(buf, map->prefix) == 0) + { + perfect_match = true; + break; + } + map = map->next; + } + + char * prefix; + char * incomplete_cmd; + + if (perfect_match) + { + // If it's a perfect match + // - use the whole buf as the prefix + // - there is no "incomplete command" so set it to empty + prefix = new char[buf_len + 1]; + incomplete_cmd = new char[1]; + strncpy(prefix, buf, buf_len + 1); + strncpy(incomplete_cmd, "", 1); + } + else + { + // If it's not a perfect match: + // - prefix is up to the last space + // - incomplete_cmd is what's after the last space + bool no_space = (last_space_idx == -1) ? true : false; + prefix = new char[last_space_idx + 1 + no_space]; + incomplete_cmd = new char[buf_len - last_space_idx]; + strncpy(prefix, buf, last_space_idx + 1 + no_space); + strncpy(incomplete_cmd, &buf[last_space_idx + 1], buf_len - last_space_idx); + } + + // Array to pass arguments into the ForEachCommand call + void * lambda_args[] = { context, incomplete_cmd }; + map = Engine::theShellMapListHead; + + while (map != NULL && context->cmdc < CHIP_SHELL_MAX_CMD_COMPLETIONS) + { + if (strcmp(prefix, map->prefix) == 0) + { + context->ret_prefix = map->prefix; + for (unsigned i = 0; i < map->enginec; i++) + { + map->enginev[i]->ForEachCommand( + [](shell_command_t * cmd, void * arg) -> CHIP_ERROR { + cmd_completion_context * ctx = (cmd_completion_context *) ((void **) arg)[0]; + char * _incomplete_cmd = (char *) ((void **) arg)[1]; + // For end nodes like "ble adv", need to avoid duplicate returns. + // Return for prefix="ble adv" cmd=""; reject for prefix="ble" cmd="adv" + if ((strcmp(cmd->cmd_name, _incomplete_cmd) != 0 && + strncmp(cmd->cmd_name, _incomplete_cmd, strlen(_incomplete_cmd)) == 0) || + strcmp(_incomplete_cmd, "") == 0) + { + ctx->cmdc++; + ctx->cmdv[ctx->cmdc - 1] = cmd; + } + return CHIP_NO_ERROR; + }, + lambda_args); + } + break; + } + + map = map->next; + } + + delete[] prefix; + delete[] incomplete_cmd; + + return CHIP_NO_ERROR; } CHIP_ERROR Engine::ExecCommand(int argc, char * argv[]) { CHIP_ERROR retval = CHIP_ERROR_INVALID_ARGUMENT; - VerifyOrReturnError(argc > 0, retval); // Find the command - for (unsigned i = 0; i < _commandSetCount; i++) + for (unsigned i = 0; i < _commandCount; i++) { - for (unsigned j = 0; j < _commandSetSize[i]; j++) + if (strcmp(argv[0], _commands[i]->cmd_name) == 0) { - if (strcmp(argv[0], _commandSet[i][j].cmd_name) == 0) - { - // Execute the command! - retval = _commandSet[i][j].cmd_func(argc - 1, argv + 1); - break; - } + // Execute the command! + retval = _commands[i]->cmd_func(argc - 1, argv + 1); + break; } } diff --git a/src/lib/shell/Engine.h b/src/lib/shell/Engine.h index be65ed45c94dc0..7cbf51b7f4eeae 100644 --- a/src/lib/shell/Engine.h +++ b/src/lib/shell/Engine.h @@ -25,8 +25,9 @@ #include "streamer.h" +#include +#include #include - #include #include @@ -46,6 +47,10 @@ #define CHIP_SHELL_MAX_TOKENS 10 #endif // CHIP_SHELL_MAX_TOKENS +#ifndef CHIP_SHELL_MAX_CMD_COMPLETIONS +#define CHIP_SHELL_MAX_CMD_COMPLETIONS 32 +#endif // CHIP_SHELL_MAX_CMD_COMPLETIONS + namespace chip { namespace Shell { @@ -93,20 +98,76 @@ typedef const struct shell_command shell_command_t; */ typedef CHIP_ERROR shell_command_iterator_t(shell_command_t * command, void * arg); +class Engine; + +/** + * A map of the Engine instances and the command prefix that they correspond to. + * The struct itself is a linked list node. + * + * prefix: The command prefix (until n-1th token of full command) e.g. "device", + * or "dns resolve". + * enginev: An array of the shells that have registered commands under the preifx. + * enginec: The number of shells that have registered commands under the preifx. + * next: The next shell in the list. + * + */ +typedef struct shell_map +{ + const char * prefix; + Engine * enginev[CHIP_SHELL_MAX_MODULES]; + size_t enginec = 0; + shell_map * next; +} shell_map_t; + +/** + * A context object for passing request and receiving results with GetCmdCompletion. + * + * line_buf: The user input command to request for completion. If the applications prepends + * "matter " to all matter commands, please remove the "matter " prefix from the buffer. + * ret_prefix: The returned command prefix (up until the last space, not included). + * cmdv: The command completion candidates unter the prefix in "ret_prefix". + * cmdc: The number of command completion candidates in cmdv. + * + * Initialization: + * + * cmd_completion_context context = cmd_completion_context("dns browse c"); + * + */ +typedef struct cmd_completion_context +{ + const char * line_buf; + const char * ret_prefix; + shell_command_t * cmdv[CHIP_SHELL_MAX_CMD_COMPLETIONS]; + size_t cmdc = 0; + + cmd_completion_context(){}; + cmd_completion_context(const char * _line_buf) { line_buf = _line_buf; }; +} cmd_completion_context; + class Engine { protected: static Engine theEngineRoot; + static shell_map_t * theShellMapListHead; + shell_command_t * _commands[CHIP_SHELL_MAX_MODULES]; + unsigned _commandCount; - shell_command_t * _commandSet[CHIP_SHELL_MAX_MODULES]; - unsigned _commandSetSize[CHIP_SHELL_MAX_MODULES]; - unsigned _commandSetCount; + static void InsertShellMap(char const * prefix, Engine * shell); public: - Engine() {} + Engine(){}; /** Return the root singleton for the Shell command hierarchy. */ - static Engine & Root() { return theEngineRoot; } + static Engine & Root() { return theEngineRoot; }; + + /** + * Get command completions by command prefix. + * + * @param context The command completion context to pass request and receive results. + * + * @return CHIP_ERROR error code. + */ + static CHIP_ERROR GetCommandCompletions(cmd_completion_context * context); /** * Registers a set of defaults commands (help) for all Shell and sub-Shell instances. @@ -141,8 +202,11 @@ class Engine * * @param command_set An array of commands to add to the shell. * @param count The number of commands in the command set array. + * @param prefix The prefix of this command set in the full command path. + * e.g. "matter base64" is prefix for "endcode" and "decode". + * Use double quoted empty string `""` or nullptr for root commands. */ - void RegisterCommands(shell_command_t * command_set, unsigned count); + void RegisterCommands(shell_command_t * command_set, unsigned count, const char * prefix); /** * Runs the shell mainloop. Will display the prompt and enable interaction. diff --git a/src/lib/shell/MainLoopZephyr.cpp b/src/lib/shell/MainLoopZephyr.cpp index e70a29c52c9dce..27240379356a9f 100644 --- a/src/lib/shell/MainLoopZephyr.cpp +++ b/src/lib/shell/MainLoopZephyr.cpp @@ -14,14 +14,20 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +#include #include #include +#include #include #include #include +#include +using chip::Shell::cmd_completion_context; using chip::Shell::Engine; +using chip::Shell::shell_command_t; +using chip::Shell::shell_map_t; static int cmd_matter(const struct shell * shell, size_t argc, char ** argv) { @@ -29,15 +35,96 @@ static int cmd_matter(const struct shell * shell, size_t argc, char ** argv) return (Engine::Root().ExecCommand(argc - 1, argv + 1) == CHIP_NO_ERROR) ? 0 : -ENOEXEC; } +/** + * This recursive function obtains all sub-commands (possbile completions) for a command node + * and build it into a tree of Zephyr's shell_static_entry structure. + * + * @param prefix The path/prefix until the root command node of the tree, without tailing space. + * e.g. "dns browse". Set to emptry string "" if building tree for a root-level command. + * @param syntax The single-word command name/syntax for the root node of the tree. e.g. "resolve". + * Set to emptry string "" if calling to build from the root (to include all commands). + * @param help The help text for the command at roote node of the tree. Set to NULL if none. + * + * @return The shell_static_entry structure tree that chains all the sub-commands from the + * requested command node. + * + */ +const struct shell_static_entry * build_command_tree(const char * prefix, const char * syntax, const char * help) +{ + + char * sub_prefix = new char[strlen(prefix) + strlen(syntax) + 2]; + size_t pos = 0; + if (strcmp(prefix, "") != 0) + { + strncpy(&sub_prefix[pos], prefix, strlen(prefix)); + pos += strlen(prefix); + strncpy(&sub_prefix[pos], " ", 1); + pos += 1; + } + strncpy(&sub_prefix[pos], syntax, strlen(syntax) + 1); + + cmd_completion_context context = cmd_completion_context(sub_prefix); + CHIP_ERROR ret = Engine::GetCommandCompletions(&context); + + const struct shell_static_entry * static_entry; + if (ret == CHIP_NO_ERROR && context.cmdc != 0) + { + const struct shell_static_entry * sub_static_entry = new shell_static_entry[context.cmdc + 1]; + for (size_t i = 0; i < context.cmdc; i++) + { + shell_command_t * subcmd = context.cmdv[i]; + const struct shell_static_entry * sub_entry = build_command_tree(sub_prefix, subcmd->cmd_name, subcmd->cmd_help); + if (sub_entry == nullptr) + { + // leaf node, the command should require no argument but can accept optional arguments + const struct shell_static_entry leaf_entry = { + .syntax = subcmd->cmd_name, .help = subcmd->cmd_help, .subcmd = NULL, .handler = NULL, .args = { 0, 10 } + }; + memcpy((shell_static_entry *) &sub_static_entry[i], &leaf_entry, sizeof(struct shell_static_entry)); + continue; + } + memcpy((shell_static_entry *) &sub_static_entry[i], sub_entry, sizeof(struct shell_static_entry)); + } + + // Zephyr's shell_cmd_entry.u.entry needs an additional NULL element to terminate the array. + memset((shell_static_entry *) &sub_static_entry[context.cmdc], 0, sizeof(struct shell_static_entry)); + + const struct shell_cmd_entry * sub_cmd_entry = + new const shell_cmd_entry{ .is_dynamic = false, + .u = { .entry = (const struct shell_static_entry *) sub_static_entry } }; + + // The non-leaf nodes should require at least 1 required argument (for its child) + // Set the root command to use syntax "matter" + // Only the root "matter" node should have a handler function in Zephry-shell, so that all commands are sent + // to be dispatched by their registered Engine instances. + static_entry = new const shell_static_entry{ .syntax = (strcmp(syntax, "") == 0) ? "matter" : syntax, + .help = help, + .subcmd = (const struct shell_cmd_entry *) sub_cmd_entry, + .handler = (strcmp(syntax, "") == 0) ? cmd_matter : NULL, + .args = { 1, 10 } }; + } + else + { + static_entry = nullptr; + } + delete[] sub_prefix; + return static_entry; +} + +static void register_matter_command_to_zephyr() +{ + static const struct shell_static_entry _shell_matter = *build_command_tree("", "", "Matter commands"); + static const struct shell_cmd_entry shell_cmd_matter __attribute__((section("." STRINGIFY(shell_root_cmd_matter)))) + __attribute__((used)) = { .is_dynamic = false, .u = { .entry = &_shell_matter } }; +} + static int RegisterCommands(const struct device * dev) { Engine::Root().RegisterDefaultCommands(); + register_matter_command_to_zephyr(); return 0; } - -SYS_INIT(RegisterCommands, POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT); - -SHELL_CMD_ARG_REGISTER(matter, NULL, "Matter commands", cmd_matter, 1, 10); +SYS_INIT(RegisterCommands, APPLICATION, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT); namespace chip { namespace Shell { diff --git a/src/lib/shell/commands/BLE.cpp b/src/lib/shell/commands/BLE.cpp index 9563e4c83054bb..f52550096a1de3 100644 --- a/src/lib/shell/commands/BLE.cpp +++ b/src/lib/shell/commands/BLE.cpp @@ -111,10 +111,10 @@ void RegisterBLECommands() static const shell_command_t sBLECommand = { &BLEDispatch, "ble", "BLE transport commands" }; // Register `device` subcommands with the local shell dispatcher. - sShellDeviceSubcommands.RegisterCommands(sBLESubCommands, ArraySize(sBLESubCommands)); + sShellDeviceSubcommands.RegisterCommands(sBLESubCommands, ArraySize(sBLESubCommands), "ble"); // Register the root `btp` command with the top-level shell. - Engine::Root().RegisterCommands(&sBLECommand, 1); + Engine::Root().RegisterCommands(&sBLECommand, 1, nullptr); } } // namespace Shell diff --git a/src/lib/shell/commands/Base64.cpp b/src/lib/shell/commands/Base64.cpp index 40d4432640352a..ecc9908305b13e 100644 --- a/src/lib/shell/commands/Base64.cpp +++ b/src/lib/shell/commands/Base64.cpp @@ -88,10 +88,10 @@ void RegisterBase64Commands() static const shell_command_t sBase64Command = { &Base64Dispatch, "base64", "Base64 encode / decode utilities" }; // Register `base64` subcommands with the local shell dispatcher. - sShellBase64Commands.RegisterCommands(sBase64SubCommands, ArraySize(sBase64SubCommands)); + sShellBase64Commands.RegisterCommands(sBase64SubCommands, ArraySize(sBase64SubCommands), "base64"); // Register the root `base64` command with the top-level shell. - Engine::Root().RegisterCommands(&sBase64Command, 1); + Engine::Root().RegisterCommands(&sBase64Command, 1, nullptr); } } // namespace Shell diff --git a/src/lib/shell/commands/Config.cpp b/src/lib/shell/commands/Config.cpp index d8686c41bc5ba3..c5e6ec6458f660 100644 --- a/src/lib/shell/commands/Config.cpp +++ b/src/lib/shell/commands/Config.cpp @@ -191,7 +191,6 @@ static CHIP_ERROR ConfigHandler(int argc, char ** argv) void RegisterConfigCommands() { - static const shell_command_t sConfigComand = { &ConfigHandler, "config", "Manage device configuration. Usage to dump value: config [param_name] and " "to set some values (discriminator): config [param_name] [param_value]." }; @@ -206,10 +205,9 @@ void RegisterConfigCommands() }; // Register `config` subcommands with the local shell dispatcher. - sShellConfigSubcommands.RegisterCommands(sConfigSubCommands, ArraySize(sConfigSubCommands)); - + sShellConfigSubcommands.RegisterCommands(sConfigSubCommands, ArraySize(sConfigSubCommands), "config"); // Register the root `config` command with the top-level shell. - Engine::Root().RegisterCommands(&sConfigComand, 1); + Engine::Root().RegisterCommands(&sConfigComand, 1, nullptr); return; } diff --git a/src/lib/shell/commands/Device.cpp b/src/lib/shell/commands/Device.cpp index 7e26a75a5c32fb..56c8b91f0df506 100644 --- a/src/lib/shell/commands/Device.cpp +++ b/src/lib/shell/commands/Device.cpp @@ -65,10 +65,10 @@ void RegisterDeviceCommands() static const shell_command_t sDeviceComand = { &DeviceHandler, "device", "Device management commands" }; // Register `device` subcommands with the local shell dispatcher. - sShellDeviceSubcommands.RegisterCommands(sDeviceSubCommands, ArraySize(sDeviceSubCommands)); + sShellDeviceSubcommands.RegisterCommands(sDeviceSubCommands, ArraySize(sDeviceSubCommands), "device"); // Register the root `device` command with the top-level shell. - Engine::Root().RegisterCommands(&sDeviceComand, 1); + Engine::Root().RegisterCommands(&sDeviceComand, 1, nullptr); } } // namespace Shell diff --git a/src/lib/shell/commands/Dns.cpp b/src/lib/shell/commands/Dns.cpp index 57c182da402598..04c59026a2fefb 100644 --- a/src/lib/shell/commands/Dns.cpp +++ b/src/lib/shell/commands/Dns.cpp @@ -250,13 +250,13 @@ void RegisterDnsCommands() static const shell_command_t sDnsCommand = { &DnsHandler, "dns", "Dns client commands" }; // Register `dns browse` subcommands - sShellDnsBrowseSubcommands.RegisterCommands(sDnsBrowseSubCommands, ArraySize(sDnsBrowseSubCommands)); + sShellDnsBrowseSubcommands.RegisterCommands(sDnsBrowseSubCommands, ArraySize(sDnsBrowseSubCommands), "dns browse"); // Register `dns` subcommands with the local shell dispatcher. - sShellDnsSubcommands.RegisterCommands(sDnsSubCommands, ArraySize(sDnsSubCommands)); + sShellDnsSubcommands.RegisterCommands(sDnsSubCommands, ArraySize(sDnsSubCommands), "dns"); // Register the root `dns` command with the top-level shell. - Engine::Root().RegisterCommands(&sDnsCommand, 1); + Engine::Root().RegisterCommands(&sDnsCommand, 1, nullptr); } } // namespace Shell diff --git a/src/lib/shell/commands/Meta.cpp b/src/lib/shell/commands/Meta.cpp index 7bf188de763e43..5e061afc769951 100644 --- a/src/lib/shell/commands/Meta.cpp +++ b/src/lib/shell/commands/Meta.cpp @@ -71,7 +71,7 @@ void RegisterMetaCommands() std::atexit(AtExitShell); - Engine::Root().RegisterCommands(sCmds, ArraySize(sCmds)); + Engine::Root().RegisterCommands(sCmds, ArraySize(sCmds), nullptr); } } // namespace Shell diff --git a/src/lib/shell/commands/NFC.cpp b/src/lib/shell/commands/NFC.cpp index 23b745b1901b90..2f7d0dd588bfac 100644 --- a/src/lib/shell/commands/NFC.cpp +++ b/src/lib/shell/commands/NFC.cpp @@ -90,7 +90,7 @@ void RegisterNFCCommands() "Start, stop or get nfc emulation state. Usage: nfc " }; // Register the root `device` command with the top-level shell. - Engine::Root().RegisterCommands(&sDeviceComand, 1); + Engine::Root().RegisterCommands(&sDeviceComand, 1, nullptr); } } // namespace Shell diff --git a/src/lib/shell/commands/OnboardingCodes.cpp b/src/lib/shell/commands/OnboardingCodes.cpp index 83978e502519dd..5cded64141a55f 100644 --- a/src/lib/shell/commands/OnboardingCodes.cpp +++ b/src/lib/shell/commands/OnboardingCodes.cpp @@ -169,7 +169,7 @@ void RegisterOnboardingCodesCommands() }; // Register the root `device` command with the top-level shell. - Engine::Root().RegisterCommands(&sDeviceComand, 1); + Engine::Root().RegisterCommands(&sDeviceComand, 1, nullptr); return; } diff --git a/src/lib/shell/commands/Ota.cpp b/src/lib/shell/commands/Ota.cpp index f0569d2c68bb24..7547d3d0f4279a 100644 --- a/src/lib/shell/commands/Ota.cpp +++ b/src/lib/shell/commands/Ota.cpp @@ -198,12 +198,12 @@ void RegisterOtaCommands() { &ProgressHandler, "progress", "Gets progress of a current image update process. Usage: ota progress" } }; - sSubShell.RegisterCommands(subCommands, ArraySize(subCommands)); + sSubShell.RegisterCommands(subCommands, ArraySize(subCommands), "ota"); // Register the root `ota` command in the top-level shell. static const shell_command_t otaCommand = { &OtaHandler, "ota", "OTA commands" }; - Engine::Root().RegisterCommands(&otaCommand, 1); + Engine::Root().RegisterCommands(&otaCommand, 1, nullptr); } } // namespace Shell diff --git a/src/lib/shell/commands/WiFi.cpp b/src/lib/shell/commands/WiFi.cpp index ed531e6bf0bf20..901845f5c0e8ca 100644 --- a/src/lib/shell/commands/WiFi.cpp +++ b/src/lib/shell/commands/WiFi.cpp @@ -138,8 +138,8 @@ void RegisterWiFiCommands() }; static const shell_command_t sWiFiCommand = { &WiFiDispatch, "wifi", "Usage: wifi " }; - sShellWiFiSubCommands.RegisterCommands(sWiFiSubCommands, ArraySize(sWiFiSubCommands)); - Engine::Root().RegisterCommands(&sWiFiCommand, 1); + sShellWiFiSubCommands.RegisterCommands(sWiFiSubCommands, ArraySize(sWiFiSubCommands), "wifi"); + Engine::Root().RegisterCommands(&sWiFiCommand, 1, nullptr); } } // namespace Shell