Bladeren bron

Merge branch 'feature/linenoise_improvement' into 'master'

console: re-use the available REPL console API and improve linenoise

Closes IDFGH-5296

See merge request espressif/esp-idf!13897
Zim Kalinowski 4 jaren geleden
bovenliggende
commit
f29d873c54
53 gewijzigde bestanden met toevoegingen van 617 en 27 verwijderingen
  1. 2 0
      components/console/esp_console.h
  2. 15 6
      components/console/esp_console_repl.c
  3. 63 7
      components/console/linenoise/linenoise.c
  4. 1 0
      components/console/linenoise/linenoise.h
  5. 3 0
      docs/en/api-reference/system/console.rst
  6. 1 1
      examples/ethernet/iperf/CMakeLists.txt
  7. 1 1
      examples/ethernet/iperf/Makefile
  8. 1 1
      examples/peripherals/i2c/i2c_tools/CMakeLists.txt
  9. 1 1
      examples/peripherals/i2c/i2c_tools/Makefile
  10. 0 0
      examples/system/console/advanced/CMakeLists.txt
  11. 0 0
      examples/system/console/advanced/Makefile
  12. 0 0
      examples/system/console/advanced/README.md
  13. 0 0
      examples/system/console/advanced/components/cmd_nvs/CMakeLists.txt
  14. 0 0
      examples/system/console/advanced/components/cmd_nvs/cmd_nvs.c
  15. 0 0
      examples/system/console/advanced/components/cmd_nvs/cmd_nvs.h
  16. 0 0
      examples/system/console/advanced/components/cmd_nvs/component.mk
  17. 0 0
      examples/system/console/advanced/components/cmd_system/CMakeLists.txt
  18. 0 0
      examples/system/console/advanced/components/cmd_system/cmd_system.c
  19. 0 0
      examples/system/console/advanced/components/cmd_system/cmd_system.h
  20. 0 0
      examples/system/console/advanced/components/cmd_system/component.mk
  21. 22 0
      examples/system/console/advanced/example_test.py
  22. 0 0
      examples/system/console/advanced/main/CMakeLists.txt
  23. 0 0
      examples/system/console/advanced/main/Kconfig.projbuild
  24. 0 0
      examples/system/console/advanced/main/cmd_decl.h
  25. 0 0
      examples/system/console/advanced/main/cmd_wifi.c
  26. 0 0
      examples/system/console/advanced/main/cmd_wifi.h
  27. 0 0
      examples/system/console/advanced/main/component.mk
  28. 3 0
      examples/system/console/advanced/main/console_example_main.c
  29. 0 0
      examples/system/console/advanced/partitions_example.csv
  30. 0 0
      examples/system/console/advanced/sdkconfig.ci.history
  31. 0 0
      examples/system/console/advanced/sdkconfig.ci.nohistory
  32. 0 0
      examples/system/console/advanced/sdkconfig.defaults
  33. 8 0
      examples/system/console/basic/CMakeLists.txt
  34. 10 0
      examples/system/console/basic/Makefile
  35. 151 0
      examples/system/console/basic/README.md
  36. 5 4
      examples/system/console/basic/example_test.py
  37. 3 0
      examples/system/console/basic/main/CMakeLists.txt
  38. 18 0
      examples/system/console/basic/main/Kconfig.projbuild
  39. 21 0
      examples/system/console/basic/main/cmd_decl.h
  40. 132 0
      examples/system/console/basic/main/cmd_wifi.c
  41. 20 0
      examples/system/console/basic/main/cmd_wifi.h
  42. 4 0
      examples/system/console/basic/main/component.mk
  43. 95 0
      examples/system/console/basic/main/console_example_main.c
  44. 6 0
      examples/system/console/basic/partitions_example.csv
  45. 4 0
      examples/system/console/basic/sdkconfig.ci.history
  46. 4 0
      examples/system/console/basic/sdkconfig.ci.nohistory
  47. 17 0
      examples/system/console/basic/sdkconfig.defaults
  48. 1 1
      examples/system/console_usb/CMakeLists.txt
  49. 1 1
      examples/wifi/ftm/CMakeLists.txt
  50. 1 1
      examples/wifi/iperf/CMakeLists.txt
  51. 1 1
      examples/wifi/iperf/Makefile
  52. 1 1
      examples/wifi/simple_sniffer/CMakeLists.txt
  53. 1 1
      examples/wifi/simple_sniffer/Makefile

+ 2 - 0
components/console/esp_console.h

