diff --git a/.gitignore b/.gitignore index ed5e9538..d6a35860 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ .vscode/extensions.json src/config/config_override.h src/web/html/h/* +src/web/html/tmp/* /**/Debug /**/v16/* *.db diff --git a/User_Manual.md b/User_Manual.md index be7519ec..298eb262 100644 --- a/User_Manual.md +++ b/User_Manual.md @@ -306,7 +306,8 @@ To get the information open the URL `/api/record/info` on your AhoyDTU. The info | chehrlic | HM-600 | | 1.0.10 | 2021 | 11-01 | 104 | | | | chehrlic | TSOL-M800de | | 1.0.10 | 2021 | 11-01 | 104 | | | | B5r1oJ0A9G | HM-800 | | 1.0.10 | 2021 | | 104 | | | -| | | | | | | | | | +| B5r1oJ0A9G | HM-800 | | 1.0.10 | 2021 | | 104 | | | +| tomquist | TSOL-M1600 | | 1.0.12 | 2020 | 06-24 | 100 | | | | | | | | | | | | | ## Developer Information about Command Queue diff --git a/src/CHANGES.md b/src/CHANGES.md index 6ef3ddb6..f3501854 100644 --- a/src/CHANGES.md +++ b/src/CHANGES.md @@ -2,6 +2,28 @@ (starting from release version `0.5.66`) +## 0.5.94 +* added ePaper (for ESP32 only!), thx @dAjaY85 #735 +* improved `/live` margins #732 +* renamed `var` to `VAr` #732 + +## 0.5.93 +* improved web API for `live` +* added dark mode option +* converted all forms to reponsive design +* repaired menu with password protection #720, #716, #709 +* merged MI series fixes #729 + +## 0.5.92 +* fix mobile menu +* fix inverters in select `serial.html` #709 + +## 0.5.91 +* improved html and navi, navi is visible even when API dies #660 +* reduced maximum allowed JSON size for API to 6000Bytes #660 +* small fix: output command at `prepareDevInformCmd` #692 +* improved inverter handling #671 + ## 0.5.90 * merged PR #684, #698, #705 * webserial minor overflow fix #660 diff --git a/src/LICENSE b/src/LICENSE new file mode 100644 index 00000000..057d1565 --- /dev/null +++ b/src/LICENSE @@ -0,0 +1,7 @@ +License + +CC-CY-NC-SA 3.0 + +https://creativecommons.org/licenses/by-nc-sa/3.0/de + +This project is for non-commercial use only! diff --git a/src/app.cpp b/src/app.cpp index 782b376d..9d0cea28 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -21,16 +21,10 @@ void app::setup() { resetSystem(); - /*DBGPRINTLN("--- start"); - DBGPRINTLN(String(ESP.getFreeHeap())); - DBGPRINTLN(String(ESP.getHeapFragmentation())); - DBGPRINTLN(String(ESP.getMaxFreeBlockSize()));*/ - - mSettings.setup(); mSettings.getPtr(mConfig); DPRINT(DBG_INFO, F("Settings valid: ")); - if(mSettings.getValid()) + if (mSettings.getValid()) DBGPRINTLN(F("true")); else DBGPRINTLN(F("false")); @@ -67,7 +61,7 @@ void app::setup() { mMiPayload.setup(this, &mSys, &mNrfRadio, &mStat, mConfig->nrf.maxRetransPerPyld, &mTimestamp); mMiPayload.enableSerialDebug(mConfig->serial.debug); - if(!mNrfRadio.isChipConnected()) + if(!mNrfRadio.isChipConnected()) DPRINTLN(DBG_WARN, F("WARNING! your NRF24 module can't be reached, check the wiring")); } if(mConfig->cmt.enabled) { @@ -81,6 +75,8 @@ void app::setup() { DBGPRINTLN(String(ESP.getHeapFragmentation())); DBGPRINTLN(String(ESP.getMaxFreeBlockSize()));*/ + //if (!mSys.Radio.isChipConnected()) + // DPRINTLN(DBG_WARN, F("WARNING! your NRF24 module can't be reached, check the wiring")); // when WiFi is in client mode, then enable mqtt broker #if !defined(AP_ONLY) @@ -99,18 +95,18 @@ void app::setup() { mApi.setup(this, &mSys, &mNrfRadio, mWeb.getWebSrvPtr(), mConfig); // Plugins - if(mConfig->plugin.display.type != 0) - mMonoDisplay.setup(&mConfig->plugin.display, &mSys, &mTimestamp, 0xff, mVersion); + if (mConfig->plugin.display.type != 0) + mDisplay.setup(&mConfig->plugin.display, &mSys, &mTimestamp, 0xff, mVersion); mPubSerial.setup(mConfig, &mSys, &mTimestamp); regularTickers(); - /*DBGPRINTLN("--- end setup"); - DBGPRINTLN(String(ESP.getFreeHeap())); - DBGPRINTLN(String(ESP.getHeapFragmentation())); - DBGPRINTLN(String(ESP.getMaxFreeBlockSize()));*/ + // DBGPRINTLN("--- end setup"); + // DBGPRINTLN(String(ESP.getFreeHeap())); + // DBGPRINTLN(String(ESP.getHeapFragmentation())); + // DBGPRINTLN(String(ESP.getMaxFreeBlockSize())); } //----------------------------------------------------------------------------- @@ -137,8 +133,8 @@ void app::loopStandard(void) { mStat.frmCnt++; Inverter<> *iv = mSys.findInverter(&p->packet[1]); - if(NULL != iv) { - if(IV_HM == iv->ivGen) + if (NULL != iv) { + if (IV_HM == iv->ivGen) mPayload.add(iv, p); else mMiPayload.add(iv, p); @@ -180,7 +176,7 @@ void app::loopStandard(void) { mHmsPayload.loop(); #endif - if(mMqttEnabled) + if (mMqttEnabled) mMqtt.loop(); } @@ -194,7 +190,7 @@ void app::loopWifi(void) { void app::onWifi(bool gotIp) { DPRINTLN(DBG_DEBUG, F("onWifi")); ah::Scheduler::resetTicker(); - regularTickers(); // reinstall regular tickers + regularTickers(); // reinstall regular tickers if (gotIp) { mInnerLoopCb = std::bind(&app::loopStandard, this); every(std::bind(&app::tickSend, this), mConfig->nrf.sendInterval, "tSend"); @@ -203,14 +199,13 @@ void app::onWifi(bool gotIp) { everySec(std::bind(&CmtRadioType::tickSecond, &mCmtRadio), "tsCmt"); #endif mMqttReconnect = true; - mSunrise = 0; // needs to be set to 0, to reinstall sunrise and ivComm tickers! + mSunrise = 0; // needs to be set to 0, to reinstall sunrise and ivComm tickers! once(std::bind(&app::tickNtpUpdate, this), 2, "ntp2"); - if(WIFI_AP == WiFi.getMode()) { + if (WIFI_AP == WiFi.getMode()) { mMqttEnabled = false; everySec(std::bind(&ahoywifi::tickWifiLoop, &mWifi), "wifiL"); } - } - else { + } else { mInnerLoopCb = std::bind(&app::loopWifi, this); everySec(std::bind(&ahoywifi::tickWifiLoop, &mWifi), "wifiL"); } @@ -221,8 +216,8 @@ void app::regularTickers(void) { DPRINTLN(DBG_DEBUG, F("regularTickers")); everySec(std::bind(&WebType::tickSecond, &mWeb), "webSc"); // Plugins - if(mConfig->plugin.display.type != 0) - everySec(std::bind(&MonoDisplayType::tickerSecond, &mMonoDisplay), "disp"); + if (mConfig->plugin.display.type != 0) + everySec(std::bind(&DisplayType::tickerSecond, &mDisplay), "disp"); every(std::bind(&PubSerialType::tick, &mPubSerial), mConfig->serial.interval, "uart"); } @@ -238,26 +233,26 @@ void app::tickNtpUpdate(void) { } // only install schedulers once even if NTP wasn't successful in first loop - if(mMqttReconnect) { // @TODO: mMqttReconnect is variable which scope has changed - if(mConfig->inst.rstValsNotAvail) + if (mMqttReconnect) { // @TODO: mMqttReconnect is variable which scope has changed + if (mConfig->inst.rstValsNotAvail) everyMin(std::bind(&app::tickMinute, this), "tMin"); - if(mConfig->inst.rstYieldMidNight) { + if (mConfig->inst.rstYieldMidNight) { uint32_t localTime = gTimezone.toLocal(mTimestamp); - uint32_t midTrig = gTimezone.toUTC(localTime - (localTime % 86400) + 86400); // next midnight local time + uint32_t midTrig = gTimezone.toUTC(localTime - (localTime % 86400) + 86400); // next midnight local time onceAt(std::bind(&app::tickMidnight, this), midTrig, "midNi"); } } - nxtTrig = isOK ? 43200 : 60; // depending on NTP update success check again in 12 h or in 1 min + nxtTrig = isOK ? 43200 : 60; // depending on NTP update success check again in 12 h or in 1 min - if((mSunrise == 0) && (mConfig->sun.lat) && (mConfig->sun.lon)) { + if ((mSunrise == 0) && (mConfig->sun.lat) && (mConfig->sun.lon)) { mCalculatedTimezoneOffset = (int8_t)((mConfig->sun.lon >= 0 ? mConfig->sun.lon + 7.5 : mConfig->sun.lon - 7.5) / 15) * 3600; tickCalcSunrise(); } // immediately start communicating // @TODO: leads to reboot loops? not sure #674 - if(isOK && mSendFirst) { + if (isOK && mSendFirst) { mSendFirst = false; once(std::bind(&app::tickSend, this), 2, "senOn"); } @@ -308,17 +303,17 @@ void app::tickIVCommunication(void) { void app::tickSun(void) { // only used and enabled by MQTT (see setup()) if (!mMqtt.tickerSun(mSunrise, mSunset, mConfig->sun.offsetSec, mConfig->sun.disNightCom)) - once(std::bind(&app::tickSun, this), 1, "mqSun"); // MQTT not connected, retry + once(std::bind(&app::tickSun, this), 1, "mqSun"); // MQTT not connected, retry } //----------------------------------------------------------------------------- void app::tickComm(void) { - if((!mIVCommunicationOn) && (mConfig->inst.rstValsCommStop)) + if ((!mIVCommunicationOn) && (mConfig->inst.rstValsCommStop)) once(std::bind(&app::tickZeroValues, this), mConfig->nrf.sendInterval, "tZero"); if (mMqttEnabled) { if (!mMqtt.tickerComm(!mIVCommunicationOn)) - once(std::bind(&app::tickComm, this), 5, "mqCom"); // MQTT not connected, retry after 5s + once(std::bind(&app::tickComm, this), 5, "mqCom"); // MQTT not connected, retry after 5s } } @@ -329,7 +324,7 @@ void app::tickZeroValues(void) { for (uint8_t id = 0; id < mSys.getNumInverters(); id++) { iv = mSys.getInverterByPos(id); if (NULL == iv) - continue; // skip to next inverter + continue; // skip to next inverter mPayload.zeroInverterValues(iv); } @@ -344,9 +339,9 @@ void app::tickMinute(void) { for (uint8_t id = 0; id < mSys.getNumInverters(); id++) { iv = mSys.getInverterByPos(id); if (NULL == iv) - continue; // skip to next inverter + continue; // skip to next inverter - if(!iv->isAvailable(mTimestamp) && !iv->isProducing(mTimestamp) && iv->config->enabled) + if (!iv->isAvailable(mTimestamp) && !iv->isProducing(mTimestamp) && iv->config->enabled) mPayload.zeroInverterValues(iv); } } @@ -355,7 +350,7 @@ void app::tickMinute(void) { void app::tickMidnight(void) { // only triggered if 'reset values at midnight is enabled' uint32_t localTime = gTimezone.toLocal(mTimestamp); - uint32_t nxtTrig = gTimezone.toUTC(localTime - (localTime % 86400) + 86400); // next midnight local time + uint32_t nxtTrig = gTimezone.toUTC(localTime - (localTime % 86400) + 86400); // next midnight local time onceAt(std::bind(&app::tickMidnight, this), nxtTrig, "mid2"); Inverter<> *iv; @@ -363,7 +358,7 @@ void app::tickMidnight(void) { for (uint8_t id = 0; id < mSys.getNumInverters(); id++) { iv = mSys.getInverterByPos(id); if (NULL == iv) - continue; // skip to next inverter + continue; // skip to next inverter mPayload.zeroInverterValues(iv); mPayload.zeroYieldDay(iv); @@ -403,8 +398,8 @@ void app::tickSend(void) { } while ((NULL == iv) && ((maxLoop--) > 0)); if (NULL != iv) { - if(iv->config->enabled) { - if(iv->ivGen == IV_HM) + if (iv->config->enabled) { + if (iv->ivGen == IV_HM) mPayload.ivSend(iv); else if(iv->ivGen == IV_MI) mMiPayload.ivSend(iv); @@ -457,25 +452,25 @@ void app::setupLed(void) { * PIN ---- |<----- 3.3V * * */ - if(mConfig->led.led0 != 0xff) { + if (mConfig->led.led0 != 0xff) { pinMode(mConfig->led.led0, OUTPUT); - digitalWrite(mConfig->led.led0, HIGH); // LED off + digitalWrite(mConfig->led.led0, HIGH); // LED off } - if(mConfig->led.led1 != 0xff) { + if (mConfig->led.led1 != 0xff) { pinMode(mConfig->led.led1, OUTPUT); - digitalWrite(mConfig->led.led1, HIGH); // LED off + digitalWrite(mConfig->led.led1, HIGH); // LED off } } //----------------------------------------------------------------------------- void app::updateLed(void) { - if(mConfig->led.led0 != 0xff) { + if (mConfig->led.led0 != 0xff) { Inverter<> *iv = mSys.getInverterByPos(0); if (NULL != iv) { - if(iv->isProducing(mTimestamp)) - digitalWrite(mConfig->led.led0, LOW); // LED on + if (iv->isProducing(mTimestamp)) + digitalWrite(mConfig->led.led0, LOW); // LED on else - digitalWrite(mConfig->led.led0, HIGH); // LED off + digitalWrite(mConfig->led.led0, HIGH); // LED off } } } diff --git a/src/app.h b/src/app.h index 18e6093c..23528e64 100644 --- a/src/app.h +++ b/src/app.h @@ -6,31 +6,29 @@ #ifndef __APP_H__ #define __APP_H__ - -#include "utils/dbg.h" #include #include +#include +#include #include "appInterface.h" - #include "config/settings.h" #include "defines.h" -#include "utils/crc.h" -#include "utils/scheduler.h" - +#include "hm/hmPayload.h" #include "hm/hmSystem.h" #include "hm/hmRadio.h" #include "hms/hmsRadio.h" #include "hms/hmsPayload.h" #include "hm/hmPayload.h" #include "hm/miPayload.h" -#include "wifi/ahoywifi.h" -#include "web/web.h" -#include "web/RestApi.h" - #include "publisher/pubMqtt.h" #include "publisher/pubSerial.h" - +#include "utils/crc.h" +#include "utils/dbg.h" +#include "utils/scheduler.h" +#include "web/RestApi.h" +#include "web/web.h" +#include "wifi/ahoywifi.h" // convert degrees and radians for sun calculation #define SIN(x) (sin(radians(x))) @@ -49,12 +47,11 @@ typedef PubMqtt PubMqttType; typedef PubSerial PubSerialType; // PLUGINS -#include "plugins/MonochromeDisplay/MonochromeDisplay.h" -typedef MonochromeDisplay MonoDisplayType; - +#include "plugins/Display/Display.h" +typedef Display DisplayType; class app : public IApp, public ah::Scheduler { - public: + public: app(); ~app() {} @@ -225,7 +222,7 @@ class app : public IApp, public ah::Scheduler { mMqtt.payloadEventListener(cmd); #endif if(mConfig->plugin.display.type != 0) - mMonoDisplay.payloadEventListener(cmd); + mDisplay.payloadEventListener(cmd); } void mqttSubRxCb(JsonObject obj); @@ -301,7 +298,7 @@ class app : public IApp, public ah::Scheduler { uint32_t mSunrise, mSunset; // plugins - MonoDisplayType mMonoDisplay; + DisplayType mDisplay; }; #endif /*__APP_H__*/ diff --git a/src/config/settings.h b/src/config/settings.h index 9894dcf9..a221f9ca 100644 --- a/src/config/settings.h +++ b/src/config/settings.h @@ -7,11 +7,12 @@ #define __SETTINGS_H__ #include -#include #include +#include + +#include "../defines.h" #include "../utils/dbg.h" #include "../utils/helper.h" -#include "../defines.h" /** * More info: @@ -51,6 +52,7 @@ typedef struct { char deviceName[DEVNAME_LEN]; char adminPwd[PWD_LEN]; uint16_t protectionMask; + bool darkMode; // wifi char stationSsid[SSID_LEN]; @@ -84,7 +86,7 @@ typedef struct { typedef struct { float lat; float lon; - bool disNightCom; // disable night communication + bool disNightCom; // disable night communication uint16_t offsetSec; } cfgSun_t; @@ -95,8 +97,8 @@ typedef struct { } cfgSerial_t; typedef struct { - uint8_t led0; // first LED pin - uint8_t led1; // second LED pin + uint8_t led0; // first LED pin + uint8_t led1; // second LED pin } cfgLed_t; typedef struct { @@ -113,7 +115,7 @@ typedef struct { char name[MAX_NAME_LENGTH]; serial_u serial; uint16_t chMaxPwr[4]; - int32_t yieldCor[4]; // signed YieldTotal correction value + int32_t yieldCor[4]; // signed YieldTotal correction value char chName[4][MAX_NAME_LENGTH]; } cfgIv_t; @@ -129,14 +131,17 @@ typedef struct { typedef struct { uint8_t type; bool pwrSaveAtIvOffline; - bool logoEn; bool pxShift; - bool rot180; - uint16_t wakeUp; - uint16_t sleepAt; + uint8_t rot; + //uint16_t wakeUp; + //uint16_t sleepAt; uint8_t contrast; - uint8_t pin0; - uint8_t pin1; + uint8_t disp_data; + uint8_t disp_clk; + uint8_t disp_cs; + uint8_t disp_reset; + uint8_t disp_busy; + uint8_t disp_dc; } display_t; typedef struct { @@ -228,7 +233,7 @@ class settings { else { //DPRINTLN(DBG_INFO, fp.readString()); //fp.seek(0, SeekSet); - DynamicJsonDocument root(4500); + DynamicJsonDocument root(5500); DeserializationError err = deserializeJson(root, fp); if(!err && (root.size() > 0)) { mCfg.valid = true; @@ -262,7 +267,7 @@ class settings { return false; } - DynamicJsonDocument json(4500); + DynamicJsonDocument json(6500); JsonObject root = json.to(); jsonWifi(root.createNestedObject(F("wifi")), true); jsonNrf(root.createNestedObject(F("nrf")), true); @@ -307,6 +312,7 @@ class settings { memset(&mCfg, 0, sizeof(settings_t)); mCfg.sys.protectionMask = DEF_PROT_INDEX | DEF_PROT_LIVE | DEF_PROT_SERIAL | DEF_PROT_SETUP | DEF_PROT_UPDATE | DEF_PROT_SYSTEM | DEF_PROT_API | DEF_PROT_MQTT; + mCfg.sys.darkMode = false; // restore temp settings if(keepWifi) memcpy(&mCfg.sys, &tmp, sizeof(cfgSys_t)); @@ -359,13 +365,16 @@ class settings { memset(&mCfg.inst, 0, sizeof(cfgInst_t)); mCfg.plugin.display.pwrSaveAtIvOffline = false; - mCfg.plugin.display.contrast = 60; - mCfg.plugin.display.logoEn = true; - mCfg.plugin.display.pxShift = true; - mCfg.plugin.display.rot180 = false; - mCfg.plugin.display.pin0 = DEF_PIN_OFF; // SCL - mCfg.plugin.display.pin1 = DEF_PIN_OFF; // SDA - } + mCfg.plugin.display.contrast = 60; + mCfg.plugin.display.pxShift = true; + mCfg.plugin.display.rot = 0; + mCfg.plugin.display.disp_data = DEF_PIN_OFF; // SDA + mCfg.plugin.display.disp_clk = DEF_PIN_OFF; // SCL + mCfg.plugin.display.disp_cs = DEF_PIN_OFF; + mCfg.plugin.display.disp_reset = DEF_PIN_OFF; + mCfg.plugin.display.disp_busy = DEF_PIN_OFF; + mCfg.plugin.display.disp_dc = DEF_PIN_OFF; + } void jsonWifi(JsonObject obj, bool set = false) { if(set) { @@ -375,6 +384,7 @@ class settings { obj[F("dev")] = mCfg.sys.deviceName; obj[F("adm")] = mCfg.sys.adminPwd; obj[F("prot_mask")] = mCfg.sys.protectionMask; + obj[F("dark")] = mCfg.sys.darkMode; ah::ip2Char(mCfg.sys.ip.ip, buf); obj[F("ip")] = String(buf); ah::ip2Char(mCfg.sys.ip.mask, buf); obj[F("mask")] = String(buf); ah::ip2Char(mCfg.sys.ip.dns1, buf); obj[F("dns1")] = String(buf); @@ -386,6 +396,7 @@ class settings { snprintf(mCfg.sys.deviceName, DEVNAME_LEN, "%s", obj[F("dev")].as()); snprintf(mCfg.sys.adminPwd, PWD_LEN, "%s", obj[F("adm")].as()); mCfg.sys.protectionMask = obj[F("prot_mask")]; + mCfg.sys.darkMode = obj[F("dark")]; ah::ip2Arr(mCfg.sys.ip.ip, obj[F("ip")].as()); ah::ip2Arr(mCfg.sys.ip.mask, obj[F("mask")].as()); ah::ip2Arr(mCfg.sys.ip.dns1, obj[F("dns1")].as()); @@ -502,26 +513,32 @@ class settings { JsonObject disp = obj.createNestedObject("disp"); disp[F("type")] = mCfg.plugin.display.type; disp[F("pwrSafe")] = (bool)mCfg.plugin.display.pwrSaveAtIvOffline; - disp[F("logo")] = (bool)mCfg.plugin.display.logoEn; - disp[F("pxShift")] = (bool)mCfg.plugin.display.pxShift; - disp[F("rot180")] = (bool)mCfg.plugin.display.rot180; - disp[F("wake")] = mCfg.plugin.display.wakeUp; - disp[F("sleep")] = mCfg.plugin.display.sleepAt; + disp[F("pxShift")] = (bool)mCfg.plugin.display.pxShift; + disp[F("rotation")] = mCfg.plugin.display.rot; + //disp[F("wake")] = mCfg.plugin.display.wakeUp; + //disp[F("sleep")] = mCfg.plugin.display.sleepAt; disp[F("contrast")] = mCfg.plugin.display.contrast; - disp[F("pin0")] = mCfg.plugin.display.pin0; - disp[F("pin1")] = mCfg.plugin.display.pin1; + disp[F("data")] = mCfg.plugin.display.disp_data; + disp[F("clock")] = mCfg.plugin.display.disp_clk; + disp[F("cs")] = mCfg.plugin.display.disp_cs; + disp[F("reset")] = mCfg.plugin.display.disp_reset; + disp[F("busy")] = mCfg.plugin.display.disp_busy; + disp[F("dc")] = mCfg.plugin.display.disp_dc; } else { JsonObject disp = obj["disp"]; - mCfg.plugin.display.type = disp[F("type")]; - mCfg.plugin.display.pwrSaveAtIvOffline = (bool) disp[F("pwrSafe")]; - mCfg.plugin.display.logoEn = (bool) disp[F("logo")]; - mCfg.plugin.display.pxShift = (bool) disp[F("pxShift")]; - mCfg.plugin.display.rot180 = (bool) disp[F("rot180")]; - mCfg.plugin.display.wakeUp = disp[F("wake")]; - mCfg.plugin.display.sleepAt = disp[F("sleep")]; - mCfg.plugin.display.contrast = disp[F("contrast")]; - mCfg.plugin.display.pin0 = disp[F("pin0")]; - mCfg.plugin.display.pin1 = disp[F("pin1")]; + mCfg.plugin.display.type = disp[F("type")]; + mCfg.plugin.display.pwrSaveAtIvOffline = (bool)disp[F("pwrSafe")]; + mCfg.plugin.display.pxShift = (bool)disp[F("pxShift")]; + mCfg.plugin.display.rot = disp[F("rotation")]; + //mCfg.plugin.display.wakeUp = disp[F("wake")]; + //mCfg.plugin.display.sleepAt = disp[F("sleep")]; + mCfg.plugin.display.contrast = disp[F("contrast")]; + mCfg.plugin.display.disp_data = disp[F("data")]; + mCfg.plugin.display.disp_clk = disp[F("clock")]; + mCfg.plugin.display.disp_cs = disp[F("cs")]; + mCfg.plugin.display.disp_reset = disp[F("reset")]; + mCfg.plugin.display.disp_busy = disp[F("busy")]; + mCfg.plugin.display.disp_dc = disp[F("dc")]; } } diff --git a/src/defines.h b/src/defines.h index 36471ae0..92723091 100644 --- a/src/defines.h +++ b/src/defines.h @@ -13,7 +13,7 @@ //------------------------------------- #define VERSION_MAJOR 0 #define VERSION_MINOR 5 -#define VERSION_PATCH 90 +#define VERSION_PATCH 94 //------------------------------------- typedef struct { diff --git a/src/hm/hmDefines.h b/src/hm/hmDefines.h index 8028a450..d5630077 100644 --- a/src/hm/hmDefines.h +++ b/src/hm/hmDefines.h @@ -29,6 +29,10 @@ const char* const fields[] = {"U_DC", "I_DC", "P_DC", "YieldDay", "YieldWeek", " "active_PowerLimit", /*"reactivePowerLimit","Powerfactor",*/ "LastAlarmCode"}; const char* const notAvail = "n/a"; +const uint8_t fieldUnits[] = {UNIT_V, UNIT_A, UNIT_W, UNIT_WH, UNIT_KWH, UNIT_KWH, + UNIT_V, UNIT_A, UNIT_W, UNIT_HZ, UNIT_C, UNIT_NONE, UNIT_PCT, UNIT_PCT, UNIT_VAR, + UNIT_NONE, UNIT_NONE, UNIT_NONE, UNIT_NONE, UNIT_NONE, UNIT_NONE, UNIT_PCT, UNIT_NONE}; + // mqtt discovery device classes enum {DEVICE_CLS_NONE = 0, DEVICE_CLS_CURRENT, DEVICE_CLS_ENERGY, DEVICE_CLS_PWR, DEVICE_CLS_VOLTAGE, DEVICE_CLS_FREQ, DEVICE_CLS_TEMP}; const char* const deviceClasses[] = {0, "current", "energy", "power", "voltage", "frequency", "temperature"}; diff --git a/src/hm/hmPayload.h b/src/hm/hmPayload.h index fe079b79..6ee0ac98 100644 --- a/src/hm/hmPayload.h +++ b/src/hm/hmPayload.h @@ -9,6 +9,7 @@ #include "../utils/dbg.h" #include "../utils/crc.h" #include "../config/config.h" +#include "hmRadio.h" #include typedef struct { @@ -158,7 +159,8 @@ class HmPayload { uint8_t cmd = iv->getQueuedCmd(); DPRINT(DBG_INFO, F("(#")); DBGPRINT(String(iv->id)); - DBGPRINT(F(") prepareDevInformCmd")); // + String(cmd, HEX)); + DBGPRINT(F(") prepareDevInformCmd 0x")); + DBGPRINTLN(String(cmd, HEX)); mRadio->prepareDevInformCmd(iv->radioId.u64, cmd, mPayload[iv->id].ts, iv->alarmMesIndex, false); mPayload[iv->id].txCmd = cmd; } diff --git a/src/hm/miPayload.h b/src/hm/miPayload.h index 7d16f58a..a4020936 100644 --- a/src/hm/miPayload.h +++ b/src/hm/miPayload.h @@ -92,14 +92,14 @@ class MiPayload { } void add(Inverter<> *iv, packet_t *p) { - DPRINTLN(DBG_INFO, F("MI got data [0]=") + String(p->packet[0], HEX)); + //DPRINTLN(DBG_INFO, F("MI got data [0]=") + String(p->packet[0], HEX)); if (p->packet[0] == (0x08 + ALL_FRAMES)) { // 0x88; MI status response to 0x09 mPayload[iv->id].stsa = true; miStsDecode(iv, p); } else if (p->packet[0] == (0x11 + SINGLE_FRAME)) { // 0x92; MI status response to 0x11 mPayload[iv->id].stsb = true; - miStsDecode(iv, p, 2); + miStsDecode(iv, p, CH2); } else if (p->packet[0] == (0x09 + ALL_FRAMES)) { // MI data response to 0x09 mPayload[iv->id].txId = p->packet[0]; miDataDecode(iv,p); @@ -359,12 +359,11 @@ class MiPayload { (mCbMiPayload)(val); } - void miStsDecode(Inverter<> *iv, packet_t *p, uint8_t chan = 1) { + void miStsDecode(Inverter<> *iv, packet_t *p, uint8_t chan = CH1) { + DPRINTLN(DBG_INFO, F("Inverter ") + String(iv->id) + F(": status msg 0x") + String(p->packet[0], HEX)); record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug); // choose the record structure rec->ts = mPayload[iv->id].ts; - int8_t offset = -2; - //iv->setValue(iv->getPosByChFld(chan, FLD_YD, rec), rec, (int)((p->packet[11+6] << 8) + p->packet[12+6])); // was 11/12, might be wrong! //if (INV_TYPE_1CH == iv->type) @@ -372,16 +371,21 @@ class MiPayload { //iv->setValue(iv->getPosByChFld(chan, FLD_EVT, rec), rec, (int)((p->packet[13] << 8) + p->packet[14])); - iv->setValue(iv->getPosByChFld(0, FLD_EVT, rec), rec, (int)((p->packet[11+offset] << 8) + p->packet[12+offset])); + iv->setValue(iv->getPosByChFld(0, FLD_EVT, rec), rec, (int)((p->packet[11] << 8) + p->packet[12])); + //iv->setValue(iv->getPosByChFld(0, FLD_EVT, rec), rec, (int)((p->packet[14] << 8) + p->packet[16])); if (iv->alarmMesIndex < rec->record[iv->getPosByChFld(0, FLD_EVT, rec)]){ - iv->alarmMesIndex = rec->record[iv->getPosByChFld(0, FLD_EVT, rec)]; + iv->alarmMesIndex = rec->record[iv->getPosByChFld(0, FLD_EVT, rec)]; // seems there's no status per channel in 3rd gen. models?!? DPRINTLN(DBG_INFO, "alarm ID incremented to " + String(iv->alarmMesIndex)); iv->enqueCommand(AlarmData); } + /* Unclear how in HM inverters Info and alarm data is handled... + */ - /* for decoding see + /* int8_t offset = -2; + + for decoding see void MI600StsMsg (NRF24_packet_t *p){ STAT = (int)((p->packet[11] << 8) + p->packet[12]); FCNT = (int)((p->packet[13] << 8) + p->packet[14]); @@ -393,23 +397,22 @@ class MiPayload { #endif } */ - DPRINTLN(DBG_INFO, F("Inverter ") + String(iv->id) + F(": status msg ") + p->packet[0]); } void miDataDecode(Inverter<> *iv, packet_t *p) { record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug); // choose the parser rec->ts = mPayload[iv->id].ts; - uint8_t chan = ( p->packet[2] == 0x89 || p->packet[2] == (0x36 + ALL_FRAMES) ) ? 1 : - ( p->packet[2] == 0x91 || p->packet[2] == (0x37 + ALL_FRAMES) ) ? 2 : - p->packet[2] == (0x38 + ALL_FRAMES) ? 3 : - 4; + uint8_t datachan = ( p->packet[0] == 0x89 || p->packet[0] == (0x36 + ALL_FRAMES) ) ? CH1 : + ( p->packet[0] == 0x91 || p->packet[0] == (0x37 + ALL_FRAMES) ) ? CH2 : + p->packet[0] == (0x38 + ALL_FRAMES) ? CH3 : + CH4; int8_t offset = -2; // U_DC = (float) ((p->packet[11] << 8) + p->packet[12])/10; - iv->setValue(iv->getPosByChFld(chan, FLD_UDC, rec), rec, (float)((p->packet[11+offset] << 8) + p->packet[12+offset])/10); + iv->setValue(iv->getPosByChFld(datachan, FLD_UDC, rec), rec, (float)((p->packet[11+offset] << 8) + p->packet[12+offset])/10); yield(); // I_DC = (float) ((p->packet[13] << 8) + p->packet[14])/10; - iv->setValue(iv->getPosByChFld(chan, FLD_IDC, rec), rec, (float)((p->packet[13+offset] << 8) + p->packet[14+offset])/10); + iv->setValue(iv->getPosByChFld(datachan, FLD_IDC, rec), rec, (float)((p->packet[13+offset] << 8) + p->packet[14+offset])/10); yield(); // U_AC = (float) ((p->packet[15] << 8) + p->packet[16])/10; iv->setValue(iv->getPosByChFld(0, FLD_UAC, rec), rec, (float)((p->packet[15+offset] << 8) + p->packet[16+offset])/10); @@ -418,23 +421,23 @@ class MiPayload { //iv->setValue(iv->getPosByChFld(0, FLD_IAC, rec), rec, (float)((p->packet[17+offset] << 8) + p->packet[18+offset])/100); //yield(); // P_DC = (float)((p->packet[19] << 8) + p->packet[20])/10; - iv->setValue(iv->getPosByChFld(chan, FLD_PDC, rec), rec, (float)((p->packet[19+offset] << 8) + p->packet[20+offset])/10); + iv->setValue(iv->getPosByChFld(datachan, FLD_PDC, rec), rec, (float)((p->packet[19+offset] << 8) + p->packet[20+offset])/10); yield(); // Q_DC = (float)((p->packet[21] << 8) + p->packet[22])/1; - iv->setValue(iv->getPosByChFld(chan, FLD_Q, rec), rec, (float)((p->packet[21+offset] << 8) + p->packet[22+offset])/1); + iv->setValue(iv->getPosByChFld(datachan, FLD_YD, rec), rec, (float)((p->packet[21+offset] << 8) + p->packet[22+offset])/1); yield(); - iv->setValue(iv->getPosByChFld(0, FLD_T, rec), rec, (float) ((int16_t)(p->packet[23+offset] << 8) + p->packet[24+offset])/10); - iv->setValue(iv->getPosByChFld(0, FLD_F, rec), rec, (float) ((p->packet[17+offset] << 8) + p->packet[18+offset])/100); //23 is freq or IAC? + iv->setValue(iv->getPosByChFld(0, FLD_T, rec), rec, (float) ((int16_t)(p->packet[23+offset] << 8) + p->packet[24+offset])/10); //23 is freq or IAC? + iv->setValue(iv->getPosByChFld(0, FLD_F, rec), rec, (float) ((p->packet[17+offset] << 8) + p->packet[18+offset])/100); + iv->setValue(iv->getPosByChFld(0, FLD_IRR, rec), rec, (float) (calcIrradiation(iv, datachan))); yield(); //FLD_YD - if (p->packet[2] >= (0x36 + ALL_FRAMES) ) { + if (p->packet[0] >= (0x36 + ALL_FRAMES) ) { + /*status message analysis most liklely needs to be changed, see MiStsMst*/ /*STAT = (uint8_t)(p->packet[25] ); FCNT = (uint8_t)(p->packet[26]); - FCODE = (uint8_t)(p->packet[27]); // MI300: (int)((p->packet[15] << 8) + p->packet[16]); */ - //iv->setValue(iv->getPosByChFld(chan, FLD_YD, rec), rec, (uint8_t)(p->packet[25])); - //iv->setValue(iv->getPosByChFld(chan, FLD_EVT, rec), rec, (uint8_t)(p->packet[27])); - iv->setValue(iv->getPosByChFld(0, FLD_EVT, rec), rec, (uint8_t)(p->packet[21+offset])); + FCODE = (uint8_t)(p->packet[27]); // MI300/Mi600 stsMsg:: (int)((p->packet[15] << 8) + p->packet[16]); */ + iv->setValue(iv->getPosByChFld(0, FLD_EVT, rec), rec, (uint8_t)(p->packet[25+offset])); yield(); if (iv->alarmMesIndex < rec->record[iv->getPosByChFld(0, FLD_EVT, rec)]){ iv->alarmMesIndex = rec->record[iv->getPosByChFld(0, FLD_EVT, rec)]; @@ -443,8 +446,8 @@ class MiPayload { iv->enqueCommand(AlarmData); } } - iv->setValue(iv->getPosByChFld(0, FLD_YD, rec), rec, CALC_YD_CH0); // (getValue(iv->getPosByChFld(1, FLD_YD, rec), rec) + getValue(iv->getPosByChFld(2, FLD_YD, rec), rec))); - + //iv->setValue(iv->getPosByChFld(0, FLD_YD, rec), rec, CALC_YD_CH0); // (getValue(iv->getPosByChFld(1, FLD_YD, rec), rec) + getValue(iv->getPosByChFld(2, FLD_YD, rec), rec))); + iv->setValue(iv->getPosByChFld(0, FLD_YD, rec), rec, calcYieldDayCh0(iv,0)); //datachan)); iv->doCalculations(); notify(mPayload[iv->id].txCmd); /* @@ -502,7 +505,7 @@ class MiPayload { FCODE = (uint8_t)(p->packet[27]); } */ - DPRINTLN(DBG_INFO, F("Inverter ") + String(iv->id) + F(": data msg ") + p->packet[0]); + DPRINTLN(DBG_INFO, F("Inverter ") + String(iv->id) + F(": data msg 0x") + String(p->packet[0], HEX) + F(" channel ") + datachan); } bool build(uint8_t id, bool *complete) { diff --git a/src/platformio.ini b/src/platformio.ini index 60245f69..e52cf3fb 100644 --- a/src/platformio.ini +++ b/src/platformio.ini @@ -15,6 +15,7 @@ include_dir = . [env] framework = arduino board_build.filesystem = littlefs +upload_speed = 921600 ;build_flags = ; ;;;;; Possible Debug options ;;;;;; @@ -40,6 +41,7 @@ lib_deps = bblanchon/ArduinoJson https://github.com/JChristensen/Timezone olikraus/U8g2 + zinggjm/GxEPD2@^1.5.0 ;esp8266/DNSServer ;esp8266/EEPROM ;esp8266/ESP8266WiFi @@ -54,8 +56,9 @@ board_build.f_cpu = 80000000L build_flags = -D RELEASE monitor_filters = ;default ; Remove typical terminal control codes from input - time ; Add timestamp with milliseconds for each new line + ;time ; Add timestamp with milliseconds for each new line ;log2file ; Log data to a file “platformio-device-monitor-*.log” located in the current working directory + esp8266_exception_decoder [env:esp8266-debug] platform = espressif8266 @@ -98,8 +101,9 @@ build_flags = -D RELEASE -std=gnu++14 build_unflags = -std=gnu++11 monitor_filters = ;default ; Remove typical terminal control codes from input - time ; Add timestamp with milliseconds for each new line + ;time ; Add timestamp with milliseconds for each new line ;log2file ; Log data to a file “platformio-device-monitor-*.log” located in the current working directory + esp32_exception_decoder [env:esp32-wroom32-debug] platform = espressif32 diff --git a/src/plugins/Display/Display.h b/src/plugins/Display/Display.h new file mode 100644 index 00000000..1f229ba4 --- /dev/null +++ b/src/plugins/Display/Display.h @@ -0,0 +1,113 @@ +#ifndef __DISPLAY__ +#define __DISPLAY__ + +#include +#include + +#include "../../hm/hmSystem.h" +#include "../../utils/helper.h" +#include "Display_Mono.h" +#include "Display_ePaper.h" + +template +class Display { + public: + Display() {} + + void setup(display_t *cfg, HMSYSTEM *sys, uint32_t *utcTs, uint8_t disp_reset, const char *version) { + mCfg = cfg; + mSys = sys; + mUtcTs = utcTs; + mNewPayload = false; + mLoopCnt = 0; + mVersion = version; + + if (mCfg->type == 0) + return; + + if ((1 < mCfg->type) && (mCfg->type < 10)) { + mMono.config(mCfg->pwrSaveAtIvOffline, mCfg->pxShift, mCfg->contrast); + mMono.init(mCfg->type, mCfg->rot, mCfg->disp_cs, mCfg->disp_dc, mCfg->disp_reset, mCfg->disp_clk, mCfg->disp_data, mUtcTs, mVersion); + } else if (mCfg->type >= 10) { + #if defined(ESP32) + mRefreshCycle = 0; + mEpaper.config(mCfg->rot); + mEpaper.init(mCfg->type, mCfg->disp_cs, mCfg->disp_dc, mCfg->disp_reset, mCfg->disp_busy, mCfg->disp_clk, mCfg->disp_data, mUtcTs, mVersion); + #endif + } + } + + void payloadEventListener(uint8_t cmd) { + mNewPayload = true; + } + + void tickerSecond() { + if (mNewPayload || ((++mLoopCnt % 10) == 0)) { + mNewPayload = false; + mLoopCnt = 0; + DataScreen(); + } + } + + private: + void DataScreen() { + if (mCfg->type == 0) + return; + if (*mUtcTs == 0) + return; + + float totalPower = 0; + float totalYieldDay = 0; + float totalYieldTotal = 0; + + uint8_t isprod = 0; + + Inverter<> *iv; + record_t<> *rec; + for (uint8_t i = 0; i < mSys->getNumInverters(); i++) { + iv = mSys->getInverterByPos(i); + rec = iv->getRecordStruct(RealTimeRunData_Debug); + if (iv == NULL) + continue; + + if (iv->isProducing(*mUtcTs)) + isprod++; + + totalPower += iv->getChannelFieldValue(CH0, FLD_PAC, rec); + totalYieldDay += iv->getChannelFieldValue(CH0, FLD_YD, rec); + totalYieldTotal += iv->getChannelFieldValue(CH0, FLD_YT, rec); + } + + if ((1 < mCfg->type) && (mCfg->type < 10)) { + mMono.loop(totalPower, totalYieldDay, totalYieldTotal, isprod); + } else if (mCfg->type >= 10) { + #if defined(ESP32) + mEpaper.loop(totalPower, totalYieldDay, totalYieldTotal, isprod); + mRefreshCycle++; + #endif + } + + #if defined(ESP32) + if (mRefreshCycle > 480) { + mEpaper.fullRefresh(); + mRefreshCycle = 0; + } + #endif + } + + // private member variables + bool mNewPayload; + uint8_t mLoopCnt; + uint32_t *mUtcTs; + const char *mVersion; + display_t *mCfg; + HMSYSTEM *mSys; + uint16_t mRefreshCycle; + + #if defined(ESP32) + DisplayEPaper mEpaper; + #endif + DisplayMono mMono; +}; + +#endif /*__DISPLAY__*/ diff --git a/src/plugins/Display/Display_Mono.cpp b/src/plugins/Display/Display_Mono.cpp new file mode 100644 index 00000000..af9cd870 --- /dev/null +++ b/src/plugins/Display/Display_Mono.cpp @@ -0,0 +1,149 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#include "Display_Mono.h" + +#ifdef ESP8266 + #include +#elif defined(ESP32) + #include +#endif +#include "../../utils/helper.h" + +//#ifdef U8X8_HAVE_HW_SPI +//#include +//#endif +//#ifdef U8X8_HAVE_HW_I2C +//#include +//#endif + +DisplayMono::DisplayMono() { + mEnPowerSafe = true; + mEnScreenSaver = true; + mLuminance = 60; + _dispY = 0; + mTimeout = DISP_DEFAULT_TIMEOUT; // interval at which to power save (milliseconds) + mUtcTs = NULL; +} + + + +void DisplayMono::init(uint8_t type, uint8_t rot, uint8_t cs, uint8_t dc, uint8_t reset, uint8_t clock, uint8_t data, uint32_t *utcTs, const char* version) { + if ((0 < type) && (type < 4)) { + u8g2_cb_t *rot = (u8g2_cb_t *)((rot != 0x00) ? U8G2_R2 : U8G2_R0); + switch(type) { + case 1: + mDisplay = new U8G2_PCD8544_84X48_F_4W_HW_SPI(rot, cs, dc, reset); + break; + case 2: + mDisplay = new U8G2_SSD1306_128X64_NONAME_F_HW_I2C(rot, reset, clock, data); + break; + default: + case 3: + mDisplay = new U8G2_SH1106_128X64_NONAME_F_HW_I2C(rot, reset, clock, data); + break; + } + + mUtcTs = utcTs; + + mDisplay->begin(); + + mIsLarge = (mDisplay->getWidth() > 120); + calcLineHeights(); + + mDisplay->clearBuffer(); + mDisplay->setContrast(mLuminance); + printText("AHOY!", 0, 35); + printText("ahoydtu.de", 2, 20); + printText(version, 3, 46); + mDisplay->sendBuffer(); + } +} + +void DisplayMono::config(bool enPowerSafe, bool enScreenSaver, uint8_t lum) { + mEnPowerSafe = enPowerSafe; + mEnScreenSaver = enScreenSaver; + mLuminance = lum; +} + +void DisplayMono::loop(float totalPower, float totalYieldDay, float totalYieldTotal, uint8_t isprod) { + if (mEnPowerSafe) + if(mTimeout != 0) + mTimeout--; + + mDisplay->clearBuffer(); + + // set Contrast of the Display to raise the lifetime + mDisplay->setContrast(mLuminance); + + if ((totalPower > 0) && (isprod > 0)) { + mTimeout = DISP_DEFAULT_TIMEOUT; + mDisplay->setPowerSave(false); + if (totalPower > 999) { + snprintf(_fmtText, DISP_FMT_TEXT_LEN, "%2.2f kW", (totalPower / 1000)); + } else { + snprintf(_fmtText, DISP_FMT_TEXT_LEN, "%3.0f W", totalPower); + } + printText(_fmtText, 0); + } else { + printText("offline", 0, 25); + // check if it's time to enter power saving mode + if (mTimeout == 0) + mDisplay->setPowerSave(mEnPowerSafe); + } + + snprintf(_fmtText, DISP_FMT_TEXT_LEN, "today: %4.0f Wh", totalYieldDay); + printText(_fmtText, 1); + + snprintf(_fmtText, DISP_FMT_TEXT_LEN, "total: %.1f kWh", totalYieldTotal); + printText(_fmtText, 2); + + IPAddress ip = WiFi.localIP(); + if (!(_mExtra % 10) && (ip)) { + printText(ip.toString().c_str(), 3); + } else if (!(_mExtra % 5)) { + snprintf(_fmtText, DISP_FMT_TEXT_LEN, "#%d Inverter online", isprod); + printText(_fmtText, 3); + } else { + if(mIsLarge && (NULL != mUtcTs)) + printText(ah::getDateTimeStr(gTimezone.toLocal(*mUtcTs)).c_str(), 3); + else + printText(ah::getTimeStr(gTimezone.toLocal(*mUtcTs)).c_str(), 3); + } + + mDisplay->sendBuffer(); + + _dispY = 0; + _mExtra++; +} + +void DisplayMono::calcLineHeights() { + uint8_t yOff = 0; + for (uint8_t i = 0; i < 4; i++) { + setFont(i); + yOff += (mDisplay->getMaxCharHeight()); + mLineOffsets[i] = yOff; + } +} + +inline void DisplayMono::setFont(uint8_t line) { + switch (line) { + case 0: + mDisplay->setFont((mIsLarge) ? u8g2_font_ncenB14_tr : u8g2_font_logisoso16_tr); + break; + case 3: + mDisplay->setFont(u8g2_font_5x8_tr); + break; + default: + mDisplay->setFont((mIsLarge) ? u8g2_font_ncenB10_tr : u8g2_font_5x8_tr); + break; + } +} + +void DisplayMono::printText(const char* text, uint8_t line, uint8_t dispX) { + if (!mIsLarge) { + dispX = (line == 0) ? 10 : 5; + } + setFont(line); + + dispX += (mEnPowerSafe) ? (_mExtra % 7) : 0; + mDisplay->drawStr(dispX, mLineOffsets[line], text); +} diff --git a/src/plugins/Display/Display_Mono.h b/src/plugins/Display/Display_Mono.h new file mode 100644 index 00000000..cf14f27e --- /dev/null +++ b/src/plugins/Display/Display_Mono.h @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#pragma once + +#include +#define DISP_DEFAULT_TIMEOUT 60 // in seconds +#define DISP_FMT_TEXT_LEN 32 + +class DisplayMono { + public: + DisplayMono(); + + void init(uint8_t type, uint8_t rot, uint8_t cs, uint8_t dc, uint8_t reset, uint8_t clock, uint8_t data, uint32_t *utcTs, const char* version); + void config(bool enPowerSafe, bool enScreenSaver, uint8_t lum); + void loop(float totalPower, float totalYieldDay, float totalYieldTotal, uint8_t isprod); + + private: + void calcLineHeights(); + void setFont(uint8_t line); + void printText(const char* text, uint8_t line, uint8_t dispX = 5); + + U8G2* mDisplay; + + bool mEnPowerSafe, mEnScreenSaver; + uint8_t mLuminance; + + bool mIsLarge = false; + uint8_t mLoopCnt; + uint32_t* mUtcTs; + uint8_t mLineOffsets[5]; + + uint16_t _dispY; + + uint8_t _mExtra; + uint16_t mTimeout; + char _fmtText[DISP_FMT_TEXT_LEN]; +}; diff --git a/src/plugins/Display/Display_ePaper.cpp b/src/plugins/Display/Display_ePaper.cpp new file mode 100644 index 00000000..fea372cd --- /dev/null +++ b/src/plugins/Display/Display_ePaper.cpp @@ -0,0 +1,197 @@ +#include "Display_ePaper.h" + +#ifdef ESP8266 + #include +#elif defined(ESP32) + #include +#endif +#include "../../utils/helper.h" +#include "imagedata.h" + +#if defined(ESP32) + +static const uint32_t spiClk = 4000000; // 4 MHz + +#if defined(ESP32) && defined(USE_HSPI_FOR_EPD) +SPIClass hspi(HSPI); +#endif + +DisplayEPaper::DisplayEPaper() { + mDisplayRotation = 2; + mHeadFootPadding = 16; +} + + +//*************************************************************************** +void DisplayEPaper::init(uint8_t type, uint8_t _CS, uint8_t _DC, uint8_t _RST, uint8_t _BUSY, uint8_t _SCK, uint8_t _MOSI, uint32_t *utcTs, const char *version) { + mUtcTs = utcTs; + + if (type > 9) { + Serial.begin(115200); + _display = new GxEPD2_BW(GxEPD2_150_BN(_CS, _DC, _RST, _BUSY)); + hspi.begin(_SCK, _BUSY, _MOSI, _CS); + +#if defined(ESP32) && defined(USE_HSPI_FOR_EPD) + _display->epd2.selectSPI(hspi, SPISettings(spiClk, MSBFIRST, SPI_MODE0)); +#endif + _display->init(115200, true, 2, false); + _display->setRotation(mDisplayRotation); + _display->setFullWindow(); + + // Logo + _display->fillScreen(GxEPD_BLACK); + _display->drawBitmap(0, 0, logo, 200, 200, GxEPD_WHITE); + while (_display->nextPage()) + ; + + // clean the screen + delay(2000); + _display->fillScreen(GxEPD_WHITE); + while (_display->nextPage()) + ; + + headlineIP(); + + // call the PowerPage to change the PV Power Values + actualPowerPaged(0, 0, 0, 0); + } +} + +void DisplayEPaper::config(uint8_t rotation) { + mDisplayRotation = rotation; +} + +//*************************************************************************** +void DisplayEPaper::fullRefresh() { + // screen complete black + _display->fillScreen(GxEPD_BLACK); + while (_display->nextPage()) + ; + delay(2000); + // screen complete white + _display->fillScreen(GxEPD_WHITE); + while (_display->nextPage()) + ; +} +//*************************************************************************** +void DisplayEPaper::headlineIP() { + int16_t tbx, tby; + uint16_t tbw, tbh; + + _display->setFont(&FreeSans9pt7b); + _display->setTextColor(GxEPD_WHITE); + + _display->setPartialWindow(0, 0, _display->width(), mHeadFootPadding); + _display->fillScreen(GxEPD_BLACK); + + do { + if ((WiFi.isConnected() == true) && (WiFi.localIP() > 0)) { + snprintf(_fmtText, sizeof(_fmtText), "%s", WiFi.localIP().toString().c_str()); + } else { + snprintf(_fmtText, sizeof(_fmtText), "WiFi not connected"); + } + _display->getTextBounds(_fmtText, 0, 0, &tbx, &tby, &tbw, &tbh); + uint16_t x = ((_display->width() - tbw) / 2) - tbx; + + _display->setCursor(x, (mHeadFootPadding - 2)); + _display->println(_fmtText); + } while (_display->nextPage()); +} +//*************************************************************************** +void DisplayEPaper::lastUpdatePaged() { + int16_t tbx, tby; + uint16_t tbw, tbh; + + _display->setFont(&FreeSans9pt7b); + _display->setTextColor(GxEPD_WHITE); + + _display->setPartialWindow(0, _display->height() - mHeadFootPadding, _display->width(), mHeadFootPadding); + _display->fillScreen(GxEPD_BLACK); + do { + if (NULL != mUtcTs) { + snprintf(_fmtText, sizeof(_fmtText), ah::getDateTimeStr(gTimezone.toLocal(*mUtcTs)).c_str()); + + _display->getTextBounds(_fmtText, 0, 0, &tbx, &tby, &tbw, &tbh); + uint16_t x = ((_display->width() - tbw) / 2) - tbx; + + _display->setCursor(x, (_display->height() - 3)); + _display->println(_fmtText); + } + } while (_display->nextPage()); +} +//*************************************************************************** +void DisplayEPaper::actualPowerPaged(float _totalPower, float _totalYieldDay, float _totalYieldTotal, uint8_t _isprod) { + int16_t tbx, tby; + uint16_t tbw, tbh, x, y; + + _display->setFont(&FreeSans24pt7b); + _display->setTextColor(GxEPD_BLACK); + + _display->setPartialWindow(0, mHeadFootPadding, _display->width(), _display->height() - (mHeadFootPadding * 2)); + _display->fillScreen(GxEPD_WHITE); + do { + if (_totalPower > 9999) { + snprintf(_fmtText, sizeof(_fmtText), "%.1f kW", (_totalPower / 10000)); + _changed = true; + } else if ((_totalPower > 0) && (_totalPower <= 9999)) { + snprintf(_fmtText, sizeof(_fmtText), "%.0f W", _totalPower); + _changed = true; + } else { + snprintf(_fmtText, sizeof(_fmtText), "offline"); + } + _display->getTextBounds(_fmtText, 0, 0, &tbx, &tby, &tbw, &tbh); + x = ((_display->width() - tbw) / 2) - tbx; + _display->setCursor(x, mHeadFootPadding + tbh + 10); + _display->print(_fmtText); + + _display->setFont(&FreeSans12pt7b); + y = _display->height() / 2; + _display->setCursor(0, y); + _display->print("today:"); + snprintf(_fmtText, _display->width(), "%.0f", _totalYieldDay); + _display->getTextBounds(_fmtText, 0, 0, &tbx, &tby, &tbw, &tbh); + x = ((_display->width() - tbw) / 2) - tbx; + _display->setCursor(x, y); + _display->print(_fmtText); + _display->setCursor(_display->width() - 33, y); + _display->println("Wh"); + + y = y + tbh + 7; + _display->setCursor(0, y); + _display->print("total:"); + snprintf(_fmtText, _display->width(), "%.1f", _totalYieldTotal); + _display->getTextBounds(_fmtText, 0, 0, &tbx, &tby, &tbw, &tbh); + x = ((_display->width() - tbw) / 2) - tbx; + _display->setCursor(x, y); + _display->print(_fmtText); + _display->setCursor(_display->width() - 45, y); + _display->println("kWh"); + + _display->setCursor(0, _display->height() - (mHeadFootPadding + 10)); + snprintf(_fmtText, sizeof(_fmtText), "#%d Inverter online", _isprod); + _display->println(_fmtText); + + } while (_display->nextPage()); +} +//*************************************************************************** +void DisplayEPaper::loop(float totalPower, float totalYieldDay, float totalYieldTotal, uint8_t isprod) { + // check if the IP has changed + if (_settedIP != WiFi.localIP().toString().c_str()) { + // save the new IP and call the Headline Funktion to adapt the Headline + _settedIP = WiFi.localIP().toString().c_str(); + headlineIP(); + } + + // call the PowerPage to change the PV Power Values + actualPowerPaged(totalPower, totalYieldDay, totalYieldTotal, isprod); + + // if there was an change and the Inverter is producing set a new Timestam in the footline + if ((isprod > 0) && (_changed)) { + _changed = false; + lastUpdatePaged(); + } + + _display->powerOff(); +} +//*************************************************************************** +#endif // ESP32 diff --git a/src/plugins/Display/Display_ePaper.h b/src/plugins/Display/Display_ePaper.h new file mode 100644 index 00000000..b2729f25 --- /dev/null +++ b/src/plugins/Display/Display_ePaper.h @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#pragma once + +#if defined(ESP32) + +// uncomment next line to use HSPI for EPD (and VSPI for SD), e.g. with Waveshare ESP32 Driver Board +#define USE_HSPI_FOR_EPD + +/// uncomment next line to use class GFX of library GFX_Root instead of Adafruit_GFX, to use less code and ram +// #include +// base class GxEPD2_GFX can be used to pass references or pointers to the display instance as parameter, uses ~1.2k more code +// enable GxEPD2_GFX base class +#define ENABLE_GxEPD2_GFX 1 + +#include +#include +#include + +#include +// FreeFonts from Adafruit_GFX +#include +#include +#include +#include + +// GDEW027C44 2.7 " b/w/r 176x264, IL91874 +// GDEH0154D67 1.54" b/w 200x200 + +class DisplayEPaper { + public: + DisplayEPaper(); + void fullRefresh(); + void init(uint8_t type, uint8_t _CS, uint8_t _DC, uint8_t _RST, uint8_t _BUSY, uint8_t _SCK, uint8_t _MOSI, uint32_t *utcTs, const char* version); + void config(uint8_t rotation); + void loop(float totalPower, float totalYieldDay, float totalYieldTotal, uint8_t isprod); + + + private: + void headlineIP(); + void actualPowerPaged(float _totalPower, float _totalYieldDay, float _totalYieldTotal, uint8_t _isprod); + void lastUpdatePaged(); + + uint8_t mDisplayRotation; + bool _changed = false; + char _fmtText[35]; + const char* _settedIP; + uint8_t mHeadFootPadding; + GxEPD2_GFX* _display; + uint32_t *mUtcTs; +}; + +#endif // ESP32 diff --git a/src/plugins/Display/imagedata.h b/src/plugins/Display/imagedata.h new file mode 100644 index 00000000..baaddec8 --- /dev/null +++ b/src/plugins/Display/imagedata.h @@ -0,0 +1,329 @@ +// GxEPD2_ESP32_ESP8266_WifiData_V1_und_V2 + +#ifndef __IMAGEDATA_H__ +#define __IMAGEDATA_H__ + +#if defined(__AVR__) || defined(ARDUINO_ARCH_SAMD) +#include +#elif defined(ESP8266) || defined(ESP32) +#include +#endif + +// 'Logo', 200x200px +const unsigned char logo[] PROGMEM = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x5f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, + 0x0b, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x0f, 0xfe, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x06, + 0x0f, 0xff, 0xff, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x7e, 0x0f, 0xff, 0xff, 0xfc, 0x03, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, + 0x03, 0xfe, 0x0f, 0xff, 0xff, 0xff, 0xf0, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x19, 0xfe, 0x07, 0xff, 0xff, 0xff, 0xfe, + 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xe0, 0x70, 0x7f, 0x07, 0xff, 0xff, 0xff, 0xff, 0xc1, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x03, 0xe0, 0x3f, 0x07, 0xff, 0xff, + 0xff, 0xff, 0xf8, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xfc, 0x0f, 0xe0, 0x3f, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0f, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x3f, 0xe0, 0x1f, 0x83, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xc3, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xf1, 0xff, 0xe0, 0x1f, 0x83, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf9, 0xff, 0xe0, + 0x0f, 0x83, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf9, 0xff, 0xe0, 0x0f, 0x83, 0xff, 0xff, 0xff, 0xff, 0xfe, + 0x07, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, + 0xff, 0xc1, 0x07, 0x80, 0x07, 0xfe, 0xff, 0xff, 0xfc, 0x07, 0xe3, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xff, 0xe1, 0x07, 0xc0, 0x01, 0xe0, 0x0f, + 0xff, 0xfc, 0x0f, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xfc, 0xff, 0xe1, 0x83, 0xc0, 0x01, 0xc0, 0x07, 0xff, 0xf8, 0x0f, 0xfc, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x7f, 0xe1, 0x83, 0xc0, 0x00, + 0xc0, 0x07, 0x8f, 0xf8, 0x1f, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xfe, 0x7f, 0xe0, 0x01, 0xc0, 0x00, 0x81, 0x83, 0x07, 0xf0, 0x3f, 0xf9, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x3f, 0xe0, 0x01, + 0xe0, 0xe0, 0x87, 0xe3, 0x0f, 0xf0, 0x3f, 0xf1, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, 0xe0, 0x00, 0xe0, 0xe0, 0x87, 0xe1, 0x0c, 0x60, 0x7f, + 0xe3, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x1f, + 0xe0, 0x00, 0xe1, 0xf0, 0x87, 0xe1, 0x08, 0x60, 0x7f, 0xe7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x9f, 0xe0, 0xe0, 0xe0, 0xe0, 0x87, 0xc2, 0x00, + 0x40, 0xff, 0xc7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0x8f, 0xc0, 0xe0, 0x60, 0xe0, 0xc0, 0x82, 0x00, 0xc0, 0xff, 0x8f, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xcf, 0xc0, 0xe0, 0x60, 0xe0, 0xc0, + 0x06, 0x01, 0x81, 0xff, 0x9f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xcf, 0xe0, 0xe0, 0x20, 0xe0, 0xe0, 0x0c, 0x03, 0x81, 0xff, 0x1f, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc7, 0xc0, 0xf0, 0x30, + 0xe1, 0xf8, 0x18, 0x07, 0xe1, 0xfe, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xc7, 0xc0, 0xf0, 0x7f, 0xff, 0xff, 0xf0, 0x1f, 0xf3, 0xfe, 0x01, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x83, 0xc0, + 0xfb, 0xff, 0xff, 0xff, 0xe0, 0x3e, 0x1f, 0xfc, 0xe0, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x03, 0xc0, 0xff, 0xff, 0xff, 0xff, 0xc0, 0xfc, 0x0f, + 0xf8, 0xfc, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, + 0x33, 0xef, 0xff, 0xff, 0xff, 0xff, 0x81, 0xfc, 0x0f, 0xf1, 0xff, 0x07, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0xf1, 0xff, 0xff, 0xa0, 0x00, 0x7f, 0xe3, + 0xfc, 0x0f, 0xf3, 0xff, 0xc3, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xf1, 0xf9, 0xff, 0xf0, 0x00, 0x00, 0x00, 0xff, 0xfc, 0x0f, 0xe7, 0xff, 0xe0, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe3, 0xf9, 0xff, 0x80, 0x3f, 0xff, + 0xe0, 0x0f, 0xfe, 0x1f, 0xc7, 0xff, 0xf8, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xcf, 0xf8, 0xf0, 0x07, 0xff, 0xff, 0xff, 0x81, 0xff, 0xff, 0x8f, 0xff, 0xfc, + 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x9f, 0xfc, 0x70, 0x3f, + 0xff, 0xff, 0xff, 0xf0, 0x1f, 0xff, 0x1f, 0xff, 0xfe, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x1f, 0xfc, 0x63, 0xff, 0xff, 0xff, 0xff, 0xff, 0x03, 0xff, 0x3f, + 0xff, 0xff, 0x8f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, 0xfe, + 0x23, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x7e, 0x3f, 0xff, 0xff, 0xc7, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x7f, 0xfe, 0x23, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, + 0x0c, 0x7f, 0xff, 0xff, 0xc3, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, + 0x7f, 0xff, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0xff, 0xff, 0xff, 0xe1, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xff, 0xff, 0x87, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xf1, 0xff, 0xff, 0xff, 0xf0, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xfc, 0xff, 0xff, 0x87, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf3, 0xff, 0xff, 0xff, 0xf8, + 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf9, 0xff, 0xff, 0x87, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xf1, 0xff, 0xff, 0xcf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xfc, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf3, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x01, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xe3, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xf8, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe7, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf9, 0x00, 0x3f, 0xff, 0xf8, 0x00, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xfc, 0x00, 0x00, 0x01, 0xff, 0xf8, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xcf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x55, 0x00, 0x3f, 0xf8, 0x00, + 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xcf, 0xff, 0xfc, 0x0f, 0xff, 0xff, 0xff, + 0xff, 0xff, 0x01, 0xff, 0xff, 0xf8, 0x0f, 0xfc, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0x9f, 0xff, 0xf8, 0x03, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x0f, 0xff, 0xff, 0xff, 0x03, + 0xfc, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x9f, 0xff, 0xe3, 0xf1, 0xff, + 0xff, 0xff, 0xff, 0xe0, 0x7f, 0xff, 0xff, 0xff, 0xe0, 0xfe, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x9f, 0xff, 0xe7, 0xf9, 0xff, 0xff, 0xff, 0xff, 0x83, 0xff, 0xff, 0xff, + 0xff, 0xf8, 0x7e, 0x06, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x9f, 0xff, 0xcf, + 0xf8, 0xff, 0xff, 0xff, 0xff, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x3f, 0x03, 0x3f, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, 0xff, 0xcf, 0xfc, 0xff, 0xff, 0xff, 0xfe, 0x3f, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x1f, 0x23, 0xbf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, + 0xff, 0x9f, 0xfe, 0x7f, 0xff, 0xf3, 0xfc, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0x8f, 0xf1, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, 0xff, 0x9f, 0xfe, 0x7f, 0xff, 0xe3, 0xf8, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xfe, 0x7f, 0xff, 0x9f, 0xff, 0x0f, 0xff, 0x8f, 0xf1, 0xff, 0xff, 0xff, 0xfe, 0xf5, 0x90, 0x07, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x7f, 0xff, 0x9f, 0xff, 0x03, 0xff, + 0x1f, 0xe3, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xfe, 0x7f, 0xff, 0x3f, 0xfe, 0x31, 0xfe, 0x7f, 0xe7, 0xff, 0x80, 0x00, 0x40, 0x00, + 0x07, 0xe1, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x7f, 0xff, 0x3f, 0x3c, + 0xf9, 0xfc, 0xff, 0xe7, 0xfe, 0x3f, 0xc9, 0xff, 0xf1, 0x1f, 0xf1, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xfc, 0xff, 0xff, 0x3f, 0x3c, 0xf9, 0xf9, 0xff, 0xc7, 0xfc, 0xff, 0x90, + 0x7f, 0xf3, 0x03, 0xf9, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xff, 0xff, + 0x3f, 0x39, 0xfd, 0xf3, 0xff, 0xcf, 0xfc, 0xff, 0x90, 0x3f, 0xf3, 0x83, 0xf8, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xff, 0xff, 0x3f, 0x39, 0xf9, 0xc7, 0xff, 0xcf, 0xfc, + 0xff, 0x32, 0x7f, 0xe4, 0x77, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, + 0xff, 0xff, 0x7f, 0x33, 0xf9, 0x8f, 0xff, 0xcf, 0xf9, 0xff, 0x00, 0x7f, 0xe0, 0x67, 0xfc, 0x7f, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf9, 0xff, 0xff, 0x7f, 0xb3, 0xf3, 0xbf, 0xff, + 0xcf, 0xf9, 0xff, 0x00, 0xff, 0xfe, 0x47, 0xfe, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xf9, 0xff, 0xff, 0x7f, 0xf7, 0xf3, 0xff, 0xff, 0xcf, 0xf9, 0xff, 0xe0, 0xff, 0xfc, 0x0f, + 0xfe, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf9, 0xff, 0xff, 0x7f, 0xe7, 0xe7, + 0xff, 0xff, 0xcf, 0xf9, 0xff, 0xe1, 0xff, 0xfc, 0x1f, 0xfe, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xf9, 0xff, 0xff, 0x3f, 0xef, 0xe7, 0xef, 0xff, 0xc7, 0xf9, 0xff, 0xc3, 0xff, + 0xfc, 0x3f, 0xff, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf9, 0xff, 0xff, 0x3f, + 0xef, 0xef, 0xc0, 0xff, 0xe7, 0xf9, 0xff, 0xc3, 0xff, 0xf8, 0x3f, 0xff, 0x3f, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xf9, 0xff, 0xff, 0x3f, 0xef, 0xcf, 0xf0, 0x01, 0xe7, 0xf1, 0xff, + 0x87, 0xff, 0xf8, 0x7f, 0xff, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf9, 0xff, + 0xff, 0xbf, 0xcf, 0xe7, 0xff, 0xc1, 0xe3, 0xe1, 0xff, 0x8f, 0xff, 0xf0, 0xff, 0xff, 0x9f, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf9, 0xff, 0xff, 0x9f, 0xef, 0xe7, 0xff, 0xff, 0xf3, + 0xc1, 0xff, 0x96, 0xaf, 0xf9, 0xff, 0xff, 0x9f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xf9, 0xff, 0xff, 0x9f, 0xe7, 0xe3, 0xff, 0xff, 0xf1, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, + 0x9f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf9, 0xff, 0xff, 0xcf, 0xe7, 0xf3, 0xff, + 0xff, 0xf8, 0xc0, 0x00, 0x4a, 0x90, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xf9, 0xff, 0xff, 0xef, 0xf3, 0xf3, 0x9f, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf9, 0xff, 0xff, 0xe7, 0xf1, + 0xe7, 0xc7, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xf9, 0xff, 0xff, 0xf3, 0xf0, 0x07, 0xe3, 0xff, 0xff, 0x81, 0xff, 0xff, + 0xff, 0xff, 0xfe, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf9, 0xff, 0xff, + 0xf8, 0x07, 0x1f, 0xf1, 0xff, 0xff, 0xc3, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x8f, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf9, 0xff, 0xff, 0xfc, 0x1f, 0x9f, 0xf8, 0xff, 0xff, 0xc3, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x9f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf9, + 0xff, 0xff, 0xf8, 0xff, 0x9f, 0xfe, 0x7f, 0xff, 0xe3, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x8f, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf9, 0xff, 0xff, 0xf9, 0xff, 0x9f, 0xfe, 0x3f, + 0xff, 0xe3, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x9f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xfd, 0xff, 0xff, 0xf1, 0xff, 0x9f, 0xff, 0x9f, 0xff, 0xf3, 0xff, 0x3f, 0x3f, 0xff, 0xff, + 0xff, 0x9f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf9, 0xff, 0xff, 0xe1, 0xff, 0xcf, + 0xff, 0xc7, 0xff, 0xf3, 0xff, 0x3f, 0x9f, 0xff, 0xff, 0xff, 0x9f, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xfc, 0xff, 0xff, 0xe1, 0xff, 0x8f, 0xff, 0xe7, 0xff, 0xf3, 0xff, 0x3f, 0x9f, + 0xff, 0xff, 0xff, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xff, 0xff, 0xc1, + 0xff, 0xcf, 0xff, 0xf3, 0xff, 0xf3, 0xff, 0x3f, 0x9f, 0xff, 0xff, 0xff, 0x1f, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xff, 0xff, 0x81, 0xff, 0xcf, 0xff, 0xff, 0xff, 0xf3, 0xff, + 0x3f, 0x9f, 0xff, 0xff, 0xff, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xff, + 0xff, 0x91, 0xff, 0x8f, 0xff, 0xff, 0xff, 0xf3, 0xff, 0x3f, 0x9f, 0xff, 0xff, 0xfe, 0x3f, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0x11, 0xff, 0x9f, 0xff, 0xff, 0xff, + 0xf3, 0xff, 0x1f, 0x9f, 0xff, 0xff, 0xfe, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xfe, 0x7f, 0xff, 0x21, 0xff, 0x9f, 0xff, 0xff, 0xff, 0xf3, 0xff, 0xbf, 0x9f, 0xff, 0xff, 0xfe, + 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x7f, 0xfe, 0x20, 0xff, 0x9f, 0xff, + 0xff, 0xff, 0xf3, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xfe, 0x7f, 0xfe, 0x60, 0x7f, 0x9f, 0xff, 0xff, 0xff, 0xf3, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, 0xfc, 0x64, 0x3f, + 0x1f, 0xff, 0xff, 0xff, 0xf3, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, 0xfc, 0xe7, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xf3, 0xff, 0xff, + 0xff, 0x3f, 0xff, 0xf9, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x1f, 0xfc, + 0xe7, 0x80, 0xff, 0xff, 0xff, 0xff, 0xf3, 0xff, 0xff, 0xff, 0x3f, 0xff, 0xf1, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x9f, 0xf8, 0xe7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf3, + 0xff, 0xff, 0xfe, 0x7f, 0xff, 0xe3, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x9f, 0xf9, 0xe7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf3, 0xe7, 0xff, 0xfe, 0x7f, 0xff, 0xc3, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xcf, 0xf9, 0xe7, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xf3, 0xf3, 0xff, 0xfc, 0x7f, 0xff, 0x87, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xcf, 0xf9, 0xe7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf3, 0xf3, 0xff, 0xf8, 0xff, 0xff, + 0x8f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc7, 0xf9, 0xe7, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xf3, 0xf9, 0xff, 0xe1, 0xff, 0xfe, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xe7, 0xf3, 0xe7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf3, 0xfc, 0x3f, 0x07, + 0xff, 0xfc, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe3, 0xf3, 0xe7, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xf3, 0xfe, 0x00, 0x1f, 0xff, 0xf8, 0x7f, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf3, 0xf3, 0xe7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf3, 0xff, + 0xe0, 0xff, 0xff, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf1, + 0xf3, 0xe7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf3, 0xff, 0xff, 0xff, 0xff, 0xf1, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf1, 0xe3, 0xe7, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xf7, 0xff, 0xff, 0xff, 0xff, 0xe3, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xf8, 0x83, 0xe7, 0xff, 0xfe, 0x3f, 0xff, 0xff, 0xf7, 0xff, 0xff, 0xff, 0xff, 0xc7, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x13, 0xe7, 0xff, 0xfc, 0x03, + 0xff, 0xff, 0xf7, 0xff, 0xff, 0xff, 0xff, 0x8f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xfc, 0x31, 0xe7, 0xff, 0xfc, 0x00, 0x7f, 0xff, 0xe7, 0xff, 0xff, 0xff, 0xfe, + 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x39, 0xe3, 0xff, + 0xfc, 0x00, 0x1f, 0xff, 0xe7, 0xff, 0xff, 0xff, 0xfc, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x31, 0xf3, 0xff, 0xfc, 0x00, 0x1f, 0xff, 0xc7, 0xff, 0xff, + 0xff, 0xf8, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x19, + 0xf3, 0xff, 0xfc, 0x00, 0x07, 0xff, 0x87, 0xff, 0xff, 0xff, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x83, 0xf3, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x07, + 0xff, 0xff, 0xff, 0xe1, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0x83, 0xf3, 0xff, 0xff, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0x83, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc3, 0xf3, 0xff, 0xff, 0xff, 0xff, + 0xf8, 0x07, 0xff, 0xff, 0xff, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xe3, 0xf1, 0xff, 0xff, 0xff, 0xff, 0xff, 0x07, 0xff, 0xff, 0xfe, 0x1f, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf1, 0xe1, 0xff, 0xfe, + 0x01, 0xff, 0xfe, 0x07, 0xff, 0xff, 0xf8, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0xe1, 0xff, 0xf0, 0x00, 0x3f, 0x80, 0x07, 0xff, 0xff, 0xf0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x4c, + 0xff, 0xf0, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xc1, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x0c, 0xff, 0xf0, 0x00, 0x00, 0x0b, 0x87, 0xff, + 0xff, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0x0e, 0x7f, 0xf8, 0x00, 0x3f, 0xff, 0xc7, 0xff, 0xfe, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x86, 0x7f, 0xfe, 0x00, 0xff, 0xff, + 0xc3, 0xff, 0xf8, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0x80, 0x7f, 0xff, 0x87, 0xff, 0xff, 0xf3, 0xff, 0xe0, 0x7f, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x3f, 0xff, 0xff, + 0xff, 0xff, 0xf3, 0xff, 0x81, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xf3, 0xfe, 0x07, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x03, + 0xff, 0xff, 0xff, 0xff, 0xf3, 0xf0, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x7f, 0xff, 0xff, 0xff, 0xf3, 0xc0, 0x7f, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xf8, 0x1f, 0xff, 0xff, 0xff, 0xe3, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x83, 0xff, 0xff, 0xff, 0xe0, + 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xe0, 0x7f, 0xff, 0xff, 0xe0, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x03, 0xff, + 0xfe, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xf0, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xa0, 0x17, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff +}; + +#endif /*__IMAGEDATA_H__*/ diff --git a/src/plugins/MonochromeDisplay/MonochromeDisplay.h b/src/plugins/MonochromeDisplay/MonochromeDisplay.h deleted file mode 100644 index d7880432..00000000 --- a/src/plugins/MonochromeDisplay/MonochromeDisplay.h +++ /dev/null @@ -1,217 +0,0 @@ -#ifndef __MONOCHROME_DISPLAY__ -#define __MONOCHROME_DISPLAY__ - -#include -#include - -#include "../../utils/helper.h" -#include "../../hm/hmSystem.h" - -#define DISP_DEFAULT_TIMEOUT 60 // in seconds - - -static uint8_t bmp_logo[] PROGMEM = { - B00000000, B00000000, // ................ - B11101100, B00110111, // ..##.######.##.. - B11101100, B00110111, // ..##.######.##.. - B11100000, B00000111, // .....######..... - B11010000, B00001011, // ....#.####.#.... - B10011000, B00011001, // ...##..##..##... - B10000000, B00000001, // .......##....... - B00000000, B00000000, // ................ - B01111000, B00011110, // ...####..####... - B11111100, B00111111, // ..############.. - B01111100, B00111110, // ..#####..#####.. - B00000000, B00000000, // ................ - B11111100, B00111111, // ..############.. - B11111110, B01111111, // .##############. - B01111110, B01111110, // .######..######. - B00000000, B00000000 // ................ -}; - - -static uint8_t bmp_arrow[] PROGMEM = { - B00000000, B00011100, B00011100, B00001110, B00001110, B11111110, B01111111, - B01110000, B01110000, B00110000, B00111000, B00011000, B01111111, B00111111, - B00011110, B00001110, B00000110, B00000000, B00000000, B00000000, B00000000 -}; - - -template -class MonochromeDisplay { - public: - MonochromeDisplay() {} - - void setup(display_t *cfg, HMSYSTEM *sys, uint32_t *utcTs, uint8_t disp_reset, const char *version) { - mCfg = cfg; - mSys = sys; - mUtcTs = utcTs; - mNewPayload = false; - mLoopCnt = 0; - mTimeout = DISP_DEFAULT_TIMEOUT; // power off timeout (after inverters go offline) - - u8g2_cb_t *rot = (u8g2_cb_t *)((mCfg->rot180) ? U8G2_R2 : U8G2_R0); - - if(mCfg->type) { - switch(mCfg->type) { - case 1: - mDisplay = new U8G2_PCD8544_84X48_F_4W_HW_SPI(rot, mCfg->pin0, mCfg->pin1, disp_reset); - break; - case 2: - mDisplay = new U8G2_SSD1306_128X64_NONAME_F_HW_I2C(rot, disp_reset, mCfg->pin0, mCfg->pin1); - break; - case 3: - mDisplay = new U8G2_SH1106_128X64_NONAME_F_HW_I2C(rot, disp_reset, mCfg->pin0, mCfg->pin1); - break; - } - mDisplay->begin(); - - mIsLarge = ((mDisplay->getWidth() > 120) && (mDisplay->getHeight() > 60)); - calcLineHeights(); - - mDisplay->clearBuffer(); - mDisplay->setContrast(mCfg->contrast); - printText("Ahoy!", 0, 35); - printText("ahoydtu.de", 2, 20); - printText(version, 3, 46); - mDisplay->sendBuffer(); - } - } - - void payloadEventListener(uint8_t cmd) { - mNewPayload = true; - } - - void tickerSecond() { - if(mCfg->pwrSaveAtIvOffline) { - if(mTimeout != 0) - mTimeout--; - } - if(mNewPayload || ((++mLoopCnt % 10) == 0)) { - mNewPayload = false; - mLoopCnt = 0; - DataScreen(); - } - } - - private: - void DataScreen() { - if (mCfg->type == 0) - return; - if(*mUtcTs == 0) - return; - - float totalPower = 0; - float totalYieldDay = 0; - float totalYieldTotal = 0; - - bool isprod = false; - - Inverter<> *iv; - record_t<> *rec; - for (uint8_t i = 0; i < mSys->getNumInverters(); i++) { - iv = mSys->getInverterByPos(i); - rec = iv->getRecordStruct(RealTimeRunData_Debug); - if (iv == NULL) - continue; - - if (iv->isProducing(*mUtcTs)) - isprod = true; - - totalPower += iv->getChannelFieldValue(CH0, FLD_PAC, rec); - totalYieldDay += iv->getChannelFieldValue(CH0, FLD_YD, rec); - totalYieldTotal += iv->getChannelFieldValue(CH0, FLD_YT, rec); - } - - mDisplay->clearBuffer(); - - // Logos - // pxMovement +x (0 - 6 px) - uint8_t ex = (_mExtra % 7); - if (isprod) { - mDisplay->drawXBMP(5 + ex, 1, 8, 17, bmp_arrow); - if (mCfg->logoEn) - mDisplay->drawXBMP(mDisplay->getWidth() - 24 + ex, 2, 16, 16, bmp_logo); - } - - if ((totalPower > 0) && isprod) { - mTimeout = DISP_DEFAULT_TIMEOUT; - mDisplay->setPowerSave(false); - mDisplay->setContrast(mCfg->contrast); - if (totalPower > 999) - snprintf(_fmtText, sizeof(_fmtText), "%2.1f kW", (totalPower / 1000)); - else - snprintf(_fmtText, sizeof(_fmtText), "%3.0f W", totalPower); - printText(_fmtText, 0, 20); - } else { - printText("offline", 0, 25); - if(mCfg->pwrSaveAtIvOffline) { - if(mTimeout == 0) - mDisplay->setPowerSave(true); - } - } - - snprintf(_fmtText, sizeof(_fmtText), "today: %4.0f Wh", totalYieldDay); - printText(_fmtText, 1); - - snprintf(_fmtText, sizeof(_fmtText), "total: %.1f kWh", totalYieldTotal); - printText(_fmtText, 2); - - IPAddress ip = WiFi.localIP(); - if (!(_mExtra % 10) && (ip)) { - printText(ip.toString().c_str(), 3); - } else { - // Get current time - if(mIsLarge) - printText(ah::getDateTimeStr(gTimezone.toLocal(*mUtcTs)).c_str(), 3); - else - printText(ah::getTimeStr(gTimezone.toLocal(*mUtcTs)).c_str(), 3); - } - mDisplay->sendBuffer(); - - _mExtra++; - } - - void calcLineHeights() { - uint8_t yOff = 0; - for(uint8_t i = 0; i < 4; i++) { - setFont(i); - yOff += (mDisplay->getMaxCharHeight() + 1); - mLineOffsets[i] = yOff; - } - } - - inline void setFont(uint8_t line) { - switch (line) { - case 0: mDisplay->setFont((mIsLarge) ? u8g2_font_ncenB14_tr : u8g2_font_lubBI14_tr); break; - case 3: mDisplay->setFont(u8g2_font_5x8_tr); break; - default: mDisplay->setFont((mIsLarge) ? u8g2_font_ncenB10_tr : u8g2_font_5x8_tr); break; - } - } - - void printText(const char* text, uint8_t line, uint8_t dispX = 5) { - if(!mIsLarge) - dispX = (line == 0) ? 10 : 5; - setFont(line); - if(mCfg->pxShift) - dispX += (_mExtra % 7); // add pixel movement - mDisplay->drawStr(dispX, mLineOffsets[line], text); - } - - // private member variables - U8G2* mDisplay; - - uint8_t _mExtra; - uint16_t mTimeout; // interval at which to power save (milliseconds) - char _fmtText[32]; - - bool mNewPayload; - bool mIsLarge; - uint8_t mLoopCnt; - uint32_t *mUtcTs; - uint8_t mLineOffsets[5]; - display_t *mCfg; - HMSYSTEM *mSys; -}; - -#endif /*__MONOCHROME_DISPLAY__*/ diff --git a/src/publisher/pubMqtt.h b/src/publisher/pubMqtt.h index 113292b8..78478536 100644 --- a/src/publisher/pubMqtt.h +++ b/src/publisher/pubMqtt.h @@ -406,7 +406,7 @@ class PubMqtt { return (pos >= DEVICE_CLS_ASSIGN_LIST_LEN) ? NULL : stateClasses[deviceFieldAssignment[pos].stateClsId]; } - bool processIvStatus() { + bool processIvStatus() { // returns true if any inverter is available bool allAvail = true; // shows if all enabled inverters are available bool anyAvail = false; // shows if at least one enabled inverter is available @@ -419,17 +419,19 @@ class PubMqtt { iv = mSys->getInverterByPos(id); if (NULL == iv) continue; // skip to next inverter + if (!iv->config->enabled) + continue; // skip to next inverter rec = iv->getRecordStruct(RealTimeRunData_Debug); // inverter status uint8_t status = MQTT_STATUS_NOT_AVAIL_NOT_PROD; - if (iv->config->enabled) { - if (iv->isAvailable(*mUtcTimestamp)) - status = (iv->isProducing(*mUtcTimestamp)) ? MQTT_STATUS_AVAIL_PROD : MQTT_STATUS_AVAIL_NOT_PROD; - else // inverter is enabled but not available - allAvail = false; + if (iv->isAvailable(*mUtcTimestamp)) { + anyAvail = true; + status = (iv->isProducing(*mUtcTimestamp)) ? MQTT_STATUS_AVAIL_PROD : MQTT_STATUS_AVAIL_NOT_PROD; } + else // inverter is enabled but not available + allAvail = false; if(mLastIvState[id] != status) { // if status changed from producing to not producing send last data immediately @@ -439,11 +441,11 @@ class PubMqtt { mLastIvState[id] = status; changed = true; - snprintf(topic, 32 + MAX_NAME_LENGTH, "%s/%s", iv->config->name, mqttStr[MQTT_STR_AVAILABLE]); + snprintf(topic, 32 + MAX_NAME_LENGTH, "%s/available", iv->config->name); snprintf(val, 40, "%d", status); publish(topic, val, true); - snprintf(topic, 32 + MAX_NAME_LENGTH, "%s/%s", iv->config->name, mqttStr[MQTT_STR_LAST_SUCCESS]); + snprintf(topic, 32 + MAX_NAME_LENGTH, "%s/last_success", iv->config->name); snprintf(val, 40, "%d", iv->getLastTs(rec)); publish(topic, val, true); } @@ -451,7 +453,7 @@ class PubMqtt { if(changed) { snprintf(val, 32, "%d", ((allAvail) ? MQTT_STATUS_ONLINE : ((anyAvail) ? MQTT_STATUS_PARTIAL : MQTT_STATUS_OFFLINE))); - publish(subtopics[MQTT_STATUS], val, true); + publish("status", val, true); } return anyAvail; @@ -474,24 +476,26 @@ class PubMqtt { char topic[7 + MQTT_TOPIC_LEN], val[40]; record_t<> *rec = iv->getRecordStruct(curInfoCmd); - for (uint8_t i = 0; i < rec->length; i++) { - bool retained = false; - if (curInfoCmd == RealTimeRunData_Debug) { - switch (rec->assign[i].fieldId) { - case FLD_YT: - case FLD_YD: - if ((rec->assign[i].ch == CH0) && (!iv->isProducing(*mUtcTimestamp))) // avoids returns to 0 on restart - continue; - retained = true; - break; + if (iv->getLastTs(rec) > 0) { + for (uint8_t i = 0; i < rec->length; i++) { + bool retained = false; + if (curInfoCmd == RealTimeRunData_Debug) { + switch (rec->assign[i].fieldId) { + case FLD_YT: + case FLD_YD: + if ((rec->assign[i].ch == CH0) && (!iv->isProducing(*mUtcTimestamp))) // avoids returns to 0 on restart + continue; + retained = true; + break; + } } - } - snprintf(topic, 32 + MAX_NAME_LENGTH, "%s/ch%d/%s", iv->config->name, rec->assign[i].ch, fields[rec->assign[i].fieldId]); - snprintf(val, 40, "%g", ah::round3(iv->getValue(i, rec))); - publish(topic, val, retained); + snprintf(topic, 32 + MAX_NAME_LENGTH, "%s/ch%d/%s", iv->config->name, rec->assign[i].ch, fields[rec->assign[i].fieldId]); + snprintf(val, 40, "%g", ah::round3(iv->getValue(i, rec))); + publish(topic, val, retained); - yield(); + yield(); + } } } @@ -512,42 +516,49 @@ class PubMqtt { uint8_t curInfoCmd = mSendList.front(); if ((curInfoCmd != RealTimeRunData_Debug) || !RTRDataHasBeenSent) { // send RTR Data only once + bool sendTotals = (curInfoCmd == RealTimeRunData_Debug); + for (uint8_t id = 0; id < mSys->getNumInverters(); id++) { Inverter<> *iv = mSys->getInverterByPos(id); if (NULL == iv) continue; // skip to next inverter + if (!iv->config->enabled) + continue; // skip to next inverter // send RTR Data only if status is available - if ((curInfoCmd != RealTimeRunData_Debug) || (MQTT_STATUS_AVAIL_PROD == mLastIvState[id])) + if ((curInfoCmd != RealTimeRunData_Debug) || (MQTT_STATUS_NOT_AVAIL_NOT_PROD != mLastIvState[id])) sendData(iv, curInfoCmd); // calculate total values for RealTimeRunData_Debug - if (curInfoCmd == RealTimeRunData_Debug) { + if (sendTotals) { record_t<> *rec = iv->getRecordStruct(curInfoCmd); - for (uint8_t i = 0; i < rec->length; i++) { - if (CH0 == rec->assign[i].ch) { - switch (rec->assign[i].fieldId) { - case FLD_PAC: - total[0] += iv->getValue(i, rec); - break; - case FLD_YT: - total[1] += iv->getValue(i, rec); - break; - case FLD_YD: - total[2] += iv->getValue(i, rec); - break; - case FLD_PDC: - total[3] += iv->getValue(i, rec); - break; + sendTotals &= (iv->getLastTs(rec) > 0); + if (sendTotals) { + for (uint8_t i = 0; i < rec->length; i++) { + if (CH0 == rec->assign[i].ch) { + switch (rec->assign[i].fieldId) { + case FLD_PAC: + total[0] += iv->getValue(i, rec); + break; + case FLD_YT: + total[1] += iv->getValue(i, rec); + break; + case FLD_YD: + total[2] += iv->getValue(i, rec); + break; + case FLD_PDC: + total[3] += iv->getValue(i, rec); + break; + } } } } - yield(); } + yield(); } - if (curInfoCmd == RealTimeRunData_Debug) { + if (sendTotals) { uint8_t fieldId; for (uint8_t i = 0; i < 4; i++) { switch (i) { @@ -565,7 +576,7 @@ class PubMqtt { fieldId = FLD_PDC; break; } - snprintf(topic, 32 + MAX_NAME_LENGTH, "%s/%s", mqttStr[MQTT_STR_TOTAL], fields[fieldId]); + snprintf(topic, 32 + MAX_NAME_LENGTH, "total/%s", fields[fieldId]); snprintf(val, 40, "%g", ah::round3(total[i])); publish(topic, val, true); } diff --git a/src/web/RestApi.h b/src/web/RestApi.h index 11f87c64..5bd1a262 100644 --- a/src/web/RestApi.h +++ b/src/web/RestApi.h @@ -8,22 +8,24 @@ #include "../utils/dbg.h" #ifdef ESP32 - #include "AsyncTCP.h" +#include "AsyncTCP.h" #else - #include "ESPAsyncTCP.h" +#include "ESPAsyncTCP.h" #endif -#include "ESPAsyncWebServer.h" -#include "AsyncJson.h" +#include "../appInterface.h" #include "../hm/hmSystem.h" #include "../utils/helper.h" - -#include "../appInterface.h" +#include "AsyncJson.h" +#include "ESPAsyncWebServer.h" #if defined(F) && defined(ESP32) - #undef F - #define F(sl) (sl) +#undef F +#define F(sl) (sl) #endif +const uint8_t acList[] = {FLD_UAC, FLD_IAC, FLD_PAC, FLD_F, FLD_PF, FLD_T, FLD_YT, FLD_YD, FLD_PDC, FLD_EFF, FLD_Q}; +const uint8_t dcList[] = {FLD_UDC, FLD_IDC, FLD_PDC, FLD_YD, FLD_YT, FLD_IRR}; + template class RestApi { public: @@ -72,7 +74,7 @@ class RestApi { mHeapFrag = ESP.getHeapFragmentation(); #endif - AsyncJsonResponse* response = new AsyncJsonResponse(false, 8192); + AsyncJsonResponse* response = new AsyncJsonResponse(false, 6000); JsonObject root = response->getRoot(); String path = request->url().substring(5); @@ -84,7 +86,6 @@ class RestApi { else if(path == "reboot") getReboot(root); else if(path == "statistics") getStatistics(root); else if(path == "inverter/list") getInverterList(root); - else if(path == "menu") getMenu(root); else if(path == "index") getIndex(root); else if(path == "setup") getSetup(root); else if(path == "setup/networks") getNetworks(root); @@ -93,8 +94,12 @@ class RestApi { else if(path == "record/alarm") getRecord(root, AlarmData); else if(path == "record/config") getRecord(root, SystemConfigPara); else if(path == "record/live") getRecord(root, RealTimeRunData_Debug); - else - getNotFound(root, F("http://") + request->host() + F("/api/")); + else { + if(path.substring(0, 12) == "inverter/id/") + getInverter(root, request->url().substring(17).toInt()); + else + getNotFound(root, F("http://") + request->host() + F("/api/")); + } //DPRINTLN(DBG_INFO, "API mem usage: " + String(root.memoryUsage())); response->addHeader("Access-Control-Allow-Origin", "*"); @@ -154,13 +159,14 @@ class RestApi { ep[F("record/live")] = url + F("record/live"); } + void onDwnldSetup(AsyncWebServerRequest *request) { AsyncWebServerResponse *response; File fp = LittleFS.open("/settings.json", "r"); if(!fp) { DPRINTLN(DBG_ERROR, F("failed to load settings")); - response = request->beginResponse(200, F("application/json"), "{}"); + response = request->beginResponse(200, F("application/json; charset=utf-8"), "{}"); } else { String tmp = fp.readString(); @@ -173,7 +179,7 @@ class RestApi { tmp.remove(i, tmp.indexOf("\"", i)-i); } } - response = request->beginResponse(200, F("application/json"), tmp); + response = request->beginResponse(200, F("application/json; charset=utf-8"), tmp); } response->addHeader("Content-Type", "application/octet-stream"); @@ -184,10 +190,11 @@ class RestApi { } void getGeneric(JsonObject obj) { - obj[F("version")] = String(mApp->getVersion()); - obj[F("build")] = String(AUTO_GIT_HASH); - obj[F("wifi_rssi")] = (WiFi.status() != WL_CONNECTED) ? 0 : WiFi.RSSI(); - obj[F("ts_uptime")] = mApp->getUptime(); + obj[F("wifi_rssi")] = (WiFi.status() != WL_CONNECTED) ? 0 : WiFi.RSSI(); + obj[F("ts_uptime")] = mApp->getUptime(); + obj[F("menu_prot")] = mApp->getProtection(); + obj[F("menu_mask")] = (uint16_t)(mConfig->sys.protectionMask ); + obj[F("menu_protEn")] = (bool) (strlen(mConfig->sys.adminPwd) > 0); #if defined(ESP32) obj[F("esp_type")] = F("ESP32"); @@ -199,6 +206,7 @@ class RestApi { void getSysInfo(JsonObject obj) { obj[F("ssid")] = mConfig->sys.stationSsid; obj[F("device_name")] = mConfig->sys.deviceName; + obj[F("dark_mode")] = (bool)mConfig->sys.darkMode; obj[F("mac")] = WiFi.macAddress(); obj[F("hostname")] = mConfig->sys.deviceName; @@ -245,15 +253,12 @@ class RestApi { } void getHtmlSystem(JsonObject obj) { - getMenu(obj.createNestedObject(F("menu"))); getSysInfo(obj.createNestedObject(F("system"))); getGeneric(obj.createNestedObject(F("generic"))); obj[F("html")] = F("Factory Reset

Reboot"); - } void getHtmlLogout(JsonObject obj) { - getMenu(obj.createNestedObject(F("menu"))); getGeneric(obj.createNestedObject(F("generic"))); obj[F("refresh")] = 3; obj[F("refresh_url")] = "/"; @@ -261,7 +266,6 @@ class RestApi { } void getHtmlSave(JsonObject obj) { - getMenu(obj.createNestedObject(F("menu"))); getGeneric(obj.createNestedObject(F("generic"))); obj[F("refresh")] = 2; obj[F("refresh_url")] = "/setup"; @@ -269,7 +273,6 @@ class RestApi { } void getReboot(JsonObject obj) { - getMenu(obj.createNestedObject(F("menu"))); getGeneric(obj.createNestedObject(F("generic"))); obj[F("refresh")] = 10; obj[F("refresh_url")] = "/"; @@ -316,6 +319,41 @@ class RestApi { obj[F("rstComStop")] = (bool)mConfig->inst.rstValsCommStop; } + void getInverter(JsonObject obj, uint8_t id) { + Inverter<> *iv = mSys->getInverterByPos(id); + if(NULL != iv) { + record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug); + obj[F("id")] = id; + obj[F("enabled")] = (bool)iv->config->enabled; + obj[F("name")] = String(iv->config->name); + obj[F("serial")] = String(iv->config->serial.u64, HEX); + obj[F("version")] = String(iv->getFwVersion()); + obj[F("power_limit_read")] = ah::round3(iv->actPowerLimit); + obj[F("ts_last_success")] = rec->ts; + + JsonArray ch = obj.createNestedArray("ch"); + + // AC + uint8_t pos; + obj[F("ch_name")][0] = "AC"; + JsonArray ch0 = ch.createNestedArray(); + for (uint8_t fld = 0; fld < sizeof(acList); fld++) { + pos = (iv->getPosByChFld(CH0, acList[fld], rec)); + ch0[fld] = (0xff != pos) ? ah::round3(iv->getValue(pos, rec)) : 0.0; + } + + // DC + for(uint8_t j = 0; j < iv->channels; j ++) { + obj[F("ch_name")][j+1] = iv->config->chName[j]; + JsonArray cur = ch.createNestedArray(); + for (uint8_t fld = 0; fld < sizeof(dcList); fld++) { + pos = (iv->getPosByChFld((j+1), dcList[fld], rec)); + cur[fld] = (0xff != pos) ? ah::round3(iv->getValue(pos, rec)) : 0.0; + } + } + } + } + void getMqtt(JsonObject obj) { obj[F("broker")] = String(mConfig->mqtt.broker); obj[F("port")] = String(mConfig->mqtt.port); @@ -376,64 +414,21 @@ class RestApi { } void getDisplay(JsonObject obj) { - obj[F("disp_type")] = (uint8_t)mConfig->plugin.display.type; - obj[F("disp_pwr")] = (bool)mConfig->plugin.display.pwrSaveAtIvOffline; - obj[F("logo_en")] = (bool)mConfig->plugin.display.logoEn; - obj[F("px_shift")] = (bool)mConfig->plugin.display.pxShift; - obj[F("rot180")] = (bool)mConfig->plugin.display.rot180; - obj[F("contrast")] = (uint8_t)mConfig->plugin.display.contrast; - obj[F("pinDisp0")] = mConfig->plugin.display.pin0; - obj[F("pinDisp1")] = mConfig->plugin.display.pin1; - } - - void getMenu(JsonObject obj) { - uint8_t i = 0; - uint16_t mask = (mApp->getProtection()) ? mConfig->sys.protectionMask : 0; - if(!CHECK_MASK(mask, PROT_MASK_LIVE)) { - obj[F("name")][i] = "Live"; - obj[F("link")][i++] = "/live"; - } - if(!CHECK_MASK(mask, PROT_MASK_SERIAL)) { - obj[F("name")][i] = "Serial / Control"; - obj[F("link")][i++] = "/serial"; - } - if(!CHECK_MASK(mask, PROT_MASK_SETUP)) { - obj[F("name")][i] = "Settings"; - obj[F("link")][i++] = "/setup"; - } - obj[F("name")][i++] = "-"; - obj[F("name")][i] = "REST API"; - obj[F("link")][i] = "/api"; - obj[F("trgt")][i++] = "_blank"; - obj[F("name")][i++] = "-"; - if(!CHECK_MASK(mask, PROT_MASK_UPDATE)) { - obj[F("name")][i] = "Update"; - obj[F("link")][i++] = "/update"; - } - if(!CHECK_MASK(mask, PROT_MASK_SYSTEM)) { - obj[F("name")][i] = "System"; - obj[F("link")][i++] = "/system"; - } - obj[F("name")][i++] = "-"; - obj[F("name")][i] = "Documentation"; - obj[F("link")][i] = "https://ahoydtu.de"; - obj[F("trgt")][i++] = "_blank"; - if(strlen(mConfig->sys.adminPwd) > 0) { - obj[F("name")][i++] = "-"; - if(mApp->getProtection()) { - obj[F("name")][i] = "Login"; - obj[F("link")][i++] = "/login"; - } else { - obj[F("name")][i] = "Logout"; - obj[F("link")][i++] = "/logout"; - } - } + 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_rot")] = (uint8_t)mConfig->plugin.display.rot; + obj[F("disp_cont")] = (uint8_t)mConfig->plugin.display.contrast; + obj[F("disp_clk")] = mConfig->plugin.display.disp_clk; + obj[F("disp_data")] = mConfig->plugin.display.disp_data; + obj[F("disp_cs")] = mConfig->plugin.display.disp_cs; + obj[F("disp_dc")] = mConfig->plugin.display.disp_dc; + obj[F("disp_rst")] = mConfig->plugin.display.disp_reset; + obj[F("disp_bsy")] = mConfig->plugin.display.disp_busy; } void getIndex(JsonObject obj) { - getMenu(obj.createNestedObject(F("menu"))); getGeneric(obj.createNestedObject(F("generic"))); - obj[F("ts_now")] = mApp->getTimestamp(); obj[F("ts_sunrise")] = mApp->getSunrise(); obj[F("ts_sunset")] = mApp->getSunset(); @@ -482,10 +477,9 @@ class RestApi { } void getSetup(JsonObject obj) { - getMenu(obj.createNestedObject(F("menu"))); getGeneric(obj.createNestedObject(F("generic"))); getSysInfo(obj.createNestedObject(F("system"))); - getInverterList(obj.createNestedObject(F("inverter"))); + //getInverterList(obj.createNestedObject(F("inverter"))); getMqtt(obj.createNestedObject(F("mqtt"))); getNtp(obj.createNestedObject(F("ntp"))); getSun(obj.createNestedObject(F("sun"))); @@ -502,14 +496,29 @@ class RestApi { } void getLive(JsonObject obj) { - getMenu(obj.createNestedObject(F("menu"))); getGeneric(obj.createNestedObject(F("generic"))); - JsonArray invArr = obj.createNestedArray(F("inverter")); - obj["refresh_interval"] = mConfig->nrf.sendInterval; + //JsonArray invArr = obj.createNestedArray(F("inverter")); + obj[F("refresh")] = mConfig->nrf.sendInterval; - uint8_t list[] = {FLD_UAC, FLD_IAC, FLD_PAC, FLD_F, FLD_PF, FLD_T, FLD_YT, FLD_YD, FLD_PDC, FLD_EFF, FLD_Q}; + for (uint8_t fld = 0; fld < sizeof(acList); fld++) { + obj[F("ch0_fld_units")][fld] = String(units[fieldUnits[acList[fld]]]); + obj[F("ch0_fld_names")][fld] = String(fields[acList[fld]]); + } + for (uint8_t fld = 0; fld < sizeof(dcList); fld++) { + obj[F("fld_units")][fld] = String(units[fieldUnits[dcList[fld]]]); + obj[F("fld_names")][fld] = String(fields[dcList[fld]]); + } Inverter<> *iv; + for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i ++) { + iv = mSys->getInverterByPos(i); + bool parse = false; + if(NULL != iv) + parse = iv->config->enabled; + obj[F("iv")][i] = parse; + } + + /*Inverter<> *iv; uint8_t pos; for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i ++) { iv = mSys->getInverterByPos(i); @@ -553,7 +562,7 @@ class RestApi { } } } - } + }*/ } void getRecord(JsonObject obj, uint8_t recType) { @@ -632,8 +641,7 @@ class RestApi { mTimezoneOffset = jsonIn[F("val")]; else if(F("discovery_cfg") == jsonIn[F("cmd")]) { mApp->setMqttDiscoveryFlag(); // for homeassistant - } - else { + } else { jsonOut[F("error")] = F("unknown cmd"); return false; } diff --git a/src/web/html/api.js b/src/web/html/api.js index 2e1ffb2b..6e63740d 100644 --- a/src/web/html/api.js +++ b/src/web/html/api.js @@ -33,23 +33,66 @@ iconSuccess = [ /** * GENERIC FUNCTIONS */ +function ml(tagName, ...args) { + var el = document.createElement(tagName); + if(args[0]) { + for(var name in args[0]) { + if(name.indexOf("on") === 0) { + el.addEventListener(name.substr(2).toLowerCase(), args[0][name], false) + } else { + el.setAttribute(name, args[0][name]); + } + } + } + if (!args[1]) { + return el; + } + return nester(el, args[1]) +} -function topnav() { - toggle("topnav"); -} - -function parseMenu(obj) { - var e = document.getElementById("topnav"); - e.innerHTML = ""; - for(var i = 0; i < obj["name"].length; i ++) { - if(obj["name"][i] == "-") - e.appendChild(span("", ["seperator"])); - else { - var l = link(obj["link"][i], obj["name"][i], obj["trgt"][i]); - if(obj["link"][i] == window.location.pathname) - l.classList.add("active"); - e.appendChild(l); +function nester(el, n) { + if (typeof n === "string") { + var t = document.createTextNode(n); + el.appendChild(t); + } else if (n instanceof Array) { + for(var i = 0; i < n.length; i++) { + if (typeof n[i] === "string") { + var t = document.createTextNode(n[i]); + el.appendChild(t); + } else if (n[i] instanceof Node){ + el.appendChild(n[i]); + } } + } else if (n instanceof Node){ + el.appendChild(n) + } + return el; +} + +function topnav() { + toggle("topnav", "mobile"); +} + +function parseNav(obj) { + for(i = 0; i < 10; i++) { + if(i == 2) + continue; + var l = document.getElementById("nav"+i); + if(window.location.pathname == "/" + l.href.split('/').pop()) + l.classList.add("active"); + + if(obj["menu_protEn"]) { + if(obj["menu_prot"]) { + if(0 == i) + l.classList.remove("hide"); + else if(i > 2) { + if(((obj["menu_mask"] >> (i-2)) & 0x01) == 0x00) + l.classList.remove("hide"); + } + } else if(0 != i) + l.classList.remove("hide"); + } else if(i > 1) + l.classList.remove("hide"); } } @@ -60,7 +103,9 @@ function parseVersion(obj) { } function parseESP(obj) { - document.getElementById("esp_type").innerHTML="Board: " + obj["esp_type"]; + document.getElementById("esp_type").append( + document.createTextNode("Board: " + obj["esp_type"]) + ); } function parseRssi(obj) { @@ -69,7 +114,7 @@ function parseRssi(obj) { icon = iconWifi1; else if(obj["wifi_rssi"] <= -70) icon = iconWifi2; - document.getElementById("wifiicon").replaceChildren(svg(icon, 32, 32, "#fff", null, obj["wifi_rssi"])); + document.getElementById("wifiicon").replaceChildren(svg(icon, 32, 32, "wifi", obj["wifi_rssi"])); } function setHide(id, hide) { @@ -82,12 +127,12 @@ function setHide(id, hide) { elm.classList.remove('hide'); } -function toggle(id) { +function toggle(id, cl="hide") { var e = document.getElementById(id); - if(!e.classList.contains("hide")) - e.classList.add("hide"); + if(!e.classList.contains(cl)) + e.classList.add(cl); else - e.classList.remove('hide'); + e.classList.remove(cl); } function getAjax(url, ptr, method="GET", json=null) { @@ -198,11 +243,10 @@ function link(dst, text, target=null) { return a; } -function svg(data=null, w=24, h=24, color="#000", cl=null, tooltip=null) { +function svg(data=null, w=24, h=24, cl=null, tooltip=null) { var s = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); s.setAttribute('width', w); s.setAttribute('height', h); - s.setAttribute('fill', color); s.setAttribute('viewBox', '0 0 16 16'); if(null != cl) s.setAttribute('class', cl); if(null != data) { diff --git a/src/web/html/colorBright.css b/src/web/html/colorBright.css new file mode 100644 index 00000000..929828ca --- /dev/null +++ b/src/web/html/colorBright.css @@ -0,0 +1,27 @@ +:root { + --bg: #fff; + --fg: #000; + --fg2: #fff; + + --info: #0000dd; + --warn: #ff7700; + --success: #009900; + + --input-bg: #eee; + + --nav-bg: #333; + --primary: #006ec0; + --primary-hover: #044e86; + --secondary: #0072c8; + --nav-active: #555; + --footer-bg: #282828; + + --total-head-title: #8e5903; + --total-bg: #b06e04; + --iv-head-title: #1c6800; + --iv-head-bg: #32b004; + --ch-head-title: #003c80; + --ch-head-bg: #006ec0; + --ts-head: #333; + --ts-bg: #555; +} diff --git a/src/web/html/colorDark.css b/src/web/html/colorDark.css new file mode 100644 index 00000000..aa98c862 --- /dev/null +++ b/src/web/html/colorDark.css @@ -0,0 +1,27 @@ +:root { + --bg: #222; + --fg: #ccc; + --fg2: #fff; + + --info: #0072c8; + --warn: #ffaa00; + --success: #00bb00; + + --input-bg: #333; + + --nav-bg: #333; + --primary: #004d87; + --primary-hover: #023155; + --secondary: #0072c8; + --nav-active: #555; + --footer-bg: #282828; + + --total-head-title: #555511; + --total-bg: #666622; + --iv-head-title: #115511; + --iv-head-bg: #226622; + --ch-head-title: #112255; + --ch-head-bg: #223366; + --ts-head: #333; + --ts-bg: #555; +} diff --git a/src/web/html/convert.py b/src/web/html/convert.py index 4a8f1f32..8b67d701 100644 --- a/src/web/html/convert.py +++ b/src/web/html/convert.py @@ -2,10 +2,67 @@ import re import os import gzip import glob - +import shutil +from datetime import date from pathlib import Path +import subprocess + + +def get_git_sha(): + return subprocess.check_output(['git', 'rev-parse', '--short', 'HEAD']).decode('ascii').strip() + +def readVersion(path): + f = open(path, "r") + lines = f.readlines() + f.close() + + today = date.today() + search = ["_MAJOR", "_MINOR", "_PATCH"] + version = today.strftime("%y%m%d") + "_ahoy_" + ver = "" + for line in lines: + if(line.find("VERSION_") != -1): + for s in search: + p = line.find(s) + if(p != -1): + version += line[p+13:].rstrip() + "." + ver += line[p+13:].rstrip() + "." + return ver[:-1] + +def htmlParts(file, header, nav, footer, version): + p = ""; + f = open(file, "r") + lines = f.readlines() + f.close(); + + f = open(header, "r") + h = f.read().strip() + f.close() -def convert2Header(inFile): + f = open(nav, "r") + n = f.read().strip() + f.close() + + f = open(footer, "r") + fo = f.read().strip() + f.close() + + for line in lines: + line = line.replace("{#HTML_HEADER}", h) + line = line.replace("{#HTML_NAV}", n) + line = line.replace("{#HTML_FOOTER}", fo) + p += line + + #placeholders + link = 'GIT SHA: ' + get_git_sha() + ' :: ' + version + '' + p = p.replace("{#VERSION}", version) + p = p.replace("{#VERSION_GIT}", link) + f = open("tmp/" + file, "w") + f.write(p); + f.close(); + return p + +def convert2Header(inFile, version): fileType = inFile.split(".")[1] define = inFile.split(".")[0].upper() define2 = inFile.split(".")[1].upper() @@ -17,14 +74,19 @@ def convert2Header(inFile): Path("html/h").mkdir(exist_ok=True) else: outName = "h/" + inFileVarName + ".h" - Path("h").mkdir(exist_ok=True) + data = "" if fileType == "ico": f = open(inFile, "rb") + data = f.read() + f.close() else: - f = open(inFile, "r") - data = f.read() - f.close() + if fileType == "html": + data = htmlParts(inFile, "includes/header.html", "includes/nav.html", "includes/footer.html", version) + else: + f = open(inFile, "r") + data = f.read() + f.close() if fileType == "css": data = data.replace('\n', '') @@ -53,13 +115,17 @@ def convert2Header(inFile): f.close() # delete all files in the 'h' dir -dir = 'h' +wd = 'h' if os.getcwd()[-4:] != "html": - dir = "web/html/" + dir + wd = "web/html/" + wd -if os.path.exists(dir): - for f in os.listdir(dir): - os.remove(os.path.join(dir, f)) +if os.path.exists(wd): + for f in os.listdir(wd): + os.remove(os.path.join(wd, f)) +wd += "/tmp" +if os.path.exists(wd): + for f in os.listdir(wd): + os.remove(os.path.join(wd, f)) # grab all files with following extensions if os.getcwd()[-4:] != "html": @@ -69,6 +135,11 @@ files_grabbed = [] for files in types: files_grabbed.extend(glob.glob(files)) +Path("h").mkdir(exist_ok=True) +Path("tmp").mkdir(exist_ok=True) # created to check if webpages are valid with all replacements +shutil.copyfile("style.css", "tmp/style.css") +version = readVersion("../../defines.h") + # go throw the array for val in files_grabbed: - convert2Header(val) + convert2Header(val, version) diff --git a/src/web/html/includes/footer.html b/src/web/html/includes/footer.html new file mode 100644 index 00000000..5a2b705b --- /dev/null +++ b/src/web/html/includes/footer.html @@ -0,0 +1,16 @@ + diff --git a/src/web/html/includes/header.html b/src/web/html/includes/header.html new file mode 100644 index 00000000..f38a30f7 --- /dev/null +++ b/src/web/html/includes/header.html @@ -0,0 +1,5 @@ + + + + + diff --git a/src/web/html/includes/nav.html b/src/web/html/includes/nav.html new file mode 100644 index 00000000..f1e1a2bd --- /dev/null +++ b/src/web/html/includes/nav.html @@ -0,0 +1,25 @@ + + + diff --git a/src/web/html/index.html b/src/web/html/index.html index 6bb58432..27a46a9c 100644 --- a/src/web/html/index.html +++ b/src/web/html/index.html @@ -2,36 +2,12 @@ Index - - - + {#HTML_HEADER} -
- AhoyDTU - - - - - -
-
-
+ {#HTML_NAV}
-

Uptime:
ESP-Time: @@ -60,22 +36,7 @@

- + {#HTML_FOOTER} + {#HTML_HEADER}
-
+
-

AhoyDTU

- - +

AhoyDTU

+
+
+
+
- - + {#HTML_FOOTER} diff --git a/src/web/html/serial.html b/src/web/html/serial.html index 71388c00..da9d2816 100644 --- a/src/web/html/serial.html +++ b/src/web/html/serial.html @@ -2,73 +2,66 @@ Serial Console - - - + {#HTML_HEADER} -
- AhoyDTU - - - - - -
-
-
+ {#HTML_NAV}
-
-
- connected: - Uptime: - - -
+
+ +
+
+
connected:
+
Uptime:
+
+ + +
+
+
+

Commands

- - - - - - -
- -
-
+
+
+
Select Inverter
+
+
+
+
Power Limit Command
+
+ +
+
+
+
Power Limit Value
+
+
+
+
+
+
+
+
Control Inverter
+
-
-

Ctrl result: n/a

+
+
+
Ctrl result
+
n/a
- + {#HTML_FOOTER} + {#HTML_HEADER} -
- AhoyDTU - - - - - -
-
-
+ {#HTML_NAV}
+ +
+
Device Host Name - - +
+
Device Name
+
+
+
+
Dark Mode
+
+
+
+
+ System Config +

Pinout

+
+ +

Radio (NRF24L01+)

+
+ +

Radio (CMT2300A)

+
+ +

Serial Console

+
+
print inverter data
+
+
+
+
Serial Debug
+
+
+
+
Interval [s]
+
+
+
-
+
WiFi

Enter the credentials to your prefered WiFi station. After rebooting the device tries to connect with this information.

- -
- - - - - - + +
+
Search Networks
+
+
+ +
+
Avail Networks
+
+ +
+
+
+
SSID
+
+
+
+
Password
+
+
-
+
Static IP (optional)

Leave fields blank for DHCP
- The following fields are parsed in this format: 192.168.1.1 + The following fields are parsed in this format: 192.168.4.1

- - - - - - - - - - +
+
IP Address
+
+
+
+
Submask
+
+
+
+
DNS 1
+
+
+
+
DNS 2
+
+
+
+
Gateway
+
+
-
- Protection - - - +
+ Protection +
+
Admin Password
+
+

Select pages which should be protected by password

@@ -86,163 +133,174 @@
-
+
Inverter -

- -

General

- - - - - - - -
- -
- -
+
+
+
+
+
+
+

General

+
+
+
+
Interval [s]
+
+
+
+
Max retries per Payload
+
+
+
+
Reset values and YieldDay at midnight
+
+
+
+
Reset values when inverter polling stops at sunset
+
+
+
+
Reset values when inverter status is 'not available'
+
+
-
- NTP Server - - - - - - - - +
+ NTP Server +
+
NTP Server / IP
+
+
+
+
NTP Port
+
+
+
+
set system time
+
+ + + +
+
-
- Sunrise & Sunset -

- Use a decimal separator: '.' (dot) for Latitude and Longitude -

- - - - - - -
- -
+
+ Sunrise & Sunset +

Use a decimal separator: '.' (dot) for Latitude and Longitude

+ +
+
Latitude (decimal)
+
+
+
+
Longitude (decimal)
+
+
+
+
Offset (pre sunrise, post sunset)
+
+
+
+
Stop polling inverters during night
+
+
-
- MQTT - - - - - - - - - - +
+ MQTT +
+
Broker / Server IP
+
+
+
+
Port
+
+
+
+
Username (optional)
+
+
+
+
Password (optional)
+
+
+
+
Topic
+
+

Send Inverter data in a fixed interval, even if there is no change. A value of '0' disables the fixed interval. The data is published once it was successfully received from inverter. (default: 0)

- - - - - -
-
- - -
-
- System Config -

Pinout

-
- -

Radio (NRF24L01+)

-
- -

Radio (CMT2300A)

-
- -

Serial Console

- -
- -
- - +
+
Interval [s]
+
+
+
+
Discovery Config (homeassistant)
+
+ + +
+
-
+
Display Config
- -
- -
- -
- -
- - - +
+
+
Turn off while inverters are offline
+
+
+
+
Enable Screensaver (pixel shifting)
+
+
+
+
Contrast
+
+

Pinout

-
- - - +
+
Reboot device after successful save
+
+ + +
+
-
+
ERASE SETTINGS (not WiFi) -
+
Upload / Store JSON Settings
- Download settings (JSON file) (only saved values, passwords will be removed!) + Download settings (JSON file) (only saved values, passwords will be removed!)
- + {#HTML_FOOTER} diff --git a/src/web/html/style.css b/src/web/html/style.css index 38c76f37..ca4b0c9a 100644 --- a/src/web/html/style.css +++ b/src/web/html/style.css @@ -4,26 +4,39 @@ html, body { padding: 0; height: 100%; min-height: 100%; + background-color: var(--bg); + color: var(--fg); } h2 { padding-left: 10px; } +span, li, h3, label, fieldset { + color: var(--fg); +} + +fieldset, input[type=submit], .btn { + border-radius: 4px; +} + +#live span { + color: var(--fg2); +} + .topnav { - background-color: #333; + background-color: var(--nav-bg); position: fixed; top: 0; width: 100%; } .topnav a { - color: #fff; + color: var(--fg2); padding: 14px 14px; text-decoration: none; font-size: 17px; display: block; - height: 20px; } #topnav a { @@ -33,23 +46,26 @@ h2 { .topnav a.icon { top: 0; left: 0; - background: #333; + background: var(--nav-bg); display: block; position: absolute; } .topnav a:hover { - background-color: #044e86 !important; - color: #000; + background-color: var(--primary-hover) !important; } .topnav .info { - color: #fff; + color: var(--fg2); position: absolute; right: 24px; top: 5px; } +.topnav .mobile { + display: none; +} + svg.icon { vertical-align: middle; display: inline-block; @@ -57,8 +73,24 @@ svg.icon { padding: 5px 7px 5px 0px; } +.icon-info { + fill: var(--info); +} + +.icon-warn { + fill: var(--warn); +} + +.icon-success { + fill: var(--success); +} + +.wifi { + fill: var(--fg2); +} + .title { - background-color: #006ec0; + background-color: var(--primary); color: #fff !important; padding-left: 80px !important } @@ -74,7 +106,7 @@ svg.icon { } .topnav .active { - background-color: #555; + background-color: var(--nav-active); } span.seperator { @@ -85,6 +117,197 @@ span.seperator { display: block; } +#content { + max-width: 1140px; +} + +.total-h { + background-color: var(--total-head-title); + color: var(--fg2); +} + +.total-bg { + background-color: var(--total-bg); + color: var(--fg2); +} + +.iv-h { + background-color: var(--iv-head-title); + color: var(--fg2); +} + +.iv-bg { + background-color: var(--iv-head-bg); + color: var(--fg2); +} + +.ch-h { + background-color: var(--ch-head-title); + color: var(--fg2); +} + +.ch-bg { + background-color: var(--ch-head-bg); + color: var(--fg2); +} + +.ts-h { + background-color: var(--ts-head); + color: var(--fg2); +} + +.ts-bg { + background-color: var(--ts-bg); + color: var(--fg2); +} + +.hr { + border-top: 1px solid var(--iv-head-title); + margin: 1rem 0 1rem; +} + +p { + text-align: justify; + font-size: 13pt; + color: var(--fg); +} + +#footer { + background-color: var(--footer-bg); +} + +.row { display: flex; max-width: 100%; flex-wrap: wrap; } +.col { flex: 1 0 0%; } + +.col-1, .col-2, .col-3, .col-4, +.col-5, .col-6, .col-7, .col-8, +.col-9, .col-10, .col-11, .col-12 { flex: 0 0 auto; } + +.col-1 { width: 8.333333333%; } +.col-2 { width: 16.66666667%; } +.col-3 { width: 25%; } +.col-4 { width: 33.33333333%; } +.col-5 { width: 41.66666667%; } +.col-6 { width: 50%; } +.col-7 { width: 58.33333333%; } +.col-8 { width: 66.66666667%; } +.col-9 { width: 75%; } +.col-10 { width: 83.33333333%; } +.col-11 { width: 91.66666667%; } +.col-12 { width: 100%; } + +.p-1 { padding: 0.25rem; } +.p-2 { padding: 0.5rem; } +.p-3 { padding: 1rem; } +.p-4 { padding: 1.5rem; } +.p-5 { padding: 3rem; } + +.px-1 { padding: 0 0.25rem 0 0.25rem; } +.px-2 { padding: 0 0.5rem 0 0.5rem; } +.px-3 { padding: 0 1rem 0 1rem; } +.px-4 { padding: 0 1.5rem 0 1.5rem; } +.px-5 { padding: 0 3rem 0 3rem; } + +.py-1 { padding: 0.25rem 0 0.25rem; } +.py-2 { padding: 0.5rem 0 0.5rem; } +.py-3 { padding: 1rem 0 1rem; } +.py-4 { padding: 1.5rem 0 1.5rem; } +.py-5 { padding: 3rem 0 3rem; } + +.mx-1 { margin: 0 0.25rem 0 0.25rem; } +.mx-2 { margin: 0 0.5rem 0 0.5rem; } +.mx-3 { margin: 0 1rem 0 1rem; } +.mx-4 { margin: 0 1.5rem 0 1.5rem; } +.mx-5 { margin: 0 3rem 0 3rem; } + +.my-1 { margin: 0.25rem 0 0.25rem; } +.my-2 { margin: 0.5rem 0 0.5rem; } +.my-3 { margin: 1rem 0 1rem; } +.my-4 { margin: 1.5rem 0 1.5rem; } +.my-5 { margin: 3rem 0 3rem; } + +.mt-1 { margin-top: 0.25rem } +.mt-2 { margin-top: 0.5rem } +.mt-3 { margin-top: 1rem } +.mt-4 { margin-top: 1.5rem } +.mt-5 { margin-top: 3rem } + +.mb-1 { margin-bottom: 0.25rem } +.mb-2 { margin-bottom: 0.5rem } +.mb-3 { margin-bottom: 1rem } +.mb-4 { margin-bottom: 1.5rem } +.mb-5 { margin-bottom: 3rem } + +.fs-1 { font-size: 3.5rem; } +.fs-2 { font-size: 3rem; } +.fs-3 { font-size: 2.5rem; } +.fs-4 { font-size: 2rem; } +.fs-5 { font-size: 1.75rem; } +.fs-6 { font-size: 1.5rem; } +.fs-7 { font-size: 1.25rem; } +.fs-8 { font-size: 1rem; } +.fs-9 { font-size: 0.75rem; } +.fs-10 { font-size: 0.5rem; } + +.a-r { text-align: right; } +.a-c { text-align: center; } + +.row > * { + padding-left: 0.5rem; + padding-right: 0.5rem; +} + +*, ::after, ::before { + box-sizing: border-box; +} + +/* sm */ +@media(min-width: 768px) { + .col-sm-1 { width: 8.333333333%; } + .col-sm-2 { width: 16.66666667%; } + .col-sm-3 { width: 25%; } + .col-sm-4 { width: 33.33333333%; } + .col-sm-5 { width: 41.66666667%; } + .col-sm-6 { width: 50%; } + .col-sm-7 { width: 58.33333333%; } + .col-sm-8 { width: 66.66666667%; } + .col-sm-9 { width: 75%; } + .col-sm-10 { width: 83.33333333%; } + .col-sm-11 { width: 91.66666667%; } + .col-sm-12 { width: 100%; } + + .mb-sm-1 { margin-bottom: 0.25rem } + .mb-sm-2 { margin-bottom: 0.5rem } + .mb-sm-3 { margin-bottom: 1rem } + .mb-sm-4 { margin-bottom: 1.5rem } + .mb-sm-5 { margin-bottom: 3rem } + + .fs-sm-1 { font-size: 3.5rem; } + .fs-sm-2 { font-size: 3rem; } + .fs-sm-3 { font-size: 2.5rem; } + .fs-sm-4 { font-size: 2rem; } + .fs-sm-5 { font-size: 1.75rem; } + .fs-sm-6 { font-size: 1.5rem; } + .fs-sm-7 { font-size: 1.25rem; } + .fs-sm-8 { font-size: 1rem; } +} + +/* md */ +@media(min-width: 992px) { + .col-md-1 { width: 8.333333333%; } + .col-md-2 { width: 16.66666667%; } + .col-md-3 { width: 25%; } + .col-md-4 { width: 33.33333333%; } + .col-md-5 { width: 41.66666667%; } + .col-md-6 { width: 50%; } + .col-md-7 { width: 58.33333333%; } + .col-md-8 { width: 66.66666667%; } + .col-md-9 { width: 75%; } + .col-md-10 { width: 83.33333333%; } + .col-md-11 { width: 91.66666667%; } + .col-md-12 { width: 100%; } +} + #wrapper { min-height: 100%; } @@ -97,7 +320,6 @@ span.seperator { #footer { height: 121px; margin-top: -121px; - background-color: #555; width: 100%; font-size: 13px; } @@ -131,7 +353,7 @@ span.seperator { } .hide { - display: none; + display: none !important; } @media only screen and (min-width: 992px) { @@ -152,7 +374,7 @@ span.seperator { padding-left: 24px !important; } - .topnav .hide { + .topnav .mobile { display: block; } @@ -172,13 +394,6 @@ span.seperator { } } -/** old CSS below **/ - -p { - text-align: justify; - font-size: 13pt; -} - p.lic, p.lic a { font-size: 8pt; color: #999; @@ -187,11 +402,11 @@ p.lic, p.lic a { .des { margin-top: 20px; font-size: 13pt; - color: #006ec0; + color: var(--secondary); } .s_active, .s_collapsible:hover { - background-color: #044e86; + background-color: var(--primary-hover); color: #fff; } @@ -201,34 +416,34 @@ p.lic, p.lic a { } .s_collapsible { - background-color: #006ec0; + background-color: var(--primary); color: white; cursor: pointer; - padding: 18px; + padding: 12px; width: 100%; border: none; text-align: left; outline: none; font-size: 15px; - margin-bottom: 4px; + margin-bottom: 5px; } .subdes { font-size: 12pt; - color: #006ec0; + color: var(--secondary); margin-left: 7px; } .subsubdes { font-size:12pt; - color:#006ec0; + color:var(--secondary); margin: 0 0 7px 12px; } a:link, a:visited { text-decoration: none; font-size: 13pt; - color: #006ec0; + color: var(--secondary); } a:hover, a:focus { @@ -236,14 +451,14 @@ a:hover, a:focus { } a.btn { - background-color: #006ec0; + background-color: var(--primary); color: #fff; padding: 7px 15px 7px 15px; display: inline-block; } a.btn:hover { - background-color: #044e86 !important; + background-color: var(--primary-hover) !important; } input, select { @@ -251,11 +466,13 @@ input, select { font-size: 13pt; } -input.text, select { - width: 70%; +input[type=text], input[type=password], select, input[type=number] { + width: 100%; box-sizing: border-box; - margin-bottom: 10px; border: 1px solid #ccc; + border-radius: 4px; + background-color: var(--input-bg); + color: var(--fg); } input.sh { @@ -268,7 +485,7 @@ input.btnDel { } input.btn { - background-color: #006ec0; + background-color: var(--primary); color: #fff; border: 0px; padding: 7px 20px 7px 20px; @@ -299,10 +516,6 @@ pre { white-space: pre-wrap; } -fieldset { - margin-bottom: 15px; -} - .left { float: left; } @@ -311,88 +524,11 @@ fieldset { float: right; } -div.ch-iv { - width: 100%; - background-color: #32b004; - display: inline-block; - margin-bottom: 15px; - padding-bottom: 20px; - overflow: auto; -} - -div.ch { - width: 220px; - min-height: 350px; - background-color: #006ec0; - display: inline-block; - margin: 0 20px 10px 0px; - overflow: auto; - padding-bottom: 20px; -} - -div.ch-all { - width: 100%; - background-color: #b06e04; - display: inline-block; - margin-bottom: 15px; - padding-bottom: 20px; - overflow: auto; -} - -div.ch .value, div.ch .info, div.ch .head, div.ch-iv .value, div.ch-iv .info, div.ch-iv .head, div.ch-all .value, div.ch-all .info, div.ch-all .head { - color: #fff; - display: block; - width: 100%; - text-align: center; -} - .subgrp { float: left; width: 220px; } -div.ch .unit, div.ch-iv .unit, div.ch-all .unit { - font-size: 19px; - margin-left: 10px; -} - -div.ch .value, div.ch-iv .value, div.ch-all .value { - margin-top: 20px; - font-size: 24px; -} - -div.ch .info, div.ch-iv .info, div.ch-all .info { - margin-top: 3px; - font-size: 10px; -} - -div.ch .head { - background-color: #003c80; - padding: 10px 0 10px 0; -} - -div.ch-all .head { - background-color: #8e5903; - padding: 10px 0 10px 0; -} - -div.ch-iv .head { - background-color: #1c6800; - padding: 10px 0 10px 0; -} - -div.iv { - max-width: 960px; - margin-bottom: 40px; -} - -div.ts { - font-size: 13px; - background-color: #ddd; - border-top: 7px solid #999; - padding: 7px; -} - div.ModPwr, div.ModName, div.YieldCor { width:70%; display: inline-block; @@ -443,104 +579,55 @@ div.hr { } #login { - width: 300px; + width: 450px; height: 200px; border: 1px solid #ccc; - background-color: #eee; + background-color: var(--input-bg); position: absolute; top: 50%; left: 50%; margin-top: -160px; - margin-left: -150px; -} - -#login .pad { - padding: 20px; -} - -#login .pad input { - width: 100%; - padding: 7px 0 7px 0; - border: 0px; - margin-bottom: 10px; + margin-left: -225px; } .head { - background-color: #006ec0; + background-color: var(--primary); color: #fff; } -.row { display: flex; max-width: 100%; flex-wrap: wrap; } -.col { flex: 1 0 0%; } - -.col-1, .col-2, .col-3, .col-4, -.col-5, .col-6, .col-7, .col-8, -.col-9, .col-10, .col-11, .col-12 { flex: 0 0 auto; } - - -.col-1 { width: 8.333333333%; } -.col-2 { width: 16.66666667%; } -.col-3 { width: 25%; } -.col-4 { width: 33.33333333%; } -.col-5 { width: 41.66666667%; } -.col-6 { width: 50%; } -.col-7 { width: 58.33333333%; } -.col-8 { width: 66.66666667%; } -.col-9 { width: 75%; } -.col-10 { width: 83.33333333%; } -.col-11 { width: 91.66666667%; } -.col-12 { width: 100%; } - -.p-1 { padding: 0.25rem; } -.p-2 { padding: 0.5rem; } -.p-3 { padding: 1rem; } -.p-4 { padding: 1.5rem; } -.p-5 { padding: 3rem; } - -.mt-1 { margin-top: 0.25rem } -.mt-2 { margin-top: 0.5rem } -.mt-3 { margin-top: 1rem } -.mt-4 { margin-top: 1.5rem } -.mt-5 { margin-top: 3rem } -.mb-1 { margin-bottom: 0.25rem } -.mb-2 { margin-bottom: 0.5rem } -.mb-3 { margin-bottom: 1rem } -.mb-4 { margin-bottom: 1.5rem } -.mb-5 { margin-bottom: 3rem } - -.a-r { text-align: right; } -.a-c { text-align: center; } - -/* sm */ -@media(min-width: 768px) { - .col-sm-1 { width: 8.333333333%; } - .col-sm-2 { width: 16.66666667%; } - .col-sm-3 { width: 25%; } - .col-sm-4 { width: 33.33333333%; } - .col-sm-5 { width: 41.66666667%; } - .col-sm-6 { width: 50%; } - .col-sm-7 { width: 58.33333333%; } - .col-sm-8 { width: 66.66666667%; } - .col-sm-9 { width: 75%; } - .col-sm-10 { width: 83.33333333%; } - .col-sm-11 { width: 91.66666667%; } - .col-sm-12 { width: 100%; } +.css-tooltip{ + position: relative; } - -/* md */ -@media(min-width: 992px) { - .col-md-1 { width: 8.333333333%; } - .col-md-2 { width: 16.66666667%; } - .col-md-3 { width: 25%; } - .col-md-4 { width: 33.33333333%; } - .col-md-5 { width: 41.66666667%; } - .col-md-6 { width: 50%; } - .col-md-7 { width: 58.33333333%; } - .col-md-8 { width: 66.66666667%; } - .col-md-9 { width: 75%; } - .col-md-10 { width: 83.33333333%; } - .col-md-11 { width: 91.66666667%; } - .col-md-12 { width: 100%; } +.css-tooltip:hover:after{ + content:attr(data-tooltip); + background:#000; + padding:5px; + border-radius:3px; + display: inline-block; + position: absolute; + transform: translate(-50%,-100%); + margin:0 auto; + color:#FFF; + min-width:100px; + min-width:150px; + top:-5px; + left: 50%; + text-align:center; +} +.css-tooltip:hover:before { + top:-5px; + left: 50%; + border: solid transparent; + content: " "; + height: 0; + width: 0; + position: absolute; + pointer-events: none; + border-color: rgba(0, 0, 0, 0); + border-top-color: #000; + border-width: 5px; + margin-left: -5px; + transform: translate(0,0px); } diff --git a/src/web/html/system.html b/src/web/html/system.html index f67aee30..0f1df319 100644 --- a/src/web/html/system.html +++ b/src/web/html/system.html @@ -2,21 +2,10 @@ System - - - + {#HTML_HEADER} -
- AhoyDTU - - - - - -
-
-
+ {#HTML_NAV}

@@ -26,25 +15,10 @@
                 
- + {#HTML_FOOTER} + {#HTML_HEADER} -
- AhoyDTU - - - - - -
-
-
+ {#HTML_NAV}
-
- - -
-
-
- + {#HTML_FOOTER} diff --git a/src/web/html/visualization.html b/src/web/html/visualization.html index c278dd25..9b6dd5d5 100644 --- a/src/web/html/visualization.html +++ b/src/web/html/visualization.html @@ -2,144 +2,227 @@ Live - - + {#HTML_HEADER} - -
- AhoyDTU - - - - - -
-
-
+ {#HTML_NAV}

Every seconds the values are updated

- + {#HTML_FOOTER}