Browse Source

Merge branch 'lumapu:development03' into development03

pull/1293/head
rejoe2 1 year ago
committed by GitHub
parent
commit
b942b37cfd
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      .github/workflows/compile_development.yml
  2. 2
      .github/workflows/compile_release.yml
  3. 13
      patches/AsyncWeb_Prometheus.patch
  4. 2
      scripts/convertHtml.py
  5. 13
      scripts/getVersion.py
  6. 9
      src/CHANGES.md
  7. 2
      src/app.cpp
  8. 2
      src/defines.h
  9. 27
      src/hm/Communication.h
  10. 5
      src/hm/hmInverter.h
  11. 7
      src/hm/hmRadio.h
  12. 5
      src/hms/hmsRadio.h
  13. 15
      src/platformio.ini
  14. 14
      src/publisher/pubMqtt.h
  15. 2
      src/publisher/pubMqttIvData.h
  16. 13
      src/web/RestApi.h
  17. 8
      src/web/html/api.js
  18. 764
      src/web/html/grid_info.json
  19. 4
      src/web/html/save.html
  20. 55
      src/web/html/setup.html
  21. 75
      src/web/html/visualization.html
  22. 12
      src/web/web.h

2
.github/workflows/compile_development.yml

@ -43,7 +43,7 @@ jobs:
pip install --upgrade platformio pip install --upgrade platformio
- name: Run PlatformIO - name: Run PlatformIO
run: pio run -d src --environment esp8266 --environment esp8266-prometheus --environment esp8285 --environment esp32-wroom32 --environment esp32-wroom32-prometheus --environment esp32-wroom32-ethernet --environment esp32-s2-mini --environment opendtufusion --environment opendtufusion-ethernet run: pio run -d src --environment esp8266 --environment esp8266-prometheus --environment esp8285 --environment esp32-wroom32 --environment esp32-wroom32-prometheus --environment esp32-wroom32-ethernet --environment esp32-s2-mini --environment esp32-c3-mini --environment opendtufusion --environment opendtufusion-ethernet
- name: Copy boot_app0.bin - name: Copy boot_app0.bin
run: cp ~/.platformio/packages/framework-arduinoespressif32/tools/partitions/boot_app0.bin src/.pio/build/opendtufusion/ota.bin run: cp ~/.platformio/packages/framework-arduinoespressif32/tools/partitions/boot_app0.bin src/.pio/build/opendtufusion/ota.bin

2
.github/workflows/compile_release.yml

@ -47,7 +47,7 @@ jobs:
pip install --upgrade platformio pip install --upgrade platformio
- name: Run PlatformIO - name: Run PlatformIO
run: pio run -d src --environment esp8266 --environment esp8266-prometheus --environment esp8285 --environment esp32-wroom32 --environment esp32-wroom32-prometheus --environment esp32-wroom32-ethernet --environment esp32-s2-mini --environment opendtufusion --environment opendtufusion-ethernet run: pio run -d src --environment esp8266 --environment esp8266-prometheus --environment esp8285 --environment esp32-wroom32 --environment esp32-wroom32-prometheus --environment esp32-wroom32-ethernet --environment esp32-s2-mini --environment esp32-c3-mini --environment opendtufusion --environment opendtufusion-ethernet
- name: Copy boot_app0.bin - name: Copy boot_app0.bin
run: cp ~/.platformio/packages/framework-arduinoespressif32/tools/partitions/boot_app0.bin src/.pio/build/opendtufusion/ota.bin run: cp ~/.platformio/packages/framework-arduinoespressif32/tools/partitions/boot_app0.bin src/.pio/build/opendtufusion/ota.bin

13
patches/AsyncWeb_Prometheus.patch

@ -1,3 +1,16 @@
diff --git a/src/AsyncWebSocket.cpp b/src/AsyncWebSocket.cpp
index 12be5f8..cffeed7 100644
--- a/src/AsyncWebSocket.cpp
+++ b/src/AsyncWebSocket.cpp
@@ -737,7 +737,7 @@ void AsyncWebSocketClient::binary(const __FlashStringHelper *data, size_t len)
IPAddress AsyncWebSocketClient::remoteIP() const
{
if (!_client)
- return IPAddress(0U);
+ return IPAddress();
return _client->remoteIP();
}
diff --git a/src/WebResponses.cpp b/src/WebResponses.cpp diff --git a/src/WebResponses.cpp b/src/WebResponses.cpp
index 22a549f..e0b36b3 100644 index 22a549f..e0b36b3 100644
--- a/src/WebResponses.cpp --- a/src/WebResponses.cpp

2
scripts/convertHtml.py

@ -159,7 +159,7 @@ if os.path.exists(wd):
# grab all files with following extensions # grab all files with following extensions
os.chdir('./web/html') os.chdir('./web/html')
types = ('*.html', '*.css', '*.js', '*.ico') # the tuple of file types types = ('*.html', '*.css', '*.js', '*.ico', '*.json') # the tuple of file types
files_grabbed = [] files_grabbed = []
for files in types: for files in types:
files_grabbed.extend(glob.glob(files)) files_grabbed.extend(glob.glob(files))

13
scripts/getVersion.py

@ -55,6 +55,7 @@ def readVersion(path, infile):
os.mkdir(path + "firmware/ESP32/") os.mkdir(path + "firmware/ESP32/")
os.mkdir(path + "firmware/ESP32-S2/") os.mkdir(path + "firmware/ESP32-S2/")
os.mkdir(path + "firmware/ESP32-S3/") os.mkdir(path + "firmware/ESP32-S3/")
os.mkdir(path + "firmware/ESP32-C3/")
os.mkdir(path + "firmware/ESP32-S3-ETH/") os.mkdir(path + "firmware/ESP32-S3-ETH/")
sha = os.getenv("SHA",default="sha") sha = os.getenv("SHA",default="sha")
@ -94,6 +95,11 @@ def readVersion(path, infile):
dst = path + "firmware/ESP32-S2/" + versionout dst = path + "firmware/ESP32-S2/" + versionout
os.rename(src, dst) os.rename(src, dst)
versionout = version[:-1] + "_" + sha + "_esp32c3-mini.bin"
src = path + ".pio/build/esp32-c3-mini/firmware.bin"
dst = path + "firmware/ESP32-C3/" + versionout
os.rename(src, dst)
versionout = version[:-1] + "_" + sha + "_esp32s3.bin" versionout = version[:-1] + "_" + sha + "_esp32s3.bin"
src = path + ".pio/build/opendtufusion/firmware.bin" src = path + ".pio/build/opendtufusion/firmware.bin"
dst = path + "firmware/ESP32-S3/" + versionout dst = path + "firmware/ESP32-S3/" + versionout
@ -118,6 +124,13 @@ def readVersion(path, infile):
os.rename(src + "partitions.bin", dst + "partitions.bin") os.rename(src + "partitions.bin", dst + "partitions.bin")
genOtaBin(dst) genOtaBin(dst)
# other ESP32-C3 bin files
src = path + ".pio/build/esp32-c3-mini/"
dst = path + "firmware/ESP32-C3/"
os.rename(src + "bootloader.bin", dst + "bootloader.bin")
os.rename(src + "partitions.bin", dst + "partitions.bin")
genOtaBin(dst)
# other ESP32-S3 bin files # other ESP32-S3 bin files
src = path + ".pio/build/opendtufusion/" src = path + ".pio/build/opendtufusion/"
dst = path + "firmware/ESP32-S3/" dst = path + "firmware/ESP32-S3/"