@@ -48,6 +48,7 @@ typedef struct {
     uint32_t task_stack_size;      //!< repl task stack size
     uint32_t task_priority;        //!< repl task priority
     const char *prompt;            //!< prompt (NULL represents default: "esp> ")
+    size_t max_cmdline_length;     //!< maximum length of a command line. If 0, default value will be used
 } esp_console_repl_config_t;
 
 /**
@@ -61,6 +62,7 @@ typedef struct {
         .task_stack_size = 4096,          \
         .task_priority = 2,               \
         .prompt = NULL,                   \
+        .max_cmdline_length = 0,          \
 }
 
 /**

+ 15 - 6
components/console/esp_console_repl.c

@@ -37,7 +37,8 @@ typedef struct {
     char prompt[CONSOLE_PROMPT_MAX_LEN]; // Prompt to be printed before each line
     repl_state_t state;
     const char *history_save_path;
-    TaskHandle_t task_hdl; // REPL task handle
+    TaskHandle_t task_hdl;              // REPL task handle
+    size_t max_cmdline_length;          // Maximum length of a command line. If 0, default value will be used.
 } esp_console_repl_com_t;
 
 typedef struct {
@@ -51,7 +52,7 @@ static esp_err_t esp_console_repl_usb_cdc_delete(esp_console_repl_t *repl);
 #if CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG
 static esp_err_t esp_console_repl_usb_serial_jtag_delete(esp_console_repl_t *repl);
 #endif //CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG
-static esp_err_t esp_console_common_init(esp_console_repl_com_t *repl_com);
+static esp_err_t esp_console_common_init(size_t max_cmdline_length, esp_console_repl_com_t *repl_com);
 static esp_err_t esp_console_setup_prompt(const char *prompt, esp_console_repl_com_t *repl_com);
 static esp_err_t esp_console_setup_history(const char *history_path, uint32_t max_history_len, esp_console_repl_com_t *repl_com);
 
@@ -80,7 +81,7 @@ esp_err_t esp_console_new_repl_usb_cdc(const esp_console_dev_usb_cdc_config_t *d
     fcntl(fileno(stdin), F_SETFL, 0);
 
     // initialize console, common part
-    ret = esp_console_common_init(&cdc_repl->repl_com);
+    ret = esp_console_common_init(repl_config->max_cmdline_length, &cdc_repl->repl_com);
     if (ret != ESP_OK) {
         goto _exit;
     }
@@ -156,7 +157,7 @@ esp_err_t esp_console_new_repl_usb_serial_jtag(const esp_console_dev_usb_serial_
     }
 
     // initialize console, common part
-    ret = esp_console_common_init(&usb_serial_jtag_repl->repl_com);
+    ret = esp_console_common_init(repl_config->max_cmdline_length, &usb_serial_jtag_repl->repl_com);
     if (ret != ESP_OK) {
         goto _exit;
     }
@@ -249,7 +250,7 @@ esp_err_t esp_console_new_repl_uart(const esp_console_dev_uart_config_t *dev_con
     esp_vfs_dev_uart_use_driver(dev_config->channel);
 
     // initialize console, common part
-    ret = esp_console_common_init(&uart_repl->repl_com);
+    ret = esp_console_common_init(repl_config->max_cmdline_length, &uart_repl->repl_com);
     if (ret != ESP_OK) {
         goto _exit;
     }
@@ -353,11 +354,18 @@ _exit:
     return ret;
 }
 
-static esp_err_t esp_console_common_init(esp_console_repl_com_t *repl_com)
+static esp_err_t esp_console_common_init(size_t max_cmdline_length, esp_console_repl_com_t *repl_com)
 {
     esp_err_t ret = ESP_OK;
     /* Initialize the console */
     esp_console_config_t console_config = ESP_CONSOLE_CONFIG_DEFAULT();
+    repl_com->max_cmdline_length = console_config.max_cmdline_length;
+    /* Replace the default command line length if passed as a parameter */
+    if (max_cmdline_length != 0) {
+        console_config.max_cmdline_length = max_cmdline_length;
+        repl_com->max_cmdline_length = max_cmdline_length;
+    }
+
 #if CONFIG_LOG_COLORS
     console_config.hint_color = atoi(LOG_COLOR_CYAN);
 #endif
@@ -485,6 +493,7 @@ static void esp_console_repl_task(void *args)
                "On Windows, try using Putty instead.\r\n");
     }
 
