First steps towards history plotting

This commit is contained in:
Thomas Kolb 2024-07-06 16:51:46 +02:00
parent 8d5b1a425b
commit 1197e083f4
6 changed files with 387 additions and 221 deletions

View file

@ -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<GxEPD2_DRIVER_CLASS, MAX_HEIGHT(GxEPD2_DRIVER_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<GxEPD2_DRIVER_CLASS, MAX_HEIGHT(GxEPD2_DRIVER_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<GxEPD2_DRIVER_CLASS, MAX_HEIGHT(GxEPD2_DRIVER_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<GxEPD2_DRIVER_CLASS, MAX_HEIGHT(GxEPD2_DRIVER_CLASS)> display(GxEPD2_DRIVER_CLASS(/*CS=4*/ EPD_CS, /*DC=*/ 3, /*RST=*/ 2, /*BUSY=*/ 1));
#endif
@ -159,6 +163,7 @@ GxEPD2_DISPLAY_CLASS<GxEPD2_DRIVER_CLASS, MAX_HEIGHT(GxEPD2_DRIVER_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<GxEPD2_DRIVER_CLASS, MAX_HEIGHT(GxEPD2_DRIVER_CLASS)> display(GxEPD2_DRIVER_CLASS(/*CS=*/ EPD_CS, /*DC=*/ 8, /*RST=*/ 9, /*BUSY=*/ 7));
#endif
@ -172,6 +177,7 @@ GxEPD2_DISPLAY_CLASS<GxEPD2_DRIVER_CLASS, MAX_HEIGHT(GxEPD2_DRIVER_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<GxEPD2_DRIVER_CLASS, MAX_HEIGHT(GxEPD2_DRIVER_CLASS)> display(GxEPD2_DRIVER_CLASS(/*CS=77*/ EPD_CS, /*DC=*/ 8, /*RST=*/ 9, /*BUSY=*/ 7));
#endif
@ -185,6 +191,7 @@ GxEPD2_DISPLAY_CLASS<GxEPD2_DRIVER_CLASS, MAX_HEIGHT(GxEPD2_DRIVER_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<GxEPD2_DRIVER_CLASS, MAX_HEIGHT(GxEPD2_DRIVER_CLASS)> display(GxEPD2_DRIVER_CLASS(/*CS=4*/ 4, /*DC=*/ 7, /*RST=*/ 6, /*BUSY=*/ 5));
//GxEPD2_DISPLAY_CLASS<GxEPD2_DRIVER_CLASS, MAX_HEIGHT(GxEPD2_DRIVER_CLASS)> display(GxEPD2_DRIVER_CLASS(/*CS=4*/ 4, /*DC=*/ 3, /*RST=*/ 2, /*BUSY=*/ 1)); // my Seed XIOA0
//GxEPD2_DISPLAY_CLASS<GxEPD2_DRIVER_CLASS, MAX_HEIGHT(GxEPD2_DRIVER_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<GxEPD2_DRIVER_CLASS, MAX_HEIGHT(GxEPD2_DRIVER_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<GxEPD2_DRIVER_CLASS, MAX_HEIGHT(GxEPD2_DRIVER_CLASS)> display(GxEPD2_DRIVER_CLASS(/*CS=4*/ EPD_CS, /*DC=*/ 8, /*RST=*/ 9, /*BUSY=*/ 7));
#endif

View file

@ -1,6 +1,8 @@
#ifndef EPAPER_H
#define EPAPER_H
#include "timeseries.h"
#include <GxEPD2_BW.h>
#include "GxEPD2_display_selection_new_style.h"
@ -9,8 +11,8 @@ typedef void (*epaper_callback)(GxEPD2_DISPLAY_CLASS<GxEPD2_DRIVER_CLASS, MAX_HE
void epaper_init(void);
void epaper_test(void);
//void epaper_plot(uint16_t x, uint16_t y, uint16_t w, uint16_t h, timeseries_t *timeseries, uint32_t time_tick_interval);
void epaper_plot(uint16_t x, uint16_t y, uint16_t w, uint16_t h, timeseries_t *timeseries, uint32_t time_tick_interval);
void epaper_draw_and_hibernate(epaper_callback cb);
void epaper_draw_and_hibernate(epaper_callback cb, bool full_refresh);
#endif

16
include/timeseries.h Normal file
View file

@ -0,0 +1,16 @@
#pragma once
#include <stdint.h>
#include <vector>
typedef struct {
uint64_t tstart, tend, interval;
std::vector<float> 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);

View file

@ -8,11 +8,11 @@
#include "epaper.h"
static GxEPD2_DISPLAY_CLASS<GxEPD2_DRIVER_CLASS, MAX_HEIGHT(GxEPD2_DRIVER_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)
@ -33,17 +33,24 @@ void epaper_test(void)
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);
display.drawCircle(display.width()/2, display.height()/2, tbw*15/10/2+i, GxEPD_BLACK);
}
display.setCursor(10, FreeMonoBold9pt7b.yAdvance + 10);
for(uint16_t c = 0; c < 256; c++) {
display.print((char)c);
if((c % 32) == 31) {
display.println();
}
}
}
while (display.nextPage());
Serial.println(F("Init done. Display going to powersave…"));
display.hibernate();
}
void epaper_draw_and_hibernate(epaper_callback cb)
void epaper_draw_and_hibernate(epaper_callback cb, bool full_refresh)
{
display.setRotation(0);
display.setFullWindow();
@ -56,7 +63,6 @@ void epaper_draw_and_hibernate(epaper_callback cb)
display.hibernate();
}
#if 0
// find minimum and maximum values in a vector. NaN is ignored.
template<typename T>
void minmax(const std::vector<T> &vec, T *min, T *max)
@ -133,9 +139,9 @@ void epaper_plot(uint16_t x, uint16_t y, uint16_t w, uint16_t h, timeseries_t *t
uint16_t label_height = tbh;
// actual graph area
uint16_t plot_x = x + 150;
uint16_t plot_x = x + tbw;
uint16_t plot_y = y;
uint16_t plot_w = w - 150;
uint16_t plot_w = w - tbw;
uint16_t plot_h = h - label_height - 5;
// determine vertical tick interval
@ -242,10 +248,10 @@ void epaper_plot(uint16_t x, uint16_t y, uint16_t w, uint16_t h, timeseries_t *t
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);
display.drawLine(last_x, last_y+1, px, py+1, GxEPD_BLACK);
} else {
//Serial.println(F("First point, not drawn."));
firstpoint = false;
@ -254,40 +260,4 @@ void epaper_plot(uint16_t x, uint16_t y, uint16_t w, uint16_t h, timeseries_t *t
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);
}
#endif

View file

@ -1,12 +1,26 @@
#include <Arduino.h>
#include <Adafruit_SCD30.h>
#include <Adafruit_GFX.h>
#include <Fonts/FreeSans9pt7b.h>
#include <Fonts/FreeSans12pt7b.h>
#include <Fonts/FreeSansBold12pt7b.h>
#include <epaper.h>
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<GxEPD2_DRIVER_CLASS, MAX_HEIGHT(GxEPD2_DRIVER_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);

18
src/timeseries.cpp Normal file
View file

@ -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;
}