Automated Chicken Coop pt. 7 – Remotely Controlling The Coop pt. 6 “Our First MQTT Message”

“Our First MQTT Message”

Now its time to send our first message via MQTT! Open your Arduino IDE and ensure that your NodeMCU board is connected to your computer. Please check to make sure it is still assigned the same Port, and change the port in the Arduino IDE if necessary.  I will do my best to write all the coding examples with the knowledge that readers may not know how to program in any language.  As such, if you know how to code, and what is occurring – you can jump to the bottom of this post to grab the full code and bypass the tutorial.

About The Arduino Language

Arduino’s programming language is a variation of C.  If you have any experience with a C language, such as C++ or C# you will recognize some things, and others will come to you quickly.

 

When you start a new Sketch in Arduino you are always presented with the following lines setup() and loop().  These are known as functions.  Functions are blocks of code that can be called from other places in code to perform their actions.  Some functions will return values, others – like the setup() and loop() functions are of the type “void” meaning they do not return a value when they are done.

setup() = This is where the code that will run immediately on the boot of your device is place.  Code placed here will only run once, when the device is first powered on.

loop() = code here is continually run, over and over and over again.  Hence it’s name “loop()”.

You must always have both a setup() and loop() function in any Arduino sketch you write.

In Arduino C, Libraries provide extended functionality to the Arduino IDE.  Almost every sketch you create will include one or more Libraries.  Some of these can be found using the Arduino IDE by going to Sketch > Include Library > Manage Libraries.

Others you can find on the web and install by going to Sketch > Include Library > Add .ZIP Library and selecting the ZIP file which the Library is in.

For this part of our setup we need to include two libraries:

ESP8266WiFi  & PubSubClient  both of which can be found by searching in Manage Libraries.  After you have installed the Libraries, go to the top of the code editor and include the following lines before the void setup() line.

#include <ESP8266WiFi.h>
#include <PubSubClient.h>

Now all the code we write will have access to the extended functionality these libraries contain.  You should always place libraries at the start of your code.

Press Enter a few times to place some space between the functions and your includes.  Now we will declare some variables which will remain constant in your program.

Variables are placeholders in memory.  Once declared, they take up a specific point in memory while being used.

Below your includes type the following lines, making edits for your information.

//WiFi and MQTT Variables

const char* ssid = "YOURWIFISSID";

const char* password = "YOURWIFIPASSWORD";

const char* mqtt_server = "YOURRASPBERRYPIHOSTNAME";

Now we will step thru what you just typed.

You may notice the top line with // at the beginning looks different than the rest.  This is called a comment.  Comments are used to help programmers and coders understand what is happening in a section of code.  While they are optional, and only aid the person coding, it is HIGHLY recommended to use Comments so you can come back at a later date and understand what is happening in the code quicker.

const = indicates this variable will be constant, it cannot be changed later on in the code.  It will always contain the information that was placed in it during its declaration.

char* = char indicates the type of variable: a character variable.  Generally, a character variable can only contain one letter/number, but surely your wifi password is more than 1 character right?  Thats where the * comes in, * indicates this is a pointer variable, otherwise it is pointing to a specific location in memory that is storing the value of your wifi password.  When it is called, it creates a character array storing each character of your password in turn.

ssid, password, mqtt_server = these are the names of the variables we just declared.  When you want to use a variable, or change the value of a variable that is not a constant, you will use the variable name to reference the variable.  If the MQTT server fails to connect – put your Raspberry Pi’s static IP address here instead.

YOURWIFISSID, YOURWIFIPASSWORD, YOURRASPBERRYPIHOSTNAME = These are the values stored in their respective variables.  Each time you call ssid for example, it will return the value YOURWIFISSID.

***NOTE ON YOURRASPBERRYPIHOSTNAME***
If the MQTT broker fails to connect – put your Raspberry Pi’s static IP address here instead.

Below the variables you just created, we are going to create instances of functions stored in the Libraries we added.

//MQTT and ESP Setup

WiFiClient espClient;

PubSubClient client(espClient);

Now comes the fun parts – actually coding our NodeMCU to accept a message and show us the message.

in your empty void setup() place the following code between the brackets (  {  }  ).

void setup() {

Serial.begin(115200);

setup_wifi();

client.setServer(mqtt_server, 1883);

client.setCallback(callback);

}

