Strings, bytes, char arrays MQTT arghgh

Hi - I am having some problems !

I am trying to implement the MQTT library to have a bit of a play with some automation here at home. I have messages being sent around the network and I'm using the MQTT library on an EtherTen.

The MQTT library uses a callback function that is fired whenever an MQTT message hits the topic that the Arduino is subscribed to. Anyway you wont to know about that to help me solve this !

The callback function takes this form:

void callback(char* topic, byte* payload,int length) {
//stuff in here
}

I am having problems with the payload parameter. It is (I think) an array of bytes. Now I'm not sure how I need these presented to the part of my code hat make decisions. Let's go back a step...

I want to pass a message along the lines of:

PUMP:ON

Now if I pass that message via MQ I guess I will end up with an array of byte values.

Should I put all these bytes back together into a String and then use an if/else statement to loop over the possible values in the message, ie. PUMP:OFF or VALVE:1:OPEN

If so, how do I do this ? If not what would you suggest I do to be able to perform conditional logic based on the values in the messages I pass.

Any help would be appreciated.

This is the code I have written:

Its the callback function that I am stuck on. If I pass an MQTT message I can see by the serial console that the Arduino receives and processes the message. For example, if I send the message PUMP:ON I see 50 55 4D 50 3A 4F 4E in the serial console. I need to know how to get something more useful that I can loop over in the callback function. For example the message PUMP:ON should send a digital I/O PIN HIGH to turn on a relay. That sort of thing.

My main issue is that I dont know if I should be doing all of this in HEX, or Strings.

#include <SPI.h>
#include <Ethernet.h>
#include <PubSubClient.h>


byte mac[] = {0x00, 0xAA, 0xBB, 0xCC, 0xDE, 0x02 };
byte ip[] = { 192, 168, 10, 10 };
byte server[] = { 192, 168, 10, 129 };
const int pin13 = 13;

long lastRun = 0;

char* pumpControlTopic="tanks/controller1";

PubSubClient client(server, 1883, callback);

void callback(char* topic, byte* payload, unsigned int length) {
  //check for paylaod; is it there at all ?
  if (length > 0) {
    if (strcmp(topic,pumpControlTopic)==0) {
      for (int i=0; i<length; i++) {
          if (payload[i] < 16)
          Serial.print("0");
          Serial.print(payload[i],HEX);
          Serial.print(" ");
      } 
      Serial.println();
    }
  }
  //after we work out how to get the paylod we can turn stuff on and off
  //if(mqttMsg == "PUMP:ON") {blah blah blah}
  //this sort of thing
  
}

void setup()
{
  Serial.begin(9600);
  pinMode(pin13,OUTPUT);
  digitalWrite(pin13,LOW); //something strange going on; pin13 is still lit up...
  Ethernet.begin(mac, ip);
  if (client.connect("arduino")) {
    client.publish("tanks/status","CONTROLLER1:STARTUP");
    client.subscribe("tanks/controller1");
 
 //char *p = sz;
 //char *str;
 //while ((str = strtok_r(p, ";", &p)) != NULL) // delimiter is the semicolon
 //  Serial.println(str);
} 
  
  
}

void loop()
{
  client.loop();
  if ((millis() - lastRun) > 10000) {
    lastRun = millis();
  }

}

50 55 4D 50 3A 4F 4E
P U M P : O N

Please, don't use Strings, just simple strings.

Oh - why is that ? I have read that Strings (capital S) use more memory where strings (which I believe are just byte arrays with a terminating /o character) use less.

How do I write an if/else statement using these 'simple' strings ?

if (strcmp (myString, "PUMP:ON") == 0)

Of course, you need to ensure "myString" is correctly terminated with a '\0'.

AWOL:

if (strcmp (myString, "PUMP:ON") == 0)

Of course, you need to ensure "myString" is correctly terminated with a '\0'.

But how do I go from the byte array to a string object ? At the moment, by looping over the payload byte array I just get individual bytes. I need a string ?

As far as I understand the MQTT format, the content of the payload variable is not defined (by MQTT) but can be anything. You seem to opt for an ASCII based protocol which is easy to debug and easily portable but not as efficient as a binary format would be.
If you choose an ASCII format, I would still strongly suggest to define standard structure of such a string, like "TYPE:POSITION:VALUE", so in your example it would not be "PUMP:ON" but "PUMP:1:1" or "VALVE:1:0" instead of "VALVE:1:OPEN". This makes it much easier to parse the incoming message and organise the call distribution. You can use strtok() then to split the message into the type, position and value. Position and value can be fed to atoi immediately after the split up and you already have integers.

The conversion from a byte array to a character array is simply a type cast:

char *cstring = (char *) payload;

pylon:
As far as I understand the MQTT format, the content of the payload variable is not defined (by MQTT) but can be anything. You seem to opt for an ASCII based protocol which is easy to debug and easily portable but not as efficient as a binary format would be.
If you choose an ASCII format, I would still strongly suggest to define standard structure of such a string, like "TYPE:POSITION:VALUE", so in your example it would not be "PUMP:ON" but "PUMP:1:1" or "VALVE:1:0" instead of "VALVE:1:OPEN". This makes it much easier to parse the incoming message and organise the call distribution. You can use strtok() then to split the message into the type, position and value. Position and value can be fed to atoi immediately after the split up and you already have integers.

