Kaynağa Gözat

[shell] enable shell as an optional module in apps (#7080)

This PR allows users to build shell into the devices for debugging and
testing purpose.
Jiacheng Guo 4 yıl önce
ebeveyn
işleme
c8f286f6d7

+ 7 - 0
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

+ 12 - 0
examples/all-clusters-app/esp32/main/main.cpp

@@ -49,6 +49,7 @@
 #include <app/server/Mdns.h>
 #include <app/server/OnboardingCodesUtil.h>
 #include <app/server/Server.h>
+#include <lib/shell/Engine.h>
 #include <platform/CHIPDeviceLayer.h>
 #include <setup_payload/ManualSetupPayloadGenerator.h>
 #include <setup_payload/QRCodeSetupPayloadGenerator.h>
@@ -545,6 +546,13 @@ public:
     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();

+ 3 - 0
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

+ 9 - 0
examples/platform/linux/AppMain.cpp

@@ -25,6 +25,7 @@
 #include <app/server/OnboardingCodesUtil.h>
 #include <app/server/Server.h>
 #include <core/CHIPError.h>
+#include <lib/shell/Engine.h>
 #include <setup_payload/QRCodeSetupPayloadGenerator.h>
 #include <setup_payload/SetupPayload.h>
 #include <support/CHIPMem.h>
@@ -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 @@ exit:
 
 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
 }

+ 12 - 1
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" ]
 }

+ 7 - 60
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 <ChipShellCollection.h>
 #include <lib/shell/Engine.h>
+#include <lib/support/CHIPMem.h>
 #include <lib/support/CHIPPlatformMemory.h>
 #include <platform/CHIPDeviceLayer.h>
 
-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<ShellLineArgs *>(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<intptr_t>(&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);
 }

+ 1 - 1
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));
 }

+ 2 - 2
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
 }

+ 1 - 1
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));
 }

+ 1 - 1
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));
 }

+ 1 - 1
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;
 }

+ 7 - 2
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") {

+ 0 - 41
src/lib/shell/Commands.cpp

@@ -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 <lib/shell/Commands.h>
-#include <lib/shell/Engine.h>
-#include <platform/CHIPDeviceLayer.h>
-
-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

+ 15 - 190
src/lib/shell/Engine.cpp

@@ -24,6 +24,7 @@
 #include <lib/shell/Engine.h>
 
 #include <core/CHIPError.h>
+#include <lib/shell/Commands.h>
 #include <lib/support/CHIPMem.h>
 #include <platform/CHIPDeviceLayer.h>
 #include <support/CodeUtils.h>
@@ -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<int>(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<int>(*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<char *>(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<char *>(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<intptr_t>(line));
-#else
-        ProcessShellLineTask(reinterpret_cast<intptr_t>(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

+ 7 - 31
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 @@ public:
     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

+ 201 - 0
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 <lib/shell/Engine.h>
+#include <lib/support/CHIPMem.h>
+#include <platform/CHIPDeviceLayer.h>
+
+#include <ctype.h>
+#include <string.h>
+
+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<int>(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<int>(*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<char *>(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<char *>(Platform::MemoryAlloc(CHIP_SHELL_MAX_LINE_SIZE));
+        ReadLine(line, CHIP_SHELL_MAX_LINE_SIZE);
+#if CONFIG_DEVICE_LAYER
+        DeviceLayer::PlatformMgr().ScheduleWork(ProcessShellLine, reinterpret_cast<intptr_t>(line));
+#else
+        ProcessShellLine(reinterpret_cast<intptr_t>(line));
+#endif
+    }
+}
+
+} // namespace Shell
+} // namespace chip

+ 99 - 0
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 <lib/shell/Engine.h>
+#include <platform/CHIPDeviceLayer.h>
+
+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<ShellLineArgs *>(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<intptr_t>(&shellArgs));
+        shellArgs.WaitShellProcessDone();
+        linenoiseFree(line);
+    }
+}
+
+} // namespace Shell
+} // namespace chip

+ 2 - 2
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

+ 2 - 2
src/lib/shell/commands/Base64.cpp

@@ -30,7 +30,7 @@
 #include <lib/support/CodeUtils.h>
 #include <lib/support/RandUtils.h>
 
-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

+ 1 - 2
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;
 }
 

+ 2 - 2
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

+ 2 - 2
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 <subcommand>" };
 
     sShellWifiSubCommands.RegisterCommands(sWifiSubCommands, ArraySize(sWifiSubCommands));
-    shell_register(&sWifiCommand, 1);
+    Engine::Root().RegisterCommands(&sWifiCommand, 1);
 }
 
 } // namespace Shell

+ 1 - 2
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;
 }
 

+ 2 - 6
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" ]
 }

+ 0 - 110
src/lib/shell/tests/TestShell.cpp

@@ -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 <shell/Engine.h>
-#include <support/CodeUtils.h>
-#include <support/UnitTestRegistration.h>
-
-#include <inttypes.h>
-#include <stdarg.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
-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)

+ 0 - 34
src/lib/shell/tests/TestShellDriver.cpp

@@ -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());
-}

+ 1 - 1
src/lib/shell/tests/TestStreamerStdio.cpp

@@ -15,7 +15,7 @@
  *    limitations under the License.
  */
 
-#include "TestShell.h"
+#include "TestStreamerStdio.h"
 
 #include <shell/Engine.h>
 #include <support/CodeUtils.h>

+ 0 - 1
src/lib/shell/tests/TestShell.h → src/lib/shell/tests/TestStreamerStdio.h

@@ -30,7 +30,6 @@
 extern "C" {
 #endif
 
-int TestShell(void);
 int TestStreamerStdio(void);
 
 #ifdef __cplusplus

+ 1 - 1
src/lib/shell/tests/TestStreamerStdioDriver.cpp

@@ -23,7 +23,7 @@
  *
  */
 
-#include "TestShell.h"
+#include "TestStreamerStdio.h"
 
 int main(int argc, char * argv[])
 {