From 1197e083f4b0b26beeea56a79ebd98f5f126da43 Mon Sep 17 00:00:00 2001 From: Thomas Kolb Date: Sat, 6 Jul 2024 16:51:46 +0200 Subject: [PATCH] First steps towards history plotting --- include/GxEPD2_display_selection_new_style.h | 12 +- include/epaper.h | 6 +- include/timeseries.h | 16 + src/epaper.cpp | 402 +++++++++---------- src/main.cpp | 154 ++++++- src/timeseries.cpp | 18 + 6 files changed, 387 insertions(+), 221 deletions(-) create mode 100644 include/timeseries.h create mode 100644 src/timeseries.cpp diff --git a/include/GxEPD2_display_selection_new_style.h b/include/GxEPD2_display_selection_new_style.h index 1b878ba..d9de459 100644 --- a/include/GxEPD2_display_selection_new_style.h +++ b/include/GxEPD2_display_selection_new_style.h @@ -42,8 +42,8 @@ //#define GxEPD2_DRIVER_CLASS GxEPD2_260_M01 // GDEW026M01 152x296, UC8151 (IL0373) //#define GxEPD2_DRIVER_CLASS GxEPD2_270 // GDEW027W3 176x264, EK79652 (IL91874) //#define GxEPD2_DRIVER_CLASS GxEPD2_371 // GDEW0371W7 240x416, UC8171 (IL0324) -#define GxEPD2_DRIVER_CLASS GxEPD2_420 // GDEW042T2 400x300, UC8176 (IL0398) -//#define GxEPD2_DRIVER_CLASS GxEPD2_420_M01 // GDEW042M01 400x300, UC8176 (IL0398) +//#define GxEPD2_DRIVER_CLASS GxEPD2_420 // GDEW042T2 400x300, UC8176 (IL0398) +#define GxEPD2_DRIVER_CLASS GxEPD2_420_M01 // GDEW042M01 400x300, UC8176 (IL0398) //#define GxEPD2_DRIVER_CLASS GxEPD2_583 // GDEW0583T7 600x448, UC8179 (IL0371) //#define GxEPD2_DRIVER_CLASS GxEPD2_583_T8 // GDEW0583T8 648x480, GD7965 //#define GxEPD2_DRIVER_CLASS GxEPD2_750 // GDEW075T8 640x384, UC8179 (IL0371) @@ -114,8 +114,10 @@ // adapt the constructor parameters to your wiring #if !IS_GxEPD2_1248(GxEPD2_DRIVER_CLASS) #if defined(ARDUINO_LOLIN_D32_PRO) +#error Wrong constructur GxEPD2_DISPLAY_CLASS display(GxEPD2_DRIVER_CLASS(/*CS=5*/ EPD_CS, /*DC=*/ 0, /*RST=*/ 2, /*BUSY=*/ 15)); #elif defined(ARDUINO_ESP32_DEV) // e.g. TTGO T8 ESP32-WROVER +#error Wrong constructur GxEPD2_DISPLAY_CLASS display(GxEPD2_DRIVER_CLASS(/*CS=5*/ EPD_CS, /*DC=*/ 2, /*RST=*/ 0, /*BUSY=*/ 4)); #else // moved to epaper.c because of multiple definitions when in header @@ -124,6 +126,7 @@ GxEPD2_DISPLAY_CLASS displ #else // GxEPD2_1248 // Waveshare 12.48 b/w SPI display board and frame or Good Display 12.48 b/w panel GDEW1248T3 // general constructor for use with all parameters, e.g. for Waveshare ESP32 driver board mounted on connection board +#error Wrong constructur GxEPD2_BW < GxEPD2_1248, GxEPD2_1248::HEIGHT / 4 > display(GxEPD2_1248(/*sck=*/ 13, /*miso=*/ 12, /*mosi=*/ 14, /*cs_m1=*/ 23, /*cs_s1=*/ 22, /*cs_m2=*/ 16, /*cs_s2=*/ 19, /*dc1=*/ 25, /*dc2=*/ 17, /*rst1=*/ 33, /*rst2=*/ 5, @@ -142,6 +145,7 @@ GxEPD2_BW < GxEPD2_1248, GxEPD2_1248::HEIGHT / 4 > display(GxEPD2_1248(/*sck=*/ #define MAX_HEIGHT(EPD) (EPD::HEIGHT <= (MAX_DISPLAY_BUFFER_SIZE) / (EPD::WIDTH / 2) ? EPD::HEIGHT : (MAX_DISPLAY_BUFFER_SIZE) / (EPD::WIDTH / 2)) #endif // adapt the constructor parameters to your wiring +#error Wrong constructur GxEPD2_DISPLAY_CLASS display(GxEPD2_DRIVER_CLASS(/*CS=4*/ EPD_CS, /*DC=*/ 3, /*RST=*/ 2, /*BUSY=*/ 1)); #endif @@ -159,6 +163,7 @@ GxEPD2_DISPLAY_CLASS displ #define MAX_HEIGHT(EPD) (EPD::HEIGHT <= (MAX_DISPLAY_BUFFER_SIZE) / (EPD::WIDTH / 2) ? EPD::HEIGHT : (MAX_DISPLAY_BUFFER_SIZE) / (EPD::WIDTH / 2)) #endif // adapt the constructor parameters to your wiring +#error Wrong constructur GxEPD2_DISPLAY_CLASS display(GxEPD2_DRIVER_CLASS(/*CS=*/ EPD_CS, /*DC=*/ 8, /*RST=*/ 9, /*BUSY=*/ 7)); #endif @@ -172,6 +177,7 @@ GxEPD2_DISPLAY_CLASS displ #define MAX_HEIGHT(EPD) (EPD::HEIGHT <= (MAX_DISPLAY_BUFFER_SIZE) / (EPD::WIDTH / 2) ? EPD::HEIGHT : (MAX_DISPLAY_BUFFER_SIZE) / (EPD::WIDTH / 2)) #endif // adapt the constructor parameters to your wiring +#error Wrong constructur GxEPD2_DISPLAY_CLASS display(GxEPD2_DRIVER_CLASS(/*CS=77*/ EPD_CS, /*DC=*/ 8, /*RST=*/ 9, /*BUSY=*/ 7)); #endif @@ -185,6 +191,7 @@ GxEPD2_DISPLAY_CLASS displ #define MAX_HEIGHT(EPD) (EPD::HEIGHT <= (MAX_DISPLAY_BUFFER_SIZE) / (EPD::WIDTH / 2) ? EPD::HEIGHT : (MAX_DISPLAY_BUFFER_SIZE) / (EPD::WIDTH / 2)) #endif // adapt the constructor parameters to your wiring +#error Wrong constructur GxEPD2_DISPLAY_CLASS display(GxEPD2_DRIVER_CLASS(/*CS=4*/ 4, /*DC=*/ 7, /*RST=*/ 6, /*BUSY=*/ 5)); //GxEPD2_DISPLAY_CLASS display(GxEPD2_DRIVER_CLASS(/*CS=4*/ 4, /*DC=*/ 3, /*RST=*/ 2, /*BUSY=*/ 1)); // my Seed XIOA0 //GxEPD2_DISPLAY_CLASS display(GxEPD2_DRIVER_CLASS(/*CS=4*/ 3, /*DC=*/ 2, /*RST=*/ 1, /*BUSY=*/ 0)); // my other Seed XIOA0 @@ -200,6 +207,7 @@ GxEPD2_DISPLAY_CLASS displ #define MAX_HEIGHT(EPD) (EPD::HEIGHT <= (MAX_DISPLAY_BUFFER_SIZE) / (EPD::WIDTH / 2) ? EPD::HEIGHT : (MAX_DISPLAY_BUFFER_SIZE) / (EPD::WIDTH / 2)) #endif // adapt the constructor parameters to your wiring +#error Wrong constructur GxEPD2_DISPLAY_CLASS display(GxEPD2_DRIVER_CLASS(/*CS=4*/ EPD_CS, /*DC=*/ 8, /*RST=*/ 9, /*BUSY=*/ 7)); #endif diff --git a/include/epaper.h b/include/epaper.h index 6aebb62..ab32215 100644 --- a/include/epaper.h +++ b/include/epaper.h @@ -1,6 +1,8 @@ #ifndef EPAPER_H #define EPAPER_H +#include "timeseries.h" + #include #include "GxEPD2_display_selection_new_style.h" @@ -9,8 +11,8 @@ typedef void (*epaper_callback)(GxEPD2_DISPLAY_CLASS + +#include + +typedef struct { + uint64_t tstart, tend, interval; + std::vector data; + const char *unit; + const char *format; +} timeseries_t; + +void timeseries_init(timeseries_t *ts, uint64_t tstart, uint64_t interval, const char *unit, const char *format); + +void timeseries_append(timeseries_t *ts, float value); diff --git a/src/epaper.cpp b/src/epaper.cpp index e37a4ba..99e14e7 100644 --- a/src/epaper.cpp +++ b/src/epaper.cpp @@ -8,286 +8,256 @@ #include "epaper.h" static GxEPD2_DISPLAY_CLASS - display(GxEPD2_DRIVER_CLASS(/*CS=5*/ EPD_CS, /*DC=*/ 17, /*RST=*/ 16, /*BUSY=*/ 4)); + display(GxEPD2_DRIVER_CLASS(/*CS=*/ 17, /*DC=*/ 18, /*RST=*/ 19, /*BUSY=*/ 22)); void epaper_init(void) { - display.init(); + display.init(0, true, 2, false); } void epaper_test(void) { - display.setRotation(0); - display.setFont(&FreeMonoBold9pt7b); - display.setTextColor(GxEPD_BLACK); - int16_t tbx, tby; uint16_t tbw, tbh; - display.getTextBounds("Hello World!", 0, 0, &tbx, &tby, &tbw, &tbh); - // center the bounding box by transposition of the origin: - uint16_t x = ((display.width() - tbw) / 2) - tbx; - uint16_t y = ((display.height() - tbh) / 2) - tby; - display.setFullWindow(); - display.firstPage(); - do - { - display.fillScreen(GxEPD_WHITE); - display.setCursor(x, y); - display.print("Hello World!"); - for(int i = -1; i <= 1; i++) { - display.drawCircle(display.width()/2, display.height()/2, tbw*15/10/2+i, GxEPD_RED); - } - } - while (display.nextPage()); + display.setRotation(0); + display.setFont(&FreeMonoBold9pt7b); + display.setTextColor(GxEPD_BLACK); + int16_t tbx, tby; uint16_t tbw, tbh; + display.getTextBounds("Hello World!", 0, 0, &tbx, &tby, &tbw, &tbh); + // center the bounding box by transposition of the origin: + uint16_t x = ((display.width() - tbw) / 2) - tbx; + uint16_t y = ((display.height() - tbh) / 2) - tby; + display.setFullWindow(); + display.firstPage(); + do + { + display.fillScreen(GxEPD_WHITE); + display.setCursor(x, y); + display.print("Hello World!"); + for(int i = -1; i <= 1; i++) { + display.drawCircle(display.width()/2, display.height()/2, tbw*15/10/2+i, GxEPD_BLACK); + } - Serial.println(F("Init done. Display going to powersave…")); + display.setCursor(10, FreeMonoBold9pt7b.yAdvance + 10); + for(uint16_t c = 0; c < 256; c++) { + display.print((char)c); - display.hibernate(); + if((c % 32) == 31) { + display.println(); + } + } + } + while (display.nextPage()); + + Serial.println(F("Init done. Display going to powersave…")); } -void epaper_draw_and_hibernate(epaper_callback cb) +void epaper_draw_and_hibernate(epaper_callback cb, bool full_refresh) { - display.setRotation(0); - display.setFullWindow(); - display.firstPage(); + display.setRotation(0); + display.setFullWindow(); + display.firstPage(); - do { - cb(&display); - } while(display.nextPage()); + do { + cb(&display); + } while(display.nextPage()); - display.hibernate(); + display.hibernate(); } -#if 0 // find minimum and maximum values in a vector. NaN is ignored. -template + template void minmax(const std::vector &vec, T *min, T *max) { - bool first = true; + bool first = true; - // fallback values: will be used if the vector is empty or only consists of NaNs - *min = 0; - *max = 1; + // fallback values: will be used if the vector is empty or only consists of NaNs + *min = 0; + *max = 1; - for(auto &v: vec) { - if(isnan(v)) { - continue; - } + for(auto &v: vec) { + if(isnan(v)) { + continue; + } - if(first) { - *min = *max = v; - first = false; - continue; - } + if(first) { + *min = *max = v; + first = false; + continue; + } - if(v < *min) { - *min = v; - } + if(v < *min) { + *min = v; + } - if(v > *max) { - *max = v; - } - } + if(v > *max) { + *max = v; + } + } } void epaper_plot(uint16_t x, uint16_t y, uint16_t w, uint16_t h, timeseries_t *timeseries, uint32_t time_tick_interval) { - char s[32]; + char s[32]; - uint32_t trange = timeseries->tend - timeseries->tstart; - uint32_t tstep = timeseries->interval; + uint32_t trange = timeseries->tend - timeseries->tstart; + uint32_t tstep = timeseries->interval; - if(trange == 0) { - trange = 3600; - } + if(trange == 0) { + trange = 3600; + } - float vmin, vmax; - minmax(timeseries->data, &vmin, &vmax); + float vmin, vmax; + minmax(timeseries->data, &vmin, &vmax); - float vrange = vmax - vmin; + float vrange = vmax - vmin; - Serial.print(F("Value range: ")); - Serial.print(vrange); - Serial.print(F(" – Min: ")); - Serial.print(vmin); - Serial.print(F(" – Max: ")); - Serial.println(vmax); + Serial.print(F("Value range: ")); + Serial.print(vrange); + Serial.print(F(" – Min: ")); + Serial.print(vmin); + Serial.print(F(" – Max: ")); + Serial.println(vmax); - if(vrange == 0.0f) { - vrange = 1.0f; - } + if(vrange == 0.0f) { + vrange = 1.0f; + } - // add some margin - vmax += vrange * 0.05f; - vmin -= vrange * 0.05f; - vrange = vmax - vmin; + // add some margin + vmax += vrange * 0.05f; + vmin -= vrange * 0.05f; + vrange = vmax - vmin; - bool firstpoint = true; - uint16_t last_x; - uint16_t last_y; + bool firstpoint = true; + uint16_t last_x; + uint16_t last_y; - // set font for axis labels - display.setFont(&Org_01); - display.setTextColor(GxEPD_BLACK); - int16_t tbx, tby; uint16_t tbw, tbh; - display.getTextBounds("20:48", 0, 0, &tbx, &tby, &tbw, &tbh); + // set font for axis labels + display.setFont(&Org_01); + display.setTextColor(GxEPD_BLACK); + int16_t tbx, tby; uint16_t tbw, tbh; + display.getTextBounds("20:48", 0, 0, &tbx, &tby, &tbw, &tbh); - uint16_t label_height = tbh; + uint16_t label_height = tbh; - // actual graph area - uint16_t plot_x = x + 150; - uint16_t plot_y = y; - uint16_t plot_w = w - 150; - uint16_t plot_h = h - label_height - 5; + // actual graph area + uint16_t plot_x = x + tbw; + uint16_t plot_y = y; + uint16_t plot_w = w - tbw; + uint16_t plot_h = h - label_height - 5; - // determine vertical tick interval - uint16_t max_vert_tick_count = plot_h / (label_height + 5); - float min_vert_tick_step = vrange / (float)max_vert_tick_count; + // determine vertical tick interval + uint16_t max_vert_tick_count = plot_h / (label_height + 5); + float min_vert_tick_step = vrange / (float)max_vert_tick_count; - static const PROGMEM float TEST_FACTORS[] = {1.0f, 0.5f, 0.25f, 0.1f}; + static const PROGMEM float TEST_FACTORS[] = {1.0f, 0.5f, 0.25f, 0.1f}; - float scale = 1e3f; - float accepted_scale = scale; - bool scale_found = false; + float scale = 1e3f; + float accepted_scale = scale; + bool scale_found = false; - while((scale > 1e-3f) && !scale_found) { - float tmp_scale; + while((scale > 1e-3f) && !scale_found) { + float tmp_scale; - for(auto factor: TEST_FACTORS) { - tmp_scale = scale * factor; - if(tmp_scale < min_vert_tick_step) { - scale_found = true; - break; - } - accepted_scale = tmp_scale; - } - scale = tmp_scale; - } + for(auto factor: TEST_FACTORS) { + tmp_scale = scale * factor; + if(tmp_scale < min_vert_tick_step) { + scale_found = true; + break; + } + accepted_scale = tmp_scale; + } + scale = tmp_scale; + } - /* Start drawing */ + /* Start drawing */ - display.drawRect(plot_x, plot_y, plot_w, plot_h, GxEPD_BLACK); + display.drawRect(plot_x, plot_y, plot_w, plot_h, GxEPD_BLACK); - // draw vertical grid lines - for(uint64_t reltime = ((timeseries->tstart / time_tick_interval) + 1) * time_tick_interval - timeseries->tstart; - reltime < trange; - reltime += time_tick_interval) { - uint16_t px = plot_x + plot_w * reltime / trange; - uint16_t py = plot_y; - uint16_t ph = plot_h + 2; + // draw vertical grid lines + for(uint64_t reltime = ((timeseries->tstart / time_tick_interval) + 1) * time_tick_interval - timeseries->tstart; + reltime < trange; + reltime += time_tick_interval) { + uint16_t px = plot_x + plot_w * reltime / trange; + uint16_t py = plot_y; + uint16_t ph = plot_h + 2; - display.drawFastVLine(px, py, ph, GxEPD_BLACK); + display.drawFastVLine(px, py, ph, GxEPD_BLACK); - time_t t = reltime + timeseries->tstart; - tm *gmt = gmtime(&t); - if(t % 86400 == 0) { - strftime(s, sizeof(s), "%d.%m.", gmt); - } else { - strftime(s, sizeof(s), "%H:%M", gmt); - } + time_t t = reltime + timeseries->tstart; + tm *gmt = gmtime(&t); + if(t % 86400 == 0) { + strftime(s, sizeof(s), "%d.%m.", gmt); + } else { + strftime(s, sizeof(s), "%H:%M", gmt); + } - display.getTextBounds(s, 0, 0, &tbx, &tby, &tbw, &tbh); + display.getTextBounds(s, 0, 0, &tbx, &tby, &tbw, &tbh); - display.setCursor(px - tbw/2, y+h + tby); - display.print(s); - } + display.setCursor(px - tbw/2, y+h + tby); + display.print(s); + } - // draw horizontal grid lines - for(float tick_val = (float)((int)(vmin / accepted_scale) + 1) * accepted_scale; - tick_val < vmax; - tick_val += accepted_scale) { + // draw horizontal grid lines + for(float tick_val = (float)((int)(vmin / accepted_scale) + 1) * accepted_scale; + tick_val < vmax; + tick_val += accepted_scale) { - uint16_t py = plot_y - (float)plot_h * (tick_val - vmax) / vrange; - uint16_t px = plot_x - 3; - uint16_t pw = plot_w + 3; + uint16_t py = plot_y - (float)plot_h * (tick_val - vmax) / vrange; + uint16_t px = plot_x - 3; + uint16_t pw = plot_w + 3; - display.drawFastHLine(px, py, pw, GxEPD_BLACK); + display.drawFastHLine(px, py, pw, GxEPD_BLACK); - snprintf(s, sizeof(s), timeseries->format, tick_val); + snprintf(s, sizeof(s), timeseries->format, tick_val); - display.getTextBounds(s, 0, 0, &tbx, &tby, &tbw, &tbh); + display.getTextBounds(s, 0, 0, &tbx, &tby, &tbw, &tbh); - display.setCursor(px - tbw - 2, py + tby + tbh); - display.print(s); - } + display.setCursor(px - tbw - 2, py + tby + tbh); + display.print(s); + } - size_t i = 0; - for(uint32_t reltime = 0; reltime < trange; reltime += tstep) { - if(i >= timeseries->data.size()) { - Serial.println(F("Index out of range! Time range does not match data points.")); - break; - } + size_t i = 0; + for(uint32_t reltime = 0; reltime < trange; reltime += tstep) { + if(i >= timeseries->data.size()) { + Serial.println(F("Index out of range! Time range does not match data points.")); + break; + } - /*Serial.print(F("Processing data point at dt=")); - Serial.print(reltime); - Serial.print(F("… "));*/ + /*Serial.print(F("Processing data point at dt=")); + Serial.print(reltime); + Serial.print(F("… "));*/ - float val = timeseries->data[i++]; - if(isnan(val)) { - //Serial.println(F("Skipping NaN!")); + float val = timeseries->data[i++]; + if(isnan(val)) { + //Serial.println(F("Skipping NaN!")); - firstpoint = true; - continue; - } + firstpoint = true; + continue; + } - uint16_t px = plot_x + plot_w * reltime / trange; - uint16_t py = plot_y - (float)plot_h * (val - vmax) / vrange; + uint16_t px = plot_x + plot_w * reltime / trange; + uint16_t py = plot_y - (float)plot_h * (val - vmax) / vrange; - if(!firstpoint) { - /*Serial.print(F("Line from (")); - Serial.print(last_x); - Serial.print(F(",")); - Serial.print(last_y); - Serial.print(F(") to (")); - Serial.print(px); - Serial.print(F(",")); - Serial.print(py); - Serial.println(F(")."));*/ + if(!firstpoint) { + /*Serial.print(F("Line from (")); + Serial.print(last_x); + Serial.print(F(",")); + Serial.print(last_y); + Serial.print(F(") to (")); + Serial.print(px); + Serial.print(F(",")); + Serial.print(py); + Serial.println(F(")."));*/ - display.drawLine(last_x, last_y, px, py, GxEPD_RED); + display.drawLine(last_x, last_y, px, py, GxEPD_BLACK); - // draw it again, 1px below, to give it more weight - display.drawLine(last_x, last_y+1, px, py+1, GxEPD_RED); - } else { - //Serial.println(F("First point, not drawn.")); - firstpoint = false; - } + // draw it again, 1px below, to give it more weight + display.drawLine(last_x, last_y+1, px, py+1, GxEPD_BLACK); + } else { + //Serial.println(F("First point, not drawn.")); + firstpoint = false; + } - last_x = px; - last_y = py; - } - - /* Draw large current value + unit */ - - size_t cur_val_idx = timeseries->data.size() - 1; - while((cur_val_idx > 0) && isnan(timeseries->data[cur_val_idx])) { - cur_val_idx--; - } - - float current_value = timeseries->data[cur_val_idx]; - - display.setFont(&din1451e24pt7b); - display.setTextColor(GxEPD_RED); - - char fmt[16]; - - snprintf(fmt, sizeof(fmt), "%s %%s", timeseries->format); - snprintf(s, sizeof(s), fmt, current_value, timeseries->unit); - - display.getTextBounds(s, 0, 0, &tbx, &tby, &tbw, &tbh); - - display.setCursor(plot_x - tbw - 35, plot_y + plot_h/2 - tby - tbh/2 - 5); - display.print(s); - - uint16_t large_text_height = tbh; - - time_t t = timeseries->tstart + trange * cur_val_idx / timeseries->data.size(); - tm *gmt = gmtime(&t); - strftime(s, sizeof(s), "%Y-%m-%d %H:%M", gmt); - - display.setFont(&Org_01); - - display.getTextBounds(s, 0, 0, &tbx, &tby, &tbw, &tbh); - - display.setCursor(plot_x - tbw - 35, plot_y + plot_h/2 - tby + large_text_height/2 + 5); - display.print(s); + last_x = px; + last_y = py; + } } -#endif diff --git a/src/main.cpp b/src/main.cpp index 1c68349..64649c8 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,12 +1,26 @@ #include #include +#include +#include +#include +#include + #include Adafruit_SCD30 scd30; +static timeseries_t ts_scd30_co2, ts_scd30_temperature, ts_scd30_humidity; + +#define REF_ALTITUDE 320 // meters +#define REF_CO2_PPM 425 // clean air according to DWD at 2024-06-17 + void initSCD30(void) { + timeseries_init(&ts_scd30_co2, 0, 15, "ppm", "%.1f"); + timeseries_init(&ts_scd30_temperature, 0, 15, "°C", "%.1f"); + timeseries_init(&ts_scd30_humidity, 0, 15, "%", "%.1f"); + // Try to initialize! if (!scd30.begin()) { Serial.println("scd30: Failed to find SCD30 chip"); @@ -14,6 +28,7 @@ void initSCD30(void) } Serial.println("scd30: SCD30 Found!"); + // TODO: check what this actually resets. Maybe also the calibration? scd30.reset(); if (!scd30.setMeasurementInterval(15)){ // seconds @@ -24,6 +39,32 @@ void initSCD30(void) Serial.print(scd30.getMeasurementInterval()); Serial.println(" seconds"); + uint16_t pressureOffset = scd30.getAmbientPressureOffset(); + uint16_t altitudeOffset = scd30.getAltitudeOffset(); + bool selfCalEnabled = scd30.selfCalibrationEnabled(); + + if(altitudeOffset != REF_ALTITUDE) { + Serial.println("scd30: updating altitude offset."); + if(!scd30.setAltitudeOffset(REF_ALTITUDE)) { + Serial.println("scd30: failed to set altitude offset!"); + } + altitudeOffset = REF_ALTITUDE; + } + + if(selfCalEnabled) { + Serial.println("scd30: self calibration was enabled -> disabling."); + scd30.selfCalibrationEnabled(false); + } + + Serial.println("scd30: offset readout:"); + Serial.print(" Pressure: "); + Serial.print(pressureOffset); + Serial.println(" mbar"); + Serial.print(" Altitude: "); + Serial.print(altitudeOffset); + Serial.println(" m"); + Serial.println(); + if(!scd30.startContinuousMeasurement()) { Serial.println("scd30: Could not start continuous measurement"); } @@ -32,20 +73,113 @@ void initSCD30(void) void setup(void) { + Serial.begin(115200); + Serial.println("Hello World!"); + + delay(3000); + initSCD30(); epaper_init(); epaper_test(); } +void draw_epaper_callback(GxEPD2_DISPLAY_CLASS *display) +{ + display->setRotation(1); + display->setTextColor(GxEPD_BLACK); + + int16_t y = FreeSans12pt7b.yAdvance; + + // first line: CO₂ + display->setCursor(0, y); + + display->setFont(&FreeSans12pt7b); + display->print("CO"); + + display->setFont(&FreeSans9pt7b); + display->print("2"); + + display->setFont(&FreeSans12pt7b); + display->print(": "); + + y += FreeSans12pt7b.yAdvance; + display->setCursor(0, y); + + display->setFont(&FreeSansBold12pt7b); + display->print(scd30.CO2, 2); + + display->setFont(&FreeSans12pt7b); + display->print(" ppm"); + + epaper_plot(150, 5, 150, 65, &ts_scd30_co2, 900); + + // second line: temperature + y = 75 + FreeSans12pt7b.yAdvance; + display->setCursor(0, y); + + display->setFont(&FreeSans12pt7b); + display->print("Temp.: "); + + y += FreeSans12pt7b.yAdvance; + display->setCursor(0, y); + + display->setFont(&FreeSansBold12pt7b); + display->print(scd30.temperature, 2); + + display->setFont(&FreeSans12pt7b); + display->print(" "); + + // cheat a bit for the ° character + uint16_t stored_baseline_y = display->getCursorY(); + uint16_t cursor_x = display->getCursorX(); + + display->setCursor(cursor_x, stored_baseline_y - FreeSans12pt7b.yAdvance/4); + display->print("o"); + + cursor_x = display->getCursorX(); + display->setCursor(cursor_x, stored_baseline_y); + display->print("C"); + + epaper_plot(150, 80, 150, 65, &ts_scd30_temperature, 900); + + // second line: Humidity + y = 150 + FreeSans12pt7b.yAdvance; + display->setCursor(0, y); + + display->setCursor(0, y); + + display->setFont(&FreeSans12pt7b); + display->print("Feuchte: "); + + y += FreeSans12pt7b.yAdvance; + display->setCursor(0, y); + + display->setFont(&FreeSansBold12pt7b); + display->print(scd30.relative_humidity, 1); + + display->setFont(&FreeSans12pt7b); + display->print(" %rH"); + + epaper_plot(150, 155, 150, 65, &ts_scd30_humidity, 900); +} + void loop(void) { + static bool calibrationDone = true; // change to false to recalibrate at REF_CO2_PPM + static uint32_t lastEPaperRefresh = 0; + uint32_t now = millis(); + if(scd30.dataReady()) { if (!scd30.read()) { - Serial.println("Error reading sensor data"); + Serial.println("scd30: Error reading sensor data"); return; } + timeseries_append(&ts_scd30_co2, scd30.CO2); + timeseries_append(&ts_scd30_temperature, scd30.temperature); + timeseries_append(&ts_scd30_humidity, scd30.relative_humidity); + Serial.print("CO2: "); Serial.print(scd30.CO2, 3); Serial.println(" ppm"); @@ -56,6 +190,24 @@ void loop(void) Serial.print(scd30.relative_humidity, 2); Serial.println(" %"); Serial.println(""); + + if((now - lastEPaperRefresh) >= 60000) { + lastEPaperRefresh = now; + epaper_draw_and_hibernate(draw_epaper_callback, false); + } + + if(!calibrationDone && now >= 300000) { + uint16_t calValue = scd30.getForcedCalibrationReference(); + + if(calValue != REF_CO2_PPM) { + Serial.println("scd30: Setting calibration reference value."); + if(!scd30.forceRecalibrationWithReference(REF_CO2_PPM)) { + Serial.println("scd30: Recalibration failed!"); + } + } + + calibrationDone = true; + } } delay(1000); diff --git a/src/timeseries.cpp b/src/timeseries.cpp new file mode 100644 index 0000000..44687b3 --- /dev/null +++ b/src/timeseries.cpp @@ -0,0 +1,18 @@ +#include "timeseries.h" + + +void timeseries_init(timeseries_t *ts, uint64_t tstart, uint64_t interval, const char *unit, const char *format) +{ + ts->tstart = tstart; + ts->tend = tstart; + ts->interval = interval; + ts->unit = unit; + ts->format = format; +} + + +void timeseries_append(timeseries_t *ts, float value) +{ + ts->data.push_back(value); + ts->tend += ts->interval; +}