diff --git a/.github/workflows/compile_esp8266.yml b/.github/workflows/compile_esp8266.yml index 245a5aac..787465d3 100644 --- a/.github/workflows/compile_esp8266.yml +++ b/.github/workflows/compile_esp8266.yml @@ -39,7 +39,7 @@ jobs: working-directory: tools/esp8266/html run: python convert.py - name: Run PlatformIO - run: pio run -d tools/esp8266 --environment esp8266-release + run: pio run -d tools/esp8266 --environment esp8266-release --environment esp32-wroom32-release - name: rename-binary-files id: rename-binary-files working-directory: tools/esp8266/scripts @@ -72,4 +72,4 @@ jobs: upload_url: ${{ steps.create-release.outputs.upload_url }} asset_path: ./${{ steps.rename-binary-files.outputs.name }}.zip asset_name: ${{ steps.rename-binary-files.outputs.name }}.zip - asset_content_type: application/zip \ No newline at end of file + asset_content_type: application/zip diff --git a/README.md b/README.md index b28364bb..2411a7f2 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,21 @@ ![Logo](https://github.com/grindylow/ahoy/blob/main/doc/logo1_small.png?raw=true) # ahoy -Various tools, examples, and documentation for communicating with Hoymiles microinverters. +Ahoi is a project to bypass the original Hoymiles cloud solution. +In order to use this project, it is important what the area of ​​​​application looks like. +With each version it is necessary to have an NRF24L01+. -In particular: +Click on the link below you are interested in. +There you will find further explanations on how to proceed. (*Note: It is still under construction!*) -* `doc/hoymiles-format-description.txt` is a [detailed description of the communications format](doc/hoymiles-format-description.md) and the history of this project -* `doc/getting-started-ESP8266.md` shows the [hardware setup for an ESP8266-based system](doc/getting-started-ESP8266.md) -* The `tools` folder contains various software tools for RaspberryPi, Arduino and ESP8266/ESP32: - * A [version for ESP8266](tools/esp8266/) that includes an web interface ![](../../actions/workflows/compile_esp8266.yml/badge.svg) - * A [version for Arduino Nano](tools/nano/NRF24_SendRcv/) - * An [alternative Version of the above](tools/NRF24_SendRcv/) - * A [different implementation](tools/HoyDtuSim/) - * An [implementation for Raspberry Pi](tools/rpi/) that polls an inverter and archives results as log files/stdout as well as posting them to an MQTT broker. +##### Most updated section +- [ESP8266](tools/esp8266/) that includes an web interface ![](../../actions/workflows/compile_esp8266.yml/badge.svg) -Contributors are always welcome! +##### will be updated as needed +- [Arduino Nano](tools/nano/NRF24_SendRcv/) +- [Raspberry Pi](tools/rpi/) +- [others](tools/nano/NRF24_SendRcv/) + +If errors occur or you have suggestions for ideas, please feel free to contact us [here](https://github.com/grindylow/ahoy/issues). + +**Contributors are always welcome!** diff --git a/tools/esp8266/README.md b/tools/esp8266/README.md index 8ea1ade0..0cece43f 100644 --- a/tools/esp8266/README.md +++ b/tools/esp8266/README.md @@ -1,56 +1,9 @@ ## OVERVIEW -This code is intended to run on a Wemos D1mini or similar. The code is based on 'Hubi's code, which can be found here: - -The NRF24L01+ radio module is connected to the standard SPI pins. Additional there are 3 pins, which can be set individual: CS, CE and IRQ -These pins can be changed from the /setup URL - - -## Compile - -This code can be compiled using Visual Studio Code and **PlatformIO** Addon. The settings were: - -- Board: Generic ESP8266 Module -- Flash-Size: 1MB (FS: none, OTA: 502kB) -- Install libraries (not included in the Arduino IDE 1.8.19): - - Time Arduino Time library (TimeLib.h) - - RF24 Optimized high speed nRF24L01+ driver class documentation - - PubSubClient A client library for MQTT messaging. By Nick O'Leary - - ArduinoJson Arduino Json library - -### Optional Configuration before compilation - -- number of supported inverters (set to 3 by default) `defines.h` -- DTU radio id `hmRadio.h` -- unformated list in webbrowser `/livedata` `config.h`, `LIVEDATA_VISUALIZED` - - -## Flash ESP with firmware - -1. flash the ESP with the compiled firmware using the UART pins or any preinstalled firmware with OTA capabilities -2. repower the ESP -3. the ESP will start as access point (AP) if there is no network config stored in its eeprom -4. connect to the AP, you will be forwarded to the setup page -5. configure your WiFi settings, save, repower -6. check your router or serial console for the IP address of the module. You can try ping the configured device name as well. - - -## Usage - -Connect the ESP to power and to your serial console (optional). The webinterface has the following abilities: - -- OTA Update (over the air update) -- Configuration (Wifi, inverter(s), Pinout, MQTT) -- visual display of the connected inverters / modules -- some statistics about communication (debug) - -The serial console will print the converted values which were read out of the inverter(s) - - -## Compatiblity +This page describes how the module of a Wemos D1 mini and ESP8266 is wired to the radio module, flashed and how the further steps are to communicate with the WR HM series. +#### Compatiblity For now the following inverters should work out of the box: - - HM350 - HM400 - HM600 @@ -59,13 +12,95 @@ For now the following inverters should work out of the box: - HM1200 - HM1500 -## USED LIBRARIES +The NRF24L01+ radio module is connected to the standard SPI pins. +Additional there are 3 pins, which can be set individual: CS, CE and IRQ +These pins can be changed in the http:///setup URL or with a click on the Setup link. -- `ESP8266WiFi` 1.0 -- `DNSServer` 1.1.0 -- `Ticker` 1.0 -- `ESP8266HTTPUpdateServer` 1.0 -- `Time` 1.6.1 -- `RF24` 1.4.5 -- `PubSubClient` 2.8 -- `ArduinoJson` 6.19.4 \ No newline at end of file +## ESP8266 electr. associate + + +## Compile + +This code can be compiled using Visual Studio Code and **PlatformIO** Addon. The settings were: + +- Board: Generic ESP8266 Module +- Flash-Size: 1MB (FS: none, OTA: 502kB) +- Install libraries (not included in the Arduino IDE 1.8.19): + - `Time` 1.6.1 + - `RF24` 1.4.5 + - `PubSubClient` 2.8 + - `ArduinoJson` 6.19.4 + +### Optional Configuration before compilation + +- number of supported inverters (set to 3 by default) `config.h` +- DTU radio id `hmRadio.h` (default = 1234567801 +- unformated list in webbrowser `/livedata` `config.h`, `LIVEDATA_VISUALIZED` + + +## Flash ESP with firmware + +#### nodemcu-pyflasher (easy way) +1. download the flash-tool [nodemcu-pyflasher](https://github.com/marcelstoer/nodemcu-pyflasher) +2. download latest release bin-file from [ahoy_](https://github.com/grindylow/ahoy/releases) +3. connect the target device with your pc. +4. Set the correct serial port and select the correct *.bin file +5. click now on "Flash NodeMCU" + +1. flash the ESP with the compiled firmware using the UART pins or any preinstalled firmware with OTA capabilities +2. repower the ESP +3. the ESP will start as access point (AP) if there is no network config stored in its eeprom +4. connect to the AP, you will be forwarded to the setup page + +X. configure your WiFi settings, save, repower +Y. check your router or serial console for the IP address of the module. You can try ping the configured device name as well. + +! ATTENTION: If you update from a very low version to the newest, please make sure to wipe all flash data! + +## pages +| page | output | +| ---- | ------ | +| /uptime | 0 Days, 01:37:34; now: 2022-08-21 11:13:53 | +| /reboot | reboot dtu device | +| /erase | | +| /factory | | +| /setup | | +| /save | open the setup site | +| /cmdstat | show stat from the home site | +| /visualization | | +| /livedata | | +| /json | json output from the livedata | +| /api | | + +## Usage + +The webinterface has the following abilities: +- OTA Update (over the air update) +- Configuration (Wifi, inverter(s), NTP Server, Pinout, MQTT, Amplifier Power Level, Debug) +- visual display of the connected inverters / modules +- some statistics about communication (debug) + +The serial console will print the converted values which were read out of the inverter(s) + +### MQTT command to set the DTU without webinterface + [Read here](https://github.com/grindylow/ahoy/blob/development02/tools/esp8266/User_Manual.md) + + ## Todo's [See this post](https://github.com/grindylow/ahoy/issues/142) + +- [ ] Wechsel zu AsyncWebServer und ElegantOTA für Stabilität +- [x] klarer Scheduler / Task manager, der ggf. den Receive Task priorisieren kann +- [x] Device Info Kommandos (Firmware Version, etc.) über das Dashboard anzeigen [Device Information ( `0x15` `REQ_ARW_DAT_ALL` ) SubCmd Kommandos #145](https://github.com/grindylow/ahoy/issues/145) +- [ ] AlarmData & AlarmUpdate Parsen und auf eigener Seite darstellen + +------------------ SWIM LANE --------------------------- + +- [ ] Device Control Kommandos aus dem Setup ermöglichen (TurnOn, TurnOff, Restart, ActivePower Limit, ReactivePower Limit, SetPowerFactor, etc.) +- [ ] Settings exportieren / importieren (API/UI) +- [ ] Settings in settings.ini speichern (LittleFS statt EEPROM) [Settings in settings.ini speichern (LittleFS statt EEPROM) #164](https://github.com/grindylow/ahoy/issues/164) +- [ ] Homepage aufräumen nur ein Status (aktuell drei AJAX Calls /uptime, /time, /cmdstat) +- [ ] app.cpp aufräumen und in hmRadio / hmProtokollGen3 auslagern +- [ ] MI Wechselrichter unterstützen (miSystem, miInverter, miDefines, miProtokollGen2 etc.) +- [ ] nRF24 Interrupt Handling sinnvoll oder warum macht die nRF24 Bibliothek ständig `0x07` Statusabfragen [NRF24 polling trotz aktiviertem IRQ #83](https://github.com/grindylow/ahoy/issues/83) +- [ ] Debug Level im Setup änderbar -auch Livedata Visualisierung abschalten ? +- [ ] MQTT Discovery (HomeAssistant) im Setup optional machen +- [x] MQTT Subscribe nur beim Reconnect [Das subscribe in der Reconnect Procedure sollte doch nur nach einem conect ausgeführt werden und nicht bei jedem Duchlauf #139](https://github.com/grindylow/ahoy/issues/139) diff --git a/tools/esp8266/app.cpp b/tools/esp8266/app.cpp index c33bd7f4..2bdb29ec 100644 --- a/tools/esp8266/app.cpp +++ b/tools/esp8266/app.cpp @@ -120,28 +120,28 @@ void app::loop(void) { } if(NULL != iv && p->packet[0] == (TX_REQ_DEVCONTROL + 0x80)) { // response from dev control command DPRINTLN(DBG_DEBUG, F("Response from devcontrol request received")); - iv->devControlRequest = false; - switch (p->packet[12]){ + iv->devControlRequest = false; + switch (p->packet[12]) { case ActivePowerContr: - if (iv->devControlCmd >= ActivePowerContr && iv->devControlCmd <= PFSet){ // ok inverter accepted the set point copy it to dtu eeprom - if ((iv->powerLimit[1] & 0xff00) >0){ // User want to have it persistent - mEep->write(ADDR_INV_PWR_LIM + iv->id * 2,iv->powerLimit[0]); - mEep->write(ADDR_INV_PWR_LIM_CON + iv->id * 2,iv->powerLimit[1]); + if (iv->devControlCmd >= ActivePowerContr && iv->devControlCmd <= PFSet) { // ok inverter accepted the set point copy it to dtu eeprom + if ((iv->powerLimit[1] & 0xff00) > 0) { // User want to have it persistent + mEep->write(ADDR_INV_PWR_LIM + iv->id * 2, iv->powerLimit[0]); + mEep->write(ADDR_INV_PWR_LIM_CON + iv->id * 2, iv->powerLimit[1]); updateCrc(); mEep->commit(); - DPRINTLN(DBG_INFO, F("Inverter ") + String(iv->id) + F(" has accepted power limit set point ") + String(iv->powerLimit[0]) + F(" with PowerLimitControl ") + String(iv->powerLimit[1]) + F(", written to dtu eeprom")); - } else { + DPRINTLN(DBG_INFO, F("Inverter ") + String(iv->id) + F(" has accepted power limit set point ") + String(iv->powerLimit[0]) + F(" with PowerLimitControl ") + String(iv->powerLimit[1]) + F(", written to dtu eeprom")); + } else DPRINTLN(DBG_INFO, F("Inverter ") + String(iv->id) + F(" has accepted power limit set point ") + String(iv->powerLimit[0]) + F(" with PowerLimitControl ") + String(iv->powerLimit[1])); - } iv->devControlCmd = Init; } break; + default: - if (iv->devControlCmd == ActivePowerContr){ + if (iv->devControlCmd == ActivePowerContr) { //case inverter did not accept the sent limit; set back to last stored limit mEep->read(ADDR_INV_PWR_LIM + iv->id * 2, (uint16_t *)&(iv->powerLimit[0])); mEep->read(ADDR_INV_PWR_LIM_CON + iv->id * 2, (uint16_t *)&(iv->powerLimit[1])); - DPRINTLN(DBG_INFO, F("Inverter has not accepted power limit set point")); + DPRINTLN(DBG_INFO, F("Inverter has not accepted power limit set point")); } iv->devControlCmd = Init; break; @@ -166,20 +166,7 @@ void app::loop(void) { if((++mMqttTicker >= mMqttInterval) && (mMqttInterval != 0xffff) && mMqttActive) { mMqttTicker = 0; mMqtt.isConnected(true); // really needed? See comment from HorstG-57 #176 - char topic[30], val[10]; - for(uint8_t id = 0; id < mSys->getNumInverters(); id++) { - Inverter<> *iv = mSys->getInverterByPos(id); - if(NULL != iv) { - if(iv->isAvailable(mTimestamp)) { - for(uint8_t i = 0; i < iv->listLen; i++) { - snprintf(topic, 30, "%s/ch%d/%s", iv->name, iv->assign[i].ch, fields[iv->assign[i].fieldId]); - snprintf(val, 10, "%.3f", iv->getValue(i)); - mMqtt.sendMsg(topic, val); - yield(); - } - } - } - } + char val[10]; snprintf(val, 10, "%ld", millis()/1000); #ifndef __MQTT_NO_DISCOVERCONFIG__ @@ -245,6 +232,10 @@ void app::loop(void) { if(!mPayload[iv->id].complete) { mRxFailed++; + iv->setQueuedCmdFinished(); // command failed + if(mConfig.serialDebug) { + DPRINTLN(DBG_INFO, F("enqueued cmd failed/timeout")); + } if(mConfig.serialDebug) { DPRINT(DBG_INFO, F("Inverter #") + String(iv->id) + " "); DPRINTLN(DBG_INFO, F("no Payload received! (retransmits: ") + String(mPayload[iv->id].retransmits) + ")"); @@ -255,12 +246,12 @@ void app::loop(void) { yield(); if(mConfig.serialDebug) - DPRINTLN(DBG_DEBUG, F("app:loop WiFi WiFi.status ") + String(WiFi.status()) ); + DPRINTLN(DBG_DEBUG, F("app:loop WiFi WiFi.status ") + String(WiFi.status())); DPRINTLN(DBG_INFO, F("Requesting Inverter SN ") + String(iv->serial.u64, HEX)); - if(iv->devControlRequest && iv->powerLimit[0] > 0){ // prevent to "switch off" + if(iv->devControlRequest && (iv->powerLimit[0] > 0) && (NoPowerLimit != iv->powerLimit[1])) { // prevent to "switch off" if(mConfig.serialDebug) DPRINTLN(DBG_INFO, F("Devcontrol request ") + String(iv->devControlCmd) + F(" power limit ") + String(iv->powerLimit[0])); - mSys->Radio.sendControlPacket(iv->radioId.u64,iv->devControlCmd ,iv->powerLimit); + mSys->Radio.sendControlPacket(iv->radioId.u64, iv->devControlCmd ,iv->powerLimit); iv->enqueCommand(SystemConfigPara); } else { mSys->Radio.sendTimePacket(iv->radioId.u64,iv->getQueuedCmd(), mPayload[iv->id].ts,iv->alarmMesIndex); @@ -352,8 +343,11 @@ void app::processPayload(bool retransmit) { else { mPayload[iv->id].complete = true; iv->ts = mPayload[iv->id].ts; - uint8_t payload[128] = {0}; + uint8_t payload[128]; uint8_t offs = 0; + + memset(payload, 0, 128); + for(uint8_t i = 0; i < (mPayload[iv->id].maxPackId); i ++) { memcpy(&payload[offs], mPayload[iv->id].data[i], (mPayload[iv->id].len[i])); offs += (mPayload[iv->id].len[i]); @@ -372,11 +366,34 @@ void app::processPayload(bool retransmit) { yield(); } iv->doCalculations(); // cmd value decides which parser is used to decode payload + + iv->setQueuedCmdFinished(); + + // MQTT send out + if(mMqttActive) { + char topic[30], val[10]; + for (uint8_t id = 0; id < mSys->getNumInverters(); id++) + { + Inverter<> *iv = mSys->getInverterByPos(id); + if (NULL != iv) + { + if (iv->isAvailable(mTimestamp)) + { + for (uint8_t i = 0; i < iv->listLen; i++) + { + snprintf(topic, 30, "%s/ch%d/%s", iv->name, iv->assign[i].ch, fields[iv->assign[i].fieldId]); + snprintf(val, 10, "%.3f", iv->getValue(i)); + mMqtt.sendMsg(topic, val); + yield(); + } + } + } + } + } #ifdef __MQTT_AFTER_RX__ doMQTT = true; #endif - iv->setQueuedCmdFinished(); } } yield(); @@ -489,9 +506,10 @@ String app::getStatistics(void) { Inverter<> *iv; for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i++) { iv = mSys->getInverterByPos(i); + content += F("Inverter #") + String(i) + F(": "); if(NULL != iv) { bool avail = true; - content += F("Inverter '") + String(iv->name) + F(" (FW-Version: ") + String(iv->fwVersion) +F(")") + F("' is "); + content += String(iv->name) + F(" (v") + String(iv->fwVersion) +F(")") + F(" is "); if(!iv->isAvailable(mTimestamp)) { content += F("not "); avail = false; @@ -506,10 +524,9 @@ String app::getStatistics(void) { content += F("-> last successful transmission: ") + getDateTimeStr(iv->getLastTs()) + "\n"; } } - else { - content += F("Inverter ") + String(i) + F(" not (correctly) configured\n"); + else + content += F("n/a\n"); } - } if(!mSys->Radio.isChipConnected()) content += F("WARNING! your NRF24 module can't be reached, check the wiring and pinout (setup)\n"); @@ -529,119 +546,6 @@ String app::getStatistics(void) { } - -//----------------------------------------------------------------------------- -String app::getLiveData(void) -{ - String modHtml; - for (uint8_t id = 0; id < mSys->getNumInverters(); id++) - { - Inverter<> *iv = mSys->getInverterByPos(id); - if (NULL != iv) - { -#ifdef LIVEDATA_VISUALIZED - uint8_t modNum, pos; - switch (iv->type) - { - default: - case INV_TYPE_1CH: - modNum = 1; - break; - case INV_TYPE_2CH: - modNum = 2; - break; - case INV_TYPE_4CH: - modNum = 4; - break; - } - - modHtml += F("
" - "
") + - String(iv->name) + F(" Limit ") + String(iv->actPowerLimit); - if (true) - { // live Power Limit from inverter is always in % - modHtml += F(" %"); - } - else - { - modHtml += F(" W"); - } - uint8_t list[] = {FLD_UAC, FLD_IAC, FLD_PAC, FLD_F, FLD_PCT, FLD_T, FLD_YT, FLD_YD, FLD_PDC, FLD_EFF, FLD_PRA, FLD_ALARM_MES_ID}; - - for (uint8_t fld = 0; fld < 12; fld++) - { - pos = (iv->getPosByChFld(CH0, list[fld])); - if (0xff != pos) - { - modHtml += F("
"); - modHtml += F("") + String(iv->getValue(pos)); - modHtml += F("") + String(iv->getUnit(pos)) + F(""); - modHtml += F("") + String(iv->getFieldName(pos)) + F(""); - modHtml += F("
"); - } - } - modHtml += "
"; - - for (uint8_t ch = 1; ch <= modNum; ch++) - { - modHtml += F("
"); - if (iv->chName[ch - 1][0] == 0) - modHtml += F("CHANNEL ") + String(ch); - else - modHtml += String(iv->chName[ch - 1]); - modHtml += F(""); - for (uint8_t j = 0; j < 6; j++) - { - switch (j) - { - default: - pos = (iv->getPosByChFld(ch, FLD_UDC)); - break; - case 1: - pos = (iv->getPosByChFld(ch, FLD_IDC)); - break; - case 2: - pos = (iv->getPosByChFld(ch, FLD_PDC)); - break; - case 3: - pos = (iv->getPosByChFld(ch, FLD_YD)); - break; - case 4: - pos = (iv->getPosByChFld(ch, FLD_YT)); - break; - case 5: - pos = (iv->getPosByChFld(ch, FLD_IRR)); - break; - } - if (0xff != pos) - { - modHtml += F("") + String(iv->getValue(pos)); - modHtml += F("") + String(iv->getUnit(pos)) + F(""); - modHtml += F("") + String(iv->getFieldName(pos)) + F(""); - } - } - modHtml += "
"; - yield(); - } - modHtml += F("
Last received data requested at: ") + getDateTimeStr(iv->ts) + F("
"); - modHtml += F("
"); -#else - // dump all data to web frontend - modHtml = F("
");
-            char topic[30], val[10];
-            for (uint8_t i = 0; i < iv->listLen; i++)
-            {
-                snprintf(topic, 30, "%s/ch%d/%s", iv->name, iv->assign[i].ch, iv->getFieldName(i));
-                snprintf(val, 10, "%.3f %s", iv->getValue(i), iv->getUnit(i));
-                modHtml += String(topic) + ": " + String(val) + "\n";
-            }
-            modHtml += F("
"); -#endif - } - } - return modHtml; -} - //----------------------------------------------------------------------------- String app::getJson(void) { DPRINTLN(DBG_VERBOSE, F("app::showJson")); @@ -858,11 +762,16 @@ void app::loadEEpconfig(void) { // it is "doppelt-gemoppelt" because the inverter shall remember the setting if the dtu makes a power cycle / reboot if (iv->powerLimit[0] != 0xffff) { iv->devControlCmd = ActivePowerContr; // set active power limit - if (iv->powerLimit[1] & 0x0001){ - DPRINTLN(DBG_INFO, F("add inverter: ") + String(name) + ", SN: " + String(invSerial, HEX) + ", Power Limit: " + String(iv->powerLimit[0]) + " in %"); - } else { - DPRINTLN(DBG_INFO, F("add inverter: ") + String(name) + ", SN: " + String(invSerial, HEX) + ", Power Limit: " + String(iv->powerLimit[0]) + " in Watt"); + DPRINT(DBG_INFO, F("add inverter: ") + String(name) + ", SN: " + String(invSerial, HEX)); + if(iv->powerLimit[1] != NoPowerLimit) { + DBGPRINT(F(", Power Limit: ") + String(iv->powerLimit[0])); + if ((iv->powerLimit[1] & 0x0001) == 0x0001) + DBGPRINTLN(F(" in %")); + else + DBGPRINTLN(F(" in Watt")); } + else + DBGPRINTLN(F(" ")); } for(uint8_t j = 0; j < 4; j++) { mEep->read(ADDR_INV_CH_NAME + (i * 4 * MAX_NAME_LENGTH) + j * MAX_NAME_LENGTH, iv->chName[j], MAX_NAME_LENGTH); @@ -885,8 +794,7 @@ void app::saveValues(void) { mEep->write(ADDR_CFG, (uint8_t*)&mConfig, CFG_LEN); Inverter<> *iv; for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i ++) { - iv = mSys->getInverterByPos(i); - if(NULL != iv) { + iv = mSys->getInverterByPos(i, false); mEep->write(ADDR_INV_ADDR + (i * 8), iv->serial.u64); mEep->write(ADDR_INV_PWR_LIM + i * 2, iv->powerLimit[0]); mEep->write(ADDR_INV_PWR_LIM_CON + i * 2, iv->powerLimit[1]); @@ -897,7 +805,6 @@ void app::saveValues(void) { mEep->write(ADDR_INV_CH_NAME + (i * 4 * MAX_NAME_LENGTH) + j * MAX_NAME_LENGTH, iv->chName[j], MAX_NAME_LENGTH); } } - } updateCrc(); mEep->commit(); diff --git a/tools/esp8266/app.h b/tools/esp8266/app.h index 308f222c..9e66fcd3 100644 --- a/tools/esp8266/app.h +++ b/tools/esp8266/app.h @@ -71,7 +71,6 @@ class app { void saveValues(void); void resetPayload(Inverter<>* iv); String getStatistics(void); - String getLiveData(void); String getJson(void); bool getWifiApActive(void); @@ -80,9 +79,10 @@ class app { } uint64_t Serial2u64(const char *val) { - char tmp[3] = {0}; + char tmp[3]; uint64_t ret = 0ULL; uint64_t u64; + memset(tmp, 0, 3); for(uint8_t i = 0; i < 6; i++) { tmp[0] = val[i*2]; tmp[1] = val[i*2 + 1]; @@ -95,7 +95,7 @@ class app { } String getDateTimeStr(time_t t) { - char str[20] = {0}; + char str[20]; if(0 == t) sprintf(str, "n/a"); else @@ -113,9 +113,11 @@ class app { void eraseSettings(bool all = false) { //DPRINTLN(DBG_VERBOSE, F("main.h:eraseSettings")); - uint8_t buf[64] = {0}; + uint8_t buf[64]; uint16_t addr = (all) ? ADDR_START : ADDR_START_SETTINGS; uint16_t end; + + memset(buf, 0xff, 64); do { end = addr + 64; if(end > (ADDR_SETTINGS_CRC + 2)) diff --git a/tools/esp8266/defines.h b/tools/esp8266/defines.h index 3fbf8489..33732ff5 100644 --- a/tools/esp8266/defines.h +++ b/tools/esp8266/defines.h @@ -58,6 +58,7 @@ typedef enum { } DevControlCmdType; typedef enum { // ToDo: to be verified by field tests + NoPowerLimit = 0xffff, // ahoy internal value, no hoymiles value! AbsolutNonPersistent = 0UL, // 0x0000 RelativNonPersistent = 1UL, // 0x0001 AbsolutPersistent = 256UL, // 0x0100 @@ -104,6 +105,7 @@ typedef enum { // ToDo: to be verified by field tests #define MQTT_PORT_LEN 2 // uint16_t #define MQTT_DISCOVERY_PREFIX "homeassistant" #define MQTT_MAX_PACKET_SIZE 384 +#define MQTT_RECONNECT_DELAY 5000 #define SER_ENABLE_LEN 1 // uint8_t #define SER_DEBUG_LEN 1 // uint8_t diff --git a/tools/esp8266/hmDefines.h b/tools/esp8266/hmDefines.h index 36139bb2..6330db98 100644 --- a/tools/esp8266/hmDefines.h +++ b/tools/esp8266/hmDefines.h @@ -23,9 +23,13 @@ const char* const units[] = {"V", "A", "W", "Wh", "kWh", "Hz", "°C", "%","VAr", // field types enum {FLD_UDC = 0, FLD_IDC, FLD_PDC, FLD_YD, FLD_YW, FLD_YT, - FLD_UAC, FLD_IAC, FLD_PAC, FLD_F, FLD_T, FLD_PCT, FLD_EFF, FLD_IRR, FLD_PRA,FLD_ALARM_MES_ID,FLD_FW_VERSION,FLD_FW_BUILD_YEAR,FLD_FW_BUILD_MONTH_DAY,FLD_HW_ID,FLD_ACT_PWR_LIMIT}; + FLD_UAC, FLD_IAC, FLD_PAC, FLD_F, FLD_T, FLD_PCT, FLD_EFF, + FLD_IRR, FLD_PRA,FLD_ALARM_MES_ID,FLD_FW_VERSION,FLD_FW_BUILD_YEAR, + FLD_FW_BUILD_MONTH_DAY,FLD_HW_ID,FLD_ACT_PWR_LIMIT,FLD_LAST_ALARM_CODE}; + const char* const fields[] = {"U_DC", "I_DC", "P_DC", "YieldDay", "YieldWeek", "YieldTotal", - "U_AC", "I_AC", "P_AC", "Freq", "Temp", "Pct", "Efficiency", "Irradiation","P_ACr","ALARM_MES_ID","FWVersion","FWBuildYear","FWBuildMonthDay","HWPartId","PowerLimit"}; + "U_AC", "I_AC", "P_AC", "Freq", "Temp", "Pct", "Efficiency", "Irradiation","P_ACr", + "ALARM_MES_ID","FWVersion","FWBuildYear","FWBuildMonthDay","HWPartId","PowerLimit","LastAlarmCode"}; // 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}; @@ -97,6 +101,10 @@ const byteAssign_t SystemConfigParaAssignment[] = { }; #define HMSYSTEM_LIST_LEN (sizeof(SystemConfigParaAssignment) / sizeof(byteAssign_t)) +const byteAssign_t AlarmDataAssignment[] = { + { FLD_LAST_ALARM_CODE, UNIT_NONE, CH0, 0, 2, 1 } +}; +#define HMALARMDATA_LIST_LEN (sizeof(AlarmDataAssignment) / sizeof(byteAssign_t)) diff --git a/tools/esp8266/hmInverter.h b/tools/esp8266/hmInverter.h index 774fd809..fd2eafee 100644 --- a/tools/esp8266/hmInverter.h +++ b/tools/esp8266/hmInverter.h @@ -115,17 +115,20 @@ class Inverter { RECORDTYPE *record; // pointer for values uint16_t chMaxPwr[4]; // maximum power of the modules (Wp) char chName[4][MAX_NAME_LENGTH]; // human readable name for channel + String lastAlarmMsg; bool initialized; // needed to check if the inverter was correctly added (ESP32 specific - union types are never null) Inverter() { ts = 0; powerLimit[0] = 0xffff; // 65535 W Limit -> unlimited - powerLimit[1] = 0x0000; // + powerLimit[1] = NoPowerLimit; // actPowerLimit = 0xffff; // init feedback from inverter to -1 devControlRequest = false; - devControlCmd = 0xff; + devControlCmd = InitDataState; initialized = false; fwVersion = 0; + lastAlarmMsg = "nothing"; + alarmMesIndex = 0; } ~Inverter() { @@ -141,7 +144,8 @@ class Inverter { void setQueuedCmdFinished(){ if (!_commandQueue.empty()){ - _commandQueue.pop(); // Will destroy CommandAbstract Class Object (?) + // Will destroy CommandAbstract Class Object (?) + _commandQueue.pop(); } } @@ -150,7 +154,14 @@ class Inverter { if (_commandQueue.empty()){ // Fill with default commands enqueCommand(RealTimeRunData_Debug); - //enqueCommand(SystemConfigPara); + if (fwVersion == 0) + { // info needed maybe after "one night" (=> DC>0 to DC=0 and to DC>0) or reboot + enqueCommand(InverterDevInform_All); + } + if (actPowerLimit == 0xffff) + { // info needed maybe after "one nigth" (=> DC>0 to DC=0 and to DC>0) or reboot + enqueCommand(SystemConfigPara); + } } return _commandQueue.front().get()->getCmd(); } @@ -164,8 +175,6 @@ class Inverter { memset(name, 0, MAX_NAME_LENGTH); memset(chName, 0, MAX_NAME_LENGTH * 4); memset(record, 0, sizeof(RECORDTYPE) * listLen); - enqueCommand(InverterDevInform_All); - enqueCommand(SystemConfigPara); initialized = true; } @@ -206,12 +215,25 @@ class Inverter { val <<= 8; val |= buf[ptr]; } while(++ptr != end); - record[pos] = (RECORDTYPE)(val) / (RECORDTYPE)(div); + if ((RECORDTYPE)(div) > 1){ + record[pos] = (RECORDTYPE)(val) / (RECORDTYPE)(div); + } + else { + record[pos] = (RECORDTYPE)(val); + } + } if (cmd == RealTimeRunData_Debug) { // get last alarm message index and save it in the inverter object if (getPosByChFld(0, FLD_ALARM_MES_ID) == pos){ - alarmMesIndex = record[pos]; + if (alarmMesIndex < record[pos]){ + alarmMesIndex = record[pos]; + //enqueCommand(AlarmUpdate); // What is the function of AlarmUpdate? + enqueCommand(AlarmData); + } + else { + alarmMesIndex = record[pos]; // no change + } } } if (cmd == InverterDevInform_All) { @@ -228,6 +250,11 @@ class Inverter { DPRINT(DBG_DEBUG, F("Inverter actual power limit: ") + String(actPowerLimit)); } } + if (cmd == AlarmData){ + if (getPosByChFld(0, FLD_LAST_ALARM_CODE) == pos){ + lastAlarmMsg = getAlarmStr(record[pos]); + } + } } RECORDTYPE getValue(uint8_t pos) { @@ -263,52 +290,273 @@ class Inverter { return false; } - uint32_t getLastTs(void) { + uint32_t getLastTs(void) + { DPRINTLN(DBG_VERBOSE, F("hmInverter.h:getLastTs")); return ts; } - void getAssignment() { + void getAssignment() + { DPRINTLN(DBG_DEBUG, F("hmInverter.h:getAssignment")); - uint8_t cmd = getQueuedCmd(); - switch (cmd) + // Default assignment; + if (INV_TYPE_1CH == type) { - case RealTimeRunData_Debug: - if (INV_TYPE_1CH == type) - { - listLen = (uint8_t)(HM1CH_LIST_LEN); - assign = (byteAssign_t *)hm1chAssignment; - channels = 1; - } - else if (INV_TYPE_2CH == type) - { - listLen = (uint8_t)(HM2CH_LIST_LEN); - assign = (byteAssign_t *)hm2chAssignment; - channels = 2; - } - else if (INV_TYPE_4CH == type) - { - listLen = (uint8_t)(HM4CH_LIST_LEN); - assign = (byteAssign_t *)hm4chAssignment; - channels = 4; - } - else - { - listLen = 0; - channels = 0; - assign = NULL; - } + listLen = (uint8_t)(HM1CH_LIST_LEN); + assign = (byteAssign_t *)hm1chAssignment; + channels = 1; + } + else if (INV_TYPE_2CH == type) + { + listLen = (uint8_t)(HM2CH_LIST_LEN); + assign = (byteAssign_t *)hm2chAssignment; + channels = 2; + } + else if (INV_TYPE_4CH == type) + { + listLen = (uint8_t)(HM4CH_LIST_LEN); + assign = (byteAssign_t *)hm4chAssignment; + channels = 4; + } + else + { + listLen = 0; + channels = 0; + assign = NULL; + } + + switch (getQueuedCmd()) { + case RealTimeRunData_Debug: + // Do nothing will use default + break; + case InverterDevInform_All: + listLen = (uint8_t)(HMINFO_LIST_LEN); + assign = (byteAssign_t *)InfoAssignment; + break; + case SystemConfigPara: + listLen = (uint8_t)(HMSYSTEM_LIST_LEN); + assign = (byteAssign_t *)SystemConfigParaAssignment; + break; + case AlarmData: + listLen = (uint8_t)(HMALARMDATA_LIST_LEN); + assign = (byteAssign_t *)AlarmDataAssignment; + break; + default: + DPRINTLN(DBG_INFO, "Parser not implemented"); + break; + } + } + String getAlarmStr(u_int16_t alarmCode) + { + switch (alarmCode) + { + case 1: + return String(F("Inverter start")); break; - case InverterDevInform_All: - listLen = (uint8_t)(HMINFO_LIST_LEN); - assign = (byteAssign_t *)InfoAssignment; + case 2: + return String(F("DTU command failed")); break; - case SystemConfigPara: - listLen = (uint8_t)(HMSYSTEM_LIST_LEN); - assign = (byteAssign_t *)SystemConfigParaAssignment; + case 121: + return String(F("Over temperature protection")); + break; + case 125: + return String(F("Grid configuration parameter error")); + break; + case 126: + return String(F("Software error code 126")); + break; + case 127: + return String(F("Firmware error")); + break; + case 128: + return String(F("Software error code 128")); + break; + case 129: + return String(F("Software error code 129")); + break; + case 130: + return String(F("Offline")); + break; + case 141: + return String(F("Grid overvoltage")); + break; + case 142: + return String(F("Average grid overvoltage")); + break; + case 143: + return String(F("Grid undervoltage")); + break; + case 144: + return String(F("Grid overfrequency")); + break; + case 145: + return String(F("Grid underfrequency")); + break; + case 146: + return String(F("Rapid grid frequency change")); + break; + case 147: + return String(F("Power grid outage")); + break; + case 148: + return String(F("Grid disconnection")); + break; + case 149: + return String(F("Island detected")); + break; + case 205: + return String(F("Input port 1 & 2 overvoltage")); + break; + case 206: + return String(F("Input port 3 & 4 overvoltage")); + break; + case 207: + return String(F("Input port 1 & 2 undervoltage")); + break; + case 208: + return String(F("Input port 3 & 4 undervoltage")); + break; + case 209: + return String(F("Port 1 no input")); + break; + case 210: + return String(F("Port 2 no input")); + break; + case 211: + return String(F("Port 3 no input")); + break; + case 212: + return String(F("Port 4 no input")); + break; + case 213: + return String(F("PV-1 & PV-2 abnormal wiring")); + break; + case 214: + return String(F("PV-3 & PV-4 abnormal wiring")); + break; + case 215: + return String(F("PV-1 Input overvoltage")); + break; + case 216: + return String(F("PV-1 Input undervoltage")); + break; + case 217: + return String(F("PV-2 Input overvoltage")); + break; + case 218: + return String(F("PV-2 Input undervoltage")); + break; + case 219: + return String(F("PV-3 Input overvoltage")); + break; + case 220: + return String(F("PV-3 Input undervoltage")); + break; + case 221: + return String(F("PV-4 Input overvoltage")); + break; + case 222: + return String(F("PV-4 Input undervoltage")); + break; + case 301: + return String(F("Hardware error code 301")); + break; + case 302: + return String(F("Hardware error code 302")); + break; + case 303: + return String(F("Hardware error code 303")); + break; + case 304: + return String(F("Hardware error code 304")); + break; + case 305: + return String(F("Hardware error code 305")); + break; + case 306: + return String(F("Hardware error code 306")); + break; + case 307: + return String(F("Hardware error code 307")); + break; + case 308: + return String(F("Hardware error code 308")); + break; + case 309: + return String(F("Hardware error code 309")); + break; + case 310: + return String(F("Hardware error code 310")); + break; + case 311: + return String(F("Hardware error code 311")); + break; + case 312: + return String(F("Hardware error code 312")); + break; + case 313: + return String(F("Hardware error code 313")); + break; + case 314: + return String(F("Hardware error code 314")); + break; + case 5041: + return String(F("Error code-04 Port 1")); + break; + case 5042: + return String(F("Error code-04 Port 2")); + break; + case 5043: + return String(F("Error code-04 Port 3")); + break; + case 5044: + return String(F("Error code-04 Port 4")); + break; + case 5051: + return String(F("PV Input 1 Overvoltage/Undervoltage")); + break; + case 5052: + return String(F("PV Input 2 Overvoltage/Undervoltage")); + break; + case 5053: + return String(F("PV Input 3 Overvoltage/Undervoltage")); + break; + case 5054: + return String(F("PV Input 4 Overvoltage/Undervoltage")); + break; + case 5060: + return String(F("Abnormal bias")); + break; + case 5070: + return String(F("Over temperature protection")); + break; + case 5080: + return String(F("Grid Overvoltage/Undervoltage")); + break; + case 5090: + return String(F("Grid Overfrequency/Underfrequency")); + break; + case 5100: + return String(F("Island detected")); + break; + case 5120: + return String(F("EEPROM reading and writing error")); + break; + case 5150: + return String(F("10 min value grid overvoltage")); + break; + case 5200: + return String(F("Firmware error")); + break; + case 8310: + return String(F("Shut down")); + break; + case 9000: + return String(F("Microinverter is suspected of being stolen")); break; default: - DPRINTLN(DBG_INFO, "Parser not implemented"); + return String(F("Unknown")); + break; } } diff --git a/tools/esp8266/hmRadio.h b/tools/esp8266/hmRadio.h index 7decd2c2..de900218 100644 --- a/tools/esp8266/hmRadio.h +++ b/tools/esp8266/hmRadio.h @@ -192,10 +192,9 @@ class HmRadio { mTxBuf[10] = cmd; // cid mTxBuf[11] = 0x00; CP_U32_LittleEndian(&mTxBuf[12], ts); - if (cmd == RealTimeRunData_Debug || cmd == AlarmData || cmd == AlarmUpdate ){ + if (cmd == RealTimeRunData_Debug || cmd == AlarmData ){ mTxBuf[18] = (alarmMesId >> 8) & 0xff; mTxBuf[19] = (alarmMesId ) & 0xff; - //mTxBuf[19] = 0x05; // ToDo: Shall be the last received Alarm Index Number } else { mTxBuf[18] = 0x00; mTxBuf[19] = 0x00; diff --git a/tools/esp8266/html/h/setup_html.h b/tools/esp8266/html/h/setup_html.h index 4ffbe94a..990ab73a 100644 --- a/tools/esp8266/html/h/setup_html.h +++ b/tools/esp8266/html/h/setup_html.h @@ -1,5 +1,5 @@ #ifndef __SETUP_HTML_H__ #define __SETUP_HTML_H__ -const char setup_html[] PROGMEM = "Setup - {#DEVICE}

Setup

ERASE SETTINGS (not WiFi)
Device Host Name
WiFi

Enter the credentials to your prefered WiFi station. After rebooting the device tries to connect with this information.

Inverter{#INVERTERS}

General

NTP Server
MQTT
System Config

Pinout (Wemos)

{#PINOUT}

Radio (NRF24L01+)

Serial Console



"; -const uint32_t setup_html_len = 5023; +const char setup_html[] PROGMEM = "Setup - {#DEVICE}

Setup

ERASE SETTINGS (not WiFi)
Device Host Name
WiFi

Enter the credentials to your prefered WiFi station. After rebooting the device tries to connect with this information.

Inverter{#INVERTERS}

General

NTP Server
MQTT
System Config

Pinout (Wemos)

{#PINOUT}

Radio (NRF24L01+)

Serial Console



"; +const uint32_t setup_html_len = 5019; #endif /*__SETUP_HTML_H__*/ diff --git a/tools/esp8266/html/setup.html b/tools/esp8266/html/setup.html index 53404b4c..9d32fedb 100644 --- a/tools/esp8266/html/setup.html +++ b/tools/esp8266/html/setup.html @@ -128,7 +128,7 @@

Serial Console


- +
diff --git a/tools/esp8266/mqtt.h b/tools/esp8266/mqtt.h index 74193afa..28ca2c1c 100644 --- a/tools/esp8266/mqtt.h +++ b/tools/esp8266/mqtt.h @@ -85,7 +85,8 @@ class mqtt { #endif boolean resub = false; - if(!mClient->connected()) { + if(!mClient->connected() && (millis() - lastReconnect) > MQTT_RECONNECT_DELAY ) { + lastReconnect = millis(); if(strlen(mDevName) > 0) { // der Server und der Port müssen neu gesetzt werden, // da ein MQTT_CONNECTION_LOST -3 die Werte zerstört hat. @@ -95,14 +96,14 @@ class mqtt { resub = mClient->connect(mDevName, mCfg->user, mCfg->pwd); else resub = mClient->connect(mDevName); - } - // ein Subscribe ist nur nach einem connect notwendig - if(resub) { - char topic[MQTT_TOPIC_LEN + 13 ]; // "/devcontrol/#" --> + 6 byte - // ToDo: "/devcontrol/#" is hardcoded - snprintf(topic, MQTT_TOPIC_LEN + 13, "%s/devcontrol/#", mCfg->topic); - DPRINTLN(DBG_INFO, F("subscribe to ") + String(topic)); - mClient->subscribe(topic); // subscribe to mTopic + "/devcontrol/#" + // ein Subscribe ist nur nach einem connect notwendig + if(resub) { + char topic[MQTT_TOPIC_LEN + 13 ]; // "/devcontrol/#" --> + 6 byte + // ToDo: "/devcontrol/#" is hardcoded + snprintf(topic, MQTT_TOPIC_LEN + 13, "%s/devcontrol/#", mCfg->topic); + DPRINTLN(DBG_INFO, F("subscribe to ") + String(topic)); + mClient->subscribe(topic); // subscribe to mTopic + "/devcontrol/#" + } } } } @@ -113,6 +114,7 @@ class mqtt { bool mAddressSet; mqttConfig_t *mCfg; char mDevName[DEVNAME_LEN]; + unsigned long lastReconnect = 0; }; #endif /*__MQTT_H_*/ diff --git a/tools/esp8266/scripts/getVersion.py b/tools/esp8266/scripts/getVersion.py index 9907eb55..6ebe71b0 100644 --- a/tools/esp8266/scripts/getVersion.py +++ b/tools/esp8266/scripts/getVersion.py @@ -24,6 +24,12 @@ def readVersion(path, infile): src = path + ".pio/build/esp8266-release/firmware.bin" dst = path + ".pio/build/out/" + versionout os.rename(src, dst) + + versionout = version[:-1] + "_esp32_" + sha + ".bin" + src = path + ".pio/build/esp32-wroom32-release/firmware.bin" + dst = path + ".pio/build/out/" + versionout + os.rename(src, dst) + print("::set-output name=name::" + versionnumber[:-1] ) diff --git a/tools/esp8266/web.cpp b/tools/esp8266/web.cpp index 01ab9345..4ac2b004 100644 --- a/tools/esp8266/web.cpp +++ b/tools/esp8266/web.cpp @@ -17,6 +17,23 @@ #include "html/h/visualization_html.h" #include "html/h/update_html.h" + +const uint16_t pwrLimitOptionValues[] { + NoPowerLimit, + AbsolutNonPersistent, + AbsolutPersistent, + RelativNonPersistent, + RelativPersistent +}; + +const char* const pwrLimitOptions[] { + "no power limit", + "absolute in Watt non persistent", + "absolute in Watt persistent", + "relativ in percent non persistent", + "relativ in percent persistent" +}; + //----------------------------------------------------------------------------- web::web(app *main, sysConfig_t *sysCfg, config_t *config, char version[]) { mMain = main; @@ -213,13 +230,14 @@ void web::showSave(AsyncWebServerRequest *request) { iv->powerLimit[1] = actPwrLimitControl; iv->devControlCmd = ActivePowerContr; iv->devControlRequest = true; - if (iv->powerLimit[1] & 0x0001) + if ((iv->powerLimit[1] & 0x0001) == 0x0001) DPRINTLN(DBG_INFO, F("Power limit for inverter ") + String(iv->id) + F(" set to ") + String(iv->powerLimit[0]) + F("%") ); - else + else { DPRINTLN(DBG_INFO, F("Power limit for inverter ") + String(iv->id) + F(" set to ") + String(iv->powerLimit[0]) + F("W") ); DPRINTLN(DBG_INFO, F("Power Limit Control Setting ") + String(iv->powerLimit[1])); } - if (actPwrLimit == 0xffff){ // set to 100% + } + if (actPwrLimit == 0xffff) { // set to 100% iv->powerLimit[0] = 100; iv->powerLimit[1] = RelativPersistent; iv->devControlCmd = ActivePowerContr; @@ -314,7 +332,84 @@ void web::showVisualization(AsyncWebServerRequest *request) { //----------------------------------------------------------------------------- void web::showLiveData(AsyncWebServerRequest *request) { DPRINTLN(DBG_VERBOSE, F("web::showLiveData")); - request->send(200, F("text/html"), mMain->getLiveData()); + + String modHtml; + for (uint8_t id = 0; id < mMain->mSys->getNumInverters(); id++) { + Inverter<> *iv = mMain->mSys->getInverterByPos(id); + if (NULL != iv) { +#ifdef LIVEDATA_VISUALIZED + uint8_t modNum, pos; + switch (iv->type) { + default: + case INV_TYPE_1CH: modNum = 1; break; + case INV_TYPE_2CH: modNum = 2; break; + case INV_TYPE_4CH: modNum = 4; break; + } + + modHtml += F("
" + "
") + + String(iv->name) + F(" Limit ") + + String(iv->actPowerLimit) + F("%"); + if(NoPowerLimit == iv->powerLimit[1]) + modHtml += F(" (not controlled)"); + modHtml += F(" | last Alarm: ") + iv->lastAlarmMsg + F(""); + + uint8_t list[] = {FLD_UAC, FLD_IAC, FLD_PAC, FLD_F, FLD_PCT, FLD_T, FLD_YT, FLD_YD, FLD_PDC, FLD_EFF, FLD_PRA, FLD_ALARM_MES_ID}; + + for (uint8_t fld = 0; fld < 11; fld++) { + pos = (iv->getPosByChFld(CH0, list[fld])); + if (0xff != pos) { + modHtml += F("
"); + modHtml += F("") + String(iv->getValue(pos)); + modHtml += F("") + String(iv->getUnit(pos)) + F(""); + modHtml += F("") + String(iv->getFieldName(pos)) + F(""); + modHtml += F("
"); + } + } + modHtml += "
"; + + for (uint8_t ch = 1; ch <= modNum; ch++) { + modHtml += F("
"); + if (iv->chName[ch - 1][0] == 0) + modHtml += F("CHANNEL ") + String(ch); + else + modHtml += String(iv->chName[ch - 1]); + modHtml += F(""); + for (uint8_t j = 0; j < 6; j++) { + switch (j) { + default: pos = (iv->getPosByChFld(ch, FLD_UDC)); break; + case 1: pos = (iv->getPosByChFld(ch, FLD_IDC)); break; + case 2: pos = (iv->getPosByChFld(ch, FLD_PDC)); break; + case 3: pos = (iv->getPosByChFld(ch, FLD_YD)); break; + case 4: pos = (iv->getPosByChFld(ch, FLD_YT)); break; + case 5: pos = (iv->getPosByChFld(ch, FLD_IRR)); break; + } + if (0xff != pos) { + modHtml += F("") + String(iv->getValue(pos)); + modHtml += F("") + String(iv->getUnit(pos)) + F(""); + modHtml += F("") + String(iv->getFieldName(pos)) + F(""); + } + } + modHtml += "
"; + yield(); + } + modHtml += F("
Last received data requested at: ") + mMain->getDateTimeStr(iv->ts) + F("
"); + modHtml += F("
"); +#else + // dump all data to web frontend + modHtml = F("
");
+            char topic[30], val[10];
+            for (uint8_t i = 0; i < iv->listLen; i++) {
+                snprintf(topic, 30, "%s/ch%d/%s", iv->name, iv->assign[i].ch, iv->getFieldName(i));
+                snprintf(val, 10, "%.3f %s", iv->getValue(i), iv->getUnit(i));
+                modHtml += String(topic) + ": " + String(val) + "\n";
+            }
+            modHtml += F("
"); +#endif + } + } + + request->send(200, F("text/html"), modHtml); } @@ -344,10 +439,11 @@ void web::showWebApi(AsyncWebServerRequest *request) if (response["tx_request"] == (uint8_t)TX_REQ_INFO) { // if the AlarmData is requested set the Alarm Index to the requested one - if (cmd == AlarmData){ - iv->alarmMesIndex = response["payload"]; + if (cmd == AlarmData || cmd == AlarmUpdate){ + // set the AlarmMesIndex for the request from user input + iv->alarmMesIndex = response["payload"]; } - //DPRINTLN(DBG_INFO, F("Will make tx-request 0x15 with subcmd ") + String(cmd) + F(" and payload ") + String(response["payload"])); + DPRINTLN(DBG_INFO, F("Will make tx-request 0x15 with subcmd ") + String(cmd) + F(" and payload ") + String((uint16_t) response["payload"])); // process payload from web request corresponding to the cmd iv->enqueCommand(cmd); } @@ -382,6 +478,14 @@ void web::showWebApi(AsyncWebServerRequest *request) iv->devControlRequest = true; // queue it in the request loop } } + if (response["cmd"] == (uint8_t)TurnOff){ + iv->devControlCmd = TurnOff; + iv->devControlRequest = true; // queue it in the request loop + } + if (response["cmd"] == (uint8_t)TurnOn){ + iv->devControlCmd = TurnOn; + iv->devControlRequest = true; // queue it in the request loop + } } } request->send(200, "text/json", "{success:true}"); @@ -470,37 +574,31 @@ String web::showSetupCb(char* key) { inv += F("serial.u64, HEX); - inv += F("\" maxlength=\"12\">"); + inv += F("\"/ maxlength=\"12\">"); inv += F(""); inv += F("name); - inv += F("\" maxlength=\"") + String(MAX_NAME_LENGTH) + "\">"; + inv += F("\"/ maxlength=\"") + String(MAX_NAME_LENGTH) + "\">"; inv += F(""); inv += F("powerLimit[0]); - inv += F("\" maxlength=\"") + String(6) + "\">"; + inv += F("\"/ maxlength=\"") + String(6) + "\">"; inv += F(""); - inv += F(""); - if(iv->powerLimit[1] == RelativNonPersistent) - inv += F("PowerLimitControl\">"); - if(iv->powerLimit[1] == AbsolutPersistent) - inv += F("PowerLimitControl\">"); - if(iv->powerLimit[1] == RelativPersistent) - inv += F("PowerLimitControl\">"); + inv += F(""); - // UGLY! But I do not know it a better way --// + inv += F(""); inv += F("
"); @@ -508,7 +606,7 @@ String web::showSetupCb(char* key) { inv += F("chMaxPwr[j]); - inv += F("\" maxlength=\"4\">"); + inv += F("\"/ maxlength=\"4\">"); } inv += F("

"); @@ -516,7 +614,7 @@ String web::showSetupCb(char* key) { inv += F("chName[j]); - inv += F("\" maxlength=\"") + String(MAX_NAME_LENGTH) + "\">"; + inv += F("\"/ maxlength=\"") + String(MAX_NAME_LENGTH) + "\">"; } inv += F("
"); }