SensorCube-Firmware/src/epaper.cpp

294 lines
7.1 KiB
C++
Raw Normal View History

2024-06-16 16:00:19 +02:00
#include <algorithm>
#include <Adafruit_GFX.h>
#include <Fonts/FreeMonoBold9pt7b.h>
#include <Font_din1451e.h>
#include <Fonts/Org_01.h>
#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));
void epaper_init(void)
{
display.init();
}
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());
Serial.println(F("Init done. Display going to powersave…"));
display.hibernate();
}
void epaper_draw_and_hibernate(epaper_callback cb)
{
display.setRotation(0);
display.setFullWindow();
display.firstPage();
do {
cb(&display);
} while(display.nextPage());
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)
{
bool first = true;
// 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;
}
if(first) {
*min = *max = v;
first = false;
continue;
}
if(v < *min) {
*min = 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];
uint32_t trange = timeseries->tend - timeseries->tstart;
uint32_t tstep = timeseries->interval;
if(trange == 0) {
trange = 3600;
}
float vmin, vmax;
minmax(timeseries->data, &vmin, &vmax);
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);
if(vrange == 0.0f) {
vrange = 1.0f;
}
// 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;
// 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;
// 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;
// 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};
float scale = 1e3f;
float accepted_scale = scale;
bool scale_found = false;
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;
}
/* Start drawing */
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;
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);
}
display.getTextBounds(s, 0, 0, &tbx, &tby, &tbw, &tbh);
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) {
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);
snprintf(s, sizeof(s), timeseries->format, tick_val);
display.getTextBounds(s, 0, 0, &tbx, &tby, &tbw, &tbh);
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;
}
/*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!"));
firstpoint = true;
continue;
}
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(")."));*/
display.drawLine(last_x, last_y, px, py, GxEPD_RED);
// 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;
}
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