1 # Command Line Interface
3 These classes implement a non-blocking command line interface on the Serial
4 port. In ther words, you can implement a primitive "shell" for the Arduino.
6 * `StreamReader` - a class that provided non-blocking methods for
7 reading various types of input tokens from the `Serial` port. Supported tokens
8 include whole lines (terminated by newline), words (separated by whitespace),
10 * `CommandDispatcher` - a class that uses the `StreamReader` and
11 the `AceRoutine` library to read command lines from the `Serial` port, parse
12 the command line string, then dispatch to the appropriate `CommandHandler` to
13 perform the associated command.
15 These classes were initially an experiment to validate the `AceRoutine` macros
16 and classes but they seem to be useful as an independent library. They may be
17 moved to a separate project/repository later.
23 The basic steps for adding a command line interface to an Arduino sketch
24 using the `cli/` library is the following:
26 1. Create a `StreamReader`, giving it the buffers that it needs.
27 1. Create a `DispatchTable` containing the list of commands whose signature
28 matches `CommandHandler`.
29 1. Create a `CommanDispatcher` which reads a whole line from the Serial port,
30 and dispatches to the appropriate command.
31 1. Run the `CommandDispatcher` as an `AceRoutine` coroutine in the global
34 ### Command Dispatcher
36 The `CommandDispatcher` is a subclass of the `Coroutine` class and implements a
37 coroutine in the `run()` method. This is a manually created coroutine,
38 not managed by the `COROUTINE()` macro, so the
39 the `resume()` method must be called to add it to the `CoroutineScheduler`.
41 The calling client is expected to create one of 2 subclasses of
43 * `CommandDispatcherC`: accepts an array of `DispatchRecordC` objects which
44 hold normal C-strings (i.e. `const char*`)
45 * `CommandDispatcherF`: accepts an array of `DispatchRecordF` objects which
47 [PROGMEM](https://arduino.cc/reference/en/language/variables/utilities/progmem/)
48 strings stored in flash memory (i.e. `const __FlashStringHelper*`)
50 On devices with limited static RAM like AVR boards, using flash strings can be
51 critical to allowing the program to run.
53 ### Command Handler and Arguments
55 The `CommandHandler` typedef is a pointer to a user-defined function that has
56 the following signature:
58 typedef void (*CommandHandler)(Print& printer, int argc, const char** argv);
61 * `printer` is the output device, which will normally be the global `Serial`
63 * `argc` is the number of `argv` arguments
64 * `argv` is the array of `cont char*` pointers, each pointing to the words
65 of the command line delimited by whitespaces. These are identical to
66 the `argc` and `argv` parameters passed to the C-language `main(argc, argv)`
67 function. For example, `argv[0]` is the name of the command, and `argv[1]`
68 is the first argument after the command (if it exists).
72 Both the `StreamReader` and `CommandDispatcher` perform no memory allocations
73 internally (no `malloc()`, no `new` operator) to avoid memory problems. All
74 data structures used by these classes must be pre-allocated by the calling code.
75 Normally they will be created at static initialization time (before the global
76 `setup()` method is called), but the calling code is allowed to create them on
77 the heap if it wants or needs to.
79 ### Structure of Client Calling Code
81 An Arduino `.ino` file that uses the CLI classes to implement a commmand line
82 shell will look something like this:
85 #include <AceRoutine.h>
86 #include <ace_routine/cli/StreamReader.h>
87 #include <ace_routine/cli/CommandDispatcher.h>
89 using namespace ace_routine;
90 using namespace ace_routine::cli;
92 // Create the StreamReader with buffers.
93 const int BUF_SIZE = 64;
94 char lineBuffer[BUF_SIZE];
95 StreamReader streamReader(Serial, lineBuffer, BUF_SIZE);
97 // Define the command handlers.
98 void newCommand(Print& printer, int argc, const char** argv) {
102 void anotherCommand(Print& printer, int argc, const char** argv) {
106 // Create the dispatch table of commands (using C-strings)
107 const DispatchRecordC dispatchTable[] = {
108 {&newCommand, "name-of-command", "help-string"},
109 {&anotherCommand, "name-of-other-command", "help-string"},
112 const uint8_t NUM_COMMANDS = sizeof(dispatchTable) / sizeof(DispatchRecordC);
114 // Create CommandDispatcher with buffers.
115 const int8_t ARGV_SIZE = 10;
116 const char* argv[ARGV_SIZE];
117 CommandDispatcher dispatcher(streamReader, Serial,
118 dispatchTable, NUM_COMMANDS argv, ARGV_SIZE);
121 Serial.begin(115200);
122 while (!Serial); // micro/leonardo
124 dispatcher.resume(); // insert into the scheduler
125 CoroutineScheduler::setup();
129 CoroutineScheduler::loop();
135 See [examples/CommandLineInterface/](../../../examples/CommandLineInterface/)
136 for an demo program that implements 5 commands:
142 * `delay (on | off) millis`