I am not a seasoned C programmer, knowing just enough to usually figure things out.
But I am running into a problem with MQTT pubsub, specifically client.publish.
I can publish ("topic","payload") all day long, but when I try to publish a string variable for the payload, I get errors that are way beyond my pay grade.
//Global Variables
String payLoad;
//In loop()
//This is pretty straightforward, if val is 1, I want to publish "on", and if 0, publish "off".
//Sounds simple?
if val == 1{
payLoad="on";
}else{
payLoad="off";
}
client.publish("garden/light",payLoad);
The error:
"no matching function for call to 'PubSubClient::publish(const char [13], String&)'"
The workaround is, of course, to just publish the literal string in the payload:
if val == 1{
client.publish("garden/light","1");
}else{
client.publish("garden/light","0");
}
But I am mysterified as to why I can't send a string to client.publish().
What am I missing here?
Thanks
The publish functions expect char[] types to be passed in rather than Strings.
You need to use the String.toCharArray() function to convert your strings to the necessary type.
I do have a Payload:
{"Date":"1520125040","Node":"ESP8266-IOT-DH22","IP":"192.168.1.141","Temperatur":"21.80","Humidity":"53.20"} which ist a string.
When I run a client.publish(MQTT_Topic, (char*) payload.c_str()) I do get a Publish failed.
a client.publish(MQTT_Topic, "abc123") works find. Not quite sure how I can use the String.toCharArray()
Payload got created by:
String payload = "{"Date":";
payload += """;
payload += now;
payload += """;
payload += ","Node":";
payload += """;
payload += MQTT_CLIENT;
payload += """;
payload += ","IP":";
payload += """;
payload += WiFi.localIP().toString();
payload += """;
payload += ","Temperatur":";
payload += """;
payload += String(t);
payload += """;
payload += ","Humidity":";
payload += """;
payload += String(h);
payload += """;
payload += "}";
But I can not publish it any longer.
Also a
payload.toCharArray(MQTT_msg, 110);
// client.publish will publish the JSON string
if (client.publish(MQTT_Topic, MQTT_msg))
does fail. Again just a "Message" goes through???
//This is a simple code to handle payload as String.
const char* inTopic = "home/living";
const char* msgTopic = "home/msg";
char msgIN;
WiFiClient espClient;
PubSubClient client(espClient);
void setup()
{
Serial.begin(115200);
setup_wifi();
client.setServer(mqtt_server, 1883);
client.setCallback(callback);
pinMode(C0, OUTPUT);
pinMode(C1, OUTPUT);
pinMode(C2, OUTPUT);
pinMode(C3, OUTPUT);
pinMode(C4, OUTPUT);
pinMode(C5, OUTPUT);
pinMode(C6, OUTPUT);
pinMode(C7, OUTPUT);
pinMode(C8, OUTPUT);
digitalWrite(C0, LOW);
digitalWrite(C1, LOW);
digitalWrite(C2, LOW);
digitalWrite(C3, LOW);
digitalWrite(C4, LOW);
digitalWrite(C5, LOW);
digitalWrite(C6, LOW);
digitalWrite(C7, LOW);
digitalWrite(C8, LOW);
}
void loop()
{
if (!client.connected())
{
reconnect();
}
client.loop();
delay(100);
}
void reconnect()
{
while (!client.connected())
{
Serial.println("Attempting MQTT connection...");
if (client.connect(client_name))
{
client.publish(msgTopic, "MQTT is connected");
Serial.println("connected");
client.subscribe(inTopic);
}
else
{
Serial.print("failed, rc=");
Serial.print(client.state());
Serial.println(" try again in 5 seconds");
// Wait 5 seconds before retrying
delay(5000);
}
}
}
void setup_wifi()
{
delay(100);
Serial.println("Connecting to ");
Serial.print(ssid);
WiFi.begin(ssid, password);
WiFi.mode(WIFI_STA);
while (WiFi.status() != WL_CONNECTED)
{
delay(500);
Serial.print(".");
}
Serial.println("WiFi connected");
Serial.println("IP address: ");
Serial.print(WiFi.localIP());
}
void callback(char* topic, byte* payload, unsigned int length)
{
String msgIN = "";
for (int i=0;i
msgIN += (char)payload[i];
}
String msgString = msgIN;
Serial.println(msgString);
//Message as String
if (msgString == "CH1ON")
{
Serial.println("Channel1 is ON");
client.publish(msgTopic, "Channel1 is ON");
digitalWrite(C1, HIGH);
} else if (msgString == "CH1OFF")
{
Serial.println("Channel1 is OFF");
client.publish(msgTopic, "Channel1 is OFF");
digitalWrite(C1, LOW);
}
}
in a nutshell the statement works where payload is a declared String using latest IDE and PubSubClient
client.publish(topic, (char*) payload.c_str())
See other issue published at here - igrr/esp8266_pubsubclient.ino
" The publish functions expect char[] types to be passed in rather than Strings.
You need to use the String.toCharArray() function to convert your strings to the necessary type. "
Would be great if there was some examples how to do this.
I have a question, my project is modbus-serial, but I cannot use "npm i modbus-serial" since the server will fail. After a while, our team have to write down the api for handling mqtt, but to working with serial register, i.e. read/write to specific register I am still confused, anyone is care to clearify?
Would be great if there was some examples how to do this.
@donmueang, the String.toCharArray() wasn't working for me. Instead I found some ways to convert strings to char arrays here and built a publish function that accepts strings (by casting them to char arrays internally). It is working for me, but please mind that I am a complete C++ beginner.
char* toCharArray(string str) {
return &str[0];
}
void publish(string doc, string topic) {
client.publish(toCharArray(topic), toCharArray(buffer));
}
Maybe it helps you (or anybody else stumbling upon this issue).
Would this help? (retainBool is not required)
with
String topicStr and String payloadStr
bool result = mqttClient->publish( topicStr.c_str( ), payloadStr.c_str( ),
retainBool );
On Sun, Jan 10, 2021 at 2:10 PM Jan-Benedikt Jagusch <
[email protected]> wrote:
Would be great if there was some examples how to do this.
@donmueang https://github.com/donmueang, the String.toCharArray()
wasn't working for me. Instead I found some ways to convert strings to char
arrays here https://www.geeksforgeeks.org/convert-string-char-array-cpp/
and built a publish function that accepts strings (by casting them to char
arrays internally). It is working for me, but please mind that I am a
complete C++ beginner.char* toCharArray(string str) {
return &str[0];
}
void publish(string doc, string topic) {
client.publish(toCharArray(topic), toCharArray(buffer));
}Maybe it helps you (or anybody else stumbling upon this issue).
—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
https://github.com/knolleary/pubsubclient/issues/334#issuecomment-757535973,
or unsubscribe
https://github.com/notifications/unsubscribe-auth/ADEX2XUEYO7M6L6QABERKLTSZICTPANCNFSM4D4AYDRQ
.
the String.toCharArray() wasn't working for me.@janjagusch I have a simillar backgroud as you in data science so also new to C++. But as far as I understand there is a difference between a string and a String. String.toCharArray() is a function of a String, but in your code you are working with strings. Maybe that helps
Simple solution. Don't use String class.
Simple solution. Don't use String class.
I read that also a lot, but what should I use instead? Only char arrays? always create buffers first? Do they have to have the correct length? I’m confused, probably need more googling
I went through the same pain about 6 months ago when my program on an Uno was crashing weirdly. Strings were eating up my RAM and the failure mode was different every time.
It takes longer to write code using char arrays instead of Strings, but it's getting easier.
Following are notes in my notebook. (I don't know whom to credit for this). Hope this helps.
The String class was created for people who don't have advanced programming skills. But even with advanced programming skills you can never work around all the shortcomings of the String class. So instead you really need to learn how to do without the String class altogether. And that means using "C strings".
First a little bit of anatomy.
A C string is simply an array of characters, but it is an array of characters that must obey certain rules. The biggest rule of C strings is that they are NULL terminated.
That means that the very last character of every C string must be ASCII character 0 ('0').
The internal C string manipulation functions all look for that final NULL character as a marker for the end of the string. The reason is because in C an array, even when you specified a size at compile time, doesn't have that size stored as part of it. Neither does a C string. It is perfectly possible to say you're working with a string of 10 characters and then fill it with 20 characters. That is a very bad thing to do, so you must learn to take care of these things. The result of that is known as a buffer overrun and is one of the most prevalent hack attacks used by cyber-criminals - fill up an input buffer with more data than it can handle until you end up writing your data over part of the program that is being run - and then your data (which could quite happily be instructions for a program) would get executed, thus compromising the device. So care must really be taken to avoid that.
Creating a C string is as simple as:
char string[30];
That will create an array of up to 30 characters. If you do it globally it will be stored in the BSS segment (uninitialized static data, both variables and constants. A.K.A Better Save Space). If you do it in a function it will be stored in the stack. Not a hint of it even going near the heap.
Don't forget that the 30 character space that you have reserved includes the NULL character, so actually you only have room for 29 characters if you are to be able to follow the rules for a C string.
Getting things into C strings is a little more tricky though. You can specify some content right at the start if you like:
char string[30] = "This is a string";
And that's simple enough. But what about if you want to change the content on the fly? Unlike with the String class you can't just do this:
string = "New content";
Instead you have to change each of the characters in the array individually. You see string doesn't contain the test, it just points to where it is in memory. So you need to manipulate the memory that it's pointing to, not the pointer itself.
So to change what is in the string you can use the strcpy() function:
strcpy(string, "New content");
That will iterate character by character over the second string and place those characters into the first string's memory.
Another thing you can't do with C strings is adding them together. This will not work:
char hi[7] = "Hello ";
char name[5] = "Fred";
char all[14] = hi + name;
Remember, the variables hi and name just point to locations in memory where those strings are stored. So in fact what you are doing there is adding to addresses (numbers) together and ending up with some bigger number which you then try to assign to an array (which doesn't work).
Instead you need to use the handy strcat() function:
char hi[7] = "Hello ";
char name[5] = "Fred";
char all[14] = "";
strcat(all, hi);
strcat(all, name);
The strcat function, like the strcpy function, copies the memory content character by character from the right hand string to the left hand one. Unlike strcpy though, strcat starts from the end of the first string, not the start.
Now, when it comes to buffer overruns, there is a special variation of all the string handling functions available. Every C string function has an n variant available, for instance strcpy has strncpy. These variations will perform on up to n characters. That allows you to limit the maximum number of characters you will work with, and thus help you to prevent buffer overruns from existing.
One of the hardest parts of working with C strings, though, is that of working out what is in a string. You can't just compare strings:
char a[10] = "Part A";
char b[10] = "Part B";
if (a == b) {
....
}
That kind of comparison is merely comparing the pointers to the memory where the strings are stored. Instead you must compare the content of the memory character by character.
Fortunately, again, there are functions to do that for you. strcmp is a good one to start with.
strcmp will take two strings and compare them character by character. If the strings are the same it will return a 0 (false). If one string is logically "less" than the other (in that "a" is less that "g") it will return -1 (non-zero=true). If one string is greater than the other then it will return +1. Most of the time you don't care about greater than or less than, only is it equal. So to compare the two strings above you would use:
if (strcmp(a, b) == 0) { do stuff }
or
if (!strcmp(a,b) { do stuff }
Another useful variation on that function is strcasecmp. This does the exact same job but it doesn't care about upper or lowercase letters. So "Hello" would equal "hello" with strcasecmp but not with strcmp.
Again there are n variants of both those functions available, strncmp and strncasecmp.
So far I have only shown you one way of creating strings:
char [num] { = "content";}
but there are others, and they each have special meaning.
char *string;
The char *string format just creates a pointer to a string. It doesn't actually point to any string at all - it's like a string with no size to it whatsoever. You may thing that's useless, but it's not - it's incredibly useful. You can use it to point to any other string, and since it's really just a number, you can move it around anywhere within a string. More on pointer manipulation later.
char *string = "This is text";
That is creating a pointer to some text in memory. That memory could well be Flash memory, or it may have been copied into RAM first, depending on your architecture. It is never safe to change the content of that string since it may be in read-only memory. The size of the string is determined by the length of the content at compile time.
char string[] = "This is text";
Just like above this will create a block of memory whose size is equal to the length of the content with the content in it and point the string at it. However, this differed from the one above in that it will always be copied into RAM first. It is perfectly safe to change the content of the string.
Note the subtle difference between those last two. The first is supposed to be read only and could cause untold problems if you try to change it - the second, though it looks almost identical, is safe to change. In the former it's normal to prefix the declaration with const which tells the compiler "I will never change this. Don't let me even try".
const char *string = "This is text";
Now on topointer manipulation. Passing C strings to functions.
Unlike a String object you cannot pass the entire string to a function as a parameter. Instead what you pass is the pointer to the memory where the string data is located. For example:
void PrintVal(char *tag, char *value) {
Serial.print(tag);
Serial.print(" = ");
Serial.println(value);
}
Just a tiny change, but one with lots to it.
Now you're passing the address in memory of where the two strings are stored, and those are then passed on to the print function which knows how to handle them. It then, staring at the address given, works character by character printing each one in turn up until it reaches that all important NULL character.
However, all is not well there. Imagine you call the function as:
char temp[] = "23C";
PrintVal("Temperature", temp);
You'd think that would be fine. And most of the time it would be. However, the compiler isn't completely happy with it. If you allow the compiler to show warnings (turned off by default in most Arduino and Ardiuno-like IDEs) you would see it moaning. Simply because you have specified the parameters as char *, which means, as parameters, "Pointers to memory that I can edit", yet you are passing the first parameter as a string literal "Temperature". That's not a string in memory that you can edit, it's a literal string in Flash memory. So it moans.
So the rule of thumb, any char pointers that you are passing into a function that you know will not be modified by the function must be done as const char pointers:
void PrintVal(const char *tag, const char *value) {
Serial.print(tag);
Serial.print(" = ");
Serial.println(value);
}
Now the compiler knows that you're never going to modify the memory pointed to by the pointers you pass, and so it is perfectly happy for you to pass a read-only string.
I have mentioned a few times phrases like "iterates over each character until it reaches the NULL character". But what do I mean and, more importantly, how does it do it? Well, let's take a little look at an example - printing a string to Serial character by character.
There's a number of ways this can be done, but I'm going to show you the pointer way of doing it. Here's a little function to whet your appetite. I have purposely broken it down into lots of small steps so we can analyse what is going on:
void PrintString(const char *str) {
const char *p;
p = str;
while (*p) {
Serial.print(*p);
p++;
}
}
I know, you're thinking "WTF?!", right? Well, don't worry, it's all quite simple.
Firstly you should recognise that this is a function to which we're passing a pointer to a block of memory that we know we won't be modifying (const char str). So we have a string pointer *str to work with.
Next we are creating a new string pointer, but it's not pointing anywhere and not got any size. That "useless" one from before, remember?
(char *p)
Next we are pointing that new variable to the same area of memory that str is pointing to (p = str) - so p now contains the same address as str does, and they both point to the same piece of memory - that is, the start of the string we want to print.
Now comes a while loop, with the enigmatic test "p". The * in front of the *p means "Give me the value that is stored in the memory that p is pointing at". Initially, then, that means "Give me the first character from the string" since p is pointing to the start of the string. Now the magic here is that the NULL character at the end of the string is 0, which is the same as FALSE, so when *p equates to 0 the while loop will finish.
Next we use that same * operator again to get the character that is currently pointed to and print it.
Finally, the last operation in the while loop, is to increment p (p++). Because p is just a number (the memory address), incrementing p causes it to point to the next address in memory. That means p is now pointing to the next character in the string.
And so it continues until the character pointed to by p is NULL.
So you can now see the importance of that NULL character - without it how would functions like these know when to stop?
Most helpful comment
" The publish functions expect char[] types to be passed in rather than Strings.
You need to use the String.toCharArray() function to convert your strings to the necessary type. "
Would be great if there was some examples how to do this.