Hallo Zusammen,
ich habe ein Sektch welches seit über einem Jahr stabil auf einem ESp32 läuft.
Es erkennt Daten die über Serielle Schnittstelle von einer Füllstandsanzeige kommt.
Wertet diese aus und stellt sie über Webserver dar. Weiterhin läd es die Daten zu Thinkspeak hoch und füttert eine InfluxDB. Debug Infos werden über Telnet gesendet.
Da am Aufstellungsort nicht immer WLAN vorhanden ist, werden Daten die während der Zeit ohne WLAN angefallen sind gecached.
Aus Platzgründen würde ich das Ganze gerne auf einen ESP8266 ESP01 bringen.
Leider kommt es hier immer wieder zu Abstürzen. Die ich seit Wochen versuche auszumerzen.
Bisher ohne Erfolg ![]()
Was ich bisher festgestellt habe, es passiert häufig nach dem WLAN reconnet.
Wenn man das Modul anpingt ist auffällig, dass nach dem ersten connect und somit den ersten 10-20 erfolgreichen Pings wieder ein paar Aussetzer kommen. Häufig hat es Ihn in dem Moment gekillt manchmal auch 10min später.
Was noch auffällig ist, dass Telnet häufiger nach dem Reconnect keine Daten mehr sendet trotzdessen die Verbindung hergestellt werden kann.
Darum habe ich schon abfragen eingebaut die Telnet und andere Dienste die auf WiFi angewiesen sein ausklammern solange WiFi nicht länger stabiel läuft.
Aber bisher habe ich es hat das nicht gefruchtet.
Hier mal das Skript. Ja, ich bin Anfänger ![]()
// heap fragmentation mitloggen
//V3 --> Umbau loop nur 3 Datensätze auf einmal. Qeue wird alle 5 sec. geleert
//V4 --> TelnetPrint
// V5 ohne Telnet
// V6 Umbau Transfer ohne StringStream
// V7 wieder mit Telnet
// V8 mit Telnet.Stop
// V9 all in one TAB
//=======Libraries
#include <ThingSpeak.h> // Thinkspeak
#include <ESP8266WebServer.h>
#include <ESP8266HTTPClient.h>
#include <uptime_formatter.h>
#include <time.h>
#include <ArduinoOTA.h> // IDE OTA Upload via ArduinoIDE
#include <TelnetPrint.h>
#include <QList.h>
//=======SETTINGS=======SETTINGS=======SETTINGS=======SETTINGS=======SETTINGS=======SETTINGS=======SETTINGS=======SETTINGS
//Hostname
const char myhostname[] {"ESP8266-Zisterne"}; // Eindeutige Kennung für diesen ESP32
//ThingSpeak
unsigned long myChannelNumber = XXX; // ThingSpeak Channel
const char * myWriteAPIKey = "XXX"; // ThingSpeak WriteAPI
String lastgood = "no entry until now"; // ThinkSpeak Übertragung
//Timer für Thingspeak
unsigned long ThingSpeakMillis = 0; // will store last time ThingSpeak was updated
const long ThingSpeakinterval = 300000; // interval at which to send (milliseconds)
//variables for string
const byte numChars = 46; //Länge Zeichenkette
char receivedChars[numChars];
char tempChars[numChars]; // temporary array for use when parsing
//variables to hold the parsed data
int start = 0;
int date = 0;
int pressure = 0;
int temperature = 0;
int height = 0;
int percent = 0;
int volume = 0;
boolean newData = false; //Serial input prüfen
// Define data record
struct Measurement {
uint16_t waterlevel;
int fragmentation;
time_t time;
};
// lesbare Anzeige der Speichergrößen
const String formatBytes(size_t const& bytes) {
return bytes < 1024 ? static_cast<String>(bytes) + " Byte" : bytes < 1048576 ? static_cast<String>(bytes / 1024.0) + " KB" : static_cast<String>(bytes / 1048576.0) + " MB";
};
//Queue for caching Data
QList<Measurement> queue;
//holding HTTP response Influx DB
int httpResponseCode = 0;
//Datas for Influx DB
char payload[500];
//Timer für InfluxDB
unsigned long InfluxDBMillis = 0; // will store last time InfluxDB was updated
const long InfluxDBinterval = 20000; // interval at which to send (milliseconds)
//Webserver
ESP8266WebServer server(80); // WebServer Library als Server initialisieren
// WiFiClient espClient; // WiFiClient Library als Client initialisieren <<-- jetzt in ThinkSpeak Void
uint16_t MinFreeHeap = 50000;
int MaxHeapFragmentation = 0;
//Timer für Heartbeat
unsigned long HeartbeatMillis = 0; // will store last time Heartbeat was running
const long Heartbeatinterval = 1000; // interval at which to send (milliseconds)
//Timer für WiFi reconnect
unsigned long WiFiMillis = 0; // will store last time Heartbeat was running
const long WiFiinterval = 60000; // interval at which to send (milliseconds)
//Checking WiFi Status
int WiFiactivecount = 0;
bool Wifiactive = false;
//status of telnetprint
bool TelnetPrintactive = false;
//WiFi
const char* ssid = "XXX"; // << kann bis zu 32 Zeichen haben
const char* password = "XXX"; // << mindestens 8 Zeichen jedoch nicht länger als 64 Zeichen
//============VOID============VOID============VOID============VOID============VOID============VOID============VOID============VOID
/**
WiFi
*/
void Connect() { // Funktionsaufruf "Connect();" muss im Setup eingebunden werden
byte i = 0;
WiFi.disconnect(true);
WiFi.setAutoReconnect(false);
WiFi.mode(WIFI_STA);
WiFi.hostname("ESP8266-Zisterne");
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
++i;
if (i > 9) {
time_t now;
time(&now);
if (now < 1000000000) {
ESP.restart();
}
WiFi.mode(WIFI_OFF);
break;
}
}
}
/**
Uhrzeit generieren
*/
//== LocalTime
void timeSetup()
{
// Zeitzone einstellen https://github.com/nayarsystems/posix_tz_db/blob/master/zones.csv
// Es können durch Komma getrennt bis zu 3 NTP Server eingegeben werden.
//configTime("CET-1CEST,M3.5.0,M10.5.0/3", "fritz.box", "de.pool.ntp.org");
//https://fipsok.de/Esp8266-Webserver/ntp-zeit-esp8266.tab
// für Core Versionen vor 2.6.0. folgende Konfiguration verwenden.
configTime(0, 0, "fritz.box", "de.pool.ntp.org");
setenv("TZ", "CET-1CEST,M3.5.0,M10.5.0/3", 1);
server.on("/time", []() {
server.send(200, "application/json", localTime());
});
}
char* localTime() {
struct tm tm;
static char buf[26];
time_t now = time(&now);
localtime_r(&now, &tm);
strftime (buf, sizeof(buf), R"(%d.%m.%Y - %T)", &tm); // http://www.cplusplus.com/reference/ctime/strftime/
return buf;
}
/**
http Webserver ohne OTA
*/
void serverSetup() {
MessDaten(height, volume, pressure, percent).reserve(900);
//-------------- Send 'MessDaten' HTMLPage when a client requests URI "/"
server.on("/", HTTP_GET, []() {
server.sendHeader("Connection", "close");
server.send(200, "text/html", MessDaten(height, volume, pressure, percent));
});
server.on("/reconnect", []() {
Connect();
});
server.on("/reset", []() {
ESP.restart();
});
//------------- When a client requests an unknown URI (i.e. something other than "/"), call function "handleNotFound"
server.onNotFound(handleNotFound);
server.begin(); // Webserver starten
}
/*
Webseiten des Webservers
*/
//==wenn Client anfrage ungültig
void handleNotFound() {
String message = "File Not Found\n\n";
message += "URI: ";
message += server.uri();
message += "\nMethod: ";
message += (server.method() == HTTP_GET) ? "GET" : "POST";
message += "\nArguments: ";
message += server.args();
message += "\n";
for (uint8_t i = 0; i < server.args(); i++) {
message += " " + server.argName(i) + ": " + server.arg(i) + "\n";
}
server.send(404, "text/plain", message);
}
//== Messdaten Webseite
String MessDaten(int height, int volume, int pressure, int percent) //Send temperature , humidity , pressure und altitude
{
String str = F("<!DOCTYPE html> <html>\n");
str += F("<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, user-scalable=no\">\n"
"<meta http-equiv=\"refresh\" content=\"1\">\n"
"<title>Zisterne Fuellstand</title>\n"
"<style>html { font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}\n"
"h1 {color: #0000FF;}\n"
"body{margin-top: 50px;} h1 {color: #0000FF;margin: 50px auto 30px;}\n"
"p {font-size: 24px;color: #444444;margin-bottom: 10px;}\n"
"</style>\n"
"</head>\n"
"<body>\n"
"<div id=\"webpage\">\n"
"<h1>Zisterne Füllstand</h1>\n"
"<p>Gefüllt: ");
str += percent;
str += F("%</p>"
"<p>Füllhöhe: ");
str += height;
str += F("cm</p>"
"<p>Füllmenge: ");
str += volume;
str += F("l</p>"
"<p>Duckwert: ");
str += pressure;
str += F("<br><br>"
"<br><br>"
"<br><br>"
"<br><br>"
"<p style=line-height:1.2;font-size:10px>"
"UpTime: ");
str += uptime_formatter::getUptime();
str += F("<br><br>"
"Last ThingSpeak update: ");
str += lastgood;
str += F("<br><br>"
"Free RAM: ");
str += formatBytes(ESP.getFreeHeap());
str += F(" | Lowest free RAM: ");
str += formatBytes(MinFreeHeap);
str += F("<br><br>"
"RAM Fragmentation: ");
str += ESP.getHeapFragmentation();
str += F("% | Max. RAM Fragmentation: ");
str += MaxHeapFragmentation;
str += F("%<br><br> "
"WiFi RSSI: ");
str += WiFi.RSSI();
str += F("</div>\n"
"</body>\n"
"</html>\n");
return str;
}
/**
Daten auf Korrektheit prüfen und ggf. in die Variablen schieben
*/
//==Datenerkennung
void datenerkennung() {
static boolean recvInProgress = false;
static byte ndx = 0;
char startMarker = '#';
char endMarker = '?';
char rxdata;
rxdata = Serial.read();
// TelnetPrint.println(rxdata);
if (recvInProgress == true) {
if (rxdata != endMarker) {
receivedChars[ndx] = rxdata;
ndx++;
if (ndx >= numChars) {
ndx = numChars - 1;
}
}
else {
receivedChars[ndx] = '\0'; // terminate the string
recvInProgress = false;
ndx = 0;
newData = true;
if (Wifiactive == true) {
TelnetPrint.println();
TelnetPrint.println(F("Ende der relevanten Daten erkannt"));
}
}
}
else if (rxdata == startMarker) {
recvInProgress = true;
if (Wifiactive == true) {
TelnetPrint.println();
TelnetPrint.println(F("Relevante Daten erkannt"));
}
}
}
//==PARSEN
void parseData() { // split the data into its parts
char * strtokIndx;
if (Wifiactive == true) {
TelnetPrint.println();
TelnetPrint.println(F("Daten String: "));
TelnetPrint.println(tempChars);
}
strtokIndx = strtok(tempChars, " ");
start = atoi(strtokIndx); // startkey
strtokIndx = strtok(NULL, " ");
date = atoi(strtokIndx); // date
strtokIndx = strtok(NULL, " ");
pressure = atoi(strtokIndx); // pressure
strtokIndx = strtok(NULL, " ");
temperature = atoi(strtokIndx); // temperature
strtokIndx = strtok(NULL, " ");
height = atoi(strtokIndx); // height
strtokIndx = strtok(NULL, " ");
percent = atoi(strtokIndx); // percent
strtokIndx = strtok(NULL, " ");
volume = atoi(strtokIndx); // volume
}
//==showParsedData
void showParsedData() {
if (Wifiactive == true) {
TelnetPrint.println();
TelnetPrint.println(F("Paresd Data:"));
TelnetPrint.print(F("Key "));
TelnetPrint.println(start);
TelnetPrint.print(F("Datum "));
TelnetPrint.println(date);
TelnetPrint.print(F("Druck "));
TelnetPrint.println(pressure);
TelnetPrint.print(F("Hoehe "));
TelnetPrint.println(height);
TelnetPrint.print(F("Prozent "));
TelnetPrint.println(percent);
TelnetPrint.print(F("Liter "));
TelnetPrint.println(volume);
TelnetPrint.println();
}
}
/**
IDE OTA Upload via ArduinoIDE
*/
void ideOTASetup()
{
ArduinoOTA.setHostname("ESP8266-Zisterne"); // IDE OTA give a name to your ESP for the Arduino IDE
ArduinoOTA.onStart([]() {
TelnetPrint.println("[otaide] OTA started");
});
}
/**
Updates the measure for the sensor
Args:
m: pointer to the Measurement to update
*/
void writeSensorMeasure(struct Measurement *m) {
m->waterlevel = height;
m->fragmentation = ESP.getHeapFragmentation();
time(&m->time);
if (Wifiactive == true) {
TelnetPrint.print(F("Recording measurement with waterlevel: "));
TelnetPrint.print(m->waterlevel);
TelnetPrint.println ();
TelnetPrint.print(F("Records in cache: "));
TelnetPrint.println(queue.size() + 1);
TelnetPrint.println(F("-----------------------------------------------------"));
TelnetPrint.println();
TelnetPrint.println();
}
}
/**
Transfers the data to the server.
*/
void transferData() {
TelnetPrint.println(F("Transfering data to InfluxDB"));
int endIndex = queue.size() - 1;
int startIndex = endIndex - 7;
if (startIndex < 0) {
startIndex = 0;
}
WiFiClient client; //https://forum.arduino.cc/t/absturze-beim-senden-und-empfangen-von-http-requestes/859254/29
HTTPClient http;
http.begin(client, "http://192.168.0.13:8086/write?db=peffs&u=admin&p=XXX");
http.addHeader("Content-type", "text/plain");
char *pos = payload;
for (int i = endIndex; i >= startIndex; i--) {
struct Measurement m = queue[i];
pos += sprintf(pos, "Zisterne value=%d,fragmentation=%d %d000000000\n", m.waterlevel, m.fragmentation, m.time);
}
TelnetPrint.println(payload);
int httpResponseCode = http.POST((uint8_t *)payload, strlen(payload));
http.end();
if (httpResponseCode == 204) {
// clear the transferred items from the queue
for (int i = endIndex; i >= startIndex; i--) {
queue.clear(i);
}
TelnetPrint.println(F("Data successfully sent to DB"));
TelnetPrint.print(F("Records in cache: "));
TelnetPrint.println(queue.size());
}
else {
TelnetPrint.println(F("DB not reachable. Retry...next Time."));
TelnetPrint.print(F("Records in cache: "));
TelnetPrint.println(queue.size());
}
TelnetPrint.println();
TelnetPrint.println(F("-----------------------------------------------------"));
TelnetPrint.println();
TelnetPrint.println();
}
/**
Daten nach Thinkspeak hochschieben
*/
void thingspeak() {
WiFiClient espClient;
ThingSpeak.begin(espClient);
ThingSpeak.setField(1, percent); // Feld 1 mit Prozent
ThingSpeak.setField(2, height); // Feld 2 mit Fuellhoehe
ThingSpeak.setField(3, volume); // Feld 3 mit Volumen
// ThingSpeak.setField(4, pressure); // Feld 4 mit Druck
int x = ThingSpeak.writeFields(myChannelNumber, myWriteAPIKey); // write to the ThingSpeak channel
if (x == 200) {
TelnetPrint.println();
TelnetPrint.println(F("ThinkSpeak Channel update successful."));
lastgood = localTime();
}
else {
// lastfail = localTime() + String(x);
TelnetPrint.println();
TelnetPrint.println("Problem updating ThinkSpeak channel. HTTP error code " + String(x));
}
}
//============SETUP============SETUP============SETUP============SETUP============SETUP============SETUP============SETUP============SETUP
void setup() {
Serial.begin(9600);
// ThingSpeak.begin(espClient); // Initialize ThingSpeak mit espClient <<--jetzt in Thinkspeak Void
Connect();
timeSetup();
ideOTASetup();
ArduinoOTA.begin();
// TelnetPrint.begin(); // now startet with Connect void
serverSetup();
}
//============LOOP============LOOP============LOOP============LOOP============LOOP============LOOP============LOOP============LOOP============LOOP
void loop() {
if (Wifiactive == true) {
server.handleClient(); // Listen for HTTP requests from clients
delay(2); //allow the cpu to switch to other tasks
ArduinoOTA.handle(); // IDE OTA Upload via ArduinoIDE
}
if (ESP.getFreeHeap() < MinFreeHeap) {
MinFreeHeap = ESP.getFreeHeap();
}
if (ESP.getHeapFragmentation() > MaxHeapFragmentation) {
MaxHeapFragmentation = ESP.getHeapFragmentation();
}
while (Serial.available() > 0 && newData == false) {
datenerkennung();
}
if (newData == true) {
strcpy(tempChars, receivedChars); // this temporary copy is necessary to protect the original data because strtok() used in parseData() replaces the commas with \0
parseData();
showParsedData();
struct Measurement m;
writeSensorMeasure(&m);
queue.push_back(m);
newData = false;
}
unsigned long currentMillis = millis();
if (currentMillis - HeartbeatMillis >= Heartbeatinterval) {
HeartbeatMillis = currentMillis;
if (WiFi.status() == WL_CONNECTED) {
++WiFiactivecount;
if (WiFiactivecount >= 30) {
Wifiactive = true;
TelnetPrint.print(F("."));
--WiFiactivecount;
if (TelnetPrintactive == false) {
TelnetPrint.begin();
TelnetPrintactive = true;
}
}
}
else {
WiFiactivecount = 0;
Wifiactive = false;
TelnetPrintactive = false;
TelnetPrint.stop();
}
}
if (currentMillis - ThingSpeakMillis >= ThingSpeakinterval) {
ThingSpeakMillis = currentMillis; // save the last time send to ThingSpeak
if (Wifiactive == true) {
thingspeak();
}
}
if ((currentMillis - InfluxDBMillis >= InfluxDBinterval) && (queue.size() >= 1)) {
InfluxDBMillis = currentMillis;
if (Wifiactive == true) {
TelnetPrint.print(F("Remaining free RAM: "));
TelnetPrint.println(formatBytes(ESP.getFreeHeap()));
TelnetPrint.print(F("Lowest free RAM: "));
TelnetPrint.println(formatBytes(MinFreeHeap));
TelnetPrint.print(F("RSSI: --> "));
TelnetPrint.println(WiFi.RSSI());
TelnetPrint.print(F("Heap Fragmentation: --> "));
TelnetPrint.println(ESP.getHeapFragmentation());
TelnetPrint.print(F("Heap LargestBlock: --> "));
TelnetPrint.println(formatBytes(ESP.getMaxFreeBlockSize()));
transferData ();
}
}
if ((currentMillis - WiFiMillis >= WiFiinterval) && (WiFi.status() != WL_CONNECTED)) {
WiFiMillis = currentMillis;
Connect();
}
}