Skip to content

Commit

Permalink
Feature: Added fullscreen diagram to display
Browse files Browse the repository at this point in the history
  • Loading branch information
tbnobody committed Jan 13, 2024
1 parent 637d4f0 commit f013698
Show file tree
Hide file tree
Showing 8 changed files with 129 additions and 65 deletions.
13 changes: 11 additions & 2 deletions include/Display_Graphic.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,14 @@
#include <TaskSchedulerDeclarations.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 {
None,
PCD8544,
Expand All @@ -18,6 +26,7 @@ enum DisplayType_t {
enum DiagramMode_t {
Off,
Small,
Fullscreen,
DisplayMode_Max,
};

Expand Down Expand Up @@ -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;
Expand Down
16 changes: 6 additions & 10 deletions include/Display_Graphic_Diagram.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,36 +5,32 @@
#include <U8g2lib.h>
#include <array>

#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();

private:
void averageLoop();
void dataPointLoop();

static uint32_t getSecondsPerDot();
uint32_t getSecondsPerDot();

Task _averageTask;
Task _dataPointTask;

U8G2* _display = nullptr;
std::array<float, CHART_WIDTH> _graphValues = {};
std::array<float, MAX_DATAPOINTS> _graphValues = {};
uint8_t _graphValuesCount = 0;

uint8_t _chartWidth = MAX_DATAPOINTS;

float _iRunningAverage = 0;
uint16_t _iRunningAverageCnt = 0;
};
93 changes: 63 additions & 30 deletions src/Display_Graphic.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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" };
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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();
}
//<=======================
Expand All @@ -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++;
Expand Down
66 changes: 44 additions & 22 deletions src/Display_Graphic_Diagram.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,64 +52,86 @@ 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) {
snprintf(fmtText, sizeof(fmtText), "%2.1fkW", maxWatts / 1000);
} else {
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
const float scaleFactor = maxWatts / CHART_HEIGHT;
uint8_t axisTick = 1;
const float scaleFactorY = maxWatts / static_cast<float>(height);
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++) {
// 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<int16_t>(0, _graphValues[i - 1] / scaleFactor - 0.5),
graphPosX + i, horizontal_line_y - std::max<int16_t>(0, _graphValues[i] / scaleFactor - 0.5));
graphPosX + (i - 1) / scaleFactorX, horizontal_line_y - std::max<int16_t>(0, _graphValues[i - 1] / scaleFactorY - 0.5),
graphPosX + i / scaleFactorX, horizontal_line_y - std::max<int16_t>(0, _graphValues[i] / scaleFactorY - 0.5));
}
}
3 changes: 2 additions & 1 deletion webapp/src/locales/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
1 change: 1 addition & 0 deletions webapp/src/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
1 change: 1 addition & 0 deletions webapp/src/locales/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
1 change: 1 addition & 0 deletions webapp/src/views/DeviceAdminView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,7 @@ export default defineComponent({
diagramModeList: [
{ key: 0, value: "off" },
{ key: 1, value: "small" },
{ key: 2, value: "fullscreen" },
]
}
},
Expand Down

0 comments on commit f013698

Please sign in to comment.