Merge branch 'feature/linenoise_improvement' into 'master'

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

Closes IDFGH-5296

See merge request espressif/esp-idf!13897
This commit is contained in:
Zim Kalinowski
2021-07-15 03:37:19 +00:00
53 changed files with 617 additions and 27 deletions

View File

@@ -48,6 +48,7 @@ typedef struct {
uint32_t task_stack_size; //!< repl task stack size uint32_t task_stack_size; //!< repl task stack size
uint32_t task_priority; //!< repl task priority uint32_t task_priority; //!< repl task priority
const char *prompt; //!< prompt (NULL represents default: "esp> ") 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; } esp_console_repl_config_t;
/** /**
@@ -61,6 +62,7 @@ typedef struct {
.task_stack_size = 4096, \ .task_stack_size = 4096, \
.task_priority = 2, \ .task_priority = 2, \
.prompt = NULL, \ .prompt = NULL, \
.max_cmdline_length = 0, \
} }
/** /**

View File

@@ -38,6 +38,7 @@ typedef struct {
repl_state_t state; repl_state_t state;
const char *history_save_path; 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; } esp_console_repl_com_t;
typedef struct { 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 #if CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG
static esp_err_t esp_console_repl_usb_serial_jtag_delete(esp_console_repl_t *repl); static esp_err_t esp_console_repl_usb_serial_jtag_delete(esp_console_repl_t *repl);
#endif //CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG #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_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); 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); fcntl(fileno(stdin), F_SETFL, 0);
// initialize console, common part // 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) { if (ret != ESP_OK) {
goto _exit; 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 // 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) { if (ret != ESP_OK) {
goto _exit; 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); esp_vfs_dev_uart_use_driver(dev_config->channel);
// initialize console, common part // 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) { if (ret != ESP_OK) {
goto _exit; goto _exit;
} }
@@ -353,11 +354,18 @@ _exit:
return ret; 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; esp_err_t ret = ESP_OK;
/* Initialize the console */ /* Initialize the console */
esp_console_config_t console_config = ESP_CONSOLE_CONFIG_DEFAULT(); 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 #if CONFIG_LOG_COLORS
console_config.hint_color = atoi(LOG_COLOR_CYAN); console_config.hint_color = atoi(LOG_COLOR_CYAN);
#endif #endif
@@ -485,6 +493,7 @@ static void esp_console_repl_task(void *args)
"On Windows, try using Putty instead.\r\n"); "On Windows, try using Putty instead.\r\n");
} }
linenoiseSetMaxLineLen(repl_com->max_cmdline_length);
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) {

View File

@@ -119,13 +119,16 @@
#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_DEFAULT_MAX_LINE 4096
#define LINENOISE_MINIMAL_MAX_LINE 64
#define LINENOISE_COMMAND_MAX_LEN 32 #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 linenoiseCompletionCallback *completionCallback = NULL;
static linenoiseHintsCallback *hintsCallback = NULL; static linenoiseHintsCallback *hintsCallback = NULL;
static linenoiseFreeHintsCallback *freeHintsCallback = 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 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 dumbmode = 0; /* Dumb mode where line editing is disabled. Off by default */
static int history_max_len = LINENOISE_DEFAULT_HISTORY_MAX_LEN; static int history_max_len = LINENOISE_DEFAULT_HISTORY_MAX_LEN;
@@ -683,6 +686,21 @@ int linenoiseEditInsert(struct linenoiseState *l, char c) {
return 0; 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. */ /* Move cursor on the left. */
void linenoiseEditMoveLeft(struct linenoiseState *l) { void linenoiseEditMoveLeft(struct linenoiseState *l) {
if (l->pos > 0) { if (l->pos > 0) {
@@ -779,6 +797,12 @@ void linenoiseEditDeletePrevWord(struct linenoiseState *l) {
refreshLine(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. /* 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 * It expects 'fd' to be already in "raw mode" so that every key pressed
* will be returned ASAP to read(). * will be returned ASAP to read().
@@ -789,6 +813,7 @@ void linenoiseEditDeletePrevWord(struct linenoiseState *l) {
* The function returns the length of the current buffer. */ * The function returns the length of the current buffer. */
static int linenoiseEdit(char *buf, size_t buflen, const char *prompt) static int linenoiseEdit(char *buf, size_t buflen, const char *prompt)
{ {
uint32_t t1 = 0;
struct linenoiseState l; struct linenoiseState l;
int out_fd = fileno(stdout); int out_fd = fileno(stdout);
int in_fd = fileno(stdin); int in_fd = fileno(stdin);
@@ -827,9 +852,29 @@ static int linenoiseEdit(char *buf, size_t buflen, const char *prompt)
int nread; int nread;
char seq[3]; 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); nread = read(in_fd, &c, 1);
if (nread <= 0) return l.len; 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 /* Only autocomplete when the callback is set. It returns < 0 when
* there was an error reading from fd. Otherwise it will return the * there was an error reading from fd. Otherwise it will return the
* character that should be handled next. */ * 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. */ /* The high level function that is the main API of the linenoise library. */
char *linenoise(const char *prompt) { char *linenoise(const char *prompt) {
char *buf = calloc(1, LINENOISE_MAX_LINE); char *buf = calloc(1, max_cmdline_length);
int count = 0; int count = 0;
if (buf == NULL) { if (buf == NULL) {
return NULL; return NULL;
} }
if (!dumbmode) { if (!dumbmode) {
count = linenoiseRaw(buf, LINENOISE_MAX_LINE, prompt); count = linenoiseRaw(buf, max_cmdline_length, prompt);
} else { } else {
count = linenoiseDumb(buf, LINENOISE_MAX_LINE, prompt); count = linenoiseDumb(buf, max_cmdline_length, prompt);
} }
if (count > 0) { if (count > 0) {
sanitize(buf); sanitize(buf);
@@ -1214,13 +1259,13 @@ int linenoiseHistoryLoad(const char *filename) {
return -1; return -1;
} }
char *buf = calloc(1, LINENOISE_MAX_LINE); char *buf = calloc(1, max_cmdline_length);
if (buf == NULL) { if (buf == NULL) {
fclose(fp); fclose(fp);
return -1; return -1;
} }
while (fgets(buf,LINENOISE_MAX_LINE,fp) != NULL) { while (fgets(buf, max_cmdline_length, fp) != NULL) {
char *p; char *p;
p = strchr(buf,'\r'); p = strchr(buf,'\r');
@@ -1234,3 +1279,14 @@ int linenoiseHistoryLoad(const char *filename) {
return 0; 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;
}

View File

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

View File

@@ -39,6 +39,9 @@ Linenoise library does not need explicit initialization. However, some configura
:cpp:func:`linenoiseAllowEmpty` :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. 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 Main loop
^^^^^^^^^ ^^^^^^^^^

View File

@@ -2,7 +2,7 @@
# in this exact order for cmake to work correctly # in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.5) 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) $ENV{IDF_PATH}/examples/wifi/iperf/components)
include($ENV{IDF_PATH}/tools/cmake/project.cmake) include($ENV{IDF_PATH}/tools/cmake/project.cmake)

View File

@@ -5,7 +5,7 @@
PROJECT_NAME := ethernet_iperf 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 EXTRA_COMPONENT_DIRS += $(IDF_PATH)/examples/wifi/iperf/components

View File

@@ -2,7 +2,7 @@
# in this exact order for cmake to work correctly # in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.5) 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) include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(i2c-tools) project(i2c-tools)

View File

@@ -5,6 +5,6 @@
PROJECT_NAME := i2c-tools 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 include $(IDF_PATH)/make/project.mk

View File

@@ -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()

View File

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

View File

@@ -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)

View File

@@ -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

View File

@@ -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.

View File

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

View File

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

View File

@@ -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

View File

@@ -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

View File

@@ -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) );
}

View File

@@ -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

View File

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

View File

@@ -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));
}

View File

@@ -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,
1 # Name, Type, SubType, Offset, Size, Flags
2 # Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap
3 nvs, data, nvs, 0x9000, 0x6000,
4 phy_init, data, phy, 0xf000, 0x1000,
5 factory, app, factory, 0x10000, 1M,
6 storage, data, fat, , 1M,

View File

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

View File

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

View File

@@ -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

View File

@@ -2,7 +2,7 @@
# in this exact order for cmake to work correctly # in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.5) 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) include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(console_usb) project(console_usb)

View File

@@ -2,7 +2,7 @@
# in this exact order for cmake to work correctly # in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.5) 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) include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(ftm) project(ftm)

View File

@@ -2,7 +2,7 @@
# in this exact order for cmake to work correctly # in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.5) 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) include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(iperf) project(iperf)

View File

@@ -5,6 +5,6 @@
PROJECT_NAME := iperf 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 include $(IDF_PATH)/make/project.mk

View File

@@ -2,7 +2,7 @@
# in this exact order for cmake to work correctly # in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.5) 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) include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(simple_sniffer) project(simple_sniffer)

View File

@@ -5,6 +5,6 @@
PROJECT_NAME := simple_sniffer 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 include $(IDF_PATH)/make/project.mk