The purpose of this library is to ease the implementation of a command set.
Each command has an identifier, which is just a simple string. The identifier can be followed by 0 or more characters, which are passed the command implementation as its only argument.
Defining what those characters mean is up to the function that executes the command.
Each command + parameters string is supposed to be terminated by \r or \n or both. These characters are not passed to the command function.
A simple example:
LIGHTON
LIGHTOFF
the command identifier here is "LIGHT". The function implementing it would work more or less like this:
if arg == "ON"
turn on the light
else if arg == "OFF"
turn off the light
else
print "ERR"
endif
If we had a single temperature sensor we could have a
TEMP
command that would just ignore its argument and simpli print back the sensor value.
A command to get or set the current date could be:
DATE2012-01-01
which would work as follow:
if strlen(arg) > 0
d = parse date (arg)
if date was properly formatted
set current date = d
else
print "ERR"
endif
else
d = curr date
print(d)
endif
Here's a complete example sketch:
#include <SerialCommands.h>
const byte LED_PIN = 13;
SerialCommands commands;
// Purpose: making sure the sketch is still alive.
// Expected argument: none.
// Behaviour: prints back the command identifier.
void pingFunction(struct SerialCommand* cmd, const char* str) {
Serial.println(cmd->cmdString);
}
// Purpose: turn on or off a led. The pin number is code-defined.
// Expected argument: '0' or '1' to turn the led off or on respectively.
// Behaviour: if the argument is '0', drives the pin LOW
// if the argument is '1', drivers the pin HIGH.
void ledFunction(struct SerialCommand* cmd, const char* str) {
if (str == NULL) {
printErr();
}
if (str[0] == '0') {
digitalWrite(LED_PIN, LOW);
printOk();
}
else if (str[0] == '1') {
digitalWrite(LED_PIN, HIGH);
printErr();
}
else {
printErr();
}
}
// Purpose: read one or more analog channels.
// Expected argument: a list of analog channels to read, for example 01234
// Behaviour: for each char, reads the corresponding analog channel, or prints error if the char is outside the '0'..'5' range.
void readAnalogsFunction(struct SerialCommand* cmd, const char* str) {
if (str == NULL) {
printErr();
}
for (byte i = 0; i < strlen(str); i++) {
char ch = str[i];
if (ch >= '0' && ch <='5') {
byte anCh = ch - '0';
Serial.println(analogRead(anCh));
}
else {
printErr();
}
}
}
void cmdNotFound(const char* receivedString) {
if (receivedString != NULL) {
Serial.print(receivedString);
Serial.println(": command not found");
}
}
void bufferFull() {
Serial.print("Serial buffer full. Size = ");
Serial.println(commands.BUFFER_SIZE);
}
void printOk() {
Serial.println("OK");
}
void printErr() {
Serial.println("ERR");
}
// Print a list of available commands.
void listCommands() {
Serial.print(commands.getNumCommands());
Serial.println(" commands defined.");
for (byte i = 0; i < commands.getNumCommands(); i++) {
Serial.print("command ");
Serial.print(i, DEC);
Serial.print(": ");
Serial.println(commands.getCmdString(i));
}
}
void setup() {
Serial.begin(115200);
pinMode(LED_PIN, OUTPUT);
commands.setCmdNotFoundCallback(cmdNotFound);
commands.setBufferFullCallback(bufferFull);
commands.addCommand("PING", pingFunction);
commands.addCommand("LED", ledFunction);
commands.addCommand("ANREAD", readAnalogsFunction);
listCommands();
}
void loop() {
if (Serial.available() > 0) {
commands.parseCh(Serial.read());
}
}
The attached zip file contains the library, an example and doxygen-generated documentation (work in progress...).
I hope somebody will find this useful. Comments and suggestions are welcome.
TODO: the SerialCommand struct should probably be turned into a proper class, to avoid exposing its internals to the command implementations...
SerialCommands.zip (109 KB)