From 0d0bec6c31db2eb462cf710955459e988984a46b Mon Sep 17 00:00:00 2001 From: Omar Chebib Date: Wed, 19 May 2021 12:08:12 +0800 Subject: [PATCH] console: fix a bug preventing us from starting a CLI on non-default UART It is now possible to start a REPL CLI on another UART than the default one. Closes https://github.com/espressif/esp-idf/issues/6897 --- components/console/esp_console_repl.c | 96 ++++++---- components/console/linenoise/linenoise.c | 112 ++++++++--- components/console/linenoise/linenoise.h | 1 + .../peripherals/uart/uart_repl/CMakeLists.txt | 6 + examples/peripherals/uart/uart_repl/Makefile | 8 + examples/peripherals/uart/uart_repl/README.md | 61 ++++++ .../uart/uart_repl/main/CMakeLists.txt | 2 + .../uart/uart_repl/main/component.mk | 3 + .../uart_repl/main/uart_repl_example_main.c | 181 ++++++++++++++++++ 9 files changed, 411 insertions(+), 59 deletions(-) create mode 100644 examples/peripherals/uart/uart_repl/CMakeLists.txt create mode 100644 examples/peripherals/uart/uart_repl/Makefile create mode 100644 examples/peripherals/uart/uart_repl/README.md create mode 100644 examples/peripherals/uart/uart_repl/main/CMakeLists.txt create mode 100644 examples/peripherals/uart/uart_repl/main/component.mk create mode 100644 examples/peripherals/uart/uart_repl/main/uart_repl_example_main.c diff --git a/components/console/esp_console_repl.c b/components/console/esp_console_repl.c index 19dd9d85ab..1bf1704e40 100644 --- a/components/console/esp_console_repl.c +++ b/components/console/esp_console_repl.c @@ -30,6 +30,7 @@ static const char *TAG = "console.repl"; #define CONSOLE_PROMPT_MAX_LEN (32) +#define CONSOLE_PATH_MAX_LEN (ESP_VFS_PATH_MAX) typedef enum { CONSOLE_REPL_STATE_DEINIT, @@ -48,11 +49,7 @@ typedef struct { typedef struct { esp_console_repl_com_t repl_com; // base class 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 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 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) { ret = ESP_ERR_INVALID_ARG; goto _exit; } // 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) { ret = ESP_ERR_NO_MEM; goto _exit; } - /* Disable buffering on stdin */ - setvbuf(stdin, NULL, _IONBF, 0); - /* Minicom, screen, idf_monitor send CR when ENTER key is pressed */ esp_vfs_dev_cdcacm_set_rx_line_endings(ESP_LINE_ENDINGS_CR); /* 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 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 */ 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; 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; return ESP_OK; _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 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) { ret = ESP_ERR_INVALID_ARG; goto _exit; } // 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) { ret = ESP_ERR_NO_MEM; 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); fsync(fileno(stdout)); - /* Disable buffering on stdin */ - setvbuf(stdin, NULL, _IONBF, 0); - /* 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); /* 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 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, - &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; 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; return ESP_OK; _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); - 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 */ int probe_status = linenoiseProbe(); if (probe_status) { /* 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); #if CONFIG_LOG_COLORS /* 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_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 if (repl_com->state == CONSOLE_REPL_STATE_DEINIT) { 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_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 if (repl_com->state == CONSOLE_REPL_STATE_DEINIT) { ESP_LOGE(TAG, "already de-initialized"); @@ -361,9 +349,45 @@ _exit: 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); + + /* 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) { char *line = linenoise(repl_com->prompt); if (line == NULL) { diff --git a/components/console/linenoise/linenoise.c b/components/console/linenoise/linenoise.c index 2661debba5..e09719dad2 100644 --- a/components/console/linenoise/linenoise.c +++ b/components/console/linenoise/linenoise.c @@ -115,10 +115,12 @@ #include #include #include +#include #include "linenoise.h" #define LINENOISE_DEFAULT_HISTORY_MAX_LEN 100 #define LINENOISE_MAX_LINE 4096 +#define LINENOISE_COMMAND_MAX_LEN 32 static linenoiseCompletionCallback *completionCallback = NULL; static linenoiseHintsCallback *hintsCallback = NULL; @@ -203,6 +205,11 @@ void linenoiseSetDumbMode(int set) { dumbmode = set; } +/* Returns whether the current mode is dumbmode or not. */ +bool linenoiseIsDumbMode(void) { + return dumbmode; +} + static void flushWrite(void) { if (__fbufsize(stdout) > 0) { fflush(stdout); @@ -214,47 +221,106 @@ static void flushWrite(void) { * and return it. On error -1 is returned, on success the position of the * cursor. */ static int getCursorPosition(void) { - char buf[32]; - int cols, rows; - unsigned int i = 0; + 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"; - /* Report cursor location */ - fprintf(stdout, "\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(); - /* 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) { - 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'; - /* 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; } /* Try to get the number of columns in the current terminal, or assume 80 * if it fails. */ 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. */ 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(); - cols = getCursorPosition(); - if (cols == -1) goto failed; - /* Restore position. */ + /* 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(); + if (cols == -1) { + goto failed; + } + + /* Restore the position of the cursor back. */ 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... */ } flushWrite(); diff --git a/components/console/linenoise/linenoise.h b/components/console/linenoise/linenoise.h index 58756a5230..610cacc6b8 100644 --- a/components/console/linenoise/linenoise.h +++ b/components/console/linenoise/linenoise.h @@ -69,6 +69,7 @@ void linenoiseHistoryFree(void); void linenoiseClearScreen(void); void linenoiseSetMultiLine(int ml); void linenoiseSetDumbMode(int set); +bool linenoiseIsDumbMode(void); void linenoisePrintKeyCodes(void); void linenoiseAllowEmpty(bool); diff --git a/examples/peripherals/uart/uart_repl/CMakeLists.txt b/examples/peripherals/uart/uart_repl/CMakeLists.txt new file mode 100644 index 0000000000..7900979425 --- /dev/null +++ b/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) diff --git a/examples/peripherals/uart/uart_repl/Makefile b/examples/peripherals/uart/uart_repl/Makefile new file mode 100644 index 0000000000..1209a9b3b7 --- /dev/null +++ b/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 diff --git a/examples/peripherals/uart/uart_repl/README.md b/examples/peripherals/uart/uart_repl/README.md new file mode 100644 index 0000000000..17822a0ff6 --- /dev/null +++ b/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". \ No newline at end of file diff --git a/examples/peripherals/uart/uart_repl/main/CMakeLists.txt b/examples/peripherals/uart/uart_repl/main/CMakeLists.txt new file mode 100644 index 0000000000..be3f1fa35a --- /dev/null +++ b/examples/peripherals/uart/uart_repl/main/CMakeLists.txt @@ -0,0 +1,2 @@ +idf_component_register(SRCS "uart_repl_example_main.c" + INCLUDE_DIRS ".") diff --git a/examples/peripherals/uart/uart_repl/main/component.mk b/examples/peripherals/uart/uart_repl/main/component.mk new file mode 100644 index 0000000000..44bd2b5273 --- /dev/null +++ b/examples/peripherals/uart/uart_repl/main/component.mk @@ -0,0 +1,3 @@ +# +# Main Makefile. This is basically the same as a component makefile. +# diff --git a/examples/peripherals/uart/uart_repl/main/uart_repl_example_main.c b/examples/peripherals/uart/uart_repl/main/uart_repl_example_main.c new file mode 100644 index 0000000000..b5fc01a329 --- /dev/null +++ b/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 +#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 + +#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)); +}