The command handler simplifies receiving, decoding and dispatching serial commands. It is included in our Arduino library.
The command handler recognizes commands received from the serial port which begin with a ‘!
‘ and end with carriage return/new-line characters (\r\n
).
For example: !SetOnTime 100\r\n
.
After you’ve registered command names with command functions, the command handler will call the registered function whenever it receives the command name.
This page covers handling serial commands in your Arduino sketch. Getting started building an Arduino interface shows you how to build an interface panel in MegunoLink to send commands when a button is clicked.
The video below provides a quick overview of using the command handler in your Arduino sketch.
Anatomy of a Command
A command is a serial message made up of 4 parts:
- Start marker: a
!
that marks the beginning of the command. - Command name: the unique name that identifies the command made up from upper and lower case letters, numbers and underscore (
_
) characters - Command parameters: optional parameters for the command; parameters are separated by spaces
- End marker: a carriage return and new-line character marking the end of the command (
\r\n
)
Set up the Command Handler
To use the command handler, in your Arduino sketch, you need to:
#include "CommandHandler.h"
- Create a global variable
CommandHandler<> SerialCommandHandler;
- Register the commands you want to handle in your
setup()
function - Implement the command functions
- Call
SerialCommandHandler.Process()
in yourloop()
function to receive and decode commands
Boiler plate code to call functions for each command sent is generated automatically in the designer’s example code window. Click the Command Handler button, then copy and paste into your Arduino sketch to quickly get started.
A Command Handler Variable
1 2 3 |
#include "MegunoLink.h" CommandHandler<> SerialCommandHandler; |
The global SerialCommandHandler;
looks after three things:
- keeps track of the functions to call when a command is received;
- reads serial messages and looks for commands;
- calls the registered function whenever it finds a command
The command handler is a template so make sure to include the <>
right after the CommandHandler
type name. You can customize the command handler’s behavior by adding parameters between the <>
‘s.
Registering Commands
Registering commands in your setup()
function links the name of the command to the function that will be called when the command is received.
Register commands using: SerialCommandHandler.AddCommand(F("CommandName"), Cmd_FunctionToCall)
. Where:
CommandName
is the command text to matchCmd_FunctionToCall
is the Arduino function that will be called when the command is received
Only 10 commands can be registered by default, but you can reserve space for more. Your Arduino sketch will print AddCommand: full
if you try adding too many commands.
Implementing Command Functions
Command functions are called by the SerialCommandHandler
whenever it receives the registered command name. They need to be defined like this:
1 2 3 4 |
void Cmd_FunctionToCall(CommandParameter &Parameters) { // Do something when the command is received } |
Parameters
gives you access to any parameters included after the command name. It must be included, even if you don’t use any parameters.
Working with Command Parameters
Parameters are the optional part of a command which follow the command name. There is 1 space between the command and the first parameter; each parameter is separated by 1 space.
Parameters can be retrieved inside the command handler function. The CommandParameter ¶meters
argument includes methods to retrieve each parameter value in turn. These methods include:
parameters.NextParameter()
: returns the next parameter as textparameters.NextParameterAsInteger(Default)
: returns the next parameter as an integer value, orDefault
value if there aren’t any more parametersparameters.RemainingParameters()
: returns the rest of the parameters (including any spaces) up to the end marker
For example, the following command handler function retrieves a single integer parameter from a command like !SetOnTime 120\r\n
. This implementation just prints the value, but you could save it or use it to perform an action such as move a robot:
1 2 3 4 5 6 |
void Cmd_SetOnTime(CommandParameter ¶meters) { int Value = parameters.NextParameterAsInteger(10); Serial.print("New on time = "); Serial.println(Value); } |
Handling Unknown Commands
Sending incorrect commands is the most common problem when using the serial command handler. Building an Interface Panel visualizer to send the commands is an easy way to avoid typing mistakes when sending commands. You can also setup a function to handle unknown commands. It can print an error message to let the user know the command was received, but not understood.
39 40 41 42 |
void Cmd_Unknown() { Serial.println(F("I don't understand")); } |
Feeding the Command Handler
The command handler needs to regularly check the Serial
port for new characters to make sure it doesn’t miss any commands. This happens inside its Process
function. Call the Process
function as part of your main loop:
1 2 3 4 |
void loop() { SerialCommandHandler.Process(); } |
If the serial command handler gets too many characters before receiving a complete command then it will print Ovrflw
. By default, messages can include up to 30 characters. You can increase the internal buffer size if 30 characters isn’t enough for your commands.
Arduino Command Processing Example
This example extends the classic Arduino blink program with serial commands to control how long the LED is on and off. It uses
- the command handler library to process serial commands, and
- a MegunoLink Interface Panel visualizer to send serial commands
The sketch accepts these commands:
!SetOnTime 40\r\n
Sets the time the LED remains on to 40 ms!SetOffTime 100\r\n
Sets the time the LED remains off to 100 ms!ListAll\r\n
Lists the current setting of all parameters
Values for the on- and off-time commands come from number controls on the interface panel. Getting started building an Arduino interface walks through making an interface panel for the Blink 2.0 project.
First we’ll look at the entire sketch and then break it down:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 |
#include "CommandHandler.h" CommandHandler<> SerialCommandHandler; long LastBlink = 0; // Time we last blinked the LED int OnTime = 10; // Amount of time the LED remains on [milliseconds] int OffTime = 100; // Amount of time the LED remains off [milliseconds] const int LEDPin = 13; // Pin the LED is attached to void setup() { Serial.begin(9600); Serial.println(F("Blink 2.0")); Serial.println(F("==========")); // Setup the serial commands we can repond to SerialCommandHandler.AddCommand(F("OnTime"), Cmd_SetOnTime); SerialCommandHandler.AddCommand(F("OffTime"), Cmd_SetOffTime); SerialCommandHandler.AddCommand(F("ListAll"), Cmd_ListAll); SerialCommandHandler.SetDefaultHandler(Cmd_Unknown); pinMode(LEDPin, OUTPUT); } void loop() { // Check for serial commands and dispatch them. SerialCommandHandler.Process(); // Update the LED uint32_t uNow = millis(); if (uNow - LastBlink < OnTime) { digitalWrite(LEDPin, HIGH); } else { digitalWrite(LEDPin, LOW); } if (uNow - LastBlink > OnTime + OffTime) { LastBlink = uNow; } } void Cmd_ListAll(CommandParameter &Parameters) { Serial.print(F("OnTime [ms]=")); Serial.println(OnTime); Serial.print(F("OffTime [ms]=")); Serial.println(OffTime); } void Cmd_SetOnTime(CommandParameter &Parameters) { OnTime = Parameters.NextParameterAsInteger(OnTime); } void Cmd_SetOffTime(CommandParameter &Parameters) { OffTime = Parameters.NextParameterAsInteger(OffTime); } void Cmd_Unknown() { Serial.println(F("I don't understand")); } |
Detailed Example Walk-through
Blinking the LED
Blinking the LED using delay(…)
won’t work for this sketch because the delay
function prevents the Arduino from doing much else. That’s a problem for our command handler. If the Arduino is busy waiting for the next blink it can’t receive serial commands.
So we use time to decide when to blink the LED instead. The code responsible for this is:
5 6 7 |
long LastBlink = 0; // Time we last blinked the LED int OnTime = 10; // Amount of time the LED remains on [milliseconds] int OffTime = 100; // Amount of time the LED remains off [milliseconds] |
26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
void loop() { // Check for serial commands and dispatch them. SerialCommandHandler.Process(); // Update the LED uint32_t uNow = millis(); if (uNow - LastBlink < OnTime) { digitalWrite(LEDPin, HIGH); } else { digitalWrite(LEDPin, LOW); } if (uNow - LastBlink > OnTime + OffTime) { LastBlink = uNow; } } |
The first block sets up the variables needed:
LastBlink
: the time that the LED was last turned on. This comes from the Arduino’smillis()
timer, and is measured in milliseconds since the Arduino was last reset.OnTime
: time that the LED spends on (in milliseconds).OffTime
: time that the LED spends off (in milliseconds).
The OnTime
and OffTime
variables are changed when we receive the serial commands !SetOnTime
and !SetOffTime
(more on that a bit later). Changing these values will change the blink rate.
The second block lives in the program loop. It is responsible for turning the LED on and off. Each time through the loop we:
- put the current time in
uNow
, and - if the time since we last turned the led on (
LastBlink
) is less than the amount of time the LED should be on then the LED is turned on, otherwise it it is turned off. - If it has been longer than the total amount of time the LED should be on and off, then
LastBlink
is reset so blinking continues.
So the blinking rate will change when the OnTime
and OffTime
variables are changed. Before looking at how these are changed by the serial commands, lets look at how the command handler is setup.
Set up the Command Handler
There are three blocks of code that setup and run the command handler.
First, include the library and create global command handler variable:
1 2 3 |
#include "CommandHandler.h" CommandHandler<> SerialCommandHandler; |
The SerialCommandHandler
variable is declared at file scope so it can be used in the setup()
and loop()
functions.
Next, add each command to the command handler inside the setup
function. Of course, the Arduino Serial
library has to be initialized too:
11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
void setup() { Serial.begin(9600); Serial.println(F("Blink 2.0")); Serial.println(F("==========")); // Setup the serial commands we can repond to SerialCommandHandler.AddCommand(F("OnTime"), Cmd_SetOnTime); SerialCommandHandler.AddCommand(F("OffTime"), Cmd_SetOffTime); SerialCommandHandler.AddCommand(F("ListAll"), Cmd_ListAll); SerialCommandHandler.SetDefaultHandler(Cmd_Unknown); pinMode(LEDPin, OUTPUT); } |
Finally, at the start of the loop()
function, the Process()
method is called. This method receives characters from the serial port and calls the appropriate function whenever a command is received:
26 27 28 29 |
void loop() { // Check for serial commands and dispatch them. SerialCommandHandler.Process(); |
Command Functions
Command functions are called by the SerialCommandHandler
when the matching serial command is received. This example registers 3 command functions:
Cmd_SetOnTime
: called for theSetOnTime
command.Cmd_SetOffTime
: called for theSetOffTime
command.Cmd_ListAll
: called for theListAll
command.
Here’s the implementation for the Cmd_SetOnTime
function (the others are similar):
56 57 58 59 |
void Cmd_SetOnTime(CommandParameter &Parameters) { OnTime = Parameters.NextParameterAsInteger(OnTime); } |
The SerialCommandHandler
passes a CommandParameter
variable to the function when it is called. This variable provides access to any parameters following the command.
In this example, the new time to turn the LED on is retrieved as an integer parameter using Parameters.NextParameterAsInteger(OnTime)
and saved in the global variable OnTime
to set a new flashing rate.
The current value of OnTime
is passed to NextParameterAsInteger
, which uses it as the default value. So if the command sent doesn’t include a valid integer, the function will simply return the current value by default and the flash rate won’t change.
You must include CommandParameter ¶meters
as a parameter, even if you don’t use it in the function. Check out the command handler documentation for other methods you can use to retrieve parameters.
Updating an Interface Panel
Here we set up an Arduino sketch to process commands sent from an interface panel. Your Arduino code can also send commands to MegunoLink to change control values. Take a look at the set parameters example and video below for more information.