Почему dtostrf() не работает для этого значения?

Я строю метеостанцию, используя ESP32 и BME280. Я передаю данные через MQTT и Python в базу данных, которая затем используется для создания удобной информационной панели для данных.

Сообщение MQTT должно быть символом, а показания датчика — числами с плавающей запятой, поэтому я использую dtostrf() для их преобразования. Это отлично работает для показаний температуры и давления, но по какой-то причине не работает для показаний влажности.

Я объявляю свои переменные:

float temp;
float hum;
float pres;

char mqttTemp[6];
char mqttHum[6];
char mqttPres[5];

Затем я получаю показания:

temp = mySensor.readTempC();
Serial.print("Temp: ");
Serial.print(temp);
hum = mySensor.readFloatHumidity();
Serial.print("Hum: ");
Serial.print(hum);
pres = mySensor.readFloatPressure();
Serial.print("Pres: ");
Serial.print(pres);

Это возвращает:

Темп: 25,57 Уровень шума: 40,15 Pres: 97684,39

Затем я пропускаю результаты через функцию:

dtostrf(temp,4,2,mqttTemp);
dtostrf(hum,4,2,mqttHum);
dtostrf(pres,5,0,mqttPres);

Затем я вывожу их в Serial:

Serial.print("Temperature: ");
Serial.print(mqttTemp);
Serial.print("Humidity: ");
Serial.print(mqttHum);
Serial.print("Pressure: ");
Serial.print(mqttPres);

Что дает мне:

Temperature: 25.57
------------------------------
Humidity: 
------------------------------
Pressure: 97684

Сообщение о влажности через MQTT также не отправляется, но для двух других показаний оно отлично работает. Что дает?

, 👍1

Обсуждение

Используете esp32 и пытаетесь сжать массив до самого последнего байта и далее? Это бессмысленно. Можете ли вы сделать массивы символов по 16 байтов каждый (или 20 или 100 байтов). Размер dtostrf — это **минимальный** размер, плюс, возможно, отрицательный знак, плюс нулевой терминатор, плюс еще немного для безопасности, что намного больше, чем просто 6 или 5. Что, если датчик отключен и есть некоторые значения ошибок? возвращаются и эти ошибки преобразуются в текст?, @Jot

