kopia lustrzana https://github.com/espressif/esp-idf
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!13897pull/7307/head
commit
f29d873c54
|
@ -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, \
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -72,6 +72,7 @@ void linenoiseSetDumbMode(int set);
|
|||
bool linenoiseIsDumbMode(void);
|
||||
void linenoisePrintKeyCodes(void);
|
||||
void linenoiseAllowEmpty(bool);
|
||||
int linenoiseSetMaxLineLen(size_t len);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
|
|
@ -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
|
||||
^^^^^^^^^
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 +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()
|
|
@ -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 +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)
|
|
@ -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
|
|
@ -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.
|
|
@ -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()
|
|
@ -0,0 +1,3 @@
|
|||
idf_component_register(SRCS "cmd_wifi.c"
|
||||
"console_example_main.c"
|
||||
INCLUDE_DIRS ".")
|
|
@ -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
|
|
@ -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
|
|
@ -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) );
|
||||
}
|
|
@ -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
|
|
@ -0,0 +1,4 @@
|
|||
#
|
||||
# "main" pseudo-component makefile.
|
||||
#
|
||||
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
|
|
@ -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));
|
||||
}
|
|
@ -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,
|
|
|
@ -0,0 +1,4 @@
|
|||
CONFIG_CONSOLE_STORE_HISTORY=y
|
||||
# IDF-3090
|
||||
CONFIG_ESP32C3_REV_MIN_0=y
|
||||
CONFIG_ESP32C3_REV_MIN=0
|
|
@ -0,0 +1,4 @@
|
|||
CONFIG_CONSOLE_STORE_HISTORY=n
|
||||
# IDF-3090
|
||||
CONFIG_ESP32C3_REV_MIN_0=y
|
||||
CONFIG_ESP32C3_REV_MIN=0
|
|
@ -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
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
Ładowanie…
Reference in New Issue