EmbeddedTerminal is a platform-independent C++ library for embedded systems providing a complete terminal/shell implementation with command parsing, file system abstraction, and network interfaces. Works seamlessly across Arduino, ESP-IDF, and native platforms.
The Readme is partially generated using AI but was proven to be inaccurate in some places. Please refer to the source code and documentation for the most accurate information.
- ✅ Platform Independent: Works on Arduino, ESP32 (ESP-IDF & Arduino), and native environments
- ✅ Command System: Register and execute custom commands with keyword-based parsing
- ✅ File System Abstraction: Unified interface for different file systems (SPIFFS, SD, LittleFS, Native)
- ✅ Built-in Commands: cat, cd, ls, mkdir, rm, rmdir, df, tail, help, ip, download
- ✅ Network Interface: Abstract network interface for displaying connection information
- ✅ Cross-Platform String Handling: Custom
ETStringclass works across all platforms - ✅ Directory Navigation: Full path traversal and manipulation
- ✅ Comprehensive Testing: Mock implementations and test suites included
Add to your platformio.ini:
[env]
lib_deps =
boeserfrosch/EmbeddedTerminal@^0.2.1- Open Arduino IDE
- Go to Sketch → Include Library → Manage Libraries
- Search for "EmbeddedTerminal"
- Click Install
- Download or clone this repository
- Copy the folder to your libraries directory:
- Arduino:
Documents/Arduino/libraries/ - PlatformIO: Include in
lib_depsor place inlib/
- Arduino:
Full example: examples/BasicTerminal/BasicTerminal.ino
#include <Terminal.h>
#include <BuiltinCommandFactory.h>
#include <hal/ArduinoFileSystem.h>
ArduinoFileSystem fs;
DirectoryNavigator nav(&fs);
Terminal term(Serial);
BuiltinCommandFactory factory;
void setup() {
Serial.begin(115200);
// Register all built-in commands using factory
factory.registerAllCommands(term, nav, nullptr);
Serial.println("Terminal ready! Type 'help' for available commands.");
Serial.print("> ");
}
void loop() {
term.loop();
}Register only specific command categories using flags:
Full example: examples/SelectiveRegistration/SelectiveRegistration.ino
#include <BuiltinCommandFlags.h>
BuiltinCommandFactory factory;
void setup() {
Serial.begin(115200);
// Register only filesystem navigation commands
factory.registerFilesystemCommands(term, nav, CMD_LS | CMD_CD | CMD_CAT);
// Register disk usage command
factory.registerDiskCommands(term, nav);
// Register network commands (requires INetworkInterface)
// factory.registerNetworkCommands(term, networkInterface);
// Register help command
factory.registerHelpCommand(term);
Serial.println("Terminal ready! Type 'help' for available commands.");
Serial.print("> ");
}Full example: examples/NetworkTerminal/NetworkTerminal.ino
#include <Terminal.h>
#include <BuiltinCommandFactory.h>
#include <hal/ESPNetworkInterface.h>
ArduinoFileSystem fs;
DirectoryNavigator nav(&fs);
ESPNetworkInterface netInterface;
Terminal term(Serial);
BuiltinCommandFactory factory;
void setup() {
Serial.begin(115200);
WiFi.begin("SSID", "password");
// Register all commands including network
factory.registerAllCommands(term, nav, &netInterface);
Serial.println("Terminal ready! Type 'help' for available commands.");
Serial.print("> ");
}
void loop() {
term.loop();
}Full example: examples/SimpleCustomCommand/SimpleCustomCommand.ino
#include <Terminal.h>
#include <interfaces/ICommand.h>
#include <DirectoryNavigator.h>
// Platform-specific file system
#if defined(ESP32)
#include <hal/SDMMCFileSystem.h>
SDMMCFileSystem fs;
#elif defined(ARDUINO)
#include <hal/ArduinoFileSystem.h>
ArduinoFileSystem fs;
#else
#include <hal/NativeFileSystem.h>
NativeFileSystem fs;
#endif
class MyCommand : public ICommand {
public:
ETString trigger(const ETString &keyword, const ETString &additional) override {
return "Hello from custom command!";
}
ETString usage(const ETString &keyword) override {
return "mycmd - Custom command example";
}
};
DirectoryNavigator nav(&fs);
MyCommand myCmd;
Terminal term(Serial);
void setup() {
Serial.begin(115200);
// Register custom command
term.registerCommand("mycmd", &myCmd);
Serial.println("Terminal ready! Type 'help' for available commands.");
Serial.print("> ");
}
void loop() {
term.loop();
}See also: examples/CustomCommand/CustomCommand.ino for more advanced custom command examples (echo, uptime, LED control)
EmbeddedTerminal supports TAB-based auto completion for commands and file paths. When the user presses TAB while typing, the terminal:
- Looks up the command being typed
- Asks that command for completion suggestions
- Auto-fills the longest common prefix
- Displays remaining options if multiple matches exist
No setup required! Auto completion works automatically with built-in commands that support it:
cd <TAB>: Suggests available directoriescat <TAB>: Suggests available files and directoriesls <TAB>: Suggests available directories- Any path argument: Auto-complete works mid-word on any path
> cd /t[TAB]
> cd /tmp/
data/
logs/
cache/If only one match, auto-completes immediately:
> cat /data/m[TAB]
> cat /data/message.txtTo add auto completion to your custom command, implement the IAutoCompleter interface:
#include <Terminal.h>
#include <interfaces/ICommand.h>
#include <interfaces/IAutoCompleter.h>
#include <DefaultAutoCompleters.h>
class MyAutocompleteCommand : public ICommand, public IAutoCompleter {
public:
MyAutocompleteCommand(DirectoryNavigator &nav) : nav_(nav) {}
ETString trigger(const ETString &keyword, const ETString &additional) override {
return "Command: " + additional;
}
ETString usage(const ETString &keyword) override {
return "myautocmd [argument] - Command with auto completion";
}
// Provide auto completion suggestions
ETVector<ETString> getSuggestions(const ETString &partial) override {
ETVector<ETString> suggestions;
// Return suggestions that match the partial input
// Example: suggest files in current directory
ETVector<ETString> files = nav_.ls("/");
for (const auto &file : files) {
if (file.startsWith(partial)) {
suggestions.push_back(file);
}
}
return suggestions;
}
private:
DirectoryNavigator &nav_;
};
// Register like any other command
MyAutocompleteCommand myCmd(nav);
term.registerCommand("myautocmd", &myCmd);Built-in Auto Completers (available in src/DefaultAutoCompleters.h):
CommandCompleter: Suggests available command names from the terminal's command mapFilePathCompleter: Suggests files and directories that match the partial pathDirectoryCompleter: Suggests only directories (useful forcd,ls, etc.)
You can use these in your own commands:
class MyCommand : public ICommand, public IAutoCompleter {
DirectoryNavigator &nav_;
ETVector<ETString> getSuggestions(const ETString &partial) override {
// Use DirectoryCompleter to suggest directories
DirectoryCompleter completer(nav_);
return completer.getSuggestions(partial);
}
};| Platform | Framework | Status | Notes |
|---|---|---|---|
| ESP32-S3 | ESP-IDF | ✅ Tested | Full support |
| ESP32-S3 | Arduino | ✅ Tested | Full support |
| ESP32 | ESP-IDF | ✅ Compatible | Should work |
| ESP32 | Arduino | ✅ Compatible | Should work |
| Arduino | Arduino | ✅ Compatible | Requires ArxContainer |
| Native | Desktop C++ | ✅ Tested | For testing/development |
| Command | Description | Example |
|---|---|---|
help |
List available commands | help |
cat |
Display file contents | cat file.txt |
cd |
Change directory | cd /folder |
ls |
List directory contents | ls, ls -l, ls -d |
mkdir |
Create directory | mkdir newfolder |
rm |
Remove file | rm file.txt |
rmdir |
Remove directory | rmdir folder |
df |
Show disk usage | df |
tail |
Show end of file | tail file.txt |
ip |
Show network interfaces | ip, ip eth0 |
download |
Download file via network | download url |
Factory class for creating and managing built-in commands.
class BuiltinCommandFactory {
public:
// Register all built-in commands
void registerAllCommands(Terminal &term, DirectoryNavigator &nav, INetworkInterface *net);
// Register command categories
void registerFilesystemCommands(Terminal &term, DirectoryNavigator &nav);
void registerFilesystemCommands(Terminal &term, DirectoryNavigator &nav, uint16_t flags);
void registerDiskCommands(Terminal &term, DirectoryNavigator &nav);
void registerNetworkCommands(Terminal &term, INetworkInterface &net);
void registerHelpCommand(Terminal &term);
// Deregister all commands
void deregisterAllCommands(Terminal &term);
// Destructor automatically cleans up all owned commands
~BuiltinCommandFactory();
};Available Command Flags:
CMD_CAT- Display file contentsCMD_CD- Change directoryCMD_DOWNLOAD- Download file via terminalCMD_LS- List directory contentsCMD_MKDIR- Create directoryCMD_RM- Remove fileCMD_RMDIR- Remove directoryCMD_TAIL- Display end of fileCMD_DF- Show disk usageCMD_IP- Show network interfacesCMD_HELP- Display help
Main terminal execution engine for processing commands.
class Terminal {
public:
// Constructors
Terminal(ITerminalStream &input);
Terminal(Stream &stream); // Arduino/ESP32 platforms
// Main execution loop
void loop();
// Command registration (for custom commands)
void registerCommand(const ETString &keyword, ICommand *command);
void deregisterCommand(const ETString &keyword);
// Get registered commands
const ETMap<ETString, ICommand *>& getCommands() const;
};Usage Notes:
- Terminal does NOT own built-in commands - use BuiltinCommandFactory for those
- Custom commands registered via
registerCommand()must be owned by caller - Call
loop()in your main loop to process terminal input - On Arduino/ESP32, you can pass
Serialor anyStreamdirectly
See also: examples/OwnershipExample/OwnershipExample.ino for a clear demonstration of the ownership model
#include <hal/NativeFileSystem.h> // Or ArduinoFileSystem, SDMMCFileSystem
#include <DirectoryNavigator.h>
NativeFileSystem fs;
DirectoryNavigator nav(&fs);
// Access filesystem from navigator
IFileSystem* fsPtr = nav.getFileSystem();#include <hal/ESPNetworkInterface.h> // ESP32 WiFi/Ethernet
ESPNetworkInterface netInterface;
// Use with network commands
factory.registerNetworkCommands(term, netInterface);class ICommand {
public:
virtual ETString trigger(const ETString &keyword, const ETString &additional) = 0;
virtual ETString usage(const ETString &keyword) const = 0;
};Cross-platform string class with automatic platform adaptation:
class ETString {
public:
ETString();
ETString(const char *s);
size_t length() const;
bool empty() const;
ETString substr(size_t pos, size_t len = npos) const;
size_t find(const ETString &str, size_t pos = 0) const;
ETString trim() const;
ETString cleanupString() const;
const char *c_str() const;
// Operators: +, +=, ==, !=, <, >, <=, >=, []
};class IFileSystem {
public:
virtual bool exists(const char *path) = 0;
virtual bool mkdir(const char *path) = 0;
virtual bool rmdir(const char *path) = 0;
virtual bool remove(const char *path) = 0;
virtual ETVector<ETFile> list(const char *path) const = 0;
virtual ETFile open(const char *path, const char *mode = "r", bool create = false) = 0;
virtual bool isDirectory(const char *path) = 0;
virtual bool isEmpty(const char *path) = 0;
virtual unsigned long long capacity() const = 0;
virtual unsigned long long totalBytes() const = 0;
virtual unsigned long long usedBytes() const = 0;
};EmbeddedTerminal/
├── src/
│ ├── Terminal.h/cpp # Main terminal engine
│ ├── ETTypes.h/cpp # Cross-platform types
│ ├── DirectoryNavigator.h # Directory navigation
│ ├── ETFile.h # File wrapper
│ ├── commands/ # Built-in commands
│ │ ├── cat.h/cpp
│ │ ├── cd.h/cpp
│ │ ├── ls.h/cpp
│ │ ├── help.h/cpp
│ │ └── ...
│ ├── interfaces/ # Abstract interfaces
│ │ ├── ICommand.h
│ │ ├── IFile.h
│ │ ├── IFileSystem.h
│ │ ├── ITerminalStream.h
│ │ └── INetworkInterface.h
│ └── hal/ # Hardware abstraction
│ ├── NativeFileSystem.h
│ ├── ArduinoFileSystem.h
│ └── SDMMCFileSystem.h
├── test/ # Unit tests
├── examples/ # Example sketches
└── library.json # PlatformIO metadata
The library includes comprehensive unit tests using PlatformIO's testing framework:
# Test on native platform
pio test -e native
# Test on ESP32
pio test -e esp32s3_espressif
# Test on Arduino framework
pio test -e esp32s3_arduinoMock implementations are provided for testing custom commands without hardware.
class MyFileSystem : public IFileSystem {
public:
bool exists(const char *path) override {
// Your implementation
}
// Implement all pure virtual methods
};Full example: examples/DualNetworkTerminal/DualNetworkTerminal.ino
#include <interfaces/INetworkInterface.h>
class MyNetwork : public INetworkInterface {
public:
ETVector<NetworkInfo> getAll() override {
ETVector<NetworkInfo> interfaces;
NetworkInfo eth0;
eth0.name = "eth0";
eth0.ip = "192.168.1.100";
eth0.mac = "AA:BB:CC:DD:EE:FF";
interfaces.push_back(eth0);
return interfaces;
}
};Contributions are welcome! Please:
- Fork the repository
- Create a feature branch
- Add tests for new features
- Ensure all tests pass
- Submit a pull request
- Arduino platforms require ArxContainer for STL-like containers
- Some platforms may have memory constraints with large command sets
- File system operations depend on platform-specific implementations
- Add more built-in commands (grep, find, chmod, etc.)
- Implement command history and auto-completion
- Add scripting support for command sequences
- Improve documentation with more examples
- Add GUI terminal emulation support
This project is licensed under the GNU General Public License v3.0 - see the LICENSE file for details.
Guido Lehne
- Report issues: GitHub Issues
- Documentation: GitHub Wiki
- Examples: examples/