ESP32+OLED time-stamped graphical data logger

rin67630
Posts: 141
Joined: Sun Mar 11, 2018 5:13 pm

ESP32+OLED time-stamped graphical data logger

Postby rin67630 » Sun Apr 08, 2018 9:22 am

I am currently building a time-stamped data logger at a one second pace.
It is optimized for gaussian events that last beween 30sec and 90sec
It features:
- Processes one analog input in range 0..1V for 0..100% (or here for 0..100dB). Read several times for accuracy.
- Lazy software: sleeps 95% of the time @ 40MHz, Low-power: takes only 5mA@5V can be solar-powered. (2mA without display).
- Exact time is gathed from SNTP.
- Logs every second in a one hour buffer.
- Displays time, logged value as
- digital value
- bargraph
- 18 minutes trend on the OLED display.
- Computes the average value of the last hour and the Leq value ( logarithmic average of exponentials).
- Statistics and event detection on serial monitor
- Hourly report up to 400 events with:
-time,
- maximum level
- duration.
- NAT (Numer above treshold) for 3 adjustable tresholds.
- Averages by hour for one month.
- Buit-in simulation to test without sensor.
- Hourly report of timing quality:
-# of seconds processed must be 3600,
-avg of idle time (should be ~50mS), trim sleep time (affects power consumption)
-# of zero idle (a few is OK, if too high, reduce sleep time)

Planned:
-writing the one hour buffer in a SPIFF file named dd.mm.yy.dat that builds up for 24h.
-writing the daily stats to a SPIFF file named dd.mm.yy.csv
-ftp that files to any remote computer once a day.
-resync time once a day.
-multiple OLED displays selectable by touch.
-clean the code and remove debugging info.

Image

Code: Select all

#include <WiFi.h>
#include <WiFiUdp.h>
#include "SSD1306.h"

#define NTP_SERVER "de.pool.ntp.org"
#define TZ_INFO "WEST-1DWEST-2,M3.5.0/02:00:00,M10.5.0/03:00:00" // Western European Time
// Define time zone as https://www.di-mgt.com.au/wclock/tz-syntax.txt

#define ssid "************"
#define pass "**************"

//***Options***
#define OLED true
#define serial true
#define simulation true
#define SLEEPTIME 940000 //microseconds

//***Hardware Definitions***
// ESP32 OLED
#define SCL 5  // GPIO for I2C (Wire) System Clock
#define SDA 4  // GPIO for I2C (Wire) System Data
#define MOSI  13 // GPIO for SPI Master Out
#define MISO  12 // GPIO for SPI Master In
#define SCLK  14 // GPIO for SPI System Clock
#define OLED_RESET 0  // GPIO0
/* ESP32 Lolin
  #define SCL 22  // GPIO for I2C (Wire) System Clock
  #define SDA 21  // GPIO for I2C (Wire) System Data
  #define MOSI  23 // GPIO for SPI Master Out
  #define MISO  19 // GPIO for SPI Master In
  #define SCLK  18 // GPIO for SPI System Clock
*/
#define  An0 36   // select the input pin for A0 (ADC0)
#define  An1 25   // select the input pin for A1 (ADC18)
#define  An2 26   // select the input pin for A2 (ADC19)
#define  An3 39   // select the input pin for A3 (ADC19)

//*** Network Definitions ***
#define Ser2NetSendPort 3051
#define Ser2NetRecvPort 3051
#define SerCommSendPort 6001
#define SerCommRecvPort 6001
#define localPort 8888;      // local port to listen for UDP packets

//*** Number Above Treshold (NAT) parameters ***
#define NAT1 60
#define NAT2 70
#define NAT3 80

#define Treshold 45
#define Duration 30

//***Number Above Treshold (NAT) variables ***
byte nNAT1;
byte nNAT2;
byte nNAT3;

byte ATDuration;
byte ATsec;
byte ATmin;
byte AThour;
byte ATMax;
char filbuff4[2000];  //Buffer for max 400 daily Above treshold events (NAT), each 5 bytes: Hr,Min,Sec,Max,Duration.
int ATpointer;

//*** Variables ***
long sensorValueA0 = 0;  // variable to store the value coming from the sensor
static tm local;
static long now;
static int second;
static int minute;
static int hour;
static int day;
static int month;
static int year;
static int weekday;
IPAddress ip;

//*** Buffers ***
char charbuff[32];    //String buffer
byte filbuff1[3600];  //Buffer for measures of one hour 60min*60sec
byte filbuff2[775];   //Buffer for Hourly Leq of one month 25*31 (H25=Full day)
byte filbuff3[31];    //Buffer daily Leq3 of one month


