Deploying devices using Particle’s Cloud is easy. Publishing to the cloud is dead easy. Finding a reliable way to do the opposite can be.. complicated. This is especially true for devices that have many modes or functions. Luckily there’s a solution.
Your very own command protocol.
In this blog post, we’ll be talking about you can use them in your own code. So, by the end of the post you’ll be an expert at developing your own command set, using a FIFO/queue to process them and more.
Let’s dig in.
Why use a command set?
Using a command set has two main benefits. First, it creates a common structure to facilitate communication. This simplifies messaging between cloud and device with only a byte or two of data!
Secondly, it allows you to set up a straight forward way of processing events as they occur. You can even use these events to change the state of the device. This kind of functionality is also known as a state machine. In this example, we’ll be focusing more on sending one-off commands. That means we won’t be retaining any state which makes this example much simpler!
How do you organize them?
First of all, we need to create the “variables” we’ll be using to represent the many states. In my practice I use a typedef enum
to crate all the states. For this example, we’ll call ours Control_Command_t
.
// Constants used for recieving commands from Particle
typedef enum {
Party_Mode = 0,
Sad_Mode,
Silent_Mode
} Control_Command_t;
The value of each enum
entry starts at 0 and increases by one for each entry. For example, Sad_Mode
is equal to 1, Silent_Mode
is equal to 2 and so on. If you want to override a certain number, you can also set them manually similar to Party_Mode
.
Then, we’ll set up a queue
for adding and removing commands as they happen. Fortunately, Particle Workbench includes the standard C++ queue.h
!
#include <queue>
A queue
allows you to add objects of any type. The queue keeps each entry in sequential order. When they’re removed, it removes oldest one first. This functionality is known as FIFO or first in, first out. It’s extremely common in the programming land at all levels.
You can place your queue
above your setup()
function. You can see that it’s defined to use Control_Command_t
. (That part is important!)
// FIFO for events that occur. They are generated by:
// - Self generated using timers
// - From Particle Cloud/Customer App
std::queue<Control_Command_t> events;
Once created, you’re ready to push events on and pop them off. We’ll jump into that in the next section.
How do you use them to shape the operation of your device?
The source of events can come in many places. Some can get generated by other embedded devices. Some are internal that occur when a timer fires. Some are generated by the cloud. In most of these cases, these events get generated by interrupt.
Interrupts occur when events happen outside of the main loop()
of your program. You can set up anything to interrupt the execution of your program. For example, you can set up a GPIO to send an interrupt event if the pin changes. Another example is setting up a subscription to a cloud or mesh function.
For example, here is a cloud function that watches for a message called “party.” Upon receiving the message, queue up a Party_Mode
event!
Particle.subscribe("party",partyHandler);
Where partyHandler
looks like this:
void partyHandler() {
// Queue up another Party_Mode
events.push(Party_Mode);
}
This will generate Party_Mode
and enqueue it. This is a relatively quick operation. That means you can easily do this in an interrupt routine without bogging down your device.
But, why don’t we enable Party_Mode
in partyHandler
itself?
Interrupts, by nature, stop your application code from running. When they take too long to execute, other important functions can’t execute. This could lead to unresponsiveness or cause the device to fault & restart.
So where do we do the processing?
In your loop()
function.
Here’s an example of what it would look like for processing events in a FIFO manor.
void loop() {
// Check if event queue is not empty
if( !events.empty() ) {
// Get the next event
Control_Command_t event = events.front();
// Remove it from the queue
events.pop();
// Print out event name:
Log.info("%s",Control_Command_Names[event]]);
// Now process the event
switch (event)
{
case Party_Mode:
Log.info("Dance dance dance.");
break;
case Sad_Mode:
Log.info("Looks like you need a picker upper.");
break;
case Silent_Mode:
Log.info("Silence is golden.");
break;
default:
break;
}
// Do some other stuff....
}
In the example above, you can see that the front of the queue gets removed. Then, we use a switch
statement to go through each possible outcome. Don’t forget the default
statement if you don’t define all of the commands!
Code execution happens inside the main loop rather than the interrupt context. It’s good practice unless your application is time constrained. Using Particle Mesh devices alone for these applications is not recommended. Use an external chip like a SAMD21 to do all the dedicated high speed stuff!
Debugging
When debugging, you can an array of c-strings and use them to help identify which state you’re in. We’ll map each entry to the same value as each of the commands. Here’s an example:
const char *Control_Command_Names[] = {
"Party Mode",
"Sad Mode",
"Silent Mode",
};
To use them, you can do something like this:
Log.info("%s",Control_Command_Names[event]]);
This will cause the logger to print out the command name as a text representation. 😎👍
Conclusion
Processing events in the main loop allow for consistent and reliable device operation. Using a queue
with command definitions is one way of handling many events at one time. Consider it another tool in your programming toolbox to get your projects done!
Last Modified: 2020.3.7