Now lets step thru this code:

Serial.begin(115200); = here we are starting serial communications, these are very handy when debugging your program.  You can use the serial monitor to view messages you print to the serial port.

setup_wifi(); = here we are calling a function that we will be adding shortly, this function will connect our NodeMCU to our wireless network.

client.setServer(mqtt_server, 1883); = here we are telling the NodeMCU what the server hosting our MQTT broker is, and what TCP port number to communicate with.  By default, MQTT protocol uses port 1883.

client.setCallback(callback); = what the NodeMCU should do when it receives a message from the Broker.  In this case, it sends the message to the callback function.

All of this occurs the second the NodeMCU receives power.  It immediately tries to connect to WiFi and on success sets the MQTT Broker information and what to do when receiving a message.

Next – lets add some code to our void loop() section.

void loop() {

//Check if the client is connected to the MQTT Server, if not call function to connect and subscribe

if (!client.connected()) {

reconnect();

}

client.loop();

}

Here we have our first conditional statement.  In programming a conditional statement tells code what to when a condition is true/false.

If we were to read out the if statement above it would read like this – “If client is NOT connected, execute the reconnect function”.  The exclamation point before “client.connected()” says “client.connected() = false”.

If the client is connected to the MQTT broker, all the code inside the IF statement will be bypassed, and the only code that will run inside the loop() function is client.loop();

So far we have called three functions that have yet to be written(and one more that will be declared later for a total of 4).  So lets fix that starting with the setup_wifi() function.

Below your loop function enter the following lines of code, here you will be creating your own function, so dont leave out any code.

void setup_wifi() {
delay(20);
Serial.println();
Serial.print("Connecting to ");
Serial.println(ssid);

WiFi.begin(ssid, password);

while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}

Serial.println("");
Serial.println("WiFi Connected");
Serial.println("IP Address is: ");
Serial.println(WiFi.localIP());
Serial.println("");

}

So lets step thru whats happening here.

First thing you may have noticed were ALOT of “Serial.println” lines in code.  Remember earlier in the setup() function we had a line that read Serial.begin(115200); that we said would enable communications between the NodeMCU and the PC it was attached to for debugging purposes?  Well now we are actually sending messages to the PC so we can monitor the status of whats going on.

WiFi.begin(ssid, password); = this is where we actually begin the connection process to the WiFi network.

Right below this we have another conditional statement

while(WiFi.status() != WL_CONNECTED) = speaking this out it reads “While the WiFi.Status IS NOT “WL_CONNECTED” do the following”.

The code inside the While statement will loop until WiFi.status reports that it is connected, then the code below will execute.  Here take note of the line “Serial.println(WiFi.localIP());”  here we will print out the IP ADDRESS assigned to the NodeMCU board.

 

Now lets create the “reconnect()” function.

void reconnect() {
// Loop until we're reconnected
while (!client.connected()) {
Serial.print("Attempting MQTT connection...");
// Attempt to connect
if (client.connect("ESP8266 Client")) {
Serial.println("connected");
// ... and subscribe to topic
client.subscribe("test/topic1");
client.subscribe("test/topic2");
client.subscribe("test/topic3");
} else {
Serial.print("failed, rc=");
Serial.print(client.state());
Serial.println(" try again in 5 seconds");
// Wait 5 seconds before retrying
delay(5000);
}
}
}

Once again we will step thru this code.

We start with a While Statement, this statement actually contains the following IF/ELSE statement.  This is what is known as nesting.  The IF/ELSE statement is NESTED within in While Statement.

While the client is not connected we will run thru the If/else statement inside.

So IF the client Connects, it will print out “connected” and proceed to subscribe to the topics listed.

There are two things to take notice of here.  In the client.connected line we include “ESP8266 Client” this is the name the MQTT Broker will know the NodeMCU board by, if you have multiple boards, each must have its own unique name.  Lastly, when we subscribe to topics you might have noticed we now have 3 topics, each with a first name of test.  You can think of this as folders within a folder. ” test” is the main folder and each topic is a subfolder.  This allows us to create main topics and subtopics for use later.

If the client does not connect (for example the raspberry pi is off) it will print the lines under the Else clause and pause the code for 5 seconds before returning to the beginning of the While Statement.

Finally lets decide what happens when the NodeMCU receives a message it has subscribed to by creating the callback function.  Here we will learn how to also publish messages back to the MQTT Broker.

