The Arduino library that provides an interface to handle commands and streams.
- Runner
- What problem does it solve
- Method documentation
- Usage
- Commands
- Redirect input, output and error, and pipeline
- Contribution
Sometimes, using Arduino, you feel that it could be useful to have a sort of shell executing some kind of command, maybe handling input/output/error as stream lines like Serial (or some other serial stuff imported with a library like sd card reader or the i2c from Wire.h).
This is the problem that this library solves.
It also implements a simple event interface based on the same command system, so you can execute multiple commands when an event is triggered.
there are other libraries around that do the same but this one does have a some strengths
- is object oriented, so you can create different instances of runner::Interface (and shells) that could share commands
- it handles commands that operates on streams
- a shell can be attached to every stream
- it does provide an easy way to handle multiple commands when an event occurs
- it does offer stream redirection with well known symbols like
>
,<
,&
and|
The following is an informal list of methods and their semantics
void add(String name, [Stream|Command] * ptr)
add a Command or a Stream to the systemint8_t run(String cmd, Stream in, Stream out, Stream err)
run a command on the given streamsvoid trigger(String cmd, Stream in, Stream out, Stream err)
run all commands with a given nameEntry<T> find<T = void>(String name)
find the entry associated withname
Shell shell(Stream in, Stream out, Stream err)
build a shell
int8_t run()
runs all commands from the input Stream until there are bytes availablevoid bind(String event = "loop")
execute the run method whenevent
is triggeredvoid set(Stream in, Stream out, Stream err)
set default input output and error of the shell
String const * name
the node's nameT ref()
get theCommand
orStream
associted with theEntry<T>
String type()
get astring
rappresentation of T, can be "Command" or "Stream")static bool verify(EntryBase * arg)
verify thatarg
is anEntry<T>
String info()
get aString
rappresentation of the node
The following is an example of usage of this library along with some commands already defined in the header files provided by the library
#include "runner.hpp" // includes Arduino.h for String and Serial
#include "runner.ArduinoCommands.hpp" // Arduino functions mapped as commands
#include "runner.utils.hpp" // FreeMemory, Info and other commands
runner::Interface os = runner::Interface();
runner::Shell shell = os.shell(); // input output and error are linked to Serial by default
void setup() {
Serial.begin(9600);
os.add("serial", &Serial); // register Serial as "serial"
os.add("pm", new runner::cmd::PinMode()); // add pinMode as command
os.add("dw", new runner::cmd::DigitalWrite());
// the following command prints the amount of free memory
os.add("free", new runner::cmd::FreeMemory());
// the following command prints a list of all the added entries
os.add("info", new runner::cmd::Info());
// run the shell when an event is triggered
// by default it is bound to the loop event
shell.bind(/* "loop" */);
os.trigger(runner::setup); // trigger the "setup" event
}
void loop() {
os.trigger(runner::loop); // trigger the "loop" event
}
When the above sketch is flashed on the Arduino board, the sketch will provides
- a shell interface on the Serial line
pm
command for pin mode,dw
command for digital write- a couple of utils commands to have some useful information about the system.
You can write on the serial interfce of the Arduino ide pm 13 1
, then dw 13 1
and dw 13 0
to make Arduino blink at your will, also you can write free
to get info about free memory and info
to have name and type of all the entries added to the the system
How to use shipped commands and define custom ones
This commands are already in the library, in the corresponding files
PinMode
DigitalRead
DigitalWrite
AnalogRead
AnalogWrite
Tone
The above commands simply map the corresponding Arduino functions
StreamDump
prints a hex dump of a stream, can be useful for sd or eeprom inspectionFreeMemory
prints the amount of free memoryEcho
prints the argumentCat
prints the content of a fileInfo
prints the list of the streams and commands registered in the systemStatus
prints the sequence of commands to restore the current status*Trigger
calls all commands with a given nameFlush
invokes flush method on a streamShell
invokes a shell on a stream and executes the commands on that strean until data is available
*Status is available for custom commands that implement the void status(const String &, Stream &) const
method. All the shipped commands are stateless.
The following is an example of the whole command library included in a sketch
#include "runner.hpp"
#include "runner.ArduinoCommands.hpp"
#include "runner.utils.hpp"
runner::Interface os = runner::Interface();
runner::Shell mainShell = os.shell();
void setup() {
Serial.begin(9600);
os.add("serial", &Serial);
os.add("pm", new runner::cmd::PinMode());
os.add("dr", new runner::cmd::DigitalRead());
os.add("dw", new runner::cmd::DigitalWrite());
os.add("ar", new runner::cmd::AnalogRead());
os.add("aw", new runner::cmd::AnalogWrite());
os.add("to", new runner::cmd::Tone());
os.add("dump", new runner::cmd::StreamDump());
os.add("free", new runner::cmd::FreeMemory());
os.add("echo", new runner::cmd::Echo());
os.add("cat", new runner::cmd::Cat());
os.add("info", new runner::cmd::Info());
os.add("status", new runner::cmd::Status());
os.add("trigger", new runner::cmd::Trigger());
os.add("flush", new runner::cmd::Flush());
os.add("sh", new runner::cmd::Shell());
os.trigger(runner::setup);
mainShell.bind();
}
void loop() {
os.trigger(runner::loop);
}
You can call the above commands from the Serial popup of the Arduino IDE by sending a string with the command name followed by the argument string you want to pass, for example dw 13 1
will turn on the integrated led.
Anyway on an atmega328p system the above sketch is enough to fill the whole memory leaving only a couple of hundred of bytes free, it is recommended indeed to use at least an Arduino mega for large sketches.
In order to have a custom function you can extend the Command class or instantiate a FuncCommand class with a function reference as argument.
#include "runner.hpp"
runner::Interface os = runner::Interface();
runner::Shell shell = os.shell(); // input output and error are linked to Serial by default
// extend the Command clsss
struct MyCmd : runner::Command {
RUNNER_COMMAND(MyCmd) // adds String type() const
String last;
int8_t run(
runner::Interface * scope,
String args[], // args[0] = cmd name, args[1] = argument string
Stream & in,
Stream & out,
Stream & err
){
// do some stuff
last = args[1];
out.println("doing some cool stuff");
return 0;
}
virtual void status(const String & name, Stream & o) const {
// print the sequene of commands to restore current status
if(last){
o.println(name + " " + last);
}
}
};
MyCmd my{};
// use FuncCommand
auto my2 = new runner::FuncCommand([](
runner::Interface * scope,
String args[], // args[0] = cmd name, args[1] = argument string
Stream & in,
Stream & out,
Stream & err
){
out.println("doing some lambda stuff");
return (int8_t) 0;
}, "MyFuncCmd");
void setup() {
Serial.begin(9600);
os.add("foo", &my);
os.add("bar", &my2);
// adding an instance of the following command will allows
// invokation from the shell of the status method of MyCmd
os.add("status", new runner::cmd::Status());
shell.bind(); // run the shell on an event (default "loop")
os.trigger(runner::setup); // run all commands named "setup"
}
void loop() {
os.trigger(runner::loop); // run all commands named "loop"
}
// call foo and bar from Arduino ide
For FuncCommand
s it is not possible to save the status.
Input, output and error redirection can be made from the shell using
<
for input, >
for output and &
for error.
For example it is possible to invoke a command from the shell in this way
echo hello world > serial
Is it possible to combine all the redirections. For example, assuming there are an eeprom stream and an i2c stream registered on the system
cat > serial < i2c & eeprom
To store and restore the status of the system, you can use the status command in combination with the shell command For example, assuming an eeprom stream is availabe, it is possible to store the status on it with
status > eeprom
then you can restore it by calling (assuming that you have registered the Shell
command as sh
)
sh < eeprom
It is also possible to combine multiple commands in a sequential pipeline using the symbol |
followed by the size of the buffer shared among the commands
echo hello world |12 cat > serial
Feel free to contribute by adding requests and pull requests, I will appreciate!