ESP32+OLED time-stamped graphical data logger
Posted: 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.
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.
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;
}