Talk To Your Device
Subscribe to a topic on your device, then send a command to that topic from the app.
In the previous lesson, we sent data from the device to the platform. Here we will explore how to subscribe to topics with our devices, so we can send MQTT messages to our devices. In this lesson, we'll send an MQTT from the app. But you can actually send a message from any device that is connected to the broker.
This is standard MQTT — publish and subscribe work in both directions. The dashboard is just another MQTT client.
An aside on working with the SDK
You can start this lesson from wherever you last left off, so long as you at least have a device that is connecting to the MQTT broker. Find these two chunks for code in your main:
// REGULAR SUBSCRIPTIONS AND MESSAGE HANDLING Part 1 ----------------------------------
static const char * subscription_list[] = {
"~/~/cypress",
nullptr // keep this
};
void messageHandler(char * topic, char * payload) {
Serial.println(payload); // replace this with something more interesting when you are ready
}and...// REGULAR SUBSCRIPTIONS AND MESSAGE HANDLING Part 2 -----------------------------------
mqtt.set_subscriptions(subscription_list);
mqtt.set_callback(messageHandler);As you can see from the labels, those two sections go together. This is the first time you are seeing a pattern that will show up repeatedly.
Let's take a step back. In order to build scalable IoT systems, you need to have some core libraries that can be used across different projects, without customizations in the code. Take MQTT, for instance. We need an MQTT module that works the same across all of our projects, so that we can be confident that it will behave as expected.
BUT, individual devices will have device-specific code that changes from project to project. When that device-specific code is MQTT-related, we need a mechanism to use that code in the module without needing to modify the core MQTT library itself. Topic subscriptions are a good example of this.
So solution that we use through the SDK is to 'inject' certain login into the different modules, so that the modules can execute device-specific logic, without actually requiring a modification to the module itself.
You already see this pattern in the PubSubClient library itself, if you were to use it. In that case, you need to send a static function to the library, and then the library calls that function every time a message comes in. So, you define how a message is handled. But you need to send that logic to the library itself so it can call your function when the message comes in.
That's what's going on here, but we are setting up two injections. The first is our subscription list. The second is the function that we write to handle messages when they come in.
If you are already familiar with the PubSubClient library, you are probably aware that it gives you the payload as a raw binary, and then you need to convert it to a string to use it in your code. This device SDK already does that conversion for you, so you just need to send two buffers in your function - one to receive the topic and the other to receive the payload.
Part 1 - Define your device-specific logic
The subscription list is a null-terminated array of topic strings. The SDK subscribes to each one when MQTT connects, and re-subscribes automatically on every reconnect. Replace ~/~/cypress with ~/command
static const char * subscription_list[] = {
"~/command", // modified
nullptr // keep this
};It's important to leave that nullptr in the array. That is the signal to the MQTT module that it's reached the end of the list.
After you have a list of subscriptions, you need to define how the messages that come in our handled. Whenever an MQTT message is sent to the topics listed in your subscription list, the MQTT module will call the function you provide, which must take two parameters, so it can take in both the topic and the payload that came in for the messages.
void messageHandler(char * topic, char * payload) {
Serial.println(payload); // replace this with something more interesting when you are ready
}For all of the different messages that come in, you can provide just one function to handle them. So, you will probably have some sort of filter in this function to take different actions based on the different messages that come in.
Part 2 - Inject your logic into the MQTT module
Now that you have defined what you are going to listen two and what you are going to do when messages come in, you should had that information over to the MQTT module, so it can execute your plans on your behalf.
We do this by calling public methods available from the MQTT modules to accept your submissions. Call these functions inside the setup() of your main code:
mqtt.set_subscriptions(subscription_list);
mqtt.set_callback(messageHandler);Try it out
Let's set up a basic command in the app. In the Controls tab, create a new command, and click to edit it. Name it "Command" and select "freeform MQTT".
when you edit your freeform command, notice that it will always start with your MQTT username as the first element of the topic. Add the second element of your topic, command. Select a fixed payload, and enter the word "restart".
If you upload your firmware now, then when you click the Command button, you will see in the serial monitor of your device that the command was received. But right now, we are just logging it. Let's do something more interesting.
Rather than just log, let's check out payload, and whenever our payload is restart, we will call for the device to restart.
void messageHandler(char * topic, char * payload) {
if (strcmp(message, "restart") == 0) {
Serial.println("got a call to restart");
ESP.restart();
}
}Try it out. Your device should restart when you
How it works
When you clicked the button, the dashboard published the message blink to the topic larry/device1/command on the MQTT broker. Your ESP32 was subscribed to that exact topic, so the broker delivered the message. Your callback ran, matched the payload, and blinked the LED.
There's nothing OhioIoT-specific about this. The dashboard is just an MQTT client that publishes to the topic you configured. Anything that can publish MQTT can control your device — a Node.js script, a Python program, another ESP32, or a phone app.
You can prove to yourself that this is plain MQTT. MQTT Explorer is a really good tool for just monitoring your MQTT connection and confirming it will work. In MQTT Explorer, after connecting, you can publish to[MQTT username]/commmand and the device will still receive the message.