void callback(char* topic, byte* payload, unsigned int length) 
{

int topicSwitch;
String strTopic = String((char*)topic);
String strPayload = String((char*) payload);

//Determine which topic the message was sent to and 
//set the topicSwitch Variable to a number representing 
//that topic.
if (strTopic == "test/topic1"){
topicSwitch = 1;
}else if (strTopic == "test/topic2") {
topicSwitch = 2;
}else if (strTopic == "test/topic3") {
topicSwitch = 3;
}else {
topicSwitch = 4;
}
switch (topicSwitch) {
case 1:
{
// Response to messages sent on the "test/topic1" topic
Serial.println("The " + strTopic + " message was received");
break;
}
case 2:
{
// Response to messages sent on the "test\topic2" topic
sendMsg("test/test2", "Message on test/topic2 was received");
break;
}
case 3: 
{
//Response to messages sent on the "test/topic3" topic
Serial.println("The " + strTopic + " message was received");
break;
}
}

}

 

So there is a good amount of code here, more than you’ve handled in previous functions.  And there is a new type of conditional statement as well the Switch Statement.

So lets start with the first line of the code – where we declared the function.

This might look different than other functions because we’ve included what looks like variables inside the ().  And infact – we have.  These are variables that must be passed to the callback function for it to work.  You’ll see how passing variables to a function works later when we call the sendMsg function.  For now just know that when the NodeMCU receives a message from the MQTT Broker, the “setCallback” line from our setup takes the message and divides it into various segments to pass to the callback function.

So now we have some variables declared inside the function.

int topicSwitch;
String strTopic = String((char*)topic);
String strPayload = String((byte*) payload);

These are LOCAL variables – meaning they can only be accessed inside the callback function, and once the callback function is done – they are destroyed.

You have two new types of variables in those lines –

  • int = an integer, or whole number
  • string = an actual string of characters.  You’ve already used strings when printing via Serial in other functions.  Strings are signified by being contained within ” “.

But look at the String variables – we are assigning them a value, but using the variables passed to the function at the beginning.  The String((char*)topic)  and String((char*) payload) lines mean Convert the Value stored in the char* variable topic to a string.  The same goes for the payload variable.

The reason we are declaring the payload line with the value of ((char*) payload) instead of byte* is char is a form of a byte.  By declaring it as a char, we indicate that this should be read as characters rather than bytes.

Now for the IF statement.  Notice how we have two = signs in each condition?  We use two signs when asking “Is this equal” you only use one equal sign when you are setting the value of a variable.  Ignore the code inside each IF and ELSE IF brackets and try to say the statement like we’ve done on other statements.

You should end up reading it as “If (condition is true) then (perform code) else if (this condition is true) then (perform code)” and so on.

Lastly the Switch Statement.

Switch statements are like IF Statements such that they compare a variable to a given value and execute code if that variable matches the value.  In fact, had we wanted, we could have completely left out the IF Statement above and used a Switch Statement alone!

When you declare a switch statement with the switch keyword you also provide the variable which will be compared.  In our case – topicSwitch.

Inside the switch statement you then create cases.  Each case represents the comparison for the variable.  so in:

case 1:  – we are comparing the value stored in topicSwitch to the number 1.  If they match, we execute the code inside that case.

The “break” line of code tells the program to jump out of the case statement, without it when the program matched case 1  it would also execute the code for case 2, case 3, case 4 and so on.  At times it can be useful to forgo a break, but for our purposes we need break lines.

Lets take a look at the code inside Case 2:

Here we are calling a function, but we’ve included some strings beside it inside the ().  We are passing the sendMsg function these values, we could have also passed actual variables.  This is called passing variables.  Since the variables inside the callback function are inaccessible to code outside the function, this allows us to pass the values of the variables to another function.

So last function – I PROMISE – we need to create the sendMsg function then we will upload it all to our NodeMCU and perform tests to make sure everything works as expected.

void sendMsg(String loTopic, String loMsg){

String topicToSend = loTopic;
String msgToSend = loMsg;

if (client.publish((char*) topicToSend.c_str(), (char*) msgToSend.c_str()) )
{
Serial.println("Publish Succeeded!");
}
else
{
Serial.println("Publish Failed!");
}

return;

}

