Browse Source

Merge branch 'dAjaY85-development03' into development03

pull/735/head^2
lumapu 2 years ago
parent
commit
501daf04bc
  1. 87
      src/app.cpp
  2. 42
      src/app.h
  3. 197
      src/config/settings.h
  4. 1
      src/platformio.ini
  5. 125
      src/plugins/Display/Display.h
  6. 144
      src/plugins/Display/Display_Mono.cpp
  7. 41
      src/plugins/Display/Display_Mono.h
  8. 192
      src/plugins/Display/Display_ePaper.cpp
  9. 51
      src/plugins/Display/Display_ePaper.h
  10. 642
      src/plugins/Display/imagedata.cpp
  11. 5
      src/plugins/Display/imagedata.h
  12. 217
      src/plugins/MonochromeDisplay/MonochromeDisplay.h
  13. 271
      src/web/RestApi.h
  14. 355
      src/web/web.h

87
src/app.cpp

@ -4,13 +4,14 @@
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
#include "app.h" #include "app.h"
#include <ArduinoJson.h> #include <ArduinoJson.h>
#include "utils/sun.h" #include "utils/sun.h"
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
app::app() : ah::Scheduler() {} app::app() : ah::Scheduler() {}
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
void app::setup() { void app::setup() {
Serial.begin(115200); Serial.begin(115200);
@ -24,7 +25,7 @@ void app::setup() {
mSettings.setup(); mSettings.setup();
mSettings.getPtr(mConfig); mSettings.getPtr(mConfig);
DPRINT(DBG_INFO, F("Settings valid: ")); DPRINT(DBG_INFO, F("Settings valid: "));
if(mSettings.getValid()) if (mSettings.getValid())
DBGPRINTLN(F("true")); DBGPRINTLN(F("true"));
else else
DBGPRINTLN(F("false")); DBGPRINTLN(F("false"));
@ -32,16 +33,16 @@ void app::setup() {
mSys.enableDebug(); mSys.enableDebug();
mSys.setup(mConfig->nrf.amplifierPower, mConfig->nrf.pinIrq, mConfig->nrf.pinCe, mConfig->nrf.pinCs); 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); mInnerLoopCb = std::bind(&app::loopStandard, this);
#else #else
mInnerLoopCb = std::bind(&app::loopWifi, this); mInnerLoopCb = std::bind(&app::loopWifi, this);
#endif #endif
mWifi.setup(mConfig, &mTimestamp, std::bind(&app::onWifi, this, std::placeholders::_1)); 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"); everySec(std::bind(&ahoywifi::tickWifiLoop, &mWifi), "wifiL");
#endif #endif
mSys.addInverters(&mConfig->inst); mSys.addInverters(&mConfig->inst);
@ -52,23 +53,23 @@ void app::setup() {
mMiPayload.setup(this, &mSys, &mStat, mConfig->nrf.maxRetransPerPyld, &mTimestamp); mMiPayload.setup(this, &mSys, &mStat, mConfig->nrf.maxRetransPerPyld, &mTimestamp);
mMiPayload.enableSerialDebug(mConfig->serial.debug); mMiPayload.enableSerialDebug(mConfig->serial.debug);
//DBGPRINTLN("--- after payload"); // DBGPRINTLN("--- after payload");
//DBGPRINTLN(String(ESP.getFreeHeap())); // DBGPRINTLN(String(ESP.getFreeHeap()));
//DBGPRINTLN(String(ESP.getHeapFragmentation())); // DBGPRINTLN(String(ESP.getHeapFragmentation()));
//DBGPRINTLN(String(ESP.getMaxFreeBlockSize())); // DBGPRINTLN(String(ESP.getMaxFreeBlockSize()));
if(!mSys.Radio.isChipConnected()) if (!mSys.Radio.isChipConnected())
DPRINTLN(DBG_WARN, F("WARNING! your NRF24 module can't be reached, check the wiring")); 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 // when WiFi is in client mode, then enable mqtt broker
#if !defined(AP_ONLY) #if !defined(AP_ONLY)
mMqttEnabled = (mConfig->mqtt.broker[0] > 0); mMqttEnabled = (mConfig->mqtt.broker[0] > 0);
if (mMqttEnabled) { if (mMqttEnabled) {
mMqtt.setup(&mConfig->mqtt, mConfig->sys.deviceName, mVersion, &mSys, &mTimestamp); mMqtt.setup(&mConfig->mqtt, mConfig->sys.deviceName, mVersion, &mSys, &mTimestamp);
mMqtt.setSubscriptionCb(std::bind(&app::mqttSubRxCb, this, std::placeholders::_1)); 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)); mPayload.addAlarmListener(std::bind(&PubMqttType::alarmEventListener, &mMqtt, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
} }
#endif #endif
setupLed(); setupLed();
mWeb.setup(this, &mSys, mConfig); mWeb.setup(this, &mSys, mConfig);
@ -77,18 +78,17 @@ void app::setup() {
mApi.setup(this, &mSys, mWeb.getWebSrvPtr(), mConfig); mApi.setup(this, &mSys, mWeb.getWebSrvPtr(), mConfig);
// Plugins // Plugins
if(mConfig->plugin.display.type != 0) if (mConfig->plugin.display.type != 0)
mMonoDisplay.setup(&mConfig->plugin.display, &mSys, &mTimestamp, 0xff, mVersion); mDisplay.setup(&mConfig->plugin.display, &mSys, &mTimestamp, 0xff, mVersion);
mPubSerial.setup(mConfig, &mSys, &mTimestamp); mPubSerial.setup(mConfig, &mSys, &mTimestamp);
regularTickers(); regularTickers();
// DBGPRINTLN("--- end setup");
//DBGPRINTLN("--- end setup"); // DBGPRINTLN(String(ESP.getFreeHeap()));
//DBGPRINTLN(String(ESP.getFreeHeap())); // DBGPRINTLN(String(ESP.getHeapFragmentation()));
//DBGPRINTLN(String(ESP.getHeapFragmentation())); // DBGPRINTLN(String(ESP.getMaxFreeBlockSize()));
//DBGPRINTLN(String(ESP.getMaxFreeBlockSize()));
} }
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
@ -115,8 +115,8 @@ void app::loopStandard(void) {
mStat.frmCnt++; mStat.frmCnt++;
Inverter<> *iv = mSys.findInverter(&p->packet[1]); Inverter<> *iv = mSys.findInverter(&p->packet[1]);
if(NULL != iv) { if (NULL != iv) {
if(IV_HM == iv->ivGen) if (IV_HM == iv->ivGen)
mPayload.add(iv, p); mPayload.add(iv, p);
else else
mMiPayload.add(iv, p); mMiPayload.add(iv, p);
@ -130,7 +130,7 @@ void app::loopStandard(void) {
mPayload.loop(); mPayload.loop();
mMiPayload.loop(); mMiPayload.loop();
if(mMqttEnabled) if (mMqttEnabled)
mMqtt.loop(); mMqtt.loop();
} }
@ -151,12 +151,11 @@ void app::onWifi(bool gotIp) {
mMqttReconnect = true; mMqttReconnect = true;
mSunrise = 0; // needs to be set to 0, to reinstall sunrise and ivComm tickers! mSunrise = 0; // needs to be set to 0, to reinstall sunrise and ivComm tickers!
once(std::bind(&app::tickNtpUpdate, this), 2, "ntp2"); once(std::bind(&app::tickNtpUpdate, this), 2, "ntp2");
if(WIFI_AP == WiFi.getMode()) { if (WIFI_AP == WiFi.getMode()) {
mMqttEnabled = false; mMqttEnabled = false;
everySec(std::bind(&ahoywifi::tickWifiLoop, &mWifi), "wifiL"); everySec(std::bind(&ahoywifi::tickWifiLoop, &mWifi), "wifiL");
} }
} } else {
else {
mInnerLoopCb = std::bind(&app::loopWifi, this); mInnerLoopCb = std::bind(&app::loopWifi, this);
everySec(std::bind(&ahoywifi::tickWifiLoop, &mWifi), "wifiL"); everySec(std::bind(&ahoywifi::tickWifiLoop, &mWifi), "wifiL");
} }
@ -167,8 +166,8 @@ void app::regularTickers(void) {
DPRINTLN(DBG_DEBUG, F("regularTickers")); DPRINTLN(DBG_DEBUG, F("regularTickers"));
everySec(std::bind(&WebType::tickSecond, &mWeb), "webSc"); everySec(std::bind(&WebType::tickSecond, &mWeb), "webSc");
// Plugins // Plugins
if(mConfig->plugin.display.type != 0) if (mConfig->plugin.display.type != 0)
everySec(std::bind(&MonoDisplayType::tickerSecond, &mMonoDisplay), "disp"); everySec(std::bind(&DisplayType::tickerSecond, &mDisplay), "disp");
every(std::bind(&PubSerialType::tick, &mPubSerial), mConfig->serial.interval, "uart"); every(std::bind(&PubSerialType::tick, &mPubSerial), mConfig->serial.interval, "uart");
} }
@ -184,10 +183,10 @@ void app::tickNtpUpdate(void) {
} }
// only install schedulers once even if NTP wasn't successful in first loop // only install schedulers once even if NTP wasn't successful in first loop
if(mMqttReconnect) { // @TODO: mMqttReconnect is variable which scope has changed if (mMqttReconnect) { // @TODO: mMqttReconnect is variable which scope has changed
if(mConfig->inst.rstValsNotAvail) if (mConfig->inst.rstValsNotAvail)
everyMin(std::bind(&app::tickMinute, this), "tMin"); everyMin(std::bind(&app::tickMinute, this), "tMin");
if(mConfig->inst.rstYieldMidNight) { if (mConfig->inst.rstYieldMidNight) {
uint32_t localTime = gTimezone.toLocal(mTimestamp); uint32_t localTime = gTimezone.toLocal(mTimestamp);
uint32_t midTrig = gTimezone.toUTC(localTime - (localTime % 86400) + 86400); // next midnight local time uint32_t midTrig = gTimezone.toUTC(localTime - (localTime % 86400) + 86400); // next midnight local time
onceAt(std::bind(&app::tickMidnight, this), midTrig, "midNi"); onceAt(std::bind(&app::tickMidnight, this), midTrig, "midNi");
@ -196,14 +195,14 @@ void app::tickNtpUpdate(void) {
nxtTrig = isOK ? 43200 : 60; // depending on NTP update success check again in 12 h or in 1 min nxtTrig = isOK ? 43200 : 60; // depending on NTP update success check again in 12 h or in 1 min
if((mSunrise == 0) && (mConfig->sun.lat) && (mConfig->sun.lon)) { if ((mSunrise == 0) && (mConfig->sun.lat) && (mConfig->sun.lon)) {
mCalculatedTimezoneOffset = (int8_t)((mConfig->sun.lon >= 0 ? mConfig->sun.lon + 7.5 : mConfig->sun.lon - 7.5) / 15) * 3600; mCalculatedTimezoneOffset = (int8_t)((mConfig->sun.lon >= 0 ? mConfig->sun.lon + 7.5 : mConfig->sun.lon - 7.5) / 15) * 3600;
tickCalcSunrise(); tickCalcSunrise();
} }
// immediately start communicating // immediately start communicating
// @TODO: leads to reboot loops? not sure #674 // @TODO: leads to reboot loops? not sure #674
if(isOK && mSendFirst) { if (isOK && mSendFirst) {
mSendFirst = false; mSendFirst = false;
once(std::bind(&app::tickSend, this), 2, "senOn"); once(std::bind(&app::tickSend, this), 2, "senOn");
} }
@ -259,7 +258,7 @@ void app::tickSun(void) {
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
void app::tickComm(void) { void app::tickComm(void) {
if((!mIVCommunicationOn) && (mConfig->inst.rstValsCommStop)) if ((!mIVCommunicationOn) && (mConfig->inst.rstValsCommStop))
once(std::bind(&app::tickZeroValues, this), mConfig->nrf.sendInterval, "tZero"); once(std::bind(&app::tickZeroValues, this), mConfig->nrf.sendInterval, "tZero");
if (mMqttEnabled) { if (mMqttEnabled) {
@ -292,7 +291,7 @@ void app::tickMinute(void) {
if (NULL == iv) if (NULL == iv)
continue; // skip to next inverter continue; // skip to next inverter
if(!iv->isAvailable(mTimestamp) && !iv->isProducing(mTimestamp) && iv->config->enabled) if (!iv->isAvailable(mTimestamp) && !iv->isProducing(mTimestamp) && iv->config->enabled)
mPayload.zeroInverterValues(iv); mPayload.zeroInverterValues(iv);
} }
} }
@ -321,7 +320,7 @@ void app::tickMidnight(void) {
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
void app::tickSend(void) { void app::tickSend(void) {
if(!mSys.Radio.isChipConnected()) { if (!mSys.Radio.isChipConnected()) {
DPRINTLN(DBG_WARN, F("NRF24 not connected!")); DPRINTLN(DBG_WARN, F("NRF24 not connected!"));
return; return;
} }
@ -341,8 +340,8 @@ void app::tickSend(void) {
} while ((NULL == iv) && ((maxLoop--) > 0)); } while ((NULL == iv) && ((maxLoop--) > 0));
if (NULL != iv) { if (NULL != iv) {
if(iv->config->enabled) { if (iv->config->enabled) {
if(iv->ivGen == IV_HM) if (iv->ivGen == IV_HM)
mPayload.ivSend(iv); mPayload.ivSend(iv);
else else
mMiPayload.ivSend(iv); mMiPayload.ivSend(iv);
@ -391,11 +390,11 @@ void app::setupLed(void) {
* PIN ---- |<----- 3.3V * PIN ---- |<----- 3.3V
* *
* */ * */
if(mConfig->led.led0 != 0xff) { if (mConfig->led.led0 != 0xff) {
pinMode(mConfig->led.led0, OUTPUT); pinMode(mConfig->led.led0, OUTPUT);
digitalWrite(mConfig->led.led0, HIGH); // LED off digitalWrite(mConfig->led.led0, HIGH); // LED off
} }
if(mConfig->led.led1 != 0xff) { if (mConfig->led.led1 != 0xff) {
pinMode(mConfig->led.led1, OUTPUT); pinMode(mConfig->led.led1, OUTPUT);
digitalWrite(mConfig->led.led1, HIGH); // LED off digitalWrite(mConfig->led.led1, HIGH); // LED off
} }
@ -403,10 +402,10 @@ void app::setupLed(void) {
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
void app::updateLed(void) { void app::updateLed(void) {
if(mConfig->led.led0 != 0xff) { if (mConfig->led.led0 != 0xff) {
Inverter<> *iv = mSys.getInverterByPos(0); Inverter<> *iv = mSys.getInverterByPos(0);
if (NULL != iv) { if (NULL != iv) {
if(iv->isProducing(mTimestamp)) if (iv->isProducing(mTimestamp))
digitalWrite(mConfig->led.led0, LOW); // LED on digitalWrite(mConfig->led.led0, LOW); // LED on
else else
digitalWrite(mConfig->led.led0, HIGH); // LED off digitalWrite(mConfig->led.led0, HIGH); // LED off

42
src/app.h

@ -6,30 +6,25 @@
#ifndef __APP_H__ #ifndef __APP_H__
#define __APP_H__ #define __APP_H__
#include "utils/dbg.h"
#include <Arduino.h> #include <Arduino.h>
#include <ArduinoJson.h>
#include <RF24.h> #include <RF24.h>
#include <RF24_config.h> #include <RF24_config.h>
#include <ArduinoJson.h>
#include "appInterface.h" #include "appInterface.h"
#include "config/settings.h" #include "config/settings.h"
#include "defines.h" #include "defines.h"
#include "utils/crc.h"
#include "utils/scheduler.h"
#include "hm/hmSystem.h"
#include "hm/hmPayload.h" #include "hm/hmPayload.h"
#include "hm/hmSystem.h"
#include "hm/miPayload.h" #include "hm/miPayload.h"
#include "wifi/ahoywifi.h"
#include "web/web.h"
#include "web/RestApi.h"
#include "publisher/pubMqtt.h" #include "publisher/pubMqtt.h"
#include "publisher/pubSerial.h" #include "publisher/pubSerial.h"
#include "utils/crc.h"
#include "utils/dbg.h"
#include "utils/scheduler.h"
#include "web/RestApi.h"
#include "web/web.h"
#include "wifi/ahoywifi.h"
// convert degrees and radians for sun calculation // convert degrees and radians for sun calculation
#define SIN(x) (sin(radians(x))) #define SIN(x) (sin(radians(x)))
@ -46,9 +41,8 @@ typedef PubMqtt<HmSystemType> PubMqttType;
typedef PubSerial<HmSystemType> PubSerialType; typedef PubSerial<HmSystemType> PubSerialType;
// PLUGINS // PLUGINS
#include "plugins/MonochromeDisplay/MonochromeDisplay.h" #include "plugins/Display/Display.h"
typedef MonochromeDisplay<HmSystemType> MonoDisplayType; typedef Display<HmSystemType> DisplayType;
class app : public IApp, public ah::Scheduler { class app : public IApp, public ah::Scheduler {
public: public:
@ -136,7 +130,7 @@ class app : public IApp, public ah::Scheduler {
} }
void ivSendHighPrio(Inverter<> *iv) { void ivSendHighPrio(Inverter<> *iv) {
if(mIVCommunicationOn) // only send commands if communcation is enabled if (mIVCommunicationOn) // only send commands if communcation is enabled
mPayload.ivSendHighPrio(iv); mPayload.ivSendHighPrio(iv);
} }
@ -162,7 +156,7 @@ class app : public IApp, public ah::Scheduler {
String getTimeStr(uint32_t offset = 0) { String getTimeStr(uint32_t offset = 0) {
char str[10]; char str[10];
if(0 == mTimestamp) if (0 == mTimestamp)
sprintf(str, "n/a"); sprintf(str, "n/a");
else else
sprintf(str, "%02d:%02d:%02d ", hour(mTimestamp + offset), minute(mTimestamp + offset), second(mTimestamp + offset)); sprintf(str, "%02d:%02d:%02d ", hour(mTimestamp + offset), minute(mTimestamp + offset), second(mTimestamp + offset));
@ -184,7 +178,7 @@ class app : public IApp, public ah::Scheduler {
void setTimestamp(uint32_t newTime) { void setTimestamp(uint32_t newTime) {
DPRINT(DBG_DEBUG, F("setTimestamp: ")); DPRINT(DBG_DEBUG, F("setTimestamp: "));
DBGPRINTLN(String(newTime)); DBGPRINTLN(String(newTime));
if(0 == newTime) if (0 == newTime)
mWifi.getNtpTime(); mWifi.getNtpTime();
else else
Scheduler::setTimestamp(newTime); Scheduler::setTimestamp(newTime);
@ -198,12 +192,12 @@ class app : public IApp, public ah::Scheduler {
void resetSystem(void); void resetSystem(void);
void payloadEventListener(uint8_t cmd) { void payloadEventListener(uint8_t cmd) {
#if !defined(AP_ONLY) #if !defined(AP_ONLY)
if (mMqttEnabled) if (mMqttEnabled)
mMqtt.payloadEventListener(cmd); mMqtt.payloadEventListener(cmd);
#endif #endif
if(mConfig->plugin.display.type != 0) if (mConfig->plugin.display.type != 0)
mMonoDisplay.payloadEventListener(cmd); mDisplay.payloadEventListener(cmd);
} }
void mqttSubRxCb(JsonObject obj); void mqttSubRxCb(JsonObject obj);
@ -274,7 +268,7 @@ class app : public IApp, public ah::Scheduler {
uint32_t mSunrise, mSunset; uint32_t mSunrise, mSunset;
// plugins // plugins
MonoDisplayType mMonoDisplay; DisplayType mDisplay;
}; };
#endif /*__APP_H__*/ #endif /*__APP_H__*/

197
src/config/settings.h

@ -7,11 +7,12 @@
#define __SETTINGS_H__ #define __SETTINGS_H__
#include <Arduino.h> #include <Arduino.h>
#include <LittleFS.h>
#include <ArduinoJson.h> #include <ArduinoJson.h>
#include <LittleFS.h>
#include "../defines.h"
#include "../utils/dbg.h" #include "../utils/dbg.h"
#include "../utils/helper.h" #include "../utils/helper.h"
#include "../defines.h"
/** /**
* More info: * More info:
@ -19,7 +20,6 @@
* */ * */
#define DEF_PIN_OFF 255 #define DEF_PIN_OFF 255
#define PROT_MASK_INDEX 0x0001 #define PROT_MASK_INDEX 0x0001
#define PROT_MASK_LIVE 0x0002 #define PROT_MASK_LIVE 0x0002
#define PROT_MASK_SERIAL 0x0004 #define PROT_MASK_SERIAL 0x0004
@ -38,7 +38,6 @@
#define DEF_PROT_API 0x0000 #define DEF_PROT_API 0x0000
#define DEF_PROT_MQTT 0x0000 #define DEF_PROT_MQTT 0x0000
typedef struct { typedef struct {
uint8_t ip[4]; // ip address uint8_t ip[4]; // ip address
uint8_t mask[4]; // sub mask uint8_t mask[4]; // sub mask
@ -124,12 +123,17 @@ typedef struct {
bool pwrSaveAtIvOffline; bool pwrSaveAtIvOffline;
bool logoEn; bool logoEn;
bool pxShift; bool pxShift;
bool rot180; uint8_t rot;
uint16_t period;
uint16_t wakeUp; uint16_t wakeUp;
uint16_t sleepAt; uint16_t sleepAt;
uint8_t contrast; uint8_t contrast;
uint8_t pin0; uint8_t disp_data;
uint8_t pin1; uint8_t disp_clk;
uint8_t disp_cs;
uint8_t disp_reset;
uint8_t disp_busy;
uint8_t disp_dc;
} display_t; } display_t;
typedef struct { typedef struct {
@ -157,28 +161,27 @@ class settings {
DPRINTLN(DBG_INFO, F("Initializing FS ..")); DPRINTLN(DBG_INFO, F("Initializing FS .."));
mCfg.valid = false; mCfg.valid = false;
#if !defined(ESP32) #if !defined(ESP32)
LittleFSConfig cfg; LittleFSConfig cfg;
cfg.setAutoFormat(false); cfg.setAutoFormat(false);
LittleFS.setConfig(cfg); LittleFS.setConfig(cfg);
#define LITTLFS_TRUE #define LITTLFS_TRUE
#define LITTLFS_FALSE #define LITTLFS_FALSE
#else #else
#define LITTLFS_TRUE true #define LITTLFS_TRUE true
#define LITTLFS_FALSE false #define LITTLFS_FALSE false
#endif #endif
if(!LittleFS.begin(LITTLFS_FALSE)) { if (!LittleFS.begin(LITTLFS_FALSE)) {
DPRINTLN(DBG_INFO, F(".. format ..")); DPRINTLN(DBG_INFO, F(".. format .."));
LittleFS.format(); LittleFS.format();
if(LittleFS.begin(LITTLFS_TRUE)) { if (LittleFS.begin(LITTLFS_TRUE)) {
DPRINTLN(DBG_INFO, F(".. success")); DPRINTLN(DBG_INFO, F(".. success"));
} else { } else {
DPRINTLN(DBG_INFO, F(".. failed")); DPRINTLN(DBG_INFO, F(".. failed"));
} }
} } else
else
DPRINTLN(DBG_INFO, F(" .. done")); DPRINTLN(DBG_INFO, F(" .. done"));
readSettings("/settings.json"); readSettings("/settings.json");
@ -199,7 +202,7 @@ class settings {
} }
void getInfo(uint32_t *used, uint32_t *size) { void getInfo(uint32_t *used, uint32_t *size) {
#if !defined(ESP32) #if !defined(ESP32)
FSInfo info; FSInfo info;
LittleFS.info(info); LittleFS.info(info);
*used = info.usedBytes; *used = info.usedBytes;
@ -207,22 +210,22 @@ class settings {
DPRINTLN(DBG_INFO, F("-- FILESYSTEM INFO --")); DPRINTLN(DBG_INFO, F("-- FILESYSTEM INFO --"));
DPRINTLN(DBG_INFO, String(info.usedBytes) + F(" of ") + String(info.totalBytes) + F(" used")); DPRINTLN(DBG_INFO, String(info.usedBytes) + F(" of ") + String(info.totalBytes) + F(" used"));
#else #else
DPRINTLN(DBG_WARN, F("not supported by ESP32")); DPRINTLN(DBG_WARN, F("not supported by ESP32"));
#endif #endif
} }
bool readSettings(const char* path) { bool readSettings(const char *path) {
loadDefaults(); loadDefaults();
File fp = LittleFS.open(path, "r"); File fp = LittleFS.open(path, "r");
if(!fp) if (!fp)
DPRINTLN(DBG_WARN, F("failed to load json, using default config")); DPRINTLN(DBG_WARN, F("failed to load json, using default config"));
else { else {
//DPRINTLN(DBG_INFO, fp.readString()); // DPRINTLN(DBG_INFO, fp.readString());
//fp.seek(0, SeekSet); // fp.seek(0, SeekSet);
DynamicJsonDocument root(4500); DynamicJsonDocument root(4500);
DeserializationError err = deserializeJson(root, fp); DeserializationError err = deserializeJson(root, fp);
if(!err && (root.size() > 0)) { if (!err && (root.size() > 0)) {
mCfg.valid = true; mCfg.valid = true;
jsonWifi(root[F("wifi")]); jsonWifi(root[F("wifi")]);
jsonNrf(root[F("nrf")]); jsonNrf(root[F("nrf")]);
@ -233,8 +236,7 @@ class settings {
jsonLed(root[F("led")]); jsonLed(root[F("led")]);
jsonPlugin(root[F("plugin")]); jsonPlugin(root[F("plugin")]);
jsonInst(root[F("inst")]); jsonInst(root[F("inst")]);
} } else {
else {
Serial.println(F("failed to parse json, using default config")); Serial.println(F("failed to parse json, using default config"));
} }
@ -246,7 +248,7 @@ class settings {
bool saveSettings(void) { bool saveSettings(void) {
DPRINTLN(DBG_DEBUG, F("save settings")); DPRINTLN(DBG_DEBUG, F("save settings"));
File fp = LittleFS.open("/settings.json", "w"); File fp = LittleFS.open("/settings.json", "w");
if(!fp) { if (!fp) {
DPRINTLN(DBG_ERROR, F("can't open settings file!")); DPRINTLN(DBG_ERROR, F("can't open settings file!"));
return false; return false;
} }
@ -263,7 +265,7 @@ class settings {
jsonPlugin(root.createNestedObject(F("plugin")), true); jsonPlugin(root.createNestedObject(F("plugin")), true);
jsonInst(root.createNestedObject(F("inst")), true); jsonInst(root.createNestedObject(F("inst")), true);
if(0 == serializeJson(root, fp)) { if (0 == serializeJson(root, fp)) {
DPRINTLN(DBG_ERROR, F("can't write settings file!")); DPRINTLN(DBG_ERROR, F("can't write settings file!"));
return false; return false;
} }
@ -273,7 +275,7 @@ class settings {
} }
bool eraseSettings(bool eraseWifi = false) { bool eraseSettings(bool eraseWifi = false) {
if(true == eraseWifi) if (true == eraseWifi)
return LittleFS.format(); return LittleFS.format();
loadDefaults(!eraseWifi); loadDefaults(!eraseWifi);
return saveSettings(); return saveSettings();
@ -284,18 +286,17 @@ class settings {
DPRINTLN(DBG_VERBOSE, F("loadDefaults")); DPRINTLN(DBG_VERBOSE, F("loadDefaults"));
cfgSys_t tmp; cfgSys_t tmp;
if(keepWifi) { if (keepWifi) {
// copy contents which should not be deleted // copy contents which should not be deleted
memset(&tmp.adminPwd, 0, PWD_LEN); memset(&tmp.adminPwd, 0, PWD_LEN);
memcpy(&tmp, &mCfg.sys, sizeof(cfgSys_t)); memcpy(&tmp, &mCfg.sys, sizeof(cfgSys_t));
} }
// erase all settings and reset to default // erase all settings and reset to default
memset(&mCfg, 0, sizeof(settings_t)); memset(&mCfg, 0, sizeof(settings_t));
mCfg.sys.protectionMask = DEF_PROT_INDEX | DEF_PROT_LIVE | DEF_PROT_SERIAL | DEF_PROT_SETUP 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;
| DEF_PROT_UPDATE | DEF_PROT_SYSTEM | DEF_PROT_API | DEF_PROT_MQTT;
mCfg.sys.darkMode = false; mCfg.sys.darkMode = false;
// restore temp settings // restore temp settings
if(keepWifi) if (keepWifi)
memcpy(&mCfg.sys, &tmp, sizeof(cfgSys_t)); memcpy(&mCfg.sys, &tmp, sizeof(cfgSys_t));
else { else {
snprintf(mCfg.sys.stationSsid, SSID_LEN, FB_WIFI_SSID); snprintf(mCfg.sys.stationSsid, SSID_LEN, FB_WIFI_SSID);
@ -343,13 +344,18 @@ class settings {
mCfg.plugin.display.contrast = 60; mCfg.plugin.display.contrast = 60;
mCfg.plugin.display.logoEn = true; mCfg.plugin.display.logoEn = true;
mCfg.plugin.display.pxShift = true; mCfg.plugin.display.pxShift = true;
mCfg.plugin.display.rot180 = false; mCfg.plugin.display.rot = 0;
mCfg.plugin.display.pin0 = DEF_PIN_OFF; // SCL mCfg.plugin.display.period = 10000;
mCfg.plugin.display.pin1 = DEF_PIN_OFF; // SDA 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) { void jsonWifi(JsonObject obj, bool set = false) {
if(set) { if (set) {
char buf[16]; char buf[16];
obj[F("ssid")] = mCfg.sys.stationSsid; obj[F("ssid")] = mCfg.sys.stationSsid;
obj[F("pwd")] = mCfg.sys.stationPwd; obj[F("pwd")] = mCfg.sys.stationPwd;
@ -357,32 +363,36 @@ class settings {
obj[F("adm")] = mCfg.sys.adminPwd; obj[F("adm")] = mCfg.sys.adminPwd;
obj[F("prot_mask")] = mCfg.sys.protectionMask; obj[F("prot_mask")] = mCfg.sys.protectionMask;
obj[F("dark")] = mCfg.sys.darkMode; obj[F("dark")] = mCfg.sys.darkMode;
ah::ip2Char(mCfg.sys.ip.ip, buf); obj[F("ip")] = String(buf); ah::ip2Char(mCfg.sys.ip.ip, buf);
ah::ip2Char(mCfg.sys.ip.mask, buf); obj[F("mask")] = String(buf); obj[F("ip")] = String(buf);
ah::ip2Char(mCfg.sys.ip.dns1, buf); obj[F("dns1")] = String(buf); ah::ip2Char(mCfg.sys.ip.mask, buf);
ah::ip2Char(mCfg.sys.ip.dns2, buf); obj[F("dns2")] = String(buf); obj[F("mask")] = String(buf);
ah::ip2Char(mCfg.sys.ip.gateway, buf); obj[F("gtwy")] = 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 { } else {
snprintf(mCfg.sys.stationSsid, SSID_LEN, "%s", obj[F("ssid")].as<const char*>()); snprintf(mCfg.sys.stationSsid, SSID_LEN, "%s", obj[F("ssid")].as<const char *>());
snprintf(mCfg.sys.stationPwd, PWD_LEN, "%s", obj[F("pwd")].as<const char*>()); snprintf(mCfg.sys.stationPwd, PWD_LEN, "%s", obj[F("pwd")].as<const char *>());
snprintf(mCfg.sys.deviceName, DEVNAME_LEN, "%s", obj[F("dev")].as<const char*>()); snprintf(mCfg.sys.deviceName, DEVNAME_LEN, "%s", obj[F("dev")].as<const char *>());
snprintf(mCfg.sys.adminPwd, PWD_LEN, "%s", obj[F("adm")].as<const char*>()); snprintf(mCfg.sys.adminPwd, PWD_LEN, "%s", obj[F("adm")].as<const char *>());
mCfg.sys.protectionMask = obj[F("prot_mask")]; mCfg.sys.protectionMask = obj[F("prot_mask")];
mCfg.sys.darkMode = obj[F("dark")]; mCfg.sys.darkMode = obj[F("dark")];
ah::ip2Arr(mCfg.sys.ip.ip, obj[F("ip")].as<const char*>()); ah::ip2Arr(mCfg.sys.ip.ip, obj[F("ip")].as<const char *>());
ah::ip2Arr(mCfg.sys.ip.mask, obj[F("mask")].as<const char*>()); ah::ip2Arr(mCfg.sys.ip.mask, obj[F("mask")].as<const char *>());
ah::ip2Arr(mCfg.sys.ip.dns1, obj[F("dns1")].as<const char*>()); ah::ip2Arr(mCfg.sys.ip.dns1, obj[F("dns1")].as<const char *>());
ah::ip2Arr(mCfg.sys.ip.dns2, obj[F("dns2")].as<const char*>()); ah::ip2Arr(mCfg.sys.ip.dns2, obj[F("dns2")].as<const char *>());
ah::ip2Arr(mCfg.sys.ip.gateway, obj[F("gtwy")].as<const char*>()); ah::ip2Arr(mCfg.sys.ip.gateway, obj[F("gtwy")].as<const char *>());
if(mCfg.sys.protectionMask == 0) if (mCfg.sys.protectionMask == 0)
mCfg.sys.protectionMask = DEF_PROT_INDEX | DEF_PROT_LIVE | DEF_PROT_SERIAL | DEF_PROT_SETUP 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;
| DEF_PROT_UPDATE | DEF_PROT_SYSTEM | DEF_PROT_API | DEF_PROT_MQTT;
} }
} }
void jsonNrf(JsonObject obj, bool set = false) { void jsonNrf(JsonObject obj, bool set = false) {
if(set) { if (set) {
obj[F("intvl")] = mCfg.nrf.sendInterval; obj[F("intvl")] = mCfg.nrf.sendInterval;
obj[F("maxRetry")] = mCfg.nrf.maxRetransPerPyld; obj[F("maxRetry")] = mCfg.nrf.maxRetransPerPyld;
obj[F("cs")] = mCfg.nrf.pinCs; obj[F("cs")] = mCfg.nrf.pinCs;
@ -400,17 +410,17 @@ class settings {
} }
void jsonNtp(JsonObject obj, bool set = false) { void jsonNtp(JsonObject obj, bool set = false) {
if(set) { if (set) {
obj[F("addr")] = mCfg.ntp.addr; obj[F("addr")] = mCfg.ntp.addr;
obj[F("port")] = mCfg.ntp.port; obj[F("port")] = mCfg.ntp.port;
} else { } else {
snprintf(mCfg.ntp.addr, NTP_ADDR_LEN, "%s", obj[F("addr")].as<const char*>()); snprintf(mCfg.ntp.addr, NTP_ADDR_LEN, "%s", obj[F("addr")].as<const char *>());
mCfg.ntp.port = obj[F("port")]; mCfg.ntp.port = obj[F("port")];
} }
} }
void jsonSun(JsonObject obj, bool set = false) { void jsonSun(JsonObject obj, bool set = false) {
if(set) { if (set) {
obj[F("lat")] = mCfg.sun.lat; obj[F("lat")] = mCfg.sun.lat;
obj[F("lon")] = mCfg.sun.lon; obj[F("lon")] = mCfg.sun.lon;
obj[F("dis")] = mCfg.sun.disNightCom; obj[F("dis")] = mCfg.sun.disNightCom;
@ -424,7 +434,7 @@ class settings {
} }
void jsonSerial(JsonObject obj, bool set = false) { void jsonSerial(JsonObject obj, bool set = false) {
if(set) { if (set) {
obj[F("intvl")] = mCfg.serial.interval; obj[F("intvl")] = mCfg.serial.interval;
obj[F("show")] = mCfg.serial.showIv; obj[F("show")] = mCfg.serial.showIv;
obj[F("debug")] = mCfg.serial.debug; obj[F("debug")] = mCfg.serial.debug;
@ -436,7 +446,7 @@ class settings {
} }
void jsonMqtt(JsonObject obj, bool set = false) { void jsonMqtt(JsonObject obj, bool set = false) {
if(set) { if (set) {
obj[F("broker")] = mCfg.mqtt.broker; obj[F("broker")] = mCfg.mqtt.broker;
obj[F("port")] = mCfg.mqtt.port; obj[F("port")] = mCfg.mqtt.port;
obj[F("user")] = mCfg.mqtt.user; obj[F("user")] = mCfg.mqtt.user;
@ -447,15 +457,15 @@ class settings {
} else { } else {
mCfg.mqtt.port = obj[F("port")]; mCfg.mqtt.port = obj[F("port")];
mCfg.mqtt.interval = obj[F("intvl")]; mCfg.mqtt.interval = obj[F("intvl")];
snprintf(mCfg.mqtt.broker, MQTT_ADDR_LEN, "%s", obj[F("broker")].as<const char*>()); snprintf(mCfg.mqtt.broker, MQTT_ADDR_LEN, "%s", obj[F("broker")].as<const char *>());
snprintf(mCfg.mqtt.user, MQTT_USER_LEN, "%s", obj[F("user")].as<const char*>()); snprintf(mCfg.mqtt.user, MQTT_USER_LEN, "%s", obj[F("user")].as<const char *>());
snprintf(mCfg.mqtt.pwd, MQTT_PWD_LEN, "%s", obj[F("pwd")].as<const char*>()); snprintf(mCfg.mqtt.pwd, MQTT_PWD_LEN, "%s", obj[F("pwd")].as<const char *>());
snprintf(mCfg.mqtt.topic, MQTT_TOPIC_LEN, "%s", obj[F("topic")].as<const char*>()); snprintf(mCfg.mqtt.topic, MQTT_TOPIC_LEN, "%s", obj[F("topic")].as<const char *>());
} }
} }
void jsonLed(JsonObject obj, bool set = false) { void jsonLed(JsonObject obj, bool set = false) {
if(set) { if (set) {
obj[F("0")] = mCfg.led.led0; obj[F("0")] = mCfg.led.led0;
obj[F("1")] = mCfg.led.led1; obj[F("1")] = mCfg.led.led1;
} else { } else {
@ -465,41 +475,48 @@ class settings {
} }
void jsonPlugin(JsonObject obj, bool set = false) { void jsonPlugin(JsonObject obj, bool set = false) {
if(set) { if (set) {
JsonObject disp = obj.createNestedObject("disp"); JsonObject disp = obj.createNestedObject("disp");
disp[F("type")] = mCfg.plugin.display.type; disp[F("type")] = mCfg.plugin.display.type;
disp[F("pwrSafe")] = (bool)mCfg.plugin.display.pwrSaveAtIvOffline; disp[F("pwrSafe")] = (bool)mCfg.plugin.display.pwrSaveAtIvOffline;
disp[F("logo")] = (bool)mCfg.plugin.display.logoEn; disp[F("period")] = mCfg.plugin.display.period;
disp[F("pxShift")] = (bool)mCfg.plugin.display.pxShift; disp[F("pxShift")] = (bool)mCfg.plugin.display.pxShift;
disp[F("rot180")] = (bool)mCfg.plugin.display.rot180; disp[F("rotation")] = mCfg.plugin.display.rot;
disp[F("wake")] = mCfg.plugin.display.wakeUp; disp[F("wake")] = mCfg.plugin.display.wakeUp;
disp[F("sleep")] = mCfg.plugin.display.sleepAt; disp[F("sleep")] = mCfg.plugin.display.sleepAt;
disp[F("contrast")] = mCfg.plugin.display.contrast; disp[F("contrast")] = mCfg.plugin.display.contrast;
disp[F("pin0")] = mCfg.plugin.display.pin0; disp[F("data")] = mCfg.plugin.display.disp_data;
disp[F("pin1")] = mCfg.plugin.display.pin1; 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 { } else {
JsonObject disp = obj["disp"]; JsonObject disp = obj["disp"];
mCfg.plugin.display.type = disp[F("type")]; mCfg.plugin.display.type = disp[F("type")];
mCfg.plugin.display.pwrSaveAtIvOffline = (bool) disp[F("pwrSafe")]; mCfg.plugin.display.pwrSaveAtIvOffline = (bool)disp[F("pwrSafe")];
mCfg.plugin.display.logoEn = (bool) disp[F("logo")]; mCfg.plugin.display.period = disp[F("period")];
mCfg.plugin.display.pxShift = (bool) disp[F("pxShift")]; mCfg.plugin.display.pxShift = (bool)disp[F("pxShift")];
mCfg.plugin.display.rot180 = (bool) disp[F("rot180")]; mCfg.plugin.display.rot = disp[F("rotation")];
mCfg.plugin.display.wakeUp = disp[F("wake")]; mCfg.plugin.display.wakeUp = disp[F("wake")];
mCfg.plugin.display.sleepAt = disp[F("sleep")]; mCfg.plugin.display.sleepAt = disp[F("sleep")];
mCfg.plugin.display.contrast = disp[F("contrast")]; mCfg.plugin.display.contrast = disp[F("contrast")];
mCfg.plugin.display.pin0 = disp[F("pin0")]; mCfg.plugin.display.disp_data = disp[F("data")];
mCfg.plugin.display.pin1 = disp[F("pin1")]; 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 jsonInst(JsonObject obj, bool set = false) { void jsonInst(JsonObject obj, bool set = false) {
if(set) { if (set) {
obj[F("en")] = (bool)mCfg.inst.enabled; obj[F("en")] = (bool)mCfg.inst.enabled;
obj[F("rstMidNight")] = (bool)mCfg.inst.rstYieldMidNight; obj[F("rstMidNight")] = (bool)mCfg.inst.rstYieldMidNight;
obj[F("rstNotAvail")] = (bool)mCfg.inst.rstValsNotAvail; obj[F("rstNotAvail")] = (bool)mCfg.inst.rstValsNotAvail;
obj[F("rstComStop")] = (bool)mCfg.inst.rstValsCommStop; obj[F("rstComStop")] = (bool)mCfg.inst.rstValsCommStop;
} } else {
else {
mCfg.inst.enabled = (bool)obj[F("en")]; mCfg.inst.enabled = (bool)obj[F("en")];
mCfg.inst.rstYieldMidNight = (bool)obj["rstMidNight"]; mCfg.inst.rstYieldMidNight = (bool)obj["rstMidNight"];
mCfg.inst.rstValsNotAvail = (bool)obj["rstNotAvail"]; mCfg.inst.rstValsNotAvail = (bool)obj["rstNotAvail"];
@ -507,10 +524,10 @@ class settings {
} }
JsonArray ivArr; JsonArray ivArr;
if(set) if (set)
ivArr = obj.createNestedArray(F("iv")); ivArr = obj.createNestedArray(F("iv"));
for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i++) { for (uint8_t i = 0; i < MAX_NUM_INVERTERS; i++) {
if(set) if (set)
jsonIv(ivArr.createNestedObject(), &mCfg.inst.iv[i], true); jsonIv(ivArr.createNestedObject(), &mCfg.inst.iv[i], true);
else else
jsonIv(obj[F("iv")][i], &mCfg.inst.iv[i]); jsonIv(obj[F("iv")][i], &mCfg.inst.iv[i]);
@ -518,23 +535,23 @@ class settings {
} }
void jsonIv(JsonObject obj, cfgIv_t *cfg, bool set = false) { void jsonIv(JsonObject obj, cfgIv_t *cfg, bool set = false) {
if(set) { if (set) {
obj[F("en")] = (bool)cfg->enabled; obj[F("en")] = (bool)cfg->enabled;
obj[F("name")] = cfg->name; obj[F("name")] = cfg->name;
obj[F("sn")] = cfg->serial.u64; obj[F("sn")] = cfg->serial.u64;
for(uint8_t i = 0; i < 4; i++) { for (uint8_t i = 0; i < 4; i++) {
obj[F("yield")][i] = cfg->yieldCor[i]; obj[F("yield")][i] = cfg->yieldCor[i];
obj[F("pwr")][i] = cfg->chMaxPwr[i]; obj[F("pwr")][i] = cfg->chMaxPwr[i];
obj[F("chName")][i] = cfg->chName[i]; obj[F("chName")][i] = cfg->chName[i];
} }
} else { } else {
cfg->enabled = (bool)obj[F("en")]; cfg->enabled = (bool)obj[F("en")];
snprintf(cfg->name, MAX_NAME_LENGTH, "%s", obj[F("name")].as<const char*>()); snprintf(cfg->name, MAX_NAME_LENGTH, "%s", obj[F("name")].as<const char *>());
cfg->serial.u64 = obj[F("sn")]; cfg->serial.u64 = obj[F("sn")];
for(uint8_t i = 0; i < 4; i++) { for (uint8_t i = 0; i < 4; i++) {
cfg->yieldCor[i] = obj[F("yield")][i]; cfg->yieldCor[i] = obj[F("yield")][i];
cfg->chMaxPwr[i] = obj[F("pwr")][i]; cfg->chMaxPwr[i] = obj[F("pwr")][i];
snprintf(cfg->chName[i], MAX_NAME_LENGTH, "%s", obj[F("chName")][i].as<const char*>()); snprintf(cfg->chName[i], MAX_NAME_LENGTH, "%s", obj[F("chName")][i].as<const char *>());
} }
} }
} }

1
src/platformio.ini

@ -41,6 +41,7 @@ lib_deps =
bblanchon/ArduinoJson bblanchon/ArduinoJson
https://github.com/JChristensen/Timezone https://github.com/JChristensen/Timezone
olikraus/U8g2 olikraus/U8g2
zinggjm/GxEPD2@^1.5.0
;esp8266/DNSServer ;esp8266/DNSServer
;esp8266/EEPROM ;esp8266/EEPROM
;esp8266/ESP8266WiFi ;esp8266/ESP8266WiFi

125
src/plugins/Display/Display.h

@ -0,0 +1,125 @@
#ifndef __DISPLAY__
#define __DISPLAY__
#include <Timezone.h>
#include <U8g2lib.h>
#include "../../hm/hmSystem.h"
#include "../../utils/helper.h"
#include "Display_Mono.h"
#include "Display_ePaper.h"
#include "imagedata.h"
template <class HMSYSTEM>
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;
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 payloadEventListener(uint8_t cmd) {
mNewPayload = true;
}
void tickerSecond() {
if (mNewPayload || ((++mLoopCnt % 10) == 0)) {
mNewPayload = false;
mLoopCnt = 0;
DataScreen();
}
}
private:
void DataScreen() {
if (mCfg->type == 0)
return;
if (*mUtcTs == 0)
return;
if ((millis() - _lastDisplayUpdate) > mCfg->period) {
float totalPower = 0;
float totalYieldDay = 0;
float totalYieldTotal = 0;
uint8_t isprod = 0;
Inverter<> *iv;
record_t<> *rec;
for (uint8_t i = 0; i < mSys->getNumInverters(); i++) {
iv = mSys->getInverterByPos(i);
rec = iv->getRecordStruct(RealTimeRunData_Debug);
if (iv == NULL)
continue;
if (iv->isProducing(*mUtcTs))
uint8_t isprod = 0;
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++;
}
_lastDisplayUpdate = millis();
}
if (counterEPaper > 480) {
DisplayEPaper.fullRefresh();
counterEPaper = 0;
}
}
// 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;
};
#endif /*__DISPLAY__*/

144
src/plugins/Display/Display_Mono.cpp

@ -0,0 +1,144 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include "Display_Mono.h"
#include <time.h>
#include <map>
#include "WiFi.h"
#include "imagedata.h"
#ifdef U8X8_HAVE_HW_SPI
#include <SPI.h>
#endif
#ifdef U8X8_HAVE_HW_I2C
#include <Wire.h>
#endif
std::map<uint8_t, std::function<U8G2*(uint8_t, uint8_t, uint8_t, uint8_t, uint8_t)>> 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;
}
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);
dispX += enableScreensaver ? (_mExtra % 7) : 0;
_display->drawStr(dispX, mLineOffsets[line], text);
}
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);
_mIsLarge = (_display->getWidth() > 100);
calcLineHeights();
_display->clearBuffer();
if (contrast < 255) {
_display->setContrast(contrast);
}
printText("AHOY!", 0, 35);
printText("ahoydtu.de", 2, 20);
printText(version, 3, 46);
_display->sendBuffer();
}
}
void DisplayMonoClass::loop(float totalPower, float totalYieldDay, float totalYieldTotal, uint8_t isprod) {
_display->clearBuffer();
// set Contrast of the Display to raise the lifetime
if (contrast < 255) {
_display->setContrast(contrast);
}
//=====> Actual Production ==========
if ((totalPower > 0) && (isprod > 0)) {
_display->setPowerSave(false);
if (totalPower > 999) {
snprintf(_fmtText, sizeof(_fmtText), "%2.2f kW", (totalPower / 1000));
} else {
snprintf(_fmtText, sizeof(_fmtText), "%3.0f W", totalPower);
}
printText(_fmtText, 0);
_previousMillis = millis();
}
//<=======================
//=====> Offline ===========
else {
printText("offline", 0);
// check if it's time to enter power saving mode
if (millis() - _previousMillis >= (_mTimeout * 2)) {
_display->setPowerSave(enablePowerSafe);
}
}
//<=======================
//=====> Today & Total Production =======
snprintf(_fmtText, sizeof(_fmtText), "today: %4.0f Wh", totalYieldDay);
printText(_fmtText, 1);
snprintf(_fmtText, sizeof(_fmtText), "total: %.1f kWh", totalYieldTotal);
printText(_fmtText, 2);
//<=======================
//=====> IP or Date-Time ========
if (!(_mExtra % 10) && WiFi.localIP()) {
printText(WiFi.localIP().toString().c_str(), 3);
} else if (!(_mExtra % 5)) {
snprintf(_fmtText, sizeof(_fmtText), "#%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);
}
_display->sendBuffer();
_dispY = 0;
_mExtra++;
}
DisplayMonoClass DisplayMono;

41
src/plugins/Display/Display_Mono.h

@ -0,0 +1,41 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <U8g2lib.h>
#define DISP_DEFAULT_TIMEOUT 60000 // in milliseconds
class DisplayMonoClass {
public:
DisplayMonoClass();
~DisplayMonoClass();
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;
private:
void calcLineHeights();
void setFont(uint8_t line);
void printText(const char* text, uint8_t line, uint8_t dispX);
U8G2* _display;
bool _mIsLarge = false;
uint8_t mLoopCnt;
uint32_t* mUtcTs;
uint8_t mLineOffsets[5];
uint16_t _dispY = 0;
uint32_t _previousMillis = 0;
uint8_t _mExtra;
uint16_t _mTimeout = DISP_DEFAULT_TIMEOUT; // interval at which to power save (milliseconds)
char _fmtText[32];
};
extern DisplayMonoClass DisplayMono;

192
src/plugins/Display/Display_ePaper.cpp

@ -0,0 +1,192 @@
#include "Display_ePaper.h"
#include "WiFi.h"
#include "imagedata.h"
static const uint32_t spiClk = 4000000; // 4 MHz
#if defined(ESP32) && defined(USE_HSPI_FOR_EPD)
SPIClass hspi(HSPI);
#endif
std::map<uint8_t, std::function<GxEPD2_GFX *(uint8_t, uint8_t, uint8_t, uint8_t)>> _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, GxEPD2_150_BN::HEIGHT>(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, GxEPD2_270c::HEIGHT>(GxEPD2_270c(_CS, _DC, _RST, _BUSY)); }},
};
DisplayEPaperClass::DisplayEPaperClass() {
}
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) {
Serial.begin(115200);
auto constructor = _ePaperTypes[type];
_display = constructor(_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->setFullWindow();
// Logo
_display->fillScreen(GxEPD_BLACK);
_display->drawBitmap(0, 0, AhoyLogo, 200, 200, GxEPD_WHITE);
//_display->drawBitmap(0, 0, OpenDTULogo, 200, 200, GxEPD_WHITE);
while (_display->nextPage())
;
// clean the screen
delay(2000);
_display->fillScreen(GxEPD_WHITE);
while (_display->nextPage())
;
headlineIP();
// call the PowerPage to change the PV Power Values
actualPowerPaged(0, 0, 0, 0);
}
}
//***************************************************************************
void DisplayEPaperClass::fullRefresh() {
// screen complete black
_display->fillScreen(GxEPD_BLACK);
while (_display->nextPage())
;
delay(2000);
// screen complete white
_display->fillScreen(GxEPD_WHITE);
while (_display->nextPage())
;
}
//***************************************************************************
void DisplayEPaperClass::headlineIP() {
int16_t tbx, tby;
uint16_t tbw, tbh;
_display->setFont(&FreeSans9pt7b);
_display->setTextColor(GxEPD_WHITE);
_display->setPartialWindow(0, 0, _display->width(), headfootline);
_display->fillScreen(GxEPD_BLACK);
do {
if ((WiFi.isConnected() == true) && (WiFi.localIP() > 0)) {
snprintf(_fmtText, sizeof(_fmtText), "%s", WiFi.localIP().toString().c_str());
} else {
snprintf(_fmtText, sizeof(_fmtText), "WiFi not connected");
}
_display->getTextBounds(_fmtText, 0, 0, &tbx, &tby, &tbw, &tbh);
uint16_t x = ((_display->width() - tbw) / 2) - tbx;
_display->setCursor(x, (headfootline - 2));
_display->println(_fmtText);
} while (_display->nextPage());
}
//***************************************************************************
void DisplayEPaperClass::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->fillScreen(GxEPD_BLACK);
do {
time_t now = time(nullptr);
strftime(_fmtText, sizeof(_fmtText), "%d.%m.%Y %H:%M", localtime(&now));
_display->getTextBounds(_fmtText, 0, 0, &tbx, &tby, &tbw, &tbh);
uint16_t x = ((_display->width() - tbw) / 2) - tbx;
_display->setCursor(x, (_display->height() - 3));
_display->println(_fmtText);
} while (_display->nextPage());
}
//***************************************************************************
void DisplayEPaperClass::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->fillScreen(GxEPD_WHITE);
do {
if (_totalPower > 9999) {
snprintf(_fmtText, sizeof(_fmtText), "%.1f kW", (_totalPower / 10000));
_changed = true;
} else if ((_totalPower > 0) && (_totalPower <= 9999)) {
snprintf(_fmtText, sizeof(_fmtText), "%.0f W", _totalPower);
_changed = true;
} else {
snprintf(_fmtText, sizeof(_fmtText), "offline");
}
_display->getTextBounds(_fmtText, 0, 0, &tbx, &tby, &tbw, &tbh);
x = ((_display->width() - tbw) / 2) - tbx;
_display->setCursor(x, headfootline + tbh + 10);
_display->print(_fmtText);
_display->setFont(&FreeSans12pt7b);
y = _display->height() / 2;
_display->setCursor(0, y);
_display->print("today:");
snprintf(_fmtText, _display->width(), "%.0f", _totalYieldDay);
_display->getTextBounds(_fmtText, 0, 0, &tbx, &tby, &tbw, &tbh);
x = ((_display->width() - tbw) / 2) - tbx;
_display->setCursor(x, y);
_display->print(_fmtText);
_display->setCursor(_display->width() - 33, y);
_display->println("Wh");
y = y + tbh + 7;
_display->setCursor(0, y);
_display->print("total:");
snprintf(_fmtText, _display->width(), "%.1f", _totalYieldTotal);
_display->getTextBounds(_fmtText, 0, 0, &tbx, &tby, &tbw, &tbh);
x = ((_display->width() - tbw) / 2) - tbx;
_display->setCursor(x, y);
_display->print(_fmtText);
_display->setCursor(_display->width() - 45, y);
_display->println("kWh");
_display->setCursor(0, _display->height() - (headfootline + 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) {
// check if the IP has changed
if (_settedIP != WiFi.localIP().toString().c_str()) {
// save the new IP and call the Headline Funktion to adapt the Headline
_settedIP = WiFi.localIP().toString().c_str();
headlineIP();
}
// call the PowerPage to change the PV Power Values
actualPowerPaged(totalPower, totalYieldDay, totalYieldTotal, isprod);
// if there was an change and the Inverter is producing set a new Timestam in the footline
if ((isprod > 0) && (_changed)) {
_changed = false;
lastUpdatePaged();
}
_display->powerOff();
}
//***************************************************************************
DisplayEPaperClass DisplayEPaper;

51
src/plugins/Display/Display_ePaper.h

@ -0,0 +1,51 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
// uncomment next line to use HSPI for EPD (and VSPI for SD), e.g. with Waveshare ESP32 Driver Board
#define USE_HSPI_FOR_EPD
/// uncomment next line to use class GFX of library GFX_Root instead of Adafruit_GFX, to use less code and ram
// #include <GFX.h>
// base class GxEPD2_GFX can be used to pass references or pointers to the display instance as parameter, uses ~1.2k more code
// enable GxEPD2_GFX base class
#define ENABLE_GxEPD2_GFX 1
#include <GxEPD2_3C.h>
#include <GxEPD2_BW.h>
#include <SPI.h>
#include <map>
// FreeFonts from Adafruit_GFX
#include <Fonts/FreeSans12pt7b.h>
#include <Fonts/FreeSans18pt7b.h>
#include <Fonts/FreeSans24pt7b.h>
#include <Fonts/FreeSans9pt7b.h>
#include "imagedata.h"
// GDEW027C44 2.7 " b/w/r 176x264, IL91874
// GDEH0154D67 1.54" b/w 200x200
class DisplayEPaperClass {
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);
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;
};
extern DisplayEPaperClass DisplayEPaper;

642
src/plugins/Display/imagedata.cpp

@ -0,0 +1,642 @@
// GxEPD2_ESP32_ESP8266_WifiData_V1_und_V2
#include "imagedata.h"
#if defined(__AVR__) || defined(ARDUINO_ARCH_SAMD)
#include <avr/pgmspace.h>
#elif defined(ESP8266) || defined(ESP32)
#include <pgmspace.h>
#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
};

5
src/plugins/Display/imagedata.h

@ -0,0 +1,5 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
extern const unsigned char AhoyLogo[];
extern const unsigned char OpenDTULogo[];

217
src/plugins/MonochromeDisplay/MonochromeDisplay.h

@ -1,217 +0,0 @@
#ifndef __MONOCHROME_DISPLAY__
#define __MONOCHROME_DISPLAY__
#include <U8g2lib.h>
#include <Timezone.h>
#include "../../utils/helper.h"
#include "../../hm/hmSystem.h"
#define DISP_DEFAULT_TIMEOUT 60 // in seconds
static uint8_t bmp_logo[] PROGMEM = {
B00000000, B00000000, // ................
B11101100, B00110111, // ..##.######.##..
B11101100, B00110111, // ..##.######.##..
B11100000, B00000111, // .....######.....
B11010000, B00001011, // ....#.####.#....
B10011000, B00011001, // ...##..##..##...
B10000000, B00000001, // .......##.......
B00000000, B00000000, // ................
B01111000, B00011110, // ...####..####...
B11111100, B00111111, // ..############..
B01111100, B00111110, // ..#####..#####..
B00000000, B00000000, // ................
B11111100, B00111111, // ..############..
B11111110, B01111111, // .##############.
B01111110, B01111110, // .######..######.
B00000000, B00000000 // ................
};
static uint8_t bmp_arrow[] PROGMEM = {
B00000000, B00011100, B00011100, B00001110, B00001110, B11111110, B01111111,
B01110000, B01110000, B00110000, B00111000, B00011000, B01111111, B00111111,
B00011110, B00001110, B00000110, B00000000, B00000000, B00000000, B00000000
};
template<class HMSYSTEM>
class MonochromeDisplay {
public:
MonochromeDisplay() {}
void setup(display_t *cfg, HMSYSTEM *sys, uint32_t *utcTs, uint8_t disp_reset, const char *version) {
mCfg = cfg;
mSys = sys;
mUtcTs = utcTs;
mNewPayload = false;
mLoopCnt = 0;
mTimeout = DISP_DEFAULT_TIMEOUT; // power off timeout (after inverters go offline)
u8g2_cb_t *rot = (u8g2_cb_t *)((mCfg->rot180) ? U8G2_R2 : U8G2_R0);
if(mCfg->type) {
switch(mCfg->type) {
case 1:
mDisplay = new U8G2_PCD8544_84X48_F_4W_HW_SPI(rot, mCfg->pin0, mCfg->pin1, disp_reset);
break;
case 2:
mDisplay = new U8G2_SSD1306_128X64_NONAME_F_HW_I2C(rot, disp_reset, mCfg->pin0, mCfg->pin1);
break;
case 3:
mDisplay = new U8G2_SH1106_128X64_NONAME_F_HW_I2C(rot, disp_reset, mCfg->pin0, mCfg->pin1);
break;
}
mDisplay->begin();
mIsLarge = ((mDisplay->getWidth() > 120) && (mDisplay->getHeight() > 60));
calcLineHeights();
mDisplay->clearBuffer();
mDisplay->setContrast(mCfg->contrast);
printText("Ahoy!", 0, 35);
printText("ahoydtu.de", 2, 20);
printText(version, 3, 46);
mDisplay->sendBuffer();
}
}
void payloadEventListener(uint8_t cmd) {
mNewPayload = true;
}
void tickerSecond() {
if(mCfg->pwrSaveAtIvOffline) {
if(mTimeout != 0)
mTimeout--;
}
if(mNewPayload || ((++mLoopCnt % 10) == 0)) {
mNewPayload = false;
mLoopCnt = 0;
DataScreen();
}
}
private:
void DataScreen() {
if (mCfg->type == 0)
return;
if(*mUtcTs == 0)
return;
float totalPower = 0;
float totalYieldDay = 0;
float totalYieldTotal = 0;
bool isprod = false;
Inverter<> *iv;
record_t<> *rec;
for (uint8_t i = 0; i < mSys->getNumInverters(); i++) {
iv = mSys->getInverterByPos(i);
rec = iv->getRecordStruct(RealTimeRunData_Debug);
if (iv == NULL)
continue;
if (iv->isProducing(*mUtcTs))
isprod = true;
totalPower += iv->getChannelFieldValue(CH0, FLD_PAC, rec);
totalYieldDay += iv->getChannelFieldValue(CH0, FLD_YD, rec);
totalYieldTotal += iv->getChannelFieldValue(CH0, FLD_YT, rec);
}
mDisplay->clearBuffer();
// Logos
// pxMovement +x (0 - 6 px)
uint8_t ex = (_mExtra % 7);
if (isprod) {
mDisplay->drawXBMP(5 + ex, 1, 8, 17, bmp_arrow);
if (mCfg->logoEn)
mDisplay->drawXBMP(mDisplay->getWidth() - 24 + ex, 2, 16, 16, bmp_logo);
}
if ((totalPower > 0) && isprod) {
mTimeout = DISP_DEFAULT_TIMEOUT;
mDisplay->setPowerSave(false);
mDisplay->setContrast(mCfg->contrast);
if (totalPower > 999)
snprintf(_fmtText, sizeof(_fmtText), "%2.1f kW", (totalPower / 1000));
else
snprintf(_fmtText, sizeof(_fmtText), "%3.0f W", totalPower);
printText(_fmtText, 0, 20);
} else {
printText("offline", 0, 25);
if(mCfg->pwrSaveAtIvOffline) {
if(mTimeout == 0)
mDisplay->setPowerSave(true);
}
}
snprintf(_fmtText, sizeof(_fmtText), "today: %4.0f Wh", totalYieldDay);
printText(_fmtText, 1);
snprintf(_fmtText, sizeof(_fmtText), "total: %.1f kWh", totalYieldTotal);
printText(_fmtText, 2);
IPAddress ip = WiFi.localIP();
if (!(_mExtra % 10) && (ip)) {
printText(ip.toString().c_str(), 3);
} else {
// Get current time
if(mIsLarge)
printText(ah::getDateTimeStr(gTimezone.toLocal(*mUtcTs)).c_str(), 3);
else
printText(ah::getTimeStr(gTimezone.toLocal(*mUtcTs)).c_str(), 3);
}
mDisplay->sendBuffer();
_mExtra++;
}
void calcLineHeights() {
uint8_t yOff = 0;
for(uint8_t i = 0; i < 4; i++) {
setFont(i);
yOff += (mDisplay->getMaxCharHeight() + 1);
mLineOffsets[i] = yOff;
}
}
inline void setFont(uint8_t line) {
switch (line) {
case 0: mDisplay->setFont((mIsLarge) ? u8g2_font_ncenB14_tr : u8g2_font_lubBI14_tr); break;
case 3: mDisplay->setFont(u8g2_font_5x8_tr); break;
default: mDisplay->setFont((mIsLarge) ? u8g2_font_ncenB10_tr : u8g2_font_5x8_tr); break;
}
}
void printText(const char* text, uint8_t line, uint8_t dispX = 5) {
if(!mIsLarge)
dispX = (line == 0) ? 10 : 5;
setFont(line);
if(mCfg->pxShift)
dispX += (_mExtra % 7); // add pixel movement
mDisplay->drawStr(dispX, mLineOffsets[line], text);
}
// private member variables
U8G2* mDisplay;
uint8_t _mExtra;
uint16_t mTimeout; // interval at which to power save (milliseconds)
char _fmtText[32];
bool mNewPayload;
bool mIsLarge;
uint8_t mLoopCnt;
uint32_t *mUtcTs;
uint8_t mLineOffsets[5];
display_t *mCfg;
HMSYSTEM *mSys;
};
#endif /*__MONOCHROME_DISPLAY__*/

271
src/web/RestApi.h

@ -8,26 +8,25 @@
#include "../utils/dbg.h" #include "../utils/dbg.h"
#ifdef ESP32 #ifdef ESP32
#include "AsyncTCP.h" #include "AsyncTCP.h"
#else #else
#include "ESPAsyncTCP.h" #include "ESPAsyncTCP.h"
#endif #endif
#include "ESPAsyncWebServer.h" #include "../appInterface.h"
#include "AsyncJson.h"
#include "../hm/hmSystem.h" #include "../hm/hmSystem.h"
#include "../utils/helper.h" #include "../utils/helper.h"
#include "AsyncJson.h"
#include "../appInterface.h" #include "ESPAsyncWebServer.h"
#if defined(F) && defined(ESP32) #if defined(F) && defined(ESP32)
#undef F #undef F
#define F(sl) (sl) #define F(sl) (sl)
#endif #endif
const uint8_t acList[] = {FLD_UAC, FLD_IAC, FLD_PAC, FLD_F, FLD_PF, FLD_T, FLD_YT, FLD_YD, FLD_PDC, FLD_EFF, FLD_Q}; const uint8_t acList[] = {FLD_UAC, FLD_IAC, FLD_PAC, FLD_F, FLD_PF, FLD_T, FLD_YT, FLD_YD, FLD_PDC, FLD_EFF, FLD_Q};
const uint8_t dcList[] = {FLD_UDC, FLD_IDC, FLD_PDC, FLD_YD, FLD_YT, FLD_IRR}; const uint8_t dcList[] = {FLD_UDC, FLD_IDC, FLD_PDC, FLD_YD, FLD_YT, FLD_IRR};
template<class HMSYSTEM> template <class HMSYSTEM>
class RestApi { class RestApi {
public: public:
RestApi() { RestApi() {
@ -44,8 +43,7 @@ class RestApi {
mSys = sys; mSys = sys;
mConfig = config; mConfig = config;
mSrv->on("/api", HTTP_GET, std::bind(&RestApi::onApi, this, std::placeholders::_1)); 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( 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));
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)); mSrv->on("/get_setup", HTTP_GET, std::bind(&RestApi::onDwnldSetup, this, std::placeholders::_1));
} }
@ -60,48 +58,64 @@ class RestApi {
DPRINTLN(DBG_INFO, "RestApi: " + String(out));*/ DPRINTLN(DBG_INFO, "RestApi: " + String(out));*/
DynamicJsonDocument json(128); DynamicJsonDocument json(128);
JsonObject dummy = json.as<JsonObject>(); JsonObject dummy = json.as<JsonObject>();
if(obj[F("path")] == "ctrl") if (obj[F("path")] == "ctrl")
setCtrl(obj, dummy); setCtrl(obj, dummy);
else if(obj[F("path")] == "setup") else if (obj[F("path")] == "setup")
setSetup(obj, dummy); setSetup(obj, dummy);
} }
private: private:
void onApi(AsyncWebServerRequest *request) { void onApi(AsyncWebServerRequest *request) {
mHeapFree = ESP.getFreeHeap(); mHeapFree = ESP.getFreeHeap();
#ifndef ESP32 #ifndef ESP32
mHeapFreeBlk = ESP.getMaxFreeBlockSize(); mHeapFreeBlk = ESP.getMaxFreeBlockSize();
mHeapFrag = ESP.getHeapFragmentation(); mHeapFrag = ESP.getHeapFragmentation();
#endif #endif
AsyncJsonResponse* response = new AsyncJsonResponse(false, 6000); AsyncJsonResponse *response = new AsyncJsonResponse(false, 6000);
JsonObject root = response->getRoot(); JsonObject root = response->getRoot();
String path = request->url().substring(5); String path = request->url().substring(5);
if(path == "html/system") getHtmlSystem(root); if (path == "html/system")
else if(path == "html/logout") getHtmlLogout(root); getHtmlSystem(root);
else if(path == "html/save") getHtmlSave(root); else if (path == "html/logout")
else if(path == "system") getSysInfo(root); getHtmlLogout(root);
else if(path == "generic") getGeneric(root); else if (path == "html/save")
else if(path == "reboot") getReboot(root); getHtmlSave(root);
else if(path == "statistics") getStatistics(root); else if (path == "system")
else if(path == "inverter/list") getInverterList(root); getSysInfo(root);
else if(path == "index") getIndex(root); else if (path == "generic")
else if(path == "setup") getSetup(root); getGeneric(root);
else if(path == "setup/networks") getNetworks(root); else if (path == "reboot")
else if(path == "live") getLive(root); getReboot(root);
else if(path == "record/info") getRecord(root, InverterDevInform_All); else if (path == "statistics")
else if(path == "record/alarm") getRecord(root, AlarmData); getStatistics(root);
else if(path == "record/config") getRecord(root, SystemConfigPara); else if (path == "inverter/list")
else if(path == "record/live") getRecord(root, RealTimeRunData_Debug); 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 { else {
if(path.substring(0, 12) == "inverter/id/") if (path.substring(0, 12) == "inverter/id/")
getInverter(root, request->url().substring(17).toInt()); getInverter(root, request->url().substring(17).toInt());
else else
getNotFound(root, F("http://") + request->host() + F("/api/")); getNotFound(root, F("http://") + request->host() + F("/api/"));
} }
//DPRINTLN(DBG_INFO, "API mem usage: " + String(root.memoryUsage())); // DPRINTLN(DBG_INFO, "API mem usage: " + String(root.memoryUsage()));
response->addHeader("Access-Control-Allow-Origin", "*"); response->addHeader("Access-Control-Allow-Origin", "*");
response->addHeader("Access-Control-Allow-Headers", "content-type"); response->addHeader("Access-Control-Allow-Headers", "content-type");
response->setLength(); response->setLength();
@ -115,29 +129,35 @@ class RestApi {
void onApiPostBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) { void onApiPostBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) {
DPRINTLN(DBG_VERBOSE, "onApiPostBody"); DPRINTLN(DBG_VERBOSE, "onApiPostBody");
DynamicJsonDocument json(200); DynamicJsonDocument json(200);
AsyncJsonResponse* response = new AsyncJsonResponse(false, 200); AsyncJsonResponse *response = new AsyncJsonResponse(false, 200);
JsonObject root = response->getRoot(); JsonObject root = response->getRoot();
DeserializationError err = deserializeJson(json, (const char *)data, len); DeserializationError err = deserializeJson(json, (const char *)data, len);
JsonObject obj = json.as<JsonObject>(); JsonObject obj = json.as<JsonObject>();
root[F("success")] = (err) ? false : true; root[F("success")] = (err) ? false : true;
if(!err) { if (!err) {
String path = request->url().substring(5); String path = request->url().substring(5);
if(path == "ctrl") if (path == "ctrl")
root[F("success")] = setCtrl(obj, root); root[F("success")] = setCtrl(obj, root);
else if(path == "setup") else if (path == "setup")
root[F("success")] = setSetup(obj, root); root[F("success")] = setSetup(obj, root);
else { else {
root[F("success")] = false; root[F("success")] = false;
root[F("error")] = "Path not found: " + path; root[F("error")] = "Path not found: " + path;
} }
} } else {
else {
switch (err.code()) { switch (err.code()) {
case DeserializationError::Ok: break; case DeserializationError::Ok:
case DeserializationError::InvalidInput: root[F("error")] = F("Invalid input"); break; break;
case DeserializationError::NoMemory: root[F("error")] = F("Not enough memory"); break; case DeserializationError::InvalidInput:
default: root[F("error")] = F("Deserialization failed"); break; 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;
} }
} }
@ -163,19 +183,18 @@ class RestApi {
AsyncWebServerResponse *response; AsyncWebServerResponse *response;
File fp = LittleFS.open("/settings.json", "r"); File fp = LittleFS.open("/settings.json", "r");
if(!fp) { if (!fp) {
DPRINTLN(DBG_ERROR, F("failed to load settings")); DPRINTLN(DBG_ERROR, F("failed to load settings"));
response = request->beginResponse(200, F("application/json; charset=utf-8"), "{}"); response = request->beginResponse(200, F("application/json; charset=utf-8"), "{}");
} } else {
else {
String tmp = fp.readString(); String tmp = fp.readString();
int i = 0; int i = 0;
// remove all passwords // remove all passwords
while (i != -1) { while (i != -1) {
i = tmp.indexOf("\"pwd\":", i); i = tmp.indexOf("\"pwd\":", i);
if(-1 != i) { if (-1 != i) {
i+=7; i += 7;
tmp.remove(i, tmp.indexOf("\"", i)-i); tmp.remove(i, tmp.indexOf("\"", i) - i);
} }
} }
response = request->beginResponse(200, F("application/json; charset=utf-8"), tmp); response = request->beginResponse(200, F("application/json; charset=utf-8"), tmp);
@ -192,14 +211,14 @@ class RestApi {
obj[F("wifi_rssi")] = (WiFi.status() != WL_CONNECTED) ? 0 : WiFi.RSSI(); obj[F("wifi_rssi")] = (WiFi.status() != WL_CONNECTED) ? 0 : WiFi.RSSI();
obj[F("ts_uptime")] = mApp->getUptime(); obj[F("ts_uptime")] = mApp->getUptime();
obj[F("menu_prot")] = mApp->getProtection(); obj[F("menu_prot")] = mApp->getProtection();
obj[F("menu_mask")] = (uint16_t)(mConfig->sys.protectionMask ); obj[F("menu_mask")] = (uint16_t)(mConfig->sys.protectionMask);
obj[F("menu_protEn")] = (bool) (strlen(mConfig->sys.adminPwd) > 0); obj[F("menu_protEn")] = (bool)(strlen(mConfig->sys.adminPwd) > 0);
#if defined(ESP32) #if defined(ESP32)
obj[F("esp_type")] = F("ESP32"); obj[F("esp_type")] = F("ESP32");
#else #else
obj[F("esp_type")] = F("ESP8266"); obj[F("esp_type")] = F("ESP8266");
#endif #endif
} }
void getSysInfo(JsonObject obj) { void getSysInfo(JsonObject obj) {
@ -222,29 +241,29 @@ class RestApi {
getRadio(obj.createNestedObject(F("radio"))); getRadio(obj.createNestedObject(F("radio")));
getStatistics(obj.createNestedObject(F("statistics"))); getStatistics(obj.createNestedObject(F("statistics")));
#if defined(ESP32) #if defined(ESP32)
obj[F("heap_total")] = ESP.getHeapSize(); obj[F("heap_total")] = ESP.getHeapSize();
obj[F("chip_revision")] = ESP.getChipRevision(); obj[F("chip_revision")] = ESP.getChipRevision();
obj[F("chip_model")] = ESP.getChipModel(); obj[F("chip_model")] = ESP.getChipModel();
obj[F("chip_cores")] = ESP.getChipCores(); obj[F("chip_cores")] = ESP.getChipCores();
//obj[F("core_version")] = F("n/a"); // obj[F("core_version")] = F("n/a");
//obj[F("flash_size")] = F("n/a"); // obj[F("flash_size")] = F("n/a");
//obj[F("heap_frag")] = F("n/a"); // obj[F("heap_frag")] = F("n/a");
//obj[F("max_free_blk")] = F("n/a"); // obj[F("max_free_blk")] = F("n/a");
//obj[F("reboot_reason")] = F("n/a"); // obj[F("reboot_reason")] = F("n/a");
#else #else
//obj[F("heap_total")] = F("n/a"); // obj[F("heap_total")] = F("n/a");
//obj[F("chip_revision")] = F("n/a"); // obj[F("chip_revision")] = F("n/a");
//obj[F("chip_model")] = F("n/a"); // obj[F("chip_model")] = F("n/a");
//obj[F("chip_cores")] = F("n/a"); // obj[F("chip_cores")] = F("n/a");
obj[F("core_version")] = ESP.getCoreVersion(); obj[F("core_version")] = ESP.getCoreVersion();
obj[F("flash_size")] = ESP.getFlashChipRealSize() / 1024; // in kb obj[F("flash_size")] = ESP.getFlashChipRealSize() / 1024; // in kb
obj[F("heap_frag")] = mHeapFrag; obj[F("heap_frag")] = mHeapFrag;
obj[F("max_free_blk")] = mHeapFreeBlk; obj[F("max_free_blk")] = mHeapFreeBlk;
obj[F("reboot_reason")] = ESP.getResetReason(); obj[F("reboot_reason")] = ESP.getResetReason();
#endif #endif
//obj[F("littlefs_total")] = LittleFS.totalBytes(); // obj[F("littlefs_total")] = LittleFS.totalBytes();
//obj[F("littlefs_used")] = LittleFS.usedBytes(); // obj[F("littlefs_used")] = LittleFS.usedBytes();
uint8_t max; uint8_t max;
mApp->getSchedulerInfo(&max); mApp->getSchedulerInfo(&max);
@ -255,7 +274,6 @@ class RestApi {
getSysInfo(obj.createNestedObject(F("system"))); getSysInfo(obj.createNestedObject(F("system")));
getGeneric(obj.createNestedObject(F("generic"))); getGeneric(obj.createNestedObject(F("generic")));
obj[F("html")] = F("<a href=\"/factory\" class=\"btn\">Factory Reset</a><br/><br/><a href=\"/reboot\" class=\"btn\">Reboot</a>"); obj[F("html")] = F("<a href=\"/factory\" class=\"btn\">Factory Reset</a><br/><br/><a href=\"/reboot\" class=\"btn\">Reboot</a>");
} }
void getHtmlLogout(JsonObject obj) { void getHtmlLogout(JsonObject obj) {
@ -293,9 +311,9 @@ class RestApi {
JsonArray invArr = obj.createNestedArray(F("inverter")); JsonArray invArr = obj.createNestedArray(F("inverter"));
Inverter<> *iv; Inverter<> *iv;
for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i ++) { for (uint8_t i = 0; i < MAX_NUM_INVERTERS; i++) {
iv = mSys->getInverterByPos(i); iv = mSys->getInverterByPos(i);
if(NULL != iv) { if (NULL != iv) {
JsonObject obj2 = invArr.createNestedObject(); JsonObject obj2 = invArr.createNestedObject();
obj2[F("enabled")] = (bool)iv->config->enabled; obj2[F("enabled")] = (bool)iv->config->enabled;
obj2[F("id")] = i; obj2[F("id")] = i;
@ -304,7 +322,7 @@ class RestApi {
obj2[F("channels")] = iv->channels; obj2[F("channels")] = iv->channels;
obj2[F("version")] = String(iv->getFwVersion()); obj2[F("version")] = String(iv->getFwVersion());
for(uint8_t j = 0; j < iv->channels; j ++) { for (uint8_t j = 0; j < iv->channels; j++) {
obj2[F("ch_yield_cor")][j] = iv->config->yieldCor[j]; obj2[F("ch_yield_cor")][j] = iv->config->yieldCor[j];
obj2[F("ch_max_power")][j] = iv->config->chMaxPwr[j]; obj2[F("ch_max_power")][j] = iv->config->chMaxPwr[j];
obj2[F("ch_name")][j] = iv->config->chName[j]; obj2[F("ch_name")][j] = iv->config->chName[j];
@ -321,7 +339,7 @@ class RestApi {
void getInverter(JsonObject obj, uint8_t id) { void getInverter(JsonObject obj, uint8_t id) {
Inverter<> *iv = mSys->getInverterByPos(id); Inverter<> *iv = mSys->getInverterByPos(id);
if(NULL != iv) { if (NULL != iv) {
record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug); record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug);
obj[F("id")] = id; obj[F("id")] = id;
obj[F("enabled")] = (bool)iv->config->enabled; obj[F("enabled")] = (bool)iv->config->enabled;
@ -343,11 +361,11 @@ class RestApi {
} }
// DC // DC
for(uint8_t j = 0; j < iv->channels; j ++) { for (uint8_t j = 0; j < iv->channels; j++) {
obj[F("ch_name")][j+1] = iv->config->chName[j]; obj[F("ch_name")][j + 1] = iv->config->chName[j];
JsonArray cur = ch.createNestedArray(); JsonArray cur = ch.createNestedArray();
for (uint8_t fld = 0; fld < sizeof(dcList); fld++) { for (uint8_t fld = 0; fld < sizeof(dcList); fld++) {
pos = (iv->getPosByChFld((j+1), dcList[fld], rec)); pos = (iv->getPosByChFld((j + 1), dcList[fld], rec));
cur[fld] = (0xff != pos) ? ah::round3(iv->getValue(pos, rec)) : 0.0; cur[fld] = (0xff != pos) ? ah::round3(iv->getValue(pos, rec)) : 0.0;
} }
} }
@ -398,25 +416,33 @@ class RestApi {
void getStaticIp(JsonObject obj) { void getStaticIp(JsonObject obj) {
char buf[16]; char buf[16];
ah::ip2Char(mConfig->sys.ip.ip, buf); obj[F("ip")] = String(buf); ah::ip2Char(mConfig->sys.ip.ip, buf);
ah::ip2Char(mConfig->sys.ip.mask, buf); obj[F("mask")] = String(buf); obj[F("ip")] = String(buf);
ah::ip2Char(mConfig->sys.ip.dns1, buf); obj[F("dns1")] = String(buf); ah::ip2Char(mConfig->sys.ip.mask, buf);
ah::ip2Char(mConfig->sys.ip.dns2, buf); obj[F("dns2")] = String(buf); obj[F("mask")] = String(buf);
ah::ip2Char(mConfig->sys.ip.gateway, buf); obj[F("gateway")] = 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) { void getDisplay(JsonObject obj) {
obj[F("disp_type")] = (uint8_t)mConfig->plugin.display.type; obj[F("disp_type")] = (uint8_t)mConfig->plugin.display.type;
obj[F("disp_pwr")] = (bool)mConfig->plugin.display.pwrSaveAtIvOffline; obj[F("disp_pwr")] = (bool)mConfig->plugin.display.pwrSaveAtIvOffline;
obj[F("logo_en")] = (bool)mConfig->plugin.display.logoEn;
obj[F("px_shift")] = (bool)mConfig->plugin.display.pxShift; obj[F("px_shift")] = (bool)mConfig->plugin.display.pxShift;
obj[F("rot180")] = (bool)mConfig->plugin.display.rot180; 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("contrast")] = (uint8_t)mConfig->plugin.display.contrast;
obj[F("pinDisp0")] = mConfig->plugin.display.pin0; obj[F("data")] = mConfig->plugin.display.disp_data;
obj[F("pinDisp1")] = mConfig->plugin.display.pin1; 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) { void getIndex(JsonObject obj) {
getGeneric(obj.createNestedObject(F("generic"))); getGeneric(obj.createNestedObject(F("generic")));
obj[F("ts_now")] = mApp->getTimestamp(); obj[F("ts_now")] = mApp->getTimestamp();
@ -427,9 +453,9 @@ class RestApi {
JsonArray inv = obj.createNestedArray(F("inverter")); JsonArray inv = obj.createNestedArray(F("inverter"));
Inverter<> *iv; Inverter<> *iv;
for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i ++) { for (uint8_t i = 0; i < MAX_NUM_INVERTERS; i++) {
iv = mSys->getInverterByPos(i); iv = mSys->getInverterByPos(i);
if(NULL != iv) { if (NULL != iv) {
record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug); record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug);
JsonObject invObj = inv.createNestedObject(); JsonObject invObj = inv.createNestedObject();
invObj[F("enabled")] = (bool)iv->config->enabled; invObj[F("enabled")] = (bool)iv->config->enabled;
@ -443,33 +469,33 @@ class RestApi {
} }
JsonArray warn = obj.createNestedArray(F("warnings")); JsonArray warn = obj.createNestedArray(F("warnings"));
if(!mSys->Radio.isChipConnected()) if (!mSys->Radio.isChipConnected())
warn.add(F("your NRF24 module can't be reached, check the wiring and pinout")); warn.add(F("your NRF24 module can't be reached, check the wiring and pinout"));
else if(!mSys->Radio.isPVariant()) else if (!mSys->Radio.isPVariant())
warn.add(F("your NRF24 module isn't a plus version(+), maybe incompatible")); warn.add(F("your NRF24 module isn't a plus version(+), maybe incompatible"));
if(!mApp->getSettingsValid()) if (!mApp->getSettingsValid())
warn.add(F("your settings are invalid")); warn.add(F("your settings are invalid"));
if(mApp->getRebootRequestState()) if (mApp->getRebootRequestState())
warn.add(F("reboot your ESP to apply all your configuration changes")); warn.add(F("reboot your ESP to apply all your configuration changes"));
if(0 == mApp->getTimestamp()) if (0 == mApp->getTimestamp())
warn.add(F("time not set. No communication to inverter possible")); warn.add(F("time not set. No communication to inverter possible"));
/*if(0 == mSys->getNumInverters()) /*if(0 == mSys->getNumInverters())
warn.add(F("no inverter configured"));*/ warn.add(F("no inverter configured"));*/
if((!mApp->getMqttIsConnected()) && (String(mConfig->mqtt.broker).length() > 0)) if ((!mApp->getMqttIsConnected()) && (String(mConfig->mqtt.broker).length() > 0))
warn.add(F("MQTT is not connected")); warn.add(F("MQTT is not connected"));
JsonArray info = obj.createNestedArray(F("infos")); JsonArray info = obj.createNestedArray(F("infos"));
if(mApp->getMqttIsConnected()) if (mApp->getMqttIsConnected())
info.add(F("MQTT is connected, ") + String(mApp->getMqttTxCnt()) + F(" packets sent, ") + String(mApp->getMqttRxCnt()) + F(" packets received")); info.add(F("MQTT is connected, ") + String(mApp->getMqttTxCnt()) + F(" packets sent, ") + String(mApp->getMqttRxCnt()) + F(" packets received"));
if(mConfig->mqtt.interval > 0) if (mConfig->mqtt.interval > 0)
info.add(F("MQTT publishes in a fixed interval of ") + String(mConfig->mqtt.interval) + F(" seconds")); info.add(F("MQTT publishes in a fixed interval of ") + String(mConfig->mqtt.interval) + F(" seconds"));
} }
void getSetup(JsonObject obj) { void getSetup(JsonObject obj) {
getGeneric(obj.createNestedObject(F("generic"))); getGeneric(obj.createNestedObject(F("generic")));
getSysInfo(obj.createNestedObject(F("system"))); getSysInfo(obj.createNestedObject(F("system")));
//getInverterList(obj.createNestedObject(F("inverter"))); // getInverterList(obj.createNestedObject(F("inverter")));
getMqtt(obj.createNestedObject(F("mqtt"))); getMqtt(obj.createNestedObject(F("mqtt")));
getNtp(obj.createNestedObject(F("ntp"))); getNtp(obj.createNestedObject(F("ntp")));
getSun(obj.createNestedObject(F("sun"))); getSun(obj.createNestedObject(F("sun")));
@ -486,7 +512,7 @@ class RestApi {
void getLive(JsonObject obj) { void getLive(JsonObject obj) {
getGeneric(obj.createNestedObject(F("generic"))); getGeneric(obj.createNestedObject(F("generic")));
//JsonArray invArr = obj.createNestedArray(F("inverter")); // JsonArray invArr = obj.createNestedArray(F("inverter"));
obj[F("refresh")] = mConfig->nrf.sendInterval; obj[F("refresh")] = mConfig->nrf.sendInterval;
for (uint8_t fld = 0; fld < sizeof(acList); fld++) { for (uint8_t fld = 0; fld < sizeof(acList); fld++) {
@ -499,10 +525,10 @@ class RestApi {
} }
Inverter<> *iv; Inverter<> *iv;
for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i ++) { for (uint8_t i = 0; i < MAX_NUM_INVERTERS; i++) {
iv = mSys->getInverterByPos(i); iv = mSys->getInverterByPos(i);
bool parse = false; bool parse = false;
if(NULL != iv) if (NULL != iv)
parse = iv->config->enabled; parse = iv->config->enabled;
obj[F("iv")][i] = parse; obj[F("iv")][i] = parse;
} }
@ -560,12 +586,12 @@ class RestApi {
Inverter<> *iv; Inverter<> *iv;
record_t<> *rec; record_t<> *rec;
uint8_t pos; uint8_t pos;
for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i ++) { for (uint8_t i = 0; i < MAX_NUM_INVERTERS; i++) {
iv = mSys->getInverterByPos(i); iv = mSys->getInverterByPos(i);
if(NULL != iv) { if (NULL != iv) {
rec = iv->getRecordStruct(recType); rec = iv->getRecordStruct(recType);
JsonArray obj2 = invArr.createNestedArray(); JsonArray obj2 = invArr.createNestedArray();
for(uint8_t j = 0; j < rec->length; j++) { for (uint8_t j = 0; j < rec->length; j++) {
byteAssign_t *assign = iv->getByteAssign(j, rec); byteAssign_t *assign = iv->getByteAssign(j, rec);
pos = (iv->getPosByChFld(assign->ch, assign->fieldId, rec)); pos = (iv->getPosByChFld(assign->ch, assign->fieldId, rec));
obj2[j]["fld"] = (0xff != pos) ? String(iv->getFieldName(pos, rec)) : notAvail; obj2[j]["fld"] = (0xff != pos) ? String(iv->getFieldName(pos, rec)) : notAvail;
@ -579,38 +605,36 @@ class RestApi {
bool setCtrl(JsonObject jsonIn, JsonObject jsonOut) { bool setCtrl(JsonObject jsonIn, JsonObject jsonOut) {
Inverter<> *iv = mSys->getInverterByPos(jsonIn[F("id")]); Inverter<> *iv = mSys->getInverterByPos(jsonIn[F("id")]);
bool accepted = true; bool accepted = true;
if(NULL == iv) { if (NULL == iv) {
jsonOut[F("error")] = F("inverter index invalid: ") + jsonIn[F("id")].as<String>(); jsonOut[F("error")] = F("inverter index invalid: ") + jsonIn[F("id")].as<String>();
return false; return false;
} }
if(F("power") == jsonIn[F("cmd")]) if (F("power") == jsonIn[F("cmd")])
accepted = iv->setDevControlRequest((jsonIn[F("val")] == 1) ? TurnOn : TurnOff); accepted = iv->setDevControlRequest((jsonIn[F("val")] == 1) ? TurnOn : TurnOff);
else if(F("restart") == jsonIn[F("restart")]) else if (F("restart") == jsonIn[F("restart")])
accepted = iv->setDevControlRequest(Restart); accepted = iv->setDevControlRequest(Restart);
else if(0 == strncmp("limit_", jsonIn[F("cmd")].as<const char*>(), 6)) { else if (0 == strncmp("limit_", jsonIn[F("cmd")].as<const char *>(), 6)) {
iv->powerLimit[0] = jsonIn["val"]; iv->powerLimit[0] = jsonIn["val"];
if(F("limit_persistent_relative") == jsonIn[F("cmd")]) if (F("limit_persistent_relative") == jsonIn[F("cmd")])
iv->powerLimit[1] = RelativPersistent; iv->powerLimit[1] = RelativPersistent;
else if(F("limit_persistent_absolute") == jsonIn[F("cmd")]) else if (F("limit_persistent_absolute") == jsonIn[F("cmd")])
iv->powerLimit[1] = AbsolutPersistent; iv->powerLimit[1] = AbsolutPersistent;
else if(F("limit_nonpersistent_relative") == jsonIn[F("cmd")]) else if (F("limit_nonpersistent_relative") == jsonIn[F("cmd")])
iv->powerLimit[1] = RelativNonPersistent; iv->powerLimit[1] = RelativNonPersistent;
else if(F("limit_nonpersistent_absolute") == jsonIn[F("cmd")]) else if (F("limit_nonpersistent_absolute") == jsonIn[F("cmd")])
iv->powerLimit[1] = AbsolutNonPersistent; iv->powerLimit[1] = AbsolutNonPersistent;
accepted = iv->setDevControlRequest(ActivePowerContr); accepted = iv->setDevControlRequest(ActivePowerContr);
} } else if (F("dev") == jsonIn[F("cmd")]) {
else if(F("dev") == jsonIn[F("cmd")]) {
DPRINTLN(DBG_INFO, F("dev cmd")); DPRINTLN(DBG_INFO, F("dev cmd"));
iv->enqueCommand<InfoCommand>(jsonIn[F("val")].as<int>()); iv->enqueCommand<InfoCommand>(jsonIn[F("val")].as<int>());
} } else {
else {
jsonOut[F("error")] = F("unknown cmd: '") + jsonIn["cmd"].as<String>() + "'"; jsonOut[F("error")] = F("unknown cmd: '") + jsonIn["cmd"].as<String>() + "'";
return false; return false;
} }
if(!accepted) { if (!accepted) {
jsonOut[F("error")] = F("inverter does not accept dev control request at this moment"); jsonOut[F("error")] = F("inverter does not accept dev control request at this moment");
return false; return false;
} else } else
@ -620,18 +644,17 @@ class RestApi {
} }
bool setSetup(JsonObject jsonIn, JsonObject jsonOut) { bool setSetup(JsonObject jsonIn, JsonObject jsonOut) {
if(F("scan_wifi") == jsonIn[F("cmd")]) if (F("scan_wifi") == jsonIn[F("cmd")])
mApp->scanAvailNetworks(); mApp->scanAvailNetworks();
else if(F("set_time") == jsonIn[F("cmd")]) else if (F("set_time") == jsonIn[F("cmd")])
mApp->setTimestamp(jsonIn[F("val")]); mApp->setTimestamp(jsonIn[F("val")]);
else if(F("sync_ntp") == jsonIn[F("cmd")]) else if (F("sync_ntp") == jsonIn[F("cmd")])
mApp->setTimestamp(0); // 0: update ntp flag mApp->setTimestamp(0); // 0: update ntp flag
else if(F("serial_utc_offset") == jsonIn[F("cmd")]) else if (F("serial_utc_offset") == jsonIn[F("cmd")])
mTimezoneOffset = jsonIn[F("val")]; mTimezoneOffset = jsonIn[F("val")];
else if(F("discovery_cfg") == jsonIn[F("cmd")]) { else if (F("discovery_cfg") == jsonIn[F("cmd")]) {
mApp->setMqttDiscoveryFlag(); // for homeassistant mApp->setMqttDiscoveryFlag(); // for homeassistant
} } else {
else {
jsonOut[F("error")] = F("unknown cmd"); jsonOut[F("error")] = F("unknown cmd");
return false; return false;
} }

355
src/web/web.h

@ -8,36 +8,33 @@
#include "../utils/dbg.h" #include "../utils/dbg.h"
#ifdef ESP32 #ifdef ESP32
#include "AsyncTCP.h" #include "AsyncTCP.h"
#include "Update.h" #include "Update.h"
#else #else
#include "ESPAsyncTCP.h" #include "ESPAsyncTCP.h"
#endif #endif
#include "ESPAsyncWebServer.h"
#include "../appInterface.h" #include "../appInterface.h"
#include "../hm/hmSystem.h" #include "../hm/hmSystem.h"
#include "../utils/helper.h" #include "../utils/helper.h"
#include "ESPAsyncWebServer.h"
#include "html/h/index_html.h"
#include "html/h/login_html.h"
#include "html/h/style_css.h"
#include "html/h/colorDark_css.h"
#include "html/h/colorBright_css.h"
#include "html/h/api_js.h" #include "html/h/api_js.h"
#include "html/h/colorBright_css.h"
#include "html/h/colorDark_css.h"
#include "html/h/favicon_ico.h" #include "html/h/favicon_ico.h"
#include "html/h/setup_html.h" #include "html/h/index_html.h"
#include "html/h/visualization_html.h" #include "html/h/login_html.h"
#include "html/h/update_html.h"
#include "html/h/serial_html.h" #include "html/h/serial_html.h"
#include "html/h/setup_html.h"
#include "html/h/style_css.h"
#include "html/h/system_html.h" #include "html/h/system_html.h"
#include "html/h/update_html.h"
#include "html/h/visualization_html.h"
#define WEB_SERIAL_BUF_SIZE 2048 #define WEB_SERIAL_BUF_SIZE 2048
const char* const pinArgNames[] = {"pinCs", "pinCe", "pinIrq", "pinLed0", "pinLed1"}; const char *const pinArgNames[] = {"pinCs", "pinCe", "pinIrq", "pinLed0", "pinLed1"};
template<class HMSYSTEM> template <class HMSYSTEM>
class Web { class Web {
public: public:
Web(void) : mWeb(80), mEvts("/events") { Web(void) : mWeb(80), mEvts("/events") {
@ -63,7 +60,7 @@ class Web {
mWeb.on("/style.css", HTTP_GET, std::bind(&Web::onCss, 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("/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.on("/favicon.ico", HTTP_GET, std::bind(&Web::onFavicon, this, std::placeholders::_1));
mWeb.onNotFound ( std::bind(&Web::showNotFound, 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("/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("/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("/erase", HTTP_ANY, std::bind(&Web::showErase, this, std::placeholders::_1));
@ -73,14 +70,14 @@ class Web {
mWeb.on("/save", HTTP_ANY, std::bind(&Web::showSave, 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("/live", HTTP_ANY, std::bind(&Web::onLive, this, std::placeholders::_1));
//mWeb.on("/api1", HTTP_POST, std::bind(&Web::showWebApi, this, std::placeholders::_1)); // mWeb.on("/api1", HTTP_POST, std::bind(&Web::showWebApi, this, std::placeholders::_1));
#ifdef ENABLE_JSON_EP #ifdef ENABLE_JSON_EP
mWeb.on("/json", HTTP_ANY, std::bind(&Web::showJson, this, std::placeholders::_1)); mWeb.on("/json", HTTP_ANY, std::bind(&Web::showJson, this, std::placeholders::_1));
#endif #endif
#ifdef ENABLE_PROMETHEUS_EP #ifdef ENABLE_PROMETHEUS_EP
mWeb.on("/metrics", HTTP_ANY, std::bind(&Web::showMetrics, this, std::placeholders::_1)); mWeb.on("/metrics", HTTP_ANY, std::bind(&Web::showMetrics, this, std::placeholders::_1));
#endif #endif
mWeb.on("/update", HTTP_GET, std::bind(&Web::onUpdate, this, std::placeholders::_1)); 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), mWeb.on("/update", HTTP_POST, std::bind(&Web::showUpdate, this, std::placeholders::_1),
@ -90,7 +87,6 @@ class Web {
mWeb.on("/serial", HTTP_GET, std::bind(&Web::onSerial, this, std::placeholders::_1)); 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)); mWeb.on("/debug", HTTP_GET, std::bind(&Web::onDebug, this, std::placeholders::_1));
mEvts.onConnect(std::bind(&Web::onConnect, this, std::placeholders::_1)); mEvts.onConnect(std::bind(&Web::onConnect, this, std::placeholders::_1));
mWeb.addHandler(&mEvts); mWeb.addHandler(&mEvts);
@ -102,18 +98,18 @@ class Web {
} }
void tickSecond() { void tickSecond() {
if(0 != mLogoutTimeout) { if (0 != mLogoutTimeout) {
mLogoutTimeout -= 1; mLogoutTimeout -= 1;
if(0 == mLogoutTimeout) { if (0 == mLogoutTimeout) {
if(strlen(mConfig->sys.adminPwd) > 0) if (strlen(mConfig->sys.adminPwd) > 0)
mProtected = true; mProtected = true;
} }
DPRINTLN(DBG_DEBUG, "auto logout in " + String(mLogoutTimeout)); DPRINTLN(DBG_DEBUG, "auto logout in " + String(mLogoutTimeout));
} }
if(mSerialClientConnnected) { if (mSerialClientConnnected) {
if(mSerialBufFill > 0) { if (mSerialBufFill > 0) {
mEvts.send(mSerialBuf, "serial", millis()); mEvts.send(mSerialBuf, "serial", millis());
memset(mSerialBuf, 0, WEB_SERIAL_BUF_SIZE); memset(mSerialBuf, 0, WEB_SERIAL_BUF_SIZE);
mSerialBufFill = 0; mSerialBufFill = 0;
@ -136,23 +132,23 @@ class Web {
void showUpdate2(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) { void showUpdate2(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) {
mApp->setOnUpdate(); mApp->setOnUpdate();
if(!index) { if (!index) {
Serial.printf("Update Start: %s\n", filename.c_str()); Serial.printf("Update Start: %s\n", filename.c_str());
#ifndef ESP32 #ifndef ESP32
Update.runAsync(true); Update.runAsync(true);
#endif #endif
if(!Update.begin((ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000)) { if (!Update.begin((ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000)) {
Update.printError(Serial); Update.printError(Serial);
} }
} }
if(!Update.hasError()) { if (!Update.hasError()) {
if(Update.write(data, len) != len){ if (Update.write(data, len) != len) {
Update.printError(Serial); Update.printError(Serial);
} }
} }
if(final) { if (final) {
if(Update.end(true)) { if (Update.end(true)) {
Serial.printf("Update Success: %uB\n", index+len); Serial.printf("Update Success: %uB\n", index + len);
} else { } else {
Update.printError(Serial); Update.printError(Serial);
} }
@ -160,27 +156,26 @@ class Web {
} }
void onUpload2(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) { void onUpload2(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) {
if(!index) { if (!index) {
mUploadFail = false; mUploadFail = false;
mUploadFp = LittleFS.open("/tmp.json", "w"); mUploadFp = LittleFS.open("/tmp.json", "w");
if(!mUploadFp) { if (!mUploadFp) {
DPRINTLN(DBG_ERROR, F("can't open file!")); DPRINTLN(DBG_ERROR, F("can't open file!"));
mUploadFail = true; mUploadFail = true;
mUploadFp.close(); mUploadFp.close();
} }
} }
mUploadFp.write(data, len); mUploadFp.write(data, len);
if(final) { if (final) {
mUploadFp.close(); mUploadFp.close();
File fp = LittleFS.open("/tmp.json", "r"); File fp = LittleFS.open("/tmp.json", "r");
if(!fp) if (!fp)
mUploadFail = true; mUploadFail = true;
else { else {
if(!mApp->readSettings("tmp.json")) { if (!mApp->readSettings("tmp.json")) {
mUploadFail = true; mUploadFail = true;
DPRINTLN(DBG_ERROR, F("upload JSON error!")); DPRINTLN(DBG_ERROR, F("upload JSON error!"));
} } else
else
mApp->saveSettings(); mApp->saveSettings();
} }
DPRINTLN(DBG_INFO, F("upload finished!")); DPRINTLN(DBG_INFO, F("upload finished!"));
@ -188,18 +183,17 @@ class Web {
} }
void serialCb(String msg) { void serialCb(String msg) {
if(!mSerialClientConnnected) if (!mSerialClientConnnected)
return; return;
msg.replace("\r\n", "<rn>"); msg.replace("\r\n", "<rn>");
if(mSerialAddTime) { if (mSerialAddTime) {
if((9 + mSerialBufFill) < WEB_SERIAL_BUF_SIZE) { if ((9 + mSerialBufFill) < WEB_SERIAL_BUF_SIZE) {
if(mApp->getTimestamp() > 0) { if (mApp->getTimestamp() > 0) {
strncpy(&mSerialBuf[mSerialBufFill], mApp->getTimeStr(mApp->getTimezoneOffset()).c_str(), 9); strncpy(&mSerialBuf[mSerialBufFill], mApp->getTimeStr(mApp->getTimezoneOffset()).c_str(), 9);
mSerialBufFill += 9; mSerialBufFill += 9;
} }
} } else {
else {
mSerialBufFill = 0; mSerialBufFill = 0;
mEvts.send("webSerial, buffer overflow!<rn>", "serial", millis()); mEvts.send("webSerial, buffer overflow!<rn>", "serial", millis());
return; return;
@ -207,15 +201,14 @@ class Web {
mSerialAddTime = false; mSerialAddTime = false;
} }
if(msg.endsWith("<rn>")) if (msg.endsWith("<rn>"))
mSerialAddTime = true; mSerialAddTime = true;
uint16_t length = msg.length(); uint16_t length = msg.length();
if((length + mSerialBufFill) < WEB_SERIAL_BUF_SIZE) { if ((length + mSerialBufFill) < WEB_SERIAL_BUF_SIZE) {
strncpy(&mSerialBuf[mSerialBufFill], msg.c_str(), length); strncpy(&mSerialBuf[mSerialBufFill], msg.c_str(), length);
mSerialBufFill += length; mSerialBufFill += length;
} } else {
else {
mSerialBufFill = 0; mSerialBufFill = 0;
mEvts.send("webSerial, buffer overflow!<rn>", "serial", millis()); mEvts.send("webSerial, buffer overflow!<rn>", "serial", millis());
} }
@ -223,13 +216,13 @@ class Web {
private: private:
void checkRedirect(AsyncWebServerRequest *request) { void checkRedirect(AsyncWebServerRequest *request) {
if((mConfig->sys.protectionMask & PROT_MASK_INDEX) != PROT_MASK_INDEX) if ((mConfig->sys.protectionMask & PROT_MASK_INDEX) != PROT_MASK_INDEX)
request->redirect(F("/index")); request->redirect(F("/index"));
else if((mConfig->sys.protectionMask & PROT_MASK_LIVE) != PROT_MASK_LIVE) else if ((mConfig->sys.protectionMask & PROT_MASK_LIVE) != PROT_MASK_LIVE)
request->redirect(F("/live")); request->redirect(F("/live"));
else if((mConfig->sys.protectionMask & PROT_MASK_SERIAL) != PROT_MASK_SERIAL) else if ((mConfig->sys.protectionMask & PROT_MASK_SERIAL) != PROT_MASK_SERIAL)
request->redirect(F("/serial")); request->redirect(F("/serial"));
else if((mConfig->sys.protectionMask & PROT_MASK_SYSTEM) != PROT_MASK_SYSTEM) else if ((mConfig->sys.protectionMask & PROT_MASK_SYSTEM) != PROT_MASK_SYSTEM)
request->redirect(F("/system")); request->redirect(F("/system"));
else else
request->redirect(F("/login")); request->redirect(F("/login"));
@ -238,8 +231,8 @@ class Web {
void onUpdate(AsyncWebServerRequest *request) { void onUpdate(AsyncWebServerRequest *request) {
DPRINTLN(DBG_VERBOSE, F("onUpdate")); DPRINTLN(DBG_VERBOSE, F("onUpdate"));
if(CHECK_MASK(mConfig->sys.protectionMask, PROT_MASK_UPDATE)) { if (CHECK_MASK(mConfig->sys.protectionMask, PROT_MASK_UPDATE)) {
if(mProtected) { if (mProtected) {
checkRedirect(request); checkRedirect(request);
return; return;
} }
@ -254,7 +247,7 @@ class Web {
bool reboot = !Update.hasError(); bool reboot = !Update.hasError();
String html = F("<!doctype html><html><head><title>Update</title><meta http-equiv=\"refresh\" content=\"20; URL=/\"></head><body>Update: "); String html = F("<!doctype html><html><head><title>Update</title><meta http-equiv=\"refresh\" content=\"20; URL=/\"></head><body>Update: ");
if(reboot) if (reboot)
html += "success"; html += "success";
else else
html += "failed"; html += "failed";
@ -270,7 +263,7 @@ class Web {
bool reboot = !mUploadFail; bool reboot = !mUploadFail;
String html = F("<!doctype html><html><head><title>Upload</title><meta http-equiv=\"refresh\" content=\"20; URL=/\"></head><body>Upload: "); String html = F("<!doctype html><html><head><title>Upload</title><meta http-equiv=\"refresh\" content=\"20; URL=/\"></head><body>Upload: ");
if(reboot) if (reboot)
html += "success"; html += "success";
else else
html += "failed"; html += "failed";
@ -287,7 +280,7 @@ class Web {
mSerialClientConnnected = true; mSerialClientConnnected = true;
if(client->lastId()) if (client->lastId())
DPRINTLN(DBG_VERBOSE, "Client reconnected! Last message ID that it got is: " + String(client->lastId())); DPRINTLN(DBG_VERBOSE, "Client reconnected! Last message ID that it got is: " + String(client->lastId()));
client->send("hello!", NULL, millis(), 1000); client->send("hello!", NULL, millis(), 1000);
@ -296,8 +289,8 @@ class Web {
void onIndex(AsyncWebServerRequest *request) { void onIndex(AsyncWebServerRequest *request) {
DPRINTLN(DBG_VERBOSE, F("onIndex")); DPRINTLN(DBG_VERBOSE, F("onIndex"));
if(CHECK_MASK(mConfig->sys.protectionMask, PROT_MASK_INDEX)) { if (CHECK_MASK(mConfig->sys.protectionMask, PROT_MASK_INDEX)) {
if(mProtected) { if (mProtected) {
checkRedirect(request); checkRedirect(request);
return; return;
} }
@ -311,8 +304,8 @@ class Web {
void onLogin(AsyncWebServerRequest *request) { void onLogin(AsyncWebServerRequest *request) {
DPRINTLN(DBG_VERBOSE, F("onLogin")); DPRINTLN(DBG_VERBOSE, F("onLogin"));
if(request->args() > 0) { if (request->args() > 0) {
if(String(request->arg("pwd")) == String(mConfig->sys.adminPwd)) { if (String(request->arg("pwd")) == String(mConfig->sys.adminPwd)) {
mProtected = false; mProtected = false;
request->redirect("/"); request->redirect("/");
} }
@ -326,7 +319,7 @@ class Web {
void onLogout(AsyncWebServerRequest *request) { void onLogout(AsyncWebServerRequest *request) {
DPRINTLN(DBG_VERBOSE, F("onLogout")); DPRINTLN(DBG_VERBOSE, F("onLogout"));
if(mProtected) { if (mProtected) {
checkRedirect(request); checkRedirect(request);
return; return;
} }
@ -341,7 +334,7 @@ class Web {
void onColor(AsyncWebServerRequest *request) { void onColor(AsyncWebServerRequest *request) {
DPRINTLN(DBG_VERBOSE, F("onColor")); DPRINTLN(DBG_VERBOSE, F("onColor"));
AsyncWebServerResponse *response; AsyncWebServerResponse *response;
if(mConfig->sys.darkMode) if (mConfig->sys.darkMode)
response = request->beginResponse_P(200, F("text/css"), colorDark_css, colorDark_css_len); response = request->beginResponse_P(200, F("text/css"), colorDark_css, colorDark_css_len);
else else
response = request->beginResponse_P(200, F("text/css"), colorBright_css, colorBright_css_len); response = request->beginResponse_P(200, F("text/css"), colorBright_css, colorBright_css_len);
@ -373,7 +366,7 @@ class Web {
} }
void showNotFound(AsyncWebServerRequest *request) { void showNotFound(AsyncWebServerRequest *request) {
if(mProtected) if (mProtected)
checkRedirect(request); checkRedirect(request);
else else
request->redirect("/setup"); request->redirect("/setup");
@ -387,7 +380,7 @@ class Web {
} }
void showErase(AsyncWebServerRequest *request) { void showErase(AsyncWebServerRequest *request) {
if(mProtected) { if (mProtected) {
checkRedirect(request); checkRedirect(request);
return; return;
} }
@ -398,7 +391,7 @@ class Web {
} }
void showFactoryRst(AsyncWebServerRequest *request) { void showFactoryRst(AsyncWebServerRequest *request) {
if(mProtected) { if (mProtected) {
checkRedirect(request); checkRedirect(request);
return; return;
} }
@ -406,26 +399,25 @@ class Web {
DPRINTLN(DBG_VERBOSE, F("showFactoryRst")); DPRINTLN(DBG_VERBOSE, F("showFactoryRst"));
String content = ""; String content = "";
int refresh = 3; int refresh = 3;
if(request->args() > 0) { if (request->args() > 0) {
if(request->arg("reset").toInt() == 1) { if (request->arg("reset").toInt() == 1) {
refresh = 10; refresh = 10;
if(mApp->eraseSettings(true)) if (mApp->eraseSettings(true))
content = F("factory reset: success\n\nrebooting ... "); content = F("factory reset: success\n\nrebooting ... ");
else else
content = F("factory reset: failed\n\nrebooting ... "); content = F("factory reset: failed\n\nrebooting ... ");
} } else {
else {
content = F("factory reset: aborted"); content = F("factory reset: aborted");
refresh = 3; refresh = 3;
} }
} } else {
else { content = F(
content = F("<h1>Factory Reset</h1>" "<h1>Factory Reset</h1>"
"<p><a href=\"/factory?reset=1\">RESET</a><br/><br/><a href=\"/factory?reset=0\">CANCEL</a><br/></p>"); "<p><a href=\"/factory?reset=1\">RESET</a><br/><br/><a href=\"/factory?reset=0\">CANCEL</a><br/></p>");
refresh = 120; refresh = 120;
} }
request->send(200, F("text/html; charset=UTF-8"), F("<!doctype html><html><head><title>Factory Reset</title><meta http-equiv=\"refresh\" content=\"") + String(refresh) + F("; URL=/\"></head><body>") + content + F("</body></html>")); request->send(200, F("text/html; charset=UTF-8"), F("<!doctype html><html><head><title>Factory Reset</title><meta http-equiv=\"refresh\" content=\"") + String(refresh) + F("; URL=/\"></head><body>") + content + F("</body></html>"));
if(refresh == 10) { if (refresh == 10) {
delay(1000); delay(1000);
ESP.restart(); ESP.restart();
} }
@ -434,8 +426,8 @@ class Web {
void onSetup(AsyncWebServerRequest *request) { void onSetup(AsyncWebServerRequest *request) {
DPRINTLN(DBG_VERBOSE, F("onSetup")); DPRINTLN(DBG_VERBOSE, F("onSetup"));
if(CHECK_MASK(mConfig->sys.protectionMask, PROT_MASK_SETUP)) { if (CHECK_MASK(mConfig->sys.protectionMask, PROT_MASK_SETUP)) {
if(mProtected) { if (mProtected) {
checkRedirect(request); checkRedirect(request);
return; return;
} }
@ -449,33 +441,33 @@ class Web {
void showSave(AsyncWebServerRequest *request) { void showSave(AsyncWebServerRequest *request) {
DPRINTLN(DBG_VERBOSE, F("showSave")); DPRINTLN(DBG_VERBOSE, F("showSave"));
if(mProtected) { if (mProtected) {
checkRedirect(request); checkRedirect(request);
return; return;
} }
if(request->args() == 0) if (request->args() == 0)
return; return;
char buf[20] = {0}; char buf[20] = {0};
// general // general
if(request->arg("ssid") != "") if (request->arg("ssid") != "")
request->arg("ssid").toCharArray(mConfig->sys.stationSsid, SSID_LEN); request->arg("ssid").toCharArray(mConfig->sys.stationSsid, SSID_LEN);
if(request->arg("pwd") != "{PWD}") if (request->arg("pwd") != "{PWD}")
request->arg("pwd").toCharArray(mConfig->sys.stationPwd, PWD_LEN); request->arg("pwd").toCharArray(mConfig->sys.stationPwd, PWD_LEN);
if(request->arg("device") != "") if (request->arg("device") != "")
request->arg("device").toCharArray(mConfig->sys.deviceName, DEVNAME_LEN); request->arg("device").toCharArray(mConfig->sys.deviceName, DEVNAME_LEN);
mConfig->sys.darkMode = (request->arg("darkMode") == "on"); mConfig->sys.darkMode = (request->arg("darkMode") == "on");
// protection // protection
if(request->arg("adminpwd") != "{PWD}") { if (request->arg("adminpwd") != "{PWD}") {
request->arg("adminpwd").toCharArray(mConfig->sys.adminPwd, PWD_LEN); request->arg("adminpwd").toCharArray(mConfig->sys.adminPwd, PWD_LEN);
mProtected = (strlen(mConfig->sys.adminPwd) > 0); mProtected = (strlen(mConfig->sys.adminPwd) > 0);
} }
mConfig->sys.protectionMask = 0x0000; mConfig->sys.protectionMask = 0x0000;
for(uint8_t i = 0; i < 6; i++) { for (uint8_t i = 0; i < 6; i++) {
if(request->arg("protMask" + String(i)) == "on") if (request->arg("protMask" + String(i)) == "on")
mConfig->sys.protectionMask |= (1 << i); mConfig->sys.protectionMask |= (1 << i);
} }
@ -491,30 +483,39 @@ class Web {
request->arg("ipGateway").toCharArray(buf, 20); request->arg("ipGateway").toCharArray(buf, 20);
ah::ip2Arr(mConfig->sys.ip.gateway, buf); ah::ip2Arr(mConfig->sys.ip.gateway, buf);
// inverter // inverter
Inverter<> *iv; Inverter<> *iv;
for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i ++) { for (uint8_t i = 0; i < MAX_NUM_INVERTERS; i++) {
iv = mSys->getInverterByPos(i, false); iv = mSys->getInverterByPos(i, false);
// enable communication // enable communication
iv->config->enabled = (request->arg("inv" + String(i) + "Enable") == "on"); iv->config->enabled = (request->arg("inv" + String(i) + "Enable") == "on");
// address // address
request->arg("inv" + String(i) + "Addr").toCharArray(buf, 20); request->arg("inv" + String(i) + "Addr").toCharArray(buf, 20);
if(strlen(buf) == 0) if (strlen(buf) == 0)
memset(buf, 0, 20); memset(buf, 0, 20);
iv->config->serial.u64 = ah::Serial2u64(buf); iv->config->serial.u64 = ah::Serial2u64(buf);
switch(iv->config->serial.b[4]) { switch (iv->config->serial.b[4]) {
case 0x21: iv->type = INV_TYPE_1CH; iv->channels = 1; break; case 0x21:
case 0x41: iv->type = INV_TYPE_2CH; iv->channels = 2; break; iv->type = INV_TYPE_1CH;
case 0x61: iv->type = INV_TYPE_4CH; iv->channels = 4; break; iv->channels = 1;
default: break; 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 // name
request->arg("inv" + String(i) + "Name").toCharArray(iv->config->name, MAX_NAME_LENGTH); request->arg("inv" + String(i) + "Name").toCharArray(iv->config->name, MAX_NAME_LENGTH);
// max channel power / name // max channel power / name
for(uint8_t j = 0; j < 4; j++) { for (uint8_t j = 0; j < 4; j++) {
iv->config->yieldCor[j] = request->arg("inv" + String(i) + "YieldCor" + String(j)).toInt(); 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; 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); request->arg("inv" + String(i) + "ModName" + String(j)).toCharArray(iv->config->chName[j], MAX_NAME_LENGTH);
@ -522,9 +523,9 @@ class Web {
iv->initialized = true; iv->initialized = true;
} }
if(request->arg("invInterval") != "") if (request->arg("invInterval") != "")
mConfig->nrf.sendInterval = request->arg("invInterval").toInt(); mConfig->nrf.sendInterval = request->arg("invInterval").toInt();
if(request->arg("invRetry") != "") if (request->arg("invRetry") != "")
mConfig->nrf.maxRetransPerPyld = request->arg("invRetry").toInt(); mConfig->nrf.maxRetransPerPyld = request->arg("invRetry").toInt();
mConfig->inst.rstYieldMidNight = (request->arg("invRstMid") == "on"); mConfig->inst.rstYieldMidNight = (request->arg("invRstMid") == "on");
mConfig->inst.rstValsCommStop = (request->arg("invRstComStop") == "on"); mConfig->inst.rstValsCommStop = (request->arg("invRstComStop") == "on");
@ -532,14 +533,24 @@ class Web {
// pinout // pinout
uint8_t pin; uint8_t pin;
for(uint8_t i = 0; i < 5; i ++) { for (uint8_t i = 0; i < 5; i++) {
pin = request->arg(String(pinArgNames[i])).toInt(); pin = request->arg(String(pinArgNames[i])).toInt();
switch(i) { switch (i) {
default: mConfig->nrf.pinCs = ((pin != 0xff) ? pin : DEF_CS_PIN); break; default:
case 1: mConfig->nrf.pinCe = ((pin != 0xff) ? pin : DEF_CE_PIN); break; mConfig->nrf.pinCs = ((pin != 0xff) ? pin : DEF_CS_PIN);
case 2: mConfig->nrf.pinIrq = ((pin != 0xff) ? pin : DEF_IRQ_PIN); break; break;
case 3: mConfig->led.led0 = pin; break; case 1:
case 4: mConfig->led.led1 = pin; break; 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;
} }
} }
@ -547,13 +558,13 @@ class Web {
mConfig->nrf.amplifierPower = request->arg("rf24Power").toInt() & 0x03; mConfig->nrf.amplifierPower = request->arg("rf24Power").toInt() & 0x03;
// ntp // ntp
if(request->arg("ntpAddr") != "") { if (request->arg("ntpAddr") != "") {
request->arg("ntpAddr").toCharArray(mConfig->ntp.addr, NTP_ADDR_LEN); request->arg("ntpAddr").toCharArray(mConfig->ntp.addr, NTP_ADDR_LEN);
mConfig->ntp.port = request->arg("ntpPort").toInt() & 0xffff; mConfig->ntp.port = request->arg("ntpPort").toInt() & 0xffff;
} }
// sun // sun
if(request->arg("sunLat") == "" || (request->arg("sunLon") == "")) { if (request->arg("sunLat") == "" || (request->arg("sunLon") == "")) {
mConfig->sun.lat = 0.0; mConfig->sun.lat = 0.0;
mConfig->sun.lon = 0.0; mConfig->sun.lon = 0.0;
mConfig->sun.disNightCom = false; mConfig->sun.disNightCom = false;
@ -566,22 +577,21 @@ class Web {
} }
// mqtt // mqtt
if(request->arg("mqttAddr") != "") { if (request->arg("mqttAddr") != "") {
String addr = request->arg("mqttAddr"); String addr = request->arg("mqttAddr");
addr.trim(); addr.trim();
addr.toCharArray(mConfig->mqtt.broker, MQTT_ADDR_LEN); addr.toCharArray(mConfig->mqtt.broker, MQTT_ADDR_LEN);
} } else
else
mConfig->mqtt.broker[0] = '\0'; mConfig->mqtt.broker[0] = '\0';
request->arg("mqttUser").toCharArray(mConfig->mqtt.user, MQTT_USER_LEN); request->arg("mqttUser").toCharArray(mConfig->mqtt.user, MQTT_USER_LEN);
if(request->arg("mqttPwd") != "{PWD}") if (request->arg("mqttPwd") != "{PWD}")
request->arg("mqttPwd").toCharArray(mConfig->mqtt.pwd, MQTT_PWD_LEN); request->arg("mqttPwd").toCharArray(mConfig->mqtt.pwd, MQTT_PWD_LEN);
request->arg("mqttTopic").toCharArray(mConfig->mqtt.topic, MQTT_TOPIC_LEN); request->arg("mqttTopic").toCharArray(mConfig->mqtt.topic, MQTT_TOPIC_LEN);
mConfig->mqtt.port = request->arg("mqttPort").toInt(); mConfig->mqtt.port = request->arg("mqttPort").toInt();
mConfig->mqtt.interval = request->arg("mqttInterval").toInt(); mConfig->mqtt.interval = request->arg("mqttInterval").toInt();
// serial console // serial console
if(request->arg("serIntvl") != "") { if (request->arg("serIntvl") != "") {
mConfig->serial.interval = request->arg("serIntvl").toInt() & 0xffff; mConfig->serial.interval = request->arg("serIntvl").toInt() & 0xffff;
mConfig->serial.debug = (request->arg("serDbg") == "on"); mConfig->serial.debug = (request->arg("serDbg") == "on");
@ -592,18 +602,21 @@ class Web {
// display // display
mConfig->plugin.display.pwrSaveAtIvOffline = (request->arg("dispPwr") == "on"); mConfig->plugin.display.pwrSaveAtIvOffline = (request->arg("dispPwr") == "on");
mConfig->plugin.display.logoEn = (request->arg("logoEn") == "on");
mConfig->plugin.display.pxShift = (request->arg("dispPxSh") == "on"); mConfig->plugin.display.pxShift = (request->arg("dispPxSh") == "on");
mConfig->plugin.display.rot180 = (request->arg("disp180") == "on"); mConfig->plugin.display.rot = request->arg("rotation").toInt();
mConfig->plugin.display.type = request->arg("dispType").toInt(); mConfig->plugin.display.type = request->arg("dispType").toInt();
mConfig->plugin.display.contrast = request->arg("dispCont").toInt(); mConfig->plugin.display.contrast = request->arg("dispCont").toInt();
mConfig->plugin.display.pin0 = request->arg("pinDisp0").toInt(); mConfig->plugin.display.period = request->arg("period").toInt();
mConfig->plugin.display.pin1 = request->arg("pinDisp1").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(); mApp->saveSettings();
if(request->arg("reboot") == "on") if (request->arg("reboot") == "on")
onReboot(request); onReboot(request);
else { else {
AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html; charset=UTF-8"), system_html, system_html_len); AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html; charset=UTF-8"), system_html, system_html_len);
@ -615,8 +628,8 @@ class Web {
void onLive(AsyncWebServerRequest *request) { void onLive(AsyncWebServerRequest *request) {
DPRINTLN(DBG_VERBOSE, F("onLive")); DPRINTLN(DBG_VERBOSE, F("onLive"));
if(CHECK_MASK(mConfig->sys.protectionMask, PROT_MASK_LIVE)) { if (CHECK_MASK(mConfig->sys.protectionMask, PROT_MASK_LIVE)) {
if(mProtected) { if (mProtected) {
checkRedirect(request); checkRedirect(request);
return; return;
} }
@ -703,8 +716,8 @@ class Web {
void onSerial(AsyncWebServerRequest *request) { void onSerial(AsyncWebServerRequest *request) {
DPRINTLN(DBG_VERBOSE, F("onSerial")); DPRINTLN(DBG_VERBOSE, F("onSerial"));
if(CHECK_MASK(mConfig->sys.protectionMask, PROT_MASK_SERIAL)) { if (CHECK_MASK(mConfig->sys.protectionMask, PROT_MASK_SERIAL)) {
if(mProtected) { if (mProtected) {
checkRedirect(request); checkRedirect(request);
return; return;
} }
@ -718,8 +731,8 @@ class Web {
void onSystem(AsyncWebServerRequest *request) { void onSystem(AsyncWebServerRequest *request) {
DPRINTLN(DBG_VERBOSE, F("onSystem")); DPRINTLN(DBG_VERBOSE, F("onSystem"));
if(CHECK_MASK(mConfig->sys.protectionMask, PROT_MASK_SYSTEM)) { if (CHECK_MASK(mConfig->sys.protectionMask, PROT_MASK_SYSTEM)) {
if(mProtected) { if (mProtected) {
checkRedirect(request); checkRedirect(request);
return; return;
} }
@ -739,15 +752,15 @@ class Web {
char topic[40], val[25]; char topic[40], val[25];
modJson = F("{\n"); modJson = F("{\n");
for(uint8_t id = 0; id < mSys->getNumInverters(); id++) { for (uint8_t id = 0; id < mSys->getNumInverters(); id++) {
iv = mSys->getInverterByPos(id); iv = mSys->getInverterByPos(id);
if(NULL == iv) if (NULL == iv)
continue; continue;
rec = iv->getRecordStruct(RealTimeRunData_Debug); rec = iv->getRecordStruct(RealTimeRunData_Debug);
snprintf(topic, 30, "\"%s\": {\n", iv->config->name); snprintf(topic, 30, "\"%s\": {\n", iv->config->name);
modJson += String(topic); modJson += String(topic);
for(uint8_t i = 0; i < rec->length; i++) { 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(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)); snprintf(val, 25, "[%.3f, \"%s\"]", iv->getValue(i, rec), iv->getUnit(i, rec));
modJson += String(topic) + ": " + String(val) + F(",\n"); modJson += String(topic) + ": " + String(val) + F(",\n");
@ -763,17 +776,20 @@ class Web {
#ifdef ENABLE_PROMETHEUS_EP #ifdef ENABLE_PROMETHEUS_EP
enum { enum {
metricsStateStart, metricsStateInverter, metricStateRealtimeData,metricsStateAlarmData,metricsStateEnd metricsStateStart,
metricsStateInverter,
metricStateRealtimeData,
metricsStateAlarmData,
metricsStateEnd
} metricsStep; } metricsStep;
int metricsInverterId,metricsChannelId; int metricsInverterId, metricsChannelId;
void showMetrics(AsyncWebServerRequest *request) { void showMetrics(AsyncWebServerRequest *request) {
DPRINTLN(DBG_VERBOSE, F("web::showMetrics")); DPRINTLN(DBG_VERBOSE, F("web::showMetrics"));
metricsStep = metricsStateStart; metricsStep = metricsStateStart;
AsyncWebServerResponse *response = request->beginChunkedResponse(F("text/plain"), AsyncWebServerResponse *response = request->beginChunkedResponse(F("text/plain"),
[this](uint8_t *buffer, size_t maxLen, size_t filledLength) -> size_t [this](uint8_t *buffer, size_t maxLen, size_t filledLength) -> size_t {
{
Inverter<> *iv; Inverter<> *iv;
record_t<> *rec; record_t<> *rec;
statistics_t *stat; statistics_t *stat;
@ -785,12 +801,12 @@ class Web {
switch (metricsStep) { switch (metricsStep) {
case metricsStateStart: // System Info & NRF Statistics : fit to one packet case metricsStateStart: // System Info & NRF Statistics : fit to one packet
snprintf(type,sizeof(type),"# TYPE ahoy_solar_info gauge\n"); snprintf(type, sizeof(type), "# TYPE ahoy_solar_info gauge\n");
snprintf(topic,sizeof(topic),"ahoy_solar_info{version=\"%s\",image=\"\",devicename=\"%s\"} 1\n", snprintf(topic, sizeof(topic), "ahoy_solar_info{version=\"%s\",image=\"\",devicename=\"%s\"} 1\n",
mApp->getVersion(), mConfig->sys.deviceName); mApp->getVersion(), mConfig->sys.deviceName);
metrics = String(type) + String(topic); 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()); snprintf(topic, sizeof(topic), "# TYPE ahoy_solar_freeheap gauge\nahoy_solar_freeheap{devicename=\"%s\"} %u\n", mConfig->sys.deviceName, ESP.getFreeHeap());
metrics += String(topic); metrics += String(topic);
// NRF Statistics // NRF Statistics
@ -801,7 +817,7 @@ class Web {
metrics += radioStatistic(F("frame_cnt"), stat->frmCnt); metrics += radioStatistic(F("frame_cnt"), stat->frmCnt);
metrics += radioStatistic(F("tx_cnt"), mSys->Radio.mSendCnt); metrics += radioStatistic(F("tx_cnt"), mSys->Radio.mSendCnt);
len = snprintf((char *)buffer,maxLen,"%s",metrics.c_str()); len = snprintf((char *)buffer, maxLen, "%s", metrics.c_str());
// Start Inverter loop // Start Inverter loop
metricsInverterId = 0; metricsInverterId = 0;
metricsStep = metricsStateInverter; metricsStep = metricsStateInverter;
@ -810,26 +826,26 @@ class Web {
case metricsStateInverter: // Inverter loop case metricsStateInverter: // Inverter loop
if (metricsInverterId < mSys->getNumInverters()) { if (metricsInverterId < mSys->getNumInverters()) {
iv = mSys->getInverterByPos(metricsInverterId); iv = mSys->getInverterByPos(metricsInverterId);
if(NULL != iv) { if (NULL != iv) {
// Inverter info : fit to one packet // Inverter info : fit to one packet
snprintf(type,sizeof(type),"# TYPE ahoy_solar_inverter_info gauge\n"); 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", snprintf(topic, sizeof(topic), "ahoy_solar_inverter_info{name=\"%s\",serial=\"%12llx\"} 1\n",
iv->config->name, iv->config->serial.u64); iv->config->name, iv->config->serial.u64);
metrics = String(type) + String(topic); metrics = String(type) + String(topic);
snprintf(type,sizeof(type),"# TYPE ahoy_solar_inverter_is_enabled gauge\n"); 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); snprintf(topic, sizeof(topic), "ahoy_solar_inverter_is_enabled {inverter=\"%s\"} %d\n", iv->config->name, iv->config->enabled);
metrics += String(type) + String(topic); metrics += String(type) + String(topic);
snprintf(type,sizeof(type),"# TYPE ahoy_solar_inverter_is_available gauge\n"); 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())); 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); metrics += String(type) + String(topic);
snprintf(type,sizeof(type),"# TYPE ahoy_solar_inverter_is_producing gauge\n"); 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())); 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); metrics += String(type) + String(topic);
len = snprintf((char *)buffer,maxLen,"%s",metrics.c_str()); len = snprintf((char *)buffer, maxLen, "%s", metrics.c_str());
// Start Realtime Data Channel loop for this inverter // Start Realtime Data Channel loop for this inverter
metricsChannelId = 0; metricsChannelId = 0;
@ -850,14 +866,14 @@ class Web {
if (0 == channel) { if (0 == channel) {
snprintf(topic, sizeof(topic), "ahoy_solar_%s%s{inverter=\"%s\"}", iv->getFieldName(metricsChannelId, rec), promUnit.c_str(), iv->config->name); snprintf(topic, sizeof(topic), "ahoy_solar_%s%s{inverter=\"%s\"}", iv->getFieldName(metricsChannelId, rec), promUnit.c_str(), iv->config->name);
} else { } 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(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)); snprintf(val, sizeof(val), "%.3f", iv->getValue(metricsChannelId, rec));
len = snprintf((char*)buffer,maxLen,"%s\n%s %s\n",type,topic,val); len = snprintf((char *)buffer, maxLen, "%s\n%s %s\n", type, topic, val);
metricsChannelId++; metricsChannelId++;
} else { } else {
len = snprintf((char*)buffer,maxLen,"#\n"); // At least one char to send otherwise the transmission ends. len = snprintf((char *)buffer, maxLen, "#\n"); // At least one char to send otherwise the transmission ends.
// All realtime data channels processed --> try alarm data // All realtime data channels processed --> try alarm data
metricsStep = metricsStateAlarmData; metricsStep = metricsStateAlarmData;
@ -871,16 +887,15 @@ class Web {
// TODO: find the right one channel with the alarm id // TODO: find the right one channel with the alarm id
alarmChannelId = 0; alarmChannelId = 0;
// printf("AlarmData Length %d\n",rec->length); // printf("AlarmData Length %d\n",rec->length);
if (alarmChannelId < rec->length) if (alarmChannelId < rec->length) {
{ // uint8_t channel = rec->assign[alarmChannelId].ch;
//uint8_t channel = rec->assign[alarmChannelId].ch;
std::tie(promUnit, promType) = convertToPromUnits(iv->getUnit(alarmChannelId, rec)); 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(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(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)); snprintf(val, sizeof(val), "%.3f", iv->getValue(alarmChannelId, rec));
len = snprintf((char*)buffer,maxLen,"%s\n%s %s\n",type,topic,val); len = snprintf((char *)buffer, maxLen, "%s\n%s %s\n", type, topic, val);
} else { } else {
len = snprintf((char*)buffer,maxLen,"#\n"); // At least one char to send otherwise the transmission ends. len = snprintf((char *)buffer, maxLen, "#\n"); // At least one char to send otherwise the transmission ends.
} }
// alarm channel processed --> try next inverter // alarm channel processed --> try next inverter
metricsInverterId++; metricsInverterId++;
@ -899,22 +914,22 @@ class Web {
String radioStatistic(String statistic, uint32_t value) { String radioStatistic(String statistic, uint32_t value) {
char type[60], topic[80], val[25]; char type[60], topic[80], val[25];
snprintf(type, sizeof(type), "# TYPE ahoy_solar_radio_%s gauge",statistic.c_str()); 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(topic, sizeof(topic), "ahoy_solar_radio_%s", statistic.c_str());
snprintf(val, sizeof(val), "%d", value); snprintf(val, sizeof(val), "%d", value);
return ( String(type) + "\n" + String(topic) + " " + String(val) + "\n"); return (String(type) + "\n" + String(topic) + " " + String(val) + "\n");
} }
std::pair<String, String> convertToPromUnits(String shortUnit) { std::pair<String, String> convertToPromUnits(String shortUnit) {
if(shortUnit == "A") return {"_ampere", "gauge"}; if (shortUnit == "A") return {"_ampere", "gauge"};
if(shortUnit == "V") return {"_volt", "gauge"}; if (shortUnit == "V") return {"_volt", "gauge"};
if(shortUnit == "%") return {"_ratio", "gauge"}; if (shortUnit == "%") return {"_ratio", "gauge"};
if(shortUnit == "W") return {"_watt", "gauge"}; if (shortUnit == "W") return {"_watt", "gauge"};
if(shortUnit == "Wh") return {"_wattHours", "counter"}; if (shortUnit == "Wh") return {"_wattHours", "counter"};
if(shortUnit == "kWh") return {"_kilowattHours", "counter"}; if (shortUnit == "kWh") return {"_kilowattHours", "counter"};
if(shortUnit == "°C") return {"_celsius", "gauge"}; if (shortUnit == "°C") return {"_celsius", "gauge"};
if(shortUnit == "var") return {"_var", "gauge"}; if (shortUnit == "var") return {"_var", "gauge"};
if(shortUnit == "Hz") return {"_hertz", "gauge"}; if (shortUnit == "Hz") return {"_hertz", "gauge"};
return {"", "gauge"}; return {"", "gauge"};
} }
#endif #endif

Loading…
Cancel
Save