Skip to content

Instantly share code, notes, and snippets.

@ilovetogetspamed
Created September 26, 2019 12:19
Show Gist options
  • Save ilovetogetspamed/f64b72a274794600f0ad63b6d6b7c7eb to your computer and use it in GitHub Desktop.
Save ilovetogetspamed/f64b72a274794600f0ad63b6d6b7c7eb to your computer and use it in GitHub Desktop.
Simple Command Line parser for Arduino
/*****************************************************************************
How to Use CommandLine:
Create a sketch. Look below for a sample setup and main loop code and copy and paste it in into the new sketch.
Create a new tab. (Use the drop down menu (little triangle) on the far right of the Arduino Editor.
Name the tab CommandLine.h
Paste this file into it.
Test:
Download the sketch you just created to your Arduino as usual and open the Serial Window. Typey these commands followed by return:
add 5, 10
subtract 10, 5
Look at the add and subtract commands included and then write your own!
*****************************************************************************
Here's what's going on under the covers
*****************************************************************************
Simple and Clear Command Line Interpreter
This file will allow you to type commands into the Serial Window like,
add 23,599
blink 5
playSong Yesterday
to your sketch running on the Arduino and execute them.
Implementation note: This will use C strings as opposed to String Objects based on the assumption that if you need a commandLine interpreter,
you are probably short on space too and the String object tends to be space inefficient.
1) Simple Protocol
Commands are words and numbers either space or comma spearated
The first word is the command, each additional word is an argument
"\n" terminates each command
2) Using the C library routine strtok:
A command is a word separated by spaces or commas. A word separated by certain characters (like space or comma) is called a token.
To get tokens one by one, I use the C lib routing strtok (part of C stdlib.h see below how to include it).
It's part of C language library <string.h> which you can look up online. Basically you:
1) pass it a string (and the delimeters you use, i.e. space and comman) and it will return the first token from the string
2) on subsequent calls, pass it NULL (instead of the string ptr) and it will continue where it left off with the initial string.
I've written a couple of basic helper routines:
readNumber: uses strtok and atoi (atoi: ascii to int, again part of C stdlib.h) to return an integer.
Note that atoi returns an int and if you are using 1 byte ints like uint8_t you'll have to get the lowByte().
readWord: returns a ptr to a text word
4) DoMyCommand: A list of if-then-elses for each command. You could make this a case statement if all commands were a single char.
Using a word is more readable.
For the purposes of this example we have:
Add
Subtract
nullCommand
*/
/******************sample main loop code ************************************
#include "CommandLine.h"
void
setup() {
Serial.begin(115200);
}
void
loop() {
bool received = getCommandLineFromSerialPort(CommandLine); //global CommandLine is defined in CommandLine.h
if (received) DoMyCommand(CommandLine);
}
**********************************************************************************/
//Name this tab: CommandLine.h
#include <string.h>
#include <stdlib.h>
//this following macro is good for debugging, e.g. print2("myVar= ", myVar);
#define print2(x,y) (Serial.print(x), Serial.println(y))
#define CR '\r'
#define LF '\n'
#define BS '\b'
#define NULLCHAR '\0'
#define SPACE ' '
#define COMMAND_BUFFER_LENGTH 25 //length of serial buffer for incoming commands
char CommandLine[COMMAND_BUFFER_LENGTH + 1]; //Read commands into this buffer from Serial. +1 in length for a termination char
const char *delimiters = ", \n"; //commands can be separated by return, space or comma
/*************************************************************************************************************
your Command Names Here
*/
const char *addCommandToken = "add"; //Modify here
const char *subtractCommandToken = "sub"; //Modify here
/*************************************************************************************************************
getCommandLineFromSerialPort()
Return the string of the next command. Commands are delimited by return"
Handle BackSpace character
Make all chars lowercase
*************************************************************************************************************/
bool
getCommandLineFromSerialPort(char * commandLine)
{
static uint8_t charsRead = 0; //note: COMAND_BUFFER_LENGTH must be less than 255 chars long
//read asynchronously until full command input
while (Serial.available()) {
char c = Serial.read();
switch (c) {
case CR: //likely have full command in buffer now, commands are terminated by CR and/or LS
case LF:
commandLine[charsRead] = NULLCHAR; //null terminate our command char array
if (charsRead > 0) {
charsRead = 0; //charsRead is static, so have to reset
Serial.println(commandLine);
return true;
}
break;
case BS: // handle backspace in input: put a space in last char
if (charsRead > 0) { //and adjust commandLine and charsRead
commandLine[--charsRead] = NULLCHAR;
Serial << byte(BS) << byte(SPACE) << byte(BS); //no idea how this works, found it on the Internet
}
break;
default:
// c = tolower(c);
if (charsRead < COMMAND_BUFFER_LENGTH) {
commandLine[charsRead++] = c;
}
commandLine[charsRead] = NULLCHAR; //just in case
break;
}
}
return false;
}
/* ****************************
readNumber: return a 16bit (for Arduino Uno) signed integer from the command line
readWord: get a text word from the command line
*/
int
readNumber () {
char * numTextPtr = strtok(NULL, delimiters); //K&R string.h pg. 250
return atoi(numTextPtr); //K&R string.h pg. 251
}
char * readWord() {
char * word = strtok(NULL, delimiters); //K&R string.h pg. 250
return word;
}
void
nullCommand(char * ptrToCommandName) {
print2("Command not found: ", ptrToCommandName); //see above for macro print2
}
/****************************************************
Add your commands here
*/
int addCommand() { //Modify here
int firstOperand = readNumber();
int secondOperand = readNumber();
return firstOperand + secondOperand;
}
int subtractCommand() { //Modify here
int firstOperand = readNumber();
int secondOperand = readNumber();
return firstOperand - secondOperand;
}
/****************************************************
DoMyCommand
*/
bool
DoMyCommand(char * commandLine) {
// print2("\nCommand: ", commandLine);
int result;
char * ptrToCommandName = strtok(commandLine, delimiters);
// print2("commandName= ", ptrToCommandName);
if (strcmp(ptrToCommandName, addCommandToken) == 0) { //Modify here
result = addCommand();
print2("> The sum is = ", result);
} else {
if (strcmp(ptrToCommandName, subtractCommandToken) == 0) { //Modify here
result = subtractCommand(); //K&R string.h pg. 251
print2("> The difference is = ", result);
} else {
nullCommand(ptrToCommandName);
}
}
}
@tgopaul
Copy link

tgopaul commented Apr 11, 2023

Greatings.
I found your CommandLine.h code while looking for examples on command input.

I don't see a license file in your github.
I've added your CommandLine.h file with my edits to my project at https://github.com/Protospace/pinballwizard/tree/master/atmel
How would you like me to give attribution for your code?
If I give attribution someone who finds my project can link back to your git hub.

I am using CommandLine to receive commands over USB serial to access the shared RAM of a pinball machine we are upgrading for our maker club.

Tim G

@ilovetogetspamed
Copy link
Author

There's no license. Its Public Domain. Enjoy!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment