Designing and using an IoT controller command protocol is as simple as typing some text. Ideally, the system should be foolproof.
Remember the old days when you needed to type commands to you computer? Commands were specific text strings. (I guess Linux still works this way.) Anyway, even if you use a GUI, a mouse click or screen swipe still results in a text command under the hood. You just don’t see it.
Simply put, a protocol is a formal way to control behaviors. You can design an IoT controller command protocol by defining a restricted set of text strings. These, and only these, tell your Arduino or NodeMCU what to do. Here is command protocol for my relay switch.
/* Command Variables */ String CommandStrings[] = { "C", "X", "?", "R", "F", "M1", "M2" }; enum CommandIDs { setControlOn, // C Set controller ON setControlOff, // X Set controller OFF setReportStatus,// ? or ?=0..4 request status report setReceiver, // R=0..4 select one of four radios setFilter, // F=0..1 insert Medium Wave filter setLoopMode_1, // M1=1..4 to switch AAA-1C modes setLoopMode_2 // M2=1..4 };
Each of my commands is a one or two character text followed by an optional parameter, separated by an “=” sign. Each of my commands is then ended or delimited with a Line Feed, “\n” or ASCII 10. Any other text arriving at the microcontroller outside of this restricted set is ignored.
My IoT controller command protocol is supported by some important definitions.
#define COMMAND_ERROR_NONE 0 #define COMMAND_ERROR_INVALID_COMMAND 1 #define COMMAND_ERROR_BAD_PARAMETER 2 #define COMMAND_ERROR_NOT_CONNECTED 3 #define COMMAND_ERROR_BUSY 4 #define PARAM_NULL 99999 // Magic number for null parameter #define COMMAND_COUNT 7 // Number of different commands
Every time a command is received, the controller sends back an error code. Obviously, you desire a “no error” response. A wide variety of technologies can be used to end and receive simple ASCII text for control.
IoT Controller Command Protocol Actions
Inside your code, you need a routine to parse commands and take appropriate actions. Here’s my approach.
int ProcessCommands() { String Command; String Parameter; long ParamValue = 0; int i; int CommandIndex = -1; /* Pre process Buffer - Parse */ i = (Buffer.indexOf("=")); if (i == -1){ Command = Buffer; Parameter = "";} else { Command = Buffer.substring(0, i); Parameter = Buffer.substring(i + 1); } /* Identify command index */ for (int k = 0; k < COMMAND_COUNT; k++) { if (Command.equals(CommandStrings[k])) { CommandIndex = k; break; } } if (CommandIndex == -1) return COMMAND_ERROR_INVALID_COMMAND; //E2 if (Parameter == "") { ParamValue = PARAM_NULL;} else { ParamValue = Parameter.toInt(); } if ((!Status.Connected) && (CommandIndex != 0)) { return COMMAND_ERROR_NOT_CONNECTED; //E1 } switch (CommandIDs(CommandIndex)) { case setControlOn: Respond("+"); delay(50); Status.Connected = true; break; case setControlOff: Status.Connected = false; Initialize(); delay(50); Respond("-"); break; case setReportStatus: if (ParamValue == PARAM_NULL) { Respond(Report(0));} else { Respond(Report(ParamValue)); } break; case setReceiver: if ((ParamValue < 0) || (ParamValue > 4)) { return COMMAND_ERROR_BAD_PARAMETER; } SetReceiver(ParamValue); break; case setFilter: if ((ParamValue < 0) || (ParamValue > 1)) { return COMMAND_ERROR_BAD_PARAMETER; } SetFilter(ParamValue); break; case setLoopMode_1: if ((ParamValue < 0) || (ParamValue > 3)) { return COMMAND_ERROR_BAD_PARAMETER; } SetLoopMode(1, ParamValue); break; case setLoopMode_2: if ((ParamValue < 0) || (ParamValue > 3)) { return COMMAND_ERROR_BAD_PARAMETER; } SetLoopMode(2, ParamValue); break; } return COMMAND_ERROR_NONE }
As you can see, the first part of this code is all about parsing and formatting, making sure the commands are proper. Then, the second part takes action based on what the command wants to do, and makes sure your parameters are within range. Actions are taken by a group of routines later in the program to turn relays on and off.