├── .gitignore ├── keywords.txt ├── CHANGELOG.md ├── LICENSE ├── examples ├── PrePost │ └── PrePost.pde └── Basic │ └── Basic.pde ├── README.md ├── CommandLine.h └── CommandLine.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /keywords.txt: -------------------------------------------------------------------------------- 1 | CommandLine 2 | Command -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## v2.1.0 4 | Released 02 August 2015 5 | 6 | Highlights: 7 | * Added: optional support for pre/post callbacks. 8 | * Added: pre/post callbacks example sketch (read README.md). 9 | * Added: basic command history support. 10 | * Improved: more uniform code style. 11 | * Fixed: don't add commands twice. 12 | * Fixed: basic example help message was incorrect. 13 | 14 | The full list of commits can be found [here](https://github.com/basilfx/Arduino-CommandLine/compare/v2.0.0...v2.1.0). 15 | 16 | ## v2.0.0 17 | Released 03 May 2015 18 | 19 | * Improved: rewrite of code to remove redundant methods. 20 | * Added: dynamically add commands. 21 | * Added: basic example. 22 | 23 | The full list of commits can be found [here](https://github.com/basilfx/Arduino-CommandLine/compare/v1.0.0...v2.0.0). 24 | 25 | ## v1.0.0 26 | Released 14 August 2013 27 | 28 | The full list of commits can be found [here](https://github.com/basilfx/Arduino-CommandLine/compare/9c161e00223c03c6daf565fad58d27b17efa027e...v1.0.0). 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013-2015 Bas Stottelaar 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. -------------------------------------------------------------------------------- /examples/PrePost/PrePost.pde: -------------------------------------------------------------------------------- 1 | // NOTE: Read the README.md file first! 2 | 3 | #include "CommandLine.h" 4 | 5 | // Keep track of number of bytes processed. 6 | int count = 0; 7 | 8 | // CommandLine instance. 9 | CommandLine commandLine(Serial, "> "); 10 | 11 | /** 12 | * Setup serial port and add commands. 13 | */ 14 | void setup() 15 | { 16 | Serial.begin(9600); 17 | 18 | // Attach pre and post command handlers. 19 | commandLine.attachPre(handlePre); 20 | commandLine.attachPost(handlePost); 21 | 22 | // Add commands. 23 | commandLine.add("count", handleCount); 24 | commandLine.add("help", handleHelp); 25 | } 26 | 27 | /** 28 | * Read-eval-print-loop. 29 | */ 30 | void loop() 31 | { 32 | commandLine.update(); 33 | } 34 | 35 | /** 36 | * Handle the count command. The command has one additional argument that can be the integer to set the count to. 37 | * 38 | * @param tokens The rest of the input command. 39 | */ 40 | void handleCount(char* tokens) 41 | { 42 | Serial.print("Number of input bytes processed: "); 43 | Serial.println(count); 44 | } 45 | 46 | /** 47 | * Handle pre-command callback. 48 | * 49 | * @param tokens The the input command. 50 | */ 51 | void handlePre(char* tokens) 52 | { 53 | count = count + strlen(tokens); 54 | } 55 | 56 | /** 57 | * Remove the count command. 58 | * 59 | * @param tokens The the input command. 60 | * @param success True if some command was executed. 61 | */ 62 | void handlePost(char* tokens, bool success) 63 | { 64 | if (!success) 65 | { 66 | Serial.println("Unknown command. Type 'help' for help."); 67 | } 68 | } 69 | 70 | /** 71 | * Print some help. 72 | * 73 | * @param tokens The rest of the input command. 74 | */ 75 | void handleHelp(char* tokens) 76 | { 77 | Serial.println("Use the commands 'help' or 'count'."); 78 | } 79 | -------------------------------------------------------------------------------- /examples/Basic/Basic.pde: -------------------------------------------------------------------------------- 1 | #include "CommandLine.h" 2 | 3 | // Keep track of the count, for the count command. 4 | int count = 0; 5 | 6 | // CommandLine instance. 7 | CommandLine commandLine(Serial, "> "); 8 | 9 | // Commands are simple structures that can be. 10 | Command command = Command("count", &handleCount); 11 | Command addCount = Command("add", &handleAdd); 12 | Command removeCount = Command("remove", &handleRemove); 13 | 14 | /** 15 | * Setup serial port and add commands. 16 | */ 17 | void setup() 18 | { 19 | Serial.begin(9600); 20 | 21 | // Pre-defined commands 22 | commandLine.add(command); 23 | commandLine.add(addCount); 24 | commandLine.add(removeCount); 25 | 26 | // On-the-fly commands -- instance is allocated dynamically 27 | commandLine.add("help", handleHelp); 28 | } 29 | 30 | /** 31 | * Read-eval-print-loop. 32 | */ 33 | void loop() 34 | { 35 | commandLine.update(); 36 | } 37 | 38 | /** 39 | * Handle the count command. The command has one additional argument that can be the integer to set the count to. 40 | * 41 | * @param tokens The rest of the input command. 42 | */ 43 | void handleCount(char* tokens) 44 | { 45 | char* token = strtok(NULL, " "); 46 | 47 | if (token != NULL) { 48 | count = atoi(token); 49 | } else { 50 | count++; 51 | } 52 | 53 | Serial.println(count); 54 | } 55 | 56 | /** 57 | * Add the count command. 58 | * 59 | * @param tokens The rest of the input command. 60 | */ 61 | void handleAdd(char* tokens) 62 | { 63 | commandLine.add(command); 64 | Serial.println("Command 'count' added."); 65 | } 66 | 67 | /** 68 | * Remove the count command. 69 | * 70 | * @param tokens The rest of the input command. 71 | */ 72 | void handleRemove(char* tokens) 73 | { 74 | commandLine.remove(command); 75 | Serial.println("Command 'count' removed."); 76 | } 77 | 78 | /** 79 | * Print some help. 80 | * 81 | * @param tokens The rest of the input command. 82 | */ 83 | void handleHelp(char* tokens) 84 | { 85 | Serial.println("Use the commands 'help', 'count', 'add' or 'remove'."); 86 | } 87 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CommandLine v2.1.0 2 | No-nonsense serial command line interpreter for Arduino. 3 | 4 | ## Installation 5 | * [Download](https://github.com/basilfx/Arduino-CommandLine/archive/master.zip) or clone this repository to `Arduino/libraries/CommandLine`. 6 | * Restart Arduino IDE. 7 | * Check the examples. 8 | 9 | ## Howto 10 | A `CommandLine` instance wraps a `Serial` instance. The `CommandLine::update` method should be invoked to run the read, print and eval loop. 11 | 12 | Commands can be added with `CommandLine::add` by passing a `Command` structure that takes a command and a callback. Alternatively, you can add a command on-the-fly. In that case, it will allocate memory via `malloc`, but you cannot remove or disable a command afterwards. 13 | 14 | A command can be a single word only (no spaces). It will be tokenized using `strtok` using spaces. If an input string matches a command (first match), the attached callback will be invoked with a `char *` to the rest of the arguments (should be parsed using `strtok`). 15 | 16 | Basic support for history is added in version 2.1.0. 17 | 18 | ## Configuration 19 | This library shouldn't be the biggest part of your flash and/or memory footprint. Therefore, several parameters and functions that can be toggled or modified to improve memory usage. Just define them before `CommandLine.h` is included. 20 | 21 | * `COMMANDLINE_BUFFER` — number of characters to buffer for one line. Default is 32 characters. 22 | * `COMMANDLINE_COUNT` — number of commands that can be added or remove (including on-the-fly ones). Default is 8. 23 | * `COMMANDLINE_PRE_POST` — if defined, enable support for pre and post callback. 24 | * `COMMANDLINE_HISTORY` — number of commands to keep in history (for up/down key support). Default is 2. 25 | 26 | ### Note 1 27 | Due to the limitations (or design choices) of the Arduino IDE and its build process, the above defines cannot be overridden when using the Arduino IDE. There are three options: 28 | 29 | * Add `CommandLine.cpp` and `CommandLine.h` to your project and modify `CommandLine.h` as desired. Make sure you remove the library from your `Arduino/libraries/` folder. 30 | * Modify the `*.build.extra_flags` property in `boards.txt` to add compiler defines (e.g. `-DCOMMANDLINE_PRE_POST`). 31 | * Use a Makefile to compile your sketch and add the desired defines (e.g. `CXX_FLAGS=-DCOMMANDLINE_PRE_POST`). 32 | 33 | ### Note 2 34 | Enabling history support may cause memory issues. When enabled, it will allocate `N * COMMANDLINE_BUFFER` bytes, so use with caution. 35 | 36 | ## License 37 | See the `LICENSE` file (MIT license). 38 | -------------------------------------------------------------------------------- /CommandLine.h: -------------------------------------------------------------------------------- 1 | #ifndef __COMMANDLINE_H__ 2 | #define __COMMANDLINE_H__ 3 | 4 | #include 5 | #include 6 | 7 | #include "Arduino.h" 8 | 9 | // Number of commands to support. 10 | #ifndef COMMANDLINE_COUNT 11 | #define COMMANDLINE_COUNT 8 12 | #endif 13 | 14 | // Maximum number of characters in input buffer. 15 | #ifndef COMMANDLINE_BUFFER 16 | #define COMMANDLINE_BUFFER 32 17 | #endif 18 | 19 | // Maximum number of commands to keep in history (for up/down support). 20 | #ifndef COMMANDLINE_HISTORY 21 | #define COMMANDLINE_HISTORY 2 22 | #endif 23 | 24 | // Add support for pre and post command execution. 25 | #define COMMANDLINE_PRE_POST 26 | 27 | // Keycode defines. 28 | #define KEYCODE_BACKSPACE 8 29 | #define KEYCODE_TAB 9 30 | #define KEYCODE_ENTER 13 31 | #define KEYCODE_SPACE 32 32 | #define KEYCODE_UP 65 33 | #define KEYCODE_DOWN 66 34 | #define KEYCODE_DELETE 127 35 | 36 | /** 37 | * Command definition 38 | */ 39 | struct Command 40 | { 41 | char* command; 42 | void (*callback)(char*); 43 | 44 | /** 45 | * Construct a new command 46 | * 47 | * @param command Name of the command 48 | * @param callback Function pointer 49 | */ 50 | Command(char* _command, void (*_callback)(char*)): command(_command), callback(_callback) {} 51 | }; 52 | 53 | /** 54 | * CommandLine instance. 55 | */ 56 | class CommandLine 57 | { 58 | 59 | public: 60 | 61 | /** 62 | * Construct a new CommandLine instance 63 | * 64 | * @param serial Serial stream to wrap 65 | * @param token Command line token to indicate new line (e.g. "> ") 66 | */ 67 | CommandLine(Stream& serial, char* token); 68 | 69 | /** 70 | * Read the serial stream and evaluate commands. 71 | * 72 | * @return True if a command was evaluated and executed, false otherwise. 73 | */ 74 | bool update(void); 75 | 76 | /** 77 | * Add a new command. 78 | * 79 | * @param Comand instance to add. 80 | * @return True on success, false otherwise. 81 | */ 82 | bool add(Command& command); 83 | 84 | /** 85 | * Add a new command, dynamically. 86 | * 87 | * @param command Name of the command. 88 | * @param callback Function pointer. 89 | * @return True on success, false otherwise. 90 | */ 91 | bool add(char* command, void (*callback)(char*)); 92 | 93 | /** 94 | * Remove a command instance 95 | * 96 | * @param Comand instance to remove. 97 | * @return True on success, false otherwise. 98 | */ 99 | bool remove(Command& command); 100 | 101 | #ifdef COMMANDLINE_PRE_POST 102 | /** 103 | * Attach pre-command execution handler. This callback is invoked 104 | * before each non-empty input command. Set to NULL to clear. 105 | * 106 | * @param callback Function pointer callback. Parameter is input the 107 | * string. 108 | */ 109 | void attachPre(void (*callback)(char*)); 110 | 111 | /** 112 | * Attach post-command execution handler. This callback is invoked 113 | * after each non-empty input command. Set to NULL to clear. 114 | * 115 | * @param callback Function pointer callback. First parameter is the 116 | * input string, second parameter indicates success. 117 | */ 118 | void attachPost(void (*callback)(char*, bool)); 119 | #endif 120 | 121 | private: 122 | Stream& serial; 123 | 124 | struct 125 | { 126 | uint8_t index; 127 | char buffer[COMMANDLINE_BUFFER + 1]; 128 | } input; 129 | 130 | struct 131 | { 132 | uint8_t index; 133 | Command* items[COMMANDLINE_COUNT]; 134 | } commands; 135 | 136 | #if COMMANDLINE_HISTORY > 0 137 | struct 138 | { 139 | uint8_t current; 140 | uint8_t index; 141 | char items[COMMANDLINE_HISTORY][COMMANDLINE_BUFFER + 1]; 142 | } history; 143 | 144 | void restore(); 145 | #endif 146 | 147 | char* token; 148 | 149 | #ifdef COMMANDLINE_PRE_POST 150 | void (*preCallback)(char*); 151 | void (*postCallback)(char*, bool); 152 | #endif 153 | }; 154 | 155 | #endif 156 | -------------------------------------------------------------------------------- /CommandLine.cpp: -------------------------------------------------------------------------------- 1 | #include "CommandLine.h" 2 | 3 | CommandLine::CommandLine(Stream& _serial, char* _token): serial(_serial), token(_token) 4 | { 5 | input.index = 0; 6 | commands.index = 0; 7 | 8 | #if COMMANDLINE_HISTORY 9 | history.index = 0; 10 | #endif 11 | } 12 | 13 | bool CommandLine::update() 14 | { 15 | bool success = false; 16 | 17 | if (this->serial.available()) { 18 | char input = this->serial.read(); 19 | 20 | switch (input) { 21 | case KEYCODE_ENTER: 22 | // Write newline 23 | this->serial.println(""); 24 | 25 | // Parse command 26 | if (this->input.index > 0) { 27 | // Add to history if it is not the same as the previous item added. 28 | #if COMMANDLINE_HISTORY > 0 29 | if (this->history.index == 0 || strncmp(this->history.items[0], this->input.buffer, COMMANDLINE_BUFFER) != 0) { 30 | if (this->history.index < COMMANDLINE_HISTORY) { 31 | this->history.index++; 32 | } 33 | 34 | // Dequeue first item added by shifting each item one index up. 35 | for (int i = this->history.index - 1; i > 0; i--) { 36 | strncpy(this->history.items[i], this->history.items[i - 1], COMMANDLINE_BUFFER); 37 | } 38 | 39 | // Current buffer is inserted at position zero. 40 | strncpy(this->history.items[0], this->input.buffer, COMMANDLINE_BUFFER); 41 | } 42 | #endif 43 | 44 | // Split command name 45 | char* split = strtok(this->input.buffer, " "); 46 | uint8_t length = strnlen(split, COMMANDLINE_BUFFER); 47 | 48 | // Handle post command callback. 49 | #ifdef COMMANDLINE_PRE_POST 50 | if (this->preCallback != NULL) { 51 | this->preCallback(this->input.buffer); 52 | } 53 | #endif 54 | 55 | // Find the first matching command and invoke callback. 56 | for (int i = 0; i < this->commands.index; i++) { 57 | if (strncmp(split, this->commands.items[i]->command, length) == 0) { 58 | if (strlen(this->commands.items[i]->command) == length) { 59 | this->commands.items[i]->callback(&this->input.buffer[this->input.index]); 60 | success = true; 61 | break; 62 | } 63 | } 64 | } 65 | 66 | // Handle post command callback. 67 | #ifdef COMMANDLINE_PRE_POST 68 | if (this->postCallback != NULL) { 69 | this->postCallback(this->input.buffer, success); 70 | } 71 | #endif 72 | } 73 | 74 | // Reset the byte buffer index. 75 | this->input.index = 0; 76 | 77 | // Point to last history item added. By setting it to the history length, the up/down 78 | // handler will treat the first keypress as a special one. 79 | #if COMMANDLINE_HISTORY > 0 80 | this->history.current = this->history.index; 81 | #endif 82 | 83 | // Write a new input token. 84 | this->serial.print(this->token); 85 | 86 | break; 87 | case KEYCODE_BACKSPACE: 88 | case KEYCODE_DELETE: 89 | if (this->input.index > 0) { 90 | // Reduce byte buffer index. 91 | this->input.index--; 92 | 93 | // Clear last char on screen. 94 | this->serial.write(KEYCODE_BACKSPACE); 95 | this->serial.write(KEYCODE_SPACE); 96 | this->serial.write(KEYCODE_BACKSPACE); 97 | } 98 | 99 | break; 100 | #if COMMANDLINE_HISTORY > 0 101 | case KEYCODE_UP: 102 | if (this->history.index > 0) { 103 | // Decide on item to show. If current is equal to the index, show the newest item first. 104 | if (this->history.current == this->history.index) { 105 | this->history.current = 0; 106 | } else { 107 | this->history.current = (this->history.current + 1) % this->history.index; 108 | } 109 | 110 | // Restore from history. 111 | this->restore(); 112 | } 113 | 114 | break; 115 | case KEYCODE_DOWN: 116 | if (this->history.index > 0) { 117 | // Decide on item to show. If current is equal to the index, show the oldest item first. 118 | if (this->history.current == this->history.index) { 119 | this->history.current = this->history.index - 1; 120 | } else { 121 | this->history.current = (this->history.current == 0 ? this->history.index : this->history.current) - 1; 122 | } 123 | 124 | // Restore from history. 125 | this->restore(); 126 | } 127 | 128 | break; 129 | #endif 130 | default: 131 | if (input > 31 && input < 127) { 132 | if (this->input.index < COMMANDLINE_BUFFER) { 133 | // Store input, append NULL char after input for safety reasons. 134 | this->input.buffer[this->input.index] = input; 135 | this->input.buffer[this->input.index + 1] = '\0'; 136 | 137 | // Move pointer 138 | this->input.index++; 139 | 140 | // Repeat char 141 | this->serial.write(input); 142 | } 143 | } 144 | 145 | break; 146 | } 147 | } 148 | 149 | // Done 150 | return success; 151 | } 152 | 153 | bool CommandLine::add(Command& command) 154 | { 155 | // Check if command was already added. 156 | for (int i = 0; i < COMMANDLINE_COUNT; i++) { 157 | if (this->commands.items[i] == &command) { 158 | // Command was already added 159 | return true; 160 | } 161 | } 162 | 163 | // Add it to the list. 164 | if (this->commands.index < COMMANDLINE_COUNT) { 165 | this->commands.items[this->commands.index] = &command; 166 | this->commands.index++; 167 | 168 | // Command added 169 | return true; 170 | } 171 | 172 | // No space left 173 | return false; 174 | } 175 | 176 | bool CommandLine::add(char* command, void (*callback)(char*)) 177 | { 178 | Command* cmd = (Command*) malloc(sizeof(Command)); 179 | 180 | cmd->command = command; 181 | cmd->callback = callback; 182 | 183 | this->add(*cmd); 184 | } 185 | 186 | bool CommandLine::remove(Command& command) 187 | { 188 | for (int i = 0; i < this->commands.index; i++) { 189 | if (this->commands.items[i] == &command) { 190 | // Move other commands one index to the left 191 | for (int j = i + 1; j < this->commands.index; j++) { 192 | this->commands.items[j - 1] = this->commands.items[j]; 193 | } 194 | 195 | this->commands.index--; 196 | 197 | // Done 198 | return true; 199 | } 200 | } 201 | 202 | // Command not found 203 | return false; 204 | } 205 | 206 | #if COMMANDLINE_HISTORY > 0 207 | void CommandLine::restore() 208 | { 209 | // Copy history to buffer 210 | strncpy(this->input.buffer, this->history.items[this->history.current], COMMANDLINE_BUFFER); 211 | this->input.index = strnlen(this->input.buffer, COMMANDLINE_BUFFER); 212 | 213 | // Show new line 214 | this->serial.write("\33[2K\r"); 215 | this->serial.print(this->token); 216 | this->serial.print(this->input.buffer); 217 | } 218 | #endif 219 | 220 | #ifdef COMMANDLINE_PRE_POST 221 | void CommandLine::attachPre(void (*callback)(char*)) 222 | { 223 | this->preCallback = callback; 224 | } 225 | 226 | void CommandLine::attachPost(void (*callback)(char*, bool)) 227 | { 228 | this->postCallback = callback; 229 | } 230 | #endif 231 | --------------------------------------------------------------------------------