diff --git a/config/esp32/components/chip/component.mk b/config/esp32/components/chip/component.mk index 8f94ff98e2200d..bf7cbee1f79dd6 100644 --- a/config/esp32/components/chip/component.mk +++ b/config/esp32/components/chip/component.mk @@ -83,6 +83,10 @@ 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 @@ -135,6 +139,9 @@ 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 44e4c9d077111c..fad81186e81789 100644 --- a/examples/all-clusters-app/esp32/main/main.cpp +++ b/examples/all-clusters-app/esp32/main/main.cpp @@ -49,6 +49,7 @@ #include #include #include +#include #include #include #include @@ -545,6 +546,13 @@ 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() @@ -599,6 +607,10 @@ 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 6f6777ab62edd8..e12494a01910a7 100644 --- a/examples/all-clusters-app/esp32/sdkconfig.defaults +++ b/examples/all-clusters-app/esp32/sdkconfig.defaults @@ -45,3 +45,6 @@ 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 216c3c0069c9ab..c5968cb66a81e1 100644 --- a/examples/platform/linux/AppMain.cpp +++ b/examples/platform/linux/AppMain.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -40,6 +41,7 @@ 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) @@ -110,8 +112,15 @@ 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 a8964d31b74cbb..189bd1310c6ac2 100644 --- a/examples/platform/linux/BUILD.gn +++ b/examples/platform/linux/BUILD.gn @@ -19,6 +19,10 @@ config("app-main-config") { include_dirs = [ "." ] } +declare_args() { + chip_enable_shell = false +} + source_set("app-main") { sources = [ "AppMain.cpp", @@ -27,8 +31,10 @@ source_set("app-main") { "Options.h", ] + defines = [] + if (chip_enable_pw_rpc) { - defines = [ "PW_RPC_ENABLED" ] + defines += [ "PW_RPC_ENABLED" ] } public_deps = [ @@ -36,5 +42,10 @@ 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 fe76a23118c88b..7f6879663c9080 100644 --- a/examples/shell/esp32/main/main.cpp +++ b/examples/shell/esp32/main/main.cpp @@ -15,80 +15,27 @@ * 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 -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; -}; +using chip::Shell::Engine; -static void process_shell_line(intptr_t context) +static void chip_shell_task(void * args) { - 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(); + Engine::Root().RunMainLoop(); } -static void chip_shell_task(void * args) +extern "C" void app_main(void) { + 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(); - 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); + xTaskCreate(&chip_shell_task, "chip_shell", 2048, NULL, 5, NULL); } diff --git a/examples/shell/shell_common/cmd_misc.cpp b/examples/shell/shell_common/cmd_misc.cpp index 1d666c6b172823..da05dbd33425ed 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() { - shell_register(cmds_misc, ArraySize(cmds_misc)); + Engine::Root().RegisterCommands(cmds_misc, ArraySize(cmds_misc)); } diff --git a/examples/shell/shell_common/cmd_otcli.cpp b/examples/shell/shell_common/cmd_otcli.cpp index b6cf7c017599ea..c74dacbd1d0e60 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::Shell sShellOtcliSubcommands; +static chip::Shell::Engine 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. - shell_register(&cmds_otcli_root, 1); + Engine::Root().RegisterCommands(&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 f8995db411deff..3e3c75f1e97f5e 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() { - shell_register(cmds_ping, ArraySize(cmds_ping)); + Engine::Root().RegisterCommands(cmds_ping, ArraySize(cmds_ping)); } diff --git a/examples/shell/shell_common/cmd_send.cpp b/examples/shell/shell_common/cmd_send.cpp index 9a6ef3f4a9869d..5a08a05b8f028a 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() { - shell_register(cmds_send, ArraySize(cmds_send)); + Engine::Root().RegisterCommands(cmds_send, ArraySize(cmds_send)); } diff --git a/examples/shell/standalone/main.cpp b/examples/shell/standalone/main.cpp index 28e2f6376b82ab..96586f002a1619 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(); - shell_task(nullptr); + Engine::Root().RunMainLoop(); return 0; } diff --git a/src/lib/shell/BUILD.gn b/src/lib/shell/BUILD.gn index 80ac80b3e07969..d4d765c1675998 100644 --- a/src/lib/shell/BUILD.gn +++ b/src/lib/shell/BUILD.gn @@ -37,14 +37,19 @@ static_library("shell") { output_name = "libCHIPShell" output_dir = "${root_out_dir}/lib" - sources = [ "Commands.cpp" ] + sources = [] if (chip_target_style == "unix") { sources += [ "streamer_stdio.cpp" ] } if (chip_device_platform == "esp32") { - sources += [ "streamer_esp32.cpp" ] + sources += [ + "MainLoopESP32.cpp", + "streamer_esp32.cpp", + ] + } else { + sources += [ "MainLoopDefault.cpp" ] } if (current_os == "zephyr") { diff --git a/src/lib/shell/Commands.cpp b/src/lib/shell/Commands.cpp deleted file mode 100644 index 02f6ab86fc4048..00000000000000 --- a/src/lib/shell/Commands.cpp +++ /dev/null @@ -1,41 +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 -#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 7752b5bcc2f183..616cf528c9fdad 100644 --- a/src/lib/shell/Engine.cpp +++ b/src/lib/shell/Engine.cpp @@ -24,6 +24,7 @@ #include #include +#include #include #include #include @@ -41,66 +42,9 @@ using namespace chip::Logging; namespace chip { namespace Shell { -Shell Shell::theShellRoot; +Engine Engine::theEngineRoot; -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) +void Engine::ForEachCommand(shell_command_iterator_t * on_command, void * arg) { for (unsigned i = 0; i < _commandSetCount; i++) { @@ -114,7 +58,7 @@ void Shell::ForEachCommand(shell_command_iterator_t * on_command, void * arg) } } -void Shell::RegisterCommands(shell_command_t * command_set, unsigned count) +void Engine::RegisterCommands(shell_command_t * command_set, unsigned count) { if (_commandSetCount >= CHIP_SHELL_MAX_MODULES) { @@ -127,7 +71,7 @@ void Shell::RegisterCommands(shell_command_t * command_set, unsigned count) ++_commandSetCount; } -int Shell::ExecCommand(int argc, char * argv[]) +int Engine::ExecCommand(int argc, char * argv[]) { int retval = CHIP_ERROR_INVALID_ARGUMENT; @@ -149,138 +93,19 @@ int Shell::ExecCommand(int argc, char * argv[]) return retval; } -static bool IsSeparator(char aChar) -{ - 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) +void Engine::RegisterDefaultCommands() { - // 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); + 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 - DeviceLayer::PlatformMgr().ScheduleWork(ProcessShellLineTask, reinterpret_cast(line)); -#else - ProcessShellLineTask(reinterpret_cast(line)); + RegisterConfigCommands(); #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 383492762f9bfb..f8b280c91ba0aa 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 Shell +class Engine { protected: - static Shell theShellRoot; + static Engine theEngineRoot; shell_command_t * _commandSet[CHIP_SHELL_MAX_MODULES]; unsigned _commandSetSize[CHIP_SHELL_MAX_MODULES]; unsigned _commandSetCount; public: - Shell() {} + Engine() {} /** Return the root singleton for the Shell command hierarchy. */ - static Shell & Root() { return theShellRoot; } + static Engine & Root() { return theEngineRoot; } /** * Registers a set of defaults commands (help) for all Shell and sub-Shell instances. @@ -143,40 +143,16 @@ class Shell void RegisterCommands(shell_command_t * command_set, unsigned count); /** - * Utility function for converting a raw line typed into a shell into an array of words or tokens. + * Runs the shell mainloop. Will display the prompt and enable interaction. * - * @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. + * @note This is a blocking call and will not return until user types "exit" * - * @return Number of tokens generated (argc). */ - 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); + void RunMainLoop(); 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 new file mode 100644 index 00000000000000..9266d45850389e --- /dev/null +++ b/src/lib/shell/MainLoopDefault.cpp @@ -0,0 +1,201 @@ +/* + * + * 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 new file mode 100644 index 00000000000000..ed0d91c2fa4d96 --- /dev/null +++ b/src/lib/shell/MainLoopESP32.cpp @@ -0,0 +1,99 @@ +/* + * + * 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 b12c2d34526350..0843c454192d62 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::Shell sShellDeviceSubcommands; +static chip::Shell::Engine 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. - shell_register(&sBLECommand, 1); + Engine::Root().RegisterCommands(&sBLECommand, 1); } } // namespace Shell diff --git a/src/lib/shell/commands/Base64.cpp b/src/lib/shell/commands/Base64.cpp index ea396893f47e10..f2518a3c68e57b 100644 --- a/src/lib/shell/commands/Base64.cpp +++ b/src/lib/shell/commands/Base64.cpp @@ -30,7 +30,7 @@ #include #include -chip::Shell::Shell sShellBase64Commands; +chip::Shell::Engine 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. - shell_register(&sBase64Command, 1); + Engine::Root().RegisterCommands(&sBase64Command, 1); } } // namespace Shell diff --git a/src/lib/shell/commands/Config.cpp b/src/lib/shell/commands/Config.cpp index 10071dbde344f1..1bc778dc3b5f31 100644 --- a/src/lib/shell/commands/Config.cpp +++ b/src/lib/shell/commands/Config.cpp @@ -195,8 +195,7 @@ void RegisterConfigCommands() "Dump device configuration. Usage: config [param_name]" }; // Register the root `device` command with the top-level shell. - shell_register(&sDeviceComand, 1); - + Engine::Root().RegisterCommands(&sDeviceComand, 1); return; } diff --git a/src/lib/shell/commands/Meta.cpp b/src/lib/shell/commands/Meta.cpp index 9ce7e2a98c117a..2e95e8fe6b7743 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) { - shell_command_foreach(PrintCommandHelp, nullptr); + Engine::Root().ForEachCommand(PrintCommandHelp, nullptr); return 0; } @@ -63,7 +63,7 @@ void RegisterMetaCommands() { &VersionHandler, "version", "Output the software version" }, }; - shell_register(sCmds, ArraySize(sCmds)); + Engine::Root().RegisterCommands(sCmds, ArraySize(sCmds)); } } // namespace Shell diff --git a/src/lib/shell/commands/WiFi.cpp b/src/lib/shell/commands/WiFi.cpp index 14b396fa6fe5c4..e282b9ffbf91f9 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::Shell sShellWifiSubCommands; +static chip::Shell::Engine 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)); - shell_register(&sWifiCommand, 1); + Engine::Root().RegisterCommands(&sWifiCommand, 1); } } // namespace Shell diff --git a/src/lib/shell/streamer_esp32.cpp b/src/lib/shell/streamer_esp32.cpp index 7a485393014d62..7badae9dc83452 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 chip::Shell::Shell::Root().ExecCommand(argc - 1, argv + 1); + return Engine::Root().ExecCommand(argc - 1, argv + 1); } else { @@ -72,7 +72,6 @@ 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 8fa5327dfd9c65..6180de017c3312 100644 --- a/src/lib/shell/tests/BUILD.gn +++ b/src/lib/shell/tests/BUILD.gn @@ -22,9 +22,8 @@ chip_test_suite("tests") { output_name = "libTestShell" sources = [ - "TestShell.cpp", - "TestShell.h", "TestStreamerStdio.cpp", + "TestStreamerStdio.h", ] cflags = [ "-Wconversion" ] @@ -35,8 +34,5 @@ chip_test_suite("tests") { "${nlunit_test_root}:nlunit-test", ] - tests = [ - "TestShell", - "TestStreamerStdio", - ] + tests = [ "TestStreamerStdio" ] } diff --git a/src/lib/shell/tests/TestShell.cpp b/src/lib/shell/tests/TestShell.cpp deleted file mode 100644 index 79bda9ff5fa799..00000000000000 --- a/src/lib/shell/tests/TestShell.cpp +++ /dev/null @@ -1,110 +0,0 @@ -/* - * - * 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/TestShellDriver.cpp b/src/lib/shell/tests/TestShellDriver.cpp deleted file mode 100644 index e5d3eb9283c656..00000000000000 --- a/src/lib/shell/tests/TestShellDriver.cpp +++ /dev/null @@ -1,34 +0,0 @@ -/* - * - * 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 018aed6f4e336c..0950ce2ade8485 100644 --- a/src/lib/shell/tests/TestStreamerStdio.cpp +++ b/src/lib/shell/tests/TestStreamerStdio.cpp @@ -15,7 +15,7 @@ * limitations under the License. */ -#include "TestShell.h" +#include "TestStreamerStdio.h" #include #include diff --git a/src/lib/shell/tests/TestShell.h b/src/lib/shell/tests/TestStreamerStdio.h similarity index 97% rename from src/lib/shell/tests/TestShell.h rename to src/lib/shell/tests/TestStreamerStdio.h index 2c2d3639c17cd0..64e1904edde935 100644 --- a/src/lib/shell/tests/TestShell.h +++ b/src/lib/shell/tests/TestStreamerStdio.h @@ -30,7 +30,6 @@ extern "C" { #endif -int TestShell(void); int TestStreamerStdio(void); #ifdef __cplusplus diff --git a/src/lib/shell/tests/TestStreamerStdioDriver.cpp b/src/lib/shell/tests/TestStreamerStdioDriver.cpp index cbbc162ade9994..f10f8fecdad611 100644 --- a/src/lib/shell/tests/TestStreamerStdioDriver.cpp +++ b/src/lib/shell/tests/TestStreamerStdioDriver.cpp @@ -23,7 +23,7 @@ * */ -#include "TestShell.h" +#include "TestStreamerStdio.h" int main(int argc, char * argv[]) {