int pointer;
int oldPointer;
int barLength;

int i;
int j;
int k;
int l;


//*** Simulation Variables ***
int simx1;            // Simulation Sinus1
int simy1;            // Simulation Cosinus1
int simx2;            // Simulation Sinus2
int simy2;            // Simulation Cosinus2
int simx3;            // Simulation Sinus3
int simy3;            // Simulation Cosinus3
int simz;             // Simulation Mixed
int simm;             // Simulation Mixed
int simn;             // Simulation Mixed

//*** Measure Variables ***
int A0Raw;
float A0dB;        //A0 in dB
float A0dBMax;     //A0 in dB
float A0dBMin;     //A0 in dB
byte A0Byte;       //
byte A0Max;
float Accumulator;
float HourMean;
float HourLeq;
float DayLeq;
float DayLeq3;

//*** System diag Variables ***
RTC_DATA_ATTR int bootCount = 0; //Boot count should be stored in the non volatile RTC Memory
int idleTime;
int avgIdle;
int nIdle0;
int secProcessed;

//***Start instances***
//FtpServer ftpSrv;   //set #define FTP_DEBUG in ESP8266FtpServer.h to see ftp verbose on serial
//ThingerESP32 thing("rin67630", "ESP8266", "nimZptoc8atf");
//WiFiUDP Udp;

// Initialize the OLED display
SSD1306  display(0x3c, SCL, SDA);


void setup()
{
  if (serial)
  {
    Serial.begin(115200);
  }
  analogSetWidth(10);                           // 10Bit resolution
  analogSetAttenuation((adc_attenuation_t)0);   // 0=0db (0..1V) 1= 2,5dB; 2=-6dB (0..1V); 3=-11dB
  if (OLED)
  {
    display.init();
    display.setColor(WHITE);
    display.setFont(ArialMT_Plain_10); //..Plain_24, ..Plain_16
    display.setTextAlignment(TEXT_ALIGN_LEFT);
    display.clear();
  }
  if (simulation)
  {
    simy1 = simy2 = simy3 = 1000;
    simx1 = simx2 = simx3 = 0;
  }
  configTzTime(TZ_INFO, NTP_SERVER);
  esp_sleep_wakeup_cause_t wakeup_cause;
  wakeup_cause = esp_sleep_get_wakeup_cause();
  if (wakeup_cause != 3)
  {
    getNTPTime();
    if (OLED) displayIP();
  }
  // Serial.println(wakeup_cause);
}

