I already said all in the subject: My Wemos D1 mini works great for some time, but then stops doing what I want it to do.
The code is quite simple, it takes data from softserial, then averages this and sends it over Wifi to an Mqtt server.
Any idea what could cause this? The code is c&p and adopted to my needs. I'm no programmer and my have made some massive coding errors, which may be obvious to you gurus out there.
Checking the Wemos die and log it may be difficult as I don't know when this happens ... it runs for 1-2 days and then stops.
This is the code:
#include "ESPHelper.h"
#include <Metro.h>
#include <ESP8266WiFi.h> // ESP8266 Core WiFi Library (you most likely already have this in your sketch)
#include <DNSServer.h> // Local DNS Server used for redirecting all requests to the configuration portal
#include <ESP8266WebServer.h> // Local WebServer used to serve the configuration portal
#include <WiFiManager.h> // https://github.com/tzapu/WiFiManager WiFi Configuration Magic
#include <SoftwareSerial.h>
SoftwareSerial SoftwareSerial(D6, D5);
#define AVG_COUNT 3
const char* voltTopic = "emon/volt";
const char* wattTopic = "emon/watt";
const char* hostnameStr = "emon";
const char* otaPass = "xxxxxx";
netInfo homeNet = { .mqttHost = "192.168.1.11",
.mqttUser = "xxxxx",
.mqttPass = "xxxxx",
.mqttPort = 1883,
.ssid = "xxxxx",
.pass = "xxxxx"
};
ESPHelper myESP(&homeNet);
Metro powerMetro = Metro(10000);
void setup() {
Serial.begin(115200);
SoftwareSerial.begin(115200);
myESP.OTA_enable();
myESP.OTA_setPassword(otaPass);
myESP.OTA_setHostnameWithVersion(hostnameStr);
myESP.setHopping(false);
myESP.begin();
WiFiManager wifiManager;
wifiManager.autoConnect("test", "xxxxx");
}
void loop() {
int count = 0;
// where to store the data to be averaged
double volts[AVG_COUNT];
double watts[AVG_COUNT];
double watt1[AVG_COUNT];
double watt2[AVG_COUNT];
double watt3[AVG_COUNT];
double voltsAvg = 0;
double wattsAvg = 0;
double watt1Avg = 0;
double watt2Avg = 0;
double watt3Avg = 0;
// the serial buffer of 64 bytes
char serialBuf[64];
while (1) {
if (count >= AVG_COUNT) {
count = 0;
}
// get data from serial line
while (SoftwareSerial.available()) {
// '*' marks the beginning of a transmission
Serial.println("start data");
bool start = SoftwareSerial.find('*');
// parse out the floats
if (start) {
volts[count] = SoftwareSerial.parseFloat();
watts[count] = SoftwareSerial.parseFloat();
watt1[count] = SoftwareSerial.parseFloat();
watt2[count] = SoftwareSerial.parseFloat();
watt3[count] = SoftwareSerial.parseFloat();
count++;
break;
}
delay(1);
}
// calculate averages
voltsAvg = 0;
wattsAvg = 0;
watt1Avg = 0;
watt2Avg = 0;
watt3Avg = 0;
for (int i = 0; i < AVG_COUNT; i++) {
voltsAvg += volts[i];
wattsAvg += watts[i];
watt1Avg += watt1[i];
watt2Avg += watt2[i];
watt3Avg += watt3[i];
}
voltsAvg /= AVG_COUNT;
wattsAvg /= AVG_COUNT;
watt1Avg /= AVG_COUNT;
watt2Avg /= AVG_COUNT;
watt3Avg /= AVG_COUNT;
// only send the data every so often (set by the metro timer) and only when connected to WiFi and MQTT
if (myESP.loop() == FULL_CONNECTION && powerMetro.check()) {
String payload = "{";
payload += "\"Volt\":"; payload += voltsAvg; payload += ",";
payload += "\"Watt\":"; payload += wattsAvg; payload += ",";
payload += "\"Watt1\":"; payload += watt1Avg; payload += ",";
payload += "\"Watt2\":"; payload += watt2Avg; payload += ",";
payload += "\"Watt3\":"; payload += watt3Avg;
payload += "}";
Serial.println(payload);
char attributes[200];
payload.toCharArray( attributes, 200 );
myESP.publish( "emon", attributes, true );
Serial.println( attributes );
// myESP.publish("emon", "payload", true);
}
yield();
}
}
void callback(char* topic, uint8_t* payload, unsigned int length) {
}
I would suggest that you look carefully at your array accesses. The behavior that you describe can be the result of writing beyond the bounds of an array into memory that you do not "own",
Maybe try removing that so that loop() can terminate normally. You will need to make some/all of the variables global which are currently local to loop().
After looking up the Wemos D1 mini and seeing that it had 96kB of ram, I would not have thought the String would have caused that much of a problem. Is the memory handling really that bad?
It would be fairly simple to get rid of the String, since it is being converted to a string (null-terminated character array) before being used anyway.
david_2018:
After looking up the Wemos D1 mini and seeing that it had 96kB of ram, I would not have thought the String would have caused that much of a problem. Is the memory handling really that bad?
What does the amount of RAM have to do with garbage collection? PCs have several Gigabytes!
96 kB of which over half gets taken by the WiFi stack.
Leave it connected to the Serial monitor until it crashes; you probably get a stack dump or other info that helps tracking down the problem. Use the EspExceptionDecoder IDE plug-in to decode what's in there.
groundFungus:
Ah, Strings. I somehow missed that. Have a look at the evils of Strings page to see why the String class can cause the kind of problem you are having.
I only see string where data is prepared for sending to mqtt?! And this string is overwritten all the time.
Why does your code do this?
Maybe try removing that so that loop() can terminate normally. You will need to make some/all of the variables global which are currently local to loop().
TBH I don't know why it's there. It was in the code that i c&p'd, so i left it there. I understand that it's there to let the code finish before it goes on.
I can remove it, but first i will test the "Exception Stack" plugin and post the outcome.
izeman:
TBH I don't know why it's there. It was in the code that i c&p'd, so i left it there. I understand that it's there to let the code finish before it goes on.
That loop will never finish, and shows that whoever wrote that code also didn't know much about programming.
You can declare the variables above it static, and remove the while (1) and the yield() statement.
Now that i think about it: I'm quite sure that the problem appeared AFTER I added this averaging stuff with the arrays. If someone knows a better approach to do the averaging, I'll be happy to use it
This is also a potential problem. The parseFloat() function is blocking - if for whatever reason no complete float is received, it waits for a timeout, and if you're blocking for more than 250 ms or so the WDT will reset your ESP. That timeout is by default 1000 ms. This is also why you have this yield() in the while loop.
A much better solution is to have loop() run, so no while() blocks, reading characters as they come in, store them in a string (char array) then when you see the termination character try to parse your floats. Remember to do sanity checking on that data.See this tutorial.
izeman:
while (SoftwareSerial.available()) {
This is also rather nonsensical as it's quite impossible fro there to be more than one character in the Serial buffer when this point is reached. An if statement would have the same effect here.
wvmarle:
This is also a potential problem. The parseFloat() function is blocking - if for whatever reason no complete float is received, it waits for a timeout, and if you're blocking for more than 250 ms or so the WDT will reset your ESP. That timeout is by default 1000 ms. This is also why you have this yield() in the while loop
I understand that this is not the best way to do it, but resetting the ESP wouldn't cause any big issues i guess? I resets and starts doing what it was supposed to do?? I would loose one reading (i get readings every 30s or so), but that's it.
I once checked the smoothing/averaging examples, and this is what it looks like:
void loop() {
// subtract the last reading:
total = total - readings[readIndex];
// read from the sensor:
readings[readIndex] = analogRead(inputPin);
// add the reading to the total:
total = total + readings[readIndex];
// advance to the next position in the array:
readIndex = readIndex + 1;
// if we're at the end of the array...
if (readIndex >= numReadings) {
// ...wrap around to the beginning:
readIndex = 0;
}
// calculate the average:
average = total / numReadings;
// send it to the computer as ASCII digits
Serial.println(average);
delay(1); // delay in between reads for stability
}
The only difference I see is that I use double instead of integer. Would it help to change my sketch to integer as well? I don't care for tenth of a Watt anyway.
wvmarle:
Make sure you catch the information printed by your ESP the moment it crashes. Until you have that, any solution is a mere shot in the dark.
It can help in debugging to put Serial.flush () after each print statement, otherwise the crash can occur while there is still several lines of text in the buffer.