From f013698471907f39dc65a8c565854fc676e2ac18 Mon Sep 17 00:00:00 2001 From: Thomas Basler Date: Sat, 13 Jan 2024 11:31:12 +0100 Subject: [PATCH] Feature: Added fullscreen diagram to display --- include/Display_Graphic.h | 13 +++- include/Display_Graphic_Diagram.h | 16 ++--- src/Display_Graphic.cpp | 93 +++++++++++++++++++--------- src/Display_Graphic_Diagram.cpp | 66 +++++++++++++------- webapp/src/locales/de.json | 3 +- webapp/src/locales/en.json | 1 + webapp/src/locales/fr.json | 1 + webapp/src/views/DeviceAdminView.vue | 1 + 8 files changed, 129 insertions(+), 65 deletions(-) diff --git a/include/Display_Graphic.h b/include/Display_Graphic.h index 127c70fe1..270ee81da 100644 --- a/include/Display_Graphic.h +++ b/include/Display_Graphic.h @@ -6,6 +6,14 @@ #include #include +#define CHART_HEIGHT 20 // chart area hight in pixels +#define CHART_WIDTH 47 // chart area width in pixels + +// Left-Upper position of diagram is drawn +// (text of Y-axis is display left of that pos) +#define CHART_POSX 80 +#define CHART_POSY 0 + enum DisplayType_t { None, PCD8544, @@ -18,6 +26,7 @@ enum DisplayType_t { enum DiagramMode_t { Off, Small, + Fullscreen, DisplayMode_Max, }; @@ -57,8 +66,8 @@ class DisplayGraphicClass { DiagramMode_t _diagram_mode = DiagramMode_t::Off; uint8_t _display_language = DISPLAY_LANGUAGE; uint8_t _mExtra; - uint16_t _period = 1000; - uint16_t _interval = 60000; // interval at which to power save (milliseconds) + const uint16_t _period = 1000; + const uint16_t _interval = 60000; // interval at which to power save (milliseconds) uint32_t _previousMillis = 0; char _fmtText[32]; bool _isLarge = false; diff --git a/include/Display_Graphic_Diagram.h b/include/Display_Graphic_Diagram.h index 2067a3f09..26cacc372 100644 --- a/include/Display_Graphic_Diagram.h +++ b/include/Display_Graphic_Diagram.h @@ -5,20 +5,14 @@ #include #include -#define CHART_HEIGHT 20 // chart area hight in pixels -#define CHART_WIDTH 47 // chart area width in pixels - -// Left-Upper position of diagram is drawn -// (text of Y-axis is display left of that pos) -#define DIAG_POSX 80 -#define DIAG_POSY 0 +#define MAX_DATAPOINTS 128 class DisplayGraphicDiagramClass { public: DisplayGraphicDiagramClass(); void init(Scheduler& scheduler, U8G2* display); - void redraw(uint8_t screenSaverOffsetX); + void redraw(uint8_t screenSaverOffsetX, uint8_t xPos, uint8_t yPos, uint8_t width, uint8_t height, bool isFullscreen); void updatePeriod(); @@ -26,15 +20,17 @@ class DisplayGraphicDiagramClass { void averageLoop(); void dataPointLoop(); - static uint32_t getSecondsPerDot(); + uint32_t getSecondsPerDot(); Task _averageTask; Task _dataPointTask; U8G2* _display = nullptr; - std::array _graphValues = {}; + std::array _graphValues = {}; uint8_t _graphValuesCount = 0; + uint8_t _chartWidth = MAX_DATAPOINTS; + float _iRunningAverage = 0; uint16_t _iRunningAverageCnt = 0; }; diff --git a/src/Display_Graphic.cpp b/src/Display_Graphic.cpp index a013e88b7..04195fd66 100644 --- a/src/Display_Graphic.cpp +++ b/src/Display_Graphic.cpp @@ -28,8 +28,8 @@ const uint8_t languages[] = { }; static const char* const i18n_offline[] = { "Offline", "Offline", "Offline" }; -static const char* const i18n_current_power_w[] = { "%3.0f W", "%3.0f W", "%3.0f W" }; -static const char* const i18n_current_power_kw[] = { "%2.1f kW", "%2.1f kW", "%2.1f kW" }; +static const char* const i18n_current_power_w[] = { "%.0f W", "%.0f W", "%.0f W" }; +static const char* const i18n_current_power_kw[] = { "%.1f kW", "%.1f kW", "%.1f kW" }; static const char* const i18n_yield_today_wh[] = { "today: %4.0f Wh", "Heute: %4.0f Wh", "auj.: %4.0f Wh" }; static const char* const i18n_yield_total_kwh[] = { "total: %.1f kWh", "Ges.: %.1f kWh", "total: %.1f kWh" }; static const char* const i18n_date_format[] = { "%m/%d/%Y %H:%M", "%d.%m.%Y %H:%M", "%d/%m/%Y %H:%M" }; @@ -94,18 +94,31 @@ bool DisplayGraphicClass::isValidDisplay() void DisplayGraphicClass::printText(const char* text, const uint8_t line) { + setFont(line); + uint8_t dispX; if (!_isLarge) { dispX = (line == 0) ? 5 : 0; } else { - if (_diagram_mode == DiagramMode_t::Small) { - dispX = (line == 0) ? 10 : 5; - } else { - dispX = (line == 0) ? 20 : 5; + switch (line) { + case 0: + if (_diagram_mode == DiagramMode_t::Small) { + // Center between left border and diagram + dispX = (CHART_POSX - _display->getStrWidth(text)) / 2; + } else { + // Center on screen + dispX = (_display->getDisplayWidth() - _display->getStrWidth(text)) / 2; + } + break; + case 3: + // Center on screen + dispX = (_display->getDisplayWidth() - _display->getStrWidth(text)) / 2; + break; + default: + dispX = 5; + break; } - } - setFont(line); dispX += enableScreensaver ? (_mExtra % 7) : 0; _display->drawStr(dispX, _lineOffsets[line], text); @@ -170,21 +183,37 @@ void DisplayGraphicClass::loop() _display->clearBuffer(); bool displayPowerSave = false; + bool showText = true; //=====> Actual Production ========== if (Datastore.getIsAtLeastOneReachable()) { displayPowerSave = false; - if (_isLarge && _diagram_mode == DiagramMode_t::Small) { + if (_isLarge) { uint8_t screenSaverOffsetX = enableScreensaver ? (_mExtra % 7) : 0; - _diagram.redraw(screenSaverOffsetX); + switch (_diagram_mode) { + case DiagramMode_t::Small: + _diagram.redraw(screenSaverOffsetX, CHART_POSX, CHART_POSY, CHART_WIDTH, CHART_HEIGHT, false); + break; + case DiagramMode_t::Fullscreen: + // Every 10 seconds + if (_mExtra % (10 * 2) < 10) { + _diagram.redraw(screenSaverOffsetX, 10, 0, _display->getDisplayWidth() - 12, _display->getDisplayHeight() - 3, true); + showText = false; + } + break; + default: + break; + } } - const float watts = Datastore.getTotalAcPowerEnabled(); - if (watts > 999) { - snprintf(_fmtText, sizeof(_fmtText), i18n_current_power_kw[_display_language], watts / 1000); - } else { - snprintf(_fmtText, sizeof(_fmtText), i18n_current_power_w[_display_language], watts); + if (showText) { + const float watts = Datastore.getTotalAcPowerEnabled(); + if (watts > 999) { + snprintf(_fmtText, sizeof(_fmtText), i18n_current_power_kw[_display_language], watts / 1000); + } else { + snprintf(_fmtText, sizeof(_fmtText), i18n_current_power_w[_display_language], watts); + } + printText(_fmtText, 0); } - printText(_fmtText, 0); _previousMillis = millis(); } //<======================= @@ -199,23 +228,27 @@ void DisplayGraphicClass::loop() } //<======================= - //=====> Today & Total Production ======= - snprintf(_fmtText, sizeof(_fmtText), i18n_yield_today_wh[_display_language], Datastore.getTotalAcYieldDayEnabled()); - printText(_fmtText, 1); + if (showText) { + //=====> Today & Total Production ======= + snprintf(_fmtText, sizeof(_fmtText), i18n_yield_today_wh[_display_language], Datastore.getTotalAcYieldDayEnabled()); + printText(_fmtText, 1); - snprintf(_fmtText, sizeof(_fmtText), i18n_yield_total_kwh[_display_language], Datastore.getTotalAcYieldTotalEnabled()); - printText(_fmtText, 2); - //<======================= + snprintf(_fmtText, sizeof(_fmtText), i18n_yield_total_kwh[_display_language], Datastore.getTotalAcYieldTotalEnabled()); + printText(_fmtText, 2); + //<======================= - //=====> IP or Date-Time ======== - if (!(_mExtra % 10) && NetworkSettings.localIP()) { - printText(NetworkSettings.localIP().toString().c_str(), 3); - } else { - // Get current time - time_t now = time(nullptr); - strftime(_fmtText, sizeof(_fmtText), i18n_date_format[_display_language], localtime(&now)); - printText(_fmtText, 3); + //=====> IP or Date-Time ======== + // Change every 3 seconds + if (!(_mExtra % (3 * 2) < 3) && NetworkSettings.localIP()) { + printText(NetworkSettings.localIP().toString().c_str(), 3); + } else { + // Get current time + time_t now = time(nullptr); + strftime(_fmtText, sizeof(_fmtText), i18n_date_format[_display_language], localtime(&now)); + printText(_fmtText, 3); + } } + _display->sendBuffer(); _mExtra++; diff --git a/src/Display_Graphic_Diagram.cpp b/src/Display_Graphic_Diagram.cpp index 326e26bc8..498123b90 100644 --- a/src/Display_Graphic_Diagram.cpp +++ b/src/Display_Graphic_Diagram.cpp @@ -52,38 +52,39 @@ void DisplayGraphicDiagramClass::dataPointLoop() uint32_t DisplayGraphicDiagramClass::getSecondsPerDot() { - return Configuration.get().Display.Diagram.Duration / CHART_WIDTH; + return Configuration.get().Display.Diagram.Duration / _chartWidth; } void DisplayGraphicDiagramClass::updatePeriod() { - _dataPointTask.setInterval(getSecondsPerDot() * TASK_SECOND); + // Calculate seconds per datapoint + _dataPointTask.setInterval(Configuration.get().Display.Diagram.Duration * TASK_SECOND / MAX_DATAPOINTS ); } -void DisplayGraphicDiagramClass::redraw(uint8_t screenSaverOffsetX) +void DisplayGraphicDiagramClass::redraw(uint8_t screenSaverOffsetX, uint8_t xPos, uint8_t yPos, uint8_t width, uint8_t height, bool isFullscreen) { + _chartWidth = width; + // screenSaverOffsetX expected to be in range 0..6 - const uint8_t graphPosX = DIAG_POSX + ((screenSaverOffsetX > 3) ? 1 : 0); - const uint8_t graphPosY = DIAG_POSY + ((screenSaverOffsetX > 3) ? 1 : 0); + const uint8_t graphPosX = xPos + ((screenSaverOffsetX > 3) ? 1 : 0); + const uint8_t graphPosY = yPos + ((screenSaverOffsetX > 3) ? 1 : 0); - const uint8_t horizontal_line_y = graphPosY + CHART_HEIGHT - 1; + const uint8_t horizontal_line_y = graphPosY + height - 1; const uint8_t arrow_size = 2; // draw diagram axis - _display->drawVLine(graphPosX, graphPosY, CHART_HEIGHT); - _display->drawHLine(graphPosX, horizontal_line_y, CHART_WIDTH); + _display->drawVLine(graphPosX, graphPosY, height); + _display->drawHLine(graphPosX, horizontal_line_y, width); // UP-arrow _display->drawLine(graphPosX, graphPosY, graphPosX + arrow_size, graphPosY + arrow_size); _display->drawLine(graphPosX, graphPosY, graphPosX - arrow_size, graphPosY + arrow_size); // LEFT-arrow - _display->drawLine(graphPosX + CHART_WIDTH - 1, horizontal_line_y, graphPosX + CHART_WIDTH - 1 - arrow_size, horizontal_line_y - arrow_size); - _display->drawLine(graphPosX + CHART_WIDTH - 1, horizontal_line_y, graphPosX + CHART_WIDTH - 1 - arrow_size, horizontal_line_y + arrow_size); + _display->drawLine(graphPosX + width - 1, horizontal_line_y, graphPosX + width - 1 - arrow_size, horizontal_line_y - arrow_size); + _display->drawLine(graphPosX + width - 1, horizontal_line_y, graphPosX + width - 1 - arrow_size, horizontal_line_y + arrow_size); // draw AC value - // 4 pixels per char - _display->setFont(u8g2_font_tom_thumb_4x6_mr); char fmtText[7]; const float maxWatts = *std::max_element(_graphValues.begin(), _graphValues.end()); if (maxWatts > 999) { @@ -91,25 +92,46 @@ void DisplayGraphicDiagramClass::redraw(uint8_t screenSaverOffsetX) } else { snprintf(fmtText, sizeof(fmtText), "%dW", static_cast(maxWatts)); } - const uint8_t textLength = strlen(fmtText); - _display->drawStr(graphPosX - arrow_size - textLength * 4, graphPosY + 5, fmtText); + + if (isFullscreen) { + _display->setFont(u8g2_font_5x8_tr); + _display->setFontDirection(3); + _display->drawStr(graphPosX - arrow_size, graphPosY + _display->getStrWidth(fmtText), fmtText); + _display->setFontDirection(0); + } else { + // 4 pixels per char + _display->setFont(u8g2_font_tom_thumb_4x6_mr); + _display->drawStr(graphPosX - arrow_size - _display->getStrWidth(fmtText), graphPosY + 5, fmtText); + } // draw chart - const float scaleFactor = maxWatts / CHART_HEIGHT; - uint8_t axisTick = 1; + const float scaleFactorY = maxWatts / static_cast(height); + const float scaleFactorX = static_cast(MAX_DATAPOINTS) / static_cast(_chartWidth); + + if (maxWatts > 0 && isFullscreen) { + // draw y axis ticks + const uint16_t yAxisWattPerTick = maxWatts <= 100 ? 10 : maxWatts <= 1000 ? 100 : maxWatts < 5000 ? 500 : 1000; + const uint8_t yAxisTickSizePixel = height / (maxWatts / yAxisWattPerTick); + + for (int16_t tickYPos = graphPosY + height; tickYPos > graphPosY - arrow_size; tickYPos -= yAxisTickSizePixel) { + _display->drawPixel(graphPosX - 1, tickYPos); + } + } + + uint8_t xAxisTicks = 1; for (uint8_t i = 1; i < _graphValuesCount; i++) { // draw one tick per hour to the x-axis - if (i * getSecondsPerDot() > (3600u * axisTick)) { - _display->drawPixel(graphPosX + 1 + i, graphPosY + CHART_HEIGHT); - axisTick++; + if (i * getSecondsPerDot() > (3600u * xAxisTicks)) { + _display->drawPixel((graphPosX + 1 + i) * scaleFactorX, graphPosY + height); + xAxisTicks++; } - if (scaleFactor == 0) { + if (scaleFactorY == 0 || scaleFactorX == 0) { continue; } _display->drawLine( - graphPosX + i - 1, horizontal_line_y - std::max(0, _graphValues[i - 1] / scaleFactor - 0.5), - graphPosX + i, horizontal_line_y - std::max(0, _graphValues[i] / scaleFactor - 0.5)); + graphPosX + (i - 1) / scaleFactorX, horizontal_line_y - std::max(0, _graphValues[i - 1] / scaleFactorY - 0.5), + graphPosX + i / scaleFactorX, horizontal_line_y - std::max(0, _graphValues[i] / scaleFactorY - 0.5)); } } diff --git a/webapp/src/locales/de.json b/webapp/src/locales/de.json index 5400910f8..2f0fb8392 100644 --- a/webapp/src/locales/de.json +++ b/webapp/src/locales/de.json @@ -582,9 +582,10 @@ "PowerSafeHint": "Schaltet das Display aus, wenn kein Wechselrichter Strom erzeugt", "Screensaver": "Bildschirmschoner aktivieren:", "ScreensaverHint": "Bewegt die Ausgabe bei jeder Aktualisierung um ein Einbrennen zu verhindern (v. a. für OLED-Displays nützlich)", - "DiagramMode": "Diagram Modus:", + "DiagramMode": "Diagramm Modus:", "off": "Deaktiviert", "small": "Klein", + "fullscreen": "Vollbild", "DiagramDuration": "Diagramm Periode:", "DiagramDurationHint": "Die Zeitperiode welche im Diagramm dargestellt wird.", "Seconds": "Sekunden", diff --git a/webapp/src/locales/en.json b/webapp/src/locales/en.json index c8b783515..31cff24a3 100644 --- a/webapp/src/locales/en.json +++ b/webapp/src/locales/en.json @@ -585,6 +585,7 @@ "DiagramMode": "Diagram mode:", "off": "Off", "small": "Small", + "fullscreen": "Fullscreen", "DiagramDuration": "Diagram duration:", "DiagramDurationHint": "The time period which is shown in the diagram.", "Seconds": "Seconds", diff --git a/webapp/src/locales/fr.json b/webapp/src/locales/fr.json index a1c5eb265..3404efd63 100644 --- a/webapp/src/locales/fr.json +++ b/webapp/src/locales/fr.json @@ -585,6 +585,7 @@ "DiagramMode": "Diagram mode:", "off": "Off", "small": "Small", + "fullscreen": "Fullscreen", "DiagramDuration": "Diagram duration:", "DiagramDurationHint": "The time period which is shown in the diagram.", "Seconds": "Seconds", diff --git a/webapp/src/views/DeviceAdminView.vue b/webapp/src/views/DeviceAdminView.vue index 2f6419923..c74500873 100644 --- a/webapp/src/views/DeviceAdminView.vue +++ b/webapp/src/views/DeviceAdminView.vue @@ -199,6 +199,7 @@ export default defineComponent({ diagramModeList: [ { key: 0, value: "off" }, { key: 1, value: "small" }, + { key: 2, value: "fullscreen" }, ] } },