MQTT-подключение не работает при использовании строковых объектов

На моем ESP32 я пытаюсь подключиться к серверу HomeAssistant, используя библиотеку ArduinoHA.

Я могу легко подключиться, если жестко запрограммирую учетные данные: mqtt.begin("server", "username", "password");

Но недавно я попытался перенести учетные данные в файл конфигурации, который я читал при настройке:

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());

Подключение не устанавливается, и состояние MQTT возвращает -2 (StateConnectionFailed).

Я попробовал сравнить считанные значения с жестко запрограммированными значениями:

  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()));

Но все возвращает 0 (т.е. это одна и та же строка).

Из любопытства я попробовал это сделать, не читая файл конфигурации, и получил тот же результат:

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

Итак, кто-нибудь знает, почему соединение не устанавливается при использовании объекта String? Может, я что-то упускаю?

== ИЗМЕНИТЬ ==

Похоже, проблема связана с памятью: строки удаляются до того, как mqtt использует их для подключения.

Поэтому я попытался извлечь char* из объекта String, но получил тот же результат:

  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);

Я заметил одну интересную вещь: использование const char* вместо String действительно работает, но, полагаю, компилятор просто преобразует его в исходную жестко закодированную версию.

Единственное, что мне помогло, — это определение объекта Credentials как глобальной переменной. Таким образом, учетные данные не будут удалены из памяти.

, 👍5

Обсуждение

Библиотека могла бы хранить адреса C-строк и использовать их позже, но ваши аргументы хранятся недостаточно долго. Проверьте, что произойдёт, если скопировать C-строки в другие переменные. -- Или используйте C-строковые переменные в качестве эксперимента (в этом комментарии просто сокращённый вариант: char url[] = "192.168.1.45"; /* ... */ mqtt.begin(url, ...);), @the busybee

@thebusybee Интересно, похоже, это связано с памятью. Я обновлю вопрос, внеся изменения., @SagiZiv

Похоже, метод begin() ожидает, что пользователь предоставит статическое хранилище для аргументов типа char*, как и сказал @the busybee. Класс сохраняет только копию указателя. См.: https://github.com/dawidchyrzynski/arduino-home-assistant/blob/main/src/HAMqtt.cpp и соответствующий файл .h. Вот похожий случай (см. мои комментарии). К сожалению, придётся покопаться в исходном коде, чтобы узнать, сохраняет ли метод развёрнутую копию аргументов или просто указатель. https://arduinoprosto.ru/q/96796/wifi-credentials-pulled-from-an-sd-card-fail-but-work-hard-coded, @6v6gt

@6v6gt Да, я тоже только что посмотрел исходный код. Похоже, класс не создаёт копию учётных данных, поэтому мне нужно сделать учётные данные глобальной переменной..., @SagiZiv

Вам не нужны глобальные переменные для строк, только статические переменные., @the busybee

3 строки кода для назначения массива символов? char url[]="192.168.1.45";..., @dda


1 ответ


Лучший ответ:

4

Благодаря комментариям @thebusybee и @6v6gt, а также просмотру исходного кода класса HAMqtt я понял, что метод begin сохраняет ссылку на строки и не создает копию.

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;
}

Поскольку объекты String удаляются из памяти после выполнения функции настройки, мне пришлось сохранить объект Credentials, сделав его либо глобальной переменной, либо статическим. Вот решение, которое мне помогло:

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();
}

,

Код теперь работает?, @liaifat85

@liaifat85 Да, кажется, работает отлично ☺, @SagiZiv