This file is intended to provide infos on the public API of libCli which is intended to be used by 'users'. This file will not provide detailed information on the internal workings of the library, as this is done in the source code by comments where necessary.
Description: Macro to define and automatically register a command.
Usage:
CLI_COMMAND(commandname) {
// Command implementation. Note: argv[0] is the first argument, if any,
// NOT the command name itself - see Command Function Signature below.
if (argc > 0) {
ioStream.printf("First argument: %s\n", argv[0]);
}
return 0;
}Details:
- Creates a function named
cmd_<name>with the proper signature - Automatically registers the command in the global command table
- No manual table management needed
- Registration happens at program startup via static constructors
Example:
CLI_COMMAND(status) {
ioStream.printf("System status: OK\n");
return 0;
}
CLI_COMMAND(echo) {
for (uint8_t i = 0; i < argc; i++) {
ioStream.printf("%s ", argv[i]);
}
ioStream.println();
return 0;
}How does auto-registration work?
The CLI_COMMAND macro uses constructor-based registration via static C++ objects initialized before main(). This approach is preprocessor-aware and works across all Arduino platforms without requiring linker tricks or build scripts.
For a detailed explanation of the registration mechanism, why this design was chosen over alternatives (Python scripts, linker sections), and how it handles conditional compilation, see Command Registration.
Description: Macro to only define the command signature without registration. Useful for forward declarations.
This macro can be used when you need to or want to declare a command fucntion which is implemented somewhere else but you want to execute it directly with minimal overhead.
However, the CliCommand::exec() method can be used to execute commands by name at runtime without the need for forward declarations, as it looks up the command in the command table. So in most cases, you don't need to use CLI_COMMAND_DEF unless you want to call the command function directly by its name (e.g., cmd_) instead of using CliCommand::exec(). See CliCommand Class documentation for more details on how to execute commands programmatically.
Usage:
// Forward declaration
CLI_COMMAND_DEF(mycommand);
// Example for manual command execution with forward declaration
cmd_mycommand(ioStream, nullptr, 0);
// Example for manual command execution without the need of forward declarations.
// In this case you don't need forward decalartion because it command is found by name at runtime.
CliCommand::exec(ioStream, "mycommand", nullptr, 0);
Main class for managing the command-line interface.
Cli();Creates a new Cli instance.
void begin(Stream *pIoStr = &Serial, bool sortCmdTab = CLI_CMDTAB_SORTING_DEFAULT);Initializes the CLI with the specified I/O stream.
Parameters:
pIoStr- Pointer to the Stream object to use (default: &Serial)sortCmdTab- Whether to sort the command table (default: CLI_CMDTAB_SORTING_DEFAULT)
Please note that the sortCmdTab parameter is optional and its default value depends on if command completion is enabled or not. CLI_CMDTAB_SORTING_DEFAULT is set to true if CLI_TAB_COMPLETION is enabled and to false if CLI_TAB_COMPLETION is disabled. This is because the command completion code relies on the command table being sorted to display commands in alphabetical order. It does not sort matches every time to save processing time. So if you enable command completion and don't provide a value for sortCmdTab, the command table will be sorted automatically. If you provide a value for sortCmdTab, it will be used regardless of the state of CLI_TAB_COMPLETION.
Example:
Cli cli;
void setup() {
Serial.begin(115200);
cli.begin(); // Uses Serial by default
}int8_t loop(void);Main processing function to call in your main loop. Checks for incoming data and processes commands.
Returns:
0- No command was recognizedINT8_MIN- Parsing error occurred- Command return code - The value returned by the executed command
Example:
void loop() {
int8_t result = cli.loop();
if (result < 0) {
// Handle error
}
}int8_t read(char byte);Process a single incoming byte. Useful for custom input handling. Usually you don't need this method as loop() handles reading from the stream by calling read() internally, but it's available for corner cases if needed.
Parameters:
byte- Character to process
Returns: Same as loop()
Example:
if (customStream.available()) {
char c = customStream.read();
cli.read(c);
}void setStream(Stream *pIoStr);Change the I/O stream during runtime.
Important: Changing the stream implicitly resets the CLI state (input buffer, history navigation, tab completion state) to avoid inconsistencies.
Parameters:
pIoStr- Pointer to the new Stream object
Example:
// Switch from Serial to telnet stream
cli.setStream(&telnetStream);void setEcho(bool state);Enable or disable per-character echo of user input.
When disabled, characters typed by the user (including backspace and tab-completion) are no longer echoed back automatically. This does not suppress everything the library writes: prompts, the bell signal, error messages and the tab-completion match list are always written regardless of this setting, since they originate from the library itself rather than being an echo of user input. Do not rely on this for password input - pressing arrow-up (history) or Ctrl+L (clear screen) still triggers refreshPrompt(), which unconditionally writes the current buffer content.
Parameters:
state-trueto enable character echo,falseto disable
Use Cases:
- A host application driving the CLI programmatically that handles its own echo
Example:
cli.setEcho(false); // Disable character echo
// Process commands
cli.setEcho(true); // Re-enablevoid sendBell(void);Send a VT100 bell signal to the terminal (audible beep or visual flash).
Example:
CLI_COMMAND(alert) {
cli.sendBell();
ioStream.println("Alert!");
return 0;
}void refreshPrompt(void);Redraw the prompt and current buffer content in the terminal. Useful after clearing the screen or for manual screen updates.
Example:
CLI_COMMAND(clear) {
ioStream.print("\033[2J\033[H"); // Clear screen
cli.refreshPrompt();
return 0;
}void clearLine(void);Clear the current line and move cursor to the first column using VT100 sequences.
Example:
cli.clearLine();
cli.refreshPrompt(); // Show fresh promptvoid clearScreen(void);Clear the entire screen and move the cursor to the first row and column using VT100 sequences.
Example:
CLI_COMMAND(clear) {
cli.clearScreen();
cli.refreshPrompt();
return 0;
}void saveCursor(void);Save the current terminal cursor position using VT100 escape sequences (DECSC). The position can be restored later with restoreCursor().
Be aware that not all terminals support this feature at all, and behavior may be inconsistent if restoreCursor() is called without a prior saveCursor().
Use Cases:
- Multi-line status displays
Example:
cli.saveCursor();
ioStream.println("\nTemporary status info");
cli.restoreCursor(); // Back to original positionvoid restoreCursor(void);Restore the terminal cursor to the position previously saved by saveCursor() using VT100 escape sequences (DECRC).
Be aware that not all terminals support this feature at all, and behavior may be inconsistent if restoreCursor() is called without a prior saveCursor().
Note: Must be called after saveCursor(), otherwise behavior is terminal-dependent.
Example:
cli.saveCursor();
ioStream.println("\nStatus: Processing...");
delay(1000);
cli.restoreCursor();
ioStream.println("\nStatus: Complete! ");void reset(void);Reset the internal CLI state and print a clean prompt. Call this after errors or to start fresh.
Example:
if (error_occurred) {
ioStream.println("Error occurred, resetting CLI");
cli.reset();
}Static class for accessing the command table and executing commands programmatically.
Usually libCLI users do not need to interact with this class directly because its main purpose is to manage and create the internal command table and provide access to it. However, it can be useful for advanced use cases such as dynamic command execution or custom command management.
static void sortTable(void);Sort the command table alphabetically by command name. This is called by Cli::begin() if its sortCmdTab parameter is true. The used sorting algorithm is qsort from the C standard library, which is efficient for small to medium-sized command tables. It has been choosen as it is good compromise between number of iterations and code size and because can be expected to be optimized by the standard library implementation.
Usually users don't need to call this method directly, as sorting is handled automatically by Cli::begin(). However, it can be useful if you resuse the Class for your own command management and want to sort the table after adding or removing commands at runtime.
*Example:
// After dynamically adding commands to the table
CliCommand::sortTable();static cliCmd_t* getTable(void);Get a pointer to the global command table.
Returns: Pointer to the command table array
Example:
cliCmd_t* cmds = CliCommand::getTable();
size_t count = CliCommand::getCmdCnt();
for (size_t i = 0; i < count; i++) {
ioStream.printf("Command: %s\n", cmds[i].name);
}static size_t getCmdCnt(void);Get the number of successfully registered commands.
Returns: Number of registered commands
static size_t getDropCnt(void);Get the number of commands that couldn't be registered (exceeded CLI_COMMANDS_MAX).
Returns: Number of dropped commands
Example:
size_t dropped = CliCommand::getDropCnt();
if (dropped > 0) {
ioStream.printf("WARNING: %d commands were dropped!\n", dropped);
ioStream.printf("Increase CLI_COMMANDS_MAX in configuration\n");
}static CmdFuncPtr getCmd(const char* name);Find a command by name and return its function pointer.
Parameters:
name- Command name to search for
Returns: Function pointer or nullptr if not found
Example:
CmdFuncPtr cmd = CliCommand::getCmd("status");
if (cmd != nullptr) {
// Command exists
}static int8_t exec(Stream& ioStream, const char* name,
const char* argv[], uint8_t argc);Execute a command programmatically by name.
Parameters:
ioStream- Stream object for I/Oname- Command name to executeargv- Argument array; must NOT include the command name, exactly like the array the command receives when invoked interactivelyargc- Number of arguments, excluding the command name
Returns: Command return code or error
Example:
const char* args[] = {"verbose"};
int8_t result = CliCommand::exec(Serial, "status", args, 1);All commands must follow this signature:
int8_t cmd_<name>(Stream& ioStream, const char *argv[], uint8_t argc)Parameters:
Reference to the I/O stream for input/output operations.
Usage:
ioStream.printf("Output: %d\n", value);
ioStream.println("Hello");
ioStream.write(buffer, length);Array of argument strings. argv[0] is the first argument after the
command name - the command name itself is never part of argv. This
differs from the traditional Unix main(argc, argv) convention where
argv[0] is the program name; keep that in mind when porting code.
This is a deliberate deviation, not an oversight: in Unix, a process needs
argv[0] because it has no other reliable way to learn its own name (it may
be invoked via different symlinks, paths, etc). A libCli command already
knows its own name statically - it's the argument to CLI_COMMAND(name) -
so repeating it in argv would only waste one of the limited CLI_ARGVSIZ
slots and force every command to skip argv[0]/argc - 1 for no benefit.
Example:
For input "status system verbose" (command status):
argv[0]="system"argv[1]="verbose"
Number of arguments, excluding the command name.
Example:
For input "status system verbose" (command status): argc = 2
Commands should return appropriate status codes:
| Code | Meaning |
|---|---|
0 |
Success |
!= 0 |
Error condition |
When a command returns a non-zero value, libCli will print an error message:
Error, cmd fails: <error code>
to the terminal, where <error code> is the value returned by the command. This allows users to implement their own error code scheme.
in case of a parsing error (e.g., unterminated string, too many arguments), read() and loop() return INT8_MIN and print a specific error message to the terminal:
Command not found:
#> unknown_cmd
Error: Command not found
Too many arguments:
#> cmd arg1 arg2 arg3 arg4 arg5
Error: Too many arguments
Unterminated string:
#> cmd "unterminated string
Error: Unterminated string
Command too long:
The buffer silently truncates at CLI_COMMANDSIZ-1 and rings the bell.
Example:
CLI_COMMAND(setvalue) {
if (argc < 1) {
ioStream.println("Error: Missing argument");
// Error
return -1;
}
int value = atoi(argv[0]);
if (value < 0 || value > 100) {
ioStream.println("Error: Value out of range");
// Error
return -2;
}
// Success
return 0;
}libCli supports advanced argument parsing:
Use quotes to include spaces in arguments:
command "argument with spaces" normal_arg
Becomes:
argv[0]="argument with spaces"argv[1]="normal_arg"
Use backslash to escape special characters:
Supported escape sequences:
\"- Double quote\\- Backslash
Example:
command "quote: \" backslash: \\" arg
Becomes:
argv[0]=quote: " backslash: \argv[1]=arg
#include <Arduino.h>
#include <cli/cli.hpp>
Cli cli;
CLI_COMMAND(ver) {
ioStream.println("myApp version 1.0");
return 0;
}
CLI_COMMAND(help) {
ioStream.println("Available commands:");
cliCmd_t* cmds = CliCommand::getTable();
size_t count = CliCommand::getCmdCnt();
for (size_t i = 0; i < count; i++) {
ioStream.printf(" %s\n", cmds[i].name);
}
return 0;
}
CLI_COMMAND(echo) {
for (uint8_t i = 0; i < argc; i++) {
if (i > 0) ioStream.print(" ");
ioStream.print(argv[i]);
}
ioStream.println();
return 0;
}
CLI_COMMAND(info) {
ioStream.printf("Registered: %d/%d commands\n",
CliCommand::getCmdCnt(), CLI_COMMANDS_MAX);
size_t dropped = CliCommand::getDropCnt();
if (dropped > 0) {
ioStream.printf("WARNING: %d commands dropped!\n", dropped);
}
return 0;
}
void setup() {
Serial.begin(115200);
while (!Serial);
// Call by name at runtime without forward declaration
CliCommand::exec(Serial, "ver", nullptr, 0);
// Direct call without lookup depending on forward declaration
cmd_info(Serial, nullptr, 0);
cli.begin();
// Check for dropped commands
if (CliCommand::getDropCnt() > 0) {
Serial.println("ERROR: Some commands were not registered!");
}
}
void loop() {
cli.loop();
}