Утечка памяти без участия строк

Я пытаюсь считать данные, передаваемые через инфракрасный порт, с моего Smartmeter на моем ESP8266, используя следующий скетч:

#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include <ESP8266HTTPUpdateServer.h>
#include <Ticker.h>
#include <AsyncMqttClient.h>

#ifndef STASSID
#define STASSID "mySSID"
#define STAPSK  "myPassword"
#endif

#ifndef MQTT_HOST
#define MQTT_HOST IPAddress(192, 168, 0, 100)
#define MQTT_PORT 1883
#endif

#ifndef IR
#define SerialDebug Serial1
#define IR Serial
#endif

AsyncMqttClient mqttClient;
Ticker mqttReconnectTimer;

WiFiEventHandler wifiConnectHandler;
WiFiEventHandler wifiDisconnectHandler;
Ticker wifiReconnectTimer;

const char* host = "esp8266-webupdate";

const byte firstByte = 0x7E;
const byte lastByte = 0x7E;

uint8_t i;

ESP8266WebServer httpServer(80);
ESP8266HTTPUpdateServer httpUpdater;

void connectToWifi() {
  SerialDebug.println("Connecting to WiFi ...");
  WiFi.begin(STASSID, STAPSK);
}

void connectToMqtt() {
  SerialDebug.println("Connecting to MQTT ...");
  mqttClient.connect();
}

void onWifiConnect(const WiFiEventStationModeGotIP& event) {
  SerialDebug.println("Connected to Wi-Fi.");
  connectToMqtt();
}

void onWifiDisconnect(const WiFiEventStationModeDisconnected& event) {
  SerialDebug.println("Disconnected from Wi-Fi.");
  mqttReconnectTimer.detach();
  wifiReconnectTimer.once(2, connectToWifi);
}

void onMqttConnect(bool sessionPresent) {
  SerialDebug.println("Connected to MQTT.");
  SerialDebug.print("Session present: ");
  SerialDebug.println(sessionPresent);
}

void onMqttDisconnect(AsyncMqttClientDisconnectReason reason) {
  SerialDebug.println("Disconnected from MQTT.");

  if (WiFi.isConnected()) {
    mqttReconnectTimer.once(2, connectToMqtt);
  }
}

void array_to_string(byte array[], unsigned int len, char buffer[]) {
  for (unsigned int i = 0; i < len; i++) {
    byte nib1 = (array[i] >> 4) & 0x0F;
    byte nib2 = (array[i] >> 0) & 0x0F;
    buffer[i*2+0] = nib1  < 0xA ? '0' + nib1  : 'A' + nib1  - 0xA;
    buffer[i*2+1] = nib2  < 0xA ? '0' + nib2  : 'A' + nib2  - 0xA;
  }
  buffer[len*2] = '\0';
}

void setup() {

  SerialDebug.begin(115200);
  IR.begin(9600);
  SerialDebug.println();
  SerialDebug.println("Booting Sketch...");
  
  wifiConnectHandler = WiFi.onStationModeGotIP(onWifiConnect);
  wifiDisconnectHandler = WiFi.onStationModeDisconnected(onWifiDisconnect);

  mqttClient.onConnect(onMqttConnect);
  mqttClient.onDisconnect(onMqttDisconnect);
  mqttClient.setServer(MQTT_HOST, MQTT_PORT);

  connectToWifi();

  httpUpdater.setup(&httpServer);
  httpServer.begin();

  mqttClient.setWill("smartmeter/online", 0, true, "no");
}

void loop() {
  httpServer.handleClient();
  
  uint32_t timeout = 2000;
  
  int readCnt = 0;
  uint32_t start_time = millis();
  byte tempChar = 0;
  byte *readMessage;
  uint32_t messageLen = 0;
  
  while ((tempChar != 0x7E) && (millis() - start_time < timeout)) {
    if (IR.available()) {
      tempChar = IR.read();
    }
  }
  
  start_time = millis();
  timeout = 1000;
  bool done = false;
  
  if (tempChar == firstByte) { // если первый байт == 0x7E
    while ((millis() - start_time) < timeout && !done) {
      if (IR.available()) {
        tempChar = IR.read(); // второй байт должен быть 0xA0
        if(tempChar == 0xA0) {
          while ((millis() - start_time) < timeout && !done) {
            if (IR.available()) {
              tempChar = IR.read(); // 3-й байт сообщает длину сообщения
    
              readMessage = new byte[tempChar+2];
              memset(readMessage, 0, tempChar+2);
              readMessage[0] = firstByte;
              readMessage[1] = 0xA0;
              readMessage[2] = tempChar;
              messageLen = ((uint32_t)(tempChar))+2;
              readCnt = 3;
      
              while ( readCnt < messageLen && ((millis() - start_time) < timeout)) { // минимум len 120 символов для формата 0x7E
                if (IR.available()) {
                  readMessage[readCnt] = IR.read(); 
                  readCnt++; 
        
                  if(readCnt == tempChar+2 && readMessage[readCnt-1] != lastByte) { // правильный len, но последний байт не 0x7E
                    done = true;
                    SerialDebug.printf("Wrong end byte found - %d\n", readMessage[readCnt-1]);
                  } else if(readCnt == tempChar+2) { // исправить len и исправить последний байт
                    done = true;
                    char str[251] = "";
                    array_to_string(readMessage, readCnt, str);
                    mqttClient.publish("smartmeter/raw", 0, false, str);
                  }
                }
              }
            }
          }
        }
      }
    }
  }
  mqttClient.publish("smartmeter/online", 0, true, "yes");
  char heap[6] = "";
  itoa(ESP.getFreeHeap(), heap, 10);
  mqttClient.publish("smartmeter/freeHeap", 0, false, heap);
}

Я получаю закодированные HEX-данные, опубликованные в моей теме MQTT по желанию, длина выходных данных составляет 250 символов (= 125 байт), что правильно:

7ea07bcf000200231362b1e6e700db08534d536770075f626120000e32e62addedaede38e64c8c3173035a0a853851a28efc57f7fd76a9df59dbb152f796939328ff0f28df7f257d20b5cb2a458c4eb9188e2c1c251701c891244d859ed9c159714bb4451c090d9b1ed3bbb1fc89785ebafbf59ec4f9d540eb4c90d47e

Как я выяснил, куча ESP уменьшается на 100-300 с каждой итерацией цикла (данные передаются каждую секунду через ИК-порт из Power-grid Smartmeter), пока ESP в конечном итоге не будет перезагружен сторожевым таймером после примерно 5–8 минут.

Я явно не использовал строковые переменные в скетче, но не могу найти причину утечки памяти. Пробовал отключать Wi-Fi, веб-сервер и mqtt один за другим, что на самом деле не имело значения.

Размер кучи остается на постоянном уровне, хотя данные не принимаются через последовательный инфракрасный порт, поэтому я предполагаю, что что-то не так при сохранении данных в массиве байтов "readMessage".

Что-нибудь очевидное, что сразу бросается в глаза, но я пропустил?

, 👍1

Обсуждение

«проблема со строками» — это проблема с «новым», скрытым в классе String. ваше использование «нового» не скрыто, @Juraj


1 ответ


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

2

Вы делаете new внутри цикла

 readMessage = new byte[tempChar+2];

что приводит к заполнению кучи байтами tempChar+2, но вы никогда не удаляете это.

Попробуйте повторно использовать эту память (сделав ее локальной или "грязной" глобальной).

Возможно (не проверялось), приведенную выше строку можно переписать так:

byte readMessage[tempChar+2];
,

К настоящему времени я уже был уверен, что это что-то настолько очевидное *вздох* Простой `delete[] readMessage` после публикации MQTT переменной "str" сделал свое дело! Даже если бы я пошел другим путем, чтобы действительно решить проблему, вы дали настоящую подсказку, которая привела меня туда, поэтому я отмечу ваш ответ как принятый. Большое спасибо!, @alphachris

Добро пожаловать ... однако имейте в виду, что в этом случае не нужны new и delete, так как данные живут только в одной функции. Обычно new / delete используется, когда продолжительность жизни данных выше, чем у одной функции, или когда количество данных заранее неизвестно/варьируется., @Michel Keijzers

Это правда - я обязательно приму это во внимание, когда буду реализовывать части этого скетча в окончательный код!, @alphachris