+    linenoiseSetMaxLineLen(repl_com->max_cmdline_length);
     while (repl_com->state == CONSOLE_REPL_STATE_START) {
         char *line = linenoise(repl_com->prompt);
         if (line == NULL) {

+ 63 - 7
components/console/linenoise/linenoise.c

@@ -119,13 +119,16 @@
 #include "linenoise.h"
 
 #define LINENOISE_DEFAULT_HISTORY_MAX_LEN 100
-#define LINENOISE_MAX_LINE 4096
+#define LINENOISE_DEFAULT_MAX_LINE 4096
+#define LINENOISE_MINIMAL_MAX_LINE 64
 #define LINENOISE_COMMAND_MAX_LEN 32
+#define LINENOISE_PASTE_KEY_DELAY 30 /* Delay, in milliseconds, between two characters being pasted from clipboard */
 
 static linenoiseCompletionCallback *completionCallback = NULL;
 static linenoiseHintsCallback *hintsCallback = NULL;
 static linenoiseFreeHintsCallback *freeHintsCallback = NULL;
 
+static size_t max_cmdline_length = LINENOISE_DEFAULT_MAX_LINE;
 static int mlmode = 0;  /* Multi line mode. Default is single line. */
 static int dumbmode = 0; /* Dumb mode where line editing is disabled. Off by default */
 static int history_max_len = LINENOISE_DEFAULT_HISTORY_MAX_LEN;
@@ -683,6 +686,21 @@ int linenoiseEditInsert(struct linenoiseState *l, char c) {
     return 0;
 }
 
+int linenoiseInsertPastedChar(struct linenoiseState *l, char c) {
+    int fd = fileno(stdout);
+    if (l->len < l->buflen && l->len == l->pos) {
+        l->buf[l->pos] = c;
+        l->pos++;
+        l->len++;
+        l->buf[l->len] = '\0';
+        if (write(fd, &c,1) == -1) {
+            return -1;
+        }
+        flushWrite();
+    }
+    return 0;
+}
+
 /* Move cursor on the left. */
 void linenoiseEditMoveLeft(struct linenoiseState *l) {
     if (l->pos > 0) {
@@ -779,6 +797,12 @@ void linenoiseEditDeletePrevWord(struct linenoiseState *l) {
     refreshLine(l);
 }
 
+uint32_t getMillis(void) {
+    struct timeval tv = { 0 };
+    gettimeofday(&tv, NULL);
+    return tv.tv_sec * 1000 + tv.tv_usec / 1000;
+}
+
 /* This function is the core of the line editing capability of linenoise.
  * It expects 'fd' to be already in "raw mode" so that every key pressed
  * will be returned ASAP to read().
@@ -789,6 +813,7 @@ void linenoiseEditDeletePrevWord(struct linenoiseState *l) {
  * The function returns the length of the current buffer. */
 static int linenoiseEdit(char *buf, size_t buflen, const char *prompt)
 {
+    uint32_t t1 = 0;
     struct linenoiseState l;
     int out_fd = fileno(stdout);
     int in_fd = fileno(stdin);
@@ -827,9 +852,29 @@ static int linenoiseEdit(char *buf, size_t buflen, const char *prompt)
         int nread;
         char seq[3];
 
+        /**
+         * To determine whether the user is pasting data or typing itself, we
+         * need to calculate how many milliseconds elapsed between two key
+         * presses. Indeed, if there is less than LINENOISE_PASTE_KEY_DELAY
+         * (typically 30-40ms), then a paste is being performed, else, the
+         * user is typing.
+         * NOTE: pressing a key down without releasing it will also spend
+         * about 40ms (or even more)
+         */
+        t1 = getMillis();
         nread = read(in_fd, &c, 1);
         if (nread <= 0) return l.len;
 
+        if ( (getMillis() - t1) < LINENOISE_PASTE_KEY_DELAY ) {
+            /* Pasting data, insert characters without formatting.
+             * This can only be performed when the cursor is at the end of the
+             * line. */
+            if (linenoiseInsertPastedChar(&l,c)) {
+                return -1;
+            }
+            continue;
+        }
+
         /* Only autocomplete when the callback is set. It returns < 0 when
          * there was an error reading from fd. Otherwise it will return the
          * character that should be handled next. */
@@ -1079,15 +1124,15 @@ static void sanitize(char* src) {
 
 /* The high level function that is the main API of the linenoise library. */
 char *linenoise(const char *prompt) {
-    char *buf = calloc(1, LINENOISE_MAX_LINE);
+    char *buf = calloc(1, max_cmdline_length);
     int count = 0;
     if (buf == NULL) {
         return NULL;
     }
     if (!dumbmode) {
-        count = linenoiseRaw(buf, LINENOISE_MAX_LINE, prompt);
+        count = linenoiseRaw(buf, max_cmdline_length, prompt);
     } else {
-        count = linenoiseDumb(buf, LINENOISE_MAX_LINE, prompt);
+        count = linenoiseDumb(buf, max_cmdline_length, prompt);
     }
     if (count > 0) {
         sanitize(buf);
@@ -1209,18 +1254,18 @@ int linenoiseHistorySave(const char *filename) {
  * If the file exists and the operation succeeded 0 is returned, otherwise
  * on error -1 is returned. */
 int linenoiseHistoryLoad(const char *filename) {
-    FILE *fp = fopen(filename,"r");
+    FILE *fp = fopen(filename, "r");
     if (fp == NULL) {
         return -1;
     }
 
-    char *buf = calloc(1, LINENOISE_MAX_LINE);
+    char *buf = calloc(1, max_cmdline_length);
     if (buf == NULL) {
         fclose(fp);
         return -1;
     }
 
-    while (fgets(buf,LINENOISE_MAX_LINE,fp) != NULL) {
+    while (fgets(buf, max_cmdline_length, fp) != NULL) {
         char *p;
 
         p = strchr(buf,'\r');
@@ -1234,3 +1279,14 @@ int linenoiseHistoryLoad(const char *filename) {
 
     return 0;
 }
+
+/* Set line maximum length. If len parameter is smaller than
+ * LINENOISE_MINIMAL_MAX_LINE, -1 is returned
+ * otherwise 0 is returned. */
+int linenoiseSetMaxLineLen(size_t len) {
+    if (len < LINENOISE_MINIMAL_MAX_LINE) {
+        return -1;
+    }
+    max_cmdline_length = len;
+    return 0;
+}

+ 1 - 0
components/console/linenoise/linenoise.h

@@ -72,6 +72,7 @@ void linenoiseSetDumbMode(int set);
 bool linenoiseIsDumbMode(void);
 void linenoisePrintKeyCodes(void);
 void linenoiseAllowEmpty(bool);
+int linenoiseSetMaxLineLen(size_t len);
 
 #ifdef __cplusplus
 }

+ 3 - 0
docs/en/api-reference/system/console.rst

@@ -39,6 +39,9 @@ Linenoise library does not need explicit initialization. However, some configura
 :cpp:func:`linenoiseAllowEmpty`
   Set whether linenoise library will return a zero-length string (if ``true``) or ``NULL`` (if ``false``) for empty lines. By default, zero-length strings are returned.
 
+:cpp:func:`linenoiseSetMaxLineLen`
+  Set maximum length of the line for linenoise library. Default length is 4096. If you need optimize RAM memory usage, you can do it by this function by setting a value less than default 4kB.
+
 
 Main loop
 ^^^^^^^^^

+ 1 - 1
examples/ethernet/iperf/CMakeLists.txt

@@ -2,7 +2,7 @@
 # in this exact order for cmake to work correctly
 cmake_minimum_required(VERSION 3.5)
 
-set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/system/console/components
+set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/system/console/advanced/components
                          $ENV{IDF_PATH}/examples/wifi/iperf/components)
 
 include($ENV{IDF_PATH}/tools/cmake/project.cmake)

+ 1 - 1
examples/ethernet/iperf/Makefile

@@ -5,7 +5,7 @@
 
 PROJECT_NAME := ethernet_iperf
 
-EXTRA_COMPONENT_DIRS = $(IDF_PATH)/examples/system/console/components
+EXTRA_COMPONENT_DIRS = $(IDF_PATH)/examples/system/console/advanced/components
 EXTRA_COMPONENT_DIRS += $(IDF_PATH)/examples/wifi/iperf/components
 
 

+ 1 - 1
examples/peripherals/i2c/i2c_tools/CMakeLists.txt

@@ -2,7 +2,7 @@
 # in this exact order for cmake to work correctly
 cmake_minimum_required(VERSION 3.5)
 
-set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/system/console/components)
+set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/system/console/advanced/components)
 
 include($ENV{IDF_PATH}/tools/cmake/project.cmake)
 project(i2c-tools)

+ 1 - 1
examples/peripherals/i2c/i2c_tools/Makefile

@@ -5,6 +5,6 @@
 
 PROJECT_NAME := i2c-tools
 
-EXTRA_COMPONENT_DIRS = $(IDF_PATH)/examples/system/console/components
+EXTRA_COMPONENT_DIRS = $(IDF_PATH)/examples/system/console/advanced/components
 
 include $(IDF_PATH)/make/project.mk

+ 0 - 0
examples/system/console/CMakeLists.txt → examples/system/console/advanced/CMakeLists.txt


+ 0 - 0
examples/system/console/Makefile → examples/system/console/advanced/Makefile


+ 0 - 0
examples/system/console/README.md → examples/system/console/advanced/README.md


+ 0 - 0
examples/system/console/components/cmd_nvs/CMakeLists.txt → examples/system/console/advanced/components/cmd_nvs/CMakeLists.txt


+ 0 - 0
examples/system/console/components/cmd_nvs/cmd_nvs.c → examples/system/console/advanced/components/cmd_nvs/cmd_nvs.c


+ 0 - 0
examples/system/console/components/cmd_nvs/cmd_nvs.h → examples/system/console/advanced/components/cmd_nvs/cmd_nvs.h


+ 0 - 0
examples/system/console/components/cmd_nvs/component.mk → examples/system/console/advanced/components/cmd_nvs/component.mk


+ 0 - 0
examples/system/console/components/cmd_system/CMakeLists.txt → examples/system/console/advanced/components/cmd_system/CMakeLists.txt


+ 0 - 0
examples/system/console/components/cmd_system/cmd_system.c → examples/system/console/advanced/components/cmd_system/cmd_system.c


+ 0 - 0
examples/system/console/components/cmd_system/cmd_system.h → examples/system/console/advanced/components/cmd_system/cmd_system.h


+ 0 - 0
examples/system/console/components/cmd_system/component.mk → examples/system/console/advanced/components/cmd_system/component.mk


+ 22 - 0
examples/system/console/advanced/example_test.py

@@ -0,0 +1,22 @@
+# type: ignore
+from __future__ import print_function
+
+import ttfw_idf
+
+
+@ttfw_idf.idf_example_test(env_tag='Example_GENERIC', target=['esp32', 'esp32c3'])
+def test_examples_system_console_advanced(env, _):
+    dut = env.get_dut('console_example', 'examples/system/console/advanced', app_config_name='history')
+    print('Using binary path: {}'.format(dut.app.binary_path))
+    dut.start_app()
+    dut.expect('Command history enabled')
+    env.close_dut(dut.name)
+
+    dut = env.get_dut('console_example', 'examples/system/console/advanced', app_config_name='nohistory')
+    print('Using binary path: {}'.format(dut.app.binary_path))
+    dut.start_app()
+    dut.expect('Command history disabled')
+
+
+if __name__ == '__main__':
+    test_examples_system_console_advanced()

+ 0 - 0
examples/system/console/main/CMakeLists.txt → examples/system/console/advanced/main/CMakeLists.txt


+ 0 - 0
examples/system/console/main/Kconfig.projbuild → examples/system/console/advanced/main/Kconfig.projbuild


+ 0 - 0
examples/system/console/main/cmd_decl.h → examples/system/console/advanced/main/cmd_decl.h


+ 0 - 0
examples/system/console/main/cmd_wifi.c → examples/system/console/advanced/main/cmd_wifi.c


+ 0 - 0
examples/system/console/main/cmd_wifi.h → examples/system/console/advanced/main/cmd_wifi.h


+ 0 - 0
examples/system/console/main/component.mk → examples/system/console/advanced/main/component.mk


+ 3 - 0
examples/system/console/main/console_example_main.c → examples/system/console/advanced/main/console_example_main.c

@@ -121,6 +121,9 @@ static void initialize_console(void)
     /* Set command history size */
     linenoiseHistorySetMaxLen(100);
 
+    /* Set command maximum length */
+    linenoiseSetMaxLineLen(console_config.max_cmdline_length);
+
     /* Don't return empty lines */
     linenoiseAllowEmpty(false);
 

+ 0 - 0
examples/system/console/partitions_example.csv → examples/system/console/advanced/partitions_example.csv


+ 0 - 0
examples/system/console/sdkconfig.ci.history → examples/system/console/advanced/sdkconfig.ci.history


+ 0 - 0
examples/system/console/sdkconfig.ci.nohistory → examples/system/console/advanced/sdkconfig.ci.nohistory


+ 0 - 0
examples/system/console/sdkconfig.defaults → examples/system/console/advanced/sdkconfig.defaults


+ 8 - 0
examples/system/console/basic/CMakeLists.txt

@@ -0,0 +1,8 @@
+# The following lines of boilerplate have to be in your project's CMakeLists
+# in this exact order for cmake to work correctly
+cmake_minimum_required(VERSION 3.5)
+
+set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/system/console/advanced/components)
+
+include($ENV{IDF_PATH}/tools/cmake/project.cmake)
+project(console)

+ 10 - 0
examples/system/console/basic/Makefile

@@ -0,0 +1,10 @@
+#
+# This is a project Makefile. It is assumed the directory this Makefile resides in is a
+# project subdirectory.
+#
+
+PROJECT_NAME := console
+
+EXTRA_COMPONENT_DIRS := $(IDF_PATH)/examples/system/console/advanced/components
+
+include $(IDF_PATH)/make/project.mk

+ 151 - 0
examples/system/console/basic/README.md

@@ -0,0 +1,151 @@
+# Console Example
+
+(See the README.md file in the upper level 'examples' directory for more information about examples.)
+
+This example illustrates the usage of the [Console Component](https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/console.html#console) to create an interactive shell on the ESP chip. The interactive shell running on the ESP chip can then be controlled/interacted with over a serial port (UART).
+
+The interactive shell implemented in this example contains a wide variety of commands, and can act as a basis for applications that require a command-line interface (CLI).
+
+## How to use example
+
+### Hardware Required
+
+This example should be able to run on any commonly available Espressif development board.
+
+### Configure the project
+
+```
+idf.py menuconfig
+```
+
+* Enable/disable `Example Configuration > Store command history in flash` as necessary
+
+### Build and Flash
+
+Build the project and flash it to the board, then run monitor tool to view serial output:
+
+```
+idf.py -p PORT flash monitor
+```
+
+(Replace PORT with the name of the serial port to use.)
+
+(To exit the serial monitor, type ``Ctrl-]``.)
+
+See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
+
+## Example Output
+
+Enter the `help` command get a full list of all available commands. The following is a sample session of the Console Example where a variety of commands provided by the Console Example are used. Note that GPIO15 is connected to GND to remove the boot log output. 
+
+```
+This is an example of ESP-IDF console component.
+Type 'help' to get the list of commands.
+Use UP/DOWN arrows to navigate through command history.
+Press TAB when typing command name to auto-complete.
+[esp32]> help
+help 
+  Print the list of registered commands
+
+free 
+  Get the total size of heap memory available
+
+restart 
+  Restart the program
+
+deep_sleep  [-t <t>] [--io=<n>] [--io_level=<0|1>]
+  Enter deep sleep mode. Two wakeup modes are supported: timer and GPIO. If no
+  wakeup option is specified, will sleep indefinitely.
+  -t, --time=<t>  Wake up time, ms
+      --io=<n>  If specified, wakeup using GPIO with given number
+  --io_level=<0|1>  GPIO level to trigger wakeup
+
+join  [--timeout=<t>] <ssid> [<pass>]
+  Join WiFi AP as a station
+  --timeout=<t>  Connection timeout, ms
+        <ssid>  SSID of AP
+        <pass>  PSK of AP
+
+[esp32]> free
+257200
+[esp32]> deep_sleep -t 1000
+I (146929) deep_sleep: Enabling timer wakeup, timeout=1000000us
+I (619) heap_init: Initializing. RAM available for dynamic allocation:
+I (620) heap_init: At 3FFAE2A0 len 00001D60 (7 KiB): DRAM
+I (626) heap_init: At 3FFB7EA0 len 00028160 (160 KiB): DRAM
+I (645) heap_init: At 3FFE0440 len 00003BC0 (14 KiB): D/IRAM
+I (664) heap_init: At 3FFE4350 len 0001BCB0 (111 KiB): D/IRAM
+I (684) heap_init: At 40093EA8 len 0000C158 (48 KiB): IRAM
+
+This is an example of ESP-IDF console component.
+Type 'help' to get the list of commands.
+Use UP/DOWN arrows to navigate through command history.
+Press TAB when typing command name to auto-complete.
+[esp32]> join --timeout 10000 test_ap test_password
+I (182639) connect: Connecting to 'test_ap'
+I (184619) connect: Connected
+[esp32]> free
+212328
+[esp32]> restart
+I (205639) restart: Restarting
+I (616) heap_init: Initializing. RAM available for dynamic allocation:
+I (617) heap_init: At 3FFAE2A0 len 00001D60 (7 KiB): DRAM
+I (623) heap_init: At 3FFB7EA0 len 00028160 (160 KiB): DRAM
+I (642) heap_init: At 3FFE0440 len 00003BC0 (14 KiB): D/IRAM
+I (661) heap_init: At 3FFE4350 len 0001BCB0 (111 KiB): D/IRAM
+I (681) heap_init: At 40093EA8 len 0000C158 (48 KiB): IRAM
+
+This is an example of ESP-IDF console component.
+Type 'help' to get the list of commands.
+Use UP/DOWN arrows to navigate through command history.
+Press TAB when typing command name to auto-complete.
+[esp32]> 
+
+```
+
+## Troubleshooting
+
+### Line Endings
+
+The line endings in the Console Example are configured to match particular serial monitors. Therefore, if the following log output appears, consider using a different serial monitor (e.g. Putty for Windows) or modify the example's [UART configuration](#Configuring-UART-and-VFS).
+
+```
+This is an example of ESP-IDF console component.
+Type 'help' to get the list of commands.
+Use UP/DOWN arrows to navigate through command history.
+Press TAB when typing command name to auto-complete.
+Your terminal application does not support escape sequences.
+Line editing and history features are disabled.
+On Windows, try using Putty instead.
+esp32>
+```
+
+## Example Breakdown
+
+### Configuring UART
+
+The ``initialize_console()`` function in the example configures some aspects of UART relevant to the operation of the console. 
+
+- **Line Endings**: The default line endings are configured to match those expected/generated by common serial monitor programs, such as `screen`, `minicom`, and the `idf_monitor.py` included in the SDK. The default behavior for these commands are:
+    - When 'enter' key is pressed on the keyboard, `CR` (0x13) code is sent to the serial device.
+    - To move the cursor to the beginning of the next line, serial device needs to send `CR LF` (0x13 0x10) sequence.
+
+### Line editing
+
+The main source file of the example illustrates how to use `linenoise` library, including line completion, hints, and history.
+
+### Commands
+
+Several commands are registered using `esp_console_cmd_register()` function. See the `register_wifi()` and `register_system()` functions in `cmd_wifi.c` and `cmd_system.c` files.
+
+### Command handling
+
+Main loop inside `app_main()` function illustrates how to use `linenoise` and `esp_console_run()` to implement read/eval loop.
+
+### Argument parsing
+
+Several commands implemented in `cmd_wifi.c` and `cmd_system.c` use the Argtable3 library to parse and check the arguments.
+
+### Command history
+
+Each time a new command line is obtained from `linenoise`, it is written into history and the history is saved into a file in flash memory. On reset, history is initialized from that file.

+ 5 - 4
examples/system/console/example_test.py → examples/system/console/basic/example_test.py

@@ -1,21 +1,22 @@
+# type: ignore
 from __future__ import print_function
 
 import ttfw_idf
 
 
 @ttfw_idf.idf_example_test(env_tag='Example_GENERIC', target=['esp32', 'esp32c3'])
-def test_examples_system_console(env, extra_data):
-    dut = env.get_dut('console_example', 'examples/system/console', app_config_name='history')
+def test_examples_system_console_basic(env, _):
+    dut = env.get_dut('console_example', 'examples/system/console/basic', app_config_name='history')
     print('Using binary path: {}'.format(dut.app.binary_path))
     dut.start_app()
     dut.expect('Command history enabled')
     env.close_dut(dut.name)
 
-    dut = env.get_dut('console_example', 'examples/system/console', app_config_name='nohistory')
+    dut = env.get_dut('console_example', 'examples/system/console/basic', app_config_name='nohistory')
     print('Using binary path: {}'.format(dut.app.binary_path))
     dut.start_app()
     dut.expect('Command history disabled')
 
 
 if __name__ == '__main__':
-    test_examples_system_console()
+    test_examples_system_console_basic()

+ 3 - 0
examples/system/console/basic/main/CMakeLists.txt

@@ -0,0 +1,3 @@
+idf_component_register(SRCS "cmd_wifi.c"
+                            "console_example_main.c"
+                    INCLUDE_DIRS ".")

+ 18 - 0
examples/system/console/basic/main/Kconfig.projbuild

@@ -0,0 +1,18 @@
+menu "Example Configuration"
+
+    config CONSOLE_STORE_HISTORY
+        bool "Store command history in flash"
+        default y
+        help
+            Linenoise line editing library provides functions to save and load
+            command history. If this option is enabled, initalizes a FAT filesystem
+            and uses it to store command history.
+
+    config CONSOLE_MAX_COMMAND_LINE_LENGTH
+        int "Maximum command line length"
+        default 1024
+        help
+            This value marks the maximum length of a single command line. Once it is
+            reached, no more characters will be accepted by the console.
+
+endmenu

+ 21 - 0
examples/system/console/basic/main/cmd_decl.h

@@ -0,0 +1,21 @@
+/* Console example — declarations of command registration functions.
+
+   This example code is in the Public Domain (or CC0 licensed, at your option.)
+
+   Unless required by applicable law or agreed to in writing, this
+   software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+   CONDITIONS OF ANY KIND, either express or implied.
+*/
+#pragma once
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "cmd_system.h"
+#include "cmd_wifi.h"
+#include "cmd_nvs.h"
+
+#ifdef __cplusplus
+}
+#endif

+ 132 - 0
examples/system/console/basic/main/cmd_wifi.c

@@ -0,0 +1,132 @@
+/* Console example — WiFi commands
+
+   This example code is in the Public Domain (or CC0 licensed, at your option.)
+
+   Unless required by applicable law or agreed to in writing, this
+   software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+   CONDITIONS OF ANY KIND, either express or implied.
+*/
+
+#include <stdio.h>
+#include <string.h>
+#include "esp_log.h"
+#include "esp_console.h"
+#include "argtable3/argtable3.h"
+#include "cmd_decl.h"
+#include "freertos/FreeRTOS.h"
+#include "freertos/event_groups.h"
+#include "esp_wifi.h"
+#include "esp_netif.h"
+#include "esp_event.h"
+#include "cmd_wifi.h"
+
+#define JOIN_TIMEOUT_MS (10000)
+
+static EventGroupHandle_t wifi_event_group;
+const int CONNECTED_BIT = BIT0;
+
+
+static void event_handler(void* arg, esp_event_base_t event_base,
+                                int32_t event_id, void* event_data)
+{
+    if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
+        esp_wifi_connect();
+        xEventGroupClearBits(wifi_event_group, CONNECTED_BIT);
+    } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
+        xEventGroupSetBits(wifi_event_group, CONNECTED_BIT);
+    }
+}
+
+static void initialise_wifi(void)
+{
+    esp_log_level_set("wifi", ESP_LOG_WARN);
+    static bool initialized = false;
+    if (initialized) {
+        return;
+    }
+    ESP_ERROR_CHECK(esp_netif_init());
+    wifi_event_group = xEventGroupCreate();
+    ESP_ERROR_CHECK(esp_event_loop_create_default());
+    esp_netif_t *ap_netif = esp_netif_create_default_wifi_ap();
+    assert(ap_netif);
+    esp_netif_t *sta_netif = esp_netif_create_default_wifi_sta();
+    assert(sta_netif);
+    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
+    ESP_ERROR_CHECK( esp_wifi_init(&cfg) );
+    ESP_ERROR_CHECK( esp_event_handler_register(WIFI_EVENT, WIFI_EVENT_STA_DISCONNECTED, &event_handler, NULL) );
+    ESP_ERROR_CHECK( esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &event_handler, NULL) );
+    ESP_ERROR_CHECK( esp_wifi_set_storage(WIFI_STORAGE_RAM) );
+    ESP_ERROR_CHECK( esp_wifi_set_mode(WIFI_MODE_NULL) );
+    ESP_ERROR_CHECK( esp_wifi_start() );
+    initialized = true;
+}
+
+static bool wifi_join(const char *ssid, const char *pass, int timeout_ms)
+{
+    initialise_wifi();
+    wifi_config_t wifi_config = { 0 };
+    strlcpy((char *) wifi_config.sta.ssid, ssid, sizeof(wifi_config.sta.ssid));
+    if (pass) {
+        strlcpy((char *) wifi_config.sta.password, pass, sizeof(wifi_config.sta.password));
+    }
+
+    ESP_ERROR_CHECK( esp_wifi_set_mode(WIFI_MODE_STA) );
+    ESP_ERROR_CHECK( esp_wifi_set_config(WIFI_IF_STA, &wifi_config) );
+    esp_wifi_connect();
+
+    int bits = xEventGroupWaitBits(wifi_event_group, CONNECTED_BIT,
+                                   pdFALSE, pdTRUE, timeout_ms / portTICK_PERIOD_MS);
+    return (bits & CONNECTED_BIT) != 0;
+}
+
+/** Arguments used by 'join' function */
+static struct {
+    struct arg_int *timeout;
+    struct arg_str *ssid;
+    struct arg_str *password;
+    struct arg_end *end;
+} join_args;
+
+static int connect(int argc, char **argv)
+{
+    int nerrors = arg_parse(argc, argv, (void **) &join_args);
+    if (nerrors != 0) {
+        arg_print_errors(stderr, join_args.end, argv[0]);
+        return 1;
+    }
+    ESP_LOGI(__func__, "Connecting to '%s'",
+             join_args.ssid->sval[0]);
+
+    /* set default value*/
+    if (join_args.timeout->count == 0) {
+        join_args.timeout->ival[0] = JOIN_TIMEOUT_MS;
+    }
+
+    bool connected = wifi_join(join_args.ssid->sval[0],
+                               join_args.password->sval[0],
+                               join_args.timeout->ival[0]);
+    if (!connected) {
+        ESP_LOGW(__func__, "Connection timed out");
+        return 1;
+    }
+    ESP_LOGI(__func__, "Connected");
+    return 0;
+}
+
+void register_wifi(void)
+{
+    join_args.timeout = arg_int0(NULL, "timeout", "<t>", "Connection timeout, ms");
+    join_args.ssid = arg_str1(NULL, NULL, "<ssid>", "SSID of AP");
+    join_args.password = arg_str0(NULL, NULL, "<pass>", "PSK of AP");
+    join_args.end = arg_end(2);
+
+    const esp_console_cmd_t join_cmd = {
+        .command = "join",
+        .help = "Join WiFi AP as a station",
+        .hint = NULL,
+        .func = &connect,
+        .argtable = &join_args
+    };
+
+    ESP_ERROR_CHECK( esp_console_cmd_register(&join_cmd) );
+}

+ 20 - 0
examples/system/console/basic/main/cmd_wifi.h

@@ -0,0 +1,20 @@
+/* Console example — declarations of command registration functions.
+
+   This example code is in the Public Domain (or CC0 licensed, at your option.)
+
+   Unless required by applicable law or agreed to in writing, this
+   software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+   CONDITIONS OF ANY KIND, either express or implied.
+*/
+#pragma once
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// Register WiFi functions
+void register_wifi(void);
+
+#ifdef __cplusplus
+}
+#endif

+ 4 - 0
examples/system/console/basic/main/component.mk

@@ -0,0 +1,4 @@
+#
+# "main" pseudo-component makefile.
+#
+# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)

+ 95 - 0
examples/system/console/basic/main/console_example_main.c

@@ -0,0 +1,95 @@
+/* Console example
+
+   This example code is in the Public Domain (or CC0 licensed, at your option.)
+
+   Unless required by applicable law or agreed to in writing, this
+   software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+   CONDITIONS OF ANY KIND, either express or implied.
+*/
+
+#include <stdio.h>
+#include <string.h>
+#include "esp_system.h"
+#include "esp_log.h"
+#include "esp_console.h"
+#include "esp_vfs_dev.h"
+#include "driver/uart.h"
+#include "linenoise/linenoise.h"
+#include "argtable3/argtable3.h"
+#include "cmd_decl.h"
+#include "esp_vfs_fat.h"
+#include "nvs.h"
+#include "nvs_flash.h"
+
+#ifdef CONFIG_ESP_CONSOLE_USB_CDC
+#error This example is incompatible with USB CDC console. Please try "console_usb" example instead.
+#endif // CONFIG_ESP_CONSOLE_USB_CDC
+
+static const char* TAG = "example";
+#define PROMPT_STR CONFIG_IDF_TARGET
+
+/* Console command history can be stored to and loaded from a file.
+ * The easiest way to do this is to use FATFS filesystem on top of
+ * wear_levelling library.
+ */
+#if CONFIG_CONSOLE_STORE_HISTORY
+
+#define MOUNT_PATH "/data"
+#define HISTORY_PATH MOUNT_PATH "/history.txt"
+
+static void initialize_filesystem(void)
+{
+    static wl_handle_t wl_handle;
+    const esp_vfs_fat_mount_config_t mount_config = {
+            .max_files = 4,
+            .format_if_mount_failed = true
+    };
+    esp_err_t err = esp_vfs_fat_spiflash_mount(MOUNT_PATH, "storage", &mount_config, &wl_handle);
+    if (err != ESP_OK) {
+        ESP_LOGE(TAG, "Failed to mount FATFS (%s)", esp_err_to_name(err));
+        return;
+    }
+}
+#endif // CONFIG_STORE_HISTORY
+
+static void initialize_nvs(void)
+{
+    esp_err_t err = nvs_flash_init();
+    if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) {
+        ESP_ERROR_CHECK( nvs_flash_erase() );
+        err = nvs_flash_init();
+    }
+    ESP_ERROR_CHECK(err);
+}
+
+void app_main(void)
+{
+    esp_console_repl_t *repl = NULL;
+    esp_console_repl_config_t repl_config = ESP_CONSOLE_REPL_CONFIG_DEFAULT();
+    esp_console_dev_uart_config_t uart_config = ESP_CONSOLE_DEV_UART_CONFIG_DEFAULT();
+    /* Prompt to be printed before each line.
+     * This can be customized, made dynamic, etc.
+     */
+    repl_config.prompt = PROMPT_STR ">";
+    repl_config.max_cmdline_length = CONFIG_CONSOLE_MAX_COMMAND_LINE_LENGTH;
+
+    initialize_nvs();
+
+#if CONFIG_CONSOLE_STORE_HISTORY
+    initialize_filesystem();
+    repl_config.history_save_path = HISTORY_PATH;
+    ESP_LOGI(TAG, "Command history enabled");
+#else
+    ESP_LOGI(TAG, "Command history disabled");
+#endif
+
+    /* Register commands */
+    esp_console_register_help_command();
+    register_system();
+    register_wifi();
+    register_nvs();
+
+    ESP_ERROR_CHECK(esp_console_new_repl_uart(&uart_config, &repl_config, &repl));
+
+    ESP_ERROR_CHECK(esp_console_start_repl(repl));
+}

+ 6 - 0
examples/system/console/basic/partitions_example.csv

@@ -0,0 +1,6 @@
+# Name,   Type, SubType, Offset,  Size, Flags
+# Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap
+nvs,      data, nvs,     0x9000,  0x6000,
+phy_init, data, phy,     0xf000,  0x1000,
+factory,  app,  factory, 0x10000, 1M,
+storage,  data, fat,     ,        1M,

+ 4 - 0
examples/system/console/basic/sdkconfig.ci.history

@@ -0,0 +1,4 @@
+CONFIG_CONSOLE_STORE_HISTORY=y
+# IDF-3090
+CONFIG_ESP32C3_REV_MIN_0=y
+CONFIG_ESP32C3_REV_MIN=0

+ 4 - 0
examples/system/console/basic/sdkconfig.ci.nohistory

@@ -0,0 +1,4 @@
+CONFIG_CONSOLE_STORE_HISTORY=n
+# IDF-3090
+CONFIG_ESP32C3_REV_MIN_0=y
+CONFIG_ESP32C3_REV_MIN=0

+ 17 - 0
examples/system/console/basic/sdkconfig.defaults

@@ -0,0 +1,17 @@
+# Reduce bootloader log verbosity
+CONFIG_BOOTLOADER_LOG_LEVEL_WARN=y
+CONFIG_BOOTLOADER_LOG_LEVEL=2
+
+# Increase main task stack size
+CONFIG_ESP_MAIN_TASK_STACK_SIZE=7168
+
+# Enable filesystem
+CONFIG_PARTITION_TABLE_CUSTOM=y
+CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions_example.csv"
+CONFIG_PARTITION_TABLE_FILENAME="partitions_example.csv"
+
+# Enable FreeRTOS stats formatting functions, needed for 'tasks' command
+CONFIG_FREERTOS_USE_TRACE_FACILITY=y
+CONFIG_FREERTOS_USE_STATS_FORMATTING_FUNCTIONS=y
+
+CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y

+ 1 - 1
examples/system/console_usb/CMakeLists.txt

@@ -2,7 +2,7 @@
 # in this exact order for cmake to work correctly
 cmake_minimum_required(VERSION 3.5)
 
-set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/system/console/components)
+set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/system/console/advanced/components)
 
 include($ENV{IDF_PATH}/tools/cmake/project.cmake)
 project(console_usb)

