mirror of
https://github.com/lumapu/ahoy.git
synced 2025-05-25 14:56:11 +02:00
Merge branch 'development03' into hms
This commit is contained in:
commit
7ce78ab56e
48 changed files with 2854 additions and 1511 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -6,6 +6,7 @@
|
||||||
.vscode/extensions.json
|
.vscode/extensions.json
|
||||||
src/config/config_override.h
|
src/config/config_override.h
|
||||||
src/web/html/h/*
|
src/web/html/h/*
|
||||||
|
src/web/html/tmp/*
|
||||||
/**/Debug
|
/**/Debug
|
||||||
/**/v16/*
|
/**/v16/*
|
||||||
*.db
|
*.db
|
||||||
|
|
|
@ -306,7 +306,8 @@ To get the information open the URL `/api/record/info` on your AhoyDTU. The info
|
||||||
| chehrlic | HM-600 | | 1.0.10 | 2021 | 11-01 | 104 | | |
|
| chehrlic | HM-600 | | 1.0.10 | 2021 | 11-01 | 104 | | |
|
||||||
| chehrlic | TSOL-M800de | | 1.0.10 | 2021 | 11-01 | 104 | | |
|
| chehrlic | TSOL-M800de | | 1.0.10 | 2021 | 11-01 | 104 | | |
|
||||||
| B5r1oJ0A9G | HM-800 | | 1.0.10 | 2021 | | 104 | | |
|
| B5r1oJ0A9G | HM-800 | | 1.0.10 | 2021 | | 104 | | |
|
||||||
| | | | | | | | | |
|
| B5r1oJ0A9G | HM-800 | | 1.0.10 | 2021 | | 104 | | |
|
||||||
|
| tomquist | TSOL-M1600 | | 1.0.12 | 2020 | 06-24 | 100 | | |
|
||||||
| | | | | | | | | |
|
| | | | | | | | | |
|
||||||
|
|
||||||
## Developer Information about Command Queue
|
## Developer Information about Command Queue
|
||||||
|
|
|
@ -2,6 +2,28 @@
|
||||||
|
|
||||||
(starting from release version `0.5.66`)
|
(starting from release version `0.5.66`)
|
||||||
|
|
||||||
|
## 0.5.94
|
||||||
|
* added ePaper (for ESP32 only!), thx @dAjaY85 #735
|
||||||
|
* improved `/live` margins #732
|
||||||
|
* renamed `var` to `VAr` #732
|
||||||
|
|
||||||
|
## 0.5.93
|
||||||
|
* improved web API for `live`
|
||||||
|
* added dark mode option
|
||||||
|
* converted all forms to reponsive design
|
||||||
|
* repaired menu with password protection #720, #716, #709
|
||||||
|
* merged MI series fixes #729
|
||||||
|
|
||||||
|
## 0.5.92
|
||||||
|
* fix mobile menu
|
||||||
|
* fix inverters in select `serial.html` #709
|
||||||
|
|
||||||
|
## 0.5.91
|
||||||
|
* improved html and navi, navi is visible even when API dies #660
|
||||||
|
* reduced maximum allowed JSON size for API to 6000Bytes #660
|
||||||
|
* small fix: output command at `prepareDevInformCmd` #692
|
||||||
|
* improved inverter handling #671
|
||||||
|
|
||||||
## 0.5.90
|
## 0.5.90
|
||||||
* merged PR #684, #698, #705
|
* merged PR #684, #698, #705
|
||||||
* webserial minor overflow fix #660
|
* webserial minor overflow fix #660
|
||||||
|
|
7
src/LICENSE
Normal file
7
src/LICENSE
Normal file
|
@ -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!
|
93
src/app.cpp
93
src/app.cpp
|
@ -21,16 +21,10 @@ void app::setup() {
|
||||||
|
|
||||||
resetSystem();
|
resetSystem();
|
||||||
|
|
||||||
/*DBGPRINTLN("--- start");
|
|
||||||
DBGPRINTLN(String(ESP.getFreeHeap()));
|
|
||||||
DBGPRINTLN(String(ESP.getHeapFragmentation()));
|
|
||||||
DBGPRINTLN(String(ESP.getMaxFreeBlockSize()));*/
|
|
||||||
|
|
||||||
|
|
||||||
mSettings.setup();
|
mSettings.setup();
|
||||||
mSettings.getPtr(mConfig);
|
mSettings.getPtr(mConfig);
|
||||||
DPRINT(DBG_INFO, F("Settings valid: "));
|
DPRINT(DBG_INFO, F("Settings valid: "));
|
||||||
if(mSettings.getValid())
|
if (mSettings.getValid())
|
||||||
DBGPRINTLN(F("true"));
|
DBGPRINTLN(F("true"));
|
||||||
else
|
else
|
||||||
DBGPRINTLN(F("false"));
|
DBGPRINTLN(F("false"));
|
||||||
|
@ -67,7 +61,7 @@ void app::setup() {
|
||||||
mMiPayload.setup(this, &mSys, &mNrfRadio, &mStat, mConfig->nrf.maxRetransPerPyld, &mTimestamp);
|
mMiPayload.setup(this, &mSys, &mNrfRadio, &mStat, mConfig->nrf.maxRetransPerPyld, &mTimestamp);
|
||||||
mMiPayload.enableSerialDebug(mConfig->serial.debug);
|
mMiPayload.enableSerialDebug(mConfig->serial.debug);
|
||||||
|
|
||||||
if(!mNrfRadio.isChipConnected())
|
if(!mNrfRadio.isChipConnected())
|
||||||
DPRINTLN(DBG_WARN, F("WARNING! your NRF24 module can't be reached, check the wiring"));
|
DPRINTLN(DBG_WARN, F("WARNING! your NRF24 module can't be reached, check the wiring"));
|
||||||
}
|
}
|
||||||
if(mConfig->cmt.enabled) {
|
if(mConfig->cmt.enabled) {
|
||||||
|
@ -81,6 +75,8 @@ void app::setup() {
|
||||||
DBGPRINTLN(String(ESP.getHeapFragmentation()));
|
DBGPRINTLN(String(ESP.getHeapFragmentation()));
|
||||||
DBGPRINTLN(String(ESP.getMaxFreeBlockSize()));*/
|
DBGPRINTLN(String(ESP.getMaxFreeBlockSize()));*/
|
||||||
|
|
||||||
|
//if (!mSys.Radio.isChipConnected())
|
||||||
|
// DPRINTLN(DBG_WARN, F("WARNING! your NRF24 module can't be reached, check the wiring"));
|
||||||
|
|
||||||
// when WiFi is in client mode, then enable mqtt broker
|
// when WiFi is in client mode, then enable mqtt broker
|
||||||
#if !defined(AP_ONLY)
|
#if !defined(AP_ONLY)
|
||||||
|
@ -99,18 +95,18 @@ void app::setup() {
|
||||||
mApi.setup(this, &mSys, &mNrfRadio, mWeb.getWebSrvPtr(), mConfig);
|
mApi.setup(this, &mSys, &mNrfRadio, mWeb.getWebSrvPtr(), mConfig);
|
||||||
|
|
||||||
// Plugins
|
// Plugins
|
||||||
if(mConfig->plugin.display.type != 0)
|
if (mConfig->plugin.display.type != 0)
|
||||||
mMonoDisplay.setup(&mConfig->plugin.display, &mSys, &mTimestamp, 0xff, mVersion);
|
mDisplay.setup(&mConfig->plugin.display, &mSys, &mTimestamp, 0xff, mVersion);
|
||||||
|
|
||||||
mPubSerial.setup(mConfig, &mSys, &mTimestamp);
|
mPubSerial.setup(mConfig, &mSys, &mTimestamp);
|
||||||
|
|
||||||
regularTickers();
|
regularTickers();
|
||||||
|
|
||||||
|
|
||||||
/*DBGPRINTLN("--- end setup");
|
// DBGPRINTLN("--- end setup");
|
||||||
DBGPRINTLN(String(ESP.getFreeHeap()));
|
// DBGPRINTLN(String(ESP.getFreeHeap()));
|
||||||
DBGPRINTLN(String(ESP.getHeapFragmentation()));
|
// DBGPRINTLN(String(ESP.getHeapFragmentation()));
|
||||||
DBGPRINTLN(String(ESP.getMaxFreeBlockSize()));*/
|
// DBGPRINTLN(String(ESP.getMaxFreeBlockSize()));
|
||||||
}
|
}
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
|
@ -137,8 +133,8 @@ void app::loopStandard(void) {
|
||||||
mStat.frmCnt++;
|
mStat.frmCnt++;
|
||||||
|
|
||||||
Inverter<> *iv = mSys.findInverter(&p->packet[1]);
|
Inverter<> *iv = mSys.findInverter(&p->packet[1]);
|
||||||
if(NULL != iv) {
|
if (NULL != iv) {
|
||||||
if(IV_HM == iv->ivGen)
|
if (IV_HM == iv->ivGen)
|
||||||
mPayload.add(iv, p);
|
mPayload.add(iv, p);
|
||||||
else
|
else
|
||||||
mMiPayload.add(iv, p);
|
mMiPayload.add(iv, p);
|
||||||
|
@ -180,7 +176,7 @@ void app::loopStandard(void) {
|
||||||
mHmsPayload.loop();
|
mHmsPayload.loop();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if(mMqttEnabled)
|
if (mMqttEnabled)
|
||||||
mMqtt.loop();
|
mMqtt.loop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -194,7 +190,7 @@ void app::loopWifi(void) {
|
||||||
void app::onWifi(bool gotIp) {
|
void app::onWifi(bool gotIp) {
|
||||||
DPRINTLN(DBG_DEBUG, F("onWifi"));
|
DPRINTLN(DBG_DEBUG, F("onWifi"));
|
||||||
ah::Scheduler::resetTicker();
|
ah::Scheduler::resetTicker();
|
||||||
regularTickers(); // reinstall regular tickers
|
regularTickers(); // reinstall regular tickers
|
||||||
if (gotIp) {
|
if (gotIp) {
|
||||||
mInnerLoopCb = std::bind(&app::loopStandard, this);
|
mInnerLoopCb = std::bind(&app::loopStandard, this);
|
||||||
every(std::bind(&app::tickSend, this), mConfig->nrf.sendInterval, "tSend");
|
every(std::bind(&app::tickSend, this), mConfig->nrf.sendInterval, "tSend");
|
||||||
|
@ -203,14 +199,13 @@ void app::onWifi(bool gotIp) {
|
||||||
everySec(std::bind(&CmtRadioType::tickSecond, &mCmtRadio), "tsCmt");
|
everySec(std::bind(&CmtRadioType::tickSecond, &mCmtRadio), "tsCmt");
|
||||||
#endif
|
#endif
|
||||||
mMqttReconnect = true;
|
mMqttReconnect = true;
|
||||||
mSunrise = 0; // needs to be set to 0, to reinstall sunrise and ivComm tickers!
|
mSunrise = 0; // needs to be set to 0, to reinstall sunrise and ivComm tickers!
|
||||||
once(std::bind(&app::tickNtpUpdate, this), 2, "ntp2");
|
once(std::bind(&app::tickNtpUpdate, this), 2, "ntp2");
|
||||||
if(WIFI_AP == WiFi.getMode()) {
|
if (WIFI_AP == WiFi.getMode()) {
|
||||||
mMqttEnabled = false;
|
mMqttEnabled = false;
|
||||||
everySec(std::bind(&ahoywifi::tickWifiLoop, &mWifi), "wifiL");
|
everySec(std::bind(&ahoywifi::tickWifiLoop, &mWifi), "wifiL");
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
mInnerLoopCb = std::bind(&app::loopWifi, this);
|
mInnerLoopCb = std::bind(&app::loopWifi, this);
|
||||||
everySec(std::bind(&ahoywifi::tickWifiLoop, &mWifi), "wifiL");
|
everySec(std::bind(&ahoywifi::tickWifiLoop, &mWifi), "wifiL");
|
||||||
}
|
}
|
||||||
|
@ -221,8 +216,8 @@ void app::regularTickers(void) {
|
||||||
DPRINTLN(DBG_DEBUG, F("regularTickers"));
|
DPRINTLN(DBG_DEBUG, F("regularTickers"));
|
||||||
everySec(std::bind(&WebType::tickSecond, &mWeb), "webSc");
|
everySec(std::bind(&WebType::tickSecond, &mWeb), "webSc");
|
||||||
// Plugins
|
// Plugins
|
||||||
if(mConfig->plugin.display.type != 0)
|
if (mConfig->plugin.display.type != 0)
|
||||||
everySec(std::bind(&MonoDisplayType::tickerSecond, &mMonoDisplay), "disp");
|
everySec(std::bind(&DisplayType::tickerSecond, &mDisplay), "disp");
|
||||||
every(std::bind(&PubSerialType::tick, &mPubSerial), mConfig->serial.interval, "uart");
|
every(std::bind(&PubSerialType::tick, &mPubSerial), mConfig->serial.interval, "uart");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -238,26 +233,26 @@ void app::tickNtpUpdate(void) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// only install schedulers once even if NTP wasn't successful in first loop
|
// only install schedulers once even if NTP wasn't successful in first loop
|
||||||
if(mMqttReconnect) { // @TODO: mMqttReconnect is variable which scope has changed
|
if (mMqttReconnect) { // @TODO: mMqttReconnect is variable which scope has changed
|
||||||
if(mConfig->inst.rstValsNotAvail)
|
if (mConfig->inst.rstValsNotAvail)
|
||||||
everyMin(std::bind(&app::tickMinute, this), "tMin");
|
everyMin(std::bind(&app::tickMinute, this), "tMin");
|
||||||
if(mConfig->inst.rstYieldMidNight) {
|
if (mConfig->inst.rstYieldMidNight) {
|
||||||
uint32_t localTime = gTimezone.toLocal(mTimestamp);
|
uint32_t localTime = gTimezone.toLocal(mTimestamp);
|
||||||
uint32_t midTrig = gTimezone.toUTC(localTime - (localTime % 86400) + 86400); // next midnight local time
|
uint32_t midTrig = gTimezone.toUTC(localTime - (localTime % 86400) + 86400); // next midnight local time
|
||||||
onceAt(std::bind(&app::tickMidnight, this), midTrig, "midNi");
|
onceAt(std::bind(&app::tickMidnight, this), midTrig, "midNi");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
nxtTrig = isOK ? 43200 : 60; // depending on NTP update success check again in 12 h or in 1 min
|
nxtTrig = isOK ? 43200 : 60; // depending on NTP update success check again in 12 h or in 1 min
|
||||||
|
|
||||||
if((mSunrise == 0) && (mConfig->sun.lat) && (mConfig->sun.lon)) {
|
if ((mSunrise == 0) && (mConfig->sun.lat) && (mConfig->sun.lon)) {
|
||||||
mCalculatedTimezoneOffset = (int8_t)((mConfig->sun.lon >= 0 ? mConfig->sun.lon + 7.5 : mConfig->sun.lon - 7.5) / 15) * 3600;
|
mCalculatedTimezoneOffset = (int8_t)((mConfig->sun.lon >= 0 ? mConfig->sun.lon + 7.5 : mConfig->sun.lon - 7.5) / 15) * 3600;
|
||||||
tickCalcSunrise();
|
tickCalcSunrise();
|
||||||
}
|
}
|
||||||
|
|
||||||
// immediately start communicating
|
// immediately start communicating
|
||||||
// @TODO: leads to reboot loops? not sure #674
|
// @TODO: leads to reboot loops? not sure #674
|
||||||
if(isOK && mSendFirst) {
|
if (isOK && mSendFirst) {
|
||||||
mSendFirst = false;
|
mSendFirst = false;
|
||||||
once(std::bind(&app::tickSend, this), 2, "senOn");
|
once(std::bind(&app::tickSend, this), 2, "senOn");
|
||||||
}
|
}
|
||||||
|
@ -308,17 +303,17 @@ void app::tickIVCommunication(void) {
|
||||||
void app::tickSun(void) {
|
void app::tickSun(void) {
|
||||||
// only used and enabled by MQTT (see setup())
|
// only used and enabled by MQTT (see setup())
|
||||||
if (!mMqtt.tickerSun(mSunrise, mSunset, mConfig->sun.offsetSec, mConfig->sun.disNightCom))
|
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) {
|
void app::tickComm(void) {
|
||||||
if((!mIVCommunicationOn) && (mConfig->inst.rstValsCommStop))
|
if ((!mIVCommunicationOn) && (mConfig->inst.rstValsCommStop))
|
||||||
once(std::bind(&app::tickZeroValues, this), mConfig->nrf.sendInterval, "tZero");
|
once(std::bind(&app::tickZeroValues, this), mConfig->nrf.sendInterval, "tZero");
|
||||||
|
|
||||||
if (mMqttEnabled) {
|
if (mMqttEnabled) {
|
||||||
if (!mMqtt.tickerComm(!mIVCommunicationOn))
|
if (!mMqtt.tickerComm(!mIVCommunicationOn))
|
||||||
once(std::bind(&app::tickComm, this), 5, "mqCom"); // MQTT not connected, retry after 5s
|
once(std::bind(&app::tickComm, this), 5, "mqCom"); // MQTT not connected, retry after 5s
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -329,7 +324,7 @@ void app::tickZeroValues(void) {
|
||||||
for (uint8_t id = 0; id < mSys.getNumInverters(); id++) {
|
for (uint8_t id = 0; id < mSys.getNumInverters(); id++) {
|
||||||
iv = mSys.getInverterByPos(id);
|
iv = mSys.getInverterByPos(id);
|
||||||
if (NULL == iv)
|
if (NULL == iv)
|
||||||
continue; // skip to next inverter
|
continue; // skip to next inverter
|
||||||
|
|
||||||
mPayload.zeroInverterValues(iv);
|
mPayload.zeroInverterValues(iv);
|
||||||
}
|
}
|
||||||
|
@ -344,9 +339,9 @@ void app::tickMinute(void) {
|
||||||
for (uint8_t id = 0; id < mSys.getNumInverters(); id++) {
|
for (uint8_t id = 0; id < mSys.getNumInverters(); id++) {
|
||||||
iv = mSys.getInverterByPos(id);
|
iv = mSys.getInverterByPos(id);
|
||||||
if (NULL == iv)
|
if (NULL == iv)
|
||||||
continue; // skip to next inverter
|
continue; // skip to next inverter
|
||||||
|
|
||||||
if(!iv->isAvailable(mTimestamp) && !iv->isProducing(mTimestamp) && iv->config->enabled)
|
if (!iv->isAvailable(mTimestamp) && !iv->isProducing(mTimestamp) && iv->config->enabled)
|
||||||
mPayload.zeroInverterValues(iv);
|
mPayload.zeroInverterValues(iv);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -355,7 +350,7 @@ void app::tickMinute(void) {
|
||||||
void app::tickMidnight(void) {
|
void app::tickMidnight(void) {
|
||||||
// only triggered if 'reset values at midnight is enabled'
|
// only triggered if 'reset values at midnight is enabled'
|
||||||
uint32_t localTime = gTimezone.toLocal(mTimestamp);
|
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");
|
onceAt(std::bind(&app::tickMidnight, this), nxtTrig, "mid2");
|
||||||
|
|
||||||
Inverter<> *iv;
|
Inverter<> *iv;
|
||||||
|
@ -363,7 +358,7 @@ void app::tickMidnight(void) {
|
||||||
for (uint8_t id = 0; id < mSys.getNumInverters(); id++) {
|
for (uint8_t id = 0; id < mSys.getNumInverters(); id++) {
|
||||||
iv = mSys.getInverterByPos(id);
|
iv = mSys.getInverterByPos(id);
|
||||||
if (NULL == iv)
|
if (NULL == iv)
|
||||||
continue; // skip to next inverter
|
continue; // skip to next inverter
|
||||||
|
|
||||||
mPayload.zeroInverterValues(iv);
|
mPayload.zeroInverterValues(iv);
|
||||||
mPayload.zeroYieldDay(iv);
|
mPayload.zeroYieldDay(iv);
|
||||||
|
@ -403,8 +398,8 @@ void app::tickSend(void) {
|
||||||
} while ((NULL == iv) && ((maxLoop--) > 0));
|
} while ((NULL == iv) && ((maxLoop--) > 0));
|
||||||
|
|
||||||
if (NULL != iv) {
|
if (NULL != iv) {
|
||||||
if(iv->config->enabled) {
|
if (iv->config->enabled) {
|
||||||
if(iv->ivGen == IV_HM)
|
if (iv->ivGen == IV_HM)
|
||||||
mPayload.ivSend(iv);
|
mPayload.ivSend(iv);
|
||||||
else if(iv->ivGen == IV_MI)
|
else if(iv->ivGen == IV_MI)
|
||||||
mMiPayload.ivSend(iv);
|
mMiPayload.ivSend(iv);
|
||||||
|
@ -457,25 +452,25 @@ void app::setupLed(void) {
|
||||||
* PIN ---- |<----- 3.3V
|
* PIN ---- |<----- 3.3V
|
||||||
*
|
*
|
||||||
* */
|
* */
|
||||||
if(mConfig->led.led0 != 0xff) {
|
if (mConfig->led.led0 != 0xff) {
|
||||||
pinMode(mConfig->led.led0, OUTPUT);
|
pinMode(mConfig->led.led0, OUTPUT);
|
||||||
digitalWrite(mConfig->led.led0, HIGH); // LED off
|
digitalWrite(mConfig->led.led0, HIGH); // LED off
|
||||||
}
|
}
|
||||||
if(mConfig->led.led1 != 0xff) {
|
if (mConfig->led.led1 != 0xff) {
|
||||||
pinMode(mConfig->led.led1, OUTPUT);
|
pinMode(mConfig->led.led1, OUTPUT);
|
||||||
digitalWrite(mConfig->led.led1, HIGH); // LED off
|
digitalWrite(mConfig->led.led1, HIGH); // LED off
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
void app::updateLed(void) {
|
void app::updateLed(void) {
|
||||||
if(mConfig->led.led0 != 0xff) {
|
if (mConfig->led.led0 != 0xff) {
|
||||||
Inverter<> *iv = mSys.getInverterByPos(0);
|
Inverter<> *iv = mSys.getInverterByPos(0);
|
||||||
if (NULL != iv) {
|
if (NULL != iv) {
|
||||||
if(iv->isProducing(mTimestamp))
|
if (iv->isProducing(mTimestamp))
|
||||||
digitalWrite(mConfig->led.led0, LOW); // LED on
|
digitalWrite(mConfig->led.led0, LOW); // LED on
|
||||||
else
|
else
|
||||||
digitalWrite(mConfig->led.led0, HIGH); // LED off
|
digitalWrite(mConfig->led.led0, HIGH); // LED off
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
31
src/app.h
31
src/app.h
|
@ -6,31 +6,29 @@
|
||||||
#ifndef __APP_H__
|
#ifndef __APP_H__
|
||||||
#define __APP_H__
|
#define __APP_H__
|
||||||
|
|
||||||
|
|
||||||
#include "utils/dbg.h"
|
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include <ArduinoJson.h>
|
#include <ArduinoJson.h>
|
||||||
|
#include <RF24.h>
|
||||||
|
#include <RF24_config.h>
|
||||||
|
|
||||||
#include "appInterface.h"
|
#include "appInterface.h"
|
||||||
|
|
||||||
#include "config/settings.h"
|
#include "config/settings.h"
|
||||||
#include "defines.h"
|
#include "defines.h"
|
||||||
#include "utils/crc.h"
|
#include "hm/hmPayload.h"
|
||||||
#include "utils/scheduler.h"
|
|
||||||
|
|
||||||
#include "hm/hmSystem.h"
|
#include "hm/hmSystem.h"
|
||||||
#include "hm/hmRadio.h"
|
#include "hm/hmRadio.h"
|
||||||
#include "hms/hmsRadio.h"
|
#include "hms/hmsRadio.h"
|
||||||
#include "hms/hmsPayload.h"
|
#include "hms/hmsPayload.h"
|
||||||
#include "hm/hmPayload.h"
|
#include "hm/hmPayload.h"
|
||||||
#include "hm/miPayload.h"
|
#include "hm/miPayload.h"
|
||||||
#include "wifi/ahoywifi.h"
|
|
||||||
#include "web/web.h"
|
|
||||||
#include "web/RestApi.h"
|
|
||||||
|
|
||||||
#include "publisher/pubMqtt.h"
|
#include "publisher/pubMqtt.h"
|
||||||
#include "publisher/pubSerial.h"
|
#include "publisher/pubSerial.h"
|
||||||
|
#include "utils/crc.h"
|
||||||
|
#include "utils/dbg.h"
|
||||||
|
#include "utils/scheduler.h"
|
||||||
|
#include "web/RestApi.h"
|
||||||
|
#include "web/web.h"
|
||||||
|
#include "wifi/ahoywifi.h"
|
||||||
|
|
||||||
// convert degrees and radians for sun calculation
|
// convert degrees and radians for sun calculation
|
||||||
#define SIN(x) (sin(radians(x)))
|
#define SIN(x) (sin(radians(x)))
|
||||||
|
@ -49,12 +47,11 @@ typedef PubMqtt<HmSystemType> PubMqttType;
|
||||||
typedef PubSerial<HmSystemType> PubSerialType;
|
typedef PubSerial<HmSystemType> PubSerialType;
|
||||||
|
|
||||||
// PLUGINS
|
// PLUGINS
|
||||||
#include "plugins/MonochromeDisplay/MonochromeDisplay.h"
|
#include "plugins/Display/Display.h"
|
||||||
typedef MonochromeDisplay<HmSystemType> MonoDisplayType;
|
typedef Display<HmSystemType> DisplayType;
|
||||||
|
|
||||||
|
|
||||||
class app : public IApp, public ah::Scheduler {
|
class app : public IApp, public ah::Scheduler {
|
||||||
public:
|
public:
|
||||||
app();
|
app();
|
||||||
~app() {}
|
~app() {}
|
||||||
|
|
||||||
|
@ -225,7 +222,7 @@ class app : public IApp, public ah::Scheduler {
|
||||||
mMqtt.payloadEventListener(cmd);
|
mMqtt.payloadEventListener(cmd);
|
||||||
#endif
|
#endif
|
||||||
if(mConfig->plugin.display.type != 0)
|
if(mConfig->plugin.display.type != 0)
|
||||||
mMonoDisplay.payloadEventListener(cmd);
|
mDisplay.payloadEventListener(cmd);
|
||||||
}
|
}
|
||||||
|
|
||||||
void mqttSubRxCb(JsonObject obj);
|
void mqttSubRxCb(JsonObject obj);
|
||||||
|
@ -301,7 +298,7 @@ class app : public IApp, public ah::Scheduler {
|
||||||
uint32_t mSunrise, mSunset;
|
uint32_t mSunrise, mSunset;
|
||||||
|
|
||||||
// plugins
|
// plugins
|
||||||
MonoDisplayType mMonoDisplay;
|
DisplayType mDisplay;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif /*__APP_H__*/
|
#endif /*__APP_H__*/
|
||||||
|
|
|
@ -7,11 +7,12 @@
|
||||||
#define __SETTINGS_H__
|
#define __SETTINGS_H__
|
||||||
|
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include <LittleFS.h>
|
|
||||||
#include <ArduinoJson.h>
|
#include <ArduinoJson.h>
|
||||||
|
#include <LittleFS.h>
|
||||||
|
|
||||||
|
#include "../defines.h"
|
||||||
#include "../utils/dbg.h"
|
#include "../utils/dbg.h"
|
||||||
#include "../utils/helper.h"
|
#include "../utils/helper.h"
|
||||||
#include "../defines.h"
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* More info:
|
* More info:
|
||||||
|
@ -51,6 +52,7 @@ typedef struct {
|
||||||
char deviceName[DEVNAME_LEN];
|
char deviceName[DEVNAME_LEN];
|
||||||
char adminPwd[PWD_LEN];
|
char adminPwd[PWD_LEN];
|
||||||
uint16_t protectionMask;
|
uint16_t protectionMask;
|
||||||
|
bool darkMode;
|
||||||
|
|
||||||
// wifi
|
// wifi
|
||||||
char stationSsid[SSID_LEN];
|
char stationSsid[SSID_LEN];
|
||||||
|
@ -84,7 +86,7 @@ typedef struct {
|
||||||
typedef struct {
|
typedef struct {
|
||||||
float lat;
|
float lat;
|
||||||
float lon;
|
float lon;
|
||||||
bool disNightCom; // disable night communication
|
bool disNightCom; // disable night communication
|
||||||
uint16_t offsetSec;
|
uint16_t offsetSec;
|
||||||
} cfgSun_t;
|
} cfgSun_t;
|
||||||
|
|
||||||
|
@ -95,8 +97,8 @@ typedef struct {
|
||||||
} cfgSerial_t;
|
} cfgSerial_t;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
uint8_t led0; // first LED pin
|
uint8_t led0; // first LED pin
|
||||||
uint8_t led1; // second LED pin
|
uint8_t led1; // second LED pin
|
||||||
} cfgLed_t;
|
} cfgLed_t;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
@ -113,7 +115,7 @@ typedef struct {
|
||||||
char name[MAX_NAME_LENGTH];
|
char name[MAX_NAME_LENGTH];
|
||||||
serial_u serial;
|
serial_u serial;
|
||||||
uint16_t chMaxPwr[4];
|
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];
|
char chName[4][MAX_NAME_LENGTH];
|
||||||
} cfgIv_t;
|
} cfgIv_t;
|
||||||
|
|
||||||
|
@ -129,14 +131,17 @@ typedef struct {
|
||||||
typedef struct {
|
typedef struct {
|
||||||
uint8_t type;
|
uint8_t type;
|
||||||
bool pwrSaveAtIvOffline;
|
bool pwrSaveAtIvOffline;
|
||||||
bool logoEn;
|
|
||||||
bool pxShift;
|
bool pxShift;
|
||||||
bool rot180;
|
uint8_t rot;
|
||||||
uint16_t wakeUp;
|
//uint16_t wakeUp;
|
||||||
uint16_t sleepAt;
|
//uint16_t sleepAt;
|
||||||
uint8_t contrast;
|
uint8_t contrast;
|
||||||
uint8_t pin0;
|
uint8_t disp_data;
|
||||||
uint8_t pin1;
|
uint8_t disp_clk;
|
||||||
|
uint8_t disp_cs;
|
||||||
|
uint8_t disp_reset;
|
||||||
|
uint8_t disp_busy;
|
||||||
|
uint8_t disp_dc;
|
||||||
} display_t;
|
} display_t;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
@ -228,7 +233,7 @@ class settings {
|
||||||
else {
|
else {
|
||||||
//DPRINTLN(DBG_INFO, fp.readString());
|
//DPRINTLN(DBG_INFO, fp.readString());
|
||||||
//fp.seek(0, SeekSet);
|
//fp.seek(0, SeekSet);
|
||||||
DynamicJsonDocument root(4500);
|
DynamicJsonDocument root(5500);
|
||||||
DeserializationError err = deserializeJson(root, fp);
|
DeserializationError err = deserializeJson(root, fp);
|
||||||
if(!err && (root.size() > 0)) {
|
if(!err && (root.size() > 0)) {
|
||||||
mCfg.valid = true;
|
mCfg.valid = true;
|
||||||
|
@ -262,7 +267,7 @@ class settings {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
DynamicJsonDocument json(4500);
|
DynamicJsonDocument json(6500);
|
||||||
JsonObject root = json.to<JsonObject>();
|
JsonObject root = json.to<JsonObject>();
|
||||||
jsonWifi(root.createNestedObject(F("wifi")), true);
|
jsonWifi(root.createNestedObject(F("wifi")), true);
|
||||||
jsonNrf(root.createNestedObject(F("nrf")), true);
|
jsonNrf(root.createNestedObject(F("nrf")), true);
|
||||||
|
@ -307,6 +312,7 @@ class settings {
|
||||||
memset(&mCfg, 0, sizeof(settings_t));
|
memset(&mCfg, 0, sizeof(settings_t));
|
||||||
mCfg.sys.protectionMask = DEF_PROT_INDEX | DEF_PROT_LIVE | DEF_PROT_SERIAL | DEF_PROT_SETUP
|
mCfg.sys.protectionMask = DEF_PROT_INDEX | DEF_PROT_LIVE | DEF_PROT_SERIAL | DEF_PROT_SETUP
|
||||||
| DEF_PROT_UPDATE | DEF_PROT_SYSTEM | DEF_PROT_API | DEF_PROT_MQTT;
|
| DEF_PROT_UPDATE | DEF_PROT_SYSTEM | DEF_PROT_API | DEF_PROT_MQTT;
|
||||||
|
mCfg.sys.darkMode = false;
|
||||||
// restore temp settings
|
// restore temp settings
|
||||||
if(keepWifi)
|
if(keepWifi)
|
||||||
memcpy(&mCfg.sys, &tmp, sizeof(cfgSys_t));
|
memcpy(&mCfg.sys, &tmp, sizeof(cfgSys_t));
|
||||||
|
@ -359,13 +365,16 @@ class settings {
|
||||||
memset(&mCfg.inst, 0, sizeof(cfgInst_t));
|
memset(&mCfg.inst, 0, sizeof(cfgInst_t));
|
||||||
|
|
||||||
mCfg.plugin.display.pwrSaveAtIvOffline = false;
|
mCfg.plugin.display.pwrSaveAtIvOffline = false;
|
||||||
mCfg.plugin.display.contrast = 60;
|
mCfg.plugin.display.contrast = 60;
|
||||||
mCfg.plugin.display.logoEn = true;
|
mCfg.plugin.display.pxShift = true;
|
||||||
mCfg.plugin.display.pxShift = true;
|
mCfg.plugin.display.rot = 0;
|
||||||
mCfg.plugin.display.rot180 = false;
|
mCfg.plugin.display.disp_data = DEF_PIN_OFF; // SDA
|
||||||
mCfg.plugin.display.pin0 = DEF_PIN_OFF; // SCL
|
mCfg.plugin.display.disp_clk = DEF_PIN_OFF; // SCL
|
||||||
mCfg.plugin.display.pin1 = DEF_PIN_OFF; // SDA
|
mCfg.plugin.display.disp_cs = DEF_PIN_OFF;
|
||||||
}
|
mCfg.plugin.display.disp_reset = DEF_PIN_OFF;
|
||||||
|
mCfg.plugin.display.disp_busy = DEF_PIN_OFF;
|
||||||
|
mCfg.plugin.display.disp_dc = DEF_PIN_OFF;
|
||||||
|
}
|
||||||
|
|
||||||
void jsonWifi(JsonObject obj, bool set = false) {
|
void jsonWifi(JsonObject obj, bool set = false) {
|
||||||
if(set) {
|
if(set) {
|
||||||
|
@ -375,6 +384,7 @@ class settings {
|
||||||
obj[F("dev")] = mCfg.sys.deviceName;
|
obj[F("dev")] = mCfg.sys.deviceName;
|
||||||
obj[F("adm")] = mCfg.sys.adminPwd;
|
obj[F("adm")] = mCfg.sys.adminPwd;
|
||||||
obj[F("prot_mask")] = mCfg.sys.protectionMask;
|
obj[F("prot_mask")] = mCfg.sys.protectionMask;
|
||||||
|
obj[F("dark")] = mCfg.sys.darkMode;
|
||||||
ah::ip2Char(mCfg.sys.ip.ip, buf); obj[F("ip")] = String(buf);
|
ah::ip2Char(mCfg.sys.ip.ip, buf); obj[F("ip")] = String(buf);
|
||||||
ah::ip2Char(mCfg.sys.ip.mask, buf); obj[F("mask")] = String(buf);
|
ah::ip2Char(mCfg.sys.ip.mask, buf); obj[F("mask")] = String(buf);
|
||||||
ah::ip2Char(mCfg.sys.ip.dns1, buf); obj[F("dns1")] = String(buf);
|
ah::ip2Char(mCfg.sys.ip.dns1, buf); obj[F("dns1")] = String(buf);
|
||||||
|
@ -386,6 +396,7 @@ class settings {
|
||||||
snprintf(mCfg.sys.deviceName, DEVNAME_LEN, "%s", obj[F("dev")].as<const char*>());
|
snprintf(mCfg.sys.deviceName, DEVNAME_LEN, "%s", obj[F("dev")].as<const char*>());
|
||||||
snprintf(mCfg.sys.adminPwd, PWD_LEN, "%s", obj[F("adm")].as<const char*>());
|
snprintf(mCfg.sys.adminPwd, PWD_LEN, "%s", obj[F("adm")].as<const char*>());
|
||||||
mCfg.sys.protectionMask = obj[F("prot_mask")];
|
mCfg.sys.protectionMask = obj[F("prot_mask")];
|
||||||
|
mCfg.sys.darkMode = obj[F("dark")];
|
||||||
ah::ip2Arr(mCfg.sys.ip.ip, obj[F("ip")].as<const char*>());
|
ah::ip2Arr(mCfg.sys.ip.ip, obj[F("ip")].as<const char*>());
|
||||||
ah::ip2Arr(mCfg.sys.ip.mask, obj[F("mask")].as<const char*>());
|
ah::ip2Arr(mCfg.sys.ip.mask, obj[F("mask")].as<const char*>());
|
||||||
ah::ip2Arr(mCfg.sys.ip.dns1, obj[F("dns1")].as<const char*>());
|
ah::ip2Arr(mCfg.sys.ip.dns1, obj[F("dns1")].as<const char*>());
|
||||||
|
@ -502,26 +513,32 @@ class settings {
|
||||||
JsonObject disp = obj.createNestedObject("disp");
|
JsonObject disp = obj.createNestedObject("disp");
|
||||||
disp[F("type")] = mCfg.plugin.display.type;
|
disp[F("type")] = mCfg.plugin.display.type;
|
||||||
disp[F("pwrSafe")] = (bool)mCfg.plugin.display.pwrSaveAtIvOffline;
|
disp[F("pwrSafe")] = (bool)mCfg.plugin.display.pwrSaveAtIvOffline;
|
||||||
disp[F("logo")] = (bool)mCfg.plugin.display.logoEn;
|
disp[F("pxShift")] = (bool)mCfg.plugin.display.pxShift;
|
||||||
disp[F("pxShift")] = (bool)mCfg.plugin.display.pxShift;
|
disp[F("rotation")] = mCfg.plugin.display.rot;
|
||||||
disp[F("rot180")] = (bool)mCfg.plugin.display.rot180;
|
//disp[F("wake")] = mCfg.plugin.display.wakeUp;
|
||||||
disp[F("wake")] = mCfg.plugin.display.wakeUp;
|
//disp[F("sleep")] = mCfg.plugin.display.sleepAt;
|
||||||
disp[F("sleep")] = mCfg.plugin.display.sleepAt;
|
|
||||||
disp[F("contrast")] = mCfg.plugin.display.contrast;
|
disp[F("contrast")] = mCfg.plugin.display.contrast;
|
||||||
disp[F("pin0")] = mCfg.plugin.display.pin0;
|
disp[F("data")] = mCfg.plugin.display.disp_data;
|
||||||
disp[F("pin1")] = mCfg.plugin.display.pin1;
|
disp[F("clock")] = mCfg.plugin.display.disp_clk;
|
||||||
|
disp[F("cs")] = mCfg.plugin.display.disp_cs;
|
||||||
|
disp[F("reset")] = mCfg.plugin.display.disp_reset;
|
||||||
|
disp[F("busy")] = mCfg.plugin.display.disp_busy;
|
||||||
|
disp[F("dc")] = mCfg.plugin.display.disp_dc;
|
||||||
} else {
|
} else {
|
||||||
JsonObject disp = obj["disp"];
|
JsonObject disp = obj["disp"];
|
||||||
mCfg.plugin.display.type = disp[F("type")];
|
mCfg.plugin.display.type = disp[F("type")];
|
||||||
mCfg.plugin.display.pwrSaveAtIvOffline = (bool) disp[F("pwrSafe")];
|
mCfg.plugin.display.pwrSaveAtIvOffline = (bool)disp[F("pwrSafe")];
|
||||||
mCfg.plugin.display.logoEn = (bool) disp[F("logo")];
|
mCfg.plugin.display.pxShift = (bool)disp[F("pxShift")];
|
||||||
mCfg.plugin.display.pxShift = (bool) disp[F("pxShift")];
|
mCfg.plugin.display.rot = disp[F("rotation")];
|
||||||
mCfg.plugin.display.rot180 = (bool) disp[F("rot180")];
|
//mCfg.plugin.display.wakeUp = disp[F("wake")];
|
||||||
mCfg.plugin.display.wakeUp = disp[F("wake")];
|
//mCfg.plugin.display.sleepAt = disp[F("sleep")];
|
||||||
mCfg.plugin.display.sleepAt = disp[F("sleep")];
|
mCfg.plugin.display.contrast = disp[F("contrast")];
|
||||||
mCfg.plugin.display.contrast = disp[F("contrast")];
|
mCfg.plugin.display.disp_data = disp[F("data")];
|
||||||
mCfg.plugin.display.pin0 = disp[F("pin0")];
|
mCfg.plugin.display.disp_clk = disp[F("clock")];
|
||||||
mCfg.plugin.display.pin1 = disp[F("pin1")];
|
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")];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
//-------------------------------------
|
//-------------------------------------
|
||||||
#define VERSION_MAJOR 0
|
#define VERSION_MAJOR 0
|
||||||
#define VERSION_MINOR 5
|
#define VERSION_MINOR 5
|
||||||
#define VERSION_PATCH 90
|
#define VERSION_PATCH 94
|
||||||
|
|
||||||
//-------------------------------------
|
//-------------------------------------
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
|
|
@ -29,6 +29,10 @@ const char* const fields[] = {"U_DC", "I_DC", "P_DC", "YieldDay", "YieldWeek", "
|
||||||
"active_PowerLimit", /*"reactivePowerLimit","Powerfactor",*/ "LastAlarmCode"};
|
"active_PowerLimit", /*"reactivePowerLimit","Powerfactor",*/ "LastAlarmCode"};
|
||||||
const char* const notAvail = "n/a";
|
const char* const notAvail = "n/a";
|
||||||
|
|
||||||
|
const uint8_t fieldUnits[] = {UNIT_V, UNIT_A, UNIT_W, UNIT_WH, UNIT_KWH, UNIT_KWH,
|
||||||
|
UNIT_V, UNIT_A, UNIT_W, UNIT_HZ, UNIT_C, UNIT_NONE, UNIT_PCT, UNIT_PCT, UNIT_VAR,
|
||||||
|
UNIT_NONE, UNIT_NONE, UNIT_NONE, UNIT_NONE, UNIT_NONE, UNIT_NONE, UNIT_PCT, UNIT_NONE};
|
||||||
|
|
||||||
// mqtt discovery device classes
|
// mqtt discovery device classes
|
||||||
enum {DEVICE_CLS_NONE = 0, DEVICE_CLS_CURRENT, DEVICE_CLS_ENERGY, DEVICE_CLS_PWR, DEVICE_CLS_VOLTAGE, DEVICE_CLS_FREQ, DEVICE_CLS_TEMP};
|
enum {DEVICE_CLS_NONE = 0, DEVICE_CLS_CURRENT, DEVICE_CLS_ENERGY, DEVICE_CLS_PWR, DEVICE_CLS_VOLTAGE, DEVICE_CLS_FREQ, DEVICE_CLS_TEMP};
|
||||||
const char* const deviceClasses[] = {0, "current", "energy", "power", "voltage", "frequency", "temperature"};
|
const char* const deviceClasses[] = {0, "current", "energy", "power", "voltage", "frequency", "temperature"};
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
#include "../utils/dbg.h"
|
#include "../utils/dbg.h"
|
||||||
#include "../utils/crc.h"
|
#include "../utils/crc.h"
|
||||||
#include "../config/config.h"
|
#include "../config/config.h"
|
||||||
|
#include "hmRadio.h"
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
@ -158,7 +159,8 @@ class HmPayload {
|
||||||
uint8_t cmd = iv->getQueuedCmd();
|
uint8_t cmd = iv->getQueuedCmd();
|
||||||
DPRINT(DBG_INFO, F("(#"));
|
DPRINT(DBG_INFO, F("(#"));
|
||||||
DBGPRINT(String(iv->id));
|
DBGPRINT(String(iv->id));
|
||||||
DBGPRINT(F(") prepareDevInformCmd")); // + String(cmd, HEX));
|
DBGPRINT(F(") prepareDevInformCmd 0x"));
|
||||||
|
DBGPRINTLN(String(cmd, HEX));
|
||||||
mRadio->prepareDevInformCmd(iv->radioId.u64, cmd, mPayload[iv->id].ts, iv->alarmMesIndex, false);
|
mRadio->prepareDevInformCmd(iv->radioId.u64, cmd, mPayload[iv->id].ts, iv->alarmMesIndex, false);
|
||||||
mPayload[iv->id].txCmd = cmd;
|
mPayload[iv->id].txCmd = cmd;
|
||||||
}
|
}
|
||||||
|
|
|
@ -92,14 +92,14 @@ class MiPayload {
|
||||||
}
|
}
|
||||||
|
|
||||||
void add(Inverter<> *iv, packet_t *p) {
|
void add(Inverter<> *iv, packet_t *p) {
|
||||||
DPRINTLN(DBG_INFO, F("MI got data [0]=") + String(p->packet[0], HEX));
|
//DPRINTLN(DBG_INFO, F("MI got data [0]=") + String(p->packet[0], HEX));
|
||||||
|
|
||||||
if (p->packet[0] == (0x08 + ALL_FRAMES)) { // 0x88; MI status response to 0x09
|
if (p->packet[0] == (0x08 + ALL_FRAMES)) { // 0x88; MI status response to 0x09
|
||||||
mPayload[iv->id].stsa = true;
|
mPayload[iv->id].stsa = true;
|
||||||
miStsDecode(iv, p);
|
miStsDecode(iv, p);
|
||||||
} else if (p->packet[0] == (0x11 + SINGLE_FRAME)) { // 0x92; MI status response to 0x11
|
} else if (p->packet[0] == (0x11 + SINGLE_FRAME)) { // 0x92; MI status response to 0x11
|
||||||
mPayload[iv->id].stsb = true;
|
mPayload[iv->id].stsb = true;
|
||||||
miStsDecode(iv, p, 2);
|
miStsDecode(iv, p, CH2);
|
||||||
} else if (p->packet[0] == (0x09 + ALL_FRAMES)) { // MI data response to 0x09
|
} else if (p->packet[0] == (0x09 + ALL_FRAMES)) { // MI data response to 0x09
|
||||||
mPayload[iv->id].txId = p->packet[0];
|
mPayload[iv->id].txId = p->packet[0];
|
||||||
miDataDecode(iv,p);
|
miDataDecode(iv,p);
|
||||||
|
@ -359,12 +359,11 @@ class MiPayload {
|
||||||
(mCbMiPayload)(val);
|
(mCbMiPayload)(val);
|
||||||
}
|
}
|
||||||
|
|
||||||
void miStsDecode(Inverter<> *iv, packet_t *p, uint8_t chan = 1) {
|
void miStsDecode(Inverter<> *iv, packet_t *p, uint8_t chan = CH1) {
|
||||||
|
DPRINTLN(DBG_INFO, F("Inverter ") + String(iv->id) + F(": status msg 0x") + String(p->packet[0], HEX));
|
||||||
record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug); // choose the record structure
|
record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug); // choose the record structure
|
||||||
rec->ts = mPayload[iv->id].ts;
|
rec->ts = mPayload[iv->id].ts;
|
||||||
|
|
||||||
int8_t offset = -2;
|
|
||||||
|
|
||||||
//iv->setValue(iv->getPosByChFld(chan, FLD_YD, rec), rec, (int)((p->packet[11+6] << 8) + p->packet[12+6])); // was 11/12, might be wrong!
|
//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)
|
//if (INV_TYPE_1CH == iv->type)
|
||||||
|
@ -372,16 +371,21 @@ class MiPayload {
|
||||||
|
|
||||||
//iv->setValue(iv->getPosByChFld(chan, FLD_EVT, rec), rec, (int)((p->packet[13] << 8) + p->packet[14]));
|
//iv->setValue(iv->getPosByChFld(chan, FLD_EVT, rec), rec, (int)((p->packet[13] << 8) + p->packet[14]));
|
||||||
|
|
||||||
iv->setValue(iv->getPosByChFld(0, FLD_EVT, rec), rec, (int)((p->packet[11+offset] << 8) + p->packet[12+offset]));
|
iv->setValue(iv->getPosByChFld(0, FLD_EVT, rec), rec, (int)((p->packet[11] << 8) + p->packet[12]));
|
||||||
|
//iv->setValue(iv->getPosByChFld(0, FLD_EVT, rec), rec, (int)((p->packet[14] << 8) + p->packet[16]));
|
||||||
if (iv->alarmMesIndex < rec->record[iv->getPosByChFld(0, FLD_EVT, rec)]){
|
if (iv->alarmMesIndex < rec->record[iv->getPosByChFld(0, FLD_EVT, rec)]){
|
||||||
iv->alarmMesIndex = rec->record[iv->getPosByChFld(0, FLD_EVT, rec)];
|
iv->alarmMesIndex = rec->record[iv->getPosByChFld(0, FLD_EVT, rec)]; // seems there's no status per channel in 3rd gen. models?!?
|
||||||
|
|
||||||
DPRINTLN(DBG_INFO, "alarm ID incremented to " + String(iv->alarmMesIndex));
|
DPRINTLN(DBG_INFO, "alarm ID incremented to " + String(iv->alarmMesIndex));
|
||||||
iv->enqueCommand<InfoCommand>(AlarmData);
|
iv->enqueCommand<InfoCommand>(AlarmData);
|
||||||
}
|
}
|
||||||
|
/* Unclear how in HM inverters Info and alarm data is handled...
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
/* for decoding see
|
/* int8_t offset = -2;
|
||||||
|
|
||||||
|
for decoding see
|
||||||
void MI600StsMsg (NRF24_packet_t *p){
|
void MI600StsMsg (NRF24_packet_t *p){
|
||||||
STAT = (int)((p->packet[11] << 8) + p->packet[12]);
|
STAT = (int)((p->packet[11] << 8) + p->packet[12]);
|
||||||
FCNT = (int)((p->packet[13] << 8) + p->packet[14]);
|
FCNT = (int)((p->packet[13] << 8) + p->packet[14]);
|
||||||
|
@ -393,23 +397,22 @@ class MiPayload {
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
DPRINTLN(DBG_INFO, F("Inverter ") + String(iv->id) + F(": status msg ") + p->packet[0]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void miDataDecode(Inverter<> *iv, packet_t *p) {
|
void miDataDecode(Inverter<> *iv, packet_t *p) {
|
||||||
record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug); // choose the parser
|
record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug); // choose the parser
|
||||||
rec->ts = mPayload[iv->id].ts;
|
rec->ts = mPayload[iv->id].ts;
|
||||||
|
|
||||||
uint8_t chan = ( p->packet[2] == 0x89 || p->packet[2] == (0x36 + ALL_FRAMES) ) ? 1 :
|
uint8_t datachan = ( p->packet[0] == 0x89 || p->packet[0] == (0x36 + ALL_FRAMES) ) ? CH1 :
|
||||||
( p->packet[2] == 0x91 || p->packet[2] == (0x37 + ALL_FRAMES) ) ? 2 :
|
( p->packet[0] == 0x91 || p->packet[0] == (0x37 + ALL_FRAMES) ) ? CH2 :
|
||||||
p->packet[2] == (0x38 + ALL_FRAMES) ? 3 :
|
p->packet[0] == (0x38 + ALL_FRAMES) ? CH3 :
|
||||||
4;
|
CH4;
|
||||||
int8_t offset = -2;
|
int8_t offset = -2;
|
||||||
// U_DC = (float) ((p->packet[11] << 8) + p->packet[12])/10;
|
// U_DC = (float) ((p->packet[11] << 8) + p->packet[12])/10;
|
||||||
iv->setValue(iv->getPosByChFld(chan, FLD_UDC, rec), rec, (float)((p->packet[11+offset] << 8) + p->packet[12+offset])/10);
|
iv->setValue(iv->getPosByChFld(datachan, FLD_UDC, rec), rec, (float)((p->packet[11+offset] << 8) + p->packet[12+offset])/10);
|
||||||
yield();
|
yield();
|
||||||
// I_DC = (float) ((p->packet[13] << 8) + p->packet[14])/10;
|
// I_DC = (float) ((p->packet[13] << 8) + p->packet[14])/10;
|
||||||
iv->setValue(iv->getPosByChFld(chan, FLD_IDC, rec), rec, (float)((p->packet[13+offset] << 8) + p->packet[14+offset])/10);
|
iv->setValue(iv->getPosByChFld(datachan, FLD_IDC, rec), rec, (float)((p->packet[13+offset] << 8) + p->packet[14+offset])/10);
|
||||||
yield();
|
yield();
|
||||||
// U_AC = (float) ((p->packet[15] << 8) + p->packet[16])/10;
|
// 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[15+offset] << 8) + p->packet[16+offset])/10);
|
||||||
|
@ -418,23 +421,23 @@ class MiPayload {
|
||||||
//iv->setValue(iv->getPosByChFld(0, FLD_IAC, rec), rec, (float)((p->packet[17+offset] << 8) + p->packet[18+offset])/100);
|
//iv->setValue(iv->getPosByChFld(0, FLD_IAC, rec), rec, (float)((p->packet[17+offset] << 8) + p->packet[18+offset])/100);
|
||||||
//yield();
|
//yield();
|
||||||
// P_DC = (float)((p->packet[19] << 8) + p->packet[20])/10;
|
// P_DC = (float)((p->packet[19] << 8) + p->packet[20])/10;
|
||||||
iv->setValue(iv->getPosByChFld(chan, FLD_PDC, rec), rec, (float)((p->packet[19+offset] << 8) + p->packet[20+offset])/10);
|
iv->setValue(iv->getPosByChFld(datachan, FLD_PDC, rec), rec, (float)((p->packet[19+offset] << 8) + p->packet[20+offset])/10);
|
||||||
yield();
|
yield();
|
||||||
// Q_DC = (float)((p->packet[21] << 8) + p->packet[22])/1;
|
// Q_DC = (float)((p->packet[21] << 8) + p->packet[22])/1;
|
||||||
iv->setValue(iv->getPosByChFld(chan, FLD_Q, rec), rec, (float)((p->packet[21+offset] << 8) + p->packet[22+offset])/1);
|
iv->setValue(iv->getPosByChFld(datachan, FLD_YD, rec), rec, (float)((p->packet[21+offset] << 8) + p->packet[22+offset])/1);
|
||||||
yield();
|
yield();
|
||||||
iv->setValue(iv->getPosByChFld(0, FLD_T, rec), rec, (float) ((int16_t)(p->packet[23+offset] << 8) + p->packet[24+offset])/10);
|
iv->setValue(iv->getPosByChFld(0, FLD_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); //23 is freq or IAC?
|
iv->setValue(iv->getPosByChFld(0, FLD_F, rec), rec, (float) ((p->packet[17+offset] << 8) + p->packet[18+offset])/100);
|
||||||
|
iv->setValue(iv->getPosByChFld(0, FLD_IRR, rec), rec, (float) (calcIrradiation(iv, datachan)));
|
||||||
yield();
|
yield();
|
||||||
//FLD_YD
|
//FLD_YD
|
||||||
|
|
||||||
if (p->packet[2] >= (0x36 + ALL_FRAMES) ) {
|
if (p->packet[0] >= (0x36 + ALL_FRAMES) ) {
|
||||||
|
/*status message analysis most liklely needs to be changed, see MiStsMst*/
|
||||||
/*STAT = (uint8_t)(p->packet[25] );
|
/*STAT = (uint8_t)(p->packet[25] );
|
||||||
FCNT = (uint8_t)(p->packet[26]);
|
FCNT = (uint8_t)(p->packet[26]);
|
||||||
FCODE = (uint8_t)(p->packet[27]); // MI300: (int)((p->packet[15] << 8) + p->packet[16]); */
|
FCODE = (uint8_t)(p->packet[27]); // MI300/Mi600 stsMsg:: (int)((p->packet[15] << 8) + p->packet[16]); */
|
||||||
//iv->setValue(iv->getPosByChFld(chan, FLD_YD, rec), rec, (uint8_t)(p->packet[25]));
|
iv->setValue(iv->getPosByChFld(0, FLD_EVT, rec), rec, (uint8_t)(p->packet[25+offset]));
|
||||||
//iv->setValue(iv->getPosByChFld(chan, FLD_EVT, rec), rec, (uint8_t)(p->packet[27]));
|
|
||||||
iv->setValue(iv->getPosByChFld(0, FLD_EVT, rec), rec, (uint8_t)(p->packet[21+offset]));
|
|
||||||
yield();
|
yield();
|
||||||
if (iv->alarmMesIndex < rec->record[iv->getPosByChFld(0, FLD_EVT, rec)]){
|
if (iv->alarmMesIndex < rec->record[iv->getPosByChFld(0, FLD_EVT, rec)]){
|
||||||
iv->alarmMesIndex = rec->record[iv->getPosByChFld(0, FLD_EVT, rec)];
|
iv->alarmMesIndex = rec->record[iv->getPosByChFld(0, FLD_EVT, rec)];
|
||||||
|
@ -443,8 +446,8 @@ class MiPayload {
|
||||||
iv->enqueCommand<InfoCommand>(AlarmData);
|
iv->enqueCommand<InfoCommand>(AlarmData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
iv->setValue(iv->getPosByChFld(0, FLD_YD, rec), rec, CALC_YD_CH0); // (getValue(iv->getPosByChFld(1, FLD_YD, rec), rec) + getValue(iv->getPosByChFld(2, FLD_YD, rec), rec)));
|
//iv->setValue(iv->getPosByChFld(0, FLD_YD, rec), rec, CALC_YD_CH0); // (getValue(iv->getPosByChFld(1, FLD_YD, rec), rec) + getValue(iv->getPosByChFld(2, FLD_YD, rec), rec)));
|
||||||
|
iv->setValue(iv->getPosByChFld(0, FLD_YD, rec), rec, calcYieldDayCh0(iv,0)); //datachan));
|
||||||
iv->doCalculations();
|
iv->doCalculations();
|
||||||
notify(mPayload[iv->id].txCmd);
|
notify(mPayload[iv->id].txCmd);
|
||||||
/*
|
/*
|
||||||
|
@ -502,7 +505,7 @@ class MiPayload {
|
||||||
FCODE = (uint8_t)(p->packet[27]);
|
FCODE = (uint8_t)(p->packet[27]);
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
DPRINTLN(DBG_INFO, F("Inverter ") + String(iv->id) + F(": data msg ") + p->packet[0]);
|
DPRINTLN(DBG_INFO, F("Inverter ") + String(iv->id) + F(": data msg 0x") + String(p->packet[0], HEX) + F(" channel ") + datachan);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool build(uint8_t id, bool *complete) {
|
bool build(uint8_t id, bool *complete) {
|
||||||
|
|
|
@ -15,6 +15,7 @@ include_dir = .
|
||||||
[env]
|
[env]
|
||||||
framework = arduino
|
framework = arduino
|
||||||
board_build.filesystem = littlefs
|
board_build.filesystem = littlefs
|
||||||
|
upload_speed = 921600
|
||||||
|
|
||||||
;build_flags =
|
;build_flags =
|
||||||
; ;;;;; Possible Debug options ;;;;;;
|
; ;;;;; Possible Debug options ;;;;;;
|
||||||
|
@ -40,6 +41,7 @@ lib_deps =
|
||||||
bblanchon/ArduinoJson
|
bblanchon/ArduinoJson
|
||||||
https://github.com/JChristensen/Timezone
|
https://github.com/JChristensen/Timezone
|
||||||
olikraus/U8g2
|
olikraus/U8g2
|
||||||
|
zinggjm/GxEPD2@^1.5.0
|
||||||
;esp8266/DNSServer
|
;esp8266/DNSServer
|
||||||
;esp8266/EEPROM
|
;esp8266/EEPROM
|
||||||
;esp8266/ESP8266WiFi
|
;esp8266/ESP8266WiFi
|
||||||
|
@ -54,8 +56,9 @@ board_build.f_cpu = 80000000L
|
||||||
build_flags = -D RELEASE
|
build_flags = -D RELEASE
|
||||||
monitor_filters =
|
monitor_filters =
|
||||||
;default ; Remove typical terminal control codes from input
|
;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
|
;log2file ; Log data to a file “platformio-device-monitor-*.log” located in the current working directory
|
||||||
|
esp8266_exception_decoder
|
||||||
|
|
||||||
[env:esp8266-debug]
|
[env:esp8266-debug]
|
||||||
platform = espressif8266
|
platform = espressif8266
|
||||||
|
@ -98,8 +101,9 @@ build_flags = -D RELEASE -std=gnu++14
|
||||||
build_unflags = -std=gnu++11
|
build_unflags = -std=gnu++11
|
||||||
monitor_filters =
|
monitor_filters =
|
||||||
;default ; Remove typical terminal control codes from input
|
;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
|
;log2file ; Log data to a file “platformio-device-monitor-*.log” located in the current working directory
|
||||||
|
esp32_exception_decoder
|
||||||
|
|
||||||
[env:esp32-wroom32-debug]
|
[env:esp32-wroom32-debug]
|
||||||
platform = espressif32
|
platform = espressif32
|
||||||
|
|
113
src/plugins/Display/Display.h
Normal file
113
src/plugins/Display/Display.h
Normal file
|
@ -0,0 +1,113 @@
|
||||||
|
#ifndef __DISPLAY__
|
||||||
|
#define __DISPLAY__
|
||||||
|
|
||||||
|
#include <Timezone.h>
|
||||||
|
#include <U8g2lib.h>
|
||||||
|
|
||||||
|
#include "../../hm/hmSystem.h"
|
||||||
|
#include "../../utils/helper.h"
|
||||||
|
#include "Display_Mono.h"
|
||||||
|
#include "Display_ePaper.h"
|
||||||
|
|
||||||
|
template <class HMSYSTEM>
|
||||||
|
class Display {
|
||||||
|
public:
|
||||||
|
Display() {}
|
||||||
|
|
||||||
|
void setup(display_t *cfg, HMSYSTEM *sys, uint32_t *utcTs, uint8_t disp_reset, const char *version) {
|
||||||
|
mCfg = cfg;
|
||||||
|
mSys = sys;
|
||||||
|
mUtcTs = utcTs;
|
||||||
|
mNewPayload = false;
|
||||||
|
mLoopCnt = 0;
|
||||||
|
mVersion = version;
|
||||||
|
|
||||||
|
if (mCfg->type == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
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__*/
|
149
src/plugins/Display/Display_Mono.cpp
Normal file
149
src/plugins/Display/Display_Mono.cpp
Normal file
|
@ -0,0 +1,149 @@
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
#include "Display_Mono.h"
|
||||||
|
|
||||||
|
#ifdef ESP8266
|
||||||
|
#include <ESP8266WiFi.h>
|
||||||
|
#elif defined(ESP32)
|
||||||
|
#include <WiFi.h>
|
||||||
|
#endif
|
||||||
|
#include "../../utils/helper.h"
|
||||||
|
|
||||||
|
//#ifdef U8X8_HAVE_HW_SPI
|
||||||
|
//#include <SPI.h>
|
||||||
|
//#endif
|
||||||
|
//#ifdef U8X8_HAVE_HW_I2C
|
||||||
|
//#include <Wire.h>
|
||||||
|
//#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);
|
||||||
|
}
|
36
src/plugins/Display/Display_Mono.h
Normal file
36
src/plugins/Display/Display_Mono.h
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <U8g2lib.h>
|
||||||
|
#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];
|
||||||
|
};
|
197
src/plugins/Display/Display_ePaper.cpp
Normal file
197
src/plugins/Display/Display_ePaper.cpp
Normal file
|
@ -0,0 +1,197 @@
|
||||||
|
#include "Display_ePaper.h"
|
||||||
|
|
||||||
|
#ifdef ESP8266
|
||||||
|
#include <ESP8266WiFi.h>
|
||||||
|
#elif defined(ESP32)
|
||||||
|
#include <WiFi.h>
|
||||||
|
#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, GxEPD2_150_BN::HEIGHT>(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
|
52
src/plugins/Display/Display_ePaper.h
Normal file
52
src/plugins/Display/Display_ePaper.h
Normal file
|
@ -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 <GFX.h>
|
||||||
|
// base class GxEPD2_GFX can be used to pass references or pointers to the display instance as parameter, uses ~1.2k more code
|
||||||
|
// enable GxEPD2_GFX base class
|
||||||
|
#define ENABLE_GxEPD2_GFX 1
|
||||||
|
|
||||||
|
#include <GxEPD2_3C.h>
|
||||||
|
#include <GxEPD2_BW.h>
|
||||||
|
#include <SPI.h>
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
// FreeFonts from Adafruit_GFX
|
||||||
|
#include <Fonts/FreeSans12pt7b.h>
|
||||||
|
#include <Fonts/FreeSans18pt7b.h>
|
||||||
|
#include <Fonts/FreeSans24pt7b.h>
|
||||||
|
#include <Fonts/FreeSans9pt7b.h>
|
||||||
|
|
||||||
|
// 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
|
329
src/plugins/Display/imagedata.h
Normal file
329
src/plugins/Display/imagedata.h
Normal file
|
@ -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 <avr/pgmspace.h>
|
||||||
|
#elif defined(ESP8266) || defined(ESP32)
|
||||||
|
#include <pgmspace.h>
|
||||||
|
#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__*/
|
|
@ -1,217 +0,0 @@
|
||||||
#ifndef __MONOCHROME_DISPLAY__
|
|
||||||
#define __MONOCHROME_DISPLAY__
|
|
||||||
|
|
||||||
#include <U8g2lib.h>
|
|
||||||
#include <Timezone.h>
|
|
||||||
|
|
||||||
#include "../../utils/helper.h"
|
|
||||||
#include "../../hm/hmSystem.h"
|
|
||||||
|
|
||||||
#define DISP_DEFAULT_TIMEOUT 60 // in seconds
|
|
||||||
|
|
||||||
|
|
||||||
static uint8_t bmp_logo[] PROGMEM = {
|
|
||||||
B00000000, B00000000, // ................
|
|
||||||
B11101100, B00110111, // ..##.######.##..
|
|
||||||
B11101100, B00110111, // ..##.######.##..
|
|
||||||
B11100000, B00000111, // .....######.....
|
|
||||||
B11010000, B00001011, // ....#.####.#....
|
|
||||||
B10011000, B00011001, // ...##..##..##...
|
|
||||||
B10000000, B00000001, // .......##.......
|
|
||||||
B00000000, B00000000, // ................
|
|
||||||
B01111000, B00011110, // ...####..####...
|
|
||||||
B11111100, B00111111, // ..############..
|
|
||||||
B01111100, B00111110, // ..#####..#####..
|
|
||||||
B00000000, B00000000, // ................
|
|
||||||
B11111100, B00111111, // ..############..
|
|
||||||
B11111110, B01111111, // .##############.
|
|
||||||
B01111110, B01111110, // .######..######.
|
|
||||||
B00000000, B00000000 // ................
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
static uint8_t bmp_arrow[] PROGMEM = {
|
|
||||||
B00000000, B00011100, B00011100, B00001110, B00001110, B11111110, B01111111,
|
|
||||||
B01110000, B01110000, B00110000, B00111000, B00011000, B01111111, B00111111,
|
|
||||||
B00011110, B00001110, B00000110, B00000000, B00000000, B00000000, B00000000
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
template<class HMSYSTEM>
|
|
||||||
class MonochromeDisplay {
|
|
||||||
public:
|
|
||||||
MonochromeDisplay() {}
|
|
||||||
|
|
||||||
void setup(display_t *cfg, HMSYSTEM *sys, uint32_t *utcTs, uint8_t disp_reset, const char *version) {
|
|
||||||
mCfg = cfg;
|
|
||||||
mSys = sys;
|
|
||||||
mUtcTs = utcTs;
|
|
||||||
mNewPayload = false;
|
|
||||||
mLoopCnt = 0;
|
|
||||||
mTimeout = DISP_DEFAULT_TIMEOUT; // power off timeout (after inverters go offline)
|
|
||||||
|
|
||||||
u8g2_cb_t *rot = (u8g2_cb_t *)((mCfg->rot180) ? U8G2_R2 : U8G2_R0);
|
|
||||||
|
|
||||||
if(mCfg->type) {
|
|
||||||
switch(mCfg->type) {
|
|
||||||
case 1:
|
|
||||||
mDisplay = new U8G2_PCD8544_84X48_F_4W_HW_SPI(rot, mCfg->pin0, mCfg->pin1, disp_reset);
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
mDisplay = new U8G2_SSD1306_128X64_NONAME_F_HW_I2C(rot, disp_reset, mCfg->pin0, mCfg->pin1);
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
mDisplay = new U8G2_SH1106_128X64_NONAME_F_HW_I2C(rot, disp_reset, mCfg->pin0, mCfg->pin1);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
mDisplay->begin();
|
|
||||||
|
|
||||||
mIsLarge = ((mDisplay->getWidth() > 120) && (mDisplay->getHeight() > 60));
|
|
||||||
calcLineHeights();
|
|
||||||
|
|
||||||
mDisplay->clearBuffer();
|
|
||||||
mDisplay->setContrast(mCfg->contrast);
|
|
||||||
printText("Ahoy!", 0, 35);
|
|
||||||
printText("ahoydtu.de", 2, 20);
|
|
||||||
printText(version, 3, 46);
|
|
||||||
mDisplay->sendBuffer();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void payloadEventListener(uint8_t cmd) {
|
|
||||||
mNewPayload = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void tickerSecond() {
|
|
||||||
if(mCfg->pwrSaveAtIvOffline) {
|
|
||||||
if(mTimeout != 0)
|
|
||||||
mTimeout--;
|
|
||||||
}
|
|
||||||
if(mNewPayload || ((++mLoopCnt % 10) == 0)) {
|
|
||||||
mNewPayload = false;
|
|
||||||
mLoopCnt = 0;
|
|
||||||
DataScreen();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
void DataScreen() {
|
|
||||||
if (mCfg->type == 0)
|
|
||||||
return;
|
|
||||||
if(*mUtcTs == 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
float totalPower = 0;
|
|
||||||
float totalYieldDay = 0;
|
|
||||||
float totalYieldTotal = 0;
|
|
||||||
|
|
||||||
bool isprod = false;
|
|
||||||
|
|
||||||
Inverter<> *iv;
|
|
||||||
record_t<> *rec;
|
|
||||||
for (uint8_t i = 0; i < mSys->getNumInverters(); i++) {
|
|
||||||
iv = mSys->getInverterByPos(i);
|
|
||||||
rec = iv->getRecordStruct(RealTimeRunData_Debug);
|
|
||||||
if (iv == NULL)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (iv->isProducing(*mUtcTs))
|
|
||||||
isprod = true;
|
|
||||||
|
|
||||||
totalPower += iv->getChannelFieldValue(CH0, FLD_PAC, rec);
|
|
||||||
totalYieldDay += iv->getChannelFieldValue(CH0, FLD_YD, rec);
|
|
||||||
totalYieldTotal += iv->getChannelFieldValue(CH0, FLD_YT, rec);
|
|
||||||
}
|
|
||||||
|
|
||||||
mDisplay->clearBuffer();
|
|
||||||
|
|
||||||
// Logos
|
|
||||||
// pxMovement +x (0 - 6 px)
|
|
||||||
uint8_t ex = (_mExtra % 7);
|
|
||||||
if (isprod) {
|
|
||||||
mDisplay->drawXBMP(5 + ex, 1, 8, 17, bmp_arrow);
|
|
||||||
if (mCfg->logoEn)
|
|
||||||
mDisplay->drawXBMP(mDisplay->getWidth() - 24 + ex, 2, 16, 16, bmp_logo);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((totalPower > 0) && isprod) {
|
|
||||||
mTimeout = DISP_DEFAULT_TIMEOUT;
|
|
||||||
mDisplay->setPowerSave(false);
|
|
||||||
mDisplay->setContrast(mCfg->contrast);
|
|
||||||
if (totalPower > 999)
|
|
||||||
snprintf(_fmtText, sizeof(_fmtText), "%2.1f kW", (totalPower / 1000));
|
|
||||||
else
|
|
||||||
snprintf(_fmtText, sizeof(_fmtText), "%3.0f W", totalPower);
|
|
||||||
printText(_fmtText, 0, 20);
|
|
||||||
} else {
|
|
||||||
printText("offline", 0, 25);
|
|
||||||
if(mCfg->pwrSaveAtIvOffline) {
|
|
||||||
if(mTimeout == 0)
|
|
||||||
mDisplay->setPowerSave(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
snprintf(_fmtText, sizeof(_fmtText), "today: %4.0f Wh", totalYieldDay);
|
|
||||||
printText(_fmtText, 1);
|
|
||||||
|
|
||||||
snprintf(_fmtText, sizeof(_fmtText), "total: %.1f kWh", totalYieldTotal);
|
|
||||||
printText(_fmtText, 2);
|
|
||||||
|
|
||||||
IPAddress ip = WiFi.localIP();
|
|
||||||
if (!(_mExtra % 10) && (ip)) {
|
|
||||||
printText(ip.toString().c_str(), 3);
|
|
||||||
} else {
|
|
||||||
// Get current time
|
|
||||||
if(mIsLarge)
|
|
||||||
printText(ah::getDateTimeStr(gTimezone.toLocal(*mUtcTs)).c_str(), 3);
|
|
||||||
else
|
|
||||||
printText(ah::getTimeStr(gTimezone.toLocal(*mUtcTs)).c_str(), 3);
|
|
||||||
}
|
|
||||||
mDisplay->sendBuffer();
|
|
||||||
|
|
||||||
_mExtra++;
|
|
||||||
}
|
|
||||||
|
|
||||||
void calcLineHeights() {
|
|
||||||
uint8_t yOff = 0;
|
|
||||||
for(uint8_t i = 0; i < 4; i++) {
|
|
||||||
setFont(i);
|
|
||||||
yOff += (mDisplay->getMaxCharHeight() + 1);
|
|
||||||
mLineOffsets[i] = yOff;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
inline void setFont(uint8_t line) {
|
|
||||||
switch (line) {
|
|
||||||
case 0: mDisplay->setFont((mIsLarge) ? u8g2_font_ncenB14_tr : u8g2_font_lubBI14_tr); break;
|
|
||||||
case 3: mDisplay->setFont(u8g2_font_5x8_tr); break;
|
|
||||||
default: mDisplay->setFont((mIsLarge) ? u8g2_font_ncenB10_tr : u8g2_font_5x8_tr); break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void printText(const char* text, uint8_t line, uint8_t dispX = 5) {
|
|
||||||
if(!mIsLarge)
|
|
||||||
dispX = (line == 0) ? 10 : 5;
|
|
||||||
setFont(line);
|
|
||||||
if(mCfg->pxShift)
|
|
||||||
dispX += (_mExtra % 7); // add pixel movement
|
|
||||||
mDisplay->drawStr(dispX, mLineOffsets[line], text);
|
|
||||||
}
|
|
||||||
|
|
||||||
// private member variables
|
|
||||||
U8G2* mDisplay;
|
|
||||||
|
|
||||||
uint8_t _mExtra;
|
|
||||||
uint16_t mTimeout; // interval at which to power save (milliseconds)
|
|
||||||
char _fmtText[32];
|
|
||||||
|
|
||||||
bool mNewPayload;
|
|
||||||
bool mIsLarge;
|
|
||||||
uint8_t mLoopCnt;
|
|
||||||
uint32_t *mUtcTs;
|
|
||||||
uint8_t mLineOffsets[5];
|
|
||||||
display_t *mCfg;
|
|
||||||
HMSYSTEM *mSys;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif /*__MONOCHROME_DISPLAY__*/
|
|
|
@ -406,7 +406,7 @@ class PubMqtt {
|
||||||
return (pos >= DEVICE_CLS_ASSIGN_LIST_LEN) ? NULL : stateClasses[deviceFieldAssignment[pos].stateClsId];
|
return (pos >= DEVICE_CLS_ASSIGN_LIST_LEN) ? NULL : stateClasses[deviceFieldAssignment[pos].stateClsId];
|
||||||
}
|
}
|
||||||
|
|
||||||
bool processIvStatus() {
|
bool processIvStatus() {
|
||||||
// returns true if any inverter is available
|
// returns true if any inverter is available
|
||||||
bool allAvail = true; // shows if all enabled inverters are available
|
bool allAvail = true; // shows if all enabled inverters are available
|
||||||
bool anyAvail = false; // shows if at least one enabled inverter is available
|
bool anyAvail = false; // shows if at least one enabled inverter is available
|
||||||
|
@ -419,17 +419,19 @@ class PubMqtt {
|
||||||
iv = mSys->getInverterByPos(id);
|
iv = mSys->getInverterByPos(id);
|
||||||
if (NULL == iv)
|
if (NULL == iv)
|
||||||
continue; // skip to next inverter
|
continue; // skip to next inverter
|
||||||
|
if (!iv->config->enabled)
|
||||||
|
continue; // skip to next inverter
|
||||||
|
|
||||||
rec = iv->getRecordStruct(RealTimeRunData_Debug);
|
rec = iv->getRecordStruct(RealTimeRunData_Debug);
|
||||||
|
|
||||||
// inverter status
|
// inverter status
|
||||||
uint8_t status = MQTT_STATUS_NOT_AVAIL_NOT_PROD;
|
uint8_t status = MQTT_STATUS_NOT_AVAIL_NOT_PROD;
|
||||||
if (iv->config->enabled) {
|
if (iv->isAvailable(*mUtcTimestamp)) {
|
||||||
if (iv->isAvailable(*mUtcTimestamp))
|
anyAvail = true;
|
||||||
status = (iv->isProducing(*mUtcTimestamp)) ? MQTT_STATUS_AVAIL_PROD : MQTT_STATUS_AVAIL_NOT_PROD;
|
status = (iv->isProducing(*mUtcTimestamp)) ? MQTT_STATUS_AVAIL_PROD : MQTT_STATUS_AVAIL_NOT_PROD;
|
||||||
else // inverter is enabled but not available
|
|
||||||
allAvail = false;
|
|
||||||
}
|
}
|
||||||
|
else // inverter is enabled but not available
|
||||||
|
allAvail = false;
|
||||||
|
|
||||||
if(mLastIvState[id] != status) {
|
if(mLastIvState[id] != status) {
|
||||||
// if status changed from producing to not producing send last data immediately
|
// if status changed from producing to not producing send last data immediately
|
||||||
|
@ -439,11 +441,11 @@ class PubMqtt {
|
||||||
mLastIvState[id] = status;
|
mLastIvState[id] = status;
|
||||||
changed = true;
|
changed = true;
|
||||||
|
|
||||||
snprintf(topic, 32 + MAX_NAME_LENGTH, "%s/%s", iv->config->name, mqttStr[MQTT_STR_AVAILABLE]);
|
snprintf(topic, 32 + MAX_NAME_LENGTH, "%s/available", iv->config->name);
|
||||||
snprintf(val, 40, "%d", status);
|
snprintf(val, 40, "%d", status);
|
||||||
publish(topic, val, true);
|
publish(topic, val, true);
|
||||||
|
|
||||||
snprintf(topic, 32 + MAX_NAME_LENGTH, "%s/%s", iv->config->name, mqttStr[MQTT_STR_LAST_SUCCESS]);
|
snprintf(topic, 32 + MAX_NAME_LENGTH, "%s/last_success", iv->config->name);
|
||||||
snprintf(val, 40, "%d", iv->getLastTs(rec));
|
snprintf(val, 40, "%d", iv->getLastTs(rec));
|
||||||
publish(topic, val, true);
|
publish(topic, val, true);
|
||||||
}
|
}
|
||||||
|
@ -451,7 +453,7 @@ class PubMqtt {
|
||||||
|
|
||||||
if(changed) {
|
if(changed) {
|
||||||
snprintf(val, 32, "%d", ((allAvail) ? MQTT_STATUS_ONLINE : ((anyAvail) ? MQTT_STATUS_PARTIAL : MQTT_STATUS_OFFLINE)));
|
snprintf(val, 32, "%d", ((allAvail) ? MQTT_STATUS_ONLINE : ((anyAvail) ? MQTT_STATUS_PARTIAL : MQTT_STATUS_OFFLINE)));
|
||||||
publish(subtopics[MQTT_STATUS], val, true);
|
publish("status", val, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
return anyAvail;
|
return anyAvail;
|
||||||
|
@ -474,24 +476,26 @@ class PubMqtt {
|
||||||
char topic[7 + MQTT_TOPIC_LEN], val[40];
|
char topic[7 + MQTT_TOPIC_LEN], val[40];
|
||||||
record_t<> *rec = iv->getRecordStruct(curInfoCmd);
|
record_t<> *rec = iv->getRecordStruct(curInfoCmd);
|
||||||
|
|
||||||
for (uint8_t i = 0; i < rec->length; i++) {
|
if (iv->getLastTs(rec) > 0) {
|
||||||
bool retained = false;
|
for (uint8_t i = 0; i < rec->length; i++) {
|
||||||
if (curInfoCmd == RealTimeRunData_Debug) {
|
bool retained = false;
|
||||||
switch (rec->assign[i].fieldId) {
|
if (curInfoCmd == RealTimeRunData_Debug) {
|
||||||
case FLD_YT:
|
switch (rec->assign[i].fieldId) {
|
||||||
case FLD_YD:
|
case FLD_YT:
|
||||||
if ((rec->assign[i].ch == CH0) && (!iv->isProducing(*mUtcTimestamp))) // avoids returns to 0 on restart
|
case FLD_YD:
|
||||||
continue;
|
if ((rec->assign[i].ch == CH0) && (!iv->isProducing(*mUtcTimestamp))) // avoids returns to 0 on restart
|
||||||
retained = true;
|
continue;
|
||||||
break;
|
retained = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
snprintf(topic, 32 + MAX_NAME_LENGTH, "%s/ch%d/%s", iv->config->name, rec->assign[i].ch, fields[rec->assign[i].fieldId]);
|
||||||
|
snprintf(val, 40, "%g", ah::round3(iv->getValue(i, rec)));
|
||||||
|
publish(topic, val, retained);
|
||||||
|
|
||||||
|
yield();
|
||||||
}
|
}
|
||||||
|
|
||||||
snprintf(topic, 32 + MAX_NAME_LENGTH, "%s/ch%d/%s", iv->config->name, rec->assign[i].ch, fields[rec->assign[i].fieldId]);
|
|
||||||
snprintf(val, 40, "%g", ah::round3(iv->getValue(i, rec)));
|
|
||||||
publish(topic, val, retained);
|
|
||||||
|
|
||||||
yield();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -512,42 +516,49 @@ class PubMqtt {
|
||||||
uint8_t curInfoCmd = mSendList.front();
|
uint8_t curInfoCmd = mSendList.front();
|
||||||
|
|
||||||
if ((curInfoCmd != RealTimeRunData_Debug) || !RTRDataHasBeenSent) { // send RTR Data only once
|
if ((curInfoCmd != RealTimeRunData_Debug) || !RTRDataHasBeenSent) { // send RTR Data only once
|
||||||
|
bool sendTotals = (curInfoCmd == RealTimeRunData_Debug);
|
||||||
|
|
||||||
for (uint8_t id = 0; id < mSys->getNumInverters(); id++) {
|
for (uint8_t id = 0; id < mSys->getNumInverters(); id++) {
|
||||||
Inverter<> *iv = mSys->getInverterByPos(id);
|
Inverter<> *iv = mSys->getInverterByPos(id);
|
||||||
if (NULL == iv)
|
if (NULL == iv)
|
||||||
continue; // skip to next inverter
|
continue; // skip to next inverter
|
||||||
|
if (!iv->config->enabled)
|
||||||
|
continue; // skip to next inverter
|
||||||
|
|
||||||
// send RTR Data only if status is available
|
// send RTR Data only if status is available
|
||||||
if ((curInfoCmd != RealTimeRunData_Debug) || (MQTT_STATUS_AVAIL_PROD == mLastIvState[id]))
|
if ((curInfoCmd != RealTimeRunData_Debug) || (MQTT_STATUS_NOT_AVAIL_NOT_PROD != mLastIvState[id]))
|
||||||
sendData(iv, curInfoCmd);
|
sendData(iv, curInfoCmd);
|
||||||
|
|
||||||
// calculate total values for RealTimeRunData_Debug
|
// calculate total values for RealTimeRunData_Debug
|
||||||
if (curInfoCmd == RealTimeRunData_Debug) {
|
if (sendTotals) {
|
||||||
record_t<> *rec = iv->getRecordStruct(curInfoCmd);
|
record_t<> *rec = iv->getRecordStruct(curInfoCmd);
|
||||||
|
|
||||||
for (uint8_t i = 0; i < rec->length; i++) {
|
sendTotals &= (iv->getLastTs(rec) > 0);
|
||||||
if (CH0 == rec->assign[i].ch) {
|
if (sendTotals) {
|
||||||
switch (rec->assign[i].fieldId) {
|
for (uint8_t i = 0; i < rec->length; i++) {
|
||||||
case FLD_PAC:
|
if (CH0 == rec->assign[i].ch) {
|
||||||
total[0] += iv->getValue(i, rec);
|
switch (rec->assign[i].fieldId) {
|
||||||
break;
|
case FLD_PAC:
|
||||||
case FLD_YT:
|
total[0] += iv->getValue(i, rec);
|
||||||
total[1] += iv->getValue(i, rec);
|
break;
|
||||||
break;
|
case FLD_YT:
|
||||||
case FLD_YD:
|
total[1] += iv->getValue(i, rec);
|
||||||
total[2] += iv->getValue(i, rec);
|
break;
|
||||||
break;
|
case FLD_YD:
|
||||||
case FLD_PDC:
|
total[2] += iv->getValue(i, rec);
|
||||||
total[3] += iv->getValue(i, rec);
|
break;
|
||||||
break;
|
case FLD_PDC:
|
||||||
|
total[3] += iv->getValue(i, rec);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
yield();
|
|
||||||
}
|
}
|
||||||
|
yield();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (curInfoCmd == RealTimeRunData_Debug) {
|
if (sendTotals) {
|
||||||
uint8_t fieldId;
|
uint8_t fieldId;
|
||||||
for (uint8_t i = 0; i < 4; i++) {
|
for (uint8_t i = 0; i < 4; i++) {
|
||||||
switch (i) {
|
switch (i) {
|
||||||
|
@ -565,7 +576,7 @@ class PubMqtt {
|
||||||
fieldId = FLD_PDC;
|
fieldId = FLD_PDC;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
snprintf(topic, 32 + MAX_NAME_LENGTH, "%s/%s", mqttStr[MQTT_STR_TOTAL], fields[fieldId]);
|
snprintf(topic, 32 + MAX_NAME_LENGTH, "total/%s", fields[fieldId]);
|
||||||
snprintf(val, 40, "%g", ah::round3(total[i]));
|
snprintf(val, 40, "%g", ah::round3(total[i]));
|
||||||
publish(topic, val, true);
|
publish(topic, val, true);
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,22 +8,24 @@
|
||||||
|
|
||||||
#include "../utils/dbg.h"
|
#include "../utils/dbg.h"
|
||||||
#ifdef ESP32
|
#ifdef ESP32
|
||||||
#include "AsyncTCP.h"
|
#include "AsyncTCP.h"
|
||||||
#else
|
#else
|
||||||
#include "ESPAsyncTCP.h"
|
#include "ESPAsyncTCP.h"
|
||||||
#endif
|
#endif
|
||||||
#include "ESPAsyncWebServer.h"
|
#include "../appInterface.h"
|
||||||
#include "AsyncJson.h"
|
|
||||||
#include "../hm/hmSystem.h"
|
#include "../hm/hmSystem.h"
|
||||||
#include "../utils/helper.h"
|
#include "../utils/helper.h"
|
||||||
|
#include "AsyncJson.h"
|
||||||
#include "../appInterface.h"
|
#include "ESPAsyncWebServer.h"
|
||||||
|
|
||||||
#if defined(F) && defined(ESP32)
|
#if defined(F) && defined(ESP32)
|
||||||
#undef F
|
#undef F
|
||||||
#define F(sl) (sl)
|
#define F(sl) (sl)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
const uint8_t acList[] = {FLD_UAC, FLD_IAC, FLD_PAC, FLD_F, FLD_PF, FLD_T, FLD_YT, FLD_YD, FLD_PDC, FLD_EFF, FLD_Q};
|
||||||
|
const uint8_t dcList[] = {FLD_UDC, FLD_IDC, FLD_PDC, FLD_YD, FLD_YT, FLD_IRR};
|
||||||
|
|
||||||
template<class HMSYSTEM, class HMRADIO>
|
template<class HMSYSTEM, class HMRADIO>
|
||||||
class RestApi {
|
class RestApi {
|
||||||
public:
|
public:
|
||||||
|
@ -72,7 +74,7 @@ class RestApi {
|
||||||
mHeapFrag = ESP.getHeapFragmentation();
|
mHeapFrag = ESP.getHeapFragmentation();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
AsyncJsonResponse* response = new AsyncJsonResponse(false, 8192);
|
AsyncJsonResponse* response = new AsyncJsonResponse(false, 6000);
|
||||||
JsonObject root = response->getRoot();
|
JsonObject root = response->getRoot();
|
||||||
|
|
||||||
String path = request->url().substring(5);
|
String path = request->url().substring(5);
|
||||||
|
@ -84,7 +86,6 @@ class RestApi {
|
||||||
else if(path == "reboot") getReboot(root);
|
else if(path == "reboot") getReboot(root);
|
||||||
else if(path == "statistics") getStatistics(root);
|
else if(path == "statistics") getStatistics(root);
|
||||||
else if(path == "inverter/list") getInverterList(root);
|
else if(path == "inverter/list") getInverterList(root);
|
||||||
else if(path == "menu") getMenu(root);
|
|
||||||
else if(path == "index") getIndex(root);
|
else if(path == "index") getIndex(root);
|
||||||
else if(path == "setup") getSetup(root);
|
else if(path == "setup") getSetup(root);
|
||||||
else if(path == "setup/networks") getNetworks(root);
|
else if(path == "setup/networks") getNetworks(root);
|
||||||
|
@ -93,8 +94,12 @@ class RestApi {
|
||||||
else if(path == "record/alarm") getRecord(root, AlarmData);
|
else if(path == "record/alarm") getRecord(root, AlarmData);
|
||||||
else if(path == "record/config") getRecord(root, SystemConfigPara);
|
else if(path == "record/config") getRecord(root, SystemConfigPara);
|
||||||
else if(path == "record/live") getRecord(root, RealTimeRunData_Debug);
|
else if(path == "record/live") getRecord(root, RealTimeRunData_Debug);
|
||||||
else
|
else {
|
||||||
getNotFound(root, F("http://") + request->host() + F("/api/"));
|
if(path.substring(0, 12) == "inverter/id/")
|
||||||
|
getInverter(root, request->url().substring(17).toInt());
|
||||||
|
else
|
||||||
|
getNotFound(root, F("http://") + request->host() + F("/api/"));
|
||||||
|
}
|
||||||
|
|
||||||
//DPRINTLN(DBG_INFO, "API mem usage: " + String(root.memoryUsage()));
|
//DPRINTLN(DBG_INFO, "API mem usage: " + String(root.memoryUsage()));
|
||||||
response->addHeader("Access-Control-Allow-Origin", "*");
|
response->addHeader("Access-Control-Allow-Origin", "*");
|
||||||
|
@ -154,13 +159,14 @@ class RestApi {
|
||||||
ep[F("record/live")] = url + F("record/live");
|
ep[F("record/live")] = url + F("record/live");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void onDwnldSetup(AsyncWebServerRequest *request) {
|
void onDwnldSetup(AsyncWebServerRequest *request) {
|
||||||
AsyncWebServerResponse *response;
|
AsyncWebServerResponse *response;
|
||||||
|
|
||||||
File fp = LittleFS.open("/settings.json", "r");
|
File fp = LittleFS.open("/settings.json", "r");
|
||||||
if(!fp) {
|
if(!fp) {
|
||||||
DPRINTLN(DBG_ERROR, F("failed to load settings"));
|
DPRINTLN(DBG_ERROR, F("failed to load settings"));
|
||||||
response = request->beginResponse(200, F("application/json"), "{}");
|
response = request->beginResponse(200, F("application/json; charset=utf-8"), "{}");
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
String tmp = fp.readString();
|
String tmp = fp.readString();
|
||||||
|
@ -173,7 +179,7 @@ class RestApi {
|
||||||
tmp.remove(i, tmp.indexOf("\"", i)-i);
|
tmp.remove(i, tmp.indexOf("\"", i)-i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
response = request->beginResponse(200, F("application/json"), tmp);
|
response = request->beginResponse(200, F("application/json; charset=utf-8"), tmp);
|
||||||
}
|
}
|
||||||
|
|
||||||
response->addHeader("Content-Type", "application/octet-stream");
|
response->addHeader("Content-Type", "application/octet-stream");
|
||||||
|
@ -184,10 +190,11 @@ class RestApi {
|
||||||
}
|
}
|
||||||
|
|
||||||
void getGeneric(JsonObject obj) {
|
void getGeneric(JsonObject obj) {
|
||||||
obj[F("version")] = String(mApp->getVersion());
|
obj[F("wifi_rssi")] = (WiFi.status() != WL_CONNECTED) ? 0 : WiFi.RSSI();
|
||||||
obj[F("build")] = String(AUTO_GIT_HASH);
|
obj[F("ts_uptime")] = mApp->getUptime();
|
||||||
obj[F("wifi_rssi")] = (WiFi.status() != WL_CONNECTED) ? 0 : WiFi.RSSI();
|
obj[F("menu_prot")] = mApp->getProtection();
|
||||||
obj[F("ts_uptime")] = mApp->getUptime();
|
obj[F("menu_mask")] = (uint16_t)(mConfig->sys.protectionMask );
|
||||||
|
obj[F("menu_protEn")] = (bool) (strlen(mConfig->sys.adminPwd) > 0);
|
||||||
|
|
||||||
#if defined(ESP32)
|
#if defined(ESP32)
|
||||||
obj[F("esp_type")] = F("ESP32");
|
obj[F("esp_type")] = F("ESP32");
|
||||||
|
@ -199,6 +206,7 @@ class RestApi {
|
||||||
void getSysInfo(JsonObject obj) {
|
void getSysInfo(JsonObject obj) {
|
||||||
obj[F("ssid")] = mConfig->sys.stationSsid;
|
obj[F("ssid")] = mConfig->sys.stationSsid;
|
||||||
obj[F("device_name")] = mConfig->sys.deviceName;
|
obj[F("device_name")] = mConfig->sys.deviceName;
|
||||||
|
obj[F("dark_mode")] = (bool)mConfig->sys.darkMode;
|
||||||
|
|
||||||
obj[F("mac")] = WiFi.macAddress();
|
obj[F("mac")] = WiFi.macAddress();
|
||||||
obj[F("hostname")] = mConfig->sys.deviceName;
|
obj[F("hostname")] = mConfig->sys.deviceName;
|
||||||
|
@ -245,15 +253,12 @@ class RestApi {
|
||||||
}
|
}
|
||||||
|
|
||||||
void getHtmlSystem(JsonObject obj) {
|
void getHtmlSystem(JsonObject obj) {
|
||||||
getMenu(obj.createNestedObject(F("menu")));
|
|
||||||
getSysInfo(obj.createNestedObject(F("system")));
|
getSysInfo(obj.createNestedObject(F("system")));
|
||||||
getGeneric(obj.createNestedObject(F("generic")));
|
getGeneric(obj.createNestedObject(F("generic")));
|
||||||
obj[F("html")] = F("<a href=\"/factory\" class=\"btn\">Factory Reset</a><br/><br/><a href=\"/reboot\" class=\"btn\">Reboot</a>");
|
obj[F("html")] = F("<a href=\"/factory\" class=\"btn\">Factory Reset</a><br/><br/><a href=\"/reboot\" class=\"btn\">Reboot</a>");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void getHtmlLogout(JsonObject obj) {
|
void getHtmlLogout(JsonObject obj) {
|
||||||
getMenu(obj.createNestedObject(F("menu")));
|
|
||||||
getGeneric(obj.createNestedObject(F("generic")));
|
getGeneric(obj.createNestedObject(F("generic")));
|
||||||
obj[F("refresh")] = 3;
|
obj[F("refresh")] = 3;
|
||||||
obj[F("refresh_url")] = "/";
|
obj[F("refresh_url")] = "/";
|
||||||
|
@ -261,7 +266,6 @@ class RestApi {
|
||||||
}
|
}
|
||||||
|
|
||||||
void getHtmlSave(JsonObject obj) {
|
void getHtmlSave(JsonObject obj) {
|
||||||
getMenu(obj.createNestedObject(F("menu")));
|
|
||||||
getGeneric(obj.createNestedObject(F("generic")));
|
getGeneric(obj.createNestedObject(F("generic")));
|
||||||
obj[F("refresh")] = 2;
|
obj[F("refresh")] = 2;
|
||||||
obj[F("refresh_url")] = "/setup";
|
obj[F("refresh_url")] = "/setup";
|
||||||
|
@ -269,7 +273,6 @@ class RestApi {
|
||||||
}
|
}
|
||||||
|
|
||||||
void getReboot(JsonObject obj) {
|
void getReboot(JsonObject obj) {
|
||||||
getMenu(obj.createNestedObject(F("menu")));
|
|
||||||
getGeneric(obj.createNestedObject(F("generic")));
|
getGeneric(obj.createNestedObject(F("generic")));
|
||||||
obj[F("refresh")] = 10;
|
obj[F("refresh")] = 10;
|
||||||
obj[F("refresh_url")] = "/";
|
obj[F("refresh_url")] = "/";
|
||||||
|
@ -316,6 +319,41 @@ class RestApi {
|
||||||
obj[F("rstComStop")] = (bool)mConfig->inst.rstValsCommStop;
|
obj[F("rstComStop")] = (bool)mConfig->inst.rstValsCommStop;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void getInverter(JsonObject obj, uint8_t id) {
|
||||||
|
Inverter<> *iv = mSys->getInverterByPos(id);
|
||||||
|
if(NULL != iv) {
|
||||||
|
record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug);
|
||||||
|
obj[F("id")] = id;
|
||||||
|
obj[F("enabled")] = (bool)iv->config->enabled;
|
||||||
|
obj[F("name")] = String(iv->config->name);
|
||||||
|
obj[F("serial")] = String(iv->config->serial.u64, HEX);
|
||||||
|
obj[F("version")] = String(iv->getFwVersion());
|
||||||
|
obj[F("power_limit_read")] = ah::round3(iv->actPowerLimit);
|
||||||
|
obj[F("ts_last_success")] = rec->ts;
|
||||||
|
|
||||||
|
JsonArray ch = obj.createNestedArray("ch");
|
||||||
|
|
||||||
|
// AC
|
||||||
|
uint8_t pos;
|
||||||
|
obj[F("ch_name")][0] = "AC";
|
||||||
|
JsonArray ch0 = ch.createNestedArray();
|
||||||
|
for (uint8_t fld = 0; fld < sizeof(acList); fld++) {
|
||||||
|
pos = (iv->getPosByChFld(CH0, acList[fld], rec));
|
||||||
|
ch0[fld] = (0xff != pos) ? ah::round3(iv->getValue(pos, rec)) : 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// DC
|
||||||
|
for(uint8_t j = 0; j < iv->channels; j ++) {
|
||||||
|
obj[F("ch_name")][j+1] = iv->config->chName[j];
|
||||||
|
JsonArray cur = ch.createNestedArray();
|
||||||
|
for (uint8_t fld = 0; fld < sizeof(dcList); fld++) {
|
||||||
|
pos = (iv->getPosByChFld((j+1), dcList[fld], rec));
|
||||||
|
cur[fld] = (0xff != pos) ? ah::round3(iv->getValue(pos, rec)) : 0.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void getMqtt(JsonObject obj) {
|
void getMqtt(JsonObject obj) {
|
||||||
obj[F("broker")] = String(mConfig->mqtt.broker);
|
obj[F("broker")] = String(mConfig->mqtt.broker);
|
||||||
obj[F("port")] = String(mConfig->mqtt.port);
|
obj[F("port")] = String(mConfig->mqtt.port);
|
||||||
|
@ -376,64 +414,21 @@ class RestApi {
|
||||||
}
|
}
|
||||||
|
|
||||||
void getDisplay(JsonObject obj) {
|
void getDisplay(JsonObject obj) {
|
||||||
obj[F("disp_type")] = (uint8_t)mConfig->plugin.display.type;
|
obj[F("disp_typ")] = (uint8_t)mConfig->plugin.display.type;
|
||||||
obj[F("disp_pwr")] = (bool)mConfig->plugin.display.pwrSaveAtIvOffline;
|
obj[F("disp_pwr")] = (bool)mConfig->plugin.display.pwrSaveAtIvOffline;
|
||||||
obj[F("logo_en")] = (bool)mConfig->plugin.display.logoEn;
|
obj[F("disp_pxshift")] = (bool)mConfig->plugin.display.pxShift;
|
||||||
obj[F("px_shift")] = (bool)mConfig->plugin.display.pxShift;
|
obj[F("disp_rot")] = (uint8_t)mConfig->plugin.display.rot;
|
||||||
obj[F("rot180")] = (bool)mConfig->plugin.display.rot180;
|
obj[F("disp_cont")] = (uint8_t)mConfig->plugin.display.contrast;
|
||||||
obj[F("contrast")] = (uint8_t)mConfig->plugin.display.contrast;
|
obj[F("disp_clk")] = mConfig->plugin.display.disp_clk;
|
||||||
obj[F("pinDisp0")] = mConfig->plugin.display.pin0;
|
obj[F("disp_data")] = mConfig->plugin.display.disp_data;
|
||||||
obj[F("pinDisp1")] = mConfig->plugin.display.pin1;
|
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;
|
||||||
void getMenu(JsonObject obj) {
|
obj[F("disp_bsy")] = mConfig->plugin.display.disp_busy;
|
||||||
uint8_t i = 0;
|
|
||||||
uint16_t mask = (mApp->getProtection()) ? mConfig->sys.protectionMask : 0;
|
|
||||||
if(!CHECK_MASK(mask, PROT_MASK_LIVE)) {
|
|
||||||
obj[F("name")][i] = "Live";
|
|
||||||
obj[F("link")][i++] = "/live";
|
|
||||||
}
|
|
||||||
if(!CHECK_MASK(mask, PROT_MASK_SERIAL)) {
|
|
||||||
obj[F("name")][i] = "Serial / Control";
|
|
||||||
obj[F("link")][i++] = "/serial";
|
|
||||||
}
|
|
||||||
if(!CHECK_MASK(mask, PROT_MASK_SETUP)) {
|
|
||||||
obj[F("name")][i] = "Settings";
|
|
||||||
obj[F("link")][i++] = "/setup";
|
|
||||||
}
|
|
||||||
obj[F("name")][i++] = "-";
|
|
||||||
obj[F("name")][i] = "REST API";
|
|
||||||
obj[F("link")][i] = "/api";
|
|
||||||
obj[F("trgt")][i++] = "_blank";
|
|
||||||
obj[F("name")][i++] = "-";
|
|
||||||
if(!CHECK_MASK(mask, PROT_MASK_UPDATE)) {
|
|
||||||
obj[F("name")][i] = "Update";
|
|
||||||
obj[F("link")][i++] = "/update";
|
|
||||||
}
|
|
||||||
if(!CHECK_MASK(mask, PROT_MASK_SYSTEM)) {
|
|
||||||
obj[F("name")][i] = "System";
|
|
||||||
obj[F("link")][i++] = "/system";
|
|
||||||
}
|
|
||||||
obj[F("name")][i++] = "-";
|
|
||||||
obj[F("name")][i] = "Documentation";
|
|
||||||
obj[F("link")][i] = "https://ahoydtu.de";
|
|
||||||
obj[F("trgt")][i++] = "_blank";
|
|
||||||
if(strlen(mConfig->sys.adminPwd) > 0) {
|
|
||||||
obj[F("name")][i++] = "-";
|
|
||||||
if(mApp->getProtection()) {
|
|
||||||
obj[F("name")][i] = "Login";
|
|
||||||
obj[F("link")][i++] = "/login";
|
|
||||||
} else {
|
|
||||||
obj[F("name")][i] = "Logout";
|
|
||||||
obj[F("link")][i++] = "/logout";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void getIndex(JsonObject obj) {
|
void getIndex(JsonObject obj) {
|
||||||
getMenu(obj.createNestedObject(F("menu")));
|
|
||||||
getGeneric(obj.createNestedObject(F("generic")));
|
getGeneric(obj.createNestedObject(F("generic")));
|
||||||
|
|
||||||
obj[F("ts_now")] = mApp->getTimestamp();
|
obj[F("ts_now")] = mApp->getTimestamp();
|
||||||
obj[F("ts_sunrise")] = mApp->getSunrise();
|
obj[F("ts_sunrise")] = mApp->getSunrise();
|
||||||
obj[F("ts_sunset")] = mApp->getSunset();
|
obj[F("ts_sunset")] = mApp->getSunset();
|
||||||
|
@ -482,10 +477,9 @@ class RestApi {
|
||||||
}
|
}
|
||||||
|
|
||||||
void getSetup(JsonObject obj) {
|
void getSetup(JsonObject obj) {
|
||||||
getMenu(obj.createNestedObject(F("menu")));
|
|
||||||
getGeneric(obj.createNestedObject(F("generic")));
|
getGeneric(obj.createNestedObject(F("generic")));
|
||||||
getSysInfo(obj.createNestedObject(F("system")));
|
getSysInfo(obj.createNestedObject(F("system")));
|
||||||
getInverterList(obj.createNestedObject(F("inverter")));
|
//getInverterList(obj.createNestedObject(F("inverter")));
|
||||||
getMqtt(obj.createNestedObject(F("mqtt")));
|
getMqtt(obj.createNestedObject(F("mqtt")));
|
||||||
getNtp(obj.createNestedObject(F("ntp")));
|
getNtp(obj.createNestedObject(F("ntp")));
|
||||||
getSun(obj.createNestedObject(F("sun")));
|
getSun(obj.createNestedObject(F("sun")));
|
||||||
|
@ -502,14 +496,29 @@ class RestApi {
|
||||||
}
|
}
|
||||||
|
|
||||||
void getLive(JsonObject obj) {
|
void getLive(JsonObject obj) {
|
||||||
getMenu(obj.createNestedObject(F("menu")));
|
|
||||||
getGeneric(obj.createNestedObject(F("generic")));
|
getGeneric(obj.createNestedObject(F("generic")));
|
||||||
JsonArray invArr = obj.createNestedArray(F("inverter"));
|
//JsonArray invArr = obj.createNestedArray(F("inverter"));
|
||||||
obj["refresh_interval"] = mConfig->nrf.sendInterval;
|
obj[F("refresh")] = mConfig->nrf.sendInterval;
|
||||||
|
|
||||||
uint8_t list[] = {FLD_UAC, FLD_IAC, FLD_PAC, FLD_F, FLD_PF, FLD_T, FLD_YT, FLD_YD, FLD_PDC, FLD_EFF, FLD_Q};
|
for (uint8_t fld = 0; fld < sizeof(acList); fld++) {
|
||||||
|
obj[F("ch0_fld_units")][fld] = String(units[fieldUnits[acList[fld]]]);
|
||||||
|
obj[F("ch0_fld_names")][fld] = String(fields[acList[fld]]);
|
||||||
|
}
|
||||||
|
for (uint8_t fld = 0; fld < sizeof(dcList); fld++) {
|
||||||
|
obj[F("fld_units")][fld] = String(units[fieldUnits[dcList[fld]]]);
|
||||||
|
obj[F("fld_names")][fld] = String(fields[dcList[fld]]);
|
||||||
|
}
|
||||||
|
|
||||||
Inverter<> *iv;
|
Inverter<> *iv;
|
||||||
|
for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i ++) {
|
||||||
|
iv = mSys->getInverterByPos(i);
|
||||||
|
bool parse = false;
|
||||||
|
if(NULL != iv)
|
||||||
|
parse = iv->config->enabled;
|
||||||
|
obj[F("iv")][i] = parse;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*Inverter<> *iv;
|
||||||
uint8_t pos;
|
uint8_t pos;
|
||||||
for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i ++) {
|
for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i ++) {
|
||||||
iv = mSys->getInverterByPos(i);
|
iv = mSys->getInverterByPos(i);
|
||||||
|
@ -553,7 +562,7 @@ class RestApi {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}*/
|
||||||
}
|
}
|
||||||
|
|
||||||
void getRecord(JsonObject obj, uint8_t recType) {
|
void getRecord(JsonObject obj, uint8_t recType) {
|
||||||
|
@ -632,8 +641,7 @@ class RestApi {
|
||||||
mTimezoneOffset = jsonIn[F("val")];
|
mTimezoneOffset = jsonIn[F("val")];
|
||||||
else if(F("discovery_cfg") == jsonIn[F("cmd")]) {
|
else if(F("discovery_cfg") == jsonIn[F("cmd")]) {
|
||||||
mApp->setMqttDiscoveryFlag(); // for homeassistant
|
mApp->setMqttDiscoveryFlag(); // for homeassistant
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
jsonOut[F("error")] = F("unknown cmd");
|
jsonOut[F("error")] = F("unknown cmd");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,23 +33,66 @@ iconSuccess = [
|
||||||
/**
|
/**
|
||||||
* GENERIC FUNCTIONS
|
* GENERIC FUNCTIONS
|
||||||
*/
|
*/
|
||||||
|
function ml(tagName, ...args) {
|
||||||
function topnav() {
|
var el = document.createElement(tagName);
|
||||||
toggle("topnav");
|
if(args[0]) {
|
||||||
|
for(var name in args[0]) {
|
||||||
|
if(name.indexOf("on") === 0) {
|
||||||
|
el.addEventListener(name.substr(2).toLowerCase(), args[0][name], false)
|
||||||
|
} else {
|
||||||
|
el.setAttribute(name, args[0][name]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!args[1]) {
|
||||||
|
return el;
|
||||||
|
}
|
||||||
|
return nester(el, args[1])
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseMenu(obj) {
|
function nester(el, n) {
|
||||||
var e = document.getElementById("topnav");
|
if (typeof n === "string") {
|
||||||
e.innerHTML = "";
|
var t = document.createTextNode(n);
|
||||||
for(var i = 0; i < obj["name"].length; i ++) {
|
el.appendChild(t);
|
||||||
if(obj["name"][i] == "-")
|
} else if (n instanceof Array) {
|
||||||
e.appendChild(span("", ["seperator"]));
|
for(var i = 0; i < n.length; i++) {
|
||||||
else {
|
if (typeof n[i] === "string") {
|
||||||
var l = link(obj["link"][i], obj["name"][i], obj["trgt"][i]);
|
var t = document.createTextNode(n[i]);
|
||||||
if(obj["link"][i] == window.location.pathname)
|
el.appendChild(t);
|
||||||
l.classList.add("active");
|
} else if (n[i] instanceof Node){
|
||||||
e.appendChild(l);
|
el.appendChild(n[i]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} else if (n instanceof Node){
|
||||||
|
el.appendChild(n)
|
||||||
|
}
|
||||||
|
return el;
|
||||||
|
}
|
||||||
|
|
||||||
|
function topnav() {
|
||||||
|
toggle("topnav", "mobile");
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseNav(obj) {
|
||||||
|
for(i = 0; i < 10; i++) {
|
||||||
|
if(i == 2)
|
||||||
|
continue;
|
||||||
|
var l = document.getElementById("nav"+i);
|
||||||
|
if(window.location.pathname == "/" + l.href.split('/').pop())
|
||||||
|
l.classList.add("active");
|
||||||
|
|
||||||
|
if(obj["menu_protEn"]) {
|
||||||
|
if(obj["menu_prot"]) {
|
||||||
|
if(0 == i)
|
||||||
|
l.classList.remove("hide");
|
||||||
|
else if(i > 2) {
|
||||||
|
if(((obj["menu_mask"] >> (i-2)) & 0x01) == 0x00)
|
||||||
|
l.classList.remove("hide");
|
||||||
|
}
|
||||||
|
} else if(0 != i)
|
||||||
|
l.classList.remove("hide");
|
||||||
|
} else if(i > 1)
|
||||||
|
l.classList.remove("hide");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,7 +103,9 @@ function parseVersion(obj) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseESP(obj) {
|
function parseESP(obj) {
|
||||||
document.getElementById("esp_type").innerHTML="Board: " + obj["esp_type"];
|
document.getElementById("esp_type").append(
|
||||||
|
document.createTextNode("Board: " + obj["esp_type"])
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseRssi(obj) {
|
function parseRssi(obj) {
|
||||||
|
@ -69,7 +114,7 @@ function parseRssi(obj) {
|
||||||
icon = iconWifi1;
|
icon = iconWifi1;
|
||||||
else if(obj["wifi_rssi"] <= -70)
|
else if(obj["wifi_rssi"] <= -70)
|
||||||
icon = iconWifi2;
|
icon = iconWifi2;
|
||||||
document.getElementById("wifiicon").replaceChildren(svg(icon, 32, 32, "#fff", null, obj["wifi_rssi"]));
|
document.getElementById("wifiicon").replaceChildren(svg(icon, 32, 32, "wifi", obj["wifi_rssi"]));
|
||||||
}
|
}
|
||||||
|
|
||||||
function setHide(id, hide) {
|
function setHide(id, hide) {
|
||||||
|
@ -82,12 +127,12 @@ function setHide(id, hide) {
|
||||||
elm.classList.remove('hide');
|
elm.classList.remove('hide');
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggle(id) {
|
function toggle(id, cl="hide") {
|
||||||
var e = document.getElementById(id);
|
var e = document.getElementById(id);
|
||||||
if(!e.classList.contains("hide"))
|
if(!e.classList.contains(cl))
|
||||||
e.classList.add("hide");
|
e.classList.add(cl);
|
||||||
else
|
else
|
||||||
e.classList.remove('hide');
|
e.classList.remove(cl);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getAjax(url, ptr, method="GET", json=null) {
|
function getAjax(url, ptr, method="GET", json=null) {
|
||||||
|
@ -198,11 +243,10 @@ function link(dst, text, target=null) {
|
||||||
return a;
|
return a;
|
||||||
}
|
}
|
||||||
|
|
||||||
function svg(data=null, w=24, h=24, color="#000", cl=null, tooltip=null) {
|
function svg(data=null, w=24, h=24, cl=null, tooltip=null) {
|
||||||
var s = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
var s = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
||||||
s.setAttribute('width', w);
|
s.setAttribute('width', w);
|
||||||
s.setAttribute('height', h);
|
s.setAttribute('height', h);
|
||||||
s.setAttribute('fill', color);
|
|
||||||
s.setAttribute('viewBox', '0 0 16 16');
|
s.setAttribute('viewBox', '0 0 16 16');
|
||||||
if(null != cl) s.setAttribute('class', cl);
|
if(null != cl) s.setAttribute('class', cl);
|
||||||
if(null != data) {
|
if(null != data) {
|
||||||
|
|
27
src/web/html/colorBright.css
Normal file
27
src/web/html/colorBright.css
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
:root {
|
||||||
|
--bg: #fff;
|
||||||
|
--fg: #000;
|
||||||
|
--fg2: #fff;
|
||||||
|
|
||||||
|
--info: #0000dd;
|
||||||
|
--warn: #ff7700;
|
||||||
|
--success: #009900;
|
||||||
|
|
||||||
|
--input-bg: #eee;
|
||||||
|
|
||||||
|
--nav-bg: #333;
|
||||||
|
--primary: #006ec0;
|
||||||
|
--primary-hover: #044e86;
|
||||||
|
--secondary: #0072c8;
|
||||||
|
--nav-active: #555;
|
||||||
|
--footer-bg: #282828;
|
||||||
|
|
||||||
|
--total-head-title: #8e5903;
|
||||||
|
--total-bg: #b06e04;
|
||||||
|
--iv-head-title: #1c6800;
|
||||||
|
--iv-head-bg: #32b004;
|
||||||
|
--ch-head-title: #003c80;
|
||||||
|
--ch-head-bg: #006ec0;
|
||||||
|
--ts-head: #333;
|
||||||
|
--ts-bg: #555;
|
||||||
|
}
|
27
src/web/html/colorDark.css
Normal file
27
src/web/html/colorDark.css
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
:root {
|
||||||
|
--bg: #222;
|
||||||
|
--fg: #ccc;
|
||||||
|
--fg2: #fff;
|
||||||
|
|
||||||
|
--info: #0072c8;
|
||||||
|
--warn: #ffaa00;
|
||||||
|
--success: #00bb00;
|
||||||
|
|
||||||
|
--input-bg: #333;
|
||||||
|
|
||||||
|
--nav-bg: #333;
|
||||||
|
--primary: #004d87;
|
||||||
|
--primary-hover: #023155;
|
||||||
|
--secondary: #0072c8;
|
||||||
|
--nav-active: #555;
|
||||||
|
--footer-bg: #282828;
|
||||||
|
|
||||||
|
--total-head-title: #555511;
|
||||||
|
--total-bg: #666622;
|
||||||
|
--iv-head-title: #115511;
|
||||||
|
--iv-head-bg: #226622;
|
||||||
|
--ch-head-title: #112255;
|
||||||
|
--ch-head-bg: #223366;
|
||||||
|
--ts-head: #333;
|
||||||
|
--ts-bg: #555;
|
||||||
|
}
|
|
@ -2,10 +2,67 @@ import re
|
||||||
import os
|
import os
|
||||||
import gzip
|
import gzip
|
||||||
import glob
|
import glob
|
||||||
|
import shutil
|
||||||
|
from datetime import date
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
import subprocess
|
||||||
|
|
||||||
def convert2Header(inFile):
|
|
||||||
|
def get_git_sha():
|
||||||
|
return subprocess.check_output(['git', 'rev-parse', '--short', 'HEAD']).decode('ascii').strip()
|
||||||
|
|
||||||
|
def readVersion(path):
|
||||||
|
f = open(path, "r")
|
||||||
|
lines = f.readlines()
|
||||||
|
f.close()
|
||||||
|
|
||||||
|
today = date.today()
|
||||||
|
search = ["_MAJOR", "_MINOR", "_PATCH"]
|
||||||
|
version = today.strftime("%y%m%d") + "_ahoy_"
|
||||||
|
ver = ""
|
||||||
|
for line in lines:
|
||||||
|
if(line.find("VERSION_") != -1):
|
||||||
|
for s in search:
|
||||||
|
p = line.find(s)
|
||||||
|
if(p != -1):
|
||||||
|
version += line[p+13:].rstrip() + "."
|
||||||
|
ver += line[p+13:].rstrip() + "."
|
||||||
|
return ver[:-1]
|
||||||
|
|
||||||
|
def htmlParts(file, header, nav, footer, version):
|
||||||
|
p = "";
|
||||||
|
f = open(file, "r")
|
||||||
|
lines = f.readlines()
|
||||||
|
f.close();
|
||||||
|
|
||||||
|
f = open(header, "r")
|
||||||
|
h = f.read().strip()
|
||||||
|
f.close()
|
||||||
|
|
||||||
|
f = open(nav, "r")
|
||||||
|
n = f.read().strip()
|
||||||
|
f.close()
|
||||||
|
|
||||||
|
f = open(footer, "r")
|
||||||
|
fo = f.read().strip()
|
||||||
|
f.close()
|
||||||
|
|
||||||
|
for line in lines:
|
||||||
|
line = line.replace("{#HTML_HEADER}", h)
|
||||||
|
line = line.replace("{#HTML_NAV}", n)
|
||||||
|
line = line.replace("{#HTML_FOOTER}", fo)
|
||||||
|
p += line
|
||||||
|
|
||||||
|
#placeholders
|
||||||
|
link = '<a target="_blank" href="https://github.com/lumapu/ahoy/commits/' + get_git_sha() + '">GIT SHA: ' + get_git_sha() + ' :: ' + version + '</a>'
|
||||||
|
p = p.replace("{#VERSION}", version)
|
||||||
|
p = p.replace("{#VERSION_GIT}", link)
|
||||||
|
f = open("tmp/" + file, "w")
|
||||||
|
f.write(p);
|
||||||
|
f.close();
|
||||||
|
return p
|
||||||
|
|
||||||
|
def convert2Header(inFile, version):
|
||||||
fileType = inFile.split(".")[1]
|
fileType = inFile.split(".")[1]
|
||||||
define = inFile.split(".")[0].upper()
|
define = inFile.split(".")[0].upper()
|
||||||
define2 = inFile.split(".")[1].upper()
|
define2 = inFile.split(".")[1].upper()
|
||||||
|
@ -17,14 +74,19 @@ def convert2Header(inFile):
|
||||||
Path("html/h").mkdir(exist_ok=True)
|
Path("html/h").mkdir(exist_ok=True)
|
||||||
else:
|
else:
|
||||||
outName = "h/" + inFileVarName + ".h"
|
outName = "h/" + inFileVarName + ".h"
|
||||||
Path("h").mkdir(exist_ok=True)
|
|
||||||
|
|
||||||
|
data = ""
|
||||||
if fileType == "ico":
|
if fileType == "ico":
|
||||||
f = open(inFile, "rb")
|
f = open(inFile, "rb")
|
||||||
|
data = f.read()
|
||||||
|
f.close()
|
||||||
else:
|
else:
|
||||||
f = open(inFile, "r")
|
if fileType == "html":
|
||||||
data = f.read()
|
data = htmlParts(inFile, "includes/header.html", "includes/nav.html", "includes/footer.html", version)
|
||||||
f.close()
|
else:
|
||||||
|
f = open(inFile, "r")
|
||||||
|
data = f.read()
|
||||||
|
f.close()
|
||||||
|
|
||||||
if fileType == "css":
|
if fileType == "css":
|
||||||
data = data.replace('\n', '')
|
data = data.replace('\n', '')
|
||||||
|
@ -53,13 +115,17 @@ def convert2Header(inFile):
|
||||||
f.close()
|
f.close()
|
||||||
|
|
||||||
# delete all files in the 'h' dir
|
# delete all files in the 'h' dir
|
||||||
dir = 'h'
|
wd = 'h'
|
||||||
if os.getcwd()[-4:] != "html":
|
if os.getcwd()[-4:] != "html":
|
||||||
dir = "web/html/" + dir
|
wd = "web/html/" + wd
|
||||||
|
|
||||||
if os.path.exists(dir):
|
if os.path.exists(wd):
|
||||||
for f in os.listdir(dir):
|
for f in os.listdir(wd):
|
||||||
os.remove(os.path.join(dir, f))
|
os.remove(os.path.join(wd, f))
|
||||||
|
wd += "/tmp"
|
||||||
|
if os.path.exists(wd):
|
||||||
|
for f in os.listdir(wd):
|
||||||
|
os.remove(os.path.join(wd, f))
|
||||||
|
|
||||||
# grab all files with following extensions
|
# grab all files with following extensions
|
||||||
if os.getcwd()[-4:] != "html":
|
if os.getcwd()[-4:] != "html":
|
||||||
|
@ -69,6 +135,11 @@ files_grabbed = []
|
||||||
for files in types:
|
for files in types:
|
||||||
files_grabbed.extend(glob.glob(files))
|
files_grabbed.extend(glob.glob(files))
|
||||||
|
|
||||||
|
Path("h").mkdir(exist_ok=True)
|
||||||
|
Path("tmp").mkdir(exist_ok=True) # created to check if webpages are valid with all replacements
|
||||||
|
shutil.copyfile("style.css", "tmp/style.css")
|
||||||
|
version = readVersion("../../defines.h")
|
||||||
|
|
||||||
# go throw the array
|
# go throw the array
|
||||||
for val in files_grabbed:
|
for val in files_grabbed:
|
||||||
convert2Header(val)
|
convert2Header(val, version)
|
||||||
|
|
16
src/web/html/includes/footer.html
Normal file
16
src/web/html/includes/footer.html
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
<div id="footer">
|
||||||
|
<div class="left">
|
||||||
|
<a href="https://ahoydtu.de" target="_blank">AhoyDTU © 2023</a>
|
||||||
|
<ul>
|
||||||
|
<li><a href="https://discord.gg/WzhxEY62mB" target="_blank">Discord</a></li>
|
||||||
|
<li><a href="https://github.com/lumapu/ahoy" target="_blank">Github</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="right">
|
||||||
|
<ul>
|
||||||
|
<li>{#VERSION_GIT}</li>
|
||||||
|
<li id="esp_type"></li>
|
||||||
|
<li><a href="https://creativecommons.org/licenses/by-nc-sa/3.0/de" target="_blank" >CC BY-NC-SA 3.0</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
5
src/web/html/includes/header.html
Normal file
5
src/web/html/includes/header.html
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
<link rel="stylesheet" type="text/css" href="colors.css"/>
|
||||||
|
<link rel="stylesheet" type="text/css" href="style.css"/>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<script type="text/javascript" src="api.js"></script>
|
25
src/web/html/includes/nav.html
Normal file
25
src/web/html/includes/nav.html
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
<div class="topnav">
|
||||||
|
<a href="/" class="title">AhoyDTU</a>
|
||||||
|
<a href="javascript:void(0);" class="icon" onclick="topnav()">
|
||||||
|
<span></span>
|
||||||
|
<span></span>
|
||||||
|
<span></span>
|
||||||
|
</a>
|
||||||
|
<div id="topnav" class="mobile">
|
||||||
|
<a id="nav3" class="hide" href="/live">Live</a>
|
||||||
|
<a id="nav4" class="hide" href="/serial">Serial / Control</a>
|
||||||
|
<a id="nav5" class="hide" href="/setup">Settings</a>
|
||||||
|
<span class="seperator"></span>
|
||||||
|
<a id="nav6" class="hide" href="/update">Update</a>
|
||||||
|
<a id="nav7" class="hide" href="/system">System</a>
|
||||||
|
<span class="seperator"></span>
|
||||||
|
<a id="nav8" href="/api" target="_blank">REST API</a>
|
||||||
|
<a id="nav9" href="https://ahoydtu.de" target="_blank">Documentation</a>
|
||||||
|
<span class="seperator"></span>
|
||||||
|
<a id="nav0" class="hide" href="/login">Login</a>
|
||||||
|
<a id="nav1" class="hide" href="/logout">Logout</a>
|
||||||
|
</div>
|
||||||
|
<div id="wifiicon" class="info"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -2,36 +2,12 @@
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>Index</title>
|
<title>Index</title>
|
||||||
<link rel="stylesheet" type="text/css" href="style.css"/>
|
{#HTML_HEADER}
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<script type="text/javascript" src="api.js"></script>
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="topnav">
|
{#HTML_NAV}
|
||||||
<a href="/" class="title">AhoyDTU</a>
|
|
||||||
<a href="javascript:void(0);" class="icon" onclick="topnav()">
|
|
||||||
<span></span>
|
|
||||||
<span></span>
|
|
||||||
<span></span>
|
|
||||||
</a>
|
|
||||||
<div id="topnav" class="hide"></div>
|
|
||||||
<div id="wifiicon" class="info"></div>
|
|
||||||
</div>
|
|
||||||
<div id="wrapper">
|
<div id="wrapper">
|
||||||
<div id="content">
|
<div id="content">
|
||||||
<script>
|
|
||||||
function promptFunction() {
|
|
||||||
var Text = prompt("This project was started from https://www.mikrocontroller.net/topic/525778 this discussion.\n\n" +
|
|
||||||
"The Hoymiles protocol was decrypted through the voluntary efforts of many participants. ahoy, among others, was developed based on this work.\n" +
|
|
||||||
"The software was developed to the best of our knowledge and belief. Nevertheless, no liability can be accepted for a malfunction or guarantee loss of the inverter.\n\n" +
|
|
||||||
"Ahoy is freely available. If you paid money for the software, you probably got ripped off.\n\nPlease type in 'YeS', you are accept our Disclaim. You should then save your config.", "");
|
|
||||||
if (Text != "YeS")
|
|
||||||
promptFunction();
|
|
||||||
else
|
|
||||||
return true;
|
|
||||||
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<p>
|
<p>
|
||||||
<span class="des">Uptime: </span><span id="uptime"></span><br/>
|
<span class="des">Uptime: </span><span id="uptime"></span><br/>
|
||||||
<span class="des">ESP-Time: </span><span id="date"></span>
|
<span class="des">ESP-Time: </span><span id="date"></span>
|
||||||
|
@ -60,22 +36,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="footer">
|
{#HTML_FOOTER}
|
||||||
<div class="left">
|
|
||||||
<a href="https://ahoydtu.de" target="_blank">AhoyDTU © 2023</a>
|
|
||||||
<ul>
|
|
||||||
<li><a href="https://discord.gg/WzhxEY62mB" target="_blank">Discord</a></li>
|
|
||||||
<li><a href="https://github.com/lumapu/ahoy" target="_blank">Github</a></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div class="right">
|
|
||||||
<ul>
|
|
||||||
<li><span id="version"></span></li>
|
|
||||||
<li><span id="esp_type"></span></li>
|
|
||||||
<li><a href="https://creativecommons.org/licenses/by-nc-sa/3.0/de" target="_blank" >CC BY-NC-SA 3.0</a></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
var exeOnce = true;
|
var exeOnce = true;
|
||||||
var tickCnt = 0;
|
var tickCnt = 0;
|
||||||
|
@ -106,12 +67,8 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseGeneric(obj) {
|
function parseGeneric(obj) {
|
||||||
// Disclaimer
|
if(exeOnce)
|
||||||
//if(obj["disclaimer"] == false) sessionStorage.setItem("gDisclaimer", promptFunction());
|
|
||||||
if(exeOnce){
|
|
||||||
parseVersion(obj);
|
|
||||||
parseESP(obj);
|
parseESP(obj);
|
||||||
}
|
|
||||||
parseRssi(obj);
|
parseRssi(obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -163,14 +120,14 @@
|
||||||
var p = div(["none"]);
|
var p = div(["none"]);
|
||||||
for(var i of obj) {
|
for(var i of obj) {
|
||||||
var icon = iconWarn;
|
var icon = iconWarn;
|
||||||
var color = "#F70";
|
var cl = "icon-warn";
|
||||||
avail = "";
|
avail = "";
|
||||||
if(false == i["enabled"]) {
|
if(false == i["enabled"]) {
|
||||||
avail = "disabled";
|
avail = "disabled";
|
||||||
}
|
}
|
||||||
else if(false == i["is_avail"]) {
|
else if(false == i["is_avail"]) {
|
||||||
icon = iconInfo;
|
icon = iconInfo;
|
||||||
color = "#00d";
|
cl = "icon-info";
|
||||||
avail = "not yet available";
|
avail = "not yet available";
|
||||||
}
|
}
|
||||||
else if(0 == i["ts_last_success"]) {
|
else if(0 == i["ts_last_success"]) {
|
||||||
|
@ -183,12 +140,12 @@
|
||||||
if(false == i["is_producing"])
|
if(false == i["is_producing"])
|
||||||
avail += "not ";
|
avail += "not ";
|
||||||
else
|
else
|
||||||
color = "#090";
|
cl = "icon-success";
|
||||||
avail += "producing";
|
avail += "producing";
|
||||||
}
|
}
|
||||||
|
|
||||||
p.append(
|
p.append(
|
||||||
svg(icon, 20, 20, color, "icon"),
|
svg(icon, 30, 30, "icon " + cl),
|
||||||
span("Inverter #" + i["id"] + ": " + i["name"] + " (v" + i["version"] + ") is " + avail),
|
span("Inverter #" + i["id"] + ": " + i["name"] + " (v" + i["version"] + ") is " + avail),
|
||||||
br()
|
br()
|
||||||
);
|
);
|
||||||
|
@ -203,25 +160,25 @@
|
||||||
document.getElementById("iv").replaceChildren(p);
|
document.getElementById("iv").replaceChildren(p);
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseWarnInfo(warn, success, version) {
|
function parseWarnInfo(warn, success) {
|
||||||
var p = div(["none"]);
|
var p = div(["none"]);
|
||||||
for(var w of warn) {
|
for(var w of warn) {
|
||||||
p.append(svg(iconWarn, 20, 20, "#F70", "icon"), span(w), br());
|
p.append(svg(iconWarn, 30, 30, "icon icon-warn"), span(w), br());
|
||||||
}
|
}
|
||||||
for(var i of success) {
|
for(var i of success) {
|
||||||
p.append(svg(iconSuccess, 20, 20, "#090", "icon"), span(i), br());
|
p.append(svg(iconSuccess, 30, 30, "icon icon-success"), span(i), br());
|
||||||
}
|
}
|
||||||
|
|
||||||
if(commInfo.length > 0)
|
if(commInfo.length > 0)
|
||||||
p.append(svg(iconInfo, 20, 20, "#00d", "icon"), span(commInfo), br());
|
p.append(svg(iconInfo, 30, 30, "icon icon-info"), span(commInfo), br());
|
||||||
|
|
||||||
if(null != release) {
|
if(null != release) {
|
||||||
if(getVerInt(version) < getVerInt(release))
|
if(getVerInt("{#VERSION}") < getVerInt(release))
|
||||||
p.append(svg(iconInfo, 20, 20, "#00d", "icon"), span("Update available, current released version: " + release), br());
|
p.append(svg(iconInfo, 30, 30, "icon icon-info"), span("Update available, current released version: " + release), br());
|
||||||
else if(getVerInt(version) > getVerInt(release))
|
else if(getVerInt("{#VERSION}") > getVerInt(release))
|
||||||
p.append(svg(iconInfo, 20, 20, "#00d", "icon"), span("You are using development version " + version +". In case of issues you may want to try the current stable release: " + release), br());
|
p.append(svg(iconInfo, 30, 30, "icon icon-info"), span("You are using development version {#VERSION}. In case of issues you may want to try the current stable release: " + release), br());
|
||||||
else
|
else
|
||||||
p.append(svg(iconInfo, 20, 20, "#00d", "icon"), span("You are using the current stable release: " + release), br());
|
p.append(svg(iconInfo, 30, 30, "icon icon-info"), span("You are using the current stable release: " + release), br());
|
||||||
}
|
}
|
||||||
|
|
||||||
document.getElementById("warn_info").replaceChildren(p);
|
document.getElementById("warn_info").replaceChildren(p);
|
||||||
|
@ -239,11 +196,11 @@
|
||||||
function parse(obj) {
|
function parse(obj) {
|
||||||
if(null != obj) {
|
if(null != obj) {
|
||||||
if(exeOnce)
|
if(exeOnce)
|
||||||
parseMenu(obj["menu"]);
|
parseNav(obj["generic"]);
|
||||||
parseGeneric(obj["generic"]);
|
parseGeneric(obj["generic"]);
|
||||||
parseSys(obj);
|
parseSys(obj);
|
||||||
parseIv(obj["inverter"]);
|
parseIv(obj["inverter"]);
|
||||||
parseWarnInfo(obj["warnings"], obj["infos"], obj["generic"]["version"]);
|
parseWarnInfo(obj["warnings"], obj["infos"]);
|
||||||
if(exeOnce) {
|
if(exeOnce) {
|
||||||
window.setInterval("tick()", 1000);
|
window.setInterval("tick()", 1000);
|
||||||
exeOnce = false;
|
exeOnce = false;
|
||||||
|
|
|
@ -2,41 +2,22 @@
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>Login</title>
|
<title>Login</title>
|
||||||
<link rel="stylesheet" type="text/css" href="style.css"/>
|
{#HTML_HEADER}
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<script type="text/javascript" src="api.js"></script>
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="wrapper">
|
<div id="wrapper">
|
||||||
<div id="login">
|
<div id="login">
|
||||||
<div class="pad">
|
<div class="p-4">
|
||||||
<form action="/login" method="post">
|
<form action="/login" method="post">
|
||||||
<h2>AhoyDTU</h2>
|
<div class="row"><h2>AhoyDTU</h2></div>
|
||||||
<input type="password" name="pwd" value="" autofocus>
|
<div class="row">
|
||||||
<input type="submit" name="login" value="login" class="btn">
|
<div class="col-8"><input type="password" name="pwd" autofocus></div>
|
||||||
|
<div class="col-4"><input type="submit" name="login" value="login" class="btn"></div>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="footer">
|
{#HTML_FOOTER}
|
||||||
<div class="left">
|
|
||||||
<a href="https://ahoydtu.de" target="_blank">AhoyDTU © 2023</a>
|
|
||||||
<ul>
|
|
||||||
<li><a href="https://discord.gg/WzhxEY62mB" target="_blank">Discord</a></li>
|
|
||||||
<li><a href="https://github.com/lumapu/ahoy" target="_blank">Github</a></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div class="right">
|
|
||||||
<span id="version"></span><br/><br/>
|
|
||||||
<a href="https://creativecommons.org/licenses/by-nc-sa/3.0/de" target="_blank" >CC BY-NC-SA 3.0</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<script type="text/javascript">
|
|
||||||
function parse(obj) {
|
|
||||||
parseVersion(obj["general"]);
|
|
||||||
}
|
|
||||||
|
|
||||||
getAjax("/api/generic", parse);
|
|
||||||
</script>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -2,73 +2,66 @@
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>Serial Console</title>
|
<title>Serial Console</title>
|
||||||
<link rel="stylesheet" type="text/css" href="style.css"/>
|
{#HTML_HEADER}
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<script type="text/javascript" src="api.js"></script>
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="topnav">
|
{#HTML_NAV}
|
||||||
<a href="/" class="title">AhoyDTU</a>
|
|
||||||
<a href="javascript:void(0);" class="icon" onclick="topnav()">
|
|
||||||
<span></span>
|
|
||||||
<span></span>
|
|
||||||
<span></span>
|
|
||||||
</a>
|
|
||||||
<div id="topnav" class="hide"></div>
|
|
||||||
<div id="wifiicon" class="info"></div>
|
|
||||||
</div>
|
|
||||||
<div id="wrapper">
|
<div id="wrapper">
|
||||||
<div id="content">
|
<div id="content">
|
||||||
<div class="serial">
|
<div class="row">
|
||||||
<textarea id="serial" cols="80" rows="20" readonly></textarea><br/>
|
<textarea id="serial" class="mt-3" cols="80" rows="20" readonly></textarea>
|
||||||
connected: <span class="dot" id="connected"></span>
|
</div>
|
||||||
Uptime: <span id="uptime"></span>
|
<div class="row my-3">
|
||||||
<input type="button" value="clear" class="btn" id="clear"/>
|
<div class="col-3">connected: <span class="dot" id="connected"></span></div>
|
||||||
<input type="button" value="autoscroll" class="btn" id="scroll"/>
|
<div class="col-3 col-sm-4 my-3">Uptime: <span id="uptime"></span></div>
|
||||||
<div class="hr mt-3 mb-3"></div>
|
<div class="col-6 col-sm-4">
|
||||||
|
<input type="button" value="clear" class="btn" id="clear"/>
|
||||||
|
<input type="button" value="autoscroll" class="btn" id="scroll"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="hr my-3"></div>
|
||||||
|
<div class="row mb-3">
|
||||||
<h3>Commands</h3>
|
<h3>Commands</h3>
|
||||||
<label for="iv">Select Inverter:</label>
|
</div>
|
||||||
<select name="iv" id="InvID">
|
<div class="row mb-3">
|
||||||
</select>
|
<div class="col-12 col-sm-3 my-2">Select Inverter</div>
|
||||||
<label for="pwrlimval">Power Limit Value</label>
|
<div class="col-12 col-sm-9"><select name="iv" id="InvID"></select></div>
|
||||||
<input type="number" class="text" name="pwrlimval" maxlength="4"/>
|
</div>
|
||||||
<label for="pwrlimctrl">Power Limit Command</label>
|
<div class="row mb-3">
|
||||||
<select name="pwrlimctrl">
|
<div class="col-12 col-sm-3 my-2">Power Limit Command</div>
|
||||||
<option value="" selected disabled hidden>select the unit and persistence</option>
|
<div class="col-12 col-sm-9">
|
||||||
<option value="limit_nonpersistent_absolute">absolute non persistent [W]</option>
|
<select name="pwrlimctrl">
|
||||||
<option value="limit_nonpersistent_relative">relative non persistent [%]</option>
|
<option value="" selected disabled hidden>select the unit and persistence</option>
|
||||||
<option value="limit_persistent_absolute">absolute persistent [W]</option>
|
<option value="limit_nonpersistent_absolute">absolute non persistent [W]</option>
|
||||||
<option value="limit_persistent_relative">relative persistent [%]</option>
|
<option value="limit_nonpersistent_relative">relative non persistent [%]</option>
|
||||||
</select>
|
<option value="limit_persistent_absolute">absolute persistent [W]</option>
|
||||||
<br/>
|
<option value="limit_persistent_relative">relative persistent [%]</option>
|
||||||
<input type="button" value="Send Power Limit" class="btn" id="sendpwrlim"/>
|
</select>
|
||||||
<div class="hr mt-3 mb-3"></div>
|
</div>
|
||||||
<div id="power" class="mt-3">
|
</div>
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-12 col-sm-3 my-2">Power Limit Value</div>
|
||||||
|
<div class="col-12 col-sm-9"><input type="number" name="pwrlimval" maxlength="4"/></div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-12 col-sm-3"></div>
|
||||||
|
<div class="col-12 col-sm-9"><input type="button" value="Send Power Limit" class="btn" id="sendpwrlim"/></div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-12 col-sm-3 my-2">Control Inverter</div>
|
||||||
|
<div class="col-12 col-sm-9" id="power">
|
||||||
<input type="button" value="Restart" class="btn" id="restart"/>
|
<input type="button" value="Restart" class="btn" id="restart"/>
|
||||||
<input type="button" value="Turn Off" class="btn" id="power_off"/>
|
<input type="button" value="Turn Off" class="btn" id="power_off"/>
|
||||||
<input type="button" value="Turn On" class="btn" id="power_on"/>
|
<input type="button" value="Turn On" class="btn" id="power_on"/>
|
||||||
</div>
|
</div>
|
||||||
<br/>
|
</div>
|
||||||
<p>Ctrl result: <span id="result">n/a</span></p>
|
<div class="row mb-5">
|
||||||
|
<div class="col-3 my-2">Ctrl result</div>
|
||||||
|
<div class="col-9 my-2"><span id="result">n/a</span></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="footer">
|
{#HTML_FOOTER}
|
||||||
<div class="left">
|
|
||||||
<a href="https://ahoydtu.de" target="_blank">AhoyDTU © 2023</a>
|
|
||||||
<ul>
|
|
||||||
<li><a href="https://discord.gg/WzhxEY62mB" target="_blank">Discord</a></li>
|
|
||||||
<li><a href="https://github.com/lumapu/ahoy" target="_blank">Github</a></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div class="right">
|
|
||||||
<ul>
|
|
||||||
<li><span id="version"></span></li>
|
|
||||||
<li><span id="esp_type"></span></li>
|
|
||||||
<li><a href="https://creativecommons.org/licenses/by-nc-sa/3.0/de" target="_blank" >CC BY-NC-SA 3.0</a></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
var mAutoScroll = true;
|
var mAutoScroll = true;
|
||||||
var con = document.getElementById("serial");
|
var con = document.getElementById("serial");
|
||||||
|
@ -87,22 +80,21 @@
|
||||||
|
|
||||||
parseRssi(obj);
|
parseRssi(obj);
|
||||||
if(true == exeOnce) {
|
if(true == exeOnce) {
|
||||||
parseVersion(obj);
|
parseNav(obj);
|
||||||
parseESP(obj);
|
parseESP(obj);
|
||||||
window.setInterval("getAjax('/api/generic', parseGeneric)", 10000);
|
window.setInterval("getAjax('/api/generic', parseGeneric)", 10000);
|
||||||
exeOnce = false;
|
exeOnce = false;
|
||||||
getAjax("/api/setup", parse);
|
getAjax("/api/inverter/list", parse);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function parse(root) {
|
function parse(root) {
|
||||||
parseMenu(root["menu"]);
|
|
||||||
select = document.getElementById('InvID');
|
select = document.getElementById('InvID');
|
||||||
|
|
||||||
if(null == root) return;
|
if(null == root) return;
|
||||||
root = root.inverter;
|
root = root.inverter;
|
||||||
for(var i = 0; i < root.inverter.length; i++) {
|
for(var i = 0; i < root.length; i++) {
|
||||||
inv = root.inverter[i];
|
inv = root[i];
|
||||||
var opt = document.createElement('option');
|
var opt = document.createElement('option');
|
||||||
opt.value = inv.id;
|
opt.value = inv.id;
|
||||||
opt.innerHTML = inv.name;
|
opt.innerHTML = inv.name;
|
||||||
|
|
|
@ -2,9 +2,7 @@
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>Setup</title>
|
<title>Setup</title>
|
||||||
<link rel="stylesheet" type="text/css" href="style.css"/>
|
{#HTML_HEADER}
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<script type="text/javascript" src="api.js"></script>
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
function load() {
|
function load() {
|
||||||
for(it of document.getElementsByClassName("s_collapsible")) {
|
for(it of document.getElementsByClassName("s_collapsible")) {
|
||||||
|
@ -18,154 +16,25 @@
|
||||||
</script>
|
</script>
|
||||||
</head>
|
</head>
|
||||||
<body onload="load()">
|
<body onload="load()">
|
||||||
<div class="topnav">
|
{#HTML_NAV}
|
||||||
<a href="/" class="title">AhoyDTU</a>
|
|
||||||
<a href="javascript:void(0);" class="icon" onclick="topnav()">
|
|
||||||
<span></span>
|
|
||||||
<span></span>
|
|
||||||
<span></span>
|
|
||||||
</a>
|
|
||||||
<div id="topnav" class="hide"></div>
|
|
||||||
<div id="wifiicon" class="info"></div>
|
|
||||||
</div>
|
|
||||||
<div id="wrapper">
|
<div id="wrapper">
|
||||||
<div id="content">
|
<div id="content">
|
||||||
<form method="post" action="/save">
|
<form method="post" action="/save">
|
||||||
<fieldset>
|
<fieldset>
|
||||||
|
<button type="button" class="s_collapsible mt-4">System Config</button>
|
||||||
|
<div class="s_content">
|
||||||
|
<fieldset class="mb-2">
|
||||||
<legend class="des">Device Host Name</legend>
|
<legend class="des">Device Host Name</legend>
|
||||||
<label for="device">Device Name</label>
|
<div class="row mb-3">
|
||||||
<input type="text" name="device" class="text"/>
|
<div class="col-12 col-sm-3">Device Name</div>
|
||||||
|
<div class="col-12 col-sm-9"><input type="text" name="device"/></div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-8 col-sm-3">Dark Mode</div>
|
||||||
|
<div class="col-4 col-sm-9"><input type="checkbox" name="darkMode"/></div>
|
||||||
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
<fieldset class="mb-4">
|
||||||
<button type="button" class="s_collapsible">Network</button>
|
|
||||||
<div class="s_content">
|
|
||||||
<fieldset>
|
|
||||||
<legend class="des">WiFi</legend>
|
|
||||||
<p>Enter the credentials to your prefered WiFi station. After rebooting the device tries to connect with this information.</p>
|
|
||||||
<label for="scanbtn">Search Networks</label>
|
|
||||||
<input type="button" name="scanbtn" id="scanbtn" class="btn" value="scan" onclick="scan()"/><br/>
|
|
||||||
<label for="networks">Avail Networks</label>
|
|
||||||
<select name="networks" id="networks" onChange="selNet()">
|
|
||||||
<option value="-1" selected disabled hidden>not scanned</option>
|
|
||||||
</select>
|
|
||||||
<label for="ssid">SSID</label>
|
|
||||||
<input type="text" name="ssid" class="text"/>
|
|
||||||
<label for="pwd">Password</label>
|
|
||||||
<input type="password" class="text" name="pwd" value="{PWD}"/>
|
|
||||||
</fieldset>
|
|
||||||
<fieldset>
|
|
||||||
<legend class="des">Static IP (optional)</legend>
|
|
||||||
<p>
|
|
||||||
Leave fields blank for DHCP<br/>
|
|
||||||
The following fields are parsed in this format: 192.168.1.1
|
|
||||||
</p>
|
|
||||||
<label for="ipAddr">IP Address</label>
|
|
||||||
<input type="text" name="ipAddr" class="text" maxlength="15" />
|
|
||||||
<label for="ipMask">Submask</label>
|
|
||||||
<input type="text" name="ipMask" class="text" maxlength="15" />
|
|
||||||
<label for="ipDns1">DNS 1</label>
|
|
||||||
<input type="text" name="ipDns1" class="text" maxlength="15" />
|
|
||||||
<label for="ipDns2">DNS 2</label>
|
|
||||||
<input type="text" name="ipDns2" class="text" maxlength="15" />
|
|
||||||
<label for="ipGateway">Gateway</label>
|
|
||||||
<input type="text" name="ipGateway" class="text" maxlength="15" />
|
|
||||||
</fieldset>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button type="button" class="s_collapsible">Protection</button>
|
|
||||||
<div class="s_content">
|
|
||||||
<fieldset>
|
|
||||||
<legend class="des">Protection</legend>
|
|
||||||
<label for="adminpwd">Admin Password</label>
|
|
||||||
<input type="password" name="adminpwd" class="text" value="{PWD}"/>
|
|
||||||
<input type="hidden" name="disclaimer" value="false" id="disclaimer">
|
|
||||||
<p>Select pages which should be protected by password</p>
|
|
||||||
<div id="prot_mask"></div>
|
|
||||||
</fieldset>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button type="button" class="s_collapsible">Inverter</button>
|
|
||||||
<div class="s_content">
|
|
||||||
<fieldset>
|
|
||||||
<legend class="des">Inverter</legend>
|
|
||||||
<div id="inverter"></div><br/>
|
|
||||||
<input type="button" id="btnAdd" class="btn" value="Add Inverter"/>
|
|
||||||
<p class="subdes">General</p>
|
|
||||||
<label for="invInterval">Interval [s]</label>
|
|
||||||
<input type="text" class="text" name="invInterval" pattern="[0-9]+" title="Invalid input"/>
|
|
||||||
<label for="invRetry">Max retries per Payload</label>
|
|
||||||
<input type="text" class="text" name="invRetry"/>
|
|
||||||
|
|
||||||
|
|
||||||
<label for="invRstMid">Reset values and YieldDay at midnight</label>
|
|
||||||
<input type="checkbox" class="cb" name="invRstMid"/><br/>
|
|
||||||
<label for="invRstComStop">Reset values when inverter polling stops at sunset</label>
|
|
||||||
<input type="checkbox" class="cb" name="invRstComStop"/><br/>
|
|
||||||
<label for="invRstNotAvail">Reset values when inverter status is 'not available'</label>
|
|
||||||
<input type="checkbox" class="cb" name="invRstNotAvail"/><br/>
|
|
||||||
</fieldset>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button type="button" class="s_collapsible">NTP Server</button>
|
|
||||||
<div class="s_content">
|
|
||||||
<fieldset>
|
|
||||||
<legend class="des">NTP Server</legend>
|
|
||||||
<label for="ntpAddr">NTP Server / IP</label>
|
|
||||||
<input type="text" class="text" name="ntpAddr"/>
|
|
||||||
<label for="ntpPort">NTP Port</label>
|
|
||||||
<input type="text" class="text" name="ntpPort"/>
|
|
||||||
<label for="ntpBtn">set system time</label>
|
|
||||||
<input type="button" name="ntpBtn" id="ntpBtn" class="btn" value="from browser" onclick="setTime()"/>
|
|
||||||
<input type="button" name="ntpSync" id="ntpSync" class="btn" value="sync NTP" onclick="syncTime()"/>
|
|
||||||
<span id="apiResultNtp"></span>
|
|
||||||
</fieldset>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button type="button" class="s_collapsible">Sunrise & Sunset</button>
|
|
||||||
<div class="s_content">
|
|
||||||
<fieldset>
|
|
||||||
<legend class="des">Sunrise & Sunset</legend>
|
|
||||||
<p>
|
|
||||||
Use a decimal separator: '.' (dot) for Latitude and Longitude
|
|
||||||
</p>
|
|
||||||
<label for="sunLat">Latitude (decimal)</label>
|
|
||||||
<input type="text" class="text" name="sunLat"/>
|
|
||||||
<label for="sunLon">Longitude (decimal)</label>
|
|
||||||
<input type="text" class="text" name="sunLon"/>
|
|
||||||
<label for="sunOffs">Offset (pre sunrise, post sunset)</label>
|
|
||||||
<select name="sunOffs"></select>
|
|
||||||
<br>
|
|
||||||
<label for="sunDisNightCom">Stop polling inverters during night</label>
|
|
||||||
<input type="checkbox" class="cb" name="sunDisNightCom"/><br/>
|
|
||||||
</fieldset>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button type="button" class="s_collapsible">MQTT</button>
|
|
||||||
<div class="s_content">
|
|
||||||
<fieldset>
|
|
||||||
<legend class="des">MQTT</legend>
|
|
||||||
<label for="mqttAddr">Broker / Server IP</label>
|
|
||||||
<input type="text" class="text" name="mqttAddr"/>
|
|
||||||
<label for="mqttPort">Port</label>
|
|
||||||
<input type="text" class="text" name="mqttPort"/>
|
|
||||||
<label for="mqttUser">Username (optional)</label>
|
|
||||||
<input type="text" class="text" name="mqttUser"/>
|
|
||||||
<label for="mqttPwd">Password (optional)</label>
|
|
||||||
<input type="password" class="text" name="mqttPwd"/>
|
|
||||||
<label for="mqttTopic">Topic</label>
|
|
||||||
<input type="text" class="text" name="mqttTopic" pattern="[A-Za-z0-9./#$%&=+_-]+" title="Invalid input" />
|
|
||||||
<p class="des">Send Inverter data in a fixed interval, even if there is no change. A value of '0' disables the fixed interval. The data is published once it was successfully received from inverter. (default: 0)</p>
|
|
||||||
<label for="mqttIntvl">Interval [s]</label>
|
|
||||||
<input type="text" class="text" name="mqttInterval" pattern="[0-9]+" title="Invalid input" />
|
|
||||||
<label for="mqttBtn">Discovery Config (homeassistant)</label>
|
|
||||||
<input type="button" name="mqttDiscovery" id="mqttDiscovery" class="btn" value="send" onclick="sendDiscoveryConfig()"/>
|
|
||||||
<span id="apiResultMqtt"></span>
|
|
||||||
</fieldset>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button type="button" class="s_collapsible">System Config</button>
|
|
||||||
<div class="s_content">
|
|
||||||
<fieldset>
|
|
||||||
<legend class="des">System Config</legend>
|
<legend class="des">System Config</legend>
|
||||||
<p class="des">Pinout</p>
|
<p class="des">Pinout</p>
|
||||||
<div id="pinout"></div>
|
<div id="pinout"></div>
|
||||||
|
@ -177,72 +46,261 @@
|
||||||
<div id="cmt"></div>
|
<div id="cmt"></div>
|
||||||
|
|
||||||
<p class="des">Serial Console</p>
|
<p class="des">Serial Console</p>
|
||||||
<label for="serEn">print inverter data</label>
|
<div class="row mb-3">
|
||||||
<input type="checkbox" class="cb" name="serEn"/><br/>
|
<div class="col-8 col-sm-3">print inverter data</div>
|
||||||
<label for="serDbg">Serial Debug</label>
|
<div class="col-4 col-sm-9"><input type="checkbox" name="serEn"/></div>
|
||||||
<input type="checkbox" class="cb" name="serDbg"/><br/>
|
</div>
|
||||||
<label for="serIntvl">Interval [s]</label>
|
<div class="row mb-3">
|
||||||
<input type="text" class="text" name="serIntvl" pattern="[0-9]+" title="Invalid input"/>
|
<div class="col-8 col-sm-3">Serial Debug</div>
|
||||||
|
<div class="col-4 col-sm-9"><input type="checkbox" name="serDbg"/></div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-12 col-sm-3 my-2">Interval [s]</div>
|
||||||
|
<div class="col-12 col-sm-9"><input type="text" name="serIntvl" pattern="[0-9]+" title="Invalid input"/></div>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="button" class="s_collapsible">Network</button>
|
||||||
|
<div class="s_content">
|
||||||
|
<fieldset class="mb-2">
|
||||||
|
<legend class="des">WiFi</legend>
|
||||||
|
<p>Enter the credentials to your prefered WiFi station. After rebooting the device tries to connect with this information.</p>
|
||||||
|
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-12 col-sm-3 my-2">Search Networks</div>
|
||||||
|
<div class="col-12 col-sm-9"><input type="button" name="scanbtn" id="scanbtn" class="btn" value="scan" onclick="scan()"/></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row mb-2 mb-sm-3">
|
||||||
|
<div class="col-12 col-sm-3 my-2">Avail Networks</div>
|
||||||
|
<div class="col-12 col-sm-9">
|
||||||
|
<select name="networks" id="networks" onChange="selNet()">
|
||||||
|
<option value="-1" selected disabled hidden>not scanned</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-2 mb-sm-3">
|
||||||
|
<div class="col-12 col-sm-3 my-2">SSID</div>
|
||||||
|
<div class="col-12 col-sm-9"><input type="text" name="ssid"/></div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-2 mb-sm-3">
|
||||||
|
<div class="col-12 col-sm-3 my-2">Password</div>
|
||||||
|
<div class="col-12 col-sm-9"><input type="password" name="pwd" value="{PWD}"/></div>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
<fieldset class="mb-4">
|
||||||
|
<legend class="des">Static IP (optional)</legend>
|
||||||
|
<p>
|
||||||
|
Leave fields blank for DHCP<br/>
|
||||||
|
The following fields are parsed in this format: 192.168.4.1
|
||||||
|
</p>
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-12 col-sm-3 my-2">IP Address</div>
|
||||||
|
<div class="col-12 col-sm-9"><input type="text" name="ipAddr" maxlength="15" /></div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-12 col-sm-3 my-2">Submask</div>
|
||||||
|
<div class="col-12 col-sm-9"><input type="text" name="ipMask" maxlength="15" /></div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-12 col-sm-3 my-2">DNS 1</div>
|
||||||
|
<div class="col-12 col-sm-9"><input type="text" name="ipDns1" maxlength="15" /></div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-12 col-sm-3 my-2">DNS 2</div>
|
||||||
|
<div class="col-12 col-sm-9"><input type="text" name="ipDns2" maxlength="15" /></div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-12 col-sm-3 my-2">Gateway</div>
|
||||||
|
<div class="col-12 col-sm-9"><input type="text" name="ipGateway" maxlength="15" /></div>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="button" class="s_collapsible">Protection</button>
|
||||||
|
<div class="s_content">
|
||||||
|
<fieldset class="mb-4">
|
||||||
|
<legend class="des mx-2">Protection</legend>
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-12 col-sm-3 mb-2 mt-2">Admin Password</div>
|
||||||
|
<div class="col-12 col-sm-9"><input type="password" name="adminpwd" value="{PWD}"/></div>
|
||||||
|
</div>
|
||||||
|
<p>Select pages which should be protected by password</p>
|
||||||
|
<div id="prot_mask"></div>
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="button" class="s_collapsible">Inverter</button>
|
||||||
|
<div class="s_content">
|
||||||
|
<fieldset class="mb-4">
|
||||||
|
<legend class="des">Inverter</legend>
|
||||||
|
<div id="inverter"></div>
|
||||||
|
<div class="row mb-2">
|
||||||
|
<div class="col-12 col-sm-3"></div>
|
||||||
|
<div class="col-12 col-sm-9"><input type="button" id="btnAdd" class="btn" value="Add Inverter"/></div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-2">
|
||||||
|
<div class="col-12 col-sm-3"><p class="subdes">General</p></div>
|
||||||
|
<div class="col-12 col-sm-9"></div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-12 col-sm-3 my-2">Interval [s]</div>
|
||||||
|
<div class="col-12 col-sm-9"><input type="text" name="invInterval" pattern="[0-9]+" title="Invalid input"/></div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-12 col-sm-3 my-2">Max retries per Payload</div>
|
||||||
|
<div class="col-12 col-sm-9"><input type="text" name="invRetry"/></div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-8 col-sm-3 mb-2">Reset values and YieldDay at midnight</div>
|
||||||
|
<div class="col-4 col-sm-9"><input type="checkbox" name="invRstMid"/></div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-8 col-sm-3 mb-2">Reset values when inverter polling stops at sunset</div>
|
||||||
|
<div class="col-4 col-sm-9"><input type="checkbox" name="invRstComStop"/></div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-8 col-sm-3">Reset values when inverter status is 'not available'</div>
|
||||||
|
<div class="col-4 col-sm-9"><input type="checkbox" name="invRstNotAvail"/></div>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="button" class="s_collapsible">NTP Server</button>
|
||||||
|
<div class="s_content">
|
||||||
|
<fieldset class="mb-4">
|
||||||
|
<legend class="des">NTP Server</legend>
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-12 col-sm-3 my-2">NTP Server / IP</div>
|
||||||
|
<div class="col-12 col-sm-9"><input type="text" name="ntpAddr"/></div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-12 col-sm-3 my-2">NTP Port</div>
|
||||||
|
<div class="col-12 col-sm-9"><input type="text" name="ntpPort"/></div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-12 col-sm-3 my-2">set system time</div>
|
||||||
|
<div class="col-12 col-sm-9">
|
||||||
|
<input type="button" name="ntpBtn" id="ntpBtn" class="btn" value="from browser" onclick="setTime()"/>
|
||||||
|
<input type="button" name="ntpSync" id="ntpSync" class="btn" value="sync NTP" onclick="syncTime()"/>
|
||||||
|
<span id="apiResultNtp"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="button" class="s_collapsible">Sunrise & Sunset</button>
|
||||||
|
<div class="s_content">
|
||||||
|
<fieldset class="mb-4">
|
||||||
|
<legend class="des">Sunrise & Sunset</legend>
|
||||||
|
<p>Use a decimal separator: '.' (dot) for Latitude and Longitude</p>
|
||||||
|
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-12 col-sm-3 my-2">Latitude (decimal)</div>
|
||||||
|
<div class="col-12 col-sm-9"><input type="text" name="sunLat"/></div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-12 col-sm-3 my-2">Longitude (decimal)</div>
|
||||||
|
<div class="col-12 col-sm-9"><input type="text" name="sunLon"/></div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-12 col-sm-3 my-2">Offset (pre sunrise, post sunset)</div>
|
||||||
|
<div class="col-12 col-sm-9"><select name="sunOffs"></select></div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-8 col-sm-3">Stop polling inverters during night</div>
|
||||||
|
<div class="col-4 col-sm-9"><input type="checkbox" name="sunDisNightCom"/></div>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="button" class="s_collapsible">MQTT</button>
|
||||||
|
<div class="s_content">
|
||||||
|
<fieldset class="mb-4">
|
||||||
|
<legend class="des">MQTT</legend>
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-12 col-sm-3 my-2">Broker / Server IP</div>
|
||||||
|
<div class="col-12 col-sm-9"><input type="text" name="mqttAddr"/></div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-12 col-sm-3 my-2">Port</div>
|
||||||
|
<div class="col-12 col-sm-9"><input type="text" name="mqttPort"/></div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-12 col-sm-3 my-2">Username (optional)</div>
|
||||||
|
<div class="col-12 col-sm-9"><input type="text" name="mqttUser"/></div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-12 col-sm-3 my-2">Password (optional)</div>
|
||||||
|
<div class="col-12 col-sm-9"><input type="password" name="mqttPwd"/></div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-12 col-sm-3 my-2">Topic</div>
|
||||||
|
<div class="col-12 col-sm-9"><input type="text" name="mqttTopic" pattern="[A-Za-z0-9./#$%&=+_-]+" title="Invalid input" /></div>
|
||||||
|
</div>
|
||||||
|
<p class="des">Send Inverter data in a fixed interval, even if there is no change. A value of '0' disables the fixed interval. The data is published once it was successfully received from inverter. (default: 0)</p>
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-12 col-sm-3 my-2">Interval [s]</div>
|
||||||
|
<div class="col-12 col-sm-9"><input type="text" name="mqttInterval" pattern="[0-9]+" title="Invalid input" /></div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-12 col-sm-3 my-2">Discovery Config (homeassistant)</div>
|
||||||
|
<div class="col-12 col-sm-9">
|
||||||
|
<input type="button" name="mqttDiscovery" id="mqttDiscovery" class="btn" value="send" onclick="sendDiscoveryConfig()"/>
|
||||||
|
<span id="apiResultMqtt"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button type="button" class="s_collapsible">Display Config</button>
|
<button type="button" class="s_collapsible">Display Config</button>
|
||||||
<div class="s_content">
|
<div class="s_content">
|
||||||
<fieldset>
|
<fieldset class="mb-4">
|
||||||
<legend class="des">Display Config</legend>
|
<legend class="des">Display Config</legend>
|
||||||
<div id="dispType"></div>
|
<div id="dispType"></div>
|
||||||
<label for="logoEn">Show Logo</label>
|
<div id="dispRot"></div>
|
||||||
<input type="checkbox" class="cb" name="logoEn"/><br/>
|
<div class="row mb-3">
|
||||||
<label for="dispPwr">Turn off while inverters are offline</label>
|
<div class="col-8 col-sm-3">Turn off while inverters are offline</div>
|
||||||
<input type="checkbox" class="cb" name="dispPwr"/><br/>
|
<div class="col-4 col-sm-9"><input type="checkbox" name="disp_pwr"/></div>
|
||||||
<label for="dispPxSh">Enable pixel shifting</label>
|
</div>
|
||||||
<input type="checkbox" class="cb" name="dispPxSh"/><br/>
|
<div class="row mb-3">
|
||||||
<label for="disp180">Rotate 180 degree</label>
|
<div class="col-8 col-sm-3">Enable Screensaver (pixel shifting)</div>
|
||||||
<input type="checkbox" class="cb" name="disp180"/><br/>
|
<div class="col-4 col-sm-9"><input type="checkbox" name="disp_pxshift"/></div>
|
||||||
|
</div>
|
||||||
<label for="dispCont">Contrast</label>
|
<div class="row mb-3">
|
||||||
<select name="dispCont" id="contrast"></select>
|
<div class="col-12 col-sm-3 my-2">Contrast</div>
|
||||||
|
<div class="col-12 col-sm-9"><input type="number" name="disp_cont" min="1" max="100"></select></div>
|
||||||
|
</div>
|
||||||
<p class="des">Pinout</p>
|
<p class="des">Pinout</p>
|
||||||
<div id="dispPins"></div>
|
<div id="dispPins"></div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-3">
|
<div class="row mb-4 mt-4">
|
||||||
<label for="reboot">Reboot device after successful save</label>
|
<div class="col-8 col-sm-3">Reboot device after successful save</div>
|
||||||
<input type="checkbox" class="cb" name="reboot" checked />
|
<div class="col-2 col-md-6">
|
||||||
<input type="submit" value="save" class="btn right"/>
|
<input type="checkbox" name="reboot" checked />
|
||||||
|
<input type="submit" value="save" class="btn right"/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="hr mb-3 mt-3"></div>
|
<div class="hr mb-3 mt-3"></div>
|
||||||
<div class="mb-4">
|
<div class="mb-4 mt-4">
|
||||||
<a class="btn" href="/erase">ERASE SETTINGS (not WiFi)</a>
|
<a class="btn" href="/erase">ERASE SETTINGS (not WiFi)</a>
|
||||||
<fieldset>
|
<fieldset class="mb-4">
|
||||||
<legend class="des">Upload / Store JSON Settings</legend>
|
<legend class="des">Upload / Store JSON Settings</legend>
|
||||||
<form id="form" method="POST" action="/upload" enctype="multipart/form-data" accept-charset="utf-8">
|
<form id="form" method="POST" action="/upload" enctype="multipart/form-data" accept-charset="utf-8">
|
||||||
<input type="file" name="upload">
|
<input type="file" name="upload">
|
||||||
<input type="button" class="btn" value="Upload" onclick="hide()">
|
<input type="button" class="btn" value="Upload" onclick="hide()">
|
||||||
</form>
|
</form>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<a class="btn" href="/get_setup" target="_blank">Download settings (JSON file)</a> (only saved values, passwords will be removed!)
|
<a class="btn" href="/get_setup" target="_blank">Download settings (JSON file)</a><span> (only saved values, passwords will be removed!)</span>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="footer">
|
{#HTML_FOOTER}
|
||||||
<div class="left">
|
|
||||||
<a href="https://ahoydtu.de" target="_blank">AhoyDTU © 2023</a>
|
|
||||||
<ul>
|
|
||||||
<li><a href="https://discord.gg/WzhxEY62mB" target="_blank">Discord</a></li>
|
|
||||||
<li><a href="https://github.com/lumapu/ahoy" target="_blank">Github</a></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div class="right">
|
|
||||||
<ul>
|
|
||||||
<li><span id="version"></span></li>
|
|
||||||
<li><span id="esp_type"></span></li>
|
|
||||||
<li><a href="https://creativecommons.org/licenses/by-nc-sa/3.0/de" target="_blank" >CC BY-NC-SA 3.0</a></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
var highestId = 0;
|
var highestId = 0;
|
||||||
var maxInv = 0;
|
var maxInv = 0;
|
||||||
|
@ -372,22 +430,38 @@
|
||||||
document.getElementsByName(id + "Name")[0].value = "";
|
document.getElementsByName(id + "Name")[0].value = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function mlCb(id, des, chk=false) {
|
||||||
|
var cb = ml("input", {type: "checkbox", id: id, name: id}, "");
|
||||||
|
if(chk)
|
||||||
|
cb.checked = true;
|
||||||
|
return ml("div", {class: "row mb-3"}, [
|
||||||
|
ml("div", {class: "col-8 col-sm-3"}, des),
|
||||||
|
ml("div", {class: "col-4 col-sm-9"}, cb)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function mlE(des, e) {
|
||||||
|
return ml("div", {class: "row mb-3"}, [
|
||||||
|
ml("div", {class: "col-12 col-sm-3 my-2"}, des),
|
||||||
|
ml("div", {class: "col-12 col-sm-9"}, e)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
function ivHtml(obj, id) {
|
function ivHtml(obj, id) {
|
||||||
highestId = id + 1;
|
highestId = id + 1;
|
||||||
if(highestId == maxInv)
|
if(highestId == maxInv)
|
||||||
setHide("btnAdd", true);
|
setHide("btnAdd", true);
|
||||||
iv = document.getElementById("inverter");
|
|
||||||
|
var iv = document.getElementById("inverter");
|
||||||
iv.appendChild(des("Inverter " + id));
|
iv.appendChild(des("Inverter " + id));
|
||||||
id = "inv" + id;
|
id = "inv" + id;
|
||||||
|
|
||||||
iv.appendChild(lbl(id + "Enable", "Communication Enable"));
|
|
||||||
var en = inp(id + "Enable", null, null, ["cb"], id + "Enable", "checkbox");
|
|
||||||
en.checked = obj["enabled"];
|
|
||||||
iv.append(en, br());
|
|
||||||
|
|
||||||
iv.appendChild(lbl(id + "Addr", "Serial Number (12 digits)*"));
|
|
||||||
var addr = inp(id + "Addr", obj["serial"], 12, ["text"], null, "text", "[0-9]+", "Invalid input");
|
var addr = inp(id + "Addr", obj["serial"], 12, ["text"], null, "text", "[0-9]+", "Invalid input");
|
||||||
iv.appendChild(addr);
|
iv.append(
|
||||||
|
mlCb(id + "Enable", "Communication Enable", obj["enabled"]),
|
||||||
|
mlE("Serial Number (12 digits)*", addr)
|
||||||
|
);
|
||||||
|
|
||||||
['keyup', 'change'].forEach(function(evt) {
|
['keyup', 'change'].forEach(function(evt) {
|
||||||
addr.addEventListener(evt, (e) => {
|
addr.addEventListener(evt, (e) => {
|
||||||
var serial = addr.value.substring(0,4);
|
var serial = addr.value.substring(0,4);
|
||||||
|
@ -397,9 +471,9 @@
|
||||||
setHide(id+"ModName"+i, true);
|
setHide(id+"ModName"+i, true);
|
||||||
setHide(id+"YieldCor"+i, true);
|
setHide(id+"YieldCor"+i, true);
|
||||||
}
|
}
|
||||||
setHide("lbl"+id+"ModPwr", true);
|
setHide("row"+id+"ModPwr", true);
|
||||||
setHide("lbl"+id+"ModName", true);
|
setHide("row"+id+"ModName", true);
|
||||||
setHide("lbl"+id+"YieldCor", true);
|
setHide("row"+id+"YieldCor", true);
|
||||||
|
|
||||||
if(serial.charAt(0) == 1) {
|
if(serial.charAt(0) == 1) {
|
||||||
if((serial.charAt(1) == 0) || (serial.charAt(1) == 1)) {
|
if((serial.charAt(1) == 0) || (serial.charAt(1) == 1)) {
|
||||||
|
@ -423,39 +497,44 @@
|
||||||
setHide(id+"ModName"+i, false);
|
setHide(id+"ModName"+i, false);
|
||||||
setHide(id+"YieldCor"+i, false);
|
setHide(id+"YieldCor"+i, false);
|
||||||
}
|
}
|
||||||
setHide("lbl"+id+"ModPwr", false);
|
setHide("row"+id+"ModPwr", false);
|
||||||
setHide("lbl"+id+"ModName", false);
|
setHide("row"+id+"ModName", false);
|
||||||
setHide("lbl"+id+"YieldCor", false);
|
setHide("row"+id+"YieldCor", false);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
iv.append(
|
iv.append(mlE("Name*", inp(id + "Name", obj["name"], 16, ["text"], null, "text", "[A-Za-z0-9./#$%&=+_-]+", "Invalid input")));
|
||||||
lbl(id + "Name", "Name*"),
|
|
||||||
inp(id + "Name", obj["name"], 32, ["text"], null, "text", "[A-Za-z0-9./#$%&=+_-]+", "Invalid input")
|
|
||||||
);
|
|
||||||
|
|
||||||
for(var j of [
|
for(var j of [
|
||||||
["ModPwr", "ch_max_power", "Max Module Power (Wp)", 4, "[0-9]+"],
|
["ModPwr", "ch_max_power", "Max Module Power (Wp)", 4, "[0-9]+"],
|
||||||
["ModName", "ch_name", "Module Name", 16, null],
|
["ModName", "ch_name", "Module Name", 15, null],
|
||||||
["YieldCor", "ch_yield_cor", "Yield Total Correction [kWh]", 16, "[0-9-]+"]]) {
|
["YieldCor", "ch_yield_cor", "Yield Total Correction [kWh]", 8, "[0-9-]+"]]) {
|
||||||
var cl = (re.test(obj["serial"])) ? null : ["hide"];
|
|
||||||
iv.appendChild(lbl(null, j[2], cl, "lbl" + id + j[0]));
|
var cl = (re.test(obj["serial"])) ? "" : " hide";
|
||||||
d = div([j[0]]);
|
|
||||||
i = 0;
|
i = 0;
|
||||||
cl = (re.test(obj["serial"])) ? ["text", "sh"] : ["text", "sh", "hide"];
|
arrIn = [];
|
||||||
for(it of obj[j[1]]) {
|
for(it of obj[j[1]]) {
|
||||||
d.appendChild(inp(id + j[0] + i, it, j[3], cl, id + j[0] + i, "text", j[4], "Invalid input"));
|
arrIn.push(ml("div", {class: "col-3 "},
|
||||||
|
inp(id + j[0] + i, it, j[3], [], id + j[0] + i, "text", j[4], "Invalid input")
|
||||||
|
));
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
iv.appendChild(d);
|
|
||||||
|
iv.append(
|
||||||
|
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)
|
||||||
|
)
|
||||||
|
])
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
var del = inp(id+"del", "X", 0, ["btn", "btnDel"], id+"del", "button");
|
var del = inp(id+"del", "X", 0, ["btn", "btnDel"], id+"del", "button");
|
||||||
del.addEventListener("click", delIv);
|
del.addEventListener("click", delIv);
|
||||||
iv.append(
|
iv.append(mlE("Delete", del));
|
||||||
lbl(id + "lbldel", "Delete"),
|
|
||||||
del
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function ivGlob(obj) {
|
function ivGlob(obj) {
|
||||||
|
@ -468,24 +547,22 @@
|
||||||
function parseSys(obj) {
|
function parseSys(obj) {
|
||||||
for(var i of [["device", "device_name"], ["ssid", "ssid"]])
|
for(var i of [["device", "device_name"], ["ssid", "ssid"]])
|
||||||
document.getElementsByName(i[0])[0].value = obj[i[1]];
|
document.getElementsByName(i[0])[0].value = obj[i[1]];
|
||||||
var e = document.getElementsByName("adminpwd")[0];
|
document.getElementsByName("darkMode")[0].checked = obj["dark_mode"];
|
||||||
|
e = document.getElementsByName("adminpwd")[0];
|
||||||
if(!obj["pwd_set"])
|
if(!obj["pwd_set"])
|
||||||
e.value = "";
|
e.value = "";
|
||||||
var d = document.getElementById("prot_mask");
|
var d = document.getElementById("prot_mask");
|
||||||
var a = ["Index", "Live", "Serial / Console", "Settings", "Update", "System"]
|
var a = ["Index", "Live", "Serial / Console", "Settings", "Update", "System"];
|
||||||
|
var el = [];
|
||||||
for(var i = 0; i < 6; i++) {
|
for(var i = 0; i < 6; i++) {
|
||||||
var chkd = ((obj["prot_mask"] & (1 << i)) == (1 << i));
|
var chk = ((obj["prot_mask"] & (1 << i)) == (1 << i));
|
||||||
var sp = lbl("protMask" + i, a[i]);
|
el.push(mlCb("protMask" + i, a[i], chk))
|
||||||
var cb = inp("protMask" + i, null, null, ["cb"], "protMask" + i, "checkbox", null, null, chkd);
|
|
||||||
if(0 == i)
|
|
||||||
d.replaceChildren(sp, cb, br());
|
|
||||||
else
|
|
||||||
d.append(sp, cb, br());
|
|
||||||
}
|
}
|
||||||
|
d.append(...el);
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseGeneric(obj) {
|
function parseGeneric(obj) {
|
||||||
parseVersion(obj);
|
parseNav(obj);
|
||||||
parseESP(obj);
|
parseESP(obj);
|
||||||
parseRssi(obj);
|
parseRssi(obj);
|
||||||
}
|
}
|
||||||
|
@ -528,35 +605,30 @@
|
||||||
pins = [['led0', 'pinLed0'], ['led1', 'pinLed1']];
|
pins = [['led0', 'pinLed0'], ['led1', 'pinLed1']];
|
||||||
for(p of pins) {
|
for(p of pins) {
|
||||||
e.append(
|
e.append(
|
||||||
lbl(p[1], p[0].toUpperCase()),
|
ml("div", {class: "row mb-3"}, [
|
||||||
sel(p[1], ("ESP8266" == type) ? esp8266pins : esp32pins, obj[p[0]])
|
ml("div", {class: "col-12 col-sm-3 my-2"}, p[0].toUpperCase()),
|
||||||
|
ml("div", {class: "col-12 col-sm-9"},
|
||||||
|
sel(p[1], ("ESP8266" == type) ? esp8266pins : esp32pins, obj[p[0]])
|
||||||
|
)
|
||||||
|
])
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseNrfRadio(obj, type) {
|
function parseNrfRadio(obj, type) {
|
||||||
var e = document.getElementById("rf24");
|
var e = document.getElementById("rf24").append(
|
||||||
var en = inp("rf24Enable", null, null, ["cb"], "rf24Enable", "checkbox");
|
ml("div", {class: "row mb-3"}, [
|
||||||
en.checked = obj["en"];
|
ml("div", {class: "col-12 col-sm-3 my-2"}, "Power Level"),
|
||||||
|
ml("div", {class: "col-12 col-sm-9"},
|
||||||
e.append(
|
sel("rf24Power", [
|
||||||
lbl("rf24Enable", "NRF24 Enable"),
|
[0, "MIN"],
|
||||||
en, br(),
|
[1, "LOW"],
|
||||||
lbl("rf24Power", "Amplifier Power Level"),
|
[2, "HIGH"],
|
||||||
sel("rf24Power", [
|
[3, "MAX"]
|
||||||
[0, "MIN"],
|
], obj["power_level"])
|
||||||
[1, "LOW"],
|
)
|
||||||
[2, "HIGH"],
|
])
|
||||||
[3, "MAX"]
|
|
||||||
], obj["power_level"])
|
|
||||||
);
|
);
|
||||||
pins = [['cs', 'pinCs'], ['ce', 'pinCe'], ['irq', 'pinIrq']];
|
|
||||||
for(p of pins) {
|
|
||||||
e.append(
|
|
||||||
lbl(p[1], p[0].toUpperCase()),
|
|
||||||
sel(p[1], ("ESP8266" == type) ? esp8266pins : esp32pins, obj[p[0]])
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseCmtRadio(obj, type) {
|
function parseCmtRadio(obj, type) {
|
||||||
|
@ -584,35 +656,56 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseDisplay(obj, type) {
|
function parseDisplay(obj, type) {
|
||||||
for(var i of [["logoEn", "logo_en"], ["dispPwr", "disp_pwr"], ["dispPxSh", "px_shift"], ["disp180", "rot180"]])
|
for(var i of ["disp_pwr", "disp_pxshift"])
|
||||||
document.getElementsByName(i[0])[0].checked = obj[i[1]];
|
document.getElementsByName(i)[0].checked = obj[i];
|
||||||
|
|
||||||
var e = document.getElementById("dispPins");
|
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) {
|
for(p of pins) {
|
||||||
e.appendChild(lbl(p[1], p[0].toUpperCase()));
|
if(("ESP8266" == type) && p[0] == "cs")
|
||||||
e.appendChild(sel(p[1], ("ESP8266" == type) ? esp8266pins : esp32pins, obj[p[1]]));
|
break;
|
||||||
|
e.append(
|
||||||
|
ml("div", {class: "row mb-3"}, [
|
||||||
|
ml("div", {class: "col-12 col-sm-3 my-2"}, p[0].toUpperCase()),
|
||||||
|
ml("div", {class: "col-12 col-sm-9"},
|
||||||
|
sel(p[1], ("ESP8266" == type) ? esp8266pins : esp32pins, obj[p[1]])
|
||||||
|
)
|
||||||
|
])
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
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(
|
document.getElementById("dispType").append(
|
||||||
lbl("dispType", "Type"),
|
ml("div", {class: "row mb-3"}, [
|
||||||
sel("dispType", opts, obj["disp_type"])
|
ml("div", {class: "col-12 col-sm-3 my-2"}, "Type"),
|
||||||
|
ml("div", {class: "col-12 col-sm-9"}, sel("disp_typ", opts, obj["disp_typ"]))
|
||||||
|
])
|
||||||
);
|
);
|
||||||
|
|
||||||
e = document.getElementById("contrast");
|
opts = [[0, "0°"], [2, "180°"]];
|
||||||
for(var i = 30; i < 101; i += 2) {
|
if("ESP32" == type) {
|
||||||
e.appendChild(opt(i, i, (i == obj["contrast"])));
|
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) {
|
function parse(root) {
|
||||||
if(null != root) {
|
if(null != root) {
|
||||||
parseMenu(root["menu"]);
|
|
||||||
parseSys(root["system"]);
|
parseSys(root["system"]);
|
||||||
parseGeneric(root["generic"]);
|
parseGeneric(root["generic"]);
|
||||||
parseStaticIp(root["static_ip"]);
|
parseStaticIp(root["static_ip"]);
|
||||||
parseIv(root["inverter"]);
|
|
||||||
parseMqtt(root["mqtt"]);
|
parseMqtt(root["mqtt"]);
|
||||||
parseNtp(root["ntp"]);
|
parseNtp(root["ntp"]);
|
||||||
parseSun(root["sun"]);
|
parseSun(root["sun"]);
|
||||||
|
@ -622,6 +715,7 @@
|
||||||
parseCmtRadio(root["radioCmt"], root["system"]["esp_type"]);
|
parseCmtRadio(root["radioCmt"], root["system"]["esp_type"]);
|
||||||
parseSerial(root["serial"]);
|
parseSerial(root["serial"]);
|
||||||
parseDisplay(root["display"], root["system"]["esp_type"]);
|
parseDisplay(root["display"], root["system"]["esp_type"]);
|
||||||
|
getAjax("/api/inverter/list", parseIv);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -644,11 +738,7 @@
|
||||||
e.value = s.value;
|
e.value = s.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
hiddenInput = document.getElementById("disclaimer")
|
|
||||||
hiddenInput.value = sessionStorage.getItem("gDisclaimer");
|
|
||||||
|
|
||||||
getAjax("/api/setup", parse);
|
getAjax("/api/setup", parse);
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -4,26 +4,39 @@ html, body {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
min-height: 100%;
|
min-height: 100%;
|
||||||
|
background-color: var(--bg);
|
||||||
|
color: var(--fg);
|
||||||
}
|
}
|
||||||
|
|
||||||
h2 {
|
h2 {
|
||||||
padding-left: 10px;
|
padding-left: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
span, li, h3, label, fieldset {
|
||||||
|
color: var(--fg);
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldset, input[type=submit], .btn {
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#live span {
|
||||||
|
color: var(--fg2);
|
||||||
|
}
|
||||||
|
|
||||||
.topnav {
|
.topnav {
|
||||||
background-color: #333;
|
background-color: var(--nav-bg);
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.topnav a {
|
.topnav a {
|
||||||
color: #fff;
|
color: var(--fg2);
|
||||||
padding: 14px 14px;
|
padding: 14px 14px;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
font-size: 17px;
|
font-size: 17px;
|
||||||
display: block;
|
display: block;
|
||||||
height: 20px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#topnav a {
|
#topnav a {
|
||||||
|
@ -33,23 +46,26 @@ h2 {
|
||||||
.topnav a.icon {
|
.topnav a.icon {
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
background: #333;
|
background: var(--nav-bg);
|
||||||
display: block;
|
display: block;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
}
|
}
|
||||||
|
|
||||||
.topnav a:hover {
|
.topnav a:hover {
|
||||||
background-color: #044e86 !important;
|
background-color: var(--primary-hover) !important;
|
||||||
color: #000;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.topnav .info {
|
.topnav .info {
|
||||||
color: #fff;
|
color: var(--fg2);
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 24px;
|
right: 24px;
|
||||||
top: 5px;
|
top: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.topnav .mobile {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
svg.icon {
|
svg.icon {
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
@ -57,8 +73,24 @@ svg.icon {
|
||||||
padding: 5px 7px 5px 0px;
|
padding: 5px 7px 5px 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.icon-info {
|
||||||
|
fill: var(--info);
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-warn {
|
||||||
|
fill: var(--warn);
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-success {
|
||||||
|
fill: var(--success);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wifi {
|
||||||
|
fill: var(--fg2);
|
||||||
|
}
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
background-color: #006ec0;
|
background-color: var(--primary);
|
||||||
color: #fff !important;
|
color: #fff !important;
|
||||||
padding-left: 80px !important
|
padding-left: 80px !important
|
||||||
}
|
}
|
||||||
|
@ -74,7 +106,7 @@ svg.icon {
|
||||||
}
|
}
|
||||||
|
|
||||||
.topnav .active {
|
.topnav .active {
|
||||||
background-color: #555;
|
background-color: var(--nav-active);
|
||||||
}
|
}
|
||||||
|
|
||||||
span.seperator {
|
span.seperator {
|
||||||
|
@ -85,6 +117,197 @@ span.seperator {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#content {
|
||||||
|
max-width: 1140px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.total-h {
|
||||||
|
background-color: var(--total-head-title);
|
||||||
|
color: var(--fg2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.total-bg {
|
||||||
|
background-color: var(--total-bg);
|
||||||
|
color: var(--fg2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.iv-h {
|
||||||
|
background-color: var(--iv-head-title);
|
||||||
|
color: var(--fg2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.iv-bg {
|
||||||
|
background-color: var(--iv-head-bg);
|
||||||
|
color: var(--fg2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ch-h {
|
||||||
|
background-color: var(--ch-head-title);
|
||||||
|
color: var(--fg2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ch-bg {
|
||||||
|
background-color: var(--ch-head-bg);
|
||||||
|
color: var(--fg2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ts-h {
|
||||||
|
background-color: var(--ts-head);
|
||||||
|
color: var(--fg2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ts-bg {
|
||||||
|
background-color: var(--ts-bg);
|
||||||
|
color: var(--fg2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hr {
|
||||||
|
border-top: 1px solid var(--iv-head-title);
|
||||||
|
margin: 1rem 0 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
text-align: justify;
|
||||||
|
font-size: 13pt;
|
||||||
|
color: var(--fg);
|
||||||
|
}
|
||||||
|
|
||||||
|
#footer {
|
||||||
|
background-color: var(--footer-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.row { display: flex; max-width: 100%; flex-wrap: wrap; }
|
||||||
|
.col { flex: 1 0 0%; }
|
||||||
|
|
||||||
|
.col-1, .col-2, .col-3, .col-4,
|
||||||
|
.col-5, .col-6, .col-7, .col-8,
|
||||||
|
.col-9, .col-10, .col-11, .col-12 { flex: 0 0 auto; }
|
||||||
|
|
||||||
|
.col-1 { width: 8.333333333%; }
|
||||||
|
.col-2 { width: 16.66666667%; }
|
||||||
|
.col-3 { width: 25%; }
|
||||||
|
.col-4 { width: 33.33333333%; }
|
||||||
|
.col-5 { width: 41.66666667%; }
|
||||||
|
.col-6 { width: 50%; }
|
||||||
|
.col-7 { width: 58.33333333%; }
|
||||||
|
.col-8 { width: 66.66666667%; }
|
||||||
|
.col-9 { width: 75%; }
|
||||||
|
.col-10 { width: 83.33333333%; }
|
||||||
|
.col-11 { width: 91.66666667%; }
|
||||||
|
.col-12 { width: 100%; }
|
||||||
|
|
||||||
|
.p-1 { padding: 0.25rem; }
|
||||||
|
.p-2 { padding: 0.5rem; }
|
||||||
|
.p-3 { padding: 1rem; }
|
||||||
|
.p-4 { padding: 1.5rem; }
|
||||||
|
.p-5 { padding: 3rem; }
|
||||||
|
|
||||||
|
.px-1 { padding: 0 0.25rem 0 0.25rem; }
|
||||||
|
.px-2 { padding: 0 0.5rem 0 0.5rem; }
|
||||||
|
.px-3 { padding: 0 1rem 0 1rem; }
|
||||||
|
.px-4 { padding: 0 1.5rem 0 1.5rem; }
|
||||||
|
.px-5 { padding: 0 3rem 0 3rem; }
|
||||||
|
|
||||||
|
.py-1 { padding: 0.25rem 0 0.25rem; }
|
||||||
|
.py-2 { padding: 0.5rem 0 0.5rem; }
|
||||||
|
.py-3 { padding: 1rem 0 1rem; }
|
||||||
|
.py-4 { padding: 1.5rem 0 1.5rem; }
|
||||||
|
.py-5 { padding: 3rem 0 3rem; }
|
||||||
|
|
||||||
|
.mx-1 { margin: 0 0.25rem 0 0.25rem; }
|
||||||
|
.mx-2 { margin: 0 0.5rem 0 0.5rem; }
|
||||||
|
.mx-3 { margin: 0 1rem 0 1rem; }
|
||||||
|
.mx-4 { margin: 0 1.5rem 0 1.5rem; }
|
||||||
|
.mx-5 { margin: 0 3rem 0 3rem; }
|
||||||
|
|
||||||
|
.my-1 { margin: 0.25rem 0 0.25rem; }
|
||||||
|
.my-2 { margin: 0.5rem 0 0.5rem; }
|
||||||
|
.my-3 { margin: 1rem 0 1rem; }
|
||||||
|
.my-4 { margin: 1.5rem 0 1.5rem; }
|
||||||
|
.my-5 { margin: 3rem 0 3rem; }
|
||||||
|
|
||||||
|
.mt-1 { margin-top: 0.25rem }
|
||||||
|
.mt-2 { margin-top: 0.5rem }
|
||||||
|
.mt-3 { margin-top: 1rem }
|
||||||
|
.mt-4 { margin-top: 1.5rem }
|
||||||
|
.mt-5 { margin-top: 3rem }
|
||||||
|
|
||||||
|
.mb-1 { margin-bottom: 0.25rem }
|
||||||
|
.mb-2 { margin-bottom: 0.5rem }
|
||||||
|
.mb-3 { margin-bottom: 1rem }
|
||||||
|
.mb-4 { margin-bottom: 1.5rem }
|
||||||
|
.mb-5 { margin-bottom: 3rem }
|
||||||
|
|
||||||
|
.fs-1 { font-size: 3.5rem; }
|
||||||
|
.fs-2 { font-size: 3rem; }
|
||||||
|
.fs-3 { font-size: 2.5rem; }
|
||||||
|
.fs-4 { font-size: 2rem; }
|
||||||
|
.fs-5 { font-size: 1.75rem; }
|
||||||
|
.fs-6 { font-size: 1.5rem; }
|
||||||
|
.fs-7 { font-size: 1.25rem; }
|
||||||
|
.fs-8 { font-size: 1rem; }
|
||||||
|
.fs-9 { font-size: 0.75rem; }
|
||||||
|
.fs-10 { font-size: 0.5rem; }
|
||||||
|
|
||||||
|
.a-r { text-align: right; }
|
||||||
|
.a-c { text-align: center; }
|
||||||
|
|
||||||
|
.row > * {
|
||||||
|
padding-left: 0.5rem;
|
||||||
|
padding-right: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
*, ::after, ::before {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* sm */
|
||||||
|
@media(min-width: 768px) {
|
||||||
|
.col-sm-1 { width: 8.333333333%; }
|
||||||
|
.col-sm-2 { width: 16.66666667%; }
|
||||||
|
.col-sm-3 { width: 25%; }
|
||||||
|
.col-sm-4 { width: 33.33333333%; }
|
||||||
|
.col-sm-5 { width: 41.66666667%; }
|
||||||
|
.col-sm-6 { width: 50%; }
|
||||||
|
.col-sm-7 { width: 58.33333333%; }
|
||||||
|
.col-sm-8 { width: 66.66666667%; }
|
||||||
|
.col-sm-9 { width: 75%; }
|
||||||
|
.col-sm-10 { width: 83.33333333%; }
|
||||||
|
.col-sm-11 { width: 91.66666667%; }
|
||||||
|
.col-sm-12 { width: 100%; }
|
||||||
|
|
||||||
|
.mb-sm-1 { margin-bottom: 0.25rem }
|
||||||
|
.mb-sm-2 { margin-bottom: 0.5rem }
|
||||||
|
.mb-sm-3 { margin-bottom: 1rem }
|
||||||
|
.mb-sm-4 { margin-bottom: 1.5rem }
|
||||||
|
.mb-sm-5 { margin-bottom: 3rem }
|
||||||
|
|
||||||
|
.fs-sm-1 { font-size: 3.5rem; }
|
||||||
|
.fs-sm-2 { font-size: 3rem; }
|
||||||
|
.fs-sm-3 { font-size: 2.5rem; }
|
||||||
|
.fs-sm-4 { font-size: 2rem; }
|
||||||
|
.fs-sm-5 { font-size: 1.75rem; }
|
||||||
|
.fs-sm-6 { font-size: 1.5rem; }
|
||||||
|
.fs-sm-7 { font-size: 1.25rem; }
|
||||||
|
.fs-sm-8 { font-size: 1rem; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* md */
|
||||||
|
@media(min-width: 992px) {
|
||||||
|
.col-md-1 { width: 8.333333333%; }
|
||||||
|
.col-md-2 { width: 16.66666667%; }
|
||||||
|
.col-md-3 { width: 25%; }
|
||||||
|
.col-md-4 { width: 33.33333333%; }
|
||||||
|
.col-md-5 { width: 41.66666667%; }
|
||||||
|
.col-md-6 { width: 50%; }
|
||||||
|
.col-md-7 { width: 58.33333333%; }
|
||||||
|
.col-md-8 { width: 66.66666667%; }
|
||||||
|
.col-md-9 { width: 75%; }
|
||||||
|
.col-md-10 { width: 83.33333333%; }
|
||||||
|
.col-md-11 { width: 91.66666667%; }
|
||||||
|
.col-md-12 { width: 100%; }
|
||||||
|
}
|
||||||
|
|
||||||
#wrapper {
|
#wrapper {
|
||||||
min-height: 100%;
|
min-height: 100%;
|
||||||
}
|
}
|
||||||
|
@ -97,7 +320,6 @@ span.seperator {
|
||||||
#footer {
|
#footer {
|
||||||
height: 121px;
|
height: 121px;
|
||||||
margin-top: -121px;
|
margin-top: -121px;
|
||||||
background-color: #555;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
|
@ -131,7 +353,7 @@ span.seperator {
|
||||||
}
|
}
|
||||||
|
|
||||||
.hide {
|
.hide {
|
||||||
display: none;
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media only screen and (min-width: 992px) {
|
@media only screen and (min-width: 992px) {
|
||||||
|
@ -152,7 +374,7 @@ span.seperator {
|
||||||
padding-left: 24px !important;
|
padding-left: 24px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.topnav .hide {
|
.topnav .mobile {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -172,13 +394,6 @@ span.seperator {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** old CSS below **/
|
|
||||||
|
|
||||||
p {
|
|
||||||
text-align: justify;
|
|
||||||
font-size: 13pt;
|
|
||||||
}
|
|
||||||
|
|
||||||
p.lic, p.lic a {
|
p.lic, p.lic a {
|
||||||
font-size: 8pt;
|
font-size: 8pt;
|
||||||
color: #999;
|
color: #999;
|
||||||
|
@ -187,11 +402,11 @@ p.lic, p.lic a {
|
||||||
.des {
|
.des {
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
font-size: 13pt;
|
font-size: 13pt;
|
||||||
color: #006ec0;
|
color: var(--secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.s_active, .s_collapsible:hover {
|
.s_active, .s_collapsible:hover {
|
||||||
background-color: #044e86;
|
background-color: var(--primary-hover);
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -201,34 +416,34 @@ p.lic, p.lic a {
|
||||||
}
|
}
|
||||||
|
|
||||||
.s_collapsible {
|
.s_collapsible {
|
||||||
background-color: #006ec0;
|
background-color: var(--primary);
|
||||||
color: white;
|
color: white;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
padding: 18px;
|
padding: 12px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border: none;
|
border: none;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
outline: none;
|
outline: none;
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
margin-bottom: 4px;
|
margin-bottom: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.subdes {
|
.subdes {
|
||||||
font-size: 12pt;
|
font-size: 12pt;
|
||||||
color: #006ec0;
|
color: var(--secondary);
|
||||||
margin-left: 7px;
|
margin-left: 7px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.subsubdes {
|
.subsubdes {
|
||||||
font-size:12pt;
|
font-size:12pt;
|
||||||
color:#006ec0;
|
color:var(--secondary);
|
||||||
margin: 0 0 7px 12px;
|
margin: 0 0 7px 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
a:link, a:visited {
|
a:link, a:visited {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
font-size: 13pt;
|
font-size: 13pt;
|
||||||
color: #006ec0;
|
color: var(--secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
a:hover, a:focus {
|
a:hover, a:focus {
|
||||||
|
@ -236,14 +451,14 @@ a:hover, a:focus {
|
||||||
}
|
}
|
||||||
|
|
||||||
a.btn {
|
a.btn {
|
||||||
background-color: #006ec0;
|
background-color: var(--primary);
|
||||||
color: #fff;
|
color: #fff;
|
||||||
padding: 7px 15px 7px 15px;
|
padding: 7px 15px 7px 15px;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
|
||||||
a.btn:hover {
|
a.btn:hover {
|
||||||
background-color: #044e86 !important;
|
background-color: var(--primary-hover) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
input, select {
|
input, select {
|
||||||
|
@ -251,11 +466,13 @@ input, select {
|
||||||
font-size: 13pt;
|
font-size: 13pt;
|
||||||
}
|
}
|
||||||
|
|
||||||
input.text, select {
|
input[type=text], input[type=password], select, input[type=number] {
|
||||||
width: 70%;
|
width: 100%;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
margin-bottom: 10px;
|
|
||||||
border: 1px solid #ccc;
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 4px;
|
||||||
|
background-color: var(--input-bg);
|
||||||
|
color: var(--fg);
|
||||||
}
|
}
|
||||||
|
|
||||||
input.sh {
|
input.sh {
|
||||||
|
@ -268,7 +485,7 @@ input.btnDel {
|
||||||
}
|
}
|
||||||
|
|
||||||
input.btn {
|
input.btn {
|
||||||
background-color: #006ec0;
|
background-color: var(--primary);
|
||||||
color: #fff;
|
color: #fff;
|
||||||
border: 0px;
|
border: 0px;
|
||||||
padding: 7px 20px 7px 20px;
|
padding: 7px 20px 7px 20px;
|
||||||
|
@ -299,10 +516,6 @@ pre {
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
fieldset {
|
|
||||||
margin-bottom: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.left {
|
.left {
|
||||||
float: left;
|
float: left;
|
||||||
}
|
}
|
||||||
|
@ -311,88 +524,11 @@ fieldset {
|
||||||
float: right;
|
float: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.ch-iv {
|
|
||||||
width: 100%;
|
|
||||||
background-color: #32b004;
|
|
||||||
display: inline-block;
|
|
||||||
margin-bottom: 15px;
|
|
||||||
padding-bottom: 20px;
|
|
||||||
overflow: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.ch {
|
|
||||||
width: 220px;
|
|
||||||
min-height: 350px;
|
|
||||||
background-color: #006ec0;
|
|
||||||
display: inline-block;
|
|
||||||
margin: 0 20px 10px 0px;
|
|
||||||
overflow: auto;
|
|
||||||
padding-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.ch-all {
|
|
||||||
width: 100%;
|
|
||||||
background-color: #b06e04;
|
|
||||||
display: inline-block;
|
|
||||||
margin-bottom: 15px;
|
|
||||||
padding-bottom: 20px;
|
|
||||||
overflow: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.ch .value, div.ch .info, div.ch .head, div.ch-iv .value, div.ch-iv .info, div.ch-iv .head, div.ch-all .value, div.ch-all .info, div.ch-all .head {
|
|
||||||
color: #fff;
|
|
||||||
display: block;
|
|
||||||
width: 100%;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.subgrp {
|
.subgrp {
|
||||||
float: left;
|
float: left;
|
||||||
width: 220px;
|
width: 220px;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.ch .unit, div.ch-iv .unit, div.ch-all .unit {
|
|
||||||
font-size: 19px;
|
|
||||||
margin-left: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.ch .value, div.ch-iv .value, div.ch-all .value {
|
|
||||||
margin-top: 20px;
|
|
||||||
font-size: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.ch .info, div.ch-iv .info, div.ch-all .info {
|
|
||||||
margin-top: 3px;
|
|
||||||
font-size: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.ch .head {
|
|
||||||
background-color: #003c80;
|
|
||||||
padding: 10px 0 10px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.ch-all .head {
|
|
||||||
background-color: #8e5903;
|
|
||||||
padding: 10px 0 10px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.ch-iv .head {
|
|
||||||
background-color: #1c6800;
|
|
||||||
padding: 10px 0 10px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.iv {
|
|
||||||
max-width: 960px;
|
|
||||||
margin-bottom: 40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.ts {
|
|
||||||
font-size: 13px;
|
|
||||||
background-color: #ddd;
|
|
||||||
border-top: 7px solid #999;
|
|
||||||
padding: 7px;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.ModPwr, div.ModName, div.YieldCor {
|
div.ModPwr, div.ModName, div.YieldCor {
|
||||||
width:70%;
|
width:70%;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
@ -443,104 +579,55 @@ div.hr {
|
||||||
}
|
}
|
||||||
|
|
||||||
#login {
|
#login {
|
||||||
width: 300px;
|
width: 450px;
|
||||||
height: 200px;
|
height: 200px;
|
||||||
border: 1px solid #ccc;
|
border: 1px solid #ccc;
|
||||||
background-color: #eee;
|
background-color: var(--input-bg);
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
margin-top: -160px;
|
margin-top: -160px;
|
||||||
margin-left: -150px;
|
margin-left: -225px;
|
||||||
}
|
|
||||||
|
|
||||||
#login .pad {
|
|
||||||
padding: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#login .pad input {
|
|
||||||
width: 100%;
|
|
||||||
padding: 7px 0 7px 0;
|
|
||||||
border: 0px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.head {
|
.head {
|
||||||
background-color: #006ec0;
|
background-color: var(--primary);
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.row { display: flex; max-width: 100%; flex-wrap: wrap; }
|
|
||||||
.col { flex: 1 0 0%; }
|
|
||||||
|
|
||||||
.col-1, .col-2, .col-3, .col-4,
|
.css-tooltip{
|
||||||
.col-5, .col-6, .col-7, .col-8,
|
position: relative;
|
||||||
.col-9, .col-10, .col-11, .col-12 { flex: 0 0 auto; }
|
|
||||||
|
|
||||||
|
|
||||||
.col-1 { width: 8.333333333%; }
|
|
||||||
.col-2 { width: 16.66666667%; }
|
|
||||||
.col-3 { width: 25%; }
|
|
||||||
.col-4 { width: 33.33333333%; }
|
|
||||||
.col-5 { width: 41.66666667%; }
|
|
||||||
.col-6 { width: 50%; }
|
|
||||||
.col-7 { width: 58.33333333%; }
|
|
||||||
.col-8 { width: 66.66666667%; }
|
|
||||||
.col-9 { width: 75%; }
|
|
||||||
.col-10 { width: 83.33333333%; }
|
|
||||||
.col-11 { width: 91.66666667%; }
|
|
||||||
.col-12 { width: 100%; }
|
|
||||||
|
|
||||||
.p-1 { padding: 0.25rem; }
|
|
||||||
.p-2 { padding: 0.5rem; }
|
|
||||||
.p-3 { padding: 1rem; }
|
|
||||||
.p-4 { padding: 1.5rem; }
|
|
||||||
.p-5 { padding: 3rem; }
|
|
||||||
|
|
||||||
.mt-1 { margin-top: 0.25rem }
|
|
||||||
.mt-2 { margin-top: 0.5rem }
|
|
||||||
.mt-3 { margin-top: 1rem }
|
|
||||||
.mt-4 { margin-top: 1.5rem }
|
|
||||||
.mt-5 { margin-top: 3rem }
|
|
||||||
|
|
||||||
.mb-1 { margin-bottom: 0.25rem }
|
|
||||||
.mb-2 { margin-bottom: 0.5rem }
|
|
||||||
.mb-3 { margin-bottom: 1rem }
|
|
||||||
.mb-4 { margin-bottom: 1.5rem }
|
|
||||||
.mb-5 { margin-bottom: 3rem }
|
|
||||||
|
|
||||||
.a-r { text-align: right; }
|
|
||||||
.a-c { text-align: center; }
|
|
||||||
|
|
||||||
/* sm */
|
|
||||||
@media(min-width: 768px) {
|
|
||||||
.col-sm-1 { width: 8.333333333%; }
|
|
||||||
.col-sm-2 { width: 16.66666667%; }
|
|
||||||
.col-sm-3 { width: 25%; }
|
|
||||||
.col-sm-4 { width: 33.33333333%; }
|
|
||||||
.col-sm-5 { width: 41.66666667%; }
|
|
||||||
.col-sm-6 { width: 50%; }
|
|
||||||
.col-sm-7 { width: 58.33333333%; }
|
|
||||||
.col-sm-8 { width: 66.66666667%; }
|
|
||||||
.col-sm-9 { width: 75%; }
|
|
||||||
.col-sm-10 { width: 83.33333333%; }
|
|
||||||
.col-sm-11 { width: 91.66666667%; }
|
|
||||||
.col-sm-12 { width: 100%; }
|
|
||||||
}
|
}
|
||||||
|
.css-tooltip:hover:after{
|
||||||
/* md */
|
content:attr(data-tooltip);
|
||||||
@media(min-width: 992px) {
|
background:#000;
|
||||||
.col-md-1 { width: 8.333333333%; }
|
padding:5px;
|
||||||
.col-md-2 { width: 16.66666667%; }
|
border-radius:3px;
|
||||||
.col-md-3 { width: 25%; }
|
display: inline-block;
|
||||||
.col-md-4 { width: 33.33333333%; }
|
position: absolute;
|
||||||
.col-md-5 { width: 41.66666667%; }
|
transform: translate(-50%,-100%);
|
||||||
.col-md-6 { width: 50%; }
|
margin:0 auto;
|
||||||
.col-md-7 { width: 58.33333333%; }
|
color:#FFF;
|
||||||
.col-md-8 { width: 66.66666667%; }
|
min-width:100px;
|
||||||
.col-md-9 { width: 75%; }
|
min-width:150px;
|
||||||
.col-md-10 { width: 83.33333333%; }
|
top:-5px;
|
||||||
.col-md-11 { width: 91.66666667%; }
|
left: 50%;
|
||||||
.col-md-12 { width: 100%; }
|
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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,21 +2,10 @@
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>System</title>
|
<title>System</title>
|
||||||
<link rel="stylesheet" type="text/css" href="style.css"/>
|
{#HTML_HEADER}
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<script type="text/javascript" src="api.js"></script>
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="topnav">
|
{#HTML_NAV}
|
||||||
<a href="/" class="title">AhoyDTU</a>
|
|
||||||
<a href="javascript:void(0);" class="icon" onclick="topnav()">
|
|
||||||
<span></span>
|
|
||||||
<span></span>
|
|
||||||
<span></span>
|
|
||||||
</a>
|
|
||||||
<div id="topnav" class="hide"></div>
|
|
||||||
<div id="wifiicon" class="info"></div>
|
|
||||||
</div>
|
|
||||||
<div id="wrapper">
|
<div id="wrapper">
|
||||||
<div id="content">
|
<div id="content">
|
||||||
<pre id="stat"></pre>
|
<pre id="stat"></pre>
|
||||||
|
@ -26,25 +15,10 @@
|
||||||
<div id="html" class="mt-3 mb-3"></div>
|
<div id="html" class="mt-3 mb-3"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="footer">
|
{#HTML_FOOTER}
|
||||||
<div class="left">
|
|
||||||
<a href="https://ahoydtu.de" target="_blank">AhoyDTU © 2023</a>
|
|
||||||
<ul>
|
|
||||||
<li><a href="https://discord.gg/WzhxEY62mB" target="_blank">Discord</a></li>
|
|
||||||
<li><a href="https://github.com/lumapu/ahoy" target="_blank">Github</a></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div class="right">
|
|
||||||
<ul>
|
|
||||||
<li><span id="version"></span></li>
|
|
||||||
<li><span id="esp_type"></span></li>
|
|
||||||
<li><a href="https://creativecommons.org/licenses/by-nc-sa/3.0/de" target="_blank" >CC BY-NC-SA 3.0</a></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
function parseGeneric(obj) {
|
function parseGeneric(obj) {
|
||||||
parseVersion(obj);
|
parseNav(obj);
|
||||||
parseESP(obj);
|
parseESP(obj);
|
||||||
parseRssi(obj);
|
parseRssi(obj);
|
||||||
}
|
}
|
||||||
|
@ -123,7 +97,6 @@
|
||||||
|
|
||||||
function parse(obj) {
|
function parse(obj) {
|
||||||
if(null != obj) {
|
if(null != obj) {
|
||||||
parseMenu(obj["menu"]);
|
|
||||||
parseGeneric(obj["generic"]);
|
parseGeneric(obj["generic"]);
|
||||||
|
|
||||||
if(null != obj["refresh"]) {
|
if(null != obj["refresh"]) {
|
||||||
|
|
|
@ -2,66 +2,36 @@
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>Update</title>
|
<title>Update</title>
|
||||||
<link rel="stylesheet" type="text/css" href="style.css"/>
|
{#HTML_HEADER}
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<script type="text/javascript" src="api.js"></script>
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="topnav">
|
{#HTML_NAV}
|
||||||
<a href="/" class="title">AhoyDTU</a>
|
|
||||||
<a href="javascript:void(0);" class="icon" onclick="topnav()">
|
|
||||||
<span></span>
|
|
||||||
<span></span>
|
|
||||||
<span></span>
|
|
||||||
</a>
|
|
||||||
<div id="topnav" class="hide"></div>
|
|
||||||
<div id="wifiicon" class="info"></div>
|
|
||||||
</div>
|
|
||||||
<div id="wrapper">
|
<div id="wrapper">
|
||||||
<div id="content">
|
<div id="content">
|
||||||
<form id="form" method="POST" action="/update" enctype="multipart/form-data" accept-charset="utf-8">
|
<fieldset>
|
||||||
<input type="file" name="update">
|
<legend class="des">Select firmware file (*.bin)</legend>
|
||||||
<input type="button" class="btn" value="Update" onclick="hide()">
|
<form id="form" method="POST" action="/update" enctype="multipart/form-data" accept-charset="utf-8">
|
||||||
</form>
|
<input type="file" name="update">
|
||||||
</div>
|
<input type="button" class="btn" value="Update" onclick="hide()">
|
||||||
</div>
|
</form>
|
||||||
<div id="footer">
|
</fieldset>
|
||||||
<div class="left">
|
|
||||||
<a href="https://ahoydtu.de" target="_blank">AhoyDTU © 2023</a>
|
|
||||||
<ul>
|
|
||||||
<li><a href="https://discord.gg/WzhxEY62mB" target="_blank">Discord</a></li>
|
|
||||||
<li><a href="https://github.com/lumapu/ahoy" target="_blank">Github</a></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div class="right">
|
|
||||||
<ul>
|
|
||||||
<li><span id="version"></span></li>
|
|
||||||
<li><span id="esp_type"></span></li>
|
|
||||||
<li><a href="https://creativecommons.org/licenses/by-nc-sa/3.0/de" target="_blank" >CC BY-NC-SA 3.0</a></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{#HTML_FOOTER}
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
function parseGeneric(obj) {
|
function parseGeneric(obj) {
|
||||||
parseVersion(obj);
|
parseNav(obj);
|
||||||
parseESP(obj);
|
parseESP(obj);
|
||||||
parseRssi(obj);
|
parseRssi(obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
function parse(obj) {
|
|
||||||
if(null != obj) {
|
|
||||||
parseMenu(obj["menu"]);
|
|
||||||
parseGeneric(obj["generic"]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function hide() {
|
function hide() {
|
||||||
document.getElementById("form").submit();
|
document.getElementById("form").submit();
|
||||||
var e = document.getElementById("content");
|
var e = document.getElementById("content");
|
||||||
e.replaceChildren(span("update started"));
|
e.replaceChildren(span("update started"));
|
||||||
}
|
}
|
||||||
|
|
||||||
getAjax("/api/index", parse);
|
getAjax("/api/generic", parseGeneric);
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -2,144 +2,227 @@
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>Live</title>
|
<title>Live</title>
|
||||||
<link rel="stylesheet" type="text/css" href="style.css"/>
|
{#HTML_HEADER}
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||||
<script type="text/javascript" src="api.js"></script>
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="topnav">
|
{#HTML_NAV}
|
||||||
<a href="/" class="title">AhoyDTU</a>
|
|
||||||
<a href="javascript:void(0);" class="icon" onclick="topnav()">
|
|
||||||
<span></span>
|
|
||||||
<span></span>
|
|
||||||
<span></span>
|
|
||||||
</a>
|
|
||||||
<div id="topnav" class="hide"></div>
|
|
||||||
<div id="wifiicon" class="info"></div>
|
|
||||||
</div>
|
|
||||||
<div id="wrapper">
|
<div id="wrapper">
|
||||||
<div id="content">
|
<div id="content">
|
||||||
<div id="live"></div>
|
<div id="live"></div>
|
||||||
<p>Every <span id="refresh"></span> seconds the values are updated</p>
|
<p>Every <span id="refresh"></span> seconds the values are updated</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="footer">
|
{#HTML_FOOTER}
|
||||||
<div class="left">
|
|
||||||
<a href="https://ahoydtu.de" target="_blank">AhoyDTU © 2023</a>
|
|
||||||
<ul>
|
|
||||||
<li><a href="https://discord.gg/WzhxEY62mB" target="_blank">Discord</a></li>
|
|
||||||
<li><a href="https://github.com/lumapu/ahoy" target="_blank">Github</a></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div class="right">
|
|
||||||
<ul>
|
|
||||||
<li><span id="version"></span></li>
|
|
||||||
<li><span id="esp_type"></span></li>
|
|
||||||
<li><a href="https://creativecommons.org/licenses/by-nc-sa/3.0/de" target="_blank" >CC BY-NC-SA 3.0</a></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
var exeOnce = true;
|
var exeOnce = true;
|
||||||
|
var units, ivEn;
|
||||||
|
var mIvHtml = [];
|
||||||
|
var mNum = 0;
|
||||||
|
var names = ["Voltage", "Current", "Power", "Yield Day", "Yield Total", "Irradiation"];
|
||||||
|
var total = Array(5).fill(0);
|
||||||
|
|
||||||
function parseGeneric(obj) {
|
function parseGeneric(obj) {
|
||||||
if(true == exeOnce){
|
if(true == exeOnce){
|
||||||
parseVersion(obj);
|
parseNav(obj);
|
||||||
parseESP(obj);
|
parseESP(obj);
|
||||||
}
|
}
|
||||||
parseRssi(obj);
|
parseRssi(obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseIv(obj, root) {
|
function numBig(val, unit, des) {
|
||||||
var ivHtml = [];
|
return ml("div", {class: "col-6 col-sm-4 a-c"}, [
|
||||||
|
ml("div", {class: "row"},
|
||||||
|
ml("div", {class: "col"}, [
|
||||||
|
ml("span", {class: "fs-5 fs-md-4"}, String(val)),
|
||||||
|
ml("span", {class: "fs-6 fs-md-7 mx-1"}, unit)
|
||||||
|
])),
|
||||||
|
ml("div", {class: "row"},
|
||||||
|
ml("div", {class: "col"},
|
||||||
|
ml("span", {class: "fs-9 px-1"}, des)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
var tDiv = div(["ch-all", "iv"]);
|
function numMid(val, unit, des) {
|
||||||
tDiv.appendChild(span("Total", ["head"]));
|
return ml("div", {class: "col-6 col-sm-4 col-md-3 mb-2"}, [
|
||||||
var total = new Array(root.ch0_fld_names.length).fill(0);
|
ml("div", {class: "row"},
|
||||||
if(obj.length > 1)
|
ml("div", {class: "col"}, [
|
||||||
ivHtml.push(tDiv);
|
ml("span", {class: "fs-6"}, String(val)),
|
||||||
|
ml("span", {class: "fs-8 mx-1"}, unit)
|
||||||
|
])),
|
||||||
|
ml("div", {class: "row"},
|
||||||
|
ml("div", {class: "col"},
|
||||||
|
ml("span", {class: "fs-9"}, des)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
for(var iv of obj) {
|
function totals() {
|
||||||
if(iv["enabled"]) {
|
for(var i = 0; i < 5; i++) {
|
||||||
main = div(["iv"]);
|
total[i] = Math.round(total[i] * 100) / 100;
|
||||||
var ch0 = div(["ch-iv"]);
|
|
||||||
var limit = iv["power_limit_read"] + "%";
|
|
||||||
if(limit == "65535%")
|
|
||||||
limit = "n/a";
|
|
||||||
ch0.appendChild(span(iv["name"] + " Limit " + limit, ["head"]));
|
|
||||||
|
|
||||||
for(var j = 0; j < root.ch0_fld_names.length; j++) {
|
|
||||||
var val = Math.round(iv["ch"][0][j] * 100) / 100;
|
|
||||||
var sub = div(["subgrp"]);
|
|
||||||
sub.appendChild(span(val + " " + span(root["ch0_fld_units"][j], ["unit"]).innerHTML, ["value"]));
|
|
||||||
sub.appendChild(span(root["ch0_fld_names"][j], ["info"]));
|
|
||||||
ch0.appendChild(sub);
|
|
||||||
|
|
||||||
switch(j) {
|
|
||||||
case 2: total[j] += val; break; // P_AC
|
|
||||||
case 6: total[j] += val; break; // YieldTotal
|
|
||||||
case 7: total[j] += val; break; // YieldDay
|
|
||||||
case 8: total[j] += val; break; // P_DC
|
|
||||||
case 10: total[j] += val; break; // Q_AC
|
|
||||||
}
|
|
||||||
}
|
|
||||||
main.appendChild(ch0);
|
|
||||||
|
|
||||||
|
|
||||||
for(var i = 1; i < (iv["channels"] + 1); i++) {
|
|
||||||
var ch = div(["ch"]);
|
|
||||||
ch.appendChild(span(("" == iv["ch_names"][i]) ? ("CHANNEL " + i) : iv["ch_names"][i], ["head"]));
|
|
||||||
|
|
||||||
for(var j = 0; j < root.fld_names.length; j++) {
|
|
||||||
var val = Math.round(iv["ch"][i][j] * 100) / 100;
|
|
||||||
ch.appendChild(span(val + " " + span(root["fld_units"][j], ["unit"]).innerHTML, ["value"]));
|
|
||||||
ch.appendChild(span(root["fld_names"][j], ["info"]));
|
|
||||||
}
|
|
||||||
main.appendChild(ch);
|
|
||||||
}
|
|
||||||
|
|
||||||
var ts = div(["ts"]);
|
|
||||||
var ageInfo = "Last received data requested at: ";
|
|
||||||
if(iv["ts_last_success"] > 0) {
|
|
||||||
var date = new Date(iv["ts_last_success"] * 1000);
|
|
||||||
ageInfo += date.toLocaleString('de-DE');
|
|
||||||
}
|
|
||||||
else
|
|
||||||
ageInfo += "nothing received";
|
|
||||||
|
|
||||||
ts.innerHTML = ageInfo;
|
|
||||||
|
|
||||||
main.appendChild(ts);
|
|
||||||
ivHtml.push(main);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// total
|
return ml("div", {class: "row mt-3 mb-5"},
|
||||||
if(obj.length > 1) {
|
ml("div", {class: "col"}, [
|
||||||
for(var j = 0; j < root.ch0_fld_names.length; j++) {
|
ml("div", {class: "p-2 total-h"},
|
||||||
var val = Math.round(total[j] * 100) / 100;
|
ml("div", {class: "row"},
|
||||||
if((j == 2) || (j == 6) || (j == 7) || (j == 8) || (j == 10)) {
|
ml("div", {class: "col mx-2 mx-md-1"}, "TOTAL")
|
||||||
var sub = div(["subgrp"]);
|
),
|
||||||
sub.appendChild(span(val + " " + span(root["ch0_fld_units"][j], ["unit"]).innerHTML, ["value"]));
|
),
|
||||||
sub.appendChild(span(root["ch0_fld_names"][j], ["info"]));
|
ml("div", {class: "p-2 total-bg"}, [
|
||||||
tDiv.appendChild(sub);
|
ml("div", {class: "row"}, [
|
||||||
}
|
numBig(total[0], "W", "AC Power"),
|
||||||
|
numBig(total[1], "Wh", "Yield Day"),
|
||||||
|
numBig(total[2], "kWh", "Yield Total")
|
||||||
|
]),
|
||||||
|
ml("div", {class: "hr"}),
|
||||||
|
ml("div", {class: "row"}, [
|
||||||
|
numMid(total[3], "W", "DC Power"),
|
||||||
|
numMid(total[4], "var", "Reactive Power")
|
||||||
|
])
|
||||||
|
])
|
||||||
|
])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
function ivHead(obj) {
|
||||||
|
total[0] += obj.ch[0][2]; // P_AC
|
||||||
|
total[1] += obj.ch[0][7]; // YieldDay
|
||||||
|
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]) + t.innerHTML)
|
||||||
|
])
|
||||||
|
),
|
||||||
|
ml("div", {class: "p-2 iv-bg"}, [
|
||||||
|
ml("div", {class: "row"},[
|
||||||
|
numBig(obj.ch[0][2], "W", "AC Power"),
|
||||||
|
numBig(obj.ch[0][7], "Wh", "Yield Day"),
|
||||||
|
numBig(obj.ch[0][6], "kWh", "Yield Total")
|
||||||
|
]),
|
||||||
|
ml("div", {class: "hr"}),
|
||||||
|
ml("div", {class: "row mt-2"},[
|
||||||
|
numMid(obj.ch[0][8], "W", "DC Power"),
|
||||||
|
numMid(obj.ch[0][0], "V", "Voltage"),
|
||||||
|
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][4], "", "Power Factor")
|
||||||
|
])
|
||||||
|
])
|
||||||
|
])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function numCh(val, unit, des) {
|
||||||
|
return ml("div", {class: "col-12 col-sm-6 col-md-12 mb-2"}, [
|
||||||
|
ml("div", {class: "row"},
|
||||||
|
ml("div", {class: "col"}, [
|
||||||
|
ml("span", {class: "fs-6 fs-md-7"}, String(val)),
|
||||||
|
ml("span", {class: "fs-8 mx-2"}, unit)
|
||||||
|
])),
|
||||||
|
ml("div", {class: "row"},
|
||||||
|
ml("div", {class: "col"},
|
||||||
|
ml("span", {class: "fs-9"}, des)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ch(name, vals) {
|
||||||
|
return ml("div", {class: "col-6 col-md-3 mt-2"}, [
|
||||||
|
ml("div", {class: "ch-h p-2 a-c"}, name),
|
||||||
|
ml("div", {class: "p-2 ch-bg"}, [
|
||||||
|
ml("div", {class: "row"}, [
|
||||||
|
numCh(vals[2], units[2], "Power"),
|
||||||
|
numCh(vals[5], units[5], "Irradiation"),
|
||||||
|
numCh(vals[3], units[3], "Yield Day"),
|
||||||
|
numCh(vals[4], units[4], "Yield Total"),
|
||||||
|
numCh(vals[0], units[0], "Voltage"),
|
||||||
|
numCh(vals[1], units[1], "Current")
|
||||||
|
])
|
||||||
|
])
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function tsInfo(ts) {
|
||||||
|
var ageInfo = "Last received data requested at: ";
|
||||||
|
if(ts > 0) {
|
||||||
|
var date = new Date(ts * 1000);
|
||||||
|
ageInfo += date.toLocaleString('de-DE');
|
||||||
|
}
|
||||||
|
else
|
||||||
|
ageInfo += "nothing received";
|
||||||
|
|
||||||
|
return ml("div", {class: "mb-5"}, [
|
||||||
|
ml("div", {class: "row p-1 ts-h mx-2"},
|
||||||
|
ml("div", {class: "col"}, "")
|
||||||
|
),
|
||||||
|
ml("div", {class: "row p-2 ts-bg mx-2"},
|
||||||
|
ml("div", {class: "col mx-2"}, ageInfo)
|
||||||
|
)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseIv(obj) {
|
||||||
|
mNum++;
|
||||||
|
|
||||||
|
var chn = [];
|
||||||
|
for(var i = 1; i < obj.ch.length; i++) {
|
||||||
|
var name = obj.ch_name[i];
|
||||||
|
if(name.length == 0)
|
||||||
|
name = "CHANNEL " + i;
|
||||||
|
chn.push(ch(name, obj.ch[i]));
|
||||||
|
}
|
||||||
|
mIvHtml.push(
|
||||||
|
ml("div", {}, [
|
||||||
|
ivHead(obj),
|
||||||
|
ml("div", {class: "row mb-2"}, chn),
|
||||||
|
tsInfo(obj.ts_last_success)
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
var last = true;
|
||||||
|
for(var i = obj.id + 1; i < ivEn.length; i++) {
|
||||||
|
if((i != ivEn.length) && ivEn[i]) {
|
||||||
|
last = false;
|
||||||
|
getAjax("/api/inverter/id/" + i, parseIv);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if(last) {
|
||||||
document.getElementById("live").replaceChildren(...ivHtml);
|
if(mNum > 1)
|
||||||
|
mIvHtml.unshift(totals());
|
||||||
|
document.getElementById("live").replaceChildren(...mIvHtml);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function parse(obj) {
|
function parse(obj) {
|
||||||
if(null != obj) {
|
if(null != obj) {
|
||||||
if(true == exeOnce)
|
|
||||||
parseMenu(obj["menu"]);
|
|
||||||
parseGeneric(obj["generic"]);
|
parseGeneric(obj["generic"]);
|
||||||
parseIv(obj["inverter"], obj);
|
units = Object.assign({}, obj["fld_units"]);
|
||||||
document.getElementById("refresh").innerHTML = obj["refresh_interval"];
|
ivEn = Object.values(Object.assign({}, obj["iv"]));
|
||||||
|
mIvHtml = [];
|
||||||
|
mNum = 0;
|
||||||
|
total.fill(0);
|
||||||
|
for(var i = 0; i < obj.iv.length; i++) {
|
||||||
|
if(obj.iv[i])
|
||||||
|
getAjax("/api/inverter/id/" + i, parseIv);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
document.getElementById("refresh").innerHTML = obj["refresh"];
|
||||||
if(true == exeOnce) {
|
if(true == exeOnce) {
|
||||||
window.setInterval("getAjax('/api/live', parse)", obj["refresh_interval"] * 1000);
|
window.setInterval("getAjax('/api/live', parse)", obj["refresh"] * 1000);
|
||||||
exeOnce = false;
|
exeOnce = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
309
src/web/web.h
309
src/web/web.h
|
@ -8,36 +8,35 @@
|
||||||
|
|
||||||
#include "../utils/dbg.h"
|
#include "../utils/dbg.h"
|
||||||
#ifdef ESP32
|
#ifdef ESP32
|
||||||
#include "AsyncTCP.h"
|
#include "AsyncTCP.h"
|
||||||
#include "Update.h"
|
#include "Update.h"
|
||||||
#else
|
#else
|
||||||
#include "ESPAsyncTCP.h"
|
#include "ESPAsyncTCP.h"
|
||||||
#endif
|
#endif
|
||||||
#include "ESPAsyncWebServer.h"
|
|
||||||
|
|
||||||
#include "../appInterface.h"
|
#include "../appInterface.h"
|
||||||
|
|
||||||
#include "../hm/hmSystem.h"
|
#include "../hm/hmSystem.h"
|
||||||
#include "../utils/helper.h"
|
#include "../utils/helper.h"
|
||||||
|
#include "ESPAsyncWebServer.h"
|
||||||
|
#include "html/h/api_js.h"
|
||||||
|
#include "html/h/colorBright_css.h"
|
||||||
|
#include "html/h/colorDark_css.h"
|
||||||
|
#include "html/h/favicon_ico.h"
|
||||||
#include "html/h/index_html.h"
|
#include "html/h/index_html.h"
|
||||||
#include "html/h/login_html.h"
|
#include "html/h/login_html.h"
|
||||||
#include "html/h/style_css.h"
|
|
||||||
#include "html/h/api_js.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/serial_html.h"
|
#include "html/h/serial_html.h"
|
||||||
|
#include "html/h/setup_html.h"
|
||||||
|
#include "html/h/style_css.h"
|
||||||
#include "html/h/system_html.h"
|
#include "html/h/system_html.h"
|
||||||
|
#include "html/h/update_html.h"
|
||||||
|
#include "html/h/visualization_html.h"
|
||||||
|
|
||||||
#define WEB_SERIAL_BUF_SIZE 2048
|
#define WEB_SERIAL_BUF_SIZE 2048
|
||||||
|
|
||||||
const char* const pinArgNames[] = {"pinCs", "pinCe", "pinIrq", "pinLed0", "pinLed1", "pinCsb", "pinFcsb", "pinGpio3"};
|
const char* const pinArgNames[] = {"pinCs", "pinCe", "pinIrq", "pinLed0", "pinLed1", "pinCsb", "pinFcsb", "pinGpio3"};
|
||||||
|
|
||||||
template<class HMSYSTEM>
|
template <class HMSYSTEM>
|
||||||
class Web {
|
class Web {
|
||||||
public:
|
public:
|
||||||
Web(void) : mWeb(80), mEvts("/events") {
|
Web(void) : mWeb(80), mEvts("/events") {
|
||||||
mProtected = true;
|
mProtected = true;
|
||||||
mLogoutTimeout = 0;
|
mLogoutTimeout = 0;
|
||||||
|
@ -57,6 +56,7 @@ class Web {
|
||||||
mWeb.on("/", HTTP_GET, std::bind(&Web::onIndex, this, std::placeholders::_1));
|
mWeb.on("/", HTTP_GET, std::bind(&Web::onIndex, this, std::placeholders::_1));
|
||||||
mWeb.on("/login", HTTP_ANY, std::bind(&Web::onLogin, this, std::placeholders::_1));
|
mWeb.on("/login", HTTP_ANY, std::bind(&Web::onLogin, this, std::placeholders::_1));
|
||||||
mWeb.on("/logout", HTTP_GET, std::bind(&Web::onLogout, this, std::placeholders::_1));
|
mWeb.on("/logout", HTTP_GET, std::bind(&Web::onLogout, this, std::placeholders::_1));
|
||||||
|
mWeb.on("/colors.css", HTTP_GET, std::bind(&Web::onColor, this, std::placeholders::_1));
|
||||||
mWeb.on("/style.css", HTTP_GET, std::bind(&Web::onCss, this, std::placeholders::_1));
|
mWeb.on("/style.css", HTTP_GET, std::bind(&Web::onCss, this, std::placeholders::_1));
|
||||||
mWeb.on("/api.js", HTTP_GET, std::bind(&Web::onApiJs, this, std::placeholders::_1));
|
mWeb.on("/api.js", HTTP_GET, std::bind(&Web::onApiJs, this, std::placeholders::_1));
|
||||||
mWeb.on("/favicon.ico", HTTP_GET, std::bind(&Web::onFavicon, this, std::placeholders::_1));
|
mWeb.on("/favicon.ico", HTTP_GET, std::bind(&Web::onFavicon, this, std::placeholders::_1));
|
||||||
|
@ -99,18 +99,18 @@ class Web {
|
||||||
}
|
}
|
||||||
|
|
||||||
void tickSecond() {
|
void tickSecond() {
|
||||||
if(0 != mLogoutTimeout) {
|
if (0 != mLogoutTimeout) {
|
||||||
mLogoutTimeout -= 1;
|
mLogoutTimeout -= 1;
|
||||||
if(0 == mLogoutTimeout) {
|
if (0 == mLogoutTimeout) {
|
||||||
if(strlen(mConfig->sys.adminPwd) > 0)
|
if (strlen(mConfig->sys.adminPwd) > 0)
|
||||||
mProtected = true;
|
mProtected = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
DPRINTLN(DBG_DEBUG, "auto logout in " + String(mLogoutTimeout));
|
DPRINTLN(DBG_DEBUG, "auto logout in " + String(mLogoutTimeout));
|
||||||
}
|
}
|
||||||
|
|
||||||
if(mSerialClientConnnected) {
|
if (mSerialClientConnnected) {
|
||||||
if(mSerialBufFill > 0) {
|
if (mSerialBufFill > 0) {
|
||||||
mEvts.send(mSerialBuf, "serial", millis());
|
mEvts.send(mSerialBuf, "serial", millis());
|
||||||
memset(mSerialBuf, 0, WEB_SERIAL_BUF_SIZE);
|
memset(mSerialBuf, 0, WEB_SERIAL_BUF_SIZE);
|
||||||
mSerialBufFill = 0;
|
mSerialBufFill = 0;
|
||||||
|
@ -133,23 +133,23 @@ class Web {
|
||||||
void showUpdate2(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) {
|
void showUpdate2(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) {
|
||||||
mApp->setOnUpdate();
|
mApp->setOnUpdate();
|
||||||
|
|
||||||
if(!index) {
|
if (!index) {
|
||||||
Serial.printf("Update Start: %s\n", filename.c_str());
|
Serial.printf("Update Start: %s\n", filename.c_str());
|
||||||
#ifndef ESP32
|
#ifndef ESP32
|
||||||
Update.runAsync(true);
|
Update.runAsync(true);
|
||||||
#endif
|
#endif
|
||||||
if(!Update.begin((ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000)) {
|
if (!Update.begin((ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000)) {
|
||||||
Update.printError(Serial);
|
Update.printError(Serial);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(!Update.hasError()) {
|
if (!Update.hasError()) {
|
||||||
if(Update.write(data, len) != len){
|
if (Update.write(data, len) != len) {
|
||||||
Update.printError(Serial);
|
Update.printError(Serial);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(final) {
|
if (final) {
|
||||||
if(Update.end(true)) {
|
if (Update.end(true)) {
|
||||||
Serial.printf("Update Success: %uB\n", index+len);
|
Serial.printf("Update Success: %uB\n", index + len);
|
||||||
} else {
|
} else {
|
||||||
Update.printError(Serial);
|
Update.printError(Serial);
|
||||||
}
|
}
|
||||||
|
@ -157,27 +157,26 @@ class Web {
|
||||||
}
|
}
|
||||||
|
|
||||||
void onUpload2(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) {
|
void onUpload2(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) {
|
||||||
if(!index) {
|
if (!index) {
|
||||||
mUploadFail = false;
|
mUploadFail = false;
|
||||||
mUploadFp = LittleFS.open("/tmp.json", "w");
|
mUploadFp = LittleFS.open("/tmp.json", "w");
|
||||||
if(!mUploadFp) {
|
if (!mUploadFp) {
|
||||||
DPRINTLN(DBG_ERROR, F("can't open file!"));
|
DPRINTLN(DBG_ERROR, F("can't open file!"));
|
||||||
mUploadFail = true;
|
mUploadFail = true;
|
||||||
mUploadFp.close();
|
mUploadFp.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
mUploadFp.write(data, len);
|
mUploadFp.write(data, len);
|
||||||
if(final) {
|
if (final) {
|
||||||
mUploadFp.close();
|
mUploadFp.close();
|
||||||
File fp = LittleFS.open("/tmp.json", "r");
|
File fp = LittleFS.open("/tmp.json", "r");
|
||||||
if(!fp)
|
if (!fp)
|
||||||
mUploadFail = true;
|
mUploadFail = true;
|
||||||
else {
|
else {
|
||||||
if(!mApp->readSettings("tmp.json")) {
|
if (!mApp->readSettings("tmp.json")) {
|
||||||
mUploadFail = true;
|
mUploadFail = true;
|
||||||
DPRINTLN(DBG_ERROR, F("upload JSON error!"));
|
DPRINTLN(DBG_ERROR, F("upload JSON error!"));
|
||||||
}
|
} else
|
||||||
else
|
|
||||||
mApp->saveSettings();
|
mApp->saveSettings();
|
||||||
}
|
}
|
||||||
DPRINTLN(DBG_INFO, F("upload finished!"));
|
DPRINTLN(DBG_INFO, F("upload finished!"));
|
||||||
|
@ -185,18 +184,17 @@ class Web {
|
||||||
}
|
}
|
||||||
|
|
||||||
void serialCb(String msg) {
|
void serialCb(String msg) {
|
||||||
if(!mSerialClientConnnected)
|
if (!mSerialClientConnnected)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
msg.replace("\r\n", "<rn>");
|
msg.replace("\r\n", "<rn>");
|
||||||
if(mSerialAddTime) {
|
if (mSerialAddTime) {
|
||||||
if((9 + mSerialBufFill) < WEB_SERIAL_BUF_SIZE) {
|
if ((9 + mSerialBufFill) < WEB_SERIAL_BUF_SIZE) {
|
||||||
if(mApp->getTimestamp() > 0) {
|
if (mApp->getTimestamp() > 0) {
|
||||||
strncpy(&mSerialBuf[mSerialBufFill], mApp->getTimeStr(mApp->getTimezoneOffset()).c_str(), 9);
|
strncpy(&mSerialBuf[mSerialBufFill], mApp->getTimeStr(mApp->getTimezoneOffset()).c_str(), 9);
|
||||||
mSerialBufFill += 9;
|
mSerialBufFill += 9;
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
mSerialBufFill = 0;
|
mSerialBufFill = 0;
|
||||||
mEvts.send("webSerial, buffer overflow!<rn>", "serial", millis());
|
mEvts.send("webSerial, buffer overflow!<rn>", "serial", millis());
|
||||||
return;
|
return;
|
||||||
|
@ -204,32 +202,44 @@ class Web {
|
||||||
mSerialAddTime = false;
|
mSerialAddTime = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(msg.endsWith("<rn>"))
|
if (msg.endsWith("<rn>"))
|
||||||
mSerialAddTime = true;
|
mSerialAddTime = true;
|
||||||
|
|
||||||
uint16_t length = msg.length();
|
uint16_t length = msg.length();
|
||||||
if((length + mSerialBufFill) < WEB_SERIAL_BUF_SIZE) {
|
if ((length + mSerialBufFill) < WEB_SERIAL_BUF_SIZE) {
|
||||||
strncpy(&mSerialBuf[mSerialBufFill], msg.c_str(), length);
|
strncpy(&mSerialBuf[mSerialBufFill], msg.c_str(), length);
|
||||||
mSerialBufFill += length;
|
mSerialBufFill += length;
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
mSerialBufFill = 0;
|
mSerialBufFill = 0;
|
||||||
mEvts.send("webSerial, buffer overflow!<rn>", "serial", millis());
|
mEvts.send("webSerial, buffer overflow!<rn>", "serial", millis());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void checkRedirect(AsyncWebServerRequest *request) {
|
||||||
|
if ((mConfig->sys.protectionMask & PROT_MASK_INDEX) != PROT_MASK_INDEX)
|
||||||
|
request->redirect(F("/index"));
|
||||||
|
else if ((mConfig->sys.protectionMask & PROT_MASK_LIVE) != PROT_MASK_LIVE)
|
||||||
|
request->redirect(F("/live"));
|
||||||
|
else if ((mConfig->sys.protectionMask & PROT_MASK_SERIAL) != PROT_MASK_SERIAL)
|
||||||
|
request->redirect(F("/serial"));
|
||||||
|
else if ((mConfig->sys.protectionMask & PROT_MASK_SYSTEM) != PROT_MASK_SYSTEM)
|
||||||
|
request->redirect(F("/system"));
|
||||||
|
else
|
||||||
|
request->redirect(F("/login"));
|
||||||
|
}
|
||||||
|
|
||||||
void onUpdate(AsyncWebServerRequest *request) {
|
void onUpdate(AsyncWebServerRequest *request) {
|
||||||
DPRINTLN(DBG_VERBOSE, F("onUpdate"));
|
DPRINTLN(DBG_VERBOSE, F("onUpdate"));
|
||||||
|
|
||||||
if(CHECK_MASK(mConfig->sys.protectionMask, PROT_MASK_UPDATE)) {
|
if (CHECK_MASK(mConfig->sys.protectionMask, PROT_MASK_UPDATE)) {
|
||||||
if(mProtected) {
|
if (mProtected) {
|
||||||
request->redirect("/login");
|
checkRedirect(request);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html"), update_html, update_html_len);
|
AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html; charset=UTF-8"), update_html, update_html_len);
|
||||||
response->addHeader(F("Content-Encoding"), "gzip");
|
response->addHeader(F("Content-Encoding"), "gzip");
|
||||||
request->send(response);
|
request->send(response);
|
||||||
}
|
}
|
||||||
|
@ -238,34 +248,32 @@ class Web {
|
||||||
bool reboot = !Update.hasError();
|
bool reboot = !Update.hasError();
|
||||||
|
|
||||||
String html = F("<!doctype html><html><head><title>Update</title><meta http-equiv=\"refresh\" content=\"20; URL=/\"></head><body>Update: ");
|
String html = F("<!doctype html><html><head><title>Update</title><meta http-equiv=\"refresh\" content=\"20; URL=/\"></head><body>Update: ");
|
||||||
if(reboot)
|
if (reboot)
|
||||||
html += "success";
|
html += "success";
|
||||||
else
|
else
|
||||||
html += "failed";
|
html += "failed";
|
||||||
html += F("<br/><br/>rebooting ... auto reload after 20s</body></html>");
|
html += F("<br/><br/>rebooting ... auto reload after 20s</body></html>");
|
||||||
|
|
||||||
AsyncWebServerResponse *response = request->beginResponse(200, F("text/html"), html);
|
AsyncWebServerResponse *response = request->beginResponse(200, F("text/html; charset=UTF-8"), html);
|
||||||
response->addHeader("Connection", "close");
|
response->addHeader("Connection", "close");
|
||||||
request->send(response);
|
request->send(response);
|
||||||
//if(reboot)
|
mApp->setRebootFlag();
|
||||||
mApp->setRebootFlag();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void onUpload(AsyncWebServerRequest *request) {
|
void onUpload(AsyncWebServerRequest *request) {
|
||||||
bool reboot = !mUploadFail;
|
bool reboot = !mUploadFail;
|
||||||
|
|
||||||
String html = F("<!doctype html><html><head><title>Upload</title><meta http-equiv=\"refresh\" content=\"20; URL=/\"></head><body>Upload: ");
|
String html = F("<!doctype html><html><head><title>Upload</title><meta http-equiv=\"refresh\" content=\"20; URL=/\"></head><body>Upload: ");
|
||||||
if(reboot)
|
if (reboot)
|
||||||
html += "success";
|
html += "success";
|
||||||
else
|
else
|
||||||
html += "failed";
|
html += "failed";
|
||||||
html += F("<br/><br/>rebooting ... auto reload after 20s</body></html>");
|
html += F("<br/><br/>rebooting ... auto reload after 20s</body></html>");
|
||||||
|
|
||||||
AsyncWebServerResponse *response = request->beginResponse(200, F("text/html"), html);
|
AsyncWebServerResponse *response = request->beginResponse(200, F("text/html; charset=UTF-8"), html);
|
||||||
response->addHeader("Connection", "close");
|
response->addHeader("Connection", "close");
|
||||||
request->send(response);
|
request->send(response);
|
||||||
//if(reboot)
|
mApp->setRebootFlag();
|
||||||
mApp->setRebootFlag();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void onConnect(AsyncEventSourceClient *client) {
|
void onConnect(AsyncEventSourceClient *client) {
|
||||||
|
@ -273,7 +281,7 @@ class Web {
|
||||||
|
|
||||||
mSerialClientConnnected = true;
|
mSerialClientConnnected = true;
|
||||||
|
|
||||||
if(client->lastId())
|
if (client->lastId())
|
||||||
DPRINTLN(DBG_VERBOSE, "Client reconnected! Last message ID that it got is: " + String(client->lastId()));
|
DPRINTLN(DBG_VERBOSE, "Client reconnected! Last message ID that it got is: " + String(client->lastId()));
|
||||||
|
|
||||||
client->send("hello!", NULL, millis(), 1000);
|
client->send("hello!", NULL, millis(), 1000);
|
||||||
|
@ -282,14 +290,14 @@ class Web {
|
||||||
void onIndex(AsyncWebServerRequest *request) {
|
void onIndex(AsyncWebServerRequest *request) {
|
||||||
DPRINTLN(DBG_VERBOSE, F("onIndex"));
|
DPRINTLN(DBG_VERBOSE, F("onIndex"));
|
||||||
|
|
||||||
if(CHECK_MASK(mConfig->sys.protectionMask, PROT_MASK_INDEX)) {
|
if (CHECK_MASK(mConfig->sys.protectionMask, PROT_MASK_INDEX)) {
|
||||||
if(mProtected) {
|
if (mProtected) {
|
||||||
request->redirect("/login");
|
checkRedirect(request);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html"), index_html, index_html_len);
|
AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html; charset=UTF-8"), index_html, index_html_len);
|
||||||
response->addHeader(F("Content-Encoding"), "gzip");
|
response->addHeader(F("Content-Encoding"), "gzip");
|
||||||
request->send(response);
|
request->send(response);
|
||||||
}
|
}
|
||||||
|
@ -297,14 +305,14 @@ class Web {
|
||||||
void onLogin(AsyncWebServerRequest *request) {
|
void onLogin(AsyncWebServerRequest *request) {
|
||||||
DPRINTLN(DBG_VERBOSE, F("onLogin"));
|
DPRINTLN(DBG_VERBOSE, F("onLogin"));
|
||||||
|
|
||||||
if(request->args() > 0) {
|
if (request->args() > 0) {
|
||||||
if(String(request->arg("pwd")) == String(mConfig->sys.adminPwd)) {
|
if (String(request->arg("pwd")) == String(mConfig->sys.adminPwd)) {
|
||||||
mProtected = false;
|
mProtected = false;
|
||||||
request->redirect("/");
|
request->redirect("/");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html"), login_html, login_html_len);
|
AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html; charset=UTF-8"), login_html, login_html_len);
|
||||||
response->addHeader(F("Content-Encoding"), "gzip");
|
response->addHeader(F("Content-Encoding"), "gzip");
|
||||||
request->send(response);
|
request->send(response);
|
||||||
}
|
}
|
||||||
|
@ -312,14 +320,25 @@ class Web {
|
||||||
void onLogout(AsyncWebServerRequest *request) {
|
void onLogout(AsyncWebServerRequest *request) {
|
||||||
DPRINTLN(DBG_VERBOSE, F("onLogout"));
|
DPRINTLN(DBG_VERBOSE, F("onLogout"));
|
||||||
|
|
||||||
if(mProtected) {
|
if (mProtected) {
|
||||||
request->redirect("/login");
|
checkRedirect(request);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
mProtected = true;
|
mProtected = true;
|
||||||
|
|
||||||
AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html"), system_html, system_html_len);
|
AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html; charset=UTF-8"), system_html, system_html_len);
|
||||||
|
response->addHeader(F("Content-Encoding"), "gzip");
|
||||||
|
request->send(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
void onColor(AsyncWebServerRequest *request) {
|
||||||
|
DPRINTLN(DBG_VERBOSE, F("onColor"));
|
||||||
|
AsyncWebServerResponse *response;
|
||||||
|
if (mConfig->sys.darkMode)
|
||||||
|
response = request->beginResponse_P(200, F("text/css"), colorDark_css, colorDark_css_len);
|
||||||
|
else
|
||||||
|
response = request->beginResponse_P(200, F("text/css"), colorBright_css, colorBright_css_len);
|
||||||
response->addHeader(F("Content-Encoding"), "gzip");
|
response->addHeader(F("Content-Encoding"), "gzip");
|
||||||
request->send(response);
|
request->send(response);
|
||||||
}
|
}
|
||||||
|
@ -348,22 +367,22 @@ class Web {
|
||||||
}
|
}
|
||||||
|
|
||||||
void showNotFound(AsyncWebServerRequest *request) {
|
void showNotFound(AsyncWebServerRequest *request) {
|
||||||
if(mProtected)
|
if (mProtected)
|
||||||
request->redirect("/login");
|
checkRedirect(request);
|
||||||
else
|
else
|
||||||
request->redirect("/setup");
|
request->redirect("/setup");
|
||||||
}
|
}
|
||||||
|
|
||||||
void onReboot(AsyncWebServerRequest *request) {
|
void onReboot(AsyncWebServerRequest *request) {
|
||||||
mApp->setRebootFlag();
|
mApp->setRebootFlag();
|
||||||
AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html"), system_html, system_html_len);
|
AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html; charset=UTF-8"), system_html, system_html_len);
|
||||||
response->addHeader(F("Content-Encoding"), "gzip");
|
response->addHeader(F("Content-Encoding"), "gzip");
|
||||||
request->send(response);
|
request->send(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
void showErase(AsyncWebServerRequest *request) {
|
void showErase(AsyncWebServerRequest *request) {
|
||||||
if(mProtected) {
|
if (mProtected) {
|
||||||
request->redirect("/login");
|
checkRedirect(request);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -373,34 +392,32 @@ class Web {
|
||||||
}
|
}
|
||||||
|
|
||||||
void showFactoryRst(AsyncWebServerRequest *request) {
|
void showFactoryRst(AsyncWebServerRequest *request) {
|
||||||
if(mProtected) {
|
if (mProtected) {
|
||||||
request->redirect("/login");
|
checkRedirect(request);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
DPRINTLN(DBG_VERBOSE, F("showFactoryRst"));
|
DPRINTLN(DBG_VERBOSE, F("showFactoryRst"));
|
||||||
String content = "";
|
String content = "";
|
||||||
int refresh = 3;
|
int refresh = 3;
|
||||||
if(request->args() > 0) {
|
if (request->args() > 0) {
|
||||||
if(request->arg("reset").toInt() == 1) {
|
if (request->arg("reset").toInt() == 1) {
|
||||||
refresh = 10;
|
refresh = 10;
|
||||||
if(mApp->eraseSettings(true))
|
if (mApp->eraseSettings(true))
|
||||||
content = F("factory reset: success\n\nrebooting ... ");
|
content = F("factory reset: success\n\nrebooting ... ");
|
||||||
else
|
else
|
||||||
content = F("factory reset: failed\n\nrebooting ... ");
|
content = F("factory reset: failed\n\nrebooting ... ");
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
content = F("factory reset: aborted");
|
content = F("factory reset: aborted");
|
||||||
refresh = 3;
|
refresh = 3;
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
content = F("<h1>Factory Reset</h1>"
|
content = F("<h1>Factory Reset</h1>"
|
||||||
"<p><a href=\"/factory?reset=1\">RESET</a><br/><br/><a href=\"/factory?reset=0\">CANCEL</a><br/></p>");
|
"<p><a href=\"/factory?reset=1\">RESET</a><br/><br/><a href=\"/factory?reset=0\">CANCEL</a><br/></p>");
|
||||||
refresh = 120;
|
refresh = 120;
|
||||||
}
|
}
|
||||||
request->send(200, F("text/html"), F("<!doctype html><html><head><title>Factory Reset</title><meta http-equiv=\"refresh\" content=\"") + String(refresh) + F("; URL=/\"></head><body>") + content + F("</body></html>"));
|
request->send(200, F("text/html; charset=UTF-8"), F("<!doctype html><html><head><title>Factory Reset</title><meta http-equiv=\"refresh\" content=\"") + String(refresh) + F("; URL=/\"></head><body>") + content + F("</body></html>"));
|
||||||
if(refresh == 10) {
|
if (refresh == 10) {
|
||||||
delay(1000);
|
delay(1000);
|
||||||
ESP.restart();
|
ESP.restart();
|
||||||
}
|
}
|
||||||
|
@ -409,14 +426,14 @@ class Web {
|
||||||
void onSetup(AsyncWebServerRequest *request) {
|
void onSetup(AsyncWebServerRequest *request) {
|
||||||
DPRINTLN(DBG_VERBOSE, F("onSetup"));
|
DPRINTLN(DBG_VERBOSE, F("onSetup"));
|
||||||
|
|
||||||
if(CHECK_MASK(mConfig->sys.protectionMask, PROT_MASK_SETUP)) {
|
if (CHECK_MASK(mConfig->sys.protectionMask, PROT_MASK_SETUP)) {
|
||||||
if(mProtected) {
|
if (mProtected) {
|
||||||
request->redirect("/login");
|
checkRedirect(request);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html"), setup_html, setup_html_len);
|
AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html; charset=UTF-8"), setup_html, setup_html_len);
|
||||||
response->addHeader(F("Content-Encoding"), "gzip");
|
response->addHeader(F("Content-Encoding"), "gzip");
|
||||||
request->send(response);
|
request->send(response);
|
||||||
}
|
}
|
||||||
|
@ -424,32 +441,33 @@ class Web {
|
||||||
void showSave(AsyncWebServerRequest *request) {
|
void showSave(AsyncWebServerRequest *request) {
|
||||||
DPRINTLN(DBG_VERBOSE, F("showSave"));
|
DPRINTLN(DBG_VERBOSE, F("showSave"));
|
||||||
|
|
||||||
if(mProtected) {
|
if (mProtected) {
|
||||||
request->redirect("/login");
|
checkRedirect(request);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(request->args() == 0)
|
if (request->args() == 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
char buf[20] = {0};
|
char buf[20] = {0};
|
||||||
|
|
||||||
// general
|
// general
|
||||||
if(request->arg("ssid") != "")
|
if (request->arg("ssid") != "")
|
||||||
request->arg("ssid").toCharArray(mConfig->sys.stationSsid, SSID_LEN);
|
request->arg("ssid").toCharArray(mConfig->sys.stationSsid, SSID_LEN);
|
||||||
if(request->arg("pwd") != "{PWD}")
|
if (request->arg("pwd") != "{PWD}")
|
||||||
request->arg("pwd").toCharArray(mConfig->sys.stationPwd, PWD_LEN);
|
request->arg("pwd").toCharArray(mConfig->sys.stationPwd, PWD_LEN);
|
||||||
if(request->arg("device") != "")
|
if (request->arg("device") != "")
|
||||||
request->arg("device").toCharArray(mConfig->sys.deviceName, DEVNAME_LEN);
|
request->arg("device").toCharArray(mConfig->sys.deviceName, DEVNAME_LEN);
|
||||||
|
mConfig->sys.darkMode = (request->arg("darkMode") == "on");
|
||||||
|
|
||||||
// protection
|
// protection
|
||||||
if(request->arg("adminpwd") != "{PWD}") {
|
if (request->arg("adminpwd") != "{PWD}") {
|
||||||
request->arg("adminpwd").toCharArray(mConfig->sys.adminPwd, PWD_LEN);
|
request->arg("adminpwd").toCharArray(mConfig->sys.adminPwd, PWD_LEN);
|
||||||
mProtected = (strlen(mConfig->sys.adminPwd) > 0);
|
mProtected = (strlen(mConfig->sys.adminPwd) > 0);
|
||||||
}
|
}
|
||||||
mConfig->sys.protectionMask = 0x0000;
|
mConfig->sys.protectionMask = 0x0000;
|
||||||
for(uint8_t i = 0; i < 6; i++) {
|
for (uint8_t i = 0; i < 6; i++) {
|
||||||
if(request->arg("protMask" + String(i)) == "on")
|
if (request->arg("protMask" + String(i)) == "on")
|
||||||
mConfig->sys.protectionMask |= (1 << i);
|
mConfig->sys.protectionMask |= (1 << i);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -465,16 +483,15 @@ class Web {
|
||||||
request->arg("ipGateway").toCharArray(buf, 20);
|
request->arg("ipGateway").toCharArray(buf, 20);
|
||||||
ah::ip2Arr(mConfig->sys.ip.gateway, buf);
|
ah::ip2Arr(mConfig->sys.ip.gateway, buf);
|
||||||
|
|
||||||
|
|
||||||
// inverter
|
// inverter
|
||||||
Inverter<> *iv;
|
Inverter<> *iv;
|
||||||
for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i ++) {
|
for (uint8_t i = 0; i < MAX_NUM_INVERTERS; i++) {
|
||||||
iv = mSys->getInverterByPos(i, false);
|
iv = mSys->getInverterByPos(i, false);
|
||||||
// enable communication
|
// enable communication
|
||||||
iv->config->enabled = (request->arg("inv" + String(i) + "Enable") == "on");
|
iv->config->enabled = (request->arg("inv" + String(i) + "Enable") == "on");
|
||||||
// address
|
// address
|
||||||
request->arg("inv" + String(i) + "Addr").toCharArray(buf, 20);
|
request->arg("inv" + String(i) + "Addr").toCharArray(buf, 20);
|
||||||
if(strlen(buf) == 0)
|
if (strlen(buf) == 0)
|
||||||
memset(buf, 0, 20);
|
memset(buf, 0, 20);
|
||||||
iv->config->serial.u64 = ah::Serial2u64(buf);
|
iv->config->serial.u64 = ah::Serial2u64(buf);
|
||||||
switch(iv->config->serial.b[4]) {
|
switch(iv->config->serial.b[4]) {
|
||||||
|
@ -488,7 +505,7 @@ class Web {
|
||||||
request->arg("inv" + String(i) + "Name").toCharArray(iv->config->name, MAX_NAME_LENGTH);
|
request->arg("inv" + String(i) + "Name").toCharArray(iv->config->name, MAX_NAME_LENGTH);
|
||||||
|
|
||||||
// max channel power / name
|
// max channel power / name
|
||||||
for(uint8_t j = 0; j < 4; j++) {
|
for (uint8_t j = 0; j < 4; j++) {
|
||||||
iv->config->yieldCor[j] = request->arg("inv" + String(i) + "YieldCor" + String(j)).toInt();
|
iv->config->yieldCor[j] = request->arg("inv" + String(i) + "YieldCor" + String(j)).toInt();
|
||||||
iv->config->chMaxPwr[j] = request->arg("inv" + String(i) + "ModPwr" + String(j)).toInt() & 0xffff;
|
iv->config->chMaxPwr[j] = request->arg("inv" + String(i) + "ModPwr" + String(j)).toInt() & 0xffff;
|
||||||
request->arg("inv" + String(i) + "ModName" + String(j)).toCharArray(iv->config->chName[j], MAX_NAME_LENGTH);
|
request->arg("inv" + String(i) + "ModName" + String(j)).toCharArray(iv->config->chName[j], MAX_NAME_LENGTH);
|
||||||
|
@ -496,13 +513,13 @@ class Web {
|
||||||
iv->initialized = true;
|
iv->initialized = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(request->arg("invInterval") != "")
|
if (request->arg("invInterval") != "")
|
||||||
mConfig->nrf.sendInterval = request->arg("invInterval").toInt();
|
mConfig->nrf.sendInterval = request->arg("invInterval").toInt();
|
||||||
if(request->arg("invRetry") != "")
|
if (request->arg("invRetry") != "")
|
||||||
mConfig->nrf.maxRetransPerPyld = request->arg("invRetry").toInt();
|
mConfig->nrf.maxRetransPerPyld = request->arg("invRetry").toInt();
|
||||||
mConfig->inst.rstYieldMidNight = (request->arg("invRstMid") == "on");
|
mConfig->inst.rstYieldMidNight = (request->arg("invRstMid") == "on");
|
||||||
mConfig->inst.rstValsCommStop = (request->arg("invRstComStop") == "on");
|
mConfig->inst.rstValsCommStop = (request->arg("invRstComStop") == "on");
|
||||||
mConfig->inst.rstValsNotAvail = (request->arg("invRstNotAvail") == "on");
|
mConfig->inst.rstValsNotAvail = (request->arg("invRstNotAvail") == "on");
|
||||||
|
|
||||||
// pinout
|
// pinout
|
||||||
uint8_t pin;
|
uint8_t pin;
|
||||||
|
@ -528,13 +545,13 @@ class Web {
|
||||||
mConfig->cmt.enabled = (request->arg("cmtEnable") == "on");
|
mConfig->cmt.enabled = (request->arg("cmtEnable") == "on");
|
||||||
|
|
||||||
// ntp
|
// ntp
|
||||||
if(request->arg("ntpAddr") != "") {
|
if (request->arg("ntpAddr") != "") {
|
||||||
request->arg("ntpAddr").toCharArray(mConfig->ntp.addr, NTP_ADDR_LEN);
|
request->arg("ntpAddr").toCharArray(mConfig->ntp.addr, NTP_ADDR_LEN);
|
||||||
mConfig->ntp.port = request->arg("ntpPort").toInt() & 0xffff;
|
mConfig->ntp.port = request->arg("ntpPort").toInt() & 0xffff;
|
||||||
}
|
}
|
||||||
|
|
||||||
// sun
|
// sun
|
||||||
if(request->arg("sunLat") == "" || (request->arg("sunLon") == "")) {
|
if (request->arg("sunLat") == "" || (request->arg("sunLon") == "")) {
|
||||||
mConfig->sun.lat = 0.0;
|
mConfig->sun.lat = 0.0;
|
||||||
mConfig->sun.lon = 0.0;
|
mConfig->sun.lon = 0.0;
|
||||||
mConfig->sun.disNightCom = false;
|
mConfig->sun.disNightCom = false;
|
||||||
|
@ -547,47 +564,48 @@ class Web {
|
||||||
}
|
}
|
||||||
|
|
||||||
// mqtt
|
// mqtt
|
||||||
if(request->arg("mqttAddr") != "") {
|
if (request->arg("mqttAddr") != "") {
|
||||||
String addr = request->arg("mqttAddr");
|
String addr = request->arg("mqttAddr");
|
||||||
addr.trim();
|
addr.trim();
|
||||||
addr.toCharArray(mConfig->mqtt.broker, MQTT_ADDR_LEN);
|
addr.toCharArray(mConfig->mqtt.broker, MQTT_ADDR_LEN);
|
||||||
}
|
} else
|
||||||
else
|
|
||||||
mConfig->mqtt.broker[0] = '\0';
|
mConfig->mqtt.broker[0] = '\0';
|
||||||
request->arg("mqttUser").toCharArray(mConfig->mqtt.user, MQTT_USER_LEN);
|
request->arg("mqttUser").toCharArray(mConfig->mqtt.user, MQTT_USER_LEN);
|
||||||
if(request->arg("mqttPwd") != "{PWD}")
|
if (request->arg("mqttPwd") != "{PWD}")
|
||||||
request->arg("mqttPwd").toCharArray(mConfig->mqtt.pwd, MQTT_PWD_LEN);
|
request->arg("mqttPwd").toCharArray(mConfig->mqtt.pwd, MQTT_PWD_LEN);
|
||||||
request->arg("mqttTopic").toCharArray(mConfig->mqtt.topic, MQTT_TOPIC_LEN);
|
request->arg("mqttTopic").toCharArray(mConfig->mqtt.topic, MQTT_TOPIC_LEN);
|
||||||
mConfig->mqtt.port = request->arg("mqttPort").toInt();
|
mConfig->mqtt.port = request->arg("mqttPort").toInt();
|
||||||
mConfig->mqtt.interval = request->arg("mqttInterval").toInt();
|
mConfig->mqtt.interval = request->arg("mqttInterval").toInt();
|
||||||
|
|
||||||
// serial console
|
// serial console
|
||||||
if(request->arg("serIntvl") != "") {
|
if (request->arg("serIntvl") != "") {
|
||||||
mConfig->serial.interval = request->arg("serIntvl").toInt() & 0xffff;
|
mConfig->serial.interval = request->arg("serIntvl").toInt() & 0xffff;
|
||||||
|
|
||||||
mConfig->serial.debug = (request->arg("serDbg") == "on");
|
mConfig->serial.debug = (request->arg("serDbg") == "on");
|
||||||
mConfig->serial.showIv = (request->arg("serEn") == "on");
|
mConfig->serial.showIv = (request->arg("serEn") == "on");
|
||||||
// Needed to log TX buffers to serial console
|
// Needed to log TX buffers to serial console
|
||||||
// mSys->Radio.mSerialDebug = mConfig->serial.debug;
|
// mSys->Radio.mSerialDebug = mConfig->serial.debug;
|
||||||
}
|
}
|
||||||
|
|
||||||
// display
|
// display
|
||||||
mConfig->plugin.display.pwrSaveAtIvOffline = (request->arg("dispPwr") == "on");
|
mConfig->plugin.display.pwrSaveAtIvOffline = (request->arg("disp_pwr") == "on");
|
||||||
mConfig->plugin.display.logoEn = (request->arg("logoEn") == "on");
|
mConfig->plugin.display.pxShift = (request->arg("disp_pxshift") == "on");
|
||||||
mConfig->plugin.display.pxShift = (request->arg("dispPxSh") == "on");
|
mConfig->plugin.display.rot = request->arg("disp_rot").toInt();
|
||||||
mConfig->plugin.display.rot180 = (request->arg("disp180") == "on");
|
mConfig->plugin.display.type = request->arg("disp_typ").toInt();
|
||||||
mConfig->plugin.display.type = request->arg("dispType").toInt();
|
mConfig->plugin.display.contrast = request->arg("disp_cont").toInt();
|
||||||
mConfig->plugin.display.contrast = request->arg("dispCont").toInt();
|
mConfig->plugin.display.disp_data = request->arg("disp_data").toInt();
|
||||||
mConfig->plugin.display.pin0 = request->arg("pinDisp0").toInt();
|
mConfig->plugin.display.disp_clk = request->arg("disp_clk").toInt();
|
||||||
mConfig->plugin.display.pin1 = request->arg("pinDisp1").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();
|
mApp->saveSettings();
|
||||||
|
|
||||||
if(request->arg("reboot") == "on")
|
if (request->arg("reboot") == "on")
|
||||||
onReboot(request);
|
onReboot(request);
|
||||||
else {
|
else {
|
||||||
AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html"), system_html, system_html_len);
|
AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html; charset=UTF-8"), system_html, system_html_len);
|
||||||
response->addHeader(F("Content-Encoding"), "gzip");
|
response->addHeader(F("Content-Encoding"), "gzip");
|
||||||
request->send(response);
|
request->send(response);
|
||||||
}
|
}
|
||||||
|
@ -596,15 +614,17 @@ class Web {
|
||||||
void onLive(AsyncWebServerRequest *request) {
|
void onLive(AsyncWebServerRequest *request) {
|
||||||
DPRINTLN(DBG_VERBOSE, F("onLive"));
|
DPRINTLN(DBG_VERBOSE, F("onLive"));
|
||||||
|
|
||||||
if(CHECK_MASK(mConfig->sys.protectionMask, PROT_MASK_LIVE)) {
|
if (CHECK_MASK(mConfig->sys.protectionMask, PROT_MASK_LIVE)) {
|
||||||
if(mProtected) {
|
if (mProtected) {
|
||||||
request->redirect("/login");
|
checkRedirect(request);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html"), visualization_html, visualization_html_len);
|
AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html; charset=UTF-8"), visualization_html, visualization_html_len);
|
||||||
response->addHeader(F("Content-Encoding"), "gzip");
|
response->addHeader(F("Content-Encoding"), "gzip");
|
||||||
|
response->addHeader(F("content-type"), "text/html; charset=UTF-8");
|
||||||
|
|
||||||
request->send(response);
|
request->send(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -675,21 +695,21 @@ class Web {
|
||||||
|
|
||||||
void onDebug(AsyncWebServerRequest *request) {
|
void onDebug(AsyncWebServerRequest *request) {
|
||||||
mApp->getSchedulerNames();
|
mApp->getSchedulerNames();
|
||||||
AsyncWebServerResponse *response = request->beginResponse(200, F("text/html"), "ok");
|
AsyncWebServerResponse *response = request->beginResponse(200, F("text/html; charset=UTF-8"), "ok");
|
||||||
request->send(response);
|
request->send(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
void onSerial(AsyncWebServerRequest *request) {
|
void onSerial(AsyncWebServerRequest *request) {
|
||||||
DPRINTLN(DBG_VERBOSE, F("onSerial"));
|
DPRINTLN(DBG_VERBOSE, F("onSerial"));
|
||||||
|
|
||||||
if(CHECK_MASK(mConfig->sys.protectionMask, PROT_MASK_SERIAL)) {
|
if (CHECK_MASK(mConfig->sys.protectionMask, PROT_MASK_SERIAL)) {
|
||||||
if(mProtected) {
|
if (mProtected) {
|
||||||
request->redirect("/login");
|
checkRedirect(request);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html"), serial_html, serial_html_len);
|
AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html; charset=UTF-8"), serial_html, serial_html_len);
|
||||||
response->addHeader(F("Content-Encoding"), "gzip");
|
response->addHeader(F("Content-Encoding"), "gzip");
|
||||||
request->send(response);
|
request->send(response);
|
||||||
}
|
}
|
||||||
|
@ -697,36 +717,36 @@ class Web {
|
||||||
void onSystem(AsyncWebServerRequest *request) {
|
void onSystem(AsyncWebServerRequest *request) {
|
||||||
DPRINTLN(DBG_VERBOSE, F("onSystem"));
|
DPRINTLN(DBG_VERBOSE, F("onSystem"));
|
||||||
|
|
||||||
if(CHECK_MASK(mConfig->sys.protectionMask, PROT_MASK_SYSTEM)) {
|
if (CHECK_MASK(mConfig->sys.protectionMask, PROT_MASK_SYSTEM)) {
|
||||||
if(mProtected) {
|
if (mProtected) {
|
||||||
request->redirect("/login");
|
checkRedirect(request);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html"), system_html, system_html_len);
|
AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html; charset=UTF-8"), system_html, system_html_len);
|
||||||
response->addHeader(F("Content-Encoding"), "gzip");
|
response->addHeader(F("Content-Encoding"), "gzip");
|
||||||
request->send(response);
|
request->send(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef ENABLE_JSON_EP
|
#ifdef ENABLE_JSON_EP
|
||||||
void showJson(AsyncWebServerRequest *request) {
|
void showJson(AsyncWebServerRequest *request) {
|
||||||
DPRINTLN(DBG_VERBOSE, F("web::showJson"));
|
DPRINTLN(DBG_VERBOSE, F("web::showJson"));
|
||||||
String modJson;
|
String modJson;
|
||||||
Inverter<> *iv;
|
Inverter<> *iv;
|
||||||
record_t<> *rec;
|
record_t<> *rec;
|
||||||
char topic[40], val[25];
|
char topic[40], val[25];
|
||||||
|
|
||||||
modJson = F("{\n");
|
modJson = F("{\n");
|
||||||
for(uint8_t id = 0; id < mSys->getNumInverters(); id++) {
|
for (uint8_t id = 0; id < mSys->getNumInverters(); id++) {
|
||||||
iv = mSys->getInverterByPos(id);
|
iv = mSys->getInverterByPos(id);
|
||||||
if(NULL == iv)
|
if (NULL == iv)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
rec = iv->getRecordStruct(RealTimeRunData_Debug);
|
rec = iv->getRecordStruct(RealTimeRunData_Debug);
|
||||||
snprintf(topic, 30, "\"%s\": {\n", iv->config->name);
|
snprintf(topic, 30, "\"%s\": {\n", iv->config->name);
|
||||||
modJson += String(topic);
|
modJson += String(topic);
|
||||||
for(uint8_t i = 0; i < rec->length; i++) {
|
for (uint8_t i = 0; i < rec->length; i++) {
|
||||||
snprintf(topic, 40, "\t\"ch%d/%s\"", rec->assign[i].ch, iv->getFieldName(i, rec));
|
snprintf(topic, 40, "\t\"ch%d/%s\"", rec->assign[i].ch, iv->getFieldName(i, rec));
|
||||||
snprintf(val, 25, "[%.3f, \"%s\"]", iv->getValue(i, rec), iv->getUnit(i, rec));
|
snprintf(val, 25, "[%.3f, \"%s\"]", iv->getValue(i, rec), iv->getUnit(i, rec));
|
||||||
modJson += String(topic) + ": " + String(val) + F(",\n");
|
modJson += String(topic) + ": " + String(val) + F(",\n");
|
||||||
|
@ -853,8 +873,7 @@ class Web {
|
||||||
// TODO: find the right one channel with the alarm id
|
// TODO: find the right one channel with the alarm id
|
||||||
alarmChannelId = 0;
|
alarmChannelId = 0;
|
||||||
// printf("AlarmData Length %d\n",rec->length);
|
// printf("AlarmData Length %d\n",rec->length);
|
||||||
if (alarmChannelId < rec->length)
|
if (alarmChannelId < rec->length) {
|
||||||
{
|
|
||||||
//uint8_t channel = rec->assign[alarmChannelId].ch;
|
//uint8_t channel = rec->assign[alarmChannelId].ch;
|
||||||
std::tie(promUnit, promType) = convertToPromUnits(iv->getUnit(alarmChannelId, rec));
|
std::tie(promUnit, promType) = convertToPromUnits(iv->getUnit(alarmChannelId, rec));
|
||||||
snprintf(type, sizeof(type), "# TYPE ahoy_solar_%s%s %s", iv->getFieldName(alarmChannelId, rec), promUnit.c_str(), promType.c_str());
|
snprintf(type, sizeof(type), "# TYPE ahoy_solar_%s%s %s", iv->getFieldName(alarmChannelId, rec), promUnit.c_str(), promType.c_str());
|
||||||
|
|
BIN
tools/cases/EKD_ESPNRF_Case/EKDESPNRFCase.jpg
Normal file
BIN
tools/cases/EKD_ESPNRF_Case/EKDESPNRFCase.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.5 MiB |
BIN
tools/cases/EKD_ESPNRF_Case/EKD_ESPNRF_Case_Body.3mf
Normal file
BIN
tools/cases/EKD_ESPNRF_Case/EKD_ESPNRF_Case_Body.3mf
Normal file
Binary file not shown.
BIN
tools/cases/EKD_ESPNRF_Case/EKD_ESPNRF_Case_Lid.3mf
Normal file
BIN
tools/cases/EKD_ESPNRF_Case/EKD_ESPNRF_Case_Lid.3mf
Normal file
Binary file not shown.
30
tools/cases/EKD_ESPNRF_Case/Readme.md
Normal file
30
tools/cases/EKD_ESPNRF_Case/Readme.md
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
# EKD ESPNRF Case
|
||||||
|
<picture>
|
||||||
|
<source media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/10756851/221722400-eefc8790-6283-4c00-a82b-e2699cae72d6.jpg">
|
||||||
|
<source media="(prefers-color-scheme: light)" srcset="https://user-images.githubusercontent.com/10756851/221722400-eefc8790-6283-4c00-a82b-e2699cae72d6.jpg">
|
||||||
|
<img alt="EKD ESPNRF Case" src="https://user-images.githubusercontent.com/10756851/221722400-eefc8790-6283-4c00-a82b-e2699cae72d6.jpg">
|
||||||
|
</picture>
|
||||||
|
|
||||||
|
### Print Details:
|
||||||
|
- Print with 0.2 mm Layers
|
||||||
|
- use 100% infill
|
||||||
|
- no supports needed
|
||||||
|
|
||||||
|
### Things needed:
|
||||||
|
- 3D Printer
|
||||||
|
- Wemos D1 Mini (format style)
|
||||||
|
- NRF24L01+ Board
|
||||||
|
- ~ 15cm wire
|
||||||
|
- Soldering Iron + Solder
|
||||||
|
- Suction pump to free the NRF Board from the pins.
|
||||||
|
(Solder wick works too but i do not recommend =)
|
||||||
|
- If you want to go for a wall mounted device, add some screws.
|
||||||
|
|
||||||
|
|
||||||
|
Unsolder the Pins from the NRF Board and use short wires instead. I went this way to keep the design as flat as possible.
|
||||||
|
<picture>
|
||||||
|
<img alt="EKD ESPNRF Case" src="https://user-images.githubusercontent.com/10756851/221722732-1ae9162c-ef77-492e-babf-075045b81f69.png">
|
||||||
|
</picture>
|
||||||
|
If you got questions or need help feel free to ask on discord.
|
||||||
|
or find me on github.com/subdancer
|
||||||
|
Cheers.
|
|
@ -80,12 +80,79 @@ python3 getting_started.py # to test and see whether RF24 class can be loaded as
|
||||||
|
|
||||||
If there are no error messages on the last step, then the NRF24 Wrapper has been installed successfully.
|
If there are no error messages on the last step, then the NRF24 Wrapper has been installed successfully.
|
||||||
|
|
||||||
|
|
||||||
|
Building RF24 Wrapper for Debian 11 (bullseye) 64 bit operating system
|
||||||
|
----------------------------------------------------------------------
|
||||||
|
The description above does not work on Debian 11 (bullseye) 64 bit operating system.
|
||||||
|
Please check first, if you have Debian 11 (bullseye) 64 bit operating system installed:
|
||||||
|
- `uname -a` search for aarch64
|
||||||
|
- `lsb_release -d`
|
||||||
|
- `cat /etc/debian_version`
|
||||||
|
|
||||||
|
There are 2 possible solutions to install the RF24 wrapper:
|
||||||
|
|
||||||
|
**__1. Solution:__**
|
||||||
|
```code
|
||||||
|
sudo apt install cmake git python3-dev libboost-python-dev python3-pip python3-rpi.gpio
|
||||||
|
|
||||||
|
sudo ln -s $(ls /usr/lib/$(ls /usr/lib/gcc | \
|
||||||
|
head -1)/libboost_python3*.so | \
|
||||||
|
tail -1) /usr/lib/$(ls /usr/lib/gcc | \
|
||||||
|
head -1)/libboost_python3.so
|
||||||
|
|
||||||
|
git clone https://github.com/nRF24/RF24.git
|
||||||
|
cd RF24
|
||||||
|
|
||||||
|
rm -rf build Makefile.inc
|
||||||
|
./configure --driver=SPIDEV
|
||||||
|
```
|
||||||
|
> _edit `Makefile.inc` with your prefered editor e.g. nano or vi_
|
||||||
|
>
|
||||||
|
> old:
|
||||||
|
>```code
|
||||||
|
> CPUFLAGS=-marm -march=armv6zk -mtune=arm1176jzf-s -mfpu=vfp -mfloat-abi=hard
|
||||||
|
> CFLAGS=-marm -march=armv6zk -mtune=arm1176jzf-s -mfpu=vfp -mfloat-abi=hard -Ofast -Wall -pthread
|
||||||
|
>```
|
||||||
|
> new:
|
||||||
|
>```code
|
||||||
|
> CPUFLAGS=
|
||||||
|
> CFLAGS=-Ofast -Wall -pthread
|
||||||
|
>```
|
||||||
|
_continue now_
|
||||||
|
```code
|
||||||
|
make
|
||||||
|
sudo make install
|
||||||
|
|
||||||
|
cd pyRF24
|
||||||
|
rm -r ./build/ ./dist/ ./RF24.egg-info/ ./__pycache__/ #just to make sure there is no old stuff
|
||||||
|
python3 -m pip install --upgrade pip
|
||||||
|
python3 -m pip install .
|
||||||
|
python3 -m pip list #watch for RF24 module - if its there its installed
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
**__2. Solution:__**
|
||||||
|
```code
|
||||||
|
sudo apt install git python3-dev libboost-python-dev python3-pip python3-rpi.gpio
|
||||||
|
|
||||||
|
git clone --recurse-submodules https://github.com/nRF24/pyRF24.git
|
||||||
|
cd pyRF24
|
||||||
|
python3 -m pip install . -v # this step takes about 5 minutes on my RPI-4 !
|
||||||
|
```
|
||||||
|
|
||||||
|
If you have problems with your radio module from ahoi, e.g.: cannot interpret received data,
|
||||||
|
please try to reduce the speed of your radio module!
|
||||||
|
Add the following parameter to your ahoy.yml configuration file in "nrf" section:
|
||||||
|
`spispeed: 600000` (0.6 MHz)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Required python modules
|
Required python modules
|
||||||
-----------------------
|
-----------------------
|
||||||
|
|
||||||
Some modules are not installed by default on a RaspberryPi, therefore add them manually:
|
Some modules are not installed by default on a RaspberryPi, therefore add them manually:
|
||||||
|
|
||||||
```
|
```code
|
||||||
pip install crcmod pyyaml paho-mqtt SunTimes
|
pip install crcmod pyyaml paho-mqtt SunTimes
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -112,7 +179,7 @@ Python parameters
|
||||||
|
|
||||||
|
|
||||||
The application describes itself
|
The application describes itself
|
||||||
```
|
```code
|
||||||
python3 -m hoymiles --help
|
python3 -m hoymiles --help
|
||||||
usage: hoymiles [-h] -c [CONFIG_FILE] [--log-transactions] [--verbose]
|
usage: hoymiles [-h] -c [CONFIG_FILE] [--log-transactions] [--verbose]
|
||||||
|
|
||||||
|
@ -180,7 +247,7 @@ Todo
|
||||||
- Ability to talk to multiple inverters
|
- Ability to talk to multiple inverters
|
||||||
- MQTT gateway
|
- MQTT gateway
|
||||||
- understand channel hopping
|
- understand channel hopping
|
||||||
- configurable polling interval
|
- ~~configurable polling interval~~ done: interval ist configurable in ahoy.yml
|
||||||
- commands
|
- commands
|
||||||
- picture of setup!
|
- picture of setup!
|
||||||
- python module
|
- python module
|
||||||
|
|
|
@ -6,11 +6,9 @@
|
||||||
# WorkingDirectory (absolute path to your private ahoy dir)
|
# WorkingDirectory (absolute path to your private ahoy dir)
|
||||||
# To change other config parameter, please consult systemd documentation
|
# To change other config parameter, please consult systemd documentation
|
||||||
#
|
#
|
||||||
# To activate this service, create a link, enable and start the ahoy.service
|
# To activate this service, enable and start ahoy.service
|
||||||
# $ mkdir -p $HOME/.config/systemd/user
|
# $ systemctl --user enable $(pwd)/ahoy/tools/rpi/ahoy.service
|
||||||
# $ ln -sf $(pwd)/ahoy/tools/rpi/ahoy.service -t $HOME/.config/systemd/user
|
|
||||||
# $ systemctl --user status ahoy
|
# $ systemctl --user status ahoy
|
||||||
# $ systemctl --user enable ahoy
|
|
||||||
# $ systemctl --user start ahoy
|
# $ systemctl --user start ahoy
|
||||||
# $ systemctl --user status ahoy
|
# $ systemctl --user status ahoy
|
||||||
#
|
#
|
||||||
|
|
|
@ -31,7 +31,7 @@ ahoy:
|
||||||
QoS: 0
|
QoS: 0
|
||||||
Retain: True
|
Retain: True
|
||||||
last_will:
|
last_will:
|
||||||
topic: Appelweg_PV/114181807700 # defaults to 'hoymiles/{serial}'
|
topic: my_DTU_name # Name of DTU - default: hoymiles/{DTU-serial}
|
||||||
payload: "LAST-WILL-MESSAGE: Please check my HOST and Process!"
|
payload: "LAST-WILL-MESSAGE: Please check my HOST and Process!"
|
||||||
|
|
||||||
# Influx2 output
|
# Influx2 output
|
||||||
|
@ -96,6 +96,7 @@ ahoy:
|
||||||
|
|
||||||
dtu:
|
dtu:
|
||||||
serial: 99978563001
|
serial: 99978563001
|
||||||
|
name: my_DTU_name
|
||||||
|
|
||||||
inverters:
|
inverters:
|
||||||
- name: 'balkon'
|
- name: 'balkon'
|
||||||
|
@ -103,14 +104,14 @@ ahoy:
|
||||||
txpower: 'low' # txpower per inverter (min,low,high,max)
|
txpower: 'low' # txpower per inverter (min,low,high,max)
|
||||||
mqtt:
|
mqtt:
|
||||||
send_raw_enabled: false # allow inject debug data via mqtt
|
send_raw_enabled: false # allow inject debug data via mqtt
|
||||||
topic: 'hoymiles/114172221234' # defaults to '{inverter-name}/{serial}'
|
topic: 'hoymiles/114172220003' # defaults to '{inverter-name}/{serial}'
|
||||||
strings: # list all available strings
|
strings: # list all available strings
|
||||||
- s_name: 'String 1 left' # String 1 name
|
- s_name: 'String 1 left' # String 1 name
|
||||||
s_maxpower: 395 # String 1 max power in Wp
|
s_maxpower: 395 # String 1 max power in inverter
|
||||||
- s_name: 'String 2 right' # String 2 name
|
- s_name: 'String 2 right' # String 2 name
|
||||||
s_maxpower: 400 # String 2 max power in Wp
|
s_maxpower: 400 # String 2 max power in inverter
|
||||||
- s_name: 'String 3 up' # String 3 name
|
- s_name: 'String 3 up' # String 3 name
|
||||||
s_maxpower: 405 # String 3 max power in Wp
|
s_maxpower: 405 # String 3 max power in inverter
|
||||||
- s_name: 'String 4 down' # String 4 name
|
- s_name: 'String 4 down' # String 4 name
|
||||||
s_maxpower: 410 # String 4 max power in Wp
|
s_maxpower: 410 # String 4 max power in inverter
|
||||||
|
|
||||||
|
|
|
@ -11,8 +11,28 @@ import re
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
import logging
|
import logging
|
||||||
import crcmod
|
import crcmod
|
||||||
from RF24 import RF24, RF24_PA_MIN, RF24_PA_LOW, RF24_PA_HIGH, RF24_PA_MAX, RF24_250KBPS, RF24_CRC_DISABLED, RF24_CRC_8, RF24_CRC_16
|
|
||||||
from .decoders import *
|
from .decoders import *
|
||||||
|
from os import environ
|
||||||
|
|
||||||
|
try:
|
||||||
|
# OSI Layer 2 driver for nRF24L01 on Arduino & Raspberry Pi/Linux Devices
|
||||||
|
# https://github.com/nRF24/RF24.git
|
||||||
|
from RF24 import RF24, RF24_PA_MIN, RF24_PA_LOW, RF24_PA_HIGH, RF24_PA_MAX, RF24_250KBPS, RF24_CRC_DISABLED, RF24_CRC_8, RF24_CRC_16
|
||||||
|
if environ.get('TERM') is not None:
|
||||||
|
print('Using python Module: RF24')
|
||||||
|
except ModuleNotFoundError as e:
|
||||||
|
if environ.get('TERM') is not None:
|
||||||
|
print(f'{e} - try to use module: RF24')
|
||||||
|
try:
|
||||||
|
# Repo for pyRF24 package
|
||||||
|
# https://github.com/nRF24/pyRF24.git
|
||||||
|
from pyrf24 import RF24, RF24_PA_MIN, RF24_PA_LOW, RF24_PA_HIGH, RF24_PA_MAX, RF24_250KBPS, RF24_CRC_DISABLED, RF24_CRC_8, RF24_CRC_16
|
||||||
|
if environ.get('TERM') is not None:
|
||||||
|
print(f'{e} - Using python Module: pyrf24')
|
||||||
|
except ModuleNotFoundError as e:
|
||||||
|
if environ.get('TERM') is not None:
|
||||||
|
print(f'{e} - exit')
|
||||||
|
exit()
|
||||||
|
|
||||||
f_crc_m = crcmod.predefined.mkPredefinedCrcFun('modbus')
|
f_crc_m = crcmod.predefined.mkPredefinedCrcFun('modbus')
|
||||||
f_crc8 = crcmod.mkCrcFun(0x101, initCrc=0, xorOut=0)
|
f_crc8 = crcmod.mkCrcFun(0x101, initCrc=0, xorOut=0)
|
||||||
|
@ -158,15 +178,26 @@ class ResponseDecoder(ResponseDecoderFactory):
|
||||||
model = self.inverter_model
|
model = self.inverter_model
|
||||||
command = self.request_command
|
command = self.request_command
|
||||||
|
|
||||||
c_datetime = self.time_rx.strftime("%Y-%m-%d %H:%M:%S.%f")
|
if HOYMILES_DEBUG_LOGGING:
|
||||||
logging.info(f'{c_datetime} model_decoder: {model}Decode{command.upper()}')
|
if command.upper() == '01':
|
||||||
|
model_desc = "Firmware version / date"
|
||||||
|
elif command.upper() == '02':
|
||||||
|
model_desc = "Inverter generic events log"
|
||||||
|
elif command.upper() == '0B':
|
||||||
|
model_desc = "mirco-inverters status data"
|
||||||
|
elif command.upper() == '0C':
|
||||||
|
model_desc = "mirco-inverters status data"
|
||||||
|
elif command.upper() == '11':
|
||||||
|
model_desc = "Inverter generic events log"
|
||||||
|
elif command.upper() == '12':
|
||||||
|
model_desc = "Inverter major events log"
|
||||||
|
logging.info(f'model_decoder: {model}Decode{command.upper()} - {model_desc}')
|
||||||
|
|
||||||
model_decoders = __import__('hoymiles.decoders')
|
model_decoders = __import__('hoymiles.decoders')
|
||||||
if hasattr(model_decoders, f'{model}Decode{command.upper()}'):
|
if hasattr(model_decoders, f'{model}Decode{command.upper()}'):
|
||||||
device = getattr(model_decoders, f'{model}Decode{command.upper()}')
|
device = getattr(model_decoders, f'{model}Decode{command.upper()}')
|
||||||
else:
|
else:
|
||||||
if HOYMILES_DEBUG_LOGGING:
|
device = getattr(model_decoders, 'DebugDecodeAny')
|
||||||
device = getattr(model_decoders, 'DebugDecodeAny')
|
|
||||||
|
|
||||||
return device(self.response,
|
return device(self.response,
|
||||||
time_rx=self.time_rx,
|
time_rx=self.time_rx,
|
||||||
|
|
|
@ -33,6 +33,12 @@ def signal_handler(sig_num, frame):
|
||||||
if mqtt_client:
|
if mqtt_client:
|
||||||
mqtt_client.disco()
|
mqtt_client.disco()
|
||||||
|
|
||||||
|
if influx_client:
|
||||||
|
influx_client.disco()
|
||||||
|
|
||||||
|
if volkszaehler_client:
|
||||||
|
volkszaehler_client.disco()
|
||||||
|
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
signal(SIGINT, signal_handler) # Interrupt from keyboard (CTRL + C)
|
signal(SIGINT, signal_handler) # Interrupt from keyboard (CTRL + C)
|
||||||
|
@ -75,7 +81,6 @@ class SunsetHandler:
|
||||||
else:
|
else:
|
||||||
logging.info('Sunset disabled.')
|
logging.info('Sunset disabled.')
|
||||||
|
|
||||||
|
|
||||||
def checkWaitForSunrise(self):
|
def checkWaitForSunrise(self):
|
||||||
if not self.suntimes:
|
if not self.suntimes:
|
||||||
return
|
return
|
||||||
|
@ -94,6 +99,23 @@ class SunsetHandler:
|
||||||
time.sleep(time_to_sleep)
|
time.sleep(time_to_sleep)
|
||||||
logging.info (f'Woke up...')
|
logging.info (f'Woke up...')
|
||||||
|
|
||||||
|
def sun_status2mqtt(self, dtu_ser, dtu_name):
|
||||||
|
if not mqtt_client:
|
||||||
|
return
|
||||||
|
local_sunrise = self.suntimes.riselocal(datetime.now()).strftime("%d.%m.%YT%H:%M")
|
||||||
|
local_sunset = self.suntimes.setlocal(datetime.now()).strftime("%d.%m.%YT%H:%M")
|
||||||
|
local_zone = self.suntimes.setlocal(datetime.now()).tzinfo._key
|
||||||
|
if self.suntimes:
|
||||||
|
mqtt_client.info2mqtt({'topic' : f'{dtu_name}/{dtu_ser}'}, \
|
||||||
|
{'dis_night_comm' : 'True', \
|
||||||
|
'local_sunrise' : local_sunrise, \
|
||||||
|
'local_sunset' : local_sunset,
|
||||||
|
'local_zone' : local_zone})
|
||||||
|
else:
|
||||||
|
mqtt_client.sun_info2mqtt({'sun_topic': f'{dtu_name}/{dtu_ser}'}, \
|
||||||
|
{'dis_night_comm': 'False'})
|
||||||
|
|
||||||
|
|
||||||
def main_loop(ahoy_config):
|
def main_loop(ahoy_config):
|
||||||
"""Main loop"""
|
"""Main loop"""
|
||||||
inverters = [
|
inverters = [
|
||||||
|
@ -101,7 +123,9 @@ def main_loop(ahoy_config):
|
||||||
if not inverter.get('disabled', False)]
|
if not inverter.get('disabled', False)]
|
||||||
|
|
||||||
sunset = SunsetHandler(ahoy_config.get('sunset'))
|
sunset = SunsetHandler(ahoy_config.get('sunset'))
|
||||||
dtu_ser = ahoy_config.get('dtu', {}).get('serial')
|
dtu_ser = ahoy_config.get('dtu', {}).get('serial', None)
|
||||||
|
dtu_name = ahoy_config.get('dtu', {}).get('name', 'hoymiles-dtu')
|
||||||
|
sunset.sun_status2mqtt(dtu_ser, dtu_name)
|
||||||
loop_interval = ahoy_config.get('interval', 1)
|
loop_interval = ahoy_config.get('interval', 1)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -112,6 +136,11 @@ def main_loop(ahoy_config):
|
||||||
t_loop_start = time.time()
|
t_loop_start = time.time()
|
||||||
|
|
||||||
for inverter in inverters:
|
for inverter in inverters:
|
||||||
|
if not 'name' in inverter:
|
||||||
|
inverter['name'] = 'hoymiles'
|
||||||
|
if not 'serial' in inverter:
|
||||||
|
logging.error("No inverter serial number found in ahoy.yml - exit")
|
||||||
|
sys.exit(999)
|
||||||
if hoymiles.HOYMILES_DEBUG_LOGGING:
|
if hoymiles.HOYMILES_DEBUG_LOGGING:
|
||||||
logging.info(f'Poll inverter name={inverter["name"]} ser={inverter["serial"]}')
|
logging.info(f'Poll inverter name={inverter["name"]} ser={inverter["serial"]}')
|
||||||
poll_inverter(inverter, dtu_ser, do_init, 3)
|
poll_inverter(inverter, dtu_ser, do_init, 3)
|
||||||
|
@ -122,8 +151,6 @@ def main_loop(ahoy_config):
|
||||||
if time_to_sleep > 0:
|
if time_to_sleep > 0:
|
||||||
time.sleep(time_to_sleep)
|
time.sleep(time_to_sleep)
|
||||||
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
sys.exit()
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.fatal('Exception catched: %s' % e)
|
logging.fatal('Exception catched: %s' % e)
|
||||||
logging.fatal(traceback.print_exc())
|
logging.fatal(traceback.print_exc())
|
||||||
|
@ -174,13 +201,14 @@ def poll_inverter(inverter, dtu_ser, do_init, retries):
|
||||||
response = com.get_payload()
|
response = com.get_payload()
|
||||||
payload_ttl = 0
|
payload_ttl = 0
|
||||||
except Exception as e_all:
|
except Exception as e_all:
|
||||||
logging.error(f'Error while retrieving data: {e_all}')
|
if hoymiles.HOYMILES_TRANSACTION_LOGGING:
|
||||||
|
logging.error(f'Error while retrieving data: {e_all}')
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# Handle the response data if any
|
# Handle the response data if any
|
||||||
if response:
|
if response:
|
||||||
c_datetime = datetime.now()
|
if hoymiles.HOYMILES_TRANSACTION_LOGGING:
|
||||||
if hoymiles.HOYMILES_DEBUG_LOGGING:
|
c_datetime = datetime.now()
|
||||||
logging.debug(f'{c_datetime} Payload: ' + hoymiles.hexify_payload(response))
|
logging.debug(f'{c_datetime} Payload: ' + hoymiles.hexify_payload(response))
|
||||||
|
|
||||||
# prepare decoder object
|
# prepare decoder object
|
||||||
|
@ -195,7 +223,7 @@ def poll_inverter(inverter, dtu_ser, do_init, retries):
|
||||||
# get decoder object
|
# get decoder object
|
||||||
result = decoder.decode()
|
result = decoder.decode()
|
||||||
if hoymiles.HOYMILES_DEBUG_LOGGING:
|
if hoymiles.HOYMILES_DEBUG_LOGGING:
|
||||||
logging.info(f'{c_datetime} Decoded: {result.__dict__()}')
|
logging.info(f'Decoded: {result.__dict__()}')
|
||||||
|
|
||||||
# check decoder object for output
|
# check decoder object for output
|
||||||
if isinstance(result, hoymiles.decoders.StatusResponse):
|
if isinstance(result, hoymiles.decoders.StatusResponse):
|
||||||
|
@ -281,7 +309,13 @@ def init_logging(ahoy_config):
|
||||||
lvl = logging.WARNING
|
lvl = logging.WARNING
|
||||||
elif level == 'ERROR':
|
elif level == 'ERROR':
|
||||||
lvl = logging.ERROR
|
lvl = logging.ERROR
|
||||||
|
elif level == 'FATAL':
|
||||||
|
lvl = logging.FATAL
|
||||||
|
if hoymiles.HOYMILES_TRANSACTION_LOGGING:
|
||||||
|
lvl = logging.DEBUG
|
||||||
logging.basicConfig(filename=fn, format='%(asctime)s %(levelname)s: %(message)s', datefmt='%Y-%m-%d %H:%M:%S', level=lvl)
|
logging.basicConfig(filename=fn, format='%(asctime)s %(levelname)s: %(message)s', datefmt='%Y-%m-%d %H:%M:%S', level=lvl)
|
||||||
|
dtu_name = ahoy_config.get('dtu',{}).get('name','hoymiles-dtu')
|
||||||
|
logging.info(f'start logging for {dtu_name} with level: {logging.root.level}')
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
parser = argparse.ArgumentParser(description='Ahoy - Hoymiles solar inverter gateway', prog="hoymiles")
|
parser = argparse.ArgumentParser(description='Ahoy - Hoymiles solar inverter gateway', prog="hoymiles")
|
||||||
|
@ -308,29 +342,29 @@ if __name__ == '__main__':
|
||||||
logging.error(f'Failed to load config file {global_config.config_file}: {e_yaml}')
|
logging.error(f'Failed to load config file {global_config.config_file}: {e_yaml}')
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
# read AHOY configuration file and prepare logging
|
|
||||||
ahoy_config = dict(cfg.get('ahoy', {}))
|
|
||||||
init_logging(ahoy_config)
|
|
||||||
|
|
||||||
if global_config.log_transactions:
|
if global_config.log_transactions:
|
||||||
hoymiles.HOYMILES_TRANSACTION_LOGGING=True
|
hoymiles.HOYMILES_TRANSACTION_LOGGING=True
|
||||||
if global_config.verbose:
|
if global_config.verbose:
|
||||||
hoymiles.HOYMILES_DEBUG_LOGGING=True
|
hoymiles.HOYMILES_DEBUG_LOGGING=True
|
||||||
|
|
||||||
|
# read AHOY configuration file and prepare logging
|
||||||
|
ahoy_config = dict(cfg.get('ahoy', {}))
|
||||||
|
init_logging(ahoy_config)
|
||||||
|
|
||||||
# Prepare for multiple transceivers, makes them configurable
|
# Prepare for multiple transceivers, makes them configurable
|
||||||
for radio_config in ahoy_config.get('nrf', [{}]):
|
for radio_config in ahoy_config.get('nrf', [{}]):
|
||||||
hmradio = hoymiles.HoymilesNRF(**radio_config)
|
hmradio = hoymiles.HoymilesNRF(**radio_config)
|
||||||
|
|
||||||
# create MQTT - client object
|
# create MQTT - client object
|
||||||
mqtt_client = None
|
mqtt_client = None
|
||||||
mqtt_config = ahoy_config.get('mqtt', {})
|
mqtt_config = ahoy_config.get('mqtt', None)
|
||||||
if mqtt_config and not mqtt_config.get('disabled', False):
|
if mqtt_config and not mqtt_config.get('disabled', False):
|
||||||
from .outputs import MqttOutputPlugin
|
from .outputs import MqttOutputPlugin
|
||||||
mqtt_client = MqttOutputPlugin(mqtt_config)
|
mqtt_client = MqttOutputPlugin(mqtt_config)
|
||||||
|
|
||||||
# create INFLUX - client object
|
# create INFLUX - client object
|
||||||
influx_client = None
|
influx_client = None
|
||||||
influx_config = ahoy_config.get('influxdb', {})
|
influx_config = ahoy_config.get('influxdb', None)
|
||||||
if influx_config and not influx_config.get('disabled', False):
|
if influx_config and not influx_config.get('disabled', False):
|
||||||
from .outputs import InfluxOutputPlugin
|
from .outputs import InfluxOutputPlugin
|
||||||
influx_client = InfluxOutputPlugin(
|
influx_client = InfluxOutputPlugin(
|
||||||
|
|
|
@ -99,6 +99,7 @@ class StatusResponse(Response):
|
||||||
frequency = None
|
frequency = None
|
||||||
powerfactor = None
|
powerfactor = None
|
||||||
event_count = None
|
event_count = None
|
||||||
|
unpack_error = False
|
||||||
|
|
||||||
def unpack(self, fmt, base):
|
def unpack(self, fmt, base):
|
||||||
"""
|
"""
|
||||||
|
@ -110,6 +111,10 @@ class StatusResponse(Response):
|
||||||
:rtype: tuple
|
:rtype: tuple
|
||||||
"""
|
"""
|
||||||
size = struct.calcsize(fmt)
|
size = struct.calcsize(fmt)
|
||||||
|
if (len(self.response) < base+size):
|
||||||
|
self.unpack_error = True
|
||||||
|
logging.error(f'base: {base} size: {size} len: {len(self.response)} fmt: {fmt} rep: {self.response}')
|
||||||
|
return [0]
|
||||||
return struct.unpack(fmt, self.response[base:base+size])
|
return struct.unpack(fmt, self.response[base:base+size])
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -150,6 +155,7 @@ class StatusResponse(Response):
|
||||||
s_exists = False
|
s_exists = False
|
||||||
string_id = len(strings)
|
string_id = len(strings)
|
||||||
string = {}
|
string = {}
|
||||||
|
string['name'] = self.inv_strings[string_id]['s_name']
|
||||||
for key in self.string_keys:
|
for key in self.string_keys:
|
||||||
prop = f'dc_{key}_{string_id}'
|
prop = f'dc_{key}_{string_id}'
|
||||||
if hasattr(self, prop):
|
if hasattr(self, prop):
|
||||||
|
@ -193,7 +199,8 @@ class StatusResponse(Response):
|
||||||
data['event_count'] = self.event_count
|
data['event_count'] = self.event_count
|
||||||
data['time'] = self.time_rx
|
data['time'] = self.time_rx
|
||||||
|
|
||||||
return data
|
if not self.unpack_error:
|
||||||
|
return data
|
||||||
|
|
||||||
class UnknownResponse(Response):
|
class UnknownResponse(Response):
|
||||||
"""
|
"""
|
||||||
|
@ -321,9 +328,9 @@ class EventsResponse(UnknownResponse):
|
||||||
#logging.debug(' payload has valid modbus crc')
|
#logging.debug(' payload has valid modbus crc')
|
||||||
self.response = self.response[:-2]
|
self.response = self.response[:-2]
|
||||||
|
|
||||||
status = struct.unpack('>H', self.response[:2])[0]
|
self.status = struct.unpack('>H', self.response[:2])[0]
|
||||||
a_text = self.alarm_codes.get(status, 'N/A')
|
self.a_text = self.alarm_codes.get(self.status, 'N/A')
|
||||||
logging.info (f' Inverter status: {a_text} ({status})')
|
logging.info (f'Inverter status: {self.a_text} ({self.status})')
|
||||||
|
|
||||||
chunk_size = 12
|
chunk_size = 12
|
||||||
for i_chunk in range(2, len(self.response), chunk_size):
|
for i_chunk in range(2, len(self.response), chunk_size):
|
||||||
|
@ -331,9 +338,12 @@ class EventsResponse(UnknownResponse):
|
||||||
|
|
||||||
logging.debug(' '.join([f'{byte:02x}' for byte in chunk]) + ': ')
|
logging.debug(' '.join([f'{byte:02x}' for byte in chunk]) + ': ')
|
||||||
|
|
||||||
|
if (len(chunk[0:6]) < 6):
|
||||||
|
logging.error(f'length of chunk must be greater or equal 6 bytes: {chunk}')
|
||||||
|
return
|
||||||
|
|
||||||
opcode, a_code, a_count, uptime_sec = struct.unpack('>BBHH', chunk[0:6])
|
opcode, a_code, a_count, uptime_sec = struct.unpack('>BBHH', chunk[0:6])
|
||||||
a_text = self.alarm_codes.get(a_code, 'N/A')
|
a_text = self.alarm_codes.get(a_code, 'N/A')
|
||||||
|
|
||||||
logging.debug(f' uptime={timedelta(seconds=uptime_sec)} a_count={a_count} opcode={opcode} a_code={a_code} a_text={a_text}')
|
logging.debug(f' uptime={timedelta(seconds=uptime_sec)} a_count={a_count} opcode={opcode} a_code={a_code} a_text={a_text}')
|
||||||
|
|
||||||
dbg = ''
|
dbg = ''
|
||||||
|
@ -341,6 +351,14 @@ class EventsResponse(UnknownResponse):
|
||||||
dbg += f' {fmt:7}: ' + str(struct.unpack('>' + fmt, chunk))
|
dbg += f' {fmt:7}: ' + str(struct.unpack('>' + fmt, chunk))
|
||||||
logging.debug(dbg)
|
logging.debug(dbg)
|
||||||
|
|
||||||
|
def __dict__(self):
|
||||||
|
""" Base values, availabe in each __dict__ call """
|
||||||
|
|
||||||
|
data = super().__dict__()
|
||||||
|
data['inv_stat_num'] = self.status
|
||||||
|
data['inv_stat_txt'] = self.a_text
|
||||||
|
return data
|
||||||
|
|
||||||
class HardwareInfoResponse(UnknownResponse):
|
class HardwareInfoResponse(UnknownResponse):
|
||||||
def __init__(self, *args, **params):
|
def __init__(self, *args, **params):
|
||||||
super().__init__(*args, **params)
|
super().__init__(*args, **params)
|
||||||
|
@ -361,9 +379,14 @@ class HardwareInfoResponse(UnknownResponse):
|
||||||
def __dict__(self):
|
def __dict__(self):
|
||||||
""" Base values, availabe in each __dict__ call """
|
""" Base values, availabe in each __dict__ call """
|
||||||
|
|
||||||
responce_info = self.response
|
data = super().__dict__()
|
||||||
logging.info(f'HardwareInfoResponse: {struct.unpack(">HHHHHHHH", responce_info)}')
|
|
||||||
|
|
||||||
|
if (len(self.response) != 16):
|
||||||
|
logging.error(f'HardwareInfoResponse: data length should be 16 bytes - measured {len(self.response)} bytes')
|
||||||
|
logging.error(f'HardwareInfoResponse: data: {self.response}')
|
||||||
|
return data
|
||||||
|
|
||||||
|
logging.info(f'HardwareInfoResponse: {struct.unpack(">HHHHHHHH", self.response[0:16])}')
|
||||||
fw_version, fw_build_yyyy, fw_build_mmdd, fw_build_hhmm, hw_id = struct.unpack('>HHHHH', self.response[0:10])
|
fw_version, fw_build_yyyy, fw_build_mmdd, fw_build_hhmm, hw_id = struct.unpack('>HHHHH', self.response[0:10])
|
||||||
|
|
||||||
fw_version_maj = int((fw_version / 10000))
|
fw_version_maj = int((fw_version / 10000))
|
||||||
|
@ -377,7 +400,6 @@ class HardwareInfoResponse(UnknownResponse):
|
||||||
f'build at {fw_build_dd:>02}/{fw_build_mm:>02}/{fw_build_yyyy}T{fw_build_HH:>02}:{fw_build_MM:>02}, '\
|
f'build at {fw_build_dd:>02}/{fw_build_mm:>02}/{fw_build_yyyy}T{fw_build_HH:>02}:{fw_build_MM:>02}, '\
|
||||||
f'HW revision {hw_id}')
|
f'HW revision {hw_id}')
|
||||||
|
|
||||||
data = super().__dict__()
|
|
||||||
data['FW_ver_maj'] = fw_version_maj
|
data['FW_ver_maj'] = fw_version_maj
|
||||||
data['FW_ver_min'] = fw_version_min
|
data['FW_ver_min'] = fw_version_min
|
||||||
data['FW_ver_pat'] = fw_version_pat
|
data['FW_ver_pat'] = fw_version_pat
|
||||||
|
@ -468,6 +490,8 @@ class Hm300Decode0B(StatusResponse):
|
||||||
""" String 1 irratiation in percent """
|
""" String 1 irratiation in percent """
|
||||||
if self.inv_strings is None:
|
if self.inv_strings is None:
|
||||||
return None
|
return None
|
||||||
|
if self.inv_strings[0]['s_maxpower'] == 0:
|
||||||
|
return 0.00
|
||||||
return round(self.unpack('>H', 6)[0]/10/self.inv_strings[0]['s_maxpower']*100, 3)
|
return round(self.unpack('>H', 6)[0]/10/self.inv_strings[0]['s_maxpower']*100, 3)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -540,6 +564,8 @@ class Hm600Decode0B(StatusResponse):
|
||||||
""" String 1 irratiation in percent """
|
""" String 1 irratiation in percent """
|
||||||
if self.inv_strings is None:
|
if self.inv_strings is None:
|
||||||
return None
|
return None
|
||||||
|
if self.inv_strings[0]['s_maxpower'] == 0:
|
||||||
|
return 0.00
|
||||||
return round(self.unpack('>H', 6)[0]/10/self.inv_strings[0]['s_maxpower']*100, 3)
|
return round(self.unpack('>H', 6)[0]/10/self.inv_strings[0]['s_maxpower']*100, 3)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -567,6 +593,8 @@ class Hm600Decode0B(StatusResponse):
|
||||||
""" String 2 irratiation in percent """
|
""" String 2 irratiation in percent """
|
||||||
if self.inv_strings is None:
|
if self.inv_strings is None:
|
||||||
return None
|
return None
|
||||||
|
if self.inv_strings[1]['s_maxpower'] == 0:
|
||||||
|
return 0.00
|
||||||
return round(self.unpack('>H', 12)[0]/10/self.inv_strings[1]['s_maxpower']*100, 3)
|
return round(self.unpack('>H', 12)[0]/10/self.inv_strings[1]['s_maxpower']*100, 3)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -647,6 +675,8 @@ class Hm1200Decode0B(StatusResponse):
|
||||||
""" String 1 irratiation in percent """
|
""" String 1 irratiation in percent """
|
||||||
if self.inv_strings is None:
|
if self.inv_strings is None:
|
||||||
return None
|
return None
|
||||||
|
if self.inv_strings[0]['s_maxpower'] == 0:
|
||||||
|
return 0.00
|
||||||
return round(self.unpack('>H', 8)[0]/10/self.inv_strings[0]['s_maxpower']*100, 3)
|
return round(self.unpack('>H', 8)[0]/10/self.inv_strings[0]['s_maxpower']*100, 3)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -674,6 +704,8 @@ class Hm1200Decode0B(StatusResponse):
|
||||||
""" String 2 irratiation in percent """
|
""" String 2 irratiation in percent """
|
||||||
if self.inv_strings is None:
|
if self.inv_strings is None:
|
||||||
return None
|
return None
|
||||||
|
if self.inv_strings[1]['s_maxpower'] == 0:
|
||||||
|
return 0.00
|
||||||
return round(self.unpack('>H', 10)[0]/10/self.inv_strings[1]['s_maxpower']*100, 3)
|
return round(self.unpack('>H', 10)[0]/10/self.inv_strings[1]['s_maxpower']*100, 3)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -701,6 +733,8 @@ class Hm1200Decode0B(StatusResponse):
|
||||||
""" String 3 irratiation in percent """
|
""" String 3 irratiation in percent """
|
||||||
if self.inv_strings is None:
|
if self.inv_strings is None:
|
||||||
return None
|
return None
|
||||||
|
if self.inv_strings[2]['s_maxpower'] == 0:
|
||||||
|
return 0.00
|
||||||
return round(self.unpack('>H', 30)[0]/10/self.inv_strings[2]['s_maxpower']*100, 3)
|
return round(self.unpack('>H', 30)[0]/10/self.inv_strings[2]['s_maxpower']*100, 3)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -728,6 +762,8 @@ class Hm1200Decode0B(StatusResponse):
|
||||||
""" String 4 irratiation in percent """
|
""" String 4 irratiation in percent """
|
||||||
if self.inv_strings is None:
|
if self.inv_strings is None:
|
||||||
return None
|
return None
|
||||||
|
if self.inv_strings[3]['s_maxpower'] == 0:
|
||||||
|
return 0.00
|
||||||
return round(self.unpack('>H', 32)[0]/10/self.inv_strings[3]['s_maxpower']*100, 3)
|
return round(self.unpack('>H', 32)[0]/10/self.inv_strings[3]['s_maxpower']*100, 3)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
|
@ -9,6 +9,7 @@ import socket
|
||||||
import logging
|
import logging
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
from hoymiles.decoders import StatusResponse, HardwareInfoResponse
|
from hoymiles.decoders import StatusResponse, HardwareInfoResponse
|
||||||
|
from hoymiles import HOYMILES_TRANSACTION_LOGGING, HOYMILES_DEBUG_LOGGING
|
||||||
|
|
||||||
class OutputPluginFactory:
|
class OutputPluginFactory:
|
||||||
def __init__(self, **params):
|
def __init__(self, **params):
|
||||||
|
@ -39,6 +40,7 @@ class InfluxOutputPlugin(OutputPluginFactory):
|
||||||
def __init__(self, url, token, **params):
|
def __init__(self, url, token, **params):
|
||||||
"""
|
"""
|
||||||
Initialize InfluxOutputPlugin
|
Initialize InfluxOutputPlugin
|
||||||
|
https://influxdb-client.readthedocs.io/en/stable/api.html#influxdbclient
|
||||||
|
|
||||||
The following targets must be present in your InfluxDB. This does not
|
The following targets must be present in your InfluxDB. This does not
|
||||||
automatically create anything for You.
|
automatically create anything for You.
|
||||||
|
@ -68,8 +70,12 @@ class InfluxOutputPlugin(OutputPluginFactory):
|
||||||
self._org = params.get('org', '')
|
self._org = params.get('org', '')
|
||||||
self._measurement = params.get('measurement', f'inverter,host={socket.gethostname()}')
|
self._measurement = params.get('measurement', f'inverter,host={socket.gethostname()}')
|
||||||
|
|
||||||
client = InfluxDBClient(url, token, bucket=self._bucket)
|
with InfluxDBClient(url, token, bucket=self._bucket) as self.client:
|
||||||
self.api = client.write_api()
|
self.api = self.client.write_api()
|
||||||
|
|
||||||
|
def disco(self, **params):
|
||||||
|
self.client.close() # Shutdown the client
|
||||||
|
return
|
||||||
|
|
||||||
def store_status(self, response, **params):
|
def store_status(self, response, **params):
|
||||||
"""
|
"""
|
||||||
|
@ -102,6 +108,9 @@ class InfluxOutputPlugin(OutputPluginFactory):
|
||||||
# InfluxDB requires nanoseconds
|
# InfluxDB requires nanoseconds
|
||||||
ctime = int(utctime.timestamp() * 1e9)
|
ctime = int(utctime.timestamp() * 1e9)
|
||||||
|
|
||||||
|
if HOYMILES_DEBUG_LOGGING:
|
||||||
|
logging.info(f'InfluxDB: utctime: {utctime}')
|
||||||
|
|
||||||
# AC Data
|
# AC Data
|
||||||
phase_id = 0
|
phase_id = 0
|
||||||
for phase in data['phases']:
|
for phase in data['phases']:
|
||||||
|
@ -135,6 +144,9 @@ class InfluxOutputPlugin(OutputPluginFactory):
|
||||||
data_stack.append(f'{measurement},type=YieldToday value={data["yield_today"]/1000:.3f} {ctime}')
|
data_stack.append(f'{measurement},type=YieldToday value={data["yield_today"]/1000:.3f} {ctime}')
|
||||||
data_stack.append(f'{measurement},type=Efficiency value={data["efficiency"]:.2f} {ctime}')
|
data_stack.append(f'{measurement},type=Efficiency value={data["efficiency"]:.2f} {ctime}')
|
||||||
|
|
||||||
|
if HOYMILES_DEBUG_LOGGING:
|
||||||
|
#logging.debug(f'INFLUX data to DB: {data_stack}')
|
||||||
|
pass
|
||||||
self.api.write(self._bucket, self._org, data_stack)
|
self.api.write(self._bucket, self._org, data_stack)
|
||||||
|
|
||||||
class MqttOutputPlugin(OutputPluginFactory):
|
class MqttOutputPlugin(OutputPluginFactory):
|
||||||
|
@ -196,6 +208,12 @@ class MqttOutputPlugin(OutputPluginFactory):
|
||||||
def disco(self, **params):
|
def disco(self, **params):
|
||||||
self.client.loop_stop() # Stop loop
|
self.client.loop_stop() # Stop loop
|
||||||
self.client.disconnect() # disconnect
|
self.client.disconnect() # disconnect
|
||||||
|
return
|
||||||
|
|
||||||
|
def info2mqtt(self, mqtt_topic, mqtt_data):
|
||||||
|
for mqtt_key in mqtt_data:
|
||||||
|
self.client.publish(f'{mqtt_topic["topic"]}/{mqtt_key}', mqtt_data[mqtt_key], self.qos, self.ret)
|
||||||
|
return
|
||||||
|
|
||||||
def store_status(self, response, **params):
|
def store_status(self, response, **params):
|
||||||
"""
|
"""
|
||||||
|
@ -209,13 +227,18 @@ class MqttOutputPlugin(OutputPluginFactory):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
data = response.__dict__()
|
data = response.__dict__()
|
||||||
topic = f'{data.get("inverter_name", "hoymiles")}/{data.get("inverter_ser", None)}'
|
topic = params.get('topic', None)
|
||||||
|
if not topic:
|
||||||
|
topic = f'{data.get("inverter_name", "hoymiles")}/{data.get("inverter_ser", None)}'
|
||||||
|
|
||||||
|
if HOYMILES_DEBUG_LOGGING:
|
||||||
|
logging.info(f'MQTT-topic: {topic} data-type: {type(response)}')
|
||||||
|
|
||||||
if isinstance(response, StatusResponse):
|
if isinstance(response, StatusResponse):
|
||||||
|
|
||||||
# Global Head
|
# Global Head
|
||||||
if data['time'] is not None:
|
if data['time'] is not None:
|
||||||
self.client.publish(f'{topic}/time', data['time'].strftime("%d.%m.%y - %H:%M:%S"), self.qos, self.ret)
|
self.client.publish(f'{topic}/time', data['time'].strftime("%d.%m.%YT%H:%M:%S"), self.qos, self.ret)
|
||||||
|
|
||||||
# AC Data
|
# AC Data
|
||||||
phase_id = 0
|
phase_id = 0
|
||||||
|
@ -233,12 +256,16 @@ class MqttOutputPlugin(OutputPluginFactory):
|
||||||
string_id = 0
|
string_id = 0
|
||||||
string_sum_power = 0
|
string_sum_power = 0
|
||||||
for string in data['strings']:
|
for string in data['strings']:
|
||||||
self.client.publish(f'{topic}/emeter-dc/{string_id}/voltage', string['voltage'], self.qos, self.ret)
|
if 'name' in string:
|
||||||
self.client.publish(f'{topic}/emeter-dc/{string_id}/current', string['current'], self.qos, self.ret)
|
string_name = string['name'].replace(" ","_")
|
||||||
self.client.publish(f'{topic}/emeter-dc/{string_id}/power', string['power'], self.qos, self.ret)
|
else:
|
||||||
self.client.publish(f'{topic}/emeter-dc/{string_id}/YieldDay', string['energy_daily'], self.qos, self.ret)
|
string_name = string_id
|
||||||
self.client.publish(f'{topic}/emeter-dc/{string_id}/YieldTotal', string['energy_total']/1000, self.qos, self.ret)
|
self.client.publish(f'{topic}/emeter-dc/{string_name}/voltage', string['voltage'], self.qos, self.ret)
|
||||||
self.client.publish(f'{topic}/emeter-dc/{string_id}/Irradiation', string['irradiation'], self.qos, self.ret)
|
self.client.publish(f'{topic}/emeter-dc/{string_name}/current', string['current'], self.qos, self.ret)
|
||||||
|
self.client.publish(f'{topic}/emeter-dc/{string_name}/power', string['power'], self.qos, self.ret)
|
||||||
|
self.client.publish(f'{topic}/emeter-dc/{string_name}/YieldDay', string['energy_daily'], self.qos, self.ret)
|
||||||
|
self.client.publish(f'{topic}/emeter-dc/{string_name}/YieldTotal', string['energy_total']/1000, self.qos, self.ret)
|
||||||
|
self.client.publish(f'{topic}/emeter-dc/{string_name}/Irradiation', string['irradiation'], self.qos, self.ret)
|
||||||
string_id = string_id + 1
|
string_id = string_id + 1
|
||||||
string_sum_power += string['power']
|
string_sum_power += string['power']
|
||||||
|
|
||||||
|
@ -277,9 +304,10 @@ class VzInverterOutput:
|
||||||
self.channels = dict()
|
self.channels = dict()
|
||||||
|
|
||||||
for channel in config.get('channels', []):
|
for channel in config.get('channels', []):
|
||||||
uid = channel.get('uid')
|
uid = channel.get('uid', None)
|
||||||
ctype = channel.get('type')
|
ctype = channel.get('type')
|
||||||
if uid and ctype:
|
# if uid and ctype:
|
||||||
|
if ctype:
|
||||||
self.channels[ctype] = uid
|
self.channels[ctype] = uid
|
||||||
|
|
||||||
def store_status(self, data, session):
|
def store_status(self, data, session):
|
||||||
|
@ -295,6 +323,9 @@ class VzInverterOutput:
|
||||||
|
|
||||||
ts = int(round(data['time'].timestamp() * 1000))
|
ts = int(round(data['time'].timestamp() * 1000))
|
||||||
|
|
||||||
|
if HOYMILES_DEBUG_LOGGING:
|
||||||
|
logging.info(f'Volkszaehler-Timestamp: {ts}')
|
||||||
|
|
||||||
# AC Data
|
# AC Data
|
||||||
phase_id = 0
|
phase_id = 0
|
||||||
for phase in data['phases']:
|
for phase in data['phases']:
|
||||||
|
@ -327,13 +358,24 @@ class VzInverterOutput:
|
||||||
if data['yield_today'] is not None:
|
if data['yield_today'] is not None:
|
||||||
self.try_publish(ts, f'yield_today', data['yield_today'])
|
self.try_publish(ts, f'yield_today', data['yield_today'])
|
||||||
self.try_publish(ts, f'efficiency', data['efficiency'])
|
self.try_publish(ts, f'efficiency', data['efficiency'])
|
||||||
|
return
|
||||||
|
|
||||||
def try_publish(self, ts, ctype, value):
|
def try_publish(self, ts, ctype, value):
|
||||||
if not ctype in self.channels:
|
if not ctype in self.channels:
|
||||||
logging.warning(f'ctype \"{ctype}\" not found in ahoy.yml')
|
if HOYMILES_DEBUG_LOGGING:
|
||||||
|
logging.warning(f'ctype \"{ctype}\" not found in ahoy.yml')
|
||||||
return
|
return
|
||||||
|
|
||||||
uid = self.channels[ctype]
|
uid = self.channels[ctype]
|
||||||
url = f'{self.baseurl}/data/{uid}.json?operation=add&ts={ts}&value={value}'
|
url = f'{self.baseurl}/data/{uid}.json?operation=add&ts={ts}&value={value}'
|
||||||
|
if uid == None:
|
||||||
|
if HOYMILES_DEBUG_LOGGING:
|
||||||
|
logging.debug(f'ctype \"{ctype}\" has no configured uid-value in ahoy.yml')
|
||||||
|
return
|
||||||
|
|
||||||
|
if HOYMILES_DEBUG_LOGGING:
|
||||||
|
logging.debug(f'VZ-url: {url}')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
r = self.session.get(url)
|
r = self.session.get(url)
|
||||||
if r.status_code == 404:
|
if r.status_code == 404:
|
||||||
|
@ -344,6 +386,7 @@ class VzInverterOutput:
|
||||||
raise ValueError(f'Transmit result {url}')
|
raise ValueError(f'Transmit result {url}')
|
||||||
except ConnectionError as e:
|
except ConnectionError as e:
|
||||||
raise ValueError(f'Could not connect VZ-DB {type(e)} {e.keys()}')
|
raise ValueError(f'Could not connect VZ-DB {type(e)} {e.keys()}')
|
||||||
|
return
|
||||||
|
|
||||||
class VolkszaehlerOutputPlugin(OutputPluginFactory):
|
class VolkszaehlerOutputPlugin(OutputPluginFactory):
|
||||||
def __init__(self, config, **params):
|
def __init__(self, config, **params):
|
||||||
|
@ -364,13 +407,17 @@ class VolkszaehlerOutputPlugin(OutputPluginFactory):
|
||||||
exit(1)
|
exit(1)
|
||||||
|
|
||||||
self.session = requests.Session()
|
self.session = requests.Session()
|
||||||
self.inverters = dict()
|
|
||||||
|
|
||||||
|
self.inverters = dict()
|
||||||
for inverterconfig in config.get('inverters', []):
|
for inverterconfig in config.get('inverters', []):
|
||||||
serial = inverterconfig.get('serial')
|
serial = inverterconfig.get('serial')
|
||||||
output = VzInverterOutput(inverterconfig, self.session)
|
output = VzInverterOutput(inverterconfig, self.session)
|
||||||
self.inverters[serial] = output
|
self.inverters[serial] = output
|
||||||
|
|
||||||
|
def disco(self, **params):
|
||||||
|
self.session.close() # closing the connection
|
||||||
|
return
|
||||||
|
|
||||||
def store_status(self, response, **params):
|
def store_status(self, response, **params):
|
||||||
"""
|
"""
|
||||||
Publish StatusResponse object
|
Publish StatusResponse object
|
||||||
|
@ -395,3 +442,4 @@ class VolkszaehlerOutputPlugin(OutputPluginFactory):
|
||||||
output.store_status(data, self.session)
|
output.store_status(data, self.session)
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
logging.warning('Could not send data to volkszaehler instance: %s' % e)
|
logging.warning('Could not send data to volkszaehler instance: %s' % e)
|
||||||
|
return
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue