Утечка памяти без участия строк
Я пытаюсь считать данные, передаваемые через инфракрасный порт, с моего 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".
Что-нибудь очевидное, что сразу бросается в глаза, но я пропустил?
@alphachris, 👍1
Обсуждение1 ответ
Лучший ответ:
Вы делаете new
внутри цикла
readMessage = new byte[tempChar+2];
что приводит к заполнению кучи байтами tempChar+2
, но вы никогда не удаляете это.
Попробуйте повторно использовать эту память (сделав ее локальной или "грязной" глобальной).
Возможно (не проверялось), приведенную выше строку можно переписать так:
byte readMessage[tempChar+2];
К настоящему времени я уже был уверен, что это что-то настолько очевидное *вздох*
Простой `
delete[] readMessage`
после публикации MQTT переменной "str" сделал свое дело!
Даже если бы я пошел другим путем, чтобы действительно решить проблему, вы дали настоящую подсказку, которая привела меня туда, поэтому я отмечу ваш ответ как принятый.
Большое спасибо!, @alphachris
Добро пожаловать ... однако имейте в виду, что в этом случае не нужны new
и delete
, так как данные живут только в одной функции. Обычно new
/ delete
используется, когда продолжительность жизни данных выше, чем у одной функции, или когда количество данных заранее неизвестно/варьируется., @Michel Keijzers
Это правда - я обязательно приму это во внимание, когда буду реализовывать части этого скетча в окончательный код!, @alphachris
- Esp8266 Vin контакт
- Чтение строки, разделенной запятыми
- Spiffs против Eeprom на esp8266
- Ошибка 'Serial' was not declared in this scope
- Отправка данных из ESP8266 в PHP
- Как исправить код утечки памяти в ESP8266/NodeMCU, вызванный концентрацией строк?
- ОТА-программа SPIFFS на ESP8266
- Порт Arduino OTA не обновляется в Arduino IDE
«проблема со строками» — это проблема с «новым», скрытым в классе String. ваше использование «нового» не скрыто, @Juraj