+ 1 - 1
examples/wifi/ftm/CMakeLists.txt

@@ -2,7 +2,7 @@
 # in this exact order for cmake to work correctly
 cmake_minimum_required(VERSION 3.5)
 
-set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/system/console/components)
+set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/system/console/advanced/components)
 
 include($ENV{IDF_PATH}/tools/cmake/project.cmake)
 project(ftm)

+ 1 - 1
examples/wifi/iperf/CMakeLists.txt

@@ -2,7 +2,7 @@
 # in this exact order for cmake to work correctly
 cmake_minimum_required(VERSION 3.5)
 
-set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/system/console/components)
+set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/system/console/advanced/components)
 
 include($ENV{IDF_PATH}/tools/cmake/project.cmake)
 project(iperf)

+ 1 - 1
examples/wifi/iperf/Makefile

@@ -5,6 +5,6 @@
 
 PROJECT_NAME := iperf
 
-EXTRA_COMPONENT_DIRS := $(IDF_PATH)/examples/system/console/components
+EXTRA_COMPONENT_DIRS := $(IDF_PATH)/examples/system/console/advanced/components
 
 include $(IDF_PATH)/make/project.mk

+ 1 - 1
examples/wifi/simple_sniffer/CMakeLists.txt

@@ -2,7 +2,7 @@
 # in this exact order for cmake to work correctly
 cmake_minimum_required(VERSION 3.5)
 
-set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/system/console/components)
+set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/system/console/advanced/components)
 
 include($ENV{IDF_PATH}/tools/cmake/project.cmake)
 project(simple_sniffer)

+ 1 - 1
examples/wifi/simple_sniffer/Makefile

@@ -5,6 +5,6 @@
 
 PROJECT_NAME := simple_sniffer
 
-EXTRA_COMPONENT_DIRS := $(IDF_PATH)/examples/system/console/components
+EXTRA_COMPONENT_DIRS := $(IDF_PATH)/examples/system/console/advanced/components
 
 include $(IDF_PATH)/make/project.mk