Development Build (ESP8266 / ESP32)
diff --git a/src/CHANGES.md b/src/CHANGES.md
index 043e8578..602af895 100644
--- a/src/CHANGES.md
+++ b/src/CHANGES.md
@@ -2,6 +2,15 @@
(starting from release version `0.5.66`)
+## 0.5.95
+* merged #742 MI Improvments
+* merged #736 remove obsolete JSON Endpoint
+
+## 0.5.94
+* added ePaper (for ESP32 only!), thx @dAjaY85 #735
+* improved `/live` margins #732
+* renamed `var` to `VAr` #732
+
## 0.5.93
* improved web API for `live`
* added dark mode option
diff --git a/src/LICENSE b/src/LICENSE
new file mode 100644
index 00000000..057d1565
--- /dev/null
+++ b/src/LICENSE
@@ -0,0 +1,7 @@
+License
+
+CC-CY-NC-SA 3.0
+
+https://creativecommons.org/licenses/by-nc-sa/3.0/de
+
+This project is for non-commercial use only!
diff --git a/src/app.cpp b/src/app.cpp
index d83e169b..d65dbe1d 100644
--- a/src/app.cpp
+++ b/src/app.cpp
@@ -24,7 +24,7 @@ void app::setup() {
mSettings.setup();
mSettings.getPtr(mConfig);
DPRINT(DBG_INFO, F("Settings valid: "));
- if(mSettings.getValid())
+ if (mSettings.getValid())
DBGPRINTLN(F("true"));
else
DBGPRINTLN(F("false"));
@@ -52,12 +52,12 @@ void app::setup() {
mMiPayload.setup(this, &mSys, &mStat, mConfig->nrf.maxRetransPerPyld, &mTimestamp);
mMiPayload.enableSerialDebug(mConfig->serial.debug);
- //DBGPRINTLN("--- after payload");
- //DBGPRINTLN(String(ESP.getFreeHeap()));
- //DBGPRINTLN(String(ESP.getHeapFragmentation()));
- //DBGPRINTLN(String(ESP.getMaxFreeBlockSize()));
+ // DBGPRINTLN("--- after payload");
+ // DBGPRINTLN(String(ESP.getFreeHeap()));
+ // DBGPRINTLN(String(ESP.getHeapFragmentation()));
+ // 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"));
// when WiFi is in client mode, then enable mqtt broker
@@ -77,18 +77,18 @@ void app::setup() {
mApi.setup(this, &mSys, mWeb.getWebSrvPtr(), mConfig);
// Plugins
- if(mConfig->plugin.display.type != 0)
- mMonoDisplay.setup(&mConfig->plugin.display, &mSys, &mTimestamp, 0xff, mVersion);
+ if (mConfig->plugin.display.type != 0)
+ mDisplay.setup(&mConfig->plugin.display, &mSys, &mTimestamp, 0xff, mVersion);
mPubSerial.setup(mConfig, &mSys, &mTimestamp);
regularTickers();
- //DBGPRINTLN("--- end setup");
- //DBGPRINTLN(String(ESP.getFreeHeap()));
- //DBGPRINTLN(String(ESP.getHeapFragmentation()));
- //DBGPRINTLN(String(ESP.getMaxFreeBlockSize()));
+ // DBGPRINTLN("--- end setup");
+ // DBGPRINTLN(String(ESP.getFreeHeap()));
+ // DBGPRINTLN(String(ESP.getHeapFragmentation()));
+ // DBGPRINTLN(String(ESP.getMaxFreeBlockSize()));
}
//-----------------------------------------------------------------------------
@@ -115,8 +115,8 @@ void app::loopStandard(void) {
mStat.frmCnt++;
Inverter<> *iv = mSys.findInverter(&p->packet[1]);
- if(NULL != iv) {
- if(IV_HM == iv->ivGen)
+ if (NULL != iv) {
+ if (IV_HM == iv->ivGen)
mPayload.add(iv, p);
else
mMiPayload.add(iv, p);
@@ -130,7 +130,7 @@ void app::loopStandard(void) {
mPayload.loop();
mMiPayload.loop();
- if(mMqttEnabled)
+ if (mMqttEnabled)
mMqtt.loop();
}
@@ -144,19 +144,18 @@ void app::loopWifi(void) {
void app::onWifi(bool gotIp) {
DPRINTLN(DBG_DEBUG, F("onWifi"));
ah::Scheduler::resetTicker();
- regularTickers(); // reinstall regular tickers
+ regularTickers(); // reinstall regular tickers
if (gotIp) {
mInnerLoopCb = std::bind(&app::loopStandard, this);
every(std::bind(&app::tickSend, this), mConfig->nrf.sendInterval, "tSend");
mMqttReconnect = true;
- mSunrise = 0; // needs to be set to 0, to reinstall sunrise and ivComm tickers!
+ mSunrise = 0; // needs to be set to 0, to reinstall sunrise and ivComm tickers!
once(std::bind(&app::tickNtpUpdate, this), 2, "ntp2");
- if(WIFI_AP == WiFi.getMode()) {
+ if (WIFI_AP == WiFi.getMode()) {
mMqttEnabled = false;
everySec(std::bind(&ahoywifi::tickWifiLoop, &mWifi), "wifiL");
}
- }
- else {
+ } else {
mInnerLoopCb = std::bind(&app::loopWifi, this);
everySec(std::bind(&ahoywifi::tickWifiLoop, &mWifi), "wifiL");
}
@@ -167,8 +166,8 @@ void app::regularTickers(void) {
DPRINTLN(DBG_DEBUG, F("regularTickers"));
everySec(std::bind(&WebType::tickSecond, &mWeb), "webSc");
// Plugins
- if(mConfig->plugin.display.type != 0)
- everySec(std::bind(&MonoDisplayType::tickerSecond, &mMonoDisplay), "disp");
+ if (mConfig->plugin.display.type != 0)
+ everySec(std::bind(&DisplayType::tickerSecond, &mDisplay), "disp");
every(std::bind(&PubSerialType::tick, &mPubSerial), mConfig->serial.interval, "uart");
}
@@ -184,26 +183,26 @@ void app::tickNtpUpdate(void) {
}
// only install schedulers once even if NTP wasn't successful in first loop
- if(mMqttReconnect) { // @TODO: mMqttReconnect is variable which scope has changed
- if(mConfig->inst.rstValsNotAvail)
+ if (mMqttReconnect) { // @TODO: mMqttReconnect is variable which scope has changed
+ if (mConfig->inst.rstValsNotAvail)
everyMin(std::bind(&app::tickMinute, this), "tMin");
- if(mConfig->inst.rstYieldMidNight) {
+ if (mConfig->inst.rstYieldMidNight) {
uint32_t localTime = gTimezone.toLocal(mTimestamp);
- uint32_t midTrig = gTimezone.toUTC(localTime - (localTime % 86400) + 86400); // next midnight local time
+ uint32_t midTrig = gTimezone.toUTC(localTime - (localTime % 86400) + 86400); // next midnight local time
onceAt(std::bind(&app::tickMidnight, this), midTrig, "midNi");
}
}
- nxtTrig = isOK ? 43200 : 60; // depending on NTP update success check again in 12 h or in 1 min
+ nxtTrig = isOK ? 43200 : 60; // depending on NTP update success check again in 12 h or in 1 min
- if((mSunrise == 0) && (mConfig->sun.lat) && (mConfig->sun.lon)) {
+ if ((mSunrise == 0) && (mConfig->sun.lat) && (mConfig->sun.lon)) {
mCalculatedTimezoneOffset = (int8_t)((mConfig->sun.lon >= 0 ? mConfig->sun.lon + 7.5 : mConfig->sun.lon - 7.5) / 15) * 3600;
tickCalcSunrise();
}
// immediately start communicating
// @TODO: leads to reboot loops? not sure #674
- if(isOK && mSendFirst) {
+ if (isOK && mSendFirst) {
mSendFirst = false;
once(std::bind(&app::tickSend, this), 2, "senOn");
}
@@ -254,17 +253,17 @@ void app::tickIVCommunication(void) {
void app::tickSun(void) {
// only used and enabled by MQTT (see setup())
if (!mMqtt.tickerSun(mSunrise, mSunset, mConfig->sun.offsetSec, mConfig->sun.disNightCom))
- once(std::bind(&app::tickSun, this), 1, "mqSun"); // MQTT not connected, retry
+ once(std::bind(&app::tickSun, this), 1, "mqSun"); // MQTT not connected, retry
}
//-----------------------------------------------------------------------------
void app::tickComm(void) {
- if((!mIVCommunicationOn) && (mConfig->inst.rstValsCommStop))
+ if ((!mIVCommunicationOn) && (mConfig->inst.rstValsCommStop))
once(std::bind(&app::tickZeroValues, this), mConfig->nrf.sendInterval, "tZero");
if (mMqttEnabled) {
if (!mMqtt.tickerComm(!mIVCommunicationOn))
- once(std::bind(&app::tickComm, this), 5, "mqCom"); // MQTT not connected, retry after 5s
+ once(std::bind(&app::tickComm, this), 5, "mqCom"); // MQTT not connected, retry after 5s
}
}
@@ -275,7 +274,7 @@ void app::tickZeroValues(void) {
for (uint8_t id = 0; id < mSys.getNumInverters(); id++) {
iv = mSys.getInverterByPos(id);
if (NULL == iv)
- continue; // skip to next inverter
+ continue; // skip to next inverter
mPayload.zeroInverterValues(iv);
}
@@ -290,9 +289,9 @@ void app::tickMinute(void) {
for (uint8_t id = 0; id < mSys.getNumInverters(); id++) {
iv = mSys.getInverterByPos(id);
if (NULL == iv)
- continue; // skip to next inverter
+ continue; // skip to next inverter
- if(!iv->isAvailable(mTimestamp) && !iv->isProducing(mTimestamp) && iv->config->enabled)
+ if (!iv->isAvailable(mTimestamp) && !iv->isProducing(mTimestamp) && iv->config->enabled)
mPayload.zeroInverterValues(iv);
}
}
@@ -301,7 +300,7 @@ void app::tickMinute(void) {
void app::tickMidnight(void) {
// only triggered if 'reset values at midnight is enabled'
uint32_t localTime = gTimezone.toLocal(mTimestamp);
- uint32_t nxtTrig = gTimezone.toUTC(localTime - (localTime % 86400) + 86400); // next midnight local time
+ uint32_t nxtTrig = gTimezone.toUTC(localTime - (localTime % 86400) + 86400); // next midnight local time
onceAt(std::bind(&app::tickMidnight, this), nxtTrig, "mid2");
Inverter<> *iv;
@@ -309,7 +308,7 @@ void app::tickMidnight(void) {
for (uint8_t id = 0; id < mSys.getNumInverters(); id++) {
iv = mSys.getInverterByPos(id);
if (NULL == iv)
- continue; // skip to next inverter
+ continue; // skip to next inverter
mPayload.zeroInverterValues(iv);
mPayload.zeroYieldDay(iv);
@@ -321,7 +320,7 @@ void app::tickMidnight(void) {
//-----------------------------------------------------------------------------
void app::tickSend(void) {
- if(!mSys.Radio.isChipConnected()) {
+ if (!mSys.Radio.isChipConnected()) {
DPRINTLN(DBG_WARN, F("NRF24 not connected!"));
return;
}
@@ -341,8 +340,8 @@ void app::tickSend(void) {
} while ((NULL == iv) && ((maxLoop--) > 0));
if (NULL != iv) {
- if(iv->config->enabled) {
- if(iv->ivGen == IV_HM)
+ if (iv->config->enabled) {
+ if (iv->ivGen == IV_HM)
mPayload.ivSend(iv);
else
mMiPayload.ivSend(iv);
@@ -391,25 +390,25 @@ void app::setupLed(void) {
* PIN ---- |<----- 3.3V
*
* */
- if(mConfig->led.led0 != 0xff) {
+ if (mConfig->led.led0 != 0xff) {
pinMode(mConfig->led.led0, OUTPUT);
- digitalWrite(mConfig->led.led0, HIGH); // LED off
+ digitalWrite(mConfig->led.led0, HIGH); // LED off
}
- if(mConfig->led.led1 != 0xff) {
+ if (mConfig->led.led1 != 0xff) {
pinMode(mConfig->led.led1, OUTPUT);
- digitalWrite(mConfig->led.led1, HIGH); // LED off
+ digitalWrite(mConfig->led.led1, HIGH); // LED off
}
}
//-----------------------------------------------------------------------------
void app::updateLed(void) {
- if(mConfig->led.led0 != 0xff) {
+ if (mConfig->led.led0 != 0xff) {
Inverter<> *iv = mSys.getInverterByPos(0);
if (NULL != iv) {
- if(iv->isProducing(mTimestamp))
- digitalWrite(mConfig->led.led0, LOW); // LED on
+ if (iv->isProducing(mTimestamp))
+ digitalWrite(mConfig->led.led0, LOW); // LED on
else
- digitalWrite(mConfig->led.led0, HIGH); // LED off
+ digitalWrite(mConfig->led.led0, HIGH); // LED off
}
}
}
diff --git a/src/app.h b/src/app.h
index 68281492..b68dc0c4 100644
--- a/src/app.h
+++ b/src/app.h
@@ -6,30 +6,25 @@
#ifndef __APP_H__
#define __APP_H__
-
-#include "utils/dbg.h"
#include
+#include
#include
#include
-#include
#include "appInterface.h"
-
#include "config/settings.h"
#include "defines.h"
-#include "utils/crc.h"
-#include "utils/scheduler.h"
-
-#include "hm/hmSystem.h"
#include "hm/hmPayload.h"
+#include "hm/hmSystem.h"
#include "hm/miPayload.h"
-#include "wifi/ahoywifi.h"
-#include "web/web.h"
-#include "web/RestApi.h"
-
#include "publisher/pubMqtt.h"
#include "publisher/pubSerial.h"
-
+#include "utils/crc.h"
+#include "utils/dbg.h"
+#include "utils/scheduler.h"
+#include "web/RestApi.h"
+#include "web/web.h"
+#include "wifi/ahoywifi.h"
// convert degrees and radians for sun calculation
#define SIN(x) (sin(radians(x)))
@@ -46,12 +41,11 @@ typedef PubMqtt PubMqttType;
typedef PubSerial PubSerialType;
// PLUGINS
-#include "plugins/MonochromeDisplay/MonochromeDisplay.h"
-typedef MonochromeDisplay MonoDisplayType;
-
+#include "plugins/Display/Display.h"
+typedef Display DisplayType;
class app : public IApp, public ah::Scheduler {
- public:
+ public:
app();
~app() {}
@@ -203,7 +197,7 @@ class app : public IApp, public ah::Scheduler {
mMqtt.payloadEventListener(cmd);
#endif
if(mConfig->plugin.display.type != 0)
- mMonoDisplay.payloadEventListener(cmd);
+ mDisplay.payloadEventListener(cmd);
}
void mqttSubRxCb(JsonObject obj);
@@ -274,7 +268,7 @@ class app : public IApp, public ah::Scheduler {
uint32_t mSunrise, mSunset;
// plugins
- MonoDisplayType mMonoDisplay;
+ DisplayType mDisplay;
};
#endif /*__APP_H__*/
diff --git a/src/config/settings.h b/src/config/settings.h
index f674dac4..0e118bde 100644
--- a/src/config/settings.h
+++ b/src/config/settings.h
@@ -7,11 +7,12 @@
#define __SETTINGS_H__
#include
-#include
#include
+#include
+
+#include "../defines.h"
#include "../utils/dbg.h"
#include "../utils/helper.h"
-#include "../defines.h"
/**
* More info:
@@ -77,7 +78,7 @@ typedef struct {
typedef struct {
float lat;
float lon;
- bool disNightCom; // disable night communication
+ bool disNightCom; // disable night communication
uint16_t offsetSec;
} cfgSun_t;
@@ -88,8 +89,8 @@ typedef struct {
} cfgSerial_t;
typedef struct {
- uint8_t led0; // first LED pin
- uint8_t led1; // second LED pin
+ uint8_t led0; // first LED pin
+ uint8_t led1; // second LED pin
} cfgLed_t;
typedef struct {
@@ -106,7 +107,7 @@ typedef struct {
char name[MAX_NAME_LENGTH];
serial_u serial;
uint16_t chMaxPwr[4];
- int32_t yieldCor[4]; // signed YieldTotal correction value
+ int32_t yieldCor[4]; // signed YieldTotal correction value
char chName[4][MAX_NAME_LENGTH];
} cfgIv_t;
@@ -122,14 +123,17 @@ typedef struct {
typedef struct {
uint8_t type;
bool pwrSaveAtIvOffline;
- bool logoEn;
bool pxShift;
- bool rot180;
- uint16_t wakeUp;
- uint16_t sleepAt;
+ uint8_t rot;
+ //uint16_t wakeUp;
+ //uint16_t sleepAt;
uint8_t contrast;
- uint8_t pin0;
- uint8_t pin1;
+ uint8_t disp_data;
+ uint8_t disp_clk;
+ uint8_t disp_cs;
+ uint8_t disp_reset;
+ uint8_t disp_busy;
+ uint8_t disp_dc;
} display_t;
typedef struct {
@@ -220,7 +224,7 @@ class settings {
else {
//DPRINTLN(DBG_INFO, fp.readString());
//fp.seek(0, SeekSet);
- DynamicJsonDocument root(4500);
+ DynamicJsonDocument root(5500);
DeserializationError err = deserializeJson(root, fp);
if(!err && (root.size() > 0)) {
mCfg.valid = true;
@@ -251,7 +255,7 @@ class settings {
return false;
}
- DynamicJsonDocument json(5500);
+ DynamicJsonDocument json(6500);
JsonObject root = json.to();
jsonWifi(root.createNestedObject(F("wifi")), true);
jsonNrf(root.createNestedObject(F("nrf")), true);
@@ -340,13 +344,16 @@ class settings {
memset(&mCfg.inst, 0, sizeof(cfgInst_t));
mCfg.plugin.display.pwrSaveAtIvOffline = false;
- mCfg.plugin.display.contrast = 60;
- mCfg.plugin.display.logoEn = true;
- mCfg.plugin.display.pxShift = true;
- mCfg.plugin.display.rot180 = false;
- mCfg.plugin.display.pin0 = DEF_PIN_OFF; // SCL
- mCfg.plugin.display.pin1 = DEF_PIN_OFF; // SDA
- }
+ mCfg.plugin.display.contrast = 60;
+ mCfg.plugin.display.pxShift = true;
+ mCfg.plugin.display.rot = 0;
+ mCfg.plugin.display.disp_data = DEF_PIN_OFF; // SDA
+ mCfg.plugin.display.disp_clk = DEF_PIN_OFF; // SCL
+ mCfg.plugin.display.disp_cs = DEF_PIN_OFF;
+ mCfg.plugin.display.disp_reset = DEF_PIN_OFF;
+ mCfg.plugin.display.disp_busy = DEF_PIN_OFF;
+ mCfg.plugin.display.disp_dc = DEF_PIN_OFF;
+ }
void jsonWifi(JsonObject obj, bool set = false) {
if(set) {
@@ -469,26 +476,32 @@ class settings {
JsonObject disp = obj.createNestedObject("disp");
disp[F("type")] = mCfg.plugin.display.type;
disp[F("pwrSafe")] = (bool)mCfg.plugin.display.pwrSaveAtIvOffline;
- disp[F("logo")] = (bool)mCfg.plugin.display.logoEn;
- disp[F("pxShift")] = (bool)mCfg.plugin.display.pxShift;
- disp[F("rot180")] = (bool)mCfg.plugin.display.rot180;
- disp[F("wake")] = mCfg.plugin.display.wakeUp;
- disp[F("sleep")] = mCfg.plugin.display.sleepAt;
+ disp[F("pxShift")] = (bool)mCfg.plugin.display.pxShift;
+ disp[F("rotation")] = mCfg.plugin.display.rot;
+ //disp[F("wake")] = mCfg.plugin.display.wakeUp;
+ //disp[F("sleep")] = mCfg.plugin.display.sleepAt;
disp[F("contrast")] = mCfg.plugin.display.contrast;
- disp[F("pin0")] = mCfg.plugin.display.pin0;
- disp[F("pin1")] = mCfg.plugin.display.pin1;
+ disp[F("data")] = mCfg.plugin.display.disp_data;
+ disp[F("clock")] = mCfg.plugin.display.disp_clk;
+ disp[F("cs")] = mCfg.plugin.display.disp_cs;
+ disp[F("reset")] = mCfg.plugin.display.disp_reset;
+ disp[F("busy")] = mCfg.plugin.display.disp_busy;
+ disp[F("dc")] = mCfg.plugin.display.disp_dc;
} else {
JsonObject disp = obj["disp"];
- mCfg.plugin.display.type = disp[F("type")];
- mCfg.plugin.display.pwrSaveAtIvOffline = (bool) disp[F("pwrSafe")];
- mCfg.plugin.display.logoEn = (bool) disp[F("logo")];
- mCfg.plugin.display.pxShift = (bool) disp[F("pxShift")];
- mCfg.plugin.display.rot180 = (bool) disp[F("rot180")];
- mCfg.plugin.display.wakeUp = disp[F("wake")];
- mCfg.plugin.display.sleepAt = disp[F("sleep")];
- mCfg.plugin.display.contrast = disp[F("contrast")];
- mCfg.plugin.display.pin0 = disp[F("pin0")];
- mCfg.plugin.display.pin1 = disp[F("pin1")];
+ mCfg.plugin.display.type = disp[F("type")];
+ mCfg.plugin.display.pwrSaveAtIvOffline = (bool)disp[F("pwrSafe")];
+ mCfg.plugin.display.pxShift = (bool)disp[F("pxShift")];
+ mCfg.plugin.display.rot = disp[F("rotation")];
+ //mCfg.plugin.display.wakeUp = disp[F("wake")];
+ //mCfg.plugin.display.sleepAt = disp[F("sleep")];
+ mCfg.plugin.display.contrast = disp[F("contrast")];
+ mCfg.plugin.display.disp_data = disp[F("data")];
+ mCfg.plugin.display.disp_clk = disp[F("clock")];
+ mCfg.plugin.display.disp_cs = disp[F("cs")];
+ mCfg.plugin.display.disp_reset = disp[F("reset")];
+ mCfg.plugin.display.disp_busy = disp[F("busy")];
+ mCfg.plugin.display.disp_dc = disp[F("dc")];
}
}
diff --git a/src/defines.h b/src/defines.h
index 56e02583..83157143 100644
--- a/src/defines.h
+++ b/src/defines.h
@@ -13,7 +13,7 @@
//-------------------------------------
#define VERSION_MAJOR 0
#define VERSION_MINOR 5
-#define VERSION_PATCH 93
+#define VERSION_PATCH 95
//-------------------------------------
typedef struct {
diff --git a/src/hm/hmInverter.h b/src/hm/hmInverter.h
index c764cee0..e8b83726 100644
--- a/src/hm/hmInverter.h
+++ b/src/hm/hmInverter.h
@@ -163,9 +163,26 @@ class Inverter {
uint8_t getQueuedCmd() {
if (_commandQueue.empty()) {
- if (getFwVersion() == 0)
- enqueCommand(InverterDevInform_All); // firmware version
- enqueCommand(RealTimeRunData_Debug); // live data
+ if (ivGen != IV_MI) {
+ if (getFwVersion() == 0)
+ enqueCommand(InverterDevInform_All); // firmware version
+ enqueCommand(RealTimeRunData_Debug); // live data
+ } else if (ivGen == IV_MI){
+ if (type == INV_TYPE_4CH) {
+ enqueCommand(0x36);
+ /*for(uint8_t i = 0x36; i <= 0x39; i++) {
+ enqueCommand(i); // live data
+ }*/
+ } else if (type == INV_TYPE_2CH) {
+ enqueCommand(0x09);
+ //enqueCommand(0x11);
+ } else if (type == INV_TYPE_1CH) {
+ enqueCommand(0x09);
+ }
+ //if (getFwVersion() == 0)
+ // enqueCommand(InverterDevInform_All); // firmware version, might not work, esp. for 1/2 ch hardware
+ }
+
if ((actPowerLimit == 0xffff) && isConnected)
enqueCommand(SystemConfigPara); // power limit info
}
diff --git a/src/hm/miPayload.h b/src/hm/miPayload.h
index aa295db7..24e6693a 100644
--- a/src/hm/miPayload.h
+++ b/src/hm/miPayload.h
@@ -18,11 +18,13 @@ typedef struct {
uint8_t txCmd;
uint8_t len[MAX_PAYLOAD_ENTRIES];
bool complete;
- bool stsa;
- bool stsb;
+ bool dataAB[3];
+ bool stsAB[3];
+ uint8_t sts[5];
uint8_t txId;
uint8_t invId;
uint8_t retransmits;
+ bool gotFragment;
/*
uint8_t data[MAX_PAYLOAD_ENTRIES][MAX_RF_PAYLOAD_SIZE];
@@ -65,6 +67,7 @@ class MiPayload {
void addAlarmListener(alarmListenerType cb) {
mCbMiAlarm = cb;
}
+
void loop() {
/*if(NULL != mHighPrioIv) {
iv->ivSend(mHighPrioIv, true); // should request firmware version etc.?
@@ -84,62 +87,108 @@ class MiPayload {
if (mSerialDebug)
DPRINTLN(DBG_INFO, F("(#") + String(iv->id) + F(") Requesting Inv SN ") + String(iv->config->serial.u64, HEX));
- uint8_t cmd = iv->type == INV_TYPE_4CH ? 0x36 : 0x09; //iv->getQueuedCmd();
- DPRINTLN(DBG_INFO, F("(#") + String(iv->id) + F(") prepareDevInformCmd"));
- mSys->Radio.prepareDevInformCmd(iv->radioId.u64, cmd, mPayload[iv->id].ts, iv->alarmMesIndex, false, cmd);
+ uint8_t cmd = iv->getQueuedCmd();
+ DPRINT(DBG_INFO, F("(#"));
+ DBGPRINT(String(iv->id));
+ DBGPRINT(F(") prepareDevInformCmd 0x"));
+ DBGPRINTLN(String(cmd, HEX));
+ uint8_t cmd2 = cmd;
+ if (cmd == 0x1 ) {
+ cmd = TX_REQ_INFO;
+ cmd2 = 0x00;
+ };
+
+ mSys->Radio.prepareDevInformCmd(iv->radioId.u64, cmd2, mPayload[iv->id].ts, iv->alarmMesIndex, false, cmd);
mPayload[iv->id].txCmd = cmd;
+ if (iv->type == INV_TYPE_1CH || iv->type == INV_TYPE_2CH) {
+ mPayload[iv->id].dataAB[CH1] = false;
+ mPayload[iv->id].stsAB[CH1] = false;
+ mPayload[iv->id].dataAB[CH0] = false;
+ mPayload[iv->id].stsAB[CH0] = false;
+ }
+
+ if (iv->type == INV_TYPE_2CH) {
+ mPayload[iv->id].dataAB[CH2] = false;
+ mPayload[iv->id].stsAB[CH2] = false;
+ }
}
void add(Inverter<> *iv, packet_t *p) {
//DPRINTLN(DBG_INFO, F("MI got data [0]=") + String(p->packet[0], HEX));
if (p->packet[0] == (0x08 + ALL_FRAMES)) { // 0x88; MI status response to 0x09
- mPayload[iv->id].stsa = true;
miStsDecode(iv, p);
} else if (p->packet[0] == (0x11 + SINGLE_FRAME)) { // 0x92; MI status response to 0x11
- mPayload[iv->id].stsb = true;
miStsDecode(iv, p, CH2);
- } else if (p->packet[0] == (0x09 + ALL_FRAMES)) { // MI data response to 0x09
+ /*} else if (p->packet[0] == (0x09 + ALL_FRAMES)) { // MI data response to 0x09
mPayload[iv->id].txId = p->packet[0];
miDataDecode(iv,p);
- iv->setQueuedCmdFinished();
+ //iv->setQueuedCmdFinished();
if (INV_TYPE_2CH == iv->type) {
//mSys->Radio.prepareDevInformCmd(iv->radioId.u64, iv->getQueuedCmd(), mPayload[iv->id].ts, iv->alarmMesIndex, false, 0x11);
- mSys->Radio.prepareDevInformCmd(iv->radioId.u64, 0x11, mPayload[iv->id].ts, iv->alarmMesIndex, false, 0x11);
+ //mSys->Radio.prepareDevInformCmd(iv->radioId.u64, 0x11, mPayload[iv->id].ts, iv->alarmMesIndex, false, 0x11);
} else { // additional check for mPayload[iv->id].stsa == true might be a good idea (request retransmit?)
mPayload[iv->id].complete = true;
//iv->setQueuedCmdFinished();
}
- } else if (p->packet[0] == (0x11 + ALL_FRAMES)) { // MI data response to 0x11
+ } else if (p->packet[0] == ()) { // MI data response to 0x11
mPayload[iv->id].txId = p->packet[0];
- mPayload[iv->id].complete = true;
miDataDecode(iv,p);
- iv->setQueuedCmdFinished();
+ mStat->rxSuccess++;
+ //iv->setQueuedCmdFinished();*/
- } else if (p->packet[0] >= (0x36 + ALL_FRAMES) && p->packet[0] < (0x39 + SINGLE_FRAME)) { // MI 1500 data response to 0x36, 0x37, 0x38 and 0x39
+ } else if ( p->packet[0] == 0x09 + ALL_FRAMES ||
+ p->packet[0] == 0x11 + ALL_FRAMES ||
+ ( p->packet[0] >= (0x36 + ALL_FRAMES) && p->packet[0] < (0x39 + SINGLE_FRAME) ) ) { // small MI or MI 1500 data responses to 0x09, 0x11, 0x36, 0x37, 0x38 and 0x39
mPayload[iv->id].txId = p->packet[0];
miDataDecode(iv,p);
- iv->setQueuedCmdFinished();
- if (p->packet[0] < (0x39 + ALL_FRAMES)) {
+ //mStat->rxSuccess++;
+ //iv->setQueuedCmdFinished();
+ /*if (p->packet[0] < (0x39 + ALL_FRAMES)) {
//mSys->Radio.prepareDevInformCmd(iv->radioId.u64, iv->getQueuedCmd(), mPayload[iv->id].ts, iv->alarmMesIndex, false, p->packet[0] + 1 - ALL_FRAMES);
- mSys->Radio.prepareDevInformCmd(iv->radioId.u64, p->packet[0] + 1 - ALL_FRAMES, mPayload[iv->id].ts, iv->alarmMesIndex, false, p->packet[0] + 1 - ALL_FRAMES);
+ //mSys->Radio.prepareDevInformCmd(iv->radioId.u64, p->packet[0] + 1 - ALL_FRAMES, mPayload[iv->id].ts, iv->alarmMesIndex, false, p->packet[0] + 1 - ALL_FRAMES);
} else {
mPayload[iv->id].complete = true;
//iv->setValue(iv->getPosByChFld(0, FLD_YD, rec), rec, CALC_YD_CH0);
//iv->setQueuedCmdFinished();
- }
+ }*/
- /*}
+ //}
- if (p->packet[0] == (TX_REQ_INFO + ALL_FRAMES)) { // response from get information command
+ } else if (p->packet[0] == (TX_REQ_INFO + ALL_FRAMES)) { // response from get information command
+ // atm, we just do nothing else than print out what we got...
+ // for decoding see xls- Data collection instructions - #147ff
+/*
+ Polling the device software and hardware version number command
+ start byte Command word routing address target address User data check end byte
+ byte[0] byte[1] byte[2] byte[3] byte[4] byte[5] byte[6] byte[7] byte[8] byte[9] byte[10] byte[11] byte[12]
+ 0x7e 0x0f xx xx xx xx YY YY YY YY 0x00 CRC 0x7f
+
+ Command Receipt - First Frame
+ start byte Command word target address routing address Multi-frame marking User data User data User data User data User data User data User data User data User data User data User data User data User data User data User data User data check end byte
+ byte[0] byte[1] byte[2] byte[3] byte[4] byte[5] byte[6] byte[7] byte[8] byte[9] byte[10] byte[11] byte[12] byte[13] byte[14] byte[15] byte[16] byte[17] byte[18] byte[19] byte[20] byte[21] byte[22] byte[23] byte[24] byte[25] byte[26] byte[27] byte[28]
+ 0x7e 0x8f YY YY YY YY xx xx xx xx 0x00 USFWBuild_VER APPFWBuild_VER APPFWBuild_YYYY APPFWBuild_MMDD APPFWBuild_HHMM APPFW_PN HW_VER CRC 0x7f
+ Command Receipt - Second Frame
+ start byte Command word target address routing address Multi-frame marking User data User data User data User data User data User data User data User data User data User data User data User data User data User data User data User data check end byte
+ byte[0] byte[1] byte[2] byte[3] byte[4] byte[5] byte[6] byte[7] byte[8] byte[9] byte[10] byte[11] byte[12] byte[13] byte[14] byte[15] byte[16] byte[17] byte[18] byte[19] byte[20] byte[21] byte[22] byte[23] byte[24] byte[25] byte[26] byte[27] byte[28]
+ 0x7e 0x8f YY YY YY YY xx xx xx xx 0x01 HW_PN HW_FB_TLmValue HW_FB_ReSPRT HW_GridSamp_ResValule HW_ECapValue Matching_APPFW_PN CRC 0x7f
+ Command receipt - third frame
+ start byte Command word target address routing address Multi-frame marking User data User data User data User data User data User data User data User data check end byte
+ byte[0] byte[1] byte[2] byte[3] byte[4] byte[5] byte[6] byte[7] byte[8] byte[9] byte[10] byte[11] byte[12] byte[13] byte[14] byte[15] byte[16] byte[15] byte[16] byte[17] byte[18]
+ 0x7e 0x8f YY YY YY YY xx xx xx xx 0x12 APPFW_MINVER HWInfoAddr PNInfoCRC_gusv PNInfoCRC_gusv CRC 0x7f
+
+*/
mPayload[iv->id].txId = p->packet[0];
DPRINTLN(DBG_DEBUG, F("Response from info request received"));
uint8_t *pid = &p->packet[9];
if (*pid == 0x00) {
- DPRINT(DBG_DEBUG, F("fragment number zero received and ignored"));
- } else {
+ DPRINT(DBG_DEBUG, F("fragment number zero received"));
+
+ iv->setQueuedCmdFinished();
+ } //else {
DPRINTLN(DBG_DEBUG, "PID: 0x" + String(*pid, HEX));
+ /*
if ((*pid & 0x7F) < MAX_PAYLOAD_ENTRIES) {
memcpy(mPayload[iv->id].data[(*pid & 0x7F) - 1], &p->packet[10], p->len - 11);
mPayload[iv->id].len[(*pid & 0x7F) - 1] = p->len - 11;
@@ -242,13 +291,21 @@ class MiPayload {
if (IV_HM == iv->ivGen) // only process MI inverters
continue; // skip to next inverter
- /*if ((mPayload[iv->id].txId != (TX_REQ_INFO + ALL_FRAMES)) && (0 != mPayload[iv->id].txId)) {
- // no processing needed if txId is not 0x95
+ if ((mPayload[iv->id].txId != (TX_REQ_INFO + ALL_FRAMES)) &&
+ (mPayload[iv->id].txId < (0x36 + ALL_FRAMES)) &&
+ (mPayload[iv->id].txId > (0x39 + ALL_FRAMES)) &&
+ (mPayload[iv->id].txId != (0x09 + ALL_FRAMES)) &&
+ (mPayload[iv->id].txId != (0x11 + ALL_FRAMES)) &&
+ (mPayload[iv->id].txId != (0x88)) &&
+ (mPayload[iv->id].txId != (0x92)) &&
+ (mPayload[iv->id].txId != 0 )) {
+ // no processing needed if txId is not one of 0x95, 0x88, 0x89, 0x91, 0x92 or resonse to 0x36ff
mPayload[iv->id].complete = true;
continue; // skip to next inverter
- }*/
+ }
if (!mPayload[iv->id].complete) {
+ //DPRINTLN(DBG_INFO, F("Pyld incompl code")); //info for testing only
bool crcPass, pyldComplete;
crcPass = build(iv->id, &pyldComplete);
if (!crcPass && !pyldComplete) { // payload not complete
@@ -263,21 +320,32 @@ class MiPayload {
} else {
if (mPayload[iv->id].retransmits < mMaxRetrans) {
mPayload[iv->id].retransmits++;
- //mSys->Radio.prepareDevInformCmd(iv->radioId.u64, iv->getQueuedCmd(), mPayload[iv->id].ts, iv->alarmMesIndex, false, 0x11);
- mSys->Radio.sendCmdPacket(iv->radioId.u64, iv->getQueuedCmd(), 24, true);
- /*if(false == mPayload[iv->id].gotFragment) {
- DPRINTLN(DBG_WARN, F("(#") + String(iv->id) + F(") nothing received"));
+ if( !mPayload[iv->id].gotFragment ) {
+ DPRINT(DBG_INFO, F("(#"));
+ DBGPRINT(String(iv->id));
+ DBGPRINTLN(F(") nothing received"));
mPayload[iv->id].retransmits = mMaxRetrans;
} else {
- for (uint8_t i = 0; i < (mPayload[iv->id].maxPackId - 1); i++) {
- if (mPayload[iv->id].len[i] == 0) {
- DPRINTLN(DBG_WARN, F("Frame ") + String(i + 1) + F(" missing: Request Retransmit"));
- mSys->Radio.sendCmdPacket(iv->radioId.u64, TX_REQ_INFO, (SINGLE_FRAME + i), true);
- break; // only request retransmit one frame per loop
+ uint8_t cmd = mPayload[iv->id].txCmd;
+ if ( cmd >= 0x36 && cmd < 0x39 ) { // MI-1500 Data command
+ cmd++; // just request the next channel
+ } else if ( cmd == 0x09 ) {//MI single or dual channel device
+ if ( mPayload[iv->id].dataAB[CH1] && iv->type == INV_TYPE_2CH ) {
+ if (!mPayload[iv->id].stsAB[CH2] || !mPayload[iv->id].dataAB[CH2] )
+ cmd = 0x11;
+ }
+ } else if ( cmd == 0x11) {
+ if ( mPayload[iv->id].dataAB[CH2] ) { // data is there, but no status
+ if (!mPayload[iv->id].stsAB[CH1] || !mPayload[iv->id].dataAB[CH1] )
+ cmd = 0x09;
}
- yield();
}
- }*/
+ DPRINTLN(DBG_INFO, F("(#") + String(iv->id) + F(") next request is 0x") + String(cmd, HEX));
+ //mSys->Radio.sendCmdPacket(iv->radioId.u64, cmd, cmd, true);
+ mSys->Radio.prepareDevInformCmd(iv->radioId.u64, cmd, mPayload[iv->id].ts, iv->alarmMesIndex, true, cmd);
+ mPayload[iv->id].txCmd = cmd;
+ yield();
+ }
}
}
}
@@ -289,14 +357,25 @@ class MiPayload {
DPRINTLN(DBG_INFO, F("(#") + String(iv->id) + F(") prepareDevInformCmd 0x") + String(mPayload[iv->id].txCmd, HEX));
mSys->Radio.prepareDevInformCmd(iv->radioId.u64, mPayload[iv->id].txCmd, mPayload[iv->id].ts, iv->alarmMesIndex, true);
}
- } /*else { // payload complete
+ } else { // payload complete
DPRINTLN(DBG_INFO, F("procPyld: cmd: 0x") + String(mPayload[iv->id].txCmd, HEX));
DPRINTLN(DBG_INFO, F("procPyld: txid: 0x") + String(mPayload[iv->id].txId, HEX));
- DPRINTLN(DBG_DEBUG, F("procPyld: max: ") + String(mPayload[iv->id].maxPackId));
- record_t<> *rec = iv->getRecordStruct(mPayload[iv->id].txCmd); // choose the parser
- mPayload[iv->id].complete = true;
+ //DPRINTLN(DBG_DEBUG, F("procPyld: max: ") + String(mPayload[iv->id].maxPackId));
+ //record_t<> *rec = iv->getRecordStruct(mPayload[iv->id].txCmd); // choose the parser
- uint8_t payload[128];
+ mPayload[iv->id].complete = true;
+ uint8_t ac_pow = 0;
+ //if (mPayload[iv->id].sts[0] == 3) {
+ ac_pow = calcPowerDcCh0(iv, 0)*9.5;
+ //}
+ record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug); // choose the parser
+ iv->setValue(iv->getPosByChFld(0, FLD_PAC, rec), rec, (float) (ac_pow/10));
+
+ DPRINTLN(DBG_INFO, F("proces: compl. set of msgs detected"));
+ iv->setValue(iv->getPosByChFld(0, FLD_YD, rec), rec, calcYieldDayCh0(iv,0));
+ iv->doCalculations();
+
+ /*uint8_t payload[128];
uint8_t payloadLen = 0;
memset(payload, 0, 128);
@@ -345,8 +424,8 @@ class MiPayload {
mStat->rxFail++;
}
- iv->setQueuedCmdFinished();
- }*/
+ iv->setQueuedCmdFinished(); */
+ }
}
yield();
}
@@ -359,96 +438,159 @@ class MiPayload {
}
void miStsDecode(Inverter<> *iv, packet_t *p, uint8_t chan = CH1) {
- DPRINTLN(DBG_INFO, F("Inverter ") + String(iv->id) + F(": status msg 0x") + String(p->packet[0], HEX));
+ DPRINTLN(DBG_INFO, F("(#") + String(iv->id) + F(") status msg 0x") + String(p->packet[0], HEX));
record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug); // choose the record structure
rec->ts = mPayload[iv->id].ts;
+ mPayload[iv->id].gotFragment = true;
+ mPayload[iv->id].txId = p->packet[0];
+
+ uint8_t status = (p->packet[11] << 8) + p->packet[12];
+ uint8_t stschan = p->packet[0] == 0x88 ? CH1 : CH2;
+ mPayload[iv->id].sts[stschan] = status;
+ mPayload[iv->id].stsAB[stschan] = true;
+ if (mPayload[iv->id].stsAB[CH1] && mPayload[iv->id].stsAB[CH2])
+ mPayload[iv->id].stsAB[CH0] = true;
+ if ( !mPayload[iv->id].sts[0] || status < mPayload[iv->id].sts[0]) {
+ mPayload[iv->id].sts[0] = status;
+ iv->setValue(iv->getPosByChFld(0, FLD_EVT, rec), rec, status);
+ }
- //iv->setValue(iv->getPosByChFld(chan, FLD_YD, rec), rec, (int)((p->packet[11+6] << 8) + p->packet[12+6])); // was 11/12, might be wrong!
-
- //if (INV_TYPE_1CH == iv->type)
- //iv->setValue(iv->getPosByChFld(0, FLD_YD, rec), rec, (int)((p->packet[11+6] << 8) + p->packet[12+6]));
-
- //iv->setValue(iv->getPosByChFld(chan, FLD_EVT, rec), rec, (int)((p->packet[13] << 8) + p->packet[14]));
+ /*if ( !mPayload[iv->id].dataAB[0] || !mPayload[iv->id].dataAB[1] ) {
+ uint8_t cmd = mPayload[iv->id].dataAB[0] ? 0x11 : 0x09;
+ DPRINTLN(DBG_INFO, F("request missing status 0x") + String(cmd, HEX));
+ mSys->Radio.prepareDevInformCmd(iv->radioId.u64, cmd, mPayload[iv->id].ts, iv->alarmMesIndex, false, cmd);
+ mPayload[iv->id].txCmd = cmd;
+ rem: gotFragment should be a better solution
+ } */
- iv->setValue(iv->getPosByChFld(0, FLD_EVT, rec), rec, (int)((p->packet[11] << 8) + p->packet[12]));
- //iv->setValue(iv->getPosByChFld(0, FLD_EVT, rec), rec, (int)((p->packet[14] << 8) + p->packet[16]));
if (iv->alarmMesIndex < rec->record[iv->getPosByChFld(0, FLD_EVT, rec)]){
iv->alarmMesIndex = rec->record[iv->getPosByChFld(0, FLD_EVT, rec)]; // seems there's no status per channel in 3rd gen. models?!?
DPRINTLN(DBG_INFO, "alarm ID incremented to " + String(iv->alarmMesIndex));
iv->enqueCommand(AlarmData);
}
- /* Unclear how in HM inverters Info and alarm data is handled...
- */
-
-
- /* int8_t offset = -2;
+ if (mPayload[iv->id].stsAB[CH0] && mPayload[iv->id].dataAB[CH0] && !mPayload[iv->id].complete) {
+ mPayload[iv->id].complete = true;
+ DPRINTLN(DBG_INFO, F("rec. complete set of msgs"));
+ iv->setValue(iv->getPosByChFld(0, FLD_YD, rec), rec, calcYieldDayCh0(iv,0));
+ iv->setQueuedCmdFinished();
+ iv->doCalculations();
+ notify(mPayload[iv->id].txCmd);
+ }
- for decoding see
- void MI600StsMsg (NRF24_packet_t *p){
- STAT = (int)((p->packet[11] << 8) + p->packet[12]);
- FCNT = (int)((p->packet[13] << 8) + p->packet[14]);
- FCODE = (int)((p->packet[15] << 8) + p->packet[16]);
- #ifdef ESP8266
- VALUES[PV][5]=STAT;
- VALUES[PV][6]=FCNT;
- VALUES[PV][7]=FCODE;
- #endif
- }
- */
}
void miDataDecode(Inverter<> *iv, packet_t *p) {
record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug); // choose the parser
rec->ts = mPayload[iv->id].ts;
+ mPayload[iv->id].gotFragment = true;
uint8_t datachan = ( p->packet[0] == 0x89 || p->packet[0] == (0x36 + ALL_FRAMES) ) ? CH1 :
( p->packet[0] == 0x91 || p->packet[0] == (0x37 + ALL_FRAMES) ) ? CH2 :
p->packet[0] == (0x38 + ALL_FRAMES) ? CH3 :
CH4;
- int8_t offset = -2;
- // U_DC = (float) ((p->packet[11] << 8) + p->packet[12])/10;
- iv->setValue(iv->getPosByChFld(datachan, FLD_UDC, rec), rec, (float)((p->packet[11+offset] << 8) + p->packet[12+offset])/10);
+ DPRINTLN(DBG_INFO, F("(#") + String(iv->id) + F(") data msg 0x") + String(p->packet[0], HEX) + F(" channel ") + datachan);
+ // count in RF_communication_protocol.xlsx is with offset = -1
+ iv->setValue(iv->getPosByChFld(datachan, FLD_UDC, rec), rec, (float)((p->packet[9] << 8) + p->packet[10])/10);
yield();
- // I_DC = (float) ((p->packet[13] << 8) + p->packet[14])/10;
- iv->setValue(iv->getPosByChFld(datachan, FLD_IDC, rec), rec, (float)((p->packet[13+offset] << 8) + p->packet[14+offset])/10);
+ iv->setValue(iv->getPosByChFld(datachan, FLD_IDC, rec), rec, (float)((p->packet[11] << 8) + p->packet[12])/10);
yield();
- // U_AC = (float) ((p->packet[15] << 8) + p->packet[16])/10;
- iv->setValue(iv->getPosByChFld(0, FLD_UAC, rec), rec, (float)((p->packet[15+offset] << 8) + p->packet[16+offset])/10);
+ iv->setValue(iv->getPosByChFld(0, FLD_UAC, rec), rec, (float)((p->packet[13] << 8) + p->packet[14])/10);
yield();
- // F_AC = (float) ((p->packet[17] << 8) + p->packet[18])/100;
- //iv->setValue(iv->getPosByChFld(0, FLD_IAC, rec), rec, (float)((p->packet[17+offset] << 8) + p->packet[18+offset])/100);
- //yield();
- // P_DC = (float)((p->packet[19] << 8) + p->packet[20])/10;
- iv->setValue(iv->getPosByChFld(datachan, FLD_PDC, rec), rec, (float)((p->packet[19+offset] << 8) + p->packet[20+offset])/10);
+ iv->setValue(iv->getPosByChFld(0, FLD_F, rec), rec, (float) ((p->packet[15] << 8) + p->packet[16])/100);
+ iv->setValue(iv->getPosByChFld(datachan, FLD_PDC, rec), rec, (float)((p->packet[17] << 8) + p->packet[18])/10);
yield();
- // Q_DC = (float)((p->packet[21] << 8) + p->packet[22])/1;
- iv->setValue(iv->getPosByChFld(datachan, FLD_YD, rec), rec, (float)((p->packet[21+offset] << 8) + p->packet[22+offset])/1);
+ iv->setValue(iv->getPosByChFld(datachan, FLD_YD, rec), rec, (float)((p->packet[19] << 8) + p->packet[20])/1);
yield();
- iv->setValue(iv->getPosByChFld(0, FLD_T, rec), rec, (float) ((int16_t)(p->packet[23+offset] << 8) + p->packet[24+offset])/10); //23 is freq or IAC?
- iv->setValue(iv->getPosByChFld(0, FLD_F, rec), rec, (float) ((p->packet[17+offset] << 8) + p->packet[18+offset])/100);
+ iv->setValue(iv->getPosByChFld(0, FLD_T, rec), rec, (float) ((int16_t)(p->packet[21] << 8) + p->packet[22])/10);
iv->setValue(iv->getPosByChFld(0, FLD_IRR, rec), rec, (float) (calcIrradiation(iv, datachan)));
- yield();
- //FLD_YD
+ //AC Power is missing; we may have to calculate, as no respective data is in payload
+
+ if ( datachan < 3 ) {
+ mPayload[iv->id].dataAB[datachan] = true;
+ }
+ if ( !mPayload[iv->id].dataAB[CH0] && mPayload[iv->id].dataAB[CH2] && mPayload[iv->id].dataAB[CH2] ) {
+ mPayload[iv->id].dataAB[CH0] = true;
+ }
if (p->packet[0] >= (0x36 + ALL_FRAMES) ) {
- /*status message analysis most liklely needs to be changed, see MiStsMst*/
- /*STAT = (uint8_t)(p->packet[25] );
- FCNT = (uint8_t)(p->packet[26]);
- FCODE = (uint8_t)(p->packet[27]); // MI300/Mi600 stsMsg:: (int)((p->packet[15] << 8) + p->packet[16]); */
- iv->setValue(iv->getPosByChFld(0, FLD_EVT, rec), rec, (uint8_t)(p->packet[25+offset]));
- yield();
+
+ /*For MI1500:
+ if (MI1500) {
+ STAT = (uint8_t)(p->packet[25] );
+ FCNT = (uint8_t)(p->packet[26]);
+ FCODE = (uint8_t)(p->packet[27]);
+ }*/
+
+ uint8_t status = (uint8_t)(p->packet[23]);
+ mPayload[iv->id].sts[datachan] = status;
+ if ( !mPayload[iv->id].sts[0] || status < mPayload[iv->id].sts[0]) {
+ mPayload[iv->id].sts[0] = status;
+ iv->setValue(iv->getPosByChFld(0, FLD_EVT, rec), rec, status);
+ }
+
+ if (p->packet[0] < (0x39 + ALL_FRAMES) ) {
+ /*uint8_t cmd = p->packet[0] - ALL_FRAMES + 1;
+ mSys->Radio.prepareDevInformCmd(iv->radioId.u64, cmd, mPayload[iv->id].ts, iv->alarmMesIndex, false, cmd);
+ mPayload[iv->id].txCmd = cmd;*/
+ mPayload[iv->id].complete = false;
+ }
+
+ else if (p->packet[0] == (0x39 + ALL_FRAMES) ) {
+ /*uint8_t cmd = p->packet[0] - ALL_FRAMES + 1;
+ mSys->Radio.prepareDevInformCmd(iv->radioId.u64, cmd, mPayload[iv->id].ts, iv->alarmMesIndex, false, cmd);
+ mPayload[iv->id].txCmd = cmd;*/
+ mPayload[iv->id].complete = true;
+ }
+
+ //iv->setValue(iv->getPosByChFld(0, FLD_EVT, rec), rec, calcMiSts(iv));yield();
if (iv->alarmMesIndex < rec->record[iv->getPosByChFld(0, FLD_EVT, rec)]){
iv->alarmMesIndex = rec->record[iv->getPosByChFld(0, FLD_EVT, rec)];
- DPRINTLN(DBG_INFO, "alarm ID incremented to " + String(iv->alarmMesIndex));
- iv->enqueCommand(AlarmData);
+ DPRINTLN(DBG_INFO, F("alarm ID incremented to ") + String(iv->alarmMesIndex));
+ //iv->enqueCommand(AlarmData);
}
+
}
- //iv->setValue(iv->getPosByChFld(0, FLD_YD, rec), rec, CALC_YD_CH0); // (getValue(iv->getPosByChFld(1, FLD_YD, rec), rec) + getValue(iv->getPosByChFld(2, FLD_YD, rec), rec)));
- iv->setValue(iv->getPosByChFld(0, FLD_YD, rec), rec, calcYieldDayCh0(iv,0)); //datachan));
- iv->doCalculations();
- notify(mPayload[iv->id].txCmd);
+
+ //preliminary AC calculation...
+ uint8_t ac_pow = 0;
+ //if (mPayload[iv->id].sts[0] == 3) {
+ ac_pow = calcPowerDcCh0(iv, 0)*9.5;
+ //}
+ iv->setValue(iv->getPosByChFld(0, FLD_PAC, rec), rec, (float) (ac_pow/10));
+
+ if ( mPayload[iv->id].complete || //4ch device
+ iv->type != INV_TYPE_4CH //other devices
+ && mPayload[iv->id].dataAB[CH0]
+ && mPayload[iv->id].stsAB[CH0] ) {
+ mPayload[iv->id].complete = true; // For 2 CH devices, this might be too short...
+ DPRINTLN(DBG_INFO, F("rec. complete set of msgs"));
+ iv->setValue(iv->getPosByChFld(0, FLD_YD, rec), rec, calcYieldDayCh0(iv,0));
+ iv->doCalculations();
+ /*} else {
+ //retry to get missing status info for one or two channel devices
+ DPRINTLN(DBG_INFO, F("request missing data or status 0x") + String(cmd, HEX));
+ mSys->Radio.prepareDevInformCmd(iv->radioId.u64, cmd, mPayload[iv->id].ts, iv->alarmMesIndex, false, cmd);
+ mPayload[iv->id].txCmd = cmd;
+ //iv->enqueCommand(cmd); // mPayload[iv->id].dataAB[1] ? 0x09 : 0x11)
+ }*/
+ }
+ /* should be included in process()
+ else if (mPayload[iv->id].txCmd == 0x09 && iv->type == INV_TYPE_2CH) {
+ uint8_t cmd = 0x11;
+ DPRINTLN(DBG_INFO, F("request second data channel 0x") + String(cmd, HEX));
+ mSys->Radio.prepareDevInformCmd(iv->radioId.u64, cmd, mPayload[iv->id].ts, iv->alarmMesIndex, false, cmd);
+ mPayload[iv->id].txCmd = cmd;
+ mPayload[iv->id].complete = false;
+ }*/
+
+ iv->setQueuedCmdFinished();
+ mStat->rxSuccess++;
+ yield();
+
+ notify(mPayload[iv->id].txCmd);
+
/*
if(AlarmData == mPayload[iv->id].txCmd) {
uint8_t i = 0;
@@ -463,66 +605,26 @@ class MiPayload {
yield();
}
}*/
-/*decode here or memcopy payload for later decoding?
- void MI600DataMsg(NRF24_packet_t *p){
- U_DC = (float) ((p->packet[11] << 8) + p->packet[12])/10;
- I_DC = (float) ((p->packet[13] << 8) + p->packet[14])/10;
- U_AC = (float) ((p->packet[15] << 8) + p->packet[16])/10;
- F_AC = (float) ((p->packet[17] << 8) + p->packet[18])/100;
- P_DC = (float)((p->packet[19] << 8) + p->packet[20])/10;
- Q_DC = (float)((p->packet[21] << 8) + p->packet[22])/1;
- TEMP = (float) ((p->packet[23] << 8) + p->packet[24])/10; //(int16_t)
-
- if ((30packet[2] == 0x89) {PV= 0; TotalP[1]=P_DC; pvCnt[0]=1;}//port 1
- if (p->packet[2] == 0x91) {PV= 1; TotalP[2]=P_DC; pvCnt[1]=1;}//port 2
-
- TotalP[0]=TotalP[1]+TotalP[2]+TotalP[3]+TotalP[4];//in TotalP[0] is the totalPV power
- if((P_DC>400) || (P_DC<0) || (TotalP[0]>MAXPOWER)){// cant be!!
- TotalP[0]=0;
- return;
- }
- #ifdef ESP8266
- VALUES[PV][0]=PV;
- VALUES[PV][1]=P_DC;
- VALUES[PV][2]=U_DC;
- VALUES[PV][3]=I_DC;
- VALUES[PV][4]=Q_DC;
- #endif
- PMI=TotalP[0];
- LIM=(uint16_t)Limit;
- PrintOutValues();
- }*/
-
- /*For MI1500:
- if (MI1500) {
- STAT = (uint8_t)(p->packet[25] );
- FCNT = (uint8_t)(p->packet[26]);
- FCODE = (uint8_t)(p->packet[27]);
- }
- */
- DPRINTLN(DBG_INFO, F("Inverter ") + String(iv->id) + F(": data msg 0x") + String(p->packet[0], HEX) + F(" channel ") + datachan);
}
bool build(uint8_t id, bool *complete) {
- /*DPRINTLN(DBG_VERBOSE, F("build"));
- uint16_t crc = 0xffff, crcRcv = 0x0000;
+ DPRINTLN(DBG_VERBOSE, F("build"));
+ /*uint16_t crc = 0xffff, crcRcv = 0x0000;
if (mPayload[id].maxPackId > MAX_PAYLOAD_ENTRIES)
mPayload[id].maxPackId = MAX_PAYLOAD_ENTRIES;
-
- // check if all fragments are there
- *complete = true;
- for (uint8_t i = 0; i < mPayload[id].maxPackId; i++) {
- if(mPayload[id].len[i] == 0)
- *complete = false;
+ */
+ // check if all messages are there
+
+ *complete = mPayload[id].complete;
+ uint8_t txCmd = mPayload[id].txCmd;
+ //uint8_t cmd = getQueuedCmd();
+ if(!*complete) {
+ DPRINTLN(DBG_VERBOSE, F("incomlete, txCmd is 0x") + String(txCmd, HEX)); // + F("cmd is 0x") + String(cmd, HEX));
+ if (txCmd == 0x09 || txCmd == 0x11 || txCmd >= 0x36 && txCmd <= 0x39 )
+ return false;
}
- if(!*complete)
- return false;
- for (uint8_t i = 0; i < mPayload[id].maxPackId; i++) {
+ /*for (uint8_t i = 0; i < mPayload[id].maxPackId; i++) {
if (mPayload[id].len[i] > 0) {
if (i == (mPayload[id].maxPackId - 1)) {
crc = ah::crc16(mPayload[id].data[i], mPayload[id].len[i] - 2, crc);
@@ -538,21 +640,53 @@ class MiPayload {
}
void reset(uint8_t id) {
- DPRINTLN(DBG_INFO, "resetPayload: id: " + String(id));
+ DPRINTLN(DBG_INFO, F("resetPayload: id: ") + String(id));
memset(mPayload[id].len, 0, MAX_PAYLOAD_ENTRIES);
- /*
mPayload[id].gotFragment = false;
- mPayload[id].maxPackId = MAX_PAYLOAD_ENTRIES;
+ /*mPayload[id].maxPackId = MAX_PAYLOAD_ENTRIES;
mPayload[id].lastFound = false;*/
mPayload[id].retransmits = 0;
mPayload[id].complete = false;
+ mPayload[id].dataAB[CH0] = true; //required for 1CH and 2CH devices
+ mPayload[id].dataAB[CH1] = true; //required for 1CH and 2CH devices
+ mPayload[id].dataAB[CH2] = true; //only required for 2CH devices
+ mPayload[id].stsAB[CH0] = true; //required for 1CH and 2CH devices
+ mPayload[id].stsAB[CH1] = true; //required for 1CH and 2CH devices
+ mPayload[id].stsAB[CH2] = true; //only required for 2CH devices
mPayload[id].txCmd = 0;
mPayload[id].requested = false;
mPayload[id].ts = *mTimestamp;
- mPayload[id].stsa = false;
- mPayload[id].stsb = false;
+ mPayload[id].sts[0] = 0; //disable this in case gotFragment is not working
+ mPayload[id].sts[CH1] = 0;
+ mPayload[id].sts[CH2] = 0;
+ mPayload[id].sts[CH3] = 0;
+ mPayload[id].sts[CH4] = 0;
}
+
+/* template
+ static T calcMiSts(Inverter<> *iv) {
+ if(NULL != iv) {
+ T result = 0;
+ bool stsComplete = true;
+ uint8_t stsCh;
+ for(uint8_t i = 1; i <= iv->channels; i++) {
+ stsCh = mPayload[iv->id].sts[i];
+ if (!stsCh) {
+ stsComplete = false;
+ } else if ( !result || stsCh < result ) {
+ result = stsCh;
+ if (stsComplete && stsCh > stsComplete) {
+ stsComplete = stsCh;
+ }
+ }
+ }
+ mPayload[iv->id].sts[0] = stsComplete;
+ return result;
+ }
+ return 0;
+ } */
+
IApp *mApp;
HMSYSTEM *mSys;
statistics_t *mStat;
diff --git a/src/platformio.ini b/src/platformio.ini
index caea7b42..e52cf3fb 100644
--- a/src/platformio.ini
+++ b/src/platformio.ini
@@ -41,6 +41,7 @@ lib_deps =
bblanchon/ArduinoJson
https://github.com/JChristensen/Timezone
olikraus/U8g2
+ zinggjm/GxEPD2@^1.5.0
;esp8266/DNSServer
;esp8266/EEPROM
;esp8266/ESP8266WiFi
@@ -55,9 +56,9 @@ board_build.f_cpu = 80000000L
build_flags = -D RELEASE
monitor_filters =
;default ; Remove typical terminal control codes from input
- time ; Add timestamp with milliseconds for each new line
+ ;time ; Add timestamp with milliseconds for each new line
;log2file ; Log data to a file “platformio-device-monitor-*.log” located in the current working directory
- esp8266_exception_decoder, default
+ esp8266_exception_decoder
[env:esp8266-debug]
platform = espressif8266
@@ -100,8 +101,9 @@ build_flags = -D RELEASE -std=gnu++14
build_unflags = -std=gnu++11
monitor_filters =
;default ; Remove typical terminal control codes from input
- time ; Add timestamp with milliseconds for each new line
+ ;time ; Add timestamp with milliseconds for each new line
;log2file ; Log data to a file “platformio-device-monitor-*.log” located in the current working directory
+ esp32_exception_decoder
[env:esp32-wroom32-debug]
platform = espressif32
diff --git a/src/plugins/Display/Display.h b/src/plugins/Display/Display.h
new file mode 100644
index 00000000..1f229ba4
--- /dev/null
+++ b/src/plugins/Display/Display.h
@@ -0,0 +1,113 @@
+#ifndef __DISPLAY__
+#define __DISPLAY__
+
+#include
+#include
+
+#include "../../hm/hmSystem.h"
+#include "../../utils/helper.h"
+#include "Display_Mono.h"
+#include "Display_ePaper.h"
+
+template
+class Display {
+ public:
+ Display() {}
+
+ void setup(display_t *cfg, HMSYSTEM *sys, uint32_t *utcTs, uint8_t disp_reset, const char *version) {
+ mCfg = cfg;
+ mSys = sys;
+ mUtcTs = utcTs;
+ mNewPayload = false;
+ mLoopCnt = 0;
+ mVersion = version;
+
+ if (mCfg->type == 0)
+ return;
+
+ if ((1 < mCfg->type) && (mCfg->type < 10)) {
+ mMono.config(mCfg->pwrSaveAtIvOffline, mCfg->pxShift, mCfg->contrast);
+ mMono.init(mCfg->type, mCfg->rot, mCfg->disp_cs, mCfg->disp_dc, mCfg->disp_reset, mCfg->disp_clk, mCfg->disp_data, mUtcTs, mVersion);
+ } else if (mCfg->type >= 10) {
+ #if defined(ESP32)
+ mRefreshCycle = 0;
+ mEpaper.config(mCfg->rot);
+ mEpaper.init(mCfg->type, mCfg->disp_cs, mCfg->disp_dc, mCfg->disp_reset, mCfg->disp_busy, mCfg->disp_clk, mCfg->disp_data, mUtcTs, mVersion);
+ #endif
+ }
+ }
+
+ void payloadEventListener(uint8_t cmd) {
+ mNewPayload = true;
+ }
+
+ void tickerSecond() {
+ if (mNewPayload || ((++mLoopCnt % 10) == 0)) {
+ mNewPayload = false;
+ mLoopCnt = 0;
+ DataScreen();
+ }
+ }
+
+ private:
+ void DataScreen() {
+ if (mCfg->type == 0)
+ return;
+ if (*mUtcTs == 0)
+ return;
+
+ float totalPower = 0;
+ float totalYieldDay = 0;
+ float totalYieldTotal = 0;
+
+ uint8_t isprod = 0;
+
+ Inverter<> *iv;
+ record_t<> *rec;
+ for (uint8_t i = 0; i < mSys->getNumInverters(); i++) {
+ iv = mSys->getInverterByPos(i);
+ rec = iv->getRecordStruct(RealTimeRunData_Debug);
+ if (iv == NULL)
+ continue;
+
+ if (iv->isProducing(*mUtcTs))
+ isprod++;
+
+ totalPower += iv->getChannelFieldValue(CH0, FLD_PAC, rec);
+ totalYieldDay += iv->getChannelFieldValue(CH0, FLD_YD, rec);
+ totalYieldTotal += iv->getChannelFieldValue(CH0, FLD_YT, rec);
+ }
+
+ if ((1 < mCfg->type) && (mCfg->type < 10)) {
+ mMono.loop(totalPower, totalYieldDay, totalYieldTotal, isprod);
+ } else if (mCfg->type >= 10) {
+ #if defined(ESP32)
+ mEpaper.loop(totalPower, totalYieldDay, totalYieldTotal, isprod);
+ mRefreshCycle++;
+ #endif
+ }
+
+ #if defined(ESP32)
+ if (mRefreshCycle > 480) {
+ mEpaper.fullRefresh();
+ mRefreshCycle = 0;
+ }
+ #endif
+ }
+
+ // private member variables
+ bool mNewPayload;
+ uint8_t mLoopCnt;
+ uint32_t *mUtcTs;
+ const char *mVersion;
+ display_t *mCfg;
+ HMSYSTEM *mSys;
+ uint16_t mRefreshCycle;
+
+ #if defined(ESP32)
+ DisplayEPaper mEpaper;
+ #endif
+ DisplayMono mMono;
+};
+
+#endif /*__DISPLAY__*/
diff --git a/src/plugins/Display/Display_Mono.cpp b/src/plugins/Display/Display_Mono.cpp
new file mode 100644
index 00000000..af9cd870
--- /dev/null
+++ b/src/plugins/Display/Display_Mono.cpp
@@ -0,0 +1,149 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#include "Display_Mono.h"
+
+#ifdef ESP8266
+ #include
+#elif defined(ESP32)
+ #include
+#endif
+#include "../../utils/helper.h"
+
+//#ifdef U8X8_HAVE_HW_SPI
+//#include
+//#endif
+//#ifdef U8X8_HAVE_HW_I2C
+//#include
+//#endif
+
+DisplayMono::DisplayMono() {
+ mEnPowerSafe = true;
+ mEnScreenSaver = true;
+ mLuminance = 60;
+ _dispY = 0;
+ mTimeout = DISP_DEFAULT_TIMEOUT; // interval at which to power save (milliseconds)
+ mUtcTs = NULL;
+}
+
+
+
+void DisplayMono::init(uint8_t type, uint8_t rot, uint8_t cs, uint8_t dc, uint8_t reset, uint8_t clock, uint8_t data, uint32_t *utcTs, const char* version) {
+ if ((0 < type) && (type < 4)) {
+ u8g2_cb_t *rot = (u8g2_cb_t *)((rot != 0x00) ? U8G2_R2 : U8G2_R0);
+ switch(type) {
+ case 1:
+ mDisplay = new U8G2_PCD8544_84X48_F_4W_HW_SPI(rot, cs, dc, reset);
+ break;
+ case 2:
+ mDisplay = new U8G2_SSD1306_128X64_NONAME_F_HW_I2C(rot, reset, clock, data);
+ break;
+ default:
+ case 3:
+ mDisplay = new U8G2_SH1106_128X64_NONAME_F_HW_I2C(rot, reset, clock, data);
+ break;
+ }
+
+ mUtcTs = utcTs;
+
+ mDisplay->begin();
+
+ mIsLarge = (mDisplay->getWidth() > 120);
+ calcLineHeights();
+
+ mDisplay->clearBuffer();
+ mDisplay->setContrast(mLuminance);
+ printText("AHOY!", 0, 35);
+ printText("ahoydtu.de", 2, 20);
+ printText(version, 3, 46);
+ mDisplay->sendBuffer();
+ }
+}
+
+void DisplayMono::config(bool enPowerSafe, bool enScreenSaver, uint8_t lum) {
+ mEnPowerSafe = enPowerSafe;
+ mEnScreenSaver = enScreenSaver;
+ mLuminance = lum;
+}
+
+void DisplayMono::loop(float totalPower, float totalYieldDay, float totalYieldTotal, uint8_t isprod) {
+ if (mEnPowerSafe)
+ if(mTimeout != 0)
+ mTimeout--;
+
+ mDisplay->clearBuffer();
+
+ // set Contrast of the Display to raise the lifetime
+ mDisplay->setContrast(mLuminance);
+
+ if ((totalPower > 0) && (isprod > 0)) {
+ mTimeout = DISP_DEFAULT_TIMEOUT;
+ mDisplay->setPowerSave(false);
+ if (totalPower > 999) {
+ snprintf(_fmtText, DISP_FMT_TEXT_LEN, "%2.2f kW", (totalPower / 1000));
+ } else {
+ snprintf(_fmtText, DISP_FMT_TEXT_LEN, "%3.0f W", totalPower);
+ }
+ printText(_fmtText, 0);
+ } else {
+ printText("offline", 0, 25);
+ // check if it's time to enter power saving mode
+ if (mTimeout == 0)
+ mDisplay->setPowerSave(mEnPowerSafe);
+ }
+
+ snprintf(_fmtText, DISP_FMT_TEXT_LEN, "today: %4.0f Wh", totalYieldDay);
+ printText(_fmtText, 1);
+
+ snprintf(_fmtText, DISP_FMT_TEXT_LEN, "total: %.1f kWh", totalYieldTotal);
+ printText(_fmtText, 2);
+
+ IPAddress ip = WiFi.localIP();
+ if (!(_mExtra % 10) && (ip)) {
+ printText(ip.toString().c_str(), 3);
+ } else if (!(_mExtra % 5)) {
+ snprintf(_fmtText, DISP_FMT_TEXT_LEN, "#%d Inverter online", isprod);
+ printText(_fmtText, 3);
+ } else {
+ if(mIsLarge && (NULL != mUtcTs))
+ printText(ah::getDateTimeStr(gTimezone.toLocal(*mUtcTs)).c_str(), 3);
+ else
+ printText(ah::getTimeStr(gTimezone.toLocal(*mUtcTs)).c_str(), 3);
+ }
+
+ mDisplay->sendBuffer();
+
+ _dispY = 0;
+ _mExtra++;
+}
+
+void DisplayMono::calcLineHeights() {
+ uint8_t yOff = 0;
+ for (uint8_t i = 0; i < 4; i++) {
+ setFont(i);
+ yOff += (mDisplay->getMaxCharHeight());
+ mLineOffsets[i] = yOff;
+ }
+}
+
+inline void DisplayMono::setFont(uint8_t line) {
+ switch (line) {
+ case 0:
+ mDisplay->setFont((mIsLarge) ? u8g2_font_ncenB14_tr : u8g2_font_logisoso16_tr);
+ break;
+ case 3:
+ mDisplay->setFont(u8g2_font_5x8_tr);
+ break;
+ default:
+ mDisplay->setFont((mIsLarge) ? u8g2_font_ncenB10_tr : u8g2_font_5x8_tr);
+ break;
+ }
+}
+
+void DisplayMono::printText(const char* text, uint8_t line, uint8_t dispX) {
+ if (!mIsLarge) {
+ dispX = (line == 0) ? 10 : 5;
+ }
+ setFont(line);
+
+ dispX += (mEnPowerSafe) ? (_mExtra % 7) : 0;
+ mDisplay->drawStr(dispX, mLineOffsets[line], text);
+}
diff --git a/src/plugins/Display/Display_Mono.h b/src/plugins/Display/Display_Mono.h
new file mode 100644
index 00000000..cf14f27e
--- /dev/null
+++ b/src/plugins/Display/Display_Mono.h
@@ -0,0 +1,36 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#pragma once
+
+#include
+#define DISP_DEFAULT_TIMEOUT 60 // in seconds
+#define DISP_FMT_TEXT_LEN 32
+
+class DisplayMono {
+ public:
+ DisplayMono();
+
+ void init(uint8_t type, uint8_t rot, uint8_t cs, uint8_t dc, uint8_t reset, uint8_t clock, uint8_t data, uint32_t *utcTs, const char* version);
+ void config(bool enPowerSafe, bool enScreenSaver, uint8_t lum);
+ void loop(float totalPower, float totalYieldDay, float totalYieldTotal, uint8_t isprod);
+
+ private:
+ void calcLineHeights();
+ void setFont(uint8_t line);
+ void printText(const char* text, uint8_t line, uint8_t dispX = 5);
+
+ U8G2* mDisplay;
+
+ bool mEnPowerSafe, mEnScreenSaver;
+ uint8_t mLuminance;
+
+ bool mIsLarge = false;
+ uint8_t mLoopCnt;
+ uint32_t* mUtcTs;
+ uint8_t mLineOffsets[5];
+
+ uint16_t _dispY;
+
+ uint8_t _mExtra;
+ uint16_t mTimeout;
+ char _fmtText[DISP_FMT_TEXT_LEN];
+};
diff --git a/src/plugins/Display/Display_ePaper.cpp b/src/plugins/Display/Display_ePaper.cpp
new file mode 100644
index 00000000..fea372cd
--- /dev/null
+++ b/src/plugins/Display/Display_ePaper.cpp
@@ -0,0 +1,197 @@
+#include "Display_ePaper.h"
+
+#ifdef ESP8266
+ #include
+#elif defined(ESP32)
+ #include
+#endif
+#include "../../utils/helper.h"
+#include "imagedata.h"
+
+#if defined(ESP32)
+
+static const uint32_t spiClk = 4000000; // 4 MHz
+
+#if defined(ESP32) && defined(USE_HSPI_FOR_EPD)
+SPIClass hspi(HSPI);
+#endif
+
+DisplayEPaper::DisplayEPaper() {
+ mDisplayRotation = 2;
+ mHeadFootPadding = 16;
+}
+
+
+//***************************************************************************
+void DisplayEPaper::init(uint8_t type, uint8_t _CS, uint8_t _DC, uint8_t _RST, uint8_t _BUSY, uint8_t _SCK, uint8_t _MOSI, uint32_t *utcTs, const char *version) {
+ mUtcTs = utcTs;
+
+ if (type > 9) {
+ Serial.begin(115200);
+ _display = new GxEPD2_BW(GxEPD2_150_BN(_CS, _DC, _RST, _BUSY));
+ hspi.begin(_SCK, _BUSY, _MOSI, _CS);
+
+#if defined(ESP32) && defined(USE_HSPI_FOR_EPD)
+ _display->epd2.selectSPI(hspi, SPISettings(spiClk, MSBFIRST, SPI_MODE0));
+#endif
+ _display->init(115200, true, 2, false);
+ _display->setRotation(mDisplayRotation);
+ _display->setFullWindow();
+
+ // Logo
+ _display->fillScreen(GxEPD_BLACK);
+ _display->drawBitmap(0, 0, logo, 200, 200, GxEPD_WHITE);
+ while (_display->nextPage())
+ ;
+
+ // clean the screen
+ delay(2000);
+ _display->fillScreen(GxEPD_WHITE);
+ while (_display->nextPage())
+ ;
+
+ headlineIP();
+
+ // call the PowerPage to change the PV Power Values
+ actualPowerPaged(0, 0, 0, 0);
+ }
+}
+
+void DisplayEPaper::config(uint8_t rotation) {
+ mDisplayRotation = rotation;
+}
+
+//***************************************************************************
+void DisplayEPaper::fullRefresh() {
+ // screen complete black
+ _display->fillScreen(GxEPD_BLACK);
+ while (_display->nextPage())
+ ;
+ delay(2000);
+ // screen complete white
+ _display->fillScreen(GxEPD_WHITE);
+ while (_display->nextPage())
+ ;
+}
+//***************************************************************************
+void DisplayEPaper::headlineIP() {
+ int16_t tbx, tby;
+ uint16_t tbw, tbh;
+
+ _display->setFont(&FreeSans9pt7b);
+ _display->setTextColor(GxEPD_WHITE);
+
+ _display->setPartialWindow(0, 0, _display->width(), mHeadFootPadding);
+ _display->fillScreen(GxEPD_BLACK);
+
+ do {
+ if ((WiFi.isConnected() == true) && (WiFi.localIP() > 0)) {
+ snprintf(_fmtText, sizeof(_fmtText), "%s", WiFi.localIP().toString().c_str());
+ } else {
+ snprintf(_fmtText, sizeof(_fmtText), "WiFi not connected");
+ }
+ _display->getTextBounds(_fmtText, 0, 0, &tbx, &tby, &tbw, &tbh);
+ uint16_t x = ((_display->width() - tbw) / 2) - tbx;
+
+ _display->setCursor(x, (mHeadFootPadding - 2));
+ _display->println(_fmtText);
+ } while (_display->nextPage());
+}
+//***************************************************************************
+void DisplayEPaper::lastUpdatePaged() {
+ int16_t tbx, tby;
+ uint16_t tbw, tbh;
+
+ _display->setFont(&FreeSans9pt7b);
+ _display->setTextColor(GxEPD_WHITE);
+
+ _display->setPartialWindow(0, _display->height() - mHeadFootPadding, _display->width(), mHeadFootPadding);
+ _display->fillScreen(GxEPD_BLACK);
+ do {
+ if (NULL != mUtcTs) {
+ snprintf(_fmtText, sizeof(_fmtText), ah::getDateTimeStr(gTimezone.toLocal(*mUtcTs)).c_str());
+
+ _display->getTextBounds(_fmtText, 0, 0, &tbx, &tby, &tbw, &tbh);
+ uint16_t x = ((_display->width() - tbw) / 2) - tbx;
+
+ _display->setCursor(x, (_display->height() - 3));
+ _display->println(_fmtText);
+ }
+ } while (_display->nextPage());
+}
+//***************************************************************************
+void DisplayEPaper::actualPowerPaged(float _totalPower, float _totalYieldDay, float _totalYieldTotal, uint8_t _isprod) {
+ int16_t tbx, tby;
+ uint16_t tbw, tbh, x, y;
+
+ _display->setFont(&FreeSans24pt7b);
+ _display->setTextColor(GxEPD_BLACK);
+
+ _display->setPartialWindow(0, mHeadFootPadding, _display->width(), _display->height() - (mHeadFootPadding * 2));
+ _display->fillScreen(GxEPD_WHITE);
+ do {
+ if (_totalPower > 9999) {
+ snprintf(_fmtText, sizeof(_fmtText), "%.1f kW", (_totalPower / 10000));
+ _changed = true;
+ } else if ((_totalPower > 0) && (_totalPower <= 9999)) {
+ snprintf(_fmtText, sizeof(_fmtText), "%.0f W", _totalPower);
+ _changed = true;
+ } else {
+ snprintf(_fmtText, sizeof(_fmtText), "offline");
+ }
+ _display->getTextBounds(_fmtText, 0, 0, &tbx, &tby, &tbw, &tbh);
+ x = ((_display->width() - tbw) / 2) - tbx;
+ _display->setCursor(x, mHeadFootPadding + tbh + 10);
+ _display->print(_fmtText);
+
+ _display->setFont(&FreeSans12pt7b);
+ y = _display->height() / 2;
+ _display->setCursor(0, y);
+ _display->print("today:");
+ snprintf(_fmtText, _display->width(), "%.0f", _totalYieldDay);
+ _display->getTextBounds(_fmtText, 0, 0, &tbx, &tby, &tbw, &tbh);
+ x = ((_display->width() - tbw) / 2) - tbx;
+ _display->setCursor(x, y);
+ _display->print(_fmtText);
+ _display->setCursor(_display->width() - 33, y);
+ _display->println("Wh");
+
+ y = y + tbh + 7;
+ _display->setCursor(0, y);
+ _display->print("total:");
+ snprintf(_fmtText, _display->width(), "%.1f", _totalYieldTotal);
+ _display->getTextBounds(_fmtText, 0, 0, &tbx, &tby, &tbw, &tbh);
+ x = ((_display->width() - tbw) / 2) - tbx;
+ _display->setCursor(x, y);
+ _display->print(_fmtText);
+ _display->setCursor(_display->width() - 45, y);
+ _display->println("kWh");
+
+ _display->setCursor(0, _display->height() - (mHeadFootPadding + 10));
+ snprintf(_fmtText, sizeof(_fmtText), "#%d Inverter online", _isprod);
+ _display->println(_fmtText);
+
+ } while (_display->nextPage());
+}
+//***************************************************************************
+void DisplayEPaper::loop(float totalPower, float totalYieldDay, float totalYieldTotal, uint8_t isprod) {
+ // check if the IP has changed
+ if (_settedIP != WiFi.localIP().toString().c_str()) {
+ // save the new IP and call the Headline Funktion to adapt the Headline
+ _settedIP = WiFi.localIP().toString().c_str();
+ headlineIP();
+ }
+
+ // call the PowerPage to change the PV Power Values
+ actualPowerPaged(totalPower, totalYieldDay, totalYieldTotal, isprod);
+
+ // if there was an change and the Inverter is producing set a new Timestam in the footline
+ if ((isprod > 0) && (_changed)) {
+ _changed = false;
+ lastUpdatePaged();
+ }
+
+ _display->powerOff();
+}
+//***************************************************************************
+#endif // ESP32
diff --git a/src/plugins/Display/Display_ePaper.h b/src/plugins/Display/Display_ePaper.h
new file mode 100644
index 00000000..b2729f25
--- /dev/null
+++ b/src/plugins/Display/Display_ePaper.h
@@ -0,0 +1,52 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#pragma once
+
+#if defined(ESP32)
+
+// uncomment next line to use HSPI for EPD (and VSPI for SD), e.g. with Waveshare ESP32 Driver Board
+#define USE_HSPI_FOR_EPD
+
+/// uncomment next line to use class GFX of library GFX_Root instead of Adafruit_GFX, to use less code and ram
+// #include
+// base class GxEPD2_GFX can be used to pass references or pointers to the display instance as parameter, uses ~1.2k more code
+// enable GxEPD2_GFX base class
+#define ENABLE_GxEPD2_GFX 1
+
+#include
+#include
+#include
+
+#include
+// FreeFonts from Adafruit_GFX
+#include
+#include
+#include
+#include
+
+// GDEW027C44 2.7 " b/w/r 176x264, IL91874
+// GDEH0154D67 1.54" b/w 200x200
+
+class DisplayEPaper {
+ public:
+ DisplayEPaper();
+ void fullRefresh();
+ void init(uint8_t type, uint8_t _CS, uint8_t _DC, uint8_t _RST, uint8_t _BUSY, uint8_t _SCK, uint8_t _MOSI, uint32_t *utcTs, const char* version);
+ void config(uint8_t rotation);
+ void loop(float totalPower, float totalYieldDay, float totalYieldTotal, uint8_t isprod);
+
+
+ private:
+ void headlineIP();
+ void actualPowerPaged(float _totalPower, float _totalYieldDay, float _totalYieldTotal, uint8_t _isprod);
+ void lastUpdatePaged();
+
+ uint8_t mDisplayRotation;
+ bool _changed = false;
+ char _fmtText[35];
+ const char* _settedIP;
+ uint8_t mHeadFootPadding;
+ GxEPD2_GFX* _display;
+ uint32_t *mUtcTs;
+};
+
+#endif // ESP32
diff --git a/src/plugins/Display/imagedata.h b/src/plugins/Display/imagedata.h
new file mode 100644
index 00000000..baaddec8
--- /dev/null
+++ b/src/plugins/Display/imagedata.h
@@ -0,0 +1,329 @@
+// GxEPD2_ESP32_ESP8266_WifiData_V1_und_V2
+
+#ifndef __IMAGEDATA_H__
+#define __IMAGEDATA_H__
+
+#if defined(__AVR__) || defined(ARDUINO_ARCH_SAMD)
+#include
+#elif defined(ESP8266) || defined(ESP32)
+#include
+#endif
+
+// 'Logo', 200x200px
+const unsigned char logo[] PROGMEM = {
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x5f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00,
+ 0x0b, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x0f, 0xfe, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x06,
+ 0x0f, 0xff, 0xff, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x7e, 0x0f, 0xff, 0xff, 0xfc, 0x03, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe,
+ 0x03, 0xfe, 0x0f, 0xff, 0xff, 0xff, 0xf0, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x19, 0xfe, 0x07, 0xff, 0xff, 0xff, 0xfe,
+ 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xe0, 0x70, 0x7f, 0x07, 0xff, 0xff, 0xff, 0xff, 0xc1, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x03, 0xe0, 0x3f, 0x07, 0xff, 0xff,
+ 0xff, 0xff, 0xf8, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xfc, 0x0f, 0xe0, 0x3f, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0f, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x3f, 0xe0, 0x1f, 0x83,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xc3, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xf1, 0xff, 0xe0, 0x1f, 0x83, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf9, 0xff, 0xe0,
+ 0x0f, 0x83, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf9, 0xff, 0xe0, 0x0f, 0x83, 0xff, 0xff, 0xff, 0xff, 0xfe,
+ 0x07, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc,
+ 0xff, 0xc1, 0x07, 0x80, 0x07, 0xfe, 0xff, 0xff, 0xfc, 0x07, 0xe3, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xff, 0xe1, 0x07, 0xc0, 0x01, 0xe0, 0x0f,
+ 0xff, 0xfc, 0x0f, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xfc, 0xff, 0xe1, 0x83, 0xc0, 0x01, 0xc0, 0x07, 0xff, 0xf8, 0x0f, 0xfc, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x7f, 0xe1, 0x83, 0xc0, 0x00,
+ 0xc0, 0x07, 0x8f, 0xf8, 0x1f, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xfe, 0x7f, 0xe0, 0x01, 0xc0, 0x00, 0x81, 0x83, 0x07, 0xf0, 0x3f, 0xf9, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x3f, 0xe0, 0x01,
+ 0xe0, 0xe0, 0x87, 0xe3, 0x0f, 0xf0, 0x3f, 0xf1, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, 0xe0, 0x00, 0xe0, 0xe0, 0x87, 0xe1, 0x0c, 0x60, 0x7f,
+ 0xe3, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x1f,
+ 0xe0, 0x00, 0xe1, 0xf0, 0x87, 0xe1, 0x08, 0x60, 0x7f, 0xe7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x9f, 0xe0, 0xe0, 0xe0, 0xe0, 0x87, 0xc2, 0x00,
+ 0x40, 0xff, 0xc7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0x8f, 0xc0, 0xe0, 0x60, 0xe0, 0xc0, 0x82, 0x00, 0xc0, 0xff, 0x8f, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xcf, 0xc0, 0xe0, 0x60, 0xe0, 0xc0,
+ 0x06, 0x01, 0x81, 0xff, 0x9f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xcf, 0xe0, 0xe0, 0x20, 0xe0, 0xe0, 0x0c, 0x03, 0x81, 0xff, 0x1f, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc7, 0xc0, 0xf0, 0x30,
+ 0xe1, 0xf8, 0x18, 0x07, 0xe1, 0xfe, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xc7, 0xc0, 0xf0, 0x7f, 0xff, 0xff, 0xf0, 0x1f, 0xf3, 0xfe, 0x01,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x83, 0xc0,
+ 0xfb, 0xff, 0xff, 0xff, 0xe0, 0x3e, 0x1f, 0xfc, 0xe0, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x03, 0xc0, 0xff, 0xff, 0xff, 0xff, 0xc0, 0xfc, 0x0f,
+ 0xf8, 0xfc, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc,
+ 0x33, 0xef, 0xff, 0xff, 0xff, 0xff, 0x81, 0xfc, 0x0f, 0xf1, 0xff, 0x07, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0xf1, 0xff, 0xff, 0xa0, 0x00, 0x7f, 0xe3,
+ 0xfc, 0x0f, 0xf3, 0xff, 0xc3, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xf1, 0xf9, 0xff, 0xf0, 0x00, 0x00, 0x00, 0xff, 0xfc, 0x0f, 0xe7, 0xff, 0xe0, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe3, 0xf9, 0xff, 0x80, 0x3f, 0xff,
+ 0xe0, 0x0f, 0xfe, 0x1f, 0xc7, 0xff, 0xf8, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xcf, 0xf8, 0xf0, 0x07, 0xff, 0xff, 0xff, 0x81, 0xff, 0xff, 0x8f, 0xff, 0xfc,
+ 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x9f, 0xfc, 0x70, 0x3f,
+ 0xff, 0xff, 0xff, 0xf0, 0x1f, 0xff, 0x1f, 0xff, 0xfe, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0x1f, 0xfc, 0x63, 0xff, 0xff, 0xff, 0xff, 0xff, 0x03, 0xff, 0x3f,
+ 0xff, 0xff, 0x8f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, 0xfe,
+ 0x23, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x7e, 0x3f, 0xff, 0xff, 0xc7, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x7f, 0xfe, 0x23, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe,
+ 0x0c, 0x7f, 0xff, 0xff, 0xc3, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe,
+ 0x7f, 0xff, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0xff, 0xff, 0xff, 0xe1, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xff, 0xff, 0x87, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xf1, 0xff, 0xff, 0xff, 0xf0, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xfc, 0xff, 0xff, 0x87, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf3, 0xff, 0xff, 0xff, 0xf8,
+ 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf9, 0xff, 0xff, 0x87, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xf1, 0xff, 0xff, 0xcf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xfc, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf3, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x01, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xe3, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xf8, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe7, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf9, 0x00, 0x3f, 0xff, 0xf8, 0x00, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xfc, 0x00, 0x00, 0x01, 0xff, 0xf8, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xcf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x55, 0x00, 0x3f, 0xf8, 0x00,
+ 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xcf, 0xff, 0xfc, 0x0f, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0x01, 0xff, 0xff, 0xf8, 0x0f, 0xfc, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0x9f, 0xff, 0xf8, 0x03, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x0f, 0xff, 0xff, 0xff, 0x03,
+ 0xfc, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x9f, 0xff, 0xe3, 0xf1, 0xff,
+ 0xff, 0xff, 0xff, 0xe0, 0x7f, 0xff, 0xff, 0xff, 0xe0, 0xfe, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0x9f, 0xff, 0xe7, 0xf9, 0xff, 0xff, 0xff, 0xff, 0x83, 0xff, 0xff, 0xff,
+ 0xff, 0xf8, 0x7e, 0x06, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x9f, 0xff, 0xcf,
+ 0xf8, 0xff, 0xff, 0xff, 0xff, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x3f, 0x03, 0x3f, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, 0xff, 0xcf, 0xfc, 0xff, 0xff, 0xff, 0xfe, 0x3f, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0x1f, 0x23, 0xbf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f,
+ 0xff, 0x9f, 0xfe, 0x7f, 0xff, 0xf3, 0xfc, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0x8f, 0xf1, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, 0xff, 0x9f, 0xfe, 0x7f, 0xff, 0xe3, 0xf8,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xfe, 0x7f, 0xff, 0x9f, 0xff, 0x0f, 0xff, 0x8f, 0xf1, 0xff, 0xff, 0xff, 0xfe, 0xf5, 0x90, 0x07,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x7f, 0xff, 0x9f, 0xff, 0x03, 0xff,
+ 0x1f, 0xe3, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xfe, 0x7f, 0xff, 0x3f, 0xfe, 0x31, 0xfe, 0x7f, 0xe7, 0xff, 0x80, 0x00, 0x40, 0x00,
+ 0x07, 0xe1, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x7f, 0xff, 0x3f, 0x3c,
+ 0xf9, 0xfc, 0xff, 0xe7, 0xfe, 0x3f, 0xc9, 0xff, 0xf1, 0x1f, 0xf1, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xfc, 0xff, 0xff, 0x3f, 0x3c, 0xf9, 0xf9, 0xff, 0xc7, 0xfc, 0xff, 0x90,
+ 0x7f, 0xf3, 0x03, 0xf9, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xff, 0xff,
+ 0x3f, 0x39, 0xfd, 0xf3, 0xff, 0xcf, 0xfc, 0xff, 0x90, 0x3f, 0xf3, 0x83, 0xf8, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xff, 0xff, 0x3f, 0x39, 0xf9, 0xc7, 0xff, 0xcf, 0xfc,
+ 0xff, 0x32, 0x7f, 0xe4, 0x77, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc,
+ 0xff, 0xff, 0x7f, 0x33, 0xf9, 0x8f, 0xff, 0xcf, 0xf9, 0xff, 0x00, 0x7f, 0xe0, 0x67, 0xfc, 0x7f,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf9, 0xff, 0xff, 0x7f, 0xb3, 0xf3, 0xbf, 0xff,
+ 0xcf, 0xf9, 0xff, 0x00, 0xff, 0xfe, 0x47, 0xfe, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xf9, 0xff, 0xff, 0x7f, 0xf7, 0xf3, 0xff, 0xff, 0xcf, 0xf9, 0xff, 0xe0, 0xff, 0xfc, 0x0f,
+ 0xfe, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf9, 0xff, 0xff, 0x7f, 0xe7, 0xe7,
+ 0xff, 0xff, 0xcf, 0xf9, 0xff, 0xe1, 0xff, 0xfc, 0x1f, 0xfe, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xf9, 0xff, 0xff, 0x3f, 0xef, 0xe7, 0xef, 0xff, 0xc7, 0xf9, 0xff, 0xc3, 0xff,
+ 0xfc, 0x3f, 0xff, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf9, 0xff, 0xff, 0x3f,
+ 0xef, 0xef, 0xc0, 0xff, 0xe7, 0xf9, 0xff, 0xc3, 0xff, 0xf8, 0x3f, 0xff, 0x3f, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xf9, 0xff, 0xff, 0x3f, 0xef, 0xcf, 0xf0, 0x01, 0xe7, 0xf1, 0xff,
+ 0x87, 0xff, 0xf8, 0x7f, 0xff, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf9, 0xff,
+ 0xff, 0xbf, 0xcf, 0xe7, 0xff, 0xc1, 0xe3, 0xe1, 0xff, 0x8f, 0xff, 0xf0, 0xff, 0xff, 0x9f, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf9, 0xff, 0xff, 0x9f, 0xef, 0xe7, 0xff, 0xff, 0xf3,
+ 0xc1, 0xff, 0x96, 0xaf, 0xf9, 0xff, 0xff, 0x9f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xf9, 0xff, 0xff, 0x9f, 0xe7, 0xe3, 0xff, 0xff, 0xf1, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff,
+ 0x9f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf9, 0xff, 0xff, 0xcf, 0xe7, 0xf3, 0xff,
+ 0xff, 0xf8, 0xc0, 0x00, 0x4a, 0x90, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xf9, 0xff, 0xff, 0xef, 0xf3, 0xf3, 0x9f, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf9, 0xff, 0xff, 0xe7, 0xf1,
+ 0xe7, 0xc7, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xf9, 0xff, 0xff, 0xf3, 0xf0, 0x07, 0xe3, 0xff, 0xff, 0x81, 0xff, 0xff,
+ 0xff, 0xff, 0xfe, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf9, 0xff, 0xff,
+ 0xf8, 0x07, 0x1f, 0xf1, 0xff, 0xff, 0xc3, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x8f, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf9, 0xff, 0xff, 0xfc, 0x1f, 0x9f, 0xf8, 0xff, 0xff, 0xc3,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x9f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf9,
+ 0xff, 0xff, 0xf8, 0xff, 0x9f, 0xfe, 0x7f, 0xff, 0xe3, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x8f,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf9, 0xff, 0xff, 0xf9, 0xff, 0x9f, 0xfe, 0x3f,
+ 0xff, 0xe3, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x9f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xfd, 0xff, 0xff, 0xf1, 0xff, 0x9f, 0xff, 0x9f, 0xff, 0xf3, 0xff, 0x3f, 0x3f, 0xff, 0xff,
+ 0xff, 0x9f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf9, 0xff, 0xff, 0xe1, 0xff, 0xcf,
+ 0xff, 0xc7, 0xff, 0xf3, 0xff, 0x3f, 0x9f, 0xff, 0xff, 0xff, 0x9f, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xfc, 0xff, 0xff, 0xe1, 0xff, 0x8f, 0xff, 0xe7, 0xff, 0xf3, 0xff, 0x3f, 0x9f,
+ 0xff, 0xff, 0xff, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xff, 0xff, 0xc1,
+ 0xff, 0xcf, 0xff, 0xf3, 0xff, 0xf3, 0xff, 0x3f, 0x9f, 0xff, 0xff, 0xff, 0x1f, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xff, 0xff, 0x81, 0xff, 0xcf, 0xff, 0xff, 0xff, 0xf3, 0xff,
+ 0x3f, 0x9f, 0xff, 0xff, 0xff, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xff,
+ 0xff, 0x91, 0xff, 0x8f, 0xff, 0xff, 0xff, 0xf3, 0xff, 0x3f, 0x9f, 0xff, 0xff, 0xfe, 0x3f, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0x11, 0xff, 0x9f, 0xff, 0xff, 0xff,
+ 0xf3, 0xff, 0x1f, 0x9f, 0xff, 0xff, 0xfe, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xfe, 0x7f, 0xff, 0x21, 0xff, 0x9f, 0xff, 0xff, 0xff, 0xf3, 0xff, 0xbf, 0x9f, 0xff, 0xff, 0xfe,
+ 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x7f, 0xfe, 0x20, 0xff, 0x9f, 0xff,
+ 0xff, 0xff, 0xf3, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xfe, 0x7f, 0xfe, 0x60, 0x7f, 0x9f, 0xff, 0xff, 0xff, 0xf3, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, 0xfc, 0x64, 0x3f,
+ 0x1f, 0xff, 0xff, 0xff, 0xf3, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, 0xfc, 0xe7, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xf3, 0xff, 0xff,
+ 0xff, 0x3f, 0xff, 0xf9, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x1f, 0xfc,
+ 0xe7, 0x80, 0xff, 0xff, 0xff, 0xff, 0xf3, 0xff, 0xff, 0xff, 0x3f, 0xff, 0xf1, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x9f, 0xf8, 0xe7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf3,
+ 0xff, 0xff, 0xfe, 0x7f, 0xff, 0xe3, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0x9f, 0xf9, 0xe7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf3, 0xe7, 0xff, 0xfe, 0x7f, 0xff, 0xc3, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xcf, 0xf9, 0xe7, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xf3, 0xf3, 0xff, 0xfc, 0x7f, 0xff, 0x87, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xcf, 0xf9, 0xe7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf3, 0xf3, 0xff, 0xf8, 0xff, 0xff,
+ 0x8f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc7, 0xf9, 0xe7, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xf3, 0xf9, 0xff, 0xe1, 0xff, 0xfe, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xe7, 0xf3, 0xe7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf3, 0xfc, 0x3f, 0x07,
+ 0xff, 0xfc, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe3, 0xf3, 0xe7,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xf3, 0xfe, 0x00, 0x1f, 0xff, 0xf8, 0x7f, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf3, 0xf3, 0xe7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf3, 0xff,
+ 0xe0, 0xff, 0xff, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf1,
+ 0xf3, 0xe7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf3, 0xff, 0xff, 0xff, 0xff, 0xf1, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf1, 0xe3, 0xe7, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xf7, 0xff, 0xff, 0xff, 0xff, 0xe3, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xf8, 0x83, 0xe7, 0xff, 0xfe, 0x3f, 0xff, 0xff, 0xf7, 0xff, 0xff, 0xff, 0xff, 0xc7, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x13, 0xe7, 0xff, 0xfc, 0x03,
+ 0xff, 0xff, 0xf7, 0xff, 0xff, 0xff, 0xff, 0x8f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xfc, 0x31, 0xe7, 0xff, 0xfc, 0x00, 0x7f, 0xff, 0xe7, 0xff, 0xff, 0xff, 0xfe,
+ 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x39, 0xe3, 0xff,
+ 0xfc, 0x00, 0x1f, 0xff, 0xe7, 0xff, 0xff, 0xff, 0xfc, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x31, 0xf3, 0xff, 0xfc, 0x00, 0x1f, 0xff, 0xc7, 0xff, 0xff,
+ 0xff, 0xf8, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x19,
+ 0xf3, 0xff, 0xfc, 0x00, 0x07, 0xff, 0x87, 0xff, 0xff, 0xff, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x83, 0xf3, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x07,
+ 0xff, 0xff, 0xff, 0xe1, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0x83, 0xf3, 0xff, 0xff, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0x83, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc3, 0xf3, 0xff, 0xff, 0xff, 0xff,
+ 0xf8, 0x07, 0xff, 0xff, 0xff, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xe3, 0xf1, 0xff, 0xff, 0xff, 0xff, 0xff, 0x07, 0xff, 0xff, 0xfe, 0x1f, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf1, 0xe1, 0xff, 0xfe,
+ 0x01, 0xff, 0xfe, 0x07, 0xff, 0xff, 0xf8, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0xe1, 0xff, 0xf0, 0x00, 0x3f, 0x80, 0x07, 0xff, 0xff, 0xf0,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x4c,
+ 0xff, 0xf0, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xc1, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x0c, 0xff, 0xf0, 0x00, 0x00, 0x0b, 0x87, 0xff,
+ 0xff, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0x0e, 0x7f, 0xf8, 0x00, 0x3f, 0xff, 0xc7, 0xff, 0xfe, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x86, 0x7f, 0xfe, 0x00, 0xff, 0xff,
+ 0xc3, 0xff, 0xf8, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0x80, 0x7f, 0xff, 0x87, 0xff, 0xff, 0xf3, 0xff, 0xe0, 0x7f, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x3f, 0xff, 0xff,
+ 0xff, 0xff, 0xf3, 0xff, 0x81, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xf3, 0xfe, 0x07, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x03,
+ 0xff, 0xff, 0xff, 0xff, 0xf3, 0xf0, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x7f, 0xff, 0xff, 0xff, 0xf3, 0xc0, 0x7f,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xf8, 0x1f, 0xff, 0xff, 0xff, 0xe3, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x83, 0xff, 0xff, 0xff, 0xe0,
+ 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xe0, 0x7f, 0xff, 0xff, 0xe0, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x03, 0xff,
+ 0xfe, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xf0, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xa0, 0x17, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
+};
+
+#endif /*__IMAGEDATA_H__*/
diff --git a/src/plugins/MonochromeDisplay/MonochromeDisplay.h b/src/plugins/MonochromeDisplay/MonochromeDisplay.h
deleted file mode 100644
index d7880432..00000000
--- a/src/plugins/MonochromeDisplay/MonochromeDisplay.h
+++ /dev/null
@@ -1,217 +0,0 @@
-#ifndef __MONOCHROME_DISPLAY__
-#define __MONOCHROME_DISPLAY__
-
-#include
-#include
-
-#include "../../utils/helper.h"
-#include "../../hm/hmSystem.h"
-
-#define DISP_DEFAULT_TIMEOUT 60 // in seconds
-
-
-static uint8_t bmp_logo[] PROGMEM = {
- B00000000, B00000000, // ................
- B11101100, B00110111, // ..##.######.##..
- B11101100, B00110111, // ..##.######.##..
- B11100000, B00000111, // .....######.....
- B11010000, B00001011, // ....#.####.#....
- B10011000, B00011001, // ...##..##..##...
- B10000000, B00000001, // .......##.......
- B00000000, B00000000, // ................
- B01111000, B00011110, // ...####..####...
- B11111100, B00111111, // ..############..
- B01111100, B00111110, // ..#####..#####..
- B00000000, B00000000, // ................
- B11111100, B00111111, // ..############..
- B11111110, B01111111, // .##############.
- B01111110, B01111110, // .######..######.
- B00000000, B00000000 // ................
-};
-
-
-static uint8_t bmp_arrow[] PROGMEM = {
- B00000000, B00011100, B00011100, B00001110, B00001110, B11111110, B01111111,
- B01110000, B01110000, B00110000, B00111000, B00011000, B01111111, B00111111,
- B00011110, B00001110, B00000110, B00000000, B00000000, B00000000, B00000000
-};
-
-
-template
-class MonochromeDisplay {
- public:
- MonochromeDisplay() {}
-
- void setup(display_t *cfg, HMSYSTEM *sys, uint32_t *utcTs, uint8_t disp_reset, const char *version) {
- mCfg = cfg;
- mSys = sys;
- mUtcTs = utcTs;
- mNewPayload = false;
- mLoopCnt = 0;
- mTimeout = DISP_DEFAULT_TIMEOUT; // power off timeout (after inverters go offline)
-
- u8g2_cb_t *rot = (u8g2_cb_t *)((mCfg->rot180) ? U8G2_R2 : U8G2_R0);
-
- if(mCfg->type) {
- switch(mCfg->type) {
- case 1:
- mDisplay = new U8G2_PCD8544_84X48_F_4W_HW_SPI(rot, mCfg->pin0, mCfg->pin1, disp_reset);
- break;
- case 2:
- mDisplay = new U8G2_SSD1306_128X64_NONAME_F_HW_I2C(rot, disp_reset, mCfg->pin0, mCfg->pin1);
- break;
- case 3:
- mDisplay = new U8G2_SH1106_128X64_NONAME_F_HW_I2C(rot, disp_reset, mCfg->pin0, mCfg->pin1);
- break;
- }
- mDisplay->begin();
-
- mIsLarge = ((mDisplay->getWidth() > 120) && (mDisplay->getHeight() > 60));
- calcLineHeights();
-
- mDisplay->clearBuffer();
- mDisplay->setContrast(mCfg->contrast);
- printText("Ahoy!", 0, 35);
- printText("ahoydtu.de", 2, 20);
- printText(version, 3, 46);
- mDisplay->sendBuffer();
- }
- }
-
- void payloadEventListener(uint8_t cmd) {
- mNewPayload = true;
- }
-
- void tickerSecond() {
- if(mCfg->pwrSaveAtIvOffline) {
- if(mTimeout != 0)
- mTimeout--;
- }
- if(mNewPayload || ((++mLoopCnt % 10) == 0)) {
- mNewPayload = false;
- mLoopCnt = 0;
- DataScreen();
- }
- }
-
- private:
- void DataScreen() {
- if (mCfg->type == 0)
- return;
- if(*mUtcTs == 0)
- return;
-
- float totalPower = 0;
- float totalYieldDay = 0;
- float totalYieldTotal = 0;
-
- bool isprod = false;
-
- Inverter<> *iv;
- record_t<> *rec;
- for (uint8_t i = 0; i < mSys->getNumInverters(); i++) {
- iv = mSys->getInverterByPos(i);
- rec = iv->getRecordStruct(RealTimeRunData_Debug);
- if (iv == NULL)
- continue;
-
- if (iv->isProducing(*mUtcTs))
- isprod = true;
-
- totalPower += iv->getChannelFieldValue(CH0, FLD_PAC, rec);
- totalYieldDay += iv->getChannelFieldValue(CH0, FLD_YD, rec);
- totalYieldTotal += iv->getChannelFieldValue(CH0, FLD_YT, rec);
- }
-
- mDisplay->clearBuffer();
-
- // Logos
- // pxMovement +x (0 - 6 px)
- uint8_t ex = (_mExtra % 7);
- if (isprod) {
- mDisplay->drawXBMP(5 + ex, 1, 8, 17, bmp_arrow);
- if (mCfg->logoEn)
- mDisplay->drawXBMP(mDisplay->getWidth() - 24 + ex, 2, 16, 16, bmp_logo);
- }
-
- if ((totalPower > 0) && isprod) {
- mTimeout = DISP_DEFAULT_TIMEOUT;
- mDisplay->setPowerSave(false);
- mDisplay->setContrast(mCfg->contrast);
- if (totalPower > 999)
- snprintf(_fmtText, sizeof(_fmtText), "%2.1f kW", (totalPower / 1000));
- else
- snprintf(_fmtText, sizeof(_fmtText), "%3.0f W", totalPower);
- printText(_fmtText, 0, 20);
- } else {
- printText("offline", 0, 25);
- if(mCfg->pwrSaveAtIvOffline) {
- if(mTimeout == 0)
- mDisplay->setPowerSave(true);
- }
- }
-
- snprintf(_fmtText, sizeof(_fmtText), "today: %4.0f Wh", totalYieldDay);
- printText(_fmtText, 1);
-
- snprintf(_fmtText, sizeof(_fmtText), "total: %.1f kWh", totalYieldTotal);
- printText(_fmtText, 2);
-
- IPAddress ip = WiFi.localIP();
- if (!(_mExtra % 10) && (ip)) {
- printText(ip.toString().c_str(), 3);
- } else {
- // Get current time
- if(mIsLarge)
- printText(ah::getDateTimeStr(gTimezone.toLocal(*mUtcTs)).c_str(), 3);
- else
- printText(ah::getTimeStr(gTimezone.toLocal(*mUtcTs)).c_str(), 3);
- }
- mDisplay->sendBuffer();
-
- _mExtra++;
- }
-
- void calcLineHeights() {
- uint8_t yOff = 0;
- for(uint8_t i = 0; i < 4; i++) {
- setFont(i);
- yOff += (mDisplay->getMaxCharHeight() + 1);
- mLineOffsets[i] = yOff;
- }
- }
-
- inline void setFont(uint8_t line) {
- switch (line) {
- case 0: mDisplay->setFont((mIsLarge) ? u8g2_font_ncenB14_tr : u8g2_font_lubBI14_tr); break;
- case 3: mDisplay->setFont(u8g2_font_5x8_tr); break;
- default: mDisplay->setFont((mIsLarge) ? u8g2_font_ncenB10_tr : u8g2_font_5x8_tr); break;
- }
- }
-
- void printText(const char* text, uint8_t line, uint8_t dispX = 5) {
- if(!mIsLarge)
- dispX = (line == 0) ? 10 : 5;
- setFont(line);
- if(mCfg->pxShift)
- dispX += (_mExtra % 7); // add pixel movement
- mDisplay->drawStr(dispX, mLineOffsets[line], text);
- }
-
- // private member variables
- U8G2* mDisplay;
-
- uint8_t _mExtra;
- uint16_t mTimeout; // interval at which to power save (milliseconds)
- char _fmtText[32];
-
- bool mNewPayload;
- bool mIsLarge;
- uint8_t mLoopCnt;
- uint32_t *mUtcTs;
- uint8_t mLineOffsets[5];
- display_t *mCfg;
- HMSYSTEM *mSys;
-};
-
-#endif /*__MONOCHROME_DISPLAY__*/
diff --git a/src/web/RestApi.h b/src/web/RestApi.h
index 0f18728d..e9479a0b 100644
--- a/src/web/RestApi.h
+++ b/src/web/RestApi.h
@@ -8,26 +8,25 @@
#include "../utils/dbg.h"
#ifdef ESP32
- #include "AsyncTCP.h"
+#include "AsyncTCP.h"
#else
- #include "ESPAsyncTCP.h"
+#include "ESPAsyncTCP.h"
#endif
-#include "ESPAsyncWebServer.h"
-#include "AsyncJson.h"
+#include "../appInterface.h"
#include "../hm/hmSystem.h"
#include "../utils/helper.h"
-
-#include "../appInterface.h"
+#include "AsyncJson.h"
+#include "ESPAsyncWebServer.h"
#if defined(F) && defined(ESP32)
- #undef F
- #define F(sl) (sl)
+#undef F
+#define F(sl) (sl)
#endif
const uint8_t acList[] = {FLD_UAC, FLD_IAC, FLD_PAC, FLD_F, FLD_PF, FLD_T, FLD_YT, FLD_YD, FLD_PDC, FLD_EFF, FLD_Q};
const uint8_t dcList[] = {FLD_UDC, FLD_IDC, FLD_PDC, FLD_YD, FLD_YT, FLD_IRR};
-template
+template
class RestApi {
public:
RestApi() {
@@ -159,6 +158,7 @@ class RestApi {
ep[F("record/live")] = url + F("record/live");
}
+
void onDwnldSetup(AsyncWebServerRequest *request) {
AsyncWebServerResponse *response;
@@ -255,7 +255,6 @@ class RestApi {
getSysInfo(obj.createNestedObject(F("system")));
getGeneric(obj.createNestedObject(F("generic")));
obj[F("html")] = F("Factory Reset Reboot ");
-
}
void getHtmlLogout(JsonObject obj) {
@@ -406,17 +405,19 @@ class RestApi {
}
void getDisplay(JsonObject obj) {
- obj[F("disp_type")] = (uint8_t)mConfig->plugin.display.type;
- obj[F("disp_pwr")] = (bool)mConfig->plugin.display.pwrSaveAtIvOffline;
- obj[F("logo_en")] = (bool)mConfig->plugin.display.logoEn;
- obj[F("px_shift")] = (bool)mConfig->plugin.display.pxShift;
- obj[F("rot180")] = (bool)mConfig->plugin.display.rot180;
- obj[F("contrast")] = (uint8_t)mConfig->plugin.display.contrast;
- obj[F("pinDisp0")] = mConfig->plugin.display.pin0;
- obj[F("pinDisp1")] = mConfig->plugin.display.pin1;
+ obj[F("disp_typ")] = (uint8_t)mConfig->plugin.display.type;
+ obj[F("disp_pwr")] = (bool)mConfig->plugin.display.pwrSaveAtIvOffline;
+ obj[F("disp_pxshift")] = (bool)mConfig->plugin.display.pxShift;
+ obj[F("disp_rot")] = (uint8_t)mConfig->plugin.display.rot;
+ obj[F("disp_cont")] = (uint8_t)mConfig->plugin.display.contrast;
+ obj[F("disp_clk")] = mConfig->plugin.display.disp_clk;
+ obj[F("disp_data")] = mConfig->plugin.display.disp_data;
+ obj[F("disp_cs")] = mConfig->plugin.display.disp_cs;
+ obj[F("disp_dc")] = mConfig->plugin.display.disp_dc;
+ obj[F("disp_rst")] = mConfig->plugin.display.disp_reset;
+ obj[F("disp_bsy")] = mConfig->plugin.display.disp_busy;
}
-
void getIndex(JsonObject obj) {
getGeneric(obj.createNestedObject(F("generic")));
obj[F("ts_now")] = mApp->getTimestamp();
@@ -630,8 +631,7 @@ class RestApi {
mTimezoneOffset = jsonIn[F("val")];
else if(F("discovery_cfg") == jsonIn[F("cmd")]) {
mApp->setMqttDiscoveryFlag(); // for homeassistant
- }
- else {
+ } else {
jsonOut[F("error")] = F("unknown cmd");
return false;
}
diff --git a/src/web/html/setup.html b/src/web/html/setup.html
index 7342a121..09cc4a67 100644
--- a/src/web/html/setup.html
+++ b/src/web/html/setup.html
@@ -255,28 +255,19 @@
Display Config
-
+
Turn off while inverters are offline
-
-
-
-
Enable pixel shifting
-
+
-
Rotate 180 degree
-
+
Enable Screensaver (pixel shifting)
+
-
-
Pinout
@@ -509,10 +500,10 @@
for(var j of [
["ModPwr", "ch_max_power", "Max Module Power (Wp)", 4, "[0-9]+"],
- ["ModName", "ch_name", "Module Name", 16, null],
- ["YieldCor", "ch_yield_cor", "Yield Total Correction [kWh]", 16, "[0-9-]+"]]) {
+ ["ModName", "ch_name", "Module Name", 15, null],
+ ["YieldCor", "ch_yield_cor", "Yield Total Correction [kWh]", 8, "[0-9-]+"]]) {
- var cl = (re.test(obj["serial"])) ? null : ["hide"];
+ var cl = (re.test(obj["serial"])) ? "" : " hide";
i = 0;
arrIn = [];
@@ -524,7 +515,7 @@
}
iv.append(
- ml("div", {class: "row mb-2 mb-sm-3", id: "row" + id + j[0]}, [
+ ml("div", {class: "row mb-2 mb-sm-3" + cl, id: "row" + id + j[0]}, [
ml("div", {class: "col-12 col-sm-3 my-2"}, j[2]),
ml("div", {class: "col-12 col-sm-9"},
ml("div", {class: "row"}, arrIn)
@@ -639,12 +630,14 @@
}
function parseDisplay(obj, type) {
- for(var i of [["logoEn", "logo_en"], ["dispPwr", "disp_pwr"], ["dispPxSh", "px_shift"], ["disp180", "rot180"]])
- document.getElementsByName(i[0])[0].checked = obj[i[1]];
+ for(var i of ["disp_pwr", "disp_pxshift"])
+ document.getElementsByName(i)[0].checked = obj[i];
var e = document.getElementById("dispPins");
- pins = [['SCL / CS', 'pinDisp0'], ['SDA / DC', 'pinDisp1']];
+ pins = [['clock', 'disp_clk'], ['data', 'disp_data'], ['cs', 'disp_cs'], ['dc', 'disp_dc'], ['reset', 'disp_rst'], ['busy', 'disp_bsy']];
for(p of pins) {
+ if(("ESP8266" == type) && p[0] == "cs")
+ break;
e.append(
ml("div", {class: "row mb-3"}, [
ml("div", {class: "col-12 col-sm-3 my-2"}, p[0].toUpperCase()),
@@ -655,18 +648,31 @@
);
}
- var opts = [[0, "None"], [1, "Nokia5110"], [2, "SSD1306 0.96\""], [3, "SH1106 1.3\""]];
+ var opts = [[0, "None"], [2, "SSD1306 0.96\""], [3, "SH1106 1.3\""]];
+ if("ESP32" == type) {
+ opts.push([1, "Nokia5110"]);
+ opts.push([10, "ePaper"]);
+ }
document.getElementById("dispType").append(
ml("div", {class: "row mb-3"}, [
ml("div", {class: "col-12 col-sm-3 my-2"}, "Type"),
- ml("div", {class: "col-12 col-sm-9"}, sel("dispType", opts, obj["disp_type"]))
+ ml("div", {class: "col-12 col-sm-9"}, sel("disp_typ", opts, obj["disp_typ"]))
])
);
- e = document.getElementById("contrast");
- for(var i = 30; i < 101; i += 2) {
- e.appendChild(opt(i, i, (i == obj["contrast"])));
+ opts = [[0, "0°"], [2, "180°"]];
+ if("ESP32" == type) {
+ opts.push([1, "90°"]);
+ opts.push([3, "270°"]);
}
+ document.getElementById("dispRot").append(
+ ml("div", {class: "row mb-3"}, [
+ ml("div", {class: "col-12 col-sm-3 my-2"}, "Rotation"),
+ ml("div", {class: "col-12 col-sm-9"}, sel("disp_rot", opts, obj["disp_rot"]))
+ ])
+ );
+
+ document.getElementsByName("disp_cont")[0].value = obj["disp_cont"];
}
function parse(root) {
diff --git a/src/web/html/style.css b/src/web/html/style.css
index 1b0f8c48..ca4b0c9a 100644
--- a/src/web/html/style.css
+++ b/src/web/html/style.css
@@ -595,3 +595,39 @@ div.hr {
background-color: var(--primary);
color: #fff;
}
+
+
+.css-tooltip{
+ position: relative;
+}
+.css-tooltip:hover:after{
+ content:attr(data-tooltip);
+ background:#000;
+ padding:5px;
+ border-radius:3px;
+ display: inline-block;
+ position: absolute;
+ transform: translate(-50%,-100%);
+ margin:0 auto;
+ color:#FFF;
+ min-width:100px;
+ min-width:150px;
+ top:-5px;
+ left: 50%;
+ text-align:center;
+}
+.css-tooltip:hover:before {
+ top:-5px;
+ left: 50%;
+ border: solid transparent;
+ content: " ";
+ height: 0;
+ width: 0;
+ position: absolute;
+ pointer-events: none;
+ border-color: rgba(0, 0, 0, 0);
+ border-top-color: #000;
+ border-width: 5px;
+ margin-left: -5px;
+ transform: translate(0,0px);
+}
diff --git a/src/web/html/visualization.html b/src/web/html/visualization.html
index 4c3c6a47..9b6dd5d5 100644
--- a/src/web/html/visualization.html
+++ b/src/web/html/visualization.html
@@ -46,7 +46,7 @@
}
function numMid(val, unit, des) {
- return ml("div", {class: "col-6 col-sm-4 col-md-3"}, [
+ return ml("div", {class: "col-6 col-sm-4 col-md-3 mb-2"}, [
ml("div", {class: "row"},
ml("div", {class: "col"}, [
ml("span", {class: "fs-6"}, String(val)),
@@ -93,13 +93,14 @@
total[2] += obj.ch[0][6]; // YieldTotal
total[3] += obj.ch[0][8]; // P_DC
total[4] += obj.ch[0][10]; // Q_AC
+ var t = span(" ° C");
return ml("div", {class: "row"},
ml("div", {class: "col"}, [
ml("div", {class: "p-2 iv-h"},
ml("div", {class: "row"}, [
ml("div", {class: "col mx-2 mx-md-1"}, obj.name),
ml("div", {class: "col a-c"}, "Power limit " + ((obj.power_limit_read == 65535) ? "n/a" : (obj.power_limit_read + " %"))),
- ml("div", {class: "col a-r mx-2 mx-md-1"}, String(obj.ch[0][5]) + " C")
+ ml("div", {class: "col a-r mx-2 mx-md-1"}, String(obj.ch[0][5]) + t.innerHTML)
])
),
ml("div", {class: "p-2 iv-bg"}, [
@@ -115,7 +116,7 @@
numMid(obj.ch[0][1], "A", "Current"),
numMid(obj.ch[0][3], "Hz", "Frequency"),
numMid(obj.ch[0][9], "%", "Efficiency"),
- numMid(obj.ch[0][10], "var", "Reactive Power"),
+ numMid(obj.ch[0][10], "VAr", "Reactive Power"),
numMid(obj.ch[0][4], "", "Power Factor")
])
])
diff --git a/src/web/web.h b/src/web/web.h
index 2c4f1ad8..15b22eb0 100644
--- a/src/web/web.h
+++ b/src/web/web.h
@@ -8,38 +8,35 @@
#include "../utils/dbg.h"
#ifdef ESP32
- #include "AsyncTCP.h"
- #include "Update.h"
+#include "AsyncTCP.h"
+#include "Update.h"
#else
- #include "ESPAsyncTCP.h"
+#include "ESPAsyncTCP.h"
#endif
-#include "ESPAsyncWebServer.h"
-
#include "../appInterface.h"
-
#include "../hm/hmSystem.h"
#include "../utils/helper.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 "ESPAsyncWebServer.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/setup_html.h"
-#include "html/h/visualization_html.h"
-#include "html/h/update_html.h"
+#include "html/h/index_html.h"
+#include "html/h/login_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/update_html.h"
+#include "html/h/visualization_html.h"
#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
+template
class Web {
- public:
+ public:
Web(void) : mWeb(80), mEvts("/events") {
mProtected = true;
mLogoutTimeout = 0;
@@ -99,18 +96,18 @@ class Web {
}
void tickSecond() {
- if(0 != mLogoutTimeout) {
+ if (0 != mLogoutTimeout) {
mLogoutTimeout -= 1;
- if(0 == mLogoutTimeout) {
- if(strlen(mConfig->sys.adminPwd) > 0)
+ if (0 == mLogoutTimeout) {
+ if (strlen(mConfig->sys.adminPwd) > 0)
mProtected = true;
}
DPRINTLN(DBG_DEBUG, "auto logout in " + String(mLogoutTimeout));
}
- if(mSerialClientConnnected) {
- if(mSerialBufFill > 0) {
+ if (mSerialClientConnnected) {
+ if (mSerialBufFill > 0) {
mEvts.send(mSerialBuf, "serial", millis());
memset(mSerialBuf, 0, WEB_SERIAL_BUF_SIZE);
mSerialBufFill = 0;
@@ -133,23 +130,23 @@ class Web {
void showUpdate2(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) {
mApp->setOnUpdate();
- if(!index) {
+ if (!index) {
Serial.printf("Update Start: %s\n", filename.c_str());
- #ifndef ESP32
+ #ifndef ESP32
Update.runAsync(true);
- #endif
- if(!Update.begin((ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000)) {
+ #endif
+ if (!Update.begin((ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000)) {
Update.printError(Serial);
}
}
- if(!Update.hasError()) {
- if(Update.write(data, len) != len){
+ if (!Update.hasError()) {
+ if (Update.write(data, len) != len) {
Update.printError(Serial);
}
}
- if(final) {
- if(Update.end(true)) {
- Serial.printf("Update Success: %uB\n", index+len);
+ if (final) {
+ if (Update.end(true)) {
+ Serial.printf("Update Success: %uB\n", index + len);
} else {
Update.printError(Serial);
}
@@ -157,27 +154,26 @@ class Web {
}
void onUpload2(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) {
- if(!index) {
+ if (!index) {
mUploadFail = false;
mUploadFp = LittleFS.open("/tmp.json", "w");
- if(!mUploadFp) {
+ if (!mUploadFp) {
DPRINTLN(DBG_ERROR, F("can't open file!"));
mUploadFail = true;
mUploadFp.close();
}
}
mUploadFp.write(data, len);
- if(final) {
+ if (final) {
mUploadFp.close();
File fp = LittleFS.open("/tmp.json", "r");
- if(!fp)
+ if (!fp)
mUploadFail = true;
else {
- if(!mApp->readSettings("tmp.json")) {
+ if (!mApp->readSettings("tmp.json")) {
mUploadFail = true;
DPRINTLN(DBG_ERROR, F("upload JSON error!"));
- }
- else
+ } else
mApp->saveSettings();
}
DPRINTLN(DBG_INFO, F("upload finished!"));
@@ -185,18 +181,17 @@ class Web {
}
void serialCb(String msg) {
- if(!mSerialClientConnnected)
+ if (!mSerialClientConnnected)
return;
msg.replace("\r\n", "");
- if(mSerialAddTime) {
- if((9 + mSerialBufFill) < WEB_SERIAL_BUF_SIZE) {
- if(mApp->getTimestamp() > 0) {
+ if (mSerialAddTime) {
+ if ((9 + mSerialBufFill) < WEB_SERIAL_BUF_SIZE) {
+ if (mApp->getTimestamp() > 0) {
strncpy(&mSerialBuf[mSerialBufFill], mApp->getTimeStr(mApp->getTimezoneOffset()).c_str(), 9);
mSerialBufFill += 9;
}
- }
- else {
+ } else {
mSerialBufFill = 0;
mEvts.send("webSerial, buffer overflow!", "serial", millis());
return;
@@ -204,15 +199,14 @@ class Web {
mSerialAddTime = false;
}
- if(msg.endsWith(""))
+ if (msg.endsWith(""))
mSerialAddTime = true;
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);
mSerialBufFill += length;
- }
- else {
+ } else {
mSerialBufFill = 0;
mEvts.send("webSerial, buffer overflow!", "serial", millis());
}
@@ -220,13 +214,13 @@ class Web {
private:
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"));
- 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"));
- 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"));
- 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"));
else
request->redirect(F("/login"));
@@ -235,8 +229,8 @@ class Web {
void onUpdate(AsyncWebServerRequest *request) {
DPRINTLN(DBG_VERBOSE, F("onUpdate"));
- if(CHECK_MASK(mConfig->sys.protectionMask, PROT_MASK_UPDATE)) {
- if(mProtected) {
+ if (CHECK_MASK(mConfig->sys.protectionMask, PROT_MASK_UPDATE)) {
+ if (mProtected) {
checkRedirect(request);
return;
}
@@ -251,7 +245,7 @@ class Web {
bool reboot = !Update.hasError();
String html = F("Update Update: ");
- if(reboot)
+ if (reboot)
html += "success";
else
html += "failed";
@@ -267,7 +261,7 @@ class Web {
bool reboot = !mUploadFail;
String html = F("Upload Upload: ");
- if(reboot)
+ if (reboot)
html += "success";
else
html += "failed";
@@ -284,7 +278,7 @@ class Web {
mSerialClientConnnected = true;
- if(client->lastId())
+ if (client->lastId())
DPRINTLN(DBG_VERBOSE, "Client reconnected! Last message ID that it got is: " + String(client->lastId()));
client->send("hello!", NULL, millis(), 1000);
@@ -293,8 +287,8 @@ class Web {
void onIndex(AsyncWebServerRequest *request) {
DPRINTLN(DBG_VERBOSE, F("onIndex"));
- if(CHECK_MASK(mConfig->sys.protectionMask, PROT_MASK_INDEX)) {
- if(mProtected) {
+ if (CHECK_MASK(mConfig->sys.protectionMask, PROT_MASK_INDEX)) {
+ if (mProtected) {
checkRedirect(request);
return;
}
@@ -308,8 +302,8 @@ class Web {
void onLogin(AsyncWebServerRequest *request) {
DPRINTLN(DBG_VERBOSE, F("onLogin"));
- if(request->args() > 0) {
- if(String(request->arg("pwd")) == String(mConfig->sys.adminPwd)) {
+ if (request->args() > 0) {
+ if (String(request->arg("pwd")) == String(mConfig->sys.adminPwd)) {
mProtected = false;
request->redirect("/");
}
@@ -323,7 +317,7 @@ class Web {
void onLogout(AsyncWebServerRequest *request) {
DPRINTLN(DBG_VERBOSE, F("onLogout"));
- if(mProtected) {
+ if (mProtected) {
checkRedirect(request);
return;
}
@@ -338,7 +332,7 @@ class Web {
void onColor(AsyncWebServerRequest *request) {
DPRINTLN(DBG_VERBOSE, F("onColor"));
AsyncWebServerResponse *response;
- if(mConfig->sys.darkMode)
+ if (mConfig->sys.darkMode)
response = request->beginResponse_P(200, F("text/css"), colorDark_css, colorDark_css_len);
else
response = request->beginResponse_P(200, F("text/css"), colorBright_css, colorBright_css_len);
@@ -370,7 +364,7 @@ class Web {
}
void showNotFound(AsyncWebServerRequest *request) {
- if(mProtected)
+ if (mProtected)
checkRedirect(request);
else
request->redirect("/setup");
@@ -384,7 +378,7 @@ class Web {
}
void showErase(AsyncWebServerRequest *request) {
- if(mProtected) {
+ if (mProtected) {
checkRedirect(request);
return;
}
@@ -395,7 +389,7 @@ class Web {
}
void showFactoryRst(AsyncWebServerRequest *request) {
- if(mProtected) {
+ if (mProtected) {
checkRedirect(request);
return;
}
@@ -403,26 +397,24 @@ class Web {
DPRINTLN(DBG_VERBOSE, F("showFactoryRst"));
String content = "";
int refresh = 3;
- if(request->args() > 0) {
- if(request->arg("reset").toInt() == 1) {
+ if (request->args() > 0) {
+ if (request->arg("reset").toInt() == 1) {
refresh = 10;
- if(mApp->eraseSettings(true))
+ if (mApp->eraseSettings(true))
content = F("factory reset: success\n\nrebooting ... ");
else
content = F("factory reset: failed\n\nrebooting ... ");
- }
- else {
+ } else {
content = F("factory reset: aborted");
refresh = 3;
}
- }
- else {
+ } else {
content = F("Factory Reset "
"RESET CANCEL
");
refresh = 120;
}
request->send(200, F("text/html; charset=UTF-8"), F("Factory Reset ") + content + F(""));
- if(refresh == 10) {
+ if (refresh == 10) {
delay(1000);
ESP.restart();
}
@@ -431,8 +423,8 @@ class Web {
void onSetup(AsyncWebServerRequest *request) {
DPRINTLN(DBG_VERBOSE, F("onSetup"));
- if(CHECK_MASK(mConfig->sys.protectionMask, PROT_MASK_SETUP)) {
- if(mProtected) {
+ if (CHECK_MASK(mConfig->sys.protectionMask, PROT_MASK_SETUP)) {
+ if (mProtected) {
checkRedirect(request);
return;
}
@@ -446,33 +438,33 @@ class Web {
void showSave(AsyncWebServerRequest *request) {
DPRINTLN(DBG_VERBOSE, F("showSave"));
- if(mProtected) {
+ if (mProtected) {
checkRedirect(request);
return;
}
- if(request->args() == 0)
+ if (request->args() == 0)
return;
char buf[20] = {0};
// general
- if(request->arg("ssid") != "")
+ if (request->arg("ssid") != "")
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);
- if(request->arg("device") != "")
+ if (request->arg("device") != "")
request->arg("device").toCharArray(mConfig->sys.deviceName, DEVNAME_LEN);
mConfig->sys.darkMode = (request->arg("darkMode") == "on");
// protection
- if(request->arg("adminpwd") != "{PWD}") {
+ if (request->arg("adminpwd") != "{PWD}") {
request->arg("adminpwd").toCharArray(mConfig->sys.adminPwd, PWD_LEN);
mProtected = (strlen(mConfig->sys.adminPwd) > 0);
}
mConfig->sys.protectionMask = 0x0000;
- for(uint8_t i = 0; i < 6; i++) {
- if(request->arg("protMask" + String(i)) == "on")
+ for (uint8_t i = 0; i < 6; i++) {
+ if (request->arg("protMask" + String(i)) == "on")
mConfig->sys.protectionMask |= (1 << i);
}
@@ -488,16 +480,15 @@ class Web {
request->arg("ipGateway").toCharArray(buf, 20);
ah::ip2Arr(mConfig->sys.ip.gateway, buf);
-
// inverter
Inverter<> *iv;
- for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i ++) {
+ for (uint8_t i = 0; i < MAX_NUM_INVERTERS; i++) {
iv = mSys->getInverterByPos(i, false);
// enable communication
iv->config->enabled = (request->arg("inv" + String(i) + "Enable") == "on");
// address
request->arg("inv" + String(i) + "Addr").toCharArray(buf, 20);
- if(strlen(buf) == 0)
+ if (strlen(buf) == 0)
memset(buf, 0, 20);
iv->config->serial.u64 = ah::Serial2u64(buf);
switch(iv->config->serial.b[4]) {
@@ -511,7 +502,7 @@ class Web {
request->arg("inv" + String(i) + "Name").toCharArray(iv->config->name, MAX_NAME_LENGTH);
// max channel power / name
- for(uint8_t j = 0; j < 4; j++) {
+ for (uint8_t j = 0; j < 4; j++) {
iv->config->yieldCor[j] = request->arg("inv" + String(i) + "YieldCor" + String(j)).toInt();
iv->config->chMaxPwr[j] = request->arg("inv" + String(i) + "ModPwr" + String(j)).toInt() & 0xffff;
request->arg("inv" + String(i) + "ModName" + String(j)).toCharArray(iv->config->chName[j], MAX_NAME_LENGTH);
@@ -519,17 +510,17 @@ class Web {
iv->initialized = true;
}
- if(request->arg("invInterval") != "")
+ if (request->arg("invInterval") != "")
mConfig->nrf.sendInterval = request->arg("invInterval").toInt();
- if(request->arg("invRetry") != "")
+ if (request->arg("invRetry") != "")
mConfig->nrf.maxRetransPerPyld = request->arg("invRetry").toInt();
mConfig->inst.rstYieldMidNight = (request->arg("invRstMid") == "on");
- mConfig->inst.rstValsCommStop = (request->arg("invRstComStop") == "on");
- mConfig->inst.rstValsNotAvail = (request->arg("invRstNotAvail") == "on");
+ mConfig->inst.rstValsCommStop = (request->arg("invRstComStop") == "on");
+ mConfig->inst.rstValsNotAvail = (request->arg("invRstNotAvail") == "on");
// pinout
uint8_t pin;
- for(uint8_t i = 0; i < 5; i ++) {
+ for (uint8_t i = 0; i < 5; i++) {
pin = request->arg(String(pinArgNames[i])).toInt();
switch(i) {
default: mConfig->nrf.pinCs = ((pin != 0xff) ? pin : DEF_CS_PIN); break;
@@ -544,13 +535,13 @@ class Web {
mConfig->nrf.amplifierPower = request->arg("rf24Power").toInt() & 0x03;
// ntp
- if(request->arg("ntpAddr") != "") {
+ if (request->arg("ntpAddr") != "") {
request->arg("ntpAddr").toCharArray(mConfig->ntp.addr, NTP_ADDR_LEN);
mConfig->ntp.port = request->arg("ntpPort").toInt() & 0xffff;
}
// sun
- if(request->arg("sunLat") == "" || (request->arg("sunLon") == "")) {
+ if (request->arg("sunLat") == "" || (request->arg("sunLon") == "")) {
mConfig->sun.lat = 0.0;
mConfig->sun.lon = 0.0;
mConfig->sun.disNightCom = false;
@@ -563,44 +554,45 @@ class Web {
}
// mqtt
- if(request->arg("mqttAddr") != "") {
+ if (request->arg("mqttAddr") != "") {
String addr = request->arg("mqttAddr");
addr.trim();
addr.toCharArray(mConfig->mqtt.broker, MQTT_ADDR_LEN);
- }
- else
+ } else
mConfig->mqtt.broker[0] = '\0';
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("mqttTopic").toCharArray(mConfig->mqtt.topic, MQTT_TOPIC_LEN);
mConfig->mqtt.port = request->arg("mqttPort").toInt();
mConfig->mqtt.interval = request->arg("mqttInterval").toInt();
// serial console
- if(request->arg("serIntvl") != "") {
+ if (request->arg("serIntvl") != "") {
mConfig->serial.interval = request->arg("serIntvl").toInt() & 0xffff;
- mConfig->serial.debug = (request->arg("serDbg") == "on");
+ mConfig->serial.debug = (request->arg("serDbg") == "on");
mConfig->serial.showIv = (request->arg("serEn") == "on");
// Needed to log TX buffers to serial console
mSys->Radio.mSerialDebug = mConfig->serial.debug;
}
// display
- mConfig->plugin.display.pwrSaveAtIvOffline = (request->arg("dispPwr") == "on");
- mConfig->plugin.display.logoEn = (request->arg("logoEn") == "on");
- mConfig->plugin.display.pxShift = (request->arg("dispPxSh") == "on");
- mConfig->plugin.display.rot180 = (request->arg("disp180") == "on");
- mConfig->plugin.display.type = request->arg("dispType").toInt();
- mConfig->plugin.display.contrast = request->arg("dispCont").toInt();
- mConfig->plugin.display.pin0 = request->arg("pinDisp0").toInt();
- mConfig->plugin.display.pin1 = request->arg("pinDisp1").toInt();
-
+ mConfig->plugin.display.pwrSaveAtIvOffline = (request->arg("disp_pwr") == "on");
+ mConfig->plugin.display.pxShift = (request->arg("disp_pxshift") == "on");
+ mConfig->plugin.display.rot = request->arg("disp_rot").toInt();
+ mConfig->plugin.display.type = request->arg("disp_typ").toInt();
+ mConfig->plugin.display.contrast = request->arg("disp_cont").toInt();
+ mConfig->plugin.display.disp_data = request->arg("disp_data").toInt();
+ mConfig->plugin.display.disp_clk = request->arg("disp_clk").toInt();
+ mConfig->plugin.display.disp_cs = request->arg("disp_cs").toInt();
+ mConfig->plugin.display.disp_reset = request->arg("disp_rst").toInt();
+ mConfig->plugin.display.disp_busy = request->arg("disp_bsy").toInt();
+ mConfig->plugin.display.disp_dc = request->arg("disp_dc").toInt();
mApp->saveSettings();
- if(request->arg("reboot") == "on")
+ if (request->arg("reboot") == "on")
onReboot(request);
else {
AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html; charset=UTF-8"), system_html, system_html_len);
@@ -612,8 +604,8 @@ class Web {
void onLive(AsyncWebServerRequest *request) {
DPRINTLN(DBG_VERBOSE, F("onLive"));
- if(CHECK_MASK(mConfig->sys.protectionMask, PROT_MASK_LIVE)) {
- if(mProtected) {
+ if (CHECK_MASK(mConfig->sys.protectionMask, PROT_MASK_LIVE)) {
+ if (mProtected) {
checkRedirect(request);
return;
}
@@ -700,8 +692,8 @@ class Web {
void onSerial(AsyncWebServerRequest *request) {
DPRINTLN(DBG_VERBOSE, F("onSerial"));
- if(CHECK_MASK(mConfig->sys.protectionMask, PROT_MASK_SERIAL)) {
- if(mProtected) {
+ if (CHECK_MASK(mConfig->sys.protectionMask, PROT_MASK_SERIAL)) {
+ if (mProtected) {
checkRedirect(request);
return;
}
@@ -715,8 +707,8 @@ class Web {
void onSystem(AsyncWebServerRequest *request) {
DPRINTLN(DBG_VERBOSE, F("onSystem"));
- if(CHECK_MASK(mConfig->sys.protectionMask, PROT_MASK_SYSTEM)) {
- if(mProtected) {
+ if (CHECK_MASK(mConfig->sys.protectionMask, PROT_MASK_SYSTEM)) {
+ if (mProtected) {
checkRedirect(request);
return;
}
@@ -838,8 +830,7 @@ class Web {
// TODO: find the right one channel with the alarm id
alarmChannelId = 0;
// printf("AlarmData Length %d\n",rec->length);
- if (alarmChannelId < rec->length)
- {
+ if (alarmChannelId < rec->length) {
//uint8_t channel = rec->assign[alarmChannelId].ch;
std::tie(promUnit, promType) = convertToPromUnits(iv->getUnit(alarmChannelId, rec));
snprintf(type, sizeof(type), "# TYPE ahoy_solar_%s%s %s", iv->getFieldName(alarmChannelId, rec), promUnit.c_str(), promType.c_str());