void loop()
{
  getLocalTime(&local);
  getTimeValues();
  pointer = 60 * minute + second;
  idleTime = 0;
  while ( oldPointer == pointer)
  {
    delay(10);
    getLocalTime(&local);
    getTimeValues();
    pointer = 60 * minute + second;
    idleTime ++;
    secProcessed ++;
    avgIdle += idleTime;
    if (idleTime == 0) nIdle0 ++;
  }
  oldPointer = pointer;
  if (simulation)
  {
    // ==== (Running simulation) ======
    // This code generates a pseudo random sequence of gaussian events with some noise.
    simy1 = simy1 - simx1 / 24;                   //Compute a Cosinus
    simx1 = simx1 + simy1 / 24;                   //Compute a Sinus
    simy2 = simy2 - simx2 / 27;                   //Compute a somewhat slower Cosinus
    simx2 = simx2 + simy2 / 27;                   //Compute a somewhat slower Sinus
    simy3 = simy3 - simx3 / 31;                   //Compute a slower Sinus
    simx3 = simx3 + simy3 / 31;                   //Compute a slower Cosinus
    simn = random(0 , 25);                   //Create 2,5% Noise
    simm = ((simx1 < 0) ? 0 : simx1) + simy2 / 6 + simy3 / 8 + 120; //Suppress negative part of the first wave, mix with a bit of second and third
    simz = (simm * simm) / 3500 + simn + 350;             // Square the result, add noise, add bias to simulate flyby events.
    A0dB = float(simz) / 10;
  }
  else
  {
    // === (Running data acquisition) ===
    // Performing 3 reads to get a reliable reading.
    A0Raw = analogRead(An0); // 1st read  0...1V = 0 ..1023
    delay(10);
    A0Raw += analogRead(An0); // 2nd read
    delay(10);
    A0Raw += analogRead(An0); // 3rd read
    A0Raw = A0Raw / 3;
    A0dB = float(A0Raw) / 10;
  }

  bootCount++ ;
  A0Byte = byte(A0dB);
  filbuff1[pointer] = A0Byte;
  // Statistics and Accumulators run every minute: This part is pretty acoustics specific.
  // You might put your part here. Remember to split tasks in small chunks to keep within 100mS.


  // Detection of Events Above Treshold, that must last for at least Duration and max 3xDuration
  // Find maximum value and record corresponding time.
  if (A0Byte > Treshold && ATpointer < 394)
  {
    ATDuration ++;
    if (A0Byte > ATMax)
    {
      ATMax = A0Byte;
      AThour = hour;
      ATmin = minute;
      ATsec = second;
    }
  }
  else
  {
    if (ATDuration > Duration && ATDuration < 3 * Duration) // record event data in Array
    {
      filbuff4[ATpointer] = AThour;
      ATpointer ++;
      filbuff4[ATpointer] = ATmin;
      ATpointer ++;
      filbuff4[ATpointer] = ATsec;
      ATpointer ++;
      filbuff4[ATpointer] = ATMax; // Maximum
      ATpointer ++;
      filbuff4[ATpointer] = ATDuration;
      ATpointer ++;
      ATDuration = 0;
      if (ATMax > NAT1) nNAT1 ++;
      if (ATMax > NAT2) nNAT2 ++;
      if (ATMax > NAT3) nNAT3 ++;

    }
    else
    {
      ATDuration = 0;
      ATMax = 0;
    } // end if/else duration
  } // end if/else above treshold

  switch (second) // Statistics and Averages
  {
    case 0: // Reinitialisations
      switch (minute) //
      {
        case 0: //At the beginning of every hour
          switch (hour) //
          {
            case 0: //At the beginning of every day
              // reset NAT Array.
              for (i = 0; i <= 1999; i++) filbuff4[i] = 0;
              ATpointer = 0;
              break;
          }
          break;
      }

    case 2: // Compute simple moving Average
      Accumulator = 0;
      for (i = 0; i <= 3599; i++)
      {
        Accumulator += float(filbuff1[i]);
      }
      HourMean = Accumulator / 3600;
      break;
    //This part is pretty specific for acoustics. https://en.wikipedia.org/wiki/Sound_level_meter#LAT_or_Leq:_Equivalent_continuous_sound_level
    case 3: // Compute moving Lequ (1) Split in 3 slices for performance reasons
      Accumulator = 0;
      for (i = 0; i <= 1199; i++)
      {
        Accumulator =  Accumulator + pow(10, float(filbuff1[i]) / 10);
      }
      break;
    case 5: // Compute Hourly Lequ (2)
      for (i = 1200; i <= 2399; i++)
      {
        Accumulator =  Accumulator + pow(10, float(filbuff1[i]) / 10);
      }
      break;
    case 7: // Compute Hourly Lequ (3) and finalize
      for (i = 2400; i <= 3599; i++)
      {
        Accumulator =  Accumulator + pow(10, float(filbuff1[i]) / 10);
      }
      HourLeq = 10 * log10(Accumulator / 3600);
      break;

    case 59: // Summaries
      switch (minute) //
      {
        case 59: //At the end of every hour
          j = 25 * day + hour;
          filbuff2[j] = HourLeq;
          switch (hour) //
          {
            case 23: //At the end of every day
              // Add Leq for the whole day
              DayLeq = 0;
              for (i = 0; i <= -23; i--) DayLeq = +  filbuff2[j + i];
              DayLeq = DayLeq / 24;
              filbuff2[j + 1] = DayLeq;
              break;
          }
          break;
      }
    default:
      break;
  } //end switch second

  if (OLED)
  {
    display.clear();
    display.fillRect(0, 63, second * 2, 1); //display seconds progress bar
    barLength = A0Byte / 2 - 10;
    display.fillRect(125, (52 - barLength), 2, barLength); //display dB progress bar
    displayDigitalClock(); displayDigitalDate(); display.drawString(84, 52, String(A0dB)); display.drawString(110, 52, "dB");
    for (i = 1; i <= 124; i++)
    {
      j = pointer - (9 * i);
      k = j + 8;
      if (j < 0) j = j + 3600;
      if (k < 0) k = k + 3600;
      display.drawLine(125 - i, 52 - (filbuff1[j] / 2 - 10), 126 - i, 52 - (filbuff1[k] / 2 - 10));
    } //end for Loop
    display.drawString(0, 0, "NaT:"); display.drawString(22, 0, String(nNAT1)); display.drawString(42, 0, String(nNAT2)); display.drawString(62, 0, String(nNAT3));
    display.drawString(82, 0, "Leq="); display.drawString(107, 0, String(HourLeq));
    display.display();
    delay(5);
  }// end if OLED

  if (serial)
  {
    if (second == 1 || second == 31)
    {
      Serial.println (" dB");
      printDigitalClock();
      Serial.print (" - ");
    }
    Serial.print(A0Byte);
    Serial.print(' ');
    if (minute + second == 0)
    {
      Serial.println (" dB");
      printDigitalClock();
      printDigitalDate();
      Serial.println();
      for (i = 0; i <= ATpointer;)
      {
        Serial.print(" Event @ ");
        j = filbuff4[i];
        Serial.print(j);
        i ++;
        Serial.print(":");
        j = filbuff4[i];
        Serial.print(j);
        i ++;
        Serial.print(":");
        j = filbuff4[i];
        Serial.print(j);
        i ++;
        Serial.print(" -> ");
        j = filbuff4[i];
        Serial.print(j);
        i ++;
        Serial.print(" dB, ");
        j = filbuff4[i];
        Serial.print(j);
        i ++;
        Serial.println(" Seconds");
      } // end for loop
      
      Serial.print(" Timing check: Seconds processed (must be 3600) = ");
      Serial.print(secProcessed);
      secProcessed = 1;
      Serial.print(" | Avg Idle = ");
      Serial.print(avgIdle / 3600);
      avgIdle = 0;
      Serial.print(" mS | Nb of Zero Idle = ");
      Serial.println(nIdle0);
      nIdle0 = 0;

    } // end Minute,Second == 0

  } // end if serial
  esp_sleep_enable_timer_wakeup(SLEEPTIME);
  esp_light_sleep_start();
  //esp_deep_sleep_start(); // Was not efficient for 1 second timing
} //end loop