When the sendMsg function gets called, the strings we included get stored in loTopic and loMsg respectively.

We then store those variables in the String variables topicToSend, and msgToSend, we didnt have to do this, but it helps with clarifying.

Now we move into the IF statement.  If the message is successfully published via the publish functionality, it will print “Publish Succeeded!” to our Serial Monitor.  After the message has either been sent, or failed to send, the return line tells the program to return to the end of the line of code that called it.

SO NOW TO TEST EVERYTHING

Click the check button on the Arduino IDE to check for any errors, if you have any refer to the code above(or below) and fix the errors you have.  After the check performs without errors first open the Serial Monitor of the Arduino IDE and set the baud rate to 115200 then click the Arrow button to send the code to your NodeMCU.

Once the code is loaded the NodeMCU will restart, and you should start getting information, starting with the WiFi connecting returned to your Serial Monitor.

Open two terminals on your raspberry pi and type the following line into the first one.

mosquitto_sub -h localhost -t “test/test2”

In the second terminal send a message to the test topics

(test\topic1 , test/topic2  & test/topic3)

using

mosquitto_pub -h localhost -t (your chosen topic) -m “Your message” -q 1

Notice what happens when you send a message to the different topics. Sending a message to test\topic2 should cause a message to be returned to the first terminal window.  The other topics should print out information to your Serial Monitor.

Congratulations you’ve sent and received your first MQTT messages!!!

 

FULL CODE BELOW

#include <ESP8266WiFi.h>
#include <PubSubClient.h>

//WiFi and MQTT Variables

const char* ssid = "YOURWIFISSID";

const char* password = "YOURWIFIPASSWORD";

const char* mqtt_server = "YOURRASPBERRYPIHOSTNAME";

//MQTT and ESP Setup

WiFiClient espClient;
PubSubClient client(espClient);

void setup() {

Serial.begin(115200);

setup_wifi();

client.setServer(mqtt_server, 1883);

client.setCallback(callback);

}

void loop() {

//Check if the client is connected to the MQTT Server, if not call function to connect and subscribe

if (!client.connected()) {

reconnect();

}

client.loop();

}

void setup_wifi() {
delay(20);
Serial.println();
Serial.print("Connecting to ");
Serial.println(ssid);

WiFi.begin(ssid, password);

while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}

Serial.println("");
Serial.println("WiFi Connected");
Serial.println("IP Address is: ");
Serial.println(WiFi.localIP());
Serial.println("");

}

void reconnect() {
// Loop until we're reconnected
while (!client.connected()) {
Serial.print("Attempting MQTT connection...");
// Attempt to connect
if (client.connect("ESP8266 Client")) {
Serial.println("connected");
// ... and subscribe to topic
client.subscribe("test/topic1");
client.subscribe("test/topic2");
client.subscribe("test/topic3");
} else {
Serial.print("failed, rc=");
Serial.print(client.state());
Serial.println(" try again in 5 seconds");
// Wait 5 seconds before retrying
delay(5000);
}
}
}

void callback(char* topic, byte* payload, unsigned int length) 
{

int topicSwitch;
String strTopic = String((char*)topic);
String strPayload = String((char*) payload);

//Determine which topic the message was sent to and 
//set the topicSwitch Variable to a number representing 
//that topic.
if (strTopic == "test/topic1"){
topicSwitch = 1;
}else if (strTopic == "test/topic2") {
topicSwitch = 2;
}else if (strTopic == "test/topic3") {
topicSwitch = 3;
}else {
topicSwitch = 4;
}
switch (topicSwitch) {
case 1:
{
// Response to messages sent on the "test/topic1" topic
Serial.println("The " + strTopic + " message was received");
break;
}
case 2:
{
// Response to messages sent on the "test/topic2" topic
sendMsg("test/test2", "Message on test/topic2 was received");
break;
}
case 3: 
{
//Response to messages sent on the "test/topic3" topic
Serial.println("The " + strTopic + " message was received");
break;
}
}

}

void sendMsg(String loTopic, String loMsg){

String topicToSend = loTopic;
String msgToSend = loMsg;

if (client.publish((char*) topicToSend.c_str(), (char*) msgToSend.c_str()) )
{
Serial.println("Publish Succeeded!");
}
else
{
Serial.println("Publish Failed!");
}

return;

}

Leave a Reply

Your email address will not be published. Required fields are marked *