The conversion from a byte array to a character array is simply a type cast:

char *cstring = (char *) payload;

Excellent advice - I will try this tomorrow.

I have the ultrasonic sensor set up and working, I have SSRs set up for mains switching - it's just this bl00dy string that has me stumped!

Hi- so I tried this today:

  char *cstring = (char *) payload;

and just like the previous poster said the code does indeed convert my array of bytes back to a string...of sorts.

What I end up with is the message I sent, in this case, PUMP:1 (pump on command) and some leftover characters from a previous string I sent via the code below:

client.publish("tanks/status","CONTROLLER1:STARTUP");

So what I see then via the serial terminal is:

PUMP:ONARTUP

This is a concatenation of: PUMP:ON and CONTROLLER1:STARTUP

Now what I dont understand is why this is happening. Obviously something is pointing the memory location of my new variable, cstring, to the same memory location as used to hold the string I send via the MQTT library.

The sketch is below:

#include <SPI.h>
#include <Ethernet.h>
#include <PubSubClient.h>
#include <LiquidCrystal.h>

byte mac[] = {0x00, 0xAA, 0xBB, 0xCC, 0xDE, 0x02 };
byte ip[] = { 192, 168, 10, 10 };
byte server[] = { 192, 168, 10, 129 };

PubSubClient client(server, 1883, callback);

LiquidCrystal lcd( 8, 9, 4, 5, 6, 7 );


void callback(char* topic, byte* payload, unsigned int length) {
  lcd.print(payload[0],HEX);
  lcd.print(":");
  lcd.print(payload[0],DEC);
  lcd.setCursor(0, 1);
  char *cstring = (char *) payload;
  lcd.print(cstring);
  Serial.println(cstring);
}

void setup() {
  Serial.begin(9600);
  Ethernet.begin(mac, ip);
  lcd.begin(16, 2);
  //lcd.print("hello, world!");
  if (client.connect("arduino")) {
    lcd.print("MQTT Subscribed");
    lcd.setCursor(0, 1);
    client.publish("tanks/status","CONTROLLER1:STARTUP");
    client.subscribe("tanks/controller1");
  }
}

void loop() {
  client.loop(); //check for MQ messages
  
}

My phone browser doesn't render your code properly - do you terminate the string correctly?

AWOL:
My phone browser doesn't render your code properly - do you terminate the string correctly?

Not sure I follow you. I am following the previous poster's advice to take the byte array and covert to a char array ( a pointer ??). Are you saying I need to add in a '\0' to the converted char array ?

Why then do I end up with the remainder of that previously used variable ? I am assuming that somehow I have two things pointing to the same piece of memory but have no idea how.

Thanks in advance.

Are you saying I need to add in a '\0' to the converted char array ?

Yes. The payload array is NOT NULL terminated. That is why you need to pass the length.

You may also need to call lcd.clear() before printing to the LCD, to eliminate non-overwriiten characters remaining visible.

PaulS:

Are you saying I need to add in a '\0' to the converted char array ?

Yes. The payload array is NOT NULL terminated. That is why you need to pass the length.

You may also need to call lcd.clear() before printing to the LCD, to eliminate non-overwriiten characters remaining visible.

I thought so too but I get the same output on the serial console so it must be in memory like that.

void callback(char* topic, byte* payload, unsigned int length) {
  lcd.print(payload[0],HEX);
  lcd.print(":");
  lcd.print(payload[0],DEC);
  lcd.setCursor(0, 1);
  char *cstring = (char *) payload;
  lcd.print(cstring);
  Serial.println(cstring);
}

Use the method that takes a string and length. eg.

  lcd.print(payload, length);

Ha - thanks but I also just got it working by adding the null terminator !!!

lcd.clear();
  payload[length] = '\0';
  String strPayload = String((char*)payload);
  lcd.print(strPayload);

As long as you are confident that this is OK. That payload and length are passed to you. Modifying one past the end of the payload may be corrupting memory.

ahh OK - your way does may sense.

Actually, when I try to do it with:

lcd.print(payload,length);

I see this error:

"call of overloaded 'print(byte* &, unsigned int&)' is ambiguous"

Assuming lcd is derived from Print, try:

lcd.write(payload,length);

pylon:
As far as I understand the MQTT format, the content of the payload variable is not defined (by MQTT) but can be anything. You seem to opt for an ASCII based protocol which is easy to debug and easily portable but not as efficient as a binary format would be.
If you choose an ASCII format, I would still strongly suggest to define standard structure of such a string, like "TYPE:POSITION:VALUE", so in your example it would not be "PUMP:ON" but "PUMP:1:1" or "VALVE:1:0" instead of "VALVE:1:OPEN". This makes it much easier to parse the incoming message and organise the call distribution. You can use strtok() then to split the message into the type, position and value. Position and value can be fed to atoi immediately after the split up and you already have integers.

The conversion from a byte array to a character array is simply a type cast:

char *cstring = (char *) payload;

I know this is an old thread now but I have resurrected this project and I am interested in the binary based format you mentioned above. I cant work out how to implement this.