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
|
||||
src/config/config_override.h
|
||||
src/web/html/h/*
|
||||
src/web/html/tmp/*
|
||||
/**/Debug
|
||||
/**/v16/*
|
||||
*.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 | TSOL-M800de | | 1.0.10 | 2021 | 11-01 | 104 | | |
|
||||
| B5r1oJ0A9G | HM-800 | | 1.0.10 | 2021 | | 104 | | |
|
||||
| | | | | | | | | |
|
||||
| B5r1oJ0A9G | HM-800 | | 1.0.10 | 2021 | | 104 | | |
|
||||
| tomquist | TSOL-M1600 | | 1.0.12 | 2020 | 06-24 | 100 | | |
|
||||
| | | | | | | | | |
|
||||
|
||||
## Developer Information about Command Queue
|
||||
|
|
|
@ -2,6 +2,28 @@
|
|||
|
||||
(starting from release version `0.5.66`)
|
||||
|
||||
## 0.5.94
|
||||
* added ePaper (for ESP32 only!), thx @dAjaY85 #735
|
||||
* improved `/live` margins #732
|
||||
* renamed `var` to `VAr` #732
|
||||
|
||||
## 0.5.93
|
||||
* improved web API for `live`
|
||||
* added dark mode option
|
||||
* converted all forms to reponsive design
|
||||
* repaired menu with password protection #720, #716, #709
|
||||
* merged MI series fixes #729
|
||||
|
||||
## 0.5.92
|
||||
* fix mobile menu
|
||||
* fix inverters in select `serial.html` #709
|
||||
|
||||
## 0.5.91
|
||||
* improved html and navi, navi is visible even when API dies #660
|
||||
* reduced maximum allowed JSON size for API to 6000Bytes #660
|
||||
* small fix: output command at `prepareDevInformCmd` #692
|
||||
* improved inverter handling #671
|
||||
|
||||
## 0.5.90
|
||||
* merged PR #684, #698, #705
|
||||
* webserial minor overflow fix #660
|
||||
|
|
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();
|
||||
|
||||
/*DBGPRINTLN("--- start");
|
||||
DBGPRINTLN(String(ESP.getFreeHeap()));
|
||||
DBGPRINTLN(String(ESP.getHeapFragmentation()));
|
||||
DBGPRINTLN(String(ESP.getMaxFreeBlockSize()));*/
|
||||
|
||||
|
||||
mSettings.setup();
|
||||
mSettings.getPtr(mConfig);
|
||||
DPRINT(DBG_INFO, F("Settings valid: "));
|
||||
if(mSettings.getValid())
|
||||
if (mSettings.getValid())
|
||||
DBGPRINTLN(F("true"));
|
||||
else
|
||||
DBGPRINTLN(F("false"));
|
||||
|
@ -67,7 +61,7 @@ void app::setup() {
|
|||
mMiPayload.setup(this, &mSys, &mNrfRadio, &mStat, mConfig->nrf.maxRetransPerPyld, &mTimestamp);
|
||||
mMiPayload.enableSerialDebug(mConfig->serial.debug);
|
||||
|
||||
if(!mNrfRadio.isChipConnected())
|
||||
if(!mNrfRadio.isChipConnected())
|
||||
DPRINTLN(DBG_WARN, F("WARNING! your NRF24 module can't be reached, check the wiring"));
|
||||
}
|
||||
if(mConfig->cmt.enabled) {
|
||||
|
@ -81,6 +75,8 @@ void app::setup() {
|
|||
DBGPRINTLN(String(ESP.getHeapFragmentation()));
|
||||
DBGPRINTLN(String(ESP.getMaxFreeBlockSize()));*/
|
||||
|
||||
//if (!mSys.Radio.isChipConnected())
|
||||
// DPRINTLN(DBG_WARN, F("WARNING! your NRF24 module can't be reached, check the wiring"));
|
||||
|
||||
// when WiFi is in client mode, then enable mqtt broker
|
||||
#if !defined(AP_ONLY)
|
||||
|
@ -99,18 +95,18 @@ void app::setup() {
|
|||
mApi.setup(this, &mSys, &mNrfRadio, mWeb.getWebSrvPtr(), mConfig);
|
||||
|
||||
// Plugins
|
||||
if(mConfig->plugin.display.type != 0)
|
||||
mMonoDisplay.setup(&mConfig->plugin.display, &mSys, &mTimestamp, 0xff, mVersion);
|
||||
if (mConfig->plugin.display.type != 0)
|
||||
mDisplay.setup(&mConfig->plugin.display, &mSys, &mTimestamp, 0xff, mVersion);
|
||||
|
||||
mPubSerial.setup(mConfig, &mSys, &mTimestamp);
|
||||
|
||||
regularTickers();
|
||||
|
||||
|
||||
/*DBGPRINTLN("--- end setup");
|
||||
DBGPRINTLN(String(ESP.getFreeHeap()));
|
||||
DBGPRINTLN(String(ESP.getHeapFragmentation()));
|
||||
DBGPRINTLN(String(ESP.getMaxFreeBlockSize()));*/
|
||||
// DBGPRINTLN("--- end setup");
|
||||
// DBGPRINTLN(String(ESP.getFreeHeap()));
|
||||
// DBGPRINTLN(String(ESP.getHeapFragmentation()));
|
||||
// DBGPRINTLN(String(ESP.getMaxFreeBlockSize()));
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
@ -137,8 +133,8 @@ void app::loopStandard(void) {
|
|||
mStat.frmCnt++;
|
||||
|
||||
Inverter<> *iv = mSys.findInverter(&p->packet[1]);
|
||||
if(NULL != iv) {
|
||||
if(IV_HM == iv->ivGen)
|
||||
if (NULL != iv) {
|
||||
if (IV_HM == iv->ivGen)
|
||||
mPayload.add(iv, p);
|
||||
else
|
||||
mMiPayload.add(iv, p);
|
||||
|
@ -180,7 +176,7 @@ void app::loopStandard(void) {
|
|||
mHmsPayload.loop();
|
||||
#endif
|
||||
|
||||
if(mMqttEnabled)
|
||||
if (mMqttEnabled)
|
||||
mMqtt.loop();
|
||||
}
|
||||
|
||||
|
@ -194,7 +190,7 @@ void app::loopWifi(void) {
|
|||
void app::onWifi(bool gotIp) {
|
||||
DPRINTLN(DBG_DEBUG, F("onWifi"));
|
||||
ah::Scheduler::resetTicker();
|
||||
regularTickers(); // reinstall regular tickers
|
||||
regularTickers(); // reinstall regular tickers
|
||||
if (gotIp) {
|
||||
mInnerLoopCb = std::bind(&app::loopStandard, this);
|
||||
every(std::bind(&app::tickSend, this), mConfig->nrf.sendInterval, "tSend");
|
||||
|
@ -203,14 +199,13 @@ void app::onWifi(bool gotIp) {
|
|||
everySec(std::bind(&CmtRadioType::tickSecond, &mCmtRadio), "tsCmt");
|
||||
#endif
|
||||
mMqttReconnect = true;
|
||||
mSunrise = 0; // needs to be set to 0, to reinstall sunrise and ivComm tickers!
|
||||
mSunrise = 0; // needs to be set to 0, to reinstall sunrise and ivComm tickers!
|
||||
once(std::bind(&app::tickNtpUpdate, this), 2, "ntp2");
|
||||
if(WIFI_AP == WiFi.getMode()) {
|
||||
if (WIFI_AP == WiFi.getMode()) {
|
||||
mMqttEnabled = false;
|
||||
everySec(std::bind(&ahoywifi::tickWifiLoop, &mWifi), "wifiL");
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
mInnerLoopCb = std::bind(&app::loopWifi, this);
|
||||
everySec(std::bind(&ahoywifi::tickWifiLoop, &mWifi), "wifiL");
|
||||
}
|
||||
|
@ -221,8 +216,8 @@ void app::regularTickers(void) {
|
|||
DPRINTLN(DBG_DEBUG, F("regularTickers"));
|
||||
everySec(std::bind(&WebType::tickSecond, &mWeb), "webSc");
|
||||
// Plugins
|
||||
if(mConfig->plugin.display.type != 0)
|
||||
everySec(std::bind(&MonoDisplayType::tickerSecond, &mMonoDisplay), "disp");
|
||||
if (mConfig->plugin.display.type != 0)
|
||||
everySec(std::bind(&DisplayType::tickerSecond, &mDisplay), "disp");
|
||||
every(std::bind(&PubSerialType::tick, &mPubSerial), mConfig->serial.interval, "uart");
|
||||
}
|
||||
|
||||
|
@ -238,26 +233,26 @@ void app::tickNtpUpdate(void) {
|
|||
}
|
||||
|
||||
// only install schedulers once even if NTP wasn't successful in first loop
|
||||
if(mMqttReconnect) { // @TODO: mMqttReconnect is variable which scope has changed
|
||||
if(mConfig->inst.rstValsNotAvail)
|
||||
if (mMqttReconnect) { // @TODO: mMqttReconnect is variable which scope has changed
|
||||
if (mConfig->inst.rstValsNotAvail)
|
||||
everyMin(std::bind(&app::tickMinute, this), "tMin");
|
||||
if(mConfig->inst.rstYieldMidNight) {
|
||||
if (mConfig->inst.rstYieldMidNight) {
|
||||
uint32_t localTime = gTimezone.toLocal(mTimestamp);
|
||||
uint32_t midTrig = gTimezone.toUTC(localTime - (localTime % 86400) + 86400); // next midnight local time
|
||||
uint32_t midTrig = gTimezone.toUTC(localTime - (localTime % 86400) + 86400); // next midnight local time
|
||||
onceAt(std::bind(&app::tickMidnight, this), midTrig, "midNi");
|
||||
}
|
||||
}
|
||||
|
||||
nxtTrig = isOK ? 43200 : 60; // depending on NTP update success check again in 12 h or in 1 min
|
||||
nxtTrig = isOK ? 43200 : 60; // depending on NTP update success check again in 12 h or in 1 min
|
||||
|
||||
if((mSunrise == 0) && (mConfig->sun.lat) && (mConfig->sun.lon)) {
|
||||
if ((mSunrise == 0) && (mConfig->sun.lat) && (mConfig->sun.lon)) {
|
||||
mCalculatedTimezoneOffset = (int8_t)((mConfig->sun.lon >= 0 ? mConfig->sun.lon + 7.5 : mConfig->sun.lon - 7.5) / 15) * 3600;
|
||||
tickCalcSunrise();
|
||||
}
|
||||
|
||||
// immediately start communicating
|
||||
// @TODO: leads to reboot loops? not sure #674
|
||||
if(isOK && mSendFirst) {
|
||||
if (isOK && mSendFirst) {
|
||||
mSendFirst = false;
|
||||
once(std::bind(&app::tickSend, this), 2, "senOn");
|
||||
}
|
||||
|
@ -308,17 +303,17 @@ void app::tickIVCommunication(void) {
|
|||
void app::tickSun(void) {
|
||||
// only used and enabled by MQTT (see setup())
|
||||
if (!mMqtt.tickerSun(mSunrise, mSunset, mConfig->sun.offsetSec, mConfig->sun.disNightCom))
|
||||
once(std::bind(&app::tickSun, this), 1, "mqSun"); // MQTT not connected, retry
|
||||
once(std::bind(&app::tickSun, this), 1, "mqSun"); // MQTT not connected, retry
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void app::tickComm(void) {
|
||||
if((!mIVCommunicationOn) && (mConfig->inst.rstValsCommStop))
|
||||
if ((!mIVCommunicationOn) && (mConfig->inst.rstValsCommStop))
|
||||
once(std::bind(&app::tickZeroValues, this), mConfig->nrf.sendInterval, "tZero");
|
||||
|
||||
if (mMqttEnabled) {
|
||||
if (!mMqtt.tickerComm(!mIVCommunicationOn))
|
||||
once(std::bind(&app::tickComm, this), 5, "mqCom"); // MQTT not connected, retry after 5s
|
||||
once(std::bind(&app::tickComm, this), 5, "mqCom"); // MQTT not connected, retry after 5s
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -329,7 +324,7 @@ void app::tickZeroValues(void) {
|
|||
for (uint8_t id = 0; id < mSys.getNumInverters(); id++) {
|
||||
iv = mSys.getInverterByPos(id);
|
||||
if (NULL == iv)
|
||||
continue; // skip to next inverter
|
||||
continue; // skip to next inverter
|
||||
|
||||
mPayload.zeroInverterValues(iv);
|
||||
}
|
||||
|
@ -344,9 +339,9 @@ void app::tickMinute(void) {
|
|||
for (uint8_t id = 0; id < mSys.getNumInverters(); id++) {
|
||||
iv = mSys.getInverterByPos(id);
|
||||
if (NULL == iv)
|
||||
continue; // skip to next inverter
|
||||
continue; // skip to next inverter
|
||||
|
||||
if(!iv->isAvailable(mTimestamp) && !iv->isProducing(mTimestamp) && iv->config->enabled)
|
||||
if (!iv->isAvailable(mTimestamp) && !iv->isProducing(mTimestamp) && iv->config->enabled)
|
||||
mPayload.zeroInverterValues(iv);
|
||||
}
|
||||
}
|
||||
|
@ -355,7 +350,7 @@ void app::tickMinute(void) {
|
|||
void app::tickMidnight(void) {
|
||||
// only triggered if 'reset values at midnight is enabled'
|
||||
uint32_t localTime = gTimezone.toLocal(mTimestamp);
|
||||
uint32_t nxtTrig = gTimezone.toUTC(localTime - (localTime % 86400) + 86400); // next midnight local time
|
||||
uint32_t nxtTrig = gTimezone.toUTC(localTime - (localTime % 86400) + 86400); // next midnight local time
|
||||
onceAt(std::bind(&app::tickMidnight, this), nxtTrig, "mid2");
|
||||
|
||||
Inverter<> *iv;
|
||||
|
@ -363,7 +358,7 @@ void app::tickMidnight(void) {
|
|||
for (uint8_t id = 0; id < mSys.getNumInverters(); id++) {
|
||||
iv = mSys.getInverterByPos(id);
|
||||
if (NULL == iv)
|
||||
continue; // skip to next inverter
|
||||
continue; // skip to next inverter
|
||||
|
||||
mPayload.zeroInverterValues(iv);
|
||||
mPayload.zeroYieldDay(iv);
|
||||
|
@ -403,8 +398,8 @@ void app::tickSend(void) {
|
|||
} while ((NULL == iv) && ((maxLoop--) > 0));
|
||||
|
||||
if (NULL != iv) {
|
||||
if(iv->config->enabled) {
|
||||
if(iv->ivGen == IV_HM)
|
||||
if (iv->config->enabled) {
|
||||
if (iv->ivGen == IV_HM)
|
||||
mPayload.ivSend(iv);
|
||||
else if(iv->ivGen == IV_MI)
|
||||
mMiPayload.ivSend(iv);
|
||||
|
@ -457,25 +452,25 @@ void app::setupLed(void) {
|
|||
* PIN ---- |<----- 3.3V
|
||||
*
|
||||
* */
|
||||
if(mConfig->led.led0 != 0xff) {
|
||||
if (mConfig->led.led0 != 0xff) {
|
||||
pinMode(mConfig->led.led0, OUTPUT);
|
||||
digitalWrite(mConfig->led.led0, HIGH); // LED off
|
||||
digitalWrite(mConfig->led.led0, HIGH); // LED off
|
||||
}
|
||||
if(mConfig->led.led1 != 0xff) {
|
||||
if (mConfig->led.led1 != 0xff) {
|
||||
pinMode(mConfig->led.led1, OUTPUT);
|
||||
digitalWrite(mConfig->led.led1, HIGH); // LED off
|
||||
digitalWrite(mConfig->led.led1, HIGH); // LED off
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void app::updateLed(void) {
|
||||
if(mConfig->led.led0 != 0xff) {
|
||||
if (mConfig->led.led0 != 0xff) {
|
||||
Inverter<> *iv = mSys.getInverterByPos(0);
|
||||
if (NULL != iv) {
|
||||
if(iv->isProducing(mTimestamp))
|
||||
digitalWrite(mConfig->led.led0, LOW); // LED on
|
||||
if (iv->isProducing(mTimestamp))
|
||||
digitalWrite(mConfig->led.led0, LOW); // LED on
|
||||
else
|
||||
digitalWrite(mConfig->led.led0, HIGH); // LED off
|
||||
digitalWrite(mConfig->led.led0, HIGH); // LED off
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
31
src/app.h
31
src/app.h
|
@ -6,31 +6,29 @@
|
|||
#ifndef __APP_H__
|
||||
#define __APP_H__
|
||||
|
||||
|
||||
#include "utils/dbg.h"
|
||||
#include <Arduino.h>
|
||||
#include <ArduinoJson.h>
|
||||
#include <RF24.h>
|
||||
#include <RF24_config.h>
|
||||
|
||||
#include "appInterface.h"
|
||||
|
||||
#include "config/settings.h"
|
||||
#include "defines.h"
|
||||
#include "utils/crc.h"
|
||||
#include "utils/scheduler.h"
|
||||
|
||||
#include "hm/hmPayload.h"
|
||||
#include "hm/hmSystem.h"
|
||||
#include "hm/hmRadio.h"
|
||||
#include "hms/hmsRadio.h"
|
||||
#include "hms/hmsPayload.h"
|
||||
#include "hm/hmPayload.h"
|
||||
#include "hm/miPayload.h"
|
||||
#include "wifi/ahoywifi.h"
|
||||
#include "web/web.h"
|
||||
#include "web/RestApi.h"
|
||||
|
||||
#include "publisher/pubMqtt.h"
|
||||
#include "publisher/pubSerial.h"
|
||||
|
||||
#include "utils/crc.h"
|
||||
#include "utils/dbg.h"
|
||||
#include "utils/scheduler.h"
|
||||
#include "web/RestApi.h"
|
||||
#include "web/web.h"
|
||||
#include "wifi/ahoywifi.h"
|
||||
|
||||
// convert degrees and radians for sun calculation
|
||||
#define SIN(x) (sin(radians(x)))
|
||||
|
@ -49,12 +47,11 @@ typedef PubMqtt<HmSystemType> PubMqttType;
|
|||
typedef PubSerial<HmSystemType> PubSerialType;
|
||||
|
||||
// PLUGINS
|
||||
#include "plugins/MonochromeDisplay/MonochromeDisplay.h"
|
||||
typedef MonochromeDisplay<HmSystemType> MonoDisplayType;
|
||||
|
||||
#include "plugins/Display/Display.h"
|
||||
typedef Display<HmSystemType> DisplayType;
|
||||
|
||||
class app : public IApp, public ah::Scheduler {
|
||||
public:
|
||||
public:
|
||||
app();
|
||||
~app() {}
|
||||
|
||||
|
@ -225,7 +222,7 @@ class app : public IApp, public ah::Scheduler {
|
|||
mMqtt.payloadEventListener(cmd);
|
||||
#endif
|
||||
if(mConfig->plugin.display.type != 0)
|
||||
mMonoDisplay.payloadEventListener(cmd);
|
||||
mDisplay.payloadEventListener(cmd);
|
||||
}
|
||||
|
||||
void mqttSubRxCb(JsonObject obj);
|
||||
|
@ -301,7 +298,7 @@ class app : public IApp, public ah::Scheduler {
|
|||
uint32_t mSunrise, mSunset;
|
||||
|
||||
// plugins
|
||||
MonoDisplayType mMonoDisplay;
|
||||
DisplayType mDisplay;
|
||||
};
|
||||
|
||||
#endif /*__APP_H__*/
|
||||
|
|
|
@ -7,11 +7,12 @@
|
|||
#define __SETTINGS_H__
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <LittleFS.h>
|
||||
#include <ArduinoJson.h>
|
||||
#include <LittleFS.h>
|
||||
|
||||
#include "../defines.h"
|
||||
#include "../utils/dbg.h"
|
||||
#include "../utils/helper.h"
|
||||
#include "../defines.h"
|
||||
|
||||
/**
|
||||
* More info:
|
||||
|
@ -51,6 +52,7 @@ typedef struct {
|
|||
char deviceName[DEVNAME_LEN];
|
||||
char adminPwd[PWD_LEN];
|
||||
uint16_t protectionMask;
|
||||
bool darkMode;
|
||||
|
||||
// wifi
|
||||
char stationSsid[SSID_LEN];
|
||||
|
@ -84,7 +86,7 @@ typedef struct {
|
|||
typedef struct {
|
||||
float lat;
|
||||
float lon;
|
||||
bool disNightCom; // disable night communication
|
||||
bool disNightCom; // disable night communication
|
||||
uint16_t offsetSec;
|
||||
} cfgSun_t;
|
||||
|
||||
|
@ -95,8 +97,8 @@ typedef struct {
|
|||
} cfgSerial_t;
|
||||
|
||||
typedef struct {
|
||||
uint8_t led0; // first LED pin
|
||||
uint8_t led1; // second LED pin
|
||||
uint8_t led0; // first LED pin
|
||||
uint8_t led1; // second LED pin
|
||||
} cfgLed_t;
|
||||
|
||||
typedef struct {
|
||||
|
@ -113,7 +115,7 @@ typedef struct {
|
|||
char name[MAX_NAME_LENGTH];
|
||||
serial_u serial;
|
||||
uint16_t chMaxPwr[4];
|
||||
int32_t yieldCor[4]; // signed YieldTotal correction value
|
||||
int32_t yieldCor[4]; // signed YieldTotal correction value
|
||||
char chName[4][MAX_NAME_LENGTH];
|
||||
} cfgIv_t;
|
||||
|
||||
|
@ -129,14 +131,17 @@ typedef struct {
|
|||
typedef struct {
|
||||
uint8_t type;
|
||||
bool pwrSaveAtIvOffline;
|
||||
bool logoEn;
|
||||
bool pxShift;
|
||||
bool rot180;
|
||||
uint16_t wakeUp;
|
||||
uint16_t sleepAt;
|
||||
uint8_t rot;
|
||||
//uint16_t wakeUp;
|
||||
//uint16_t sleepAt;
|
||||
uint8_t contrast;
|
||||
uint8_t pin0;
|
||||
uint8_t pin1;
|
||||
uint8_t disp_data;
|
||||
uint8_t disp_clk;
|
||||
uint8_t disp_cs;
|
||||
uint8_t disp_reset;
|
||||
uint8_t disp_busy;
|
||||
uint8_t disp_dc;
|
||||
} display_t;
|
||||
|
||||
typedef struct {
|
||||
|
@ -228,7 +233,7 @@ class settings {
|
|||
else {
|
||||
//DPRINTLN(DBG_INFO, fp.readString());
|
||||
//fp.seek(0, SeekSet);
|
||||
DynamicJsonDocument root(4500);
|
||||
DynamicJsonDocument root(5500);
|
||||
DeserializationError err = deserializeJson(root, fp);
|
||||
if(!err && (root.size() > 0)) {
|
||||
mCfg.valid = true;
|
||||
|
@ -262,7 +267,7 @@ class settings {
|
|||
return false;
|
||||
}
|
||||
|
||||
DynamicJsonDocument json(4500);
|
||||
DynamicJsonDocument json(6500);
|
||||
JsonObject root = json.to<JsonObject>();
|
||||
jsonWifi(root.createNestedObject(F("wifi")), true);
|
||||
jsonNrf(root.createNestedObject(F("nrf")), true);
|
||||
|
@ -307,6 +312,7 @@ class settings {
|
|||
memset(&mCfg, 0, sizeof(settings_t));
|
||||
mCfg.sys.protectionMask = DEF_PROT_INDEX | DEF_PROT_LIVE | DEF_PROT_SERIAL | DEF_PROT_SETUP
|
||||
| DEF_PROT_UPDATE | DEF_PROT_SYSTEM | DEF_PROT_API | DEF_PROT_MQTT;
|
||||
mCfg.sys.darkMode = false;
|
||||
// restore temp settings
|
||||
if(keepWifi)
|
||||
memcpy(&mCfg.sys, &tmp, sizeof(cfgSys_t));
|
||||
|
@ -359,13 +365,16 @@ class settings {
|
|||
memset(&mCfg.inst, 0, sizeof(cfgInst_t));
|
||||
|
||||
mCfg.plugin.display.pwrSaveAtIvOffline = false;
|
||||
mCfg.plugin.display.contrast = 60;
|
||||
mCfg.plugin.display.logoEn = true;
|
||||
mCfg.plugin.display.pxShift = true;
|
||||
mCfg.plugin.display.rot180 = false;
|
||||
mCfg.plugin.display.pin0 = DEF_PIN_OFF; // SCL
|
||||
mCfg.plugin.display.pin1 = DEF_PIN_OFF; // SDA
|
||||
}
|
||||
mCfg.plugin.display.contrast = 60;
|
||||
mCfg.plugin.display.pxShift = true;
|
||||
mCfg.plugin.display.rot = 0;
|
||||
mCfg.plugin.display.disp_data = DEF_PIN_OFF; // SDA
|
||||
mCfg.plugin.display.disp_clk = DEF_PIN_OFF; // SCL
|
||||
mCfg.plugin.display.disp_cs = DEF_PIN_OFF;
|
||||
mCfg.plugin.display.disp_reset = DEF_PIN_OFF;
|
||||
mCfg.plugin.display.disp_busy = DEF_PIN_OFF;
|
||||
mCfg.plugin.display.disp_dc = DEF_PIN_OFF;
|
||||
}
|
||||
|
||||
void jsonWifi(JsonObject obj, bool set = false) {
|
||||
if(set) {
|
||||
|
@ -375,6 +384,7 @@ class settings {
|
|||
obj[F("dev")] = mCfg.sys.deviceName;
|
||||
obj[F("adm")] = mCfg.sys.adminPwd;
|
||||
obj[F("prot_mask")] = mCfg.sys.protectionMask;
|
||||
obj[F("dark")] = mCfg.sys.darkMode;
|
||||
ah::ip2Char(mCfg.sys.ip.ip, buf); obj[F("ip")] = String(buf);
|
||||
ah::ip2Char(mCfg.sys.ip.mask, buf); obj[F("mask")] = String(buf);
|
||||
ah::ip2Char(mCfg.sys.ip.dns1, buf); obj[F("dns1")] = String(buf);
|
||||
|
@ -386,6 +396,7 @@ class settings {
|
|||
snprintf(mCfg.sys.deviceName, DEVNAME_LEN, "%s", obj[F("dev")].as<const char*>());
|
||||
snprintf(mCfg.sys.adminPwd, PWD_LEN, "%s", obj[F("adm")].as<const char*>());
|
||||
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.mask, obj[F("mask")].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");
|
||||
disp[F("type")] = mCfg.plugin.display.type;
|
||||
disp[F("pwrSafe")] = (bool)mCfg.plugin.display.pwrSaveAtIvOffline;
|
||||
disp[F("logo")] = (bool)mCfg.plugin.display.logoEn;
|
||||
disp[F("pxShift")] = (bool)mCfg.plugin.display.pxShift;
|
||||
disp[F("rot180")] = (bool)mCfg.plugin.display.rot180;
|
||||
disp[F("wake")] = mCfg.plugin.display.wakeUp;
|
||||
disp[F("sleep")] = mCfg.plugin.display.sleepAt;
|
||||
disp[F("pxShift")] = (bool)mCfg.plugin.display.pxShift;
|
||||
disp[F("rotation")] = mCfg.plugin.display.rot;
|
||||
//disp[F("wake")] = mCfg.plugin.display.wakeUp;
|
||||
//disp[F("sleep")] = mCfg.plugin.display.sleepAt;
|
||||
disp[F("contrast")] = mCfg.plugin.display.contrast;
|
||||
disp[F("pin0")] = mCfg.plugin.display.pin0;
|
||||
disp[F("pin1")] = mCfg.plugin.display.pin1;
|
||||
disp[F("data")] = mCfg.plugin.display.disp_data;
|
||||
disp[F("clock")] = mCfg.plugin.display.disp_clk;
|
||||
disp[F("cs")] = mCfg.plugin.display.disp_cs;
|
||||
disp[F("reset")] = mCfg.plugin.display.disp_reset;
|
||||
disp[F("busy")] = mCfg.plugin.display.disp_busy;
|
||||
disp[F("dc")] = mCfg.plugin.display.disp_dc;
|
||||
} else {
|
||||
JsonObject disp = obj["disp"];
|
||||
mCfg.plugin.display.type = disp[F("type")];
|
||||
mCfg.plugin.display.pwrSaveAtIvOffline = (bool) disp[F("pwrSafe")];
|
||||
mCfg.plugin.display.logoEn = (bool) disp[F("logo")];
|
||||
mCfg.plugin.display.pxShift = (bool) disp[F("pxShift")];
|
||||
mCfg.plugin.display.rot180 = (bool) disp[F("rot180")];
|
||||
mCfg.plugin.display.wakeUp = disp[F("wake")];
|
||||
mCfg.plugin.display.sleepAt = disp[F("sleep")];
|
||||
mCfg.plugin.display.contrast = disp[F("contrast")];
|
||||
mCfg.plugin.display.pin0 = disp[F("pin0")];
|
||||
mCfg.plugin.display.pin1 = disp[F("pin1")];
|
||||
mCfg.plugin.display.type = disp[F("type")];
|
||||
mCfg.plugin.display.pwrSaveAtIvOffline = (bool)disp[F("pwrSafe")];
|
||||
mCfg.plugin.display.pxShift = (bool)disp[F("pxShift")];
|
||||
mCfg.plugin.display.rot = disp[F("rotation")];
|
||||
//mCfg.plugin.display.wakeUp = disp[F("wake")];
|
||||
//mCfg.plugin.display.sleepAt = disp[F("sleep")];
|
||||
mCfg.plugin.display.contrast = disp[F("contrast")];
|
||||
mCfg.plugin.display.disp_data = disp[F("data")];
|
||||
mCfg.plugin.display.disp_clk = disp[F("clock")];
|
||||
mCfg.plugin.display.disp_cs = disp[F("cs")];
|
||||
mCfg.plugin.display.disp_reset = disp[F("reset")];
|
||||
mCfg.plugin.display.disp_busy = disp[F("busy")];
|
||||
mCfg.plugin.display.disp_dc = disp[F("dc")];
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
//-------------------------------------
|
||||
#define VERSION_MAJOR 0
|
||||
#define VERSION_MINOR 5
|
||||
#define VERSION_PATCH 90
|
||||
#define VERSION_PATCH 94
|
||||
|
||||
//-------------------------------------
|
||||
typedef struct {
|
||||
|
|
|
@ -29,6 +29,10 @@ const char* const fields[] = {"U_DC", "I_DC", "P_DC", "YieldDay", "YieldWeek", "
|
|||
"active_PowerLimit", /*"reactivePowerLimit","Powerfactor",*/ "LastAlarmCode"};
|
||||
const char* const notAvail = "n/a";
|
||||
|
||||
const uint8_t fieldUnits[] = {UNIT_V, UNIT_A, UNIT_W, UNIT_WH, UNIT_KWH, UNIT_KWH,
|
||||
UNIT_V, UNIT_A, UNIT_W, UNIT_HZ, UNIT_C, UNIT_NONE, UNIT_PCT, UNIT_PCT, UNIT_VAR,
|
||||
UNIT_NONE, UNIT_NONE, UNIT_NONE, UNIT_NONE, UNIT_NONE, UNIT_NONE, UNIT_PCT, UNIT_NONE};
|
||||
|
||||
// mqtt discovery device classes
|
||||
enum {DEVICE_CLS_NONE = 0, DEVICE_CLS_CURRENT, DEVICE_CLS_ENERGY, DEVICE_CLS_PWR, DEVICE_CLS_VOLTAGE, DEVICE_CLS_FREQ, DEVICE_CLS_TEMP};
|
||||
const char* const deviceClasses[] = {0, "current", "energy", "power", "voltage", "frequency", "temperature"};
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
#include "../utils/dbg.h"
|
||||
#include "../utils/crc.h"
|
||||
#include "../config/config.h"
|
||||
#include "hmRadio.h"
|
||||
#include <Arduino.h>
|
||||
|
||||
typedef struct {
|
||||
|
@ -158,7 +159,8 @@ class HmPayload {
|
|||
uint8_t cmd = iv->getQueuedCmd();
|
||||
DPRINT(DBG_INFO, F("(#"));
|
||||
DBGPRINT(String(iv->id));
|
||||
DBGPRINT(F(") prepareDevInformCmd")); // + String(cmd, HEX));
|
||||
DBGPRINT(F(") prepareDevInformCmd 0x"));
|
||||
DBGPRINTLN(String(cmd, HEX));
|
||||
mRadio->prepareDevInformCmd(iv->radioId.u64, cmd, mPayload[iv->id].ts, iv->alarmMesIndex, false);
|
||||
mPayload[iv->id].txCmd = cmd;
|
||||
}
|
||||
|
|
|
@ -92,14 +92,14 @@ class MiPayload {
|
|||
}
|
||||
|
||||
void add(Inverter<> *iv, packet_t *p) {
|
||||
DPRINTLN(DBG_INFO, F("MI got data [0]=") + String(p->packet[0], HEX));
|
||||
//DPRINTLN(DBG_INFO, F("MI got data [0]=") + String(p->packet[0], HEX));
|
||||
|
||||
if (p->packet[0] == (0x08 + ALL_FRAMES)) { // 0x88; MI status response to 0x09
|
||||
mPayload[iv->id].stsa = true;
|
||||
miStsDecode(iv, p);
|
||||
} else if (p->packet[0] == (0x11 + SINGLE_FRAME)) { // 0x92; MI status response to 0x11
|
||||
mPayload[iv->id].stsb = true;
|
||||
miStsDecode(iv, p, 2);
|
||||
miStsDecode(iv, p, CH2);
|
||||
} else if (p->packet[0] == (0x09 + ALL_FRAMES)) { // MI data response to 0x09
|
||||
mPayload[iv->id].txId = p->packet[0];
|
||||
miDataDecode(iv,p);
|
||||
|
@ -359,12 +359,11 @@ class MiPayload {
|
|||
(mCbMiPayload)(val);
|
||||
}
|
||||
|
||||
void miStsDecode(Inverter<> *iv, packet_t *p, uint8_t chan = 1) {
|
||||
void miStsDecode(Inverter<> *iv, packet_t *p, uint8_t chan = CH1) {
|
||||
DPRINTLN(DBG_INFO, F("Inverter ") + String(iv->id) + F(": status msg 0x") + String(p->packet[0], HEX));
|
||||
record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug); // choose the record structure
|
||||
rec->ts = mPayload[iv->id].ts;
|
||||
|
||||
int8_t offset = -2;
|
||||
|
||||
//iv->setValue(iv->getPosByChFld(chan, FLD_YD, rec), rec, (int)((p->packet[11+6] << 8) + p->packet[12+6])); // was 11/12, might be wrong!
|
||||
|
||||
//if (INV_TYPE_1CH == iv->type)
|
||||
|
@ -372,16 +371,21 @@ class MiPayload {
|
|||
|
||||
//iv->setValue(iv->getPosByChFld(chan, FLD_EVT, rec), rec, (int)((p->packet[13] << 8) + p->packet[14]));
|
||||
|
||||
iv->setValue(iv->getPosByChFld(0, FLD_EVT, rec), rec, (int)((p->packet[11+offset] << 8) + p->packet[12+offset]));
|
||||
iv->setValue(iv->getPosByChFld(0, FLD_EVT, rec), rec, (int)((p->packet[11] << 8) + p->packet[12]));
|
||||
//iv->setValue(iv->getPosByChFld(0, FLD_EVT, rec), rec, (int)((p->packet[14] << 8) + p->packet[16]));
|
||||
if (iv->alarmMesIndex < rec->record[iv->getPosByChFld(0, FLD_EVT, rec)]){
|
||||
iv->alarmMesIndex = rec->record[iv->getPosByChFld(0, FLD_EVT, rec)];
|
||||
iv->alarmMesIndex = rec->record[iv->getPosByChFld(0, FLD_EVT, rec)]; // seems there's no status per channel in 3rd gen. models?!?
|
||||
|
||||
DPRINTLN(DBG_INFO, "alarm ID incremented to " + String(iv->alarmMesIndex));
|
||||
iv->enqueCommand<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){
|
||||
STAT = (int)((p->packet[11] << 8) + p->packet[12]);
|
||||
FCNT = (int)((p->packet[13] << 8) + p->packet[14]);
|
||||
|
@ -393,23 +397,22 @@ class MiPayload {
|
|||
#endif
|
||||
}
|
||||
*/
|
||||
DPRINTLN(DBG_INFO, F("Inverter ") + String(iv->id) + F(": status msg ") + p->packet[0]);
|
||||
}
|
||||
|
||||
void miDataDecode(Inverter<> *iv, packet_t *p) {
|
||||
record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug); // choose the parser
|
||||
rec->ts = mPayload[iv->id].ts;
|
||||
|
||||
uint8_t chan = ( p->packet[2] == 0x89 || p->packet[2] == (0x36 + ALL_FRAMES) ) ? 1 :
|
||||
( p->packet[2] == 0x91 || p->packet[2] == (0x37 + ALL_FRAMES) ) ? 2 :
|
||||
p->packet[2] == (0x38 + ALL_FRAMES) ? 3 :
|
||||
4;
|
||||
uint8_t datachan = ( p->packet[0] == 0x89 || p->packet[0] == (0x36 + ALL_FRAMES) ) ? CH1 :
|
||||
( p->packet[0] == 0x91 || p->packet[0] == (0x37 + ALL_FRAMES) ) ? CH2 :
|
||||
p->packet[0] == (0x38 + ALL_FRAMES) ? CH3 :
|
||||
CH4;
|
||||
int8_t offset = -2;
|
||||
// U_DC = (float) ((p->packet[11] << 8) + p->packet[12])/10;
|
||||
iv->setValue(iv->getPosByChFld(chan, FLD_UDC, rec), rec, (float)((p->packet[11+offset] << 8) + p->packet[12+offset])/10);
|
||||
iv->setValue(iv->getPosByChFld(datachan, FLD_UDC, rec), rec, (float)((p->packet[11+offset] << 8) + p->packet[12+offset])/10);
|
||||
yield();
|
||||
// I_DC = (float) ((p->packet[13] << 8) + p->packet[14])/10;
|
||||
iv->setValue(iv->getPosByChFld(chan, FLD_IDC, rec), rec, (float)((p->packet[13+offset] << 8) + p->packet[14+offset])/10);
|
||||
iv->setValue(iv->getPosByChFld(datachan, FLD_IDC, rec), rec, (float)((p->packet[13+offset] << 8) + p->packet[14+offset])/10);
|
||||
yield();
|
||||
// U_AC = (float) ((p->packet[15] << 8) + p->packet[16])/10;
|
||||
iv->setValue(iv->getPosByChFld(0, FLD_UAC, rec), rec, (float)((p->packet[15+offset] << 8) + p->packet[16+offset])/10);
|
||||
|
@ -418,23 +421,23 @@ class MiPayload {
|
|||
//iv->setValue(iv->getPosByChFld(0, FLD_IAC, rec), rec, (float)((p->packet[17+offset] << 8) + p->packet[18+offset])/100);
|
||||
//yield();
|
||||
// P_DC = (float)((p->packet[19] << 8) + p->packet[20])/10;
|
||||
iv->setValue(iv->getPosByChFld(chan, FLD_PDC, rec), rec, (float)((p->packet[19+offset] << 8) + p->packet[20+offset])/10);
|
||||
iv->setValue(iv->getPosByChFld(datachan, FLD_PDC, rec), rec, (float)((p->packet[19+offset] << 8) + p->packet[20+offset])/10);
|
||||
yield();
|
||||
// Q_DC = (float)((p->packet[21] << 8) + p->packet[22])/1;
|
||||
iv->setValue(iv->getPosByChFld(chan, FLD_Q, rec), rec, (float)((p->packet[21+offset] << 8) + p->packet[22+offset])/1);
|
||||
iv->setValue(iv->getPosByChFld(datachan, FLD_YD, rec), rec, (float)((p->packet[21+offset] << 8) + p->packet[22+offset])/1);
|
||||
yield();
|
||||
iv->setValue(iv->getPosByChFld(0, FLD_T, rec), rec, (float) ((int16_t)(p->packet[23+offset] << 8) + p->packet[24+offset])/10);
|
||||
iv->setValue(iv->getPosByChFld(0, FLD_F, rec), rec, (float) ((p->packet[17+offset] << 8) + p->packet[18+offset])/100); //23 is freq or IAC?
|
||||
iv->setValue(iv->getPosByChFld(0, FLD_T, rec), rec, (float) ((int16_t)(p->packet[23+offset] << 8) + p->packet[24+offset])/10); //23 is freq or IAC?
|
||||
iv->setValue(iv->getPosByChFld(0, FLD_F, rec), rec, (float) ((p->packet[17+offset] << 8) + p->packet[18+offset])/100);
|
||||
iv->setValue(iv->getPosByChFld(0, FLD_IRR, rec), rec, (float) (calcIrradiation(iv, datachan)));
|
||||
yield();
|
||||
//FLD_YD
|
||||
|
||||
if (p->packet[2] >= (0x36 + ALL_FRAMES) ) {
|
||||
if (p->packet[0] >= (0x36 + ALL_FRAMES) ) {
|
||||
/*status message analysis most liklely needs to be changed, see MiStsMst*/
|
||||
/*STAT = (uint8_t)(p->packet[25] );
|
||||
FCNT = (uint8_t)(p->packet[26]);
|
||||
FCODE = (uint8_t)(p->packet[27]); // MI300: (int)((p->packet[15] << 8) + p->packet[16]); */
|
||||
//iv->setValue(iv->getPosByChFld(chan, FLD_YD, rec), rec, (uint8_t)(p->packet[25]));
|
||||
//iv->setValue(iv->getPosByChFld(chan, FLD_EVT, rec), rec, (uint8_t)(p->packet[27]));
|
||||
iv->setValue(iv->getPosByChFld(0, FLD_EVT, rec), rec, (uint8_t)(p->packet[21+offset]));
|
||||
FCODE = (uint8_t)(p->packet[27]); // MI300/Mi600 stsMsg:: (int)((p->packet[15] << 8) + p->packet[16]); */
|
||||
iv->setValue(iv->getPosByChFld(0, FLD_EVT, rec), rec, (uint8_t)(p->packet[25+offset]));
|
||||
yield();
|
||||
if (iv->alarmMesIndex < rec->record[iv->getPosByChFld(0, FLD_EVT, rec)]){
|
||||
iv->alarmMesIndex = rec->record[iv->getPosByChFld(0, FLD_EVT, rec)];
|
||||
|
@ -443,8 +446,8 @@ class MiPayload {
|
|||
iv->enqueCommand<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();
|
||||
notify(mPayload[iv->id].txCmd);
|
||||
/*
|
||||
|
@ -502,7 +505,7 @@ class MiPayload {
|
|||
FCODE = (uint8_t)(p->packet[27]);
|
||||
}
|
||||
*/
|
||||
DPRINTLN(DBG_INFO, F("Inverter ") + String(iv->id) + F(": data msg ") + p->packet[0]);
|
||||
DPRINTLN(DBG_INFO, F("Inverter ") + String(iv->id) + F(": data msg 0x") + String(p->packet[0], HEX) + F(" channel ") + datachan);
|
||||
}
|
||||
|
||||
bool build(uint8_t id, bool *complete) {
|
||||
|
|
|
@ -15,6 +15,7 @@ include_dir = .
|
|||
[env]
|
||||
framework = arduino
|
||||
board_build.filesystem = littlefs
|
||||
upload_speed = 921600
|
||||
|
||||
;build_flags =
|
||||
; ;;;;; Possible Debug options ;;;;;;
|
||||
|
@ -40,6 +41,7 @@ lib_deps =
|
|||
bblanchon/ArduinoJson
|
||||
https://github.com/JChristensen/Timezone
|
||||
olikraus/U8g2
|
||||
zinggjm/GxEPD2@^1.5.0
|
||||
;esp8266/DNSServer
|
||||
;esp8266/EEPROM
|
||||
;esp8266/ESP8266WiFi
|
||||
|
@ -54,8 +56,9 @@ board_build.f_cpu = 80000000L
|
|||
build_flags = -D RELEASE
|
||||
monitor_filters =
|
||||
;default ; Remove typical terminal control codes from input
|
||||
time ; Add timestamp with milliseconds for each new line
|
||||
;time ; Add timestamp with milliseconds for each new line
|
||||
;log2file ; Log data to a file “platformio-device-monitor-*.log” located in the current working directory
|
||||
esp8266_exception_decoder
|
||||
|
||||
[env:esp8266-debug]
|
||||
platform = espressif8266
|
||||
|
@ -98,8 +101,9 @@ build_flags = -D RELEASE -std=gnu++14
|
|||
build_unflags = -std=gnu++11
|
||||
monitor_filters =
|
||||
;default ; Remove typical terminal control codes from input
|
||||
time ; Add timestamp with milliseconds for each new line
|
||||
;time ; Add timestamp with milliseconds for each new line
|
||||
;log2file ; Log data to a file “platformio-device-monitor-*.log” located in the current working directory
|
||||
esp32_exception_decoder
|
||||
|
||||
[env:esp32-wroom32-debug]
|
||||
platform = espressif32
|
||||
|
|
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];
|
||||
}
|
||||
|
||||
bool processIvStatus() {
|
||||
bool processIvStatus() {
|
||||
// returns true if any inverter is available
|
||||
bool allAvail = true; // shows if all enabled inverters are available
|
||||
bool anyAvail = false; // shows if at least one enabled inverter is available
|
||||
|
@ -419,17 +419,19 @@ class PubMqtt {
|
|||
iv = mSys->getInverterByPos(id);
|
||||
if (NULL == iv)
|
||||
continue; // skip to next inverter
|
||||
if (!iv->config->enabled)
|
||||
continue; // skip to next inverter
|
||||
|
||||
rec = iv->getRecordStruct(RealTimeRunData_Debug);
|
||||
|
||||
// inverter status
|
||||
uint8_t status = MQTT_STATUS_NOT_AVAIL_NOT_PROD;
|
||||
if (iv->config->enabled) {
|
||||
if (iv->isAvailable(*mUtcTimestamp))
|
||||
status = (iv->isProducing(*mUtcTimestamp)) ? MQTT_STATUS_AVAIL_PROD : MQTT_STATUS_AVAIL_NOT_PROD;
|
||||
else // inverter is enabled but not available
|
||||
allAvail = false;
|
||||
if (iv->isAvailable(*mUtcTimestamp)) {
|
||||
anyAvail = true;
|
||||
status = (iv->isProducing(*mUtcTimestamp)) ? MQTT_STATUS_AVAIL_PROD : MQTT_STATUS_AVAIL_NOT_PROD;
|
||||
}
|
||||
else // inverter is enabled but not available
|
||||
allAvail = false;
|
||||
|
||||
if(mLastIvState[id] != status) {
|
||||
// if status changed from producing to not producing send last data immediately
|
||||
|
@ -439,11 +441,11 @@ class PubMqtt {
|
|||
mLastIvState[id] = status;
|
||||
changed = true;
|
||||
|
||||
snprintf(topic, 32 + MAX_NAME_LENGTH, "%s/%s", iv->config->name, mqttStr[MQTT_STR_AVAILABLE]);
|
||||
snprintf(topic, 32 + MAX_NAME_LENGTH, "%s/available", iv->config->name);
|
||||
snprintf(val, 40, "%d", status);
|
||||
publish(topic, val, true);
|
||||
|
||||
snprintf(topic, 32 + MAX_NAME_LENGTH, "%s/%s", iv->config->name, mqttStr[MQTT_STR_LAST_SUCCESS]);
|
||||
snprintf(topic, 32 + MAX_NAME_LENGTH, "%s/last_success", iv->config->name);
|
||||
snprintf(val, 40, "%d", iv->getLastTs(rec));
|
||||
publish(topic, val, true);
|
||||
}
|
||||
|
@ -451,7 +453,7 @@ class PubMqtt {
|
|||
|
||||
if(changed) {
|
||||
snprintf(val, 32, "%d", ((allAvail) ? MQTT_STATUS_ONLINE : ((anyAvail) ? MQTT_STATUS_PARTIAL : MQTT_STATUS_OFFLINE)));
|
||||
publish(subtopics[MQTT_STATUS], val, true);
|
||||
publish("status", val, true);
|
||||
}
|
||||
|
||||
return anyAvail;
|
||||
|
@ -474,24 +476,26 @@ class PubMqtt {
|
|||
char topic[7 + MQTT_TOPIC_LEN], val[40];
|
||||
record_t<> *rec = iv->getRecordStruct(curInfoCmd);
|
||||
|
||||
for (uint8_t i = 0; i < rec->length; i++) {
|
||||
bool retained = false;
|
||||
if (curInfoCmd == RealTimeRunData_Debug) {
|
||||
switch (rec->assign[i].fieldId) {
|
||||
case FLD_YT:
|
||||
case FLD_YD:
|
||||
if ((rec->assign[i].ch == CH0) && (!iv->isProducing(*mUtcTimestamp))) // avoids returns to 0 on restart
|
||||
continue;
|
||||
retained = true;
|
||||
break;
|
||||
if (iv->getLastTs(rec) > 0) {
|
||||
for (uint8_t i = 0; i < rec->length; i++) {
|
||||
bool retained = false;
|
||||
if (curInfoCmd == RealTimeRunData_Debug) {
|
||||
switch (rec->assign[i].fieldId) {
|
||||
case FLD_YT:
|
||||
case FLD_YD:
|
||||
if ((rec->assign[i].ch == CH0) && (!iv->isProducing(*mUtcTimestamp))) // avoids returns to 0 on restart
|
||||
continue;
|
||||
retained = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
snprintf(topic, 32 + MAX_NAME_LENGTH, "%s/ch%d/%s", iv->config->name, rec->assign[i].ch, fields[rec->assign[i].fieldId]);
|
||||
snprintf(val, 40, "%g", ah::round3(iv->getValue(i, rec)));
|
||||
publish(topic, val, retained);
|
||||
|
||||
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();
|
||||
|
||||
if ((curInfoCmd != RealTimeRunData_Debug) || !RTRDataHasBeenSent) { // send RTR Data only once
|
||||
bool sendTotals = (curInfoCmd == RealTimeRunData_Debug);
|
||||
|
||||
for (uint8_t id = 0; id < mSys->getNumInverters(); id++) {
|
||||
Inverter<> *iv = mSys->getInverterByPos(id);
|
||||
if (NULL == iv)
|
||||
continue; // skip to next inverter
|
||||
if (!iv->config->enabled)
|
||||
continue; // skip to next inverter
|
||||
|
||||
// send RTR Data only if status is available
|
||||
if ((curInfoCmd != RealTimeRunData_Debug) || (MQTT_STATUS_AVAIL_PROD == mLastIvState[id]))
|
||||
if ((curInfoCmd != RealTimeRunData_Debug) || (MQTT_STATUS_NOT_AVAIL_NOT_PROD != mLastIvState[id]))
|
||||
sendData(iv, curInfoCmd);
|
||||
|
||||
// calculate total values for RealTimeRunData_Debug
|
||||
if (curInfoCmd == RealTimeRunData_Debug) {
|
||||
if (sendTotals) {
|
||||
record_t<> *rec = iv->getRecordStruct(curInfoCmd);
|
||||
|
||||
for (uint8_t i = 0; i < rec->length; i++) {
|
||||
if (CH0 == rec->assign[i].ch) {
|
||||
switch (rec->assign[i].fieldId) {
|
||||
case FLD_PAC:
|
||||
total[0] += iv->getValue(i, rec);
|
||||
break;
|
||||
case FLD_YT:
|
||||
total[1] += iv->getValue(i, rec);
|
||||
break;
|
||||
case FLD_YD:
|
||||
total[2] += iv->getValue(i, rec);
|
||||
break;
|
||||
case FLD_PDC:
|
||||
total[3] += iv->getValue(i, rec);
|
||||
break;
|
||||
sendTotals &= (iv->getLastTs(rec) > 0);
|
||||
if (sendTotals) {
|
||||
for (uint8_t i = 0; i < rec->length; i++) {
|
||||
if (CH0 == rec->assign[i].ch) {
|
||||
switch (rec->assign[i].fieldId) {
|
||||
case FLD_PAC:
|
||||
total[0] += iv->getValue(i, rec);
|
||||
break;
|
||||
case FLD_YT:
|
||||
total[1] += iv->getValue(i, rec);
|
||||
break;
|
||||
case FLD_YD:
|
||||
total[2] += iv->getValue(i, rec);
|
||||
break;
|
||||
case FLD_PDC:
|
||||
total[3] += iv->getValue(i, rec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
yield();
|
||||
}
|
||||
yield();
|
||||
}
|
||||
|
||||
if (curInfoCmd == RealTimeRunData_Debug) {
|
||||
if (sendTotals) {
|
||||
uint8_t fieldId;
|
||||
for (uint8_t i = 0; i < 4; i++) {
|
||||
switch (i) {
|
||||
|
@ -565,7 +576,7 @@ class PubMqtt {
|
|||
fieldId = FLD_PDC;
|
||||
break;
|
||||
}
|
||||
snprintf(topic, 32 + MAX_NAME_LENGTH, "%s/%s", mqttStr[MQTT_STR_TOTAL], fields[fieldId]);
|
||||
snprintf(topic, 32 + MAX_NAME_LENGTH, "total/%s", fields[fieldId]);
|
||||
snprintf(val, 40, "%g", ah::round3(total[i]));
|
||||
publish(topic, val, true);
|
||||
}
|
||||
|
|
|
@ -8,22 +8,24 @@
|
|||
|
||||
#include "../utils/dbg.h"
|
||||
#ifdef ESP32
|
||||
#include "AsyncTCP.h"
|
||||
#include "AsyncTCP.h"
|
||||
#else
|
||||
#include "ESPAsyncTCP.h"
|
||||
#include "ESPAsyncTCP.h"
|
||||
#endif
|
||||
#include "ESPAsyncWebServer.h"
|
||||
#include "AsyncJson.h"
|
||||
#include "../appInterface.h"
|
||||
#include "../hm/hmSystem.h"
|
||||
#include "../utils/helper.h"
|
||||
|
||||
#include "../appInterface.h"
|
||||
#include "AsyncJson.h"
|
||||
#include "ESPAsyncWebServer.h"
|
||||
|
||||
#if defined(F) && defined(ESP32)
|
||||
#undef F
|
||||
#define F(sl) (sl)
|
||||
#undef F
|
||||
#define F(sl) (sl)
|
||||
#endif
|
||||
|
||||
const uint8_t acList[] = {FLD_UAC, FLD_IAC, FLD_PAC, FLD_F, FLD_PF, FLD_T, FLD_YT, FLD_YD, FLD_PDC, FLD_EFF, FLD_Q};
|
||||
const uint8_t dcList[] = {FLD_UDC, FLD_IDC, FLD_PDC, FLD_YD, FLD_YT, FLD_IRR};
|
||||
|
||||
template<class HMSYSTEM, class HMRADIO>
|
||||
class RestApi {
|
||||
public:
|
||||
|
@ -72,7 +74,7 @@ class RestApi {
|
|||
mHeapFrag = ESP.getHeapFragmentation();
|
||||
#endif
|
||||
|
||||
AsyncJsonResponse* response = new AsyncJsonResponse(false, 8192);
|
||||
AsyncJsonResponse* response = new AsyncJsonResponse(false, 6000);
|
||||
JsonObject root = response->getRoot();
|
||||
|
||||
String path = request->url().substring(5);
|
||||
|
@ -84,7 +86,6 @@ class RestApi {
|
|||
else if(path == "reboot") getReboot(root);
|
||||
else if(path == "statistics") getStatistics(root);
|
||||
else if(path == "inverter/list") getInverterList(root);
|
||||
else if(path == "menu") getMenu(root);
|
||||
else if(path == "index") getIndex(root);
|
||||
else if(path == "setup") getSetup(root);
|
||||
else if(path == "setup/networks") getNetworks(root);
|
||||
|
@ -93,8 +94,12 @@ class RestApi {
|
|||
else if(path == "record/alarm") getRecord(root, AlarmData);
|
||||
else if(path == "record/config") getRecord(root, SystemConfigPara);
|
||||
else if(path == "record/live") getRecord(root, RealTimeRunData_Debug);
|
||||
else
|
||||
getNotFound(root, F("http://") + request->host() + F("/api/"));
|
||||
else {
|
||||
if(path.substring(0, 12) == "inverter/id/")
|
||||
getInverter(root, request->url().substring(17).toInt());
|
||||
else
|
||||
getNotFound(root, F("http://") + request->host() + F("/api/"));
|
||||
}
|
||||
|
||||
//DPRINTLN(DBG_INFO, "API mem usage: " + String(root.memoryUsage()));
|
||||
response->addHeader("Access-Control-Allow-Origin", "*");
|
||||
|
@ -154,13 +159,14 @@ class RestApi {
|
|||
ep[F("record/live")] = url + F("record/live");
|
||||
}
|
||||
|
||||
|
||||
void onDwnldSetup(AsyncWebServerRequest *request) {
|
||||
AsyncWebServerResponse *response;
|
||||
|
||||
File fp = LittleFS.open("/settings.json", "r");
|
||||
if(!fp) {
|
||||
DPRINTLN(DBG_ERROR, F("failed to load settings"));
|
||||
response = request->beginResponse(200, F("application/json"), "{}");
|
||||
response = request->beginResponse(200, F("application/json; charset=utf-8"), "{}");
|
||||
}
|
||||
else {
|
||||
String tmp = fp.readString();
|
||||
|
@ -173,7 +179,7 @@ class RestApi {
|
|||
tmp.remove(i, tmp.indexOf("\"", i)-i);
|
||||
}
|
||||
}
|
||||
response = request->beginResponse(200, F("application/json"), tmp);
|
||||
response = request->beginResponse(200, F("application/json; charset=utf-8"), tmp);
|
||||
}
|
||||
|
||||
response->addHeader("Content-Type", "application/octet-stream");
|
||||
|
@ -184,10 +190,11 @@ class RestApi {
|
|||
}
|
||||
|
||||
void getGeneric(JsonObject obj) {
|
||||
obj[F("version")] = String(mApp->getVersion());
|
||||
obj[F("build")] = String(AUTO_GIT_HASH);
|
||||
obj[F("wifi_rssi")] = (WiFi.status() != WL_CONNECTED) ? 0 : WiFi.RSSI();
|
||||
obj[F("ts_uptime")] = mApp->getUptime();
|
||||
obj[F("wifi_rssi")] = (WiFi.status() != WL_CONNECTED) ? 0 : WiFi.RSSI();
|
||||
obj[F("ts_uptime")] = mApp->getUptime();
|
||||
obj[F("menu_prot")] = mApp->getProtection();
|
||||
obj[F("menu_mask")] = (uint16_t)(mConfig->sys.protectionMask );
|
||||
obj[F("menu_protEn")] = (bool) (strlen(mConfig->sys.adminPwd) > 0);
|
||||
|
||||
#if defined(ESP32)
|
||||
obj[F("esp_type")] = F("ESP32");
|
||||
|
@ -199,6 +206,7 @@ class RestApi {
|
|||
void getSysInfo(JsonObject obj) {
|
||||
obj[F("ssid")] = mConfig->sys.stationSsid;
|
||||
obj[F("device_name")] = mConfig->sys.deviceName;
|
||||
obj[F("dark_mode")] = (bool)mConfig->sys.darkMode;
|
||||
|
||||
obj[F("mac")] = WiFi.macAddress();
|
||||
obj[F("hostname")] = mConfig->sys.deviceName;
|
||||
|
@ -245,15 +253,12 @@ class RestApi {
|
|||
}
|
||||
|
||||
void getHtmlSystem(JsonObject obj) {
|
||||
getMenu(obj.createNestedObject(F("menu")));
|
||||
getSysInfo(obj.createNestedObject(F("system")));
|
||||
getGeneric(obj.createNestedObject(F("generic")));
|
||||
obj[F("html")] = F("<a href=\"/factory\" class=\"btn\">Factory Reset</a><br/><br/><a href=\"/reboot\" class=\"btn\">Reboot</a>");
|
||||
|
||||
}
|
||||
|
||||
void getHtmlLogout(JsonObject obj) {
|
||||
getMenu(obj.createNestedObject(F("menu")));
|
||||
getGeneric(obj.createNestedObject(F("generic")));
|
||||
obj[F("refresh")] = 3;
|
||||
obj[F("refresh_url")] = "/";
|
||||
|
@ -261,7 +266,6 @@ class RestApi {
|
|||
}
|
||||
|
||||
void getHtmlSave(JsonObject obj) {
|
||||
getMenu(obj.createNestedObject(F("menu")));
|
||||
getGeneric(obj.createNestedObject(F("generic")));
|
||||
obj[F("refresh")] = 2;
|
||||
obj[F("refresh_url")] = "/setup";
|
||||
|
@ -269,7 +273,6 @@ class RestApi {
|
|||
}
|
||||
|
||||
void getReboot(JsonObject obj) {
|
||||
getMenu(obj.createNestedObject(F("menu")));
|
||||
getGeneric(obj.createNestedObject(F("generic")));
|
||||
obj[F("refresh")] = 10;
|
||||
obj[F("refresh_url")] = "/";
|
||||
|
@ -316,6 +319,41 @@ class RestApi {
|
|||
obj[F("rstComStop")] = (bool)mConfig->inst.rstValsCommStop;
|
||||
}
|
||||
|
||||
void getInverter(JsonObject obj, uint8_t id) {
|
||||
Inverter<> *iv = mSys->getInverterByPos(id);
|
||||
if(NULL != iv) {
|
||||
record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug);
|
||||
obj[F("id")] = id;
|
||||
obj[F("enabled")] = (bool)iv->config->enabled;
|
||||
obj[F("name")] = String(iv->config->name);
|
||||
obj[F("serial")] = String(iv->config->serial.u64, HEX);
|
||||
obj[F("version")] = String(iv->getFwVersion());
|
||||
obj[F("power_limit_read")] = ah::round3(iv->actPowerLimit);
|
||||
obj[F("ts_last_success")] = rec->ts;
|
||||
|
||||
JsonArray ch = obj.createNestedArray("ch");
|
||||
|
||||
// AC
|
||||
uint8_t pos;
|
||||
obj[F("ch_name")][0] = "AC";
|
||||
JsonArray ch0 = ch.createNestedArray();
|
||||
for (uint8_t fld = 0; fld < sizeof(acList); fld++) {
|
||||
pos = (iv->getPosByChFld(CH0, acList[fld], rec));
|
||||
ch0[fld] = (0xff != pos) ? ah::round3(iv->getValue(pos, rec)) : 0.0;
|
||||
}
|
||||
|
||||
// DC
|
||||
for(uint8_t j = 0; j < iv->channels; j ++) {
|
||||
obj[F("ch_name")][j+1] = iv->config->chName[j];
|
||||
JsonArray cur = ch.createNestedArray();
|
||||
for (uint8_t fld = 0; fld < sizeof(dcList); fld++) {
|
||||
pos = (iv->getPosByChFld((j+1), dcList[fld], rec));
|
||||
cur[fld] = (0xff != pos) ? ah::round3(iv->getValue(pos, rec)) : 0.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void getMqtt(JsonObject obj) {
|
||||
obj[F("broker")] = String(mConfig->mqtt.broker);
|
||||
obj[F("port")] = String(mConfig->mqtt.port);
|
||||
|
@ -376,64 +414,21 @@ class RestApi {
|
|||
}
|
||||
|
||||
void getDisplay(JsonObject obj) {
|
||||
obj[F("disp_type")] = (uint8_t)mConfig->plugin.display.type;
|
||||
obj[F("disp_pwr")] = (bool)mConfig->plugin.display.pwrSaveAtIvOffline;
|
||||
obj[F("logo_en")] = (bool)mConfig->plugin.display.logoEn;
|
||||
obj[F("px_shift")] = (bool)mConfig->plugin.display.pxShift;
|
||||
obj[F("rot180")] = (bool)mConfig->plugin.display.rot180;
|
||||
obj[F("contrast")] = (uint8_t)mConfig->plugin.display.contrast;
|
||||
obj[F("pinDisp0")] = mConfig->plugin.display.pin0;
|
||||
obj[F("pinDisp1")] = mConfig->plugin.display.pin1;
|
||||
}
|
||||
|
||||
void getMenu(JsonObject obj) {
|
||||
uint8_t i = 0;
|
||||
uint16_t mask = (mApp->getProtection()) ? mConfig->sys.protectionMask : 0;
|
||||
if(!CHECK_MASK(mask, PROT_MASK_LIVE)) {
|
||||
obj[F("name")][i] = "Live";
|
||||
obj[F("link")][i++] = "/live";
|
||||
}
|
||||
if(!CHECK_MASK(mask, PROT_MASK_SERIAL)) {
|
||||
obj[F("name")][i] = "Serial / Control";
|
||||
obj[F("link")][i++] = "/serial";
|
||||
}
|
||||
if(!CHECK_MASK(mask, PROT_MASK_SETUP)) {
|
||||
obj[F("name")][i] = "Settings";
|
||||
obj[F("link")][i++] = "/setup";
|
||||
}
|
||||
obj[F("name")][i++] = "-";
|
||||
obj[F("name")][i] = "REST API";
|
||||
obj[F("link")][i] = "/api";
|
||||
obj[F("trgt")][i++] = "_blank";
|
||||
obj[F("name")][i++] = "-";
|
||||
if(!CHECK_MASK(mask, PROT_MASK_UPDATE)) {
|
||||
obj[F("name")][i] = "Update";
|
||||
obj[F("link")][i++] = "/update";
|
||||
}
|
||||
if(!CHECK_MASK(mask, PROT_MASK_SYSTEM)) {
|
||||
obj[F("name")][i] = "System";
|
||||
obj[F("link")][i++] = "/system";
|
||||
}
|
||||
obj[F("name")][i++] = "-";
|
||||
obj[F("name")][i] = "Documentation";
|
||||
obj[F("link")][i] = "https://ahoydtu.de";
|
||||
obj[F("trgt")][i++] = "_blank";
|
||||
if(strlen(mConfig->sys.adminPwd) > 0) {
|
||||
obj[F("name")][i++] = "-";
|
||||
if(mApp->getProtection()) {
|
||||
obj[F("name")][i] = "Login";
|
||||
obj[F("link")][i++] = "/login";
|
||||
} else {
|
||||
obj[F("name")][i] = "Logout";
|
||||
obj[F("link")][i++] = "/logout";
|
||||
}
|
||||
}
|
||||
obj[F("disp_typ")] = (uint8_t)mConfig->plugin.display.type;
|
||||
obj[F("disp_pwr")] = (bool)mConfig->plugin.display.pwrSaveAtIvOffline;
|
||||
obj[F("disp_pxshift")] = (bool)mConfig->plugin.display.pxShift;
|
||||
obj[F("disp_rot")] = (uint8_t)mConfig->plugin.display.rot;
|
||||
obj[F("disp_cont")] = (uint8_t)mConfig->plugin.display.contrast;
|
||||
obj[F("disp_clk")] = mConfig->plugin.display.disp_clk;
|
||||
obj[F("disp_data")] = mConfig->plugin.display.disp_data;
|
||||
obj[F("disp_cs")] = mConfig->plugin.display.disp_cs;
|
||||
obj[F("disp_dc")] = mConfig->plugin.display.disp_dc;
|
||||
obj[F("disp_rst")] = mConfig->plugin.display.disp_reset;
|
||||
obj[F("disp_bsy")] = mConfig->plugin.display.disp_busy;
|
||||
}
|
||||
|
||||
void getIndex(JsonObject obj) {
|
||||
getMenu(obj.createNestedObject(F("menu")));
|
||||
getGeneric(obj.createNestedObject(F("generic")));
|
||||
|
||||
obj[F("ts_now")] = mApp->getTimestamp();
|
||||
obj[F("ts_sunrise")] = mApp->getSunrise();
|
||||
obj[F("ts_sunset")] = mApp->getSunset();
|
||||
|
@ -482,10 +477,9 @@ class RestApi {
|
|||
}
|
||||
|
||||
void getSetup(JsonObject obj) {
|
||||
getMenu(obj.createNestedObject(F("menu")));
|
||||
getGeneric(obj.createNestedObject(F("generic")));
|
||||
getSysInfo(obj.createNestedObject(F("system")));
|
||||
getInverterList(obj.createNestedObject(F("inverter")));
|
||||
//getInverterList(obj.createNestedObject(F("inverter")));
|
||||
getMqtt(obj.createNestedObject(F("mqtt")));
|
||||
getNtp(obj.createNestedObject(F("ntp")));
|
||||
getSun(obj.createNestedObject(F("sun")));
|
||||
|
@ -502,14 +496,29 @@ class RestApi {
|
|||
}
|
||||
|
||||
void getLive(JsonObject obj) {
|
||||
getMenu(obj.createNestedObject(F("menu")));
|
||||
getGeneric(obj.createNestedObject(F("generic")));
|
||||
JsonArray invArr = obj.createNestedArray(F("inverter"));
|
||||
obj["refresh_interval"] = mConfig->nrf.sendInterval;
|
||||
//JsonArray invArr = obj.createNestedArray(F("inverter"));
|
||||
obj[F("refresh")] = mConfig->nrf.sendInterval;
|
||||
|
||||
uint8_t list[] = {FLD_UAC, FLD_IAC, FLD_PAC, FLD_F, FLD_PF, FLD_T, FLD_YT, FLD_YD, FLD_PDC, FLD_EFF, FLD_Q};
|
||||
for (uint8_t fld = 0; fld < sizeof(acList); fld++) {
|
||||
obj[F("ch0_fld_units")][fld] = String(units[fieldUnits[acList[fld]]]);
|
||||
obj[F("ch0_fld_names")][fld] = String(fields[acList[fld]]);
|
||||
}
|
||||
for (uint8_t fld = 0; fld < sizeof(dcList); fld++) {
|
||||
obj[F("fld_units")][fld] = String(units[fieldUnits[dcList[fld]]]);
|
||||
obj[F("fld_names")][fld] = String(fields[dcList[fld]]);
|
||||
}
|
||||
|
||||
Inverter<> *iv;
|
||||
for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i ++) {
|
||||
iv = mSys->getInverterByPos(i);
|
||||
bool parse = false;
|
||||
if(NULL != iv)
|
||||
parse = iv->config->enabled;
|
||||
obj[F("iv")][i] = parse;
|
||||
}
|
||||
|
||||
/*Inverter<> *iv;
|
||||
uint8_t pos;
|
||||
for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i ++) {
|
||||
iv = mSys->getInverterByPos(i);
|
||||
|
@ -553,7 +562,7 @@ class RestApi {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
void getRecord(JsonObject obj, uint8_t recType) {
|
||||
|
@ -632,8 +641,7 @@ class RestApi {
|
|||
mTimezoneOffset = jsonIn[F("val")];
|
||||
else if(F("discovery_cfg") == jsonIn[F("cmd")]) {
|
||||
mApp->setMqttDiscoveryFlag(); // for homeassistant
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
jsonOut[F("error")] = F("unknown cmd");
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -33,23 +33,66 @@ iconSuccess = [
|
|||
/**
|
||||
* GENERIC FUNCTIONS
|
||||
*/
|
||||
|
||||
function topnav() {
|
||||
toggle("topnav");
|
||||
function ml(tagName, ...args) {
|
||||
var el = document.createElement(tagName);
|
||||
if(args[0]) {
|
||||
for(var name in args[0]) {
|
||||
if(name.indexOf("on") === 0) {
|
||||
el.addEventListener(name.substr(2).toLowerCase(), args[0][name], false)
|
||||
} else {
|
||||
el.setAttribute(name, args[0][name]);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!args[1]) {
|
||||
return el;
|
||||
}
|
||||
return nester(el, args[1])
|
||||
}
|
||||
|
||||
function parseMenu(obj) {
|
||||
var e = document.getElementById("topnav");
|
||||
e.innerHTML = "";
|
||||
for(var i = 0; i < obj["name"].length; i ++) {
|
||||
if(obj["name"][i] == "-")
|
||||
e.appendChild(span("", ["seperator"]));
|
||||
else {
|
||||
var l = link(obj["link"][i], obj["name"][i], obj["trgt"][i]);
|
||||
if(obj["link"][i] == window.location.pathname)
|
||||
l.classList.add("active");
|
||||
e.appendChild(l);
|
||||
function nester(el, n) {
|
||||
if (typeof n === "string") {
|
||||
var t = document.createTextNode(n);
|
||||
el.appendChild(t);
|
||||
} else if (n instanceof Array) {
|
||||
for(var i = 0; i < n.length; i++) {
|
||||
if (typeof n[i] === "string") {
|
||||
var t = document.createTextNode(n[i]);
|
||||
el.appendChild(t);
|
||||
} else if (n[i] instanceof Node){
|
||||
el.appendChild(n[i]);
|
||||
}
|
||||
}
|
||||
} else if (n instanceof Node){
|
||||
el.appendChild(n)
|
||||
}
|
||||
return el;
|
||||
}
|
||||
|
||||
function topnav() {
|
||||
toggle("topnav", "mobile");
|
||||
}
|
||||
|
||||
function parseNav(obj) {
|
||||
for(i = 0; i < 10; i++) {
|
||||
if(i == 2)
|
||||
continue;
|
||||
var l = document.getElementById("nav"+i);
|
||||
if(window.location.pathname == "/" + l.href.split('/').pop())
|
||||
l.classList.add("active");
|
||||
|
||||
if(obj["menu_protEn"]) {
|
||||
if(obj["menu_prot"]) {
|
||||
if(0 == i)
|
||||
l.classList.remove("hide");
|
||||
else if(i > 2) {
|
||||
if(((obj["menu_mask"] >> (i-2)) & 0x01) == 0x00)
|
||||
l.classList.remove("hide");
|
||||
}
|
||||
} else if(0 != i)
|
||||
l.classList.remove("hide");
|
||||
} else if(i > 1)
|
||||
l.classList.remove("hide");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -60,7 +103,9 @@ function parseVersion(obj) {
|
|||
}
|
||||
|
||||
function parseESP(obj) {
|
||||
document.getElementById("esp_type").innerHTML="Board: " + obj["esp_type"];
|
||||
document.getElementById("esp_type").append(
|
||||
document.createTextNode("Board: " + obj["esp_type"])
|
||||
);
|
||||
}
|
||||
|
||||
function parseRssi(obj) {
|
||||
|
@ -69,7 +114,7 @@ function parseRssi(obj) {
|
|||
icon = iconWifi1;
|
||||
else if(obj["wifi_rssi"] <= -70)
|
||||
icon = iconWifi2;
|
||||
document.getElementById("wifiicon").replaceChildren(svg(icon, 32, 32, "#fff", null, obj["wifi_rssi"]));
|
||||
document.getElementById("wifiicon").replaceChildren(svg(icon, 32, 32, "wifi", obj["wifi_rssi"]));
|
||||
}
|
||||
|
||||
function setHide(id, hide) {
|
||||
|
@ -82,12 +127,12 @@ function setHide(id, hide) {
|
|||
elm.classList.remove('hide');
|
||||
}
|
||||
|
||||
function toggle(id) {
|
||||
function toggle(id, cl="hide") {
|
||||
var e = document.getElementById(id);
|
||||
if(!e.classList.contains("hide"))
|
||||
e.classList.add("hide");
|
||||
if(!e.classList.contains(cl))
|
||||
e.classList.add(cl);
|
||||
else
|
||||
e.classList.remove('hide');
|
||||
e.classList.remove(cl);
|
||||
}
|
||||
|
||||
function getAjax(url, ptr, method="GET", json=null) {
|
||||
|
@ -198,11 +243,10 @@ function link(dst, text, target=null) {
|
|||
return a;
|
||||
}
|
||||
|
||||
function svg(data=null, w=24, h=24, color="#000", cl=null, tooltip=null) {
|
||||
function svg(data=null, w=24, h=24, cl=null, tooltip=null) {
|
||||
var s = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
||||
s.setAttribute('width', w);
|
||||
s.setAttribute('height', h);
|
||||
s.setAttribute('fill', color);
|
||||
s.setAttribute('viewBox', '0 0 16 16');
|
||||
if(null != cl) s.setAttribute('class', cl);
|
||||
if(null != data) {
|
||||
|
|
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 gzip
|
||||
import glob
|
||||
|
||||
import shutil
|
||||
from datetime import date
|
||||
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]
|
||||
define = inFile.split(".")[0].upper()
|
||||
define2 = inFile.split(".")[1].upper()
|
||||
|
@ -17,14 +74,19 @@ def convert2Header(inFile):
|
|||
Path("html/h").mkdir(exist_ok=True)
|
||||
else:
|
||||
outName = "h/" + inFileVarName + ".h"
|
||||
Path("h").mkdir(exist_ok=True)
|
||||
|
||||
data = ""
|
||||
if fileType == "ico":
|
||||
f = open(inFile, "rb")
|
||||
data = f.read()
|
||||
f.close()
|
||||
else:
|
||||
f = open(inFile, "r")
|
||||
data = f.read()
|
||||
f.close()
|
||||
if fileType == "html":
|
||||
data = htmlParts(inFile, "includes/header.html", "includes/nav.html", "includes/footer.html", version)
|
||||
else:
|
||||
f = open(inFile, "r")
|
||||
data = f.read()
|
||||
f.close()
|
||||
|
||||
if fileType == "css":
|
||||
data = data.replace('\n', '')
|
||||
|
@ -53,13 +115,17 @@ def convert2Header(inFile):
|
|||
f.close()
|
||||
|
||||
# delete all files in the 'h' dir
|
||||
dir = 'h'
|
||||
wd = 'h'
|
||||
if os.getcwd()[-4:] != "html":
|
||||
dir = "web/html/" + dir
|
||||
wd = "web/html/" + wd
|
||||
|
||||
if os.path.exists(dir):
|
||||
for f in os.listdir(dir):
|
||||
os.remove(os.path.join(dir, f))
|
||||
if os.path.exists(wd):
|
||||
for f in os.listdir(wd):
|
||||
os.remove(os.path.join(wd, f))
|
||||
wd += "/tmp"
|
||||
if os.path.exists(wd):
|
||||
for f in os.listdir(wd):
|
||||
os.remove(os.path.join(wd, f))
|
||||
|
||||
# grab all files with following extensions
|
||||
if os.getcwd()[-4:] != "html":
|
||||
|
@ -69,6 +135,11 @@ files_grabbed = []
|
|||
for files in types:
|
||||
files_grabbed.extend(glob.glob(files))
|
||||
|
||||
Path("h").mkdir(exist_ok=True)
|
||||
Path("tmp").mkdir(exist_ok=True) # created to check if webpages are valid with all replacements
|
||||
shutil.copyfile("style.css", "tmp/style.css")
|
||||
version = readVersion("../../defines.h")
|
||||
|
||||
# go throw the array
|
||||
for val in files_grabbed:
|
||||
convert2Header(val)
|
||||
convert2Header(val, version)
|
||||
|
|
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>
|
||||
<head>
|
||||
<title>Index</title>
|
||||
<link rel="stylesheet" type="text/css" href="style.css"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<script type="text/javascript" src="api.js"></script>
|
||||
{#HTML_HEADER}
|
||||
</head>
|
||||
<body>
|
||||
<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="hide"></div>
|
||||
<div id="wifiicon" class="info"></div>
|
||||
</div>
|
||||
{#HTML_NAV}
|
||||
<div id="wrapper">
|
||||
<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>
|
||||
<span class="des">Uptime: </span><span id="uptime"></span><br/>
|
||||
<span class="des">ESP-Time: </span><span id="date"></span>
|
||||
|
@ -60,22 +36,7 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<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><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>
|
||||
{#HTML_FOOTER}
|
||||
<script type="text/javascript">
|
||||
var exeOnce = true;
|
||||
var tickCnt = 0;
|
||||
|
@ -106,12 +67,8 @@
|
|||
}
|
||||
|
||||
function parseGeneric(obj) {
|
||||
// Disclaimer
|
||||
//if(obj["disclaimer"] == false) sessionStorage.setItem("gDisclaimer", promptFunction());
|
||||
if(exeOnce){
|
||||
parseVersion(obj);
|
||||
if(exeOnce)
|
||||
parseESP(obj);
|
||||
}
|
||||
parseRssi(obj);
|
||||
}
|
||||
|
||||
|
@ -163,14 +120,14 @@
|
|||
var p = div(["none"]);
|
||||
for(var i of obj) {
|
||||
var icon = iconWarn;
|
||||
var color = "#F70";
|
||||
var cl = "icon-warn";
|
||||
avail = "";
|
||||
if(false == i["enabled"]) {
|
||||
avail = "disabled";
|
||||
}
|
||||
else if(false == i["is_avail"]) {
|
||||
icon = iconInfo;
|
||||
color = "#00d";
|
||||
cl = "icon-info";
|
||||
avail = "not yet available";
|
||||
}
|
||||
else if(0 == i["ts_last_success"]) {
|
||||
|
@ -183,12 +140,12 @@
|
|||
if(false == i["is_producing"])
|
||||
avail += "not ";
|
||||
else
|
||||
color = "#090";
|
||||
cl = "icon-success";
|
||||
avail += "producing";
|
||||
}
|
||||
|
||||
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),
|
||||
br()
|
||||
);
|
||||
|
@ -203,25 +160,25 @@
|
|||
document.getElementById("iv").replaceChildren(p);
|
||||
}
|
||||
|
||||
function parseWarnInfo(warn, success, version) {
|
||||
function parseWarnInfo(warn, success) {
|
||||
var p = div(["none"]);
|
||||
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) {
|
||||
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)
|
||||
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(getVerInt(version) < getVerInt(release))
|
||||
p.append(svg(iconInfo, 20, 20, "#00d", "icon"), span("Update available, current released version: " + release), br());
|
||||
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());
|
||||
if(getVerInt("{#VERSION}") < getVerInt(release))
|
||||
p.append(svg(iconInfo, 30, 30, "icon icon-info"), span("Update available, current released version: " + release), br());
|
||||
else if(getVerInt("{#VERSION}") > getVerInt(release))
|
||||
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
|
||||
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);
|
||||
|
@ -239,11 +196,11 @@
|
|||
function parse(obj) {
|
||||
if(null != obj) {
|
||||
if(exeOnce)
|
||||
parseMenu(obj["menu"]);
|
||||
parseNav(obj["generic"]);
|
||||
parseGeneric(obj["generic"]);
|
||||
parseSys(obj);
|
||||
parseIv(obj["inverter"]);
|
||||
parseWarnInfo(obj["warnings"], obj["infos"], obj["generic"]["version"]);
|
||||
parseWarnInfo(obj["warnings"], obj["infos"]);
|
||||
if(exeOnce) {
|
||||
window.setInterval("tick()", 1000);
|
||||
exeOnce = false;
|
||||
|
|
|
@ -2,41 +2,22 @@
|
|||
<html>
|
||||
<head>
|
||||
<title>Login</title>
|
||||
<link rel="stylesheet" type="text/css" href="style.css"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<script type="text/javascript" src="api.js"></script>
|
||||
{#HTML_HEADER}
|
||||
</head>
|
||||
<body>
|
||||
<div id="wrapper">
|
||||
<div id="login">
|
||||
<div class="pad">
|
||||
<div class="p-4">
|
||||
<form action="/login" method="post">
|
||||
<h2>AhoyDTU</h2>
|
||||
<input type="password" name="pwd" value="" autofocus>
|
||||
<input type="submit" name="login" value="login" class="btn">
|
||||
<div class="row"><h2>AhoyDTU</h2></div>
|
||||
<div class="row">
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<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">
|
||||
<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>
|
||||
{#HTML_FOOTER}
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -2,73 +2,66 @@
|
|||
<html>
|
||||
<head>
|
||||
<title>Serial Console</title>
|
||||
<link rel="stylesheet" type="text/css" href="style.css"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<script type="text/javascript" src="api.js"></script>
|
||||
{#HTML_HEADER}
|
||||
</head>
|
||||
<body>
|
||||
<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="hide"></div>
|
||||
<div id="wifiicon" class="info"></div>
|
||||
</div>
|
||||
{#HTML_NAV}
|
||||
<div id="wrapper">
|
||||
<div id="content">
|
||||
<div class="serial">
|
||||
<textarea id="serial" cols="80" rows="20" readonly></textarea><br/>
|
||||
connected: <span class="dot" id="connected"></span>
|
||||
Uptime: <span id="uptime"></span>
|
||||
<input type="button" value="clear" class="btn" id="clear"/>
|
||||
<input type="button" value="autoscroll" class="btn" id="scroll"/>
|
||||
<div class="hr mt-3 mb-3"></div>
|
||||
<div class="row">
|
||||
<textarea id="serial" class="mt-3" cols="80" rows="20" readonly></textarea>
|
||||
</div>
|
||||
<div class="row my-3">
|
||||
<div class="col-3">connected: <span class="dot" id="connected"></span></div>
|
||||
<div class="col-3 col-sm-4 my-3">Uptime: <span id="uptime"></span></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>
|
||||
<label for="iv">Select Inverter:</label>
|
||||
<select name="iv" id="InvID">
|
||||
</select>
|
||||
<label for="pwrlimval">Power Limit Value</label>
|
||||
<input type="number" class="text" name="pwrlimval" maxlength="4"/>
|
||||
<label for="pwrlimctrl">Power Limit Command</label>
|
||||
<select name="pwrlimctrl">
|
||||
<option value="" selected disabled hidden>select the unit and persistence</option>
|
||||
<option value="limit_nonpersistent_absolute">absolute non persistent [W]</option>
|
||||
<option value="limit_nonpersistent_relative">relative non persistent [%]</option>
|
||||
<option value="limit_persistent_absolute">absolute persistent [W]</option>
|
||||
<option value="limit_persistent_relative">relative persistent [%]</option>
|
||||
</select>
|
||||
<br/>
|
||||
<input type="button" value="Send Power Limit" class="btn" id="sendpwrlim"/>
|
||||
<div class="hr mt-3 mb-3"></div>
|
||||
<div id="power" class="mt-3">
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-12 col-sm-3 my-2">Select Inverter</div>
|
||||
<div class="col-12 col-sm-9"><select name="iv" id="InvID"></select></div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-12 col-sm-3 my-2">Power Limit Command</div>
|
||||
<div class="col-12 col-sm-9">
|
||||
<select name="pwrlimctrl">
|
||||
<option value="" selected disabled hidden>select the unit and persistence</option>
|
||||
<option value="limit_nonpersistent_absolute">absolute non persistent [W]</option>
|
||||
<option value="limit_nonpersistent_relative">relative non persistent [%]</option>
|
||||
<option value="limit_persistent_absolute">absolute persistent [W]</option>
|
||||
<option value="limit_persistent_relative">relative persistent [%]</option>
|
||||
</select>
|
||||
</div>
|
||||
</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="Turn Off" class="btn" id="power_off"/>
|
||||
<input type="button" value="Turn On" class="btn" id="power_on"/>
|
||||
</div>
|
||||
<br/>
|
||||
<p>Ctrl result: <span id="result">n/a</span></p>
|
||||
</div>
|
||||
<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 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><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>
|
||||
{#HTML_FOOTER}
|
||||
<script type="text/javascript">
|
||||
var mAutoScroll = true;
|
||||
var con = document.getElementById("serial");
|
||||
|
@ -87,22 +80,21 @@
|
|||
|
||||
parseRssi(obj);
|
||||
if(true == exeOnce) {
|
||||
parseVersion(obj);
|
||||
parseNav(obj);
|
||||
parseESP(obj);
|
||||
window.setInterval("getAjax('/api/generic', parseGeneric)", 10000);
|
||||
exeOnce = false;
|
||||
getAjax("/api/setup", parse);
|
||||
getAjax("/api/inverter/list", parse);
|
||||
}
|
||||
}
|
||||
|
||||
function parse(root) {
|
||||
parseMenu(root["menu"]);
|
||||
select = document.getElementById('InvID');
|
||||
|
||||
if(null == root) return;
|
||||
root = root.inverter;
|
||||
for(var i = 0; i < root.inverter.length; i++) {
|
||||
inv = root.inverter[i];
|
||||
for(var i = 0; i < root.length; i++) {
|
||||
inv = root[i];
|
||||
var opt = document.createElement('option');
|
||||
opt.value = inv.id;
|
||||
opt.innerHTML = inv.name;
|
||||
|
|
|
@ -2,9 +2,7 @@
|
|||
<html>
|
||||
<head>
|
||||
<title>Setup</title>
|
||||
<link rel="stylesheet" type="text/css" href="style.css"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<script type="text/javascript" src="api.js"></script>
|
||||
{#HTML_HEADER}
|
||||
<script type="text/javascript">
|
||||
function load() {
|
||||
for(it of document.getElementsByClassName("s_collapsible")) {
|
||||
|
@ -18,154 +16,25 @@
|
|||
</script>
|
||||
</head>
|
||||
<body onload="load()">
|
||||
<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="hide"></div>
|
||||
<div id="wifiicon" class="info"></div>
|
||||
</div>
|
||||
{#HTML_NAV}
|
||||
<div id="wrapper">
|
||||
<div id="content">
|
||||
<form method="post" action="/save">
|
||||
<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>
|
||||
<label for="device">Device Name</label>
|
||||
<input type="text" name="device" class="text"/>
|
||||
<div class="row mb-3">
|
||||
<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>
|
||||
|
||||
<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>
|
||||
<fieldset class="mb-4">
|
||||
<legend class="des">System Config</legend>
|
||||
<p class="des">Pinout</p>
|
||||
<div id="pinout"></div>
|
||||
|
@ -177,72 +46,261 @@
|
|||
<div id="cmt"></div>
|
||||
|
||||
<p class="des">Serial Console</p>
|
||||
<label for="serEn">print inverter data</label>
|
||||
<input type="checkbox" class="cb" name="serEn"/><br/>
|
||||
<label for="serDbg">Serial Debug</label>
|
||||
<input type="checkbox" class="cb" name="serDbg"/><br/>
|
||||
<label for="serIntvl">Interval [s]</label>
|
||||
<input type="text" class="text" name="serIntvl" pattern="[0-9]+" title="Invalid input"/>
|
||||
<div class="row mb-3">
|
||||
<div class="col-8 col-sm-3">print inverter data</div>
|
||||
<div class="col-4 col-sm-9"><input type="checkbox" name="serEn"/></div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<button type="button" class="s_collapsible">Display Config</button>
|
||||
<div class="s_content">
|
||||
<fieldset>
|
||||
<fieldset class="mb-4">
|
||||
<legend class="des">Display Config</legend>
|
||||
<div id="dispType"></div>
|
||||
<label for="logoEn">Show Logo</label>
|
||||
<input type="checkbox" class="cb" name="logoEn"/><br/>
|
||||
<label for="dispPwr">Turn off while inverters are offline</label>
|
||||
<input type="checkbox" class="cb" name="dispPwr"/><br/>
|
||||
<label for="dispPxSh">Enable pixel shifting</label>
|
||||
<input type="checkbox" class="cb" name="dispPxSh"/><br/>
|
||||
<label for="disp180">Rotate 180 degree</label>
|
||||
<input type="checkbox" class="cb" name="disp180"/><br/>
|
||||
|
||||
<label for="dispCont">Contrast</label>
|
||||
<select name="dispCont" id="contrast"></select>
|
||||
<div id="dispRot"></div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-8 col-sm-3">Turn off while inverters are offline</div>
|
||||
<div class="col-4 col-sm-9"><input type="checkbox" name="disp_pwr"/></div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-8 col-sm-3">Enable Screensaver (pixel shifting)</div>
|
||||
<div class="col-4 col-sm-9"><input type="checkbox" name="disp_pxshift"/></div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<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>
|
||||
<div id="dispPins"></div>
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
<div class="mt-3">
|
||||
<label for="reboot">Reboot device after successful save</label>
|
||||
<input type="checkbox" class="cb" name="reboot" checked />
|
||||
<input type="submit" value="save" class="btn right"/>
|
||||
<div class="row mb-4 mt-4">
|
||||
<div class="col-8 col-sm-3">Reboot device after successful save</div>
|
||||
<div class="col-2 col-md-6">
|
||||
<input type="checkbox" name="reboot" checked />
|
||||
<input type="submit" value="save" class="btn right"/>
|
||||
</div>
|
||||
</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>
|
||||
<fieldset>
|
||||
<fieldset class="mb-4">
|
||||
<legend class="des">Upload / Store JSON Settings</legend>
|
||||
<form id="form" method="POST" action="/upload" enctype="multipart/form-data" accept-charset="utf-8">
|
||||
<input type="file" name="upload">
|
||||
<input type="button" class="btn" value="Upload" onclick="hide()">
|
||||
</form>
|
||||
</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>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<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><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>
|
||||
{#HTML_FOOTER}
|
||||
<script type="text/javascript">
|
||||
var highestId = 0;
|
||||
var maxInv = 0;
|
||||
|
@ -372,22 +430,38 @@
|
|||
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) {
|
||||
highestId = id + 1;
|
||||
if(highestId == maxInv)
|
||||
setHide("btnAdd", true);
|
||||
iv = document.getElementById("inverter");
|
||||
|
||||
var iv = document.getElementById("inverter");
|
||||
iv.appendChild(des("Inverter " + 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");
|
||||
iv.appendChild(addr);
|
||||
iv.append(
|
||||
mlCb(id + "Enable", "Communication Enable", obj["enabled"]),
|
||||
mlE("Serial Number (12 digits)*", addr)
|
||||
);
|
||||
|
||||
['keyup', 'change'].forEach(function(evt) {
|
||||
addr.addEventListener(evt, (e) => {
|
||||
var serial = addr.value.substring(0,4);
|
||||
|
@ -397,9 +471,9 @@
|
|||
setHide(id+"ModName"+i, true);
|
||||
setHide(id+"YieldCor"+i, true);
|
||||
}
|
||||
setHide("lbl"+id+"ModPwr", true);
|
||||
setHide("lbl"+id+"ModName", true);
|
||||
setHide("lbl"+id+"YieldCor", true);
|
||||
setHide("row"+id+"ModPwr", true);
|
||||
setHide("row"+id+"ModName", true);
|
||||
setHide("row"+id+"YieldCor", true);
|
||||
|
||||
if(serial.charAt(0) == 1) {
|
||||
if((serial.charAt(1) == 0) || (serial.charAt(1) == 1)) {
|
||||
|
@ -423,39 +497,44 @@
|
|||
setHide(id+"ModName"+i, false);
|
||||
setHide(id+"YieldCor"+i, false);
|
||||
}
|
||||
setHide("lbl"+id+"ModPwr", false);
|
||||
setHide("lbl"+id+"ModName", false);
|
||||
setHide("lbl"+id+"YieldCor", false);
|
||||
setHide("row"+id+"ModPwr", false);
|
||||
setHide("row"+id+"ModName", false);
|
||||
setHide("row"+id+"YieldCor", false);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
iv.append(
|
||||
lbl(id + "Name", "Name*"),
|
||||
inp(id + "Name", obj["name"], 32, ["text"], null, "text", "[A-Za-z0-9./#$%&=+_-]+", "Invalid input")
|
||||
);
|
||||
iv.append(mlE("Name*", inp(id + "Name", obj["name"], 16, ["text"], null, "text", "[A-Za-z0-9./#$%&=+_-]+", "Invalid input")));
|
||||
|
||||
for(var j of [
|
||||
["ModPwr", "ch_max_power", "Max Module Power (Wp)", 4, "[0-9]+"],
|
||||
["ModName", "ch_name", "Module Name", 16, null],
|
||||
["YieldCor", "ch_yield_cor", "Yield Total Correction [kWh]", 16, "[0-9-]+"]]) {
|
||||
var cl = (re.test(obj["serial"])) ? null : ["hide"];
|
||||
iv.appendChild(lbl(null, j[2], cl, "lbl" + id + j[0]));
|
||||
d = div([j[0]]);
|
||||
["ModName", "ch_name", "Module Name", 15, null],
|
||||
["YieldCor", "ch_yield_cor", "Yield Total Correction [kWh]", 8, "[0-9-]+"]]) {
|
||||
|
||||
var cl = (re.test(obj["serial"])) ? "" : " hide";
|
||||
|
||||
i = 0;
|
||||
cl = (re.test(obj["serial"])) ? ["text", "sh"] : ["text", "sh", "hide"];
|
||||
arrIn = [];
|
||||
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++;
|
||||
}
|
||||
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");
|
||||
del.addEventListener("click", delIv);
|
||||
iv.append(
|
||||
lbl(id + "lbldel", "Delete"),
|
||||
del
|
||||
);
|
||||
iv.append(mlE("Delete", del));
|
||||
}
|
||||
|
||||
function ivGlob(obj) {
|
||||
|
@ -468,24 +547,22 @@
|
|||
function parseSys(obj) {
|
||||
for(var i of [["device", "device_name"], ["ssid", "ssid"]])
|
||||
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"])
|
||||
e.value = "";
|
||||
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++) {
|
||||
var chkd = ((obj["prot_mask"] & (1 << i)) == (1 << i));
|
||||
var sp = lbl("protMask" + i, a[i]);
|
||||
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());
|
||||
var chk = ((obj["prot_mask"] & (1 << i)) == (1 << i));
|
||||
el.push(mlCb("protMask" + i, a[i], chk))
|
||||
}
|
||||
d.append(...el);
|
||||
}
|
||||
|
||||
function parseGeneric(obj) {
|
||||
parseVersion(obj);
|
||||
parseNav(obj);
|
||||
parseESP(obj);
|
||||
parseRssi(obj);
|
||||
}
|
||||
|
@ -528,35 +605,30 @@
|
|||
pins = [['led0', 'pinLed0'], ['led1', 'pinLed1']];
|
||||
for(p of pins) {
|
||||
e.append(
|
||||
lbl(p[1], p[0].toUpperCase()),
|
||||
sel(p[1], ("ESP8266" == type) ? esp8266pins : esp32pins, obj[p[0]])
|
||||
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[0]])
|
||||
)
|
||||
])
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function parseNrfRadio(obj, type) {
|
||||
var e = document.getElementById("rf24");
|
||||
var en = inp("rf24Enable", null, null, ["cb"], "rf24Enable", "checkbox");
|
||||
en.checked = obj["en"];
|
||||
|
||||
e.append(
|
||||
lbl("rf24Enable", "NRF24 Enable"),
|
||||
en, br(),
|
||||
lbl("rf24Power", "Amplifier Power Level"),
|
||||
sel("rf24Power", [
|
||||
[0, "MIN"],
|
||||
[1, "LOW"],
|
||||
[2, "HIGH"],
|
||||
[3, "MAX"]
|
||||
], obj["power_level"])
|
||||
var e = document.getElementById("rf24").append(
|
||||
ml("div", {class: "row mb-3"}, [
|
||||
ml("div", {class: "col-12 col-sm-3 my-2"}, "Power Level"),
|
||||
ml("div", {class: "col-12 col-sm-9"},
|
||||
sel("rf24Power", [
|
||||
[0, "MIN"],
|
||||
[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) {
|
||||
|
@ -584,35 +656,56 @@
|
|||
}
|
||||
|
||||
function parseDisplay(obj, type) {
|
||||
for(var i of [["logoEn", "logo_en"], ["dispPwr", "disp_pwr"], ["dispPxSh", "px_shift"], ["disp180", "rot180"]])
|
||||
document.getElementsByName(i[0])[0].checked = obj[i[1]];
|
||||
for(var i of ["disp_pwr", "disp_pxshift"])
|
||||
document.getElementsByName(i)[0].checked = obj[i];
|
||||
|
||||
var e = document.getElementById("dispPins");
|
||||
pins = [['SCL / CS', 'pinDisp0'], ['SDA / DC', 'pinDisp1']];
|
||||
pins = [['clock', 'disp_clk'], ['data', 'disp_data'], ['cs', 'disp_cs'], ['dc', 'disp_dc'], ['reset', 'disp_rst'], ['busy', 'disp_bsy']];
|
||||
for(p of pins) {
|
||||
e.appendChild(lbl(p[1], p[0].toUpperCase()));
|
||||
e.appendChild(sel(p[1], ("ESP8266" == type) ? esp8266pins : esp32pins, obj[p[1]]));
|
||||
if(("ESP8266" == type) && p[0] == "cs")
|
||||
break;
|
||||
e.append(
|
||||
ml("div", {class: "row mb-3"}, [
|
||||
ml("div", {class: "col-12 col-sm-3 my-2"}, p[0].toUpperCase()),
|
||||
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(
|
||||
lbl("dispType", "Type"),
|
||||
sel("dispType", opts, obj["disp_type"])
|
||||
ml("div", {class: "row mb-3"}, [
|
||||
ml("div", {class: "col-12 col-sm-3 my-2"}, "Type"),
|
||||
ml("div", {class: "col-12 col-sm-9"}, sel("disp_typ", opts, obj["disp_typ"]))
|
||||
])
|
||||
);
|
||||
|
||||
e = document.getElementById("contrast");
|
||||
for(var i = 30; i < 101; i += 2) {
|
||||
e.appendChild(opt(i, i, (i == obj["contrast"])));
|
||||
opts = [[0, "0°"], [2, "180°"]];
|
||||
if("ESP32" == type) {
|
||||
opts.push([1, "90°"]);
|
||||
opts.push([3, "270°"]);
|
||||
}
|
||||
document.getElementById("dispRot").append(
|
||||
ml("div", {class: "row mb-3"}, [
|
||||
ml("div", {class: "col-12 col-sm-3 my-2"}, "Rotation"),
|
||||
ml("div", {class: "col-12 col-sm-9"}, sel("disp_rot", opts, obj["disp_rot"]))
|
||||
])
|
||||
);
|
||||
|
||||
document.getElementsByName("disp_cont")[0].value = obj["disp_cont"];
|
||||
}
|
||||
|
||||
function parse(root) {
|
||||
if(null != root) {
|
||||
parseMenu(root["menu"]);
|
||||
parseSys(root["system"]);
|
||||
parseGeneric(root["generic"]);
|
||||
parseStaticIp(root["static_ip"]);
|
||||
parseIv(root["inverter"]);
|
||||
parseMqtt(root["mqtt"]);
|
||||
parseNtp(root["ntp"]);
|
||||
parseSun(root["sun"]);
|
||||
|
@ -622,6 +715,7 @@
|
|||
parseCmtRadio(root["radioCmt"], root["system"]["esp_type"]);
|
||||
parseSerial(root["serial"]);
|
||||
parseDisplay(root["display"], root["system"]["esp_type"]);
|
||||
getAjax("/api/inverter/list", parseIv);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -644,11 +738,7 @@
|
|||
e.value = s.value;
|
||||
}
|
||||
|
||||
hiddenInput = document.getElementById("disclaimer")
|
||||
hiddenInput.value = sessionStorage.getItem("gDisclaimer");
|
||||
|
||||
getAjax("/api/setup", parse);
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -4,26 +4,39 @@ html, body {
|
|||
padding: 0;
|
||||
height: 100%;
|
||||
min-height: 100%;
|
||||
background-color: var(--bg);
|
||||
color: var(--fg);
|
||||
}
|
||||
|
||||
h2 {
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
span, li, h3, label, fieldset {
|
||||
color: var(--fg);
|
||||
}
|
||||
|
||||
fieldset, input[type=submit], .btn {
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
#live span {
|
||||
color: var(--fg2);
|
||||
}
|
||||
|
||||
.topnav {
|
||||
background-color: #333;
|
||||
background-color: var(--nav-bg);
|
||||
position: fixed;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.topnav a {
|
||||
color: #fff;
|
||||
color: var(--fg2);
|
||||
padding: 14px 14px;
|
||||
text-decoration: none;
|
||||
font-size: 17px;
|
||||
display: block;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
#topnav a {
|
||||
|
@ -33,23 +46,26 @@ h2 {
|
|||
.topnav a.icon {
|
||||
top: 0;
|
||||
left: 0;
|
||||
background: #333;
|
||||
background: var(--nav-bg);
|
||||
display: block;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.topnav a:hover {
|
||||
background-color: #044e86 !important;
|
||||
color: #000;
|
||||
background-color: var(--primary-hover) !important;
|
||||
}
|
||||
|
||||
.topnav .info {
|
||||
color: #fff;
|
||||
color: var(--fg2);
|
||||
position: absolute;
|
||||
right: 24px;
|
||||
top: 5px;
|
||||
}
|
||||
|
||||
.topnav .mobile {
|
||||
display: none;
|
||||
}
|
||||
|
||||
svg.icon {
|
||||
vertical-align: middle;
|
||||
display: inline-block;
|
||||
|
@ -57,8 +73,24 @@ svg.icon {
|
|||
padding: 5px 7px 5px 0px;
|
||||
}
|
||||
|
||||
.icon-info {
|
||||
fill: var(--info);
|
||||
}
|
||||
|
||||
.icon-warn {
|
||||
fill: var(--warn);
|
||||
}
|
||||
|
||||
.icon-success {
|
||||
fill: var(--success);
|
||||
}
|
||||
|
||||
.wifi {
|
||||
fill: var(--fg2);
|
||||
}
|
||||
|
||||
.title {
|
||||
background-color: #006ec0;
|
||||
background-color: var(--primary);
|
||||
color: #fff !important;
|
||||
padding-left: 80px !important
|
||||
}
|
||||
|
@ -74,7 +106,7 @@ svg.icon {
|
|||
}
|
||||
|
||||
.topnav .active {
|
||||
background-color: #555;
|
||||
background-color: var(--nav-active);
|
||||
}
|
||||
|
||||
span.seperator {
|
||||
|
@ -85,6 +117,197 @@ span.seperator {
|
|||
display: block;
|
||||
}
|
||||
|
||||
#content {
|
||||
max-width: 1140px;
|
||||
}
|
||||
|
||||
.total-h {
|
||||
background-color: var(--total-head-title);
|
||||
color: var(--fg2);
|
||||
}
|
||||
|
||||
.total-bg {
|
||||
background-color: var(--total-bg);
|
||||
color: var(--fg2);
|
||||
}
|
||||
|
||||
.iv-h {
|
||||
background-color: var(--iv-head-title);
|
||||
color: var(--fg2);
|
||||
}
|
||||
|
||||
.iv-bg {
|
||||
background-color: var(--iv-head-bg);
|
||||
color: var(--fg2);
|
||||
}
|
||||
|
||||
.ch-h {
|
||||
background-color: var(--ch-head-title);
|
||||
color: var(--fg2);
|
||||
}
|
||||
|
||||
.ch-bg {
|
||||
background-color: var(--ch-head-bg);
|
||||
color: var(--fg2);
|
||||
}
|
||||
|
||||
.ts-h {
|
||||
background-color: var(--ts-head);
|
||||
color: var(--fg2);
|
||||
}
|
||||
|
||||
.ts-bg {
|
||||
background-color: var(--ts-bg);
|
||||
color: var(--fg2);
|
||||
}
|
||||
|
||||
.hr {
|
||||
border-top: 1px solid var(--iv-head-title);
|
||||
margin: 1rem 0 1rem;
|
||||
}
|
||||
|
||||
p {
|
||||
text-align: justify;
|
||||
font-size: 13pt;
|
||||
color: var(--fg);
|
||||
}
|
||||
|
||||
#footer {
|
||||
background-color: var(--footer-bg);
|
||||
}
|
||||
|
||||
.row { display: flex; max-width: 100%; flex-wrap: wrap; }
|
||||
.col { flex: 1 0 0%; }
|
||||
|
||||
.col-1, .col-2, .col-3, .col-4,
|
||||
.col-5, .col-6, .col-7, .col-8,
|
||||
.col-9, .col-10, .col-11, .col-12 { flex: 0 0 auto; }
|
||||
|
||||
.col-1 { width: 8.333333333%; }
|
||||
.col-2 { width: 16.66666667%; }
|
||||
.col-3 { width: 25%; }
|
||||
.col-4 { width: 33.33333333%; }
|
||||
.col-5 { width: 41.66666667%; }
|
||||
.col-6 { width: 50%; }
|
||||
.col-7 { width: 58.33333333%; }
|
||||
.col-8 { width: 66.66666667%; }
|
||||
.col-9 { width: 75%; }
|
||||
.col-10 { width: 83.33333333%; }
|
||||
.col-11 { width: 91.66666667%; }
|
||||
.col-12 { width: 100%; }
|
||||
|
||||
.p-1 { padding: 0.25rem; }
|
||||
.p-2 { padding: 0.5rem; }
|
||||
.p-3 { padding: 1rem; }
|
||||
.p-4 { padding: 1.5rem; }
|
||||
.p-5 { padding: 3rem; }
|
||||
|
||||
.px-1 { padding: 0 0.25rem 0 0.25rem; }
|
||||
.px-2 { padding: 0 0.5rem 0 0.5rem; }
|
||||
.px-3 { padding: 0 1rem 0 1rem; }
|
||||
.px-4 { padding: 0 1.5rem 0 1.5rem; }
|
||||
.px-5 { padding: 0 3rem 0 3rem; }
|
||||
|
||||
.py-1 { padding: 0.25rem 0 0.25rem; }
|
||||
.py-2 { padding: 0.5rem 0 0.5rem; }
|
||||
.py-3 { padding: 1rem 0 1rem; }
|
||||
.py-4 { padding: 1.5rem 0 1.5rem; }
|
||||
.py-5 { padding: 3rem 0 3rem; }
|
||||
|
||||
.mx-1 { margin: 0 0.25rem 0 0.25rem; }
|
||||
.mx-2 { margin: 0 0.5rem 0 0.5rem; }
|
||||
.mx-3 { margin: 0 1rem 0 1rem; }
|
||||
.mx-4 { margin: 0 1.5rem 0 1.5rem; }
|
||||
.mx-5 { margin: 0 3rem 0 3rem; }
|
||||
|
||||
.my-1 { margin: 0.25rem 0 0.25rem; }
|
||||
.my-2 { margin: 0.5rem 0 0.5rem; }
|
||||
.my-3 { margin: 1rem 0 1rem; }
|
||||
.my-4 { margin: 1.5rem 0 1.5rem; }
|
||||
.my-5 { margin: 3rem 0 3rem; }
|
||||
|
||||
.mt-1 { margin-top: 0.25rem }
|
||||
.mt-2 { margin-top: 0.5rem }
|
||||
.mt-3 { margin-top: 1rem }
|
||||
.mt-4 { margin-top: 1.5rem }
|
||||
.mt-5 { margin-top: 3rem }
|
||||
|
||||
.mb-1 { margin-bottom: 0.25rem }
|
||||
.mb-2 { margin-bottom: 0.5rem }
|
||||
.mb-3 { margin-bottom: 1rem }
|
||||
.mb-4 { margin-bottom: 1.5rem }
|
||||
.mb-5 { margin-bottom: 3rem }
|
||||
|
||||
.fs-1 { font-size: 3.5rem; }
|
||||
.fs-2 { font-size: 3rem; }
|
||||
.fs-3 { font-size: 2.5rem; }
|
||||
.fs-4 { font-size: 2rem; }
|
||||
.fs-5 { font-size: 1.75rem; }
|
||||
.fs-6 { font-size: 1.5rem; }
|
||||
.fs-7 { font-size: 1.25rem; }
|
||||
.fs-8 { font-size: 1rem; }
|
||||
.fs-9 { font-size: 0.75rem; }
|
||||
.fs-10 { font-size: 0.5rem; }
|
||||
|
||||
.a-r { text-align: right; }
|
||||
.a-c { text-align: center; }
|
||||
|
||||
.row > * {
|
||||
padding-left: 0.5rem;
|
||||
padding-right: 0.5rem;
|
||||
}
|
||||
|
||||
*, ::after, ::before {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* sm */
|
||||
@media(min-width: 768px) {
|
||||
.col-sm-1 { width: 8.333333333%; }
|
||||
.col-sm-2 { width: 16.66666667%; }
|
||||
.col-sm-3 { width: 25%; }
|
||||
.col-sm-4 { width: 33.33333333%; }
|
||||
.col-sm-5 { width: 41.66666667%; }
|
||||
.col-sm-6 { width: 50%; }
|
||||
.col-sm-7 { width: 58.33333333%; }
|
||||
.col-sm-8 { width: 66.66666667%; }
|
||||
.col-sm-9 { width: 75%; }
|
||||
.col-sm-10 { width: 83.33333333%; }
|
||||
.col-sm-11 { width: 91.66666667%; }
|
||||
.col-sm-12 { width: 100%; }
|
||||
|
||||
.mb-sm-1 { margin-bottom: 0.25rem }
|
||||
.mb-sm-2 { margin-bottom: 0.5rem }
|
||||
.mb-sm-3 { margin-bottom: 1rem }
|
||||
.mb-sm-4 { margin-bottom: 1.5rem }
|
||||
.mb-sm-5 { margin-bottom: 3rem }
|
||||
|
||||
.fs-sm-1 { font-size: 3.5rem; }
|
||||
.fs-sm-2 { font-size: 3rem; }
|
||||
.fs-sm-3 { font-size: 2.5rem; }
|
||||
.fs-sm-4 { font-size: 2rem; }
|
||||
.fs-sm-5 { font-size: 1.75rem; }
|
||||
.fs-sm-6 { font-size: 1.5rem; }
|
||||
.fs-sm-7 { font-size: 1.25rem; }
|
||||
.fs-sm-8 { font-size: 1rem; }
|
||||
}
|
||||
|
||||
/* md */
|
||||
@media(min-width: 992px) {
|
||||
.col-md-1 { width: 8.333333333%; }
|
||||
.col-md-2 { width: 16.66666667%; }
|
||||
.col-md-3 { width: 25%; }
|
||||
.col-md-4 { width: 33.33333333%; }
|
||||
.col-md-5 { width: 41.66666667%; }
|
||||
.col-md-6 { width: 50%; }
|
||||
.col-md-7 { width: 58.33333333%; }
|
||||
.col-md-8 { width: 66.66666667%; }
|
||||
.col-md-9 { width: 75%; }
|
||||
.col-md-10 { width: 83.33333333%; }
|
||||
.col-md-11 { width: 91.66666667%; }
|
||||
.col-md-12 { width: 100%; }
|
||||
}
|
||||
|
||||
#wrapper {
|
||||
min-height: 100%;
|
||||
}
|
||||
|
@ -97,7 +320,6 @@ span.seperator {
|
|||
#footer {
|
||||
height: 121px;
|
||||
margin-top: -121px;
|
||||
background-color: #555;
|
||||
width: 100%;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
@ -131,7 +353,7 @@ span.seperator {
|
|||
}
|
||||
|
||||
.hide {
|
||||
display: none;
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 992px) {
|
||||
|
@ -152,7 +374,7 @@ span.seperator {
|
|||
padding-left: 24px !important;
|
||||
}
|
||||
|
||||
.topnav .hide {
|
||||
.topnav .mobile {
|
||||
display: block;
|
||||
}
|
||||
|
||||
|
@ -172,13 +394,6 @@ span.seperator {
|
|||
}
|
||||
}
|
||||
|
||||
/** old CSS below **/
|
||||
|
||||
p {
|
||||
text-align: justify;
|
||||
font-size: 13pt;
|
||||
}
|
||||
|
||||
p.lic, p.lic a {
|
||||
font-size: 8pt;
|
||||
color: #999;
|
||||
|
@ -187,11 +402,11 @@ p.lic, p.lic a {
|
|||
.des {
|
||||
margin-top: 20px;
|
||||
font-size: 13pt;
|
||||
color: #006ec0;
|
||||
color: var(--secondary);
|
||||
}
|
||||
|
||||
.s_active, .s_collapsible:hover {
|
||||
background-color: #044e86;
|
||||
background-color: var(--primary-hover);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
|
@ -201,34 +416,34 @@ p.lic, p.lic a {
|
|||
}
|
||||
|
||||
.s_collapsible {
|
||||
background-color: #006ec0;
|
||||
background-color: var(--primary);
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
padding: 18px;
|
||||
padding: 12px;
|
||||
width: 100%;
|
||||
border: none;
|
||||
text-align: left;
|
||||
outline: none;
|
||||
font-size: 15px;
|
||||
margin-bottom: 4px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.subdes {
|
||||
font-size: 12pt;
|
||||
color: #006ec0;
|
||||
color: var(--secondary);
|
||||
margin-left: 7px;
|
||||
}
|
||||
|
||||
.subsubdes {
|
||||
font-size:12pt;
|
||||
color:#006ec0;
|
||||
color:var(--secondary);
|
||||
margin: 0 0 7px 12px;
|
||||
}
|
||||
|
||||
a:link, a:visited {
|
||||
text-decoration: none;
|
||||
font-size: 13pt;
|
||||
color: #006ec0;
|
||||
color: var(--secondary);
|
||||
}
|
||||
|
||||
a:hover, a:focus {
|
||||
|
@ -236,14 +451,14 @@ a:hover, a:focus {
|
|||
}
|
||||
|
||||
a.btn {
|
||||
background-color: #006ec0;
|
||||
background-color: var(--primary);
|
||||
color: #fff;
|
||||
padding: 7px 15px 7px 15px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
a.btn:hover {
|
||||
background-color: #044e86 !important;
|
||||
background-color: var(--primary-hover) !important;
|
||||
}
|
||||
|
||||
input, select {
|
||||
|
@ -251,11 +466,13 @@ input, select {
|
|||
font-size: 13pt;
|
||||
}
|
||||
|
||||
input.text, select {
|
||||
width: 70%;
|
||||
input[type=text], input[type=password], select, input[type=number] {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
margin-bottom: 10px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
background-color: var(--input-bg);
|
||||
color: var(--fg);
|
||||
}
|
||||
|
||||
input.sh {
|
||||
|
@ -268,7 +485,7 @@ input.btnDel {
|
|||
}
|
||||
|
||||
input.btn {
|
||||
background-color: #006ec0;
|
||||
background-color: var(--primary);
|
||||
color: #fff;
|
||||
border: 0px;
|
||||
padding: 7px 20px 7px 20px;
|
||||
|
@ -299,10 +516,6 @@ pre {
|
|||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
fieldset {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.left {
|
||||
float: left;
|
||||
}
|
||||
|
@ -311,88 +524,11 @@ fieldset {
|
|||
float: right;
|
||||
}
|
||||
|
||||
div.ch-iv {
|
||||
width: 100%;
|
||||
background-color: #32b004;
|
||||
display: inline-block;
|
||||
margin-bottom: 15px;
|
||||
padding-bottom: 20px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
div.ch {
|
||||
width: 220px;
|
||||
min-height: 350px;
|
||||
background-color: #006ec0;
|
||||
display: inline-block;
|
||||
margin: 0 20px 10px 0px;
|
||||
overflow: auto;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
div.ch-all {
|
||||
width: 100%;
|
||||
background-color: #b06e04;
|
||||
display: inline-block;
|
||||
margin-bottom: 15px;
|
||||
padding-bottom: 20px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
div.ch .value, div.ch .info, div.ch .head, div.ch-iv .value, div.ch-iv .info, div.ch-iv .head, div.ch-all .value, div.ch-all .info, div.ch-all .head {
|
||||
color: #fff;
|
||||
display: block;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.subgrp {
|
||||
float: left;
|
||||
width: 220px;
|
||||
}
|
||||
|
||||
div.ch .unit, div.ch-iv .unit, div.ch-all .unit {
|
||||
font-size: 19px;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
div.ch .value, div.ch-iv .value, div.ch-all .value {
|
||||
margin-top: 20px;
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
div.ch .info, div.ch-iv .info, div.ch-all .info {
|
||||
margin-top: 3px;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
div.ch .head {
|
||||
background-color: #003c80;
|
||||
padding: 10px 0 10px 0;
|
||||
}
|
||||
|
||||
div.ch-all .head {
|
||||
background-color: #8e5903;
|
||||
padding: 10px 0 10px 0;
|
||||
}
|
||||
|
||||
div.ch-iv .head {
|
||||
background-color: #1c6800;
|
||||
padding: 10px 0 10px 0;
|
||||
}
|
||||
|
||||
div.iv {
|
||||
max-width: 960px;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
div.ts {
|
||||
font-size: 13px;
|
||||
background-color: #ddd;
|
||||
border-top: 7px solid #999;
|
||||
padding: 7px;
|
||||
}
|
||||
|
||||
div.ModPwr, div.ModName, div.YieldCor {
|
||||
width:70%;
|
||||
display: inline-block;
|
||||
|
@ -443,104 +579,55 @@ div.hr {
|
|||
}
|
||||
|
||||
#login {
|
||||
width: 300px;
|
||||
width: 450px;
|
||||
height: 200px;
|
||||
border: 1px solid #ccc;
|
||||
background-color: #eee;
|
||||
background-color: var(--input-bg);
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
margin-top: -160px;
|
||||
margin-left: -150px;
|
||||
}
|
||||
|
||||
#login .pad {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
#login .pad input {
|
||||
width: 100%;
|
||||
padding: 7px 0 7px 0;
|
||||
border: 0px;
|
||||
margin-bottom: 10px;
|
||||
margin-left: -225px;
|
||||
}
|
||||
|
||||
|
||||
.head {
|
||||
background-color: #006ec0;
|
||||
background-color: var(--primary);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.row { display: flex; max-width: 100%; flex-wrap: wrap; }
|
||||
.col { flex: 1 0 0%; }
|
||||
|
||||
.col-1, .col-2, .col-3, .col-4,
|
||||
.col-5, .col-6, .col-7, .col-8,
|
||||
.col-9, .col-10, .col-11, .col-12 { flex: 0 0 auto; }
|
||||
|
||||
|
||||
.col-1 { width: 8.333333333%; }
|
||||
.col-2 { width: 16.66666667%; }
|
||||
.col-3 { width: 25%; }
|
||||
.col-4 { width: 33.33333333%; }
|
||||
.col-5 { width: 41.66666667%; }
|
||||
.col-6 { width: 50%; }
|
||||
.col-7 { width: 58.33333333%; }
|
||||
.col-8 { width: 66.66666667%; }
|
||||
.col-9 { width: 75%; }
|
||||
.col-10 { width: 83.33333333%; }
|
||||
.col-11 { width: 91.66666667%; }
|
||||
.col-12 { width: 100%; }
|
||||
|
||||
.p-1 { padding: 0.25rem; }
|
||||
.p-2 { padding: 0.5rem; }
|
||||
.p-3 { padding: 1rem; }
|
||||
.p-4 { padding: 1.5rem; }
|
||||
.p-5 { padding: 3rem; }
|
||||
|
||||
.mt-1 { margin-top: 0.25rem }
|
||||
.mt-2 { margin-top: 0.5rem }
|
||||
.mt-3 { margin-top: 1rem }
|
||||
.mt-4 { margin-top: 1.5rem }
|
||||
.mt-5 { margin-top: 3rem }
|
||||
|
||||
.mb-1 { margin-bottom: 0.25rem }
|
||||
.mb-2 { margin-bottom: 0.5rem }
|
||||
.mb-3 { margin-bottom: 1rem }
|
||||
.mb-4 { margin-bottom: 1.5rem }
|
||||
.mb-5 { margin-bottom: 3rem }
|
||||
|
||||
.a-r { text-align: right; }
|
||||
.a-c { text-align: center; }
|
||||
|
||||
/* sm */
|
||||
@media(min-width: 768px) {
|
||||
.col-sm-1 { width: 8.333333333%; }
|
||||
.col-sm-2 { width: 16.66666667%; }
|
||||
.col-sm-3 { width: 25%; }
|
||||
.col-sm-4 { width: 33.33333333%; }
|
||||
.col-sm-5 { width: 41.66666667%; }
|
||||
.col-sm-6 { width: 50%; }
|
||||
.col-sm-7 { width: 58.33333333%; }
|
||||
.col-sm-8 { width: 66.66666667%; }
|
||||
.col-sm-9 { width: 75%; }
|
||||
.col-sm-10 { width: 83.33333333%; }
|
||||
.col-sm-11 { width: 91.66666667%; }
|
||||
.col-sm-12 { width: 100%; }
|
||||
.css-tooltip{
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* md */
|
||||
@media(min-width: 992px) {
|
||||
.col-md-1 { width: 8.333333333%; }
|
||||
.col-md-2 { width: 16.66666667%; }
|
||||
.col-md-3 { width: 25%; }
|
||||
.col-md-4 { width: 33.33333333%; }
|
||||
.col-md-5 { width: 41.66666667%; }
|
||||
.col-md-6 { width: 50%; }
|
||||
.col-md-7 { width: 58.33333333%; }
|
||||
.col-md-8 { width: 66.66666667%; }
|
||||
.col-md-9 { width: 75%; }
|
||||
.col-md-10 { width: 83.33333333%; }
|
||||
.col-md-11 { width: 91.66666667%; }
|
||||
.col-md-12 { width: 100%; }
|
||||
.css-tooltip:hover:after{
|
||||
content:attr(data-tooltip);
|
||||
background:#000;
|
||||
padding:5px;
|
||||
border-radius:3px;
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
transform: translate(-50%,-100%);
|
||||
margin:0 auto;
|
||||
color:#FFF;
|
||||
min-width:100px;
|
||||
min-width:150px;
|
||||
top:-5px;
|
||||
left: 50%;
|
||||
text-align:center;
|
||||
}
|
||||
.css-tooltip:hover:before {
|
||||
top:-5px;
|
||||
left: 50%;
|
||||
border: solid transparent;
|
||||
content: " ";
|
||||
height: 0;
|
||||
width: 0;
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
border-color: rgba(0, 0, 0, 0);
|
||||
border-top-color: #000;
|
||||
border-width: 5px;
|
||||
margin-left: -5px;
|
||||
transform: translate(0,0px);
|
||||
}
|
||||
|
|
|
@ -2,21 +2,10 @@
|
|||
<html>
|
||||
<head>
|
||||
<title>System</title>
|
||||
<link rel="stylesheet" type="text/css" href="style.css"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<script type="text/javascript" src="api.js"></script>
|
||||
{#HTML_HEADER}
|
||||
</head>
|
||||
<body>
|
||||
<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="hide"></div>
|
||||
<div id="wifiicon" class="info"></div>
|
||||
</div>
|
||||
{#HTML_NAV}
|
||||
<div id="wrapper">
|
||||
<div id="content">
|
||||
<pre id="stat"></pre>
|
||||
|
@ -26,25 +15,10 @@
|
|||
<div id="html" class="mt-3 mb-3"></div>
|
||||
</div>
|
||||
</div>
|
||||
<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><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>
|
||||
{#HTML_FOOTER}
|
||||
<script type="text/javascript">
|
||||
function parseGeneric(obj) {
|
||||
parseVersion(obj);
|
||||
parseNav(obj);
|
||||
parseESP(obj);
|
||||
parseRssi(obj);
|
||||
}
|
||||
|
@ -123,7 +97,6 @@
|
|||
|
||||
function parse(obj) {
|
||||
if(null != obj) {
|
||||
parseMenu(obj["menu"]);
|
||||
parseGeneric(obj["generic"]);
|
||||
|
||||
if(null != obj["refresh"]) {
|
||||
|
|
|
@ -2,66 +2,36 @@
|
|||
<html>
|
||||
<head>
|
||||
<title>Update</title>
|
||||
<link rel="stylesheet" type="text/css" href="style.css"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<script type="text/javascript" src="api.js"></script>
|
||||
{#HTML_HEADER}
|
||||
</head>
|
||||
<body>
|
||||
<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="hide"></div>
|
||||
<div id="wifiicon" class="info"></div>
|
||||
</div>
|
||||
{#HTML_NAV}
|
||||
<div id="wrapper">
|
||||
<div id="content">
|
||||
<form id="form" method="POST" action="/update" enctype="multipart/form-data" accept-charset="utf-8">
|
||||
<input type="file" name="update">
|
||||
<input type="button" class="btn" value="Update" onclick="hide()">
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<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><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>
|
||||
<fieldset>
|
||||
<legend class="des">Select firmware file (*.bin)</legend>
|
||||
<form id="form" method="POST" action="/update" enctype="multipart/form-data" accept-charset="utf-8">
|
||||
<input type="file" name="update">
|
||||
<input type="button" class="btn" value="Update" onclick="hide()">
|
||||
</form>
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
{#HTML_FOOTER}
|
||||
<script type="text/javascript">
|
||||
function parseGeneric(obj) {
|
||||
parseVersion(obj);
|
||||
parseNav(obj);
|
||||
parseESP(obj);
|
||||
parseRssi(obj);
|
||||
}
|
||||
|
||||
function parse(obj) {
|
||||
if(null != obj) {
|
||||
parseMenu(obj["menu"]);
|
||||
parseGeneric(obj["generic"]);
|
||||
}
|
||||
}
|
||||
|
||||
function hide() {
|
||||
document.getElementById("form").submit();
|
||||
var e = document.getElementById("content");
|
||||
e.replaceChildren(span("update started"));
|
||||
}
|
||||
|
||||
getAjax("/api/index", parse);
|
||||
getAjax("/api/generic", parseGeneric);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -2,144 +2,227 @@
|
|||
<html>
|
||||
<head>
|
||||
<title>Live</title>
|
||||
<link rel="stylesheet" type="text/css" href="style.css"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
{#HTML_HEADER}
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<script type="text/javascript" src="api.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<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="hide"></div>
|
||||
<div id="wifiicon" class="info"></div>
|
||||
</div>
|
||||
{#HTML_NAV}
|
||||
<div id="wrapper">
|
||||
<div id="content">
|
||||
<div id="live"></div>
|
||||
<p>Every <span id="refresh"></span> seconds the values are updated</p>
|
||||
</div>
|
||||
</div>
|
||||
<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><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>
|
||||
{#HTML_FOOTER}
|
||||
<script type="text/javascript">
|
||||
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) {
|
||||
if(true == exeOnce){
|
||||
parseVersion(obj);
|
||||
parseNav(obj);
|
||||
parseESP(obj);
|
||||
}
|
||||
parseRssi(obj);
|
||||
}
|
||||
|
||||
function parseIv(obj, root) {
|
||||
var ivHtml = [];
|
||||
function numBig(val, unit, des) {
|
||||
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"]);
|
||||
tDiv.appendChild(span("Total", ["head"]));
|
||||
var total = new Array(root.ch0_fld_names.length).fill(0);
|
||||
if(obj.length > 1)
|
||||
ivHtml.push(tDiv);
|
||||
function numMid(val, unit, des) {
|
||||
return ml("div", {class: "col-6 col-sm-4 col-md-3 mb-2"}, [
|
||||
ml("div", {class: "row"},
|
||||
ml("div", {class: "col"}, [
|
||||
ml("span", {class: "fs-6"}, String(val)),
|
||||
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) {
|
||||
if(iv["enabled"]) {
|
||||
main = div(["iv"]);
|
||||
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);
|
||||
}
|
||||
function totals() {
|
||||
for(var i = 0; i < 5; i++) {
|
||||
total[i] = Math.round(total[i] * 100) / 100;
|
||||
}
|
||||
|
||||
// total
|
||||
if(obj.length > 1) {
|
||||
for(var j = 0; j < root.ch0_fld_names.length; j++) {
|
||||
var val = Math.round(total[j] * 100) / 100;
|
||||
if((j == 2) || (j == 6) || (j == 7) || (j == 8) || (j == 10)) {
|
||||
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"]));
|
||||
tDiv.appendChild(sub);
|
||||
}
|
||||
return ml("div", {class: "row mt-3 mb-5"},
|
||||
ml("div", {class: "col"}, [
|
||||
ml("div", {class: "p-2 total-h"},
|
||||
ml("div", {class: "row"},
|
||||
ml("div", {class: "col mx-2 mx-md-1"}, "TOTAL")
|
||||
),
|
||||
),
|
||||
ml("div", {class: "p-2 total-bg"}, [
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById("live").replaceChildren(...ivHtml);
|
||||
if(last) {
|
||||
if(mNum > 1)
|
||||
mIvHtml.unshift(totals());
|
||||
document.getElementById("live").replaceChildren(...mIvHtml);
|
||||
}
|
||||
}
|
||||
|
||||
function parse(obj) {
|
||||
if(null != obj) {
|
||||
if(true == exeOnce)
|
||||
parseMenu(obj["menu"]);
|
||||
parseGeneric(obj["generic"]);
|
||||
parseIv(obj["inverter"], obj);
|
||||
document.getElementById("refresh").innerHTML = obj["refresh_interval"];
|
||||
units = Object.assign({}, obj["fld_units"]);
|
||||
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) {
|
||||
window.setInterval("getAjax('/api/live', parse)", obj["refresh_interval"] * 1000);
|
||||
window.setInterval("getAjax('/api/live', parse)", obj["refresh"] * 1000);
|
||||
exeOnce = false;
|
||||
}
|
||||
}
|
||||
|
|
309
src/web/web.h
309
src/web/web.h
|
@ -8,36 +8,35 @@
|
|||
|
||||
#include "../utils/dbg.h"
|
||||
#ifdef ESP32
|
||||
#include "AsyncTCP.h"
|
||||
#include "Update.h"
|
||||
#include "AsyncTCP.h"
|
||||
#include "Update.h"
|
||||
#else
|
||||
#include "ESPAsyncTCP.h"
|
||||
#include "ESPAsyncTCP.h"
|
||||
#endif
|
||||
#include "ESPAsyncWebServer.h"
|
||||
|
||||
#include "../appInterface.h"
|
||||
|
||||
#include "../hm/hmSystem.h"
|
||||
#include "../utils/helper.h"
|
||||
|
||||
#include "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/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/setup_html.h"
|
||||
#include "html/h/style_css.h"
|
||||
#include "html/h/system_html.h"
|
||||
#include "html/h/update_html.h"
|
||||
#include "html/h/visualization_html.h"
|
||||
|
||||
#define WEB_SERIAL_BUF_SIZE 2048
|
||||
|
||||
const char* const pinArgNames[] = {"pinCs", "pinCe", "pinIrq", "pinLed0", "pinLed1", "pinCsb", "pinFcsb", "pinGpio3"};
|
||||
|
||||
template<class HMSYSTEM>
|
||||
template <class HMSYSTEM>
|
||||
class Web {
|
||||
public:
|
||||
public:
|
||||
Web(void) : mWeb(80), mEvts("/events") {
|
||||
mProtected = true;
|
||||
mLogoutTimeout = 0;
|
||||
|
@ -57,6 +56,7 @@ class Web {
|
|||
mWeb.on("/", HTTP_GET, std::bind(&Web::onIndex, this, std::placeholders::_1));
|
||||
mWeb.on("/login", HTTP_ANY, std::bind(&Web::onLogin, this, std::placeholders::_1));
|
||||
mWeb.on("/logout", HTTP_GET, std::bind(&Web::onLogout, this, std::placeholders::_1));
|
||||
mWeb.on("/colors.css", HTTP_GET, std::bind(&Web::onColor, this, std::placeholders::_1));
|
||||
mWeb.on("/style.css", HTTP_GET, std::bind(&Web::onCss, this, std::placeholders::_1));
|
||||
mWeb.on("/api.js", HTTP_GET, std::bind(&Web::onApiJs, this, std::placeholders::_1));
|
||||
mWeb.on("/favicon.ico", HTTP_GET, std::bind(&Web::onFavicon, this, std::placeholders::_1));
|
||||
|
@ -99,18 +99,18 @@ class Web {
|
|||
}
|
||||
|
||||
void tickSecond() {
|
||||
if(0 != mLogoutTimeout) {
|
||||
if (0 != mLogoutTimeout) {
|
||||
mLogoutTimeout -= 1;
|
||||
if(0 == mLogoutTimeout) {
|
||||
if(strlen(mConfig->sys.adminPwd) > 0)
|
||||
if (0 == mLogoutTimeout) {
|
||||
if (strlen(mConfig->sys.adminPwd) > 0)
|
||||
mProtected = true;
|
||||
}
|
||||
|
||||
DPRINTLN(DBG_DEBUG, "auto logout in " + String(mLogoutTimeout));
|
||||
}
|
||||
|
||||
if(mSerialClientConnnected) {
|
||||
if(mSerialBufFill > 0) {
|
||||
if (mSerialClientConnnected) {
|
||||
if (mSerialBufFill > 0) {
|
||||
mEvts.send(mSerialBuf, "serial", millis());
|
||||
memset(mSerialBuf, 0, WEB_SERIAL_BUF_SIZE);
|
||||
mSerialBufFill = 0;
|
||||
|
@ -133,23 +133,23 @@ class Web {
|
|||
void showUpdate2(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) {
|
||||
mApp->setOnUpdate();
|
||||
|
||||
if(!index) {
|
||||
if (!index) {
|
||||
Serial.printf("Update Start: %s\n", filename.c_str());
|
||||
#ifndef ESP32
|
||||
#ifndef ESP32
|
||||
Update.runAsync(true);
|
||||
#endif
|
||||
if(!Update.begin((ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000)) {
|
||||
#endif
|
||||
if (!Update.begin((ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000)) {
|
||||
Update.printError(Serial);
|
||||
}
|
||||
}
|
||||
if(!Update.hasError()) {
|
||||
if(Update.write(data, len) != len){
|
||||
if (!Update.hasError()) {
|
||||
if (Update.write(data, len) != len) {
|
||||
Update.printError(Serial);
|
||||
}
|
||||
}
|
||||
if(final) {
|
||||
if(Update.end(true)) {
|
||||
Serial.printf("Update Success: %uB\n", index+len);
|
||||
if (final) {
|
||||
if (Update.end(true)) {
|
||||
Serial.printf("Update Success: %uB\n", index + len);
|
||||
} else {
|
||||
Update.printError(Serial);
|
||||
}
|
||||
|
@ -157,27 +157,26 @@ class Web {
|
|||
}
|
||||
|
||||
void onUpload2(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) {
|
||||
if(!index) {
|
||||
if (!index) {
|
||||
mUploadFail = false;
|
||||
mUploadFp = LittleFS.open("/tmp.json", "w");
|
||||
if(!mUploadFp) {
|
||||
if (!mUploadFp) {
|
||||
DPRINTLN(DBG_ERROR, F("can't open file!"));
|
||||
mUploadFail = true;
|
||||
mUploadFp.close();
|
||||
}
|
||||
}
|
||||
mUploadFp.write(data, len);
|
||||
if(final) {
|
||||
if (final) {
|
||||
mUploadFp.close();
|
||||
File fp = LittleFS.open("/tmp.json", "r");
|
||||
if(!fp)
|
||||
if (!fp)
|
||||
mUploadFail = true;
|
||||
else {
|
||||
if(!mApp->readSettings("tmp.json")) {
|
||||
if (!mApp->readSettings("tmp.json")) {
|
||||
mUploadFail = true;
|
||||
DPRINTLN(DBG_ERROR, F("upload JSON error!"));
|
||||
}
|
||||
else
|
||||
} else
|
||||
mApp->saveSettings();
|
||||
}
|
||||
DPRINTLN(DBG_INFO, F("upload finished!"));
|
||||
|
@ -185,18 +184,17 @@ class Web {
|
|||
}
|
||||
|
||||
void serialCb(String msg) {
|
||||
if(!mSerialClientConnnected)
|
||||
if (!mSerialClientConnnected)
|
||||
return;
|
||||
|
||||
msg.replace("\r\n", "<rn>");
|
||||
if(mSerialAddTime) {
|
||||
if((9 + mSerialBufFill) < WEB_SERIAL_BUF_SIZE) {
|
||||
if(mApp->getTimestamp() > 0) {
|
||||
if (mSerialAddTime) {
|
||||
if ((9 + mSerialBufFill) < WEB_SERIAL_BUF_SIZE) {
|
||||
if (mApp->getTimestamp() > 0) {
|
||||
strncpy(&mSerialBuf[mSerialBufFill], mApp->getTimeStr(mApp->getTimezoneOffset()).c_str(), 9);
|
||||
mSerialBufFill += 9;
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
mSerialBufFill = 0;
|
||||
mEvts.send("webSerial, buffer overflow!<rn>", "serial", millis());
|
||||
return;
|
||||
|
@ -204,32 +202,44 @@ class Web {
|
|||
mSerialAddTime = false;
|
||||
}
|
||||
|
||||
if(msg.endsWith("<rn>"))
|
||||
if (msg.endsWith("<rn>"))
|
||||
mSerialAddTime = true;
|
||||
|
||||
uint16_t length = msg.length();
|
||||
if((length + mSerialBufFill) < WEB_SERIAL_BUF_SIZE) {
|
||||
if ((length + mSerialBufFill) < WEB_SERIAL_BUF_SIZE) {
|
||||
strncpy(&mSerialBuf[mSerialBufFill], msg.c_str(), length);
|
||||
mSerialBufFill += length;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
mSerialBufFill = 0;
|
||||
mEvts.send("webSerial, buffer overflow!<rn>", "serial", millis());
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
void checkRedirect(AsyncWebServerRequest *request) {
|
||||
if ((mConfig->sys.protectionMask & PROT_MASK_INDEX) != PROT_MASK_INDEX)
|
||||
request->redirect(F("/index"));
|
||||
else if ((mConfig->sys.protectionMask & PROT_MASK_LIVE) != PROT_MASK_LIVE)
|
||||
request->redirect(F("/live"));
|
||||
else if ((mConfig->sys.protectionMask & PROT_MASK_SERIAL) != PROT_MASK_SERIAL)
|
||||
request->redirect(F("/serial"));
|
||||
else if ((mConfig->sys.protectionMask & PROT_MASK_SYSTEM) != PROT_MASK_SYSTEM)
|
||||
request->redirect(F("/system"));
|
||||
else
|
||||
request->redirect(F("/login"));
|
||||
}
|
||||
|
||||
void onUpdate(AsyncWebServerRequest *request) {
|
||||
DPRINTLN(DBG_VERBOSE, F("onUpdate"));
|
||||
|
||||
if(CHECK_MASK(mConfig->sys.protectionMask, PROT_MASK_UPDATE)) {
|
||||
if(mProtected) {
|
||||
request->redirect("/login");
|
||||
if (CHECK_MASK(mConfig->sys.protectionMask, PROT_MASK_UPDATE)) {
|
||||
if (mProtected) {
|
||||
checkRedirect(request);
|
||||
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");
|
||||
request->send(response);
|
||||
}
|
||||
|
@ -238,34 +248,32 @@ class Web {
|
|||
bool reboot = !Update.hasError();
|
||||
|
||||
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";
|
||||
else
|
||||
html += "failed";
|
||||
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");
|
||||
request->send(response);
|
||||
//if(reboot)
|
||||
mApp->setRebootFlag();
|
||||
mApp->setRebootFlag();
|
||||
}
|
||||
|
||||
void onUpload(AsyncWebServerRequest *request) {
|
||||
bool reboot = !mUploadFail;
|
||||
|
||||
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";
|
||||
else
|
||||
html += "failed";
|
||||
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");
|
||||
request->send(response);
|
||||
//if(reboot)
|
||||
mApp->setRebootFlag();
|
||||
mApp->setRebootFlag();
|
||||
}
|
||||
|
||||
void onConnect(AsyncEventSourceClient *client) {
|
||||
|
@ -273,7 +281,7 @@ class Web {
|
|||
|
||||
mSerialClientConnnected = true;
|
||||
|
||||
if(client->lastId())
|
||||
if (client->lastId())
|
||||
DPRINTLN(DBG_VERBOSE, "Client reconnected! Last message ID that it got is: " + String(client->lastId()));
|
||||
|
||||
client->send("hello!", NULL, millis(), 1000);
|
||||
|
@ -282,14 +290,14 @@ class Web {
|
|||
void onIndex(AsyncWebServerRequest *request) {
|
||||
DPRINTLN(DBG_VERBOSE, F("onIndex"));
|
||||
|
||||
if(CHECK_MASK(mConfig->sys.protectionMask, PROT_MASK_INDEX)) {
|
||||
if(mProtected) {
|
||||
request->redirect("/login");
|
||||
if (CHECK_MASK(mConfig->sys.protectionMask, PROT_MASK_INDEX)) {
|
||||
if (mProtected) {
|
||||
checkRedirect(request);
|
||||
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");
|
||||
request->send(response);
|
||||
}
|
||||
|
@ -297,14 +305,14 @@ class Web {
|
|||
void onLogin(AsyncWebServerRequest *request) {
|
||||
DPRINTLN(DBG_VERBOSE, F("onLogin"));
|
||||
|
||||
if(request->args() > 0) {
|
||||
if(String(request->arg("pwd")) == String(mConfig->sys.adminPwd)) {
|
||||
if (request->args() > 0) {
|
||||
if (String(request->arg("pwd")) == String(mConfig->sys.adminPwd)) {
|
||||
mProtected = false;
|
||||
request->redirect("/");
|
||||
}
|
||||
}
|
||||
|
||||
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");
|
||||
request->send(response);
|
||||
}
|
||||
|
@ -312,14 +320,25 @@ class Web {
|
|||
void onLogout(AsyncWebServerRequest *request) {
|
||||
DPRINTLN(DBG_VERBOSE, F("onLogout"));
|
||||
|
||||
if(mProtected) {
|
||||
request->redirect("/login");
|
||||
if (mProtected) {
|
||||
checkRedirect(request);
|
||||
return;
|
||||
}
|
||||
|
||||
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");
|
||||
request->send(response);
|
||||
}
|
||||
|
@ -348,22 +367,22 @@ class Web {
|
|||
}
|
||||
|
||||
void showNotFound(AsyncWebServerRequest *request) {
|
||||
if(mProtected)
|
||||
request->redirect("/login");
|
||||
if (mProtected)
|
||||
checkRedirect(request);
|
||||
else
|
||||
request->redirect("/setup");
|
||||
}
|
||||
|
||||
void onReboot(AsyncWebServerRequest *request) {
|
||||
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");
|
||||
request->send(response);
|
||||
}
|
||||
|
||||
void showErase(AsyncWebServerRequest *request) {
|
||||
if(mProtected) {
|
||||
request->redirect("/login");
|
||||
if (mProtected) {
|
||||
checkRedirect(request);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -373,34 +392,32 @@ class Web {
|
|||
}
|
||||
|
||||
void showFactoryRst(AsyncWebServerRequest *request) {
|
||||
if(mProtected) {
|
||||
request->redirect("/login");
|
||||
if (mProtected) {
|
||||
checkRedirect(request);
|
||||
return;
|
||||
}
|
||||
|
||||
DPRINTLN(DBG_VERBOSE, F("showFactoryRst"));
|
||||
String content = "";
|
||||
int refresh = 3;
|
||||
if(request->args() > 0) {
|
||||
if(request->arg("reset").toInt() == 1) {
|
||||
if (request->args() > 0) {
|
||||
if (request->arg("reset").toInt() == 1) {
|
||||
refresh = 10;
|
||||
if(mApp->eraseSettings(true))
|
||||
if (mApp->eraseSettings(true))
|
||||
content = F("factory reset: success\n\nrebooting ... ");
|
||||
else
|
||||
content = F("factory reset: failed\n\nrebooting ... ");
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
content = F("factory reset: aborted");
|
||||
refresh = 3;
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
content = F("<h1>Factory Reset</h1>"
|
||||
"<p><a href=\"/factory?reset=1\">RESET</a><br/><br/><a href=\"/factory?reset=0\">CANCEL</a><br/></p>");
|
||||
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>"));
|
||||
if(refresh == 10) {
|
||||
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) {
|
||||
delay(1000);
|
||||
ESP.restart();
|
||||
}
|
||||
|
@ -409,14 +426,14 @@ class Web {
|
|||
void onSetup(AsyncWebServerRequest *request) {
|
||||
DPRINTLN(DBG_VERBOSE, F("onSetup"));
|
||||
|
||||
if(CHECK_MASK(mConfig->sys.protectionMask, PROT_MASK_SETUP)) {
|
||||
if(mProtected) {
|
||||
request->redirect("/login");
|
||||
if (CHECK_MASK(mConfig->sys.protectionMask, PROT_MASK_SETUP)) {
|
||||
if (mProtected) {
|
||||
checkRedirect(request);
|
||||
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");
|
||||
request->send(response);
|
||||
}
|
||||
|
@ -424,32 +441,33 @@ class Web {
|
|||
void showSave(AsyncWebServerRequest *request) {
|
||||
DPRINTLN(DBG_VERBOSE, F("showSave"));
|
||||
|
||||
if(mProtected) {
|
||||
request->redirect("/login");
|
||||
if (mProtected) {
|
||||
checkRedirect(request);
|
||||
return;
|
||||
}
|
||||
|
||||
if(request->args() == 0)
|
||||
if (request->args() == 0)
|
||||
return;
|
||||
|
||||
char buf[20] = {0};
|
||||
|
||||
// general
|
||||
if(request->arg("ssid") != "")
|
||||
if (request->arg("ssid") != "")
|
||||
request->arg("ssid").toCharArray(mConfig->sys.stationSsid, SSID_LEN);
|
||||
if(request->arg("pwd") != "{PWD}")
|
||||
if (request->arg("pwd") != "{PWD}")
|
||||
request->arg("pwd").toCharArray(mConfig->sys.stationPwd, PWD_LEN);
|
||||
if(request->arg("device") != "")
|
||||
if (request->arg("device") != "")
|
||||
request->arg("device").toCharArray(mConfig->sys.deviceName, DEVNAME_LEN);
|
||||
mConfig->sys.darkMode = (request->arg("darkMode") == "on");
|
||||
|
||||
// protection
|
||||
if(request->arg("adminpwd") != "{PWD}") {
|
||||
if (request->arg("adminpwd") != "{PWD}") {
|
||||
request->arg("adminpwd").toCharArray(mConfig->sys.adminPwd, PWD_LEN);
|
||||
mProtected = (strlen(mConfig->sys.adminPwd) > 0);
|
||||
}
|
||||
mConfig->sys.protectionMask = 0x0000;
|
||||
for(uint8_t i = 0; i < 6; i++) {
|
||||
if(request->arg("protMask" + String(i)) == "on")
|
||||
for (uint8_t i = 0; i < 6; i++) {
|
||||
if (request->arg("protMask" + String(i)) == "on")
|
||||
mConfig->sys.protectionMask |= (1 << i);
|
||||
}
|
||||
|
||||
|
@ -465,16 +483,15 @@ class Web {
|
|||
request->arg("ipGateway").toCharArray(buf, 20);
|
||||
ah::ip2Arr(mConfig->sys.ip.gateway, buf);
|
||||
|
||||
|
||||
// inverter
|
||||
Inverter<> *iv;
|
||||
for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i ++) {
|
||||
for (uint8_t i = 0; i < MAX_NUM_INVERTERS; i++) {
|
||||
iv = mSys->getInverterByPos(i, false);
|
||||
// enable communication
|
||||
iv->config->enabled = (request->arg("inv" + String(i) + "Enable") == "on");
|
||||
// address
|
||||
request->arg("inv" + String(i) + "Addr").toCharArray(buf, 20);
|
||||
if(strlen(buf) == 0)
|
||||
if (strlen(buf) == 0)
|
||||
memset(buf, 0, 20);
|
||||
iv->config->serial.u64 = ah::Serial2u64(buf);
|
||||
switch(iv->config->serial.b[4]) {
|
||||
|
@ -488,7 +505,7 @@ class Web {
|
|||
request->arg("inv" + String(i) + "Name").toCharArray(iv->config->name, MAX_NAME_LENGTH);
|
||||
|
||||
// max channel power / name
|
||||
for(uint8_t j = 0; j < 4; j++) {
|
||||
for (uint8_t j = 0; j < 4; j++) {
|
||||
iv->config->yieldCor[j] = request->arg("inv" + String(i) + "YieldCor" + String(j)).toInt();
|
||||
iv->config->chMaxPwr[j] = request->arg("inv" + String(i) + "ModPwr" + String(j)).toInt() & 0xffff;
|
||||
request->arg("inv" + String(i) + "ModName" + String(j)).toCharArray(iv->config->chName[j], MAX_NAME_LENGTH);
|
||||
|
@ -496,13 +513,13 @@ class Web {
|
|||
iv->initialized = true;
|
||||
}
|
||||
|
||||
if(request->arg("invInterval") != "")
|
||||
if (request->arg("invInterval") != "")
|
||||
mConfig->nrf.sendInterval = request->arg("invInterval").toInt();
|
||||
if(request->arg("invRetry") != "")
|
||||
if (request->arg("invRetry") != "")
|
||||
mConfig->nrf.maxRetransPerPyld = request->arg("invRetry").toInt();
|
||||
mConfig->inst.rstYieldMidNight = (request->arg("invRstMid") == "on");
|
||||
mConfig->inst.rstValsCommStop = (request->arg("invRstComStop") == "on");
|
||||
mConfig->inst.rstValsNotAvail = (request->arg("invRstNotAvail") == "on");
|
||||
mConfig->inst.rstValsCommStop = (request->arg("invRstComStop") == "on");
|
||||
mConfig->inst.rstValsNotAvail = (request->arg("invRstNotAvail") == "on");
|
||||
|
||||
// pinout
|
||||
uint8_t pin;
|
||||
|
@ -528,13 +545,13 @@ class Web {
|
|||
mConfig->cmt.enabled = (request->arg("cmtEnable") == "on");
|
||||
|
||||
// ntp
|
||||
if(request->arg("ntpAddr") != "") {
|
||||
if (request->arg("ntpAddr") != "") {
|
||||
request->arg("ntpAddr").toCharArray(mConfig->ntp.addr, NTP_ADDR_LEN);
|
||||
mConfig->ntp.port = request->arg("ntpPort").toInt() & 0xffff;
|
||||
}
|
||||
|
||||
// sun
|
||||
if(request->arg("sunLat") == "" || (request->arg("sunLon") == "")) {
|
||||
if (request->arg("sunLat") == "" || (request->arg("sunLon") == "")) {
|
||||
mConfig->sun.lat = 0.0;
|
||||
mConfig->sun.lon = 0.0;
|
||||
mConfig->sun.disNightCom = false;
|
||||
|
@ -547,47 +564,48 @@ class Web {
|
|||
}
|
||||
|
||||
// mqtt
|
||||
if(request->arg("mqttAddr") != "") {
|
||||
if (request->arg("mqttAddr") != "") {
|
||||
String addr = request->arg("mqttAddr");
|
||||
addr.trim();
|
||||
addr.toCharArray(mConfig->mqtt.broker, MQTT_ADDR_LEN);
|
||||
}
|
||||
else
|
||||
} else
|
||||
mConfig->mqtt.broker[0] = '\0';
|
||||
request->arg("mqttUser").toCharArray(mConfig->mqtt.user, MQTT_USER_LEN);
|
||||
if(request->arg("mqttPwd") != "{PWD}")
|
||||
if (request->arg("mqttPwd") != "{PWD}")
|
||||
request->arg("mqttPwd").toCharArray(mConfig->mqtt.pwd, MQTT_PWD_LEN);
|
||||
request->arg("mqttTopic").toCharArray(mConfig->mqtt.topic, MQTT_TOPIC_LEN);
|
||||
mConfig->mqtt.port = request->arg("mqttPort").toInt();
|
||||
mConfig->mqtt.interval = request->arg("mqttInterval").toInt();
|
||||
|
||||
// serial console
|
||||
if(request->arg("serIntvl") != "") {
|
||||
if (request->arg("serIntvl") != "") {
|
||||
mConfig->serial.interval = request->arg("serIntvl").toInt() & 0xffff;
|
||||
|
||||
mConfig->serial.debug = (request->arg("serDbg") == "on");
|
||||
mConfig->serial.debug = (request->arg("serDbg") == "on");
|
||||
mConfig->serial.showIv = (request->arg("serEn") == "on");
|
||||
// Needed to log TX buffers to serial console
|
||||
// mSys->Radio.mSerialDebug = mConfig->serial.debug;
|
||||
}
|
||||
|
||||
// display
|
||||
mConfig->plugin.display.pwrSaveAtIvOffline = (request->arg("dispPwr") == "on");
|
||||
mConfig->plugin.display.logoEn = (request->arg("logoEn") == "on");
|
||||
mConfig->plugin.display.pxShift = (request->arg("dispPxSh") == "on");
|
||||
mConfig->plugin.display.rot180 = (request->arg("disp180") == "on");
|
||||
mConfig->plugin.display.type = request->arg("dispType").toInt();
|
||||
mConfig->plugin.display.contrast = request->arg("dispCont").toInt();
|
||||
mConfig->plugin.display.pin0 = request->arg("pinDisp0").toInt();
|
||||
mConfig->plugin.display.pin1 = request->arg("pinDisp1").toInt();
|
||||
|
||||
mConfig->plugin.display.pwrSaveAtIvOffline = (request->arg("disp_pwr") == "on");
|
||||
mConfig->plugin.display.pxShift = (request->arg("disp_pxshift") == "on");
|
||||
mConfig->plugin.display.rot = request->arg("disp_rot").toInt();
|
||||
mConfig->plugin.display.type = request->arg("disp_typ").toInt();
|
||||
mConfig->plugin.display.contrast = request->arg("disp_cont").toInt();
|
||||
mConfig->plugin.display.disp_data = request->arg("disp_data").toInt();
|
||||
mConfig->plugin.display.disp_clk = request->arg("disp_clk").toInt();
|
||||
mConfig->plugin.display.disp_cs = request->arg("disp_cs").toInt();
|
||||
mConfig->plugin.display.disp_reset = request->arg("disp_rst").toInt();
|
||||
mConfig->plugin.display.disp_busy = request->arg("disp_bsy").toInt();
|
||||
mConfig->plugin.display.disp_dc = request->arg("disp_dc").toInt();
|
||||
|
||||
mApp->saveSettings();
|
||||
|
||||
if(request->arg("reboot") == "on")
|
||||
if (request->arg("reboot") == "on")
|
||||
onReboot(request);
|
||||
else {
|
||||
AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html"), 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);
|
||||
}
|
||||
|
@ -596,15 +614,17 @@ class Web {
|
|||
void onLive(AsyncWebServerRequest *request) {
|
||||
DPRINTLN(DBG_VERBOSE, F("onLive"));
|
||||
|
||||
if(CHECK_MASK(mConfig->sys.protectionMask, PROT_MASK_LIVE)) {
|
||||
if(mProtected) {
|
||||
request->redirect("/login");
|
||||
if (CHECK_MASK(mConfig->sys.protectionMask, PROT_MASK_LIVE)) {
|
||||
if (mProtected) {
|
||||
checkRedirect(request);
|
||||
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-type"), "text/html; charset=UTF-8");
|
||||
|
||||
request->send(response);
|
||||
}
|
||||
|
||||
|
@ -675,21 +695,21 @@ class Web {
|
|||
|
||||
void onDebug(AsyncWebServerRequest *request) {
|
||||
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);
|
||||
}
|
||||
|
||||
void onSerial(AsyncWebServerRequest *request) {
|
||||
DPRINTLN(DBG_VERBOSE, F("onSerial"));
|
||||
|
||||
if(CHECK_MASK(mConfig->sys.protectionMask, PROT_MASK_SERIAL)) {
|
||||
if(mProtected) {
|
||||
request->redirect("/login");
|
||||
if (CHECK_MASK(mConfig->sys.protectionMask, PROT_MASK_SERIAL)) {
|
||||
if (mProtected) {
|
||||
checkRedirect(request);
|
||||
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");
|
||||
request->send(response);
|
||||
}
|
||||
|
@ -697,36 +717,36 @@ class Web {
|
|||
void onSystem(AsyncWebServerRequest *request) {
|
||||
DPRINTLN(DBG_VERBOSE, F("onSystem"));
|
||||
|
||||
if(CHECK_MASK(mConfig->sys.protectionMask, PROT_MASK_SYSTEM)) {
|
||||
if(mProtected) {
|
||||
request->redirect("/login");
|
||||
if (CHECK_MASK(mConfig->sys.protectionMask, PROT_MASK_SYSTEM)) {
|
||||
if (mProtected) {
|
||||
checkRedirect(request);
|
||||
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");
|
||||
request->send(response);
|
||||
}
|
||||
|
||||
#ifdef ENABLE_JSON_EP
|
||||
#ifdef ENABLE_JSON_EP
|
||||
void showJson(AsyncWebServerRequest *request) {
|
||||
DPRINTLN(DBG_VERBOSE, F("web::showJson"));
|
||||
String modJson;
|
||||
Inverter<> *iv;
|
||||
record_t<> *rec;
|
||||
char topic[40], val[25];
|
||||
char topic[40], val[25];
|
||||
|
||||
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);
|
||||
if(NULL == iv)
|
||||
if (NULL == iv)
|
||||
continue;
|
||||
|
||||
rec = iv->getRecordStruct(RealTimeRunData_Debug);
|
||||
snprintf(topic, 30, "\"%s\": {\n", iv->config->name);
|
||||
modJson += String(topic);
|
||||
for(uint8_t i = 0; i < rec->length; i++) {
|
||||
for (uint8_t i = 0; i < rec->length; i++) {
|
||||
snprintf(topic, 40, "\t\"ch%d/%s\"", rec->assign[i].ch, iv->getFieldName(i, rec));
|
||||
snprintf(val, 25, "[%.3f, \"%s\"]", iv->getValue(i, rec), iv->getUnit(i, rec));
|
||||
modJson += String(topic) + ": " + String(val) + F(",\n");
|
||||
|
@ -853,8 +873,7 @@ class Web {
|
|||
// TODO: find the right one channel with the alarm id
|
||||
alarmChannelId = 0;
|
||||
// printf("AlarmData Length %d\n",rec->length);
|
||||
if (alarmChannelId < rec->length)
|
||||
{
|
||||
if (alarmChannelId < rec->length) {
|
||||
//uint8_t channel = rec->assign[alarmChannelId].ch;
|
||||
std::tie(promUnit, promType) = convertToPromUnits(iv->getUnit(alarmChannelId, rec));
|
||||
snprintf(type, sizeof(type), "# TYPE ahoy_solar_%s%s %s", iv->getFieldName(alarmChannelId, rec), promUnit.c_str(), promType.c_str());
|
||||
|
|
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.
|
||||
|
||||
|
||||
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
|
||||
-----------------------
|
||||
|
||||
Some modules are not installed by default on a RaspberryPi, therefore add them manually:
|
||||
|
||||
```
|
||||
```code
|
||||
pip install crcmod pyyaml paho-mqtt SunTimes
|
||||
```
|
||||
|
||||
|
@ -112,7 +179,7 @@ Python parameters
|
|||
|
||||
|
||||
The application describes itself
|
||||
```
|
||||
```code
|
||||
python3 -m hoymiles --help
|
||||
usage: hoymiles [-h] -c [CONFIG_FILE] [--log-transactions] [--verbose]
|
||||
|
||||
|
@ -180,7 +247,7 @@ Todo
|
|||
- Ability to talk to multiple inverters
|
||||
- MQTT gateway
|
||||
- understand channel hopping
|
||||
- configurable polling interval
|
||||
- ~~configurable polling interval~~ done: interval ist configurable in ahoy.yml
|
||||
- commands
|
||||
- picture of setup!
|
||||
- python module
|
||||
|
|
|
@ -6,11 +6,9 @@
|
|||
# WorkingDirectory (absolute path to your private ahoy dir)
|
||||
# To change other config parameter, please consult systemd documentation
|
||||
#
|
||||
# To activate this service, create a link, enable and start the ahoy.service
|
||||
# $ mkdir -p $HOME/.config/systemd/user
|
||||
# $ ln -sf $(pwd)/ahoy/tools/rpi/ahoy.service -t $HOME/.config/systemd/user
|
||||
# To activate this service, enable and start ahoy.service
|
||||
# $ systemctl --user enable $(pwd)/ahoy/tools/rpi/ahoy.service
|
||||
# $ systemctl --user status ahoy
|
||||
# $ systemctl --user enable ahoy
|
||||
# $ systemctl --user start ahoy
|
||||
# $ systemctl --user status ahoy
|
||||
#
|
||||
|
|
|
@ -31,7 +31,7 @@ ahoy:
|
|||
QoS: 0
|
||||
Retain: True
|
||||
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!"
|
||||
|
||||
# Influx2 output
|
||||
|
@ -96,6 +96,7 @@ ahoy:
|
|||
|
||||
dtu:
|
||||
serial: 99978563001
|
||||
name: my_DTU_name
|
||||
|
||||
inverters:
|
||||
- name: 'balkon'
|
||||
|
@ -103,14 +104,14 @@ ahoy:
|
|||
txpower: 'low' # txpower per inverter (min,low,high,max)
|
||||
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
|
||||
- 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_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_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_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
|
||||
import logging
|
||||
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 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_crc8 = crcmod.mkCrcFun(0x101, initCrc=0, xorOut=0)
|
||||
|
@ -158,15 +178,26 @@ class ResponseDecoder(ResponseDecoderFactory):
|
|||
model = self.inverter_model
|
||||
command = self.request_command
|
||||
|
||||
c_datetime = self.time_rx.strftime("%Y-%m-%d %H:%M:%S.%f")
|
||||
logging.info(f'{c_datetime} model_decoder: {model}Decode{command.upper()}')
|
||||
if HOYMILES_DEBUG_LOGGING:
|
||||
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')
|
||||
if hasattr(model_decoders, f'{model}Decode{command.upper()}'):
|
||||
device = getattr(model_decoders, f'{model}Decode{command.upper()}')
|
||||
else:
|
||||
if HOYMILES_DEBUG_LOGGING:
|
||||
device = getattr(model_decoders, 'DebugDecodeAny')
|
||||
device = getattr(model_decoders, 'DebugDecodeAny')
|
||||
|
||||
return device(self.response,
|
||||
time_rx=self.time_rx,
|
||||
|
|
|
@ -33,6 +33,12 @@ def signal_handler(sig_num, frame):
|
|||
if mqtt_client:
|
||||
mqtt_client.disco()
|
||||
|
||||
if influx_client:
|
||||
influx_client.disco()
|
||||
|
||||
if volkszaehler_client:
|
||||
volkszaehler_client.disco()
|
||||
|
||||
sys.exit(0)
|
||||
|
||||
signal(SIGINT, signal_handler) # Interrupt from keyboard (CTRL + C)
|
||||
|
@ -75,7 +81,6 @@ class SunsetHandler:
|
|||
else:
|
||||
logging.info('Sunset disabled.')
|
||||
|
||||
|
||||
def checkWaitForSunrise(self):
|
||||
if not self.suntimes:
|
||||
return
|
||||
|
@ -94,6 +99,23 @@ class SunsetHandler:
|
|||
time.sleep(time_to_sleep)
|
||||
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):
|
||||
"""Main loop"""
|
||||
inverters = [
|
||||
|
@ -101,7 +123,9 @@ def main_loop(ahoy_config):
|
|||
if not inverter.get('disabled', False)]
|
||||
|
||||
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)
|
||||
|
||||
try:
|
||||
|
@ -112,6 +136,11 @@ def main_loop(ahoy_config):
|
|||
t_loop_start = time.time()
|
||||
|
||||
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:
|
||||
logging.info(f'Poll inverter name={inverter["name"]} ser={inverter["serial"]}')
|
||||
poll_inverter(inverter, dtu_ser, do_init, 3)
|
||||
|
@ -122,8 +151,6 @@ def main_loop(ahoy_config):
|
|||
if time_to_sleep > 0:
|
||||
time.sleep(time_to_sleep)
|
||||
|
||||
except KeyboardInterrupt:
|
||||
sys.exit()
|
||||
except Exception as e:
|
||||
logging.fatal('Exception catched: %s' % e)
|
||||
logging.fatal(traceback.print_exc())
|
||||
|
@ -174,13 +201,14 @@ def poll_inverter(inverter, dtu_ser, do_init, retries):
|
|||
response = com.get_payload()
|
||||
payload_ttl = 0
|
||||
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
|
||||
|
||||
# Handle the response data if any
|
||||
if response:
|
||||
c_datetime = datetime.now()
|
||||
if hoymiles.HOYMILES_DEBUG_LOGGING:
|
||||
if hoymiles.HOYMILES_TRANSACTION_LOGGING:
|
||||
c_datetime = datetime.now()
|
||||
logging.debug(f'{c_datetime} Payload: ' + hoymiles.hexify_payload(response))
|
||||
|
||||
# prepare decoder object
|
||||
|
@ -195,7 +223,7 @@ def poll_inverter(inverter, dtu_ser, do_init, retries):
|
|||
# get decoder object
|
||||
result = decoder.decode()
|
||||
if hoymiles.HOYMILES_DEBUG_LOGGING:
|
||||
logging.info(f'{c_datetime} Decoded: {result.__dict__()}')
|
||||
logging.info(f'Decoded: {result.__dict__()}')
|
||||
|
||||
# check decoder object for output
|
||||
if isinstance(result, hoymiles.decoders.StatusResponse):
|
||||
|
@ -281,7 +309,13 @@ def init_logging(ahoy_config):
|
|||
lvl = logging.WARNING
|
||||
elif level == '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)
|
||||
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__':
|
||||
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}')
|
||||
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:
|
||||
hoymiles.HOYMILES_TRANSACTION_LOGGING=True
|
||||
if global_config.verbose:
|
||||
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
|
||||
for radio_config in ahoy_config.get('nrf', [{}]):
|
||||
hmradio = hoymiles.HoymilesNRF(**radio_config)
|
||||
|
||||
# create MQTT - client object
|
||||
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):
|
||||
from .outputs import MqttOutputPlugin
|
||||
mqtt_client = MqttOutputPlugin(mqtt_config)
|
||||
|
||||
# create INFLUX - client object
|
||||
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):
|
||||
from .outputs import InfluxOutputPlugin
|
||||
influx_client = InfluxOutputPlugin(
|
||||
|
|
|
@ -99,6 +99,7 @@ class StatusResponse(Response):
|
|||
frequency = None
|
||||
powerfactor = None
|
||||
event_count = None
|
||||
unpack_error = False
|
||||
|
||||
def unpack(self, fmt, base):
|
||||
"""
|
||||
|
@ -110,6 +111,10 @@ class StatusResponse(Response):
|
|||
:rtype: tuple
|
||||
"""
|
||||
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])
|
||||
|
||||
@property
|
||||
|
@ -150,6 +155,7 @@ class StatusResponse(Response):
|
|||
s_exists = False
|
||||
string_id = len(strings)
|
||||
string = {}
|
||||
string['name'] = self.inv_strings[string_id]['s_name']
|
||||
for key in self.string_keys:
|
||||
prop = f'dc_{key}_{string_id}'
|
||||
if hasattr(self, prop):
|
||||
|
@ -193,7 +199,8 @@ class StatusResponse(Response):
|
|||
data['event_count'] = self.event_count
|
||||
data['time'] = self.time_rx
|
||||
|
||||
return data
|
||||
if not self.unpack_error:
|
||||
return data
|
||||
|
||||
class UnknownResponse(Response):
|
||||
"""
|
||||
|
@ -321,9 +328,9 @@ class EventsResponse(UnknownResponse):
|
|||
#logging.debug(' payload has valid modbus crc')
|
||||
self.response = self.response[:-2]
|
||||
|
||||
status = struct.unpack('>H', self.response[:2])[0]
|
||||
a_text = self.alarm_codes.get(status, 'N/A')
|
||||
logging.info (f' Inverter status: {a_text} ({status})')
|
||||
self.status = struct.unpack('>H', self.response[:2])[0]
|
||||
self.a_text = self.alarm_codes.get(self.status, 'N/A')
|
||||
logging.info (f'Inverter status: {self.a_text} ({self.status})')
|
||||
|
||||
chunk_size = 12
|
||||
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]) + ': ')
|
||||
|
||||
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])
|
||||
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}')
|
||||
|
||||
dbg = ''
|
||||
|
@ -341,6 +351,14 @@ class EventsResponse(UnknownResponse):
|
|||
dbg += f' {fmt:7}: ' + str(struct.unpack('>' + fmt, chunk))
|
||||
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):
|
||||
def __init__(self, *args, **params):
|
||||
super().__init__(*args, **params)
|
||||
|
@ -361,9 +379,14 @@ class HardwareInfoResponse(UnknownResponse):
|
|||
def __dict__(self):
|
||||
""" Base values, availabe in each __dict__ call """
|
||||
|
||||
responce_info = self.response
|
||||
logging.info(f'HardwareInfoResponse: {struct.unpack(">HHHHHHHH", responce_info)}')
|
||||
data = super().__dict__()
|
||||
|
||||
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_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'HW revision {hw_id}')
|
||||
|
||||
data = super().__dict__()
|
||||
data['FW_ver_maj'] = fw_version_maj
|
||||
data['FW_ver_min'] = fw_version_min
|
||||
data['FW_ver_pat'] = fw_version_pat
|
||||
|
@ -468,6 +490,8 @@ class Hm300Decode0B(StatusResponse):
|
|||
""" String 1 irratiation in percent """
|
||||
if self.inv_strings is 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)
|
||||
|
||||
@property
|
||||
|
@ -540,6 +564,8 @@ class Hm600Decode0B(StatusResponse):
|
|||
""" String 1 irratiation in percent """
|
||||
if self.inv_strings is 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)
|
||||
|
||||
@property
|
||||
|
@ -567,6 +593,8 @@ class Hm600Decode0B(StatusResponse):
|
|||
""" String 2 irratiation in percent """
|
||||
if self.inv_strings is 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)
|
||||
|
||||
@property
|
||||
|
@ -647,6 +675,8 @@ class Hm1200Decode0B(StatusResponse):
|
|||
""" String 1 irratiation in percent """
|
||||
if self.inv_strings is 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)
|
||||
|
||||
@property
|
||||
|
@ -674,6 +704,8 @@ class Hm1200Decode0B(StatusResponse):
|
|||
""" String 2 irratiation in percent """
|
||||
if self.inv_strings is 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)
|
||||
|
||||
@property
|
||||
|
@ -701,6 +733,8 @@ class Hm1200Decode0B(StatusResponse):
|
|||
""" String 3 irratiation in percent """
|
||||
if self.inv_strings is 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)
|
||||
|
||||
@property
|
||||
|
@ -728,6 +762,8 @@ class Hm1200Decode0B(StatusResponse):
|
|||
""" String 4 irratiation in percent """
|
||||
if self.inv_strings is 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)
|
||||
|
||||
@property
|
||||
|
|
|
@ -9,6 +9,7 @@ import socket
|
|||
import logging
|
||||
from datetime import datetime, timezone
|
||||
from hoymiles.decoders import StatusResponse, HardwareInfoResponse
|
||||
from hoymiles import HOYMILES_TRANSACTION_LOGGING, HOYMILES_DEBUG_LOGGING
|
||||
|
||||
class OutputPluginFactory:
|
||||
def __init__(self, **params):
|
||||
|
@ -39,6 +40,7 @@ class InfluxOutputPlugin(OutputPluginFactory):
|
|||
def __init__(self, url, token, **params):
|
||||
"""
|
||||
Initialize InfluxOutputPlugin
|
||||
https://influxdb-client.readthedocs.io/en/stable/api.html#influxdbclient
|
||||
|
||||
The following targets must be present in your InfluxDB. This does not
|
||||
automatically create anything for You.
|
||||
|
@ -68,8 +70,12 @@ class InfluxOutputPlugin(OutputPluginFactory):
|
|||
self._org = params.get('org', '')
|
||||
self._measurement = params.get('measurement', f'inverter,host={socket.gethostname()}')
|
||||
|
||||
client = InfluxDBClient(url, token, bucket=self._bucket)
|
||||
self.api = client.write_api()
|
||||
with InfluxDBClient(url, token, bucket=self._bucket) as self.client:
|
||||
self.api = self.client.write_api()
|
||||
|
||||
def disco(self, **params):
|
||||
self.client.close() # Shutdown the client
|
||||
return
|
||||
|
||||
def store_status(self, response, **params):
|
||||
"""
|
||||
|
@ -102,6 +108,9 @@ class InfluxOutputPlugin(OutputPluginFactory):
|
|||
# InfluxDB requires nanoseconds
|
||||
ctime = int(utctime.timestamp() * 1e9)
|
||||
|
||||
if HOYMILES_DEBUG_LOGGING:
|
||||
logging.info(f'InfluxDB: utctime: {utctime}')
|
||||
|
||||
# AC Data
|
||||
phase_id = 0
|
||||
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=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)
|
||||
|
||||
class MqttOutputPlugin(OutputPluginFactory):
|
||||
|
@ -196,6 +208,12 @@ class MqttOutputPlugin(OutputPluginFactory):
|
|||
def disco(self, **params):
|
||||
self.client.loop_stop() # Stop loop
|
||||
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):
|
||||
"""
|
||||
|
@ -209,13 +227,18 @@ class MqttOutputPlugin(OutputPluginFactory):
|
|||
"""
|
||||
|
||||
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):
|
||||
|
||||
# Global Head
|
||||
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
|
||||
phase_id = 0
|
||||
|
@ -233,12 +256,16 @@ class MqttOutputPlugin(OutputPluginFactory):
|
|||
string_id = 0
|
||||
string_sum_power = 0
|
||||
for string in data['strings']:
|
||||
self.client.publish(f'{topic}/emeter-dc/{string_id}/voltage', string['voltage'], self.qos, self.ret)
|
||||
self.client.publish(f'{topic}/emeter-dc/{string_id}/current', string['current'], self.qos, self.ret)
|
||||
self.client.publish(f'{topic}/emeter-dc/{string_id}/power', string['power'], self.qos, self.ret)
|
||||
self.client.publish(f'{topic}/emeter-dc/{string_id}/YieldDay', string['energy_daily'], self.qos, self.ret)
|
||||
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_id}/Irradiation', string['irradiation'], self.qos, self.ret)
|
||||
if 'name' in string:
|
||||
string_name = string['name'].replace(" ","_")
|
||||
else:
|
||||
string_name = string_id
|
||||
self.client.publish(f'{topic}/emeter-dc/{string_name}/voltage', string['voltage'], 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_sum_power += string['power']
|
||||
|
||||
|
@ -277,9 +304,10 @@ class VzInverterOutput:
|
|||
self.channels = dict()
|
||||
|
||||
for channel in config.get('channels', []):
|
||||
uid = channel.get('uid')
|
||||
uid = channel.get('uid', None)
|
||||
ctype = channel.get('type')
|
||||
if uid and ctype:
|
||||
# if uid and ctype:
|
||||
if ctype:
|
||||
self.channels[ctype] = uid
|
||||
|
||||
def store_status(self, data, session):
|
||||
|
@ -295,6 +323,9 @@ class VzInverterOutput:
|
|||
|
||||
ts = int(round(data['time'].timestamp() * 1000))
|
||||
|
||||
if HOYMILES_DEBUG_LOGGING:
|
||||
logging.info(f'Volkszaehler-Timestamp: {ts}')
|
||||
|
||||
# AC Data
|
||||
phase_id = 0
|
||||
for phase in data['phases']:
|
||||
|
@ -327,13 +358,24 @@ class VzInverterOutput:
|
|||
if data['yield_today'] is not None:
|
||||
self.try_publish(ts, f'yield_today', data['yield_today'])
|
||||
self.try_publish(ts, f'efficiency', data['efficiency'])
|
||||
return
|
||||
|
||||
def try_publish(self, ts, ctype, value):
|
||||
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
|
||||
|
||||
uid = self.channels[ctype]
|
||||
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:
|
||||
r = self.session.get(url)
|
||||
if r.status_code == 404:
|
||||
|
@ -344,6 +386,7 @@ class VzInverterOutput:
|
|||
raise ValueError(f'Transmit result {url}')
|
||||
except ConnectionError as e:
|
||||
raise ValueError(f'Could not connect VZ-DB {type(e)} {e.keys()}')
|
||||
return
|
||||
|
||||
class VolkszaehlerOutputPlugin(OutputPluginFactory):
|
||||
def __init__(self, config, **params):
|
||||
|
@ -364,13 +407,17 @@ class VolkszaehlerOutputPlugin(OutputPluginFactory):
|
|||
exit(1)
|
||||
|
||||
self.session = requests.Session()
|
||||
self.inverters = dict()
|
||||
|
||||
self.inverters = dict()
|
||||
for inverterconfig in config.get('inverters', []):
|
||||
serial = inverterconfig.get('serial')
|
||||
output = VzInverterOutput(inverterconfig, self.session)
|
||||
self.inverters[serial] = output
|
||||
|
||||
def disco(self, **params):
|
||||
self.session.close() # closing the connection
|
||||
return
|
||||
|
||||
def store_status(self, response, **params):
|
||||
"""
|
||||
Publish StatusResponse object
|
||||
|
@ -395,3 +442,4 @@ class VolkszaehlerOutputPlugin(OutputPluginFactory):
|
|||
output.store_status(data, self.session)
|
||||
except ValueError as e:
|
||||
logging.warning('Could not send data to volkszaehler instance: %s' % e)
|
||||
return
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue