* merge PR: pin selection for ESP-32 S2 #1334
* merge PR: enhancement: power graph display option #1330
This commit is contained in:
lumapu 2024-01-07 22:25:59 +01:00
parent 42926c4d26
commit 3c5be9ae35
6 changed files with 234 additions and 246 deletions

View file

@ -147,8 +147,6 @@ jobs:
with: with:
merge-multiple: true merge-multiple: true
path: firmware path: firmware
- name: Display structure of downloaded files
run: ls -R firmware
- name: Get Version from code - name: Get Version from code
id: version_name id: version_name
@ -161,19 +159,6 @@ jobs:
env: env:
VERSION: ${{ steps.version_name.outputs.name }} VERSION: ${{ steps.version_name.outputs.name }}
# - name: Create Manifest
# working-directory: src
# run: python ../scripts/buildManifest.py
#
# - name: Create Artifact
# uses: actions/upload-artifact@v3
# with:
# name: ahoydtu_dev
# path: |
# src/firmware/*
# src/User_Manual.md
# src/install.html
- name: Rename firmware directory - name: Rename firmware directory
run: mv firmware ${{ steps.version_name.outputs.name }} run: mv firmware ${{ steps.version_name.outputs.name }}

View file

@ -1,5 +1,9 @@
# Development Changes # Development Changes
## 0.8.48 - 2024-01-07
* merge PR: pin selection for ESP-32 S2 #1334
* merge PR: enhancement: power graph display option #1330
## 0.8.47 - 2024-01-06 ## 0.8.47 - 2024-01-06
* reduce GxEPD2 lib to compile faster * reduce GxEPD2 lib to compile faster
* upgraded GxEPD2 lib to `1.5.3` * upgraded GxEPD2 lib to `1.5.3`

View file

