Console
About
The Console library provides an interactive command-line interface (CLI) for ESP32 applications.
It wraps the ESP-IDF console component and exposes an Arduino-style API for registering
commands, starting a background REPL (Read-Eval-Print Loop) task, managing command history, and
parsing typed arguments using the argtable3 library.
Features include:
Background REPL task with configurable stack, priority, and core affinity.
Tab completion and inline hints powered by the linenoise library.
Persistent command history saved to LittleFS or SPIFFS.
Typed argument parsing via argtable3 (integers, strings, optional arguments, etc.).
Context-aware command callbacks for object-oriented design.
Transport-agnostic: works with UART
Serialand HWCDC / USB-JTAG (HWCDCSerial) — the same transport thatSerialuses is detected automatically from theARDUINO_USB_MODE/ARDUINO_USB_CDC_ON_BOOTbuild flags.
Note
The Console REPL currently does not support USB OTG (CDC via TinyUSB / USBSerial).
On boards where USB OTG is selected as the Serial transport, the REPL will not
receive input. Use UART or HWCDC instead.
Note that some features like history and tab completion are only available on terminals that
support VT100 escape sequences. The Arduino IDE Serial Monitor does not support them.
If needed, you can use any other terminal that supports VT100 escape sequences, such as
PuTTY, Minicom or Picocom. If your terminal prints weird sequences of characters like [5n,
it means that the terminal does not support VT100 escape sequences.
To disable VT100 escape sequences, you can use the setPlainMode() method and use a plain text mode instead.
The Console library is usable by all ESP32 variants. Depending on how the board’s USB and Serial are implemented, you may
need to ensure that USB CDC is enabled on boot and use the Hardware CDC. For example, on the most recent ESP32-P4 revisions,
you need to enable USB CDC On Boot and set the USB Mode to Hardware CDC and JTAG.
Header File
#include <Console.h>
Overview
The library exposes a single global object Console of type ConsoleClass.
Typical usage:
(Optional) Call configuration methods such as
setPrompt(),setMaxHistory(), andsetHistoryFile()before callingbegin().Call
Console.begin()to initialize the underlyingesp_consolemodule.Register commands with
Console.addCmd()orConsole.addCmdWithContext().Call
Console.addHelpCmd()to register the built-inhelpcommand.Call
Console.attachToSerial(true)to start the interactive session in a background task.
Alternatively, skip attachToSerial(true) and call Console.run() manually from your own input loop
(see the ConsoleManual example).
Two function-pointer typedefs are provided for command callbacks:
// Simple callback
typedef int (*ConsoleCommandFunc)(int argc, char **argv);
// Context-aware callback (receives a user-supplied pointer as the first argument)
typedef int (*ConsoleCommandFuncWithCtx)(void *context, int argc, char **argv);
Both argtable3 (<console/argtable3/argtable3.h>) and linenoise
(<console/linenoise/linenoise.h>) headers are included by Console.h and are directly
available in your sketch.
Argument Parsing with argtable3
The Console library re-exports the argtable3 C library, which
provides GNU-style argument parsing for your commands. Instead of manually parsing argv
strings, you declare typed argument descriptors and let argtable handle validation, error
messages, and automatic hint generation.
Declaring Arguments
Each argument is created by calling a constructor function. The two most common variants are:
arg_xxx1(...)— required argument (exactly 1 occurrence).arg_xxx0(...)— optional argument (0 or 1 occurrence).
Available types:
Constructor |
Value field |
Description |
|---|---|---|
|
|
Integer (decimal, hex |
|
|
Double-precision floating point |
|
|
String (no whitespace unless quoted) |
|
|
Boolean flag (no value — presence means true) |
|
(none) |
Sentinel that terminates the table and stores parse errors |
Every constructor takes four parameters:
struct arg_xxx* arg_xxx1(
const char *shortopts, // Short option letter, e.g. "p" for -p (or NULL)
const char *longopts, // Long option name, e.g. "pin" for --pin (or NULL)
const char *datatype, // Placeholder shown in hints, e.g. "<pin>"
const char *glossary // Description shown in help text
);
When both shortopts and longopts are NULL, the argument is positional (matched
by order, not by a flag).
Building an Argument Table
Group all argument descriptors into a struct that ends with arg_end:
// Declare a global struct for the argument table
static struct {
struct arg_int *pin; // positional, required: GPIO pin number
struct arg_int *value; // positional, required: 0 or 1
struct arg_end *end; // must always be last — tracks parse errors
} my_args;
void setup() {
// Allocate and initialize each argument descriptor.
// arg_int1: required int arg. (NULL, NULL) = positional (no -x/--xx flag).
my_args.pin = arg_int1(NULL, NULL, "<pin>", "GPIO pin number");
my_args.value = arg_int1(NULL, NULL, "<0|1>", "0 = LOW, 1 = HIGH");
// arg_end(N): sentinel — N is the max number of errors to store.
my_args.end = arg_end(2);
// Register the command, passing &my_args as the argtable.
// The Console library auto-generates the hint string from the table.
Console.addCmd("gpio", "Write a GPIO pin", &my_args, cmd_gpio);
}
Parsing Inside a Command Handler
Inside the command callback, call arg_parse() to validate the arguments and fill in the
values:
static int cmd_gpio(int argc, char **argv) {
// Parse argv against the argument table
int nerrors = arg_parse(argc, argv, (void **)&my_args);
if (nerrors != 0) {
// Print human-readable error messages to stdout
arg_print_errors(stdout, my_args.end, argv[0]);
return 1;
}
// Access the parsed values through the typed fields
int pin = my_args.pin->ival[0]; // first (and only) int value
int val = my_args.value->ival[0];
pinMode(pin, OUTPUT);
digitalWrite(pin, val ? HIGH : LOW);
printf("GPIO %d = %d\n", pin, val);
return 0;
}
Optional Arguments and Flags
Use arg_xxx0 for optional parameters, and check ->count to see if they were supplied:
static struct {
struct arg_str *ssid; // required positional string
struct arg_str *password; // optional positional string
struct arg_lit *verbose; // optional flag: -v / --verbose
struct arg_end *end;
} connect_args;
void setup() {
connect_args.ssid = arg_str1(NULL, NULL, "<ssid>", "Network name");
connect_args.password = arg_str0(NULL, NULL, "[<password>]", "Password (optional)");
connect_args.verbose = arg_lit0("v", "verbose", "Print extra info");
connect_args.end = arg_end(3);
// ...
}
static int cmd_connect(int argc, char **argv) {
int nerrors = arg_parse(argc, argv, (void **)&connect_args);
if (nerrors != 0) {
arg_print_errors(stdout, connect_args.end, argv[0]);
return 1;
}
const char *ssid = connect_args.ssid->sval[0];
// Check if the optional password was supplied
const char *pass = (connect_args.password->count > 0)
? connect_args.password->sval[0]
: "";
// Check if the -v / --verbose flag was present
bool verbose = (connect_args.verbose->count > 0);
// ...
return 0;
}
See the ConsoleGPIO and ConsoleWiFi examples for complete working sketches.
Arduino-esp32 Console API
begin
Initialize the console module. Must be called before any other method.
bool begin(size_t maxCmdLen = 256, size_t maxArgs = 32)Parameters
maxCmdLen(Optional)
Maximum length of a single command-line in bytes. Default:
256.
maxArgs(Optional)
Maximum number of whitespace-separated tokens on a line. Default:
32.
- Returns
trueon success;falseifesp_console_init()fails.- Notes
Sets up linenoise tab-completion and hints automatically.
Loads history from the file set by
setHistoryFile()if one was configured.Calling
begin()again after it has already succeeded has no effect.
end
De-initialize the console module.
void end()
- Notes
Stops the REPL task if running.
Saves command history to the configured file before freeing resources.
setPrompt
Set the prompt string displayed at the start of each input line.
void setPrompt(const char *prompt) void setPrompt(const String &prompt)
- Parameters
prompt— Prompt string. Default:"esp> ".- Notes
Must be called before
begin()to take effect in the REPL task.
setMaxHistory
Set the maximum number of command history entries kept in RAM.
void setMaxHistory(uint32_t maxLen)
- Parameters
maxLen— Maximum history entries. Default:32.
setHistoryFile
Set the filesystem and file path for persistent command history.
void setHistoryFile(fs::FS &fs, const char *path) void setHistoryFile(fs::FS &fs, const String &path)The method combines
fs.mountpoint()andpathinternally to build the full VFS path used by linenoise for loading and saving history. You only need to pass the filesystem-relative path.
- Parameters
path— File path relative to the filesystem root, e.g."/history.txt".
fs— A mounted Arduino filesystem object. AcceptsLittleFS,SPIFFS,FFat, etc.- Notes
History is loaded from this file at
begin()and saved after every command.The filesystem must be mounted before
begin()is called.Example
LittleFS.begin(true); Console.setHistoryFile(LittleFS, "/history.txt");
setTaskStackSize
Set the REPL background task stack size in bytes.
void setTaskStackSize(uint32_t size)
- Parameters
size— Stack size in bytes. Default:4096.
setTaskPriority
Set the REPL background task priority.
void setTaskPriority(uint32_t priority)
- Parameters
priority— FreeRTOS task priority. Default:2.
setTaskCore
Pin the REPL task to a specific CPU core.
void setTaskCore(BaseType_t core)
- Parameters
core— Core index (0or1) ortskNO_AFFINITY. Default:tskNO_AFFINITY.
usePsram
Route all Console PSRAM-eligible allocations to external SPI RAM.
void usePsram(bool enable)When enabled:
The
esp_consolecommand registry (heap_alloc_capsinesp_console_config_t) is allocated from PSRAM.The REPL FreeRTOS task stack is allocated from PSRAM via
xTaskCreatePinnedToCoreWithCaps().This frees internal SRAM for other uses at the cost of slightly higher latency for stack and heap accesses. Has no effect if PSRAM is not available or not initialized at boot.
- Parameters
enable—trueto use PSRAM,falsefor internal RAM (default:true).- Notes
Must be called before
begin()to affect heap allocation, and beforeattachToSerial(true)to affect the task stack.Example
Console.usePsram(true); // before begin() Console.begin(); Console.attachToSerial(true);
addCmd
Register a command. Three overloads are available depending on how the hint is provided.
// Auto-generated hint (no hint / hint from argtable) bool addCmd(const char *name, const char *help, ConsoleCommandFunc func) bool addCmd(const String &name, const String &help, ConsoleCommandFunc func) // Explicit hint string bool addCmd(const char *name, const char *help, const char *hint, ConsoleCommandFunc func) bool addCmd(const String &name, const String &help, const String &hint, ConsoleCommandFunc func) // argtable3 argument table (hint auto-generated from the table) bool addCmd(const char *name, const char *help, void *argtable, ConsoleCommandFunc func)Parameters
name(Required)
Command name as typed at the prompt. Must not contain spaces. The pointer must remain valid until
end()is called.
help(Required)
Help text shown by the built-in
helpcommand.
hint(Optional)
Short argument synopsis shown inline while typing, e.g.
"<pin> <value>".
argtable(Optional)
Pointer to an argtable3 argument table (a struct ending with
arg_end). The hint is generated automatically from the table. The table is only read during registration.
func(Required)
Command handler. Receives
argc/argvwhereargv[0]is the command name. Return0for success, non-zero for failure.
- Returns
trueon success.
addCmdWithContext
Register a context-aware command.
bool addCmdWithContext(const char *name, const char *help, ConsoleCommandFuncWithCtx func, void *ctx) bool addCmdWithContext(const String &name, const String &help, ConsoleCommandFuncWithCtx func, void *ctx) bool addCmdWithContext(const char *name, const char *help, const char *hint, ConsoleCommandFuncWithCtx func, void *ctx) bool addCmdWithContext(const String &name, const String &help, const String &hint, ConsoleCommandFuncWithCtx func, void *ctx) bool addCmdWithContext(const char *name, const char *help, void *argtable, ConsoleCommandFuncWithCtx func, void *ctx)Parameters
func— Callback of typeConsoleCommandFuncWithCtx. Thecontextpointer is passed as the first argument on every call.
ctx— Arbitrary pointer forwarded tofuncas its first argument.All other parameters are the same as
addCmd.
- Returns
trueon success.
removeCmd
Unregister a previously registered command.
bool removeCmd(const char *name) bool removeCmd(const String &name)
- Parameters
name— Name of the command to remove.- Returns
trueon success;falseif the command was not found.
addHelpCmd
Register the built-in
helpcommand.bool addHelpCmd()When called with no arguments,
helplists all registered commands with their hints and help text. When called with a command name (e.g.help gpio), it prints the details for that command only.
- Returns
trueon success.
removeHelpCmd
Remove the built-in
helpcommand.bool removeHelpCmd()
- Returns
trueon success.
setHelpVerboseLevel
Control the verbosity of the built-in
helpcommand.bool setHelpVerboseLevel(int level)
- Parameters
level
0— Brief: show command name and hint only.
1— Verbose: also show the full help text.- Returns
trueon success;falseiflevelis out of range.
attachToSerial
Attach or detach the console from the serial port.
bool attachToSerial(bool enable)When
enableistrue, starts the Read-Eval-Print Loop in a background FreeRTOS task. The task reads input throughSerial(bypassing VFSstdinto avoid a race condition with the UART/USB driver ISR) and passes each line toesp_console_run(). Non-empty entries are added to the command history. Because reading goes throughSerial, no transport-specific configuration is required — UART and HWCDC work automatically.When
enableisfalse, stops the background task and frees the serial port for other use.Note
USB OTG (CDC via TinyUSB /
USBSerial) is not currently supported as a REPL transport.At startup, the task probes the terminal for VT100 support by sending a one-time Device Status Request (
ESC[5n). If the terminal does not respond within 500 ms, plain mode is enabled automatically. UsesetPlainMode()beforeattachToSerial(true)to override the probe result.
- Parameters
enable—trueto attach and start the REPL task,falseto detach and stop it.- Returns
trueon success.- Notes
begin()must be called beforeattachToSerial(true).Calling
attachToSerial(true)a second time has no effect.Calling
attachToSerial(false)when not attached has no effect.
isAttachedToSerial
Check whether the console is currently attached to the serial port.
bool isAttachedToSerial()
- Returns
trueif the REPL background task is running.
run
Execute a command-line string directly without a REPL task.
int run(const char *cmdline) int run(const String &cmdline)
- Parameters
cmdline— Full command-line (command name followed by arguments).- Returns
The command handler’s return code (
0= success).
-1if the command was not found or anesp_consoleerror occurred.- Notes
run()can be called from inside a command handler to invoke other commands (command composition). However, the underlying IDF function shares a single parse buffer, so the caller’sargvpointers are invalidated after the call returns. Copy anyargvvalues you need into local variables before callingrun().static int cmd_led(int argc, char **argv) { // Read argv BEFORE calling run() — it becomes invalid after. bool turnOn = (strcmp(argv[1], "on") == 0); if (turnOn) { Console.run("gpio write 2 1"); } // Do NOT use argv[1] here. return 0; }History is not updated by
run(). Manage history manually when building a custom input loop (see theConsoleManualexample).
clearScreen
Clear the terminal screen.
void clearScreen()
setMultiLine
Enable or disable multi-line editing mode.
void setMultiLine(bool enable)
- Parameters
enable—trueto allow the input line to wrap across multiple terminal rows. Disabled by default.
setPlainMode
Enable or disable plain (non-VT100) input mode.
void setPlainMode(bool enable, bool force = true)In plain mode linenoise falls back to basic line input without cursor movement, arrow-key history navigation, or tab-completion rendering. Useful for terminals that do not support ANSI/VT100 escape sequences (such as the Arduino IDE Serial Monitor).
By default,
attachToSerial(true)probes the terminal at startup (sending a one-time Device Status Request) and enables plain mode automatically if the terminal does not respond.
- Parameters
enable—trueto enable plain mode,falseto restore VT100 mode.
force— Whentrue(default), the automatic VT100 probe atattachToSerial(true)startup is skipped and the mode set here is kept unconditionally. Whenfalse, the mode is applied immediately butattachToSerial(true)may still override it based on the probe result.- Notes
This wraps linenoise’s internal “dumb mode”, a legacy term from the era of character-only display terminals. The Arduino API uses the friendlier name.
isPlainMode
Check whether plain (non-VT100) input mode is currently active.
bool isPlainMode()Returns
trueif plain mode is enabled, either because it was set explicitly withsetPlainMode(true)or because the automatic VT100 probe atattachToSerial(true)startup detected a non-VT100 terminal.
- Returns
trueif plain mode is active,falseotherwise.
splitArgv
Split a command-line string into
argv-style tokens in place.static size_t Console.splitArgv(char *line, char **argv, size_t argv_size)Handles quoted strings (spaces preserved, quotes stripped) and backslash escapes. Modifies the input buffer.
Parameters
line— Null-terminated input string, modified in place.
argv— Output array; each element will point intoline.
argv_size— Size of theargvarray. At mostargv_size - 1tokens are returned;argv[argc]is always set toNULL.
- Returns
Number of tokens found (
argc).Example
char line[] = "gpio write 5 1"; char *argv[8]; size_t argc = Console.splitArgv(line, argv, 8); // argc == 4, argv[0]=="gpio", argv[1]=="write", argv[2]=="5", argv[3]=="1"
Examples
ConsoleBasic
Full interactive REPL with history, tab completion, and inline hints.
/*
* ConsoleBasic — Basic interactive REPL with history and tab completion.
*
* This example demonstrates the basic usage of the Console library.
* It uses the REPL (Read-Eval-Print Loop) API to create an interactive shell.
* These are the available commands:
*
* version — Print firmware version
* heap — Show free heap memory
* restart — Restart the device
* echo — Echo arguments back
*
* Connect with any serial terminal (115200 baud) and type "help" to list
* available commands. Note that for full functionality (like history and tab completion),
* you need to use a serial terminal that supports VT100 escape sequences (Arduino IDE does not).
*
* Board support: all ESP32 variants using UART or HWCDC (USB-JTAG).
* Currently, the Console library does not support USB OTG (CDC via TinyUSB / USBSerial).
*
* Created by lucasssvaz
*/
#include <Arduino.h>
#include <Console.h>
// ---------------------------------------------------------------------------
// Command: version
// ---------------------------------------------------------------------------
static int cmd_version(int argc, char **argv) {
printf("arduino-esp32 %s\n", ESP_ARDUINO_VERSION_STR);
return 0;
}
// ---------------------------------------------------------------------------
// Command: heap
// ---------------------------------------------------------------------------
static int cmd_heap(int argc, char **argv) {
printf("Free heap : %lu bytes\n", (unsigned long)ESP.getFreeHeap());
printf("Min heap : %lu bytes\n", (unsigned long)ESP.getMinFreeHeap());
return 0;
}
// ---------------------------------------------------------------------------
// Command: restart
// ---------------------------------------------------------------------------
static int cmd_restart(int argc, char **argv) {
printf("Restarting in 1 second...\n");
fflush(stdout);
delay(1000);
ESP.restart();
return 0;
}
// ---------------------------------------------------------------------------
// Command: echo <message...>
// ---------------------------------------------------------------------------
static int cmd_echo(int argc, char **argv) {
for (int i = 1; i < argc; i++) {
printf("%s", argv[i]);
if (i < argc - 1) {
printf(" ");
}
}
printf("\n");
return 0;
}
// ---------------------------------------------------------------------------
// setup / loop
// ---------------------------------------------------------------------------
void setup() {
Serial.begin(115200);
while (!Serial) {
delay(10);
}
// Configure Console
Console.setPrompt("esp32> ");
Console.setMaxHistory(32);
// Initialize Console
if (!Console.begin()) {
Serial.println("Console init failed");
return;
}
// Add commands
Console.addCmd("version", "Print firmware version", cmd_version);
Console.addCmd("heap", "Show free heap memory", cmd_heap);
Console.addCmd("restart", "Restart the device", cmd_restart);
Console.addCmd("echo", "Echo arguments back", "<message...>", cmd_echo);
// Add built-in help command
Console.addHelpCmd();
// Begin Read-Eval-Print Loop
if (!Console.attachToSerial(true)) {
Serial.println("REPL start failed");
}
}
void loop() {
// Loop task is not used in this example, so we delete it to free up resources
vTaskDelete(NULL);
}
ConsoleManual
Manual command execution from a custom input loop in loop(), without a background task.
/*
* ConsoleManual — Run commands from Serial without a background REPL task.
*
* Demonstrates using Console.run() inside loop() to execute commands read
* from Serial. Useful when you need full control over the input loop or
* want to integrate the console into an existing event-driven sketch.
*
* Type a command (e.g. "heap" or "help") in the Serial Monitor and press
* Enter. No background task is created.
*
* Created by lucasssvaz
*/
#include <Arduino.h>
#include <Console.h>
// ---------------------------------------------------------------------------
// Command: heap
// ---------------------------------------------------------------------------
static int cmd_heap(int argc, char **argv) {
printf("Free heap : %lu bytes\n", (unsigned long)ESP.getFreeHeap());
return 0;
}
// ---------------------------------------------------------------------------
// Command: uptime
// ---------------------------------------------------------------------------
static int cmd_uptime(int argc, char **argv) {
unsigned long ms = millis();
unsigned long s = ms / 1000;
unsigned long days = s / 86400;
unsigned long hours = (s % 86400) / 3600;
unsigned long mins = (s % 3600) / 60;
unsigned long secs = s % 60;
printf("up %lu day(s), %02lu:%02lu:%02lu (%.3f s)\n", days, hours, mins, secs, ms / 1000.0f);
return 0;
}
// ---------------------------------------------------------------------------
// setup / loop
// ---------------------------------------------------------------------------
static String inputBuffer;
void setup() {
Serial.begin(115200);
while (!Serial) {
delay(10);
}
// Configure Console prompt
Console.setPrompt(""); // No prompt — we print it manually below
// Initialize Console
if (!Console.begin()) {
Serial.println("Console init failed");
return;
}
// Add commands
Console.addCmd("heap", "Show free heap", cmd_heap);
Console.addCmd("uptime", "Show uptime", cmd_uptime);
// Add built-in help command
Console.addHelpCmd();
Serial.println("Console ready. Type a command and press Enter.");
Serial.print("> ");
}
// In this example we manually implement the input loop instead of using the REPL task.
// This is useful when you need full control over the input loop or want to integrate the console into an existing event-driven sketch.
void loop() {
while (Serial.available()) {
char c = (char)Serial.read();
if (c == '\r') {
continue;
}
if (c == '\n') {
inputBuffer.trim();
if (inputBuffer.length() > 0) {
Serial.println();
Console.run(inputBuffer);
inputBuffer = "";
}
Serial.print("> ");
} else {
Serial.print(c); // local echo
inputBuffer += c;
}
}
}
ConsoleSysInfo
System information commands: uptime, heap, tasks, version, restart.
/*
* ConsoleSysInfo — System information CLI commands.
*
* Implements a small set of diagnostic commands inspired by common Unix tools:
*
* uptime — elapsed time since boot
* heap — free and minimum heap memory
* tasks — list FreeRTOS tasks with state and stack watermark
* restart — restart the device
* version — firmware and IDF version strings
*
* Created by lucasssvaz
*/
#include <Arduino.h>
#include <Console.h>
// ---------------------------------------------------------------------------
// uptime
// ---------------------------------------------------------------------------
static int cmd_uptime(int argc, char **argv) {
unsigned long ms = millis();
unsigned long s = ms / 1000;
unsigned long days = s / 86400;
unsigned long hours = (s % 86400) / 3600;
unsigned long mins = (s % 3600) / 60;
unsigned long secs = s % 60;
printf("up %lu day(s), %02lu:%02lu:%02lu (%.3f s)\n", days, hours, mins, secs, ms / 1000.0f);
return 0;
}
// ---------------------------------------------------------------------------
// heap
// ---------------------------------------------------------------------------
static int cmd_heap(int argc, char **argv) {
printf("Free heap : %7lu bytes\n", (unsigned long)ESP.getFreeHeap());
printf("Min free heap: %7lu bytes\n", (unsigned long)ESP.getMinFreeHeap());
printf("Max alloc : %7lu bytes\n", (unsigned long)ESP.getMaxAllocHeap());
return 0;
}
// ---------------------------------------------------------------------------
// tasks
// ---------------------------------------------------------------------------
static int cmd_tasks(int argc, char **argv) {
uint32_t nTasks = uxTaskGetNumberOfTasks();
TaskStatus_t *pxTaskStatusArray = (TaskStatus_t *)malloc(nTasks * sizeof(TaskStatus_t));
if (!pxTaskStatusArray) {
printf("Out of memory\n");
return 1;
}
uint32_t ulTotalRunTime = 0;
nTasks = uxTaskGetSystemState(pxTaskStatusArray, nTasks, &ulTotalRunTime);
printf("%-20s %8s %5s %5s %4s\n", "Name", "State", "Prio", "Stack", "Core");
printf("%-20s %8s %5s %5s %4s\n", "--------------------", "--------", "-----", "-----", "----");
static const char *stateNames[] = {"Running", "Ready", "Blocked", "Suspend", "Deleted", "Invalid"};
for (uint32_t i = 0; i < nTasks; i++) {
TaskStatus_t *t = &pxTaskStatusArray[i];
int state = (int)t->eCurrentState;
if (state > 5) {
state = 5;
}
int core = (int)t->xCoreID;
char coreStr[4];
if (core == tskNO_AFFINITY) {
snprintf(coreStr, sizeof(coreStr), "any");
} else {
snprintf(coreStr, sizeof(coreStr), "%" PRIu8, (uint8_t)core);
}
printf("%-20s %8s %5lu %5lu %4s\n", t->pcTaskName, stateNames[state], (unsigned long)t->uxCurrentPriority, (unsigned long)t->usStackHighWaterMark, coreStr);
}
free(pxTaskStatusArray);
return 0;
}
// ---------------------------------------------------------------------------
// version
// ---------------------------------------------------------------------------
static int cmd_version(int argc, char **argv) {
printf("Arduino-ESP32 : %s\n", ESP_ARDUINO_VERSION_STR);
printf("ESP-IDF : %s\n", esp_get_idf_version());
printf("Chip : %s rev %d\n", ESP.getChipModel(), ESP.getChipRevision());
printf("CPU freq : %lu MHz\n", (unsigned long)ESP.getCpuFreqMHz());
printf("Flash size : %lu bytes\n", (unsigned long)ESP.getFlashChipSize());
return 0;
}
// ---------------------------------------------------------------------------
// restart
// ---------------------------------------------------------------------------
static int cmd_restart(int argc, char **argv) {
printf("Restarting in 1 second...\n");
fflush(stdout);
delay(1000);
ESP.restart();
return 0;
}
// ---------------------------------------------------------------------------
// setup / loop
// ---------------------------------------------------------------------------
void setup() {
Serial.begin(115200);
while (!Serial) {
delay(10);
}
// Configure Console prompt
Console.setPrompt("sysinfo> ");
// Initialize Console
if (!Console.begin()) {
Serial.println("Console init failed");
return;
}
// Add commands
Console.addCmd("uptime", "Show time since boot", cmd_uptime);
Console.addCmd("heap", "Show heap memory statistics", cmd_heap);
Console.addCmd("tasks", "List FreeRTOS tasks", cmd_tasks);
Console.addCmd("version", "Print firmware/chip version", cmd_version);
Console.addCmd("restart", "Restart the device", cmd_restart);
// Add built-in help command
Console.addHelpCmd();
// Begin Read-Eval-Print Loop
Console.attachToSerial(true);
}
void loop() {
// Loop task is not used in this example, so we delete it to free up resources
vTaskDelete(NULL);
}
ConsoleFS
File system CLI over LittleFS: ls, cat, rm, mkdir, write, df.
/*
* ConsoleFS — File system CLI commands over LittleFS.
*
* Implements a minimal shell for browsing and managing the LittleFS partition:
*
* ls [path] — list directory contents
* cat <file> — print file contents
* rm <file> — delete a file
* mkdir <dir> — create a directory
* write <file> <text> — write (overwrite) text to a file
* df — show filesystem usage
*
* Requires LittleFS to be mounted (LittleFS.begin() called in setup).
*
* Created by lucasssvaz
*/
#include <Arduino.h>
#include <Console.h>
#include <LittleFS.h>
// Ensure path starts with '/' so LittleFS.open() accepts it.
// Returns a pointer to either the original string or a static buffer.
static const char *normalizePath(const char *path) {
if (path[0] == '/') {
return path;
}
static char buf[256];
snprintf(buf, sizeof(buf), "/%s", path);
return buf;
}
// ---------------------------------------------------------------------------
// ls [path]
// ---------------------------------------------------------------------------
static int cmd_ls(int argc, char **argv) {
const char *path = (argc > 1) ? normalizePath(argv[1]) : "/";
File dir = LittleFS.open(path);
if (!dir || !dir.isDirectory()) {
printf("ls: %s: not a directory\n", path);
return 1;
}
File entry = dir.openNextFile();
while (entry) {
if (entry.isDirectory()) {
printf("d %s/\n", entry.name());
} else {
printf("f %-40s %zu bytes\n", entry.name(), entry.size());
}
entry = dir.openNextFile();
}
return 0;
}
// ---------------------------------------------------------------------------
// cat <file>
// ---------------------------------------------------------------------------
static int cmd_cat(int argc, char **argv) {
if (argc < 2) {
printf("Usage: cat <file>\n");
return 1;
}
const char *path = normalizePath(argv[1]);
File f = LittleFS.open(path, "r");
if (!f) {
printf("cat: %s: no such file\n", path);
return 1;
}
while (f.available()) {
int c = f.read();
putchar(c);
}
f.close();
printf("\n");
return 0;
}
// ---------------------------------------------------------------------------
// rm <file>
// ---------------------------------------------------------------------------
static int cmd_rm(int argc, char **argv) {
if (argc < 2) {
printf("Usage: rm <file>\n");
return 1;
}
const char *path = normalizePath(argv[1]);
if (!LittleFS.exists(path)) {
printf("rm: %s: no such file\n", path);
return 1;
}
if (!LittleFS.remove(path)) {
printf("rm: %s: failed\n", path);
return 1;
}
printf("removed '%s'\n", path);
return 0;
}
// ---------------------------------------------------------------------------
// mkdir <dir>
// ---------------------------------------------------------------------------
static int cmd_mkdir(int argc, char **argv) {
if (argc < 2) {
printf("Usage: mkdir <dir>\n");
return 1;
}
const char *path = normalizePath(argv[1]);
if (!LittleFS.mkdir(path)) {
printf("mkdir: %s: failed\n", path);
return 1;
}
printf("created '%s'\n", path);
return 0;
}
// ---------------------------------------------------------------------------
// write <file> <text...>
// ---------------------------------------------------------------------------
static int cmd_write(int argc, char **argv) {
if (argc < 3) {
printf("Usage: write <file> <text...>\n");
return 1;
}
const char *path = normalizePath(argv[1]);
File f = LittleFS.open(path, "w");
if (!f) {
printf("write: %s: cannot open\n", path);
return 1;
}
for (int i = 2; i < argc; i++) {
f.print(argv[i]);
if (i < argc - 1) {
f.print(' ');
}
}
f.println();
f.close();
printf("written to '%s'\n", path);
return 0;
}
// ---------------------------------------------------------------------------
// df
// ---------------------------------------------------------------------------
static int cmd_df(int argc, char **argv) {
size_t total = LittleFS.totalBytes();
size_t used = LittleFS.usedBytes();
size_t free_ = total - used;
printf("Filesystem Size Used Free Use%%\n");
printf("LittleFS %-8zu %-8zu %-8zu %3zu%%\n", total, used, free_, total ? (used * 100 / total) : 0);
return 0;
}
// ---------------------------------------------------------------------------
// setup / loop
// ---------------------------------------------------------------------------
void setup() {
Serial.begin(115200);
while (!Serial) {
delay(10);
}
// Mount LittleFS (format on first use)
if (!LittleFS.begin(true)) {
Serial.println("LittleFS mount failed");
return;
}
Serial.println("LittleFS mounted");
// Configure Console prompt
Console.setPrompt("fs> ");
// Initialize Console
if (!Console.begin()) {
Serial.println("Console init failed");
return;
}
// Add commands
Console.addCmd("ls", "List directory contents", "[path]", cmd_ls);
Console.addCmd("cat", "Print file contents", "<file>", cmd_cat);
Console.addCmd("rm", "Delete a file", "<file>", cmd_rm);
Console.addCmd("mkdir", "Create a directory", "<dir>", cmd_mkdir);
Console.addCmd("write", "Write text to a file", "<file> <text...>", cmd_write);
Console.addCmd("df", "Show filesystem usage", cmd_df);
// Add built-in help command
Console.addHelpCmd();
// Begin Read-Eval-Print Loop
Console.attachToSerial(true);
}
void loop() {
// Loop task is not used in this example, so we delete it to free up resources
vTaskDelete(NULL);
}
ConsoleGPIO
GPIO control with argtable3 argument parsing: gpio read, gpio write, gpio mode.
LED control is supported with the led command on boards with a built-in LED.
/*
* ConsoleGPIO — GPIO control via CLI using argtable3 argument parsing.
*
* Implements three sub-commands accessed as a single "gpio" command:
*
* gpio read <pin> — read and print pin level
* gpio write <pin> <0|1> — write HIGH or LOW to a pin
* gpio mode <pin> <mode> — set pin mode (in/out/in_pu/in_pd)
* led <on|off> [color] — toggle LED (with RGB color on supported boards)
*
* Demonstrates argtable3 usage: typed arguments with automatic hint
* generation and built-in error reporting. The "led" command shows how to
* reuse existing command handlers from a higher-level convenience command.
*
* Created by lucasssvaz
*/
#include <Arduino.h>
#include <Console.h>
#include "argtable3/argtable3.h"
#define RGB_BRIGHTNESS 64 // Change brightness (max 255)
// ---------------------------------------------------------------------------
// gpio read <pin>
// ---------------------------------------------------------------------------
static struct {
struct arg_int *pin; // required int: GPIO pin number
struct arg_end *end; // sentinel: tracks parse errors
} gpio_read_args;
static int cmd_gpio_read(int argc, char **argv) {
// arg_parse() validates argv against the argument table, returns error count
int nerrors = arg_parse(argc, argv, (void **)&gpio_read_args);
if (nerrors != 0) {
// Print human-readable error messages (e.g. "missing <pin> argument")
arg_print_errors(stderr, gpio_read_args.end, argv[0]);
return 1;
}
// Access the parsed integer value via ->ival[0]
int pin = gpio_read_args.pin->ival[0];
int val = digitalRead(pin);
Serial.printf("GPIO %d = %d (%s)\n", pin, val, val ? "HIGH" : "LOW");
return 0;
}
// ---------------------------------------------------------------------------
// gpio write <pin> <value>
// ---------------------------------------------------------------------------
static struct {
struct arg_int *pin; // required int: GPIO pin number
struct arg_int *value; // required int: 0 or 1
struct arg_end *end; // sentinel: tracks parse errors
} gpio_write_args;
static int cmd_gpio_write(int argc, char **argv) {
int nerrors = arg_parse(argc, argv, (void **)&gpio_write_args);
if (nerrors != 0) {
arg_print_errors(stderr, gpio_write_args.end, argv[0]);
return 1;
}
// Access the parsed integer values via ->ival[0]
int pin = gpio_write_args.pin->ival[0];
int val = gpio_write_args.value->ival[0];
if (val != 0 && val != 1) {
Serial.println("gpio write: value must be 0 or 1");
return 1;
}
digitalWrite(pin, val);
Serial.printf("GPIO %d set to %d (%s)\n", pin, val, val ? "HIGH" : "LOW");
return 0;
}
// ---------------------------------------------------------------------------
// gpio mode <pin> <mode>
// ---------------------------------------------------------------------------
static struct {
struct arg_int *pin; // required int: GPIO pin number
struct arg_str *mode; // required string: mode name (in/out/in_pu/in_pd)
struct arg_end *end; // sentinel: tracks parse errors
} gpio_mode_args;
static int cmd_gpio_mode(int argc, char **argv) {
int nerrors = arg_parse(argc, argv, (void **)&gpio_mode_args);
if (nerrors != 0) {
arg_print_errors(stderr, gpio_mode_args.end, argv[0]);
return 1;
}
// Access the parsed integer value via ->ival[0]
int pin = gpio_mode_args.pin->ival[0];
// Access the parsed string value via ->sval[0]
const char *modeStr = gpio_mode_args.mode->sval[0];
uint8_t mode;
if (strcmp(modeStr, "in") == 0) {
mode = INPUT;
} else if (strcmp(modeStr, "out") == 0) {
mode = OUTPUT;
} else if (strcmp(modeStr, "in_pu") == 0) {
mode = INPUT_PULLUP;
} else if (strcmp(modeStr, "in_pd") == 0) {
mode = INPUT_PULLDOWN;
} else {
Serial.printf("gpio mode: unknown mode '%s'. Use: in, out, in_pu, in_pd\n", modeStr);
return 1;
}
pinMode(pin, mode);
Serial.printf("GPIO %d mode set to %s\n", pin, modeStr);
return 0;
}
// ---------------------------------------------------------------------------
// Top-level dispatcher: gpio <subcmd> ...
// ---------------------------------------------------------------------------
static int cmd_gpio(int argc, char **argv) {
if (argc < 2) {
Serial.println("Usage: gpio <read|write|mode> ...");
Serial.println(" gpio read <pin>");
Serial.println(" gpio write <pin> <0|1>");
Serial.println(" gpio mode <pin> <in|out|in_pu|in_pd>");
return 1;
}
// Shift argv so the sub-command is argv[0]
if (strcmp(argv[1], "read") == 0) {
return cmd_gpio_read(argc - 1, argv + 1);
} else if (strcmp(argv[1], "write") == 0) {
return cmd_gpio_write(argc - 1, argv + 1);
} else if (strcmp(argv[1], "mode") == 0) {
return cmd_gpio_mode(argc - 1, argv + 1);
} else {
Serial.printf("gpio: unknown sub-command '%s'\n", argv[1]);
return 1;
}
}
// ---------------------------------------------------------------------------
// led <on|off> [color] — convenience command that reuses the gpio handlers
//
// When RGB_BUILTIN is defined (boards with an addressable RGB LED), an
// optional color name is accepted: red, green, blue, white, yellow.
//
// This command will reuse the gpio commands if the board does not have an RGB LED.
// Otherwise, it will use the rgbLedWrite function to set the color of the LED.
// ---------------------------------------------------------------------------
#ifdef LED_BUILTIN
#ifdef RGB_BUILTIN
struct NamedColor {
const char *name;
uint8_t r, g, b;
};
static const NamedColor colors[] = {
{"red", RGB_BRIGHTNESS, 0, 0},
{"green", 0, RGB_BRIGHTNESS, 0},
{"blue", 0, 0, RGB_BRIGHTNESS},
{"white", RGB_BRIGHTNESS, RGB_BRIGHTNESS, RGB_BRIGHTNESS},
{"yellow", RGB_BRIGHTNESS, RGB_BRIGHTNESS, 0},
};
static const size_t numColors = sizeof(colors) / sizeof(colors[0]);
#else
static bool ledInitialized = false;
#endif
static void printLedUsage() {
#ifdef RGB_BUILTIN
Serial.println("Usage: led <on [color]|off>");
Serial.println(" Colors: red, green, blue, white, yellow");
#else
Serial.println("Usage: led <on|off>");
#endif
}
static int cmd_led(int argc, char **argv) {
if (argc < 2) {
printLedUsage();
return 1;
}
#ifdef RGB_BUILTIN
// Read argv values BEFORE calling Console.run() — nested run() calls
// invalidate the caller's argv pointers (shared IDF parse buffer).
if (strcmp(argv[1], "on") == 0) {
// If the board has an RGB LED, and a color is specified, set the LED to the specified color.
// If no color is specified, set the LED to white.
// This will use the rgbLedWrite function to set the color of the LED.
const char *colorName = (argc > 2) ? argv[2] : "white";
for (size_t i = 0; i < numColors; i++) {
if (strcasecmp(colorName, colors[i].name) == 0) {
rgbLedWrite(RGB_BUILTIN, colors[i].r, colors[i].g, colors[i].b);
Serial.printf("RGB LED on: %s (%d, %d, %d)\n", colors[i].name, colors[i].r, colors[i].g, colors[i].b);
return 0;
}
}
Serial.printf("Unknown color '%s'. Available: ", colorName);
for (size_t i = 0; i < numColors; i++) {
Serial.printf("%s%s", colors[i].name, (i < numColors - 1) ? ", " : "\n");
}
return 1;
} else if (strcmp(argv[1], "off") == 0) {
rgbLedWrite(RGB_BUILTIN, 0, 0, 0);
Serial.println("RGB LED off");
return 0;
}
#else
// Read argv values BEFORE calling Console.run() — nested run() calls
// invalidate the caller's argv pointers (shared IDF parse buffer).
if (strcmp(argv[1], "on") == 0) {
// If the board does not have an RGB LED, use the gpio commands to toggle the LED.
// This will call the gpio commands to configure the pin as an output and set the LED to HIGH.
if (!ledInitialized) {
Console.run(String("gpio mode ") + LED_BUILTIN + " out");
ledInitialized = true;
}
return Console.run(String("gpio write ") + LED_BUILTIN + " 1");
} else if (strcmp(argv[1], "off") == 0) {
if (!ledInitialized) {
Console.run(String("gpio mode ") + LED_BUILTIN + " out");
ledInitialized = true;
}
return Console.run(String("gpio write ") + LED_BUILTIN + " 0");
}
#endif
printLedUsage();
return 1;
}
#endif
// ---------------------------------------------------------------------------
// setup / loop
// ---------------------------------------------------------------------------
void setup() {
Serial.begin(115200);
while (!Serial) {
delay(10);
}
// Initialize argtable argument structures for each GPIO sub-command.
// These structures help parse and validate command line arguments for the Console commands.
//
// arg_int1(shortopts, longopts, datatype, glossary)
// - Adds a required integer argument.
// arg_str1(...)
// - Adds a required string argument.
// arg_end(N)
// - Terminates the argument table and provides space for up to N error messages.
//
// Passing NULL for both shortopts and longopts makes arguments positional (not using flags like -x or --foo).
// The glossary string is a description that appears in the help message.
// Arguments for: gpio read <pin>
gpio_read_args.pin = arg_int1(NULL, NULL, "<pin>", "GPIO pin number to read (required)");
gpio_read_args.end = arg_end(1); // Up to 1 error message for reading.
// Arguments for: gpio write <pin> <0|1>
gpio_write_args.pin = arg_int1(NULL, NULL, "<pin>", "GPIO pin number to write (required)");
gpio_write_args.value = arg_int1(NULL, NULL, "<0|1>", "Value to write: 0 = LOW, 1 = HIGH (required)");
gpio_write_args.end = arg_end(2); // Up to 2 error messages for writing.
// Arguments for: gpio mode <pin> <in|out|in_pu|in_pd>
gpio_mode_args.pin = arg_int1(NULL, NULL, "<pin>", "GPIO pin number to configure (required)");
gpio_mode_args.mode = arg_str1(NULL, NULL, "<in|out|in_pu|in_pd>", "Pin mode: in, out, in_pu, or in_pd (required)");
gpio_mode_args.end = arg_end(2); // Up to 2 error messages for mode.
// Configure Console prompt
Console.setPrompt("gpio> ");
// Initialize Console
if (!Console.begin()) {
Serial.println("Console init failed");
return;
}
// Add commands
Console.addCmd("gpio", "Control GPIO pins", "<read|write|mode> ...", cmd_gpio);
#ifdef LED_BUILTIN
#ifdef RGB_BUILTIN
Console.addCmd("led", "Control RGB LED", "<on [color]|off>", cmd_led);
#else
Console.addCmd("led", "Toggle LED_BUILTIN", "<on|off>", cmd_led);
#endif
#endif
// Add built-in help command
Console.addHelpCmd();
// Begin Read-Eval-Print Loop
Console.attachToSerial(true);
}
void loop() {
// Loop task is not used in this example, so we delete it to free up resources
vTaskDelete(NULL);
}
ConsoleAdvanced
Demonstrates advanced APIs: context-aware commands (addCmdWithContext),
dynamic command registration and removal (removeCmd), REPL pause/resume
(attachToSerial(false/true)), help verbosity (setHelpVerboseLevel),
clearScreen, setPlainMode, setMultiLine, and task tuning
(setTaskStackSize, setTaskPriority, setTaskCore).
/*
* ConsoleAdvanced — Demonstrates advanced Console API features.
*
* Covers APIs not shown in the basic examples:
*
* - addCmdWithContext() — pass state to a command via a context pointer
* - removeCmd() — unregister a command at runtime
* - attachToSerial(false/true) — pause and resume the REPL
* - setHelpVerboseLevel() — toggle brief / verbose help output
* - clearScreen() — clear the terminal
* - setPlainMode() — force plain (non-VT100) input
* - setMultiLine() — enable multi-line editing
* - Task tuning: setTaskStackSize(), setTaskPriority(), setTaskCore()
*
* Created by lucasssvaz
*/
#include <Arduino.h>
#include <Console.h>
// ---------------------------------------------------------------------------
// Context-aware command: addCmdWithContext() demo
//
// A context pointer lets multiple commands share or isolate state without
// global variables. The pointer is passed as the first argument to the
// callback every time the command is invoked.
// ---------------------------------------------------------------------------
struct LedCtx {
int pin;
bool state;
const char *name;
};
static LedCtx led1 = {2, false, "LED1"};
static LedCtx led2 = {15, false, "LED2"};
static int cmd_toggle(void *context, int argc, char **argv) {
LedCtx *led = (LedCtx *)context;
led->state = !led->state;
pinMode(led->pin, OUTPUT);
digitalWrite(led->pin, led->state ? HIGH : LOW);
printf("%s (GPIO %d) = %s\n", led->name, led->pin, led->state ? "ON" : "OFF");
return 0;
}
// ---------------------------------------------------------------------------
// removeCmd demo: register/unregister a "secret" command at runtime
// ---------------------------------------------------------------------------
static int cmd_secret(int argc, char **argv) {
printf("You found the secret command!\n");
return 0;
}
static bool secretRegistered = false;
static int cmd_lock(int argc, char **argv) {
if (secretRegistered) {
Console.removeCmd("secret");
secretRegistered = false;
printf("'secret' command removed.\n");
} else {
printf("'secret' is not registered.\n");
}
return 0;
}
static int cmd_unlock(int argc, char **argv) {
if (!secretRegistered) {
Console.addCmd("secret", "A hidden command", cmd_secret);
secretRegistered = true;
printf("'secret' command registered. Try it!\n");
} else {
printf("'secret' is already available.\n");
}
return 0;
}
// ---------------------------------------------------------------------------
// attachToSerial(false/true) demo: pause the Read-Eval-Print Loop for 5 seconds
// ---------------------------------------------------------------------------
static int cmd_pause(int argc, char **argv) {
printf("Stopping Read-Eval-Print Loop for 5 seconds...\n");
fflush(stdout);
Console.attachToSerial(false);
delay(5000);
Console.attachToSerial(true);
return 0;
}
// ---------------------------------------------------------------------------
// setHelpVerboseLevel demo
// ---------------------------------------------------------------------------
static int cmd_verbose_help(int argc, char **argv) {
if (argc < 2) {
printf("Usage: verbose_help <0|1>\n");
printf(" 0 = brief (names and hints only)\n");
printf(" 1 = verbose (includes help text)\n");
return 1;
}
int level = atoi(argv[1]);
if (Console.setHelpVerboseLevel(level)) {
printf("Help verbose level set to %d.\n", level);
} else {
printf("Invalid level. Use 0 or 1.\n");
}
return 0;
}
// ---------------------------------------------------------------------------
// clearScreen demo
// ---------------------------------------------------------------------------
static int cmd_clear(int argc, char **argv) {
Console.clearScreen();
return 0;
}
// ---------------------------------------------------------------------------
// setPlainMode / setMultiLine toggles
// ---------------------------------------------------------------------------
static int cmd_plain(int argc, char **argv) {
if (argc < 2) {
printf("Usage: plain <on|off>\n");
return 1;
}
bool enable = (strcmp(argv[1], "on") == 0);
Console.setPlainMode(enable);
printf("Plain mode %s.\n", enable ? "enabled" : "disabled");
return 0;
}
static int cmd_multiline(int argc, char **argv) {
if (argc < 2) {
printf("Usage: multiline <on|off>\n");
return 1;
}
bool enable = (strcmp(argv[1], "on") == 0);
Console.setMultiLine(enable);
printf("Multi-line mode %s.\n", enable ? "enabled" : "disabled");
return 0;
}
// ---------------------------------------------------------------------------
// setup / loop
// ---------------------------------------------------------------------------
void setup() {
Serial.begin(115200);
while (!Serial) {
delay(10);
}
// Console task configuration — must be called before attachToSerial(true)
// Set the task stack size to 4096 bytes
Console.setTaskStackSize(4096);
// Set the task priority to 2
Console.setTaskPriority(2);
// Set the task core to 1 (core 1)
Console.setTaskCore(1); // pin REPL to core 1
// Configure Console prompt
Console.setPrompt("adv> ");
// Initialize Console
if (!Console.begin()) {
Serial.println("Console init failed");
return;
}
// Context-aware commands: same callback, different context pointer.
// Each LED struct carries its own pin number, state, and label.
Console.addCmdWithContext("led1", "Toggle LED1", cmd_toggle, &led1);
Console.addCmdWithContext("led2", "Toggle LED2", cmd_toggle, &led2);
// Dynamic command management
Console.addCmd("unlock", "Register the 'secret' command", cmd_unlock);
Console.addCmd("lock", "Unregister the 'secret' command", cmd_lock);
// REPL control
Console.addCmd("pause", "Stop the Read-Eval-Print Loop for 5 seconds", cmd_pause);
// Help verbosity
Console.addCmd("verbose_help", "Set help verbosity", "<0|1>", cmd_verbose_help);
// Terminal utilities
Console.addCmd("clear", "Clear the terminal screen", cmd_clear);
Console.addCmd("plain", "Toggle plain (non-VT100) mode", "<on|off>", cmd_plain);
Console.addCmd("multiline", "Toggle multi-line editing", "<on|off>", cmd_multiline);
// Add built-in help command
Console.addHelpCmd();
// Begin Read-Eval-Print Loop
if (!Console.attachToSerial(true)) {
Serial.println("Failed to attach to serial");
}
}
void loop() {
// Loop task is not used in this example, so we delete it to free up resources
vTaskDelete(NULL);
}
ConsoleHistory
Persistent command history across reboots using LittleFS (setHistoryFile),
PSRAM allocation (usePsram), and the splitArgv tokenising utility.
/*
* ConsoleHistory — Persistent history, PSRAM allocation, and splitArgv demo.
*
* Demonstrates APIs not covered by the basic examples:
*
* - setHistoryFile() — save/load history to a filesystem across reboots
* - setMaxHistory() — limit number of stored history lines
* - usePsram() — allocate Console heap and REPL task stack in PSRAM
* - splitArgv() — split a raw string into argv-style tokens
*
* On boot the REPL loads the previous session's history. Use the up/down
* arrow keys (VT100 terminals) or the "history" command to inspect entries.
* Power-cycle the board to verify history persists.
*
* Created by lucasssvaz
*/
#include <Arduino.h>
#include <Console.h>
#include <LittleFS.h>
static const char *HISTORY_PATH = "/console_history.txt";
// ---------------------------------------------------------------------------
// Command: history — print the current history buffer
// ---------------------------------------------------------------------------
static int cmd_history(int argc, char **argv) {
File f = LittleFS.open(HISTORY_PATH, "r");
if (!f) {
printf("No history saved yet.\n");
return 0;
}
int index = 1;
while (f.available()) {
String line = f.readStringUntil('\n');
line.trim();
if (line.length() > 0) {
printf(" %3d %s\n", index++, line.c_str());
}
}
f.close();
if (index == 1) {
printf("History is empty.\n");
}
return 0;
}
// ---------------------------------------------------------------------------
// Command: tokenize — demonstrate Console.splitArgv()
//
// splitArgv() splits a raw string into argc/argv tokens, handling quotes
// and backslash escapes. Useful when you receive a full command line from
// a non-REPL source (MQTT, BLE, HTTP, etc.) and need to parse it.
// ---------------------------------------------------------------------------
static int cmd_tokenize(int argc, char **argv) {
if (argc < 2) {
printf("Usage: tokenize <string>\n");
printf(" Splits the string into tokens. Use quotes for multi-word tokens.\n");
printf(" Example: tokenize hello \"big world\" foo\n");
return 1;
}
// Reconstruct the argument string from argv[1..] so the user can type
// something like: tokenize hello "big world" foo
String raw;
for (int i = 1; i < argc; i++) {
if (i > 1) {
raw += ' ';
}
raw += argv[i];
}
// splitArgv() works in-place — it modifies the buffer and sets pointers
// into it. We need a mutable copy.
char *buf = strdup(raw.c_str());
if (!buf) {
printf("Out of memory\n");
return 1;
}
char *tokens[16];
size_t count = Console.splitArgv(buf, tokens, 16);
printf("Input : \"%s\"\n", raw.c_str());
printf("Tokens: %zu\n", count);
for (size_t i = 0; i < count; i++) {
printf(" [%zu] \"%s\"\n", i, tokens[i]);
}
free(buf);
return 0;
}
// ---------------------------------------------------------------------------
// Command: heap — show free memory
// ---------------------------------------------------------------------------
static int cmd_heap(int argc, char **argv) {
printf("Free heap : %lu bytes\n", (unsigned long)ESP.getFreeHeap());
if (psramFound()) {
printf("Free PSRAM: %lu bytes\n", (unsigned long)ESP.getFreePsram());
}
return 0;
}
// ---------------------------------------------------------------------------
// setup / loop
// ---------------------------------------------------------------------------
void setup() {
Serial.begin(115200);
while (!Serial) {
delay(10);
}
// Mount LittleFS (format on first use)
if (!LittleFS.begin(true)) {
Serial.println("LittleFS mount failed — history will not persist.");
} else {
Serial.println("LittleFS mounted");
// Persistent history: lines are stored in a plain text file on LittleFS.
// On begin(), existing history is loaded; after every command, the file
// is updated automatically.
Console.setHistoryFile(LittleFS, HISTORY_PATH);
}
Console.setMaxHistory(50);
// If PSRAM is available, move Console allocations there to save internal
// SRAM. Falls back to internal RAM if PSRAM is not available.
Console.usePsram(true);
// Configure Console prompt
Console.setPrompt("hist> ");
// Initialize Console
if (!Console.begin()) {
Serial.println("Console init failed");
return;
}
// Add commands
Console.addCmd("history", "Show history info", cmd_history);
Console.addCmd("tokenize", "Split a string into tokens", "<string>", cmd_tokenize);
Console.addCmd("heap", "Show free memory", cmd_heap);
// Add built-in help command
Console.addHelpCmd();
if (!Console.attachToSerial(true)) {
Serial.println("REPL start failed");
}
}
void loop() {
// Loop task is not used in this example, so we delete it to free up resources
vTaskDelete(NULL);
}
ConsoleWiFi
Wi-Fi management: wifi scan, wifi connect, wifi disconnect, wifi status,
wifi ping.
/*
* ConsoleWiFi — Wi-Fi management CLI commands.
*
* Implements a set of commands for scanning, connecting, and monitoring Wi-Fi:
*
* wifi scan — scan and list nearby access points
* wifi connect <ssid> [<pass>] — connect to a network
* wifi disconnect — disconnect from the current network
* wifi status — show connection status and IP info
* ping <host> [-c <count>] — ICMP ping a host by name or IP
*
* Demonstrates argtable3 with optional arguments (password is optional)
* and optional flags (-c for ping count).
*
* Created by lucasssvaz
*/
#include <Arduino.h>
#include <Console.h>
#include <WiFi.h>
#include "ping/ping_sock.h"
#include "argtable3/argtable3.h"
// ---------------------------------------------------------------------------
// wifi scan
// ---------------------------------------------------------------------------
static int cmd_wifi_scan(int argc, char **argv) {
printf("Scanning...\n");
int n = WiFi.scanNetworks();
if (n == 0) {
printf("No networks found.\n");
return 0;
}
printf("%-4s %-32s %5s %4s %s\n", "#", "SSID", "RSSI", "Ch", "Encryption");
printf("%-4s %-32s %5s %4s %s\n", "---", "--------------------------------", "-----", "----", "----------");
for (int i = 0; i < n; i++) {
const char *enc;
switch (WiFi.encryptionType(i)) {
case WIFI_AUTH_OPEN: enc = "Open"; break;
case WIFI_AUTH_WEP: enc = "WEP"; break;
case WIFI_AUTH_WPA_PSK: enc = "WPA"; break;
case WIFI_AUTH_WPA2_PSK: enc = "WPA2"; break;
case WIFI_AUTH_WPA_WPA2_PSK: enc = "WPA/WPA2"; break;
case WIFI_AUTH_WPA3_PSK: enc = "WPA3"; break;
default: enc = "Unknown"; break;
}
printf("%-4d %-32s %5d %4d %s\n", i + 1, WiFi.SSID(i).c_str(), (int)WiFi.RSSI(i), (int)WiFi.channel(i), enc);
}
WiFi.scanDelete();
return 0;
}
// ---------------------------------------------------------------------------
// wifi connect <ssid> [<password>]
// ---------------------------------------------------------------------------
static struct {
struct arg_str *ssid; // required positional string: network name
struct arg_str *password; // optional positional string: network password
struct arg_end *end; // sentinel: tracks parse errors
} wifi_connect_args;
static int cmd_wifi_connect(int argc, char **argv) {
int nerrors = arg_parse(argc, argv, (void **)&wifi_connect_args);
if (nerrors != 0) {
arg_print_errors(stdout, wifi_connect_args.end, argv[0]);
return 1;
}
const char *ssid = wifi_connect_args.ssid->sval[0]; // access parsed string via ->sval[0]
// For optional args, check ->count to see if it was provided
const char *pass = (wifi_connect_args.password->count > 0) ? wifi_connect_args.password->sval[0] : NULL;
printf("Connecting to '%s'...\n", ssid);
WiFi.STA.connect(ssid, pass);
uint32_t deadline = millis() + 15000;
while (!WiFi.STA.connected() && millis() < deadline) {
delay(500);
printf(".");
fflush(stdout);
}
printf("\n");
if (WiFi.STA.connected()) {
printf("Connected. IP: %s\n", WiFi.STA.localIP().toString().c_str());
return 0;
} else {
printf("Connection failed (status %d)\n", (int)WiFi.STA.status());
return 1;
}
}
// ---------------------------------------------------------------------------
// wifi disconnect
// ---------------------------------------------------------------------------
static int cmd_wifi_disconnect(int argc, char **argv) {
WiFi.STA.disconnect();
printf("Disconnected.\n");
return 0;
}
// ---------------------------------------------------------------------------
// wifi status
// ---------------------------------------------------------------------------
static int cmd_wifi_status(int argc, char **argv) {
wl_status_t s = WiFi.STA.status();
const char *statusStr;
switch (s) {
case WL_CONNECTED: statusStr = "Connected"; break;
case WL_NO_SSID_AVAIL: statusStr = "SSID not found"; break;
case WL_CONNECT_FAILED: statusStr = "Connect failed"; break;
case WL_DISCONNECTED: statusStr = "Disconnected"; break;
case WL_IDLE_STATUS: statusStr = "Idle"; break;
default: statusStr = "Unknown"; break;
}
printf("Status : %s\n", statusStr);
if (s == WL_CONNECTED) {
printf("SSID : %s\n", WiFi.STA.SSID().c_str());
printf("BSSID : %s\n", WiFi.STA.BSSIDstr().c_str());
printf("RSSI : %d dBm\n", (int)WiFi.STA.RSSI());
printf("IP : %s\n", WiFi.STA.localIP().toString().c_str());
printf("Gateway : %s\n", WiFi.STA.gatewayIP().toString().c_str());
printf("DNS : %s\n", WiFi.STA.dnsIP().toString().c_str());
printf("Subnet : %s\n", WiFi.STA.subnetMask().toString().c_str());
}
printf("MAC : %s\n", WiFi.STA.macAddress().c_str());
return 0;
}
// ---------------------------------------------------------------------------
// Top-level dispatcher: wifi <subcmd> ...
// ---------------------------------------------------------------------------
static int cmd_wifi(int argc, char **argv) {
if (argc < 2) {
printf("Usage: wifi <scan|connect|disconnect|status>\n");
return 1;
}
if (strcmp(argv[1], "scan") == 0) {
return cmd_wifi_scan(argc - 1, argv + 1);
} else if (strcmp(argv[1], "connect") == 0) {
return cmd_wifi_connect(argc - 1, argv + 1);
} else if (strcmp(argv[1], "disconnect") == 0) {
return cmd_wifi_disconnect(argc - 1, argv + 1);
} else if (strcmp(argv[1], "status") == 0) {
return cmd_wifi_status(argc - 1, argv + 1);
} else {
printf("wifi: unknown sub-command '%s'\n", argv[1]);
return 1;
}
}
// ---------------------------------------------------------------------------
// ping <host> [-c <count>]
// ---------------------------------------------------------------------------
static struct {
struct arg_str *host; // required positional: hostname or IP address
struct arg_int *count; // optional: -c <count> (default 5)
struct arg_end *end;
} ping_args;
static SemaphoreHandle_t ping_done_sem = NULL;
static void ping_on_success(esp_ping_handle_t hdl, void *args) {
uint32_t elapsed;
uint32_t size;
uint32_t seqno;
uint8_t ttl;
ip_addr_t addr;
esp_ping_get_profile(hdl, ESP_PING_PROF_SEQNO, &seqno, sizeof(seqno));
esp_ping_get_profile(hdl, ESP_PING_PROF_TTL, &ttl, sizeof(ttl));
esp_ping_get_profile(hdl, ESP_PING_PROF_SIZE, &size, sizeof(size));
esp_ping_get_profile(hdl, ESP_PING_PROF_TIMEGAP, &elapsed, sizeof(elapsed));
esp_ping_get_profile(hdl, ESP_PING_PROF_IPADDR, &addr, sizeof(addr));
printf(
"%lu bytes from %s: icmp_seq=%lu ttl=%d time=%lu ms\n", (unsigned long)size, ipaddr_ntoa(&addr), (unsigned long)seqno, (int)ttl, (unsigned long)elapsed
);
}
static void ping_on_timeout(esp_ping_handle_t hdl, void *args) {
uint32_t seqno;
ip_addr_t addr;
esp_ping_get_profile(hdl, ESP_PING_PROF_SEQNO, &seqno, sizeof(seqno));
esp_ping_get_profile(hdl, ESP_PING_PROF_IPADDR, &addr, sizeof(addr));
printf("From %s icmp_seq=%lu timeout\n", ipaddr_ntoa(&addr), (unsigned long)seqno);
}
static void ping_on_end(esp_ping_handle_t hdl, void *args) {
uint32_t transmitted;
uint32_t received;
uint32_t total_ms;
esp_ping_get_profile(hdl, ESP_PING_PROF_REQUEST, &transmitted, sizeof(transmitted));
esp_ping_get_profile(hdl, ESP_PING_PROF_REPLY, &received, sizeof(received));
esp_ping_get_profile(hdl, ESP_PING_PROF_DURATION, &total_ms, sizeof(total_ms));
uint32_t loss = transmitted ? ((transmitted - received) * 100 / transmitted) : 0;
printf("\n--- ping statistics ---\n");
printf(
"%lu packets transmitted, %lu received, %lu%% packet loss, time %lu ms\n", (unsigned long)transmitted, (unsigned long)received, (unsigned long)loss,
(unsigned long)total_ms
);
xSemaphoreGive(ping_done_sem);
}
static int cmd_ping(int argc, char **argv) {
int nerrors = arg_parse(argc, argv, (void **)&ping_args);
if (nerrors != 0) {
arg_print_errors(stdout, ping_args.end, argv[0]);
return 1;
}
if (!WiFi.STA.connected()) {
printf("Not connected to Wi-Fi.\n");
return 1;
}
const char *host = ping_args.host->sval[0];
int count = (ping_args.count->count > 0) ? ping_args.count->ival[0] : 5;
if (count < 1) {
count = 1;
}
IPAddress addr;
if (!Network.hostByName(host, addr)) {
printf("ping: unknown host '%s'\n", host);
return 1;
}
ip_addr_t target_addr;
target_addr.type = IPADDR_TYPE_V4;
target_addr.u_addr.ip4.addr = (uint32_t)addr;
printf("PING %s (%s): 64 bytes of data, count=%d\n", host, addr.toString().c_str(), count);
fflush(stdout);
esp_ping_config_t ping_config = ESP_PING_DEFAULT_CONFIG();
ping_config.target_addr = target_addr;
ping_config.count = count;
esp_ping_callbacks_t cbs = {};
cbs.on_ping_success = ping_on_success;
cbs.on_ping_timeout = ping_on_timeout;
cbs.on_ping_end = ping_on_end;
esp_ping_handle_t ping_handle = NULL;
if (esp_ping_new_session(&ping_config, &cbs, &ping_handle) != ESP_OK) {
printf("Failed to create ping session\n");
return 1;
}
esp_ping_start(ping_handle);
xSemaphoreTake(ping_done_sem, portMAX_DELAY);
esp_ping_delete_session(ping_handle);
return 0;
}
// ---------------------------------------------------------------------------
// setup / loop
// ---------------------------------------------------------------------------
void setup() {
Serial.begin(115200);
while (!Serial) {
delay(10);
}
// Initialize WiFi
WiFi.STA.begin();
// Create semaphore for ping done
ping_done_sem = xSemaphoreCreateBinary();
// Initialize argtable.
// arg_str1() = required string. arg_str0() = optional string.
// NULL, NULL for shortopts/longopts makes the argument positional.
wifi_connect_args.ssid = arg_str1(NULL, NULL, "<ssid>", "Network SSID");
wifi_connect_args.password = arg_str0(NULL, NULL, "[<password>]", "Network password (optional for open networks)");
wifi_connect_args.end = arg_end(2); // sentinel — stores up to 2 parse errors
// arg_int0() = optional integer. "c" is the short option (-c), "count"
// is the long option (--count). Default ping count is 5 if not given.
ping_args.host = arg_str1(NULL, NULL, "<host>", "Hostname or IP address to ping");
ping_args.count = arg_int0("c", "count", "<n>", "Number of pings (default 5)");
ping_args.end = arg_end(2);
// Configure Console prompt
Console.setPrompt("wifi> ");
// Initialize Console
if (!Console.begin()) {
Serial.println("Console init failed");
return;
}
// Add commands
Console.addCmd("wifi", "Manage Wi-Fi connections", "<scan|connect|disconnect|status>", cmd_wifi);
Console.addCmd("ping", "ICMP ping a host", (void *)&ping_args, cmd_ping);
// Add built-in help command
Console.addHelpCmd();
// Begin Read-Eval-Print Loop
Console.attachToSerial(true);
}
void loop() {
// Loop task is not used in this example, so we delete it to free up resources
vTaskDelete(NULL);
}