MQTT + Blynk

Hello. This might not be directly related to Blynk, but I’m trying to use MQTT with Blynk and NodeRed, with the PubSubClient library, and I can’t wrap my head around the callback function… maybe someone could help:

void callback(char* topic, byte* payload, unsigned int length) {
  Serial.print("Message arrived [");
  Serial.print(topic);
  Serial.print("] ");
  for (int i = 0; i < length; i++) {
    Serial.print((char)payload[i]);
  }
  Serial.println();

  // Switch on the LED if an 1 was received as first character
  if ((char)payload[0] == '1') {
    digitalWrite(BUILTIN_LED, LOW);   // Turn the LED on (Note that LOW is the voltage level
    // but actually the LED is on; this is because
    // it is active low on the ESP-01)
  } else {
    digitalWrite(BUILTIN_LED, HIGH);  // Turn the LED off by making the voltage HIGH
  }

First of all, what does the * characters represent in “char*” and “byte*” represent? I’ve done some research and I’ve concluded they’re some kind of pointers… but ehat exsclty are they pointing to?

Second, I don’t understand this part at all:

  for (int i = 0; i < length; i++) {
    Serial.print((char)payload[i]);
  }
  // Switch on the LED if an 1 was received as first character
  if ((char)payload[0] == '1') {

Why do I use (char)payload when this variable is declared as byte*? When would (char)payload[0] be 1 and why?

Thanks

I absolutely hate working with Char variable types!
My solution - and this might just be lazy, but it works perfectly for me - is to convert everything to strings. I love strings because you can easily concatenate, dissect and compare them without any problems.

I structure my MQTT topics so that for each device that I’m working with I have a “base topic” that remains the same, and just the last few parts of the topic string change depending on what I’m doing. for example, if I have a device that monitors light levels and controls a relay my MQTT topic might look like this…

Home/Garden/Lights/Zone1/Light_Sensor/Level
Home/Garden/Lights/Zone1/Relay/CMD
Home/Garden/Lights/Zone1/Relay/Status

etc. etc.

Home/Garden/Lights/Zone1 is my 'base topic" for that device, which I declare just once…

String base_mqtt_topic = "Home/Garden/Lights/Zone1"; 

MY MQTT callback then looks like this…

void MQTTcallback(char* topic, byte* payload, unsigned int length)
{
  Serial.print(F("Message arrived ["));
  Serial.print(topic);
  Serial.print(F("] ["));
  for (int i=0;i<length;i++)
  {
    Serial.print((char)payload[i]);
  }
  Serial.println(F("]"));    
  
  String msg_topic = String((char *)topic);
  String msg_payload = String((char *)payload);
  msg_payload.remove(length); // Trim any unwanted characters off the end of the string
  // We now have two string variables, 'msg_topic' and 'msg_payload' that we can use in 'if' statements below... 


  if (msg_topic==base_mqtt_topic + "/Relay/CMD")
  {  
    if (msg_payload=="1")
    {
      setOutputLoadPower(1);
      return;
    }
  
    if (msg_payload=="0")
    {
      setOutputLoadPower(0);
      return;
    }
  }


  // Handle messages from other topics in here,
  // just like we did with the /Relay/CMD topic above.
  // DON'T FORGET to subscribe to the topic in void MQTT_Connect()

} // End of void MQTTcallback

You’ll see that I split the incoming message into two string elements, the topic and the payload, which makes it simpler to do if statements and direct the program flow accordingly. Of course you could easily do this without using the “base_topic” approach, but it makes the code simpler and it’s much easier top avoid typos in the topic. It also makes it much simpler if you have multiple devices doing similar tasks. In this situation you just change the base topic slightly and keep virtually everything else the same. For example, to add another zone to my garden lights I would just change my base topic to Zone 2…

String base_mqtt_topic = "Home/Garden/Lights/Zone2"; 

When I write messages back to the MQTT server then I have to concatenate the topic I want to write to on to the "base topic, and convert this into a character. If I’m sending other string values then these also need to be converted into char variables, but this is fairly easy using the c_str() command.

My MQTT_Connect() function looks like this…

void MQTT_Connect()
{
  Serial.print(F("Connecting to MQTT...  "));
  // We'll connect with a Retained Last Will that updates the '.../Status' topic with "Dead" when the device goes offline...
  // Attempt to connect...
  /* 
  MQTT Connection syntax:
  boolean connect (client_id, username, password, willTopic, willQoS, willRetain, willMessage)
  Connects the client with a Will message, username and password specified.
  Parameters
    client_id : the client ID to use when connecting to the server.
    username : the username to use. If NULL, no username or password is used (const char[])
    password : the password to use. If NULL, no password is used (const char[])
    willTopic : the topic to be used by the will message (const char[])
    willQoS : the quality of service to be used by the will message (int : 0,1 or 2)
    willRetain : whether the will should be published with the retain flag (int : 0 or 1)
    willMessage : the payload of the will message (const char[])
  Returns
    false - connection failed.
    true - connection succeeded
  */

  if(MQTTclient.connect(mqtt_client_id.c_str(), mqtt_username, mqtt_password, (base_mqtt_topic + "/Status").c_str(),0, 1, "Dead"))
  {
    // We get here if the connection was successful... 
    Serial.println(F("MQTT Connected"));
    mqtt_connect_count++;

    // Once connected, publish some announcements...
    // These all have the Retained flag set to true, so that the value is stored on the server and can be retrieved at any point
    // Check the .../Status topic to see that the device is still online before relying on the data from these retained topics
    MQTTclient.publish((base_mqtt_topic + "/MQTT_Client_ID").c_str(),mqtt_client_id.c_str(),true);   
    MQTTclient.publish((base_mqtt_topic + "/Status").c_str(),"Alive",true);
    MQTTclient.publish((base_mqtt_topic + "/Short_Filename").c_str(),short_filename.c_str(),true);                   // Sketch filename (without full path)
    MQTTclient.publish((base_mqtt_topic + "/Compiled_Date").c_str(),compiled_date.c_str(),true);                     // Sketch compilation date     
    MQTTclient.publish((base_mqtt_topic + "/Compiled_Time").c_str(),compiled_time.c_str(),true);                     // Sketch compilation time
    
    MQTTclient.loop();
    
    MQTTclient.publish((base_mqtt_topic + "/MAC_Address").c_str(),WiFi.macAddress().c_str(),true);                   // Device MAC Address
    MQTTclient.publish((base_mqtt_topic + "/IP_Address").c_str(),actual_ip_address.c_str(),true);                    // Device IP Address
    MQTTclient.publish((base_mqtt_topic + "/WiFi_Connect_Count").c_str(),String(wifi_connect_count).c_str(),true);   // Wi-Fi Connect Count
    MQTTclient.publish((base_mqtt_topic + "/MQTT_Connect_Count").c_str(),String(mqtt_connect_count).c_str(),true);   // MQTT Connect Count
    MQTTclient.publish((base_mqtt_topic + "/Uptime").c_str(),String(uptime).c_str(),true);           // Number 0f 49 day rollovers since booting
    MQTTclient.loop();
           

    // ... and then re/subscribe to the watched topics
    MQTTclient.subscribe((base_mqtt_topic + "/Relay/CMD").c_str());   // Watch the .../Relay/CMD topic for incoming MQTT messages

    // Add other watched topics in here...
  }
  else
  {
    // We get here if the connection failed...   
    Serial.print(F("MQTT Connection FAILED, Return Code = "));
    Serial.println(MQTTclient.state());
    Serial.println(); 
    /*
    MQTTclient.state return code meanings...
    -4 : MQTT_CONNECTION_TIMEOUT - the server didn't respond within the keepalive time
    -3 : MQTT_CONNECTION_LOST - the network connection was broken
    -2 : MQTT_CONNECT_FAILED - the network connection failed
    -1 : MQTT_DISCONNECTED - the client is disconnected cleanly
     0 : MQTT_CONNECTED - the client is connected
     1 : MQTT_CONNECT_BAD_PROTOCOL - the server doesn't support the requested version of MQTT
     2 : MQTT_CONNECT_BAD_CLIENT_ID - the server rejected the client identifier
     3 : MQTT_CONNECT_UNAVAILABLE - the server was unable to accept the connection
     4 : MQTT_CONNECT_BAD_CREDENTIALS - the username/password were rejected
     5 : MQTT_CONNECT_UNAUTHORIZED - the client was not authorized to connect * 
     */
  }
} // End of void MQTT_Connect

This gives you an example of how you can write string values to topics in other places within your code.

The only other thing I’d say is that I don’t like to mix Blynk and MQTT code on my devices. I have a couple of devices where I do this, so that I have a ‘kill switch’ that will work via Blynk even if my MQTT server is down, but it gets far too messy doing this for most projects.
I prefer to use Node-Red as the Blynk/MQTT bridge and use MQTT only code on my devices.

More info here, if you’ve not seen this already…

Pete.

1 Like

dragos,

I use string compare in my callback function. This method works very good for me. Example code below.

Paul

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

{

  if (strcmp(topic, "cmnd/inverter/power") == 0)

  {
    if (payload[0] == '1')

    {
      digitalWrite(inverterPin, HIGH); // Turn the inverter on
      client.publish("Inverter/Status", "ON");
    }

    else if (payload[0] == '0')

    {
      digitalWrite(inverterPin, LOW); // Turn the inverter off
      client.publish("Inverter/Status", "OFF");
    }
  }

   else if (strcmp(topic, "cmnd/PIR_sensor/power") == 0)
  {
    //turn PIRenable LOW and publish to the MQTT server a confirmation message

    if (payload[0] == '0')
    {
      digitalWrite(PIRenablePin, LOW);
      client.publish("PIR_sensor/status", "OFF");
    }

    //turn PIRenablePin HIGH and publish to the MQTT server a confirmation message
    else if (payload[0] == '1')
    {

      digitalWrite(PIRenablePin, HIGH);
      client.publish("PIR_sensor/status", "ON");
    }
  }

else if (strcmp(topic, "cmnd/Strobe/power") == 0)
  {
    //turn StrobeRelayPin LOW and publish to the MQTT server a confirmation message

    if (payload[0] == '0')
    {
      digitalWrite(StrobeRelayPin, LOW);
      client.publish("Strobe/status", "OFF");
    }

    //turn StrobeRelayPin HIGH and publish to the MQTT server a confirmation message
    else if (payload[0] == '1')
    {

      digitalWrite(StrobeRelayPin, HIGH);
      client.publish("Strobe/status", "ON");
    }
  }

}
1 Like

Thanks you both for your replies. I do a few more questions tough:

@PeteKnight I’m having a hard time understanding the MQTT_Connect function. I assume that “boolean connect” syntax shouldn’t have been commented out, right? Also, I don’t understand the purpose of each MQTTclient.publish… do they check if the credentials match, and if so, it subscribes to the Relay/CMD topic? And this might sound dumb, but why should I convert them to char types?

At the time, I’m only trying to use it to bridge 2 different boards, to turn on 2 lights simultaneously. But I’d like to learn and experiment more with it. I’d also like to try pairing them with other apps like Google Home, HomeKit or Eve, in the future. Next thing I’ll try is dimming a light through MQTT and Blynk using a slider. Would the method be simillar to only using the Blynk library to achieve the same thing? Would I be able to convert the string value received (“1” or “0”, or for the slider cas “0” to “1024”) to int type, and match the values with the analogWrite function?

And yes, I did read your post regarding the subject, it’s how I discovered NodeRed and MQTT :slight_smile:

@PWK thank you for your reply Paul. I’m unfamilliar with the strcmp(var1, var2) function… does it compare the content of var1 with the content of var2? If so, the “strcmp(var1,var2)==0” would be the same thing as “if(var1==var2), print(“true”)”, right?

Dragos

No, this is in-code documentation for the syntax used in this line…

As the documentation says, they publish some handy data to the server, with the retained flag set, so that I can easily establish things like the date and time that the firmware was compiled, the IP and MAC address of the device, connection counts etc.

The pubsub client requires char data types, so if these are held as strings (which they are in my case, as it much easier to manipulate them), they have to be converted to char data using the c_str() command.

Yes, send the slider value as an MQTT message to the device, convert the payload to an integer and do an analogWrite with it.

Pete.

1 Like

dragos,

String compare for each if statement. Example first if statement, if “cmnd/inverter/power” was published as the topic there will be a match and then it will check payload and turn on/off depending on payload value. If no match on topic it will compare next if statement topic. This is one of the easier methods to control devices using Blynk-Node-Red and MQTT.

Paul

1 Like

Hello again. I have found the ‘strcmp’ method easier to use. I have managed to control the state of an LED, however, I’m unable to control it’s intensity. Here’s my callback function:

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

  if (strcmp(topic, "test/message") == 0)
    {
    int i=payload[0];
    int y=map(i, 0, 100, 0 , 1024);
    analogWrite(led, y);
    Serial.println(payload[0]);
    }
}

Pete, I have followed your advice, trying to store the payload into an int type variable, though I am not sure if I’ve done it correctly.

I’m trying to control it using a slider from 0 to 100. While the slider works in the MQTT topic, showing correct values, the serial monitor only shows values between 50-60. It also does the same without the “map” function. What am I doing wrong?

I don’t think this works the way you expect it to.
If you try doing a serial print of i I think you’ll see why I prefer my approach of turning the data into a string as soon as it arrives, then converting that into an integer when required.

This becomes especially true when you want to send other data types backwards and forwards via MQTT.

Pete.

dragos,

I use json node to convert from string to integer and display value on node-red ui_gauge. I think this is similiar to what you are trying to accomplish.


Paul

The issue you face is that a char array, which is what arrives via the PubSubClient client, is just that - an array.
If that’s a single character (1 or 0) then this works…

if (payload[0] == '1')

But for anything longer than one digit, it won’t work.
10, 100, 1234 etc all have “1” as their initial character, so will all evaluate as true in the above if statement.

You’ll see that the PubSubClient examples use this code to print each character of the array…

for (int i = 0; i < length; i++)
{
    Serial.print((char)payload[i]);
}

When you’re sending your LED brightness commands, which will be in the range 0-100, you’ll need to to turn all of the characters in the topic array into an integer.

Pete.

@PWK it looks like it might work, but I think the main problem for me is the code, as I’m able to print slider values (0-1024) inside the MQTT Topic, as well as in a Blynk labeled value widget, using a read event node connected to a MQTT input node. The problem is the serial monitor doesn’t show the data the way I want it to. But this is just my theory, I will give it a try asap.

@PeteKnight that makes sense. Now I understand why I kept getting the same value for 1 and 100. While I did try the method you suggested in your first reply, I got a couple of errors in the connect function that I coulnd’t solve, given my undeveloped programming skills.

Dragos.

You’d be better-off using a Write event node for this.
When the value of the slider changes, the corresponding Write event node will immediately output the value, which can be sent straight to your MQTT Output node. You can do the mapping in your flow if you wish, with a Range node between the two nodes described above.

You’d get lots of “not declared in this scope” errors if you didnt decvlare sring variables for:

mqtt_client_id
mqtt_username
mqtt_password
base_mqtt_topic
short_filename
compiled_date
compiled_time
actual_ip_address

and integers for:

wifi_connect_count
mqtt_connect_count
uptime

The simple solution would be to just declare:

mqtt_client_id
mqtt_username
mqtt_password
base_mqtt_topic

and comment the rest of the lines out.
These additional lines were included because they show how to write different variable types to the MQTT server, and you’ll come back to these when you start to get more proficient.

Pete.

1 Like

I am. My flow is something like this: “Write Event-> MQTT Output” that stores the data from Slider V1 into the topic, and another “MQTT Input->Read Event” that outputs the data to the ‘Labeled Value widget’ in pin V2. I will comment out the lines you suggested tomorrow and maybe get it working. Thank you!

Dragos.

This makes no sense to me.
I’ve never had a need to use the Read Event node, or the corresponding BLYNK_READ(vPin) callback in a C++ sketch.

If you want to write data to a labelled value widget then you should use the Write node in Node-Red, which is the same as using Blynk.virtualWrite(vPin, value) in C++

Pete.

Sorry for the mispelling, I meant MQTT Input->Write Event. Here’s my flow:

Ignore the rest of the nodes…I’m still experimenting.

Also, one more quick question. Blynk nodes work flawlessly over 4G network, however, HomeKit only works while connected to my WiFi network…any ideas on why this might be happening?