Weather station sketch crash after a few hours

In general that is true. However, in that specific case also a signed int (int32_t) , that is rainLastHour, could overflow with undefined behaviour.

ESP Strings do handle small strings ~12 long using an internal static char[ ].
However Strings are actually safer on AVR boards, because the memory allocation always leaves 128bytes for stack. The ESP memory allocation does not have this stack buffer.

I just come back from holidays and i thank all for you replies.
I rewrite code with:

  • wifi reconnect function for prevent wifi connection lost:
WiFi.onEvent(WiFiStationDisconnected, SYSTEM_EVENT_STA_DISCONNECTED);
  • restart function with Esp.h library every 6 hours for prevent stekch crash:
void restartEsp() {
  long now = millis();
  if (now - restartDelay > 3600000 * 6){
    ESP.restart();
  }  
}
  • add some debouncing for prevent false values of rain sensor and wind speed sensor:
// add some debouncing to rainmeter
unsigned long lastDebounceRain = 0;  
unsigned long debounceDelayRain = 50; 

// add some debouncing to anometer
unsigned long lastDebounceWind = 0;  
unsigned long debounceDelayWind = 50;

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

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

Change:

attachInterrupt(digitalPinToInterrupt(WIND_SPD_PIN), windTick, FALLING);

attachInterrupt(digitalPinToInterrupt(RAIN_PIN), rainTick, FALLING);

to

attachInterrupt(digitalPinToInterrupt(WIND_SPD_PIN), windTick, RISING);

attachInterrupt(digitalPinToInterrupt(RAIN_PIN), rainTick, RISING);

as in this exemple that use Sparkfun Weather Kit.

The sketch has not crashed for 20 hours of operation so it has to be good.

But rain and wind speed sensors only send the values of 0 now.
Maybe debouceDelay is too short or my functions are wrong ?

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

//=================== Required Libraries  ==========================================
#include "Esp.h"
#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;

// variable for restart with Watchdog
unsigned long restartDelay = millis();;

// add some debouncing to rainmeter
unsigned long lastDebounceRain = 0;  
unsigned long debounceDelayRain = 50; 

// add some debouncing to anometer
unsigned long lastDebounceWind = 0;  
unsigned long debounceDelayWind = 50;

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

// 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;

// Variables used in calculating the wind direction & wind speed
String windDir = "";
float windSpeed = 0.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 = "mywifipassword";

//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";

//========================= Wifi Function for prevent disconnecting================================================

void WiFiStationDisconnected(WiFiEvent_t event, WiFiEventInfo_t info){
  Serial.println("Disconnected from WiFi access point");
  Serial.print("WiFi lost connection. Reason: ");
  Serial.println(info.disconnected.reason);
  Serial.println("Trying to Reconnect");
  WiFi.begin(ssid, password);
}

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

void setup() {
  delay(5);    // The CCS811 wants a brief delay after startup.
  Serial.begin(115200);

  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());
  }

  // lunch event for detect wifi disconnecting
  WiFi.onEvent(WiFiStationDisconnected, SYSTEM_EVENT_STA_DISCONNECTED);

  // 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, RISING);

  // 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, RISING);
  // Zero out the timestamp array.
  for (int i = 0; i < NO_RAIN_SAMPLES; i++) rainTickList[i] = 0;

  // 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();

}

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

void loop() {

  Read_Sensors_Data();  // Read all the Sensors
  Send_Data();  // send data to web server
  restartEsp();
  // 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(analogRead(WIND_DIR_PIN));

    // Calculate the amount of rain in the last day and hour.
    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)
{
  lastDebounceWind = millis();
  if ((millis() - lastDebounceWind) > debounceDelayWind) {
  timeSinceLastTick = millis() - lastTick;
  lastTick = millis();
  }
}

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

// restart function for prevent freeze
void restartEsp() {
  long now = millis();
  if (now - restartDelay > 3600000 * 6){
    ESP.restart();
  }  
}

// reading wind direction
void windDirCalc(int vin)
{
  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 =================================

This, at least, is not going to function as you want since that value of millis() - lastDebounceWind will almost always be 0 and very occasionally 1.

Ok. Can you help me to add some debouncing to my functions windTick and rainTick to get true values ? Thank you.

Here is an example:

void windTick(void)
{
  if ((millis() - lastDebounceWind) > debounceDelayWind) {
    timeSinceLastTick = millis() - lastTick;
    lastTick = millis();
    lastDebounceWind = millis();
  }
}

But you could probably refine it since you are already holding a value of millis() for the last successful operation of the ISR in the variable lastTick.

In principle, if the ISR is triggered too soon after the last trigger, you want to simply ignore that trigger.

My Debouncing Switches in Arduino tutorial has a debounce class with 50mS delay that you edit in the .h file.

I made these changes but something is wrong the rain sensor indicates that it is raining while it is not raining. But wind speed sensor does not indicate any more very high values ​​it is already that.

// add some debouncing to rainmeter
unsigned long lastDebounceRain = 0;  
unsigned long debounceDelayRain = 50; 

// add some debouncing to anometer
unsigned long lastDebounceWind = 0;  
unsigned long debounceDelayWind = 50;

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

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

The above variables should be declared additionally as volatile because these are updated in an ISR.

Here is my code for an ESP32 using the Sparkfun weather thingys.

/*
   Chappie Weather
   process wind speed direction and rain fall.
*/
#include <WiFi.h>
#include <PubSubClient.h>
#include "certs.h"
#include "sdkconfig.h"
#include "esp_system.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/timers.h"
#include "freertos/event_groups.h"
#include "driver/pcnt.h"
#include <driver/adc.h>
#include <SimpleKalmanFilter.h>
#include <ESP32Time.h>
////
ESP32Time rtc;
WiFiClient wifiClient;
PubSubClient MQTTclient(mqtt_server, mqtt_port, wifiClient);
////
float CalculatedVoltage = 0.0f;
float kph = 0.0f;
float rain  = 0.0f;
/*
   PCNT PCNT_UNIT_0, PCNT_CHANNEL_0 GPIO_NUM_15 = pulse input pin
   PCNT PCNT_UNIT_1, PCNT_CHANNEL_0 GPIO_NUM_4 = pulse input pin
*/
pcnt_unit_t pcnt_unit00 = PCNT_UNIT_0; //pcnt unit 0 channel 0
pcnt_unit_t pcnt_unit10 = PCNT_UNIT_1; //pcnt unit 1 channel 0
//
//
hw_timer_t * timer = NULL;
//
#define evtAnemometer  ( 1 << 0 )
#define evtRainFall    ( 1 << 1 )
#define evtParseMQTT   ( 1 << 2 )
EventGroupHandle_t eg;
#define OneMinuteGroup ( evtAnemometer | evtRainFall )
////
QueueHandle_t xQ_Message; // payload and topic queue of MQTT payload and topic
const int payloadSize = 100;
struct stu_message
{
  char payload [payloadSize] = {'\0'};
  String topic ;
} x_message;
////
SemaphoreHandle_t sema_MQTT_KeepAlive;
SemaphoreHandle_t sema_mqttOK;
SemaphoreHandle_t sema_CalculatedVoltage;
////
int mqttOK = 0; // stores a count value that is used to cause an esp reset
volatile bool TimeSet = false;
////
/*
   A single subject has been scribed to, the mqtt broker sends out "OK" messages if the client receives an OK message the mqttOK value is set back to zero
*/
////
void IRAM_ATTR mqttCallback(char* topic, byte * payload, unsigned int length)
{
  memset( x_message.payload, '\0', payloadSize ); // clear payload char buffer
  x_message.topic = ""; //clear topic string buffer
  x_message.topic = topic; //store new topic
    int i = 0; // extract payload
    for ( i; i < length; i++)
    {
      x_message.payload[i] = ((char)payload[i]);
    }
    x_message.payload[i] = '\0';
    xQueueOverwrite( xQ_Message, (void *) &x_message );// send data to queue
} // void mqttCallback(char* topic, byte* payload, unsigned int length)
////
// interrupt service routine for WiFi events put into IRAM
void IRAM_ATTR WiFiEvent(WiFiEvent_t event)
{
  switch (event) {
    case SYSTEM_EVENT_STA_CONNECTED:
      break;
    case SYSTEM_EVENT_STA_DISCONNECTED:
      log_i("Disconnected from WiFi access point");
      break;
    case SYSTEM_EVENT_AP_STADISCONNECTED:
      log_i("WiFi client disconnected");
      break;
    default: break;
  }
} // void IRAM_ATTR WiFiEvent(WiFiEvent_t event)
////
void IRAM_ATTR onTimer()
{
  BaseType_t xHigherPriorityTaskWoken;
  xEventGroupSetBitsFromISR(eg, OneMinuteGroup, &xHigherPriorityTaskWoken);
  //log_i( "ticker" );

} // void IRAM_ATTR onTimer()
////
void setup()
{
  eg = xEventGroupCreate(); // get an event group handle
  //  x_message.topic.reserve(300);
  adc1_config_width(ADC_WIDTH_12Bit);
  adc1_config_channel_atten(ADC1_CHANNEL_6, ADC_ATTEN_DB_11);// using GPIO 34 wind direction
  adc1_config_channel_atten(ADC1_CHANNEL_3, ADC_ATTEN_DB_11);// using GPIO 39 current
  adc1_config_channel_atten(ADC1_CHANNEL_0, ADC_ATTEN_DB_11);// using GPIO 36 battery volts

  // hardware timer 4 set for one minute alarm
  timer = timerBegin( 3, 80, true );
  timerAttachInterrupt( timer, &onTimer, true );
  timerAlarmWrite(timer, 60000000, true);
  timerAlarmEnable(timer);
  /* Initialize PCNT's counter */
  int PCNT_H_LIM_VAL         = 3000;
  int PCNT_L_LIM_VAL         = -10;
  // 1st PCNT counter
  pcnt_config_t pcnt_config  = {};
  pcnt_config.pulse_gpio_num = GPIO_NUM_15;// Set PCNT input signal and control GPIOs
  pcnt_config.ctrl_gpio_num  = PCNT_PIN_NOT_USED;
  pcnt_config.channel        = PCNT_CHANNEL_0;
  pcnt_config.unit           = PCNT_UNIT_0;
  // What to do on the positive / negative edge of pulse input?
  pcnt_config.pos_mode       = PCNT_COUNT_INC;   // Count up on the positive edge
  pcnt_config.neg_mode       = PCNT_COUNT_DIS;   // Count down disable
  // What to do when control input is low or high?
  pcnt_config.lctrl_mode     = PCNT_MODE_KEEP; // do not count if low reverse
  pcnt_config.hctrl_mode     = PCNT_MODE_KEEP;    // Keep the primary counter mode if high
  // Set the maximum and minimum limit values to watch
  pcnt_config.counter_h_lim  = PCNT_H_LIM_VAL;
  pcnt_config.counter_l_lim  = PCNT_L_LIM_VAL;
  pcnt_unit_config(&pcnt_config); // Initialize PCNT unit
  pcnt_set_filter_value( PCNT_UNIT_0, 1); //Configure and enable the input filter
  pcnt_filter_enable( PCNT_UNIT_0 );
  pcnt_counter_pause( PCNT_UNIT_0 );
  pcnt_counter_clear( PCNT_UNIT_0 );
  pcnt_counter_resume( PCNT_UNIT_0); // start the show
  // setup 2nd PCNT
  pcnt_config = {};
  pcnt_config.pulse_gpio_num = GPIO_NUM_4;
  pcnt_config.ctrl_gpio_num  = PCNT_PIN_NOT_USED;
  pcnt_config.channel        = PCNT_CHANNEL_0;
  pcnt_config.unit           = PCNT_UNIT_1;
  pcnt_config.pos_mode       = PCNT_COUNT_INC;
  pcnt_config.neg_mode       = PCNT_COUNT_DIS;
  pcnt_config.lctrl_mode     = PCNT_MODE_KEEP;
  pcnt_config.hctrl_mode     = PCNT_MODE_KEEP;
  pcnt_config.counter_h_lim  = PCNT_H_LIM_VAL;
  pcnt_config.counter_l_lim  = PCNT_L_LIM_VAL;
  pcnt_unit_config(&pcnt_config);
  //pcnt_set_filter_value( PCNT_UNIT_1, 1 );
  //pcnt_filter_enable  ( PCNT_UNIT_1 );
  pcnt_counter_pause  ( PCNT_UNIT_1 );
  pcnt_counter_clear  ( PCNT_UNIT_1 );
  pcnt_counter_resume ( PCNT_UNIT_1 );
  //
  xQ_Message = xQueueCreate( 1, sizeof(stu_message) );
  //
  sema_CalculatedVoltage = xSemaphoreCreateBinary();
  xSemaphoreGive( sema_CalculatedVoltage );
  sema_mqttOK = xSemaphoreCreateBinary();
  xSemaphoreGive( sema_mqttOK );
  sema_MQTT_KeepAlive = xSemaphoreCreateBinary();
  ///
  xTaskCreatePinnedToCore( MQTTkeepalive, "MQTTkeepalive", 15000, NULL, 5, NULL, 1 );
  xTaskCreatePinnedToCore( fparseMQTT, "fparseMQTT", 10000, NULL, 5, NULL, 1 ); // assign all to core 1, WiFi in use.
  xTaskCreatePinnedToCore( fReadBattery, "fReadBattery", 4000, NULL, 3, NULL, 1 );
  xTaskCreatePinnedToCore( fReadCurrent, "fReadCurrent", 4000, NULL, 3, NULL, 1 );
  xTaskCreatePinnedToCore( fWindDirection, "fWindDirection", 10000, NULL, 4, NULL, 1 );
  xTaskCreatePinnedToCore( fAnemometer, "fAnemometer", 10000, NULL, 4, NULL, 1 );
  xTaskCreatePinnedToCore( fRainFall, "fRainFall", 10000, NULL, 4, NULL, 1 );
  xTaskCreatePinnedToCore( fmqttWatchDog, "fmqttWatchDog", 3000, NULL, 3, NULL, 1 ); // assign all to core 1
} //void setup()
////


void fWindDirection( void *pvParameters )
// read the wind direction sensor, return heading in degrees
{
  float adcValue = 0.0f;
  uint64_t TimePastKalman  = esp_timer_get_time();
  SimpleKalmanFilter KF_ADC( 1.0f, 1.0f, .01f );
  float high = 0.0f;
  float low = 2000.0f;
  float ADscale = 3.3f / 4096.0f;
  TickType_t xLastWakeTime = xTaskGetTickCount();
  const TickType_t xFrequency = 100; //delay for mS
  int count = 0;
  String windDirection;
  windDirection.reserve(20);
  String MQTTinfo = "";
  MQTTinfo.reserve( 150 );
  while ( !MQTTclient.connected() )
  {
    vTaskDelay( 250 );
  }
  for (;;)
  {
    windDirection = "";
    adcValue = float( adc1_get_raw(ADC1_CHANNEL_6) ); //take a raw ADC reading
    KF_ADC.setProcessNoise( (esp_timer_get_time() - TimePastKalman) / 1000000.0f ); //get time, in microsecods, since last readings
    adcValue = KF_ADC.updateEstimate( adcValue ); // apply simple Kalman filter
    TimePastKalman = esp_timer_get_time(); // time of update complete
    adcValue = adcValue * ADscale;
    log_i( "                       adc %f", adcValue );
    if ( (adcValue >= 0.0f) & (adcValue <= .25f )  )
    {
     // log_i( " n" );
      windDirection.concat( "N" );
    }
    //
//    if ( (adcValue >= .25f) & adcValue <= .5f )
//    {
//      log_i( " nne" );
//      windDirection.concat( "NNE");
//    }
    //
    if ( (adcValue > .25f) & (adcValue <= .6f ) )
    {
    //  log_i( " e" );
      windDirection.concat( "E" );
    }
//    if ( (adcValue > 1.0f) & (adcValue < 1.75f) )
//    {
//      log_i( " sse" );
//      windDirection.concat( "S-SE");
//    }
//    //
//        if ( (adcValue >= 1.75f) & (adcValue < 2.2f) )
//    {
//      log_i( " nnw" );
//      windDirection.concat( "N-NW" );
//    }
    //
    if ( (adcValue > 2.0f) & ( adcValue < 3.3f) )
    {
   //   log_i( " s" );
      windDirection.concat( "S");
    }
    //
//    if ( (adcValue > 2.6f) & (adcValue < 3.0f ) )
//    {
//      log_i( " ssw" );
//      windDirection.concat( "S-SW" );
//    }
    if ( (adcValue >= 1.7f) & (adcValue < 2.0f ) )
    {
     // log_i( " w" );
      windDirection.concat( "W" );
    }
    if( count >= 30 )
    {
      MQTTinfo.concat( String(kph, 2) );
      MQTTinfo.concat( ",");
      MQTTinfo.concat( windDirection );
      MQTTinfo.concat( ",");
      MQTTinfo.concat( String(rain,2) );
      xSemaphoreTake( sema_MQTT_KeepAlive, portMAX_DELAY );
      MQTTclient.publish( topicWSWDRF, MQTTinfo.c_str() );
      xSemaphoreGive( sema_MQTT_KeepAlive );
      count = 0;
    }
    count++;
    MQTTinfo = "";
    xLastWakeTime = xTaskGetTickCount();
    vTaskDelayUntil( &xLastWakeTime, xFrequency );
  }
  vTaskDelete ( NULL );
}
// read rainfall
void fRainFall( void *pvParemeters )
{
  int16_t count = 0;
  pcnt_counter_pause( PCNT_UNIT_1 );
  pcnt_counter_clear( PCNT_UNIT_1 );
  pcnt_counter_resume( PCNT_UNIT_1 );
  for  (;; )
  {
    xEventGroupWaitBits (eg, evtRainFall, pdTRUE, pdTRUE, portMAX_DELAY);
    pcnt_counter_pause( PCNT_UNIT_1 );
    pcnt_get_counter_value( PCNT_UNIT_1, &count );
    pcnt_counter_clear( PCNT_UNIT_1 );
    pcnt_counter_resume( PCNT_UNIT_1 );
    if ( count != 0 )
    {
      // 0.2794mm of rain per click clear clicks at mid night
      rain = 0.2794f * (float)count;
      log_i( "count %d, rain rain = %f mm", count, rain );
    }
  }
  vTaskDelete ( NULL );
}
////
void fAnemometer( void *pvParameters )
{
  int16_t count = 0;
  pcnt_counter_clear(PCNT_UNIT_0);
  pcnt_counter_resume(PCNT_UNIT_0);
  for (;;)
  {
    xEventGroupWaitBits (eg, evtAnemometer, pdTRUE, pdTRUE, portMAX_DELAY);
    pcnt_counter_pause( PCNT_UNIT_0 );
    pcnt_get_counter_value( PCNT_UNIT_0, &count); //int16_t *count
    // A wind speed of 2.4km/h causes the switch to close once per second
    kph = 2.4 * ((float)count / 60.0f);
    //if ( count != 0 )
    //{
      log_i( "count %d, wind Kph = %f", count, kph );
    //}
    count = 0;
    pcnt_counter_clear( PCNT_UNIT_0 );
    pcnt_counter_resume( PCNT_UNIT_0 );
  }
  vTaskDelete ( NULL );
}
//////
void fmqttWatchDog( void * paramater )
{
  int UpdateImeTrigger = 86400; //seconds in a day
  int UpdateTimeInterval = 86300; // 1st time update in 100 counts
  int maxNonMQTTresponse = 60;
  for (;;)
  {
    vTaskDelay( 1000 );
    if ( mqttOK >= maxNonMQTTresponse )
    {
      ESP.restart();
    }
    xSemaphoreTake( sema_mqttOK, portMAX_DELAY );
    mqttOK++;
    xSemaphoreGive( sema_mqttOK );
    UpdateTimeInterval++; // trigger new time get
    if ( UpdateTimeInterval >= UpdateImeTrigger )
    {
      TimeSet = false; // sets doneTime to false to get an updated time after a days count of seconds
      UpdateTimeInterval = 0;
    }
  }
  vTaskDelete( NULL );
}
//////
void fparseMQTT( void *pvParameters )
{
  struct stu_message px_message;
  for (;;)
  {
    if ( xQueueReceive(xQ_Message, &px_message, portMAX_DELAY) == pdTRUE )
    {
      // parse the time from the OK message and update MCU time
      if ( String(px_message.topic) == topicOK )
      {
        if ( !TimeSet)
        {
          String temp = "";
          temp =  px_message.payload[0];
          temp += px_message.payload[1];
          temp += px_message.payload[2];
          temp += px_message.payload[3];
          int year =  temp.toInt();
          temp = "";
          temp =  px_message.payload[5];
          temp += px_message.payload[6];
          int month =  temp.toInt();
          temp =  "";
          temp =  px_message.payload[8];
          temp += px_message.payload[9];
          int day =  temp.toInt();
          temp = "";
          temp = px_message.payload[11];
          temp += px_message.payload[12];
          int hour =  temp.toInt();
          temp = "";
          temp = px_message.payload[14];
          temp += px_message.payload[15];
          int min =  temp.toInt();
          rtc.setTime( 0, min, hour, day, month, year );
          log_i( "rtc  %s ", rtc.getTime() );
          TimeSet = true;
        }
      }
      //
    } //if ( xQueueReceive(xQ_Message, &px_message, portMAX_DELAY) == pdTRUE )
    xSemaphoreTake( sema_mqttOK, portMAX_DELAY );
    mqttOK = 0;
    xSemaphoreGive( sema_mqttOK );
  }
} // void fparseMQTT( void *pvParameters )
//////
void fReadCurrent( void * parameter )
{
  float ADbits = 4096.0f;
  float ref_voltage = 3.3f;
  float offSET = .0f;
  uint64_t TimePastKalman  = esp_timer_get_time(); // used by the Kalman filter UpdateProcessNoise, time since last kalman calculation
  SimpleKalmanFilter KF_I( 1.0f, 1.0f, .01f );
  float mA = 0.0f;
  int   printCount = 0;
  const float mVperAmp = 100.0f;
  float adcValue = 0;
  float Voltage = 0;
  float Power = 0.0;
  String powerInfo = "";
  powerInfo.reserve( 150 );
  while ( !MQTTclient.connected() )
  {
    vTaskDelay( 250 );
  }
  TickType_t xLastWakeTime = xTaskGetTickCount();
  const TickType_t xFrequency = 1000; //delay for mS
  for (;;)
  {
    adc1_get_raw(ADC1_CHANNEL_3); // read once discard reading
    adcValue = ( (float)adc1_get_raw(ADC1_CHANNEL_3) );
    Voltage = ( (adcValue * ref_voltage) / ADbits ) + offSET; // Gets you mV
    mA = Voltage / mVperAmp; // get amps
    KF_I.setProcessNoise( (esp_timer_get_time() - TimePastKalman) / 1000000.0f ); //get time, in microsecods, since last readings
    mA = KF_I.updateEstimate( mA ); // apply simple Kalman filter
    TimePastKalman = esp_timer_get_time(); // time of update complete
    printCount++;
    if ( printCount == 60 )
    {
      xSemaphoreTake( sema_CalculatedVoltage, portMAX_DELAY);
      Power = CalculatedVoltage * mA;
      log_i( "Voltage=%f mA=%f Power=%f", CalculatedVoltage, mA, Power );
      printCount = 0;
      powerInfo.concat( String(CalculatedVoltage, 2) );
      xSemaphoreGive( sema_CalculatedVoltage );
      powerInfo.concat( ",");
      powerInfo.concat( String(mA, 2) );
      powerInfo.concat( ",");
      powerInfo.concat( String(Power, 4) );
      xSemaphoreTake( sema_MQTT_KeepAlive, portMAX_DELAY );
      MQTTclient.publish( topicPower, powerInfo.c_str() );
      xSemaphoreGive( sema_MQTT_KeepAlive );
      powerInfo = "";
    }
    xLastWakeTime = xTaskGetTickCount();
    vTaskDelayUntil( &xLastWakeTime, xFrequency );
  }
  vTaskDelete( NULL );
} //void fReadCurrent( void * parameter )
////
void fReadBattery( void * parameter )
{
  //float ADbits = 4096.0f;
  //float ref_voltage = 3.3f;
  float ADscale = 3.3f / 4096.0f;
  float adcValue = 0.0f;
  float offSET = 0.0f;
  const float r1 = 50500.0f; // R1 in ohm, 50K
  const float r2 = 10000.0f; // R2 in ohm, 10k potentiometer
  //float Vscale = (r1+r2)/r2;
  float Vbatt = 0.0f;
  int printCount = 0;
  float vRefScale = (3.3f / 4096.0f) * ((r1 + r2) / r2);
  uint64_t TimePastKalman  = esp_timer_get_time(); // used by the Kalman filter UpdateProcessNoise, time since last kalman calculation
  SimpleKalmanFilter KF_ADC_b( 1.0f, 1.0f, .01f );
  TickType_t xLastWakeTime = xTaskGetTickCount();
  const TickType_t xFrequency = 1000; //delay for mS
  for (;;)
  {
    adc1_get_raw(ADC1_CHANNEL_0); //read and discard
    adcValue = float( adc1_get_raw(ADC1_CHANNEL_0) ); //take a raw ADC reading
    KF_ADC_b.setProcessNoise( (esp_timer_get_time() - TimePastKalman) / 1000000.0f ); //get time, in microsecods, since last readings
    adcValue = KF_ADC_b.updateEstimate( adcValue ); // apply simple Kalman filter
    Vbatt = adcValue * vRefScale;
    xSemaphoreTake( sema_CalculatedVoltage, portMAX_DELAY );
    CalculatedVoltage = Vbatt;
    xSemaphoreGive( sema_CalculatedVoltage );
//        printCount++;
//        if ( printCount == 3 )
//        {
//        log_i( "Vbatt %f", Vbatt );
//        printCount = 0;
//        }
    TimePastKalman = esp_timer_get_time(); // time of update complete
    xLastWakeTime = xTaskGetTickCount();
    vTaskDelayUntil( &xLastWakeTime, xFrequency );
    //log_i( "fReadBattery %d",  uxTaskGetStackHighWaterMark( NULL ) );
  }
  vTaskDelete( NULL );
}
////
void MQTTkeepalive( void *pvParameters )
{
  sema_MQTT_KeepAlive   = xSemaphoreCreateBinary();
  xSemaphoreGive( sema_MQTT_KeepAlive ); // found keep alive can mess with a publish, stop keep alive during publish
  // setting must be set before a mqtt connection is made
  MQTTclient.setKeepAlive( 90 ); // setting keep alive to 90 seconds makes for a very reliable connection, must be set before the 1st connection is made.
  for (;;)
  {
    //check for a is-connected and if the WiFi 'thinks' its connected, found checking on both is more realible than just a single check
    if ( (wifiClient.connected()) && (WiFi.status() == WL_CONNECTED) )
    {
      xSemaphoreTake( sema_MQTT_KeepAlive, portMAX_DELAY ); // whiles MQTTlient.loop() is running no other mqtt operations should be in process
      MQTTclient.loop();
      xSemaphoreGive( sema_MQTT_KeepAlive );
    }
    else {
      log_i( "MQTT keep alive found MQTT status %s WiFi status %s", String(wifiClient.connected()), String(WiFi.status()) );
      if ( !(wifiClient.connected()) || !(WiFi.status() == WL_CONNECTED) )
      {
        connectToWiFi();
      }
      connectToMQTT();
    }
    vTaskDelay( 250 ); //task runs approx every 250 mS
  }
  vTaskDelete ( NULL );
}
////
void connectToWiFi()
{
  int TryCount = 0;
  //log_i( "connect to wifi" );
  while ( WiFi.status() != WL_CONNECTED )
  {
    TryCount++;
    WiFi.disconnect();
    WiFi.begin( SSID, PASSWORD );
    vTaskDelay( 4000 );
    if ( TryCount == 10 )
    {
      ESP.restart();
    }
  }
  WiFi.onEvent( WiFiEvent );
} // void connectToWiFi()
////
void connectToMQTT()
{
  MQTTclient.setKeepAlive( 90 ); // needs be made before connecting
  byte mac[5];
  WiFi.macAddress(mac);
  String clientID = String(mac[0]) + String(mac[4]) ; // use mac address to create clientID
  while ( !MQTTclient.connected() )
  {
    // boolean connect(const char* id, const char* user, const char* pass, const char* willTopic, uint8_t willQos, boolean willRetain, const char* willMessage);
    MQTTclient.connect( clientID.c_str(), mqtt_username, mqtt_password, NULL , 1, true, NULL );
    vTaskDelay( 250 );
  }
  MQTTclient.setCallback( mqttCallback );
  MQTTclient.subscribe( topicOK );
} // void connectToMQTT()
////
void loop() {}

It does not crash.