Weather station sketch crash after a few hours

Hello everyone.
I built a weather station with an ESP32 and inspired by the Solar Powered WiFi Weather Station V3.0 project : https://www.instructables.com/Solar-Powered-WiFi-Weather-Station-V30/
My project uses sensors and the Sparkfun Weather Meter Kit : https://www.sparkfun.com/products/15901
All data are sent to my web site (http://meteobarsurloup.fpvracer.fr) to display gauges and histograms of sensor values.

My sketch is here:

//======================================================================================//
//                                                                                      //
//                               Station météo V1.0 Firmware                            //
//                                                                                      //
//             Développé par Steve Pirchi, Dernière mise à jour : 06.07.2021            //
//                                                                                      //
//======================================================================================//

//=================== Required Libraries  ==========================================

#include <WiFi.h>
#include <HTTPClient.h>
#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>
#include "Adafruit_VEML6070.h"
#include <BH1750.h>
#include "Adafruit_CCS811.h"
#include <OneWire.h>
#include <DallasTemperature.h>

//=================== Pin assignment definitions ==========================================

// rain & wind sensor
#define WIND_SPD_PIN 14
#define RAIN_PIN     25
#define WIND_DIR_PIN 35

// GPIO where the DS18B20 is connected to
const int oneWireBus = 4;

// UV sensor
Adafruit_VEML6070 uv = Adafruit_VEML6070();

// Light sensor
BH1750 lightMeter(0x23);

// BME280 sensor
Adafruit_BME280 bme;

// CJMCU 8118 sensor
Adafruit_CCS811 ccs;
// Setup a oneWire instance to communicate with any OneWire devices
OneWire oneWire(oneWireBus);
// Pass our oneWire reference to Dallas Temperature sensor
DallasTemperature sensors(&oneWire);


//========================= Declaring Variables and Constants ==================================

// the following variables are unsigned longs because the time, measured in
// milliseconds, will quickly become a bigger number than can be stored in an int.
unsigned long lastTime = 0;
// Timer set to 10 minutes (600000)
//unsigned long timerDelay = 600000;
// Set timer to 5 minutes (300000)
unsigned long timerDelay = 300000;

// Variables used in calculating the windspeed
volatile unsigned long timeSinceLastTick = 0;
volatile unsigned long lastTick = 0;
float windSpeed;

// Variables used in calculating the wind direction
int vin;
String windDir = "";

// Variables and constants used in tracking rainfall
#define S_IN_DAY   86400
#define S_IN_HR     3600
#define NO_RAIN_SAMPLES 2000
volatile long rainTickList[NO_RAIN_SAMPLES];
volatile int rainTickIndex = 0;
volatile int rainTicks = 0;
int rainLastDay = 0;
int rainLastHour = 0;
int rainLastHourStart = 0;
int rainLastDayStart = 0;
long secsClock = 0;

//========================= Sea level pressure ==================================

// sea level pressure from your location
#define SEALEVELPRESSURE_HPA (1020.6)

//========================= Variables for wifi server setup =============================

// Wifi credentials
const char* ssid = "NETGEAR78";
const char* password = "mypassword";

//Your Domain name with URL path or IP address with path
const char* serverName = "http://meteobarsurloup.fpvracer.fr/esp-post-data.php";

// Keep this API Key value to be compatible with the PHP code provided in the project page.
// If you change the apiKeyValue value, the PHP file /esp-post-data.php also needs to have the same key
String apiKeyValue = "tPmAT5Ab3j7F9";
String sensorName = "BME280";
String sensorLocation = "Le Bar sur Loup";

//========================= Setup Function ================================================

void setup() {
  Serial.begin(115200);
  delay(25);
  Serial.println("\nWeather station powered on.\n");

  // start wifi
  WiFi.begin(ssid, password);
  Serial.println("Connecting");
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.print("Connected to WiFi network with IP Address: ");
  Serial.println(WiFi.localIP());

  // start bme280 sensor
  bool status = bme.begin(0x77);
  if (!status) {
    Serial.println("Could not find a valid BME280 sensor, check wiring or change I2C address!");
    while (1);
  }
  Serial.println("Timer set to 30 seconds (timerDelay variable), it will take 30 seconds before publishing the first reading.");

  // start UV sensor
  uv.begin(VEML6070_1_T);  // pass in the integration time constant

  // start Light Meter sensor
  lightMeter.begin();

  // start CCS8111 sensor
  if (!ccs.begin()) {
    Serial.println("Failed to start sensor! Please check your wiring.");
    while (1);
  }
  //calibrate temperature sensor
  while (!ccs.available());
  float temp = ccs.calculateTemperature();
  ccs.setTempOffset(temp - 25.0);

  // Start the DS18B20 sensor
  sensors.begin();

  // Wind speed sensor setup. The windspeed is calculated according to the number
  //  of ticks per second. Timestamps are captured in the interrupt, and then converted
  //  into mph.
  pinMode(WIND_SPD_PIN, INPUT);     // Wind speed sensor
  attachInterrupt(digitalPinToInterrupt(WIND_SPD_PIN), windTick, FALLING);

  // Rain sesnor setup. Rainfall is tracked by ticks per second, and timestamps of
  //  ticks are tracked so rainfall can be "aged" (i.e., rain per hour, per day, etc)
  pinMode(RAIN_PIN, INPUT);     // Rain sensor
  attachInterrupt(digitalPinToInterrupt(RAIN_PIN), rainTick, FALLING);
  // Zero out the timestamp array.
  for (int i = 0; i < NO_RAIN_SAMPLES; i++) rainTickList[i] = 0;

}

//================================ Loop Function ==============================================

void loop() {

  Read_Sensors_Data();  // Read all the Sensors
  Send_Data();  // send data to web server
  // printdata();  // Print all the sensors data on the serial monitor

}

void Send_Data()
{
  //================================ Wifi function ==============================================
  //Send an HTTP POST request every 5 minutes
  if ((millis() - lastTime) > timerDelay) {
    //Check WiFi connection status
    if (WiFi.status() == WL_CONNECTED) {
      HTTPClient http;

      // Your Domain name with URL path or IP address with path
      http.begin(serverName);

      // Specify content-type header
      http.addHeader("Content-Type", "application/x-www-form-urlencoded");

      float temperatureC = sensors.getTempCByIndex(0);

      // Prepare your HTTP POST request data
      // value1 = bme280 temperature
      // value2 = bme280 humidity
      // value3 = bme280 pressure
      // value4 = UV sensor
      // value5 = light sensor
      // value6 = CCS8111 CO2 sensor
      // value7 = CCS8111 TVOC sensor
      // value8 = DS18B20 temperature sensor
      // value9 = rain meter
      // value10 = windspeed meter
      // value 11 = windir sensor
      String httpRequestData = "api_key=" + apiKeyValue + "&sensor=" + sensorName
                               + "&location=" + sensorLocation + "&value1=" + String(bme.readTemperature() - 1.1)
                               + "&value2=" + String(bme.readHumidity()) + "&value3=" + String((bme.readPressure() / 100.0F) + 24.5) + "&value4=" + String(uv.readUV()) + "&value5=" + String(lightMeter.readLightLevel()) + "&value6=" + String(ccs.geteCO2()) + "&value7=" + String(ccs.getTVOC()) + "&value8=" + String(temperatureC) + "&value9=" + String(float(rainLastHour) * 0.011, 3) + "&value10=" + String(windSpeed * 2.4 * 1.6) + "&value11=" + String(windDir) + "";
      Serial.print("httpRequestData: ");
      Serial.println(httpRequestData);

      // You can comment the httpRequestData variable above
      // then, use the httpRequestData variable below (for testing purposes without the BME280 sensor)
      //String httpRequestData = "api_key=tPmAT5Ab3j7F9&sensor=BME280&location=Office&value1=24.75&value2=49.54&value3=1005.14";

      // Send HTTP POST request
      int httpResponseCode = http.POST(httpRequestData);

      // If you need an HTTP request with a content type: text/plain
      //http.addHeader("Content-Type", "text/plain");
      //int httpResponseCode = http.POST("Hello, World!");

      // If you need an HTTP request with a content type: application/json, use the following:
      //http.addHeader("Content-Type", "application/json");
      //int httpResponseCode = http.POST("{\"value1\":\"19\",\"value2\":\"67\",\"value3\":\"78\"}");

      if (httpResponseCode > 0) {
        Serial.print("HTTP Response code: ");
        Serial.println(httpResponseCode);
      }
      else {
        Serial.print("Error code: ");
        Serial.println(httpResponseCode);
      }
      // Free resources
      http.end();
    }
    else {
      Serial.println("WiFi Disconnected");
    }
    lastTime = millis();
  }
}

//================================ Read Sensors ==============================================

void Read_Sensors_Data() {

  //================================ CCS8111 sensor ==============================================
  // Call sensors.requestTemperatures() to issue a global temperature and Requests to all devices on the bus
  sensors.requestTemperatures();

  //================================ Sparfun sensors ==============================================
  // Read Weather Meters Datas ( Wind Speed, Rain Fall and Wind Direction )

  static unsigned long outLoopTimer = 0;
  static unsigned long wundergroundUpdateTimer = 0;
  static unsigned long clockTimer = 0;
  static unsigned long tempMSClock = 0;

  // Create a seconds clock based on the millis() count. We use this
  //  to track rainfall by the second. We've done this because the millis()
  //  count overflows eventually, in a way that makes tracking time stamps
  //  very difficult.
  tempMSClock += millis() - clockTimer;
  clockTimer = millis();
  while (tempMSClock >= 1000)
  {
    secsClock++;
    tempMSClock -= 1000;
  }

  // This is a once-per-second timer that calculates and prints off various
  //  values from the sensors attached to the system.
  if (millis() - outLoopTimer >= 2000)
  {
    outLoopTimer = millis();
    // Windspeed calculation, in mph. timeSinceLastTick gets updated by an
    //  interrupt when ticks come in from the wind speed sensor.
    if (timeSinceLastTick != 0) windSpeed = 1000.0 / timeSinceLastTick;

    // Calculate the wind direction and display it as a string.
    windDirCalc();

    rainLastHour = 0;
    rainLastDay = 0;
    // If there are any captured rain sensor ticks...
    if (rainTicks > 0)
    {
      // Start at the end of the list. rainTickIndex will always be one greater
      //  than the number of captured samples.
      int i = rainTickIndex - 1;

      // Iterate over the list and count up the number of samples that have been
      //  captured with time stamps in the last hour.
      while ((rainTickList[i] >= secsClock - S_IN_HR) && rainTickList[i] != 0)
      {
        i--;
        if (i < 0) i = NO_RAIN_SAMPLES - 1;
        rainLastHour++;
      }

      // Repeat the process, this time over days.
      i = rainTickIndex - 1;
      while ((rainTickList[i] >= secsClock - S_IN_DAY) && rainTickList[i] != 0)
      {
        i--;
        if (i < 0) i = NO_RAIN_SAMPLES - 1;
        rainLastDay++;
      }
      rainLastDayStart = i;
    }
  }
}



// Keep track of when the last tick came in on the wind sensor.
void windTick(void)
{
  timeSinceLastTick = millis() - lastTick;
  lastTick = millis();
}

// Capture timestamp of when the rain sensor got tripped.
void rainTick(void)
{
  rainTickList[rainTickIndex++] = secsClock;
  if (rainTickIndex == NO_RAIN_SAMPLES) rainTickIndex = 0;
  rainTicks++;
}

// reading wind direction
void windDirCalc()
{

  vin = analogRead(WIND_DIR_PIN);

  if      (vin < 150) windDir = "202.5";
  else if (vin < 300) windDir = "180";
  else if (vin < 400) windDir = "247.5";
  else if (vin < 600) windDir = "225";
  else if (vin < 900) windDir = "292.5";
  else if (vin < 1100) windDir = "270";
  else if (vin < 1500) windDir = "112.5";
  else if (vin < 1700) windDir = "135";
  else if (vin < 2250) windDir = "337.5";
  else if (vin < 2350) windDir = "315";
  else if (vin < 2700) windDir = "67.5";
  else if (vin < 3000) windDir = "90";
  else if (vin < 3200) windDir = "22.5";
  else if (vin < 3400) windDir = "45";
  else if (vin < 4000) windDir = "0";
  else windDir = "0";
}

//================================ Print data function ==============================================
void printdata() {

  float temperatureC = sensors.getTempCByIndex(0);

  // print values from sensor
  Serial.println("*************************************************");
  Serial.println("BME 280 TEST");
  Serial.print("Temperature: ");
  Serial.println(bme.readTemperature() - 1.1);
  Serial.print("Humidite: ");
  Serial.println(bme.readHumidity());
  Serial.print("Pression atmospherique: ");
  // calibration altitude(+24.5)
  Serial.println((bme.readPressure() / 100.0F) + 24.5);
  Serial.println("--------------------------------");
  Serial.println("VEML6070 TEST");
  Serial.print("UV light level: ");
  Serial.println(uv.readUV());
  Serial.println("--------------------------------");
  Serial.println("BH1750 TEST");
  Serial.print("Light level: ");
  Serial.println(lightMeter.readLightLevel());
  Serial.println("--------------------------------");
  Serial.println("CJMCU 8118 TEST");
  if (ccs.available()) {
    float temp = ccs.calculateTemperature();
    if (!ccs.readData()) {
      Serial.print("eCO2: ");
      Serial.println(ccs.geteCO2());
      Serial.print("TVOC: ");
      Serial.println(ccs.getTVOC());
      Serial.print("ppb   Temp:");
      Serial.println(temp);
    }
    else {
      Serial.println("ERROR!");
      while (1);
    }
  }
  Serial.println("--------------------------------");
  Serial.println("DS18B20 TEST");
  Serial.print("Temperature: ");
  Serial.print(temperatureC);
  Serial.println("ºC");
  Serial.println("Winspeed TEST");
  Serial.print("Windspeed: "); Serial.print(windSpeed * 2.4 * 1.6); Serial.println(" km/h");
  Serial.println("Winsdir TEST");
  Serial.print("Wind dir: ");  Serial.print("  "); Serial.println(windDir);
  Serial.println("Rainfall TEST");
  Serial.print("Rainfall last hour: "); Serial.println(float(rainLastHour) * 0.011, 3);
  Serial.println("*************************************************");
}

//=============================End of the Program =================================

The main problem is that the sketch crashes after 10 hours, the sensor values are no longer sent to my web site.
I suspect there is a problem with functions of the Rain Fall Sensor or Anemometer and after some code changes I have not fixed this problem.

Another problem is sometimes that the wind speed gives randomely a very high value (3840 km / h) and the rain gauge shows rain when it is not raining ...

Hope someone will see my code and help me.

please post using </>

excuse me but where do i have to use </> ?

Hi, @stevensavior1
Welcome to the forum.

To add code please click this link;

Tom... :smiley: :+1: :coffee: :australia:

sorry. click the </> to post code

ok i do this, sorry about that.

If you are sure that the electronics / wiring is not causing this, have you tried to leave it plugged to a computer and see if any exceptions happen prior to the "crash"? In your code where you print "WiFi Disconnected", have you tried to reconnect to WiFi?

EDIT: You could try to flash the on-board LED (or add a LED), periodically to test if the ESP32 has crashed or if it works OK but not transmits.

Thanks for your reply, i will dismantle weather station and monitoring it for to see if there are any exceptions after a few hours.

I wonder if it actually crashed. Danois90 makes a good point that Once wifi connectivity is lost, it will appear that it did.

You could keep an eye on free memory in case you have a leak somewhere - you're using String objects a bit, so you may have. Your controller has plenty of RAM though, so I would expect it to take longer to fail.

How consistent is the time to crash? Is it time of day related? Can you crash it sooner if you send data to the web server more frequently?

  • if the wifi connection is lost I don't understand why. the wifi router located in my garage is less than 5 meters from the esp32. The wifi signal is 5/5.
  • as for the free memory Arduino IDE says I am only using 14% of the available memory.
  • the crash seems to happen after about 10 hours of use at any time of the day.
  • I haven't tried sending the data to the web server more frequently. 5 minutes in this case.

I will try several things in the coming days and I will come back to this post to say what I found.

Thanks for your suggestions.

The IDE may tell you you're using 14% of your RAM, but that's a static measurement. You might want to know how much you're actually using when it's running because a ten hour runtime limit is hinting at a possible memory leak.

ok i understand that but how I can monitoring the memory used for several hours ?

There is a function called freeMem or FreeMemory that you can add to your code. Adafruit has one, although whether it works on an ESP32 I am not sure.

Then just print its value to serial at five minute intervals. If it's dropping, you have a leak.

i think this can help me for monitoring memory : https://techtutorialsx.com/2017/12/17/esp32-arduino-getting-the-free-heap/

Hi @stevensavior1
.
I also have a working weather station very well.
For a long time I had problems very similar to the one you are having.
The reason in my case was that she was far away from my router and lost connection with it.
As the connection was only "initialized" in setup(0, I could no longer communicate with the station.
The solution was to use an external antenna for the station.

Another detail that I noticed in your sketch was in the routine called by interrupt, I recommend the use of IRAM_ATTR , thus, the routine called by the interrupt:

void IRAM_ATTR windTick(void).

RV mineirin

thank for reply i will monitoring if wifi connection is lost or is memory leak problem.

The ESP32 uses String in its web libraries so difficult to avoid them. There may also be memory leaks in their libraries.
Check out Taming Arduino Strings for some guide lines on using your own Strings.

Do checkout possible WiFi issues and add code to re-connect if the connection is lost and test it for example by turning the router off and off, putting the ESP32 in a metal box etc.

BUT the quickest fix is probably to have your ESP32 reboot, say every 8hrs, see
ESP32/ESP8266 Adding Periodic Automatic Reboots
I have a commercial project here that I have setup to reboot daily at 3am. It sends once every 10mins.

The "String" class is not as big a problem on ESP / FreeRTOS as it is in simple AVR based MCU's. ESP's feature a much better MMU compared to the AVR's, so I would not consider memory leakage as being the obvious culprit in this case.

You may need to add some debouncing in the ISR here:

// Keep track of when the last tick came in on the wind sensor.
void windTick(void)
{
  timeSinceLastTick = millis() - lastTick;
  lastTick = millis();
}

Don't accept a result unless the last accepted result occurred more than Xms ago.

As for the crashes:

     while ((rainTickList[i] >= secsClock - S_IN_HR) && rainTickList[i] != 0)
      {
        i--;
        if (i < 0) i = NO_RAIN_SAMPLES - 1;
        rainLastHour++;
      }

If you succeed in getting an analysis of stack trace, you may be guided to the line in your code which causes the problem.
However, you could look carefully at constructs such as that above to ensure they don't cause an infinite loop. Also suspect array bounds overflows elsewhere.
You could also comment out suspect bits of code, using a sort of binary chop process, to narrow down the error.

Great advice, but an infinite loop would trigger a WatchDog timeout and reboot the ESP. The problem with transmission stopping after some time does not suggest this being the case.