@ -13,7 +13,7 @@
//------------------------------------- //-------------------------------------
#define VERSION_MAJOR 0 #define VERSION_MAJOR 0
#define VERSION_MINOR 8 #define VERSION_MINOR 8
#define VERSION_PATCH 47 #define VERSION_PATCH 48
//------------------------------------- //-------------------------------------
typedef struct { typedef struct {

View file

@ -22,256 +22,255 @@
#include "../../utils/timemonitor.h" #include "../../utils/timemonitor.h"
class DisplayMono { class DisplayMono {
public: public:
DisplayMono() {}; DisplayMono() {};
virtual void init(uint8_t type, uint8_t rot, uint8_t cs, uint8_t dc, uint8_t reset, uint8_t clock, uint8_t data, DisplayData *displayData) = 0; virtual void init(uint8_t type, uint8_t rot, uint8_t cs, uint8_t dc, uint8_t reset, uint8_t clock, uint8_t data, DisplayData *displayData) = 0;
virtual void config(bool enPowerSave, uint8_t screenSaver, uint8_t lum, uint8_t graph_ratio, uint8_t graph_size) = 0; virtual void config(bool enPowerSave, uint8_t screenSaver, uint8_t lum, uint8_t graph_ratio, uint8_t graph_size) = 0;
virtual void disp(void) = 0; virtual void disp(void) = 0;
// Common loop function, manages display on/off functions for powersave and screensaver with motionsensor // Common loop function, manages display on/off functions for powersave and screensaver with motionsensor
// can be overridden by subclasses // can be overridden by subclasses
virtual bool loop(uint8_t lum, bool motion) { virtual bool loop(uint8_t lum, bool motion) {
bool dispConditions = (!mEnPowerSave || (mDisplayData->nrProducing > 0)) && bool dispConditions = (!mEnPowerSave || (mDisplayData->nrProducing > 0)) &&
((mScreenSaver != 2) || motion); // screensaver 2 .. motionsensor ((mScreenSaver != 2) || motion); // screensaver 2 .. motionsensor
if (mDisplayActive) { if (mDisplayActive) {
if (!dispConditions) { if (!dispConditions) {
if (mDisplayTime.isTimeout()) { // switch display off after timeout if (mDisplayTime.isTimeout()) { // switch display off after timeout
mDisplayActive = false; mDisplayActive = false;
mDisplay->setPowerSave(true); mDisplay->setPowerSave(true);
DBGPRINTLN("**** Display off ****"); DBGPRINTLN("**** Display off ****");
}
}
else
mDisplayTime.reStartTimeMonitor(); // keep display on
}
else {
if (dispConditions) {
mDisplayActive = true;
mDisplayTime.reStartTimeMonitor(); // switch display on
mDisplay->setPowerSave(false);
DBGPRINTLN("**** Display on ****");
} }
} }
else
mDisplayTime.reStartTimeMonitor(); // keep display on
}
else {
if (dispConditions) {
mDisplayActive = true;
mDisplayTime.reStartTimeMonitor(); // switch display on
mDisplay->setPowerSave(false);
DBGPRINTLN("**** Display on ****");
}
}
if(mLuminance != lum) { if(mLuminance != lum) {
mLuminance = lum; mLuminance = lum;
mDisplay->setContrast(mLuminance);
}
return(monoMaintainDispSwitchState());
}
protected:
enum class DispSwitchState {
TEXT,
GRAPH
};
protected:
U8G2* mDisplay;
DisplayData *mDisplayData;
float *mPgData = nullptr;
uint8_t mPgWidth = 0;
uint8_t mPgHeight = 0;
float mPgMaxPwr = 0.0;
uint32_t mPgPeriod = 0; // seconds
uint32_t mPgTimeOfDay = 0;
uint8_t mPgLastPos = 0;
uint8_t mType;
uint16_t mDispWidth;
uint16_t mDispHeight;
bool mEnPowerSave;
uint8_t mScreenSaver = 1; // 0 .. off; 1 .. pixelShift; 2 .. motionsensor
uint8_t mLuminance;
uint8_t mGraphRatio;
uint8_t mGraphSize;
uint8_t mLoopCnt;
uint8_t mLineXOffsets[5] = {};
uint8_t mLineYOffsets[5] = {};
uint8_t mExtra;
int8_t mPixelshift=0;
TimeMonitor mDisplayTime = TimeMonitor(1000 * DISP_DEFAULT_TIMEOUT, true);
TimeMonitor mDispSwitchTime = TimeMonitor();
DispSwitchState mDispSwitchState = DispSwitchState::TEXT;
bool mDisplayActive = true; // always start with display on
char mFmtText[DISP_FMT_TEXT_LEN];
// Common initialization function to be called by subclasses
void monoInit(U8G2* display, uint8_t type, DisplayData *displayData) {
mDisplay = display;
mType = type;
mDisplayData = displayData;
mDisplay->begin();
mDisplay->setPowerSave(false); // always start with display on
mDisplay->setContrast(mLuminance); mDisplay->setContrast(mLuminance);
} mDisplay->clearBuffer();
mDispWidth = mDisplay->getDisplayWidth();
mDispHeight = mDisplay->getDisplayHeight();
mDispSwitchTime.stopTimeMonitor();
if (mGraphRatio == 100) // if graph ratio is 100% start in graph mode
mDispSwitchState = DispSwitchState::GRAPH;
else if (mGraphRatio != 0)
mDispSwitchTime.startTimeMonitor(150 * (100 - mGraphRatio)); // start display mode change only if ratio is neither 0 nor 100
}
return(monoMaintainDispSwitchState()); bool monoMaintainDispSwitchState(void) {
} bool change = false;
switch(mDispSwitchState) {
protected: case DispSwitchState::TEXT:
U8G2* mDisplay; if (mDispSwitchTime.isTimeout()) {
DisplayData *mDisplayData; mDispSwitchState = DispSwitchState::GRAPH;
mDispSwitchTime.startTimeMonitor(150 * mGraphRatio); // mGraphRatio: 0-100 Gesamtperiode 15000 ms
float *mPgData=nullptr; change = true;
uint8_t mPgWidth=0; }
uint8_t mPgHeight=0; break;
float mPgMaxPwr=0.0; case DispSwitchState::GRAPH:
// float mPgMaxAvailPower = 0.0; if (mDispSwitchTime.isTimeout()) {
uint32_t mPgPeriod=0; // seconds mDispSwitchState = DispSwitchState::TEXT;
uint32_t mPgTimeOfDay=0; mDispSwitchTime.startTimeMonitor(150 * (100 - mGraphRatio));
uint8_t mPgLastPos=0; change = true;
}
uint8_t mType; break;
uint16_t mDispWidth;
uint16_t mDispHeight;
bool mEnPowerSave;
uint8_t mScreenSaver = 1; // 0 .. off; 1 .. pixelShift; 2 .. motionsensor
uint8_t mLuminance;
uint8_t mGraphRatio;
uint8_t mGraphSize;
uint8_t mLoopCnt;
uint8_t mLineXOffsets[5] = {};
uint8_t mLineYOffsets[5] = {};
uint8_t mExtra;
int8_t mPixelshift=0;
TimeMonitor mDisplayTime = TimeMonitor(1000 * DISP_DEFAULT_TIMEOUT, true);
TimeMonitor mDispSwitchTime = TimeMonitor();
uint8_t mDispSwitchState;
bool mDisplayActive = true; // always start with display on
char mFmtText[DISP_FMT_TEXT_LEN];
enum _dispSwitchState {
d_POWER_TEXT = 0,
d_POWER_GRAPH = 1,
};
// Common initialization function to be called by subclasses
void monoInit(U8G2* display, uint8_t type, DisplayData *displayData) {
mDisplay = display;
mType = type;
mDisplayData = displayData;
mDisplay->begin();
mDisplay->setPowerSave(false); // always start with display on
mDisplay->setContrast(mLuminance);
mDisplay->clearBuffer();
mDispWidth = mDisplay->getDisplayWidth();
mDispHeight = mDisplay->getDisplayHeight();
mDispSwitchTime.stopTimeMonitor();
mDispSwitchState = d_POWER_TEXT;
if (mGraphRatio == 100) // if graph ratio is 100% start in graph mode
mDispSwitchState = d_POWER_GRAPH;
else if (mGraphRatio != 0)
mDispSwitchTime.startTimeMonitor(150 * (100 - mGraphRatio)); // start display mode change only if ratio is neither 0 nor 100
}
bool monoMaintainDispSwitchState(void) {
bool change = false;
switch(mDispSwitchState) {
case d_POWER_TEXT:
if (mDispSwitchTime.isTimeout()) {
mDispSwitchState = d_POWER_GRAPH;
mDispSwitchTime.startTimeMonitor(150 * mGraphRatio); // mGraphRatio: 0-100 Gesamtperiode 15000 ms
change = true;
}
break;
case d_POWER_GRAPH:
if (mDispSwitchTime.isTimeout()) {
mDispSwitchState = d_POWER_TEXT;
mDispSwitchTime.startTimeMonitor(150 * (100 - mGraphRatio));
change = true;
}
break;
}
return change;
}
void initPowerGraph(uint8_t width, uint8_t height) {
mPgWidth = width;
mPgHeight = height;
mPgData = new float[mPgWidth];
//memset(mPgData, 0, mPgWidth);
resetPowerGraph();
/*
Inverter<> *iv;
mPgMaxAvailPower = 0;
uint8_t nInv = mSys->getNumInverters();
for (uint8_t i = 0; i < nInv; i++) {
iv = mSys->getInverterByPos(i);
if (iv == NULL)
continue;
for (uint8_t ch = 0; ch < 6; ch++) {
mPgMaxAvailPower += iv->config->chMaxPwr[ch];
} }
} return change;
DBGPRINTLN("max. Power = " + String(mPgMaxAvailPower));*/
}
void resetPowerGraph() {
if (mPgData != nullptr) {
mPgMaxPwr = 0.0;
mPgLastPos = 0;
for (uint8_t i = 0; i < mPgWidth; i++)
mPgData[i] = 0.0;
} }
}
uint8_t sss2pgpos(uint seconds_since_start) { void initPowerGraph(uint8_t width, uint8_t height) {
return(seconds_since_start * (mPgWidth - 1) / (mDisplayData->pGraphEndTime - mDisplayData->pGraphStartTime)); mPgWidth = width;
} mPgHeight = height;
mPgData = new float[mPgWidth];
void calcPowerGraphValues() { //memset(mPgData, 0, mPgWidth);
mPgPeriod = mDisplayData->pGraphEndTime - mDisplayData->pGraphStartTime; // length of power graph for scaling of x-axis
uint32_t oldTimeOfDay = mPgTimeOfDay;
mPgTimeOfDay = (mDisplayData->utcTs > mDisplayData->pGraphStartTime) ? mDisplayData->utcTs - mDisplayData->pGraphStartTime : 0; // current time of day with respect to current sunrise time
if (oldTimeOfDay > mPgTimeOfDay) // new day -> reset old data
resetPowerGraph(); resetPowerGraph();
mPgLastPos = std::min((uint8_t) (mPgTimeOfDay * (mPgWidth - 1) / mPgPeriod), (uint8_t) (mPgWidth - 1)); // current datapoint based on currenct time of day /*
} Inverter<> *iv;
mPgMaxAvailPower = 0;
void addPowerGraphEntry(float val) { uint8_t nInv = mSys->getNumInverters();
if (mDisplayData->utcTs > 0) { // precondition: utc time available for (uint8_t i = 0; i < nInv; i++) {
calcPowerGraphValues(); iv = mSys->getInverterByPos(i);
//mPgData[mPgLastPos] = std::max(mPgData[mPgLastPos], (uint8_t) (val * 255.0 / mPgMaxAvailPower)); // normalizing of data to 0-255 if (iv == NULL)
mPgData[mPgLastPos] = std::max(mPgData[mPgLastPos], val); continue;
mPgMaxPwr = std::max(mPgMaxPwr, val); // max value of stored data for scaling of y-axis for (uint8_t ch = 0; ch < 6; ch++) {
} mPgMaxAvailPower += iv->config->chMaxPwr[ch];
} }
}
uint8_t getPowerGraphXpos(uint8_t p) { // DBGPRINTLN("max. Power = " + String(mPgMaxAvailPower));*/
if ((p <= mPgLastPos) && (mPgLastPos > 0))
return((p * (mPgWidth - 1)) / mPgLastPos); // scaling of x-axis
else
return(0);
}
uint8_t getPowerGraphYpos(uint8_t p) {
if (p < mPgWidth)
//return(((uint32_t) mPgData[p] * (uint32_t) mPgMaxAvailPower) * (uint32_t) mPgHeight / mPgMaxPwr / 255); // scaling of normalized data (0-255) to graph height
return((mPgData[p] * (uint32_t) mPgHeight / mPgMaxPwr)); // scaling of data to graph height
else
return(0);
}
void plotPowerGraph(uint8_t xoff, uint8_t yoff) {
// draw axes
mDisplay->drawLine(xoff, yoff, xoff, yoff - mPgHeight); // vertical axis
mDisplay->drawLine(xoff, yoff, xoff + mPgWidth, yoff); // horizontal axis
// draw X scale
tmElements_t tm;
breakTime(mDisplayData->pGraphEndTime, tm);
uint8_t endHourPg = tm.Hour;
breakTime(mDisplayData->utcTs, tm);
uint8_t endHour = std::min(endHourPg, tm.Hour);
breakTime(mDisplayData->pGraphStartTime, tm);
tm.Hour += 1;
tm.Minute = 0;
tm.Second = 0;
for (; tm.Hour <= endHour; tm.Hour++) {
uint8_t x_pos_screen = getPowerGraphXpos(sss2pgpos((uint32_t) makeTime(tm) - mDisplayData->pGraphStartTime)); // scale horizontal axis
mDisplay->drawPixel(xoff + x_pos_screen, yoff - 1);
} }
// draw Y scale void resetPowerGraph() {
uint16_t scale_y = 10; if (mPgData != nullptr) {
uint32_t maxpwr_int = static_cast<uint8_t>(std::round(mPgMaxPwr)); mPgMaxPwr = 0.0;
if (maxpwr_int > 100) mPgLastPos = 0;
scale_y = 100; for (uint8_t i = 0; i < mPgWidth; i++)
for (uint32_t i = scale_y; i <= maxpwr_int; i += scale_y) { mPgData[i] = 0.0;
uint8_t ypos = yoff - static_cast<uint8_t>(std::round(i * (float) mPgHeight / mPgMaxPwr)); // scale vertical axis }
mDisplay->drawPixel(xoff + 1, ypos);
} }
// draw curve uint8_t sss2pgpos(uint seconds_since_start) {
for (uint8_t i = 1; i <= mPgLastPos; i++) { return(seconds_since_start * (mPgWidth - 1) / (mDisplayData->pGraphEndTime - mDisplayData->pGraphStartTime));
mDisplay->drawLine(xoff + getPowerGraphXpos(i - 1), yoff - getPowerGraphYpos(i - 1),
xoff + getPowerGraphXpos(i), yoff - getPowerGraphYpos(i));
} }
// print max power value void calcPowerGraphValues() {
mDisplay->setFont(u8g2_font_4x6_tr); mPgPeriod = mDisplayData->pGraphEndTime - mDisplayData->pGraphStartTime; // length of power graph for scaling of x-axis
snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%dW", static_cast<uint16_t>(std::round(mPgMaxPwr))); uint32_t oldTimeOfDay = mPgTimeOfDay;
mDisplay->drawStr(xoff + 3, yoff - mPgHeight + 5, mFmtText); mPgTimeOfDay = (mDisplayData->utcTs > mDisplayData->pGraphStartTime) ? mDisplayData->utcTs - mDisplayData->pGraphStartTime : 0; // current time of day with respect to current sunrise time
} if (oldTimeOfDay > mPgTimeOfDay) // new day -> reset old data
resetPowerGraph();
mPgLastPos = std::min((uint8_t) (mPgTimeOfDay * (mPgWidth - 1) / mPgPeriod), (uint8_t) (mPgWidth - 1)); // current datapoint based on currenct time of day
}
// pixelshift screensaver with wipe effect void addPowerGraphEntry(float val) {
void calcPixelShift(int range) { if (mDisplayData->utcTs > 0) { // precondition: utc time available
int8_t mod = (millis() / 10000) % ((range >> 1) << 2); calcPowerGraphValues();
mPixelshift = mScreenSaver == 1 ? ((mod < range) ? mod - (range >> 1) : -(mod - range - (range >> 1) + 1)) : 0; //mPgData[mPgLastPos] = std::max(mPgData[mPgLastPos], (uint8_t) (val * 255.0 / mPgMaxAvailPower)); // normalizing of data to 0-255
} mPgData[mPgLastPos] = std::max(mPgData[mPgLastPos], val);
mPgMaxPwr = std::max(mPgMaxPwr, val); // max value of stored data for scaling of y-axis
}
}
uint8_t getPowerGraphXpos(uint8_t p) { //
if ((p <= mPgLastPos) && (mPgLastPos > 0))
return((p * (mPgWidth - 1)) / mPgLastPos); // scaling of x-axis
else
return(0);
}
uint8_t getPowerGraphYpos(uint8_t p) {
if (p < mPgWidth)
//return(((uint32_t) mPgData[p] * (uint32_t) mPgMaxAvailPower) * (uint32_t) mPgHeight / mPgMaxPwr / 255); // scaling of normalized data (0-255) to graph height
return((mPgData[p] * (uint32_t) mPgHeight / mPgMaxPwr)); // scaling of data to graph height
else
return(0);
}
void plotPowerGraph(uint8_t xoff, uint8_t yoff) {
// draw axes
mDisplay->drawLine(xoff, yoff, xoff, yoff - mPgHeight); // vertical axis
mDisplay->drawLine(xoff, yoff, xoff + mPgWidth, yoff); // horizontal axis
// draw X scale
tmElements_t tm;
breakTime(mDisplayData->pGraphEndTime, tm);
uint8_t endHourPg = tm.Hour;
breakTime(mDisplayData->utcTs, tm);
uint8_t endHour = std::min(endHourPg, tm.Hour);
breakTime(mDisplayData->pGraphStartTime, tm);
tm.Hour += 1;
tm.Minute = 0;
tm.Second = 0;
for (; tm.Hour <= endHour; tm.Hour++) {
uint8_t x_pos_screen = getPowerGraphXpos(sss2pgpos((uint32_t) makeTime(tm) - mDisplayData->pGraphStartTime)); // scale horizontal axis
mDisplay->drawPixel(xoff + x_pos_screen, yoff - 1);
}
// draw Y scale
uint16_t scale_y = 10;
uint32_t maxpwr_int = static_cast<uint8_t>(std::round(mPgMaxPwr));
if (maxpwr_int > 100)
scale_y = 100;
for (uint32_t i = scale_y; i <= maxpwr_int; i += scale_y) {
uint8_t ypos = yoff - static_cast<uint8_t>(std::round(i * (float) mPgHeight / mPgMaxPwr)); // scale vertical axis
mDisplay->drawPixel(xoff + 1, ypos);
}
// draw curve
for (uint8_t i = 1; i <= mPgLastPos; i++) {
mDisplay->drawLine(xoff + getPowerGraphXpos(i - 1), yoff - getPowerGraphYpos(i - 1),
xoff + getPowerGraphXpos(i), yoff - getPowerGraphYpos(i));
}
// print max power value
mDisplay->setFont(u8g2_font_4x6_tr);
snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%dW", static_cast<uint16_t>(std::round(mPgMaxPwr)));
mDisplay->drawStr(xoff + 3, yoff - mPgHeight + 5, mFmtText);
}
// pixelshift screensaver with wipe effect
void calcPixelShift(int range) {
int8_t mod = (millis() / 10000) % ((range >> 1) << 2);
mPixelshift = mScreenSaver == 1 ? ((mod < range) ? mod - (range >> 1) : -(mod - range - (range >> 1) + 1)) : 0;
}
}; };
/* adapted 5x8 Font for low-res displays with symbols /* adapted 5x8 Font for low-res displays with symbols
Symbols: Symbols:
\x80 ... antenna \x80 ... antenna
\x81 ... WiFi \x81 ... WiFi
\x82 ... suncurve \x82 ... suncurve
\x83 ... sum/sigma \x83 ... sum/sigma
\x84 ... antenna crossed \x84 ... antenna crossed
\x85 ... WiFi crossed \x85 ... WiFi crossed
\x86 ... sun \x86 ... sun
\x87 ... moon \x87 ... moon
\x88 ... calendar/day \x88 ... calendar/day
\x89 ... MQTT */ \x89 ... MQTT */
const uint8_t u8g2_font_5x8_symbols_ahoy[1049] U8G2_FONT_SECTION("u8g2_font_5x8_symbols_ahoy") = const uint8_t u8g2_font_5x8_symbols_ahoy[1049] U8G2_FONT_SECTION("u8g2_font_5x8_symbols_ahoy") =
"j\0\3\2\4\4\3\4\5\10\10\0\377\6\377\6\0\1\61\2b\4\0 \5\0\304\11!\7a\306" "j\0\3\2\4\4\3\4\5\10\10\0\377\6\377\6\0\1\61\2b\4\0 \5\0\304\11!\7a\306"
"\212!\11\42\7\63\335\212\304\22#\16u\304\232R\222\14JePJI\2$\14u\304\252l\251m" "\212!\11\42\7\63\335\212\304\22#\16u\304\232R\222\14JePJI\2$\14u\304\252l\251m"

