Эх сурвалжийг харах

Merge branch 'bugfix/repl_on_another_uart_v4.3' into 'release/v4.3'

console: fix a bug preventing us from starting a CLI on non-default UART (backport v4.3)

See merge request espressif/esp-idf!14628
Zim Kalinowski 4 жил өмнө
parent
commit
cfeca10c06

+ 60 - 36
components/console/esp_console_repl.c

@@ -30,6 +30,7 @@
 static const char *TAG = "console.repl";
 static const char *TAG = "console.repl";
 
 
 #define CONSOLE_PROMPT_MAX_LEN (32)
 #define CONSOLE_PROMPT_MAX_LEN (32)
+#define CONSOLE_PATH_MAX_LEN   (ESP_VFS_PATH_MAX)
 
 
 typedef enum {
 typedef enum {
     CONSOLE_REPL_STATE_DEINIT,
     CONSOLE_REPL_STATE_DEINIT,
@@ -48,11 +49,7 @@ typedef struct {
 typedef struct {
 typedef struct {
     esp_console_repl_com_t repl_com; // base class
     esp_console_repl_com_t repl_com; // base class
     int uart_channel;                // uart channel number
     int uart_channel;                // uart channel number
-} esp_console_repl_uart_t;
-
-typedef struct {
-    esp_console_repl_com_t repl_com; // base class
-} esp_console_repl_usb_cdc_t;
+} esp_console_repl_universal_t;
 
 
 static void esp_console_repl_task(void *args);
 static void esp_console_repl_task(void *args);
 static esp_err_t esp_console_repl_uart_delete(esp_console_repl_t *repl);
 static esp_err_t esp_console_repl_uart_delete(esp_console_repl_t *repl);
@@ -64,21 +61,18 @@ static esp_err_t esp_console_setup_history(const char *history_path, uint32_t ma
 esp_err_t esp_console_new_repl_usb_cdc(const esp_console_dev_usb_cdc_config_t *dev_config, const esp_console_repl_config_t *repl_config, esp_console_repl_t **ret_repl)
 esp_err_t esp_console_new_repl_usb_cdc(const esp_console_dev_usb_cdc_config_t *dev_config, const esp_console_repl_config_t *repl_config, esp_console_repl_t **ret_repl)
 {
 {
     esp_err_t ret = ESP_OK;
     esp_err_t ret = ESP_OK;
-    esp_console_repl_usb_cdc_t *cdc_repl = NULL;
+    esp_console_repl_universal_t *cdc_repl = NULL;
     if (!repl_config | !dev_config | !ret_repl) {
     if (!repl_config | !dev_config | !ret_repl) {
         ret = ESP_ERR_INVALID_ARG;
         ret = ESP_ERR_INVALID_ARG;
         goto _exit;
         goto _exit;
     }
     }
     // allocate memory for console REPL context
     // allocate memory for console REPL context
-    cdc_repl = calloc(1, sizeof(esp_console_repl_usb_cdc_t));
+    cdc_repl = calloc(1, sizeof(esp_console_repl_universal_t));
     if (!cdc_repl) {
     if (!cdc_repl) {
         ret = ESP_ERR_NO_MEM;
         ret = ESP_ERR_NO_MEM;
         goto _exit;
         goto _exit;
     }
     }
 
 
-    /* Disable buffering on stdin */
-    setvbuf(stdin, NULL, _IONBF, 0);
-
     /* Minicom, screen, idf_monitor send CR when ENTER key is pressed */
     /* Minicom, screen, idf_monitor send CR when ENTER key is pressed */
     esp_vfs_dev_cdcacm_set_rx_line_endings(ESP_LINE_ENDINGS_CR);
     esp_vfs_dev_cdcacm_set_rx_line_endings(ESP_LINE_ENDINGS_CR);
     /* Move the caret to the beginning of the next line on '\n' */
     /* Move the caret to the beginning of the next line on '\n' */
@@ -103,15 +97,18 @@ esp_err_t esp_console_new_repl_usb_cdc(const esp_console_dev_usb_cdc_config_t *d
     // setup prompt
     // setup prompt
     esp_console_setup_prompt(repl_config->prompt, &cdc_repl->repl_com);
     esp_console_setup_prompt(repl_config->prompt, &cdc_repl->repl_com);
 
 
+    /* Fill the structure here as it will be used directly by the created task. */
+    cdc_repl->uart_channel = CONFIG_ESP_CONSOLE_UART_NUM;
+    cdc_repl->repl_com.state = CONSOLE_REPL_STATE_INIT;
+    cdc_repl->repl_com.repl_core.del = esp_console_repl_usb_cdc_delete;
+
     /* spawn a single thread to run REPL */
     /* spawn a single thread to run REPL */
     if (xTaskCreate(esp_console_repl_task, "console_repl", repl_config->task_stack_size,
     if (xTaskCreate(esp_console_repl_task, "console_repl", repl_config->task_stack_size,
-                    &cdc_repl->repl_com, repl_config->task_priority, &cdc_repl->repl_com.task_hdl) != pdTRUE) {
+                    cdc_repl, repl_config->task_priority, &cdc_repl->repl_com.task_hdl) != pdTRUE) {
         ret = ESP_FAIL;
         ret = ESP_FAIL;
         goto _exit;
         goto _exit;
     }
     }
 
 
-    cdc_repl->repl_com.state = CONSOLE_REPL_STATE_INIT;
-    cdc_repl->repl_com.repl_core.del = esp_console_repl_usb_cdc_delete;
     *ret_repl = &cdc_repl->repl_com.repl_core;
     *ret_repl = &cdc_repl->repl_com.repl_core;
     return ESP_OK;
     return ESP_OK;
 _exit:
 _exit:
@@ -128,13 +125,13 @@ _exit:
 esp_err_t esp_console_new_repl_uart(const esp_console_dev_uart_config_t *dev_config, const esp_console_repl_config_t *repl_config, esp_console_repl_t **ret_repl)
 esp_err_t esp_console_new_repl_uart(const esp_console_dev_uart_config_t *dev_config, const esp_console_repl_config_t *repl_config, esp_console_repl_t **ret_repl)
 {
 {
     esp_err_t ret = ESP_OK;
     esp_err_t ret = ESP_OK;
-    esp_console_repl_uart_t *uart_repl = NULL;
+    esp_console_repl_universal_t *uart_repl = NULL;
     if (!repl_config | !dev_config | !ret_repl) {
     if (!repl_config | !dev_config | !ret_repl) {
         ret = ESP_ERR_INVALID_ARG;
         ret = ESP_ERR_INVALID_ARG;
         goto _exit;
         goto _exit;
     }
     }
     // allocate memory for console REPL context
     // allocate memory for console REPL context
-    uart_repl = calloc(1, sizeof(esp_console_repl_uart_t));
+    uart_repl = calloc(1, sizeof(esp_console_repl_universal_t));
     if (!uart_repl) {
     if (!uart_repl) {
         ret = ESP_ERR_NO_MEM;
         ret = ESP_ERR_NO_MEM;
         goto _exit;
         goto _exit;
@@ -144,9 +141,6 @@ esp_err_t esp_console_new_repl_uart(const esp_console_dev_uart_config_t *dev_con
     fflush(stdout);
     fflush(stdout);
     fsync(fileno(stdout));
     fsync(fileno(stdout));
 
 
-    /* Disable buffering on stdin */
-    setvbuf(stdin, NULL, _IONBF, 0);
-
     /* Minicom, screen, idf_monitor send CR when ENTER key is pressed */
     /* Minicom, screen, idf_monitor send CR when ENTER key is pressed */
     esp_vfs_dev_uart_port_set_rx_line_endings(dev_config->channel, ESP_LINE_ENDINGS_CR);
     esp_vfs_dev_uart_port_set_rx_line_endings(dev_config->channel, ESP_LINE_ENDINGS_CR);
     /* Move the caret to the beginning of the next line on '\n' */
     /* Move the caret to the beginning of the next line on '\n' */
@@ -194,16 +188,19 @@ esp_err_t esp_console_new_repl_uart(const esp_console_dev_uart_config_t *dev_con
     // setup prompt
     // setup prompt
     esp_console_setup_prompt(repl_config->prompt, &uart_repl->repl_com);
     esp_console_setup_prompt(repl_config->prompt, &uart_repl->repl_com);
 
 
-    /* spawn a single thread to run REPL */
+    /* Fill the structure here as it will be used directly by the created task. */
+    uart_repl->uart_channel = dev_config->channel;
+    uart_repl->repl_com.state = CONSOLE_REPL_STATE_INIT;
+    uart_repl->repl_com.repl_core.del = esp_console_repl_uart_delete;
+
+    /* Spawn a single thread to run REPL, we need to pass `uart_repl` to it as
+     * it also requires the uart channel. */
     if (xTaskCreate(esp_console_repl_task, "console_repl", repl_config->task_stack_size,
     if (xTaskCreate(esp_console_repl_task, "console_repl", repl_config->task_stack_size,
-                    &uart_repl->repl_com, repl_config->task_priority, &uart_repl->repl_com.task_hdl) != pdTRUE) {
+                    uart_repl, repl_config->task_priority, &uart_repl->repl_com.task_hdl) != pdTRUE) {
         ret = ESP_FAIL;
         ret = ESP_FAIL;
         goto _exit;
         goto _exit;
     }
     }
 
 
-    uart_repl->uart_channel = dev_config->channel;
-    uart_repl->repl_com.state = CONSOLE_REPL_STATE_INIT;
-    uart_repl->repl_com.repl_core.del = esp_console_repl_uart_delete;
     *ret_repl = &uart_repl->repl_com.repl_core;
     *ret_repl = &uart_repl->repl_com.repl_core;
     return ESP_OK;
     return ESP_OK;
 _exit:
 _exit:
@@ -244,19 +241,10 @@ static esp_err_t esp_console_setup_prompt(const char *prompt, esp_console_repl_c
     }
     }
     snprintf(repl_com->prompt, CONSOLE_PROMPT_MAX_LEN - 1, LOG_COLOR_I "%s " LOG_RESET_COLOR, prompt_temp);
     snprintf(repl_com->prompt, CONSOLE_PROMPT_MAX_LEN - 1, LOG_COLOR_I "%s " LOG_RESET_COLOR, prompt_temp);
 
 
-    printf("\r\n"
-           "Type 'help' to get the list of commands.\r\n"
-           "Use UP/DOWN arrows to navigate through command history.\r\n"
-           "Press TAB when typing command name to auto-complete.\r\n");
-
     /* Figure out if the terminal supports escape sequences */
     /* Figure out if the terminal supports escape sequences */
     int probe_status = linenoiseProbe();
     int probe_status = linenoiseProbe();
     if (probe_status) {
     if (probe_status) {
         /* zero indicates success */
         /* zero indicates success */
-        printf("\r\n"
-               "Your terminal application does not support escape sequences.\n\n"
-               "Line editing and history features are disabled.\n\n"
-               "On Windows, try using Putty instead.\r\n");
         linenoiseSetDumbMode(1);
         linenoiseSetDumbMode(1);
 #if CONFIG_LOG_COLORS
 #if CONFIG_LOG_COLORS
         /* Since the terminal doesn't support escape sequences,
         /* Since the terminal doesn't support escape sequences,
@@ -325,7 +313,7 @@ static esp_err_t esp_console_repl_uart_delete(esp_console_repl_t *repl)
 {
 {
     esp_err_t ret = ESP_OK;
     esp_err_t ret = ESP_OK;
     esp_console_repl_com_t *repl_com = __containerof(repl, esp_console_repl_com_t, repl_core);
     esp_console_repl_com_t *repl_com = __containerof(repl, esp_console_repl_com_t, repl_core);
-    esp_console_repl_uart_t *uart_repl = __containerof(repl_com, esp_console_repl_uart_t, repl_com);
+    esp_console_repl_universal_t *uart_repl = __containerof(repl_com, esp_console_repl_universal_t, repl_com);
     // check if already de-initialized
     // check if already de-initialized
     if (repl_com->state == CONSOLE_REPL_STATE_DEINIT) {
     if (repl_com->state == CONSOLE_REPL_STATE_DEINIT) {
         ESP_LOGE(TAG, "already de-initialized");
         ESP_LOGE(TAG, "already de-initialized");
@@ -345,7 +333,7 @@ static esp_err_t esp_console_repl_usb_cdc_delete(esp_console_repl_t *repl)
 {
 {
     esp_err_t ret = ESP_OK;
     esp_err_t ret = ESP_OK;
     esp_console_repl_com_t *repl_com = __containerof(repl, esp_console_repl_com_t, repl_core);
     esp_console_repl_com_t *repl_com = __containerof(repl, esp_console_repl_com_t, repl_core);
-    esp_console_repl_usb_cdc_t *cdc_repl = __containerof(repl_com, esp_console_repl_usb_cdc_t, repl_com);
+    esp_console_repl_universal_t *cdc_repl = __containerof(repl_com, esp_console_repl_universal_t, repl_com);
     // check if already de-initialized
     // check if already de-initialized
     if (repl_com->state == CONSOLE_REPL_STATE_DEINIT) {
     if (repl_com->state == CONSOLE_REPL_STATE_DEINIT) {
         ESP_LOGE(TAG, "already de-initialized");
         ESP_LOGE(TAG, "already de-initialized");
@@ -361,9 +349,45 @@ _exit:
 
 
 static void esp_console_repl_task(void *args)
 static void esp_console_repl_task(void *args)
 {
 {
-    esp_console_repl_com_t *repl_com = (esp_console_repl_com_t *)args;
-    // waiting for task notify
+    esp_console_repl_universal_t *repl_conf = (esp_console_repl_universal_t *) args;
+    esp_console_repl_com_t *repl_com = &repl_conf->repl_com;
+    const int uart_channel = repl_conf->uart_channel;
+
+    /* Waiting for task notify. This happens when `esp_console_start_repl()`
+     * function is called. */
     ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
     ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
+
+    /* Change standard input and output of the task if the requested UART is
+     * NOT the default one. This block will replace stdin, stdout and stderr.
+     */
+    if (uart_channel != CONFIG_ESP_CONSOLE_UART_NUM) {
+        char path[CONSOLE_PATH_MAX_LEN] = { 0 };
+        snprintf(path, CONSOLE_PATH_MAX_LEN, "/dev/uart/%d", uart_channel);
+
+        stdin = fopen(path, "r");
+        stdout = fopen(path, "w");
+        stderr = stdout;
+    }
+
+    /* Disable buffering on stdin of the current task.
+     * If the console is ran on a different UART than the default one,
+     * buffering shall only be disabled for the current one. */
+    setvbuf(stdin, NULL, _IONBF, 0);
+
+    /* This message shall be printed here and not earlier as the stdout
+     * has just been set above. */
+    printf("\r\n"
+        "Type 'help' to get the list of commands.\r\n"
+        "Use UP/DOWN arrows to navigate through command history.\r\n"
+        "Press TAB when typing command name to auto-complete.\r\n");
+
+    if (linenoiseIsDumbMode()) {
+        printf("\r\n"
+               "Your terminal application does not support escape sequences.\n\n"
+               "Line editing and history features are disabled.\n\n"
+               "On Windows, try using Putty instead.\r\n");
+    }
+
     while (repl_com->state == CONSOLE_REPL_STATE_START) {
     while (repl_com->state == CONSOLE_REPL_STATE_START) {
         char *line = linenoise(repl_com->prompt);
         char *line = linenoise(repl_com->prompt);
         if (line == NULL) {
         if (line == NULL) {

+ 89 - 23
components/console/linenoise/linenoise.c

@@ -115,10 +115,12 @@
 #include <sys/types.h>
 #include <sys/types.h>
 #include <sys/fcntl.h>
 #include <sys/fcntl.h>
 #include <unistd.h>
 #include <unistd.h>
+#include <assert.h>
 #include "linenoise.h"
 #include "linenoise.h"
 
 
 #define LINENOISE_DEFAULT_HISTORY_MAX_LEN 100
 #define LINENOISE_DEFAULT_HISTORY_MAX_LEN 100
 #define LINENOISE_MAX_LINE 4096
 #define LINENOISE_MAX_LINE 4096
+#define LINENOISE_COMMAND_MAX_LEN 32
 
 
 static linenoiseCompletionCallback *completionCallback = NULL;
 static linenoiseCompletionCallback *completionCallback = NULL;
 static linenoiseHintsCallback *hintsCallback = NULL;
 static linenoiseHintsCallback *hintsCallback = NULL;
@@ -203,6 +205,11 @@ void linenoiseSetDumbMode(int set) {
     dumbmode = set;
     dumbmode = set;
 }
 }
 
 
+/* Returns whether the current mode is dumbmode or not. */
+bool linenoiseIsDumbMode(void) {
+    return dumbmode;
+}
+
 static void flushWrite(void) {
 static void flushWrite(void) {
     if (__fbufsize(stdout) > 0) {
     if (__fbufsize(stdout) > 0) {
         fflush(stdout);
         fflush(stdout);
@@ -214,47 +221,106 @@ static void flushWrite(void) {
  * and return it. On error -1 is returned, on success the position of the
  * and return it. On error -1 is returned, on success the position of the
  * cursor. */
  * cursor. */
 static int getCursorPosition(void) {
 static int getCursorPosition(void) {
-    char buf[32];
-    int cols, rows;
-    unsigned int i = 0;
-
-    /* Report cursor location */
-    fprintf(stdout, "\x1b[6n");
+    char buf[LINENOISE_COMMAND_MAX_LEN] = { 0 };
+    int cols = 0;
+    int rows = 0;
+    int i = 0;
+    const int out_fd = fileno(stdout);
+    const int in_fd = fileno(stdin);
+    /* The following ANSI escape sequence is used to get from the TTY the
+     * cursor position. */
+    const char get_cursor_cmd[] = "\x1b[6n";
+
+    /* Send the command to the TTY on the other end of the UART.
+     * Let's use unistd's write function. Thus, data sent through it are raw
+     * reducing the overhead compared to using fputs, fprintf, etc... */
+    write(out_fd, get_cursor_cmd, sizeof(get_cursor_cmd));
+
+    /* For USB CDC, it is required to flush the output. */
     flushWrite();
     flushWrite();
-    /* Read the response: ESC [ rows ; cols R */
+
+    /* The other end will send its response which format is ESC [ rows ; cols R
+     * We don't know exactly how many bytes we have to read, thus, perform a
+     * read for each byte.
+     * Stop right before the last character of the buffer, to be able to NULL
+     * terminate it. */
     while (i < sizeof(buf)-1) {
     while (i < sizeof(buf)-1) {
-        if (fread(buf+i, 1, 1, stdin) != 1) break;
-        if (buf[i] == 'R') break;
-        i++;
+        /* Keep using unistd's functions. Here, using `read` instead of `fgets`
+         * or `fgets` guarantees us that we we can read a byte regardless on
+         * whether the sender sent end of line character(s) (CR, CRLF, LF). */
+        if (read(in_fd, buf + i, 1) != 1 || buf[i] == 'R') {
+            /* If we couldn't read a byte from STDIN or if 'R' was received,
+             * the transmission is finished. */
+            break;
+        }
+
+        /* For some reasons, it is possible that we receive new line character
+         * after querying the cursor position on some UART. Let's ignore them,
+         * this will not affect the rest of the program. */
+        if (buf[i] != '\n') {
+            i++;
+        }
     }
     }
+
+    /* NULL-terminate the buffer, this is required by `sscanf`. */
     buf[i] = '\0';
     buf[i] = '\0';
-    /* Parse it. */
-    if (buf[0] != ESC || buf[1] != '[') return -1;
-    if (sscanf(buf+2,"%d;%d",&rows,&cols) != 2) return -1;
+
+    /* Parse the received data to get the position of the cursor. */
+    if (buf[0] != ESC || buf[1] != '[' || sscanf(buf+2,"%d;%d",&rows,&cols) != 2) {
+        return -1;
+    }
     return cols;
     return cols;
 }
 }
 
 
 /* Try to get the number of columns in the current terminal, or assume 80
 /* Try to get the number of columns in the current terminal, or assume 80
  * if it fails. */
  * if it fails. */
 static int getColumns(void) {
 static int getColumns(void) {
-    int start, cols;
-    int fd = fileno(stdout);
+    int start = 0;
+    int cols = 0;
+    int written = 0;
+    char seq[LINENOISE_COMMAND_MAX_LEN] = { 0 };
+    const int fd = fileno(stdout);
+
+    /* The following ANSI escape sequence is used to tell the TTY to move
+     * the cursor to the most-right position. */
+    const char move_cursor_right[] = "\x1b[999C";
+    const size_t cmd_len = sizeof(move_cursor_right);
+
+    /* This one is used to set the cursor position. */
+    const char set_cursor_pos[] = "\x1b[%dD";
 
 
     /* Get the initial position so we can restore it later. */
     /* Get the initial position so we can restore it later. */
     start = getCursorPosition();
     start = getCursorPosition();
-    if (start == -1) goto failed;
+    if (start == -1) {
+        goto failed;
+    }
 
 
-    /* Go to right margin and get position. */
-    if (fwrite("\x1b[999C", 1, 6, stdout) != 6) goto failed;
+    /* Send the command to go to right margin. Use `write` function instead of
+     * `fwrite` for the same reasons explained in `getCursorPosition()` */
+    if (write(fd, move_cursor_right, cmd_len) != cmd_len) {
+        goto failed;
+    }
     flushWrite();
     flushWrite();
+
+    /* After sending this command, we can get the new position of the cursor,
+     * we'd get the size, in columns, of the opened TTY. */
     cols = getCursorPosition();
     cols = getCursorPosition();
-    if (cols == -1) goto failed;
+    if (cols == -1) {
+        goto failed;
+    }
 
 
-    /* Restore position. */
+    /* Restore the position of the cursor back. */
     if (cols > start) {
     if (cols > start) {
-        char seq[32];
-        snprintf(seq,32,"\x1b[%dD",cols-start);
-        if (write(fd, seq, strlen(seq)) == -1) {
+        /* Generate the move cursor command. */
+        written = snprintf(seq, LINENOISE_COMMAND_MAX_LEN, set_cursor_pos, cols-start);
+
+        /* If `written` is equal or bigger than LINENOISE_COMMAND_MAX_LEN, it
+         * means that the output has been truncated because the size provided
+         * is too small. */
+        assert (written < LINENOISE_COMMAND_MAX_LEN);
+
+        /* Send the command with `write`, which is not buffered. */
+        if (write(fd, seq, written) == -1) {
             /* Can't recover... */
             /* Can't recover... */
         }
         }
         flushWrite();
         flushWrite();

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

@@ -69,6 +69,7 @@ void linenoiseHistoryFree(void);
 void linenoiseClearScreen(void);
 void linenoiseClearScreen(void);
 void linenoiseSetMultiLine(int ml);
 void linenoiseSetMultiLine(int ml);
 void linenoiseSetDumbMode(int set);
 void linenoiseSetDumbMode(int set);
+bool linenoiseIsDumbMode(void);
 void linenoisePrintKeyCodes(void);
 void linenoisePrintKeyCodes(void);
 void linenoiseAllowEmpty(bool);
 void linenoiseAllowEmpty(bool);
 
 

+ 6 - 0
examples/peripherals/uart/uart_repl/CMakeLists.txt

@@ -0,0 +1,6 @@
+# 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)
+
+include($ENV{IDF_PATH}/tools/cmake/project.cmake)
+project(uart_repl)

+ 8 - 0
examples/peripherals/uart/uart_repl/Makefile

@@ -0,0 +1,8 @@
+#
+# This is a project Makefile. It is assumed the directory this Makefile resides in is a
+# project subdirectory.
+#
+
+PROJECT_NAME := uart_repl
+
+include $(IDF_PATH)/make/project.mk

+ 61 - 0
examples/peripherals/uart/uart_repl/README.md

@@ -0,0 +1,61 @@
+# UART REPL Example
+
+(See the README.md file in the upper level 'examples' directory for more information about examples.)
+
+This example demonstrates how to use REPL console on a different UART than the default one.
+It also shows how to connect these two UART together, either for testing or for sending commands
+without any human interaction.
+
+## How to use example
+
+### Hardware Required
+
+The example can be run on any ESP board that have at least 2 UARTs. The development board shall be connected to a
+PC with a single USB cable for flashing and monitoring. If you are willing to monitor the console UART, you may use
+a 3.3V compatible USB-to-Serial dongle on its GPIO pin.
+
+### Setup the Hardware
+
+No external connection is needed in order to run the example. However, as stated before, if you are willing to see what
+is going on on the second UART (console UART), you can connect pins CONSOLE_UART_TX_PIN (5 by default) and
+CONSOLE_UART_RX_PIN (4 by default) to a Serial-to-USB adapter.
+
+### Configure the project
+
+The default values, located at the top of `main/uart_repl_example_main.c` can be changed such as:
+DEFAULT_UART_CHANNEL, CONSOLE_UART_CHANNEL, DEFAULT_UART_RX_PIN, DEFAULT_UART_TX_PIN, CONSOLE_UART_RX_PIN,
+CONSOLE_UART_TX_PIN, UARTS_BAUD_RATE, TASK_STACK_SIZE, and READ_BUF_SIZE.
+
+### Build and Flash
+
+Build the project and flash it to the board, then run monitor tool to view default UART's serial output:
+
+```
+idf.py -p PORT flash monitor
+```
+
+(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
+
+The example will set up the default UART to use DEFAULT_UART_RX_PIN and DEFAULT_UART_TX_PIN. Then, it will set up
+the REPL console on the second UART. Finally, it will connect both UARTs together in order to let default UART
+be able to send commands and receive replies to and from the console UART.
+
+Here is a diagram of what UARTs will look like:
+
+```
+                  UART default      UART console
+
+USB monitoring <------ TX -----------> RX----+
+                                             v
+                                       Parse command
+                                     and output result
+                                             |                 Optional 3.3V
+                       RX <----------- TX<---+  (----------->) Serial-to-USB
+                                                                  Adapter
+```
+
+If everything goes fine, the output on default UART should be "Result: Success". Else, it should be "Result: Failure".

+ 2 - 0
examples/peripherals/uart/uart_repl/main/CMakeLists.txt

@@ -0,0 +1,2 @@
+idf_component_register(SRCS "uart_repl_example_main.c"
+                    INCLUDE_DIRS ".")

+ 3 - 0
examples/peripherals/uart/uart_repl/main/component.mk

@@ -0,0 +1,3 @@
+#
+# Main Makefile. This is basically the same as a component makefile.
+#

+ 181 - 0
examples/peripherals/uart/uart_repl/main/uart_repl_example_main.c

@@ -0,0 +1,181 @@
+/* UART Echo 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 "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+#include "driver/uart.h"
+#include "soc/uart_periph.h"
+#include "esp_rom_gpio.h"
+#include "driver/gpio.h"
+#include "hal/gpio_hal.h"
+#include "sdkconfig.h"
+#include "esp_console.h"
+#include "linenoise/linenoise.h"
+#include <string.h>
+
+#define DEFAULT_UART_CHANNEL    (0)
+#define CONSOLE_UART_CHANNEL    (1 - DEFAULT_UART_CHANNEL)
+#define DEFAULT_UART_RX_PIN     (3)
+#define DEFAULT_UART_TX_PIN     (2)
+#define CONSOLE_UART_RX_PIN     (4)
+#define CONSOLE_UART_TX_PIN     (5)
+
+#define UARTS_BAUD_RATE         (115200)
+#define TASK_STACK_SIZE         (2048)
+#define READ_BUF_SIZE           (1024)
+
+/* Message printed by the "consoletest" command.
+ * It will also be used by the default UART to check the reply of the second
+ * UART. As end of line characters are not standard here (\n, \r\n, \r...),
+ * let's not include it in this string. */
+const char test_message[] = "This is an example string, if you can read this, the example is a success!";
+
+/**
+ * @brief This function connects default UART TX to console UART RX and default
+ * UART RX to console UART TX. The purpose is to send commands to the console
+ * and get the reply directly by reading RX FIFO.
+ */
+static void connect_uarts(void)
+{
+    esp_rom_gpio_connect_out_signal(DEFAULT_UART_RX_PIN, uart_periph_signal[1].tx_sig, false, false);
+    esp_rom_gpio_connect_in_signal(DEFAULT_UART_RX_PIN, uart_periph_signal[0].rx_sig, false);
+
+    esp_rom_gpio_connect_out_signal(DEFAULT_UART_TX_PIN, uart_periph_signal[0].tx_sig, false, false);
+    esp_rom_gpio_connect_in_signal(DEFAULT_UART_TX_PIN, uart_periph_signal[1].rx_sig, false);
+}
+
+/**
+ * @brief Disconnect default UART from the console UART, this is used once
+ * testing is finished, it will let us print messages on the UART without
+ * sending them back to the console UART. Else, we would get an infinite
+ * loop.
+ */
+static void disconnect_uarts(void)
+{
+    esp_rom_gpio_connect_out_signal(CONSOLE_UART_TX_PIN, uart_periph_signal[1].tx_sig, false, false);
+    esp_rom_gpio_connect_in_signal(CONSOLE_UART_RX_PIN, uart_periph_signal[1].rx_sig, false);
+}
+
+/**
+ * @brief Configure and install the default UART, then, connect it to the
+ * console UART.
+ */
+static void configure_uarts(void)
+{
+    /* Configure parameters of an UART driver,
+     * communication pins and install the driver */
+    uart_config_t uart_config = {
+        .baud_rate = UARTS_BAUD_RATE,
+        .data_bits = UART_DATA_8_BITS,
+        .parity    = UART_PARITY_DISABLE,
+        .stop_bits = UART_STOP_BITS_1,
+        .flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
+        .source_clk = UART_SCLK_APB,
+    };
+
+    ESP_ERROR_CHECK(uart_driver_install(DEFAULT_UART_CHANNEL, READ_BUF_SIZE * 2, 0, 0, NULL, 0));
+    ESP_ERROR_CHECK(uart_param_config(DEFAULT_UART_CHANNEL, &uart_config));
+    ESP_ERROR_CHECK(uart_set_pin(DEFAULT_UART_CHANNEL, DEFAULT_UART_TX_PIN, DEFAULT_UART_RX_PIN,
+                                 UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE));
+
+
+    connect_uarts();
+}
+
+/**
+ * @brief Function called when command `consoletest` will be invoked.
+ * It will simply print `test_message` defined above.
+ */
+static int console_test(int argc, char **argv) {
+    printf("%s\n", test_message);
+    return 0;
+}
+
+/**
+ * @brief Function executed in another task then main one (as the one main
+ * executes REPL console).
+ * It will send "consoletest" command to the console UART and then read back
+ * the response on RX.
+ * The response shall contain the test_message string.
+ */
+static void send_commands(void* arg) {
+    static char data[READ_BUF_SIZE];
+    char command[] = "consoletest\n";
+    int len = 0;
+    void* substring = NULL;
+
+    /* Discard the first messages sent by the console. */
+    do {
+        len = uart_read_bytes(DEFAULT_UART_CHANNEL, data, READ_BUF_SIZE, 100 / portTICK_RATE_MS);
+    } while (len == 0);
+
+    if ( len == -1 ) {
+        goto end;
+    }
+    /* Send the command `consoletest` to the console UART. */
+    len = uart_write_bytes(DEFAULT_UART_CHANNEL, command, sizeof(command));
+    if ( len == -1 ) {
+        goto end;
+    }
+
+    /* Get the answer back from the console, give it some delay. */
+    do {
+        len = uart_read_bytes(DEFAULT_UART_CHANNEL, data, READ_BUF_SIZE - 1, 250 / portTICK_RATE_MS);
+    } while (len == 0);
+
+    if ( len == -1 ) {
+        goto end;
+    }
+
+    /**
+     * Check whether we can find test_message in the received message. Before
+     * that, we need to add a NULL character to terminate the string.
+     */
+    data[len] = 0;
+    substring = strcasestr(data, test_message);
+
+end:
+    /* This is a must to not send anything to the console anymore! */
+    disconnect_uarts();
+    printf("Result: %s\n", substring == NULL ? "Failure" : "Success");
+    vTaskDelete(NULL);
+}
+
+void app_main(void)
+{
+    esp_console_repl_t *repl = NULL;
+    esp_console_repl_config_t repl_config = ESP_CONSOLE_REPL_CONFIG_DEFAULT();
+    repl_config.prompt = "repl >";
+    const esp_console_cmd_t cmd = {
+        .command = "consoletest",
+        .help = "Test console by sending a message",
+        .func = &console_test,
+    };
+    esp_console_dev_uart_config_t uart_config = {
+                                                    .channel = CONSOLE_UART_CHANNEL,
+                                                    .baud_rate = UARTS_BAUD_RATE,
+                                                    .tx_gpio_num = CONSOLE_UART_TX_PIN,
+                                                    .rx_gpio_num = CONSOLE_UART_RX_PIN,
+                                                };
+    /**
+     * As we don't have a real serial terminal, (we just use default UART to
+     * send and receive commands, ) we won't handle any escape sequence, so the
+     * easiest thing to do is set the console to "dumb" mode. */
+    linenoiseSetDumbMode(1);
+
+    ESP_ERROR_CHECK(esp_console_new_repl_uart(&uart_config, &repl_config, &repl));
+    configure_uarts();
+
+    ESP_ERROR_CHECK( esp_console_cmd_register(&cmd) );
+
+    /* Create a task for sending and receiving commands to and from the second UART. */
+    xTaskCreate(send_commands, "send_commands_task", TASK_STACK_SIZE, NULL, 10, NULL);
+
+    ESP_ERROR_CHECK(esp_console_start_repl(repl));
+}