#include #include #include #include #include #include "epaper.h" static GxEPD2_DISPLAY_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 void minmax(const std::vector &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