две основные ошибки: 1) вы не объявили переменные с достаточным пространством для данных, которые они должны содержать, и 2) вы неправильно используете dtostrf (ширина температуры **минимум** 5, или 6, если вы можете получить температуру -10,00 или ниже, а ширина влажности не менее 6, поскольку 100,00 — это 6 символов., @Jaromanda X


1 ответ


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

2

Происходит следующее: переменные, которые вы объявили для mqttTemp и т. д., хранятся в памяти примерно так

variable   memory address
mqttPres @ 1000-1004 (5 bytes)
mqttHum  @ 1005-1010 (6 bytes)
mqttTemp @ 1011-1015 (6 bytes)

Для начала вам хватит места (с обязательным нулевым байтовым ограничителем строки) только для 4, 5 и 5 символов

При преобразовании давления NUL-байт помещается в качестве первого байта в mqttHum, поэтому при печати mqttHum вы получаете на выходе пустую строку (поскольку первый байт равен NUL)

Чтобы понять, что происходит, вот код, иллюстрирующий проблему

char mqttTemp[6] = "TTTTT";
char mqttHum[6] = "HHHHH";
char mqttPres[5] = "PPPP";
void setup() {
  float temp = 25.57;
  float hum = 40.15;
  float pres = 97684.39;


  Serial.begin(74880);
  // поместите сюда свой код установки для однократного запуска:
  delay(100);
  Serial.println("before");
  printThem();
  dtostrf(temp, 4, 2, mqttTemp);
  Serial.println("converted temp");
  printThem();
  dtostrf(hum, 4, 2, mqttHum);
  Serial.println("converted hum");
  printThem();
  dtostrf(pres, 5, 0, mqttPres);
  Serial.println("converted press");
  printThem();
}
void printThem()
{
  Serial.print("mqttPres ");
  Serial.print((unsigned long) &mqttPres, DEC);
  Serial.print(": ");
  for (int i = 0; i < 5; i++) {
    Serial.print((int) mqttPres[i], DEC);
    Serial.print(' ');
  }
  Serial.println();

  Serial.print("mqttHum  ");
  Serial.print((unsigned long) &mqttHum, DEC);
  Serial.print(": ");
  for (int i = 0; i < 6; i++) {
    Serial.print((int) mqttHum[i], DEC);
    Serial.print(' ');
  }
  Serial.println();

  Serial.print("mqttTemp ");
  Serial.print((unsigned long) &mqttTemp, DEC);
  Serial.print(": ");
  for (int i = 0; i < 6; i++) {
    Serial.print((int) mqttTemp[i], DEC);
    Serial.print(' ');
  }
  Serial.println();
}

void loop() {
  delay(50);
  // поместите сюда свой основной код для многократного запуска:

}

Приведенное выше выводит следующее:

before
mqttPres 1073644748: 80 80 80 80 0 
mqttHum  1073644753: 72 72 72 72 72 0 
mqttTemp 1073644759: 84 84 84 84 84 0 
converted temp
mqttPres 1073644748: 80 80 80 80 0 
mqttHum  1073644753: 72 72 72 72 72 0 
mqttTemp 1073644759: 50 53 46 53 55 0 
converted hum
mqttPres 1073644748: 80 80 80 80 0 
mqttHum  1073644753: 52 48 46 49 53 0 
mqttTemp 1073644759: 50 53 46 53 55 0 
converted press
mqttPres 1073644748: 57 55 54 56 52 
mqttHum  1073644753: 0 48 46 49 53 0 
                     ^ ======== note the leading 0
mqttTemp 1073644759: 50 53 46 53 55 0 

Самое простое решение — объявить переменные правильного размера.

char mqttTemp[7]; // потому что -10.00 занимает 6 символов + 1 для NULL
char mqttHum[7];  // потому что 100.00 занимает 6 символов + 1 для NULL
char mqttPres[6]; // потому что 97684 принимает 5 символов + 1 для NULL - если давление может достигать 100000, то здесь также используйте 7

а затем используйте dtostrf правильно, т. е. второй аргумент — это общая ширина, включая . и возможный -

dtostrf(temp,6,2,mqttTemp);
dtostrf(hum,6,2,mqttHum);
dtostrf(pres,5,0,mqttPres);
,

Спасибо за очень подробный ответ! Я подозревал, что что-то фундаментально не понимаю (не в последнюю очередь потому, что это мое состояние по умолчанию). Теперь он работает отлично., @user36735

Это минимальная ширина. Почему бы не сделать массивы размером 20 вместо того, чтобы ждать следующей проблемы, когда текст окажется длиннее, чем ожидалось?, @Jot

если температура выше 999 или ниже -99 - есть о чем беспокоиться @Jot - хотя согласен, но с минимальными размерами в приведенном выше коде не может быть никаких проблем, учитывая данные, @Jaromanda X

@JaromandaX Я должен не согласиться. Я даже вынужден очень сильно не согласиться. Ошибка в программном обеспечении не должна быть причиной лавины ошибок по всему коду. Поэтому при использовании dtostrf буфер должен быть достаточно большим, чтобы вместить все возможные значения с плавающей запятой. Предпочтительно буфер в стеке. Если значение с плавающей запятой ограничено, это ограничение должно быть в коде с операторами if и разделом комментариев, объясняющим, почему возможен буфер меньшего размера. Возможно, это полезно для Attiny, но уж точно не для ESP32., @Jot

@Jot - я понимаю, о чем вы говорите, но BME280 не выдаст вам никаких значений за пределами своего диапазона измерения, а dtostrf «запишет» не более 7, 7 и 6 байтов, включая завершение NUL в этом коде - там невозможно **в данном случае** лавины, о которой вы говорите, @Jaromanda X