diff --git a/src/config/config.h b/src/config/config.h index 5b17ca9d..4fb78107 100644 --- a/src/config/config.h +++ b/src/config/config.h @@ -98,6 +98,9 @@ #ifndef DEF_CMT_IRQ #define DEF_CMT_IRQ 34 #endif + #ifndef DEF_MOTION_SENSOR_PIN + #define DEF_MOTION_SENSOR_PIN DEF_PIN_OFF + #endif #else #ifndef DEF_NRF_CS_PIN #define DEF_NRF_CS_PIN 15 @@ -119,6 +122,9 @@ #ifndef DEF_NRF_SCLK_PIN #define DEF_NRF_SCLK_PIN 14 #endif + #ifndef DEF_MOTION_SENSOR_PIN + #define DEF_MOTION_SENSOR_PIN A0 + #endif #endif #ifndef DEF_LED0 #define DEF_LED0 DEF_PIN_OFF diff --git a/src/config/settings.h b/src/config/settings.h index 46f0a565..058d2546 100644 --- a/src/config/settings.h +++ b/src/config/settings.h @@ -164,7 +164,7 @@ typedef struct { typedef struct { uint8_t type; bool pwrSaveAtIvOffline; - bool pxShift; + uint8_t screenSaver; uint8_t rot; //uint16_t wakeUp; //uint16_t sleepAt; @@ -175,6 +175,7 @@ typedef struct { uint8_t disp_reset; uint8_t disp_busy; uint8_t disp_dc; + uint8_t pirPin; } display_t; typedef struct { @@ -458,7 +459,7 @@ class settings { mCfg.plugin.display.pwrSaveAtIvOffline = false; mCfg.plugin.display.contrast = 60; - mCfg.plugin.display.pxShift = true; + mCfg.plugin.display.screenSaver = 1; // default: 1 .. pixelshift for OLED for downward compatibility mCfg.plugin.display.rot = 0; mCfg.plugin.display.disp_data = DEF_PIN_OFF; // SDA mCfg.plugin.display.disp_clk = DEF_PIN_OFF; // SCL @@ -466,6 +467,7 @@ class settings { mCfg.plugin.display.disp_reset = DEF_PIN_OFF; mCfg.plugin.display.disp_busy = DEF_PIN_OFF; mCfg.plugin.display.disp_dc = DEF_PIN_OFF; + mCfg.plugin.display.pirPin = DEF_MOTION_SENSOR_PIN; } void loadAddedDefaults() { @@ -672,7 +674,7 @@ class settings { JsonObject disp = obj.createNestedObject("disp"); disp[F("type")] = mCfg.plugin.display.type; disp[F("pwrSafe")] = (bool)mCfg.plugin.display.pwrSaveAtIvOffline; - disp[F("pxShift")] = (bool)mCfg.plugin.display.pxShift; + disp[F("screenSaver")] = mCfg.plugin.display.screenSaver; disp[F("rotation")] = mCfg.plugin.display.rot; //disp[F("wake")] = mCfg.plugin.display.wakeUp; //disp[F("sleep")] = mCfg.plugin.display.sleepAt; @@ -683,11 +685,12 @@ class settings { disp[F("reset")] = mCfg.plugin.display.disp_reset; disp[F("busy")] = mCfg.plugin.display.disp_busy; disp[F("dc")] = mCfg.plugin.display.disp_dc; + disp[F("pirPin")] = mCfg.plugin.display.pirPin; } else { JsonObject disp = obj["disp"]; getVal<uint8_t>(disp, F("type"), &mCfg.plugin.display.type); getVal<bool>(disp, F("pwrSafe"), &mCfg.plugin.display.pwrSaveAtIvOffline); - getVal<bool>(disp, F("pxShift"), &mCfg.plugin.display.pxShift); + getVal<uint8_t>(disp, F("screenSaver"), &mCfg.plugin.display.screenSaver); getVal<uint8_t>(disp, F("rotation"), &mCfg.plugin.display.rot); //mCfg.plugin.display.wakeUp = disp[F("wake")]; //mCfg.plugin.display.sleepAt = disp[F("sleep")]; @@ -698,6 +701,7 @@ class settings { getVal<uint8_t>(disp, F("reset"), &mCfg.plugin.display.disp_reset); getVal<uint8_t>(disp, F("busy"), &mCfg.plugin.display.disp_busy); getVal<uint8_t>(disp, F("dc"), &mCfg.plugin.display.disp_dc); + getVal<uint8_t>(disp, F("pirPin"), &mCfg.plugin.display.pirPin); } } diff --git a/src/plugins/Display/Display.h b/src/plugins/Display/Display.h index 932c1a8f..c6d4dc03 100644 --- a/src/plugins/Display/Display.h +++ b/src/plugins/Display/Display.h @@ -53,9 +53,20 @@ class Display { default: mMono = NULL; break; } if(mMono) { - mMono->config(mCfg->pwrSaveAtIvOffline, mCfg->pxShift, mCfg->contrast); + mMono->config(mCfg->pwrSaveAtIvOffline, mCfg->screenSaver, mCfg->contrast); mMono->init(mCfg->type, mCfg->rot, mCfg->disp_cs, mCfg->disp_dc, 0xff, mCfg->disp_clk, mCfg->disp_data, &mDisplayData); } + + // setup PIR pin for motion sensor +#ifdef ESP32 + if ((mCfg->screenSaver == 2) && (mCfg->pirPin != DEF_PIN_OFF)) + pinMode(mCfg->pirPin, INPUT); +#endif +#ifdef ESP8266 + if ((mCfg->screenSaver == 2) && (mCfg->pirPin != DEF_PIN_OFF) && (mCfg->pirPin != A0)) + pinMode(mCfg->pirPin, INPUT); +#endif + } void payloadEventListener(uint8_t cmd) { @@ -64,19 +75,19 @@ class Display { void tickerSecond() { if (mMono != NULL) - mMono->loop(mCfg->contrast); + mMono->loop(mCfg->contrast, motionSensorActive()); - if (mNewPayload || (((++mLoopCnt) % 10) == 0)) { + if (mNewPayload || (((++mLoopCnt) % 5) == 0)) { + DataScreen(); mNewPayload = false; mLoopCnt = 0; - DataScreen(); } #if defined(ESP32) mEpaper.tickerSecond(); #endif } - private: + private: void DataScreen() { if (mCfg->type == 0) return; @@ -87,6 +98,7 @@ class Display { uint8_t nrprod = 0; uint8_t nrsleep = 0; + int8_t minQAllInv = 4; Inverter<> *iv; record_t<> *rec; @@ -97,6 +109,15 @@ class Display { if (iv == NULL) continue; + int8_t maxQInv = -6; + for(uint8_t ch = 0; ch < RF_MAX_CHANNEL_ID; ch++) { + int8_t q = iv->txRfQuality[ch]; + if (q > maxQInv) + maxQInv = q; + } + if (maxQInv < minQAllInv) + minQAllInv = maxQInv; + rec = iv->getRecordStruct(RealTimeRunData_Debug); if (iv->isProducing()) @@ -123,7 +144,7 @@ class Display { mDisplayData.RadioSymbol = mHmRadio->isChipConnected(); mDisplayData.WifiSymbol = (WiFi.status() == WL_CONNECTED); mDisplayData.MQTTSymbol = mApp->getMqttIsConnected(); - mDisplayData.RadioRSSI = (0 < mDisplayData.nrProducing) ? 0 : SCHAR_MIN; // Workaround as NRF24 has no RSSI. Could be approximated by transmisson error heuristic in the future + mDisplayData.RadioRSSI = (0 < mDisplayData.nrProducing) ? ivQuality2RadioRSSI(minQAllInv) : SCHAR_MIN; // Workaround as NRF24 has no RSSI. Approximation by quality levels from heuristic function mDisplayData.WifiRSSI = (WiFi.status() == WL_CONNECTED) ? WiFi.RSSI() : SCHAR_MIN; mDisplayData.ipAddress = WiFi.localIP(); time_t utc= mApp->getTimestamp(); @@ -148,6 +169,41 @@ class Display { #endif } + bool motionSensorActive() { + if ((mCfg->screenSaver == 2) && (mCfg->pirPin != DEF_PIN_OFF)) { +#if defined(ESP8266) + if (mCfg->pirPin == A0) + return((analogRead(A0) >= 512)); + else + return(digitalRead(mCfg->pirPin)); +#elif defined(ESP32) + return(digitalRead(mCfg->pirPin)); +#endif + } + else + return(false); + } + + // approximate RSSI in dB by invQuality levels from heuristic function (very unscientific but better than nothing :-) ) + int8_t ivQuality2RadioRSSI(int8_t invQuality) { + int8_t pseudoRSSIdB; + switch(invQuality) { + case 4: pseudoRSSIdB = -55; break; + case 3: + case 2: + case 1: pseudoRSSIdB = -65; break; + case 0: + case -1: + case -2: pseudoRSSIdB = -75; break; + case -3: + case -4: + case -5: pseudoRSSIdB = -85; break; + case -6: + default: pseudoRSSIdB = -95; break; + } + return (pseudoRSSIdB); + } + // private member variables IApp *mApp; DisplayData mDisplayData; diff --git a/src/plugins/Display/Display_Mono.h b/src/plugins/Display/Display_Mono.h index 49fd18ec..a360f796 100644 --- a/src/plugins/Display/Display_Mono.h +++ b/src/plugins/Display/Display_Mono.h @@ -18,16 +18,49 @@ #endif #include "../../utils/helper.h" #include "Display_data.h" +#include "../../utils/dbg.h" class DisplayMono { public: 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 config(bool enPowerSave, bool enScreenSaver, uint8_t lum) = 0; - virtual void loop(uint8_t lum) = 0; + virtual void config(bool enPowerSave, uint8_t screenSaver, uint8_t lum) = 0; virtual void disp(void) = 0; + // Common loop function, manages display on/off functions for powersave and screensaver with motionsensor + // can be overridden by subclasses + virtual void loop(uint8_t lum, bool motion) { + + bool dispConditions = (!mEnPowerSave || (mDisplayData->nrProducing > 0)) && + ((mScreenSaver != 2) || motion); // screensaver 2 .. motionsensor + + if (mDisplayActive) { + if (!dispConditions) { + if ((millis() - mStarttime) > DISP_DEFAULT_TIMEOUT * 1000ul) { // switch display off after timeout + mDisplayActive = false; + mDisplay->setPowerSave(true); + DBGPRINTLN("**** Display off ****"); + } + } + else + mStarttime = millis(); // keep display on + } + else { + if (dispConditions) { + mDisplayActive = true; // switch display on + mStarttime = millis(); + mDisplay->setPowerSave(false); + DBGPRINTLN("**** Display on ****"); + } + } + + if(mLuminance != lum) { + mLuminance = lum; + mDisplay->setContrast(mLuminance); + } + } + protected: U8G2* mDisplay; DisplayData *mDisplayData; @@ -36,17 +69,18 @@ class DisplayMono { uint16_t mDispWidth; uint16_t mDispHeight; - bool mEnPowerSave, mEnScreenSaver; + bool mEnPowerSave; + uint8_t mScreenSaver = 1; // 0 .. off; 1 .. pixelShift; 2 .. motionsensor uint8_t mLuminance; uint8_t mLoopCnt; uint8_t mLineXOffsets[5] = {}; uint8_t mLineYOffsets[5] = {}; - uint16_t mDispY; - uint8_t mExtra; - uint16_t mTimeout; + int8_t mPixelshift=0; + uint32_t mStarttime = millis(); + bool mDisplayActive = true; // always start with display on char mFmtText[DISP_FMT_TEXT_LEN]; // Common initialization function to be called by subclasses @@ -55,11 +89,17 @@ class DisplayMono { 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(); } + + 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 @@ -109,7 +149,6 @@ const uint8_t u8g2_font_5x8_symbols_ahoy[1052] U8G2_FONT_SECTION("u8g2_font_5x8_ "\206\20\210<\254\342\20]\302(L\246C\30E\0\207\15wD\334X\25\267\341\20\15\21\0\210\16w" "<\214\203RQ\25I\212\324a\20\211\15f\304\213)\213\244,\222\222\245\0\0\0\0"; - const uint8_t u8g2_font_ncenB08_symbols8_ahoy[173] U8G2_FONT_SECTION("u8g2_font_ncenB08_symbols8_ahoy") = "\13\0\3\2\4\4\1\2\5\10\11\0\0\10\0\10\0\0\0\0\0\0\224A\14\207\305\70H\321\222H" "k\334\6B\20\230\305\32\262\60\211\244\266\60T\243\34\326\0C\20\210\305S\243\60\312\302(\214\302(" @@ -118,12 +157,11 @@ const uint8_t u8g2_font_ncenB08_symbols8_ahoy[173] U8G2_FONT_SECTION("u8g2_font_ "Q\4H\14w\307\215Uq\33\16\321\20\1I\21\227\305\311\222aP\245H\221\244H\212\324a\20J" "\5\0\275\0K\5\0\315\0\0\0\0"; -const uint8_t u8g2_font_ncenB10_symbols10_ahoy[207] U8G2_FONT_SECTION("u8g2_font_ncenB10_symbols10_ahoy") = - "\13\0\3\2\4\4\2\2\5\13\13\0\0\13\0\13\0\0\0\0\0\0\266A\15\267\212q\220\42\251\322" +const uint8_t u8g2_font_ncenB10_symbols10_ahoy[220] U8G2_FONT_SECTION("u8g2_font_ncenB10_symbols10_ahoy") = + "\13\0\3\2\4\4\2\2\5\13\13\0\0\13\0\13\0\0\0\0\0\0\303A\15\267\212q\220\42\251\322" "\266\306\275\1B\20\230\236\65da\22Ima\250F\71\254\1C\23\272\272\251\3Q\32\366Q\212\243" "\70\212\243\70\311\221\0D\20\271\252\361\242F:\242#: {\36\16\1E\22\267\212\361\222\14I\242" "\14\332\232\216[RJ\232\12F\25\250\233\221\14I\61I\206$\252%J\250Fa\224%J\71G\30" "\273\312W\316r`T\262DJ\303\64L#%K\304\35\310\342,\3H\27\272\272\217\344P\16\351\210" "\16\354\300<\244C\70,\303 \16!\0I\24\271\252\241\34\336\1-\223\64-\323\62-\323\62\35x" - "\10J\5\0\232\1K\5\0\232\1\0\0\0"; - + "\10J\22\210\232\61Hi\64Di\64DI\226$KeiK\5\0\232\1\0\0\0"; diff --git a/src/plugins/Display/Display_Mono_128X32.h b/src/plugins/Display/Display_Mono_128X32.h index 7012604b..fa0cacdf 100644 --- a/src/plugins/Display/Display_Mono_128X32.h +++ b/src/plugins/Display/Display_Mono_128X32.h @@ -9,17 +9,12 @@ class DisplayMono128X32 : public DisplayMono { public: DisplayMono128X32() : DisplayMono() { - mEnPowerSave = true; - mEnScreenSaver = true; - mLuminance = 60; mExtra = 0; - mDispY = 0; - mTimeout = DISP_DEFAULT_TIMEOUT; // interval at which to power save (milliseconds) } - void config(bool enPowerSave, bool enScreenSaver, uint8_t lum) { + void config(bool enPowerSave, uint8_t screenSaver, uint8_t lum) { mEnPowerSave = enPowerSave; - mEnScreenSaver = enScreenSaver; + mScreenSaver = screenSaver; mLuminance = lum; } @@ -33,28 +28,13 @@ class DisplayMono128X32 : public DisplayMono { mDisplay->sendBuffer(); } - void loop(uint8_t lum) { - if (mEnPowerSave) { - if (mTimeout != 0) - mTimeout--; - } - - if(mLuminance != lum) { - mLuminance = lum; - mDisplay->setContrast(mLuminance); - } - } - void disp(void) { mDisplay->clearBuffer(); - // set Contrast of the Display to raise the lifetime - if (3 != mType) - mDisplay->setContrast(mLuminance); + // calculate current pixelshift for pixelshift screensaver + calcPixelShift(pixelShiftRange); if ((mDisplayData->totalPower > 0) && (mDisplayData->nrProducing > 0)) { - mTimeout = DISP_DEFAULT_TIMEOUT; - mDisplay->setPowerSave(false); if (mDisplayData->totalPower > 999) snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%2.2f kW", (mDisplayData->totalPower / 1000)); else @@ -63,9 +43,6 @@ class DisplayMono128X32 : public DisplayMono { printText(mFmtText, 0); } else { printText("offline", 0); - // check if it's time to enter power saving mode - if (mTimeout == 0) - mDisplay->setPowerSave(mEnPowerSave); } snprintf(mFmtText, DISP_FMT_TEXT_LEN, "today: %4.0f Wh", mDisplayData->totalYieldDay); @@ -85,11 +62,12 @@ class DisplayMono128X32 : public DisplayMono { mDisplay->sendBuffer(); - mDispY = 0; mExtra++; } private: + const uint8_t pixelShiftRange = 7; // number of pixels to shift from left to right (centered -> must be odd!) + void calcLinePositions() { uint8_t yOff[] = {0, 0}; for (uint8_t i = 0; i < 4; i++) { @@ -131,7 +109,7 @@ class DisplayMono128X32 : public DisplayMono { void printText(const char *text, uint8_t line) { setFont(line); - uint8_t dispX = mLineXOffsets[line] + ((mEnScreenSaver) ? (mExtra % 7) : 0); + uint8_t dispX = mLineXOffsets[line] + pixelShiftRange / 2 + mPixelshift; if (isTwoRowLine(line)) { String stringText = String(text); diff --git a/src/plugins/Display/Display_Mono_128X64.h b/src/plugins/Display/Display_Mono_128X64.h index 4ea85d50..78a18db6 100644 --- a/src/plugins/Display/Display_Mono_128X64.h +++ b/src/plugins/Display/Display_Mono_128X64.h @@ -9,16 +9,12 @@ class DisplayMono128X64 : public DisplayMono { public: DisplayMono128X64() : DisplayMono() { - mEnPowerSave = true; - mEnScreenSaver = true; - mLuminance = 60; - mDispY = 0; - mTimeout = DISP_DEFAULT_TIMEOUT; // interval at which to power save (milliseconds) + mExtra = 0; } - void config(bool enPowerSave, bool enScreenSaver, uint8_t lum) { + void config(bool enPowerSave, uint8_t screenSaver, uint8_t lum) { mEnPowerSave = enPowerSave; - mEnScreenSaver = enScreenSaver; + mScreenSaver = screenSaver; mLuminance = lum; } @@ -37,95 +33,188 @@ class DisplayMono128X64 : public DisplayMono { break; } calcLinePositions(); - printText("Ahoy!", 0, 35); - printText("ahoydtu.de", 2, 20); - printText(mDisplayData->version, 3, 46); - mDisplay->sendBuffer(); - } - - void loop(uint8_t lum) { - if (mEnPowerSave) { - if (mTimeout != 0) - mTimeout--; - } - if(mLuminance != lum) { - mLuminance = lum; - mDisplay->setContrast(mLuminance); - } + printText("Ahoy!", l_Ahoy, 0xff); + printText("ahoydtu.de", l_Website, 0xff); + printText(mDisplayData->version, l_Version, 0xff); + mDisplay->sendBuffer(); } void disp(void) { - mDisplay->clearBuffer(); - - // set Contrast of the Display to raise the lifetime - mDisplay->setContrast(mLuminance); + uint8_t pos, sun_pos, moon_pos; - if ((mDisplayData->totalPower > 0) && (mDisplayData->nrProducing > 0)) { - mTimeout = DISP_DEFAULT_TIMEOUT; - mDisplay->setPowerSave(false); + mDisplay->clearBuffer(); + // Layout-Test + /* + mDisplayData->nrSleeping = 10; + mDisplayData->nrProducing = 10; + mDisplayData->totalPower = 99990; + mDisplayData->totalYieldDay = 8888; + mDisplayData->totalYieldTotal = 9999; + mDisplay->drawPixel(0, 0); + mDisplay->drawPixel(mDispWidth-1, 0); + mDisplay->drawPixel(0, mDispHeight-1); + mDisplay->drawPixel(mDispWidth-1, mDispHeight-1); + */ + + // calculate current pixelshift for pixelshift screensaver + calcPixelShift(pixelShiftRange); + + // print total power + if (mDisplayData->nrProducing > 0) { if (mDisplayData->totalPower > 999) - snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%2.2f kW", (mDisplayData->totalPower / 1000)); + snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%.2f kW", (mDisplayData->totalPower / 1000)); else - snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%3.0f W", mDisplayData->totalPower); - - printText(mFmtText, 0); + snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%.0f W", mDisplayData->totalPower); + printText(mFmtText, l_TotalPower, 0xff); } else { - printText("offline", 0, 25); - // check if it's time to enter power saving mode - if (mTimeout == 0) - mDisplay->setPowerSave(mEnPowerSave); + printText("offline", l_TotalPower, 0xff); } - snprintf(mFmtText, DISP_FMT_TEXT_LEN, "today: %4.0f Wh", mDisplayData->totalYieldDay); - printText(mFmtText, 1); + // print Date and time + if (0 != mDisplayData->utcTs) + printText(ah::getDateTimeStrShort(gTimezone.toLocal(mDisplayData->utcTs)).c_str(), l_Time, 0xff); - snprintf(mFmtText, DISP_FMT_TEXT_LEN, "total: %.1f kWh", mDisplayData->totalYieldTotal); - printText(mFmtText, 2); + // dynamic status bar, alternatively: + // print ip address + if (!(mExtra % 5) && (mDisplayData->ipAddress)) { + snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%s", (mDisplayData->ipAddress).toString().c_str()); + printText(mFmtText, l_Status, 0xff); + } + // print status of inverters + else { + sun_pos = -1; + moon_pos = -1; + setLineFont(l_Status); + if (0 == mDisplayData->nrSleeping + mDisplayData->nrProducing) + snprintf(mFmtText, DISP_FMT_TEXT_LEN, "no inverter"); + else if (0 == mDisplayData->nrSleeping) { + snprintf(mFmtText, DISP_FMT_TEXT_LEN, " "); + sun_pos = 0; + } + else if (0 == mDisplayData->nrProducing) { + snprintf(mFmtText, DISP_FMT_TEXT_LEN, " "); + moon_pos = 0; + } + else { + snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%2d", mDisplayData->nrProducing); + sun_pos = mDisplay->getStrWidth(mFmtText) + 1; + snprintf(mFmtText+2, DISP_FMT_TEXT_LEN, " %2d", mDisplayData->nrSleeping); + moon_pos = mDisplay->getStrWidth(mFmtText) + 1; + snprintf(mFmtText+7, DISP_FMT_TEXT_LEN, " "); + } + printText(mFmtText, l_Status, 0xff); + + pos = (mDispWidth - mDisplay->getStrWidth(mFmtText)) / 2; + mDisplay->setFont(u8g2_font_ncenB08_symbols8_ahoy); + if (sun_pos!=-1) + mDisplay->drawStr(pos + sun_pos + mPixelshift, mLineYOffsets[l_Status], "G"); // sun + if (moon_pos!=-1) + mDisplay->drawStr(pos + moon_pos + mPixelshift, mLineYOffsets[l_Status], "H"); // moon + } + + // print yields + mDisplay->setFont(u8g2_font_ncenB10_symbols10_ahoy); + mDisplay->drawStr(17 + mPixelshift, mLineYOffsets[l_YieldDay], "I"); // day + mDisplay->drawStr(17 + mPixelshift, mLineYOffsets[l_YieldTotal], "D"); // total + snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%7.0f Wh", mDisplayData->totalYieldDay); + printText(mFmtText, l_YieldDay, 25); + snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%7.1f kWh", mDisplayData->totalYieldTotal); + printText(mFmtText, l_YieldTotal, 25); + + // draw dynamic RSSI bars + int xoffs; + if (mScreenSaver == 1) // shrink screenwidth for pixelshift screensaver + xoffs = pixelShiftRange/2; + else + xoffs = 0; + int rssi_bar_height = 9; + for (int i = 0; i < 4; i++) { + int radio_rssi_threshold = -60 - i * 10; + int wifi_rssi_threshold = -60 - i * 10; + if (mDisplayData->RadioRSSI > radio_rssi_threshold) + mDisplay->drawBox(xoffs + mPixelshift, 8 + (rssi_bar_height + 1) * i, 4 - i, rssi_bar_height); + if (mDisplayData->WifiRSSI > wifi_rssi_threshold) + mDisplay->drawBox(mDispWidth - 4 - xoffs + mPixelshift + i, 8 + (rssi_bar_height + 1) * i, 4 - i, rssi_bar_height); + } + // draw dynamic antenna and WiFi symbols + mDisplay->setFont(u8g2_font_ncenB10_symbols10_ahoy); + char sym[]=" "; + sym[0] = mDisplayData->RadioSymbol?'A':'E'; // NRF + mDisplay->drawStr(xoffs + mPixelshift, mLineYOffsets[l_RSSI], sym); + + if (mDisplayData->MQTTSymbol) + sym[0] = 'J'; // MQTT + else + sym[0] = mDisplayData->WifiSymbol?'B':'F'; // Wifi + mDisplay->drawStr(mDispWidth - mDisplay->getStrWidth(sym) - xoffs + mPixelshift, mLineYOffsets[l_RSSI], sym); + mDisplay->sendBuffer(); - IPAddress ip = WiFi.localIP(); - if (!(mExtra % 10) && (ip)) - printText(ip.toString().c_str(), 3); - else if (!(mExtra % 5)) { - snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%d Inverter on", mDisplayData->nrProducing); - printText(mFmtText, 3); - } else if (0 != mDisplayData->utcTs) - printText(ah::getDateTimeStr(gTimezone.toLocal(mDisplayData->utcTs)).c_str(), 3); mDisplay->sendBuffer(); - mDispY = 0; mExtra++; } private: + enum _dispLine { + // start page + l_Website = 0, + l_Ahoy = 2, + l_Version = 4, + // run page + l_Time = 0, + l_Status = 1, + l_TotalPower = 2, + l_YieldDay = 3, + l_YieldTotal = 4, + // run page - rssi bar symbols + l_RSSI = 4, + // End + l_MAX_LINES = 5, + }; + + const uint8_t pixelShiftRange = 11; // number of pixels to shift from left to right (centered -> must be odd!) + void calcLinePositions() { uint8_t yOff = 0; - for (uint8_t i = 0; i < 4; i++) { - setFont(i); - yOff += (mDisplay->getMaxCharHeight()); + uint8_t i = 0; + uint8_t asc, dsc; + + do { + setLineFont(i); + asc = mDisplay->getAscent(); + yOff += asc; mLineYOffsets[i] = yOff; - } + dsc = mDisplay->getDescent(); + yOff -= dsc; + if (l_Time==i) // prevent time and status line to touch + yOff+=1; // -> one pixels space + i++; + } while(l_MAX_LINES>i); } - inline void setFont(uint8_t line) { - switch (line) { - case 0: - mDisplay->setFont(u8g2_font_ncenB14_tr); - break; - case 3: - mDisplay->setFont(u8g2_font_5x8_symbols_ahoy); - break; - default: - mDisplay->setFont(u8g2_font_ncenB10_tr); - break; - } + inline void setLineFont(uint8_t line) { + if ((line == l_TotalPower) || + (line == l_Ahoy)) + mDisplay->setFont(u8g2_font_ncenB14_tr); + else if ((line == l_YieldDay) || + (line == l_YieldTotal)) + // mDisplay->setFont(u8g2_font_5x8_symbols_ahoy); + mDisplay->setFont(u8g2_font_ncenB10_tr); + else + mDisplay->setFont(u8g2_font_ncenB08_tr); } - void printText(const char *text, uint8_t line, uint8_t dispX = 5) { - setFont(line); - dispX += (mEnScreenSaver) ? (mExtra % 7) : 0; + void printText(const char *text, uint8_t line, uint8_t col=0) { + uint8_t dispX; + setLineFont(line); + if (0xff == col) + dispX = (mDispWidth - mDisplay->getStrWidth(text)) / 2; // center text + else + dispX = col; + dispX += mPixelshift; mDisplay->drawStr(dispX, mLineYOffsets[line], text); } }; diff --git a/src/plugins/Display/Display_Mono_64X48.h b/src/plugins/Display/Display_Mono_64X48.h index 43c7e365..68cac96f 100644 --- a/src/plugins/Display/Display_Mono_64X48.h +++ b/src/plugins/Display/Display_Mono_64X48.h @@ -9,17 +9,12 @@ class DisplayMono64X48 : public DisplayMono { public: DisplayMono64X48() : DisplayMono() { - mEnPowerSave = true; - mEnScreenSaver = false; - mLuminance = 20; mExtra = 0; - mDispY = 0; - mTimeout = DISP_DEFAULT_TIMEOUT; // interval at which to power save (milliseconds) } - void config(bool enPowerSave, bool enScreenSaver, uint8_t lum) { + void config(bool enPowerSave, uint8_t screenSaver, uint8_t lum) { mEnPowerSave = enPowerSave; - mEnScreenSaver = enScreenSaver; + mScreenSaver = screenSaver; mLuminance = lum; } @@ -27,6 +22,7 @@ class DisplayMono64X48 : public DisplayMono { u8g2_cb_t *rot = (u8g2_cb_t *)((rotation != 0x00) ? U8G2_R2 : U8G2_R0); // Wemos OLed Shield is not defined in u8 lib -> use nearest compatible monoInit(new U8G2_SSD1306_64X48_ER_F_HW_I2C(rot, reset, clock, data), type, displayData); + calcLinePositions(); printText("Ahoy!", 0); printText("ahoydtu.de", 1); @@ -34,28 +30,13 @@ class DisplayMono64X48 : public DisplayMono { mDisplay->sendBuffer(); } - void loop(uint8_t lum) { - if (mEnPowerSave) { - if (mTimeout != 0) - mTimeout--; - } - - if(mLuminance != lum) { - mLuminance = lum; - mDisplay->setContrast(mLuminance); - } - } - void disp(void) { mDisplay->clearBuffer(); - // set Contrast of the Display to raise the lifetime - mDisplay->setContrast(mLuminance); + // calculate current pixelshift for pixelshift screensaver + calcPixelShift(pixelShiftRange); if ((mDisplayData->totalPower > 0) && (mDisplayData->nrProducing > 0)) { - mTimeout = DISP_DEFAULT_TIMEOUT; - mDisplay->setPowerSave(false); - if (mDisplayData->totalPower > 999) snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%2.2f kW", (mDisplayData->totalPower / 1000)); else @@ -64,9 +45,6 @@ class DisplayMono64X48 : public DisplayMono { printText(mFmtText, 0); } else { printText("offline", 0); - // check if it's time to enter power saving mode - if (mTimeout == 0) - mDisplay->setPowerSave(mEnPowerSave); } snprintf(mFmtText, DISP_FMT_TEXT_LEN, "D: %4.0f Wh", mDisplayData->totalYieldDay); @@ -90,6 +68,8 @@ class DisplayMono64X48 : public DisplayMono { } private: + const uint8_t pixelShiftRange = 4; // number of pixels to shift from left to right + void calcLinePositions() { uint8_t yOff = 0; for (uint8_t i = 0; i < 4; i++) { @@ -118,8 +98,8 @@ class DisplayMono64X48 : public DisplayMono { } void printText(const char *text, uint8_t line) { - uint8_t dispX = 0; //small display, use all we have - dispX += (mEnScreenSaver) ? (mExtra % 4) : 0; + uint8_t dispX = mLineXOffsets[line] + pixelShiftRange/2 + mPixelshift; + setFont(line); mDisplay->drawStr(dispX, mLineYOffsets[line], text); } diff --git a/src/plugins/Display/Display_Mono_84X48.h b/src/plugins/Display/Display_Mono_84X48.h index ed540523..ccc2d409 100644 --- a/src/plugins/Display/Display_Mono_84X48.h +++ b/src/plugins/Display/Display_Mono_84X48.h @@ -10,17 +10,12 @@ class DisplayMono84X48 : public DisplayMono { public: DisplayMono84X48() : DisplayMono() { - mEnPowerSave = true; - mEnScreenSaver = true; - mLuminance = 140; mExtra = 0; - mDispY = 0; - mTimeout = DISP_DEFAULT_TIMEOUT; // interval at which to power save (milliseconds) } - void config(bool enPowerSave, bool enScreenSaver, uint8_t lum) { + void config(bool enPowerSave, uint8_t screenSaver, uint8_t lum) { mEnPowerSave = enPowerSave; - mEnScreenSaver = enScreenSaver; + mScreenSaver = screenSaver; mLuminance = lum; } @@ -34,36 +29,25 @@ class DisplayMono84X48 : public DisplayMono { mDisplay->sendBuffer(); } - void loop(uint8_t lum) { - if (mEnPowerSave) { - if (mTimeout != 0) - mTimeout--; - } - - if(mLuminance != lum) { - mLuminance = lum; - mDisplay->setContrast(mLuminance); - } - } - void disp(void) { - // Test + mDisplay->clearBuffer(); + + // Layout-Test /* - mDisplayData->nrSleeping = 10; - mDisplayData->nrProducing = 10; + mDisplayData->nrSleeping = 0; + mDisplayData->nrProducing = 1; mDisplayData->totalPower = 12345.67; mDisplayData->totalYieldDay = 12345.67; mDisplayData->totalYieldTotal = 1234; mDisplayData->utcTs += 1000000; + mDisplay->drawPixel(0, 0); + mDisplay->drawPixel(mDispWidth-1, 0); + mDisplay->drawPixel(0, mDispHeight-1); + mDisplay->drawPixel(mDispWidth-1, mDispHeight-1); */ - mDisplay->clearBuffer(); - // print total power if (mDisplayData->nrProducing > 0) { - mTimeout = DISP_DEFAULT_TIMEOUT; - mDisplay->setPowerSave(false); - if (mDisplayData->totalPower > 9999) snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%.2fkW", (mDisplayData->totalPower / 1000)); // forgo spacing between value and SI unit in favor of second position after decimal point else if (mDisplayData->totalPower > 999) @@ -74,9 +58,6 @@ class DisplayMono84X48 : public DisplayMono { printText(mFmtText, l_TotalPower, 0xff); } else { printText("offline", l_TotalPower, 0xff); - // check if it's time to enter power saving mode - if (mTimeout == 0) - mDisplay->setPowerSave(mEnPowerSave); } // print Date and time @@ -91,14 +72,15 @@ class DisplayMono84X48 : public DisplayMono { } // print status of inverters else { - if (0 == mDisplayData->nrSleeping) + if (0 == mDisplayData->nrSleeping + mDisplayData->nrProducing) + snprintf(mFmtText, DISP_FMT_TEXT_LEN, "no inverter"); + else if (0 == mDisplayData->nrSleeping) snprintf(mFmtText, DISP_FMT_TEXT_LEN, "\x86"); else if (0 == mDisplayData->nrProducing) snprintf(mFmtText, DISP_FMT_TEXT_LEN, "\x87"); else snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%d\x86 %d\x87", mDisplayData->nrProducing, mDisplayData->nrSleeping); - setLineFont(l_Status); - printText(mFmtText, l_Status, (mDispWidth - mDisplay->getStrWidth(mFmtText)) / 2); + printText(mFmtText, l_Status, 0xff); } // print yields diff --git a/src/web/RestApi.h b/src/web/RestApi.h index 4208be30..b53c8b79 100644 --- a/src/web/RestApi.h +++ b/src/web/RestApi.h @@ -578,7 +578,7 @@ class RestApi { void getDisplay(JsonObject obj) { obj[F("disp_typ")] = (uint8_t)mConfig->plugin.display.type; obj[F("disp_pwr")] = (bool)mConfig->plugin.display.pwrSaveAtIvOffline; - obj[F("disp_pxshift")] = (bool)mConfig->plugin.display.pxShift; + obj[F("disp_screensaver")] = (uint8_t)mConfig->plugin.display.screenSaver; obj[F("disp_rot")] = (uint8_t)mConfig->plugin.display.rot; obj[F("disp_cont")] = (uint8_t)mConfig->plugin.display.contrast; obj[F("disp_clk")] = (mConfig->plugin.display.type == 0) ? DEF_PIN_OFF : mConfig->plugin.display.disp_clk; @@ -587,6 +587,7 @@ class RestApi { obj[F("disp_dc")] = (mConfig->plugin.display.type < 3) ? DEF_PIN_OFF : mConfig->plugin.display.disp_dc; obj[F("disp_rst")] = (mConfig->plugin.display.type < 3) ? DEF_PIN_OFF : mConfig->plugin.display.disp_reset; obj[F("disp_bsy")] = (mConfig->plugin.display.type < 10) ? DEF_PIN_OFF : mConfig->plugin.display.disp_busy; + obj[F("pir_pin")] = mConfig->plugin.display.pirPin; } void getMqttInfo(JsonObject obj) { diff --git a/src/web/html/setup.html b/src/web/html/setup.html index ec68f65a..da9ab1a5 100644 --- a/src/web/html/setup.html +++ b/src/web/html/setup.html @@ -278,16 +278,14 @@ <div class="col-8 col-sm-3">Turn off while inverters are offline</div> <div class="col-4 col-sm-9"><input type="checkbox" name="disp_pwr"/></div> </div> - <div class="row mb-3"> - <div class="col-8 col-sm-3">Enable Screensaver (pixel shifting, OLED only)</div> - <div class="col-4 col-sm-9"><input type="checkbox" name="disp_pxshift"/></div> - </div> + <div id="screenSaver"></div> <div class="row mb-3"> <div class="col-12 col-sm-3 my-2">Luminance</div> <div class="col-12 col-sm-9"><input type="number" name="disp_cont" min="0" max="255"></select></div> </div> <p class="des">Pinout</p> <div id="dispPins"></div> + <div id="pirPin"></div> </fieldset> </div> @@ -946,17 +944,17 @@ } function parseDisplay(obj, type, system) { - for(var i of ["disp_pwr", "disp_pxshift"]) + for(var i of ["disp_pwr"]) document.getElementsByName(i)[0].checked = obj[i]; - var e = document.getElementById("dispPins"); + var dpins_elem = document.getElementById("dispPins"); //KEEP this order !!! var pins = [['clock', 'disp_clk'], ['data', 'disp_data'], ['cs', 'disp_cs'], ['dc', 'disp_dc'], ['reset', 'disp_rst']]; /*IF_ESP32*/ pins.push(['busy', 'disp_bsy']); /*ENDIF_ESP32*/ for(p of pins) { - e.append( + dpins_elem.append( ml("div", {class: "row mb-3", id: "row_" + p[1]}, [ ml("div", {class: "col-12 col-sm-3 my-2"}, p[0].toUpperCase()), ml("div", {class: "col-12 col-sm-9"}, @@ -965,23 +963,18 @@ ]) ); } - // keep display types grouped var opts = [[0, "None"], [2, "SH1106 1.3\" 128X64"], [5, "SSD1306 0.66\" 64X48 (Wemos OLED Shield)"], [4, "SSD1306 0.91\" 128X32"], [1, "SSD1306 0.96\" 128X64"], [6, "SSD1309 2.42\" 128X64"], [3, "Nokia5110"]]; /*IF_ESP32*/ opts.push([10, "ePaper"]); /*ENDIF_ESP32*/ - var dispType = sel("disp_typ", opts, obj["disp_typ"]); + var dtype_sel = sel("disp_typ", opts, obj["disp_typ"]); document.getElementById("dispType").append( ml("div", {class: "row mb-3"}, [ ml("div", {class: "col-12 col-sm-3 my-2"}, "Type"), - ml("div", {class: "col-12 col-sm-9"}, dispType) + ml("div", {class: "col-12 col-sm-9"}, dtype_sel) ]) ); - dispType.addEventListener('change', (e) => { - hideDispPins(pins, parseInt(e.target.value)) - }); - opts = [[0, "0°"], [2, "180°"]]; /*IF_ESP32*/ opts.push([1, "90°"]); @@ -994,7 +987,35 @@ ]) ); + var opts1 = [[0, "off"], [1, "pixel shift"], [2, "motion sensor"]]; + var screensaver_sel = sel("disp_screensaver", opts1, obj["disp_screensaver"]); + screensaver_sel.id = 'disp_screensaver'; + document.getElementById("screenSaver").append( + ml("div", {class: "row mb-3"}, [ + ml("div", {class: "col-12 col-sm-3 my-2"}, "Screensaver (OLED only)"), + ml("div", {class: "col-12 col-sm-9"}, screensaver_sel) + ]) + ); + + var esp8266pirpins = [[255, "off / default"], + [17, "A0"]]; + document.getElementById("pirPin").append( + ml("div", {class: "row mb-3"}, [ + ml("div", {class: "col-12 col-sm-3 my-2"}, "PIR sensor"), + ml("div", {class: "col-12 col-sm-9"}, sel("pir_pin", ("ESP8266" == type) ? esp8266pirpins : ("ESP32-S3" == system["chip_model"]) ? esp32s3pins : esp32pins, obj["pir_pin"])) + ]) + ); + document.getElementsByName("disp_cont")[0].value = obj["disp_cont"]; + + dtype_sel.addEventListener('change', function(e) { + hideDispPins(pins, parseInt(e.target.value)) + }); + + document.getElementById("disp_screensaver").addEventListener('change', function(e) { + hideDispPins(pins, parseInt(dtype_sel.value)) + }); + hideDispPins(pins, obj.disp_typ); } @@ -1021,6 +1042,15 @@ cl.add("hide"); } } + + var screenSaver = document.getElementById("disp_screensaver").value; + + if (2==screenSaver) { // show pir pin only for motion screensaver + setHide("pirPin", false); + } + else { // no pir pin for all others + setHide("pirPin", true); + } } function tick() { diff --git a/src/web/web.h b/src/web/web.h index c88d829a..cb5cf72a 100644 --- a/src/web/web.h +++ b/src/web/web.h @@ -565,7 +565,7 @@ class Web { // display mConfig->plugin.display.pwrSaveAtIvOffline = (request->arg("disp_pwr") == "on"); - mConfig->plugin.display.pxShift = (request->arg("disp_pxshift") == "on"); + mConfig->plugin.display.screenSaver = request->arg("disp_screensaver").toInt(); mConfig->plugin.display.rot = request->arg("disp_rot").toInt(); mConfig->plugin.display.type = request->arg("disp_typ").toInt(); mConfig->plugin.display.contrast = (mConfig->plugin.display.type == 0) ? 60 : request->arg("disp_cont").toInt(); @@ -575,6 +575,7 @@ class Web { mConfig->plugin.display.disp_reset = (mConfig->plugin.display.type < 3) ? DEF_PIN_OFF : request->arg("disp_rst").toInt(); mConfig->plugin.display.disp_dc = (mConfig->plugin.display.type < 3) ? DEF_PIN_OFF : request->arg("disp_dc").toInt(); mConfig->plugin.display.disp_busy = (mConfig->plugin.display.type < 10) ? DEF_PIN_OFF : request->arg("disp_bsy").toInt(); + mConfig->plugin.display.pirPin = request->arg("pir_pin").toInt(); mApp->saveSettings((request->arg("reboot") == "on")); diff --git a/tools/fonts/u8g2_font_ncenB10_symbols10_ahoy.bdf b/tools/fonts/u8g2_font_ncenB10_symbols10_ahoy.bdf index 8fb61063..e72a97b9 100644 --- a/tools/fonts/u8g2_font_ncenB10_symbols10_ahoy.bdf +++ b/tools/fonts/u8g2_font_ncenB10_symbols10_ahoy.bdf @@ -175,8 +175,16 @@ STARTCHAR 074 ENCODING 74 SWIDTH 648 0 DWIDTH 9 0 -BBX 0 0 0 0 +BBX 8 8 0 0 BITMAP +F9 +04 +F2 +09 +E5 +15 +D5 +D5 ENDCHAR STARTCHAR 075 ENCODING 75 diff --git a/tools/fonts/u8g2_font_ncenB10_symbols10_ahoy.c_ b/tools/fonts/u8g2_font_ncenB10_symbols10_ahoy.c_ index 45d3979e..04f29d3a 100644 --- a/tools/fonts/u8g2_font_ncenB10_symbols10_ahoy.c_ +++ b/tools/fonts/u8g2_font_ncenB10_symbols10_ahoy.c_ @@ -4,11 +4,11 @@ Glyphs: 11/11 BBX Build Mode: 0 */ -const uint8_t u8g2_font_ncenB10_symbols10_ahoy[207] U8G2_FONT_SECTION("u8g2_font_ncenB10_symbols10_ahoy") = - "\13\0\3\2\4\4\2\2\5\13\13\0\0\13\0\13\0\0\0\0\0\0\266A\15\267\212q\220\42\251\322" +const uint8_t u8g2_font_ncenB10_symbols10_ahoy[220] U8G2_FONT_SECTION("u8g2_font_ncenB10_symbols10_ahoy") = + "\13\0\3\2\4\4\2\2\5\13\13\0\0\13\0\13\0\0\0\0\0\0\303A\15\267\212q\220\42\251\322" "\266\306\275\1B\20\230\236\65da\22Ima\250F\71\254\1C\23\272\272\251\3Q\32\366Q\212\243" "\70\212\243\70\311\221\0D\20\271\252\361\242F:\242#: {\36\16\1E\22\267\212\361\222\14I\242" "\14\332\232\216[RJ\232\12F\25\250\233\221\14I\61I\206$\252%J\250Fa\224%J\71G\30" "\273\312W\316r`T\262DJ\303\64L#%K\304\35\310\342,\3H\27\272\272\217\344P\16\351\210" "\16\354\300<\244C\70,\303 \16!\0I\24\271\252\241\34\336\1-\223\64-\323\62-\323\62\35x" - "\10J\5\0\232\1K\5\0\232\1\0\0\0"; + "\10J\22\210\232\61Hi\64Di\64DI\226$KeiK\5\0\232\1\0\0\0"; diff --git a/tools/fonts/u8g2_font_ncenB10_symbols10_ahoy.fon b/tools/fonts/u8g2_font_ncenB10_symbols10_ahoy.fon index 8163a65b..161e9b92 100644 Binary files a/tools/fonts/u8g2_font_ncenB10_symbols10_ahoy.fon and b/tools/fonts/u8g2_font_ncenB10_symbols10_ahoy.fon differ