Add time stamp to SD data (ESP32 with BME680 sensor and BSEC.h)

Hi,
I´m new and started my first project using a ESP32 (30 pin) with an BME680 sensor to create IAQ monitor for a school classroom or similar.
My programming skills are only very basic... however I got the project running.
I used the BSEC.h library and I´m quite happe wit the performance.
Nice TFT display, time via the Wifi Network and data storage on a SD card.

BUT WHAT I DO NOT GET TO WORK IS TO GET THE TIMESTAMP INTO THE SD CARD >:(
Tried so hard, no success what ever I do....

Here is the code I used to get the time/date it the TFT, this is working fine....

///////////////////////////////////////////// This is getting the time, diplay on TFT is working
void printLocalTime()
{struct tm timeinfo;
  if(!getLocalTime(&timeinfo)){
    Serial.println("Failed to obtain time");
    return;  }
  Serial.println(&timeinfo, "%A, %B %d %Y %H:%M:%S");
  tft.setTextColor(TFT_WHITE, TFT_GREY);
  tft.setCursor(4, 116 ,1);
  tft.print(&timeinfo, "%B %d %Y %H:%M:%S");
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

Also the code to get the data on the SD card is working fine.

void logSDCard() {
  dataMessage = String(iaqSensor.rawTemperature) + "," + 
                String(iaqSensor.pressure) + "," +
                String(iaqSensor.rawHumidity) + "," +
                String(iaqSensor.gasResistance) + "," +
                String(iaqSensor.iaq) + "," +
                String(iaqSensor.iaqAccuracy) + "," +
                String(iaqSensor.temperature) + "," + 
                String(iaqSensor.humidity) + "," +
                String(iaqSensor.staticIaq) + "," +
                String(iaqSensor.co2Equivalent) + "," +                           
                String(iaqSensor.breathVocEquivalent) + "\r\n";
  Serial.print("Save data: ");
  Serial.println(dataMessage);
  appendFile(SD, "/data.txt", dataMessage.c_str());
}

However, whatever I try I do not success to implement the time stamp into the SD dataMessage.
Any idea or example is very welcome...
Here the complete code:

Hi Goofy,

well done posting your code as a code-section in your first post.
Anyway the code you have posted does not contain anything that does add the timestamp

You should post your complete sketch. From the ver first line to the very last line of code.
And You should post your best attempt how you tried to add thetime-stamp to the working code

best regards Stefan

First half of code:

#include <TFT_eSPI.h> // Graphics and font library for ST7735 driver chip
#include <SPI.h>  // SPI Connection

#include <WiFi.h>  // WIFI
#include "time.h" // Uhr

#include "FS.h"
#include "SD.h"
#define SD_CS 5

const char* ssid     = "sogufiwa oben";
const char* password = "2952231457911382";

const char* ntpServer = "pool.ntp.org";
const long  gmtOffset_sec = 3600;
const int   daylightOffset_sec = 3600;

TFT_eSPI tft = TFT_eSPI();  // Invoke library, pins defined in User_Setup.h

#define TFT_GREY 0x5AEB // New colour

unsigned long previousMillis = 0;  
const long SDinterval = 30000; // Interval in dem auf die SD Karte geschieben wird 1000 = 1 sec 60000 = 1x pro min 

#include "bsec.h"
// Helper functions declarations
void checkIaqSensorStatus(void);
void errLeds(void);
void alarmindicator(void);

// Create an object of the class Bsec
Bsec iaqSensor;

String output;
String dataMessage;
/////////////////

void writeFile(fs::FS &fs, const char * path, const char * message){
    Serial.printf("Writing file: %s\n", path);
    File file = fs.open(path, FILE_WRITE);
    if(!file){
        Serial.println("Failed to open file for writing");
        return;
    }
    if(file.print(message)){
        Serial.println("File written");
    } else {
        Serial.println("Write failed");
    }
    file.close();
}

void appendFile(fs::FS &fs, const char * path, const char * message){
    Serial.printf("Appending to file: %s\n", path);

    File file = fs.open(path, FILE_APPEND);
    if(!file){
        Serial.println("Failed to open file for appending");
        return;
    }
    if(file.print(message)){
        Serial.println("Message appended");
    } else {
        Serial.println("Append failed");
    }
    file.close();
}
///////////////////////////////////////////// This is getting the time, diplay on TFT is working
void printLocalTime()
{struct tm timeinfo;
  if(!getLocalTime(&timeinfo)){
    Serial.println("Failed to obtain time");
    return;  }
  Serial.println(&timeinfo, "%A, %B %d %Y %H:%M:%S");
  tft.setTextColor(TFT_WHITE, TFT_GREY);
  tft.setCursor(4, 116 ,1);
  tft.print(&timeinfo, "%B %d %Y %H:%M:%S");
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void setup(void)
{
  Serial.begin(115200);
  Wire.begin();
  tft.init();
  tft.setRotation(1);
  tft.fillScreen(TFT_BLACK);
  tft.setCursor(25, 40, 2);
  tft.setTextColor(TFT_WHITE,TFT_BLACK);  tft.setTextSize(1);
  tft.println("IAQ Anal. V SD 2.0");
  tft.println("G.Waldheuer 11.12.2020");
  delay(2000); 
  tft.fillScreen(TFT_GREY);


  iaqSensor.begin(BME680_I2C_ADDR_SECONDARY, Wire);
  output = "\nBSEC library version " + String(iaqSensor.version.major) + "." + String(iaqSensor.version.minor) + "." + String(iaqSensor.version.major_bugfix) + "." + String(iaqSensor.version.minor_bugfix);
  Serial.println(output);
  checkIaqSensorStatus();

  bsec_virtual_sensor_t sensorList[10] = {
    BSEC_OUTPUT_RAW_TEMPERATURE,
    BSEC_OUTPUT_RAW_PRESSURE,
    BSEC_OUTPUT_RAW_HUMIDITY,
    BSEC_OUTPUT_RAW_GAS,
    BSEC_OUTPUT_IAQ,
    BSEC_OUTPUT_STATIC_IAQ,
    BSEC_OUTPUT_CO2_EQUIVALENT,
    BSEC_OUTPUT_BREATH_VOC_EQUIVALENT,
    BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_TEMPERATURE,
    BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_HUMIDITY,
  };

  iaqSensor.updateSubscription(sensorList, 10, BSEC_SAMPLE_RATE_LP);
  checkIaqSensorStatus();

  // Print the header
  output = "Timestamp [ms], raw temperature [°C], pressure [hPa], raw relative humidity [%], gas [Ohm], IAQ, IAQ accuracy, temperature [°C], relative humidity [%], Static IAQ, CO2 equivalent, breath VOC equivalent";
  Serial.println(output);

  // Connect to WIFI
  tft.setCursor(4, 4 ,1);
  Serial.print("Connecting to "); 
  tft.println("Connecting to ");
  Serial.println(ssid);
  tft.println(ssid);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.println("WiFi connected.");
  tft.println("Wifi connected.");

     
  // Init and get the time
  configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
  printLocalTime();

  //disconnect WiFi as it's no longer needed
 WiFi.disconnect(true);
 WiFi.mode(WIFI_OFF);

  // SD Card check 
    if(!SD.begin()){
        Serial.println("Card Mount Failed");
        tft.setCursor(4, 60 ,1);
        tft.println("Card Mount Failed");
        delay(1000);
        tft.fillScreen(TFT_GREY);
         return;
    }
    uint8_t cardType = SD.cardType();

    if(cardType == CARD_NONE){
        Serial.println("No SD card attached");
        tft.setCursor(4, 60 ,1);
        tft.println("No SD card attached");
                return;
    }

delay(2000);
    
    writeFile(SD, "/data.txt", "rawTemp,pressure,rawHumidity,gasResistance,IAQ,");
    appendFile(SD, "/data.txt", "IAQAccuracy,Tempeature,Humidity,static IAQ,ppm CO2,ppm VOC \n");
    tft.fillScreen(TFT_GREY);
}

I wrrote COMPLETE sketch. From the very first line to the very last line.

Easiest way to do this is press Ctrl-T in the IDE
do a right click with the mouse choose copy for forum
and paste the clipboard-content into a new posting

best regards Stefan

Second half of code:

void loop(void)
    
{ 
unsigned long currentMillis = millis();
if (currentMillis - previousMillis >= SDinterval) { previousMillis = currentMillis; 
logSDCard();}
        
delay(3000); 
printLocalTime();
  
  unsigned long time_trigger = millis();
    if (iaqSensor.run()) { // If new data is available
    output = String(time_trigger);
    output += ", " + String(iaqSensor.rawTemperature);
    output += ", " + String(iaqSensor.pressure);
    output += ", " + String(iaqSensor.rawHumidity);
    output += ", " + String(iaqSensor.gasResistance);
    output += ", " + String(iaqSensor.iaq);
    output += ", " + String(iaqSensor.iaqAccuracy);
    output += ", " + String(iaqSensor.temperature);
    output += ", " + String(iaqSensor.humidity);
    output += ", " + String(iaqSensor.staticIaq);
    output += ", " + String(iaqSensor.co2Equivalent);
    output += ", " + String(iaqSensor.breathVocEquivalent);
    Serial.println(output);
}

  
  tft.setCursor(4, 4 ,1);
  tft.setTextColor(TFT_WHITE, TFT_GREY); // tft.setTextSize(0);
  tft.print(iaqSensor.temperature);tft.setCursor(60, 4 ,1); tft.println("C Room Temp.");
  tft.setCursor(4, 14 ,1);
  tft.print(iaqSensor.humidity); tft.setCursor(60, 14 ,1); tft.println("% rel. Humidity");
  tft.setCursor(4, 24 ,1);
  (iaqSensor.pressure) = ((iaqSensor.pressure)/100);
  tft.print(iaqSensor.pressure,1); tft.setCursor(60, 24 ,1); tft.println("mbar");
  tft.setCursor(4, 34 ,2); tft.setTextColor(TFT_GREEN, TFT_GREY);
  tft.print(iaqSensor.staticIaq); tft.setCursor(60, 34 ,2); tft.println("In-Air Quality");
  tft.setCursor(4, 55 ,1);tft.setTextColor(TFT_WHITE, TFT_GREY);
  tft.print(iaqSensor.co2Equivalent); tft.setCursor(60, 55 ,1); tft.println("ppm CO2 equiv.");
  tft.setCursor(4, 65 ,1);
  tft.print(iaqSensor.breathVocEquivalent); tft.setCursor(60, 65 ,1); tft.println("ppm VOC equiv.");
  tft.setCursor(4, 75 ,1);
  tft.print(iaqSensor.iaqAccuracy); tft.setCursor(60, 75 ,1); tft.print("IAQ Accuracy ");
  if (iaqSensor.staticIaq <= 50){ tft.setTextColor(TFT_BLACK, TFT_GREEN); tft.setCursor(1, 87 ,4);  tft.print("--GOOD AIR---");}
  if (iaqSensor.staticIaq > 50){ tft.setTextColor(TFT_BLACK, TFT_YELLOW); tft.setCursor(1, 87 ,4);  tft.print("AVERAGE AIR");}
  if (iaqSensor.staticIaq > 100){ tft.setTextColor(TFT_BLACK, TFT_ORANGE); tft.setCursor(1, 87 ,4); tft.print("-LITTLE BAD!--");}
  if (iaqSensor.staticIaq > 150){ tft.setTextColor(TFT_BLACK, TFT_RED); tft.setCursor(1, 87 ,4);    tft.print("----BAD AIR----");}
  if (iaqSensor.staticIaq > 200){ tft.setTextColor(TFT_BLACK, TFT_RED); tft.setCursor(1, 87 ,4);    tft.print("-WORSE AIR--");}
  if (iaqSensor.staticIaq > 300){ tft.setTextColor(TFT_BLACK, TFT_RED); tft.setCursor(1, 87 ,4);    tft.print("-TOTALY BAD-");}


  else {
    checkIaqSensorStatus();
  }
}
// Helper function definitions
void checkIaqSensorStatus(void)
{
  if (iaqSensor.status != BSEC_OK) {
    if (iaqSensor.status < BSEC_OK) {
      output = "BSEC error code : " + String(iaqSensor.status);
      Serial.println(output);
    } else {
      output = "BSEC warning code : " + String(iaqSensor.status);
      Serial.println(output);
    }
  }

  if (iaqSensor.bme680Status != BME680_OK) {
    if (iaqSensor.bme680Status < BME680_OK) {
      output = "BME680 error code : " + String(iaqSensor.bme680Status);
      Serial.println(output);
    } else {
      output = "BME680 warning code : " + String(iaqSensor.bme680Status);
      Serial.println(output);
    }
  }
}

// Write the sensor readings on the SD card
void logSDCard() {
  dataMessage = String(iaqSensor.rawTemperature) + "," + 
                String(iaqSensor.pressure) + "," +
                String(iaqSensor.rawHumidity) + "," +
                String(iaqSensor.gasResistance) + "," +
                String(iaqSensor.iaq) + "," +
                String(iaqSensor.iaqAccuracy) + "," +
                String(iaqSensor.temperature) + "," + 
                String(iaqSensor.humidity) + "," +
                String(iaqSensor.staticIaq) + "," +
                String(iaqSensor.co2Equivalent) + "," +                           
                String(iaqSensor.breathVocEquivalent) + "\r\n";
  Serial.print("Save data: ");
  Serial.println(dataMessage);
  appendFile(SD, "/data.txt", dataMessage.c_str());
}

If you managed to merge the two codes to a single one that compiles I will take a look at it
best regards Stefan

StefanL38:
I wrrote COMPLETE sketch. From the very first line to the very last line.

Easiest way to do this is press Ctrl-T in the IDE
do a right click with the mouse choose copy for forum
and paste the clipboard-content into a new posting

best regards Stefan

I´m sorry but it was over 9000 characters and did not allow to send :frowning:
and I can only send one message every 5 minutes .....

:confused: always above 9000 characters, no way to send complete.... only 290 lines

OK if the code has more than 9000 characters you can attach the file as attachment.

I did some research on how to extract hour minute second from the timestruct but had no success.

Here is a different code that uses a NTP-server to obtain time

best regards Stefan

Here is some code I use that runs on an esp32. I extract the time for printing to the screen. You should be able to determine from the code how to extract the time for your own use.

#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 "time.h"
//
//
hw_timer_t * timer = NULL;
EventGroupHandle_t eg;
#define evtDoDisplay ( 1 << 1 )
#define evtParseMQTT ( 1 << 5 )
#define evtDoTheSunLampThing ( 1 << 10 )
//
int SunLampManual = 0;
int SunLampAuto = 0;
////
WiFiClient wifiClient;
PubSubClient MQTTclient( mqtt_server, mqtt_port, wifiClient );
////
String str_eTopic;
char strPayload [300] = {null};
////
byte mac[6];
////
////
////
SemaphoreHandle_t sema_MQTT_Parser;;
SemaphoreHandle_t sema_DoTheSunLampThing;
SemaphoreHandle_t sema_MQTT_KeepAlive;
////
volatile int iDoTheThing = 0;
////
void IRAM_ATTR onTimer()
{
  BaseType_t xHigherPriorityTaskWoken;
  iDoTheThing++;
  if ( iDoTheThing == 60000 )
  {
    xEventGroupSetBitsFromISR( eg, evtDoTheSunLampThing, &xHigherPriorityTaskWoken );
    iDoTheThing = 0;
  }
}
////
void setup()
{
  //
  gpio_config_t io_cfg = {};
  io_cfg.mode = GPIO_MODE_OUTPUT;
  //bit mask of the pins to set
  io_cfg.pin_bit_mask = ( (1ULL << GPIO_NUM_15) );
  //configure GPIO with the given settings
  gpio_config(&io_cfg);
  REG_WRITE(GPIO_OUT_W1TC_REG, BIT15); // sunlamp
  //
  /* Use 4th timer of 4.
    1 tick 1/(80MHZ/80) = 1us set divider 80 and count up.
    Attach onTimer function to timer
    Set alarm to call timer ISR, every 1000uS and repeat / reset ISR (true) after each alarm
    Start an timer alarm
  */
  timer = timerBegin( 3, 80, true );
  timerAttachInterrupt( timer, &onTimer, true );
  timerAlarmWrite(timer, 1000, true);
  timerAlarmEnable(timer);
  //
  str_eTopic.reserve(300);
  //
  eg = xEventGroupCreate();
  //
  sema_MQTT_Parser = xSemaphoreCreateBinary();
  sema_DoTheSunLampThing = xSemaphoreCreateBinary();
  sema_MQTT_KeepAlive = xSemaphoreCreateBinary();
  xSemaphoreGive( sema_DoTheSunLampThing );
  xSemaphoreGive( sema_MQTT_KeepAlive ); // found keep alive can mess with a publish, stop keep alive during publish
  ////
  ////
  xTaskCreatePinnedToCore( fparseMQTT, "fparseMQTT", 7000, NULL, 5, NULL, 1 ); // assign all to core 1, WiFi in use.
  xTaskCreatePinnedToCore( MQTTkeepalive, "MQTTkeepalive", 7000, NULL, 2, NULL, 1 ); //this task makes a WiFi and MQTT connection.
  xTaskCreatePinnedToCore( fDoTheSunLampThing, "fDoTheSunLampThing", 2000, NULL, 3, NULL, 1 );
  ////
} //setup() END
////
////
////
////
void GetTheTime()
{
  char* ntpServer = "2.us.pool.ntp.org";
  int gmtOffset_sec = -(3600 * 7 );
  int daylightOffset_sec = 3600;
  configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
}
////
// http://www.cplusplus.com/reference/ctime/strftime/
////
int getHour()
{
  struct tm timeinfo;
  getLocalTime(&timeinfo);
  char _time[ 5 ];
  strftime( _time, 80, "%T", &timeinfo );
  return String(_time).toInt();
}
////
void printLocalTime()
{
  struct tm timeinfo;
  getLocalTime(&timeinfo);
  char _time[ 80 ];
  strftime( _time, 80, "%T", &timeinfo );
  log_i( "%s", _time);
}
////
void fDoTheSunLampThing( void * parameter )
{
  // int x = gpio_get_level( GPIO_NUM_15 ); // reads gpio pin state returns an int
  // SunLampManual = sunlamp on manual mode, automatic mode off for manual mode to work
  // SunLampAuto = sun lamp enable automatic mode
  bool AlreadyOn = false;
  for (;;)
  {
    xEventGroupWaitBits (eg, evtDoTheSunLampThing, pdTRUE, pdTRUE, portMAX_DELAY );
    vTaskDelay( 6 );
    xSemaphoreTake( sema_DoTheSunLampThing, portMAX_DELAY );
    // sun lamp on/off auto disabled
    if ( (SunLampManual == 1) && (SunLampAuto == 0)  )
    {
      if ( !AlreadyOn )
      {
        REG_WRITE(GPIO_OUT_W1TS_REG, BIT15);
        AlreadyOn = true;
      } else {
        REG_WRITE(GPIO_OUT_W1TC_REG, BIT15);
        AlreadyOn = false;
      }
    } else if ( (SunLampManual == 0) && (SunLampAuto == 1) ) // light off auto enabled
    {
      int _hour = getHour();
      if ( (_hour >= 7) && (_hour <= 17) )
      {
        REG_WRITE(GPIO_OUT_W1TS_REG, BIT15);
        AlreadyOn = true;
      }
      if ( (_hour < 7) || (_hour > 16) )
      {
        REG_WRITE(GPIO_OUT_W1TC_REG, BIT15);
        AlreadyOn = false;
      }
    } else {
      REG_WRITE(GPIO_OUT_W1TC_REG, BIT15);
      AlreadyOn = false;
    }
    xSemaphoreGive( sema_DoTheSunLampThing );
    //log_i( "fDoTheSunLampThing high watermark %d",  uxTaskGetStackHighWaterMark( NULL ) );
  }
  vTaskDelete( NULL );
} // void fDoTheSunLampThing( void * parameter )
////
/*
    Important to not set vtaskDelay to less then 10. Errors begin to develop with the MQTT and network connection.
    makes the initial wifi/mqtt connection and works to keeps those connections open.
*/
void MQTTkeepalive( void *pvParameters )
{
  // setting must be set before a mqtt connection is made
  MQTTclient.setKeepAlive( 90 ); // setting keep alive to 90 seconds
  for (;;)
  {
    if ( (wifiClient.connected()) && (WiFi.status() == WL_CONNECTED) )
    {
      xSemaphoreTake( sema_MQTT_KeepAlive, portMAX_DELAY ); //
      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 ( !(WiFi.status() == WL_CONNECTED) )
      {
        connectToWiFi();
      }
      connectToMQTT();
    }
    vTaskDelay( 250 );
  }
  vTaskDelete ( NULL );
}
////
void connectToMQTT()
{
  log_i( "connect to mqtt" );
  // create client ID from mac address
  String clientID = String(mac[0]) + String(mac[5]) ;
  log_i( "connect to mqtt clientID %s", clientID );
  while ( !MQTTclient.connected() )
  {
    MQTTclient.connect( clientID.c_str(), mqtt_username, mqtt_password );
    log_i( "connecting to MQTT" );
    vTaskDelay( 250 );
  }
  log_i("MQTT Connected");
  MQTTclient.setCallback( mqttCallback );
  MQTTclient.subscribe( mqtt_topic );
}
//
void connectToWiFi()
{
  log_i( "connect to wifi" );
  while ( WiFi.status() != WL_CONNECTED )
  {
    WiFi.disconnect();
    WiFi.begin( SSID, PASSWORD );
    log_i(" waiting on wifi connection" );
    vTaskDelay( 4000 );
  }
  log_i( "Connected to WiFi" );
  WiFi.macAddress(mac);
  log_i( "mac address %d.%d.%d.%d.%d", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]  );
  WiFi.onEvent( WiFiEvent );
  GetTheTime();
  printLocalTime();
}
////
void fparseMQTT( void *pvParameters )
{
  xSemaphoreGive ( sema_MQTT_Parser );
  for (;;)
  {
    xEventGroupWaitBits (eg, evtParseMQTT, pdTRUE, pdTRUE, portMAX_DELAY ); //
    xSemaphoreTake( sema_MQTT_Parser, portMAX_DELAY );
    if ( String(str_eTopic) == topic_SunLampOn )
    {
      xSemaphoreTake( sema_DoTheSunLampThing, portMAX_DELAY );
      SunLampManual = String(strPayload).toInt();
      xSemaphoreGive( sema_DoTheSunLampThing );
    }
    if ( String(str_eTopic) == topic_SunLampEnable )
    {
      xSemaphoreTake( sema_DoTheSunLampThing, portMAX_DELAY );
      SunLampAuto = String(strPayload).toInt();
      xSemaphoreGive( sema_DoTheSunLampThing );
    }
    // clear pointer locations
   // clear pointer locations
    memset( strPayload, NULL, 300 );
    str_eTopic = ""; //clear string buffer
    xSemaphoreGive( sema_MQTT_Parser );
  }
} // void fparseMQTT( void *pvParameters )
////
// Important to get as much code out of the callback for realible operations.
////
static void mqttCallback(char* topic, byte * payload, unsigned int length)
{
  xSemaphoreTake( sema_MQTT_Parser, portMAX_DELAY);
  str_eTopic = topic + '\0';
  int i = 0;
  for ( i; i < length; i++) {
    strPayload[i] = ((char)payload[i]);
  }
  strPayload[i] = '\0';
  //log_i( "topic %s payload %s" ,str_eTopicPtr, strPayloadPtr );
  xSemaphoreGive ( sema_MQTT_Parser );
  xEventGroupSetBits( eg, evtParseMQTT ); // trigger tasks
} // void mqttCallback(char* topic, byte* payload, unsigned int length)
////
// great trouble shooting tool when uncommented
////
void WiFiEvent(WiFiEvent_t event)
{
  switch (event) {
    case SYSTEM_EVENT_STA_CONNECTED:
      log_i("Connected to access point");
      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 loop() { }

Noticed you are using millis() which rolls over after 40 some odd days.

The ESP32 has a micros timer/counter that rolls over once every 207 years.

Here is some code where I used the timer

void fDo_AudioReadFreq( void *pvParameters )
{
  int64_t EndTime = esp_timer_get_time();
  int64_t StartTime = esp_timer_get_time(); //gets time in uSeconds like Arduino Micros
  for (;;)
  {
    xEventGroupWaitBits (eg, evtDo_AudioReadFreq, pdTRUE, pdTRUE, portMAX_DELAY);
    EndTime = esp_timer_get_time() - StartTime;
    // log_i( "TimeSpentOnTasks: %d", EndTime );
    Audio.ReadFreq(FreqVal);
    for (int i = 0; i < 7; i++)
    {
      FreqVal[i] = constrain( FreqVal[i], NOISE, A_D_ConversionBits );
      FreqVal[i] = map( FreqVal[i], NOISE, A_D_ConversionBits, 0, 255 );
      // log_i( "Freq %d Value: %d", i, FreqVal[i]);//used for debugging and Freq choosing
    }
    xQueueSend( xQ_LED_Info, ( void * ) &FreqVal, xTicksToWait0 );
    StartTime = esp_timer_get_time();
  }
  vTaskDelete( NULL );
} // fDo_ AudioReadFreq( void *pvParameters )

Actually your better off using freeRTOS, included with an ESP32, with vTaskDelay() which is non blocking code. Meaning whiles the task is in delay another task can be run.

In your Arduino IDE add a new tab and call it certs.h where you can put stuff like this const char* ssid = "sogufiwa oben";
const char* password = "2952231457911382";

so when you paste code to this site, we don't get in on your secrets, see post number 9 for my use of the certs page.

On a ESP32 this unsigned long previousMillis = 0; is the same as uint64_t. A regular ESP32 unsigned int does the millis() thing.

These “delay(2000);” are really terrible to use on a ESP32. Whiles delay on the ESP32 core translates into a task.yield, if you are noting tasks, freeRTOS, then delay() becomes blocking code. The ESP32 has freeRTOS, a multi tasking / multi-processing OS.

Hi idaho-walker since when is rollover if millis() a problem if you use it with unsigned longs?

unsigned long currentTime;
unsigned long previousTime ;
unsigned long period;
if (currentTime - previousTime >= period)

the experts here will see I’m not an epxpert about c++ and timing.functions

here is a demo-code that uses PString to extract hour minutes seconds from the timestructure
because PString can use the standard-function print
which especcially for ESP32 has implemented to print in this way

print(&timeinfo, "%A, %B %d %Y %H:%M:%S");

what IMHO is absolutely NOT beginner-friendly are the very short examples
like the simpletime-example
here is an extended version that shows much more to get out of it

#include <WiFi.h>
#include "time.h"
#include <PString.h>

const char* ssid       = "YOUR SSID";
const char* password   = "Ypur password";

const char* ntpServer = "pool.ntp.org";
const long  gmtOffset_sec = 3600;
const int   daylightOffset_sec = 3600;

char    MyDemo_AoC[128 + 1]; // 20 chars for characters one extra-char for terminating zero 
PString MyDemo_PS(MyDemo_AoC, sizeof(MyDemo_AoC));

byte Hour;
byte Minute;
byte Second;


void printLocalTime()
{
  struct tm timeinfo;
  if(!getLocalTime(&timeinfo)){
    Serial.println("Failed to obtain time");
    return;
  }
  Serial.println(&timeinfo, "%A, %B %d %Y %H:%M:%S");
  MyDemo_PS ="";
  MyDemo_PS.print(&timeinfo, "%A, %B %d %Y %H:%M:%S");
  Serial.print("PString#");
  Serial.print(MyDemo_PS);
  Serial.print("#");
  Serial.println();

  MyDemo_PS ="";
  MyDemo_PS.print(&timeinfo, "%H:%M:%S");
  Serial.print("PString#");
  Serial.print(MyDemo_PS);
  Serial.print("#");
  Serial.println();

  MyDemo_PS ="";
  MyDemo_PS.print(&timeinfo, "%H");
  Serial.print("PString#");
  Serial.print(MyDemo_PS);
  Serial.print("#");
  Serial.println();


  MyDemo_PS ="";
  MyDemo_PS.print(&timeinfo, "%H");  
  Hour = atoi(MyDemo_PS);
  Serial.print("Hour #");
  Serial.print(Hour );
  Serial.print("#");
  Serial.println();

  MyDemo_PS ="";
  MyDemo_PS.print(&timeinfo, "%M");  
  Minute = atoi(MyDemo_PS);
  Serial.print("Minute #");
  Serial.print(Minute );
  Serial.print("#");
  Serial.println();

  MyDemo_PS ="";
  MyDemo_PS.print(&timeinfo, "%S");  
  Second = atoi(MyDemo_PS);
  Serial.print("Second #");
  Serial.print(Second );
  Serial.print("#");
  Serial.println();
  
}

void setup()
{
  Serial.begin(115200);
  
  //connect to WiFi
  Serial.printf("Connecting to %s ", ssid);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
      delay(500);
      Serial.print(".");
  }
  Serial.println(" CONNECTED");
  
  //init and get the time
  configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
  printLocalTime();

  //disconnect WiFi as it's no longer needed
  WiFi.disconnect(true);
  WiFi.mode(WIFI_OFF);
}

char MyTimeStr[128];
 
void loop()
{
  delay(1000);
  printLocalTime();
}

best regards Stefan

Whiles the ESP32 does some memory clean up chores, when you are using freeRTOS, you should keep in mind the taming of Strings

Take this String dataMessage;. If you were to use dataMessage.reserve(XX ), such as I do in post number 9 with the setup() entry str_eTopic.reserve(300); creates a string buffer of 300 character length. To add to the string buffer you use concat like str_eTopic.concat( "sdfsdfa" ) and then doing this str_eTopic.concat( "sdfsdfa" ) would add to the end of the string buffer. str_eTopic="" empties the string buffer.

See String() - Arduino Reference

StefanL38:
Hi idaho-walker since when is rollover if millis() a problem if you use it with unsigned longs?

As I don't use millis() or delay() it's not a problem.

Hi,
thanks it is working using <PString.h> as mentioned by Stephan

Again, thanks a lot for this…

To the comment on the timer… I used millis because I read a lot comments that using delay is a bad thing,
So I understand you right after approx. 40 days I will have the rollover which then “maybe?” cause trouble?
So I will try to use your example as above (ESP32 micros timer/counter) to avoid this, but in general if a delay is not hurting the project its then more cosmetic right?
I mean I agree fully with " do it right from the beginning"…

if millis() is used in combination with unsigned long variables the result of the substraction
currentMillis - previousMillis will be right even in case of a "rollover of millis().

millis() counts up to 2^32 - 1 which is 4294967295
after that number it rolls over to 0
so after rollover millis() starts counting up again let’s say it is 1000
so the if-condition checks

currentMillis - previousMillis
1000 - 4294967295 which in “classical” signed mathematics results in the negative value -4294966295

But if the variables are unsigned long the result will be +1000 so even in case of a rollover the if-condition works the same way. And that is the reason why all millis()-tutorials recommend to use the if-condition as
new value minus older value >= period to wait
i.e.
currentMillis - previousMillis >= period

@idaho-walker:
If I understood right all ESP32-Arduino-coding is done “on top” of FreeRTOS?

This question is meant as really open to say yes or no.
would you enjoy writing some commented sample-codes how to do simple things like

  • blink an LED,
  • execute a task once every x seconds
  • after a if-conditions evaluates true execute a function just once after a x second delay
  • fast polling of an IO-pin

based on FreeRTOS-functions?

best regards Stefan

StefanL38:
@idaho-walker:
If I understood right all ESP32-Arduino-coding is done “on top” of FreeRTOS?

Yes.

StefanL38:
This question is meant as really open to say yes or no.
would you enjoy writing some commented sample-codes how to do simple things like

  • blink an LED,
  • execute a task once every x seconds
  • after a if-conditions evaluates true execute a function just once after a x second delay
  • fast polling of an IO-pin

based on FreeRTOS-functions?

best regards Stefan

Sure.

#include "esp_system.h" //This inclusion configures the peripherals in the ESP system.
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"

void setup()
{
  Serial.begin( 115200 );
  pinMode( 2, OUTPUT );
 // create a task, assign task a function name, a name, some stack space, no parameters, priority of 3, do not return a task handle, assign to core 0
  xTaskCreatePinnedToCore( fBlinkBuiltIn, "fBlinkBuiltIn", 5000, NULL, 3, NULL, 0 );
}

void fBlinkBuiltIn( void* pvParameters )
{
  // toggle built in LED off/on
  for (;;)
  {
    vTaskDelay( 10 ); //delay for 10 mS
    REG_WRITE( GPIO_OUT_W1TC_REG, BIT2 ); // set GPIO2 LOW (clear)
    vTaskDelay( 1000 ); //delay for 1000mS.
    REG_WRITE( GPIO_OUT_W1TS_REG, BIT2 ); //set GPIO2 HIGH (set)
  }
  vTaskDelete( NULL ); // incase task jump loop, destroy task
}
// do not put code in loop()
void loop() {}