From 6dbb88e27ecf77a1b6487a559fc6538d6255617d Mon Sep 17 00:00:00 2001 From: lumapu Date: Mon, 6 Mar 2023 22:53:38 +0100 Subject: [PATCH] added ePaper (for ESP32 only!), thx @dAjaY85 #735 improved `/live` margins #732 renamed `var` to `VAr` #732 --- src/CHANGES.md | 5 + src/LICENSE | 7 + src/app.cpp | 38 +- src/app.h | 426 +++---- src/config/settings.h | 826 ++++++------ src/defines.h | 2 +- src/platformio.ini | 7 +- src/plugins/Display/Display.h | 142 +-- src/plugins/Display/Display_Mono.cpp | 205 +-- src/plugins/Display/Display_Mono.h | 49 +- src/plugins/Display/Display_ePaper.cpp | 65 +- src/plugins/Display/Display_ePaper.h | 38 +- src/plugins/Display/imagedata.cpp | 642 ---------- src/plugins/Display/imagedata.h | 332 ++++- src/web/RestApi.h | 1197 +++++++++--------- src/web/html/setup.html | 58 +- src/web/html/style.css | 36 + src/web/html/visualization.html | 5 +- src/web/web.h | 1600 ++++++++++++------------ 19 files changed, 2678 insertions(+), 3002 deletions(-) create mode 100644 src/LICENSE delete mode 100644 src/plugins/Display/imagedata.cpp diff --git a/src/CHANGES.md b/src/CHANGES.md index 043e8578..78d3c728 100644 --- a/src/CHANGES.md +++ b/src/CHANGES.md @@ -2,6 +2,11 @@ (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 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 e6b577bc..d65dbe1d 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -4,14 +4,13 @@ //----------------------------------------------------------------------------- #include "app.h" - #include - #include "utils/sun.h" //----------------------------------------------------------------------------- app::app() : ah::Scheduler() {} + //----------------------------------------------------------------------------- void app::setup() { Serial.begin(115200); @@ -33,16 +32,16 @@ void app::setup() { mSys.enableDebug(); mSys.setup(mConfig->nrf.amplifierPower, mConfig->nrf.pinIrq, mConfig->nrf.pinCe, mConfig->nrf.pinCs); -#if defined(AP_ONLY) + #if defined(AP_ONLY) mInnerLoopCb = std::bind(&app::loopStandard, this); -#else + #else mInnerLoopCb = std::bind(&app::loopWifi, this); -#endif + #endif mWifi.setup(mConfig, &mTimestamp, std::bind(&app::onWifi, this, std::placeholders::_1)); -#if !defined(AP_ONLY) + #if !defined(AP_ONLY) everySec(std::bind(&ahoywifi::tickWifiLoop, &mWifi), "wifiL"); -#endif + #endif mSys.addInverters(&mConfig->inst); @@ -61,15 +60,15 @@ void app::setup() { 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) + // when WiFi is in client mode, then enable mqtt broker + #if !defined(AP_ONLY) mMqttEnabled = (mConfig->mqtt.broker[0] > 0); if (mMqttEnabled) { mMqtt.setup(&mConfig->mqtt, mConfig->sys.deviceName, mVersion, &mSys, &mTimestamp); mMqtt.setSubscriptionCb(std::bind(&app::mqttSubRxCb, this, std::placeholders::_1)); mPayload.addAlarmListener(std::bind(&PubMqttType::alarmEventListener, &mMqtt, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); } -#endif + #endif setupLed(); mWeb.setup(this, &mSys, mConfig); @@ -85,6 +84,7 @@ void app::setup() { regularTickers(); + // DBGPRINTLN("--- end setup"); // DBGPRINTLN(String(ESP.getFreeHeap())); // DBGPRINTLN(String(ESP.getHeapFragmentation())); @@ -214,15 +214,15 @@ void app::tickNtpUpdate(void) { //----------------------------------------------------------------------------- void app::tickCalcSunrise(void) { - if (mSunrise == 0) // on boot/reboot calc sun values for current time + if (mSunrise == 0) // on boot/reboot calc sun values for current time ah::calculateSunriseSunset(mTimestamp, mCalculatedTimezoneOffset, mConfig->sun.lat, mConfig->sun.lon, &mSunrise, &mSunset); - if (mTimestamp > (mSunset + mConfig->sun.offsetSec)) // current time is past communication stop, calc sun values for next day + if (mTimestamp > (mSunset + mConfig->sun.offsetSec)) // current time is past communication stop, calc sun values for next day ah::calculateSunriseSunset(mTimestamp + 86400, mCalculatedTimezoneOffset, mConfig->sun.lat, mConfig->sun.lon, &mSunrise, &mSunset); tickIVCommunication(); - uint32_t nxtTrig = mSunset + mConfig->sun.offsetSec + 60; // set next trigger to communication stop, +60 for safety that it is certain past communication stop + uint32_t nxtTrig = mSunset + mConfig->sun.offsetSec + 60; // set next trigger to communication stop, +60 for safety that it is certain past communication stop onceAt(std::bind(&app::tickCalcSunrise, this), nxtTrig, "Sunri"); if (mMqttEnabled) tickSun(); @@ -230,15 +230,15 @@ void app::tickCalcSunrise(void) { //----------------------------------------------------------------------------- void app::tickIVCommunication(void) { - mIVCommunicationOn = !mConfig->sun.disNightCom; // if sun.disNightCom is false, communication is always on - if (!mIVCommunicationOn) { // inverter communication only during the day + mIVCommunicationOn = !mConfig->sun.disNightCom; // if sun.disNightCom is false, communication is always on + if (!mIVCommunicationOn) { // inverter communication only during the day uint32_t nxtTrig; - if (mTimestamp < (mSunrise - mConfig->sun.offsetSec)) { // current time is before communication start, set next trigger to communication start + if (mTimestamp < (mSunrise - mConfig->sun.offsetSec)) { // current time is before communication start, set next trigger to communication start nxtTrig = mSunrise - mConfig->sun.offsetSec; } else { - if (mTimestamp >= (mSunset + mConfig->sun.offsetSec)) { // current time is past communication stop, nothing to do. Next update will be done at midnight by tickCalcSunrise + if (mTimestamp >= (mSunset + mConfig->sun.offsetSec)) { // current time is past communication stop, nothing to do. Next update will be done at midnight by tickCalcSunrise nxtTrig = 0; - } else { // current time lies within communication start/stop time, set next trigger to communication stop + } else { // current time lies within communication start/stop time, set next trigger to communication stop mIVCommunicationOn = true; nxtTrig = mSunset + mConfig->sun.offsetSec; } @@ -367,7 +367,7 @@ void app::resetSystem(void) { mSendFirst = true; mSunrise = 0; - mSunset = 0; + mSunset = 0; mMqttEnabled = false; diff --git a/src/app.h b/src/app.h index 8e7c5617..b68dc0c4 100644 --- a/src/app.h +++ b/src/app.h @@ -46,229 +46,229 @@ typedef Display DisplayType; class app : public IApp, public ah::Scheduler { public: - app(); - ~app() {} - - void setup(void); - void loop(void); - void loopStandard(void); - void loopWifi(void); - void onWifi(bool gotIp); - void regularTickers(void); - - void handleIntr(void) { - mSys.Radio.handleIntr(); - } - - uint32_t getUptime() { - return Scheduler::getUptime(); - } - - uint32_t getTimestamp() { - return Scheduler::getTimestamp(); - } - - bool saveSettings() { - mShowRebootRequest = true; - return mSettings.saveSettings(); - } - - bool readSettings(const char *path) { - return mSettings.readSettings(path); - } - - bool eraseSettings(bool eraseWifi = false) { - return mSettings.eraseSettings(eraseWifi); - } - - statistics_t *getStatistics() { - return &mStat; - } - - void scanAvailNetworks() { - mWifi.scanAvailNetworks(); - } - - void getAvailNetworks(JsonObject obj) { - mWifi.getAvailNetworks(obj); - } - - void setOnUpdate() { - onWifi(false); - } - - void setRebootFlag() { - once(std::bind(&app::tickReboot, this), 3, "rboot"); - } - - const char *getVersion() { - return mVersion; - } - - uint32_t getSunrise() { - return mSunrise; - } - - uint32_t getSunset() { - return mSunset; - } - - bool getSettingsValid() { - return mSettings.getValid(); - } - - bool getRebootRequestState() { - return mShowRebootRequest; - } - - void setMqttDiscoveryFlag() { - once(std::bind(&PubMqttType::sendDiscoveryConfig, &mMqtt), 1, "disCf"); - } - - void setMqttPowerLimitAck(Inverter<> *iv) { - mMqtt.setPowerLimitAck(iv); - } - - void ivSendHighPrio(Inverter<> *iv) { - if (mIVCommunicationOn) // only send commands if communcation is enabled - mPayload.ivSendHighPrio(iv); - } - - bool getMqttIsConnected() { - return mMqtt.isConnected(); - } - - uint32_t getMqttTxCnt() { - return mMqtt.getTxCnt(); - } - - uint32_t getMqttRxCnt() { - return mMqtt.getRxCnt(); - } - - bool getProtection() { - return mWeb.getProtection(); - } - - uint8_t getIrqPin(void) { - return mConfig->nrf.pinIrq; - } - - String getTimeStr(uint32_t offset = 0) { - char str[10]; - if (0 == mTimestamp) - sprintf(str, "n/a"); - else - sprintf(str, "%02d:%02d:%02d ", hour(mTimestamp + offset), minute(mTimestamp + offset), second(mTimestamp + offset)); - return String(str); - } - - uint32_t getTimezoneOffset() { - return mApi.getTimezoneOffset(); - } - - void getSchedulerInfo(uint8_t *max) { - getStat(max); - } - - void getSchedulerNames(void) { - printSchedulers(); - } - - void setTimestamp(uint32_t newTime) { - DPRINT(DBG_DEBUG, F("setTimestamp: ")); - DBGPRINTLN(String(newTime)); - if (0 == newTime) - mWifi.getNtpTime(); - else - Scheduler::setTimestamp(newTime); - } - - HmSystemType mSys; - - private: - typedef std::function innerLoopCb; - - void resetSystem(void); - - void payloadEventListener(uint8_t cmd) { -#if !defined(AP_ONLY) - if (mMqttEnabled) - mMqtt.payloadEventListener(cmd); -#endif - if (mConfig->plugin.display.type != 0) - mDisplay.payloadEventListener(cmd); - } - - void mqttSubRxCb(JsonObject obj); - - void setupLed(void); - void updateLed(void); - - void tickReboot(void) { - DPRINTLN(DBG_INFO, F("Rebooting...")); - onWifi(false); - ah::Scheduler::resetTicker(); - WiFi.disconnect(); - ESP.restart(); - } - - void tickNtpUpdate(void); - void tickCalcSunrise(void); - void tickIVCommunication(void); - void tickSun(void); - void tickComm(void); - void tickSend(void); - void tickMinute(void); - void tickZeroValues(void); - void tickMidnight(void); - /*void tickSerial(void) { - if(Serial.available() == 0) - return; - - uint8_t buf[80]; - uint8_t len = Serial.readBytes(buf, 80); - DPRINTLN(DBG_INFO, "got serial data, len: " + String(len)); - for(uint8_t i = 0; i < len; i++) { - if((0 != i) && (i % 8 == 0)) - DBGPRINTLN(""); - DBGPRINT(String(buf[i], HEX) + " "); + app(); + ~app() {} + + void setup(void); + void loop(void); + void loopStandard(void); + void loopWifi(void); + void onWifi(bool gotIp); + void regularTickers(void); + + void handleIntr(void) { + mSys.Radio.handleIntr(); } - DBGPRINTLN(""); - }*/ - innerLoopCb mInnerLoopCb; + uint32_t getUptime() { + return Scheduler::getUptime(); + } + + uint32_t getTimestamp() { + return Scheduler::getTimestamp(); + } - bool mShowRebootRequest; - bool mIVCommunicationOn; + bool saveSettings() { + mShowRebootRequest = true; + return mSettings.saveSettings(); + } - ahoywifi mWifi; - WebType mWeb; - RestApiType mApi; - PayloadType mPayload; - MiPayloadType mMiPayload; - PubSerialType mPubSerial; + bool readSettings(const char *path) { + return mSettings.readSettings(path); + } - char mVersion[12]; - settings mSettings; - settings_t *mConfig; + bool eraseSettings(bool eraseWifi = false) { + return mSettings.eraseSettings(eraseWifi); + } - uint8_t mSendLastIvId; - bool mSendFirst; + statistics_t *getStatistics() { + return &mStat; + } - statistics_t mStat; + void scanAvailNetworks() { + mWifi.scanAvailNetworks(); + } - // mqtt - PubMqttType mMqtt; - bool mMqttReconnect; - bool mMqttEnabled; + void getAvailNetworks(JsonObject obj) { + mWifi.getAvailNetworks(obj); + } + + void setOnUpdate() { + onWifi(false); + } + + void setRebootFlag() { + once(std::bind(&app::tickReboot, this), 3, "rboot"); + } + + const char *getVersion() { + return mVersion; + } + + uint32_t getSunrise() { + return mSunrise; + } + + uint32_t getSunset() { + return mSunset; + } + + bool getSettingsValid() { + return mSettings.getValid(); + } + + bool getRebootRequestState() { + return mShowRebootRequest; + } + + void setMqttDiscoveryFlag() { + once(std::bind(&PubMqttType::sendDiscoveryConfig, &mMqtt), 1, "disCf"); + } + + void setMqttPowerLimitAck(Inverter<> *iv) { + mMqtt.setPowerLimitAck(iv); + } + + void ivSendHighPrio(Inverter<> *iv) { + if(mIVCommunicationOn) // only send commands if communcation is enabled + mPayload.ivSendHighPrio(iv); + } - // sun - int32_t mCalculatedTimezoneOffset; - uint32_t mSunrise, mSunset; + bool getMqttIsConnected() { + return mMqtt.isConnected(); + } + + uint32_t getMqttTxCnt() { + return mMqtt.getTxCnt(); + } + + uint32_t getMqttRxCnt() { + return mMqtt.getRxCnt(); + } + + bool getProtection() { + return mWeb.getProtection(); + } + + uint8_t getIrqPin(void) { + return mConfig->nrf.pinIrq; + } + + String getTimeStr(uint32_t offset = 0) { + char str[10]; + if(0 == mTimestamp) + sprintf(str, "n/a"); + else + sprintf(str, "%02d:%02d:%02d ", hour(mTimestamp + offset), minute(mTimestamp + offset), second(mTimestamp + offset)); + return String(str); + } + + uint32_t getTimezoneOffset() { + return mApi.getTimezoneOffset(); + } + + void getSchedulerInfo(uint8_t *max) { + getStat(max); + } + + void getSchedulerNames(void) { + printSchedulers(); + } + + void setTimestamp(uint32_t newTime) { + DPRINT(DBG_DEBUG, F("setTimestamp: ")); + DBGPRINTLN(String(newTime)); + if(0 == newTime) + mWifi.getNtpTime(); + else + Scheduler::setTimestamp(newTime); + } + + HmSystemType mSys; + + private: + typedef std::function innerLoopCb; + + void resetSystem(void); + + void payloadEventListener(uint8_t cmd) { + #if !defined(AP_ONLY) + if (mMqttEnabled) + mMqtt.payloadEventListener(cmd); + #endif + if(mConfig->plugin.display.type != 0) + mDisplay.payloadEventListener(cmd); + } + + void mqttSubRxCb(JsonObject obj); + + void setupLed(void); + void updateLed(void); + + void tickReboot(void) { + DPRINTLN(DBG_INFO, F("Rebooting...")); + onWifi(false); + ah::Scheduler::resetTicker(); + WiFi.disconnect(); + ESP.restart(); + } - // plugins - DisplayType mDisplay; + void tickNtpUpdate(void); + void tickCalcSunrise(void); + void tickIVCommunication(void); + void tickSun(void); + void tickComm(void); + void tickSend(void); + void tickMinute(void); + void tickZeroValues(void); + void tickMidnight(void); + /*void tickSerial(void) { + if(Serial.available() == 0) + return; + + uint8_t buf[80]; + uint8_t len = Serial.readBytes(buf, 80); + DPRINTLN(DBG_INFO, "got serial data, len: " + String(len)); + for(uint8_t i = 0; i < len; i++) { + if((0 != i) && (i % 8 == 0)) + DBGPRINTLN(""); + DBGPRINT(String(buf[i], HEX) + " "); + } + DBGPRINTLN(""); + }*/ + + innerLoopCb mInnerLoopCb; + + bool mShowRebootRequest; + bool mIVCommunicationOn; + + ahoywifi mWifi; + WebType mWeb; + RestApiType mApi; + PayloadType mPayload; + MiPayloadType mMiPayload; + PubSerialType mPubSerial; + + char mVersion[12]; + settings mSettings; + settings_t *mConfig; + + uint8_t mSendLastIvId; + bool mSendFirst; + + statistics_t mStat; + + // mqtt + PubMqttType mMqtt; + bool mMqttReconnect; + bool mMqttEnabled; + + // sun + int32_t mCalculatedTimezoneOffset; + uint32_t mSunrise, mSunset; + + // plugins + DisplayType mDisplay; }; #endif /*__APP_H__*/ diff --git a/src/config/settings.h b/src/config/settings.h index b02b5a61..0e118bde 100644 --- a/src/config/settings.h +++ b/src/config/settings.h @@ -18,32 +18,34 @@ * More info: * https://arduino-esp8266.readthedocs.io/en/latest/filesystem.html#flash-layout * */ -#define DEF_PIN_OFF 255 - -#define PROT_MASK_INDEX 0x0001 -#define PROT_MASK_LIVE 0x0002 -#define PROT_MASK_SERIAL 0x0004 -#define PROT_MASK_SETUP 0x0008 -#define PROT_MASK_UPDATE 0x0010 -#define PROT_MASK_SYSTEM 0x0020 -#define PROT_MASK_API 0x0040 -#define PROT_MASK_MQTT 0x0080 - -#define DEF_PROT_INDEX 0x0001 -#define DEF_PROT_LIVE 0x0000 -#define DEF_PROT_SERIAL 0x0004 -#define DEF_PROT_SETUP 0x0008 -#define DEF_PROT_UPDATE 0x0010 -#define DEF_PROT_SYSTEM 0x0020 -#define DEF_PROT_API 0x0000 -#define DEF_PROT_MQTT 0x0000 +#define DEF_PIN_OFF 255 + + +#define PROT_MASK_INDEX 0x0001 +#define PROT_MASK_LIVE 0x0002 +#define PROT_MASK_SERIAL 0x0004 +#define PROT_MASK_SETUP 0x0008 +#define PROT_MASK_UPDATE 0x0010 +#define PROT_MASK_SYSTEM 0x0020 +#define PROT_MASK_API 0x0040 +#define PROT_MASK_MQTT 0x0080 + +#define DEF_PROT_INDEX 0x0001 +#define DEF_PROT_LIVE 0x0000 +#define DEF_PROT_SERIAL 0x0004 +#define DEF_PROT_SETUP 0x0008 +#define DEF_PROT_UPDATE 0x0010 +#define DEF_PROT_SYSTEM 0x0020 +#define DEF_PROT_API 0x0000 +#define DEF_PROT_MQTT 0x0000 + typedef struct { - uint8_t ip[4]; // ip address - uint8_t mask[4]; // sub mask - uint8_t dns1[4]; // dns 1 - uint8_t dns2[4]; // dns 2 - uint8_t gateway[4]; // standard gateway + uint8_t ip[4]; // ip address + uint8_t mask[4]; // sub mask + uint8_t dns1[4]; // dns 1 + uint8_t dns2[4]; // dns 2 + uint8_t gateway[4]; // standard gateway } cfgIp_t; typedef struct { @@ -121,12 +123,10 @@ typedef struct { typedef struct { uint8_t type; bool pwrSaveAtIvOffline; - bool logoEn; bool pxShift; uint8_t rot; - uint16_t period; - uint16_t wakeUp; - uint16_t sleepAt; + //uint16_t wakeUp; + //uint16_t sleepAt; uint8_t contrast; uint8_t disp_data; uint8_t disp_clk; @@ -141,422 +141,418 @@ typedef struct { } plugins_t; typedef struct { - cfgSys_t sys; - cfgNrf24_t nrf; - cfgNtp_t ntp; - cfgSun_t sun; + cfgSys_t sys; + cfgNrf24_t nrf; + cfgNtp_t ntp; + cfgSun_t sun; cfgSerial_t serial; - cfgMqtt_t mqtt; - cfgLed_t led; - cfgInst_t inst; - plugins_t plugin; - bool valid; + cfgMqtt_t mqtt; + cfgLed_t led; + cfgInst_t inst; + plugins_t plugin; + bool valid; } settings_t; class settings { - public: - settings() {} - - void setup() { - DPRINTLN(DBG_INFO, F("Initializing FS ..")); - - mCfg.valid = false; -#if !defined(ESP32) - LittleFSConfig cfg; - cfg.setAutoFormat(false); - LittleFS.setConfig(cfg); -#define LITTLFS_TRUE -#define LITTLFS_FALSE -#else -#define LITTLFS_TRUE true -#define LITTLFS_FALSE false -#endif - - if (!LittleFS.begin(LITTLFS_FALSE)) { - DPRINTLN(DBG_INFO, F(".. format ..")); - LittleFS.format(); - if (LittleFS.begin(LITTLFS_TRUE)) { - DPRINTLN(DBG_INFO, F(".. success")); - } else { - DPRINTLN(DBG_INFO, F(".. failed")); - } + public: + settings() {} + + void setup() { + DPRINTLN(DBG_INFO, F("Initializing FS ..")); + + mCfg.valid = false; + #if !defined(ESP32) + LittleFSConfig cfg; + cfg.setAutoFormat(false); + LittleFS.setConfig(cfg); + #define LITTLFS_TRUE + #define LITTLFS_FALSE + #else + #define LITTLFS_TRUE true + #define LITTLFS_FALSE false + #endif + + if(!LittleFS.begin(LITTLFS_FALSE)) { + DPRINTLN(DBG_INFO, F(".. format ..")); + LittleFS.format(); + if(LittleFS.begin(LITTLFS_TRUE)) { + DPRINTLN(DBG_INFO, F(".. success")); + } else { + DPRINTLN(DBG_INFO, F(".. failed")); + } - } else - DPRINTLN(DBG_INFO, F(" .. done")); - - readSettings("/settings.json"); - } - - // should be used before OTA - void stop() { - LittleFS.end(); - DPRINTLN(DBG_INFO, F("FS stopped")); - } - - void getPtr(settings_t *&cfg) { - cfg = &mCfg; - } - - bool getValid(void) { - return mCfg.valid; - } - - void getInfo(uint32_t *used, uint32_t *size) { -#if !defined(ESP32) - FSInfo info; - LittleFS.info(info); - *used = info.usedBytes; - *size = info.totalBytes; - - DPRINTLN(DBG_INFO, F("-- FILESYSTEM INFO --")); - DPRINTLN(DBG_INFO, String(info.usedBytes) + F(" of ") + String(info.totalBytes) + F(" used")); -#else - DPRINTLN(DBG_WARN, F("not supported by ESP32")); -#endif - } - - bool readSettings(const char *path) { - loadDefaults(); - File fp = LittleFS.open(path, "r"); - if (!fp) - DPRINTLN(DBG_WARN, F("failed to load json, using default config")); - else { - // DPRINTLN(DBG_INFO, fp.readString()); - // fp.seek(0, SeekSet); - DynamicJsonDocument root(4500); - DeserializationError err = deserializeJson(root, fp); - if (!err && (root.size() > 0)) { - mCfg.valid = true; - jsonWifi(root[F("wifi")]); - jsonNrf(root[F("nrf")]); - jsonNtp(root[F("ntp")]); - jsonSun(root[F("sun")]); - jsonSerial(root[F("serial")]); - jsonMqtt(root[F("mqtt")]); - jsonLed(root[F("led")]); - jsonPlugin(root[F("plugin")]); - jsonInst(root[F("inst")]); - } else { - Serial.println(F("failed to parse json, using default config")); } + else + DPRINTLN(DBG_INFO, F(" .. done")); - fp.close(); + readSettings("/settings.json"); } - return mCfg.valid; - } - - bool saveSettings(void) { - DPRINTLN(DBG_DEBUG, F("save settings")); - File fp = LittleFS.open("/settings.json", "w"); - if (!fp) { - DPRINTLN(DBG_ERROR, F("can't open settings file!")); - return false; + + // should be used before OTA + void stop() { + LittleFS.end(); + DPRINTLN(DBG_INFO, F("FS stopped")); } - DynamicJsonDocument json(5500); - JsonObject root = json.to(); - jsonWifi(root.createNestedObject(F("wifi")), true); - jsonNrf(root.createNestedObject(F("nrf")), true); - jsonNtp(root.createNestedObject(F("ntp")), true); - jsonSun(root.createNestedObject(F("sun")), true); - jsonSerial(root.createNestedObject(F("serial")), true); - jsonMqtt(root.createNestedObject(F("mqtt")), true); - jsonLed(root.createNestedObject(F("led")), true); - jsonPlugin(root.createNestedObject(F("plugin")), true); - jsonInst(root.createNestedObject(F("inst")), true); - - if (0 == serializeJson(root, fp)) { - DPRINTLN(DBG_ERROR, F("can't write settings file!")); - return false; + void getPtr(settings_t *&cfg) { + cfg = &mCfg; } - fp.close(); - - return true; - } - - bool eraseSettings(bool eraseWifi = false) { - if (true == eraseWifi) - return LittleFS.format(); - loadDefaults(!eraseWifi); - return saveSettings(); - } - - private: - void loadDefaults(bool keepWifi = false) { - DPRINTLN(DBG_VERBOSE, F("loadDefaults")); - - cfgSys_t tmp; - if (keepWifi) { - // copy contents which should not be deleted - memset(&tmp.adminPwd, 0, PWD_LEN); - memcpy(&tmp, &mCfg.sys, sizeof(cfgSys_t)); + + bool getValid(void) { + return mCfg.valid; } - // erase all settings and reset to default - 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)); - else { - snprintf(mCfg.sys.stationSsid, SSID_LEN, FB_WIFI_SSID); - snprintf(mCfg.sys.stationPwd, PWD_LEN, FB_WIFI_PWD); + + void getInfo(uint32_t *used, uint32_t *size) { + #if !defined(ESP32) + FSInfo info; + LittleFS.info(info); + *used = info.usedBytes; + *size = info.totalBytes; + + DPRINTLN(DBG_INFO, F("-- FILESYSTEM INFO --")); + DPRINTLN(DBG_INFO, String(info.usedBytes) + F(" of ") + String(info.totalBytes) + F(" used")); + #else + DPRINTLN(DBG_WARN, F("not supported by ESP32")); + #endif } - snprintf(mCfg.sys.deviceName, DEVNAME_LEN, DEF_DEVICE_NAME); - - mCfg.nrf.sendInterval = SEND_INTERVAL; - mCfg.nrf.maxRetransPerPyld = DEF_MAX_RETRANS_PER_PYLD; - mCfg.nrf.pinCs = DEF_CS_PIN; - mCfg.nrf.pinCe = DEF_CE_PIN; - mCfg.nrf.pinIrq = DEF_IRQ_PIN; - mCfg.nrf.amplifierPower = DEF_AMPLIFIERPOWER & 0x03; - - snprintf(mCfg.ntp.addr, NTP_ADDR_LEN, "%s", DEF_NTP_SERVER_NAME); - mCfg.ntp.port = DEF_NTP_PORT; - - mCfg.sun.lat = 0.0; - mCfg.sun.lon = 0.0; - mCfg.sun.disNightCom = false; - mCfg.sun.offsetSec = 0; - - mCfg.serial.interval = SERIAL_INTERVAL; - mCfg.serial.showIv = false; - mCfg.serial.debug = false; - - mCfg.mqtt.port = DEF_MQTT_PORT; - snprintf(mCfg.mqtt.broker, MQTT_ADDR_LEN, "%s", DEF_MQTT_BROKER); - snprintf(mCfg.mqtt.user, MQTT_USER_LEN, "%s", DEF_MQTT_USER); - snprintf(mCfg.mqtt.pwd, MQTT_PWD_LEN, "%s", DEF_MQTT_PWD); - snprintf(mCfg.mqtt.topic, MQTT_TOPIC_LEN, "%s", DEF_MQTT_TOPIC); - mCfg.mqtt.interval = 0; // off - - mCfg.inst.rstYieldMidNight = false; - mCfg.inst.rstValsNotAvail = false; - mCfg.inst.rstValsCommStop = false; - - mCfg.led.led0 = DEF_PIN_OFF; - mCfg.led.led1 = DEF_PIN_OFF; - - 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.rot = 0; - mCfg.plugin.display.period = 10000; - 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) { - char buf[16]; - obj[F("ssid")] = mCfg.sys.stationSsid; - obj[F("pwd")] = mCfg.sys.stationPwd; - 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); - ah::ip2Char(mCfg.sys.ip.dns2, buf); - obj[F("dns2")] = String(buf); - ah::ip2Char(mCfg.sys.ip.gateway, buf); - obj[F("gtwy")] = String(buf); - } else { - snprintf(mCfg.sys.stationSsid, SSID_LEN, "%s", obj[F("ssid")].as()); - snprintf(mCfg.sys.stationPwd, PWD_LEN, "%s", obj[F("pwd")].as()); - 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()); - ah::ip2Arr(mCfg.sys.ip.dns2, obj[F("dns2")].as()); - ah::ip2Arr(mCfg.sys.ip.gateway, obj[F("gtwy")].as()); - - if (mCfg.sys.protectionMask == 0) - 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; + bool readSettings(const char* path) { + loadDefaults(); + File fp = LittleFS.open(path, "r"); + if(!fp) + DPRINTLN(DBG_WARN, F("failed to load json, using default config")); + else { + //DPRINTLN(DBG_INFO, fp.readString()); + //fp.seek(0, SeekSet); + DynamicJsonDocument root(5500); + DeserializationError err = deserializeJson(root, fp); + if(!err && (root.size() > 0)) { + mCfg.valid = true; + jsonWifi(root[F("wifi")]); + jsonNrf(root[F("nrf")]); + jsonNtp(root[F("ntp")]); + jsonSun(root[F("sun")]); + jsonSerial(root[F("serial")]); + jsonMqtt(root[F("mqtt")]); + jsonLed(root[F("led")]); + jsonPlugin(root[F("plugin")]); + jsonInst(root[F("inst")]); + } + else { + Serial.println(F("failed to parse json, using default config")); + } + + fp.close(); + } + return mCfg.valid; } - } - - void jsonNrf(JsonObject obj, bool set = false) { - if (set) { - obj[F("intvl")] = mCfg.nrf.sendInterval; - obj[F("maxRetry")] = mCfg.nrf.maxRetransPerPyld; - obj[F("cs")] = mCfg.nrf.pinCs; - obj[F("ce")] = mCfg.nrf.pinCe; - obj[F("irq")] = mCfg.nrf.pinIrq; - obj[F("pwr")] = mCfg.nrf.amplifierPower; - } else { - mCfg.nrf.sendInterval = obj[F("intvl")]; - mCfg.nrf.maxRetransPerPyld = obj[F("maxRetry")]; - mCfg.nrf.pinCs = obj[F("cs")]; - mCfg.nrf.pinCe = obj[F("ce")]; - mCfg.nrf.pinIrq = obj[F("irq")]; - mCfg.nrf.amplifierPower = obj[F("pwr")]; + + bool saveSettings(void) { + DPRINTLN(DBG_DEBUG, F("save settings")); + File fp = LittleFS.open("/settings.json", "w"); + if(!fp) { + DPRINTLN(DBG_ERROR, F("can't open settings file!")); + return false; + } + + DynamicJsonDocument json(6500); + JsonObject root = json.to(); + jsonWifi(root.createNestedObject(F("wifi")), true); + jsonNrf(root.createNestedObject(F("nrf")), true); + jsonNtp(root.createNestedObject(F("ntp")), true); + jsonSun(root.createNestedObject(F("sun")), true); + jsonSerial(root.createNestedObject(F("serial")), true); + jsonMqtt(root.createNestedObject(F("mqtt")), true); + jsonLed(root.createNestedObject(F("led")), true); + jsonPlugin(root.createNestedObject(F("plugin")), true); + jsonInst(root.createNestedObject(F("inst")), true); + + if(0 == serializeJson(root, fp)) { + DPRINTLN(DBG_ERROR, F("can't write settings file!")); + return false; + } + fp.close(); + + return true; } - } - - void jsonNtp(JsonObject obj, bool set = false) { - if (set) { - obj[F("addr")] = mCfg.ntp.addr; - obj[F("port")] = mCfg.ntp.port; - } else { - snprintf(mCfg.ntp.addr, NTP_ADDR_LEN, "%s", obj[F("addr")].as()); - mCfg.ntp.port = obj[F("port")]; + + bool eraseSettings(bool eraseWifi = false) { + if(true == eraseWifi) + return LittleFS.format(); + loadDefaults(!eraseWifi); + return saveSettings(); } - } - - void jsonSun(JsonObject obj, bool set = false) { - if (set) { - obj[F("lat")] = mCfg.sun.lat; - obj[F("lon")] = mCfg.sun.lon; - obj[F("dis")] = mCfg.sun.disNightCom; - obj[F("offs")] = mCfg.sun.offsetSec; - } else { - mCfg.sun.lat = obj[F("lat")]; - mCfg.sun.lon = obj[F("lon")]; - mCfg.sun.disNightCom = obj[F("dis")]; - mCfg.sun.offsetSec = obj[F("offs")]; + + private: + void loadDefaults(bool keepWifi = false) { + DPRINTLN(DBG_VERBOSE, F("loadDefaults")); + + cfgSys_t tmp; + if(keepWifi) { + // copy contents which should not be deleted + memset(&tmp.adminPwd, 0, PWD_LEN); + memcpy(&tmp, &mCfg.sys, sizeof(cfgSys_t)); + } + // erase all settings and reset to default + 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)); + else { + snprintf(mCfg.sys.stationSsid, SSID_LEN, FB_WIFI_SSID); + snprintf(mCfg.sys.stationPwd, PWD_LEN, FB_WIFI_PWD); + } + + snprintf(mCfg.sys.deviceName, DEVNAME_LEN, DEF_DEVICE_NAME); + + mCfg.nrf.sendInterval = SEND_INTERVAL; + mCfg.nrf.maxRetransPerPyld = DEF_MAX_RETRANS_PER_PYLD; + mCfg.nrf.pinCs = DEF_CS_PIN; + mCfg.nrf.pinCe = DEF_CE_PIN; + mCfg.nrf.pinIrq = DEF_IRQ_PIN; + mCfg.nrf.amplifierPower = DEF_AMPLIFIERPOWER & 0x03; + + snprintf(mCfg.ntp.addr, NTP_ADDR_LEN, "%s", DEF_NTP_SERVER_NAME); + mCfg.ntp.port = DEF_NTP_PORT; + + mCfg.sun.lat = 0.0; + mCfg.sun.lon = 0.0; + mCfg.sun.disNightCom = false; + mCfg.sun.offsetSec = 0; + + mCfg.serial.interval = SERIAL_INTERVAL; + mCfg.serial.showIv = false; + mCfg.serial.debug = false; + + mCfg.mqtt.port = DEF_MQTT_PORT; + snprintf(mCfg.mqtt.broker, MQTT_ADDR_LEN, "%s", DEF_MQTT_BROKER); + snprintf(mCfg.mqtt.user, MQTT_USER_LEN, "%s", DEF_MQTT_USER); + snprintf(mCfg.mqtt.pwd, MQTT_PWD_LEN, "%s", DEF_MQTT_PWD); + snprintf(mCfg.mqtt.topic, MQTT_TOPIC_LEN, "%s", DEF_MQTT_TOPIC); + mCfg.mqtt.interval = 0; // off + + mCfg.inst.rstYieldMidNight = false; + mCfg.inst.rstValsNotAvail = false; + mCfg.inst.rstValsCommStop = false; + + mCfg.led.led0 = DEF_PIN_OFF; + mCfg.led.led1 = DEF_PIN_OFF; + + memset(&mCfg.inst, 0, sizeof(cfgInst_t)); + + mCfg.plugin.display.pwrSaveAtIvOffline = false; + 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) { + char buf[16]; + obj[F("ssid")] = mCfg.sys.stationSsid; + obj[F("pwd")] = mCfg.sys.stationPwd; + 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); + ah::ip2Char(mCfg.sys.ip.dns2, buf); obj[F("dns2")] = String(buf); + ah::ip2Char(mCfg.sys.ip.gateway, buf); obj[F("gtwy")] = String(buf); + } else { + snprintf(mCfg.sys.stationSsid, SSID_LEN, "%s", obj[F("ssid")].as()); + snprintf(mCfg.sys.stationPwd, PWD_LEN, "%s", obj[F("pwd")].as()); + 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()); + ah::ip2Arr(mCfg.sys.ip.dns2, obj[F("dns2")].as()); + ah::ip2Arr(mCfg.sys.ip.gateway, obj[F("gtwy")].as()); + + if(mCfg.sys.protectionMask == 0) + 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; + } } - } - - void jsonSerial(JsonObject obj, bool set = false) { - if (set) { - obj[F("intvl")] = mCfg.serial.interval; - obj[F("show")] = mCfg.serial.showIv; - obj[F("debug")] = mCfg.serial.debug; - } else { - mCfg.serial.interval = obj[F("intvl")]; - mCfg.serial.showIv = obj[F("show")]; - mCfg.serial.debug = obj[F("debug")]; + + void jsonNrf(JsonObject obj, bool set = false) { + if(set) { + obj[F("intvl")] = mCfg.nrf.sendInterval; + obj[F("maxRetry")] = mCfg.nrf.maxRetransPerPyld; + obj[F("cs")] = mCfg.nrf.pinCs; + obj[F("ce")] = mCfg.nrf.pinCe; + obj[F("irq")] = mCfg.nrf.pinIrq; + obj[F("pwr")] = mCfg.nrf.amplifierPower; + } else { + mCfg.nrf.sendInterval = obj[F("intvl")]; + mCfg.nrf.maxRetransPerPyld = obj[F("maxRetry")]; + mCfg.nrf.pinCs = obj[F("cs")]; + mCfg.nrf.pinCe = obj[F("ce")]; + mCfg.nrf.pinIrq = obj[F("irq")]; + mCfg.nrf.amplifierPower = obj[F("pwr")]; + } } - } - - void jsonMqtt(JsonObject obj, bool set = false) { - if (set) { - obj[F("broker")] = mCfg.mqtt.broker; - obj[F("port")] = mCfg.mqtt.port; - obj[F("user")] = mCfg.mqtt.user; - obj[F("pwd")] = mCfg.mqtt.pwd; - obj[F("topic")] = mCfg.mqtt.topic; - obj[F("intvl")] = mCfg.mqtt.interval; - - } else { - mCfg.mqtt.port = obj[F("port")]; - mCfg.mqtt.interval = obj[F("intvl")]; - snprintf(mCfg.mqtt.broker, MQTT_ADDR_LEN, "%s", obj[F("broker")].as()); - snprintf(mCfg.mqtt.user, MQTT_USER_LEN, "%s", obj[F("user")].as()); - snprintf(mCfg.mqtt.pwd, MQTT_PWD_LEN, "%s", obj[F("pwd")].as()); - snprintf(mCfg.mqtt.topic, MQTT_TOPIC_LEN, "%s", obj[F("topic")].as()); + + void jsonNtp(JsonObject obj, bool set = false) { + if(set) { + obj[F("addr")] = mCfg.ntp.addr; + obj[F("port")] = mCfg.ntp.port; + } else { + snprintf(mCfg.ntp.addr, NTP_ADDR_LEN, "%s", obj[F("addr")].as()); + mCfg.ntp.port = obj[F("port")]; + } + } + + void jsonSun(JsonObject obj, bool set = false) { + if(set) { + obj[F("lat")] = mCfg.sun.lat; + obj[F("lon")] = mCfg.sun.lon; + obj[F("dis")] = mCfg.sun.disNightCom; + obj[F("offs")] = mCfg.sun.offsetSec; + } else { + mCfg.sun.lat = obj[F("lat")]; + mCfg.sun.lon = obj[F("lon")]; + mCfg.sun.disNightCom = obj[F("dis")]; + mCfg.sun.offsetSec = obj[F("offs")]; + } } - } - - void jsonLed(JsonObject obj, bool set = false) { - if (set) { - obj[F("0")] = mCfg.led.led0; - obj[F("1")] = mCfg.led.led1; - } else { - mCfg.led.led0 = obj[F("0")]; - mCfg.led.led1 = obj[F("1")]; + + void jsonSerial(JsonObject obj, bool set = false) { + if(set) { + obj[F("intvl")] = mCfg.serial.interval; + obj[F("show")] = mCfg.serial.showIv; + obj[F("debug")] = mCfg.serial.debug; + } else { + mCfg.serial.interval = obj[F("intvl")]; + mCfg.serial.showIv = obj[F("show")]; + mCfg.serial.debug = obj[F("debug")]; + } } - } - - void jsonPlugin(JsonObject obj, bool set = false) { - if (set) { - JsonObject disp = obj.createNestedObject("disp"); - disp[F("type")] = mCfg.plugin.display.type; - disp[F("pwrSafe")] = (bool)mCfg.plugin.display.pwrSaveAtIvOffline; - disp[F("period")] = mCfg.plugin.display.period; - 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("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.period = disp[F("period")]; - 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")]; + + void jsonMqtt(JsonObject obj, bool set = false) { + if(set) { + obj[F("broker")] = mCfg.mqtt.broker; + obj[F("port")] = mCfg.mqtt.port; + obj[F("user")] = mCfg.mqtt.user; + obj[F("pwd")] = mCfg.mqtt.pwd; + obj[F("topic")] = mCfg.mqtt.topic; + obj[F("intvl")] = mCfg.mqtt.interval; + + } else { + mCfg.mqtt.port = obj[F("port")]; + mCfg.mqtt.interval = obj[F("intvl")]; + snprintf(mCfg.mqtt.broker, MQTT_ADDR_LEN, "%s", obj[F("broker")].as()); + snprintf(mCfg.mqtt.user, MQTT_USER_LEN, "%s", obj[F("user")].as()); + snprintf(mCfg.mqtt.pwd, MQTT_PWD_LEN, "%s", obj[F("pwd")].as()); + snprintf(mCfg.mqtt.topic, MQTT_TOPIC_LEN, "%s", obj[F("topic")].as()); + } } - } - - void jsonInst(JsonObject obj, bool set = false) { - if (set) { - obj[F("en")] = (bool)mCfg.inst.enabled; - obj[F("rstMidNight")] = (bool)mCfg.inst.rstYieldMidNight; - obj[F("rstNotAvail")] = (bool)mCfg.inst.rstValsNotAvail; - obj[F("rstComStop")] = (bool)mCfg.inst.rstValsCommStop; - } else { - mCfg.inst.enabled = (bool)obj[F("en")]; - mCfg.inst.rstYieldMidNight = (bool)obj["rstMidNight"]; - mCfg.inst.rstValsNotAvail = (bool)obj["rstNotAvail"]; - mCfg.inst.rstValsCommStop = (bool)obj["rstComStop"]; + + void jsonLed(JsonObject obj, bool set = false) { + if(set) { + obj[F("0")] = mCfg.led.led0; + obj[F("1")] = mCfg.led.led1; + } else { + mCfg.led.led0 = obj[F("0")]; + mCfg.led.led1 = obj[F("1")]; + } } - JsonArray ivArr; - if (set) - ivArr = obj.createNestedArray(F("iv")); - for (uint8_t i = 0; i < MAX_NUM_INVERTERS; i++) { - if (set) - jsonIv(ivArr.createNestedObject(), &mCfg.inst.iv[i], true); - else - jsonIv(obj[F("iv")][i], &mCfg.inst.iv[i]); + void jsonPlugin(JsonObject obj, bool set = false) { + if(set) { + JsonObject disp = obj.createNestedObject("disp"); + disp[F("type")] = mCfg.plugin.display.type; + disp[F("pwrSafe")] = (bool)mCfg.plugin.display.pwrSaveAtIvOffline; + disp[F("pxShift")] = (bool)mCfg.plugin.display.pxShift; + disp[F("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("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.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")]; + } } - } - - void jsonIv(JsonObject obj, cfgIv_t *cfg, bool set = false) { - if (set) { - obj[F("en")] = (bool)cfg->enabled; - obj[F("name")] = cfg->name; - obj[F("sn")] = cfg->serial.u64; - for (uint8_t i = 0; i < 4; i++) { - obj[F("yield")][i] = cfg->yieldCor[i]; - obj[F("pwr")][i] = cfg->chMaxPwr[i]; - obj[F("chName")][i] = cfg->chName[i]; + + void jsonInst(JsonObject obj, bool set = false) { + if(set) { + obj[F("en")] = (bool)mCfg.inst.enabled; + obj[F("rstMidNight")] = (bool)mCfg.inst.rstYieldMidNight; + obj[F("rstNotAvail")] = (bool)mCfg.inst.rstValsNotAvail; + obj[F("rstComStop")] = (bool)mCfg.inst.rstValsCommStop; + } + else { + mCfg.inst.enabled = (bool)obj[F("en")]; + mCfg.inst.rstYieldMidNight = (bool)obj["rstMidNight"]; + mCfg.inst.rstValsNotAvail = (bool)obj["rstNotAvail"]; + mCfg.inst.rstValsCommStop = (bool)obj["rstComStop"]; + } + + JsonArray ivArr; + if(set) + ivArr = obj.createNestedArray(F("iv")); + for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i++) { + if(set) + jsonIv(ivArr.createNestedObject(), &mCfg.inst.iv[i], true); + else + jsonIv(obj[F("iv")][i], &mCfg.inst.iv[i]); } - } else { - cfg->enabled = (bool)obj[F("en")]; - snprintf(cfg->name, MAX_NAME_LENGTH, "%s", obj[F("name")].as()); - cfg->serial.u64 = obj[F("sn")]; - for (uint8_t i = 0; i < 4; i++) { - cfg->yieldCor[i] = obj[F("yield")][i]; - cfg->chMaxPwr[i] = obj[F("pwr")][i]; - snprintf(cfg->chName[i], MAX_NAME_LENGTH, "%s", obj[F("chName")][i].as()); + } + + void jsonIv(JsonObject obj, cfgIv_t *cfg, bool set = false) { + if(set) { + obj[F("en")] = (bool)cfg->enabled; + obj[F("name")] = cfg->name; + obj[F("sn")] = cfg->serial.u64; + for(uint8_t i = 0; i < 4; i++) { + obj[F("yield")][i] = cfg->yieldCor[i]; + obj[F("pwr")][i] = cfg->chMaxPwr[i]; + obj[F("chName")][i] = cfg->chName[i]; + } + } else { + cfg->enabled = (bool)obj[F("en")]; + snprintf(cfg->name, MAX_NAME_LENGTH, "%s", obj[F("name")].as()); + cfg->serial.u64 = obj[F("sn")]; + for(uint8_t i = 0; i < 4; i++) { + cfg->yieldCor[i] = obj[F("yield")][i]; + cfg->chMaxPwr[i] = obj[F("pwr")][i]; + snprintf(cfg->chName[i], MAX_NAME_LENGTH, "%s", obj[F("chName")][i].as()); + } } } - } - settings_t mCfg; + settings_t mCfg; }; #endif /*__SETTINGS_H__*/ diff --git a/src/defines.h b/src/defines.h index 56e02583..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 93 +#define VERSION_PATCH 94 //------------------------------------- typedef struct { diff --git a/src/platformio.ini b/src/platformio.ini index 08460930..e52cf3fb 100644 --- a/src/platformio.ini +++ b/src/platformio.ini @@ -56,9 +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, default + esp8266_exception_decoder [env:esp8266-debug] platform = espressif8266 @@ -101,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 index 8509dacd..c5c9f676 100644 --- a/src/plugins/Display/Display.h +++ b/src/plugins/Display/Display.h @@ -8,72 +8,54 @@ #include "../../utils/helper.h" #include "Display_Mono.h" #include "Display_ePaper.h" -#include "imagedata.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; - } else if (1 < mCfg->type < 11) { - switch (mCfg->rot) { - case 0: - DisplayMono.disp_rotation = U8G2_R0; - break; - case 1: - DisplayMono.disp_rotation = U8G2_R1; - break; - case 2: - DisplayMono.disp_rotation = U8G2_R2; - break; - case 3: - DisplayMono.disp_rotation = U8G2_R3; - break; - } - - DisplayMono.enablePowerSafe = mCfg->pwrSaveAtIvOffline; - DisplayMono.enableScreensaver = mCfg->pxShift; - DisplayMono.contrast = mCfg->contrast; + Display() {} - DisplayMono.init(mCfg->type, mCfg->disp_cs, mCfg->disp_dc, mCfg->disp_reset, mCfg->disp_busy, mCfg->disp_clk, mCfg->disp_data, mVersion); - } else if (mCfg->type > 10) { - DisplayEPaper.displayRotation = mCfg->rot; - counterEPaper = 0; - - DisplayEPaper.init(mCfg->type, mCfg->disp_cs, mCfg->disp_dc, mCfg->disp_reset, mCfg->disp_busy, mCfg->disp_clk, mCfg->disp_data, mVersion); + 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, mVersion); + #endif + } } - } - void payloadEventListener(uint8_t cmd) { - mNewPayload = true; - } + void payloadEventListener(uint8_t cmd) { + mNewPayload = true; + } - void tickerSecond() { - if (mNewPayload || ((++mLoopCnt % 10) == 0)) { - mNewPayload = false; - mLoopCnt = 0; - DataScreen(); + void tickerSecond() { + if (mNewPayload || ((++mLoopCnt % 10) == 0)) { + mNewPayload = false; + mLoopCnt = 0; + DataScreen(); + } } - } - private: - void DataScreen() { - if (mCfg->type == 0) - return; - if (*mUtcTs == 0) - return; + private: + void DataScreen() { + if (mCfg->type == 0) + return; + if (*mUtcTs == 0) + return; - if ((millis() - _lastDisplayUpdate) > mCfg->period) { float totalPower = 0; float totalYieldDay = 0; float totalYieldTotal = 0; @@ -89,37 +71,43 @@ class Display { continue; if (iv->isProducing(*mUtcTs)) - uint8_t isprod = 0; + 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 < 11) { - DisplayMono.loop(totalPower, totalYieldDay, totalYieldTotal, isprod); - } else if (mCfg->type > 10) { - DisplayEPaper.loop(totalPower, totalYieldDay, totalYieldTotal, isprod); - counterEPaper++; + 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 } - _lastDisplayUpdate = millis(); - } - if (counterEPaper > 480) { - DisplayEPaper.fullRefresh(); - counterEPaper = 0; + #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 counterEPaper; - uint32_t _lastDisplayUpdate = 0; + + // 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 index d6caa1a0..af9cd870 100644 --- a/src/plugins/Display/Display_Mono.cpp +++ b/src/plugins/Display/Display_Mono.cpp @@ -1,144 +1,149 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include "Display_Mono.h" -#include - -#include - -#include "WiFi.h" -#include "imagedata.h" - -#ifdef U8X8_HAVE_HW_SPI -#include -#endif -#ifdef U8X8_HAVE_HW_I2C -#include +#ifdef ESP8266 + #include +#elif defined(ESP32) + #include #endif - -std::map> mono_types = { - {1, [](uint8_t reset, uint8_t clock, uint8_t data, uint8_t cs, uint8_t dc) { return new U8G2_PCD8544_84X48_F_4W_SW_SPI(U8G2_R2, clock, data, cs, dc, reset); }}, - {2, [](uint8_t reset, uint8_t clock, uint8_t data, uint8_t cs, uint8_t dc) { return new U8G2_SSD1306_128X64_NONAME_F_HW_I2C(U8G2_R0, reset, clock, data); }}, - {3, [](uint8_t reset, uint8_t clock, uint8_t data, uint8_t cs, uint8_t dc) { return new U8G2_SH1106_128X64_NONAME_F_HW_I2C(U8G2_R0, reset, clock, data); }}, -}; - -DisplayMonoClass::DisplayMonoClass() { -} - -DisplayMonoClass::~DisplayMonoClass() { - delete _display; +#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 DisplayMonoClass::calcLineHeights() { - uint8_t yOff = 0; - for (uint8_t i = 0; i < 4; i++) { - setFont(i); - yOff += (_display->getMaxCharHeight()); - mLineOffsets[i] = yOff; - } -} -inline void DisplayMonoClass::setFont(uint8_t line) { - switch (line) { - case 0: - _display->setFont((_mIsLarge) ? u8g2_font_ncenB14_tr : u8g2_font_logisoso16_tr); - break; - case 3: - _display->setFont(u8g2_font_5x8_tr); - break; - default: - _display->setFont((_mIsLarge) ? u8g2_font_ncenB10_tr : u8g2_font_5x8_tr); - break; - } -} -void DisplayMonoClass::printText(const char* text, uint8_t line, uint8_t dispX = 5) { - if (!_mIsLarge) { - dispX = (line == 0) ? 5 : 0; - } else { - dispX = (line == 0) ? 20 : 5; - } - setFont(line); +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; + } - dispX += enableScreensaver ? (_mExtra % 7) : 0; - _display->drawStr(dispX, mLineOffsets[line], text); -} + mUtcTs = utcTs; -void DisplayMonoClass::init(uint8_t _type, uint8_t _CS, uint8_t _DC, uint8_t _RST, uint8_t _BUSY, uint8_t _SCK, uint8_t _MOSI, const char* version) { - if (0 < _type < 4) { - auto constructor = mono_types[_type]; - _display = constructor(_RST, _SCK, _MOSI, _CS, _DC); - _display->begin(); - _display->setDisplayRotation(disp_rotation); + mDisplay->begin(); - _mIsLarge = (_display->getWidth() > 100); + mIsLarge = (mDisplay->getWidth() > 120); calcLineHeights(); - _display->clearBuffer(); - if (contrast < 255) { - _display->setContrast(contrast); - } + mDisplay->clearBuffer(); + mDisplay->setContrast(mLuminance); printText("AHOY!", 0, 35); printText("ahoydtu.de", 2, 20); printText(version, 3, 46); - _display->sendBuffer(); + mDisplay->sendBuffer(); } } -void DisplayMonoClass::loop(float totalPower, float totalYieldDay, float totalYieldTotal, uint8_t isprod) { - _display->clearBuffer(); +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 - if (contrast < 255) { - _display->setContrast(contrast); - } + mDisplay->setContrast(mLuminance); - //=====> Actual Production ========== if ((totalPower > 0) && (isprod > 0)) { - _display->setPowerSave(false); + mTimeout = DISP_DEFAULT_TIMEOUT; + mDisplay->setPowerSave(false); if (totalPower > 999) { - snprintf(_fmtText, sizeof(_fmtText), "%2.2f kW", (totalPower / 1000)); + snprintf(_fmtText, DISP_FMT_TEXT_LEN, "%2.2f kW", (totalPower / 1000)); } else { - snprintf(_fmtText, sizeof(_fmtText), "%3.0f W", totalPower); + snprintf(_fmtText, DISP_FMT_TEXT_LEN, "%3.0f W", totalPower); } printText(_fmtText, 0); - _previousMillis = millis(); - } - //<======================= - - //=====> Offline =========== - else { - printText("offline", 0); + } else { + printText("offline", 0, 25); // check if it's time to enter power saving mode - if (millis() - _previousMillis >= (_mTimeout * 2)) { - _display->setPowerSave(enablePowerSafe); - } + if (mTimeout == 0) + mDisplay->setPowerSave(mEnPowerSafe); } - //<======================= - //=====> Today & Total Production ======= - snprintf(_fmtText, sizeof(_fmtText), "today: %4.0f Wh", totalYieldDay); + snprintf(_fmtText, DISP_FMT_TEXT_LEN, "today: %4.0f Wh", totalYieldDay); printText(_fmtText, 1); - snprintf(_fmtText, sizeof(_fmtText), "total: %.1f kWh", totalYieldTotal); + snprintf(_fmtText, DISP_FMT_TEXT_LEN, "total: %.1f kWh", totalYieldTotal); printText(_fmtText, 2); - //<======================= - //=====> IP or Date-Time ======== - if (!(_mExtra % 10) && WiFi.localIP()) { - printText(WiFi.localIP().toString().c_str(), 3); + IPAddress ip = WiFi.localIP(); + if (!(_mExtra % 10) && (ip)) { + printText(ip.toString().c_str(), 3); } else if (!(_mExtra % 5)) { - snprintf(_fmtText, sizeof(_fmtText), "#%d Inverter online", isprod); + snprintf(_fmtText, DISP_FMT_TEXT_LEN, "#%d Inverter online", isprod); printText(_fmtText, 3); } else { - time_t now = time(nullptr); - strftime(_fmtText, sizeof(_fmtText), "%d.%m.%Y %H:%M", localtime(&now)); - printText(_fmtText, 3); + if(mIsLarge && (NULL != mUtcTs)) + printText(ah::getDateTimeStr(gTimezone.toLocal(*mUtcTs)).c_str(), 3); + else + printText(ah::getTimeStr(gTimezone.toLocal(*mUtcTs)).c_str(), 3); } - _display->sendBuffer(); + mDisplay->sendBuffer(); _dispY = 0; _mExtra++; } -DisplayMonoClass DisplayMono; \ No newline at end of file +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 index 81375605..cf14f27e 100644 --- a/src/plugins/Display/Display_Mono.h +++ b/src/plugins/Display/Display_Mono.h @@ -2,40 +2,35 @@ #pragma once #include -#define DISP_DEFAULT_TIMEOUT 60000 // in milliseconds +#define DISP_DEFAULT_TIMEOUT 60 // in seconds +#define DISP_FMT_TEXT_LEN 32 -class DisplayMonoClass { +class DisplayMono { public: - DisplayMonoClass(); - ~DisplayMonoClass(); + DisplayMono(); - void init(uint8_t type, uint8_t _CS, uint8_t _DC, uint8_t _RST, uint8_t _BUSY, uint8_t _SCK, uint8_t _MOSI, const char* version); - - void loop(float totalPower, float totalYieldDay, float totalYieldTotal, uint8_t isprod); - - bool enablePowerSafe = true; - bool enableScreensaver = true; - const u8g2_cb_t* disp_rotation = U8G2_R2; - uint8_t contrast = 60; + 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); + void calcLineHeights(); + void setFont(uint8_t line); + void printText(const char* text, uint8_t line, uint8_t dispX = 5); - U8G2* _display; + U8G2* mDisplay; - bool _mIsLarge = false; - uint8_t mLoopCnt; - uint32_t* mUtcTs; - uint8_t mLineOffsets[5]; + bool mEnPowerSafe, mEnScreenSaver; + uint8_t mLuminance; - uint16_t _dispY = 0; - uint32_t _previousMillis = 0; + bool mIsLarge = false; + uint8_t mLoopCnt; + uint32_t* mUtcTs; + uint8_t mLineOffsets[5]; - uint8_t _mExtra; - uint16_t _mTimeout = DISP_DEFAULT_TIMEOUT; // interval at which to power save (milliseconds) - char _fmtText[32]; -}; + uint16_t _dispY; -extern DisplayMonoClass DisplayMono; \ No newline at end of file + 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 index 01b641ba..f27d7cf4 100644 --- a/src/plugins/Display/Display_ePaper.cpp +++ b/src/plugins/Display/Display_ePaper.cpp @@ -1,47 +1,43 @@ #include "Display_ePaper.h" -#include "WiFi.h" +#ifdef ESP8266 + #include +#elif defined(ESP32) + #include +#endif #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 -std::map> _ePaperTypes = { - // DEPG0150BN 200x200, SSD1681, TTGO T5 V2.4.1 - {11, [](uint8_t _CS, uint8_t _DC, uint8_t _RST, uint8_t _BUSY) { return new GxEPD2_BW(GxEPD2_150_BN(_CS, _DC, _RST, _BUSY)); }}, - // GDEW027C44 2.7 " b/w/r 176x264, IL91874 - //{DisplayType_t::ePaper270, [](uint8_t _CS, uint8_t _DC, uint8_t _RST, uint8_t _BUSY) - // F { return new GxEPD2_3C(GxEPD2_270c(_CS, _DC, _RST, _BUSY)); }}, -}; - -DisplayEPaperClass::DisplayEPaperClass() { +DisplayEPaper::DisplayEPaper() { + mDisplayRotation = 2; + mHeadFootPadding = 16; } -DisplayEPaperClass::~DisplayEPaperClass() { - delete _display; -} + //*************************************************************************** -void DisplayEPaperClass::init(uint8_t type, uint8_t _CS, uint8_t _DC, uint8_t _RST, uint8_t _BUSY, uint8_t _SCK, uint8_t _MOSI, const char *version) { - if (type > 3) { +void DisplayEPaper::init(uint8_t type, uint8_t _CS, uint8_t _DC, uint8_t _RST, uint8_t _BUSY, uint8_t _SCK, uint8_t _MOSI, const char *version) { + if (type > 9) { Serial.begin(115200); - auto constructor = _ePaperTypes[type]; - _display = constructor(_CS, _DC, _RST, _BUSY); + _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(displayRotation); + _display->setRotation(mDisplayRotation); _display->setFullWindow(); // Logo _display->fillScreen(GxEPD_BLACK); - _display->drawBitmap(0, 0, AhoyLogo, 200, 200, GxEPD_WHITE); - //_display->drawBitmap(0, 0, OpenDTULogo, 200, 200, GxEPD_WHITE); + _display->drawBitmap(0, 0, logo, 200, 200, GxEPD_WHITE); while (_display->nextPage()) ; @@ -57,8 +53,13 @@ void DisplayEPaperClass::init(uint8_t type, uint8_t _CS, uint8_t _DC, uint8_t _R actualPowerPaged(0, 0, 0, 0); } } + +void DisplayEPaper::config(uint8_t rotation) { + mDisplayRotation = rotation; +} + //*************************************************************************** -void DisplayEPaperClass::fullRefresh() { +void DisplayEPaper::fullRefresh() { // screen complete black _display->fillScreen(GxEPD_BLACK); while (_display->nextPage()) @@ -70,14 +71,14 @@ void DisplayEPaperClass::fullRefresh() { ; } //*************************************************************************** -void DisplayEPaperClass::headlineIP() { +void DisplayEPaper::headlineIP() { int16_t tbx, tby; uint16_t tbw, tbh; _display->setFont(&FreeSans9pt7b); _display->setTextColor(GxEPD_WHITE); - _display->setPartialWindow(0, 0, _display->width(), headfootline); + _display->setPartialWindow(0, 0, _display->width(), mHeadFootPadding); _display->fillScreen(GxEPD_BLACK); do { @@ -89,19 +90,19 @@ void DisplayEPaperClass::headlineIP() { _display->getTextBounds(_fmtText, 0, 0, &tbx, &tby, &tbw, &tbh); uint16_t x = ((_display->width() - tbw) / 2) - tbx; - _display->setCursor(x, (headfootline - 2)); + _display->setCursor(x, (mHeadFootPadding - 2)); _display->println(_fmtText); } while (_display->nextPage()); } //*************************************************************************** -void DisplayEPaperClass::lastUpdatePaged() { +void DisplayEPaper::lastUpdatePaged() { int16_t tbx, tby; uint16_t tbw, tbh; _display->setFont(&FreeSans9pt7b); _display->setTextColor(GxEPD_WHITE); - _display->setPartialWindow(0, _display->height() - headfootline, _display->width(), headfootline); + _display->setPartialWindow(0, _display->height() - mHeadFootPadding, _display->width(), mHeadFootPadding); _display->fillScreen(GxEPD_BLACK); do { time_t now = time(nullptr); @@ -115,14 +116,14 @@ void DisplayEPaperClass::lastUpdatePaged() { } while (_display->nextPage()); } //*************************************************************************** -void DisplayEPaperClass::actualPowerPaged(float _totalPower, float _totalYieldDay, float _totalYieldTotal, uint8_t _isprod) { +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, headfootline, _display->width(), _display->height() - (headfootline * 2)); + _display->setPartialWindow(0, mHeadFootPadding, _display->width(), _display->height() - (mHeadFootPadding * 2)); _display->fillScreen(GxEPD_WHITE); do { if (_totalPower > 9999) { @@ -136,7 +137,7 @@ void DisplayEPaperClass::actualPowerPaged(float _totalPower, float _totalYieldDa } _display->getTextBounds(_fmtText, 0, 0, &tbx, &tby, &tbw, &tbh); x = ((_display->width() - tbw) / 2) - tbx; - _display->setCursor(x, headfootline + tbh + 10); + _display->setCursor(x, mHeadFootPadding + tbh + 10); _display->print(_fmtText); _display->setFont(&FreeSans12pt7b); @@ -162,14 +163,14 @@ void DisplayEPaperClass::actualPowerPaged(float _totalPower, float _totalYieldDa _display->setCursor(_display->width() - 45, y); _display->println("kWh"); - _display->setCursor(0, _display->height() - (headfootline + 10)); + _display->setCursor(0, _display->height() - (mHeadFootPadding + 10)); snprintf(_fmtText, sizeof(_fmtText), "#%d Inverter online", _isprod); _display->println(_fmtText); } while (_display->nextPage()); } //*************************************************************************** -void DisplayEPaperClass::loop(float totalPower, float totalYieldDay, float totalYieldTotal, uint8_t isprod) { +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 @@ -189,4 +190,4 @@ void DisplayEPaperClass::loop(float totalPower, float totalYieldDay, float total _display->powerOff(); } //*************************************************************************** -DisplayEPaperClass DisplayEPaper; \ No newline at end of file +#endif // ESP32 diff --git a/src/plugins/Display/Display_ePaper.h b/src/plugins/Display/Display_ePaper.h index ed8fb025..489ed7c1 100644 --- a/src/plugins/Display/Display_ePaper.h +++ b/src/plugins/Display/Display_ePaper.h @@ -1,6 +1,8 @@ // 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 @@ -21,31 +23,29 @@ #include #include -#include "imagedata.h" - // GDEW027C44 2.7 " b/w/r 176x264, IL91874 // GDEH0154D67 1.54" b/w 200x200 -class DisplayEPaperClass { +class DisplayEPaper { public: - DisplayEPaperClass(); - ~DisplayEPaperClass(); - 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, const char* version); - void loop(float totalPower, float totalYieldDay, float totalYieldTotal, uint8_t isprod); + 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, const char* version); + void config(uint8_t rotation); + void loop(float totalPower, float totalYieldDay, float totalYieldTotal, uint8_t isprod); - uint8_t displayRotation = 2; private: - void headlineIP(); - void actualPowerPaged(float _totalPower, float _totalYieldDay, float _totalYieldTotal, uint8_t _isprod); - void lastUpdatePaged(); - - bool _changed = false; - char _fmtText[35]; - const char* _settedIP; - uint8_t headfootline = 16; - GxEPD2_GFX* _display; + 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; }; -extern DisplayEPaperClass DisplayEPaper; \ No newline at end of file +#endif // ESP32 diff --git a/src/plugins/Display/imagedata.cpp b/src/plugins/Display/imagedata.cpp deleted file mode 100644 index 9cdf352c..00000000 --- a/src/plugins/Display/imagedata.cpp +++ /dev/null @@ -1,642 +0,0 @@ -// GxEPD2_ESP32_ESP8266_WifiData_V1_und_V2 - -#include "imagedata.h" -#if defined(__AVR__) || defined(ARDUINO_ARCH_SAMD) -#include -#elif defined(ESP8266) || defined(ESP32) -#include -#endif - -// 'OpenDTU', 200x200px -const unsigned char OpenDTULogo[] 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, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xfe, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x7f, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, - 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x0f, 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, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xf8, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x3f, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xf8, 0x03, 0x80, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x3f, 0xf0, - 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x7f, 0xf8, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, - 0x7f, 0xfc, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0xff, 0xfc, 0x0f, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0x00, 0xff, 0xfe, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0xff, 0xfe, 0x01, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0x01, 0xff, 0xfe, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x81, 0xff, 0xff, 0x07, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xe3, 0xff, 0xff, 0x9f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x03, 0xff, 0xff, 0xff, - 0xff, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xc0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x07, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0xff, 0xff, - 0xfc, 0x7f, 0xff, 0xfe, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0xfc, 0x0f, 0xe0, 0x0f, 0xc0, 0xfe, 0x03, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x81, - 0xf8, 0x07, 0xc0, 0x0f, 0xc0, 0x3e, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01, 0xf0, 0x07, 0xc0, 0x0f, 0x80, 0x1e, 0x03, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0x01, 0xe0, 0x07, 0xc0, 0x07, 0x80, 0x0f, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01, 0xe0, 0x07, 0xc0, 0x0f, 0x80, - 0x0f, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0x03, 0xe0, 0x07, 0xc0, 0x0f, 0xc0, 0x1f, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x03, 0xe0, 0x0f, 0xe0, - 0x0f, 0xc0, 0x1f, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xfe, 0x03, 0xf0, 0x3f, 0xf8, 0x3f, 0xf0, 0x7f, 0x81, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x07, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0x81, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, - 0x07, 0xf0, 0x7f, 0xfc, 0xff, 0xf0, 0x7f, 0xc0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x07, 0x80, 0x0f, 0xe0, 0x1f, 0xc0, 0x07, - 0xc0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xfc, 0x0f, 0x80, 0x07, 0xe0, 0x0f, 0xc0, 0x03, 0xc0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x0f, 0x00, 0x0f, 0xc0, 0x0f, - 0xc0, 0x03, 0xe0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xf8, 0x0f, 0x00, 0x0f, 0xc0, 0x07, 0xc0, 0x03, 0xe0, 0x7f, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x1f, 0x00, 0x0f, - 0xc0, 0x07, 0xe0, 0x03, 0xe0, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x1f, 0x80, 0x0f, 0xc0, 0x0f, 0xe0, 0x03, 0xe0, 0x7f, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x1f, - 0x80, 0x1f, 0xe0, 0x0f, 0xf0, 0x07, 0xf0, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x3f, 0xe0, 0x3f, 0xf8, 0x3f, 0xfc, 0x1f, 0xf0, - 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xf0, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xf8, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x03, 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, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x1f, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x3f, 0xff, 0xff, 0xff, 0x80, 0x00, - 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x0f, 0xff, 0xf8, 0x00, 0x00, 0x7f, 0x8f, 0xff, - 0xf3, 0xfe, 0x00, 0x0f, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, - 0x00, 0x01, 0xff, 0xf0, 0x00, 0x00, 0x3f, 0x07, 0xff, 0xe1, 0xfc, 0x00, 0x03, 0xff, 0xff, 0xff, - 0xf0, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x7f, 0xe0, 0x00, 0x00, 0x1f, - 0x03, 0xff, 0xc0, 0xf8, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0x00, 0x00, 0x3f, 0xe0, 0x00, 0x00, 0x1f, 0x03, 0xff, 0xc0, 0xf0, 0x00, 0x00, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x1f, 0xf0, 0x00, - 0x00, 0x1f, 0x03, 0xff, 0xc0, 0xe0, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x0f, 0xf8, 0x00, 0x00, 0x7f, 0x03, 0xff, 0xc0, 0xc0, 0x3f, - 0x80, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x03, 0xf0, 0x0f, - 0xff, 0xf0, 0x3f, 0xff, 0x03, 0xff, 0xc0, 0xc0, 0x7f, 0xe0, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x03, 0xfc, 0x07, 0xff, 0xf0, 0x3f, 0xff, 0x03, 0xff, 0xc0, - 0xc0, 0xff, 0xf0, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x03, - 0xfe, 0x03, 0xff, 0xf0, 0x3f, 0xff, 0x03, 0xff, 0xc0, 0x81, 0xff, 0xf0, 0x3f, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x03, 0xff, 0x03, 0xff, 0xf0, 0x3f, 0xff, 0x03, - 0xff, 0xc0, 0x81, 0xff, 0xf8, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0x03, 0xff, 0x03, 0xff, 0xf0, 0x3f, 0xff, 0x03, 0xff, 0xc0, 0x81, 0xff, 0xf8, 0x1f, 0xf0, - 0x00, 0x7f, 0xff, 0xf0, 0x03, 0xff, 0xf0, 0x00, 0x7f, 0xff, 0x03, 0xff, 0x81, 0xff, 0xf0, 0x3f, - 0xff, 0x03, 0xff, 0xc0, 0x83, 0xff, 0xf8, 0x1f, 0xe0, 0x00, 0x1f, 0xff, 0xc0, 0x00, 0xff, 0xe0, - 0x00, 0x1f, 0xff, 0x03, 0xff, 0x81, 0xff, 0xf0, 0x3f, 0xff, 0x03, 0xff, 0xc0, 0x03, 0xff, 0xf8, - 0x1f, 0xe0, 0x00, 0x0f, 0xff, 0x00, 0x00, 0x7f, 0xe0, 0x00, 0x0f, 0xff, 0x03, 0xff, 0x81, 0xff, - 0xf0, 0x3f, 0xff, 0x03, 0xff, 0xc0, 0x03, 0xff, 0xfc, 0x1f, 0xe0, 0x00, 0x07, 0xff, 0x00, 0x00, - 0x3f, 0xe0, 0x00, 0x07, 0xff, 0x03, 0xff, 0x81, 0xff, 0xf0, 0x3f, 0xff, 0x03, 0xff, 0xc0, 0x03, - 0xff, 0xfc, 0x1f, 0xe0, 0x00, 0x03, 0xfe, 0x00, 0x00, 0x3f, 0xe0, 0x00, 0x07, 0xff, 0x03, 0xff, - 0xc0, 0xff, 0xf0, 0x3f, 0xff, 0x03, 0xff, 0xc0, 0x03, 0xff, 0xfc, 0x1f, 0xe0, 0x3c, 0x01, 0xfe, - 0x01, 0xe0, 0x1f, 0xe0, 0x1c, 0x03, 0xff, 0x03, 0xff, 0xc0, 0xff, 0xf0, 0x3f, 0xff, 0x03, 0xff, - 0xc0, 0x03, 0xff, 0xfc, 0x1f, 0xe0, 0x7f, 0x01, 0xfc, 0x07, 0xf8, 0x1f, 0xe0, 0x7f, 0x03, 0xff, - 0x03, 0xff, 0xc0, 0xff, 0xf0, 0x3f, 0xff, 0x03, 0xff, 0xc0, 0x03, 0xff, 0xfc, 0x1f, 0xe0, 0x7f, - 0x80, 0xfc, 0x0f, 0xf8, 0x1f, 0xe0, 0x7f, 0x83, 0xff, 0x03, 0xff, 0xc0, 0xff, 0xf0, 0x3f, 0xff, - 0x03, 0xff, 0xc0, 0x03, 0xff, 0xfc, 0x1f, 0xe0, 0x7f, 0xc0, 0xfc, 0x0f, 0xe0, 0x1f, 0xe0, 0x7f, - 0x83, 0xff, 0x03, 0xff, 0xc0, 0xff, 0xf0, 0x3f, 0xff, 0x03, 0xff, 0xc0, 0x03, 0xff, 0xfc, 0x1f, - 0xe0, 0x7f, 0xc0, 0xfc, 0x0c, 0x00, 0x1f, 0xe0, 0x7f, 0x83, 0xff, 0x03, 0xff, 0xc0, 0xff, 0xf0, - 0x3f, 0xff, 0x03, 0xff, 0xc0, 0x03, 0xff, 0xfc, 0x1f, 0xe0, 0x7f, 0xc0, 0xfc, 0x00, 0x00, 0x1f, - 0xe0, 0x7f, 0x83, 0xff, 0x03, 0xff, 0xc0, 0xff, 0xf0, 0x3f, 0xff, 0x03, 0xff, 0xc0, 0x03, 0xff, - 0xfc, 0x1f, 0xe0, 0x7f, 0xc0, 0xfc, 0x00, 0x00, 0x1f, 0xe0, 0x7f, 0x83, 0xff, 0x03, 0xff, 0xc1, - 0xff, 0xf0, 0x3f, 0xff, 0x03, 0xff, 0xc0, 0x03, 0xff, 0xfc, 0x1f, 0xe0, 0x7f, 0xc0, 0xfc, 0x00, - 0x00, 0x3f, 0xe0, 0x7f, 0x83, 0xff, 0x03, 0xff, 0x81, 0xff, 0xf0, 0x3f, 0xff, 0x03, 0xff, 0xc0, - 0x03, 0xff, 0xf8, 0x1f, 0xe0, 0x7f, 0xc0, 0xfc, 0x00, 0x03, 0xff, 0xe0, 0x7f, 0x83, 0xff, 0x03, - 0xff, 0x81, 0xff, 0xf0, 0x3f, 0xff, 0x03, 0xff, 0xc0, 0x83, 0xff, 0xf8, 0x1f, 0xe0, 0x7f, 0xc0, - 0xfc, 0x00, 0x7f, 0xff, 0xe0, 0x7f, 0x83, 0xff, 0x03, 0xff, 0x81, 0xff, 0xf0, 0x3f, 0xff, 0x03, - 0xff, 0xc0, 0x81, 0xff, 0xf8, 0x1f, 0xe0, 0x7f, 0xc0, 0xfc, 0x07, 0xff, 0xff, 0xe0, 0x7f, 0x83, - 0xff, 0x03, 0xff, 0x81, 0xff, 0xf0, 0x3f, 0xff, 0x03, 0xff, 0xc0, 0x81, 0xff, 0xf8, 0x3f, 0xe0, - 0x7f, 0xc0, 0xfc, 0x0f, 0xff, 0xff, 0xe0, 0x7f, 0x83, 0xff, 0x03, 0xff, 0x03, 0xff, 0xf0, 0x3f, - 0xff, 0x03, 0xff, 0xc0, 0x81, 0xff, 0xf0, 0x3f, 0xe0, 0x7f, 0xc0, 0xfc, 0x0f, 0xff, 0xff, 0xe0, - 0x7f, 0x83, 0xff, 0x03, 0xff, 0x03, 0xff, 0xf0, 0x3f, 0xff, 0x03, 0xff, 0xc0, 0xc0, 0xff, 0xf0, - 0x3f, 0xe0, 0x7f, 0xc0, 0xfc, 0x0f, 0xff, 0xff, 0xe0, 0x7f, 0x83, 0xff, 0x03, 0xfe, 0x07, 0xff, - 0xf0, 0x3f, 0xff, 0x03, 0xff, 0x80, 0xc0, 0x7f, 0xe0, 0x3f, 0xe0, 0x7f, 0x81, 0xfc, 0x0f, 0xff, - 0xff, 0xe0, 0x7f, 0x83, 0xff, 0x03, 0xfc, 0x07, 0xff, 0xf0, 0x3f, 0xff, 0x01, 0xff, 0x81, 0xc0, - 0x3f, 0x80, 0x7f, 0xe0, 0x7e, 0x01, 0xfe, 0x07, 0xf0, 0x7f, 0xe0, 0x7f, 0x83, 0xff, 0x03, 0xe0, - 0x0f, 0xff, 0xf0, 0x3f, 0xff, 0x80, 0xfe, 0x01, 0xe0, 0x00, 0x00, 0x7f, 0xe0, 0x00, 0x03, 0xfe, - 0x00, 0x00, 0x3f, 0xe0, 0x7f, 0x83, 0xff, 0x00, 0x00, 0x0f, 0xff, 0xf0, 0x3f, 0xff, 0x80, 0x00, - 0x03, 0xf0, 0x00, 0x00, 0xff, 0xe0, 0x00, 0x03, 0xff, 0x00, 0x00, 0x3f, 0xe0, 0x7f, 0x83, 0xff, - 0x00, 0x00, 0x1f, 0xff, 0xf0, 0x3f, 0xff, 0xc0, 0x00, 0x03, 0xf8, 0x00, 0x01, 0xff, 0xe0, 0x00, - 0x07, 0xff, 0x00, 0x00, 0x7f, 0xe0, 0x7f, 0x83, 0xff, 0x00, 0x00, 0x3f, 0xff, 0xf0, 0x3f, 0xff, - 0xe0, 0x00, 0x07, 0xfc, 0x00, 0x03, 0xff, 0xe0, 0x00, 0x0f, 0xff, 0x80, 0x00, 0x7f, 0xe0, 0x7f, - 0x83, 0xff, 0x00, 0x00, 0x7f, 0xff, 0xf0, 0x3f, 0xff, 0xf0, 0x00, 0x0f, 0xff, 0x00, 0x0f, 0xff, - 0xe0, 0x00, 0x3f, 0xff, 0xe0, 0x01, 0xff, 0xf0, 0xff, 0x83, 0xff, 0x00, 0x03, 0xff, 0xff, 0xf8, - 0x7f, 0xff, 0xfc, 0x00, 0x3f, 0xff, 0xc0, 0x3f, 0xff, 0xe0, 0x00, 0xff, 0xff, 0xf8, 0x07, 0xff, - 0xf9, 0xff, 0xe7, 0xff, 0xc0, 0x1f, 0xff, 0xff, 0xfc, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xe0, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x7f, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xe0, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x7f, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, - 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xe0, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x7f, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff -}; - -// 'Logo', 200x200px -const unsigned char AhoyLogo[] 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 -}; diff --git a/src/plugins/Display/imagedata.h b/src/plugins/Display/imagedata.h index ebdb918e..baaddec8 100644 --- a/src/plugins/Display/imagedata.h +++ b/src/plugins/Display/imagedata.h @@ -1,5 +1,329 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -#pragma once +// GxEPD2_ESP32_ESP8266_WifiData_V1_und_V2 -extern const unsigned char AhoyLogo[]; -extern const unsigned char OpenDTULogo[]; +#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/web/RestApi.h b/src/web/RestApi.h index 616244fa..e9479a0b 100644 --- a/src/web/RestApi.h +++ b/src/web/RestApi.h @@ -28,649 +28,626 @@ const uint8_t dcList[] = {FLD_UDC, FLD_IDC, FLD_PDC, FLD_YD, FLD_YT, FLD_IRR}; template class RestApi { - public: - RestApi() { - mTimezoneOffset = 0; - mHeapFree = 0; - mHeapFreeBlk = 0; - mHeapFrag = 0; - nr = 0; - } - - void setup(IApp *app, HMSYSTEM *sys, AsyncWebServer *srv, settings_t *config) { - mApp = app; - mSrv = srv; - mSys = sys; - mConfig = config; - mSrv->on("/api", HTTP_GET, std::bind(&RestApi::onApi, this, std::placeholders::_1)); - mSrv->on("/api", HTTP_POST, std::bind(&RestApi::onApiPost, this, std::placeholders::_1)).onBody(std::bind(&RestApi::onApiPostBody, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5)); - - mSrv->on("/get_setup", HTTP_GET, std::bind(&RestApi::onDwnldSetup, this, std::placeholders::_1)); - } - - uint32_t getTimezoneOffset(void) { - return mTimezoneOffset; - } - - void ctrlRequest(JsonObject obj) { - /*char out[128]; - serializeJson(obj, out, 128); - DPRINTLN(DBG_INFO, "RestApi: " + String(out));*/ - DynamicJsonDocument json(128); - JsonObject dummy = json.as(); - if (obj[F("path")] == "ctrl") - setCtrl(obj, dummy); - else if (obj[F("path")] == "setup") - setSetup(obj, dummy); - } - - private: - void onApi(AsyncWebServerRequest *request) { - mHeapFree = ESP.getFreeHeap(); -#ifndef ESP32 - mHeapFreeBlk = ESP.getMaxFreeBlockSize(); - mHeapFrag = ESP.getHeapFragmentation(); -#endif + public: + RestApi() { + mTimezoneOffset = 0; + mHeapFree = 0; + mHeapFreeBlk = 0; + mHeapFrag = 0; + nr = 0; + } + + void setup(IApp *app, HMSYSTEM *sys, AsyncWebServer *srv, settings_t *config) { + mApp = app; + mSrv = srv; + mSys = sys; + mConfig = config; + mSrv->on("/api", HTTP_GET, std::bind(&RestApi::onApi, this, std::placeholders::_1)); + mSrv->on("/api", HTTP_POST, std::bind(&RestApi::onApiPost, this, std::placeholders::_1)).onBody( + std::bind(&RestApi::onApiPostBody, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5)); + + mSrv->on("/get_setup", HTTP_GET, std::bind(&RestApi::onDwnldSetup, this, std::placeholders::_1)); + } + + uint32_t getTimezoneOffset(void) { + return mTimezoneOffset; + } + + void ctrlRequest(JsonObject obj) { + /*char out[128]; + serializeJson(obj, out, 128); + DPRINTLN(DBG_INFO, "RestApi: " + String(out));*/ + DynamicJsonDocument json(128); + JsonObject dummy = json.as(); + if(obj[F("path")] == "ctrl") + setCtrl(obj, dummy); + else if(obj[F("path")] == "setup") + setSetup(obj, dummy); + } + + private: + void onApi(AsyncWebServerRequest *request) { + mHeapFree = ESP.getFreeHeap(); + #ifndef ESP32 + mHeapFreeBlk = ESP.getMaxFreeBlockSize(); + mHeapFrag = ESP.getHeapFragmentation(); + #endif + + AsyncJsonResponse* response = new AsyncJsonResponse(false, 6000); + JsonObject root = response->getRoot(); - AsyncJsonResponse *response = new AsyncJsonResponse(false, 6000); - JsonObject root = response->getRoot(); - - String path = request->url().substring(5); - if (path == "html/system") - getHtmlSystem(root); - else if (path == "html/logout") - getHtmlLogout(root); - else if (path == "html/save") - getHtmlSave(root); - else if (path == "system") - getSysInfo(root); - else if (path == "generic") - getGeneric(root); - else if (path == "reboot") - getReboot(root); - else if (path == "statistics") - getStatistics(root); - else if (path == "inverter/list") - getInverterList(root); - else if (path == "index") - getIndex(root); - else if (path == "setup") - getSetup(root); - else if (path == "setup/networks") - getNetworks(root); - else if (path == "live") - getLive(root); - else if (path == "record/info") - getRecord(root, InverterDevInform_All); - 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 { - 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", "*"); - response->addHeader("Access-Control-Allow-Headers", "content-type"); - response->setLength(); - request->send(response); - } - - void onApiPost(AsyncWebServerRequest *request) { - DPRINTLN(DBG_VERBOSE, "onApiPost"); - } - - void onApiPostBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) { - DPRINTLN(DBG_VERBOSE, "onApiPostBody"); - DynamicJsonDocument json(200); - AsyncJsonResponse *response = new AsyncJsonResponse(false, 200); - JsonObject root = response->getRoot(); - - DeserializationError err = deserializeJson(json, (const char *)data, len); - JsonObject obj = json.as(); - root[F("success")] = (err) ? false : true; - if (!err) { String path = request->url().substring(5); - if (path == "ctrl") - root[F("success")] = setCtrl(obj, root); - else if (path == "setup") - root[F("success")] = setSetup(obj, root); + if(path == "html/system") getHtmlSystem(root); + else if(path == "html/logout") getHtmlLogout(root); + else if(path == "html/save") getHtmlSave(root); + else if(path == "system") getSysInfo(root); + else if(path == "generic") getGeneric(root); + else if(path == "reboot") getReboot(root); + else if(path == "statistics") getStatistics(root); + else if(path == "inverter/list") getInverterList(root); + else if(path == "index") getIndex(root); + else if(path == "setup") getSetup(root); + else if(path == "setup/networks") getNetworks(root); + else if(path == "live") getLive(root); + else if(path == "record/info") getRecord(root, InverterDevInform_All); + 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 { - root[F("success")] = false; - root[F("error")] = "Path not found: " + path; - } - } else { - switch (err.code()) { - case DeserializationError::Ok: - break; - case DeserializationError::InvalidInput: - root[F("error")] = F("Invalid input"); - break; - case DeserializationError::NoMemory: - root[F("error")] = F("Not enough memory"); - break; - default: - root[F("error")] = F("Deserialization failed"); - break; + 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", "*"); + response->addHeader("Access-Control-Allow-Headers", "content-type"); + response->setLength(); + request->send(response); + } + + void onApiPost(AsyncWebServerRequest *request) { + DPRINTLN(DBG_VERBOSE, "onApiPost"); } - response->setLength(); - request->send(response); - } - - void getNotFound(JsonObject obj, String url) { - JsonObject ep = obj.createNestedObject("avail_endpoints"); - ep[F("system")] = url + F("system"); - ep[F("statistics")] = url + F("statistics"); - ep[F("inverter/list")] = url + F("inverter/list"); - ep[F("index")] = url + F("index"); - ep[F("setup")] = url + F("setup"); - ep[F("live")] = url + F("live"); - ep[F("record/info")] = url + F("record/info"); - ep[F("record/alarm")] = url + F("record/alarm"); - ep[F("record/config")] = url + F("record/config"); - 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; charset=utf-8"), "{}"); - } else { - String tmp = fp.readString(); - int i = 0; - // remove all passwords - while (i != -1) { - i = tmp.indexOf("\"pwd\":", i); - if (-1 != i) { - i += 7; - tmp.remove(i, tmp.indexOf("\"", i) - i); + void onApiPostBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) { + DPRINTLN(DBG_VERBOSE, "onApiPostBody"); + DynamicJsonDocument json(200); + AsyncJsonResponse* response = new AsyncJsonResponse(false, 200); + JsonObject root = response->getRoot(); + + DeserializationError err = deserializeJson(json, (const char *)data, len); + JsonObject obj = json.as(); + root[F("success")] = (err) ? false : true; + if(!err) { + String path = request->url().substring(5); + if(path == "ctrl") + root[F("success")] = setCtrl(obj, root); + else if(path == "setup") + root[F("success")] = setSetup(obj, root); + else { + root[F("success")] = false; + root[F("error")] = "Path not found: " + path; } } - response = request->beginResponse(200, F("application/json; charset=utf-8"), tmp); + else { + switch (err.code()) { + case DeserializationError::Ok: break; + case DeserializationError::InvalidInput: root[F("error")] = F("Invalid input"); break; + case DeserializationError::NoMemory: root[F("error")] = F("Not enough memory"); break; + default: root[F("error")] = F("Deserialization failed"); break; + } + } + + response->setLength(); + request->send(response); } - response->addHeader("Content-Type", "application/octet-stream"); - response->addHeader("Content-Description", "File Transfer"); - response->addHeader("Content-Disposition", "attachment; filename=ahoy_setup.json"); - request->send(response); - fp.close(); - } + void getNotFound(JsonObject obj, String url) { + JsonObject ep = obj.createNestedObject("avail_endpoints"); + ep[F("system")] = url + F("system"); + ep[F("statistics")] = url + F("statistics"); + ep[F("inverter/list")] = url + F("inverter/list"); + ep[F("index")] = url + F("index"); + ep[F("setup")] = url + F("setup"); + ep[F("live")] = url + F("live"); + ep[F("record/info")] = url + F("record/info"); + ep[F("record/alarm")] = url + F("record/alarm"); + ep[F("record/config")] = url + F("record/config"); + ep[F("record/live")] = url + F("record/live"); + } - void getGeneric(JsonObject obj) { - 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"); -#else - obj[F("esp_type")] = F("ESP8266"); -#endif - } - - 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; - obj[F("pwd_set")] = (strlen(mConfig->sys.adminPwd) > 0); - obj[F("prot_mask")] = mConfig->sys.protectionMask; - - obj[F("sdk")] = ESP.getSdkVersion(); - obj[F("cpu_freq")] = ESP.getCpuFreqMHz(); - obj[F("heap_free")] = mHeapFree; - obj[F("sketch_total")] = ESP.getFreeSketchSpace(); - obj[F("sketch_used")] = ESP.getSketchSize() / 1024; // in kb - getGeneric(obj); - - getRadio(obj.createNestedObject(F("radio"))); - getStatistics(obj.createNestedObject(F("statistics"))); - -#if defined(ESP32) - obj[F("heap_total")] = ESP.getHeapSize(); - obj[F("chip_revision")] = ESP.getChipRevision(); - obj[F("chip_model")] = ESP.getChipModel(); - obj[F("chip_cores")] = ESP.getChipCores(); - // obj[F("core_version")] = F("n/a"); - // obj[F("flash_size")] = F("n/a"); - // obj[F("heap_frag")] = F("n/a"); - // obj[F("max_free_blk")] = F("n/a"); - // obj[F("reboot_reason")] = F("n/a"); -#else - // obj[F("heap_total")] = F("n/a"); - // obj[F("chip_revision")] = F("n/a"); - // obj[F("chip_model")] = F("n/a"); - // obj[F("chip_cores")] = F("n/a"); - obj[F("core_version")] = ESP.getCoreVersion(); - obj[F("flash_size")] = ESP.getFlashChipRealSize() / 1024; // in kb - obj[F("heap_frag")] = mHeapFrag; - obj[F("max_free_blk")] = mHeapFreeBlk; - obj[F("reboot_reason")] = ESP.getResetReason(); -#endif - // obj[F("littlefs_total")] = LittleFS.totalBytes(); - // obj[F("littlefs_used")] = LittleFS.usedBytes(); - - uint8_t max; - mApp->getSchedulerInfo(&max); - obj[F("schMax")] = max; - } - - void getHtmlSystem(JsonObject obj) { - getSysInfo(obj.createNestedObject(F("system"))); - getGeneric(obj.createNestedObject(F("generic"))); - obj[F("html")] = F("Factory Reset