View file

@ -176,7 +176,7 @@ class DisplayMono128X64 : public DisplayMono {
printText(mFmtText, l_YieldTotal, 0xff); printText(mFmtText, l_YieldTotal, 0xff);
} }
if (mDispSwitchState == d_POWER_GRAPH) { if (mDispSwitchState == DispSwitchState::GRAPH) {
// plot power graph // plot power graph
plotPowerGraph((mDispWidth - mPgWidth) / 2 + mPixelshift, mLineYOffsets[graph_last_line] - 1); plotPowerGraph((mDispWidth - mPgWidth) / 2 + mPixelshift, mLineYOffsets[graph_last_line] - 1);
} }
@ -274,6 +274,6 @@ class DisplayMono128X64 : public DisplayMono {
} }
bool showLine(uint8_t line) { bool showLine(uint8_t line) {
return ((mDispSwitchState == d_POWER_TEXT) || ((line < graph_first_line) || (line > graph_last_line))); return ((mDispSwitchState == DispSwitchState::TEXT) || ((line < graph_first_line) || (line > graph_last_line)));
} }
}; };

View file

@ -137,7 +137,7 @@ class DisplayMono84X48 : public DisplayMono {
printText(mFmtText, l_YieldTotal, 0xff); printText(mFmtText, l_YieldTotal, 0xff);
} }
if (mDispSwitchState == d_POWER_GRAPH) { if (mDispSwitchState == DispSwitchState::GRAPH) {
// plot power graph // plot power graph
plotPowerGraph(8, mLineYOffsets[graph_last_line] - 1); plotPowerGraph(8, mLineYOffsets[graph_last_line] - 1);
} }
@ -227,7 +227,7 @@ class DisplayMono84X48 : public DisplayMono {
} }
bool showLine(uint8_t line) { bool showLine(uint8_t line) {
return ((mDispSwitchState == d_POWER_TEXT) || ((line < graph_first_line) || (line > graph_last_line))); return ((mDispSwitchState == DispSwitchState::TEXT) || ((line < graph_first_line) || (line > graph_last_line)));
} }
}; };