5

On my ESP32 I am trying to connect to my HomeAssistant server using ArduinoHA library.

I can easily connect when hard-coding the credentials: mqtt.begin("server", "username", "password");

But I recently tried to move the credentials to a config file that I read on setup:

typedef struct {
  String url;
  String username;
  String password;
} Credentials;


Credentials creds = readCredentials(credsFile);
mqtt.begin(creds.url.c_str(), creds.username.c_str(), creds.password.c_str());

The connection fails and the MQTT state returns -2 (StateConnectionFailed).

I tried comparing the read values to the hard-coded values:

  Serial.println(strcmp("192.168.1.45", creds.url.c_str()));
  Serial.println(strcmp("username", creds.username.c_str()));
  Serial.println(strcmp("passwird", creds.password.c_str()));

But it all returns 0 (i.e., its the same string).

Out of curiosity I tried it without reading the config file, and got same result:

  String url = "192.168.1.45";
  String username = "username";
  String password = "password";
  mqtt.begin(url.c_str(), username.c_str(), password.c_str());

So, does anyone know why the connection fails when using String object? Is there something I am missing?

== EDIT ==

It seems that the issue is memory related, the Strings are deleted before mqtt uses them to connect.

So I tried extracting the char* from the String object, but still got the same result:

  String urlStr = String("192.168.1.45");
  char url[urlStr.length() + 1];
  strcpy(url, urlStr.c_str());
  String usernameStr = String("username");
  char username[usernameStr.length() + 1];
  strcpy(username, usernameStr.c_str());
  String passwordStr = String("password");
  char password[passwordStr.length() + 1];
  strcpy(password, passwordStr.c_str());
  mqtt.begin(url, username, password);

One interesting thing I noticed is that using const char* instead of String does work, but I guess its just the compiler converting it to the original hard-coded version.

The only thing that worked for me is to define the Credentials object as a global variable, that way the credentials won't be cleared from memory.

6
  • The library could possibly store the addresses of the C strings and use them later, but your arguments do not live long enough. Please check what happens if you copy the C strings into some other variables. -- Or use C string variables as experiment before (in this comment just shorted: char url[] = "192.168.1.45"; /* ... */ mqtt.begin(url, ...);) Commented Sep 21 at 17:47
  • @thebusybee Interesting, it does seems to be memory related. I'll update the question with the modifications.
    – SagiZiv
    Commented Sep 21 at 18:39
  • It does look like the begin() method expects the user to provide static storage for the char* arguments as said by @the busybee. The class retains only a copy of the pointer. See: github.com/dawidchyrzynski/arduino-home-assistant/blob/main/src/… and the corresponding .h file. Here is a similar case (see my comments). Unfortunately, you have to poke around in the source code to see if the method retains an expanded copy of the arguments or simply a pointer. arduino.stackexchange.com/questions/96796/…
    – 6v6gt
    Commented Sep 21 at 18:44
  • @6v6gt Yeah, I just looked at the source code as well. It seems that the class doesn't create a copy of the credentials so I need to make the credentials a global variable...
    – SagiZiv
    Commented Sep 21 at 18:50
  • You do not need global variables for the strings, just static variables. Commented Sep 21 at 19:15

1 Answer 1

4

Thanks to the comments from @thebusybee and @6v6gt and looking at the source code for the HAMqtt class I realized that the begin method stores the reference to the strings and doesn't create a copy.

bool HAMqtt::begin(
    const char* serverHostname,
    const uint16_t serverPort,
    const char* username,
    const char* password
)
{
    /*
        ...
    */

    _username = username;
    _password = password;
    _initialized = true;

    _mqtt->setServer(serverHostname, serverPort);
    _mqtt->setCallback(onMessageReceived);

    return true;
}

Since the String objects I have are deleted from memory after the setup function, I had to save the Credentials object either by making it global variable or by making it static. So this is the solution that worked for me:

typedef struct {
  String url;
  String username;
  String password;
} Credentials;

HAMqtt mqtt(client, device);

void setup() {
  static Credentials creds = readCredentials();
  mqtt.begin(creds.url.c_str(), creds.username.c_str(), creds.password.c_str());
}

void loop() {
  mqtt.loop();
}

2
  • Is the code working now?
    – liaifat85
    Commented Sep 22 at 13:40
  • @liaifat85 Yeah, seems to work fine ☺
    – SagiZiv
    Commented Sep 22 at 14:03

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.