mirror of
https://github.com/espressif/esp-idf.git
synced 2025-07-30 10:47:19 +02:00
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
This commit is contained in:
@ -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;
|
} esp_console_repl_universal_t;
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
esp_console_repl_com_t repl_com; // base class
|
|
||||||
} esp_console_repl_usb_cdc_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;
|
esp_console_repl_universal_t *repl_conf = (esp_console_repl_universal_t *) args;
|
||||||
// waiting for task notify
|
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) {
|
||||||
|
@ -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];
|
char buf[LINENOISE_COMMAND_MAX_LEN] = { 0 };
|
||||||
int cols, rows;
|
int cols = 0;
|
||||||
unsigned int i = 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 */
|
/* Send the command to the TTY on the other end of the UART.
|
||||||
fprintf(stdout, "\x1b[6n");
|
* 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;
|
/* Keep using unistd's functions. Here, using `read` instead of `fgets`
|
||||||
if (buf[i] == 'R') break;
|
* or `fgets` guarantees us that we we can read a byte regardless on
|
||||||
i++;
|
* 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;
|
/* Parse the received data to get the position of the cursor. */
|
||||||
if (sscanf(buf+2,"%d;%d",&rows,&cols) != 2) return -1;
|
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 start = 0;
|
||||||
int fd = fileno(stdout);
|
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. */
|
/* Send the command to go to right margin. Use `write` function instead of
|
||||||
if (fwrite("\x1b[999C", 1, 6, stdout) != 6) goto failed;
|
* `fwrite` for the same reasons explained in `getCursorPosition()` */
|
||||||
|
if (write(fd, move_cursor_right, cmd_len) != cmd_len) {
|
||||||
|
goto failed;
|
||||||
|
}
|
||||||
flushWrite();
|
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) {
|
if (cols > start) {
|
||||||
char seq[32];
|
/* Generate the move cursor command. */
|
||||||
snprintf(seq,32,"\x1b[%dD",cols-start);
|
written = snprintf(seq, LINENOISE_COMMAND_MAX_LEN, set_cursor_pos, cols-start);
|
||||||
if (write(fd, seq, strlen(seq)) == -1) {
|
|
||||||
|
/* 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();
|
||||||
|
@ -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
examples/peripherals/uart/uart_repl/CMakeLists.txt
Normal file
6
examples/peripherals/uart/uart_repl/CMakeLists.txt
Normal file
@ -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
examples/peripherals/uart/uart_repl/Makefile
Normal file
8
examples/peripherals/uart/uart_repl/Makefile
Normal file
@ -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
examples/peripherals/uart/uart_repl/README.md
Normal file
61
examples/peripherals/uart/uart_repl/README.md
Normal file
@ -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
examples/peripherals/uart/uart_repl/main/CMakeLists.txt
Normal file
2
examples/peripherals/uart/uart_repl/main/CMakeLists.txt
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
idf_component_register(SRCS "uart_repl_example_main.c"
|
||||||
|
INCLUDE_DIRS ".")
|
3
examples/peripherals/uart/uart_repl/main/component.mk
Normal file
3
examples/peripherals/uart/uart_repl/main/component.mk
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
#
|
||||||
|
# Main Makefile. This is basically the same as a component makefile.
|
||||||
|
#
|
@ -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));
|
||||||
|
}
|
Reference in New Issue
Block a user