void getNTPTime()
{
#ifdef ssid
  WiFi.begin(ssid, pass);
#else
  WiFi.begin();
#endif
  while (WiFi.status() != WL_CONNECTED)
  {
    delay(500);
    if (serial) Serial.print(".");
  }
  delay(200);
  ip = WiFi.localIP();
  if (serial)  Serial.println(ip);
  struct tm local;
  getLocalTime(&local, 10000); // wait up to 10sec to sync
  WiFi.disconnect(true); //disconnect WiFi as it's no longer needed
  WiFi.mode(WIFI_OFF);
  if (OLED)
  {
    displayIP();
  }
}

// *** Routines ***

void printDigitalClock()
{
  // digital clock display of the time
  strftime(charbuff, sizeof(charbuff), "%H:%M:%S ", &local);
  Serial.print(charbuff);
}

void printDigitalDate()
{
  // digital clock display of the date
  strftime(charbuff, sizeof(charbuff), "%d %B %Y ", &local);
  Serial.print(charbuff);
}

void displayDigitalClock()
{
  // digital clock display of the time
  strftime(charbuff, sizeof(charbuff), "%H:%M:%S ", &local);
  display.drawString(0, 52, charbuff);
}

void displayDigitalDate()
{
  // digital clock display of the date
  strftime(charbuff, sizeof(charbuff), "-%d %b", &local);
  display.drawString(43, 52, charbuff);
}

void displayIP()
{
  display.drawString(0, 0, "ONLINE @ " + String(ssid));
  sprintf(charbuff, "IP= %03d.%03d.%03d.%03d", ip[0], ip[1], ip[2], ip[3]);
  display.drawString(0, 10, charbuff);
  display.display();
  delay(3000);
}
void getTimeValues()
{
  tm local;
  getLocalTime(&local);
  second = local.tm_sec;
  minute = local.tm_min;
  hour = local.tm_hour;
  day = local.tm_mday;
  month = local.tm_mon + 1;
  year = local.tm_year + 1900;
  weekday = local.tm_wday + 1;
}
Last edited by rin67630 on Tue Apr 17, 2018 8:45 am, edited 4 times in total.

User avatar
0miker0
Posts: 10
Joined: Fri Dec 01, 2017 1:14 pm

Re: ESP32+OLED time-stamped data logger + trend

Postby 0miker0 » Mon Apr 09, 2018 12:20 pm

Very nicely done! Thank you for posting your code as well. Next time I need to log data I'll checkout your code.

rin67630
Posts: 141
Joined: Sun Mar 11, 2018 5:13 pm

Re: ESP32+OLED time-stamped graphical data logger

Postby rin67630 » Mon Apr 16, 2018 5:03 pm

Now with a short action video.
https://youtu.be/0bM7veLTwQE

Who is online

Users browsing this forum: No registered users and 17 guests