From 309e7b81cb1de52445b866920e667f237de325a4 Mon Sep 17 00:00:00 2001 From: Boris Zbarsky Date: Thu, 27 May 2021 15:42:32 -0400 Subject: [PATCH] Revert "[shell] enable shell as an optional module in apps (#7080)" This reverts commit c8f286f6d7dcbc1c69925edd3ca5bef714824e27 because the result does not compile. Fixes https://github.com/project-chip/connectedhomeip/issues/7200 --- config/esp32/components/chip/component.mk | 7 - examples/all-clusters-app/esp32/main/main.cpp | 12 - .../all-clusters-app/esp32/sdkconfig.defaults | 3 - examples/platform/linux/AppMain.cpp | 9 - examples/platform/linux/BUILD.gn | 13 +- examples/shell/esp32/main/main.cpp | 67 +++++- examples/shell/shell_common/cmd_misc.cpp | 2 +- examples/shell/shell_common/cmd_otcli.cpp | 4 +- examples/shell/shell_common/cmd_ping.cpp | 2 +- examples/shell/shell_common/cmd_send.cpp | 2 +- examples/shell/standalone/main.cpp | 2 +- src/lib/shell/BUILD.gn | 9 +- src/lib/shell/Commands.cpp | 41 ++++ src/lib/shell/Engine.cpp | 205 ++++++++++++++++-- src/lib/shell/Engine.h | 38 +++- src/lib/shell/MainLoopDefault.cpp | 201 ----------------- src/lib/shell/MainLoopESP32.cpp | 99 --------- src/lib/shell/commands/BLE.cpp | 4 +- src/lib/shell/commands/Base64.cpp | 4 +- src/lib/shell/commands/Config.cpp | 3 +- src/lib/shell/commands/Meta.cpp | 4 +- src/lib/shell/commands/WiFi.cpp | 4 +- src/lib/shell/streamer_esp32.cpp | 3 +- src/lib/shell/tests/BUILD.gn | 8 +- src/lib/shell/tests/TestShell.cpp | 110 ++++++++++ .../{TestStreamerStdio.h => TestShell.h} | 1 + src/lib/shell/tests/TestShellDriver.cpp | 34 +++ src/lib/shell/tests/TestStreamerStdio.cpp | 2 +- .../shell/tests/TestStreamerStdioDriver.cpp | 2 +- 29 files changed, 496 insertions(+), 399 deletions(-) create mode 100644 src/lib/shell/Commands.cpp delete mode 100644 src/lib/shell/MainLoopDefault.cpp delete mode 100644 src/lib/shell/MainLoopESP32.cpp create mode 100644 src/lib/shell/tests/TestShell.cpp rename src/lib/shell/tests/{TestStreamerStdio.h => TestShell.h} (97%) create mode 100644 src/lib/shell/tests/TestShellDriver.cpp diff --git a/config/esp32/components/chip/component.mk b/config/esp32/components/chip/component.mk index bf7cbee1f79dd6..8f94ff98e2200d 100644 --- a/config/esp32/components/chip/component.mk +++ b/config/esp32/components/chip/component.mk @@ -83,10 +83,6 @@ COMPONENT_ADD_INCLUDEDIRS = project-config \ COMPONENT_ADD_LDFLAGS = -L$(OUTPUT_DIR)/lib/ \ -lCHIP -ifdef CONFIG_ENABLE_CHIP_SHELL -COMPONENT_ADD_LDFLAGS += -lCHIPShell -endif - ifdef CONFIG_ENABLE_PW_RPC COMPONENT_ADD_LDFLAGS += -lPwRpc endif @@ -139,9 +135,6 @@ endif echo "pw_sys_io_BACKEND = \"//third_party/connectedhomeip/examples/platform/esp32/pw_sys_io:pw_sys_io_esp32\"" >> $(OUTPUT_DIR)/args.gn ;\ echo "dir_pw_third_party_nanopb = \"//third_party/connectedhomeip/third_party/nanopb/repo\"" >>$(OUTPUT_DIR)/args.gn ;\ fi - if [[ "$(CONFIG_ENABLE_CHIP_SHELL)" = "y" ]]; then \ - echo "chip_build_libshell = true" >> $(OUTPUT_DIR)/args.gn ;\ - fi if [[ "$(CONFIG_USE_MINIMAL_MDNS)" = "n" ]]; then \ echo "chip_mdns = platform" >> $(OUTPUT_DIR)/args.gn ;\ fi diff --git a/examples/all-clusters-app/esp32/main/main.cpp b/examples/all-clusters-app/esp32/main/main.cpp index fad81186e81789..44e4c9d077111c 100644 --- a/examples/all-clusters-app/esp32/main/main.cpp +++ b/examples/all-clusters-app/esp32/main/main.cpp @@ -49,7 +49,6 @@ #include #include #include -#include #include #include #include @@ -546,13 +545,6 @@ class AppCallbacks : public AppDelegate void OnPairingWindowClosed() override { pairingWindowLED.Set(false); } }; -#if CONFIG_ENABLE_CHIP_SHELL -void ChipShellTask(void * args) -{ - chip::Shell::Engine::Root().RunMainLoop(); -} -#endif // CONFIG_ENABLE_CHIP_SHELL - } // namespace extern "C" void app_main() @@ -607,10 +599,6 @@ extern "C" void app_main() AppCallbacks callbacks; InitServer(&callbacks); -#if CONFIG_ENABLE_CHIP_SHELL - xTaskCreate(&ChipShellTask, "chip_shell", 2048, NULL, 5, NULL); -#endif - SetupPretendDevices(); std::string qrCodeText = createSetupPayload(); diff --git a/examples/all-clusters-app/esp32/sdkconfig.defaults b/examples/all-clusters-app/esp32/sdkconfig.defaults index e12494a01910a7..6f6777ab62edd8 100644 --- a/examples/all-clusters-app/esp32/sdkconfig.defaults +++ b/examples/all-clusters-app/esp32/sdkconfig.defaults @@ -45,6 +45,3 @@ CONFIG_DEVICE_PRODUCT_ID=0x4541 # Main task needs a bit more stack than the default # default is 3584, bump this up to 4k. CONFIG_ESP_MAIN_TASK_STACK_SIZE=4096 - -#enable debug shell -CONFIG_ENABLE_CHIP_SHELL=y diff --git a/examples/platform/linux/AppMain.cpp b/examples/platform/linux/AppMain.cpp index c5968cb66a81e1..216c3c0069c9ab 100644 --- a/examples/platform/linux/AppMain.cpp +++ b/examples/platform/linux/AppMain.cpp @@ -25,7 +25,6 @@ #include #include #include -#include #include #include #include @@ -41,7 +40,6 @@ using namespace chip; using namespace chip::Inet; using namespace chip::Transport; using namespace chip::DeviceLayer; -using chip::Shell::Engine; namespace { void EventHandler(const chip::DeviceLayer::ChipDeviceEvent * event, intptr_t arg) @@ -112,15 +110,8 @@ int ChipLinuxAppInit(int argc, char ** argv) void ChipLinuxAppMainLoop() { -#if CHIP_ENABLE_SHELL - std::thread shellThread([]() { Engine::Root().RunMainLoop(); }); -#endif - // Init ZCL Data Model and CHIP App Server InitServer(); chip::DeviceLayer::PlatformMgr().RunEventLoop(); -#if CHIP_ENABLE_SHELL - shellThread.join(); -#endif } diff --git a/examples/platform/linux/BUILD.gn b/examples/platform/linux/BUILD.gn index 189bd1310c6ac2..a8964d31b74cbb 100644 --- a/examples/platform/linux/BUILD.gn +++ b/examples/platform/linux/BUILD.gn @@ -19,10 +19,6 @@ config("app-main-config") { include_dirs = [ "." ] } -declare_args() { - chip_enable_shell = false -} - source_set("app-main") { sources = [ "AppMain.cpp", @@ -31,10 +27,8 @@ source_set("app-main") { "Options.h", ] - defines = [] - if (chip_enable_pw_rpc) { - defines += [ "PW_RPC_ENABLED" ] + defines = [ "PW_RPC_ENABLED" ] } public_deps = [ @@ -42,10 +36,5 @@ source_set("app-main") { "${chip_root}/src/lib", ] - if (chip_enable_shell) { - defines += [ "CHIP_ENABLE_SHELL" ] - public_deps += [ "${chip_root}/src/lib/shell" ] - } - public_configs = [ ":app-main-config" ] } diff --git a/examples/shell/esp32/main/main.cpp b/examples/shell/esp32/main/main.cpp index 7f6879663c9080..fe76a23118c88b 100644 --- a/examples/shell/esp32/main/main.cpp +++ b/examples/shell/esp32/main/main.cpp @@ -15,27 +15,80 @@ * limitations under the License. */ +#include "esp_console.h" +#include "esp_event.h" +#include "esp_log.h" +#include "esp_netif.h" +#include "esp_system.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "linenoise/linenoise.h" #include "nvs_flash.h" +#include "support/CHIPMem.h" +#include "support/ErrorStr.h" #include #include -#include #include #include -using chip::Shell::Engine; +class ShellLineArgs +{ +public: + ShellLineArgs(char * line, TaskHandle_t source_task) : m_line(line), m_source_task(source_task) {} + char * GetLine() { return m_line; } + void WaitShellProcessDone() { ulTaskNotifyTake(pdTRUE, portMAX_DELAY); } + void NotifyShellProcessDone() { xTaskNotifyGive(m_source_task); } -static void chip_shell_task(void * args) +private: + char * m_line; + TaskHandle_t m_source_task; +}; + +static void process_shell_line(intptr_t context) { - Engine::Root().RunMainLoop(); + ShellLineArgs * shellArgs = reinterpret_cast(context); + int ret; + esp_console_run(shellArgs->GetLine(), &ret); + if (ret) + { + printf("Error: %s\r\n", chip::ErrorStr(ret)); + } + else + { + printf("Done\r\n"); + } + + linenoiseFree(shellArgs->GetLine()); + shellArgs->NotifyShellProcessDone(); } -extern "C" void app_main(void) +static void chip_shell_task(void * args) { - ESP_ERROR_CHECK(nvs_flash_init()); chip::Platform::MemoryInit(); chip::DeviceLayer::PlatformMgr().InitChipStack(); chip::DeviceLayer::PlatformMgr().StartEventLoopTask(); + int ret = chip::Shell::streamer_init(chip::Shell::streamer_get()); + assert(ret == 0); cmd_ping_init(); - xTaskCreate(&chip_shell_task, "chip_shell", 2048, NULL, 5, NULL); + while (true) + { + const char * prompt = LOG_COLOR_I "> " LOG_RESET_COLOR; + char * line = linenoise(prompt); + printf("\r\n"); + if (line == NULL || strlen(line) == 0) + { + continue; + } + ShellLineArgs shellArgs(line, xTaskGetCurrentTaskHandle()); + linenoiseHistoryAdd(line); + chip::DeviceLayer::PlatformMgr().ScheduleWork(process_shell_line, reinterpret_cast(&shellArgs)); + shellArgs.WaitShellProcessDone(); + } +} + +extern "C" void app_main(void) +{ + ESP_ERROR_CHECK(nvs_flash_init()); + xTaskCreate(&chip_shell_task, "chip_shell", 4096, NULL, 5, NULL); } diff --git a/examples/shell/shell_common/cmd_misc.cpp b/examples/shell/shell_common/cmd_misc.cpp index da05dbd33425ed..1d666c6b172823 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)); + shell_register(cmds_misc, ArraySize(cmds_misc)); } diff --git a/examples/shell/shell_common/cmd_otcli.cpp b/examples/shell/shell_common/cmd_otcli.cpp index c74dacbd1d0e60..b6cf7c017599ea 100644 --- a/examples/shell/shell_common/cmd_otcli.cpp +++ b/examples/shell/shell_common/cmd_otcli.cpp @@ -52,7 +52,7 @@ using namespace chip::DeviceLayer; using namespace chip::Logging; using namespace chip::ArgParser; -static chip::Shell::Engine sShellOtcliSubcommands; +static chip::Shell::Shell sShellOtcliSubcommands; int cmd_otcli_help_iterator(shell_command_t * command, void * arg) { @@ -172,6 +172,6 @@ void cmd_otcli_init() #endif // Register the root otcli command with the top-level shell. - Engine::Root().RegisterCommands(&cmds_otcli_root, 1); + shell_register(&cmds_otcli_root, 1); #endif // CHIP_ENABLE_OPENTHREAD } diff --git a/examples/shell/shell_common/cmd_ping.cpp b/examples/shell/shell_common/cmd_ping.cpp index 3e3c75f1e97f5e..f8995db411deff 100644 --- a/examples/shell/shell_common/cmd_ping.cpp +++ b/examples/shell/shell_common/cmd_ping.cpp @@ -488,5 +488,5 @@ static shell_command_t cmds_ping[] = { void cmd_ping_init() { - Engine::Root().RegisterCommands(cmds_ping, ArraySize(cmds_ping)); + shell_register(cmds_ping, ArraySize(cmds_ping)); } diff --git a/examples/shell/shell_common/cmd_send.cpp b/examples/shell/shell_common/cmd_send.cpp index 5a08a05b8f028a..9a6ef3f4a9869d 100644 --- a/examples/shell/shell_common/cmd_send.cpp +++ b/examples/shell/shell_common/cmd_send.cpp @@ -441,5 +441,5 @@ static shell_command_t cmds_send[] = { void cmd_send_init() { - Engine::Root().RegisterCommands(cmds_send, ArraySize(cmds_send)); + shell_register(cmds_send, ArraySize(cmds_send)); } diff --git a/examples/shell/standalone/main.cpp b/examples/shell/standalone/main.cpp index 96586f002a1619..28e2f6376b82ab 100644 --- a/examples/shell/standalone/main.cpp +++ b/examples/shell/standalone/main.cpp @@ -53,6 +53,6 @@ int main() cmd_ping_init(); cmd_send_init(); - Engine::Root().RunMainLoop(); + shell_task(nullptr); return 0; } diff --git a/src/lib/shell/BUILD.gn b/src/lib/shell/BUILD.gn index d4d765c1675998..80ac80b3e07969 100644 --- a/src/lib/shell/BUILD.gn +++ b/src/lib/shell/BUILD.gn @@ -37,19 +37,14 @@ static_library("shell") { output_name = "libCHIPShell" output_dir = "${root_out_dir}/lib" - sources = [] + sources = [ "Commands.cpp" ] if (chip_target_style == "unix") { sources += [ "streamer_stdio.cpp" ] } if (chip_device_platform == "esp32") { - sources += [ - "MainLoopESP32.cpp", - "streamer_esp32.cpp", - ] - } else { - sources += [ "MainLoopDefault.cpp" ] + sources += [ "streamer_esp32.cpp" ] } if (current_os == "zephyr") { diff --git a/src/lib/shell/Commands.cpp b/src/lib/shell/Commands.cpp new file mode 100644 index 00000000000000..02f6ab86fc4048 --- /dev/null +++ b/src/lib/shell/Commands.cpp @@ -0,0 +1,41 @@ +/* + * + * Copyright (c) 2021 Project CHIP Authors + * + * 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. + */ + +#include +#include +#include + +namespace chip { +namespace Shell { + +void Shell::RegisterDefaultCommands() +{ + RegisterBase64Commands(); + RegisterMetaCommands(); +#if CHIP_DEVICE_CONFIG_ENABLE_CHIPOBLE + RegisterBLECommands(); +#endif +#if CHIP_DEVICE_CONFIG_ENABLE_WIFI_STATION || CHIP_DEVICE_CONFIG_ENABLE_WIFI_AP + RegisterWiFiCommands(); +#endif +#if CONFIG_DEVICE_LAYER + RegisterConfigCommands(); +#endif +} + +} // namespace Shell +} // namespace chip diff --git a/src/lib/shell/Engine.cpp b/src/lib/shell/Engine.cpp index 616cf528c9fdad..7752b5bcc2f183 100644 --- a/src/lib/shell/Engine.cpp +++ b/src/lib/shell/Engine.cpp @@ -24,7 +24,6 @@ #include #include -#include #include #include #include @@ -42,9 +41,66 @@ using namespace chip::Logging; namespace chip { namespace Shell { -Engine Engine::theEngineRoot; +Shell Shell::theShellRoot; -void Engine::ForEachCommand(shell_command_iterator_t * on_command, void * arg) +intptr_t shell_line_read(char * buffer, size_t max) +{ + ssize_t read = 0; + bool done = false; + char * inptr = buffer; + + // Read in characters until we get a new line or we hit our max size. + while (((inptr - buffer) < static_cast(max)) && !done) + { + if (read == 0) + { + read = streamer_read(streamer_get(), inptr, 1); + } + + // Process any characters we just read in. + while (read > 0) + { + switch (*inptr) + { + case '\r': + case '\n': + streamer_printf(streamer_get(), "\r\n"); + *inptr = 0; // null terminate + done = true; + break; + case 0x7F: + // delete backspace character + 1 more + inptr -= 2; + if (inptr >= buffer - 1) + { + streamer_printf(streamer_get(), "\b \b"); + } + else + { + inptr = buffer - 1; + } + break; + default: + if (isprint(static_cast(*inptr)) || *inptr == '\t') + { + streamer_printf(streamer_get(), "%c", *inptr); + } + else + { + inptr--; + } + break; + } + + inptr++; + read--; + } + } + + return inptr - buffer; +} + +void Shell::ForEachCommand(shell_command_iterator_t * on_command, void * arg) { for (unsigned i = 0; i < _commandSetCount; i++) { @@ -58,7 +114,7 @@ void Engine::ForEachCommand(shell_command_iterator_t * on_command, void * arg) } } -void Engine::RegisterCommands(shell_command_t * command_set, unsigned count) +void Shell::RegisterCommands(shell_command_t * command_set, unsigned count) { if (_commandSetCount >= CHIP_SHELL_MAX_MODULES) { @@ -71,7 +127,7 @@ void Engine::RegisterCommands(shell_command_t * command_set, unsigned count) ++_commandSetCount; } -int Engine::ExecCommand(int argc, char * argv[]) +int Shell::ExecCommand(int argc, char * argv[]) { int retval = CHIP_ERROR_INVALID_ARGUMENT; @@ -93,19 +149,138 @@ int Engine::ExecCommand(int argc, char * argv[]) return retval; } -void Engine::RegisterDefaultCommands() +static bool IsSeparator(char aChar) { - RegisterBase64Commands(); - RegisterMetaCommands(); -#if CHIP_DEVICE_CONFIG_ENABLE_CHIPOBLE - RegisterBLECommands(); -#endif -#if CHIP_DEVICE_CONFIG_ENABLE_WIFI_STATION || CHIP_DEVICE_CONFIG_ENABLE_WIFI_AP - RegisterWiFiCommands(); -#endif + return (aChar == ' ') || (aChar == '\t') || (aChar == '\r') || (aChar == '\n'); +} + +static bool IsEscape(char aChar) +{ + return (aChar == '\\'); +} + +static bool IsEscapable(char aChar) +{ + return IsSeparator(aChar) || IsEscape(aChar); +} + +int Shell::TokenizeLine(char * buffer, char ** tokens, int max_tokens) +{ + size_t len = strlen(buffer); + int cursor = 0; + size_t i = 0; + + // Strip leading spaces + while (buffer[i] && buffer[i] == ' ') + { + i++; + } + + VerifyOrExit((len - i) > 0, cursor = 0); + + // The first token starts at the beginning. + tokens[cursor++] = &buffer[i]; + + for (; i < len && cursor < max_tokens; i++) + { + if (IsEscape(buffer[i]) && IsEscapable(buffer[i + 1])) + { + // include the null terminator: strlen(cmd) = strlen(cmd + 1) + 1 + memmove(&buffer[i], &buffer[i + 1], strlen(&buffer[i])); + } + else if (IsSeparator(buffer[i])) + { + buffer[i] = 0; + if (!IsSeparator(buffer[i + 1])) + { + tokens[cursor++] = &buffer[i + 1]; + } + } + } + + tokens[cursor] = nullptr; + +exit: + return cursor; +} + +void Shell::ProcessShellLineTask(intptr_t context) +{ + char * line = reinterpret_cast(context); + int retval; + int argc; + char * argv[CHIP_SHELL_MAX_TOKENS]; + + argc = shell_line_tokenize(line, argv, CHIP_SHELL_MAX_TOKENS); + + if (argc > 0) + { + retval = theShellRoot.ExecCommand(argc, argv); + + if (retval) + { + char errorStr[160]; + bool errorStrFound = FormatCHIPError(errorStr, sizeof(errorStr), retval); + if (!errorStrFound) + { + errorStr[0] = 0; + } + streamer_printf(streamer_get(), "Error %s: %s\r\n", argv[0], errorStr); + } + else + { + streamer_printf(streamer_get(), "Done\r\n", argv[0]); + } + } + else + { + // Empty input has no output -- just display prompt + } + Platform::MemoryFree(line); + streamer_printf(streamer_get(), CHIP_SHELL_PROMPT); +} + +void Shell::TaskLoop(void * arg) +{ + // char line[CHIP_SHELL_MAX_LINE_SIZE]; + + theShellRoot.RegisterDefaultCommands(); + streamer_printf(streamer_get(), CHIP_SHELL_PROMPT); + + while (true) + { + char * line = static_cast(Platform::MemoryAlloc(CHIP_SHELL_MAX_LINE_SIZE)); + shell_line_read(line, CHIP_SHELL_MAX_LINE_SIZE); #if CONFIG_DEVICE_LAYER - RegisterConfigCommands(); + DeviceLayer::PlatformMgr().ScheduleWork(ProcessShellLineTask, reinterpret_cast(line)); +#else + ProcessShellLineTask(reinterpret_cast(line)); #endif + } +} + +/** Utility function for running ForEachCommand on root shell. */ +void shell_command_foreach(shell_command_iterator_t * on_command, void * arg) +{ + return Shell::Root().ForEachCommand(on_command, arg); +} + +/** Utility function for running ForEachCommand on Root shell. */ +void shell_register(shell_command_t * command_set, unsigned count) +{ + return Shell::Root().RegisterCommands(command_set, count); +} + +/** Utility function for to tokenize an input line. */ +int shell_line_tokenize(char * buffer, char ** tokens, int max_tokens) +{ + return Shell::TokenizeLine(buffer, tokens, max_tokens); +} + +/** Utility function to run main shell task loop. */ +void shell_task(void * arg) +{ + return Shell::TaskLoop(arg); } } // namespace Shell diff --git a/src/lib/shell/Engine.h b/src/lib/shell/Engine.h index f8b280c91ba0aa..383492762f9bfb 100644 --- a/src/lib/shell/Engine.h +++ b/src/lib/shell/Engine.h @@ -91,20 +91,20 @@ typedef const struct shell_command shell_command_t; */ typedef int shell_command_iterator_t(shell_command_t * command, void * arg); -class Engine +class Shell { protected: - static Engine theEngineRoot; + static Shell theShellRoot; shell_command_t * _commandSet[CHIP_SHELL_MAX_MODULES]; unsigned _commandSetSize[CHIP_SHELL_MAX_MODULES]; unsigned _commandSetCount; public: - Engine() {} + Shell() {} /** Return the root singleton for the Shell command hierarchy. */ - static Engine & Root() { return theEngineRoot; } + static Shell & Root() { return theShellRoot; } /** * Registers a set of defaults commands (help) for all Shell and sub-Shell instances. @@ -143,16 +143,40 @@ class Engine void RegisterCommands(shell_command_t * command_set, unsigned count); /** - * Runs the shell mainloop. Will display the prompt and enable interaction. + * Utility function for converting a raw line typed into a shell into an array of words or tokens. * - * @note This is a blocking call and will not return until user types "exit" + * @param buffer String of the raw line typed into shell. + * @param tokens Array of words to be created by the tokenizer. + * This array will point to the same memory as passed in + * via buffer. Spaces will be replaced with NULL characters. + * @param max_tokens Maximum size of token array. * + * @return Number of tokens generated (argc). */ - void RunMainLoop(); + static int TokenizeLine(char * buffer, char ** tokens, int max_tokens); + + /** + * Main loop for shell. + * + * @param arg Unused context block for shell task to comply with task function syntax. + */ + static void TaskLoop(void * arg); private: static void ProcessShellLineTask(intptr_t context); }; +/** Utility function for running ForEachCommand on root shell. */ +void shell_command_foreach(shell_command_iterator_t * on_command, void * arg); + +/** Utility function for running ForEachCommand on Root shell. */ +void shell_register(shell_command_t * command_set, unsigned count); + +/** Utility function for to tokenize an input line. */ +int shell_line_tokenize(char * buffer, char ** tokens, int max_tokens); + +/** Utility function to run main shell task loop. */ +void shell_task(void * arg); + } // namespace Shell } // namespace chip diff --git a/src/lib/shell/MainLoopDefault.cpp b/src/lib/shell/MainLoopDefault.cpp deleted file mode 100644 index 9266d45850389e..00000000000000 --- a/src/lib/shell/MainLoopDefault.cpp +++ /dev/null @@ -1,201 +0,0 @@ -/* - * - * Copyright (c) 2021 Project CHIP Authors - * - * 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. - */ - -#include "streamer.h" -#include -#include -#include - -#include -#include - -using chip::FormatCHIPError; -using chip::Platform::MemoryAlloc; -using chip::Platform::MemoryFree; -using chip::Shell::Engine; -using chip::Shell::streamer_get; - -namespace { - -void ReadLine(char * buffer, size_t max) -{ - ssize_t read = 0; - bool done = false; - char * inptr = buffer; - - // Read in characters until we get a new line or we hit our max size. - while (((inptr - buffer) < static_cast(max)) && !done) - { - if (read == 0) - { - read = streamer_read(streamer_get(), inptr, 1); - } - - // Process any characters we just read in. - while (read > 0) - { - switch (*inptr) - { - case '\r': - case '\n': - streamer_printf(streamer_get(), "\r\n"); - *inptr = 0; // null terminate - done = true; - break; - case 0x7F: - // delete backspace character + 1 more - inptr -= 2; - if (inptr >= buffer - 1) - { - streamer_printf(streamer_get(), "\b \b"); - } - else - { - inptr = buffer - 1; - } - break; - default: - if (isprint(static_cast(*inptr)) || *inptr == '\t') - { - streamer_printf(streamer_get(), "%c", *inptr); - } - else - { - inptr--; - } - break; - } - - inptr++; - read--; - } - } -} - -bool IsSeparator(char ch) -{ - return (ch == ' ') || (ch == '\t') || (ch == '\r') || (ch == '\n'); -} - -bool IsEscape(char ch) -{ - return (ch == '\\'); -} - -bool IsEscapable(char ch) -{ - return IsSeparator(ch) || IsEscape(ch); -} - -int TokenizeLine(char * buffer, char ** tokens, int max_tokens) -{ - size_t len = strlen(buffer); - int cursor = 0; - size_t i = 0; - - // Strip leading spaces - while (buffer[i] && buffer[i] == ' ') - { - i++; - } - - if (len <= i) - { - return 0; - } - - // The first token starts at the beginning. - tokens[cursor++] = &buffer[i]; - - for (; i < len && cursor < max_tokens; i++) - { - if (IsEscape(buffer[i]) && IsEscapable(buffer[i + 1])) - { - // include the null terminator: strlen(cmd) = strlen(cmd + 1) + 1 - memmove(&buffer[i], &buffer[i + 1], strlen(&buffer[i])); - } - else if (IsSeparator(buffer[i])) - { - buffer[i] = 0; - if (!IsSeparator(buffer[i + 1])) - { - tokens[cursor++] = &buffer[i + 1]; - } - } - } - - tokens[cursor] = nullptr; - - return cursor; -} - -void ProcessShellLine(intptr_t args) -{ - int retval; - int argc; - char * argv[CHIP_SHELL_MAX_TOKENS]; - - char * line = reinterpret_cast(args); - argc = TokenizeLine(line, argv, CHIP_SHELL_MAX_TOKENS); - - if (argc > 0) - { - retval = Engine::Root().ExecCommand(argc, argv); - - if (retval) - { - char errorStr[160]; - bool errorStrFound = FormatCHIPError(errorStr, sizeof(errorStr), retval); - if (!errorStrFound) - { - errorStr[0] = 0; - } - streamer_printf(streamer_get(), "Error %s: %s\r\n", argv[0], errorStr); - } - else - { - streamer_printf(streamer_get(), "Done\r\n", argv[0]); - } - } - MemoryFree(line); - streamer_printf(streamer_get(), CHIP_SHELL_PROMPT); -} - -} // namespace - -namespace chip { -namespace Shell { - -void Engine::RunMainLoop() -{ - Engine::Root().RegisterDefaultCommands(); - streamer_printf(streamer_get(), CHIP_SHELL_PROMPT); - - while (true) - { - char * line = static_cast(Platform::MemoryAlloc(CHIP_SHELL_MAX_LINE_SIZE)); - ReadLine(line, CHIP_SHELL_MAX_LINE_SIZE); -#if CONFIG_DEVICE_LAYER - DeviceLayer::PlatformMgr().ScheduleWork(ProcessShellLine, reinterpret_cast(line)); -#else - ProcessShellLine(reinterpret_cast(line)); -#endif - } -} - -} // namespace Shell -} // namespace chip diff --git a/src/lib/shell/MainLoopESP32.cpp b/src/lib/shell/MainLoopESP32.cpp deleted file mode 100644 index ed0d91c2fa4d96..00000000000000 --- a/src/lib/shell/MainLoopESP32.cpp +++ /dev/null @@ -1,99 +0,0 @@ -/* - * - * Copyright (c) 2021 Project CHIP Authors - * - * 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. - */ - -#include "esp_console.h" -#include "esp_event.h" -#include "esp_log.h" -#include "esp_netif.h" -#include "esp_system.h" -#include "freertos/FreeRTOS.h" -#include "freertos/task.h" -#include "linenoise/linenoise.h" -#include "nvs_flash.h" - -#include -#include - -using chip::DeviceLayer::PlatformMgr; -using chip::Shell::Engine; - -namespace { - -class ShellLineArgs -{ -public: - ShellLineArgs(char * line, TaskHandle_t source_task) : m_line(line), m_source_task(source_task) {} - char * GetLine() { return m_line; } - void WaitShellProcessDone() { ulTaskNotifyTake(pdTRUE, portMAX_DELAY); } - void NotifyShellProcessDone() { xTaskNotifyGive(m_source_task); } - -private: - char * m_line; - TaskHandle_t m_source_task; -}; - -void ProcessShellLine(intptr_t context) -{ - ShellLineArgs * shellArgs = reinterpret_cast(context); - int ret; - esp_console_run(shellArgs->GetLine(), &ret); - if (ret) - { - printf("Error: %s\r\n", chip::ErrorStr(ret)); - } - else - { - printf("Done\r\n"); - } - - shellArgs->NotifyShellProcessDone(); -} - -} // namespace - -namespace chip { -namespace Shell { - -void Engine::RunMainLoop() -{ - int ret = chip::Shell::streamer_init(chip::Shell::streamer_get()); - assert(ret == 0); - - Engine::Root().RegisterDefaultCommands(); - while (true) - { - const char * prompt = LOG_COLOR_I "> " LOG_RESET_COLOR; - char * line = linenoise(prompt); - printf("\r\n"); - if (line == NULL || strlen(line) == 0) - { - if (line) - { - linenoiseFree(line); - } - continue; - } - ShellLineArgs shellArgs(line, xTaskGetCurrentTaskHandle()); - linenoiseHistoryAdd(line); - PlatformMgr().ScheduleWork(ProcessShellLine, reinterpret_cast(&shellArgs)); - shellArgs.WaitShellProcessDone(); - linenoiseFree(line); - } -} - -} // namespace Shell -} // namespace chip diff --git a/src/lib/shell/commands/BLE.cpp b/src/lib/shell/commands/BLE.cpp index 0843c454192d62..b12c2d34526350 100644 --- a/src/lib/shell/commands/BLE.cpp +++ b/src/lib/shell/commands/BLE.cpp @@ -31,7 +31,7 @@ using chip::DeviceLayer::ConnectivityMgr; namespace chip { namespace Shell { -static chip::Shell::Engine sShellDeviceSubcommands; +static chip::Shell::Shell sShellDeviceSubcommands; int BLEHelpHandler(int argc, char ** argv) { @@ -103,7 +103,7 @@ void RegisterBLECommands() sShellDeviceSubcommands.RegisterCommands(sBLESubCommands, ArraySize(sBLESubCommands)); // Register the root `btp` command with the top-level shell. - Engine::Root().RegisterCommands(&sBLECommand, 1); + shell_register(&sBLECommand, 1); } } // namespace Shell diff --git a/src/lib/shell/commands/Base64.cpp b/src/lib/shell/commands/Base64.cpp index f2518a3c68e57b..ea396893f47e10 100644 --- a/src/lib/shell/commands/Base64.cpp +++ b/src/lib/shell/commands/Base64.cpp @@ -30,7 +30,7 @@ #include #include -chip::Shell::Engine sShellBase64Commands; +chip::Shell::Shell sShellBase64Commands; namespace chip { namespace Shell { @@ -96,7 +96,7 @@ void RegisterBase64Commands() sShellBase64Commands.RegisterCommands(sBase64SubCommands, ArraySize(sBase64SubCommands)); // Register the root `base64` command with the top-level shell. - Engine::Root().RegisterCommands(&sBase64Command, 1); + shell_register(&sBase64Command, 1); } } // namespace Shell diff --git a/src/lib/shell/commands/Config.cpp b/src/lib/shell/commands/Config.cpp index 1bc778dc3b5f31..10071dbde344f1 100644 --- a/src/lib/shell/commands/Config.cpp +++ b/src/lib/shell/commands/Config.cpp @@ -195,7 +195,8 @@ void RegisterConfigCommands() "Dump device configuration. Usage: config [param_name]" }; // Register the root `device` command with the top-level shell. - Engine::Root().RegisterCommands(&sDeviceComand, 1); + shell_register(&sDeviceComand, 1); + return; } diff --git a/src/lib/shell/commands/Meta.cpp b/src/lib/shell/commands/Meta.cpp index 2e95e8fe6b7743..9ce7e2a98c117a 100644 --- a/src/lib/shell/commands/Meta.cpp +++ b/src/lib/shell/commands/Meta.cpp @@ -45,7 +45,7 @@ static int ExitHandler(int argc, char ** argv) static int HelpHandler(int argc, char ** argv) { - Engine::Root().ForEachCommand(PrintCommandHelp, nullptr); + shell_command_foreach(PrintCommandHelp, nullptr); return 0; } @@ -63,7 +63,7 @@ void RegisterMetaCommands() { &VersionHandler, "version", "Output the software version" }, }; - Engine::Root().RegisterCommands(sCmds, ArraySize(sCmds)); + shell_register(sCmds, ArraySize(sCmds)); } } // namespace Shell diff --git a/src/lib/shell/commands/WiFi.cpp b/src/lib/shell/commands/WiFi.cpp index e282b9ffbf91f9..14b396fa6fe5c4 100644 --- a/src/lib/shell/commands/WiFi.cpp +++ b/src/lib/shell/commands/WiFi.cpp @@ -35,7 +35,7 @@ using chip::DeviceLayer::ConnectivityMgr; namespace chip { namespace Shell { -static chip::Shell::Engine sShellWifiSubCommands; +static chip::Shell::Shell sShellWifiSubCommands; static int WiFiHelpHandler(int argc, char ** argv) { @@ -139,7 +139,7 @@ void RegisterWiFiCommands() static const shell_command_t sWifiCommand = { &WiFiDispatch, "wifi", "Usage: wifi " }; sShellWifiSubCommands.RegisterCommands(sWifiSubCommands, ArraySize(sWifiSubCommands)); - Engine::Root().RegisterCommands(&sWifiCommand, 1); + shell_register(&sWifiCommand, 1); } } // namespace Shell diff --git a/src/lib/shell/streamer_esp32.cpp b/src/lib/shell/streamer_esp32.cpp index 7badae9dc83452..7a485393014d62 100644 --- a/src/lib/shell/streamer_esp32.cpp +++ b/src/lib/shell/streamer_esp32.cpp @@ -34,7 +34,7 @@ static int chip_command_handler(int argc, char ** argv) { if (argc > 0) { - return Engine::Root().ExecCommand(argc - 1, argv + 1); + return chip::Shell::Shell::Root().ExecCommand(argc - 1, argv + 1); } else { @@ -72,6 +72,7 @@ int streamer_esp32_init(streamer_t * streamer) esp_console_cmd_t command = { .command = "chip", .help = "CHIP utilities", .func = chip_command_handler }; ESP_ERROR_CHECK(esp_console_cmd_register(&command)); + chip::Shell::Shell::Root().RegisterDefaultCommands(); return 0; } diff --git a/src/lib/shell/tests/BUILD.gn b/src/lib/shell/tests/BUILD.gn index 6180de017c3312..8fa5327dfd9c65 100644 --- a/src/lib/shell/tests/BUILD.gn +++ b/src/lib/shell/tests/BUILD.gn @@ -22,8 +22,9 @@ chip_test_suite("tests") { output_name = "libTestShell" sources = [ + "TestShell.cpp", + "TestShell.h", "TestStreamerStdio.cpp", - "TestStreamerStdio.h", ] cflags = [ "-Wconversion" ] @@ -34,5 +35,8 @@ chip_test_suite("tests") { "${nlunit_test_root}:nlunit-test", ] - tests = [ "TestStreamerStdio" ] + tests = [ + "TestShell", + "TestStreamerStdio", + ] } diff --git a/src/lib/shell/tests/TestShell.cpp b/src/lib/shell/tests/TestShell.cpp new file mode 100644 index 00000000000000..79bda9ff5fa799 --- /dev/null +++ b/src/lib/shell/tests/TestShell.cpp @@ -0,0 +1,110 @@ +/* + * + * Copyright (c) 2020 Project CHIP Authors + * + * 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. + */ + +#include "TestShell.h" + +#include +#include +#include + +#include +#include +#include +#include +#include + +using namespace chip; +using namespace chip::Shell; +using namespace chip::Logging; + +// ================================= +// Test Vectors +// ================================= + +struct test_shell_vector +{ + const char * line; + const char ** argv; +}; + +static const struct test_shell_vector test_vector_shell_tokenizer[] = { + { .line = "hello how are you?", .argv = (const char *[]){ "hello", "how", "are", "you?" } }, + { .line = "hey yo yo", .argv = (const char *[]){ "hey", "yo", "yo" } }, + { .line = "This has double spaces", .argv = (const char *[]){ "This", "has", "double", "spaces" } }, + { .line = " leading space", .argv = (const char *[]){ "leading", "space" } }, + { .line = "trailing space ", .argv = (const char *[]){ "trailing", "space", "" } }, + { .line = "no_space", .argv = (const char *[]){ "no_space" } }, + { .line = "escaped\\ space", .argv = (const char *[]){ "escaped space" } }, + { .line = "escape\\\\", .argv = (const char *[]){ "escape\\" } }, + { .line = "extended\\ escaped\\ space and\\ more", .argv = (const char *[]){ "extended escaped space", "and more" } }, + { .line = " ", .argv = (const char *[]){ "" } }, + { .line = "", .argv = (const char *[]){} }, +}; + +// ================================= +// Unit tests +// ================================= + +#define TEST_SHELL_MAX_TOKENS 5 + +static void TestShell_Tokenizer(nlTestSuite * inSuite, void * inContext) +{ + int numOfTestVectors = ArraySize(test_vector_shell_tokenizer); + int numOfTestsRan = 0; + const struct test_shell_vector * test_params; + char * argv[TEST_SHELL_MAX_TOKENS]; + int argc = TEST_SHELL_MAX_TOKENS; + int count; + + char line[128]; + + for (int vectorIndex = 0; vectorIndex < numOfTestVectors; vectorIndex++) + { + test_params = &test_vector_shell_tokenizer[vectorIndex]; + strcpy(line, test_params->line); + + count = shell_line_tokenize(line, argv, argc); + + for (int i = 0; i < count; i++) + { + NL_TEST_ASSERT(inSuite, strcmp(argv[i], test_params->argv[i]) == 0); + } + numOfTestsRan++; + } + NL_TEST_ASSERT(inSuite, numOfTestsRan > 0); +} + +/** + * Test Suite. It lists all the test functions. + */ +static const nlTest sTests[] = { + + NL_TEST_DEF("Test Shell: TestShell_Tokenizer", TestShell_Tokenizer), + + NL_TEST_SENTINEL() +}; + +int TestShell(void) +{ + nlTestSuite theSuite = { "CHIP Shell tests", &sTests[0], nullptr, nullptr }; + + // Run test suit againt one context. + nlTestRunner(&theSuite, nullptr); + return nlTestRunnerStats(&theSuite); +} + +CHIP_REGISTER_TEST_SUITE(TestShell) diff --git a/src/lib/shell/tests/TestStreamerStdio.h b/src/lib/shell/tests/TestShell.h similarity index 97% rename from src/lib/shell/tests/TestStreamerStdio.h rename to src/lib/shell/tests/TestShell.h index 64e1904edde935..2c2d3639c17cd0 100644 --- a/src/lib/shell/tests/TestStreamerStdio.h +++ b/src/lib/shell/tests/TestShell.h @@ -30,6 +30,7 @@ extern "C" { #endif +int TestShell(void); int TestStreamerStdio(void); #ifdef __cplusplus diff --git a/src/lib/shell/tests/TestShellDriver.cpp b/src/lib/shell/tests/TestShellDriver.cpp new file mode 100644 index 00000000000000..e5d3eb9283c656 --- /dev/null +++ b/src/lib/shell/tests/TestShellDriver.cpp @@ -0,0 +1,34 @@ +/* + * + * Copyright (c) 2020 Project CHIP Authors + * + * 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. + */ + +/** + * @file + * This file implements a standalone/native program executable + * test driver for the CHIP system layer library timer unit + * tests. + * + */ + +#include "TestShell.h" + +int main(int argc, char * argv[]) +{ + // Generate machine-readable, comma-separated value (CSV) output. + nlTestSetOutputStyle(OUTPUT_CSV); + + return (TestShell()); +} diff --git a/src/lib/shell/tests/TestStreamerStdio.cpp b/src/lib/shell/tests/TestStreamerStdio.cpp index 0950ce2ade8485..018aed6f4e336c 100644 --- a/src/lib/shell/tests/TestStreamerStdio.cpp +++ b/src/lib/shell/tests/TestStreamerStdio.cpp @@ -15,7 +15,7 @@ * limitations under the License. */ -#include "TestStreamerStdio.h" +#include "TestShell.h" #include #include diff --git a/src/lib/shell/tests/TestStreamerStdioDriver.cpp b/src/lib/shell/tests/TestStreamerStdioDriver.cpp index f10f8fecdad611..cbbc162ade9994 100644 --- a/src/lib/shell/tests/TestStreamerStdioDriver.cpp +++ b/src/lib/shell/tests/TestStreamerStdioDriver.cpp @@ -23,7 +23,7 @@ * */ -#include "TestStreamerStdio.h" +#include "TestShell.h" int main(int argc, char * argv[]) {