9
src/CHANGES.md

@ -1,5 +1,14 @@
# Development Changes # Development Changes
## 0.8.29 - 2023-12-27
* fix MqTT generic topic `comm_disabled` #1265 #1286
* potential fix of #1285 (reset yield day)
* fix fraction of yield correction #1280
* fix crash if `getLossRate` was read from inverter #1288 #1290
* reduce reload time for opendtufusion ethernet variant to 5 seconds
* added basic grid parser
* added ESP32-C3 mini environment #1289
## 0.8.28 - 2023-12-23 ## 0.8.28 - 2023-12-23
* fix bug heuristic * fix bug heuristic
* add version information to clipboard once 'copy' was clicked * add version information to clipboard once 'copy' was clicked

2
src/app.cpp

@ -305,7 +305,7 @@ void app::tickMidnight(void) {
continue; // skip to next inverter continue; // skip to next inverter
// reset alarms // reset alarms
if(InverterStatus::OFF == iv->status) if(InverterStatus::OFF == iv->getStatus())
iv->resetAlarms(); iv->resetAlarms();
// clear max values // clear max values

2
src/defines.h

@ -13,7 +13,7 @@
//------------------------------------- //-------------------------------------
#define VERSION_MAJOR 0 #define VERSION_MAJOR 0
#define VERSION_MINOR 8 #define VERSION_MINOR 8
#define VERSION_PATCH 28 #define VERSION_PATCH 29
//------------------------------------- //-------------------------------------
typedef struct { typedef struct {

27
src/hm/Communication.h

@ -117,28 +117,6 @@ class Communication : public CommQueue<> {
break; break;
case States::WAIT: case States::WAIT:
/*if(millis() > mWaitTimeout_min) {
if(mIsRetransmit) { // we already have been through...
mWaitTimeout = mWaitTimeout_min;
} else if(q->iv->mGotFragment) { // nothing received yet?
if(q->iv->mGotLastMsg) {
//mState = States::CHECK_FRAMES;
mWaitTimeout = mWaitTimeout_min;
}
} else if(mFirstTry) {
if(*mSerialDebug) {
DPRINT_IVID(DBG_INFO, q->iv->id);
DBGPRINT(String(millis() - mWaitTimeout_min + mlastTO_min));
DBGPRINTLN(F("ms - second try"));
}
mFirstTry = false;
mlastTO_min = timeout_min;
q->iv->radioStatistics.retransmits++; // got nothing
mState = States::START;
break;
}
}*/
if(millis() < mWaitTimeout) if(millis() < mWaitTimeout)
return; return;
mState = States::CHECK_FRAMES; mState = States::CHECK_FRAMES;
@ -187,7 +165,7 @@ class Communication : public CommQueue<> {
if(parseMiFrame(p, q)) if(parseMiFrame(p, q))
q->iv->curFrmCnt++; q->iv->curFrmCnt++;
} }
} //else // serial does not match } //else -> serial does not match
q->iv->radio->mBufCtrl.pop(); q->iv->radio->mBufCtrl.pop();
yield(); yield();
@ -221,7 +199,6 @@ class Communication : public CommQueue<> {
else else
closeRequest(q, true); closeRequest(q, true);
} }
} }
} }
@ -306,7 +283,7 @@ class Communication : public CommQueue<> {
else else
ah::dumpBuf(p->packet, p->len); ah::dumpBuf(p->packet, p->len);
} else { } else {
DBGPRINT(F("| 0x")); DBGPRINT(F("| "));
DHEX(p->packet[0]); DHEX(p->packet[0]);
DBGPRINT(F(" ")); DBGPRINT(F(" "));
DBGHEXLN(p->packet[9]); DBGHEXLN(p->packet[9]);

5
src/hm/hmInverter.h

@ -458,6 +458,11 @@ class Inverter {
return producing; return producing;
} }
InverterStatus getStatus(){
isProducing(); // recalculate status
return status;
}
uint16_t getFwVersion() { uint16_t getFwVersion() {
record_t<> *rec = getRecordStruct(InverterDevInform_All); record_t<> *rec = getRecordStruct(InverterDevInform_All);
return getChannelFieldValue(CH0, FLD_FW_VERSION, rec); return getChannelFieldValue(CH0, FLD_FW_VERSION, rec);

7
src/hm/hmRadio.h

@ -98,7 +98,7 @@ class HmRadio : public Radio {
if(mNrf24->isChipConnected()) { if(mNrf24->isChipConnected()) {
DPRINTLN(DBG_INFO, F("Radio Config:")); DPRINTLN(DBG_INFO, F("Radio Config:"));
mNrf24->printPrettyDetails(); mNrf24->printPrettyDetails();
DPRINT(DBG_INFO, F("DTU_SN: 0x")); DPRINT(DBG_INFO, F("DTU_SN: "));
DBGPRINTLN(String(mDtuSn, HEX)); DBGPRINTLN(String(mDtuSn, HEX));
} else } else
DPRINTLN(DBG_WARN, F("WARNING! your NRF24 module can't be reached, check the wiring")); DPRINTLN(DBG_WARN, F("WARNING! your NRF24 module can't be reached, check the wiring"));
@ -152,7 +152,7 @@ class HmRadio : public Radio {
void sendControlPacket(Inverter<> *iv, uint8_t cmd, uint16_t *data, bool isRetransmit) { void sendControlPacket(Inverter<> *iv, uint8_t cmd, uint16_t *data, bool isRetransmit) {
DPRINT_IVID(DBG_INFO, iv->id); DPRINT_IVID(DBG_INFO, iv->id);
DBGPRINT(F("sendControlPacket cmd: 0x")); DBGPRINT(F("sendControlPacket cmd: "));
DBGHEXLN(cmd); DBGHEXLN(cmd);
initPacket(iv->radioId.u64, TX_REQ_DEVCONTROL, SINGLE_FRAME); initPacket(iv->radioId.u64, TX_REQ_DEVCONTROL, SINGLE_FRAME);
uint8_t cnt = 10; uint8_t cnt = 10;
@ -307,9 +307,8 @@ class HmRadio : public Radio {
else else
ah::dumpBuf(mTxBuf, len); ah::dumpBuf(mTxBuf, len);
} else { } else {
DBGPRINT(F("0x"));
DHEX(mTxBuf[0]); DHEX(mTxBuf[0]);
DBGPRINT(F(" 0x")); DBGPRINT(F(" "));
DHEX(mTxBuf[10]); DHEX(mTxBuf[10]);
DBGPRINT(F(" ")); DBGPRINT(F(" "));
DBGHEXLN(mTxBuf[9]); DBGHEXLN(mTxBuf[9]);

5
src/hms/hmsRadio.h

@ -42,7 +42,7 @@ class CmtRadio : public Radio {
} }
void sendControlPacket(Inverter<> *iv, uint8_t cmd, uint16_t *data, bool isRetransmit) { void sendControlPacket(Inverter<> *iv, uint8_t cmd, uint16_t *data, bool isRetransmit) {
DPRINT(DBG_INFO, F("sendControlPacket cmd: 0x")); DPRINT(DBG_INFO, F("sendControlPacket cmd: "));
DBGHEXLN(cmd); DBGHEXLN(cmd);
initPacket(iv->radioId.u64, TX_REQ_DEVCONTROL, SINGLE_FRAME); initPacket(iv->radioId.u64, TX_REQ_DEVCONTROL, SINGLE_FRAME);
uint8_t cnt = 10; uint8_t cnt = 10;
@ -96,9 +96,8 @@ class CmtRadio : public Radio {
else else
ah::dumpBuf(mTxBuf, len); ah::dumpBuf(mTxBuf, len);
} else { } else {
DBGPRINT(F("0x"));
DHEX(mTxBuf[0]); DHEX(mTxBuf[0]);
DBGPRINT(F(" 0x")); DBGPRINT(F(" "));
DHEX(mTxBuf[10]); DHEX(mTxBuf[10]);
DBGHEXLN(mTxBuf[9]); DBGHEXLN(mTxBuf[9]);
} }

15
src/platformio.ini

@ -123,6 +123,21 @@ build_flags = ${env.build_flags}
monitor_filters = monitor_filters =
esp32_exception_decoder esp32_exception_decoder
[env:esp32-c3-mini]
platform = espressif32@6.4.0
board = lolin_c3_mini
build_flags = ${env.build_flags}
-DUSE_HSPI_FOR_EPD
-DDEF_NRF_CS_PIN=5
-DDEF_NRF_CE_PIN=0
-DDEF_NRF_IRQ_PIN=1
-DDEF_NRF_MISO_PIN=3
-DDEF_NRF_MOSI_PIN=4
-DDEF_NRF_SCLK_PIN=2
monitor_filters =
esp32_exception_decoder
[env:opendtufusion] [env:opendtufusion]
platform = espressif32@6.4.0 platform = espressif32@6.4.0
board = esp32-s3-devkitc-1 board = esp32-s3-devkitc-1

14
src/publisher/pubMqtt.h

@ -153,6 +153,10 @@ class PubMqtt {
publish(mSubTopic, ((iv->commEnabled) ? dict[STR_TRUE] : dict[STR_FALSE]), true); publish(mSubTopic, ((iv->commEnabled) ? dict[STR_TRUE] : dict[STR_FALSE]), true);
} }
snprintf(mSubTopic, 32 + MAX_NAME_LENGTH, "comm_disabled");
publish(mSubTopic, (((*mUtcTimestamp > (sunset + offs)) || (*mUtcTimestamp < (sunrise - offs))) ? dict[STR_TRUE] : dict[STR_FALSE]), true);
return true; return true;
} }
@ -483,22 +487,22 @@ class PubMqtt {
continue; // skip to next inverter continue; // skip to next inverter
// inverter status // inverter status
iv->isProducing(); // recalculate status InverterStatus status = iv->getStatus();
if (InverterStatus::OFF < iv->status) if (InverterStatus::OFF < status)
anyAvail = true; anyAvail = true;
else // inverter is enabled but not available else // inverter is enabled but not available
allAvail = false; allAvail = false;
if(mLastIvState[id] != iv->status) { if(mLastIvState[id] != status) {
// if status changed from producing to not producing send last data immediately // if status changed from producing to not producing send last data immediately
if (InverterStatus::WAS_PRODUCING == mLastIvState[id]) if (InverterStatus::WAS_PRODUCING == mLastIvState[id])
sendData(iv, RealTimeRunData_Debug); sendData(iv, RealTimeRunData_Debug);
mLastIvState[id] = iv->status; mLastIvState[id] = status;
changed = true; changed = true;
snprintf(mSubTopic, 32 + MAX_NAME_LENGTH, "%s/available", iv->config->name); snprintf(mSubTopic, 32 + MAX_NAME_LENGTH, "%s/available", iv->config->name);
snprintf(mVal, 40, "%d", (uint8_t)iv->status); snprintf(mVal, 40, "%d", (uint8_t)status);
publish(mSubTopic, mVal, true); publish(mSubTopic, mVal, true);
} }
} }

2
src/publisher/pubMqttIvData.h

@ -146,7 +146,7 @@ class PubMqttIvData {
// calculate total values for RealTimeRunData_Debug // calculate total values for RealTimeRunData_Debug
if (CH0 == rec->assign[mPos].ch) { if (CH0 == rec->assign[mPos].ch) {
if(mIv->status > InverterStatus::STARTING) { if(mIv->getStatus() > InverterStatus::STARTING) {
if(mIv->config->add2Total) { if(mIv->config->add2Total) {
mTotalFound = true; mTotalFound = true;
switch (rec->assign[mPos].fieldId) { switch (rec->assign[mPos].fieldId) {

13
src/web/RestApi.h

@ -323,9 +323,14 @@ class RestApi {
void getHtmlSave(AsyncWebServerRequest *request, JsonObject obj) { void getHtmlSave(AsyncWebServerRequest *request, JsonObject obj) {
getGeneric(request, obj.createNestedObject(F("generic"))); getGeneric(request, obj.createNestedObject(F("generic")));
obj["pending"] = (bool)mApp->getSavePending(); obj[F("pending")] = (bool)mApp->getSavePending();
obj["success"] = (bool)mApp->getLastSaveSucceed(); obj[F("success")] = (bool)mApp->getLastSaveSucceed();
obj["reboot"] = (bool)mApp->getShouldReboot(); obj[F("reboot")] = (bool)mApp->getShouldReboot();
#if defined(ETHERNET) && defined(CONFIG_IDF_TARGET_ESP32S3)
obj[F("reload")] = 5;
#else
obj[F("reload")] = 20;
#endif
} }
void getReboot(AsyncWebServerRequest *request, JsonObject obj) { void getReboot(AsyncWebServerRequest *request, JsonObject obj) {
@ -421,7 +426,7 @@ class RestApi {
obj[F("max_pwr")] = iv->getMaxPower(); obj[F("max_pwr")] = iv->getMaxPower();
obj[F("ts_last_success")] = rec->ts; obj[F("ts_last_success")] = rec->ts;
obj[F("generation")] = iv->ivGen; obj[F("generation")] = iv->ivGen;
obj[F("status")] = (uint8_t)iv->status; obj[F("status")] = (uint8_t)iv->getStatus();
obj[F("alarm_cnt")] = iv->alarmCnt; obj[F("alarm_cnt")] = iv->alarmCnt;
obj[F("rssi")] = iv->rssi; obj[F("rssi")] = iv->rssi;

8
src/web/html/api.js

@ -175,6 +175,14 @@ function getAjax(url, ptr, method="GET", json=null) {
} }
} }
const getJSON = async url => {
const re = await fetch(url);
if(!re.ok)
throw new Error(re.statusText);
const data = re.json();
return data;
}
/** /**
* CREATE DOM FUNCTIONS * CREATE DOM FUNCTIONS
*/ */

764
src/web/html/grid_info.json

@ -0,0 +1,764 @@
{
"type": [
{"0x0300": "DE_VDE4105_2018"},
{"0x0a00": "DE NF_EN_50549-1:2019"},
{"0x0c00": "AT_TOR_Erzeuger_default"},
{"0x0d04": "France NF_EN_50549-1:2019"},
{"0x1200": "2.0.4 (EU_EN50438)"},
{"0x3700": "2.0.0 (CH_NA EEA-NE7–CH2020)"}
],
"grp_codes": [
{"0x00": "Voltage H/LVRT"},
{"0x10": "Frequency H/LFRT"},
{"0x20": "Islanding Detection"},
{"0x30": "Reconnection"},
{"0x40": "Ramp Rates"},
{"0x50": "Frequency Watt"},
{"0x60": "Volt Watt"},
{"0x70": "Active Power Control"},
{"0x80": "Volt Var"},
{"0x90": "Specified Power Factor"},
{"0xa0": "Reactive Power Control"},
{"0xb0": "Watt Power Factor"}
],
"group": [
{
"0x0003": [
{
"name": "Nominal Voltage",
"div": 10,
"def": 230,
"unit": "V"
},
{
"name": "Low Voltage 1",
"div": 10,
"min": 180,
"max": 207,
"def": 184,
"unit": "V"
},
{
"name": "LV1 Maximum Trip Time",
"div": 10,
"def": 1.5,
"unit": "s"
},
{
"name": "High Voltage 1",
"div": 10,
"min": 250,
"max": 270,
"def": 253,
"unit": "V"
},
{
"name": "HV1 Maximum Trip Time",
"div": 10,
"min": 0.1,
"max": 100,
"def": 0.1,
"unit": "s"
},
{
"name": "Low Voltage 2",
"div": 10,
"min": 80,
"max": 161,
"def": 104,
"unit": "V"
},
{
"name": "LV2 Maximum Trip Time",
"div": 100,
"min": 0.1,
"max": 5,
"def": 0.3,
"unit": "s"
},
{
"name": "High Voltage 2",
"div": 10,
"min": 230,
"max": 299,
"def": 276,
"unit": "V"
},
{
"name": "HV2 Maximum Trip Time",
"div": 100,
"min": 0,
"max": 5,
"def": 0.05,
"unit": "s"
}
]
},
{
"0x000a": [
{
"name": "Nominal Voltage",
"div": 10,
"def": 230,
"unit": "V"
},
{
"name": "Low Voltage 1",
"div": 10,
"min": 160,
"max": 195.5,
"def": 184,
"unit": "V"
},
{
"name": "LV1 Maximum Trip Time",
"div": 10,
"def": 3,
"unit": "s"
},
{
"name": "High Voltage 1",
"div": 10,
"min": 270,
"max": 287.5,
"def": 287.5,
"unit": "V"
},
{
"name": "HV1 Maximum Trip Time",
"div": 10,
"def": 0.1,
"unit": "s"
},
{
"name": "Low Voltage 2",
"div": 10,
"max": 150,
"min": 100,
"def": 103.5,
"unit": "V"
},
{
"name": "LV2 Maximum Trip Time",
"div": 100,
"def": 0.3,
"unit": "s"
},
{
"name": "10 mins Average High Voltage",
"div": 10,
"min": 250,
"max": 270,
"def": 253,
"unit": "V"
}
]
},
{
"0x000c": [
{
"name": "Nominal Voltage",
"div": 10,
"unit": "V"
},
{
"name": "Low Voltage 1",
"div": 10,
"min": 180,
"max": 207,
"def": 184,
"unit": "V"
},
{
"name": "LV1 Maximum Trip Time",
"div": 10,
"min": 0.1,
"max": 5,
"def": 1.5,
"unit": "s"
},
{
"name": "High Voltage 1",
"div": 10,
"min": 250,
"max": 270,
"def": 253,
"unit": "V"
},
{
"name": "HV1 Maximum Trip Time",
"div": 10,
"min": 0.1,
"max": 100,
"def": 3,
"unit": "s"
},
{
"name": "Low Voltage 2",
"div": 10,
"min": 80,
"max": 161,
"def": 161,
"unit": "V"
},
{
"name": "LV2 Maximum Trip Time",
"div": 100,
"min": 0.1,
"max": 5,
"def": 0.2,
"unit": "s"
},
{
"name": "High Voltage 2",
"div": 10,
"min": 230,
"max": 299,
"def": 264.5,
"unit": "V"
},
{
"name": "HV2 Maximum Trip Time",
"div": 100,
"min": 0.1,
"max": 5,
"def": 0.2,
"unit": "s"
},
{
"name": "High Voltage 3",
"div": 10,
"def": 276,
"unit": "V"
},
{
"name": "HV3 Maximum Trip Time",
"div": 100,
"min": 0.1,
"max": 10,
"def": 0.1,
"unit": "s"
},
{
"name": "10 mins Average High Voltage",
"div": 10,
"def": 253,
"unit": "V"
}
]
},
{
"0x1000": [
{
"name": "Nominal Frequency",
"div": 100,
"def": 50,
"unit": "Hz"
},
{
"name": "Low Frequency 1",
"div": 100,
"min": 47.5,
"max": 49.9,
"def": 47.5,
"unit": "Hz"
},
{
"name": "LF1 Maximum Trip Time",
"div": 10,
"def": 0.1,
"unit": "s"
},
{
"name": "High Frequency 1",
"div": 100,
"min": 50.1,
"max": 51.5,
"def": 51.5,
"unit": "Hz"
},
{
"name": "HF1 Maximum Trip Time",
"div": 10,
"def": 0.1,
"unit": "s"
}
]
},
{
"0x1003": [
{
"name": "Nominal Frequency",
"div": 100,
"def": 50,
"unit": "Hz"
},
{
"name": "Low Frequency 1",
"div": 100,
"min": 45,
"max": 49.9,
"def": 48,
"unit": "Hz"
},
{
"name": "LF1 Maximum Trip Time",
"div": 100,
"min": 0.1,
"max": 20,
"def": 2,
"unit": "s"
},
{
"name": "High Frequency 1",
"div": 100,
"min": 50,
"max": 53,
"def": 51,
"unit": "Hz"
},
{
"name": "HF1 Maximum Trip time",
"div": 10,
"min": 0.1,
"max": 20,
"def": 2,
"unit": "s"
},
{
"name": "Low Frequency 2",
"div": 100,
"min": 45,
"max": 50,
"def": 47.5,
"unit": "Hz"
},
{
"name": "LF2 Maximum Trip Time",
"div": 10,
"min": 0.1,
"max": 5,
"def": 0.5,
"unit": "s"
},
{
"name": "High Frequency 2",
"div": 100,
"min": 50,
"max": 52,
"def": 52,
"unit": "Hz"
},
{
"name": "HF2 Maximum Trip time",
"div": 10,
"min": 0.1,
"max": 5,
"def": 0.5,
"unit": "s"
}
]
},
{
"0x2000": [
{
"name": "Island Detection Activated",
"div": 1,
"min": 0,
"max": 1,
"def": 1
}
]
},
{
"0x3003": [
{
"name": "Reconnect Time",
"div": 10,
"min": 10,
"max": 300,
"def": 60,
"unit": "s"
},
{
"name": "Reconnect High Voltage",
"div": 10,
"min": 240,
"max": 276,
"def": 253,
"unit": "V"
},
{
"name": "Reconnect Low Voltage",
"div": 10,
"min": 195.5,
"max": 210,
"def": 195.5,
"unit": "V"
},
{
"name": "Reconnect High Frequency",
"div": 100,
"max": 50.9,
"min": 50.1,
"def": 50.2,
"unit": "Hz"
},
{
"name": "Reconnect Low Frequency",
"div": 100,
"min": 47.5,
"max": 49.9,
"def": 49.5,
"unit": "Hz"
}
]
},
{
"0x4000": [
{
"name": "Normal Ramp up Rate",
"div": 100,
"min": 0.1,
"max": 100,
"def": 20,
"unit": "Rated%/s"
},
{
"name": "Soft Start Ramp up Rate ",
"div": 100,
"min": 0.1,
"max": 10,
"def": 0.16,
"unit": "Rated%/s"
}
]
},
{
"0x5001": [
{
"name": "FW Function Activated",
"div": 1,
"min": 0,
"max": 1,
"def": 1
},
{
"name": "Start of Frequency Watt Droop",
"div": 100,
"min": 50.2,
"max": 53,
"def": 50.2,
"unit": "Hz"
},
{
"name": "FW Droop Slope",
"div": 10,
"min": 16.7,
"max": 100,
"def": 40,
"unit": "Pn%/Hz"
},
{
"name": "Recovery Ramp Rate",
"div": 100,
"min": 0.1,
"max": 100,
"def": 0.16,
"unit": "Pn%/s"
},
{
"name": "FW Setting Time",
"div": 10,
"min": 0,
"max": 2,
"def": 0,
"unit": "s"
}
]
},
{
"0x5008": [
{
"name": "FW Function Activated",
"div": 1,
"min": 0,
"max": 1,
"def": 1
},
{
"name": "Start of Frequency Watt Droop",
"div": 100,
"min": 50.2,
"max": 52,
"def": 50.2,
"unit": "Hz"
},
{
"name": "FW Droop Slope",
"div": 10,
"min": 16.7,
"max": 100,
"def": 40,
"unit": "Pn%/Hz"
},
{
"name": "Recovery Ramp Rate",
"div": 100,
"min": 0.1,
"max": 50,
"def": 0.16,
"unit": "Pn%/s"
},
{
"name": "Recovery High Frequency",
"div": 10,
"min": 50.1,
"max": 52,
"def": 50.2,
"unit": "Hz"
},
{
"name": "Recovery Low Frequency",
"div": 100,
"min": 49,
"max": 49.9,
"def": 49.8,
"unit": "Hz"
}
]
},
{
"0x6000": [
{
"name": "VW Function Activated",
"div": 1,
"min": 0,
"max": 1,
"def": 1
},
{
"name": "Start of Voltage Watt Droop",
"div": 10,
"def": 253,
"unit": "V"
},
{
"name": "End of Voltage Watt Droop",
"div": 10,
"min": 258,
"max": 270,
"def": 265,
"unit": "V"
},
{
"name": "VW Droop Slope",
"div": 100,
"min": 5,
"max": 18,
"def": 5.33,
"unit": "Pn%/V"
}
]
},
{
"0x7002": [
{
"name": "APC Function Activated",
"div": 1,
"min": 0,
"max": 1,
"def": 1
},
{
"name": "Power Ramp Rate",
"div": 100,
"min": 0.33,
"max": 100,
"def": 100,
"unit": "Pn%/s"
}
]
},
{
"0x8000": [
{
"name": "VV Function Activated",
"div": 1,
"min": 0,
"max": 1,
"def": 0
},
{
"name": "Voltage Set Point V1",
"div": 10,
"min": 184,
"max": 230,
"def": 213.9,
"unit": "V"
},
{
"name": "Reactive Set Point Q1",
"div": 10,
"min": 0,
"max": 100,
"def": 30,
"unit": "%Pn"
},
{
"name": "Voltage Set Point V2",
"div": 10,
"min": 210,
"max": 240,
"def": 223.1,
"unit": "V"
},
{
"name": "Voltage Set Point V3",
"div": 10,
"min": 220,
"max": 240,
"def": 236.9,
"unit": "V"
},
{
"name": "Voltage Set Point V4",
"div": 10,
"min": 230,
"max": 253,
"def": 246.1,
"unit": "V"
},
{
"name": "Reactive Set Point Q4",
"div": 10,
"min": 0,
"max": 100,
"def": 30,
"unit": "%Pn"
}
]
},
{
"0x8001": [
{
"name": "VV Function Activated",
"div": 1,
"min": 0,
"max": 1,
"def": 0
},
{
"name": "Voltage Set Point V1",
"div": 10,
"def": 213.9,
"unit": "V"
},
{
"name": "Reactive Set Point Q1",
"div": 10,
"min": 0,
"max": 100,
"def": 30,
"unit": "%Pn"
},
{
"name": "Voltage Set Point V2",
"div": 10,
"def": 223.1,
"unit": "V"
},
{
"name": "Voltage Set Point V3",
"div": 10,
"def": 236.9,
"unit": "V"
},
{
"name": "Voltage Set Point V4",
"div": 10,
"def": 246.1,
"unit": "V"
},
{
"name": "Reactive Set Point Q4",
"div": 10,
"min": 0,
"max": 100,
"def": 30,
"unit": "%Pn"
},
{
"name": "VV Setting Time",
"div": 10,
"min": 0,
"max": 60,
"def": 10,
"unit": "s"
}
]
},
{
"0x9000": [
{
"name": "Specified Power Factor Activated",
"div": 1,
"min": 0,
"max": 1,
"def": 0
},
{
"name": "Power Factor",
"div": 100,
"min": 0.9,
"max": 1,
"def": 0.95
}
]
},
{
"0xa002": [
{
"name": "RPC Function Activated",
"div": 1,
"min": 0,
"max": 1,
"def": 0
},
{
"name": "Reactive Power",
"div": 100,
"min": 0,
"max": 50,
"def": 0,
"unit": "%Sn"
}
]
},
{
"0xb000": [
{
"name": "WPF Function Activated",
"div": 1,
"min": 0,
"max": 1,
"def": 0
},
{
"name": "Start of Power of WPF",
"div": 10,
"def": 50,
"unit": "%Pn"
},
{
"name": "Power Factor ar Rated Power",
"div": 100,
"min": 0.8,
"max": 1,
"def": 0.95
}
]
}
]
}

4
src/web/html/save.html

@ -34,8 +34,8 @@
html = "Settings successfully saved. Automatic page reload in 3 seconds."; html = "Settings successfully saved. Automatic page reload in 3 seconds.";
meta.content = 3; meta.content = 3;
} else { } else {
html = "Settings successfully saved. Rebooting. Automatic redirect in 20 seconds."; html = "Settings successfully saved. Rebooting. Automatic redirect in " + obj.reload + " seconds.";
meta.content = 20 + "; URL=/"; meta.content = obj.reload + "; URL=/";
} }
document.getElementsByTagName('head')[0].appendChild(meta); document.getElementsByTagName('head')[0].appendChild(meta);
} else { } else {

55
src/web/html/setup.html

@ -435,6 +435,31 @@
[47, "GPIO47"], [47, "GPIO47"],
[48, "GPIO48"], [48, "GPIO48"],
]; ];
var esp32c3pins = [
[255, "off / default"],
[0, "GPIO0"],
[1, "GPIO1"],
[2, "GPIO2"],
[3, "GPIO3"],
[4, "GPIO4"],
[5, "GPIO5"],
[6, "GPIO6"],
[7, "GPIO7"],
[8, "GPIO8"],
[9, "GPIO9"],
[10, "GPIO10"],
[11, "GPIO11"],
[12, "GPIO12 (PSRAM/FLASH)"],
[13, "GPIO13 (PSRAM/FLASH)"],
[14, "GPIO14 (PSRAM/FLASH)"],
[15, "GPIO15 (PSRAM/FLASH)"],
[16, "GPIO16 (PSRAM/FLASH)"],
[17, "GPIO17 (PSRAM/FLASH)"],
[18, "GPIO18 (DONT USE - USB-)"],
[19, "GPIO19 (DONT USE - USB+)"],
[20, "GPIO20 (RX)"],
[21, "GPIO21 (TX)"],
];
/*ENDIF_ESP32*/ /*ENDIF_ESP32*/
var nrfPa = [ var nrfPa = [
[0, "MIN (recommended)"], [0, "MIN (recommended)"],
@ -676,7 +701,7 @@
ml("td", {}, String(i+1)), ml("td", {}, String(i+1)),
ml("td", {}, ml("input", {name: "ch_p"+i, class: "text", type: "number", max: 999, value: obj.ch_max_pwr[i]}, null)), ml("td", {}, ml("input", {name: "ch_p"+i, class: "text", type: "number", max: 999, value: obj.ch_max_pwr[i]}, null)),
ml("td", {}, ml("input", {name: "ch_n"+i, class: "text", type: "text", maxlength: 15, value: (undefined === obj.ch_name[i]) ? "" : obj.ch_name[i]}, null)), ml("td", {}, ml("input", {name: "ch_n"+i, class: "text", type: "text", maxlength: 15, value: (undefined === obj.ch_name[i]) ? "" : obj.ch_name[i]}, null)),
ml("td", {}, ml("input", {name: "yld_c"+i, class: "text", type: "number", max: 999999, value: obj.ch_yield_cor[i]}, null)) ml("td", {}, ml("input", {name: "yld_c"+i, class: "text", type: "number", max: 999999, value: obj.ch_yield_cor[i], step: "0.001"}, null))
])); ]));
} }
@ -872,13 +897,17 @@
function parsePinout(obj, type, system) { function parsePinout(obj, type, system) {
var e = document.getElementById("pinout"); var e = document.getElementById("pinout");
var pinList = esp32pins;
if("ESP8266" == type) pinList = esp8266pins;
else if ("ESP32-S3" == system["chip_model"]) pinList = esp32s3pins;
else if("ESP32-C3" == system["chip_model"]) pinList = esp32c3pins;
pins = [['led0', 'pinLed0', 'At least one inverter is producing'], ['led1', 'pinLed1', 'MqTT connected']]; pins = [['led0', 'pinLed0', 'At least one inverter is producing'], ['led1', 'pinLed1', 'MqTT connected']];
for(p of pins) { for(p of pins) {
e.append( e.append(
ml("div", {class: "row mb-3"}, [ ml("div", {class: "row mb-3"}, [
ml("div", {class: "col-12 col-sm-3 my-2"}, p[2]), ml("div", {class: "col-12 col-sm-3 my-2"}, p[2]),
ml("div", {class: "col-12 col-sm-9"}, ml("div", {class: "col-12 col-sm-9"},
sel(p[1], ("ESP8266" == type) ? esp8266pins : ("ESP32-S3" == system["chip_model"]) ? esp32s3pins : esp32pins, obj[p[0]]) sel(p[1], pinList, obj[p[0]])
) )
]) ])
); );
@ -898,6 +927,11 @@
var en = inp("nrfEnable", null, null, ["cb"], "nrfEnable", "checkbox"); var en = inp("nrfEnable", null, null, ["cb"], "nrfEnable", "checkbox");
en.checked = obj["en"]; en.checked = obj["en"];
var pinList = esp32pins;
if("ESP8266" == type) pinList = esp8266pins;
else if ("ESP32-S3" == system["chip_model"]) pinList = esp32s3pins;
else if("ESP32-C3" == system["chip_model"]) pinList = esp32c3pins;
e.replaceChildren ( e.replaceChildren (
ml("div", {class: "row mb-3"}, [ ml("div", {class: "row mb-3"}, [
ml("div", {class: "col-8 col-sm-3 my-2"}, "NRF24 Enable"), ml("div", {class: "col-8 col-sm-3 my-2"}, "NRF24 Enable"),
@ -915,7 +949,7 @@
ml("div", {class: "row mb-3"}, [ 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-3 my-2"}, p[0].toUpperCase()),
ml("div", {class: "col-12 col-sm-9"}, ml("div", {class: "col-12 col-sm-9"},
sel(p[1], ("ESP8266" == type) ? esp8266pins : ("ESP32-S3" == system["chip_model"]) ? esp32s3pins : esp32pins, objPin[p[0]]) sel(p[1], pinList, objPin[p[0]])
) )
]) ])
); );
@ -926,6 +960,10 @@
function parseCmtRadio(obj, type, system) { function parseCmtRadio(obj, type, system) {
var e = document.getElementById("cmt"); var e = document.getElementById("cmt");
var en = inp("cmtEnable", null, null, ["cb"], "cmtEnable", "checkbox"); var en = inp("cmtEnable", null, null, ["cb"], "cmtEnable", "checkbox");
var pinList = esp32pins;
if ("ESP32-S3" == system["chip_model"]) pinList = esp32s3pins;
else if("ESP32-C3" == system["chip_model"]) pinList = esp32c3pins;
en.checked = obj["en"]; en.checked = obj["en"];
e.replaceChildren ( e.replaceChildren (
@ -940,7 +978,7 @@
ml("div", {class: "row mb-3"}, [ 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-3 my-2"}, p[0].toUpperCase()),
ml("div", {class: "col-12 col-sm-9"}, ml("div", {class: "col-12 col-sm-9"},
sel(p[1], (("ESP32-S3" == system["chip_model"]) ? esp32s3pins : esp32pins), obj[p[0]]) sel(p[1], pinList, obj[p[0]])
) )
]) ])
); );
@ -954,6 +992,11 @@
} }
function parseDisplay(obj, type, system) { function parseDisplay(obj, type, system) {
var pinList = esp32pins;
if("ESP8266" == type) pinList = esp8266pirpins;
else if ("ESP32-S3" == system["chip_model"]) pinList = esp32s3pins;
else if("ESP32-C3" == system["chip_model"]) pinList = esp32c3pins;
for(var i of ["disp_pwr"]) for(var i of ["disp_pwr"])
document.getElementsByName(i)[0].checked = obj[i]; document.getElementsByName(i)[0].checked = obj[i];
@ -968,7 +1011,7 @@
ml("div", {class: "row mb-3", id: "row_" + p[1]}, [ ml("div", {class: "row mb-3", id: "row_" + p[1]}, [
ml("div", {class: "col-12 col-sm-3 my-2"}, p[0].toUpperCase()), ml("div", {class: "col-12 col-sm-3 my-2"}, p[0].toUpperCase()),
ml("div", {class: "col-12 col-sm-9"}, ml("div", {class: "col-12 col-sm-9"},
sel(p[1], ("ESP8266" == type) ? esp8266pins : ("ESP32-S3" == system["chip_model"]) ? esp32s3pins : esp32pins, obj[p[1]]) sel(p[1], pinList, obj[p[1]])
) )
]) ])
); );
@ -1018,7 +1061,7 @@
document.getElementById("pirPin").append( document.getElementById("pirPin").append(
ml("div", {class: "row mb-3"}, [ ml("div", {class: "row mb-3"}, [
ml("div", {class: "col-12 col-sm-3 my-2"}, "PIR sensor"), ml("div", {class: "col-12 col-sm-3 my-2"}, "PIR sensor"),
ml("div", {class: "col-12 col-sm-9"}, sel("pir_pin", ("ESP8266" == type) ? esp8266pirpins : ("ESP32-S3" == system["chip_model"]) ? esp32s3pins : esp32pins, obj["pir_pin"])) ml("div", {class: "col-12 col-sm-9"}, sel("pir_pin", pinList, obj["pir_pin"]))
]) ])
); );

75
src/web/html/visualization.html

@ -295,13 +295,76 @@
getAjax("/api/inverter/grid/" + obj.id, showGridProfile); getAjax("/api/inverter/grid/" + obj.id, showGridProfile);
}}, null)) }}, null))
]) ])
]); ])
modal("Info for inverter " + obj.name, ml("div", {}, html)); modal("Info for inverter " + obj.name, ml("div", {}, html))
}
function getGridValue(g) {
var val = (parseInt(g.grid.substring(g.offs*3, g.offs*3+2), 16) * 256)
+ parseInt(g.grid.substring(g.offs*3+3, g.offs*3+5), 16)
g.offs += 2
return val
}
function getGridIdentifier(g) {
return "0x" + getGridValue(g).toString(16).padStart(4, '0')
}
function getGridType(t, id) {
for(e of t) {
if(undefined !== e[id])
return e[id]
}
return null
}
function parseGridGroup(g) {
var id = getGridIdentifier(g)
var type = getGridType(g.info.grp_codes, id.substring(0, 4))
var content = []
content.push(ml("div", {class: "row"},
ml("div", {class: "col head p-2 mt-3"},
ml("div", {class: "col a-c"}, type + " (Code " + id + ")")
)
))
content.push(ml("div", {class: "row my-2"}, [
ml("div", {class: "col-4"}, ml("b", {}, "Name")),
ml("div", {class: "col-3"}, ml("b", {}, "Value")),
ml("div", {class: "col-3"}, ml("b", {}, "Range")),
ml("div", {class: "col-2"}, ml("b", {}, "Default"))
]))
for(e of g.info.group) {
if(Array.isArray(e[id])) {
for(e of e[id]) {
var v = String(getGridValue(g) / e.div);
var vt = (v !== String(e.def)) ? "b" : "span";
content.push(ml("div", {class: "row mt-2"}, [
ml("div", {class: "col-4"}, e.name),
ml("div", {class: "col-3"}, ml(vt, {}, v + ((undefined !== e.unit) ? " [" + e.unit + "]" : ""))),
ml("div", {class: "col-3"}, (undefined !== e.min) ? (e.min + " - " + e.max) : "n/a"),
ml("div", {class: "col-2"}, String(e.def))
]))
}
}
}
return ml("div", {class: "col"}, [...content])
} }
function showGridProfile(obj) { function showGridProfile(obj) {
var html = ml("pre", {}, obj.grid); getJSON("/grid_info.json").then(data => {
modal("Grid Profile for inverter " + obj.name, ml("div", {}, html)); var glob = {offs:0, grid:obj.grid, info: data}
var content = [];
content.push(ml("div", {class: "row"},
ml("div", {class: "col my-3"}, ml("h5", {}, getGridType(glob.info.type, getGridIdentifier(glob)) + " (Version " + getGridValue(glob).toString(16) + ")"))
))
while((glob.offs*3) < glob.grid.length) {
content.push(parseGridGroup(glob))
}
modal("Grid Profile for inverter " + obj.name, ml("div", {}, ml("div", {class: "col mb-2"}, [...content])))
})
} }
@ -315,8 +378,8 @@
tr2(["RX fragments", obj.frame_cnt, ""]), tr2(["RX fragments", obj.frame_cnt, ""]),
tr2(["TX retransmits", obj.retransmits, ""]) tr2(["TX retransmits", obj.retransmits, ""])
]) ])
]); ])
modal("Radio statistics for inverter " + obj.name, ml("div", {}, html)); modal("Radio statistics for inverter " + obj.name, ml("div", {}, html))
} }
function limitModal(obj) { function limitModal(obj) {

12
src/web/web.h

@ -25,6 +25,7 @@
#include "html/h/colorBright_css.h" #include "html/h/colorBright_css.h"
#include "html/h/colorDark_css.h" #include "html/h/colorDark_css.h"
#include "html/h/favicon_ico.h" #include "html/h/favicon_ico.h"
#include "html/h/grid_info_json.h"
#include "html/h/index_html.h" #include "html/h/index_html.h"
#include "html/h/login_html.h" #include "html/h/login_html.h"
#include "html/h/serial_html.h" #include "html/h/serial_html.h"
@ -65,6 +66,7 @@ class Web {
mWeb.on("/colors.css", HTTP_GET, std::bind(&Web::onColor, this, std::placeholders::_1)); mWeb.on("/colors.css", HTTP_GET, std::bind(&Web::onColor, this, std::placeholders::_1));
mWeb.on("/style.css", HTTP_GET, std::bind(&Web::onCss, this, std::placeholders::_1)); mWeb.on("/style.css", HTTP_GET, std::bind(&Web::onCss, this, std::placeholders::_1));
mWeb.on("/api.js", HTTP_GET, std::bind(&Web::onApiJs, this, std::placeholders::_1)); mWeb.on("/api.js", HTTP_GET, std::bind(&Web::onApiJs, this, std::placeholders::_1));
mWeb.on("/grid_info.json", HTTP_GET, std::bind(&Web::onGridInfoJson, this, std::placeholders::_1));
mWeb.on("/favicon.ico", HTTP_GET, std::bind(&Web::onFavicon, this, std::placeholders::_1)); mWeb.on("/favicon.ico", HTTP_GET, std::bind(&Web::onFavicon, this, std::placeholders::_1));
mWeb.onNotFound ( std::bind(&Web::showNotFound, this, std::placeholders::_1)); mWeb.onNotFound ( std::bind(&Web::showNotFound, this, std::placeholders::_1));
mWeb.on("/reboot", HTTP_ANY, std::bind(&Web::onReboot, this, std::placeholders::_1)); mWeb.on("/reboot", HTTP_ANY, std::bind(&Web::onReboot, this, std::placeholders::_1));
@ -389,6 +391,16 @@ class Web {
request->send(response); request->send(response);
} }
void onGridInfoJson(AsyncWebServerRequest *request) {
DPRINTLN(DBG_VERBOSE, F("onGridInfoJson"));
AsyncWebServerResponse *response = request->beginResponse_P(200, F("application/json; charset=utf-8"), grid_info_json, grid_info_json_len);
response->addHeader(F("Content-Encoding"), "gzip");
if(request->hasParam("v"))
response->addHeader(F("Cache-Control"), F("max-age=604800"));
request->send(response);
}
void onFavicon(AsyncWebServerRequest *request) { void onFavicon(AsyncWebServerRequest *request) {
static const char favicon_type[] PROGMEM = "image/x-icon"; static const char favicon_type[] PROGMEM = "image/x-icon";
AsyncWebServerResponse *response = request->beginResponse_P(200, favicon_type, favicon_ico, favicon_ico_len); AsyncWebServerResponse *response = request->beginResponse_P(200, favicon_type, favicon_ico, favicon_ico_len);

Loading…
Cancel
Save