Feature: Added fullscreen diagram to display

This commit is contained in:
Thomas Basler 2024-01-13 11:31:12 +01:00
parent 637d4f06a3
commit f013698471
8 changed files with 130 additions and 66 deletions

View File

@ -6,6 +6,14 @@
#include <TaskSchedulerDeclarations.h> #include <TaskSchedulerDeclarations.h>
#include <U8g2lib.h> #include <U8g2lib.h>
#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 { enum DisplayType_t {
None, None,
PCD8544, PCD8544,
@ -18,6 +26,7 @@ enum DisplayType_t {
enum DiagramMode_t { enum DiagramMode_t {
Off, Off,
Small, Small,
Fullscreen,
DisplayMode_Max, DisplayMode_Max,
}; };
@ -57,8 +66,8 @@ private:
DiagramMode_t _diagram_mode = DiagramMode_t::Off; DiagramMode_t _diagram_mode = DiagramMode_t::Off;
uint8_t _display_language = DISPLAY_LANGUAGE; uint8_t _display_language = DISPLAY_LANGUAGE;
uint8_t _mExtra; uint8_t _mExtra;
uint16_t _period = 1000; const uint16_t _period = 1000;
uint16_t _interval = 60000; // interval at which to power save (milliseconds) const uint16_t _interval = 60000; // interval at which to power save (milliseconds)
uint32_t _previousMillis = 0; uint32_t _previousMillis = 0;
char _fmtText[32]; char _fmtText[32];
bool _isLarge = false; bool _isLarge = false;

View File

@ -5,20 +5,14 @@
#include <U8g2lib.h> #include <U8g2lib.h>
#include <array> #include <array>
#define CHART_HEIGHT 20 // chart area hight in pixels #define MAX_DATAPOINTS 128
#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
class DisplayGraphicDiagramClass { class DisplayGraphicDiagramClass {
public: public:
DisplayGraphicDiagramClass(); DisplayGraphicDiagramClass();
void init(Scheduler& scheduler, U8G2* display); 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(); void updatePeriod();
@ -26,15 +20,17 @@ private:
void averageLoop(); void averageLoop();
void dataPointLoop(); void dataPointLoop();
static uint32_t getSecondsPerDot(); uint32_t getSecondsPerDot();
Task _averageTask; Task _averageTask;
Task _dataPointTask; Task _dataPointTask;
U8G2* _display = nullptr; U8G2* _display = nullptr;
std::array<float, CHART_WIDTH> _graphValues = {}; std::array<float, MAX_DATAPOINTS> _graphValues = {};
uint8_t _graphValuesCount = 0; uint8_t _graphValuesCount = 0;
uint8_t _chartWidth = MAX_DATAPOINTS;
float _iRunningAverage = 0; float _iRunningAverage = 0;
uint16_t _iRunningAverageCnt = 0; uint16_t _iRunningAverageCnt = 0;
}; };

View File

@ -28,8 +28,8 @@ const uint8_t languages[] = {
}; };
static const char* const i18n_offline[] = { "Offline", "Offline", "Offline" }; 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_w[] = { "%.0f W", "%.0f W", "%.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_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_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_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" }; 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) void DisplayGraphicClass::printText(const char* text, const uint8_t line)
{ {
setFont(line);
uint8_t dispX; uint8_t dispX;
if (!_isLarge) { if (!_isLarge) {
dispX = (line == 0) ? 5 : 0; dispX = (line == 0) ? 5 : 0;
} else { } else {
if (_diagram_mode == DiagramMode_t::Small) { switch (line) {
dispX = (line == 0) ? 10 : 5; case 0:
} else { if (_diagram_mode == DiagramMode_t::Small) {
dispX = (line == 0) ? 20 : 5; // 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; dispX += enableScreensaver ? (_mExtra % 7) : 0;
_display->drawStr(dispX, _lineOffsets[line], text); _display->drawStr(dispX, _lineOffsets[line], text);
@ -170,21 +183,37 @@ void DisplayGraphicClass::loop()
_display->clearBuffer(); _display->clearBuffer();
bool displayPowerSave = false; bool displayPowerSave = false;
bool showText = true;
//=====> Actual Production ========== //=====> Actual Production ==========
if (Datastore.getIsAtLeastOneReachable()) { if (Datastore.getIsAtLeastOneReachable()) {
displayPowerSave = false; displayPowerSave = false;
if (_isLarge && _diagram_mode == DiagramMode_t::Small) { if (_isLarge) {
uint8_t screenSaverOffsetX = enableScreensaver ? (_mExtra % 7) : 0; 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 (showText) {
if (watts > 999) { const float watts = Datastore.getTotalAcPowerEnabled();
snprintf(_fmtText, sizeof(_fmtText), i18n_current_power_kw[_display_language], watts / 1000); if (watts > 999) {
} else { snprintf(_fmtText, sizeof(_fmtText), i18n_current_power_kw[_display_language], watts / 1000);
snprintf(_fmtText, sizeof(_fmtText), i18n_current_power_w[_display_language], watts); } else {
snprintf(_fmtText, sizeof(_fmtText), i18n_current_power_w[_display_language], watts);
}
printText(_fmtText, 0);
} }
printText(_fmtText, 0);
_previousMillis = millis(); _previousMillis = millis();
} }
//<======================= //<=======================
@ -199,23 +228,27 @@ void DisplayGraphicClass::loop()
} }
//<======================= //<=======================
//=====> Today & Total Production ======= if (showText) {
snprintf(_fmtText, sizeof(_fmtText), i18n_yield_today_wh[_display_language], Datastore.getTotalAcYieldDayEnabled()); //=====> Today & Total Production =======
printText(_fmtText, 1); 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()); snprintf(_fmtText, sizeof(_fmtText), i18n_yield_total_kwh[_display_language], Datastore.getTotalAcYieldTotalEnabled());
printText(_fmtText, 2); printText(_fmtText, 2);
//<======================= //<=======================
//=====> IP or Date-Time ======== //=====> IP or Date-Time ========
if (!(_mExtra % 10) && NetworkSettings.localIP()) { // Change every 3 seconds
printText(NetworkSettings.localIP().toString().c_str(), 3); if (!(_mExtra % (3 * 2) < 3) && NetworkSettings.localIP()) {
} else { printText(NetworkSettings.localIP().toString().c_str(), 3);
// Get current time } else {
time_t now = time(nullptr); // Get current time
strftime(_fmtText, sizeof(_fmtText), i18n_date_format[_display_language], localtime(&now)); time_t now = time(nullptr);
printText(_fmtText, 3); strftime(_fmtText, sizeof(_fmtText), i18n_date_format[_display_language], localtime(&now));
printText(_fmtText, 3);
}
} }
_display->sendBuffer(); _display->sendBuffer();
_mExtra++; _mExtra++;

View File

@ -52,38 +52,39 @@ void DisplayGraphicDiagramClass::dataPointLoop()
uint32_t DisplayGraphicDiagramClass::getSecondsPerDot() uint32_t DisplayGraphicDiagramClass::getSecondsPerDot()
{ {
return Configuration.get().Display.Diagram.Duration / CHART_WIDTH; return Configuration.get().Display.Diagram.Duration / _chartWidth;
} }
void DisplayGraphicDiagramClass::updatePeriod() 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)
{ {
// screenSaverOffsetX expected to be in range 0..6 _chartWidth = width;
const uint8_t graphPosX = DIAG_POSX + ((screenSaverOffsetX > 3) ? 1 : 0);
const uint8_t graphPosY = DIAG_POSY + ((screenSaverOffsetX > 3) ? 1 : 0);
const uint8_t horizontal_line_y = graphPosY + CHART_HEIGHT - 1; // screenSaverOffsetX expected to be in range 0..6
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 + height - 1;
const uint8_t arrow_size = 2; const uint8_t arrow_size = 2;
// draw diagram axis // draw diagram axis
_display->drawVLine(graphPosX, graphPosY, CHART_HEIGHT); _display->drawVLine(graphPosX, graphPosY, height);
_display->drawHLine(graphPosX, horizontal_line_y, CHART_WIDTH); _display->drawHLine(graphPosX, horizontal_line_y, width);
// UP-arrow // UP-arrow
_display->drawLine(graphPosX, graphPosY, graphPosX + arrow_size, graphPosY + arrow_size); _display->drawLine(graphPosX, graphPosY, graphPosX + arrow_size, graphPosY + arrow_size);
_display->drawLine(graphPosX, graphPosY, graphPosX - arrow_size, graphPosY + arrow_size); _display->drawLine(graphPosX, graphPosY, graphPosX - arrow_size, graphPosY + arrow_size);
// LEFT-arrow // 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 + width - 1, horizontal_line_y, graphPosX + 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);
// draw AC value // draw AC value
// 4 pixels per char
_display->setFont(u8g2_font_tom_thumb_4x6_mr);
char fmtText[7]; char fmtText[7];
const float maxWatts = *std::max_element(_graphValues.begin(), _graphValues.end()); const float maxWatts = *std::max_element(_graphValues.begin(), _graphValues.end());
if (maxWatts > 999) { if (maxWatts > 999) {
@ -91,25 +92,46 @@ void DisplayGraphicDiagramClass::redraw(uint8_t screenSaverOffsetX)
} else { } else {
snprintf(fmtText, sizeof(fmtText), "%dW", static_cast<uint16_t>(maxWatts)); snprintf(fmtText, sizeof(fmtText), "%dW", static_cast<uint16_t>(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 // draw chart
const float scaleFactor = maxWatts / CHART_HEIGHT; const float scaleFactorY = maxWatts / static_cast<float>(height);
uint8_t axisTick = 1; const float scaleFactorX = static_cast<float>(MAX_DATAPOINTS) / static_cast<float>(_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++) { for (uint8_t i = 1; i < _graphValuesCount; i++) {
// draw one tick per hour to the x-axis // draw one tick per hour to the x-axis
if (i * getSecondsPerDot() > (3600u * axisTick)) { if (i * getSecondsPerDot() > (3600u * xAxisTicks)) {
_display->drawPixel(graphPosX + 1 + i, graphPosY + CHART_HEIGHT); _display->drawPixel((graphPosX + 1 + i) * scaleFactorX, graphPosY + height);
axisTick++; xAxisTicks++;
} }
if (scaleFactor == 0) { if (scaleFactorY == 0 || scaleFactorX == 0) {
continue; continue;
} }
_display->drawLine( _display->drawLine(
graphPosX + i - 1, horizontal_line_y - std::max<int16_t>(0, _graphValues[i - 1] / scaleFactor - 0.5), graphPosX + (i - 1) / scaleFactorX, horizontal_line_y - std::max<int16_t>(0, _graphValues[i - 1] / scaleFactorY - 0.5),
graphPosX + i, horizontal_line_y - std::max<int16_t>(0, _graphValues[i] / scaleFactor - 0.5)); graphPosX + i / scaleFactorX, horizontal_line_y - std::max<int16_t>(0, _graphValues[i] / scaleFactorY - 0.5));
} }
} }

View File

@ -582,9 +582,10 @@
"PowerSafeHint": "Schaltet das Display aus, wenn kein Wechselrichter Strom erzeugt", "PowerSafeHint": "Schaltet das Display aus, wenn kein Wechselrichter Strom erzeugt",
"Screensaver": "Bildschirmschoner aktivieren:", "Screensaver": "Bildschirmschoner aktivieren:",
"ScreensaverHint": "Bewegt die Ausgabe bei jeder Aktualisierung um ein Einbrennen zu verhindern (v. a. für OLED-Displays nützlich)", "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", "off": "Deaktiviert",
"small": "Klein", "small": "Klein",
"fullscreen": "Vollbild",
"DiagramDuration": "Diagramm Periode:", "DiagramDuration": "Diagramm Periode:",
"DiagramDurationHint": "Die Zeitperiode welche im Diagramm dargestellt wird.", "DiagramDurationHint": "Die Zeitperiode welche im Diagramm dargestellt wird.",
"Seconds": "Sekunden", "Seconds": "Sekunden",

View File

@ -585,6 +585,7 @@
"DiagramMode": "Diagram mode:", "DiagramMode": "Diagram mode:",
"off": "Off", "off": "Off",
"small": "Small", "small": "Small",
"fullscreen": "Fullscreen",
"DiagramDuration": "Diagram duration:", "DiagramDuration": "Diagram duration:",
"DiagramDurationHint": "The time period which is shown in the diagram.", "DiagramDurationHint": "The time period which is shown in the diagram.",
"Seconds": "Seconds", "Seconds": "Seconds",

View File

@ -585,6 +585,7 @@
"DiagramMode": "Diagram mode:", "DiagramMode": "Diagram mode:",
"off": "Off", "off": "Off",
"small": "Small", "small": "Small",
"fullscreen": "Fullscreen",
"DiagramDuration": "Diagram duration:", "DiagramDuration": "Diagram duration:",
"DiagramDurationHint": "The time period which is shown in the diagram.", "DiagramDurationHint": "The time period which is shown in the diagram.",
"Seconds": "Seconds", "Seconds": "Seconds",

View File

@ -199,6 +199,7 @@ export default defineComponent({
diagramModeList: [ diagramModeList: [
{ key: 0, value: "off" }, { key: 0, value: "off" },
{ key: 1, value: "small" }, { key: 1, value: "small" },
{ key: 2, value: "fullscreen" },
] ]
} }
}, },