Reboot"); - } - - void getHtmlLogout(JsonObject obj) { - getGeneric(obj.createNestedObject(F("generic"))); - obj[F("refresh")] = 3; - obj[F("refresh_url")] = "/"; - obj[F("html")] = F("succesfully logged out"); - } - - void getHtmlSave(JsonObject obj) { - getGeneric(obj.createNestedObject(F("generic"))); - obj[F("refresh")] = 2; - obj[F("refresh_url")] = "/setup"; - obj[F("html")] = F("settings succesfully save"); - } - - void getReboot(JsonObject obj) { - getGeneric(obj.createNestedObject(F("generic"))); - obj[F("refresh")] = 10; - obj[F("refresh_url")] = "/"; - obj[F("html")] = F("reboot. Autoreload after 10 seconds"); - } - - void getStatistics(JsonObject obj) { - statistics_t *stat = mApp->getStatistics(); - obj[F("rx_success")] = stat->rxSuccess; - obj[F("rx_fail")] = stat->rxFail; - obj[F("rx_fail_answer")] = stat->rxFailNoAnser; - obj[F("frame_cnt")] = stat->frmCnt; - obj[F("tx_cnt")] = mSys->Radio.mSendCnt; - obj[F("retransmits")] = mSys->Radio.mRetransmits; - } - - void getInverterList(JsonObject obj) { - JsonArray invArr = obj.createNestedArray(F("inverter")); - - Inverter<> *iv; - for (uint8_t i = 0; i < MAX_NUM_INVERTERS; i++) { - iv = mSys->getInverterByPos(i); - if (NULL != iv) { - JsonObject obj2 = invArr.createNestedObject(); - obj2[F("enabled")] = (bool)iv->config->enabled; - obj2[F("id")] = i; - obj2[F("name")] = String(iv->config->name); - obj2[F("serial")] = String(iv->config->serial.u64, HEX); - obj2[F("channels")] = iv->channels; - obj2[F("version")] = String(iv->getFwVersion()); - - for (uint8_t j = 0; j < iv->channels; j++) { - obj2[F("ch_yield_cor")][j] = iv->config->yieldCor[j]; - obj2[F("ch_max_power")][j] = iv->config->chMaxPwr[j]; - obj2[F("ch_name")][j] = iv->config->chName[j]; + 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; charset=utf-8"), "{}"); + } + else { + String tmp = fp.readString(); + int i = 0; + // remove all passwords + while (i != -1) { + i = tmp.indexOf("\"pwd\":", i); + if(-1 != i) { + i+=7; + tmp.remove(i, tmp.indexOf("\"", i)-i); + } } + response = request->beginResponse(200, F("application/json; charset=utf-8"), tmp); } + + response->addHeader("Content-Type", "application/octet-stream"); + response->addHeader("Content-Description", "File Transfer"); + response->addHeader("Content-Disposition", "attachment; filename=ahoy_setup.json"); + request->send(response); + fp.close(); } - obj[F("interval")] = String(mConfig->nrf.sendInterval); - obj[F("retries")] = String(mConfig->nrf.maxRetransPerPyld); - obj[F("max_num_inverters")] = MAX_NUM_INVERTERS; - obj[F("rstMid")] = (bool)mConfig->inst.rstYieldMidNight; - obj[F("rstNAvail")] = (bool)mConfig->inst.rstValsNotAvail; - 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 getGeneric(JsonObject obj) { + 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"); + #else + obj[F("esp_type")] = F("ESP8266"); + #endif } - } - - void getMqtt(JsonObject obj) { - obj[F("broker")] = String(mConfig->mqtt.broker); - obj[F("port")] = String(mConfig->mqtt.port); - obj[F("user")] = String(mConfig->mqtt.user); - obj[F("pwd")] = (strlen(mConfig->mqtt.pwd) > 0) ? F("{PWD}") : String(""); - obj[F("topic")] = String(mConfig->mqtt.topic); - obj[F("interval")] = String(mConfig->mqtt.interval); - } - - void getNtp(JsonObject obj) { - obj[F("addr")] = String(mConfig->ntp.addr); - obj[F("port")] = String(mConfig->ntp.port); - } - - void getSun(JsonObject obj) { - obj[F("lat")] = mConfig->sun.lat ? String(mConfig->sun.lat, 5) : ""; - obj[F("lon")] = mConfig->sun.lat ? String(mConfig->sun.lon, 5) : ""; - obj[F("disnightcom")] = mConfig->sun.disNightCom; - obj[F("offs")] = mConfig->sun.offsetSec; - } - - void getPinout(JsonObject obj) { - obj[F("cs")] = mConfig->nrf.pinCs; - obj[F("ce")] = mConfig->nrf.pinCe; - obj[F("irq")] = mConfig->nrf.pinIrq; - obj[F("led0")] = mConfig->led.led0; - obj[F("led1")] = mConfig->led.led1; - } - - void getRadio(JsonObject obj) { - obj[F("power_level")] = mConfig->nrf.amplifierPower; - obj[F("isconnected")] = mSys->Radio.isChipConnected(); - obj[F("DataRate")] = mSys->Radio.getDataRate(); - obj[F("isPVariant")] = mSys->Radio.isPVariant(); - } - - void getSerial(JsonObject obj) { - obj[F("interval")] = (uint16_t)mConfig->serial.interval; - obj[F("show_live_data")] = mConfig->serial.showIv; - obj[F("debug")] = mConfig->serial.debug; - } - - void getStaticIp(JsonObject obj) { - char buf[16]; - ah::ip2Char(mConfig->sys.ip.ip, buf); - obj[F("ip")] = String(buf); - ah::ip2Char(mConfig->sys.ip.mask, buf); - obj[F("mask")] = String(buf); - ah::ip2Char(mConfig->sys.ip.dns1, buf); - obj[F("dns1")] = String(buf); - ah::ip2Char(mConfig->sys.ip.dns2, buf); - obj[F("dns2")] = String(buf); - ah::ip2Char(mConfig->sys.ip.gateway, buf); - obj[F("gateway")] = String(buf); - } - - 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("px_shift")] = (bool)mConfig->plugin.display.pxShift; - obj[F("rotation")] = (uint8_t)mConfig->plugin.display.rot; - obj[F("period")] = (uint16_t)mConfig->plugin.display.period; - obj[F("contrast")] = (uint8_t)mConfig->plugin.display.contrast; - obj[F("data")] = mConfig->plugin.display.disp_data; - obj[F("clock")] = mConfig->plugin.display.disp_clk; - obj[F("cs")] = mConfig->plugin.display.disp_cs; - obj[F("reset")] = mConfig->plugin.display.disp_reset; - obj[F("busy")] = mConfig->plugin.display.disp_busy; - obj[F("dc")] = mConfig->plugin.display.disp_dc; - } - - void getIndex(JsonObject obj) { - getGeneric(obj.createNestedObject(F("generic"))); - obj[F("ts_now")] = mApp->getTimestamp(); - obj[F("ts_sunrise")] = mApp->getSunrise(); - obj[F("ts_sunset")] = mApp->getSunset(); - obj[F("ts_offset")] = mConfig->sun.offsetSec; - obj[F("disNightComm")] = mConfig->sun.disNightCom; - - JsonArray inv = obj.createNestedArray(F("inverter")); - Inverter<> *iv; - for (uint8_t i = 0; i < MAX_NUM_INVERTERS; i++) { - iv = mSys->getInverterByPos(i); - if (NULL != iv) { - record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug); - JsonObject invObj = inv.createNestedObject(); - invObj[F("enabled")] = (bool)iv->config->enabled; - invObj[F("id")] = i; - invObj[F("name")] = String(iv->config->name); - invObj[F("version")] = String(iv->getFwVersion()); - invObj[F("is_avail")] = iv->isAvailable(mApp->getTimestamp()); - invObj[F("is_producing")] = iv->isProducing(mApp->getTimestamp()); - invObj[F("ts_last_success")] = iv->getLastTs(rec); + + 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; + obj[F("pwd_set")] = (strlen(mConfig->sys.adminPwd) > 0); + obj[F("prot_mask")] = mConfig->sys.protectionMask; + + obj[F("sdk")] = ESP.getSdkVersion(); + obj[F("cpu_freq")] = ESP.getCpuFreqMHz(); + obj[F("heap_free")] = mHeapFree; + obj[F("sketch_total")] = ESP.getFreeSketchSpace(); + obj[F("sketch_used")] = ESP.getSketchSize() / 1024; // in kb + getGeneric(obj); + + getRadio(obj.createNestedObject(F("radio"))); + getStatistics(obj.createNestedObject(F("statistics"))); + + #if defined(ESP32) + obj[F("heap_total")] = ESP.getHeapSize(); + obj[F("chip_revision")] = ESP.getChipRevision(); + obj[F("chip_model")] = ESP.getChipModel(); + obj[F("chip_cores")] = ESP.getChipCores(); + //obj[F("core_version")] = F("n/a"); + //obj[F("flash_size")] = F("n/a"); + //obj[F("heap_frag")] = F("n/a"); + //obj[F("max_free_blk")] = F("n/a"); + //obj[F("reboot_reason")] = F("n/a"); + #else + //obj[F("heap_total")] = F("n/a"); + //obj[F("chip_revision")] = F("n/a"); + //obj[F("chip_model")] = F("n/a"); + //obj[F("chip_cores")] = F("n/a"); + obj[F("core_version")] = ESP.getCoreVersion(); + obj[F("flash_size")] = ESP.getFlashChipRealSize() / 1024; // in kb + obj[F("heap_frag")] = mHeapFrag; + obj[F("max_free_blk")] = mHeapFreeBlk; + obj[F("reboot_reason")] = ESP.getResetReason(); + #endif + //obj[F("littlefs_total")] = LittleFS.totalBytes(); + //obj[F("littlefs_used")] = LittleFS.usedBytes(); + + uint8_t max; + mApp->getSchedulerInfo(&max); + obj[F("schMax")] = max; + } + + void getHtmlSystem(JsonObject obj) { + getSysInfo(obj.createNestedObject(F("system"))); + getGeneric(obj.createNestedObject(F("generic"))); + obj[F("html")] = F("Factory Reset

Reboot"); + } + + void getHtmlLogout(JsonObject obj) { + getGeneric(obj.createNestedObject(F("generic"))); + obj[F("refresh")] = 3; + obj[F("refresh_url")] = "/"; + obj[F("html")] = F("succesfully logged out"); + } + + void getHtmlSave(JsonObject obj) { + getGeneric(obj.createNestedObject(F("generic"))); + obj[F("refresh")] = 2; + obj[F("refresh_url")] = "/setup"; + obj[F("html")] = F("settings succesfully save"); + } + + void getReboot(JsonObject obj) { + getGeneric(obj.createNestedObject(F("generic"))); + obj[F("refresh")] = 10; + obj[F("refresh_url")] = "/"; + obj[F("html")] = F("reboot. Autoreload after 10 seconds"); + } + + void getStatistics(JsonObject obj) { + statistics_t *stat = mApp->getStatistics(); + obj[F("rx_success")] = stat->rxSuccess; + obj[F("rx_fail")] = stat->rxFail; + obj[F("rx_fail_answer")] = stat->rxFailNoAnser; + obj[F("frame_cnt")] = stat->frmCnt; + obj[F("tx_cnt")] = mSys->Radio.mSendCnt; + obj[F("retransmits")] = mSys->Radio.mRetransmits; + } + + void getInverterList(JsonObject obj) { + JsonArray invArr = obj.createNestedArray(F("inverter")); + + Inverter<> *iv; + for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i ++) { + iv = mSys->getInverterByPos(i); + if(NULL != iv) { + JsonObject obj2 = invArr.createNestedObject(); + obj2[F("enabled")] = (bool)iv->config->enabled; + obj2[F("id")] = i; + obj2[F("name")] = String(iv->config->name); + obj2[F("serial")] = String(iv->config->serial.u64, HEX); + obj2[F("channels")] = iv->channels; + obj2[F("version")] = String(iv->getFwVersion()); + + for(uint8_t j = 0; j < iv->channels; j ++) { + obj2[F("ch_yield_cor")][j] = iv->config->yieldCor[j]; + obj2[F("ch_max_power")][j] = iv->config->chMaxPwr[j]; + obj2[F("ch_name")][j] = iv->config->chName[j]; + } + } } + obj[F("interval")] = String(mConfig->nrf.sendInterval); + obj[F("retries")] = String(mConfig->nrf.maxRetransPerPyld); + obj[F("max_num_inverters")] = MAX_NUM_INVERTERS; + obj[F("rstMid")] = (bool)mConfig->inst.rstYieldMidNight; + obj[F("rstNAvail")] = (bool)mConfig->inst.rstValsNotAvail; + obj[F("rstComStop")] = (bool)mConfig->inst.rstValsCommStop; } - JsonArray warn = obj.createNestedArray(F("warnings")); - if (!mSys->Radio.isChipConnected()) - warn.add(F("your NRF24 module can't be reached, check the wiring and pinout")); - else if (!mSys->Radio.isPVariant()) - warn.add(F("your NRF24 module isn't a plus version(+), maybe incompatible")); - if (!mApp->getSettingsValid()) - warn.add(F("your settings are invalid")); - if (mApp->getRebootRequestState()) - warn.add(F("reboot your ESP to apply all your configuration changes")); - if (0 == mApp->getTimestamp()) - warn.add(F("time not set. No communication to inverter possible")); - /*if(0 == mSys->getNumInverters()) - warn.add(F("no inverter configured"));*/ - - if ((!mApp->getMqttIsConnected()) && (String(mConfig->mqtt.broker).length() > 0)) - warn.add(F("MQTT is not connected")); - - JsonArray info = obj.createNestedArray(F("infos")); - if (mApp->getMqttIsConnected()) - info.add(F("MQTT is connected, ") + String(mApp->getMqttTxCnt()) + F(" packets sent, ") + String(mApp->getMqttRxCnt()) + F(" packets received")); - if (mConfig->mqtt.interval > 0) - info.add(F("MQTT publishes in a fixed interval of ") + String(mConfig->mqtt.interval) + F(" seconds")); - } - - void getSetup(JsonObject obj) { - getGeneric(obj.createNestedObject(F("generic"))); - getSysInfo(obj.createNestedObject(F("system"))); - // getInverterList(obj.createNestedObject(F("inverter"))); - getMqtt(obj.createNestedObject(F("mqtt"))); - getNtp(obj.createNestedObject(F("ntp"))); - getSun(obj.createNestedObject(F("sun"))); - getPinout(obj.createNestedObject(F("pinout"))); - getRadio(obj.createNestedObject(F("radio"))); - getSerial(obj.createNestedObject(F("serial"))); - getStaticIp(obj.createNestedObject(F("static_ip"))); - getDisplay(obj.createNestedObject(F("display"))); - } - - void getNetworks(JsonObject obj) { - mApp->getAvailNetworks(obj); - } - - void getLive(JsonObject obj) { - getGeneric(obj.createNestedObject(F("generic"))); - // JsonArray invArr = obj.createNestedArray(F("inverter")); - obj[F("refresh")] = mConfig->nrf.sendInterval; - - 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); + void getInverter(JsonObject obj, uint8_t id) { + Inverter<> *iv = mSys->getInverterByPos(id); if(NULL != iv) { record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug); - JsonObject obj2 = invArr.createNestedObject(); - obj2[F("enabled")] = (bool)iv->config->enabled; - obj2[F("name")] = String(iv->config->name); - obj2[F("channels")] = iv->channels; - obj2[F("power_limit_read")] = ah::round3(iv->actPowerLimit); - //obj2[F("last_alarm")] = String(iv->lastAlarmMsg); - obj2[F("ts_last_success")] = rec->ts; - - JsonArray ch = obj2.createNestedArray("ch"); + 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(); - obj2[F("ch_names")][0] = "AC"; - for (uint8_t fld = 0; fld < sizeof(list); fld++) { - pos = (iv->getPosByChFld(CH0, list[fld], rec)); + 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; - obj[F("ch0_fld_units")][fld] = (0xff != pos) ? String(iv->getUnit(pos, rec)) : notAvail; - obj[F("ch0_fld_names")][fld] = (0xff != pos) ? String(iv->getFieldName(pos, rec)) : notAvail; } - for(uint8_t j = 1; j <= iv->channels; j ++) { - obj2[F("ch_names")][j] = String(iv->config->chName[j-1]); + // 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 k = 0; k < 6; k++) { - switch(k) { - default: pos = (iv->getPosByChFld(j, FLD_UDC, rec)); break; - case 1: pos = (iv->getPosByChFld(j, FLD_IDC, rec)); break; - case 2: pos = (iv->getPosByChFld(j, FLD_PDC, rec)); break; - case 3: pos = (iv->getPosByChFld(j, FLD_YD, rec)); break; - case 4: pos = (iv->getPosByChFld(j, FLD_YT, rec)); break; - case 5: pos = (iv->getPosByChFld(j, FLD_IRR, rec)); break; - } - cur[k] = (0xff != pos) ? ah::round3(iv->getValue(pos, rec)) : 0.0; - if(1 == j) { - obj[F("fld_units")][k] = (0xff != pos) ? String(iv->getUnit(pos, rec)) : notAvail; - obj[F("fld_names")][k] = (0xff != pos) ? String(iv->getFieldName(pos, rec)) : notAvail; - } + 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 getRecord(JsonObject obj, uint8_t recType) { - JsonArray invArr = obj.createNestedArray(F("inverter")); - - Inverter<> *iv; - record_t<> *rec; - uint8_t pos; - for (uint8_t i = 0; i < MAX_NUM_INVERTERS; i++) { - iv = mSys->getInverterByPos(i); - if (NULL != iv) { - rec = iv->getRecordStruct(recType); - JsonArray obj2 = invArr.createNestedArray(); - for (uint8_t j = 0; j < rec->length; j++) { - byteAssign_t *assign = iv->getByteAssign(j, rec); - pos = (iv->getPosByChFld(assign->ch, assign->fieldId, rec)); - obj2[j]["fld"] = (0xff != pos) ? String(iv->getFieldName(pos, rec)) : notAvail; - obj2[j]["unit"] = (0xff != pos) ? String(iv->getUnit(pos, rec)) : notAvail; - obj2[j]["val"] = (0xff != pos) ? String(iv->getValue(pos, rec)) : notAvail; + } + + void getMqtt(JsonObject obj) { + obj[F("broker")] = String(mConfig->mqtt.broker); + obj[F("port")] = String(mConfig->mqtt.port); + obj[F("user")] = String(mConfig->mqtt.user); + obj[F("pwd")] = (strlen(mConfig->mqtt.pwd) > 0) ? F("{PWD}") : String(""); + obj[F("topic")] = String(mConfig->mqtt.topic); + obj[F("interval")] = String(mConfig->mqtt.interval); + } + + void getNtp(JsonObject obj) { + obj[F("addr")] = String(mConfig->ntp.addr); + obj[F("port")] = String(mConfig->ntp.port); + } + + void getSun(JsonObject obj) { + obj[F("lat")] = mConfig->sun.lat ? String(mConfig->sun.lat, 5) : ""; + obj[F("lon")] = mConfig->sun.lat ? String(mConfig->sun.lon, 5) : ""; + obj[F("disnightcom")] = mConfig->sun.disNightCom; + obj[F("offs")] = mConfig->sun.offsetSec; + } + + void getPinout(JsonObject obj) { + obj[F("cs")] = mConfig->nrf.pinCs; + obj[F("ce")] = mConfig->nrf.pinCe; + obj[F("irq")] = mConfig->nrf.pinIrq; + obj[F("led0")] = mConfig->led.led0; + obj[F("led1")] = mConfig->led.led1; + } + + void getRadio(JsonObject obj) { + obj[F("power_level")] = mConfig->nrf.amplifierPower; + obj[F("isconnected")] = mSys->Radio.isChipConnected(); + obj[F("DataRate")] = mSys->Radio.getDataRate(); + obj[F("isPVariant")] = mSys->Radio.isPVariant(); + } + + void getSerial(JsonObject obj) { + obj[F("interval")] = (uint16_t)mConfig->serial.interval; + obj[F("show_live_data")] = mConfig->serial.showIv; + obj[F("debug")] = mConfig->serial.debug; + } + + void getStaticIp(JsonObject obj) { + char buf[16]; + ah::ip2Char(mConfig->sys.ip.ip, buf); obj[F("ip")] = String(buf); + ah::ip2Char(mConfig->sys.ip.mask, buf); obj[F("mask")] = String(buf); + ah::ip2Char(mConfig->sys.ip.dns1, buf); obj[F("dns1")] = String(buf); + ah::ip2Char(mConfig->sys.ip.dns2, buf); obj[F("dns2")] = String(buf); + ah::ip2Char(mConfig->sys.ip.gateway, buf); obj[F("gateway")] = String(buf); + } + + void getDisplay(JsonObject obj) { + obj[F("disp_typ")] = (uint8_t)mConfig->plugin.display.type; + obj[F("disp_pwr")] = (bool)mConfig->plugin.display.pwrSaveAtIvOffline; + obj[F("disp_pxshift")] = (bool)mConfig->plugin.display.pxShift; + obj[F("disp_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) { + getGeneric(obj.createNestedObject(F("generic"))); + obj[F("ts_now")] = mApp->getTimestamp(); + obj[F("ts_sunrise")] = mApp->getSunrise(); + obj[F("ts_sunset")] = mApp->getSunset(); + obj[F("ts_offset")] = mConfig->sun.offsetSec; + obj[F("disNightComm")] = mConfig->sun.disNightCom; + + JsonArray inv = obj.createNestedArray(F("inverter")); + Inverter<> *iv; + for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i ++) { + iv = mSys->getInverterByPos(i); + if(NULL != iv) { + record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug); + JsonObject invObj = inv.createNestedObject(); + invObj[F("enabled")] = (bool)iv->config->enabled; + invObj[F("id")] = i; + invObj[F("name")] = String(iv->config->name); + invObj[F("version")] = String(iv->getFwVersion()); + invObj[F("is_avail")] = iv->isAvailable(mApp->getTimestamp()); + invObj[F("is_producing")] = iv->isProducing(mApp->getTimestamp()); + invObj[F("ts_last_success")] = iv->getLastTs(rec); + } + } + + JsonArray warn = obj.createNestedArray(F("warnings")); + if(!mSys->Radio.isChipConnected()) + warn.add(F("your NRF24 module can't be reached, check the wiring and pinout")); + else if(!mSys->Radio.isPVariant()) + warn.add(F("your NRF24 module isn't a plus version(+), maybe incompatible")); + if(!mApp->getSettingsValid()) + warn.add(F("your settings are invalid")); + if(mApp->getRebootRequestState()) + warn.add(F("reboot your ESP to apply all your configuration changes")); + if(0 == mApp->getTimestamp()) + warn.add(F("time not set. No communication to inverter possible")); + /*if(0 == mSys->getNumInverters()) + warn.add(F("no inverter configured"));*/ + + if((!mApp->getMqttIsConnected()) && (String(mConfig->mqtt.broker).length() > 0)) + warn.add(F("MQTT is not connected")); + + JsonArray info = obj.createNestedArray(F("infos")); + if(mApp->getMqttIsConnected()) + info.add(F("MQTT is connected, ") + String(mApp->getMqttTxCnt()) + F(" packets sent, ") + String(mApp->getMqttRxCnt()) + F(" packets received")); + if(mConfig->mqtt.interval > 0) + info.add(F("MQTT publishes in a fixed interval of ") + String(mConfig->mqtt.interval) + F(" seconds")); + } + + void getSetup(JsonObject obj) { + getGeneric(obj.createNestedObject(F("generic"))); + getSysInfo(obj.createNestedObject(F("system"))); + //getInverterList(obj.createNestedObject(F("inverter"))); + getMqtt(obj.createNestedObject(F("mqtt"))); + getNtp(obj.createNestedObject(F("ntp"))); + getSun(obj.createNestedObject(F("sun"))); + getPinout(obj.createNestedObject(F("pinout"))); + getRadio(obj.createNestedObject(F("radio"))); + getSerial(obj.createNestedObject(F("serial"))); + getStaticIp(obj.createNestedObject(F("static_ip"))); + getDisplay(obj.createNestedObject(F("display"))); + } + + void getNetworks(JsonObject obj) { + mApp->getAvailNetworks(obj); + } + + void getLive(JsonObject obj) { + getGeneric(obj.createNestedObject(F("generic"))); + //JsonArray invArr = obj.createNestedArray(F("inverter")); + obj[F("refresh")] = mConfig->nrf.sendInterval; + + 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); + if(NULL != iv) { + record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug); + JsonObject obj2 = invArr.createNestedObject(); + obj2[F("enabled")] = (bool)iv->config->enabled; + obj2[F("name")] = String(iv->config->name); + obj2[F("channels")] = iv->channels; + obj2[F("power_limit_read")] = ah::round3(iv->actPowerLimit); + //obj2[F("last_alarm")] = String(iv->lastAlarmMsg); + obj2[F("ts_last_success")] = rec->ts; + + JsonArray ch = obj2.createNestedArray("ch"); + JsonArray ch0 = ch.createNestedArray(); + obj2[F("ch_names")][0] = "AC"; + for (uint8_t fld = 0; fld < sizeof(list); fld++) { + pos = (iv->getPosByChFld(CH0, list[fld], rec)); + ch0[fld] = (0xff != pos) ? ah::round3(iv->getValue(pos, rec)) : 0.0; + obj[F("ch0_fld_units")][fld] = (0xff != pos) ? String(iv->getUnit(pos, rec)) : notAvail; + obj[F("ch0_fld_names")][fld] = (0xff != pos) ? String(iv->getFieldName(pos, rec)) : notAvail; + } + + for(uint8_t j = 1; j <= iv->channels; j ++) { + obj2[F("ch_names")][j] = String(iv->config->chName[j-1]); + JsonArray cur = ch.createNestedArray(); + for (uint8_t k = 0; k < 6; k++) { + switch(k) { + default: pos = (iv->getPosByChFld(j, FLD_UDC, rec)); break; + case 1: pos = (iv->getPosByChFld(j, FLD_IDC, rec)); break; + case 2: pos = (iv->getPosByChFld(j, FLD_PDC, rec)); break; + case 3: pos = (iv->getPosByChFld(j, FLD_YD, rec)); break; + case 4: pos = (iv->getPosByChFld(j, FLD_YT, rec)); break; + case 5: pos = (iv->getPosByChFld(j, FLD_IRR, rec)); break; + } + cur[k] = (0xff != pos) ? ah::round3(iv->getValue(pos, rec)) : 0.0; + if(1 == j) { + obj[F("fld_units")][k] = (0xff != pos) ? String(iv->getUnit(pos, rec)) : notAvail; + obj[F("fld_names")][k] = (0xff != pos) ? String(iv->getFieldName(pos, rec)) : notAvail; + } + } + } + } + }*/ + } + + void getRecord(JsonObject obj, uint8_t recType) { + JsonArray invArr = obj.createNestedArray(F("inverter")); + + Inverter<> *iv; + record_t<> *rec; + uint8_t pos; + for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i ++) { + iv = mSys->getInverterByPos(i); + if(NULL != iv) { + rec = iv->getRecordStruct(recType); + JsonArray obj2 = invArr.createNestedArray(); + for(uint8_t j = 0; j < rec->length; j++) { + byteAssign_t *assign = iv->getByteAssign(j, rec); + pos = (iv->getPosByChFld(assign->ch, assign->fieldId, rec)); + obj2[j]["fld"] = (0xff != pos) ? String(iv->getFieldName(pos, rec)) : notAvail; + obj2[j]["unit"] = (0xff != pos) ? String(iv->getUnit(pos, rec)) : notAvail; + obj2[j]["val"] = (0xff != pos) ? String(iv->getValue(pos, rec)) : notAvail; + } } } } - } - - bool setCtrl(JsonObject jsonIn, JsonObject jsonOut) { - Inverter<> *iv = mSys->getInverterByPos(jsonIn[F("id")]); - bool accepted = true; - if (NULL == iv) { - jsonOut[F("error")] = F("inverter index invalid: ") + jsonIn[F("id")].as(); - return false; - } - - if (F("power") == jsonIn[F("cmd")]) - accepted = iv->setDevControlRequest((jsonIn[F("val")] == 1) ? TurnOn : TurnOff); - else if (F("restart") == jsonIn[F("restart")]) - accepted = iv->setDevControlRequest(Restart); - else if (0 == strncmp("limit_", jsonIn[F("cmd")].as(), 6)) { - iv->powerLimit[0] = jsonIn["val"]; - if (F("limit_persistent_relative") == jsonIn[F("cmd")]) - iv->powerLimit[1] = RelativPersistent; - else if (F("limit_persistent_absolute") == jsonIn[F("cmd")]) - iv->powerLimit[1] = AbsolutPersistent; - else if (F("limit_nonpersistent_relative") == jsonIn[F("cmd")]) - iv->powerLimit[1] = RelativNonPersistent; - else if (F("limit_nonpersistent_absolute") == jsonIn[F("cmd")]) - iv->powerLimit[1] = AbsolutNonPersistent; - - accepted = iv->setDevControlRequest(ActivePowerContr); - } else if (F("dev") == jsonIn[F("cmd")]) { - DPRINTLN(DBG_INFO, F("dev cmd")); - iv->enqueCommand(jsonIn[F("val")].as()); - } else { - jsonOut[F("error")] = F("unknown cmd: '") + jsonIn["cmd"].as() + "'"; - return false; - } - - if (!accepted) { - jsonOut[F("error")] = F("inverter does not accept dev control request at this moment"); - return false; - } else - mApp->ivSendHighPrio(iv); - - return true; - } - - bool setSetup(JsonObject jsonIn, JsonObject jsonOut) { - if (F("scan_wifi") == jsonIn[F("cmd")]) - mApp->scanAvailNetworks(); - else if (F("set_time") == jsonIn[F("cmd")]) - mApp->setTimestamp(jsonIn[F("val")]); - else if (F("sync_ntp") == jsonIn[F("cmd")]) - mApp->setTimestamp(0); // 0: update ntp flag - else if (F("serial_utc_offset") == jsonIn[F("cmd")]) - mTimezoneOffset = jsonIn[F("val")]; - else if (F("discovery_cfg") == jsonIn[F("cmd")]) { - mApp->setMqttDiscoveryFlag(); // for homeassistant - } else { - jsonOut[F("error")] = F("unknown cmd"); - return false; - } - - return true; - } - - IApp *mApp; - HMSYSTEM *mSys; - AsyncWebServer *mSrv; - settings_t *mConfig; - - uint32_t mTimezoneOffset; - uint32_t mHeapFree, mHeapFreeBlk; - uint8_t mHeapFrag; - uint16_t nr; + + bool setCtrl(JsonObject jsonIn, JsonObject jsonOut) { + Inverter<> *iv = mSys->getInverterByPos(jsonIn[F("id")]); + bool accepted = true; + if(NULL == iv) { + jsonOut[F("error")] = F("inverter index invalid: ") + jsonIn[F("id")].as(); + return false; + } + + if(F("power") == jsonIn[F("cmd")]) + accepted = iv->setDevControlRequest((jsonIn[F("val")] == 1) ? TurnOn : TurnOff); + else if(F("restart") == jsonIn[F("restart")]) + accepted = iv->setDevControlRequest(Restart); + else if(0 == strncmp("limit_", jsonIn[F("cmd")].as(), 6)) { + iv->powerLimit[0] = jsonIn["val"]; + if(F("limit_persistent_relative") == jsonIn[F("cmd")]) + iv->powerLimit[1] = RelativPersistent; + else if(F("limit_persistent_absolute") == jsonIn[F("cmd")]) + iv->powerLimit[1] = AbsolutPersistent; + else if(F("limit_nonpersistent_relative") == jsonIn[F("cmd")]) + iv->powerLimit[1] = RelativNonPersistent; + else if(F("limit_nonpersistent_absolute") == jsonIn[F("cmd")]) + iv->powerLimit[1] = AbsolutNonPersistent; + + accepted = iv->setDevControlRequest(ActivePowerContr); + } + else if(F("dev") == jsonIn[F("cmd")]) { + DPRINTLN(DBG_INFO, F("dev cmd")); + iv->enqueCommand(jsonIn[F("val")].as()); + } + else { + jsonOut[F("error")] = F("unknown cmd: '") + jsonIn["cmd"].as() + "'"; + return false; + } + + if(!accepted) { + jsonOut[F("error")] = F("inverter does not accept dev control request at this moment"); + return false; + } else + mApp->ivSendHighPrio(iv); + + return true; + } + + bool setSetup(JsonObject jsonIn, JsonObject jsonOut) { + if(F("scan_wifi") == jsonIn[F("cmd")]) + mApp->scanAvailNetworks(); + else if(F("set_time") == jsonIn[F("cmd")]) + mApp->setTimestamp(jsonIn[F("val")]); + else if(F("sync_ntp") == jsonIn[F("cmd")]) + mApp->setTimestamp(0); // 0: update ntp flag + else if(F("serial_utc_offset") == jsonIn[F("cmd")]) + mTimezoneOffset = jsonIn[F("val")]; + else if(F("discovery_cfg") == jsonIn[F("cmd")]) { + mApp->setMqttDiscoveryFlag(); // for homeassistant + } else { + jsonOut[F("error")] = F("unknown cmd"); + return false; + } + + return true; + } + + IApp *mApp; + HMSYSTEM *mSys; + AsyncWebServer *mSrv; + settings_t *mConfig; + + uint32_t mTimezoneOffset; + uint32_t mHeapFree, mHeapFreeBlk; + uint8_t mHeapFrag; + uint16_t nr; }; #endif /*__WEB_API_H__*/ diff --git a/src/web/html/setup.html b/src/web/html/setup.html index 7342a121..09cc4a67 100644 --- a/src/web/html/setup.html +++ b/src/web/html/setup.html @@ -255,28 +255,19 @@
Display Config
-
-
Show Logo
-
-
+
Turn off while inverters are offline
-
-
-
-
Enable pixel shifting
-
+
-
Rotate 180 degree
-
+
Enable Screensaver (pixel shifting)
+
Contrast
-
+
- -

Pinout

@@ -509,10 +500,10 @@ for(var j of [ ["ModPwr", "ch_max_power", "Max Module Power (Wp)", 4, "[0-9]+"], - ["ModName", "ch_name", "Module Name", 16, null], - ["YieldCor", "ch_yield_cor", "Yield Total Correction [kWh]", 16, "[0-9-]+"]]) { + ["ModName", "ch_name", "Module Name", 15, null], + ["YieldCor", "ch_yield_cor", "Yield Total Correction [kWh]", 8, "[0-9-]+"]]) { - var cl = (re.test(obj["serial"])) ? null : ["hide"]; + var cl = (re.test(obj["serial"])) ? "" : " hide"; i = 0; arrIn = []; @@ -524,7 +515,7 @@ } iv.append( - ml("div", {class: "row mb-2 mb-sm-3", id: "row" + id + j[0]}, [ + ml("div", {class: "row mb-2 mb-sm-3" + cl, id: "row" + id + j[0]}, [ ml("div", {class: "col-12 col-sm-3 my-2"}, j[2]), ml("div", {class: "col-12 col-sm-9"}, ml("div", {class: "row"}, arrIn) @@ -639,12 +630,14 @@ } function parseDisplay(obj, type) { - for(var i of [["logoEn", "logo_en"], ["dispPwr", "disp_pwr"], ["dispPxSh", "px_shift"], ["disp180", "rot180"]]) - document.getElementsByName(i[0])[0].checked = obj[i[1]]; + for(var i of ["disp_pwr", "disp_pxshift"]) + document.getElementsByName(i)[0].checked = obj[i]; var e = document.getElementById("dispPins"); - pins = [['SCL / CS', 'pinDisp0'], ['SDA / DC', 'pinDisp1']]; + pins = [['clock', 'disp_clk'], ['data', 'disp_data'], ['cs', 'disp_cs'], ['dc', 'disp_dc'], ['reset', 'disp_rst'], ['busy', 'disp_bsy']]; for(p of pins) { + if(("ESP8266" == type) && p[0] == "cs") + break; e.append( ml("div", {class: "row mb-3"}, [ ml("div", {class: "col-12 col-sm-3 my-2"}, p[0].toUpperCase()), @@ -655,18 +648,31 @@ ); } - var opts = [[0, "None"], [1, "Nokia5110"], [2, "SSD1306 0.96\""], [3, "SH1106 1.3\""]]; + var opts = [[0, "None"], [2, "SSD1306 0.96\""], [3, "SH1106 1.3\""]]; + if("ESP32" == type) { + opts.push([1, "Nokia5110"]); + opts.push([10, "ePaper"]); + } document.getElementById("dispType").append( ml("div", {class: "row mb-3"}, [ ml("div", {class: "col-12 col-sm-3 my-2"}, "Type"), - ml("div", {class: "col-12 col-sm-9"}, sel("dispType", opts, obj["disp_type"])) + ml("div", {class: "col-12 col-sm-9"}, sel("disp_typ", opts, obj["disp_typ"])) ]) ); - e = document.getElementById("contrast"); - for(var i = 30; i < 101; i += 2) { - e.appendChild(opt(i, i, (i == obj["contrast"]))); + opts = [[0, "0°"], [2, "180°"]]; + if("ESP32" == type) { + opts.push([1, "90°"]); + opts.push([3, "270°"]); } + document.getElementById("dispRot").append( + ml("div", {class: "row mb-3"}, [ + ml("div", {class: "col-12 col-sm-3 my-2"}, "Rotation"), + ml("div", {class: "col-12 col-sm-9"}, sel("disp_rot", opts, obj["disp_rot"])) + ]) + ); + + document.getElementsByName("disp_cont")[0].value = obj["disp_cont"]; } function parse(root) { diff --git a/src/web/html/style.css b/src/web/html/style.css index 1b0f8c48..ca4b0c9a 100644 --- a/src/web/html/style.css +++ b/src/web/html/style.css @@ -595,3 +595,39 @@ div.hr { background-color: var(--primary); color: #fff; } + + +.css-tooltip{ + position: relative; +} +.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/visualization.html b/src/web/html/visualization.html index 0fbbeeee..9b6dd5d5 100644 --- a/src/web/html/visualization.html +++ b/src/web/html/visualization.html @@ -46,7 +46,7 @@ } function numMid(val, unit, des) { - return ml("div", {class: "col-6 col-sm-4 col-md-3"}, [ + return ml("div", {class: "col-6 col-sm-4 col-md-3 mb-2"}, [ ml("div", {class: "row"}, ml("div", {class: "col"}, [ ml("span", {class: "fs-6"}, String(val)), @@ -93,13 +93,14 @@ total[2] += obj.ch[0][6]; // YieldTotal total[3] += obj.ch[0][8]; // P_DC total[4] += obj.ch[0][10]; // Q_AC + var t = span(" ° C"); return ml("div", {class: "row"}, ml("div", {class: "col"}, [ ml("div", {class: "p-2 iv-h"}, ml("div", {class: "row"}, [ ml("div", {class: "col mx-2 mx-md-1"}, obj.name), ml("div", {class: "col a-c"}, "Power limit " + ((obj.power_limit_read == 65535) ? "n/a" : (obj.power_limit_read + " %"))), - ml("div", {class: "col a-r mx-2 mx-md-1"}, String(obj.ch[0][5]) + " C") + ml("div", {class: "col a-r mx-2 mx-md-1"}, String(obj.ch[0][5]) + t.innerHTML) ]) ), ml("div", {class: "p-2 iv-bg"}, [ diff --git a/src/web/web.h b/src/web/web.h index 5eed3688..9f12400e 100644 --- a/src/web/web.h +++ b/src/web/web.h @@ -37,918 +37,894 @@ const char *const pinArgNames[] = {"pinCs", "pinCe", "pinIrq", "pinLed0", "pinLe template class Web { public: - Web(void) : mWeb(80), mEvts("/events") { - mProtected = true; - mLogoutTimeout = 0; - - memset(mSerialBuf, 0, WEB_SERIAL_BUF_SIZE); - mSerialBufFill = 0; - mSerialAddTime = true; - mSerialClientConnnected = false; - } - - void setup(IApp *app, HMSYSTEM *sys, settings_t *config) { - mApp = app; - mSys = sys; - mConfig = config; - - DPRINTLN(DBG_VERBOSE, F("app::setup-on")); - mWeb.on("/", HTTP_GET, std::bind(&Web::onIndex, this, std::placeholders::_1)); - mWeb.on("/login", HTTP_ANY, std::bind(&Web::onLogin, this, std::placeholders::_1)); - mWeb.on("/logout", HTTP_GET, std::bind(&Web::onLogout, this, std::placeholders::_1)); - mWeb.on("/colors.css", HTTP_GET, std::bind(&Web::onColor, this, std::placeholders::_1)); - mWeb.on("/style.css", HTTP_GET, std::bind(&Web::onCss, this, std::placeholders::_1)); - mWeb.on("/api.js", HTTP_GET, std::bind(&Web::onApiJs, this, std::placeholders::_1)); - mWeb.on("/favicon.ico", HTTP_GET, std::bind(&Web::onFavicon, this, std::placeholders::_1)); - mWeb.onNotFound(std::bind(&Web::showNotFound, this, std::placeholders::_1)); - mWeb.on("/reboot", HTTP_ANY, std::bind(&Web::onReboot, this, std::placeholders::_1)); - mWeb.on("/system", HTTP_ANY, std::bind(&Web::onSystem, this, std::placeholders::_1)); - mWeb.on("/erase", HTTP_ANY, std::bind(&Web::showErase, this, std::placeholders::_1)); - mWeb.on("/factory", HTTP_ANY, std::bind(&Web::showFactoryRst, this, std::placeholders::_1)); - - mWeb.on("/setup", HTTP_GET, std::bind(&Web::onSetup, this, std::placeholders::_1)); - mWeb.on("/save", HTTP_ANY, std::bind(&Web::showSave, this, std::placeholders::_1)); - - mWeb.on("/live", HTTP_ANY, std::bind(&Web::onLive, this, std::placeholders::_1)); - // mWeb.on("/api1", HTTP_POST, std::bind(&Web::showWebApi, this, std::placeholders::_1)); - -#ifdef ENABLE_JSON_EP - mWeb.on("/json", HTTP_ANY, std::bind(&Web::showJson, this, std::placeholders::_1)); -#endif -#ifdef ENABLE_PROMETHEUS_EP - mWeb.on("/metrics", HTTP_ANY, std::bind(&Web::showMetrics, this, std::placeholders::_1)); -#endif - - mWeb.on("/update", HTTP_GET, std::bind(&Web::onUpdate, this, std::placeholders::_1)); - mWeb.on("/update", HTTP_POST, std::bind(&Web::showUpdate, this, std::placeholders::_1), - std::bind(&Web::showUpdate2, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5, std::placeholders::_6)); - mWeb.on("/upload", HTTP_POST, std::bind(&Web::onUpload, this, std::placeholders::_1), - std::bind(&Web::onUpload2, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5, std::placeholders::_6)); - mWeb.on("/serial", HTTP_GET, std::bind(&Web::onSerial, this, std::placeholders::_1)); - mWeb.on("/debug", HTTP_GET, std::bind(&Web::onDebug, this, std::placeholders::_1)); - - mEvts.onConnect(std::bind(&Web::onConnect, this, std::placeholders::_1)); - mWeb.addHandler(&mEvts); - - mWeb.begin(); + Web(void) : mWeb(80), mEvts("/events") { + mProtected = true; + mLogoutTimeout = 0; + + memset(mSerialBuf, 0, WEB_SERIAL_BUF_SIZE); + mSerialBufFill = 0; + mSerialAddTime = true; + mSerialClientConnnected = false; + } + + void setup(IApp *app, HMSYSTEM *sys, settings_t *config) { + mApp = app; + mSys = sys; + mConfig = config; + + DPRINTLN(DBG_VERBOSE, F("app::setup-on")); + mWeb.on("/", HTTP_GET, std::bind(&Web::onIndex, this, std::placeholders::_1)); + mWeb.on("/login", HTTP_ANY, std::bind(&Web::onLogin, this, std::placeholders::_1)); + mWeb.on("/logout", HTTP_GET, std::bind(&Web::onLogout, this, std::placeholders::_1)); + mWeb.on("/colors.css", HTTP_GET, std::bind(&Web::onColor, this, std::placeholders::_1)); + mWeb.on("/style.css", HTTP_GET, std::bind(&Web::onCss, this, std::placeholders::_1)); + mWeb.on("/api.js", HTTP_GET, std::bind(&Web::onApiJs, this, std::placeholders::_1)); + mWeb.on("/favicon.ico", HTTP_GET, std::bind(&Web::onFavicon, this, std::placeholders::_1)); + mWeb.onNotFound ( std::bind(&Web::showNotFound, this, std::placeholders::_1)); + mWeb.on("/reboot", HTTP_ANY, std::bind(&Web::onReboot, this, std::placeholders::_1)); + mWeb.on("/system", HTTP_ANY, std::bind(&Web::onSystem, this, std::placeholders::_1)); + mWeb.on("/erase", HTTP_ANY, std::bind(&Web::showErase, this, std::placeholders::_1)); + mWeb.on("/factory", HTTP_ANY, std::bind(&Web::showFactoryRst, this, std::placeholders::_1)); + + mWeb.on("/setup", HTTP_GET, std::bind(&Web::onSetup, this, std::placeholders::_1)); + mWeb.on("/save", HTTP_ANY, std::bind(&Web::showSave, this, std::placeholders::_1)); + + mWeb.on("/live", HTTP_ANY, std::bind(&Web::onLive, this, std::placeholders::_1)); + //mWeb.on("/api1", HTTP_POST, std::bind(&Web::showWebApi, this, std::placeholders::_1)); + + #ifdef ENABLE_JSON_EP + mWeb.on("/json", HTTP_ANY, std::bind(&Web::showJson, this, std::placeholders::_1)); + #endif + #ifdef ENABLE_PROMETHEUS_EP + mWeb.on("/metrics", HTTP_ANY, std::bind(&Web::showMetrics, this, std::placeholders::_1)); + #endif + + mWeb.on("/update", HTTP_GET, std::bind(&Web::onUpdate, this, std::placeholders::_1)); + mWeb.on("/update", HTTP_POST, std::bind(&Web::showUpdate, this, std::placeholders::_1), + std::bind(&Web::showUpdate2, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5, std::placeholders::_6)); + mWeb.on("/upload", HTTP_POST, std::bind(&Web::onUpload, this, std::placeholders::_1), + std::bind(&Web::onUpload2, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5, std::placeholders::_6)); + mWeb.on("/serial", HTTP_GET, std::bind(&Web::onSerial, this, std::placeholders::_1)); + mWeb.on("/debug", HTTP_GET, std::bind(&Web::onDebug, this, std::placeholders::_1)); + + + mEvts.onConnect(std::bind(&Web::onConnect, this, std::placeholders::_1)); + mWeb.addHandler(&mEvts); + + mWeb.begin(); + + registerDebugCb(std::bind(&Web::serialCb, this, std::placeholders::_1)); // dbg.h - registerDebugCb(std::bind(&Web::serialCb, this, std::placeholders::_1)); // dbg.h + mUploadFail = false; + } - mUploadFail = false; - } + void tickSecond() { + if (0 != mLogoutTimeout) { + mLogoutTimeout -= 1; + if (0 == mLogoutTimeout) { + if (strlen(mConfig->sys.adminPwd) > 0) + mProtected = true; + } - void tickSecond() { - if (0 != mLogoutTimeout) { - mLogoutTimeout -= 1; - if (0 == mLogoutTimeout) { - if (strlen(mConfig->sys.adminPwd) > 0) - mProtected = true; + DPRINTLN(DBG_DEBUG, "auto logout in " + String(mLogoutTimeout)); } - DPRINTLN(DBG_DEBUG, "auto logout in " + String(mLogoutTimeout)); - } - - if (mSerialClientConnnected) { - if (mSerialBufFill > 0) { - mEvts.send(mSerialBuf, "serial", millis()); - memset(mSerialBuf, 0, WEB_SERIAL_BUF_SIZE); - mSerialBufFill = 0; + if (mSerialClientConnnected) { + if (mSerialBufFill > 0) { + mEvts.send(mSerialBuf, "serial", millis()); + memset(mSerialBuf, 0, WEB_SERIAL_BUF_SIZE); + mSerialBufFill = 0; + } } } - } - AsyncWebServer *getWebSrvPtr(void) { - return &mWeb; - } + AsyncWebServer *getWebSrvPtr(void) { + return &mWeb; + } - void setProtection(bool protect) { - mProtected = protect; - } + void setProtection(bool protect) { + mProtected = protect; + } - bool getProtection() { - return mProtected; - } + bool getProtection() { + return mProtected; + } - void showUpdate2(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) { - mApp->setOnUpdate(); + void showUpdate2(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) { + mApp->setOnUpdate(); - if (!index) { - Serial.printf("Update Start: %s\n", filename.c_str()); -#ifndef ESP32 - Update.runAsync(true); -#endif - if (!Update.begin((ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000)) { - Update.printError(Serial); + if (!index) { + Serial.printf("Update Start: %s\n", filename.c_str()); + #ifndef ESP32 + Update.runAsync(true); + #endif + if (!Update.begin((ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000)) { + Update.printError(Serial); + } } - } - if (!Update.hasError()) { - if (Update.write(data, len) != len) { - Update.printError(Serial); + if (!Update.hasError()) { + if (Update.write(data, len) != len) { + Update.printError(Serial); + } } - } - if (final) { - if (Update.end(true)) { - Serial.printf("Update Success: %uB\n", index + len); - } else { - Update.printError(Serial); + if (final) { + if (Update.end(true)) { + Serial.printf("Update Success: %uB\n", index + len); + } else { + Update.printError(Serial); + } } } - } - void onUpload2(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) { - if (!index) { - mUploadFail = false; - mUploadFp = LittleFS.open("/tmp.json", "w"); - if (!mUploadFp) { - DPRINTLN(DBG_ERROR, F("can't open file!")); - mUploadFail = true; - mUploadFp.close(); + void onUpload2(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) { + if (!index) { + mUploadFail = false; + mUploadFp = LittleFS.open("/tmp.json", "w"); + if (!mUploadFp) { + DPRINTLN(DBG_ERROR, F("can't open file!")); + mUploadFail = true; + mUploadFp.close(); + } } - } - mUploadFp.write(data, len); - if (final) { - mUploadFp.close(); - File fp = LittleFS.open("/tmp.json", "r"); - if (!fp) - mUploadFail = true; - else { - if (!mApp->readSettings("tmp.json")) { + mUploadFp.write(data, len); + if (final) { + mUploadFp.close(); + File fp = LittleFS.open("/tmp.json", "r"); + if (!fp) mUploadFail = true; - DPRINTLN(DBG_ERROR, F("upload JSON error!")); - } else - mApp->saveSettings(); + else { + if (!mApp->readSettings("tmp.json")) { + mUploadFail = true; + DPRINTLN(DBG_ERROR, F("upload JSON error!")); + } else + mApp->saveSettings(); + } + DPRINTLN(DBG_INFO, F("upload finished!")); } - DPRINTLN(DBG_INFO, F("upload finished!")); } - } - void serialCb(String msg) { - if (!mSerialClientConnnected) - return; + void serialCb(String msg) { + if (!mSerialClientConnnected) + return; - msg.replace("\r\n", ""); - if (mSerialAddTime) { - if ((9 + mSerialBufFill) < WEB_SERIAL_BUF_SIZE) { - if (mApp->getTimestamp() > 0) { - strncpy(&mSerialBuf[mSerialBufFill], mApp->getTimeStr(mApp->getTimezoneOffset()).c_str(), 9); - mSerialBufFill += 9; + msg.replace("\r\n", ""); + if (mSerialAddTime) { + if ((9 + mSerialBufFill) < WEB_SERIAL_BUF_SIZE) { + if (mApp->getTimestamp() > 0) { + strncpy(&mSerialBuf[mSerialBufFill], mApp->getTimeStr(mApp->getTimezoneOffset()).c_str(), 9); + mSerialBufFill += 9; + } + } else { + mSerialBufFill = 0; + mEvts.send("webSerial, buffer overflow!", "serial", millis()); + return; } + mSerialAddTime = false; + } + + if (msg.endsWith("")) + mSerialAddTime = true; + + uint16_t length = msg.length(); + if ((length + mSerialBufFill) < WEB_SERIAL_BUF_SIZE) { + strncpy(&mSerialBuf[mSerialBufFill], msg.c_str(), length); + mSerialBufFill += length; } else { mSerialBufFill = 0; mEvts.send("webSerial, buffer overflow!", "serial", millis()); - return; } - mSerialAddTime = false; } - if (msg.endsWith("")) - mSerialAddTime = true; + private: + void checkRedirect(AsyncWebServerRequest *request) { + if ((mConfig->sys.protectionMask & PROT_MASK_INDEX) != PROT_MASK_INDEX) + request->redirect(F("/index")); + else if ((mConfig->sys.protectionMask & PROT_MASK_LIVE) != PROT_MASK_LIVE) + request->redirect(F("/live")); + else if ((mConfig->sys.protectionMask & PROT_MASK_SERIAL) != PROT_MASK_SERIAL) + request->redirect(F("/serial")); + else if ((mConfig->sys.protectionMask & PROT_MASK_SYSTEM) != PROT_MASK_SYSTEM) + request->redirect(F("/system")); + else + request->redirect(F("/login")); + } + + void onUpdate(AsyncWebServerRequest *request) { + DPRINTLN(DBG_VERBOSE, F("onUpdate")); + + if (CHECK_MASK(mConfig->sys.protectionMask, PROT_MASK_UPDATE)) { + if (mProtected) { + checkRedirect(request); + return; + } + } - uint16_t length = msg.length(); - if ((length + mSerialBufFill) < WEB_SERIAL_BUF_SIZE) { - strncpy(&mSerialBuf[mSerialBufFill], msg.c_str(), length); - mSerialBufFill += length; - } else { - mSerialBufFill = 0; - mEvts.send("webSerial, buffer overflow!", "serial", millis()); + AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html; charset=UTF-8"), update_html, update_html_len); + response->addHeader(F("Content-Encoding"), "gzip"); + request->send(response); } - } - private: - void checkRedirect(AsyncWebServerRequest *request) { - if ((mConfig->sys.protectionMask & PROT_MASK_INDEX) != PROT_MASK_INDEX) - request->redirect(F("/index")); - else if ((mConfig->sys.protectionMask & PROT_MASK_LIVE) != PROT_MASK_LIVE) - request->redirect(F("/live")); - else if ((mConfig->sys.protectionMask & PROT_MASK_SERIAL) != PROT_MASK_SERIAL) - request->redirect(F("/serial")); - else if ((mConfig->sys.protectionMask & PROT_MASK_SYSTEM) != PROT_MASK_SYSTEM) - request->redirect(F("/system")); - else - request->redirect(F("/login")); - } + void showUpdate(AsyncWebServerRequest *request) { + bool reboot = !Update.hasError(); - void onUpdate(AsyncWebServerRequest *request) { - DPRINTLN(DBG_VERBOSE, F("onUpdate")); + String html = F("UpdateUpdate: "); + if (reboot) + html += "success"; + else + html += "failed"; + html += F("

rebooting ... auto reload after 20s"); - if (CHECK_MASK(mConfig->sys.protectionMask, PROT_MASK_UPDATE)) { - if (mProtected) { - checkRedirect(request); - return; - } + AsyncWebServerResponse *response = request->beginResponse(200, F("text/html; charset=UTF-8"), html); + response->addHeader("Connection", "close"); + request->send(response); + mApp->setRebootFlag(); } - AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html; charset=UTF-8"), update_html, update_html_len); - response->addHeader(F("Content-Encoding"), "gzip"); - request->send(response); - } + void onUpload(AsyncWebServerRequest *request) { + bool reboot = !mUploadFail; - void showUpdate(AsyncWebServerRequest *request) { - bool reboot = !Update.hasError(); + String html = F("UploadUpload: "); + if (reboot) + html += "success"; + else + html += "failed"; + html += F("

rebooting ... auto reload after 20s"); - String html = F("UpdateUpdate: "); - if (reboot) - html += "success"; - else - html += "failed"; - html += F("

rebooting ... auto reload after 20s"); + AsyncWebServerResponse *response = request->beginResponse(200, F("text/html; charset=UTF-8"), html); + response->addHeader("Connection", "close"); + request->send(response); + mApp->setRebootFlag(); + } - AsyncWebServerResponse *response = request->beginResponse(200, F("text/html; charset=UTF-8"), html); - response->addHeader("Connection", "close"); - request->send(response); - mApp->setRebootFlag(); - } + void onConnect(AsyncEventSourceClient *client) { + DPRINTLN(DBG_VERBOSE, "onConnect"); - void onUpload(AsyncWebServerRequest *request) { - bool reboot = !mUploadFail; + mSerialClientConnnected = true; - String html = F("UploadUpload: "); - if (reboot) - html += "success"; - else - html += "failed"; - html += F("

rebooting ... auto reload after 20s"); + if (client->lastId()) + DPRINTLN(DBG_VERBOSE, "Client reconnected! Last message ID that it got is: " + String(client->lastId())); - AsyncWebServerResponse *response = request->beginResponse(200, F("text/html; charset=UTF-8"), html); - response->addHeader("Connection", "close"); - request->send(response); - mApp->setRebootFlag(); - } + client->send("hello!", NULL, millis(), 1000); + } - void onConnect(AsyncEventSourceClient *client) { - DPRINTLN(DBG_VERBOSE, "onConnect"); + void onIndex(AsyncWebServerRequest *request) { + DPRINTLN(DBG_VERBOSE, F("onIndex")); - mSerialClientConnnected = true; + if (CHECK_MASK(mConfig->sys.protectionMask, PROT_MASK_INDEX)) { + if (mProtected) { + checkRedirect(request); + return; + } + } - if (client->lastId()) - DPRINTLN(DBG_VERBOSE, "Client reconnected! Last message ID that it got is: " + String(client->lastId())); + AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html; charset=UTF-8"), index_html, index_html_len); + response->addHeader(F("Content-Encoding"), "gzip"); + request->send(response); + } - client->send("hello!", NULL, millis(), 1000); - } + void onLogin(AsyncWebServerRequest *request) { + DPRINTLN(DBG_VERBOSE, F("onLogin")); - void onIndex(AsyncWebServerRequest *request) { - DPRINTLN(DBG_VERBOSE, F("onIndex")); + if (request->args() > 0) { + if (String(request->arg("pwd")) == String(mConfig->sys.adminPwd)) { + mProtected = false; + request->redirect("/"); + } + } + + AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html; charset=UTF-8"), login_html, login_html_len); + response->addHeader(F("Content-Encoding"), "gzip"); + request->send(response); + } + + void onLogout(AsyncWebServerRequest *request) { + DPRINTLN(DBG_VERBOSE, F("onLogout")); - if (CHECK_MASK(mConfig->sys.protectionMask, PROT_MASK_INDEX)) { if (mProtected) { checkRedirect(request); return; } + + mProtected = true; + + AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html; charset=UTF-8"), system_html, system_html_len); + response->addHeader(F("Content-Encoding"), "gzip"); + request->send(response); } - AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html; charset=UTF-8"), index_html, index_html_len); - response->addHeader(F("Content-Encoding"), "gzip"); - request->send(response); - } - - void onLogin(AsyncWebServerRequest *request) { - DPRINTLN(DBG_VERBOSE, F("onLogin")); - - if (request->args() > 0) { - if (String(request->arg("pwd")) == String(mConfig->sys.adminPwd)) { - mProtected = false; - request->redirect("/"); - } - } - - AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html; charset=UTF-8"), login_html, login_html_len); - response->addHeader(F("Content-Encoding"), "gzip"); - request->send(response); - } - - void onLogout(AsyncWebServerRequest *request) { - DPRINTLN(DBG_VERBOSE, F("onLogout")); - - if (mProtected) { - checkRedirect(request); - return; - } - - mProtected = true; - - AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html; charset=UTF-8"), system_html, system_html_len); - response->addHeader(F("Content-Encoding"), "gzip"); - request->send(response); - } - - void onColor(AsyncWebServerRequest *request) { - DPRINTLN(DBG_VERBOSE, F("onColor")); - AsyncWebServerResponse *response; - if (mConfig->sys.darkMode) - response = request->beginResponse_P(200, F("text/css"), colorDark_css, colorDark_css_len); - else - response = request->beginResponse_P(200, F("text/css"), colorBright_css, colorBright_css_len); - response->addHeader(F("Content-Encoding"), "gzip"); - request->send(response); - } - - void onCss(AsyncWebServerRequest *request) { - DPRINTLN(DBG_VERBOSE, F("onCss")); - mLogoutTimeout = LOGOUT_TIMEOUT; - AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/css"), style_css, style_css_len); - response->addHeader(F("Content-Encoding"), "gzip"); - request->send(response); - } - - void onApiJs(AsyncWebServerRequest *request) { - DPRINTLN(DBG_VERBOSE, F("onApiJs")); - - AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/javascript"), api_js, api_js_len); - response->addHeader(F("Content-Encoding"), "gzip"); - request->send(response); - } - - void onFavicon(AsyncWebServerRequest *request) { - static const char favicon_type[] PROGMEM = "image/x-icon"; - AsyncWebServerResponse *response = request->beginResponse_P(200, favicon_type, favicon_ico, favicon_ico_len); - response->addHeader(F("Content-Encoding"), "gzip"); - request->send(response); - } - - void showNotFound(AsyncWebServerRequest *request) { - if (mProtected) - checkRedirect(request); - else - request->redirect("/setup"); - } - - void onReboot(AsyncWebServerRequest *request) { - mApp->setRebootFlag(); - AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html; charset=UTF-8"), system_html, system_html_len); - response->addHeader(F("Content-Encoding"), "gzip"); - request->send(response); - } - - void showErase(AsyncWebServerRequest *request) { - if (mProtected) { - checkRedirect(request); - return; - } - - DPRINTLN(DBG_VERBOSE, F("showErase")); - mApp->eraseSettings(false); - onReboot(request); - } - - void showFactoryRst(AsyncWebServerRequest *request) { - if (mProtected) { - checkRedirect(request); - return; - } - - DPRINTLN(DBG_VERBOSE, F("showFactoryRst")); - String content = ""; - int refresh = 3; - if (request->args() > 0) { - if (request->arg("reset").toInt() == 1) { - refresh = 10; - if (mApp->eraseSettings(true)) - content = F("factory reset: success\n\nrebooting ... "); - else - content = F("factory reset: failed\n\nrebooting ... "); - } else { - content = F("factory reset: aborted"); - refresh = 3; - } - } else { - content = F( - "

Factory Reset

" - "

RESET

CANCEL

"); - refresh = 120; + void onColor(AsyncWebServerRequest *request) { + DPRINTLN(DBG_VERBOSE, F("onColor")); + AsyncWebServerResponse *response; + if (mConfig->sys.darkMode) + response = request->beginResponse_P(200, F("text/css"), colorDark_css, colorDark_css_len); + else + response = request->beginResponse_P(200, F("text/css"), colorBright_css, colorBright_css_len); + response->addHeader(F("Content-Encoding"), "gzip"); + request->send(response); } - request->send(200, F("text/html; charset=UTF-8"), F("Factory Reset") + content + F("")); - if (refresh == 10) { - delay(1000); - ESP.restart(); + + void onCss(AsyncWebServerRequest *request) { + DPRINTLN(DBG_VERBOSE, F("onCss")); + mLogoutTimeout = LOGOUT_TIMEOUT; + AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/css"), style_css, style_css_len); + response->addHeader(F("Content-Encoding"), "gzip"); + request->send(response); } - } - void onSetup(AsyncWebServerRequest *request) { - DPRINTLN(DBG_VERBOSE, F("onSetup")); + void onApiJs(AsyncWebServerRequest *request) { + DPRINTLN(DBG_VERBOSE, F("onApiJs")); - if (CHECK_MASK(mConfig->sys.protectionMask, PROT_MASK_SETUP)) { - if (mProtected) { + AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/javascript"), api_js, api_js_len); + response->addHeader(F("Content-Encoding"), "gzip"); + request->send(response); + } + + void onFavicon(AsyncWebServerRequest *request) { + static const char favicon_type[] PROGMEM = "image/x-icon"; + AsyncWebServerResponse *response = request->beginResponse_P(200, favicon_type, favicon_ico, favicon_ico_len); + response->addHeader(F("Content-Encoding"), "gzip"); + request->send(response); + } + + void showNotFound(AsyncWebServerRequest *request) { + if (mProtected) checkRedirect(request); - return; - } + else + request->redirect("/setup"); } - AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html; charset=UTF-8"), setup_html, setup_html_len); - response->addHeader(F("Content-Encoding"), "gzip"); - request->send(response); - } - - void showSave(AsyncWebServerRequest *request) { - DPRINTLN(DBG_VERBOSE, F("showSave")); - - if (mProtected) { - checkRedirect(request); - return; - } - - if (request->args() == 0) - return; - - char buf[20] = {0}; - - // general - if (request->arg("ssid") != "") - request->arg("ssid").toCharArray(mConfig->sys.stationSsid, SSID_LEN); - if (request->arg("pwd") != "{PWD}") - request->arg("pwd").toCharArray(mConfig->sys.stationPwd, PWD_LEN); - if (request->arg("device") != "") - request->arg("device").toCharArray(mConfig->sys.deviceName, DEVNAME_LEN); - mConfig->sys.darkMode = (request->arg("darkMode") == "on"); - - // protection - if (request->arg("adminpwd") != "{PWD}") { - request->arg("adminpwd").toCharArray(mConfig->sys.adminPwd, PWD_LEN); - mProtected = (strlen(mConfig->sys.adminPwd) > 0); - } - mConfig->sys.protectionMask = 0x0000; - for (uint8_t i = 0; i < 6; i++) { - if (request->arg("protMask" + String(i)) == "on") - mConfig->sys.protectionMask |= (1 << i); - } - - // static ip - request->arg("ipAddr").toCharArray(buf, 20); - ah::ip2Arr(mConfig->sys.ip.ip, buf); - request->arg("ipMask").toCharArray(buf, 20); - ah::ip2Arr(mConfig->sys.ip.mask, buf); - request->arg("ipDns1").toCharArray(buf, 20); - ah::ip2Arr(mConfig->sys.ip.dns1, buf); - request->arg("ipDns2").toCharArray(buf, 20); - ah::ip2Arr(mConfig->sys.ip.dns2, buf); - request->arg("ipGateway").toCharArray(buf, 20); - ah::ip2Arr(mConfig->sys.ip.gateway, buf); - - // inverter - Inverter<> *iv; - for (uint8_t i = 0; i < MAX_NUM_INVERTERS; i++) { - iv = mSys->getInverterByPos(i, false); - // enable communication - iv->config->enabled = (request->arg("inv" + String(i) + "Enable") == "on"); - // address - request->arg("inv" + String(i) + "Addr").toCharArray(buf, 20); - if (strlen(buf) == 0) - memset(buf, 0, 20); - iv->config->serial.u64 = ah::Serial2u64(buf); - switch (iv->config->serial.b[4]) { - case 0x21: - iv->type = INV_TYPE_1CH; - iv->channels = 1; - break; - case 0x41: - iv->type = INV_TYPE_2CH; - iv->channels = 2; - break; - case 0x61: - iv->type = INV_TYPE_4CH; - iv->channels = 4; - break; - default: - break; - } - - // name - request->arg("inv" + String(i) + "Name").toCharArray(iv->config->name, MAX_NAME_LENGTH); - - // max channel power / name - for (uint8_t j = 0; j < 4; j++) { - iv->config->yieldCor[j] = request->arg("inv" + String(i) + "YieldCor" + String(j)).toInt(); - iv->config->chMaxPwr[j] = request->arg("inv" + String(i) + "ModPwr" + String(j)).toInt() & 0xffff; - request->arg("inv" + String(i) + "ModName" + String(j)).toCharArray(iv->config->chName[j], MAX_NAME_LENGTH); - } - iv->initialized = true; - } - - if (request->arg("invInterval") != "") - mConfig->nrf.sendInterval = request->arg("invInterval").toInt(); - if (request->arg("invRetry") != "") - mConfig->nrf.maxRetransPerPyld = request->arg("invRetry").toInt(); - mConfig->inst.rstYieldMidNight = (request->arg("invRstMid") == "on"); - mConfig->inst.rstValsCommStop = (request->arg("invRstComStop") == "on"); - mConfig->inst.rstValsNotAvail = (request->arg("invRstNotAvail") == "on"); - - // pinout - uint8_t pin; - for (uint8_t i = 0; i < 5; i++) { - pin = request->arg(String(pinArgNames[i])).toInt(); - switch (i) { - default: - mConfig->nrf.pinCs = ((pin != 0xff) ? pin : DEF_CS_PIN); - break; - case 1: - mConfig->nrf.pinCe = ((pin != 0xff) ? pin : DEF_CE_PIN); - break; - case 2: - mConfig->nrf.pinIrq = ((pin != 0xff) ? pin : DEF_IRQ_PIN); - break; - case 3: - mConfig->led.led0 = pin; - break; - case 4: - mConfig->led.led1 = pin; - break; - } - } - - // nrf24 amplifier power - mConfig->nrf.amplifierPower = request->arg("rf24Power").toInt() & 0x03; - - // ntp - if (request->arg("ntpAddr") != "") { - request->arg("ntpAddr").toCharArray(mConfig->ntp.addr, NTP_ADDR_LEN); - mConfig->ntp.port = request->arg("ntpPort").toInt() & 0xffff; - } - - // sun - if (request->arg("sunLat") == "" || (request->arg("sunLon") == "")) { - mConfig->sun.lat = 0.0; - mConfig->sun.lon = 0.0; - mConfig->sun.disNightCom = false; - mConfig->sun.offsetSec = 0; - } else { - mConfig->sun.lat = request->arg("sunLat").toFloat(); - mConfig->sun.lon = request->arg("sunLon").toFloat(); - mConfig->sun.disNightCom = (request->arg("sunDisNightCom") == "on"); - mConfig->sun.offsetSec = request->arg("sunOffs").toInt() * 60; - } - - // mqtt - if (request->arg("mqttAddr") != "") { - String addr = request->arg("mqttAddr"); - addr.trim(); - addr.toCharArray(mConfig->mqtt.broker, MQTT_ADDR_LEN); - } else - mConfig->mqtt.broker[0] = '\0'; - request->arg("mqttUser").toCharArray(mConfig->mqtt.user, MQTT_USER_LEN); - if (request->arg("mqttPwd") != "{PWD}") - request->arg("mqttPwd").toCharArray(mConfig->mqtt.pwd, MQTT_PWD_LEN); - request->arg("mqttTopic").toCharArray(mConfig->mqtt.topic, MQTT_TOPIC_LEN); - mConfig->mqtt.port = request->arg("mqttPort").toInt(); - mConfig->mqtt.interval = request->arg("mqttInterval").toInt(); - - // serial console - if (request->arg("serIntvl") != "") { - mConfig->serial.interval = request->arg("serIntvl").toInt() & 0xffff; - - mConfig->serial.debug = (request->arg("serDbg") == "on"); - mConfig->serial.showIv = (request->arg("serEn") == "on"); - // Needed to log TX buffers to serial console - mSys->Radio.mSerialDebug = mConfig->serial.debug; - } - - // display - mConfig->plugin.display.pwrSaveAtIvOffline = (request->arg("dispPwr") == "on"); - mConfig->plugin.display.pxShift = (request->arg("dispPxSh") == "on"); - mConfig->plugin.display.rot = request->arg("rotation").toInt(); - mConfig->plugin.display.type = request->arg("dispType").toInt(); - mConfig->plugin.display.contrast = request->arg("dispCont").toInt(); - mConfig->plugin.display.period = request->arg("period").toInt(); - mConfig->plugin.display.disp_data = request->arg("data").toInt(); - mConfig->plugin.display.disp_clk = request->arg("clock").toInt(); - mConfig->plugin.display.disp_cs = request->arg("cs").toInt(); - mConfig->plugin.display.disp_reset = request->arg("reset").toInt(); - mConfig->plugin.display.disp_busy = request->arg("busy").toInt(); - mConfig->plugin.display.disp_dc = request->arg("dc").toInt(); - - mApp->saveSettings(); - - if (request->arg("reboot") == "on") - onReboot(request); - else { + void onReboot(AsyncWebServerRequest *request) { + mApp->setRebootFlag(); AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html; charset=UTF-8"), system_html, system_html_len); response->addHeader(F("Content-Encoding"), "gzip"); request->send(response); } - } - void onLive(AsyncWebServerRequest *request) { - DPRINTLN(DBG_VERBOSE, F("onLive")); - - if (CHECK_MASK(mConfig->sys.protectionMask, PROT_MASK_LIVE)) { + void showErase(AsyncWebServerRequest *request) { if (mProtected) { checkRedirect(request); return; } + + DPRINTLN(DBG_VERBOSE, F("showErase")); + mApp->eraseSettings(false); + onReboot(request); } - AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html; charset=UTF-8"), visualization_html, visualization_html_len); - response->addHeader(F("Content-Encoding"), "gzip"); - response->addHeader(F("content-type"), "text/html; charset=UTF-8"); - - request->send(response); - } - - /*void showWebApi(AsyncWebServerRequest *request) { - // TODO: remove - DPRINTLN(DBG_VERBOSE, F("web::showWebApi")); - DPRINTLN(DBG_DEBUG, request->arg("plain")); - const size_t capacity = 200; // Use arduinojson.org/assistant to compute the capacity. - DynamicJsonDocument response(capacity); - - // Parse JSON object - deserializeJson(response, request->arg("plain")); - // ToDo: error handling for payload - uint8_t iv_id = response["inverter"]; - uint8_t cmd = response["cmd"]; - Inverter<> *iv = mSys->getInverterByPos(iv_id); - if (NULL != iv) { - if (response["tx_request"] == (uint8_t)TX_REQ_INFO) { - // if the AlarmData is requested set the Alarm Index to the requested one - if (cmd == AlarmData || cmd == AlarmUpdate) { - // set the AlarmMesIndex for the request from user input - iv->alarmMesIndex = response["payload"]; - } - DPRINTLN(DBG_INFO, F("Will make tx-request 0x15 with subcmd ") + String(cmd) + F(" and payload ") + String((uint16_t) response["payload"])); - // process payload from web request corresponding to the cmd - iv->enqueCommand(cmd); - } - - - if (response["tx_request"] == (uint8_t)TX_REQ_DEVCONTROL) { - if (response["cmd"] == (uint8_t)ActivePowerContr) { - uint16_t webapiPayload = response["payload"]; - uint16_t webapiPayload2 = response["payload2"]; - if (webapiPayload > 0 && webapiPayload < 10000) { - iv->devControlCmd = ActivePowerContr; - iv->powerLimit[0] = webapiPayload; - if (webapiPayload2 > 0) - iv->powerLimit[1] = webapiPayload2; // dev option, no sanity check - else // if not set, set it to 0x0000 default - iv->powerLimit[1] = AbsolutNonPersistent; // payload will be seted temporary in Watt absolut - if (iv->powerLimit[1] & 0x0001) - DPRINTLN(DBG_INFO, F("Power limit for inverter ") + String(iv->id) + F(" set to ") + String(iv->powerLimit[0]) + F("% via REST API")); - else - DPRINTLN(DBG_INFO, F("Power limit for inverter ") + String(iv->id) + F(" set to ") + String(iv->powerLimit[0]) + F("W via REST API")); - iv->devControlRequest = true; // queue it in the request loop - } - } - if (response["cmd"] == (uint8_t)TurnOff) { - iv->devControlCmd = TurnOff; - iv->devControlRequest = true; // queue it in the request loop - } - if (response["cmd"] == (uint8_t)TurnOn) { - iv->devControlCmd = TurnOn; - iv->devControlRequest = true; // queue it in the request loop - } - if (response["cmd"] == (uint8_t)CleanState_LockAndAlarm) { - iv->devControlCmd = CleanState_LockAndAlarm; - iv->devControlRequest = true; // queue it in the request loop - } - if (response["cmd"] == (uint8_t)Restart) { - iv->devControlCmd = Restart; - iv->devControlRequest = true; // queue it in the request loop + void showFactoryRst(AsyncWebServerRequest *request) { + if (mProtected) { + checkRedirect(request); + return; + } + + DPRINTLN(DBG_VERBOSE, F("showFactoryRst")); + String content = ""; + int refresh = 3; + if (request->args() > 0) { + if (request->arg("reset").toInt() == 1) { + refresh = 10; + if (mApp->eraseSettings(true)) + content = F("factory reset: success\n\nrebooting ... "); + else + content = F("factory reset: failed\n\nrebooting ... "); + } else { + content = F("factory reset: aborted"); + refresh = 3; } + } else { + content = F("

Factory Reset

" + "

RESET

CANCEL

"); + refresh = 120; + } + request->send(200, F("text/html; charset=UTF-8"), F("Factory Reset") + content + F("")); + if (refresh == 10) { + delay(1000); + ESP.restart(); } } - request->send(200, "text/json", "{success:true}"); - }*/ - void onDebug(AsyncWebServerRequest *request) { - mApp->getSchedulerNames(); - AsyncWebServerResponse *response = request->beginResponse(200, F("text/html; charset=UTF-8"), "ok"); - request->send(response); - } + void onSetup(AsyncWebServerRequest *request) { + DPRINTLN(DBG_VERBOSE, F("onSetup")); - void onSerial(AsyncWebServerRequest *request) { - DPRINTLN(DBG_VERBOSE, F("onSerial")); + if (CHECK_MASK(mConfig->sys.protectionMask, PROT_MASK_SETUP)) { + if (mProtected) { + checkRedirect(request); + return; + } + } + + AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html; charset=UTF-8"), setup_html, setup_html_len); + response->addHeader(F("Content-Encoding"), "gzip"); + request->send(response); + } + + void showSave(AsyncWebServerRequest *request) { + DPRINTLN(DBG_VERBOSE, F("showSave")); - if (CHECK_MASK(mConfig->sys.protectionMask, PROT_MASK_SERIAL)) { if (mProtected) { checkRedirect(request); return; } + + if (request->args() == 0) + return; + + char buf[20] = {0}; + + // general + if (request->arg("ssid") != "") + request->arg("ssid").toCharArray(mConfig->sys.stationSsid, SSID_LEN); + if (request->arg("pwd") != "{PWD}") + request->arg("pwd").toCharArray(mConfig->sys.stationPwd, PWD_LEN); + if (request->arg("device") != "") + request->arg("device").toCharArray(mConfig->sys.deviceName, DEVNAME_LEN); + mConfig->sys.darkMode = (request->arg("darkMode") == "on"); + + // protection + if (request->arg("adminpwd") != "{PWD}") { + request->arg("adminpwd").toCharArray(mConfig->sys.adminPwd, PWD_LEN); + mProtected = (strlen(mConfig->sys.adminPwd) > 0); + } + mConfig->sys.protectionMask = 0x0000; + for (uint8_t i = 0; i < 6; i++) { + if (request->arg("protMask" + String(i)) == "on") + mConfig->sys.protectionMask |= (1 << i); + } + + // static ip + request->arg("ipAddr").toCharArray(buf, 20); + ah::ip2Arr(mConfig->sys.ip.ip, buf); + request->arg("ipMask").toCharArray(buf, 20); + ah::ip2Arr(mConfig->sys.ip.mask, buf); + request->arg("ipDns1").toCharArray(buf, 20); + ah::ip2Arr(mConfig->sys.ip.dns1, buf); + request->arg("ipDns2").toCharArray(buf, 20); + ah::ip2Arr(mConfig->sys.ip.dns2, buf); + request->arg("ipGateway").toCharArray(buf, 20); + ah::ip2Arr(mConfig->sys.ip.gateway, buf); + + // inverter + Inverter<> *iv; + for (uint8_t i = 0; i < MAX_NUM_INVERTERS; i++) { + iv = mSys->getInverterByPos(i, false); + // enable communication + iv->config->enabled = (request->arg("inv" + String(i) + "Enable") == "on"); + // address + request->arg("inv" + String(i) + "Addr").toCharArray(buf, 20); + if (strlen(buf) == 0) + memset(buf, 0, 20); + iv->config->serial.u64 = ah::Serial2u64(buf); + switch(iv->config->serial.b[4]) { + case 0x21: iv->type = INV_TYPE_1CH; iv->channels = 1; break; + case 0x41: iv->type = INV_TYPE_2CH; iv->channels = 2; break; + case 0x61: iv->type = INV_TYPE_4CH; iv->channels = 4; break; + default: break; + } + + // name + request->arg("inv" + String(i) + "Name").toCharArray(iv->config->name, MAX_NAME_LENGTH); + + // max channel power / name + for (uint8_t j = 0; j < 4; j++) { + iv->config->yieldCor[j] = request->arg("inv" + String(i) + "YieldCor" + String(j)).toInt(); + iv->config->chMaxPwr[j] = request->arg("inv" + String(i) + "ModPwr" + String(j)).toInt() & 0xffff; + request->arg("inv" + String(i) + "ModName" + String(j)).toCharArray(iv->config->chName[j], MAX_NAME_LENGTH); + } + iv->initialized = true; + } + + if (request->arg("invInterval") != "") + mConfig->nrf.sendInterval = request->arg("invInterval").toInt(); + if (request->arg("invRetry") != "") + mConfig->nrf.maxRetransPerPyld = request->arg("invRetry").toInt(); + mConfig->inst.rstYieldMidNight = (request->arg("invRstMid") == "on"); + mConfig->inst.rstValsCommStop = (request->arg("invRstComStop") == "on"); + mConfig->inst.rstValsNotAvail = (request->arg("invRstNotAvail") == "on"); + + // pinout + uint8_t pin; + for (uint8_t i = 0; i < 5; i++) { + pin = request->arg(String(pinArgNames[i])).toInt(); + switch(i) { + default: mConfig->nrf.pinCs = ((pin != 0xff) ? pin : DEF_CS_PIN); break; + case 1: mConfig->nrf.pinCe = ((pin != 0xff) ? pin : DEF_CE_PIN); break; + case 2: mConfig->nrf.pinIrq = ((pin != 0xff) ? pin : DEF_IRQ_PIN); break; + case 3: mConfig->led.led0 = pin; break; + case 4: mConfig->led.led1 = pin; break; + } + } + + // nrf24 amplifier power + mConfig->nrf.amplifierPower = request->arg("rf24Power").toInt() & 0x03; + + // ntp + if (request->arg("ntpAddr") != "") { + request->arg("ntpAddr").toCharArray(mConfig->ntp.addr, NTP_ADDR_LEN); + mConfig->ntp.port = request->arg("ntpPort").toInt() & 0xffff; + } + + // sun + if (request->arg("sunLat") == "" || (request->arg("sunLon") == "")) { + mConfig->sun.lat = 0.0; + mConfig->sun.lon = 0.0; + mConfig->sun.disNightCom = false; + mConfig->sun.offsetSec = 0; + } else { + mConfig->sun.lat = request->arg("sunLat").toFloat(); + mConfig->sun.lon = request->arg("sunLon").toFloat(); + mConfig->sun.disNightCom = (request->arg("sunDisNightCom") == "on"); + mConfig->sun.offsetSec = request->arg("sunOffs").toInt() * 60; + } + + // mqtt + if (request->arg("mqttAddr") != "") { + String addr = request->arg("mqttAddr"); + addr.trim(); + addr.toCharArray(mConfig->mqtt.broker, MQTT_ADDR_LEN); + } else + mConfig->mqtt.broker[0] = '\0'; + request->arg("mqttUser").toCharArray(mConfig->mqtt.user, MQTT_USER_LEN); + if (request->arg("mqttPwd") != "{PWD}") + request->arg("mqttPwd").toCharArray(mConfig->mqtt.pwd, MQTT_PWD_LEN); + request->arg("mqttTopic").toCharArray(mConfig->mqtt.topic, MQTT_TOPIC_LEN); + mConfig->mqtt.port = request->arg("mqttPort").toInt(); + mConfig->mqtt.interval = request->arg("mqttInterval").toInt(); + + // serial console + if (request->arg("serIntvl") != "") { + mConfig->serial.interval = request->arg("serIntvl").toInt() & 0xffff; + + mConfig->serial.debug = (request->arg("serDbg") == "on"); + mConfig->serial.showIv = (request->arg("serEn") == "on"); + // Needed to log TX buffers to serial console + mSys->Radio.mSerialDebug = mConfig->serial.debug; + } + + // display + mConfig->plugin.display.pwrSaveAtIvOffline = (request->arg("disp_pwr") == "on"); + mConfig->plugin.display.pxShift = (request->arg("disp_pxshift") == "on"); + mConfig->plugin.display.rot = request->arg("disp_rot").toInt(); + mConfig->plugin.display.type = request->arg("disp_typ").toInt(); + mConfig->plugin.display.contrast = request->arg("disp_cont").toInt(); + mConfig->plugin.display.disp_data = request->arg("disp_data").toInt(); + mConfig->plugin.display.disp_clk = request->arg("disp_clk").toInt(); + mConfig->plugin.display.disp_cs = request->arg("disp_cs").toInt(); + mConfig->plugin.display.disp_reset = request->arg("disp_rst").toInt(); + mConfig->plugin.display.disp_busy = request->arg("disp_bsy").toInt(); + mConfig->plugin.display.disp_dc = request->arg("disp_dc").toInt(); + + mApp->saveSettings(); + + if (request->arg("reboot") == "on") + onReboot(request); + else { + AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html; charset=UTF-8"), system_html, system_html_len); + response->addHeader(F("Content-Encoding"), "gzip"); + request->send(response); + } } - AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html; charset=UTF-8"), serial_html, serial_html_len); - response->addHeader(F("Content-Encoding"), "gzip"); - request->send(response); - } + void onLive(AsyncWebServerRequest *request) { + DPRINTLN(DBG_VERBOSE, F("onLive")); - void onSystem(AsyncWebServerRequest *request) { - DPRINTLN(DBG_VERBOSE, F("onSystem")); + if (CHECK_MASK(mConfig->sys.protectionMask, PROT_MASK_LIVE)) { + if (mProtected) { + checkRedirect(request); + return; + } + } - if (CHECK_MASK(mConfig->sys.protectionMask, PROT_MASK_SYSTEM)) { - if (mProtected) { - checkRedirect(request); - return; + AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html; charset=UTF-8"), visualization_html, visualization_html_len); + response->addHeader(F("Content-Encoding"), "gzip"); + response->addHeader(F("content-type"), "text/html; charset=UTF-8"); + + request->send(response); + } + + /*void showWebApi(AsyncWebServerRequest *request) { + // TODO: remove + DPRINTLN(DBG_VERBOSE, F("web::showWebApi")); + DPRINTLN(DBG_DEBUG, request->arg("plain")); + const size_t capacity = 200; // Use arduinojson.org/assistant to compute the capacity. + DynamicJsonDocument response(capacity); + + // Parse JSON object + deserializeJson(response, request->arg("plain")); + // ToDo: error handling for payload + uint8_t iv_id = response["inverter"]; + uint8_t cmd = response["cmd"]; + Inverter<> *iv = mSys->getInverterByPos(iv_id); + if (NULL != iv) { + if (response["tx_request"] == (uint8_t)TX_REQ_INFO) { + // if the AlarmData is requested set the Alarm Index to the requested one + if (cmd == AlarmData || cmd == AlarmUpdate) { + // set the AlarmMesIndex for the request from user input + iv->alarmMesIndex = response["payload"]; + } + DPRINTLN(DBG_INFO, F("Will make tx-request 0x15 with subcmd ") + String(cmd) + F(" and payload ") + String((uint16_t) response["payload"])); + // process payload from web request corresponding to the cmd + iv->enqueCommand(cmd); + } + + + if (response["tx_request"] == (uint8_t)TX_REQ_DEVCONTROL) { + if (response["cmd"] == (uint8_t)ActivePowerContr) { + uint16_t webapiPayload = response["payload"]; + uint16_t webapiPayload2 = response["payload2"]; + if (webapiPayload > 0 && webapiPayload < 10000) { + iv->devControlCmd = ActivePowerContr; + iv->powerLimit[0] = webapiPayload; + if (webapiPayload2 > 0) + iv->powerLimit[1] = webapiPayload2; // dev option, no sanity check + else // if not set, set it to 0x0000 default + iv->powerLimit[1] = AbsolutNonPersistent; // payload will be seted temporary in Watt absolut + if (iv->powerLimit[1] & 0x0001) + DPRINTLN(DBG_INFO, F("Power limit for inverter ") + String(iv->id) + F(" set to ") + String(iv->powerLimit[0]) + F("% via REST API")); + else + DPRINTLN(DBG_INFO, F("Power limit for inverter ") + String(iv->id) + F(" set to ") + String(iv->powerLimit[0]) + F("W via REST API")); + iv->devControlRequest = true; // queue it in the request loop + } + } + if (response["cmd"] == (uint8_t)TurnOff) { + iv->devControlCmd = TurnOff; + iv->devControlRequest = true; // queue it in the request loop + } + if (response["cmd"] == (uint8_t)TurnOn) { + iv->devControlCmd = TurnOn; + iv->devControlRequest = true; // queue it in the request loop + } + if (response["cmd"] == (uint8_t)CleanState_LockAndAlarm) { + iv->devControlCmd = CleanState_LockAndAlarm; + iv->devControlRequest = true; // queue it in the request loop + } + if (response["cmd"] == (uint8_t)Restart) { + iv->devControlCmd = Restart; + iv->devControlRequest = true; // queue it in the request loop + } + } } + request->send(200, "text/json", "{success:true}"); + }*/ + + void onDebug(AsyncWebServerRequest *request) { + mApp->getSchedulerNames(); + AsyncWebServerResponse *response = request->beginResponse(200, F("text/html; charset=UTF-8"), "ok"); + request->send(response); } - AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html; charset=UTF-8"), system_html, system_html_len); - response->addHeader(F("Content-Encoding"), "gzip"); - request->send(response); - } + void onSerial(AsyncWebServerRequest *request) { + DPRINTLN(DBG_VERBOSE, F("onSerial")); -#ifdef ENABLE_JSON_EP - void showJson(AsyncWebServerRequest *request) { - DPRINTLN(DBG_VERBOSE, F("web::showJson")); - String modJson; - Inverter<> *iv; - record_t<> *rec; - char topic[40], val[25]; + if (CHECK_MASK(mConfig->sys.protectionMask, PROT_MASK_SERIAL)) { + if (mProtected) { + checkRedirect(request); + return; + } + } + + AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html; charset=UTF-8"), serial_html, serial_html_len); + response->addHeader(F("Content-Encoding"), "gzip"); + request->send(response); + } - modJson = F("{\n"); - for (uint8_t id = 0; id < mSys->getNumInverters(); id++) { - iv = mSys->getInverterByPos(id); - if (NULL == iv) - continue; + void onSystem(AsyncWebServerRequest *request) { + DPRINTLN(DBG_VERBOSE, F("onSystem")); - rec = iv->getRecordStruct(RealTimeRunData_Debug); - snprintf(topic, 30, "\"%s\": {\n", iv->config->name); - modJson += String(topic); - for (uint8_t i = 0; i < rec->length; i++) { - snprintf(topic, 40, "\t\"ch%d/%s\"", rec->assign[i].ch, iv->getFieldName(i, rec)); - snprintf(val, 25, "[%.3f, \"%s\"]", iv->getValue(i, rec), iv->getUnit(i, rec)); - modJson += String(topic) + ": " + String(val) + F(",\n"); + if (CHECK_MASK(mConfig->sys.protectionMask, PROT_MASK_SYSTEM)) { + if (mProtected) { + checkRedirect(request); + return; + } } - modJson += F("\t\"last_msg\": \"") + ah::getDateTimeStr(rec->ts) + F("\"\n\t},\n"); + + AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html; charset=UTF-8"), system_html, system_html_len); + response->addHeader(F("Content-Encoding"), "gzip"); + request->send(response); } - modJson += F("\"json_ts\": \"") + String(ah::getDateTimeStr(mApp->getTimestamp())) + F("\"\n}\n"); - AsyncWebServerResponse *response = request->beginResponse(200, F("application/json"), modJson); - request->send(response); - } + #ifdef ENABLE_JSON_EP + void showJson(AsyncWebServerRequest *request) { + DPRINTLN(DBG_VERBOSE, F("web::showJson")); + String modJson; + Inverter<> *iv; + record_t<> *rec; + char topic[40], val[25]; + + modJson = F("{\n"); + for (uint8_t id = 0; id < mSys->getNumInverters(); id++) { + iv = mSys->getInverterByPos(id); + if (NULL == iv) + continue; + + rec = iv->getRecordStruct(RealTimeRunData_Debug); + snprintf(topic, 30, "\"%s\": {\n", iv->config->name); + modJson += String(topic); + for (uint8_t i = 0; i < rec->length; i++) { + snprintf(topic, 40, "\t\"ch%d/%s\"", rec->assign[i].ch, iv->getFieldName(i, rec)); + snprintf(val, 25, "[%.3f, \"%s\"]", iv->getValue(i, rec), iv->getUnit(i, rec)); + modJson += String(topic) + ": " + String(val) + F(",\n"); + } + modJson += F("\t\"last_msg\": \"") + ah::getDateTimeStr(rec->ts) + F("\"\n\t},\n"); + } + modJson += F("\"json_ts\": \"") + String(ah::getDateTimeStr(mApp->getTimestamp())) + F("\"\n}\n"); + + AsyncWebServerResponse *response = request->beginResponse(200, F("application/json"), modJson); + request->send(response); + } #endif #ifdef ENABLE_PROMETHEUS_EP - enum { - metricsStateStart, - metricsStateInverter, - metricStateRealtimeData, - metricsStateAlarmData, - metricsStateEnd - } metricsStep; - int metricsInverterId, metricsChannelId; - - void showMetrics(AsyncWebServerRequest *request) { - DPRINTLN(DBG_VERBOSE, F("web::showMetrics")); - - metricsStep = metricsStateStart; - AsyncWebServerResponse *response = request->beginChunkedResponse(F("text/plain"), - [this](uint8_t *buffer, size_t maxLen, size_t filledLength) -> size_t { - Inverter<> *iv; - record_t<> *rec; - statistics_t *stat; - String promUnit, promType; - String metrics; - char type[60], topic[100], val[25]; - size_t len = 0; - int alarmChannelId; - - switch (metricsStep) { - case metricsStateStart: // System Info & NRF Statistics : fit to one packet - snprintf(type, sizeof(type), "# TYPE ahoy_solar_info gauge\n"); - snprintf(topic, sizeof(topic), "ahoy_solar_info{version=\"%s\",image=\"\",devicename=\"%s\"} 1\n", - mApp->getVersion(), mConfig->sys.deviceName); - metrics = String(type) + String(topic); - - snprintf(topic, sizeof(topic), "# TYPE ahoy_solar_freeheap gauge\nahoy_solar_freeheap{devicename=\"%s\"} %u\n", mConfig->sys.deviceName, ESP.getFreeHeap()); - metrics += String(topic); - - // NRF Statistics - stat = mApp->getStatistics(); - metrics += radioStatistic(F("rx_success"), stat->rxSuccess); - metrics += radioStatistic(F("rx_fail"), stat->rxFail); - metrics += radioStatistic(F("rx_fail_answer"), stat->rxFailNoAnser); - metrics += radioStatistic(F("frame_cnt"), stat->frmCnt); - metrics += radioStatistic(F("tx_cnt"), mSys->Radio.mSendCnt); - - len = snprintf((char *)buffer, maxLen, "%s", metrics.c_str()); - // Start Inverter loop - metricsInverterId = 0; - metricsStep = metricsStateInverter; - break; - - case metricsStateInverter: // Inverter loop - if (metricsInverterId < mSys->getNumInverters()) { - iv = mSys->getInverterByPos(metricsInverterId); - if (NULL != iv) { - // Inverter info : fit to one packet - snprintf(type, sizeof(type), "# TYPE ahoy_solar_inverter_info gauge\n"); - snprintf(topic, sizeof(topic), "ahoy_solar_inverter_info{name=\"%s\",serial=\"%12llx\"} 1\n", - iv->config->name, iv->config->serial.u64); - metrics = String(type) + String(topic); - - snprintf(type, sizeof(type), "# TYPE ahoy_solar_inverter_is_enabled gauge\n"); - snprintf(topic, sizeof(topic), "ahoy_solar_inverter_is_enabled {inverter=\"%s\"} %d\n", iv->config->name, iv->config->enabled); - metrics += String(type) + String(topic); - - snprintf(type, sizeof(type), "# TYPE ahoy_solar_inverter_is_available gauge\n"); - snprintf(topic, sizeof(topic), "ahoy_solar_inverter_is_available {inverter=\"%s\"} %d\n", iv->config->name, iv->isAvailable(mApp->getTimestamp())); - metrics += String(type) + String(topic); - - snprintf(type, sizeof(type), "# TYPE ahoy_solar_inverter_is_producing gauge\n"); - snprintf(topic, sizeof(topic), "ahoy_solar_inverter_is_producing {inverter=\"%s\"} %d\n", iv->config->name, iv->isProducing(mApp->getTimestamp())); - metrics += String(type) + String(topic); - - len = snprintf((char *)buffer, maxLen, "%s", metrics.c_str()); - - // Start Realtime Data Channel loop for this inverter - metricsChannelId = 0; - metricsStep = metricStateRealtimeData; - } - } else { - metricsStep = metricsStateEnd; - } - break; - - case metricStateRealtimeData: // Realtime Data Channel loop - iv = mSys->getInverterByPos(metricsInverterId); - rec = iv->getRecordStruct(RealTimeRunData_Debug); - if (metricsChannelId < rec->length) { - uint8_t channel = rec->assign[metricsChannelId].ch; - std::tie(promUnit, promType) = convertToPromUnits(iv->getUnit(metricsChannelId, rec)); - snprintf(type, sizeof(type), "# TYPE ahoy_solar_%s%s %s", iv->getFieldName(metricsChannelId, rec), promUnit.c_str(), promType.c_str()); - if (0 == channel) { - snprintf(topic, sizeof(topic), "ahoy_solar_%s%s{inverter=\"%s\"}", iv->getFieldName(metricsChannelId, rec), promUnit.c_str(), iv->config->name); - } else { - snprintf(topic, sizeof(topic), "ahoy_solar_%s%s{inverter=\"%s\",channel=\"%s\"}", iv->getFieldName(metricsChannelId, rec), promUnit.c_str(), iv->config->name, iv->config->chName[channel - 1]); - } - snprintf(val, sizeof(val), "%.3f", iv->getValue(metricsChannelId, rec)); - len = snprintf((char *)buffer, maxLen, "%s\n%s %s\n", type, topic, val); - - metricsChannelId++; - } else { - len = snprintf((char *)buffer, maxLen, "#\n"); // At least one char to send otherwise the transmission ends. - - // All realtime data channels processed --> try alarm data - metricsStep = metricsStateAlarmData; - } - break; - - case metricsStateAlarmData: // Alarm Info loop - iv = mSys->getInverterByPos(metricsInverterId); - rec = iv->getRecordStruct(AlarmData); - // simple hack : there is only one channel with alarm data - // TODO: find the right one channel with the alarm id - alarmChannelId = 0; - // printf("AlarmData Length %d\n",rec->length); - if (alarmChannelId < rec->length) { - // uint8_t channel = rec->assign[alarmChannelId].ch; - std::tie(promUnit, promType) = convertToPromUnits(iv->getUnit(alarmChannelId, rec)); - snprintf(type, sizeof(type), "# TYPE ahoy_solar_%s%s %s", iv->getFieldName(alarmChannelId, rec), promUnit.c_str(), promType.c_str()); - snprintf(topic, sizeof(topic), "ahoy_solar_%s%s{inverter=\"%s\"}", iv->getFieldName(alarmChannelId, rec), promUnit.c_str(), iv->config->name); - snprintf(val, sizeof(val), "%.3f", iv->getValue(alarmChannelId, rec)); - len = snprintf((char *)buffer, maxLen, "%s\n%s %s\n", type, topic, val); - } else { - len = snprintf((char *)buffer, maxLen, "#\n"); // At least one char to send otherwise the transmission ends. - } - // alarm channel processed --> try next inverter - metricsInverterId++; - metricsStep = metricsStateInverter; - break; - - case metricsStateEnd: - default: // end of transmission - len = 0; - break; - } - return len; - }); - request->send(response); - } - - String radioStatistic(String statistic, uint32_t value) { - char type[60], topic[80], val[25]; - snprintf(type, sizeof(type), "# TYPE ahoy_solar_radio_%s gauge", statistic.c_str()); - snprintf(topic, sizeof(topic), "ahoy_solar_radio_%s", statistic.c_str()); - snprintf(val, sizeof(val), "%d", value); - return (String(type) + "\n" + String(topic) + " " + String(val) + "\n"); - } - - std::pair convertToPromUnits(String shortUnit) { - if (shortUnit == "A") return {"_ampere", "gauge"}; - if (shortUnit == "V") return {"_volt", "gauge"}; - if (shortUnit == "%") return {"_ratio", "gauge"}; - if (shortUnit == "W") return {"_watt", "gauge"}; - if (shortUnit == "Wh") return {"_wattHours", "counter"}; - if (shortUnit == "kWh") return {"_kilowattHours", "counter"}; - if (shortUnit == "°C") return {"_celsius", "gauge"}; - if (shortUnit == "var") return {"_var", "gauge"}; - if (shortUnit == "Hz") return {"_hertz", "gauge"}; - return {"", "gauge"}; - } + enum { + metricsStateStart, metricsStateInverter, metricStateRealtimeData,metricsStateAlarmData,metricsStateEnd + } metricsStep; + int metricsInverterId,metricsChannelId; + + void showMetrics(AsyncWebServerRequest *request) { + DPRINTLN(DBG_VERBOSE, F("web::showMetrics")); + + metricsStep = metricsStateStart; + AsyncWebServerResponse *response = request->beginChunkedResponse(F("text/plain"), + [this](uint8_t *buffer, size_t maxLen, size_t filledLength) -> size_t + { + Inverter<> *iv; + record_t<> *rec; + statistics_t *stat; + String promUnit, promType; + String metrics; + char type[60], topic[100], val[25]; + size_t len = 0; + int alarmChannelId; + + switch (metricsStep) { + case metricsStateStart: // System Info & NRF Statistics : fit to one packet + snprintf(type,sizeof(type),"# TYPE ahoy_solar_info gauge\n"); + snprintf(topic,sizeof(topic),"ahoy_solar_info{version=\"%s\",image=\"\",devicename=\"%s\"} 1\n", + mApp->getVersion(), mConfig->sys.deviceName); + metrics = String(type) + String(topic); + + snprintf(topic,sizeof(topic),"# TYPE ahoy_solar_freeheap gauge\nahoy_solar_freeheap{devicename=\"%s\"} %u\n",mConfig->sys.deviceName,ESP.getFreeHeap()); + metrics += String(topic); + + // NRF Statistics + stat = mApp->getStatistics(); + metrics += radioStatistic(F("rx_success"), stat->rxSuccess); + metrics += radioStatistic(F("rx_fail"), stat->rxFail); + metrics += radioStatistic(F("rx_fail_answer"), stat->rxFailNoAnser); + metrics += radioStatistic(F("frame_cnt"), stat->frmCnt); + metrics += radioStatistic(F("tx_cnt"), mSys->Radio.mSendCnt); + + len = snprintf((char *)buffer,maxLen,"%s",metrics.c_str()); + // Start Inverter loop + metricsInverterId = 0; + metricsStep = metricsStateInverter; + break; + + case metricsStateInverter: // Inverter loop + if (metricsInverterId < mSys->getNumInverters()) { + iv = mSys->getInverterByPos(metricsInverterId); + if(NULL != iv) { + // Inverter info : fit to one packet + snprintf(type,sizeof(type),"# TYPE ahoy_solar_inverter_info gauge\n"); + snprintf(topic,sizeof(topic),"ahoy_solar_inverter_info{name=\"%s\",serial=\"%12llx\"} 1\n", + iv->config->name, iv->config->serial.u64); + metrics = String(type) + String(topic); + + snprintf(type,sizeof(type),"# TYPE ahoy_solar_inverter_is_enabled gauge\n"); + snprintf(topic,sizeof(topic),"ahoy_solar_inverter_is_enabled {inverter=\"%s\"} %d\n",iv->config->name,iv->config->enabled); + metrics += String(type) + String(topic); + + snprintf(type,sizeof(type),"# TYPE ahoy_solar_inverter_is_available gauge\n"); + snprintf(topic,sizeof(topic),"ahoy_solar_inverter_is_available {inverter=\"%s\"} %d\n",iv->config->name,iv->isAvailable(mApp->getTimestamp())); + metrics += String(type) + String(topic); + + snprintf(type,sizeof(type),"# TYPE ahoy_solar_inverter_is_producing gauge\n"); + snprintf(topic,sizeof(topic),"ahoy_solar_inverter_is_producing {inverter=\"%s\"} %d\n",iv->config->name,iv->isProducing(mApp->getTimestamp())); + metrics += String(type) + String(topic); + + len = snprintf((char *)buffer,maxLen,"%s",metrics.c_str()); + + // Start Realtime Data Channel loop for this inverter + metricsChannelId = 0; + metricsStep = metricStateRealtimeData; + } + } else { + metricsStep = metricsStateEnd; + } + break; + + case metricStateRealtimeData: // Realtime Data Channel loop + iv = mSys->getInverterByPos(metricsInverterId); + rec = iv->getRecordStruct(RealTimeRunData_Debug); + if (metricsChannelId < rec->length) { + uint8_t channel = rec->assign[metricsChannelId].ch; + std::tie(promUnit, promType) = convertToPromUnits(iv->getUnit(metricsChannelId, rec)); + snprintf(type, sizeof(type), "# TYPE ahoy_solar_%s%s %s", iv->getFieldName(metricsChannelId, rec), promUnit.c_str(), promType.c_str()); + if (0 == channel) { + snprintf(topic, sizeof(topic), "ahoy_solar_%s%s{inverter=\"%s\"}", iv->getFieldName(metricsChannelId, rec), promUnit.c_str(), iv->config->name); + } else { + snprintf(topic, sizeof(topic), "ahoy_solar_%s%s{inverter=\"%s\",channel=\"%s\"}", iv->getFieldName(metricsChannelId, rec), promUnit.c_str(), iv->config->name,iv->config->chName[channel-1]); + } + snprintf(val, sizeof(val), "%.3f", iv->getValue(metricsChannelId, rec)); + len = snprintf((char*)buffer,maxLen,"%s\n%s %s\n",type,topic,val); + + metricsChannelId++; + } else { + len = snprintf((char*)buffer,maxLen,"#\n"); // At least one char to send otherwise the transmission ends. + + // All realtime data channels processed --> try alarm data + metricsStep = metricsStateAlarmData; + } + break; + + case metricsStateAlarmData: // Alarm Info loop + iv = mSys->getInverterByPos(metricsInverterId); + rec = iv->getRecordStruct(AlarmData); + // simple hack : there is only one channel with alarm data + // TODO: find the right one channel with the alarm id + alarmChannelId = 0; + // printf("AlarmData Length %d\n",rec->length); + if (alarmChannelId < rec->length) { + //uint8_t channel = rec->assign[alarmChannelId].ch; + std::tie(promUnit, promType) = convertToPromUnits(iv->getUnit(alarmChannelId, rec)); + snprintf(type, sizeof(type), "# TYPE ahoy_solar_%s%s %s", iv->getFieldName(alarmChannelId, rec), promUnit.c_str(), promType.c_str()); + snprintf(topic, sizeof(topic), "ahoy_solar_%s%s{inverter=\"%s\"}", iv->getFieldName(alarmChannelId, rec), promUnit.c_str(), iv->config->name); + snprintf(val, sizeof(val), "%.3f", iv->getValue(alarmChannelId, rec)); + len = snprintf((char*)buffer,maxLen,"%s\n%s %s\n",type,topic,val); + } else { + len = snprintf((char*)buffer,maxLen,"#\n"); // At least one char to send otherwise the transmission ends. + } + // alarm channel processed --> try next inverter + metricsInverterId++; + metricsStep = metricsStateInverter; + break; + + case metricsStateEnd: + default: // end of transmission + len = 0; + break; + } + return len; + }); + request->send(response); + } + + String radioStatistic(String statistic, uint32_t value) { + char type[60], topic[80], val[25]; + snprintf(type, sizeof(type), "# TYPE ahoy_solar_radio_%s gauge",statistic.c_str()); + snprintf(topic, sizeof(topic), "ahoy_solar_radio_%s",statistic.c_str()); + snprintf(val, sizeof(val), "%d", value); + return ( String(type) + "\n" + String(topic) + " " + String(val) + "\n"); + } + + std::pair convertToPromUnits(String shortUnit) { + if(shortUnit == "A") return {"_ampere", "gauge"}; + if(shortUnit == "V") return {"_volt", "gauge"}; + if(shortUnit == "%") return {"_ratio", "gauge"}; + if(shortUnit == "W") return {"_watt", "gauge"}; + if(shortUnit == "Wh") return {"_wattHours", "counter"}; + if(shortUnit == "kWh") return {"_kilowattHours", "counter"}; + if(shortUnit == "°C") return {"_celsius", "gauge"}; + if(shortUnit == "var") return {"_var", "gauge"}; + if(shortUnit == "Hz") return {"_hertz", "gauge"}; + return {"", "gauge"}; + } #endif - AsyncWebServer mWeb; - AsyncEventSource mEvts; - bool mProtected; - uint32_t mLogoutTimeout; - IApp *mApp; - HMSYSTEM *mSys; - - settings_t *mConfig; - - bool mSerialAddTime; - char mSerialBuf[WEB_SERIAL_BUF_SIZE]; - uint16_t mSerialBufFill; - bool mSerialClientConnnected; - - File mUploadFp; - bool mUploadFail; + AsyncWebServer mWeb; + AsyncEventSource mEvts; + bool mProtected; + uint32_t mLogoutTimeout; + IApp *mApp; + HMSYSTEM *mSys; + + settings_t *mConfig; + + bool mSerialAddTime; + char mSerialBuf[WEB_SERIAL_BUF_SIZE]; + uint16_t mSerialBufFill; + bool mSerialClientConnnected; + + File mUploadFp; + bool mUploadFail; }; #endif /*__WEB_H__*/