diff --git a/.gitignore b/.gitignore index 259148f..80a12d6 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,21 @@ *.exe *.out *.app + +# VSCode +.vscode + +# PlatformIO +.pio + +# Arduino +.arduino + +# Development files (for testing only, not part of library) +# These files are kept locally for development but not distributed +platformio.ini +src/main.cpp +quick-build.sh +ESP32_README.md +build.sh + diff --git a/README.md b/README.md index 2845d9f..c1fc4e3 100644 --- a/README.md +++ b/README.md @@ -1,46 +1,70 @@ -# Serial Terminal library for Arduino +# ๐Ÿ–ฅ๏ธ Serial Terminal library for Arduino -This is a universal Serial Terminal library for Arduino to parse ASCII commands and arguments. +This is a universal Serial Terminal library for Arduino to parse ASCII commands and arguments with enhanced features including command history, tab completion, and improved terminal editing capabilities. -![Serial Terminal](https://raw.githubusercontent.com/Erriez/ErriezSerialTerminal/master/extras/ScreenshotSerialTerminal.png) +## ๐Ÿ†• What's New in This Version +This enhanced version introduces powerful new features that transform your Arduino serial terminal into a modern, user-friendly command interface. **Command History** allows you to navigate through previously entered commands using arrow keys (โ†‘/โ†“), while **Tab Completion** provides intelligent command auto-completion when you press the Tab key. The library now supports **larger buffers** (256 bytes vs 32 bytes) and **extended command lengths** (12 characters vs 8 characters), enabling more complex commands and longer argument lists. Enhanced terminal editing includes improved backspace support, better cursor control, and automatic character echoing for a seamless terminal experience. Built-in **history management functions** let you view and clear command history programmatically. All these improvements maintain full backward compatibility with existing code, ensuring your current projects continue to work without any modifications while gaining access to these powerful new capabilities. -## Hardware +Serial Terminal + + +## ๐Ÿ”ง Hardware Any Arduino hardware with a serial port, such as: -Arduino: -* UNO -* Nano -* Micro -* Pro or Pro Mini -* Mega or Mega2560 -* Leonardo +**Arduino:** +* ๐ŸŸฆ UNO +* ๐ŸŸฆ Nano +* ๐ŸŸฆ Micro +* ๐ŸŸฆ Pro or Pro Mini +* ๐ŸŸฆ Mega or Mega2560 +* ๐ŸŸฆ Leonardo -Other targets: -* DUE -* ESP8266 -* ESP32 -* SAMD21 -* STM32F1 +**Other targets:** +* ๐Ÿ”ต DUE +* ๐ŸŸ  ESP8266 +* ๐ŸŸฃ ESP32 +* ๐ŸŸข SAMD21 +* ๐Ÿ”ด STM32F1 -## Examples +## ๐Ÿ“š Examples Arduino IDE | Examples | Erriez Serial Terminal | -* [ErriezSerialTerminal](https://github.com/Erriez/ErriezSerialTerminal/blob/master/examples/ErriezSerialTerminal/ErriezSerialTerminal.ino) +* ๐Ÿ“– [ErriezSerialTerminal](https://github.com/Erriez/ErriezSerialTerminal/blob/master/examples/ErriezSerialTerminal/ErriezSerialTerminal.ino) - Basic example +* ๐Ÿ”„ [ErriezSerialTerminal_EchoAndCallback](https://github.com/Erriez/ErriezSerialTerminal/blob/master/examples/ErriezSerialTerminal_EchoAndCallback/ErriezSerialTerminal_EchoAndCallback.ino) - Advanced example with character echoing +* โญ [ErriezSerialTerminal_Enhanced](https://github.com/Erriez/ErriezSerialTerminal/blob/master/examples/ErriezSerialTerminal_Enhanced/ErriezSerialTerminal_Enhanced.ino) - **NEW**: Demonstrates all enhanced features including command history and tab completion + + +## ๐Ÿ“– Documentation + +- ๐ŸŒ [Online HTML](https://erriez.github.io/ErriezSerialTerminal) +- ๐Ÿ“„ [Download PDF](https://github.com/Erriez/ErriezSerialTerminal/raw/master/ErriezSerialTerminal.pdf) -## Documentation +## โœจ Enhanced Features -- [Online HTML](https://erriez.github.io/ErriezSerialTerminal) -- [Download PDF](https://github.com/Erriez/ErriezSerialTerminal/raw/master/ErriezSerialTerminal.pdf) +**๐Ÿ†• NEW in this version:** +* ๐Ÿ“œ **Command History**: Navigate through previously entered commands using โ†‘/โ†“ arrow keys +* ๐Ÿ”„ **Tab Completion**: Auto-complete commands by pressing the Tab key +* โœ๏ธ **Enhanced Terminal Editing**: Better backspace support and line editing capabilities +* ๐Ÿ“ˆ **Larger Buffer**: Increased from 32 to 256 bytes for longer commands and more arguments +* ๐Ÿ“ **Extended Command Length**: Support for commands up to 12 characters (increased from 8) +* ๐Ÿ—‚๏ธ **History Management**: Built-in functions to view and clear command history -## Usage +**๐Ÿ–ฅ๏ธ Terminal Features:** +* โฌ†๏ธโฌ‡๏ธ Arrow key navigation for command history +* โŒจ๏ธ Tab key for command auto-completion +* โŒซ Enhanced backspace and delete key support +* โœ๏ธ Improved line editing and cursor control +* ๐Ÿ”Š Character echoing for better terminal experience -**Initialization** +## ๐Ÿš€ Usage + +**โš™๏ธ Initialization** Create a Serial Terminal object. This can be initialized with optional newline and delimiter characters. @@ -69,7 +93,7 @@ void setup() digitalWrite(LED_BUILTIN, LOW); } ``` -**Register new commands** +**๐Ÿ“ Register new commands** Commands must be registered at startup with a corresponding ```callback handler``` . This registers the command only, excluding arguments. @@ -113,7 +137,7 @@ void cmdLedOff() } ``` -**Set default handler** +**๐ŸŽฏ Set default handler** Optional: The default handler will be called when the command is not recognized. @@ -134,7 +158,7 @@ void unknownCommand(const char *command) } ``` -**Read from serial port** +**๐Ÿ“ก Read from serial port** Read from the serial port in the main loop: @@ -146,7 +170,7 @@ void loop() } ``` -**Get next argument** +**โžก๏ธ Get next argument** Get pointer to next argument in serial receive buffer: @@ -163,7 +187,7 @@ if (arg != NULL) { } ``` -**Get remaining characters** +**๐Ÿ“„ Get remaining characters** Get pointer to remaining characters in serial receive buffer: @@ -178,7 +202,7 @@ if (arg != NULL) { } ``` -**Clear buffer** +**๐Ÿงน Clear buffer** Optional: The serial receive buffer can be cleared with the following call: @@ -187,7 +211,7 @@ term.clearBuffer(); ``` -**Enable/Disable Character Echoing** +**๐Ÿ”Š Enable/Disable Character Echoing** Optional: Allow for any entered charecters to be printed back to the Serial interface. This is useful for terminal programs like PuTTY. @@ -198,7 +222,7 @@ term.setSerialEcho(true); //Enable Character Echoing ``` -**Set Post Command Handler** +**โšก Set Post Command Handler** Optional: Add a function to be called AFTER a command has been handled. @@ -218,24 +242,98 @@ void setPostCommandHandler() } ``` -## Library configuration +**๐Ÿ“œ Command History Management** + +The library now includes built-in command history functionality: + +```c++ +void setup() +{ + ... + + // Add commands that can be accessed via history + term.addCommand("history", cmdShowHistory); + term.addCommand("clear", cmdClearHistory); +} + +void cmdShowHistory() +{ + // Display command history + term.showHistory(); +} + +void cmdClearHistory() +{ + // Clear command history + term.clearHistory(); + Serial.println(F("Command history cleared.")); +} +``` + +**โœจ Enhanced Terminal Features** + +The library automatically provides these enhanced features when character echoing is enabled: + +```c++ +void setup() +{ + ... + + // Enable character echoing for enhanced features + term.setSerialEcho(true); + + // Features automatically available: + // - Arrow key navigation (โ†‘/โ†“) for command history + // - Tab completion for commands + // - Enhanced backspace and editing +} +``` + +## โš™๏ธ Library configuration ```SerialTerminal.h``` contains the following configuration macro's: -* ```ST_RX_BUFFER_SIZE``` : The default serial receive buffer size is 32 Bytes. This includes the command and arguments, excluding the ```'\0'``` character. -* ```ST_NUM_COMMAND_CHARS```: The default number of command characters is 8 Bytes, excluding the ```'\0'``` character. +**๐Ÿ“ˆ Enhanced Configuration:** +* ๐Ÿ“ฆ ```ST_RX_BUFFER_SIZE``` : The serial receive buffer size is now **256 Bytes** (increased from 32). This includes the command and arguments, excluding the ```'\0'``` character. +* ๐Ÿ“ ```ST_NUM_COMMAND_CHARS```: The number of command characters is now **12 Bytes** (increased from 8), excluding the ```'\0'``` character. + +**๐Ÿ†• New Configuration:** +* ๐Ÿ“œ ```ST_MAX_HISTORY_ENTRIES```: Maximum number of history entries (default: 20) +* ๐Ÿ“„ ```ST_HISTORY_ENTRY_SIZE```: Maximum length of each history entry (default: 128 characters) + +**๐Ÿ”„ Legacy Configuration (backward compatible):** +* โœ… All original functionality is preserved +* โœ… Default behavior remains unchanged for existing code -## Library dependencies +## ๐Ÿ“ฆ Library dependencies -* None. +* โŒ None. -## Library installation +## ๐Ÿ“ฅ Library installation Please refer to the [Wiki](https://github.com/Erriez/ErriezArduinoLibrariesAndSketches/wiki) page. -## Other Arduino Libraries and Sketches from Erriez +## ๐Ÿ–ฅ๏ธ Terminal Compatibility + +For the best experience with enhanced features, use a terminal emulator that supports: + +**๐Ÿ’ป Recommended Terminal Emulators:** +* ๐ŸชŸ **PuTTY** (Windows) - Full support for arrow keys and Tab completion +* ๐ŸŽ **Terminal** (macOS) - Native support for all enhanced features +* ๐Ÿง **GNOME Terminal** (Linux) - Complete ANSI escape sequence support +* ๐Ÿ”ง **Arduino IDE Serial Monitor** - Basic support (limited arrow key functionality) + +**โœจ Enhanced Features Requirements:** +* ๐ŸŽฏ ANSI escape sequence support (for arrow key navigation) +* โŒจ๏ธ Tab character handling (for command completion) +* ๐Ÿ”Š Character echoing capability +* ๐Ÿ“ Proper line ending support (CR or LF) + +**โš ๏ธ Note:** The Arduino IDE Serial Monitor has limited support for arrow keys. For full enhanced functionality, use a dedicated terminal emulator like PuTTY. + +## ๐Ÿ”— Other Arduino Libraries and Sketches from Erriez -* [Erriez Libraries and Sketches](https://github.com/Erriez/ErriezArduinoLibrariesAndSketches) +* ๐Ÿ“š [Erriez Libraries and Sketches](https://github.com/Erriez/ErriezArduinoLibrariesAndSketches) diff --git a/examples/ErriezSerialTerminal_Enhanced/ErriezSerialTerminal_Enhanced.ino b/examples/ErriezSerialTerminal_Enhanced/ErriezSerialTerminal_Enhanced.ino new file mode 100644 index 0000000..79ce404 --- /dev/null +++ b/examples/ErriezSerialTerminal_Enhanced/ErriezSerialTerminal_Enhanced.ino @@ -0,0 +1,487 @@ +/* + * MIT License + * + * Copyright (c) 2018-2021 Erriez + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/*! + * \brief Enhanced Serial Terminal Example + * \details + * Source: https://github.com/Erriez/ErriezSerialTerminal + * Documentation: https://erriez.github.io/ErriezSerialTerminal + * + * This example demonstrates all the enhanced features of the SerialTerminal library: + * - Command history with arrow key navigation (โ†‘/โ†“) + * - Tab completion for commands + * - Enhanced terminal editing with backspace support + * - Larger buffer size (256 bytes) for longer commands and more arguments + * - Increased command length support (12 characters) + * - History management commands + * - System information display + * + * Hardware: Any Arduino-compatible board with serial communication + * Serial: 115200 baud, 8N1 + */ + +#include + +// LED pin - ESP32 uses GPIO 2 as built-in LED +#ifndef LED_BUILTIN +#define LED_BUILTIN 2 +#endif +#define LED_PIN LED_BUILTIN + +// Newline character '\r' or '\n' - using '\r' for better terminal compatibility +char newlineChar = '\r'; +// Separator character between commands and arguments +char delimiterChar = ' '; + +// Create serial terminal object +SerialTerminal term(newlineChar, delimiterChar); + +// Function prototypes +void unknownCommand(const char *command); +void cmdHelp(); +void cmdLedOn(); +void cmdLedOff(); +void cmdPrintHello(); +void cmdPrintIntArgument(); +void cmdPrintStringArguments(); +void cmdShowHistory(); +void cmdClearHistory(); +void cmdSystemInfo(); +void cmdLongCommand(); +void cmdTestArgs(); +void cmdTemperature(); +void cmdPwmControl(); +void cmdMemoryTest(); +void printConsoleChar(); + +// Global variables for demo features +int pwmValue = 0; +float temperature = 25.5; + +void setup() +{ + // Startup delay to initialize serial port + delay(500); + + // Initialize serial port + Serial.begin(115200); + Serial.println(F("\n=== Enhanced Serial Terminal Example ===")); + Serial.println(F("Features: History, Tab Completion, Larger Buffer")); + Serial.println(F("Type 'help' to see all available commands.")); + Serial.println(F("Try using arrow keys for history and Tab for completion!")); + printConsoleChar(); + + // Initialize the built-in LED + pinMode(LED_PIN, OUTPUT); + digitalWrite(LED_PIN, LOW); + + // Set default handler for unknown commands + term.setDefaultHandler(unknownCommand); + + // Add command callback handlers - demonstrating longer command names + term.addCommand("?", cmdHelp); + term.addCommand("help", cmdHelp); + term.addCommand("on", cmdLedOn); + term.addCommand("off", cmdLedOff); + term.addCommand("hello", cmdPrintHello); + term.addCommand("i", cmdPrintIntArgument); + term.addCommand("s", cmdPrintStringArguments); + term.addCommand("history", cmdShowHistory); + term.addCommand("clear", cmdClearHistory); + term.addCommand("info", cmdSystemInfo); + term.addCommand("longcommand", cmdLongCommand); + term.addCommand("testargs", cmdTestArgs); + term.addCommand("temperature", cmdTemperature); + term.addCommand("pwmcontrol", cmdPwmControl); + term.addCommand("memorytest", cmdMemoryTest); + + // Enable character echoing for terminal emulators + term.setSerialEcho(true); + // Set post command handler for better UX + term.setPostCommandHandler(printConsoleChar); +} + +void loop() +{ + // Read from serial port and handle command callbacks + term.readSerial(); +} + +void printConsoleChar() +{ + Serial.print(F("> ")); +} + +void unknownCommand(const char *command) +{ + // Print unknown command + Serial.print(F("Unknown command: ")); + Serial.println(command); + Serial.println(F("Type 'help' to see available commands.")); +} + +void cmdHelp() +{ + // Print comprehensive usage + Serial.println(F("\n=== Enhanced Serial Terminal Commands ===")); + Serial.println(F("Basic Commands:")); + Serial.println(F(" help or ? Print this usage")); + Serial.println(F(" on Turn LED on")); + Serial.println(F(" off Turn LED off")); + Serial.println(F(" hello [name] [age] Print greeting with name and age")); + Serial.println(F(" i Print number in decimal and hex")); + Serial.println(F(" s Print multiple string arguments")); + + Serial.println(F("\nHistory Management:")); + Serial.println(F(" history Show command history")); + Serial.println(F(" clear Clear command history")); + + Serial.println(F("\nSystem Information:")); + Serial.println(F(" info Show system information")); + Serial.println(F(" longcommand Test longer command names")); + Serial.println(F(" testargs Test many arguments with large buffer")); + + Serial.println(F("\nDemo Commands:")); + Serial.println(F(" temperature Show temperature sensor demo")); + Serial.println(F(" pwmcontrol PWM control demo")); + Serial.println(F(" memorytest Memory usage test")); + + Serial.println(F("\nEnhanced Features:")); + Serial.println(F(" โ†‘/โ†“ Arrow Keys Navigate command history")); + Serial.println(F(" Tab Auto-complete commands")); + Serial.println(F(" Backspace Delete characters")); + Serial.println(F(" Ctrl+C Clear current line")); + Serial.println(F("")); + Serial.println(F("Try typing 't' and press Tab to see completion!")); +} + +void cmdLedOn() +{ + Serial.println(F("LED turned ON")); + digitalWrite(LED_PIN, HIGH); +} + +void cmdLedOff() +{ + Serial.println(F("LED turned OFF")); + digitalWrite(LED_PIN, LOW); +} + +void cmdPrintHello() +{ + char *arg; + char *name = NULL; + int age = 0; + + // Get first argument + arg = term.getNext(); + if (arg != NULL) { + // Try to convert argument to int + if (atoi(arg)) { + // Name is not specified, only age + age = atoi(arg); + } else { + // Store first name argument + name = arg; + + // Get second age argument + arg = term.getNext(); + if (arg != NULL) { + age = atoi(arg); + } + } + } + + // Print greeting + if (name) { + Serial.print(F("Hello ")); + Serial.print(name); + Serial.println(F("!")); + } else { + Serial.println(F("Hello! You didn't specify your name.")); + } + + // Print age + if (age > 0) { + Serial.print(F("You are ")); + Serial.print(age, DEC); + Serial.println(F(" years old.")); + } else { + Serial.println(F("Age not specified.")); + } +} + +void cmdPrintIntArgument() +{ + int val; + char *arg; + + // Get argument + arg = term.getNext(); + if (arg == NULL) { + Serial.println(F("No number specified.")); + Serial.println(F("Usage: i or i 0x")); + return; + } + + // Try to convert decimal or hex argument + if (strncmp(arg, "0x", 2) == 0) { + if (sscanf(arg, "0x%x", &val) != 1) { + Serial.println(F("Invalid hex value.")); + return; + } + } else { + if (sscanf(arg, "%d", &val) != 1) { + Serial.println(F("Invalid decimal value.")); + return; + } + } + + // Print results + Serial.print(F("Input: ")); + Serial.print(arg); + Serial.print(F(" | Decimal: ")); + Serial.print(val, DEC); + Serial.print(F(" | Hex: 0x")); + if (val < 0x10) Serial.print(F("0")); + Serial.print(val, HEX); + Serial.print(F(" | Binary: ")); + Serial.println(val, BIN); +} + +void cmdPrintStringArguments() +{ + char *arg; + int argCount = 0; + + Serial.println(F("String Arguments:")); + + // Print all arguments + while ((arg = term.getNext()) != NULL) { + argCount++; + Serial.print(F(" ")); + Serial.print(argCount); + Serial.print(F(": \"")); + Serial.print(arg); + Serial.println(F("\"")); + } + + if (argCount == 0) { + Serial.println(F(" No arguments provided.")); + Serial.println(F(" Usage: s arg1 arg2 arg3 ...")); + } else { + Serial.print(F("Total arguments: ")); + Serial.println(argCount); + + // Show remaining string + char *remaining = term.getRemaining(); + if (remaining && strlen(remaining) > 0) { + Serial.print(F("Remaining: ")); + Serial.println(remaining); + } + } +} + +void cmdShowHistory() +{ + Serial.println(F("=== Command History ===")); + term.showHistory(); + Serial.println(F("Use โ†‘/โ†“ arrow keys to navigate history.")); +} + +void cmdClearHistory() +{ + term.clearHistory(); + Serial.println(F("Command history cleared.")); +} + +void cmdSystemInfo() +{ + Serial.println(F("=== System Information ===")); + + // Basic system info + Serial.println(F("Hardware:")); + Serial.print(F(" LED Pin: GPIO ")); + Serial.println(LED_PIN); + + Serial.println(F("\nLibrary Configuration:")); + Serial.print(F(" Buffer Size: ")); + Serial.print(ST_RX_BUFFER_SIZE); + Serial.println(F(" bytes")); + Serial.print(F(" Max Command Length: ")); + Serial.print(ST_NUM_COMMAND_CHARS); + Serial.println(F(" characters")); + Serial.print(F(" Max History Entries: ")); + Serial.print(ST_MAX_HISTORY_ENTRIES); + Serial.println(F(" entries")); + Serial.print(F(" History Entry Size: ")); + Serial.print(ST_HISTORY_ENTRY_SIZE); + Serial.println(F(" characters")); + + Serial.println(F("\nEnhanced Features:")); + Serial.println(F(" โœ“ Command History")); + Serial.println(F(" โœ“ Tab Completion")); + Serial.println(F(" โœ“ Arrow Key Navigation")); + Serial.println(F(" โœ“ Enhanced Editing")); + Serial.println(F(" โœ“ Larger Buffer Support")); +} + +void cmdLongCommand() +{ + Serial.println(F("=== Long Command Demo ===")); + Serial.println(F("This command demonstrates the increased command length support.")); + Serial.print(F("The library now supports commands up to ")); + Serial.print(ST_NUM_COMMAND_CHARS); + Serial.println(F(" characters long!")); + Serial.println(F("This allows for more descriptive and readable command names.")); + Serial.println(F("Try typing 'long' and press Tab to see completion in action!")); +} + +void cmdTestArgs() +{ + Serial.println(F("=== Large Buffer Test ===")); + + char *arg; + int argCount = 0; + int totalChars = 0; + + Serial.println(F("Testing multiple arguments with larger buffer...")); + + // Count arguments and characters + while ((arg = term.getNext()) != NULL) { + argCount++; + totalChars += strlen(arg); + Serial.print(F(" Arg ")); + Serial.print(argCount); + Serial.print(F(": \"")); + Serial.print(arg); + Serial.print(F("\" (")); + Serial.print(strlen(arg)); + Serial.println(F(" chars)")); + } + + if (argCount == 0) { + Serial.println(F("No arguments provided.")); + Serial.println(F("Try: testargs arg1 arg2 arg3 arg4 arg5 arg6 arg7 arg8")); + Serial.print(F("The larger buffer (")); + Serial.print(ST_RX_BUFFER_SIZE); + Serial.println(F(" bytes) allows for many more arguments!")); + } else { + Serial.print(F("Total arguments: ")); + Serial.println(argCount); + Serial.print(F("Total characters: ")); + Serial.println(totalChars); + Serial.print(F("Buffer usage: ")); + Serial.print((totalChars * 100) / ST_RX_BUFFER_SIZE); + Serial.println(F("%")); + } +} + +void cmdTemperature() +{ + Serial.println(F("=== Temperature Sensor Demo ===")); + + // Simulate temperature reading + temperature += (random(-50, 51) / 10.0); // Random change ยฑ5ยฐC + if (temperature < 0) temperature = 0; + if (temperature > 100) temperature = 100; + + Serial.print(F("Current Temperature: ")); + Serial.print(temperature, 1); + Serial.println(F("ยฐC")); + + char *arg = term.getNext(); + if (arg != NULL) { + float newTemp = atof(arg); + if (newTemp >= 0 && newTemp <= 100) { + temperature = newTemp; + Serial.print(F("Temperature set to: ")); + Serial.print(temperature, 1); + Serial.println(F("ยฐC")); + } else { + Serial.println(F("Temperature must be between 0 and 100ยฐC")); + } + } +} + +void cmdPwmControl() +{ + Serial.println(F("=== PWM Control Demo ===")); + + char *arg = term.getNext(); + if (arg != NULL) { + int newPwm = atoi(arg); + if (newPwm >= 0 && newPwm <= 255) { + pwmValue = newPwm; + Serial.print(F("PWM value set to: ")); + Serial.println(pwmValue); + + // In a real application, you would set PWM here: + // analogWrite(LED_PIN, pwmValue); + + Serial.println(F("(LED PWM simulation - use actual PWM pin for real control)")); + } else { + Serial.println(F("PWM value must be between 0 and 255")); + } + } else { + Serial.print(F("Current PWM value: ")); + Serial.println(pwmValue); + Serial.println(F("Usage: pwmcontrol <0-255>")); + } +} + +void cmdMemoryTest() +{ + Serial.println(F("=== Memory Usage Test ===")); + + // Test large string handling + Serial.println(F("Testing large string handling...")); + + char *arg; + int totalLength = 0; + + // Get all arguments and measure total length + while ((arg = term.getNext()) != NULL) { + totalLength += strlen(arg) + 1; // +1 for space + } + + Serial.print(F("Total command length: ")); + Serial.print(totalLength); + Serial.print(F(" / ")); + Serial.print(ST_RX_BUFFER_SIZE); + Serial.print(F(" bytes (")); + Serial.print((totalLength * 100) / ST_RX_BUFFER_SIZE); + Serial.println(F("%)")); + + if (totalLength > ST_RX_BUFFER_SIZE * 0.8) { + Serial.println(F("โš ๏ธ Warning: Buffer usage > 80%")); + } else { + Serial.println(F("โœ“ Buffer usage within safe limits")); + } + + // Show remaining buffer capacity + Serial.print(F("Remaining buffer capacity: ")); + Serial.print(ST_RX_BUFFER_SIZE - totalLength); + Serial.println(F(" bytes")); +} diff --git a/examples/ErriezSerialTerminal_Enhanced/README.md b/examples/ErriezSerialTerminal_Enhanced/README.md new file mode 100644 index 0000000..d972060 --- /dev/null +++ b/examples/ErriezSerialTerminal_Enhanced/README.md @@ -0,0 +1,182 @@ +# Enhanced Serial Terminal Example + +This example demonstrates all the enhanced features of the ErriezSerialTerminal library, including command history, tab completion, and improved terminal editing capabilities. + +## Features Demonstrated + +### Core Enhancements +- **Command History**: Navigate through previously entered commands using โ†‘/โ†“ arrow keys +- **Tab Completion**: Auto-complete commands by pressing the Tab key +- **Enhanced Editing**: Better backspace support and line editing +- **Larger Buffer**: 256-byte buffer for longer commands and more arguments +- **Extended Command Length**: Support for commands up to 12 characters + +### Commands Available + +#### Basic Commands +- `help` or `?` - Display comprehensive help information +- `on` / `off` - Control the built-in LED +- `hello [name] [age]` - Greeting with optional name and age +- `i ` - Display number in decimal, hex, and binary formats +- `s ` - Display multiple string arguments + +#### History Management +- `history` - Show command history +- `clear` - Clear command history + +#### System Information +- `info` - Display system and library configuration information +- `longcommand` - Demonstrate longer command name support +- `testargs` - Test multiple arguments with large buffer + +#### Demo Commands +- `temperature` - Temperature sensor simulation +- `pwmcontrol` - PWM control demonstration +- `memorytest` - Memory usage testing + +## Enhanced Terminal Features + +### Arrow Key Navigation +- **โ†‘ (Up Arrow)**: Navigate backward through command history +- **โ†“ (Down Arrow)**: Navigate forward through command history + +### Tab Completion +- **Tab Key**: Auto-complete commands +- If multiple matches exist, all possibilities are displayed +- If single match exists, command is auto-completed + +### Editing Features +- **Backspace**: Delete characters +- **Ctrl+C**: Clear current line (terminal dependent) + +## Usage Examples + +### Basic Usage +``` +> help +> on +> hello John 25 +> i 255 +> s arg1 arg2 arg3 +``` + +### History Navigation +1. Enter several commands +2. Press โ†‘ to go back through history +3. Press โ†“ to go forward through history +4. Press Enter to execute the selected command + +### Tab Completion +1. Type `t` and press Tab +2. See all commands starting with 't' +3. Type more characters and press Tab again for auto-completion + +### Large Buffer Testing +``` +> testargs arg1 arg2 arg3 arg4 arg5 arg6 arg7 arg8 arg9 arg10 +``` + +### System Information +``` +> info +``` + +## Hardware Requirements + +- Any Arduino-compatible board +- Built-in LED (for LED control commands) +- Serial communication capability + +## Serial Settings + +- **Baud Rate**: 115200 +- **Data Bits**: 8 +- **Stop Bits**: 1 +- **Parity**: None +- **Flow Control**: None +- **Line Ending**: Carriage Return (CR) - `\r` + +## Library Configuration + +This example uses the enhanced configuration: + +```cpp +// Buffer size increased from 32 to 256 bytes +#define ST_RX_BUFFER_SIZE 256 + +// Command length increased from 8 to 12 characters +#define ST_NUM_COMMAND_CHARS 12 + +// History support +#define ST_MAX_HISTORY_ENTRIES 20 +#define ST_HISTORY_ENTRY_SIZE 128 +``` + +## Compatibility + +This enhanced example is compatible with: +- Arduino Uno, Nano, Micro, Pro Mini +- Arduino Mega, Leonardo +- ESP8266, ESP32 +- STM32F1, SAMD21 +- Other Arduino-compatible boards + +## Terminal Emulator Compatibility + +For best experience, use a terminal emulator that supports: +- ANSI escape sequences (for arrow keys) +- Tab character handling +- Character echoing + +Recommended terminal emulators: +- **PuTTY** (Windows) +- **Terminal** (macOS) +- **GNOME Terminal** (Linux) +- **Arduino IDE Serial Monitor** (basic support) + +## Troubleshooting + +### Arrow Keys Not Working +- Ensure your terminal emulator supports ANSI escape sequences +- Try using PuTTY or another advanced terminal emulator +- The Arduino IDE Serial Monitor has limited support + +### Tab Completion Not Working +- Make sure character echoing is enabled +- Verify the terminal supports Tab character input +- Try typing partial commands and pressing Tab + +### History Not Saving +- Commands are saved automatically when you press Enter +- Empty commands are not saved to history +- History is cleared when you restart the Arduino + +## Performance Notes + +- History is stored in RAM, so it's cleared on power cycle +- Larger buffer uses more memory but allows longer commands +- Tab completion searches through all registered commands + +## Future Enhancements + +Potential improvements that could be added: +- Persistent history storage in EEPROM +- Command aliases and shortcuts +- Multi-line command support +- Command parameter validation +- Custom prompt configuration + +## License + +This example is part of the ErriezSerialTerminal library and is released under the MIT License. + +## Contributing + +If you have suggestions for improvements or find issues: +1. Fork the repository +2. Create a feature branch +3. Make your changes +4. Test thoroughly +5. Submit a pull request + +For more information, visit the [ErriezSerialTerminal GitHub repository](https://github.com/Erriez/ErriezSerialTerminal). diff --git a/extras/ScreenshotSerialTerminal.png b/extras/ScreenshotSerialTerminal.png index 619cf15..b60f420 100644 Binary files a/extras/ScreenshotSerialTerminal.png and b/extras/ScreenshotSerialTerminal.png differ diff --git a/src/ErriezSerialTerminal.cpp b/src/ErriezSerialTerminal.cpp index cb682c4..afa50a0 100644 --- a/src/ErriezSerialTerminal.cpp +++ b/src/ErriezSerialTerminal.cpp @@ -46,7 +46,13 @@ SerialTerminal::SerialTerminal(char newlineChar, char delimiterChar) : _numCommands(0), _newlineChar(newlineChar), _lastPos(NULL), - _defaultHandler(NULL) + _defaultHandler(NULL), + _historyCount(0), + _historyIndex(0), + _currentHistoryIndex(-1), + _inHistoryMode(false), + _tabIndex(0), + _tabMode(false) { // strtok_r needs a null-terminated string _delimiter[0] = delimiterChar; @@ -54,6 +60,11 @@ SerialTerminal::SerialTerminal(char newlineChar, char delimiterChar) : // Clear receive buffer clearBuffer(); + + // Initialize history + for (int i = 0; i < ST_MAX_HISTORY_ENTRIES; i++) { + _history[i][0] = '\0'; + } } /*! @@ -130,13 +141,39 @@ void SerialTerminal::readSerial() // Read one character from serial port c = (char)Serial.read(); + // Handle special keys first + if (c == 0x1B) { // ESC sequence start + // Wait for the next character to determine the key + if (Serial.available() > 0) { + char c2 = Serial.read(); + if (c2 == '[') { + // Wait for the third character + if (Serial.available() > 0) { + char c3 = Serial.read(); + handleSpecialKeys(c3); + } + } + } + continue; + } + // Check newline character \c '\\r' or \c '\\n' at the end of a command if (c == _newlineChar) { - //Echo received char + // Echo received char if (doCharEcho) { Serial.println(); } + // Add to history if not empty and not in tab mode + if (_rxBufferIndex > 0 && !_tabMode) { + addToHistory(_rxBuffer); + } + + // Reset history mode + _inHistoryMode = false; + _currentHistoryIndex = -1; + resetTabMode(); + // Search for command at start of buffer command = strtok_r(_rxBuffer, _delimiter, &_lastPos); @@ -156,14 +193,15 @@ void SerialTerminal::readSerial() } } - //Run the post command handler. + // Run the post command handler if (_postCommandHandler) { (*_postCommandHandler)(); } clearBuffer(); - // either ^H og 127 backspace chars - } else if (c == '\b' || c == 127) { + } + // Handle backspace and delete + else if (c == '\b' || c == 127) { if (_rxBufferIndex > 0) { _rxBufferIndex--; _rxBuffer[_rxBufferIndex] = '\0'; @@ -171,16 +209,26 @@ void SerialTerminal::readSerial() Serial.print("\b \b"); // 1 char back, space, 1 char back } } - } else if (isprint(c)) { + resetTabMode(); + } + // Handle Tab key for command completion + else if (c == '\t') { + if (_rxBufferIndex > 0) { + completeCommand(); + } + } + // Handle printable characters + else if (isprint(c)) { // Store printable characters in serial receive buffer if (_rxBufferIndex < ST_RX_BUFFER_SIZE) { _rxBuffer[_rxBufferIndex++] = c; _rxBuffer[_rxBufferIndex] = '\0'; - //Echo received char + // Echo received char if (doCharEcho) { Serial.print(c); } } + resetTabMode(); } } } @@ -215,3 +263,228 @@ char *SerialTerminal::getRemaining() { return strtok_r(NULL, "", &_lastPos); } + +/*! + * \brief Handle special keys (arrow keys, etc.) + */ +void SerialTerminal::handleSpecialKeys(char c) +{ + switch (c) { + case 'A': // Up arrow + navigateHistory(-1); + break; + case 'B': // Down arrow + navigateHistory(1); + break; + case 'C': // Right arrow + // Could be used for cursor movement in the future + break; + case 'D': // Left arrow + // Could be used for cursor movement in the future + break; + } +} + +/*! + * \brief Navigate through command history + */ +void SerialTerminal::navigateHistory(int direction) +{ + if (_historyCount == 0) return; + + if (!_inHistoryMode) { + _inHistoryMode = true; + _currentHistoryIndex = _historyCount - 1; + } else { + _currentHistoryIndex += direction; + if (_currentHistoryIndex < 0) { + _currentHistoryIndex = 0; + } else if (_currentHistoryIndex >= _historyCount) { + _currentHistoryIndex = _historyCount - 1; + } + } + + // Clear current line and show history entry + if (doCharEcho) { + clearLine(); + if (_currentHistoryIndex >= 0 && _currentHistoryIndex < _historyCount) { + strcpy(_rxBuffer, _history[_currentHistoryIndex]); + _rxBufferIndex = strlen(_rxBuffer); + Serial.print(_rxBuffer); + } + } +} + +/*! + * \brief Complete command using Tab key + */ +void SerialTerminal::completeCommand() +{ + char matches[ST_MAX_HISTORY_ENTRIES][ST_NUM_COMMAND_CHARS + 1]; + int matchCount = 0; + + // Find matching commands + findMatchingCommands(_rxBuffer, matches, &matchCount); + + if (matchCount == 0) { + // No matches found + if (doCharEcho) { + Serial.print('\a'); // Bell sound + } + } else if (matchCount == 1) { + // Single match - complete the command + if (doCharEcho) { + clearLine(); + strcpy(_rxBuffer, matches[0]); + _rxBufferIndex = strlen(_rxBuffer); + Serial.print(_rxBuffer); + } + } else { + // Multiple matches - show all possibilities + if (doCharEcho) { + Serial.println(); + for (int i = 0; i < matchCount; i++) { + Serial.print(matches[i]); + Serial.print(" "); + } + Serial.println(); + printPrompt(); + Serial.print(_rxBuffer); + } + } +} + +/*! + * \brief Reset tab completion mode + */ +void SerialTerminal::resetTabMode() +{ + _tabMode = false; + _tabIndex = 0; +} + +/*! + * \brief Print command prompt + */ +void SerialTerminal::printPrompt() +{ + Serial.print("> "); +} + +/*! + * \brief Clear current line + */ +void SerialTerminal::clearLine() +{ + // Move cursor to beginning of line + Serial.print("\r"); + // Clear the line by printing spaces (more compatible than ANSI escape sequences) + for (int i = 0; i < 80; i++) { + Serial.print(" "); + } + // Move cursor back to beginning of line + Serial.print("\r"); + // Print prompt + printPrompt(); +} + +/*! + * \brief Move cursor left + */ +void SerialTerminal::moveCursorLeft(int positions) +{ + for (int i = 0; i < positions; i++) { + Serial.print("\b"); + } +} + +/*! + * \brief Move cursor right + */ +void SerialTerminal::moveCursorRight(int positions) +{ + for (int i = 0; i < positions; i++) { + Serial.print(" "); + } +} + +/*! + * \brief Check if a command matches a partial string + */ +bool SerialTerminal::isCommandMatch(const char *partial, const char *command) +{ + return strncmp(partial, command, strlen(partial)) == 0; +} + +/*! + * \brief Find all commands that match the partial string + */ +void SerialTerminal::findMatchingCommands(const char *partial, char matches[][ST_NUM_COMMAND_CHARS + 1], int *matchCount) +{ + *matchCount = 0; + + for (int i = 0; i < _numCommands && *matchCount < ST_MAX_HISTORY_ENTRIES; i++) { + if (isCommandMatch(partial, _commandList[i].command)) { + strcpy(matches[*matchCount], _commandList[i].command); + (*matchCount)++; + } + } +} + +/*! + * \brief Add command to history + */ +void SerialTerminal::addToHistory(const char *command) +{ + // Don't add empty commands + if (strlen(command) == 0) return; + + // Don't add duplicate of the last command + if (_historyCount > 0 && strcmp(_history[_historyCount - 1], command) == 0) { + return; + } + + // Shift history if we've reached the maximum + if (_historyCount >= ST_MAX_HISTORY_ENTRIES) { + for (int i = 0; i < ST_MAX_HISTORY_ENTRIES - 1; i++) { + strcpy(_history[i], _history[i + 1]); + } + _historyCount = ST_MAX_HISTORY_ENTRIES - 1; + } + + // Add new command to history + strncpy(_history[_historyCount], command, ST_HISTORY_ENTRY_SIZE - 1); + _history[_historyCount][ST_HISTORY_ENTRY_SIZE - 1] = '\0'; + _historyCount++; +} + +/*! + * \brief Show command history + */ +void SerialTerminal::showHistory() +{ + if (_historyCount == 0) { + Serial.println("No command history available."); + return; + } + + Serial.println("Command History:"); + for (int i = 0; i < _historyCount; i++) { + Serial.print(i + 1); + Serial.print(": "); + Serial.println(_history[i]); + } +} + +/*! + * \brief Clear command history + */ +void SerialTerminal::clearHistory() +{ + for (int i = 0; i < ST_MAX_HISTORY_ENTRIES; i++) { + _history[i][0] = '\0'; + } + _historyCount = 0; + _currentHistoryIndex = -1; + _inHistoryMode = false; +} diff --git a/src/ErriezSerialTerminal.h b/src/ErriezSerialTerminal.h index a746915..b3e3d2e 100644 --- a/src/ErriezSerialTerminal.h +++ b/src/ErriezSerialTerminal.h @@ -39,12 +39,22 @@ /*! * \brief Size of the serial receive buffer in bytes (Maximum length of one command plus arguments) */ -#define ST_RX_BUFFER_SIZE 32 +#define ST_RX_BUFFER_SIZE 256 /*! * \brief Number of command characters */ -#define ST_NUM_COMMAND_CHARS 8 +#define ST_NUM_COMMAND_CHARS 12 + +/*! + * \brief Maximum number of history entries + */ +#define ST_MAX_HISTORY_ENTRIES 20 + +/*! + * \brief Maximum length of each history entry + */ +#define ST_HISTORY_ENTRY_SIZE 128 /*! * \brief SerialTerminal class @@ -66,6 +76,11 @@ class SerialTerminal char *getNext(); char *getRemaining(); + // History management functions + void addToHistory(const char *command); + void showHistory(); + void clearHistory(); + private: struct SerialTerminalCallback { char command[ST_NUM_COMMAND_CHARS + 1]; @@ -84,6 +99,30 @@ class SerialTerminal void (*_postCommandHandler)(void); void (*_defaultHandler)(const char *); + + // History management + char _history[ST_MAX_HISTORY_ENTRIES][ST_HISTORY_ENTRY_SIZE]; + int _historyCount; + int _historyIndex; + int _currentHistoryIndex; + bool _inHistoryMode; + + // Tab completion + char _tabBuffer[ST_RX_BUFFER_SIZE + 1]; + int _tabIndex; + bool _tabMode; + + // Helper functions + void handleSpecialKeys(char c); + void navigateHistory(int direction); + void completeCommand(); + void resetTabMode(); + void printPrompt(); + void clearLine(); + void moveCursorLeft(int positions); + void moveCursorRight(int positions); + bool isCommandMatch(const char *partial, const char *command); + void findMatchingCommands(const char *partial, char matches[][ST_NUM_COMMAND_CHARS + 1], int *matchCount); }; #endif // ERRIEZ_SERIAL_TERMINAL_H_