diff --git a/.gitignore b/.gitignore index f9432353..9af41f40 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,4 @@ tools/esp8266/.vscode/extensions.json .DS_Store .vscode tools/esp8266/platformio-device-monitor-*.log +tools/esp8266/html/h/* \ No newline at end of file diff --git a/tools/esp8266/CircularBuffer.h b/tools/esp8266/CircularBuffer.h index b784f639..65c9e768 100644 --- a/tools/esp8266/CircularBuffer.h +++ b/tools/esp8266/CircularBuffer.h @@ -21,10 +21,7 @@ #ifndef CircularBuffer_h #define CircularBuffer_h -#ifdef ESP8266 -#define DISABLE_IRQ noInterrupts() -#define RESTORE_IRQ interrupts() -#elif defined(ESP32) +#if defined(ESP8266) || defined(ESP32) #define DISABLE_IRQ noInterrupts() #define RESTORE_IRQ interrupts() #else diff --git a/tools/esp8266/README.md b/tools/esp8266/README.md index e1f06a35..18f00c17 100644 --- a/tools/esp8266/README.md +++ b/tools/esp8266/README.md @@ -1,23 +1,30 @@ ## Table of Contents +- [Table of Contents](#table-of-contents) - [Overview](#overview) - [Compatiblity](#compatiblity) - [Things needed](#things-needed) - + [Faked Modules Warning](#there-are-fake-nrf24l01-modules-out-there) + - [There are fake NRF24L01+ Modules out there](#there-are-fake-nrf24l01-modules-out-there) - [Wiring things up](#wiring-things-up) - + [ESP8266 wiring example](#esp8266-wiring-example) + - [ESP8266 wiring example](#esp8266-wiring-example) + - [Schematic](#schematic) + - [Symbolic view](#symbolic-view) + - [ESP32 wiring example](#esp32-wiring-example) + - [Schematic](#schematic-1) + - [Symbolic view](#symbolic-view-1) + - [ESP32 GPIO settings](#esp32-gpio-settings) - [Flash the Firmware on your Ahoy DTU Hardware](#flash-the-firmware-on-your-ahoy-dtu-hardware) - + [Compiling your own Version (the geek way)](#compiling-your-own-version) + - [Compiling your own Version](#compiling-your-own-version) - [Optional Configuration before compilation](#optional-configuration-before-compilation) - + [Using a ready-to-flash binary using nodemcu-pyflasher (the easy way)](#using-a-ready-to-flash-binary-using-nodemcu-pyflasher) + - [Using a ready-to-flash binary using nodemcu-pyflasher](#using-a-ready-to-flash-binary-using-nodemcu-pyflasher) - [Connect to your Ahoy DTU](#connect-to-your-ahoy-dtu) - + [Your Ahoy DTO is very verbose using the Serial Console](#your-ahoy-dto-is-very-verbose-using-the-serial-console) - + [Connect to the Ahoy DTU Webinterface using your Browser](#connect-to-the-ahoy-dtu-webinterface-using-your-browser) + - [Your Ahoy DTU is very verbose using the Serial Console](#your-ahoy-dtu-is-very-verbose-using-the-serial-console) + - [Connect to the Ahoy DTU Webinterface using your Browser](#connect-to-the-ahoy-dtu-webinterface-using-your-browser) - [HTTP based Pages](#http-based-pages) - [MQTT command to set the DTU without webinterface](#mqtt-command-to-set-the-dtu-without-webinterface) - [Used Libraries](#used-libraries) - [Contact](#contact) -- [ToDo's - remove when done](#todo) +- [ToDo](#todo) *** @@ -29,9 +36,11 @@ Further information will help you to communicate to the compatible inverters. You find the full [User_Manual here](User_Manual.md) ## Compatiblity + For now the following Inverters should work out of the box: Hoymiles Inverters + - HM300 - HM350 - HM400 @@ -43,26 +52,46 @@ Hoymiles Inverters - HM1500 TSun Inverters: + - TSOL-350 - TSOL-400 -- othery may work as well (need to be veryfied). - +- others may work as well (need to be verified). ## Things needed + In order to build your own Ahoy DTU, you will need some things.
This list is not closing as the Maker Community offers more Boards than we could cover in this Readme.

-We suggest to use a WEMOS D1 mini Board as well as a NRF24L01+ Breakout Board.
-Make sure it has the "+" in its name as we depend on some features provided with the plus-variant.
-Any other ESP8266 Board with at least 4MBytes of ROM could work as well, depending on your skills. +We suggest to use a WEMOS D1 mini Board as well as a NRF24L01+ Breakout Board as a bare minimum.
+Any other ESP8266 Board with at least 4MBytes of ROM could work as well, depending on your skills and goals.
+Make sure the NRF24L01+ module has the "+" in its name as we depend on the 250kbps features provided only with the plus-variant. + +| **Parts** | **Price** | +| --- | --- | +| D1 ESP8266 Mini WLAN Board Mikrokontroller | 4,40 Euro | +| NRF24L01+ SMD Modul 2,4 GHz Wi-Fi Funkmodul | 3,45 Euro | +| Jumper Wire Steckbrücken Steckbrett weiblich-weiblich | 2,49 Euro | +| **Total costs** | **10,34 Euro** | + +To also run our sister project OpenDTU and be upwards compatible for the future we would recommend to spend some more money on an ESP32 board which has two CPU cores and a NRF24L01+ module with external antenna. + +| **Parts** | **Price** | +| --- | --- | +| ESP32 Dev Board NodeMCU WROOM32 WiFi | 7,90 Euro | +| NRF24L01+ PA LNA SMA mit Antenne Long | 4,50 Euro | +| Jumper Wire Steckbrücken Steckbrett weiblich-weiblich | 2,49 Euro | +| **Total costs** | **14,89 Euro** | #### There are fake NRF24L01+ Modules out there -Whatch out, there are some fake NRF24L01+ Modules out there that seem to use rebranded NRF24L01 Chips (without the +).
-An example can be found in [Issue #230](https://github.com/lumapu/ahoy/issues/230).
-You are welcome to add more examples of faked chips. We will that information here.
+ +Watch out, there are some fake NRF24L01+ Modules out there that seem to use rebranded NRF24L01 Chips (without the +).
+An example can be found in [Issue #230](https://github.com/grindylow/ahoy/issues/230).
+You are welcome to add more examples of faked chips. We will add that information here.
## Wiring things up + The NRF24L01+ radio module is connected to the standard SPI pins: + - SCLK (Signal Clock), - MISO (Master In Slave Out) and - MOSI (Master Out Slave In) @@ -70,6 +99,7 @@ The NRF24L01+ radio module is connected to the standard SPI pins: *These pins need to be configured in the config.h.* Additional, there are 3 pins, which can be set individual: + - CS (Chip Select), - CE (Chip Enable) and - IRQ (Interrupt) @@ -77,23 +107,31 @@ Additional, there are 3 pins, which can be set individual: *These pins can be changed from the /setup URL.* #### ESP8266 wiring example + This is an example wiring using a Wemos D1 mini.
+ ##### Schematic + ![Schematic](../../doc/AhoyWemos_Schaltplan.jpg) ##### Symbolic view + ![Symbolic](../../doc/AhoyWemos_Steckplatine.jpg) #### ESP32 wiring example + Example wiring for a 38pin ESP32 module ##### Schematic + ![Schematic](../../doc/Wiring_ESP32_Schematic.png) ##### Symbolic view + ![Symbolic](../../doc/Wiring_ESP32_Symbol.png) ##### ESP32 GPIO settings + For this wiring, set the 3 individual GPIOs under the /setup URL: ``` @@ -103,11 +141,12 @@ IRQ D0 (GPIO16 - no IRQ!) ``` ## Flash the Firmware on your Ahoy DTU Hardware -Once your Hardware is ready to run, you need to flash the Ahoy DTU Firmware to your Board. -You can either build your own using your own configuration or use one or our pre-compiled generic builds. +Once your Hardware is ready to run, you need to flash the Ahoy DTU Firmware to your Board. +You can either build your own using your own configuration or use one of our pre-compiled generic builds. #### Compiling your own Version + This information suits you if you want to configure and build your own firmware. This code comes to you as a **PlatformIO** project and can be compiled using the **PlatformIO** Addon.
@@ -118,75 +157,70 @@ If you do not want to compile your own build, you can use one of our ready-to-fl - number of supported inverters (set to 3 by default) `config.h` - DTU radio id `config.h` (default = 1234567801) -- unformated list in webbrowser `/livedata` `config.h`, `LIVEDATA_VISUALIZED` +- unformatted list in webbrowser `/livedata` `config.h`, `LIVEDATA_VISUALIZED` Alternativly, instead of modifying `config.h`, `config_override_example.h` can be copied to `config_override.h` and customized. config_override.h is excluded from version control and stays local. - #### Using a ready-to-flash binary using nodemcu-pyflasher + This information suits you if you just want to use an 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. open flash-tool and connect the target device to your computer. -4. Set the correct serial port and select the correct *.bin file +4. Set the correct serial port and select the correct *.bin file 5. click on "Flash NodeMCU" 6. flash the ESP with the compiled firmware using the UART pins or 7. repower the ESP 8. the ESP will start as access point (AP) if there is no network config stored in its eeprom -9. connect to the AP, you will be forwarded to the setup page +9. connect to the AP (password: `esp_8266`), you will be forwarded to the setup page 10. configure your WiFi settings, save, repower 11. check your router or serial console for the IP address of the module. You can try ping the configured device name as well. - Once your Ahoy DTU is running, you can use the Over The Air (OTA) capabilities to update your firmware. - ! ATTENTION: If you update from a very low version to the newest, please make sure to wipe all flash data! - - - - - ## Connect to your Ahoy DTU -When everything is wired up and the firmware is flashed, it is time to connect to your Ahoy DTU. +When everything is wired up and the firmware is flashed, it is time to connect to your Ahoy DTU. #### Your Ahoy DTU is very verbose using the Serial Console + When connected to your computer, you can open a Serial Console to obtain additional information.
This might be useful in case of any troubles that might occur as well as to simply
obtain information about the converted values which were read out of the inverter(s). - - + #### Connect to the Ahoy DTU Webinterface using your Browser + After you have sucessfully flashed and powered your Ahoy DTU, you can access it via your Browser.
If your Ahoy DTU was able to log into the configured WiFi Network, it will try to obtain an IP-Address
from your local DHCP Server (in most cases thats your Router).

In case it could not connect to your configured Network, it will provide its own WiFi Network that you can
connect to for furter configuration.
- The WiFi SSID *(the WiFi Name)* and Passwort is configured in the config.h and defaults to the SSID "AHOY-DTU" with the Passwort "esp_8266".
+ The WiFi SSID *(the WiFi Name)* and Passwort is configured in the config.h and defaults to the SSID "`AHOY-DTU`" with the Passwort "`esp_8266`".
The Ahoy DTU will keep that Network open for a certain amount of time (also configurable in the config.h and defaults to 60secs).
If nothing connects to it and that time runs up, it will retry to connect to the configured network an so on.

- If connected to your local Network, you just have to find out the used IP Address. In most cases your Router will give you a hint.
- If you connect to the WiFi the Ahoy DTU opens in case it could not connect to any other Network, the IP-Address of your Ahoy DTU is 192.168.1.1.
+ If connected to your local Network, you just have to find out the used IP Address or try the default name [http://ahoy-dtu/](http://ahoy-dtu/). In most cases your Router will give you a hint.
+ If you connect to the WiFi the Ahoy DTU opens in case it could not connect to any other Network, the IP-Address of your Ahoy DTU is [http://192.168.1.1/](http://192.168.1.1/).
Just open the IP-Address in your browser.

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) - - + ##### HTTP based Pages - To take control of your Ahoy DTU, you can directly call one of the following sub-pages (e.g. http://192.168.1.1/setup ).
- + + To take control of your Ahoy DTU, you can directly call one of the following sub-pages (e.g. [http://ahoy-dtu/setup](http://ahoy-dtu/setup) or [http://192.168.1.1/setup](http://192.168.1.1/setup) ).
+ | page | use | output | | ---- | ------ | ------ | -| /uptime | displays the uptime uf your Ahoy DTU | 0 Days, 01:37:34; now: 2022-08-21 11:13:53 | +| /uptime | displays the uptime of your Ahoy DTU | 0 Days, 01:37:34; now: 2022-08-21 11:13:53 | | /reboot | reboots the Ahoy DTU | | | /erase | erases the EEPROM | | | /factory | resets to the factory defaults configured in config.h | | @@ -198,31 +232,31 @@ When everything is wired up and the firmware is flashed, it is time to connect t | /json | gets live-data in JSON format | json output from the livedata | | /api | | | - - ## MQTT command to set the DTU without webinterface - [Read here](User_Manual.md) +[Read here](tools/esp8266/User_Manual.md) +## Used Libraries -## Used Libraries - -- `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 - +| Name | version | License | +| --------------------- | ------- | -------- | +| `ESP8266WiFi` | 1.0 | LGPL-2.1 | +| `DNSServer` | 1.1.1 | LGPL-2.1 | +| `SPI` | 1.0 | LGPL-2.1 | +| `Hash` | 1.0 | LGPL-2.1 | +| `EEPROM` | 1.0 | LGPL-2.1 | +| `ESP Async WebServer` | 1.2.3 | LGPL-3.0 | +| `ESPAsyncTCP` | 1.2.2 | LGPL-3.0 | +| `Time` | 1.6.1 | LGPL-2.1 | +| `RF24` | 1.4.5 | GPL-2.0 | +| `PubSubClient` | 2.8 | MIT | +| `ArduinoJson` | 6.19.4 | MIT | ## Contact -We run a Discord Server that can be used to get in touch with the Developers and Users. - -https://discord.gg/WzhxEY62mB +We run a Discord Server that can be used to get in touch with the Developers and Users. + ## ToDo diff --git a/tools/esp8266/User_Manual.md b/tools/esp8266/User_Manual.md index 23107fd4..729d82c9 100644 --- a/tools/esp8266/User_Manual.md +++ b/tools/esp8266/User_Manual.md @@ -18,11 +18,11 @@ The ahoy dtu will publish on the following topics |U_AC | 233.300|actual AC Voltage in Volt| |I_AC | 0.300 | actual AC Current in Ampere| |P_AC | 71.000| actual AC Power in Watt| -|P_ACr | 21.200| actual AC reactive power in VAr| -|Freq | 49.990|actual AC Frequency in 1/s| -|Pct | 95.800|actual AC Power factor in %| +|Q_AC | 21.200| actual AC reactive power in var| +|F_AC | 49.990| actual AC Frequency in Hz| +|PF_AC | 95.800| actual AC Power factor| |Temp | 19.800|Temperature of inverter in Celsius| -|LARM_MES_ID | 9.000|Last Alarm Message Id| +|EVT | 9.000|Last Event/Alarm Message Index| |YieldDay | 51.000|Energy converted to AC per day in Watt hours (measured on DC)| |YieldTotal | 465.294|Energy converted to AC since reset Watt hours (measured on DC)| |P_DC | 74.600|actual DC Power in Watt| @@ -92,17 +92,17 @@ To set the active power limit (controled value is the AC Power of the inverter) The implementation allows to set any of the available `` Commands: ```C typedef enum { - TurnOn = 0, // 0x00 - TurnOff = 1, // 0x01 - Restart = 2, // 0x02 - Lock = 3, // 0x03 - Unlock = 4, // 0x04 - ActivePowerContr = 11, // 0x0b - ReactivePowerContr = 12, // 0x0c - PFSet = 13, // 0x0d + TurnOn = 0, // 0x00 + TurnOff = 1, // 0x01 + Restart = 2, // 0x02 + Lock = 3, // 0x03 + Unlock = 4, // 0x04 + ActivePowerContr = 11, // 0x0b + ReactivePowerContr = 12, // 0x0c + PFSet = 13, // 0x0d CleanState_LockAndAlarm = 20, // 0x14 - SelfInspection = 40, // 0x28, self-inspection of grid-connected protection files - Init = 0xff + SelfInspection = 40, // 0x28, self-inspection of grid-connected protection files + Init = 0xff } DevControlCmdType; ``` The MQTT payload will be set on first to bytes and ``, which is taken from the topic path will be set on the second two bytes if the corresponding DevControlCmdType supports 4 byte data. @@ -215,8 +215,8 @@ Internally this values will be set for the second two bytes for MainCmd: 0x51 Su typedef enum { AbsolutNonPersistent = 0x0000, // 0 RelativNonPersistent = 0x0001, // 1 - AbsolutPersistent = 0x0100, // 256 - RelativPersistent = 0x0101 // 257 + AbsolutPersistent = 0x0100, // 256 + RelativPersistent = 0x0101 // 257 } PowerLimitControlType; ``` diff --git a/tools/esp8266/ahoywifi.cpp b/tools/esp8266/ahoywifi.cpp index af1729fa..b3feb43c 100644 --- a/tools/esp8266/ahoywifi.cpp +++ b/tools/esp8266/ahoywifi.cpp @@ -54,7 +54,7 @@ void ahoywifi::setup(uint32_t timeout, bool settingValid) { if(mApActive) DBGPRINTLN(F("192.168.1.1")); else - DBGPRINTLN(WiFi.localIP()); + DBGPRINTLN(WiFi.localIP().toString()); DPRINTLN(DBG_INFO, F("to configure your device")); DPRINTLN(DBG_INFO, F("----------------------------------------\n")); } diff --git a/tools/esp8266/ahoywifi.h b/tools/esp8266/ahoywifi.h index 5e79568f..fdd3bff0 100644 --- a/tools/esp8266/ahoywifi.h +++ b/tools/esp8266/ahoywifi.h @@ -7,13 +7,6 @@ #define __AHOYWIFI_H__ #include "dbg.h" -#ifdef ESP8266 - #include - #include -#elif defined(ESP32) - #include - #include -#endif // NTP #include diff --git a/tools/esp8266/app.cpp b/tools/esp8266/app.cpp index f8b99f7b..c151cef0 100644 --- a/tools/esp8266/app.cpp +++ b/tools/esp8266/app.cpp @@ -23,6 +23,7 @@ app::app() { loadDefaultConfig(); mSys = new HmSystemType(); + mShouldReboot = false; } @@ -41,7 +42,7 @@ void app::setup(uint32_t timeout) { #endif mSys->setup(&mConfig); - mWebInst = new web(this, &mSysConfig, &mConfig, mVersion); + mWebInst = new web(this, &mSysConfig, &mConfig, &mStat, mVersion); mWebInst->setup(); } @@ -60,13 +61,22 @@ void app::loop(void) { } if(checkTicker(&mNtpRefreshTicker, mNtpRefreshInterval)) { - if(!apActive) { - mTimestamp = mWifi->getNtpTime(); - DPRINTLN(DBG_INFO, "[NTP]: " + getDateTimeStr(mTimestamp)); - } + if(!apActive) + mUpdateNtp = true; } + if(mUpdateNtp) { + mUpdateNtp = false; + mTimestamp = mWifi->getNtpTime(); + DPRINTLN(DBG_INFO, "[NTP]: " + getDateTimeStr(mTimestamp)); + } + if(mShouldReboot) { + DPRINTLN(DBG_INFO, F("Rebooting...")); + ESP.restart(); + } + + mSys->Radio.loop(); yield(); @@ -84,30 +94,26 @@ void app::loop(void) { DPRINT(DBG_INFO, "RX " + String(len) + "B Ch" + String(p->rxCh) + " | "); mSys->Radio.dumpBuf(NULL, p->packet, len); } - mFrameCnt++; + mStat.frmCnt++; if(0 != len) { Inverter<> *iv = mSys->findInverter(&p->packet[1]); - if(NULL != iv && p->packet[0] == (TX_REQ_INFO + 0x80)) { // response from get information command + if((NULL != iv) && (p->packet[0] == (TX_REQ_INFO + 0x80))) { // response from get information command mPayload[iv->id].txId = p->packet[0]; DPRINTLN(DBG_DEBUG, F("Response from info request received")); uint8_t *pid = &p->packet[9]; if (*pid == 0x00) - { DPRINT(DBG_DEBUG, "fragment number zero received and ignored"); - } - else - { - if ((*pid & 0x7F) < 5) - { + else { + DPRINTLN(DBG_DEBUG, "PID: 0x" + String(*pid, HEX)); + if ((*pid & 0x7F) < 5) { memcpy(mPayload[iv->id].data[(*pid & 0x7F) - 1], &p->packet[10], len - 11); mPayload[iv->id].len[(*pid & 0x7F) - 1] = len - 11; } - if ((*pid & 0x80) == 0x80) - { // Last packet - if ((*pid & 0x7f) > mPayload[iv->id].maxPackId) - { + if ((*pid & 0x80) == 0x80) { + // Last packet + if ((*pid & 0x7f) > mPayload[iv->id].maxPackId) { mPayload[iv->id].maxPackId = (*pid & 0x7f); if (*pid > 0x81) mLastPacketId = *pid; @@ -115,35 +121,17 @@ void app::loop(void) { } } } - if(NULL != iv && p->packet[0] == (TX_REQ_DEVCONTROL + 0x80)) { // response from dev control command + if((NULL != iv) && (p->packet[0] == (TX_REQ_DEVCONTROL + 0x80))) { // response from dev control command mPayload[iv->id].txId = p->packet[0]; DPRINTLN(DBG_DEBUG, F("Response from devcontrol request received")); 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]); - 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])); - iv->devControlCmd = Init; - } - break; - - default: - 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")); - } - iv->devControlCmd = Init; - break; + if ((p->packet[12] == ActivePowerContr) && (p->packet[13] == 0x00)) { + if (p->packet[10] == 0x00 && p->packet[11] == 0x00) + 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])); + else + DPRINTLN(DBG_INFO, F("Inverter ") + String(iv->id) + F(" has NOT accepted power limit set point") + String(iv->powerLimit[0]) + F(" with PowerLimitControl ") + String(iv->powerLimit[1])); } + iv->devControlCmd = Init; } } } @@ -186,12 +174,13 @@ void app::loop(void) { for(uint8_t id = 0; id < mSys->getNumInverters(); id++) { Inverter<> *iv = mSys->getInverterByPos(id); if(NULL != iv) { - if(iv->isAvailable(mTimestamp)) { + record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug); + if(iv->isAvailable(mTimestamp, rec)) { DPRINTLN(DBG_INFO, "Inverter: " + String(id)); - for(uint8_t i = 0; i < iv->listLen; i++) { - if(0.0f != iv->getValue(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)); + for(uint8_t i = 0; i < rec->length; i++) { + if(0.0f != iv->getValue(i, rec)) { + snprintf(topic, 30, "%s/ch%d/%s", iv->name, rec->assign[i].ch, iv->getFieldName(i, rec)); + snprintf(val, 10, "%.3f %s", iv->getValue(i, rec), iv->getUnit(i, rec)); DPRINTLN(DBG_INFO, String(topic) + ": " + String(val)); } yield(); @@ -218,8 +207,8 @@ void app::loop(void) { int8_t maxLoop = MAX_NUM_INVERTERS; Inverter<> *iv = mSys->getInverterByPos(mSendLastIvId); do { - if(NULL != iv) - mPayload[iv->id].requested = false; + //if(NULL != iv) + // mPayload[iv->id].requested = false; mSendLastIvId = ((MAX_NUM_INVERTERS-1) == mSendLastIvId) ? 0 : mSendLastIvId + 1; iv = mSys->getInverterByPos(mSendLastIvId); } while((NULL == iv) && ((maxLoop--) > 0)); @@ -229,11 +218,14 @@ void app::loop(void) { processPayload(false); if(!mPayload[iv->id].complete) { - mRxFailed++; + if(0 == mPayload[iv->id].maxPackId) + mStat.rxFailNoAnser++; + else + mStat.rxFail++; + iv->setQueuedCmdFinished(); // command failed - if(mConfig.serialDebug) { + 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) + ")"); @@ -241,19 +233,25 @@ void app::loop(void) { } resetPayload(iv); + mPayload[iv->id].requested = true; yield(); - if(mConfig.serialDebug) + if(mConfig.serialDebug) { 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) && (NoPowerLimit != iv->powerLimit[1])) { // prevent to "switch off" + DPRINTLN(DBG_INFO, F("Requesting Inverter SN ") + String(iv->serial.u64, HEX)); + } + if(iv->devControlRequest) { 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); + mPayload[iv->id].txCmd = iv->devControlCmd; iv->clearCmdQueue(); iv->enqueCommand(SystemConfigPara); - } else { - mSys->Radio.sendTimePacket(iv->radioId.u64,iv->getQueuedCmd(), mPayload[iv->id].ts,iv->alarmMesIndex); + } + else { + uint8_t cmd = iv->getQueuedCmd(); + mSys->Radio.sendTimePacket(iv->radioId.u64, cmd, mPayload[iv->id].ts, iv->alarmMesIndex); + mPayload[iv->id].txCmd = cmd; mRxTicker = 0; } } @@ -283,12 +281,12 @@ bool app::buildPayload(uint8_t id) { for(uint8_t i = 0; i < mPayload[id].maxPackId; i ++) { if(mPayload[id].len[i] > 0) { if(i == (mPayload[id].maxPackId-1)) { - crc = Hoymiles::crc16(mPayload[id].data[i], mPayload[id].len[i] - 2, crc); + crc = Ahoy::crc16(mPayload[id].data[i], mPayload[id].len[i] - 2, crc); crcRcv = (mPayload[id].data[i][mPayload[id].len[i] - 2] << 8) | (mPayload[id].data[i][mPayload[id].len[i] - 1]); } else - crc = Hoymiles::crc16(mPayload[id].data[i], mPayload[id].len[i], crc); + crc = Ahoy::crc16(mPayload[id].data[i], mPayload[id].len[i], crc); } yield(); } @@ -305,47 +303,63 @@ void app::processPayload(bool retransmit) { boolean doMQTT = false; #endif - DPRINTLN(DBG_VERBOSE, F("app::processPayload")); + //DPRINTLN(DBG_INFO, F("processPayload")); for(uint8_t id = 0; id < mSys->getNumInverters(); id++) { Inverter<> *iv = mSys->getInverterByPos(id); if(NULL != iv) { - if(mPayload[iv->id].txId != (TX_REQ_INFO + 0x80)) { + if((mPayload[iv->id].txId != (TX_REQ_INFO + 0x80)) && (0 != mPayload[iv->id].txId)) { // no processing needed if txId is not 0x95 + //DPRINTLN(DBG_INFO, F("processPayload - set complete, txId: ") + String(mPayload[iv->id].txId, HEX)); mPayload[iv->id].complete = true; } + if(!mPayload[iv->id].complete ) { - if(!buildPayload(iv->id)) { + if(!buildPayload(iv->id)) { // payload not complete if(mPayload[iv->id].requested) { if(retransmit) { - if(mPayload[iv->id].retransmits < mConfig.maxRetransPerPyld) { - mPayload[iv->id].retransmits++; - if(mPayload[iv->id].maxPackId != 0) { - for(uint8_t i = 0; i < (mPayload[iv->id].maxPackId-1); i ++) { - if(mPayload[iv->id].len[i] == 0) { - if(mConfig.serialDebug) - DPRINTLN(DBG_ERROR, F("while retrieving data: Frame ") + String(i+1) + F(" missing: Request Retransmit")); - mSys->Radio.sendCmdPacket(iv->radioId.u64, TX_REQ_INFO, (SINGLE_FRAME+i), true); - break; // only retransmit one frame per loop + if(iv->devControlCmd == Restart || iv->devControlCmd == CleanState_LockAndAlarm ) { + // This is required to prevent retransmissions without answer. + DPRINTLN(DBG_INFO, F("Prevent retransmit on Restart / CleanState_LockAndAlarm...")); + mPayload[iv->id].retransmits = mConfig.maxRetransPerPyld; + } else { + if(mPayload[iv->id].retransmits < mConfig.maxRetransPerPyld) { + mPayload[iv->id].retransmits++; + if(mPayload[iv->id].maxPackId != 0) { + for(uint8_t i = 0; i < (mPayload[iv->id].maxPackId-1); i++) { + if(mPayload[iv->id].len[i] == 0) { + if(mConfig.serialDebug) + DPRINTLN(DBG_WARN, F("while retrieving data: Frame ") + String(i+1) + F(" missing: Request Retransmit")); + mSys->Radio.sendCmdPacket(iv->radioId.u64, TX_REQ_INFO, (SINGLE_FRAME+i), true); + break; // only retransmit one frame per loop + } + yield(); } - yield(); } + else { + if(mConfig.serialDebug) + DPRINTLN(DBG_WARN, F("while retrieving data: last frame missing: Request Retransmit")); + if(0x00 != mLastPacketId) + mSys->Radio.sendCmdPacket(iv->radioId.u64, TX_REQ_INFO, mLastPacketId, true); + else { + mPayload[iv->id].txCmd = iv->getQueuedCmd(); + mSys->Radio.sendTimePacket(iv->radioId.u64, mPayload[iv->id].txCmd, mPayload[iv->id].ts, iv->alarmMesIndex); + } + } + mSys->Radio.switchRxCh(100); } - else { - if(mConfig.serialDebug) - DPRINTLN(DBG_ERROR, F("while retrieving data: last frame missing: Request Retransmit")); - if(0x00 != mLastPacketId) - mSys->Radio.sendCmdPacket(iv->radioId.u64, TX_REQ_INFO, mLastPacketId, true); - else - mSys->Radio.sendTimePacket(iv->radioId.u64, iv->getQueuedCmd(), mPayload[iv->id].ts,iv->alarmMesIndex); - } - mSys->Radio.switchRxCh(100); } } } } - else { + else { // payload complete + DPRINTLN(DBG_INFO, F("procPyld: cmd: ") + String(mPayload[iv->id].txCmd)); + DPRINTLN(DBG_INFO, F("procPyld: txid: 0x") + String(mPayload[iv->id].txId, HEX)); + DPRINTLN(DBG_DEBUG, F("procPyld: max: ") + String(mPayload[iv->id].maxPackId)); + record_t<> *rec = iv->getRecordStruct(mPayload[iv->id].txCmd); // choose the parser mPayload[iv->id].complete = true; - iv->ts = mPayload[iv->id].ts; + if(mPayload[iv->id].txId == (TX_REQ_INFO + 0x80)) + mStat.rxSuccess++; + uint8_t payload[128]; uint8_t offs = 0; @@ -361,44 +375,109 @@ void app::processPayload(bool retransmit) { DPRINT(DBG_INFO, F("Payload (") + String(offs) + "): "); mSys->Radio.dumpBuf(NULL, payload, offs); } - mRxSuccess++; - - iv->getAssignment(); // choose the parser - for(uint8_t i = 0; i < iv->listLen; i++) { - iv->addValue(i, payload); // cmd value decides which parser is used to decode payload - 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)); + if(NULL == rec) + DPRINTLN(DBG_ERROR, F("record is NULL!")); + else { + rec->ts = mPayload[iv->id].ts; + for(uint8_t i = 0; i < rec->length; i++) { + iv->addValue(i, payload, rec); + yield(); + } + iv->doCalculations(); + + // MQTT send out + if(mMqttActive) { + record_t<> *recRealtime = iv->getRecordStruct(RealTimeRunData_Debug); + char topic[32 + MAX_NAME_LENGTH], val[32]; + float total[4]; + memset(total, 0, sizeof(float) * 4); + for (uint8_t id = 0; id < mSys->getNumInverters(); id++) { + Inverter<> *iv = mSys->getInverterByPos(id); + if (NULL != iv) { + if (iv->isAvailable(mTimestamp, rec)) { + for (uint8_t i = 0; i < rec->length; i++) { + snprintf(topic, 32 + MAX_NAME_LENGTH, "%s/ch%d/%s", iv->name, rec->assign[i].ch, fields[rec->assign[i].fieldId]); + snprintf(val, 10, "%.3f", iv->getValue(i, rec)); + mMqtt.sendMsg(topic, val); + if(recRealtime == rec) { + 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; + } + } + } + + if(iv->isProducing(mTimestamp, rec)){ + snprintf(topic, 32 + MAX_NAME_LENGTH, "%s/available_text", iv->name); + snprintf(val, 32, DEF_MQTT_IV_MESSAGE_INVERTER_AVAIL_AND_PRODUCED); + mMqtt.sendMsg(topic, val); + snprintf(topic, 32 + MAX_NAME_LENGTH, "%s/available", iv->name); + snprintf(val, 32, "2"); + mMqtt.sendMsg(topic, val); + } else { + snprintf(topic, 32 + MAX_NAME_LENGTH, "%s/available_text", iv->name); + snprintf(val, 32, DEF_MQTT_IV_MESSAGE_INVERTER_AVAIL_AND_NOT_PRODUCED); + mMqtt.sendMsg(topic, val); + snprintf(topic, 32 + MAX_NAME_LENGTH, "%s/available", iv->name); + snprintf(val, 32, "1"); + mMqtt.sendMsg(topic, val); + } + + snprintf(topic, 32 + MAX_NAME_LENGTH, "%s/last_success", iv->name); + snprintf(val, 48, "%i", iv->getLastTs(rec) * 1000); + mMqtt.sendMsg(topic, val); + + yield(); + } + } + } + } + + // total values (sum of all inverters) + if(recRealtime == rec) { + if(mSys->getNumInverters() > 1) { + uint8_t fieldId = 0; + for (uint8_t i = 0; i < 4; i++) { + switch(i) { + case 0: fieldId = FLD_PAC; break; + case 1: fieldId = FLD_YT; break; + case 2: fieldId = FLD_YD; break; + case 3: fieldId = FLD_PDC; break; + } + snprintf(topic, 32 + MAX_NAME_LENGTH, "total/%s", fields[fieldId]); + snprintf(val, 10, "%.3f", total[i]); mMqtt.sendMsg(topic, val); - yield(); } } } } } + iv->setQueuedCmdFinished(); + #ifdef __MQTT_AFTER_RX__ doMQTT = true; #endif } } + + if(mMqttActive) { + record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug); + char topic[32 + MAX_NAME_LENGTH], val[32]; + if (!iv->isAvailable(mTimestamp, rec) && !iv->isProducing(mTimestamp, rec)){ + snprintf(topic, 32 + MAX_NAME_LENGTH, "%s/available_text", iv->name); + snprintf(val, 32, DEF_MQTT_IV_MESSAGE_NOT_AVAIL_AND_NOT_PRODUCED); + mMqtt.sendMsg(topic, val); + snprintf(topic, 32 + MAX_NAME_LENGTH, "%s/available", iv->name); + snprintf(val, 32, "0"); + mMqtt.sendMsg(topic, val); + } + } + yield(); } } @@ -484,6 +563,11 @@ void app::cbMqtt(char* topic, byte* payload, unsigned int length) { // uint16_t power_factor = std::stoi(strtok(NULL, "/")); DPRINTLN(DBG_INFO, F("Set Power Factor not implemented for inverter ") + String(iv->id) ); break; + case CleanState_LockAndAlarm: // CleanState lock & alarm + iv->devControlCmd = CleanState_LockAndAlarm; + DPRINTLN(DBG_INFO, F("CleanState lock & alarm for inverter ") + String(iv->id) ); + iv->devControlRequest = true; + break; default: DPRINTLN(DBG_INFO, "Not implemented"); break; @@ -499,82 +583,6 @@ void app::cbMqtt(char* topic, byte* payload, unsigned int length) { } -//----------------------------------------------------------------------------- -String app::getStatistics(void) { - String content = F("Receive success: ") + String(mRxSuccess) + "\n"; - content += F("Receive fail: ") + String(mRxFailed) + "\n"; - content += F("Frames received: ") + String(mFrameCnt) + "\n"; - content += F("Send Cnt: ") + String(mSys->Radio.mSendCnt) + String("\n\n"); - - 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 += String(iv->name) + F(" (v") + String(iv->fwVersion) +F(")") + F(" is "); - if(!iv->isAvailable(mTimestamp)) { - content += F("not "); - avail = false; - } - content += F("available and is "); - if(!iv->isProducing(mTimestamp)) - content += F("not "); - content += F("producing\n"); - - if(!avail) { - if(iv->getLastTs() > 0) - content += F("-> last successful transmission: ") + getDateTimeStr(iv->getLastTs()) + "\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"); - - if(mShowRebootRequest) - content += F("INFO: reboot your ESP to apply all your configuration changes!\n"); - - if(!mSettingsValid) - content += F("INFO: your settings are invalid, please switch to Setup to correct this.\n"); - - content += F("MQTT: "); - if(!mMqtt.isConnected()) - content += F("not "); - content += F("connected\n"); - - return content; -} - - -//----------------------------------------------------------------------------- -String app::getJson(void) { - DPRINTLN(DBG_VERBOSE, F("app::showJson")); - String modJson; - - modJson = F("{\n"); - for(uint8_t id = 0; id < mSys->getNumInverters(); id++) { - Inverter<> *iv = mSys->getInverterByPos(id); - if(NULL != iv) { - char topic[40], val[25]; - snprintf(topic, 30, "\"%s\": {\n", iv->name); - modJson += String(topic); - for(uint8_t i = 0; i < iv->listLen; i++) { - snprintf(topic, 40, "\t\"ch%d/%s\"", iv->assign[i].ch, iv->getFieldName(i)); - snprintf(val, 25, "[%.3f, \"%s\"]", iv->getValue(i), iv->getUnit(i)); - modJson += String(topic) + ": " + String(val) + F(",\n"); - } - modJson += F("\t\"last_msg\": \"") + getDateTimeStr(iv->ts) + F("\"\n\t},\n"); - } - } - modJson += F("\"json_ts\": \"") + String(getDateTimeStr(mTimestamp)) + F("\"\n}\n"); - - return modJson; -} - - //----------------------------------------------------------------------------- bool app::getWifiApActive(void) { return mWifi->getApActive(); @@ -589,7 +597,8 @@ void app::sendMqttDiscoveryConfig(void) { for(uint8_t id = 0; id < mSys->getNumInverters(); id++) { Inverter<> *iv = mSys->getInverterByPos(id); if(NULL != iv) { - if(iv->isAvailable(mTimestamp) && mMqttConfigSendState[id] != true) { + record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug); + if(iv->isAvailable(mTimestamp, rec) && mMqttConfigSendState[id] != true) { DynamicJsonDocument deviceDoc(128); deviceDoc["name"] = iv->name; deviceDoc["ids"] = String(iv->serial.u64, HEX); @@ -599,21 +608,21 @@ void app::sendMqttDiscoveryConfig(void) { JsonObject deviceObj = deviceDoc.as(); DynamicJsonDocument doc(384); - for(uint8_t i = 0; i < iv->listLen; i++) { - if (iv->assign[i].ch == CH0) { - snprintf(name, 32, "%s %s", iv->name, iv->getFieldName(i)); + for(uint8_t i = 0; i < rec->length; i++) { + if (rec->assign[i].ch == CH0) { + snprintf(name, 32, "%s %s", iv->name, iv->getFieldName(i, rec)); } else { - snprintf(name, 32, "%s CH%d %s", iv->name, iv->assign[i].ch, iv->getFieldName(i)); + snprintf(name, 32, "%s CH%d %s", iv->name, rec->assign[i].ch, iv->getFieldName(i, rec)); } - snprintf(stateTopic, 64, "%s/%s/ch%d/%s", mConfig.mqtt.topic, iv->name, iv->assign[i].ch, iv->getFieldName(i)); - snprintf(discoveryTopic, 64, "%s/sensor/%s/ch%d_%s/config", MQTT_DISCOVERY_PREFIX, iv->name, iv->assign[i].ch, iv->getFieldName(i)); - snprintf(uniq_id, 32, "ch%d_%s", iv->assign[i].ch, iv->getFieldName(i)); - const char* devCls = getFieldDeviceClass(iv->assign[i].fieldId); - const char* stateCls = getFieldStateClass(iv->assign[i].fieldId); + snprintf(stateTopic, 64, "%s/%s/ch%d/%s", mConfig.mqtt.topic, iv->name, rec->assign[i].ch, iv->getFieldName(i, rec)); + snprintf(discoveryTopic, 64, "%s/sensor/%s/ch%d_%s/config", MQTT_DISCOVERY_PREFIX, iv->name, rec->assign[i].ch, iv->getFieldName(i, rec)); + snprintf(uniq_id, 32, "ch%d_%s", rec->assign[i].ch, iv->getFieldName(i, rec)); + const char* devCls = getFieldDeviceClass(rec->assign[i].fieldId); + const char* stateCls = getFieldStateClass(rec->assign[i].fieldId); doc["name"] = name; doc["stat_t"] = stateTopic; - doc["unit_of_meas"] = iv->getUnit(i); + doc["unit_of_meas"] = iv->getUnit(i, rec); doc["uniq_id"] = String(iv->serial.u64, HEX) + "_" + uniq_id; doc["dev"] = deviceObj; doc["exp_aft"] = mMqttInterval + 5; // add 5 sec if connection is bad or ESP too slow @@ -662,8 +671,9 @@ const char* app::getFieldStateClass(uint8_t fieldId) { //----------------------------------------------------------------------------- void app::resetSystem(void) { - mUptimeSecs = 0; - mPrevMillis = 0; + mUptimeSecs = 0; + mPrevMillis = 0; + mUpdateNtp = false; mNtpRefreshTicker = 0; mNtpRefreshInterval = NTP_REFRESH_INTERVAL; // [ms] @@ -691,9 +701,7 @@ void app::resetSystem(void) { memset(mPayload, 0, (MAX_NUM_INVERTERS * sizeof(invPayload_t))); - mRxFailed = 0; - mRxSuccess = 0; - mFrameCnt = 0; + memset(&mStat, 0, sizeof(statistics_t)); mLastPacketId = 0x00; } @@ -761,23 +769,6 @@ void app::loadEEpconfig(void) { if(0ULL != invSerial) { iv = mSys->addInverter(name, invSerial, modPwr); if(NULL != iv) { // will run once on every dtu boot - mEep->read(ADDR_INV_PWR_LIM + (i * 2),(uint16_t *)&(iv->powerLimit[0])); - mEep->read(ADDR_INV_PWR_LIM_CON + (i * 2),(uint16_t *)&(iv->powerLimit[1])); - // only set it, if it is changed by user. Default value in the html setup page is -1 = 0xffff - // 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 - 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); } @@ -787,6 +778,12 @@ void app::loadEEpconfig(void) { mMqttInterval += mConfig.sendInterval; } } + + for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i++) { + iv = mSys->getInverterByPos(i, false); + if(NULL != iv) + resetPayload(iv); + } } } @@ -801,8 +798,6 @@ void app::saveValues(void) { for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i ++) { 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]); mEep->write(ADDR_INV_NAME + (i * MAX_NAME_LENGTH), iv->name, MAX_NAME_LENGTH); // max channel power / name for(uint8_t j = 0; j < 4; j++) { @@ -812,7 +807,6 @@ void app::saveValues(void) { } updateCrc(); - mEep->commit(); } @@ -857,13 +851,13 @@ void app::setupMqtt(void) { } //----------------------------------------------------------------------------- -void app::resetPayload(Inverter<>* iv) -{ - // reset payload data +void app::resetPayload(Inverter<>* iv) { + DPRINTLN(DBG_INFO, "resetPayload: id: " + String(iv->id)); memset(mPayload[iv->id].len, 0, MAX_PAYLOAD_ENTRIES); + mPayload[iv->id].txCmd = 0; mPayload[iv->id].retransmits = 0; - mPayload[iv->id].maxPackId = 0; - mPayload[iv->id].complete = false; - mPayload[iv->id].requested = true; - mPayload[iv->id].ts = mTimestamp; + mPayload[iv->id].maxPackId = 0; + mPayload[iv->id].complete = false; + mPayload[iv->id].requested = false; + mPayload[iv->id].ts = mTimestamp; } diff --git a/tools/esp8266/app.h b/tools/esp8266/app.h index a2738a1a..fe92bb5e 100644 --- a/tools/esp8266/app.h +++ b/tools/esp8266/app.h @@ -36,15 +36,9 @@ typedef HmRadio RadioType; typedef Inverter InverterType; typedef HmSystem HmSystemType; -const char* const wemosPins[] = {"D3 (GPIO0)", "TX (GPIO1)", "D4 (GPIO2)", "RX (GPIO3)", - "D2 (GPIO4)", "D1 (GPIO5)", "GPIO6", "GPIO7", "GPIO8", - "GPIO9", "GPIO10", "GPIO11", "D6 (GPIO12)", "D7 (GPIO13)", - "D5 (GPIO14)", "D8 (GPIO15)", "D0 (GPIO16 - no IRQ!)"}; -const char* const pinNames[] = {"CS", "CE", "IRQ"}; -const char* const pinArgNames[] = {"pinCs", "pinCe", "pinIrq"}; - typedef struct { + uint8_t txCmd; uint8_t txId; uint8_t invId; uint32_t ts; @@ -56,7 +50,6 @@ typedef struct { bool requested; } invPayload_t; - class ahoywifi; class web; @@ -71,8 +64,6 @@ class app { void cbMqtt(char* topic, byte* payload, unsigned int length); void saveValues(void); void resetPayload(Inverter<>* iv); - String getStatistics(void); - String getJson(void); bool getWifiApActive(void); uint8_t getIrqPin(void) { @@ -104,6 +95,15 @@ class app { return String(str); } + String getTimeStr(void) { + char str[20]; + if(0 == mTimestamp) + sprintf(str, "n/a"); + else + sprintf(str, "%02d:%02d:%02d ", hour(mTimestamp), minute(mTimestamp), second(mTimestamp)); + return String(str); + } + inline uint32_t getUptime(void) { return mUptimeSecs; } @@ -112,6 +112,14 @@ class app { return mTimestamp; } + inline void setTimestamp(uint32_t newTime) { + DPRINTLN(DBG_DEBUG, F("setTimestamp: ") + String(newTime)); + if(0 == newTime) + mUpdateNtp = true; + else + mTimestamp = newTime; + } + void eraseSettings(bool all = false) { //DPRINTLN(DBG_VERBOSE, F("main.h:eraseSettings")); uint8_t buf[64]; @@ -144,7 +152,12 @@ class app { return false; } + inline bool mqttIsConnected(void) { return mMqtt.isConnected(); } + inline bool getSettingsValid(void) { return mSettingsValid; } + inline bool getRebootRequestState(void) { return mShowRebootRequest; } + HmSystemType *mSys; + bool mShouldReboot; private: void resetSystem(void); @@ -169,7 +182,7 @@ class app { while(length > 0) { len = (length < 32) ? length : 32; mEep->read(start, buf, len); - crc = Hoymiles::crc16(buf, len, crc); + crc = Ahoy::crc16(buf, len, crc); start += len; length -= len; } @@ -234,6 +247,7 @@ class app { eep *mEep; uint32_t mTimestamp; + bool mUpdateNtp; bool mShowRebootRequest; @@ -247,14 +261,11 @@ class app { uint8_t mSendLastIvId; invPayload_t mPayload[MAX_NUM_INVERTERS]; - uint32_t mRxFailed; - uint32_t mRxSuccess; - uint32_t mFrameCnt; + statistics_t mStat; uint8_t mLastPacketId; // timer uint32_t mTicker; - uint32_t mRxTicker; // mqtt diff --git a/tools/esp8266/config.h b/tools/esp8266/config.h index bfbfeb86..e6a019f5 100644 --- a/tools/esp8266/config.h +++ b/tools/esp8266/config.h @@ -45,17 +45,14 @@ #define DEF_RF24_CE_PIN 2 #define DEF_RF24_IRQ_PIN 0 -// default radio ID -#define DTU_RADIO_ID ((uint64_t)0x1234567801ULL) - // default NRF24 power, possible values (0 - 3) -#define DEF_AMPLIFIERPOWER 2 +#define DEF_AMPLIFIERPOWER 1 // number of packets hold in buffer #define PACKET_BUFFER_SIZE 30 // number of configurable inverters -#define MAX_NUM_INVERTERS 3 +#define MAX_NUM_INVERTERS 4 // default serial interval #define SERIAL_INTERVAL 5 @@ -108,8 +105,10 @@ // default MQTT topic #define DEF_MQTT_TOPIC "inverter" -// changes the style of "/setup" page, visualized = nicer -#define LIVEDATA_VISUALIZED +//default MQTT Message Inverter Status +#define DEF_MQTT_IV_MESSAGE_NOT_AVAIL_AND_NOT_PRODUCED "not available and not producing" // STATUS 0 +#define DEF_MQTT_IV_MESSAGE_INVERTER_AVAIL_AND_NOT_PRODUCED "available and not producing" // STATUS 1 +#define DEF_MQTT_IV_MESSAGE_INVERTER_AVAIL_AND_PRODUCED "available and producing" // STATUS 2 #if __has_include("config_override.h") #include "config_override.h" diff --git a/tools/esp8266/config_override_example.h b/tools/esp8266/config_override_example.h index dbaf54df..5fa73f3d 100644 --- a/tools/esp8266/config_override_example.h +++ b/tools/esp8266/config_override_example.h @@ -24,7 +24,4 @@ #undef DEF_RF24_IRQ_PIN #define DEF_RF24_IRQ_PIN 16 -#undef DTU_RADIO_ID -#define DTU_RADIO_ID ((uint64_t)0x1234567802ULL) - #endif /*__CONFIG_OVERRIDE_H__*/ diff --git a/tools/esp8266/crc.cpp b/tools/esp8266/crc.cpp index 29f9b82f..879af286 100644 --- a/tools/esp8266/crc.cpp +++ b/tools/esp8266/crc.cpp @@ -5,34 +5,31 @@ #include "crc.h" -namespace Hoymiles { - -uint8_t crc8(uint8_t buf[], uint8_t len) { - uint8_t crc = CRC8_INIT; - for(uint8_t i = 0; i < len; i++) { - crc ^= buf[i]; - for(uint8_t b = 0; b < 8; b ++) { - crc = (crc << 1) ^ ((crc & 0x80) ? CRC8_POLY : 0x00); +namespace Ahoy { + uint8_t crc8(uint8_t buf[], uint8_t len) { + uint8_t crc = CRC8_INIT; + for(uint8_t i = 0; i < len; i++) { + crc ^= buf[i]; + for(uint8_t b = 0; b < 8; b ++) { + crc = (crc << 1) ^ ((crc & 0x80) ? CRC8_POLY : 0x00); + } } - yield(); + return crc; } - return crc; -} -uint16_t crc16(uint8_t buf[], uint8_t len, uint16_t start) { - uint16_t crc = start; - uint8_t shift = 0; + uint16_t crc16(uint8_t buf[], uint8_t len, uint16_t start) { + uint16_t crc = start; + uint8_t shift = 0; - for(uint8_t i = 0; i < len; i ++) { - crc = crc ^ buf[i]; - for(uint8_t bit = 0; bit < 8; bit ++) { - shift = (crc & 0x0001); - crc = crc >> 1; - if(shift != 0) - crc = crc ^ CRC16_MODBUS_POLYNOM; + for(uint8_t i = 0; i < len; i ++) { + crc = crc ^ buf[i]; + for(uint8_t bit = 0; bit < 8; bit ++) { + shift = (crc & 0x0001); + crc = crc >> 1; + if(shift != 0) + crc = crc ^ CRC16_MODBUS_POLYNOM; + } } - yield(); + return crc; } - return crc; } -} // namespace Hoymiles \ No newline at end of file diff --git a/tools/esp8266/crc.h b/tools/esp8266/crc.h index 1c8de9e0..ef7be1f8 100644 --- a/tools/esp8266/crc.h +++ b/tools/esp8266/crc.h @@ -14,10 +14,8 @@ #define CRC16_MODBUS_POLYNOM 0xA001 -namespace Hoymiles { - +namespace Ahoy { uint8_t crc8(uint8_t buf[], uint8_t len); uint16_t crc16(uint8_t buf[], uint8_t len, uint16_t start = 0xffff); - } #endif /*__CRC_H__*/ diff --git a/tools/esp8266/dbg.cpp b/tools/esp8266/dbg.cpp new file mode 100644 index 00000000..9f7c9fd3 --- /dev/null +++ b/tools/esp8266/dbg.cpp @@ -0,0 +1,3 @@ +#include "dbg.h" + +DBG_CB mCb = NULL; diff --git a/tools/esp8266/defines.h b/tools/esp8266/defines.h index 210604a5..ece45946 100644 --- a/tools/esp8266/defines.h +++ b/tools/esp8266/defines.h @@ -13,7 +13,7 @@ //------------------------------------- #define VERSION_MAJOR 0 #define VERSION_MINOR 5 -#define VERSION_PATCH 17 +#define VERSION_PATCH 19 //------------------------------------- @@ -23,55 +23,49 @@ typedef struct { } packet_t; typedef enum { - InverterDevInform_Simple = 0, // 0x00 - InverterDevInform_All = 1, // 0x01 - GridOnProFilePara = 2, // 0x02 - HardWareConfig = 3, // 0x03 - SimpleCalibrationPara = 4, // 0x04 - SystemConfigPara = 5, // 0x05 - RealTimeRunData_Debug = 11, // 0x0b - RealTimeRunData_Reality = 12, // 0x0c - RealTimeRunData_A_Phase = 13, // 0x0d - RealTimeRunData_B_Phase = 14, // 0x0e - RealTimeRunData_C_Phase = 15, // 0x0f - AlarmData = 17, // 0x11, Alarm data - all unsent alarms - AlarmUpdate = 18, // 0x12, Alarm data - all pending alarms - RecordData = 19, // 0x13 - InternalData = 20, // 0x14 - GetLossRate = 21, // 0x15 - GetSelfCheckState = 30, // 0x1e - InitDataState = 0xff + InverterDevInform_Simple = 0, // 0x00 + InverterDevInform_All = 1, // 0x01 + GridOnProFilePara = 2, // 0x02 + HardWareConfig = 3, // 0x03 + SimpleCalibrationPara = 4, // 0x04 + SystemConfigPara = 5, // 0x05 + RealTimeRunData_Debug = 11, // 0x0b + RealTimeRunData_Reality = 12, // 0x0c + RealTimeRunData_A_Phase = 13, // 0x0d + RealTimeRunData_B_Phase = 14, // 0x0e + RealTimeRunData_C_Phase = 15, // 0x0f + AlarmData = 17, // 0x11, Alarm data - all unsent alarms + AlarmUpdate = 18, // 0x12, Alarm data - all pending alarms + RecordData = 19, // 0x13 + InternalData = 20, // 0x14 + GetLossRate = 21, // 0x15 + GetSelfCheckState = 30, // 0x1e + InitDataState = 0xff } InfoCmdType; typedef enum { - TurnOn = 0, // 0x00 - TurnOff = 1, // 0x01 - Restart = 2, // 0x02 - Lock = 3, // 0x03 - Unlock = 4, // 0x04 - ActivePowerContr = 11, // 0x0b - ReactivePowerContr = 12, // 0x0c - PFSet = 13, // 0x0d + TurnOn = 0, // 0x00 + TurnOff = 1, // 0x01 + Restart = 2, // 0x02 + Lock = 3, // 0x03 + Unlock = 4, // 0x04 + ActivePowerContr = 11, // 0x0b + ReactivePowerContr = 12, // 0x0c + PFSet = 13, // 0x0d CleanState_LockAndAlarm = 20, // 0x14 - SelfInspection = 40, // 0x28, self-inspection of grid-connected protection files - Init = 0xff + SelfInspection = 40, // 0x28, self-inspection of grid-connected protection files + Init = 0xff } 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 - RelativPersistent = 257UL // 0x0101 +typedef enum { + AbsolutNonPersistent = 0UL, // 0x0000 + RelativNonPersistent = 1UL, // 0x0001 + AbsolutPersistent = 256UL, // 0x0100 + RelativPersistent = 257UL // 0x0101 } PowerLimitControlType; -// minimum serial interval #define MIN_SERIAL_INTERVAL 5 - -// minimum send interval #define MIN_SEND_INTERVAL 15 - -// minimum mqtt interval #define MIN_MQTT_INTERVAL 60 //------------------------------------- @@ -90,27 +84,16 @@ typedef enum { // ToDo: to be verified by field tests #define INV_MAX_RTRY_LEN 1 // uint8_t #define INV_PWR_LIM_LEN MAX_NUM_INVERTERS * 2 // uint16_t -#define PINOUT_LEN 3 // 3 pins: CS, CE, IRQ - -#define RF24_AMP_PWR_LEN 1 - #define NTP_ADDR_LEN 32 // DNS Name -#define NTP_PORT_LEN 2 // uint16_t #define MQTT_ADDR_LEN 32 // DNS Name #define MQTT_USER_LEN 16 #define MQTT_PWD_LEN 32 #define MQTT_TOPIC_LEN 32 -#define MQTT_INTERVAL_LEN 2 // uint16_t -#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 -#define SER_INTERVAL_LEN 2 // uint16_t - #pragma pack(push) // push current alignment to stack #pragma pack(1) // set alignment to 1 byte boundary typedef struct { @@ -119,8 +102,10 @@ typedef struct { char user[MQTT_USER_LEN]; char pwd[MQTT_PWD_LEN]; char topic[MQTT_TOPIC_LEN]; -} /*__attribute__((__packed__))*/ mqttConfig_t; +} mqttConfig_t; #pragma pack(pop) // restore original alignment from stack + + typedef struct { char deviceName[DEVNAME_LEN]; @@ -151,9 +136,16 @@ typedef struct { uint16_t serialInterval; bool serialShowIv; bool serialDebug; -} /*__attribute__((__packed__))*/ config_t; +} config_t; #pragma pack(pop) // restore original alignment from stack +typedef struct { + uint32_t rxFail; + uint32_t rxFailNoAnser; + uint32_t rxSuccess; + uint32_t frmCnt; +} statistics_t; + #define CFG_MQTT_LEN MQTT_ADDR_LEN + 2 + MQTT_USER_LEN + MQTT_PWD_LEN +MQTT_TOPIC_LEN #define CFG_SYS_LEN DEVNAME_LEN + SSID_LEN + PWD_LEN + 1 @@ -173,10 +165,8 @@ typedef struct { #define ADDR_INV_CH_NAME ADDR_INV_CH_PWR + INV_CH_CH_PWR_LEN #define ADDR_INV_INTERVAL ADDR_INV_CH_NAME + INV_CH_CH_NAME_LEN #define ADDR_INV_MAX_RTRY ADDR_INV_INTERVAL + INV_INTERVAL_LEN -#define ADDR_INV_PWR_LIM ADDR_INV_MAX_RTRY + INV_MAX_RTRY_LEN -#define ADDR_INV_PWR_LIM_CON ADDR_INV_PWR_LIM + INV_PWR_LIM_LEN -#define ADDR_NEXT ADDR_INV_PWR_LIM_CON + INV_PWR_LIM_LEN +#define ADDR_NEXT ADDR_INV_MAX_RTRY + INV_INTERVAL_LEN #define ADDR_SETTINGS_CRC ADDR_NEXT + 2 diff --git a/tools/esp8266/favicon.h b/tools/esp8266/favicon.h deleted file mode 100644 index f0c501cd..00000000 --- a/tools/esp8266/favicon.h +++ /dev/null @@ -1,194 +0,0 @@ -//----------------------------------------------------------------------------- -// 2022 Ahoy, https://www.mikrocontroller.net/topic/525778 -// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/ -//----------------------------------------------------------------------------- - - -// a) https://www.favicon-generator.org/ -// b) exiftool -all:all= -r -// c) hexlify.py: -// import sys -// f = open (sys.argv[1], 'rb').read() -// for n, c in enumerate(f): -// if n % 16 == 0: print (' "', end = '') -// print (f"\\x{c:02x}", end = '') -// if n % 16 == 15: print ('" \\') -// if n % 16 != 15: print ('"') - -#define FAVICON_PANEL_16 \ - "\x89\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52" \ - "\x00\x00\x00\x10\x00\x00\x00\x10\x08\x03\x00\x00\x00\x28\x2d\x0f" \ - "\x53\x00\x00\x00\x04\x67\x41\x4d\x41\x00\x00\xb1\x8f\x0b\xfc\x61" \ - "\x05\x00\x00\x00\x20\x63\x48\x52\x4d\x00\x00\x7a\x26\x00\x00\x80" \ - "\x84\x00\x00\xfa\x00\x00\x00\x80\xe8\x00\x00\x75\x30\x00\x00\xea" \ - "\x60\x00\x00\x3a\x98\x00\x00\x17\x70\x9c\xba\x51\x3c\x00\x00\x01" \ - "\x9b\x50\x4c\x54\x45\xfc\xfe\xff\xff\xff\xff\xcb\xcd\xcf\x22\x25" \ - "\x30\x12\x16\x21\x11\x15\x21\x11\x15\x23\x12\x16\x25\x10\x13\x1f" \ - "\x8f\x91\x93\x9b\x9d\xa1\x14\x17\x22\x14\x18\x25\x14\x17\x23\x13" \ - "\x17\x22\x14\x17\x24\x17\x19\x27\x22\x24\x2e\xc1\xc2\xc3\xf8\xfa" \ - "\xfb\x61\x63\x6b\x11\x13\x1f\x15\x19\x24\x17\x19\x24\x18\x1a\x24" \ - "\x18\x1b\x26\x16\x19\x26\x42\x44\x4b\xe7\xe8\xe9\xfd\xff\xff\xfe" \ - "\xff\xff\xe0\xe2\xe4\x33\x36\x3f\x16\x19\x24\x16\x17\x22\x16\x18" \ - "\x22\x17\x18\x23\x11\x12\x1e\x71\x73\x76\xfb\xfc\xfd\xb5\xb7\xba" \ - "\x1a\x1c\x27\x15\x17\x22\x15\x17\x21\x14\x15\x20\x14\x16\x23\x17" \ - "\x1a\x26\x1b\x1c\x25\xaa\xac\xad\x7c\x7f\x85\x11\x13\x1d\x16\x18" \ - "\x24\x17\x19\x25\x18\x1a\x25\x30\x32\x38\xd7\xd9\xd9\xef\xf1\xf2" \ - "\x47\x49\x51\x12\x13\x1e\x17\x19\x22\x16\x17\x23\x10\x11\x1c\x59" \ - "\x5a\x5f\xf4\xf6\xf7\xcc\xce\xd0\x24\x25\x2e\x16\x18\x23\x19\x1a" \ - "\x24\x16\x17\x21\x15\x16\x21\x17\x17\x21\x15\x17\x20\x92\x93\x95" \ - "\x99\x9a\x9e\x16\x16\x20\x17\x19\x23\x1a\x1b\x25\x23\x25\x2c\xc4" \ - "\xc6\xc7\xf8\xfa\xfa\x61\x62\x68\x15\x16\x20\x11\x12\x1d\x44\x45" \ - "\x49\xea\xec\xec\xdf\xe1\xe2\x33\x35\x3b\x15\x16\x1f\x13\x14\x1d" \ - "\x78\x7a\x7c\xfc\xfd\xfe\xb2\xb4\xb7\x1a\x1c\x26\x18\x1b\x25\x1a" \ - "\x1b\x22\x19\x1b\x22\xae\xaf\xb0\x86\x88\x8b\x14\x16\x1e\x19\x1b" \ - "\x25\x17\x19\x21\x17\x19\x20\x12\x13\x1a\x2c\x2d\x32\xcc\xce\xcf" \ - "\xfb\xfe\xff\xd8\xdb\xdb\x72\x74\x76\x25\x26\x2c\x19\x1c\x24\x13" \ - "\x16\x1d\x11\x13\x18\x4c\x4d\x4f\xc1\xc3\xc3\xe2\xe4\xe6\xf6\xf8" \ - "\xf8\xb6\xb9\xb9\x4f\x51\x55\x1b\x1c\x23\x19\x1b\x23\x15\x17\x1f" \ - "\x70\x71\x72\xbb\xbb\xbc\xc3\xc4\xc5\xde\xe0\xe1\xf6\xf8\xf9\xfd" \ - "\xfe\xff\xe3\xe5\xe6\x7e\x81\x82\x21\x22\x27\x1d\x1f\x26\x94\x95" \ - "\x95\xbd\xbd\xbe\xbd\xbe\xbe\xc8\xca\xcb\xe5\xe7\xe9\xfa\xfc\xfd" \ - "\x52\x5f\xd3\xea\x00\x00\x00\x01\x62\x4b\x47\x44\x01\xff\x02\x2d" \ - "\xde\x00\x00\x00\xd2\x49\x44\x41\x54\x18\xd3\x63\x60\x00\x02\x46" \ - "\x26\x66\x16\x16\x56\x36\x76\x0e\x4e\x46\x06\x30\x60\xe4\xe2\xe6" \ - "\xe1\xe5\xe3\x17\x10\x14\x82\x0a\x08\x8b\x88\x8a\x89\x4b\x48\x4a" \ - "\x49\xcb\xc8\x82\xf9\x72\xf2\x0a\xdc\x8a\x4a\xca\x2a\xaa\x6a\xea" \ - "\x50\x1d\x1a\x9a\x5a\xda\x3a\xba\x7a\xfa\x06\x50\x1d\xb2\x86\x46" \ - "\x5a\xbc\xc6\x26\xa6\x66\xe6\x72\x10\xbe\x85\xa5\x95\xb5\x89\xa9" \ - "\x8d\xad\x9d\x3d\xc4\x08\x46\x07\x47\x27\x67\x17\x57\x37\x77\x0f" \ - "\x39\xa8\x11\x9e\x4a\xa6\x36\x5e\xde\x3e\xbe\x7e\x50\x23\xfc\x03" \ - "\x02\x55\x8c\x9d\x55\x82\x82\x43\xa0\x96\x86\x86\x85\x4b\x48\xb8" \ - "\x84\x47\x44\x46\x41\x75\x44\xc7\x78\xc7\xba\x87\xc7\xc5\x27\x40" \ - "\x75\xc8\x25\x26\x25\x7b\xa7\xa4\xa6\xa5\x67\x64\x42\x5d\x91\x95" \ - "\x9d\x93\x92\x9b\x97\x5f\x50\x58\x24\x0c\x55\x52\x5c\x52\x5a\x56" \ - "\x5e\x51\x59\x55\x5d\x53\x0b\x11\xa9\x93\xab\x6f\x68\x6c\x6a\x6e" \ - "\x69\x6d\x6b\xef\x60\x00\x00\x01\x53\x2a\x2a\x63\x34\xcd\xf7\x00" \ - "\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82" - -#define FAVICON_PANEL_32 \ - "\x89\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52" \ - "\x00\x00\x00\x20\x00\x00\x00\x20\x08\x06\x00\x00\x00\x73\x7a\x7a" \ - "\xf4\x00\x00\x00\x04\x67\x41\x4d\x41\x00\x00\xb1\x8f\x0b\xfc\x61" \ - "\x05\x00\x00\x00\x20\x63\x48\x52\x4d\x00\x00\x7a\x26\x00\x00\x80" \ - "\x84\x00\x00\xfa\x00\x00\x00\x80\xe8\x00\x00\x75\x30\x00\x00\xea" \ - "\x60\x00\x00\x3a\x98\x00\x00\x17\x70\x9c\xba\x51\x3c\x00\x00\x00" \ - "\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00" \ - "\x00\x07\x4a\x49\x44\x41\x54\x58\xc3\x9d\x97\xd9\x72\x5d\xc5\x15" \ - "\x86\xbf\xee\xde\xd3\xd9\x67\xd0\x99\x74\x34\x3b\x48\x32\x48\xe0" \ - "\x21\x60\xe1\x32\xd8\x15\x5e\x80\xdc\xf2\x00\xe4\x41\x12\x9e\x21" \ - "\x37\xc9\x43\xa4\x20\x95\xe2\x9a\x82\x0c\x10\x70\x62\x06\x0f\xc2" \ - "\x24\x15\xe2\x59\x96\xce\xbc\xe7\xb1\x73\x21\x93\x32\x20\x4b\xa7" \ - "\xe8\xaa\x7d\xb3\x2f\x7a\x7d\xf5\xff\xfd\xaf\x5e\x2d\xf2\x52\x6b" \ - "\x66\x58\x4a\xc0\x67\xff\xf8\x27\x6f\xbd\xf5\x36\xa3\xf1\x04\xad" \ - "\x0b\x84\x94\x68\xad\xc9\xf3\x1c\xc7\x76\x08\xa3\x04\xdb\xb6\xc8" \ - "\xf3\x12\x0d\xb8\x15\x9b\x28\x8c\x90\x52\xb0\xfe\xdc\x02\xbf\xff" \ - "\xdd\x6f\xb9\x72\xf9\x32\xc5\x53\x15\xe5\x2c\xc5\xbf\x5b\xdf\x7e" \ - "\x7b\x87\x34\x4b\x68\x36\xeb\x38\x8e\x4d\xb3\x59\xc7\xb6\x0d\x3a" \ - "\x9d\x16\xb6\x63\x52\x71\x6c\xea\xb5\x2a\x52\x0a\x6a\xd5\x0a\x96" \ - "\x65\x02\x1a\xc7\x36\xd9\xde\xda\x64\x6b\x6b\x9b\xf2\x07\x7b\x1a" \ - "\xb3\x16\xcf\x8a\x82\x4f\x3f\xbd\x4a\x18\x86\x34\x1a\x73\x68\x24" \ - "\x52\x1a\x08\xa1\xa8\x54\x1c\xa2\x30\xa4\xd9\xac\xd3\x9d\xef\xa0" \ - "\xb5\x66\x65\x65\x91\x30\x8c\x50\x52\xd3\x6a\xd5\xd9\xda\x7a\x81" \ - "\x56\xbb\xcd\x0f\xf5\x9e\x09\x40\x0a\x78\xb4\xf7\x98\x8f\x3f\xfe" \ - "\x8c\xa2\x28\x99\x4c\xa6\x94\x25\xe4\x59\x49\x14\x47\xc4\x51\x4c" \ - "\x92\x24\x54\x9c\x0a\xfd\xfe\x88\x3c\xcb\x89\xc2\x88\x2c\xcb\xd1" \ - "\x5a\x23\x74\xce\xf6\xf6\x36\x4a\xf0\x3d\xf9\x67\x06\x10\xc0\xd7" \ - "\xb7\xbf\xa1\x3f\x18\xb1\xb0\x30\x8f\xef\xfb\x98\xa6\x49\x96\x6b" \ - "\xda\x9d\x39\x6c\x4b\x31\x1c\x8e\xa9\xd5\xaa\x8c\xc7\x1e\xae\xeb" \ - "\x50\x71\xab\x0c\xfa\x63\x4c\x43\xb2\x79\xfa\x39\x2e\xbd\xf6\x1a" \ - "\x47\x1d\xb6\x99\x2d\xb8\x7a\xf5\x73\xee\xde\x79\x80\x6d\x9b\x64" \ - "\x79\x86\x6d\xdb\x64\x59\x86\x28\x73\x22\xa1\xa1\x2c\xb1\x2d\x03" \ - "\xd3\x54\x2c\x2e\xf6\x70\x6c\x87\xb2\x28\xa8\xd5\x1c\xce\x9f\x3b" \ - "\xc3\xda\xda\x29\x4a\xfd\x13\x00\x84\x00\xcf\x0f\xb8\x7e\xfd\x3a" \ - "\xbd\x85\x0e\x8e\x6d\x31\x99\x7a\x58\x96\xc5\x70\x38\x22\x8a\x63" \ - "\xd2\x24\xc5\xb2\x4c\x1e\xde\x7f\x44\x9c\xa4\xa4\x71\x42\x91\xe7" \ - "\x14\x65\x49\x96\xda\xac\xad\xad\xe2\x58\xe6\x8f\xe4\x9f\x09\x40" \ - "\x02\xb7\xbf\xf9\x86\x4f\x3e\xb9\x4a\x92\xc4\x68\x5d\xd2\x68\xd4" \ - "\x71\x9c\x0a\x4a\x49\x1c\xdb\xe2\xe0\x60\xc0\xe2\x52\x0f\x7f\xea" \ - "\xe3\xe4\x19\xae\x5b\xe5\xe0\x60\x88\x10\x8a\x4e\xbb\xc5\xeb\x97" \ - "\x2f\x3f\x73\xff\x99\x2c\xd8\xdd\xbd\xcd\x70\x34\x46\x6b\xcd\x74" \ - "\xea\x63\x99\x16\x79\x9e\x63\x5b\x06\x9e\x80\x2c\xcb\x99\x4e\x7d" \ - "\x92\xa4\xa0\xd9\x6a\xb1\xd0\xeb\x90\x65\x39\x96\x65\xb0\xb3\xf3" \ - "\x73\xce\xbc\x74\xe6\x47\xf1\x9b\x19\x20\x2b\x0a\xbe\xf8\xe2\x3a" \ - "\x95\x4a\x85\xb9\x46\x95\xfe\x60\x48\xb7\xdb\x65\x32\x1a\xa1\x94" \ - "\x62\x3a\x0d\x90\x52\x12\x85\x11\x7e\x90\x50\x14\x05\xfd\xbd\x3d" \ - "\xb2\x3c\xa3\xde\x70\x59\x5b\x5b\x65\xae\x39\xc7\xb3\xda\xdd\xb1" \ - "\x00\x52\xc0\x83\x47\x7b\x7c\xf8\xd1\xdf\x18\x8f\x27\x44\xbe\x07" \ - "\x42\xe1\xfb\x11\x85\x96\x74\xda\x6d\xb4\x50\xb8\xae\x4d\x51\x94" \ - "\x98\x56\x82\xeb\x3a\x0c\xfb\x03\x84\x90\x28\x25\x39\x7f\xfe\x3c" \ - "\x12\x28\x7e\x8a\x02\x02\xd8\xdd\xfd\x9a\xbd\xbd\x7d\x96\x16\x7b" \ - "\x84\xbe\x87\x46\x3e\xe9\x03\x9a\xfb\xf7\xf6\x88\x93\x84\x38\x76" \ - "\x28\x72\xa8\x56\x5d\xe6\xe7\x3b\x14\x79\x86\xd6\x70\xee\xdc\xf3" \ - "\x5c\xba\x74\x89\xe3\x7a\xfd\x89\x16\x5c\xbb\xf6\x25\xfd\x7e\x9f" \ - "\xb9\x46\x0d\x29\x25\xed\x76\x0b\x06\x23\x9a\xed\x16\x9e\x17\x81" \ - "\x00\x43\x29\x02\xcf\x47\x08\xf8\xd7\xed\x7f\x13\xc7\x09\x8d\xb9" \ - "\x3a\xa7\x37\x37\x58\x5e\x59\x39\x32\x7e\x27\x02\x08\x01\x5e\x10" \ - "\x70\xfd\xc6\x4d\x2a\x8e\x4d\x96\x66\x24\x49\x42\x9e\x17\x44\x51" \ - "\x8c\x32\x14\x68\x58\x5d\x5d\x24\x4f\x13\xa4\x94\xd4\xea\xd5\x43" \ - "\xf9\xa5\xa4\x2c\x33\x36\xd6\x9f\xc3\x32\x8c\x23\xe3\x77\x22\x80" \ - "\x7c\x22\xff\xd5\xcf\xae\xd2\xeb\x75\x48\xd3\x0c\xcb\xb2\x48\x93" \ - "\x84\x2c\xcb\x49\xa2\x98\x30\x88\x88\xa3\x94\xb2\x48\x31\x0c\x03" \ - "\x77\xbe\x45\xde\xa8\xe3\xe4\x05\xa7\xd6\x7a\x5c\xbe\x72\xe5\x24" \ - "\x81\x8f\xb7\xe0\xe6\xcd\x5b\x3c\x7c\xf0\x08\x29\x0c\x0a\xad\xe9" \ - "\x74\x5a\x20\x24\xcb\x2b\x8b\xa4\x69\x8a\x65\xd9\x28\x43\x31\x1a" \ - "\x44\x80\xe4\xe1\x83\x3d\x82\x20\xa2\x5a\xaf\xb2\xbd\xf5\x02\xdb" \ - "\x2f\xbe\xf8\xcc\xf8\x9d\x08\x90\xe5\x05\x37\x6f\xdc\xa2\xd1\x68" \ - "\x50\x6f\xcc\xb1\xbf\xdf\x27\x08\x62\xa2\x30\xc4\x77\x2c\xb4\x2e" \ - "\x69\xb7\x9b\x74\xe6\xbb\xa4\x69\x4a\xaf\xd7\x65\xd0\x1f\x20\xa4" \ - "\x40\x88\x92\x53\xa7\x56\xa9\xd7\x6a\xc7\xfa\xff\x4c\x00\x29\xe0" \ - "\xde\xc3\x87\x7c\xf0\xc1\x5f\x08\xc2\x14\x43\x05\x74\x3b\x73\x18" \ - "\x96\xc3\x74\x3c\x01\x34\xa3\xe1\x98\xd1\x70\x42\x7f\x30\xa1\x2c" \ - "\x72\xb2\x34\xc3\xa9\xb8\x28\x43\xd1\xee\x34\xb8\x70\xe1\x02\xe2" \ - "\x44\x03\x9e\x01\x70\x18\xbf\x5d\x06\xc3\x21\x8e\x63\x33\x19\x4f" \ - "\xb0\x2c\x93\x42\x2b\x1a\xcd\x26\xad\xa6\x4b\x51\xe4\xd4\x1b\x75" \ - "\x1e\xef\x1d\x00\x30\x1a\x4d\xf0\xbd\x90\x8a\x6b\xb1\xbe\x73\x96" \ - "\x57\x2f\x5e\x64\x96\x51\xeb\x48\x00\x0d\x7c\x7e\xed\x4b\x26\x93" \ - "\x29\xbd\x5e\x97\x40\x81\x53\xa9\xf0\xf8\xf1\x90\xf1\x68\xcc\xb0" \ - "\xbf\x8f\x69\x2a\x5c\xd7\xa5\x5a\xab\xd2\xed\xb6\xf1\xfd\x10\xdf" \ - "\x0f\x50\x0a\xd6\x7f\x76\x8a\x85\x85\xc5\x13\xe5\x87\x23\x46\xb2" \ - "\xc3\xdb\xcf\xe7\xda\xb5\x6b\x64\x69\x42\x7f\xbf\x4f\x92\xa4\xb8" \ - "\xd5\x0a\xcd\x66\x8d\x85\xc5\x0e\x08\x41\xa9\x05\x77\xff\x7b\x9f" \ - "\xc9\xd8\xc3\x0f\x42\x84\x28\x69\xb6\x1b\x74\xba\x2d\x5e\xd8\xda" \ - "\xc2\x54\xb3\x4d\x7b\xf2\xa8\x1f\xbb\xbb\xbb\xdc\xb8\x79\x8b\xc5" \ - "\xa5\x1e\xae\xeb\xa0\x81\x7b\x77\x1f\xe2\xf9\x01\xe8\x82\x7a\xdd" \ - "\xa5\x3b\xdf\xc2\xb4\x14\x52\x68\xa6\xe3\x31\x7b\x8f\x1e\x13\xf8" \ - "\x01\xed\x76\x8b\x2b\x33\xc4\xef\x58\x0b\xbe\xfa\xea\x16\x77\xee" \ - "\x3c\xc2\x75\x1d\x2c\xd3\xa0\xd9\x6e\xe2\xfb\x11\x59\x96\xb1\xbf" \ - "\xb7\x4f\x9e\x17\x28\x29\x31\x2d\x83\xd5\xce\x12\x42\x1c\xaa\xa1" \ - "\x94\xe2\xf4\xe6\x3a\xa7\x9f\x7f\xfe\xc4\xf8\x3d\x13\x20\xcd\x0b" \ - "\xae\xdf\xb8\x49\xad\xd1\xc0\x50\x8a\xe9\x64\x42\x56\x68\xa4\x54" \ - "\x74\x3b\x4d\x3c\xcf\x27\x89\x13\xc2\x20\x24\x0c\x02\x04\x1a\xcb" \ - "\xb6\x69\x34\x1b\x54\xaa\x15\x4e\x6f\x6e\x50\x75\xdd\x99\xfc\xff" \ - "\x11\x80\x14\x70\xe7\xfe\x3d\x3e\xfa\xf0\xcf\x58\xa6\xa2\x56\xab" \ - "\x61\x59\x26\x69\x9a\x32\x9d\x78\xa0\x0b\x40\xb3\xb4\xbc\x80\xe7" \ - "\x79\x24\x69\x4c\xe0\x07\x8c\xfa\x63\x84\x61\xd0\x68\xb8\x9c\x39" \ - "\x7b\x96\xa2\xd4\x08\x31\x4b\x08\x7f\x00\x20\x80\x7b\xf7\xef\x93" \ - "\xc4\x01\xa1\x37\xc1\x9f\x4e\x59\x5a\x5e\xc2\xb2\x4c\x2c\xcb\x24" \ - "\x8e\x22\xbc\xa9\x47\x59\x14\x08\x43\x31\xbf\xd0\xc5\x75\x1d\xee" \ - "\xfc\xe7\x2e\x52\xc1\xea\xca\x12\x4b\xcb\x2b\x0c\x87\x23\xea\xf5" \ - "\xfa\x93\x77\xc1\xf1\x4b\xfd\xfa\x37\xef\xbc\xf3\x34\x41\xbd\x56" \ - "\x67\x71\xb9\xc7\x78\x32\xe2\xe0\xf1\x3e\x81\x17\xe0\x79\x3e\xdd" \ - "\x6e\x07\xc3\x30\x10\x42\x20\xa4\xc4\x9b\x7a\x68\x01\xba\x28\x91" \ - "\x4a\xd2\x6c\xd6\x79\xe9\xa5\x2d\x76\x76\x76\xc8\x8b\x02\x21\x04" \ - "\xa6\x69\xa0\x94\x3a\x56\x8d\xef\x03\x00\xae\x5b\xe1\xdc\xd9\xb3" \ - "\xbc\xfc\xca\xcb\x38\xae\xcd\x64\x32\x66\xd0\x1f\x10\xf8\x3e\x41" \ - "\x10\xd2\x5b\xe8\x61\x3b\x0e\x49\x92\x52\x16\x05\xde\xd4\x23\x4d" \ - "\x12\x2c\x5b\xf1\xfa\x6b\x97\xd8\xd8\xd8\x20\x4b\x53\xf2\x3c\x07" \ - "\x0e\x21\xbe\x03\x9f\x09\xe0\x50\x09\xc1\x42\xaf\xc7\xc5\x8b\x17" \ - "\x59\xdf\x5c\x47\x8b\x92\xc9\x74\xc2\x68\x30\x22\x89\x53\xa2\x30" \ - "\xa6\x56\xab\xd2\x9d\xef\x12\xf8\x11\x5a\xc3\xf2\x52\x8f\x5f\xbe" \ - "\xf9\x26\x8d\x46\x9d\xa2\x28\xc8\xb2\x9c\x2c\xcf\x0f\x7d\x36\x0c" \ - "\x0c\xc3\x3c\x12\xe2\x68\x00\x0e\xbb\xa1\x65\x59\x9c\xde\xdc\xe4" \ - "\xc2\x2b\xaf\xd0\x6c\xcd\x31\x1a\x8f\x89\xe3\x98\xe9\x68\x8c\x2e" \ - "\x0b\xb4\x06\xa9\x14\xcd\x56\x93\xed\xad\x4d\xde\x78\xe3\x17\x48" \ - "\xa9\x00\x8d\x2e\x4b\x8a\x22\x27\xcf\x0f\x5f\x47\x87\x10\x06\x52" \ - "\x7e\x1f\xe2\xd8\xeb\xf8\xbb\x41\x72\x75\x75\x95\x5f\xbd\xfd\x36" \ - "\xaf\xee\xec\xf0\xa7\xf7\xdf\xe7\xdd\x3f\xfc\x91\x83\xfd\x7d\xa6" \ - "\xe3\x11\x85\x96\xd8\x8e\xc9\xd2\xe2\x02\x8e\xe3\xa0\xff\x3f\x7d" \ - "\x6a\x8a\x3c\x27\x0c\x43\x84\x10\x68\x0d\x5a\x6b\xaa\xd5\x2a\xea" \ - "\xa9\x2e\x39\xd3\x58\xae\x35\x98\x86\xc9\xab\x3b\x3b\xac\x9d\x3a" \ - "\xc5\xc6\xfa\x3a\xef\xbe\xf7\x1e\x7f\xff\xeb\xa7\xe4\x71\x46\x99" \ - "\xe7\xac\xaf\xaf\xa3\x94\xa2\x2c\xf5\x93\x2b\x59\x22\xe5\xe1\xa7" \ - "\x35\x94\x65\x49\x59\x16\x68\x5d\xf2\x74\x03\xfe\x1f\xc2\x60\x72" \ - "\xe2\x6a\x9b\x4e\x8f\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60" \ - "\x82" diff --git a/tools/esp8266/hmDefines.h b/tools/esp8266/hmDefines.h index baec0f75..4fd8bd5f 100644 --- a/tools/esp8266/hmDefines.h +++ b/tools/esp8266/hmDefines.h @@ -17,19 +17,21 @@ union serial_u { // units -enum {UNIT_V = 0, UNIT_A, UNIT_W, UNIT_WH, UNIT_KWH, UNIT_HZ, UNIT_C, UNIT_PCT, UNIT_VA, UNIT_NONE}; -const char* const units[] = {"V", "A", "W", "Wh", "kWh", "Hz", "°C", "%","VAr",""}; - +enum {UNIT_V = 0, UNIT_A, UNIT_W, UNIT_WH, UNIT_KWH, UNIT_HZ, UNIT_C, UNIT_PCT, UNIT_VAR, UNIT_NONE}; +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_LAST_ALARM_CODE}; + FLD_UAC, FLD_IAC, FLD_PAC, FLD_F, FLD_T, FLD_PF, FLD_EFF, + FLD_IRR, FLD_Q, FLD_EVT, FLD_FW_VERSION, FLD_FW_BUILD_YEAR, + FLD_FW_BUILD_MONTH_DAY, FLD_FW_BUILD_HOUR_MINUTE, FLD_HW_ID, + FLD_ACT_ACTIVE_PWR_LIMIT, /*FLD_ACT_REACTIVE_PWR_LIMIT, FLD_ACT_PF,*/ 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","LastAlarmCode"}; + "U_AC", "I_AC", "P_AC", "F_AC", "Temp", "PF_AC", "Efficiency", "Irradiation","Q_AC", + "ALARM_MES_ID","FWVersion","FWBuildYear","FWBuildMonthDay","FWBuildHourMinute","HWPartId", + "active PowerLimit", /*"reactive PowerLimit","Powerfactor",*/ "LastAlarmCode"}; +const char* const notAvail = "n/a"; // 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}; @@ -53,7 +55,7 @@ const byteAssign_fieldDeviceClass deviceFieldAssignment[] = { {FLD_PAC, DEVICE_CLS_PWR, STATE_CLS_MEASUREMENT}, {FLD_F, DEVICE_CLS_FREQ, STATE_CLS_NONE}, {FLD_T, DEVICE_CLS_TEMP, STATE_CLS_MEASUREMENT}, - {FLD_PCT, DEVICE_CLS_NONE, STATE_CLS_NONE}, + {FLD_PF, DEVICE_CLS_NONE, STATE_CLS_NONE}, {FLD_EFF, DEVICE_CLS_NONE, STATE_CLS_NONE}, {FLD_IRR, DEVICE_CLS_NONE, STATE_CLS_NONE} }; @@ -92,12 +94,15 @@ const byteAssign_t InfoAssignment[] = { { FLD_FW_VERSION, UNIT_NONE, CH0, 0, 2, 1 }, { FLD_FW_BUILD_YEAR, UNIT_NONE, CH0, 2, 2, 1 }, { FLD_FW_BUILD_MONTH_DAY, UNIT_NONE, CH0, 4, 2, 1 }, + { FLD_FW_BUILD_HOUR_MINUTE, UNIT_NONE, CH0, 6, 2, 1 }, { FLD_HW_ID, UNIT_NONE, CH0, 8, 2, 1 } }; #define HMINFO_LIST_LEN (sizeof(InfoAssignment) / sizeof(byteAssign_t)) const byteAssign_t SystemConfigParaAssignment[] = { - { FLD_ACT_PWR_LIMIT, UNIT_PCT, CH0, 2, 2, 10 } + { FLD_ACT_ACTIVE_PWR_LIMIT, UNIT_PCT, CH0, 2, 2, 10 }/*, + { FLD_ACT_REACTIVE_PWR_LIMIT, UNIT_PCT, CH0, 4, 2, 10 }, + { FLD_ACT_PF, UNIT_NONE, CH0, 6, 2, 1000 }*/ }; #define HMSYSTEM_LIST_LEN (sizeof(SystemConfigParaAssignment) / sizeof(byteAssign_t)) @@ -112,24 +117,25 @@ const byteAssign_t AlarmDataAssignment[] = { // HM300, HM350, HM400 //------------------------------------- const byteAssign_t hm1chAssignment[] = { - { FLD_UDC, UNIT_V, CH1, 2, 2, 10 }, - { FLD_IDC, UNIT_A, CH1, 4, 2, 100 }, - { FLD_PDC, UNIT_W, CH1, 6, 2, 10 }, - { FLD_YD, UNIT_WH, CH1, 12, 2, 1 }, - { FLD_YT, UNIT_KWH, CH1, 8, 4, 1000 }, - { FLD_IRR, UNIT_PCT, CH1, CALC_IRR_CH, CH1, CMD_CALC }, - - { FLD_UAC, UNIT_V, CH0, 14, 2, 10 }, - { FLD_IAC, UNIT_A, CH0, 22, 2, 100 }, - { FLD_PAC, UNIT_W, CH0, 18, 2, 10 }, - { FLD_PRA, UNIT_VA, CH0, 20, 2, 10 }, - { FLD_F, UNIT_HZ, CH0, 16, 2, 100 }, - { FLD_T, UNIT_C, CH0, 26, 2, 10 }, - { FLD_ALARM_MES_ID, UNIT_NONE, CH0, 24, 2, 1 }, - { FLD_YD, UNIT_WH, CH0, CALC_YD_CH0, 0, CMD_CALC }, - { FLD_YT, UNIT_KWH, CH0, CALC_YT_CH0, 0, CMD_CALC }, - { FLD_PDC, UNIT_W, CH0, CALC_PDC_CH0, 0, CMD_CALC }, - { FLD_EFF, UNIT_PCT, CH0, CALC_EFF_CH0, 0, CMD_CALC } + { FLD_UDC, UNIT_V, CH1, 2, 2, 10 }, + { FLD_IDC, UNIT_A, CH1, 4, 2, 100 }, + { FLD_PDC, UNIT_W, CH1, 6, 2, 10 }, + { FLD_YD, UNIT_WH, CH1, 12, 2, 1 }, + { FLD_YT, UNIT_KWH, CH1, 8, 4, 1000 }, + { FLD_IRR, UNIT_PCT, CH1, CALC_IRR_CH, CH1, CMD_CALC }, + + { FLD_UAC, UNIT_V, CH0, 14, 2, 10 }, + { FLD_IAC, UNIT_A, CH0, 22, 2, 100 }, + { FLD_PAC, UNIT_W, CH0, 18, 2, 10 }, + { FLD_Q, UNIT_VAR, CH0, 20, 2, 10 }, + { FLD_F, UNIT_HZ, CH0, 16, 2, 100 }, + { FLD_PF, UNIT_NONE, CH0, 24, 2, 1000 }, + { FLD_T, UNIT_C, CH0, 26, 2, 10 }, + { FLD_EVT, UNIT_NONE, CH0, 28, 2, 1 }, + { FLD_YD, UNIT_WH, CH0, CALC_YD_CH0, 0, CMD_CALC }, + { FLD_YT, UNIT_KWH, CH0, CALC_YT_CH0, 0, CMD_CALC }, + { FLD_PDC, UNIT_W, CH0, CALC_PDC_CH0, 0, CMD_CALC }, + { FLD_EFF, UNIT_PCT, CH0, CALC_EFF_CH0, 0, CMD_CALC } }; #define HM1CH_LIST_LEN (sizeof(hm1chAssignment) / sizeof(byteAssign_t)) @@ -138,31 +144,32 @@ const byteAssign_t hm1chAssignment[] = { // HM600, HM700, HM800 //------------------------------------- const byteAssign_t hm2chAssignment[] = { - { FLD_UDC, UNIT_V, CH1, 2, 2, 10 }, - { FLD_IDC, UNIT_A, CH1, 4, 2, 100 }, - { FLD_PDC, UNIT_W, CH1, 6, 2, 10 }, - { FLD_YD, UNIT_WH, CH1, 22, 2, 1 }, - { FLD_YT, UNIT_KWH, CH1, 14, 4, 1000 }, - { FLD_IRR, UNIT_PCT, CH1, CALC_IRR_CH, CH1, CMD_CALC }, - - { FLD_UDC, UNIT_V, CH2, 8, 2, 10 }, - { FLD_IDC, UNIT_A, CH2, 10, 2, 100 }, - { FLD_PDC, UNIT_W, CH2, 12, 2, 10 }, - { FLD_YD, UNIT_WH, CH2, 24, 2, 1 }, - { FLD_YT, UNIT_KWH, CH2, 18, 4, 1000 }, - { FLD_IRR, UNIT_PCT, CH2, CALC_IRR_CH, CH2, CMD_CALC }, - - { FLD_UAC, UNIT_V, CH0, 26, 2, 10 }, - { FLD_IAC, UNIT_A, CH0, 34, 2, 100 }, - { FLD_PAC, UNIT_W, CH0, 30, 2, 10 }, - { FLD_PRA, UNIT_VA, CH0, 32, 2, 10 }, - { FLD_F, UNIT_HZ, CH0, 28, 2, 100 }, - { FLD_T, UNIT_C, CH0, 38, 2, 10 }, - { FLD_ALARM_MES_ID, UNIT_NONE, CH0, 40, 2, 1 }, - { FLD_YD, UNIT_WH, CH0, CALC_YD_CH0, 0, CMD_CALC }, - { FLD_YT, UNIT_KWH, CH0, CALC_YT_CH0, 0, CMD_CALC }, - { FLD_PDC, UNIT_W, CH0, CALC_PDC_CH0, 0, CMD_CALC }, - { FLD_EFF, UNIT_PCT, CH0, CALC_EFF_CH0, 0, CMD_CALC } + { FLD_UDC, UNIT_V, CH1, 2, 2, 10 }, + { FLD_IDC, UNIT_A, CH1, 4, 2, 100 }, + { FLD_PDC, UNIT_W, CH1, 6, 2, 10 }, + { FLD_YD, UNIT_WH, CH1, 22, 2, 1 }, + { FLD_YT, UNIT_KWH, CH1, 14, 4, 1000 }, + { FLD_IRR, UNIT_PCT, CH1, CALC_IRR_CH, CH1, CMD_CALC }, + + { FLD_UDC, UNIT_V, CH2, 8, 2, 10 }, + { FLD_IDC, UNIT_A, CH2, 10, 2, 100 }, + { FLD_PDC, UNIT_W, CH2, 12, 2, 10 }, + { FLD_YD, UNIT_WH, CH2, 24, 2, 1 }, + { FLD_YT, UNIT_KWH, CH2, 18, 4, 1000 }, + { FLD_IRR, UNIT_PCT, CH2, CALC_IRR_CH, CH2, CMD_CALC }, + + { FLD_UAC, UNIT_V, CH0, 26, 2, 10 }, + { FLD_IAC, UNIT_A, CH0, 34, 2, 100 }, + { FLD_PAC, UNIT_W, CH0, 30, 2, 10 }, + { FLD_Q, UNIT_VAR, CH0, 32, 2, 10 }, + { FLD_F, UNIT_HZ, CH0, 28, 2, 100 }, + { FLD_PF, UNIT_NONE, CH0, 36, 2, 1000 }, + { FLD_T, UNIT_C, CH0, 38, 2, 10 }, + { FLD_EVT, UNIT_NONE, CH0, 40, 2, 1 }, + { FLD_YD, UNIT_WH, CH0, CALC_YD_CH0, 0, CMD_CALC }, + { FLD_YT, UNIT_KWH, CH0, CALC_YT_CH0, 0, CMD_CALC }, + { FLD_PDC, UNIT_W, CH0, CALC_PDC_CH0, 0, CMD_CALC }, + { FLD_EFF, UNIT_PCT, CH0, CALC_EFF_CH0, 0, CMD_CALC } }; #define HM2CH_LIST_LEN (sizeof(hm2chAssignment) / sizeof(byteAssign_t)) @@ -172,46 +179,46 @@ const byteAssign_t hm2chAssignment[] = { // HM1200, HM1500 //------------------------------------- const byteAssign_t hm4chAssignment[] = { - { FLD_UDC, UNIT_V, CH1, 2, 2, 10 }, - { FLD_IDC, UNIT_A, CH1, 4, 2, 100 }, - { FLD_PDC, UNIT_W, CH1, 8, 2, 10 }, - { FLD_YD, UNIT_WH, CH1, 20, 2, 1 }, - { FLD_YT, UNIT_KWH, CH1, 12, 4, 1000 }, - { FLD_IRR, UNIT_PCT, CH1, CALC_IRR_CH, CH1, CMD_CALC }, - - { FLD_UDC, UNIT_V, CH2, CALC_UDC_CH, CH1, CMD_CALC }, - { FLD_IDC, UNIT_A, CH2, 6, 2, 100 }, - { FLD_PDC, UNIT_W, CH2, 10, 2, 10 }, - { FLD_YD, UNIT_WH, CH2, 22, 2, 1 }, - { FLD_YT, UNIT_KWH, CH2, 16, 4, 1000 }, - { FLD_IRR, UNIT_PCT, CH2, CALC_IRR_CH, CH2, CMD_CALC }, - - { FLD_UDC, UNIT_V, CH3, 24, 2, 10 }, - { FLD_IDC, UNIT_A, CH3, 26, 2, 100 }, - { FLD_PDC, UNIT_W, CH3, 30, 2, 10 }, - { FLD_YD, UNIT_WH, CH3, 42, 2, 1 }, - { FLD_YT, UNIT_KWH, CH3, 34, 4, 1000 }, - { FLD_IRR, UNIT_PCT, CH3, CALC_IRR_CH, CH3, CMD_CALC }, - - { FLD_UDC, UNIT_V, CH4, CALC_UDC_CH, CH3, CMD_CALC }, - { FLD_IDC, UNIT_A, CH4, 28, 2, 100 }, - { FLD_PDC, UNIT_W, CH4, 32, 2, 10 }, - { FLD_YD, UNIT_WH, CH4, 44, 2, 1 }, - { FLD_YT, UNIT_KWH, CH4, 38, 4, 1000 }, - { FLD_IRR, UNIT_PCT, CH4, CALC_IRR_CH, CH4, CMD_CALC }, - - { FLD_UAC, UNIT_V, CH0, 46, 2, 10 }, - { FLD_IAC, UNIT_A, CH0, 54, 2, 100 }, - { FLD_PAC, UNIT_W, CH0, 50, 2, 10 }, - { FLD_PRA, UNIT_VA, CH0, 52, 2, 10 }, - { FLD_F, UNIT_HZ, CH0, 48, 2, 100 }, - { FLD_PCT, UNIT_PCT, CH0, 56, 2, 10 }, - { FLD_T, UNIT_C, CH0, 58, 2, 10 }, - { FLD_ALARM_MES_ID, UNIT_NONE, CH0, 60, 2, 1 }, - { FLD_YD, UNIT_WH, CH0, CALC_YD_CH0, 0, CMD_CALC }, - { FLD_YT, UNIT_KWH, CH0, CALC_YT_CH0, 0, CMD_CALC }, - { FLD_PDC, UNIT_W, CH0, CALC_PDC_CH0, 0, CMD_CALC }, - { FLD_EFF, UNIT_PCT, CH0, CALC_EFF_CH0, 0, CMD_CALC } + { FLD_UDC, UNIT_V, CH1, 2, 2, 10 }, + { FLD_IDC, UNIT_A, CH1, 4, 2, 100 }, + { FLD_PDC, UNIT_W, CH1, 8, 2, 10 }, + { FLD_YD, UNIT_WH, CH1, 20, 2, 1 }, + { FLD_YT, UNIT_KWH, CH1, 12, 4, 1000 }, + { FLD_IRR, UNIT_PCT, CH1, CALC_IRR_CH, CH1, CMD_CALC }, + + { FLD_UDC, UNIT_V, CH2, CALC_UDC_CH, CH1, CMD_CALC }, + { FLD_IDC, UNIT_A, CH2, 6, 2, 100 }, + { FLD_PDC, UNIT_W, CH2, 10, 2, 10 }, + { FLD_YD, UNIT_WH, CH2, 22, 2, 1 }, + { FLD_YT, UNIT_KWH, CH2, 16, 4, 1000 }, + { FLD_IRR, UNIT_PCT, CH2, CALC_IRR_CH, CH2, CMD_CALC }, + + { FLD_UDC, UNIT_V, CH3, 24, 2, 10 }, + { FLD_IDC, UNIT_A, CH3, 26, 2, 100 }, + { FLD_PDC, UNIT_W, CH3, 30, 2, 10 }, + { FLD_YD, UNIT_WH, CH3, 42, 2, 1 }, + { FLD_YT, UNIT_KWH, CH3, 34, 4, 1000 }, + { FLD_IRR, UNIT_PCT, CH3, CALC_IRR_CH, CH3, CMD_CALC }, + + { FLD_UDC, UNIT_V, CH4, CALC_UDC_CH, CH3, CMD_CALC }, + { FLD_IDC, UNIT_A, CH4, 28, 2, 100 }, + { FLD_PDC, UNIT_W, CH4, 32, 2, 10 }, + { FLD_YD, UNIT_WH, CH4, 44, 2, 1 }, + { FLD_YT, UNIT_KWH, CH4, 38, 4, 1000 }, + { FLD_IRR, UNIT_PCT, CH4, CALC_IRR_CH, CH4, CMD_CALC }, + + { FLD_UAC, UNIT_V, CH0, 46, 2, 10 }, + { FLD_IAC, UNIT_A, CH0, 54, 2, 100 }, + { FLD_PAC, UNIT_W, CH0, 50, 2, 10 }, + { FLD_Q, UNIT_VAR, CH0, 52, 2, 10 }, + { FLD_F, UNIT_HZ, CH0, 48, 2, 100 }, + { FLD_PF, UNIT_NONE, CH0, 56, 2, 1000 }, + { FLD_T, UNIT_C, CH0, 58, 2, 10 }, + { FLD_EVT, UNIT_NONE, CH0, 60, 2, 1 }, + { FLD_YD, UNIT_WH, CH0, CALC_YD_CH0, 0, CMD_CALC }, + { FLD_YT, UNIT_KWH, CH0, CALC_YT_CH0, 0, CMD_CALC }, + { FLD_PDC, UNIT_W, CH0, CALC_PDC_CH0, 0, CMD_CALC }, + { FLD_EFF, UNIT_PCT, CH0, CALC_EFF_CH0, 0, CMD_CALC } }; #define HM4CH_LIST_LEN (sizeof(hm4chAssignment) / sizeof(byteAssign_t)) diff --git a/tools/esp8266/hmInverter.h b/tools/esp8266/hmInverter.h index fa8c557e..aeeacde1 100644 --- a/tools/esp8266/hmInverter.h +++ b/tools/esp8266/hmInverter.h @@ -23,7 +23,7 @@ */ // forward declaration of class -template +template class Inverter; @@ -55,17 +55,23 @@ struct calcFunc_t { func_t* func; // function pointer }; +template +struct record_t { + byteAssign_t* assign; // assigment of bytes in payload + uint8_t length; // length of the assignment list + T *record; // data pointer + uint32_t ts; // timestamp of last received payload +}; class CommandAbstract { public: - CommandAbstract(uint8_t txType = 0, uint8_t cmd = 0){ + CommandAbstract(uint8_t txType = 0, uint8_t cmd = 0) { _TxType = txType; _Cmd = cmd; }; virtual ~CommandAbstract() {}; - const uint8_t getCmd() - { + const uint8_t getCmd() { return _Cmd; } @@ -75,11 +81,11 @@ class CommandAbstract { }; class InfoCommand : public CommandAbstract { -public: - InfoCommand(uint8_t cmd){ - _TxType = 0x15; - _Cmd = cmd; - } + public: + InfoCommand(uint8_t cmd){ + _TxType = 0x15; + _Cmd = cmd; + } }; // list of all available functions, mapped in hmDefines.h @@ -94,35 +100,34 @@ const calcFunc_t calcFunctions[] = { }; -template +template class Inverter { public: - uint8_t id; // unique id + uint8_t id; // unique id char name[MAX_NAME_LENGTH]; // human readable name, eg. "HM-600.1" - uint8_t type; // integer which refers to inverter type - byteAssign_t* assign; // type of inverter - uint8_t listLen; // length of assignments - uint16_t alarmMesIndex; // Last recorded Alarm Message Index - uint16_t fwVersion; // Firmware Version from Info Command Request - uint16_t powerLimit[2]; // limit power output - uint16_t actPowerLimit; // - uint8_t devControlCmd; // carries the requested cmd - bool devControlRequest; // true if change needed - serial_u serial; // serial number as on barcode - serial_u radioId; // id converted to modbus - uint8_t channels; // number of PV channels (1-4) - uint32_t ts; // timestamp of last received payload - 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 + uint8_t type; // integer which refers to inverter type + uint16_t alarmMesIndex; // Last recorded Alarm Message Index + uint16_t fwVersion; // Firmware Version from Info Command Request + uint16_t powerLimit[2]; // limit power output + float actPowerLimit; // actual power limit + uint8_t devControlCmd; // carries the requested cmd + bool devControlRequest; // true if change needed + serial_u serial; // serial number as on barcode + serial_u radioId; // id converted to modbus + uint8_t channels; // number of PV channels (1-4) + record_t recordMeas; // structure for measured values + record_t recordInfo; // structure for info values + record_t recordConfig; // structure for system config values + record_t recordAlarm; // structure for alarm values + uint16_t chMaxPwr[4]; // maximum power of the modules (Wp) + char chName[4][MAX_NAME_LENGTH]; // human readable name for channels String lastAlarmMsg; - bool initialized; // needed to check if the inverter was correctly added (ESP32 specific - union types are never null) + 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] = NoPowerLimit; // - actPowerLimit = 0xffff; // init feedback from inverter to -1 + powerLimit[0] = 0xffff; // 65535 W Limit -> unlimited + powerLimit[1] = AbsolutNonPersistent; // default power limit setting + actPowerLimit = 0xffff; // init feedback from inverter to -1 devControlRequest = false; devControlCmd = InitDataState; initialized = false; @@ -136,27 +141,26 @@ class Inverter { } template - void enqueCommand(uint8_t cmd) - { + void enqueCommand(uint8_t cmd) { _commandQueue.push(std::make_shared(cmd)); DPRINTLN(DBG_INFO, "enqueuedCmd: " + String(cmd)); } - void setQueuedCmdFinished(){ - if (!_commandQueue.empty()){ + void setQueuedCmdFinished() { + if (!_commandQueue.empty()) { // Will destroy CommandAbstract Class Object (?) _commandQueue.pop(); } } - void clearCmdQueue(){ - while (!_commandQueue.empty()){ + void clearCmdQueue() { + while (!_commandQueue.empty()) { // Will destroy CommandAbstract Class Object (?) _commandQueue.pop(); } } - uint8_t getQueuedCmd() - { + + uint8_t getQueuedCmd() { if (_commandQueue.empty()){ // Fill with default commands enqueCommand(RealTimeRunData_Debug); @@ -175,394 +179,289 @@ class Inverter { void init(void) { DPRINTLN(DBG_VERBOSE, F("hmInverter.h:init")); - getAssignment(); + initAssignment(&recordMeas, RealTimeRunData_Debug); + initAssignment(&recordInfo, InverterDevInform_All); + initAssignment(&recordConfig, SystemConfigPara); + initAssignment(&recordAlarm, AlarmData); toRadioId(); - record = new RECORDTYPE[listLen]; memset(name, 0, MAX_NAME_LENGTH); memset(chName, 0, MAX_NAME_LENGTH * 4); - memset(record, 0, sizeof(RECORDTYPE) * listLen); initialized = true; } - uint8_t getPosByChFld(uint8_t channel, uint8_t fieldId) { + uint8_t getPosByChFld(uint8_t channel, uint8_t fieldId, record_t<> *rec) { DPRINTLN(DBG_VERBOSE, F("hmInverter.h:getPosByChFld")); uint8_t pos = 0; - for(; pos < listLen; pos++) { - if((assign[pos].ch == channel) && (assign[pos].fieldId == fieldId)) - break; + if(NULL != rec) { + for(; pos < rec->length; pos++) { + if((rec->assign[pos].ch == channel) && (rec->assign[pos].fieldId == fieldId)) + break; + } + return (pos >= rec->length) ? 0xff : pos; } - return (pos >= listLen) ? 0xff : pos; + else + return 0xff; } - const char *getFieldName(uint8_t pos) { + byteAssign_t *getByteAssign(uint8_t pos, record_t<> *rec) { + return &rec->assign[pos]; + } + + const char *getFieldName(uint8_t pos, record_t<> *rec) { DPRINTLN(DBG_VERBOSE, F("hmInverter.h:getFieldName")); - return fields[assign[pos].fieldId]; + if(NULL != rec) + return fields[rec->assign[pos].fieldId]; + return notAvail; } - const char *getUnit(uint8_t pos) { + const char *getUnit(uint8_t pos, record_t<> *rec) { DPRINTLN(DBG_VERBOSE, F("hmInverter.h:getUnit")); - return units[assign[pos].unitId]; + if(NULL != rec) + return units[rec->assign[pos].unitId]; + return notAvail; } - uint8_t getChannel(uint8_t pos) { + uint8_t getChannel(uint8_t pos, record_t<> *rec) { DPRINTLN(DBG_VERBOSE, F("hmInverter.h:getChannel")); - return assign[pos].ch; + if(NULL != rec) + return rec->assign[pos].ch; + return 0; } - void addValue(uint8_t pos, uint8_t buf[]) { + void addValue(uint8_t pos, uint8_t buf[], record_t<> *rec) { DPRINTLN(DBG_VERBOSE, F("hmInverter.h:addValue")); - uint8_t cmd = getQueuedCmd(); - uint8_t ptr = assign[pos].start; - uint8_t end = ptr + assign[pos].num; - uint16_t div = assign[pos].div; - if(CMD_CALC != div) { - uint32_t val = 0; - do { - val <<= 8; - val |= buf[ptr]; - } while(++ptr != end); - 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){ - if (alarmMesIndex < record[pos]){ - alarmMesIndex = record[pos]; - //enqueCommand(AlarmUpdate); // What is the function of AlarmUpdate? - enqueCommand(AlarmData); + if(NULL != rec) { + uint8_t ptr = rec->assign[pos].start; + uint8_t end = ptr + rec->assign[pos].num; + uint16_t div = rec->assign[pos].div; + + if(NULL != rec) { + if(CMD_CALC != div) { + uint32_t val = 0; + do { + val <<= 8; + val |= buf[ptr]; + } while(++ptr != end); + if ((REC_TYP)(div) > 1) + rec->record[pos] = (REC_TYP)(val) / (REC_TYP)(div); + else + rec->record[pos] = (REC_TYP)(val); } - else { - alarmMesIndex = record[pos]; // no change + } + + if(rec == &recordMeas) { + DPRINTLN(DBG_VERBOSE, "add real time"); + + // get last alarm message index and save it in the inverter object + if (getPosByChFld(0, FLD_EVT, rec) == pos){ + if (alarmMesIndex < rec->record[pos]){ + alarmMesIndex = rec->record[pos]; + //enqueCommand(AlarmUpdate); // What is the function of AlarmUpdate? + enqueCommand(AlarmData); + } + else { + alarmMesIndex = rec->record[pos]; // no change + } } } - } - if (cmd == InverterDevInform_All) { - // get at least the firmware version and save it to the inverter object - if (getPosByChFld(0, FLD_FW_VERSION) == pos){ - fwVersion = record[pos]; - DPRINT(DBG_DEBUG, F("Inverter FW-Version: ") + String(fwVersion)); + else if (rec->assign == InfoAssignment) { + DPRINTLN(DBG_DEBUG, "add info"); + // get at least the firmware version and save it to the inverter object + if (getPosByChFld(0, FLD_FW_VERSION, rec) == pos){ + fwVersion = rec->record[pos]; + DPRINT(DBG_DEBUG, F("Inverter FW-Version: ") + String(fwVersion)); + } } - } - if (cmd == SystemConfigPara) { - // get at least the firmware version and save it to the inverter object - if (getPosByChFld(0, FLD_ACT_PWR_LIMIT) == pos){ - actPowerLimit = record[pos]; - DPRINT(DBG_DEBUG, F("Inverter actual power limit: ") + String(actPowerLimit)); + else if (rec->assign == SystemConfigParaAssignment) { + DPRINTLN(DBG_DEBUG, "add config"); + // get at least the firmware version and save it to the inverter object + if (getPosByChFld(0, FLD_ACT_ACTIVE_PWR_LIMIT, rec) == pos){ + actPowerLimit = rec->record[pos]; + DPRINT(DBG_DEBUG, F("Inverter actual power limit: ") + String(actPowerLimit, 1)); + } } - } - if (cmd == AlarmData){ - if (getPosByChFld(0, FLD_LAST_ALARM_CODE) == pos){ - lastAlarmMsg = getAlarmStr(record[pos]); + else if (rec->assign == AlarmDataAssignment) { + DPRINTLN(DBG_DEBUG, "add alarm"); + if (getPosByChFld(0, FLD_LAST_ALARM_CODE, rec) == pos){ + lastAlarmMsg = getAlarmStr(rec->record[pos]); + } } + else + DPRINTLN(DBG_WARN, F("add with unknown assginment")); } + else + DPRINTLN(DBG_ERROR, F("addValue: assignment not found with cmd 0x")); } - RECORDTYPE getValue(uint8_t pos) { + REC_TYP getValue(uint8_t pos, record_t<> *rec) { DPRINTLN(DBG_VERBOSE, F("hmInverter.h:getValue")); - return record[pos]; + if(NULL == rec) + return 0; + return rec->record[pos]; } void doCalculations() { DPRINTLN(DBG_VERBOSE, F("hmInverter.h:doCalculations")); - uint8_t cmd = getQueuedCmd(); - getAssignment(); - if (cmd == RealTimeRunData_Debug){ - for(uint8_t i = 0; i < listLen; i++) { - if(CMD_CALC == assign[i].div) { - record[i] = calcFunctions[assign[i].start].func(this, assign[i].num); - } - yield(); + record_t<> *rec = getRecordStruct(RealTimeRunData_Debug); + for(uint8_t i = 0; i < rec->length; i++) { + if(CMD_CALC == rec->assign[i].div) { + rec->record[i] = calcFunctions[rec->assign[i].start].func(this, rec->assign[i].num); } + yield(); } } - bool isAvailable(uint32_t timestamp) { + bool isAvailable(uint32_t timestamp, record_t<> *rec) { DPRINTLN(DBG_VERBOSE, F("hmInverter.h:isAvailable")); - return ((timestamp - ts) < INACT_THRES_SEC); + return ((timestamp - rec->ts) < INACT_THRES_SEC); } - bool isProducing(uint32_t timestamp) { + bool isProducing(uint32_t timestamp, record_t<> *rec) { DPRINTLN(DBG_VERBOSE, F("hmInverter.h:isProducing")); - if(isAvailable(timestamp)) { - uint8_t pos = getPosByChFld(CH0, FLD_PAC); - return (getValue(pos) > INACT_PWR_THRESH); + if(isAvailable(timestamp, rec)) { + uint8_t pos = getPosByChFld(CH0, FLD_PAC, rec); + return (getValue(pos, rec) > INACT_PWR_THRESH); } return false; } - uint32_t getLastTs(void) - { + uint32_t getLastTs(record_t<> *rec) { DPRINTLN(DBG_VERBOSE, F("hmInverter.h:getLastTs")); - return ts; + return rec->ts; } - void getAssignment() - { - DPRINTLN(DBG_DEBUG, F("hmInverter.h:getAssignment")); - // Default assignment; - 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; + record_t<> *getRecordStruct(uint8_t cmd) { + switch (cmd) { + case RealTimeRunData_Debug: return &recordMeas; + case InverterDevInform_All: return &recordInfo; + case SystemConfigPara: return &recordConfig; + case AlarmData: return &recordAlarm; + default: break; } + return NULL; + } - switch (getQueuedCmd()) { + void initAssignment(record_t<> *rec, uint8_t cmd) { + DPRINTLN(DBG_VERBOSE, F("hmInverter.h:initAssignment")); + rec->ts = 0; + rec->length = 0; + switch (cmd) { case RealTimeRunData_Debug: - // Do nothing will use default + if (INV_TYPE_1CH == type) { + rec->length = (uint8_t)(HM1CH_LIST_LEN); + rec->assign = (byteAssign_t *)hm1chAssignment; + channels = 1; + } + else if (INV_TYPE_2CH == type) { + rec->length = (uint8_t)(HM2CH_LIST_LEN); + rec->assign = (byteAssign_t *)hm2chAssignment; + channels = 2; + } + else if (INV_TYPE_4CH == type) { + rec->length = (uint8_t)(HM4CH_LIST_LEN); + rec->assign = (byteAssign_t *)hm4chAssignment; + channels = 4; + } + else { + rec->length = 0; + rec->assign = NULL; + channels = 0; + } break; case InverterDevInform_All: - listLen = (uint8_t)(HMINFO_LIST_LEN); - assign = (byteAssign_t *)InfoAssignment; + rec->length = (uint8_t)(HMINFO_LIST_LEN); + rec->assign = (byteAssign_t *)InfoAssignment; break; case SystemConfigPara: - listLen = (uint8_t)(HMSYSTEM_LIST_LEN); - assign = (byteAssign_t *)SystemConfigParaAssignment; + rec->length = (uint8_t)(HMSYSTEM_LIST_LEN); + rec->assign = (byteAssign_t *)SystemConfigParaAssignment; break; case AlarmData: - listLen = (uint8_t)(HMALARMDATA_LIST_LEN); - assign = (byteAssign_t *)AlarmDataAssignment; + rec->length = (uint8_t)(HMALARMDATA_LIST_LEN); + rec->assign = (byteAssign_t *)AlarmDataAssignment; break; default: - DPRINTLN(DBG_INFO, "Parser not implemented"); + DPRINTLN(DBG_INFO, F("initAssignment: Parser not implemented")); break; } + + if(0 != rec->length) { + rec->record = new REC_TYP[rec->length]; + memset(rec->record, 0, sizeof(REC_TYP) * rec->length); + } } - String getAlarmStr(u_int16_t alarmCode) - { - switch (alarmCode) - { - case 1: - return String(F("Inverter start")); - break; - case 2: - return String(F("DTU command failed")); - break; - 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: - return String(F("Unknown")); - break; + + String getAlarmStr(u_int16_t alarmCode) { + switch (alarmCode) { // breaks are intentionally missing! + case 1: return String(F("Inverter start")); + case 2: return String(F("DTU command failed")); + case 121: return String(F("Over temperature protection")); + case 125: return String(F("Grid configuration parameter error")); + case 126: return String(F("Software error code 126")); + case 127: return String(F("Firmware error")); + case 128: return String(F("Software error code 128")); + case 129: return String(F("Software error code 129")); + case 130: return String(F("Offline")); + case 141: return String(F("Grid overvoltage")); + case 142: return String(F("Average grid overvoltage")); + case 143: return String(F("Grid undervoltage")); + case 144: return String(F("Grid overfrequency")); + case 145: return String(F("Grid underfrequency")); + case 146: return String(F("Rapid grid frequency change")); + case 147: return String(F("Power grid outage")); + case 148: return String(F("Grid disconnection")); + case 149: return String(F("Island detected")); + case 205: return String(F("Input port 1 & 2 overvoltage")); + case 206: return String(F("Input port 3 & 4 overvoltage")); + case 207: return String(F("Input port 1 & 2 undervoltage")); + case 208: return String(F("Input port 3 & 4 undervoltage")); + case 209: return String(F("Port 1 no input")); + case 210: return String(F("Port 2 no input")); + case 211: return String(F("Port 3 no input")); + case 212: return String(F("Port 4 no input")); + case 213: return String(F("PV-1 & PV-2 abnormal wiring")); + case 214: return String(F("PV-3 & PV-4 abnormal wiring")); + case 215: return String(F("PV-1 Input overvoltage")); + case 216: return String(F("PV-1 Input undervoltage")); + case 217: return String(F("PV-2 Input overvoltage")); + case 218: return String(F("PV-2 Input undervoltage")); + case 219: return String(F("PV-3 Input overvoltage")); + case 220: return String(F("PV-3 Input undervoltage")); + case 221: return String(F("PV-4 Input overvoltage")); + case 222: return String(F("PV-4 Input undervoltage")); + case 301: return String(F("Hardware error code 301")); + case 302: return String(F("Hardware error code 302")); + case 303: return String(F("Hardware error code 303")); + case 304: return String(F("Hardware error code 304")); + case 305: return String(F("Hardware error code 305")); + case 306: return String(F("Hardware error code 306")); + case 307: return String(F("Hardware error code 307")); + case 308: return String(F("Hardware error code 308")); + case 309: return String(F("Hardware error code 309")); + case 310: return String(F("Hardware error code 310")); + case 311: return String(F("Hardware error code 311")); + case 312: return String(F("Hardware error code 312")); + case 313: return String(F("Hardware error code 313")); + case 314: return String(F("Hardware error code 314")); + case 5041: return String(F("Error code-04 Port 1")); + case 5042: return String(F("Error code-04 Port 2")); + case 5043: return String(F("Error code-04 Port 3")); + case 5044: return String(F("Error code-04 Port 4")); + case 5051: return String(F("PV Input 1 Overvoltage/Undervoltage")); + case 5052: return String(F("PV Input 2 Overvoltage/Undervoltage")); + case 5053: return String(F("PV Input 3 Overvoltage/Undervoltage")); + case 5054: return String(F("PV Input 4 Overvoltage/Undervoltage")); + case 5060: return String(F("Abnormal bias")); + case 5070: return String(F("Over temperature protection")); + case 5080: return String(F("Grid Overvoltage/Undervoltage")); + case 5090: return String(F("Grid Overfrequency/Underfrequency")); + case 5100: return String(F("Island detected")); + case 5120: return String(F("EEPROM reading and writing error")); + case 5150: return String(F("10 min value grid overvoltage")); + case 5200: return String(F("Firmware error")); + case 8310: return String(F("Shut down")); + case 9000: return String(F("Microinverter is suspected of being stolen")); + default: return String(F("Unknown")); } } @@ -590,10 +489,11 @@ template static T calcYieldTotalCh0(Inverter<> *iv, uint8_t arg0) { DPRINTLN(DBG_VERBOSE, F("hmInverter.h:calcYieldTotalCh0")); if(NULL != iv) { + record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug); T yield = 0; for(uint8_t i = 1; i <= iv->channels; i++) { - uint8_t pos = iv->getPosByChFld(i, FLD_YT); - yield += iv->getValue(pos); + uint8_t pos = iv->getPosByChFld(i, FLD_YT, rec); + yield += iv->getValue(pos, rec); } return yield; } @@ -604,10 +504,11 @@ template static T calcYieldDayCh0(Inverter<> *iv, uint8_t arg0) { DPRINTLN(DBG_VERBOSE, F("hmInverter.h:calcYieldDayCh0")); if(NULL != iv) { + record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug); T yield = 0; for(uint8_t i = 1; i <= iv->channels; i++) { - uint8_t pos = iv->getPosByChFld(i, FLD_YD); - yield += iv->getValue(pos); + uint8_t pos = iv->getPosByChFld(i, FLD_YD, rec); + yield += iv->getValue(pos, rec); } return yield; } @@ -618,9 +519,10 @@ template static T calcUdcCh(Inverter<> *iv, uint8_t arg0) { DPRINTLN(DBG_VERBOSE, F("hmInverter.h:calcUdcCh")); // arg0 = channel of source - for(uint8_t i = 0; i < iv->listLen; i++) { - if((FLD_UDC == iv->assign[i].fieldId) && (arg0 == iv->assign[i].ch)) { - return iv->getValue(i); + record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug); + for(uint8_t i = 0; i < rec->length; i++) { + if((FLD_UDC == rec->assign[i].fieldId) && (arg0 == rec->assign[i].ch)) { + return iv->getValue(i, rec); } } @@ -631,10 +533,11 @@ template static T calcPowerDcCh0(Inverter<> *iv, uint8_t arg0) { DPRINTLN(DBG_VERBOSE, F("hmInverter.h:calcPowerDcCh0")); if(NULL != iv) { + record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug); T dcPower = 0; for(uint8_t i = 1; i <= iv->channels; i++) { - uint8_t pos = iv->getPosByChFld(i, FLD_PDC); - dcPower += iv->getValue(pos); + uint8_t pos = iv->getPosByChFld(i, FLD_PDC, rec); + dcPower += iv->getValue(pos, rec); } return dcPower; } @@ -645,12 +548,13 @@ template static T calcEffiencyCh0(Inverter<> *iv, uint8_t arg0) { DPRINTLN(DBG_VERBOSE, F("hmInverter.h:calcEfficiencyCh0")); if(NULL != iv) { - uint8_t pos = iv->getPosByChFld(CH0, FLD_PAC); - T acPower = iv->getValue(pos); + record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug); + uint8_t pos = iv->getPosByChFld(CH0, FLD_PAC, rec); + T acPower = iv->getValue(pos, rec); T dcPower = 0; for(uint8_t i = 1; i <= iv->channels; i++) { - pos = iv->getPosByChFld(i, FLD_PDC); - dcPower += iv->getValue(pos); + pos = iv->getPosByChFld(i, FLD_PDC, rec); + dcPower += iv->getValue(pos, rec); } if(dcPower > 0) return acPower / dcPower * 100.0f; @@ -663,9 +567,10 @@ static T calcIrradiation(Inverter<> *iv, uint8_t arg0) { DPRINTLN(DBG_VERBOSE, F("hmInverter.h:calcIrradiation")); // arg0 = channel if(NULL != iv) { - uint8_t pos = iv->getPosByChFld(arg0, FLD_PDC); + record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug); + uint8_t pos = iv->getPosByChFld(arg0, FLD_PDC, rec); if(iv->chMaxPwr[arg0-1] > 0) - return iv->getValue(pos) / iv->chMaxPwr[arg0-1] * 100.0f; + return iv->getValue(pos, rec) / iv->chMaxPwr[arg0-1] * 100.0f; } return 0.0; } diff --git a/tools/esp8266/hmRadio.h b/tools/esp8266/hmRadio.h index c7fcf0e0..737aa15a 100644 --- a/tools/esp8266/hmRadio.h +++ b/tools/esp8266/hmRadio.h @@ -21,7 +21,7 @@ #define RF_CHANNELS 5 #define RF_LOOP_CNT 300 -#define TX_REQ_INFO 0X15 +#define TX_REQ_INFO 0x15 #define TX_REQ_DEVCONTROL 0x51 #define ALL_FRAMES 0x80 #define SINGLE_FRAME 0x81 @@ -54,7 +54,7 @@ const char* const rf24AmpPowerNames[] = {"MIN", "LOW", "HIGH", "MAX"}; //----------------------------------------------------------------------------- // HM Radio class //----------------------------------------------------------------------------- -template +template class HmRadio { public: HmRadio() : mNrf24(CE_PIN, CS_PIN, SPI_SPEED) { @@ -89,6 +89,25 @@ class HmRadio { pinMode(config->pinIrq, INPUT_PULLUP); mBufCtrl = ctrl; + mSerialDebug = config->serialDebug; + + uint32_t DTU_SN = 0x87654321; + uint32_t chipID = 0; // will be filled with last 3 bytes of MAC +#ifdef ESP32 + uint64_t MAC = ESP.getEfuseMac(); + chipID = ((MAC >> 8) & 0xFF0000) | ((MAC >> 24) & 0xFF00) | ((MAC >> 40) & 0xFF); +#else + chipID = ESP.getChipId(); +#endif + if(chipID) { + DTU_SN = 0x80000000; // the first digit is an 8 for DTU production year 2022, the rest is filled with the ESP chipID in decimal + for(int i = 0; i < 7; i++) { + DTU_SN |= (chipID % 10) << (i * 4); + chipID /= 10; + } + } + // change the byte order of the DTU serial number and append the required 0x01 at the end + DTU_RADIO_ID = ((uint64_t)(((DTU_SN >> 24) & 0xFF) | ((DTU_SN >> 8) & 0xFF00) | ((DTU_SN << 8) & 0xFF0000) | ((DTU_SN << 24) & 0xFF000000)) << 8) | 0x01; mNrf24.begin(config->pinCe, config->pinCs); mNrf24.setRetries(0, 0); @@ -163,64 +182,62 @@ class HmRadio { } void sendControlPacket(uint64_t invId, uint8_t cmd, uint16_t *data) { - DPRINTLN(DBG_VERBOSE, F("hmRadio.h:sendControlPacket")); - sendCmdPacket(invId, TX_REQ_DEVCONTROL, ALL_FRAMES, false); // 0x80 implementation as original DTU code - int cnt = 0; - mTxBuf[10] = cmd; // cmd --> 0x0b => Type_ActivePowerContr, 0 on, 1 off, 2 restart, 12 reactive power, 13 power factor - mTxBuf[10 + (++cnt)] = 0x00; - if (cmd >= ActivePowerContr && cmd <= PFSet){ - mTxBuf[10 + (++cnt)] = ((data[0] * 10) >> 8) & 0xff; // power limit - mTxBuf[10 + (++cnt)] = ((data[0] * 10) ) & 0xff; // power limit - mTxBuf[10 + (++cnt)] = ((data[1] ) >> 8) & 0xff; // setting for persistens handlings - mTxBuf[10 + (++cnt)] = ((data[1] ) ) & 0xff; // setting for persistens handling + DPRINTLN(DBG_INFO, F("sendControlPacket cmd: ") + String(cmd)); + sendCmdPacket(invId, TX_REQ_DEVCONTROL, SINGLE_FRAME, false); + uint8_t cnt = 0; + mTxBuf[10 + cnt++] = cmd; // cmd -> 0 on, 1 off, 2 restart, 11 active power, 12 reactive power, 13 power factor + mTxBuf[10 + cnt++] = 0x00; + if(cmd >= ActivePowerContr && cmd <= PFSet) { // ActivePowerContr, ReactivePowerContr, PFSet + mTxBuf[10 + cnt++] = ((data[0] * 10) >> 8) & 0xff; // power limit + mTxBuf[10 + cnt++] = ((data[0] * 10) ) & 0xff; // power limit + mTxBuf[10 + cnt++] = ((data[1] ) >> 8) & 0xff; // setting for persistens handlings + mTxBuf[10 + cnt++] = ((data[1] ) ) & 0xff; // setting for persistens handling } + // crc control data - uint16_t crc = Hoymiles::crc16(&mTxBuf[10], cnt+1); - mTxBuf[10 + (++cnt)] = (crc >> 8) & 0xff; - mTxBuf[10 + (++cnt)] = (crc ) & 0xff; + uint16_t crc = Ahoy::crc16(&mTxBuf[10], cnt); + mTxBuf[10 + cnt++] = (crc >> 8) & 0xff; + mTxBuf[10 + cnt++] = (crc ) & 0xff; + // crc over all - cnt +=1; - mTxBuf[10 + cnt] = Hoymiles::crc8(mTxBuf, 10 + cnt); + mTxBuf[10 + cnt] = Ahoy::crc8(mTxBuf, 10 + cnt); - sendPacket(invId, mTxBuf, 10 + (++cnt), true); + sendPacket(invId, mTxBuf, 10 + cnt + 1, true); } void sendTimePacket(uint64_t invId, uint8_t cmd, uint32_t ts, uint16_t alarmMesId) { - //DPRINTLN(DBG_VERBOSE, F("hmRadio.h:sendTimePacket")); + DPRINTLN(DBG_INFO, F("sendTimePacket")); sendCmdPacket(invId, TX_REQ_INFO, ALL_FRAMES, false); mTxBuf[10] = cmd; // cid mTxBuf[11] = 0x00; CP_U32_LittleEndian(&mTxBuf[12], ts); - if (cmd == RealTimeRunData_Debug || cmd == AlarmData ){ + if (cmd == RealTimeRunData_Debug || cmd == AlarmData ) { mTxBuf[18] = (alarmMesId >> 8) & 0xff; mTxBuf[19] = (alarmMesId ) & 0xff; - } else { - mTxBuf[18] = 0x00; - mTxBuf[19] = 0x00; } - uint16_t crc = Hoymiles::crc16(&mTxBuf[10], 14); + uint16_t crc = Ahoy::crc16(&mTxBuf[10], 14); mTxBuf[24] = (crc >> 8) & 0xff; mTxBuf[25] = (crc ) & 0xff; - mTxBuf[26] = Hoymiles::crc8(mTxBuf, 26); + mTxBuf[26] = Ahoy::crc8(mTxBuf, 26); sendPacket(invId, mTxBuf, 27, true); } void sendCmdPacket(uint64_t invId, uint8_t mid, uint8_t pid, bool calcCrc = true) { - //DPRINTLN(DBG_VERBOSE, F("hmRadio.h:sendCmdPacket")); + DPRINTLN(DBG_VERBOSE, F("sendCmdPacket, mid: ") + String(mid, HEX) + F(" pid: ") + String(pid, HEX)); memset(mTxBuf, 0, MAX_RF_PAYLOAD_SIZE); mTxBuf[0] = mid; // message id CP_U32_BigEndian(&mTxBuf[1], (invId >> 8)); - CP_U32_BigEndian(&mTxBuf[5], (DTU_ID >> 8)); + CP_U32_BigEndian(&mTxBuf[5], (DTU_RADIO_ID >> 8)); mTxBuf[9] = pid; if(calcCrc) { - mTxBuf[10] = Hoymiles::crc8(mTxBuf, 10); + mTxBuf[10] = Ahoy::crc8(mTxBuf, 10); sendPacket(invId, mTxBuf, 11, false); } } bool checkPaketCrc(uint8_t buf[], uint8_t *len, uint8_t rxCh) { - //DPRINTLN(DBG_VERBOSE, F("hmRadio.h:checkPaketCrc")); + //DPRINTLN(DBG_INFO, F("hmRadio.h:checkPaketCrc")); *len = (buf[0] >> 2); if(*len > (MAX_RF_PAYLOAD_SIZE - 2)) *len = MAX_RF_PAYLOAD_SIZE - 2; @@ -228,7 +245,7 @@ class HmRadio { buf[i-1] = (buf[i] << 1) | (buf[i+1] >> 7); } - uint8_t crc = Hoymiles::crc8(buf, *len-1); + uint8_t crc = Ahoy::crc8(buf, *len-1); bool valid = (crc == buf[*len-1]); return valid; @@ -236,8 +253,6 @@ class HmRadio { bool switchRxCh(uint16_t addLoop = 0) { //DPRINTLN(DBG_VERBOSE, F("hmRadio.h:switchRxCh")); - //DPRINTLN(DBG_VERBOSE, F("R")); - mRxLoopCnt += addLoop; if(mRxLoopCnt != 0) { mRxLoopCnt--; @@ -325,6 +340,8 @@ class HmRadio { return mRfChLst[mRxChIdx]; } + uint64_t DTU_RADIO_ID; + uint8_t mTxCh; uint8_t mTxChIdx; diff --git a/tools/esp8266/hmSystem.h b/tools/esp8266/hmSystem.h index aa9c7645..eb246ccb 100644 --- a/tools/esp8266/hmSystem.h +++ b/tools/esp8266/hmSystem.h @@ -63,14 +63,8 @@ class HmSystem { uint8_t len = (uint8_t)strlen(name); strncpy(p->name, name, (len > MAX_NAME_LENGTH) ? MAX_NAME_LENGTH : len); - if(NULL == p->assign) { - DPRINT(DBG_ERROR, F("no assignment for type found!")); - return NULL; - } - else { - mNumInv ++; - return p; - } + mNumInv ++; + return p; } INVERTERTYPE *findInverter(uint8_t buf[]) { @@ -89,7 +83,9 @@ class HmSystem { INVERTERTYPE *getInverterByPos(uint8_t pos, bool check = true) { DPRINTLN(DBG_VERBOSE, F("hmSystem.h:getInverterByPos")); - if((mInverter[pos].initialized && mInverter[pos].serial.u64 != 0ULL) || false == check) + if(pos >= MAX_INVERTER) + return NULL; + else if((mInverter[pos].initialized && mInverter[pos].serial.u64 != 0ULL) || false == check) return &mInverter[pos]; else return NULL; diff --git a/tools/esp8266/html/api.js b/tools/esp8266/html/api.js new file mode 100644 index 00000000..04476f3a --- /dev/null +++ b/tools/esp8266/html/api.js @@ -0,0 +1,79 @@ +function toggle(id, hide) { + var elm = document.getElementById(id); + if(hide) { + if(!elm.classList.contains("hide")) + elm.classList.add("hide"); + } + else + elm.classList.remove('hide'); +} + +function getAjax(url, ptr, method="GET", json=null) { + var xhr = new XMLHttpRequest(); + if(xhr != null) { + xhr.open(method, url, true); + xhr.onreadystatechange = p; + if("POST" == method) + xhr.setRequestHeader("Content-Type", "application/json;charset=UTF-8"); + xhr.send(json); + } + function p() { + if(xhr.readyState == 4) { + if(null != xhr.responseText) + ptr(JSON.parse(xhr.responseText)); + } + } +} + +function des(val) { + e = document.createElement('p'); + e.classList.add("subdes"); + e.innerHTML = val; + return e; +} + +function lbl(htmlfor, val, cl=null, id=null) { + e = document.createElement('label'); + e.htmlFor = htmlfor; + e.innerHTML = val; + if(null != cl) e.classList.add(...cl); + if(null != id) e.id = id; + return e; +} + +function inp(name, val, max=32, cl=["text"], id=null) { + e = document.createElement('input'); + e.classList.add(...cl); + e.name = name; + e.value = val; + e.maxLength = max; + if(null != id) e.id = id; + return e; +} + +function sel(name, opt, selId) { + e = document.createElement('select'); + e.name = name; + for(it of opt) { + o = document.createElement('option'); + o.value = it[0]; + o.innerHTML = it[1]; + if(it[0] == selId) + o.selected = true; + e.appendChild(o); + } + return e; +} + +function div(cl) { + e = document.createElement('div'); + e.classList.add(...cl); + return e; +} + +function span(val, cl) { + e = document.createElement('span'); + e.innerHTML = val; + e.classList.add(...cl); + return e; +} diff --git a/tools/esp8266/html/convert.py b/tools/esp8266/html/convert.py index dadaf92d..36877dec 100755 --- a/tools/esp8266/html/convert.py +++ b/tools/esp8266/html/convert.py @@ -1,17 +1,17 @@ import re -import sys import os +import gzip from pathlib import Path -def convert2Header(inFile): - fileType = inFile.split(".")[1] +def convert2Header(inFile, compress): + fileType = inFile.split(".")[1] define = inFile.split(".")[0].upper() define2 = inFile.split(".")[1].upper() inFileVarName = inFile.replace(".", "_") + print(inFile + ", compress: " + str(compress)) if os.getcwd()[-4:] != "html": - print("ok") outName = "html/" + "h/" + inFileVarName + ".h" inFile = "html/" + inFile Path("html/h").mkdir(exist_ok=True) @@ -20,23 +20,52 @@ def convert2Header(inFile): Path("h").mkdir(exist_ok=True) f = open(inFile, "r") - data = f.read().replace('\n', '') + data = f.read() f.close() + if fileType == "html": - data = re.sub(r"\>\s+\<", '><', data) # whitespaces between xml tags - data = re.sub(r"(\;|\}|\>|\{)\s+", r'\1', data) # whitespaces inner javascript - data = re.sub(r"\"", '\\\"', data) # escape quotation marks + if False == compress: + data = data.replace('\n', '') + data = re.sub(r"\>\s+\<", '><', data) # whitespaces between xml tags + data = re.sub(r"(\r\n|\r|\n)(\s+|\s?)", '', data) # whitespaces inner javascript + length = len(data) # get unescaped length + if False == compress: + data = re.sub(r"\"", '\\\"', data) # escape quotation marks + elif fileType == "js": + #data = re.sub(r"(\r\n|\r|\n)(\s+|\s?)", '', data) # whitespaces inner javascript + #data = re.sub(r"\s?(\=|\!\=|\{|,)+\s?", r'\1', data) # whitespaces inner javascript + length = len(data) # get unescaped length + if False == compress: + data = re.sub(r"\"", '\\\"', data) # escape quotation marks else: + data = data.replace('\n', '') data = re.sub(r"(\;|\}|\:|\{)\s+", r'\1', data) # whitespaces inner css + length = len(data) # get unescaped length # get unescaped length f = open(outName, "w") f.write("#ifndef __{}_{}_H__\n".format(define, define2)) f.write("#define __{}_{}_H__\n".format(define, define2)) - f.write("const char {}[] PROGMEM = \"{}\";\n".format(inFileVarName, data)) + if compress: + zipped = gzip.compress(bytes(data, 'utf-8')) + zippedStr = "" + for i in range(len(zipped)): + zippedStr += "0x{:02x}".format(zipped[i]) #hex(zipped[i]) + if (i + 1) != len(zipped): + zippedStr += ", " + if (i + 1) % 16 == 0 and i != 0: + zippedStr += "\n" + f.write("#define {}_len {}\n".format(inFileVarName, len(zipped))) + f.write("const uint8_t {}[] PROGMEM = {{\n{}}};\n".format(inFileVarName, zippedStr)) + else: + f.write("const char {}[] PROGMEM = \"{}\";\n".format(inFileVarName, data)) + f.write("const uint32_t {}_len = {};\n".format(inFileVarName, length)) f.write("#endif /*__{}_{}_H__*/\n".format(define, define2)) f.close() -convert2Header("index.html") -convert2Header("setup.html") -convert2Header("visualization.html") -convert2Header("style.css") +convert2Header("index.html", True) +convert2Header("setup.html", True) +convert2Header("visualization.html", True) +convert2Header("update.html", True) +convert2Header("serial.html", True) +convert2Header("style.css", True) +convert2Header("api.js", True) diff --git a/tools/esp8266/html/h/favicon_ico_gz.h b/tools/esp8266/html/h/favicon_ico_gz.h new file mode 100644 index 00000000..b978fb85 --- /dev/null +++ b/tools/esp8266/html/h/favicon_ico_gz.h @@ -0,0 +1,100 @@ +#ifndef __FAVICON_ICO_GZ_H__ +#define __FAVICON_ICO_GZ_H__ +#define favicon_ico_gz_len 1533 +const uint8_t favicon_ico_gz[] PROGMEM = {0x1f, 0x8b, 0x08, 0x08, 0xf2, 0xc5, 0xd5, 0x62, 0x04, 0x00, 0x66, 0x61, 0x76, 0x69, 0x63, 0x6f, +0x6e, 0x2e, 0x69, 0x63, 0x6f, 0x00, 0xed, 0x5c, 0x49, 0x68, 0x13, 0x51, 0x18, 0xfe, 0x62, 0xa3, +0x51, 0x28, 0xd6, 0x83, 0x82, 0xa0, 0x98, 0xb8, 0x1c, 0xbc, 0x59, 0x11, 0x5c, 0x50, 0xac, 0x88, +0x8a, 0xb8, 0xdd, 0x3c, 0x89, 0xd0, 0x93, 0x7a, 0x53, 0x51, 0x9b, 0x80, 0x4b, 0x46, 0xad, 0xfb, +0xd2, 0xb4, 0x2e, 0xb8, 0xa3, 0xc6, 0xba, 0xe1, 0x02, 0xae, 0xad, 0x0a, 0x26, 0x3d, 0xe8, 0xc5, +0x83, 0x57, 0x31, 0x2d, 0xc1, 0x8b, 0xb7, 0x92, 0x63, 0x0e, 0xa1, 0xcf, 0xff, 0xcf, 0xbc, 0xc9, +0x32, 0xa4, 0x66, 0xcf, 0x4b, 0xf3, 0xfa, 0xc3, 0xc7, 0x97, 0xcc, 0xcc, 0xcb, 0xf7, 0xbe, 0x6f, +0x26, 0x6f, 0x26, 0xf3, 0x92, 0x00, 0x0e, 0x34, 0x61, 0xda, 0x34, 0x66, 0x0f, 0xf6, 0x38, 0x81, +0xa5, 0x00, 0x3c, 0x1e, 0xf3, 0xf9, 0x53, 0x5a, 0x7e, 0x8f, 0x96, 0xad, 0x59, 0x63, 0x3e, 0x5f, +0xb8, 0x16, 0xd8, 0x30, 0x03, 0x58, 0x48, 0xdb, 0xd0, 0x2a, 0x5a, 0x62, 0x2e, 0x1f, 0xad, 0x06, +0xc3, 0x5d, 0xc6, 0x60, 0x38, 0x20, 0x08, 0x5e, 0xf1, 0x08, 0x77, 0x09, 0x42, 0xf4, 0xe2, 0xd7, +0x67, 0x2f, 0x56, 0xf5, 0x7b, 0x21, 0x18, 0xfc, 0xb8, 0x63, 0x0b, 0x7e, 0x11, 0x84, 0x77, 0x0b, +0xee, 0x2e, 0x9a, 0x03, 0x6f, 0xab, 0x1b, 0x82, 0x31, 0x38, 0x10, 0x38, 0xce, 0xaf, 0x51, 0x72, +0x7b, 0xd2, 0xe6, 0xf6, 0xbf, 0xbf, 0xf6, 0xcc, 0x2e, 0xbd, 0x7d, 0xf2, 0x35, 0x76, 0x88, 0x20, +0xf6, 0x96, 0xde, 0x3e, 0xe0, 0xa5, 0x76, 0xad, 0xd4, 0xfe, 0x64, 0xc9, 0xed, 0xa9, 0xff, 0xd4, +0xd6, 0x4f, 0x68, 0x2f, 0xb9, 0x3d, 0xf5, 0x9f, 0x70, 0xb8, 0xaf, 0x03, 0x4b, 0x4b, 0x6e, 0x4f, +0xfd, 0xe7, 0x7d, 0x4b, 0x6d, 0x4f, 0x96, 0xd3, 0xde, 0x7a, 0x8d, 0x72, 0xda, 0x73, 0x7d, 0xdb, +0x8f, 0x29, 0x45, 0xb5, 0x7f, 0x84, 0x4e, 0xfb, 0x71, 0xda, 0xb1, 0x19, 0x9d, 0x05, 0xb5, 0xef, +0xc5, 0xa5, 0xd1, 0x8e, 0x75, 0xef, 0x56, 0x9c, 0xfa, 0x6f, 0xfb, 0x87, 0x58, 0x86, 0x3c, 0x75, +0x70, 0x13, 0x96, 0xe5, 0x6a, 0x1f, 0x19, 0xe8, 0x6a, 0x47, 0x81, 0xd5, 0xea, 0x41, 0x7b, 0x56, +0xfb, 0x50, 0xe0, 0x72, 0xae, 0xed, 0xbe, 0x1a, 0x98, 0xcc, 0xc8, 0xf9, 0x1a, 0x73, 0x70, 0x9a, +0xda, 0x8f, 0xda, 0xf6, 0xb9, 0x81, 0x49, 0xfd, 0x3e, 0xbc, 0xa5, 0xfd, 0xd7, 0xcf, 0xf9, 0x8f, +0xf6, 0x1a, 0xff, 0xeb, 0xe7, 0x04, 0x42, 0x13, 0xc3, 0x20, 0x84, 0x00, 0x57, 0x14, 0x68, 0x89, +0x01, 0xee, 0x38, 0xb0, 0xfa, 0x08, 0x70, 0x64, 0xb5, 0x39, 0xce, 0x78, 0x08, 0x6b, 0x0a, 0x1f, +0x67, 0x2c, 0x0c, 0xf3, 0x72, 0xf1, 0x18, 0xab, 0xe4, 0x3e, 0x4f, 0x23, 0x88, 0x37, 0xbc, 0xae, +0xcf, 0x07, 0xaf, 0xf9, 0x1e, 0x48, 0x83, 0x97, 0xf1, 0xba, 0x43, 0x5b, 0xf0, 0x86, 0x8f, 0x87, +0x4c, 0xd0, 0xfe, 0x5d, 0xc5, 0xeb, 0x28, 0x97, 0x61, 0x33, 0xdf, 0x34, 0x78, 0x39, 0x8f, 0x55, +0xaa, 0xf4, 0x23, 0x03, 0x81, 0x53, 0x56, 0x1f, 0x54, 0xe8, 0xb3, 0xe6, 0x50, 0x28, 0x70, 0xde, +0xea, 0x83, 0x0a, 0xfd, 0x64, 0x1f, 0xc2, 0x81, 0x93, 0xbc, 0x0d, 0xf1, 0x01, 0x15, 0xfa, 0x49, +0x0c, 0x74, 0x9d, 0xe3, 0xed, 0x94, 0xe9, 0x13, 0x78, 0x1f, 0x24, 0xf5, 0x7b, 0xb1, 0x54, 0x89, +0x3e, 0xef, 0x7f, 0x59, 0x7c, 0xce, 0x53, 0xa5, 0x2f, 0x7a, 0xe0, 0x92, 0x7d, 0x38, 0xad, 0x44, +0x9f, 0xf6, 0x3f, 0x9f, 0xb3, 0x65, 0x1f, 0x3a, 0x95, 0xe8, 0x9b, 0xbe, 0x8f, 0x59, 0x7d, 0x50, +0xa4, 0x9f, 0xea, 0x03, 0xe9, 0xaf, 0x57, 0xa4, 0x9f, 0xea, 0x83, 0x42, 0x7d, 0xbe, 0x7e, 0xd9, +0xcf, 0xeb, 0x3e, 0xf9, 0xd0, 0xae, 0x44, 0x9f, 0xf7, 0xbf, 0x2c, 0x3a, 0x87, 0x9f, 0x56, 0xa9, +0x6f, 0xf5, 0x41, 0xa5, 0xbe, 0xbc, 0x0e, 0x3d, 0xa3, 0x52, 0xdf, 0xea, 0x83, 0x4a, 0x7d, 0xae, +0x0f, 0x87, 0x30, 0x5f, 0xa5, 0x3e, 0x57, 0x0d, 0xf4, 0x8d, 0x7c, 0xfa, 0xa4, 0x67, 0x54, 0x49, +0xff, 0x86, 0x10, 0x74, 0x29, 0x9b, 0xbf, 0x1c, 0xa4, 0xd9, 0x5d, 0x51, 0xfd, 0x20, 0x6e, 0x16, +0xa8, 0x9d, 0xd5, 0x87, 0x8a, 0xe8, 0x17, 0xaf, 0x9d, 0xea, 0x03, 0xe9, 0xf6, 0x94, 0xa9, 0x7f, +0xab, 0x44, 0xed, 0xac, 0x3e, 0x94, 0xa4, 0xff, 0x1c, 0xcd, 0x65, 0x6a, 0xa7, 0xfa, 0x60, 0x6c, +0x47, 0x73, 0xb1, 0xfa, 0xd5, 0xa8, 0x42, 0xf4, 0x23, 0xe1, 0xc0, 0x15, 0x54, 0xa9, 0x16, 0xbb, +0x71, 0xe5, 0x7f, 0xfa, 0x91, 0x50, 0xf7, 0x6d, 0x21, 0x8c, 0x09, 0xa8, 0x5e, 0x39, 0x48, 0xf3, +0x6a, 0x86, 0x7e, 0x59, 0xda, 0xfd, 0x07, 0x31, 0x97, 0x81, 0xe2, 0x2a, 0xdd, 0x87, 0xb4, 0xfe, +0x9d, 0x62, 0xb5, 0xe9, 0xde, 0xcb, 0x1c, 0x3a, 0xef, 0x0c, 0xf5, 0x77, 0xe0, 0xcf, 0x17, 0x1f, +0xe6, 0xa1, 0xb8, 0xe2, 0x3e, 0x5c, 0x23, 0x94, 0xa4, 0xfd, 0xd1, 0x07, 0x0f, 0x5d, 0x03, 0x45, +0xad, 0x73, 0x3f, 0x3f, 0xe6, 0x65, 0x28, 0xae, 0x92, 0x7d, 0x70, 0x4e, 0x9e, 0xe6, 0xe9, 0x24, +0xac, 0x23, 0x2c, 0x20, 0x4c, 0x27, 0x34, 0x13, 0x78, 0xf9, 0x24, 0xc2, 0x44, 0x7e, 0x7c, 0x96, +0xb0, 0x91, 0xb0, 0x90, 0x30, 0xd3, 0x5c, 0xe7, 0x6c, 0x26, 0x4c, 0x25, 0xb4, 0x30, 0x02, 0x84, +0x9f, 0x84, 0x58, 0x1a, 0xee, 0xb8, 0xc7, 0xd9, 0x96, 0xf0, 0x38, 0xfd, 0x23, 0x1e, 0xa7, 0x10, +0x1e, 0x97, 0x10, 0x51, 0x0b, 0x2d, 0x23, 0xfe, 0x98, 0x3b, 0xd1, 0x16, 0x6f, 0x8b, 0xbb, 0x13, +0x0b, 0xe5, 0x3d, 0x0a, 0xa3, 0xf8, 0xfb, 0x14, 0xb9, 0x3e, 0xbb, 0x45, 0xac, 0xed, 0xe5, 0x67, +0x38, 0x2f, 0x8f, 0xa1, 0x79, 0xd1, 0x8b, 0x5d, 0xb6, 0xeb, 0x89, 0x61, 0xce, 0x35, 0x0f, 0x86, +0x6d, 0xf7, 0xc1, 0x76, 0xf1, 0x58, 0x9b, 0x17, 0x9b, 0xe1, 0xb5, 0x8d, 0x09, 0x91, 0xf4, 0xfb, +0x21, 0x8d, 0x02, 0xee, 0x93, 0x68, 0xe7, 0x3f, 0x6f, 0x06, 0x1a, 0xf8, 0xe7, 0x1a, 0x0a, 0x75, +0x9d, 0xd0, 0xcd, 0xff, 0xe0, 0x40, 0xf7, 0xa5, 0xbc, 0x19, 0x34, 0xb2, 0x7f, 0xf3, 0xda, 0xe0, +0xa2, 0x3d, 0x03, 0x9d, 0xfc, 0xe7, 0xca, 0x80, 0xef, 0x5b, 0xea, 0xe4, 0xdf, 0xcc, 0xa0, 0xeb, +0x82, 0x3d, 0x03, 0x9d, 0xfc, 0xe7, 0xca, 0x20, 0x12, 0xee, 0xee, 0xd4, 0xc9, 0x3f, 0x21, 0x35, +0x77, 0x91, 0xce, 0x20, 0xb0, 0x5b, 0x27, 0xff, 0xe9, 0xb9, 0x93, 0x74, 0xe9, 0xe6, 0xdf, 0xca, +0x40, 0x67, 0xff, 0xd6, 0xdc, 0x91, 0xd6, 0xfe, 0x93, 0xe8, 0xf6, 0xdb, 0xfc, 0xb7, 0xe7, 0xf5, +0xde, 0x48, 0xfe, 0x6d, 0xe3, 0xbf, 0x35, 0x7f, 0xa5, 0xb3, 0xff, 0x82, 0x32, 0x68, 0x70, 0xff, +0x32, 0x83, 0x33, 0xba, 0xf9, 0x17, 0x0f, 0x30, 0x2b, 0xeb, 0x79, 0x10, 0x97, 0xb5, 0xf2, 0x4f, +0xe3, 0x3f, 0xc1, 0x6f, 0xcb, 0xe0, 0xac, 0x56, 0xfe, 0xe5, 0xdc, 0xad, 0x3d, 0x03, 0xcd, 0xfc, +0x67, 0x65, 0xc0, 0xf3, 0x0a, 0xf4, 0xbc, 0x5b, 0x33, 0xff, 0x3c, 0x97, 0x74, 0xd4, 0x9e, 0x81, +0x56, 0xfe, 0x73, 0x67, 0xd0, 0xa3, 0x95, 0x7f, 0x13, 0x87, 0x6d, 0x19, 0xcc, 0xd6, 0xcc, 0x3f, +0x1f, 0xf3, 0x47, 0x32, 0xb7, 0xd5, 0xce, 0xbf, 0x3c, 0x0e, 0x34, 0xf7, 0x9f, 0xcc, 0x40, 0x73, +0xff, 0x3c, 0x26, 0x6e, 0xb3, 0xda, 0xc8, 0xef, 0x57, 0x5d, 0xd1, 0xca, 0xbf, 0x6d, 0xfc, 0x17, +0x34, 0x15, 0xd9, 0xe7, 0xc5, 0x55, 0x5d, 0xfd, 0x17, 0x98, 0x41, 0x43, 0xfb, 0xb7, 0x32, 0xa0, +0xef, 0xd8, 0x5d, 0xd3, 0xd5, 0x7f, 0x66, 0x06, 0xba, 0xfa, 0xcf, 0xcc, 0x40, 0x57, 0xff, 0x56, +0x06, 0xe4, 0xf9, 0xba, 0xae, 0xfe, 0x33, 0x33, 0xd0, 0xd5, 0x7f, 0x66, 0x06, 0xba, 0xfa, 0xcf, +0x18, 0x0f, 0x4e, 0xeb, 0xea, 0xdf, 0xaa, 0x71, 0xff, 0xe3, 0xfe, 0x1b, 0xc4, 0xff, 0x4e, 0x94, +0x59, 0x1d, 0x5b, 0xb1, 0x33, 0xaf, 0xf7, 0x7a, 0xf4, 0x1f, 0xc4, 0x13, 0xf1, 0x9c, 0x7e, 0x4e, +0x5a, 0x66, 0x19, 0x06, 0x26, 0x50, 0x06, 0xf7, 0xc7, 0x94, 0xff, 0x0a, 0x79, 0x2f, 0x2a, 0x83, +0xfa, 0xf1, 0xff, 0x54, 0x7c, 0x85, 0x13, 0x15, 0x2e, 0xce, 0x80, 0x7e, 0x83, 0xf2, 0xa0, 0xce, +0xfd, 0x57, 0xc5, 0xbb, 0x55, 0xdb, 0xb7, 0xa3, 0x89, 0x33, 0xa8, 0x53, 0xff, 0xcf, 0xaa, 0xe9, +0x3d, 0x33, 0x03, 0xf2, 0xfb, 0xb0, 0xce, 0xfc, 0xd7, 0xc4, 0xbb, 0x3d, 0x83, 0x3a, 0xf1, 0x5f, +0x53, 0xef, 0x59, 0xef, 0x85, 0xad, 0x08, 0x2a, 0xf6, 0xff, 0x5c, 0x85, 0x77, 0x7b, 0x06, 0x8a, +0xfc, 0x2b, 0xf5, 0x6e, 0xcf, 0xa0, 0xa6, 0xfe, 0x83, 0x58, 0x54, 0xc9, 0xf3, 0x7b, 0xb9, 0xc5, +0x19, 0xd0, 0xff, 0x9c, 0x2c, 0xaa, 0x95, 0xff, 0xb1, 0x50, 0xe3, 0xfe, 0xc7, 0xfd, 0xb7, 0x56, +0xc8, 0xff, 0x50, 0xb8, 0xeb, 0x3e, 0xc6, 0x58, 0xd1, 0x7f, 0xfd, 0xdc, 0x6f, 0xad, 0x8c, 0xff, +0x17, 0x3f, 0x7e, 0xdc, 0x9c, 0x88, 0x31, 0x56, 0xdb, 0xe9, 0x2f, 0x8c, 0xc8, 0x5f, 0x6f, 0x99, +0xfe, 0xc7, 0xa4, 0xf7, 0x62, 0x32, 0x68, 0x54, 0xef, 0xb6, 0x0c, 0x1e, 0x17, 0xe9, 0xff, 0x65, +0x23, 0x78, 0x2f, 0x24, 0x03, 0x95, 0xde, 0x69, 0x1e, 0xf8, 0x14, 0x03, 0x35, 0x28, 0xce, 0x60, +0x91, 0x1b, 0x4f, 0x6c, 0xfe, 0xd5, 0x79, 0xf7, 0xe1, 0x44, 0x6a, 0xce, 0xab, 0x03, 0xe7, 0x51, +0x83, 0xca, 0x91, 0x41, 0xe6, 0x77, 0xfc, 0xdf, 0xfd, 0xfe, 0xd0, 0xe3, 0x42, 0x0d, 0xea, 0x93, +0x17, 0xc7, 0xed, 0xf3, 0xbe, 0x74, 0x1c, 0x9c, 0x43, 0x0d, 0x6a, 0xc9, 0x12, 0x4c, 0x24, 0xdf, +0xaf, 0x6c, 0xfe, 0xdf, 0xab, 0xf4, 0xae, 0x28, 0x83, 0xd7, 0x84, 0xba, 0xf1, 0x6e, 0x81, 0xb7, +0x41, 0x0d, 0xca, 0xca, 0x00, 0xcb, 0x11, 0x75, 0xd1, 0xd7, 0x14, 0x0d, 0xc9, 0x21, 0xc9, 0x51, +0xc9, 0xfb, 0x24, 0x2f, 0x97, 0x3c, 0x5b, 0xf2, 0x54, 0xc9, 0x2e, 0xc9, 0x4d, 0x7d, 0x26, 0x3b, +0xe2, 0x26, 0xc3, 0x62, 0x9f, 0xe4, 0xe5, 0x92, 0x57, 0x49, 0x5e, 0x2d, 0xb9, 0x4d, 0xf2, 0x4a, +0xc3, 0xe4, 0x15, 0x21, 0xb9, 0x7d, 0x54, 0x72, 0xbb, 0xe4, 0x56, 0xc9, 0x33, 0x25, 0x37, 0x4b, +0x76, 0x49, 0x6e, 0x92, 0xec, 0xb0, 0xf4, 0xec, 0x1c, 0x93, 0x1c, 0x97, 0x9c, 0x90, 0x3c, 0x22, +0x59, 0x58, 0x7c, 0x46, 0xf2, 0x77, 0xc9, 0x7f, 0x25, 0x8b, 0x82, 0xd8, 0x41, 0x7f, 0x3b, 0x91, +0xec, 0x8f, 0x10, 0x21, 0x66, 0xfe, 0x67, 0x06, 0xe6, 0x16, 0x21, 0x62, 0xcc, 0x6e, 0x21, 0xe2, +0xcc, 0x6d, 0x42, 0x24, 0x98, 0xfd, 0x42, 0x8c, 0x30, 0x0b, 0x2a, 0xf6, 0xcf, 0x7c, 0x87, 0x72, +0x61, 0x4e, 0xe4, 0x67, 0x3f, 0xf3, 0x08, 0x7d, 0x28, 0x4f, 0x16, 0x1c, 0x26, 0x1b, 0x4d, 0xb4, +0x90, 0x56, 0x85, 0x5c, 0xb4, 0x11, 0x6d, 0x1a, 0x6d, 0x21, 0x55, 0x52, 0x8e, 0x31, 0xf8, 0x31, +0x2f, 0xe3, 0x75, 0xff, 0x00, 0xd3, 0x39, 0x74, 0x2c, 0x6e, 0x57, 0x00, 0x00}; +#endif /*__FAVICON_ICO_GZ_H__*/ diff --git a/tools/esp8266/html/index.html b/tools/esp8266/html/index.html index de381281..48517201 100644 --- a/tools/esp8266/html/index.html +++ b/tools/esp8266/html/index.html @@ -1,72 +1,127 @@ - Index - {DEVICE} + Index - + -

AHOY - {DEVICE}

+

AHOY

- Visualization
+ Visualization

Setup
+ Webserial & Commands

Uptime:

-

Statistics:

-

Every {TS}seconds the values are updated

+

ESP-Time:

+

RSSI: dBm

+

+ Statistics: +


+                

+                

+            

+

Every seconds the values are updated

This project was started from this discussion. (Mikrocontroller.net)
- New updates can be found on Github: https://github.com/grindylow/ahoy
+ New updates can be found on Github: https://github.com/lumapu/ahoy

- Please report issues using the feature provided by Github
+ Please report issues using the feature provided by Github

Discuss with us on Discord

Creative Commons - https://creativecommons.org/licenses/by-nc-sa/3.0/de/
- Check the licenses which are published on https://github.com/grindylow/ahoy as well

+ Check the licenses which are published on https://github.com/lumapu/ahoy as well

+ diff --git a/tools/esp8266/html/serial.html b/tools/esp8266/html/serial.html new file mode 100644 index 00000000..bc94fc40 --- /dev/null +++ b/tools/esp8266/html/serial.html @@ -0,0 +1,188 @@ + + + + Serial Console + + + + + +

Serial Console

+
+
+
+ connected: + Uptime: + + +
+
+
+
+
+

Commands

+
+ + +
+
+ + + +
+
+
+
+
+
+ + + + +
+ +
+

Ctrl result: n/a

+
+
+ + + + diff --git a/tools/esp8266/html/setup.html b/tools/esp8266/html/setup.html index 5be9e9d7..3c9269b1 100644 --- a/tools/esp8266/html/setup.html +++ b/tools/esp8266/html/setup.html @@ -1,50 +1,19 @@ - Setup - {DEVICE} + Setup + @@ -54,20 +23,20 @@
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.

- +
@@ -77,12 +46,13 @@
Inverter - {INVERTERS}
+

+

General

- + - +
@@ -91,9 +61,13 @@
NTP Server - + - + + + + + n/a
@@ -102,15 +76,15 @@
MQTT - + - + - + - + - +
@@ -119,47 +93,207 @@
System Config

Pinout (Wemos)

- {PINOUT} +

Radio (NRF24L01+)

- - +

Serial Console

-
+
-
+
- +
- + - - diff --git a/tools/esp8266/html/style.css b/tools/esp8266/html/style.css index f15f1cf1..258309bf 100644 --- a/tools/esp8266/html/style.css +++ b/tools/esp8266/html/style.css @@ -133,9 +133,14 @@ input.btn { background-color: #006ec0; color: #fff; border: 0px; - float: right; - margin: 10px 0 30px; + padding: 7px 20px 7px 20px; + margin-bottom: 10px; text-transform: uppercase; + cursor: pointer; +} + +input.btn:hover { + background-color: #044e86; } input.cb { @@ -245,7 +250,7 @@ div.ts { padding: 7px; } -div.modpwr, div.modname { +div.ModPwr, div.ModName { width:70%; display: inline-block; } @@ -271,3 +276,20 @@ div.modpwr, div.modname { width: 180px; } } + +#serial { + width: 100%; +} + +#content .serial { + max-width: 1000px; +} + +.dot { + height: 15px; + width: 15px; + background-color: #f00; + border-radius: 50%; + display: inline-block; + margin-top: 15px; +} diff --git a/tools/esp8266/html/update.html b/tools/esp8266/html/update.html new file mode 100644 index 00000000..55fd3e39 --- /dev/null +++ b/tools/esp8266/html/update.html @@ -0,0 +1,33 @@ + + + + Update + + + + + +

Update

+
+
+ Make sure that you have noted all your settings before starting an update. New versions may have changed their memory layout which can break your existing settings. +
+

+
+ +
+
+ + + + diff --git a/tools/esp8266/html/visualization.html b/tools/esp8266/html/visualization.html index ddb48fe7..8ee0926b 100644 --- a/tools/esp8266/html/visualization.html +++ b/tools/esp8266/html/visualization.html @@ -1,43 +1,118 @@ - Index - {DEVICE} + Live - - + -

AHOY - {DEVICE}

+

AHOY

-
-

Every {TS}seconds the values are updated

+
+

Every seconds the values are updated

+ diff --git a/tools/esp8266/include/dbg.h b/tools/esp8266/include/dbg.h index 8eb41f7c..17b334c9 100644 --- a/tools/esp8266/include/dbg.h +++ b/tools/esp8266/include/dbg.h @@ -10,6 +10,7 @@ #define F(sl) (sl) #endif +#include //----------------------------------------------------------------------------- // available levels #define DBG_ERROR 1 @@ -21,7 +22,9 @@ //----------------------------------------------------------------------------- // globally used level +#ifndef DEBUG_LEVEL #define DEBUG_LEVEL DBG_INFO +#endif #ifdef ARDUINO #include "Arduino.h" @@ -32,23 +35,40 @@ #define DBGPRINTLN(str) #else #ifdef ARDUINO + #define DBG_CB std::function + extern DBG_CB mCb; + + inline void registerDebugCb(DBG_CB cb) { + mCb = cb; + } + #ifndef DSERIAL #define DSERIAL Serial #endif - template - inline void DBGPRINT(T str) { DSERIAL.print(str); } - template - inline void DBGPRINTLN(T str) { DBGPRINT(str); DBGPRINT(F("\r\n")); } + //template + inline void DBGPRINT(String str) { DSERIAL.print(str); if(NULL != mCb) mCb(str); } + //template + inline void DBGPRINTLN(String str) { DBGPRINT(str); DBGPRINT(F("\r\n")); } inline void DHEX(uint8_t b) { - if( b<0x10 ) DSERIAL.print('0'); + if( b<0x10 ) DSERIAL.print(F("0")); DSERIAL.print(b,HEX); + if(NULL != mCb) { + if( b<0x10 ) mCb(F("0")); + mCb(String(b, HEX)); + } } inline void DHEX(uint16_t b) { if( b<0x10 ) DSERIAL.print(F("000")); else if( b<0x100 ) DSERIAL.print(F("00")); else if( b<0x1000 ) DSERIAL.print(F("0")); - DSERIAL.print(b,HEX); + DSERIAL.print(b, HEX); + if(NULL != mCb) { + if( b<0x10 ) mCb(F("000")); + else if( b<0x100 ) mCb(F("00")); + else if( b<0x1000 ) mCb(F("0")); + mCb(String(b, HEX)); + } } inline void DHEX(uint32_t b) { if( b<0x10 ) DSERIAL.print(F("0000000")); @@ -58,7 +78,17 @@ else if( b<0x100000 ) DSERIAL.print(F("000")); else if( b<0x1000000 ) DSERIAL.print(F("00")); else if( b<0x10000000 ) DSERIAL.print(F("0")); - DSERIAL.print(b,HEX); + DSERIAL.print(b, HEX); + if(NULL != mCb) { + if( b<0x10 ) mCb(F("0000000")); + else if( b<0x100 ) mCb(F("000000")); + else if( b<0x1000 ) mCb(F("00000")); + else if( b<0x10000 ) mCb(F("0000")); + else if( b<0x100000 ) mCb(F("000")); + else if( b<0x1000000 ) mCb(F("00")); + else if( b<0x10000000 ) mCb(F("0")); + mCb(String(b, HEX)); + } } #endif #endif diff --git a/tools/esp8266/platformio.ini b/tools/esp8266/platformio.ini index 48b292c7..a79fb8f7 100644 --- a/tools/esp8266/platformio.ini +++ b/tools/esp8266/platformio.ini @@ -32,17 +32,16 @@ extra_scripts = pre:html/convert.py lib_deps = - nrf24/RF24@1.4.5 - paulstoffregen/Time@^1.6.1 - knolleary/PubSubClient@^2.8 - bblanchon/ArduinoJson@^6.19.4 - ;esp8266/DNSServer@1.1.0 - ;esp8266/EEPROM@^1.0 - ;esp8266/ESP8266HTTPUpdateServer@^1.0 - ;esp8266/ESP8266WebServer@^1.0 - ;esp8266/ESP8266WiFi@^1.0 - ;esp8266/SPI@1.0 - ;esp8266/Ticker@^1.0 + https://github.com/yubox-node-org/ESPAsyncWebServer + nrf24/RF24 + paulstoffregen/Time + knolleary/PubSubClient + bblanchon/ArduinoJson + ;esp8266/DNSServer + ;esp8266/EEPROM + ;esp8266/ESP8266WiFi + ;esp8266/SPI + ;esp8266/Ticker [env:esp8266-release] platform = espressif8266 @@ -58,7 +57,7 @@ monitor_filters = platform = espressif8266 board = esp12e board_build.f_cpu = 80000000L -build_flags = -DDEBUG_ESP_CORE -DDEBUG_ESP_WIFI -DDEBUG_ESP_HTTP_CLIENT -DDEBUG_ESP_HTTP_SERVER -DDEBUG_ESP_OOM -DDEBUG_ESP_PORT=Serial +build_flags = -DDEBUG_LEVEL=DBG_DEBUG -DDEBUG_ESP_CORE -DDEBUG_ESP_WIFI -DDEBUG_ESP_HTTP_CLIENT -DDEBUG_ESP_HTTP_SERVER -DDEBUG_ESP_OOM -DDEBUG_ESP_PORT=Serial build_type = debug monitor_filters = ;default ; Remove typical terminal control codes from input @@ -68,7 +67,9 @@ monitor_filters = [env:esp32-wroom32-release] platform = espressif32 board = lolin_d32 -build_flags = -D RELEASE +build_flags = -D RELEASE -std=gnu++14 +build_unflags = -std=gnu++11 +upload_port = /dev/cu.SLAB_USBtoUART monitor_filters = ;default ; Remove typical terminal control codes from input time ; Add timestamp with milliseconds for each new line @@ -77,9 +78,11 @@ monitor_filters = [env:esp32-wroom32-debug] platform = espressif32 board = lolin_d32 -build_flags = -DDEBUG_ESP_CORE -DDEBUG_ESP_WIFI -DDEBUG_ESP_HTTP_CLIENT -DDEBUG_ESP_HTTP_SERVER -DDEBUG_ESP_OOM -DDEBUG_ESP_PORT=Serial +build_flags = -DDEBUG_LEVEL=DBG_DEBUG -DDEBUG_ESP_CORE -DDEBUG_ESP_WIFI -DDEBUG_ESP_HTTP_CLIENT -DDEBUG_ESP_HTTP_SERVER -DDEBUG_ESP_OOM -DDEBUG_ESP_PORT=Serial -std=gnu++14 +build_unflags = -std=gnu++11 build_type = debug +upload_port = /dev/cu.SLAB_USBtoUART monitor_filters = ;default ; Remove typical terminal control codes from input 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 \ No newline at end of file + log2file ; Log data to a file “platformio-device-monitor-*.log” located in the current working directory diff --git a/tools/esp8266/web.cpp b/tools/esp8266/web.cpp index 563cb635..0b64f6ba 100644 --- a/tools/esp8266/web.cpp +++ b/tools/esp8266/web.cpp @@ -12,41 +12,31 @@ #include "html/h/index_html.h" #include "html/h/style_css.h" -#include "favicon.h" +#include "html/h/api_js.h" +#include "html/h/favicon_ico_gz.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" - -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" -}; +const char* const pinArgNames[] = {"pinCs", "pinCe", "pinIrq"}; //----------------------------------------------------------------------------- -web::web(app *main, sysConfig_t *sysCfg, config_t *config, char version[]) { +web::web(app *main, sysConfig_t *sysCfg, config_t *config, statistics_t *stat, char version[]) { mMain = main; mSysCfg = sysCfg; mConfig = config; + mStat = stat; mVersion = version; - #ifdef ESP8266 - mWeb = new ESP8266WebServer(80); - mUpdater = new ESP8266HTTPUpdateServer(); - #elif defined(ESP32) - mWeb = new WebServer(80); - mUpdater = new HTTPUpdateServer(); - #endif - mUpdater->setup(mWeb); + mWeb = new AsyncWebServer(80); + mEvts = new AsyncEventSource("/events"); + mApi = new webApi(mWeb, main, sysCfg, config, stat, version); + + memset(mSerialBuf, 0, WEB_SERIAL_BUF_SIZE); + mSerialBufFill = 0; + mWebSerialTicker = 0; + mWebSerialInterval = 1000; // [ms] + mSerialAddTime = true; } @@ -54,108 +44,142 @@ web::web(app *main, sysConfig_t *sysCfg, config_t *config, char version[]) { void web::setup(void) { DPRINTLN(DBG_VERBOSE, F("app::setup-begin")); mWeb->begin(); - DPRINTLN(DBG_VERBOSE, F("app::setup-on")); - mWeb->on("/", std::bind(&web::showIndex, this)); - mWeb->on("/style.css", std::bind(&web::showCss, this)); - mWeb->on("/favicon.ico", std::bind(&web::showFavicon, this)); - mWeb->onNotFound ( std::bind(&web::showNotFound, this)); - mWeb->on("/uptime", std::bind(&web::showUptime, this)); - mWeb->on("/reboot", std::bind(&web::showReboot, this)); - mWeb->on("/erase", std::bind(&web::showErase, this)); - mWeb->on("/factory", std::bind(&web::showFactoryRst, this)); - - mWeb->on("/setup", std::bind(&web::showSetup, this)); - mWeb->on("/save", std::bind(&web::showSave, this)); - - mWeb->on("/cmdstat", std::bind(&web::showStatistics, this)); - mWeb->on("/visualization", std::bind(&web::showVisualization, this)); - mWeb->on("/livedata", std::bind(&web::showLiveData, this)); - mWeb->on("/json", std::bind(&web::showJson, this)); - mWeb->on("/api", HTTP_POST, std::bind(&web::showWebApi, this)); + DPRINTLN(DBG_VERBOSE, F("app::setup-on")); + mWeb->on("/", HTTP_GET, std::bind(&web::onIndex, 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)); + 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("/erase", HTTP_ANY, std::bind(&web::showErase, this, std::placeholders::_1)); + mWeb->on("/factory", HTTP_ANY, std::bind(&web::showFactoryRst, this, std::placeholders::_1)); + + mWeb->on("/setup", HTTP_GET, std::bind(&web::onSetup, this, std::placeholders::_1)); + mWeb->on("/save", HTTP_ANY, std::bind(&web::showSave, this, std::placeholders::_1)); + + mWeb->on("/live", HTTP_ANY, std::bind(&web::onLive, this, std::placeholders::_1)); + mWeb->on("/api1", HTTP_POST, std::bind(&web::showWebApi, this, std::placeholders::_1)); + + + mWeb->on("/update", HTTP_GET, std::bind(&web::onUpdate, this, std::placeholders::_1)); + mWeb->on("/update", HTTP_POST, std::bind(&web::showUpdate, this, std::placeholders::_1), + std::bind(&web::showUpdate2, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5, std::placeholders::_6)); + mWeb->on("/serial", HTTP_GET, std::bind(&web::onSerial, this, std::placeholders::_1)); + + + mEvts->onConnect(std::bind(&web::onConnect, this, std::placeholders::_1)); + mWeb->addHandler(mEvts); + + mApi->setup(); + + registerDebugCb(std::bind(&web::serialCb, this, std::placeholders::_1)); } //----------------------------------------------------------------------------- void web::loop(void) { - mWeb->handleClient(); + mApi->loop(); + + if(mMain->checkTicker(&mWebSerialTicker, mWebSerialInterval)) { + if(mSerialBufFill > 0) { + mEvts->send(mSerialBuf, "serial", millis()); + memset(mSerialBuf, 0, WEB_SERIAL_BUF_SIZE); + mSerialBufFill = 0; + } + } } //----------------------------------------------------------------------------- -void web::showIndex(void) { - DPRINTLN(DBG_VERBOSE, F("showIndex")); - String html = FPSTR(index_html); - html.replace(F("{DEVICE}"), mSysCfg->deviceName); - html.replace(F("{VERSION}"), mVersion); - html.replace(F("{TS}"), String(mConfig->sendInterval) + " "); - html.replace(F("{JS_TS}"), String(mConfig->sendInterval * 1000)); - html.replace(F("{BUILD}"), String(AUTO_GIT_HASH)); - mWeb->send(200, "text/html", html); +void web::onConnect(AsyncEventSourceClient *client) { + DPRINTLN(DBG_VERBOSE, "onConnect"); + + if(client->lastId()) + DPRINTLN(DBG_VERBOSE, "Client reconnected! Last message ID that it got is: " + String(client->lastId())); + + client->send("hello!", NULL, millis(), 1000); } //----------------------------------------------------------------------------- -void web::showCss(void) { - mWeb->send(200, "text/css", FPSTR(style_css)); +void web::onIndex(AsyncWebServerRequest *request) { + DPRINTLN(DBG_VERBOSE, F("onIndex")); + + AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html"), index_html, index_html_len); + response->addHeader(F("Content-Encoding"), "gzip"); + request->send(response); } //----------------------------------------------------------------------------- -void web::showFavicon(void) { - static const char favicon_type[] PROGMEM = "image/x-icon"; - static const char favicon_content[] PROGMEM = FAVICON_PANEL_16; - mWeb->send_P(200, favicon_type, favicon_content, sizeof(favicon_content)); +void web::onCss(AsyncWebServerRequest *request) { + AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/css"), style_css, style_css_len); + response->addHeader(F("Content-Encoding"), "gzip"); + request->send(response); } + //----------------------------------------------------------------------------- -void web::showNotFound(void) { - DPRINTLN(DBG_VERBOSE, F("showNotFound - ") + mWeb->uri()); - String msg = F("File Not Found\n\nURI: "); - msg += mWeb->uri(); - mWeb->send(404, F("text/plain"), msg); +void web::onApiJs(AsyncWebServerRequest *request) { + DPRINTLN(DBG_VERBOSE, F("onApiJs")); + + AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/javascript"), api_js, api_js_len); + response->addHeader(F("Content-Encoding"), "gzip"); + request->send(response); } //----------------------------------------------------------------------------- -void web::showUptime(void) { - char time[21] = {0}; - uint32_t uptime = mMain->getUptime(); +void web::onFavicon(AsyncWebServerRequest *request) { + static const char favicon_type[] PROGMEM = "image/x-icon"; + AsyncWebServerResponse *response = request->beginResponse_P(200, favicon_type, favicon_ico_gz, favicon_ico_gz_len); + response->addHeader(F("Content-Encoding"), "gzip"); + request->send(response); +} - uint32_t upTimeSc = uint32_t((uptime) % 60); - uint32_t upTimeMn = uint32_t((uptime / (60)) % 60); - uint32_t upTimeHr = uint32_t((uptime / (60 * 60)) % 24); - uint32_t upTimeDy = uint32_t((uptime / (60 * 60 * 24)) % 365); - snprintf(time, 20, "%d Days, %02d:%02d:%02d", upTimeDy, upTimeHr, upTimeMn, upTimeSc); +//----------------------------------------------------------------------------- +void web::showNotFound(AsyncWebServerRequest *request) { + DPRINTLN(DBG_VERBOSE, F("showNotFound - ") + request->url()); + String msg = F("File Not Found\n\nURL: "); + msg += request->url(); + msg += F("\nMethod: "); + msg += ( request->method() == HTTP_GET ) ? "GET" : "POST"; + msg += F("\nArguments: "); + msg += request->args(); + msg += "\n"; + + for(uint8_t i = 0; i < request->args(); i++ ) { + msg += " " + request->argName(i) + ": " + request->arg(i) + "\n"; + } - mWeb->send(200, "text/plain", String(time) + "; now: " + mMain->getDateTimeStr(mMain->getTimestamp())); + request->send(404, F("text/plain"), msg); } //----------------------------------------------------------------------------- -void web::showReboot(void) { - mWeb->send(200, F("text/html"), F("Rebooting ...rebooting ... auto reload after 10s")); - delay(1000); - ESP.restart(); +void web::onReboot(AsyncWebServerRequest *request) { + request->send(200, F("text/html"), F("Rebooting ...rebooting ... auto reload after 10s")); + mMain->mShouldReboot = true; } //----------------------------------------------------------------------------- -void web::showErase() { +void web::showErase(AsyncWebServerRequest *request) { DPRINTLN(DBG_VERBOSE, F("showErase")); mMain->eraseSettings(); - showReboot(); + onReboot(request); } //----------------------------------------------------------------------------- -void web::showFactoryRst(void) { +void web::showFactoryRst(AsyncWebServerRequest *request) { DPRINTLN(DBG_VERBOSE, F("showFactoryRst")); String content = ""; int refresh = 3; - if(mWeb->args() > 0) { - if(mWeb->arg("reset").toInt() == 1) { + if(request->args() > 0) { + if(request->arg("reset").toInt() == 1) { mMain->eraseSettings(true); content = F("factory reset: success\n\nrebooting ... "); refresh = 10; @@ -170,7 +194,7 @@ void web::showFactoryRst(void) { "

RESET

CANCEL

"); refresh = 120; } - mWeb->send(200, F("text/html"), F("Factory Reset") + content + F("")); + request->send(200, F("text/html"), F("Factory Reset") + content + F("")); if(refresh == 10) { delay(1000); ESP.restart(); @@ -179,193 +203,59 @@ void web::showFactoryRst(void) { //----------------------------------------------------------------------------- -void web::showSetup(void) { - DPRINTLN(DBG_VERBOSE, F("showSetup")); - String html = FPSTR(setup_html); - html.replace(F("{SSID}"), mSysCfg->stationSsid); - // PWD will be left at the default value (for protection) - // -> the PWD will only be changed if it does not match the default "{PWD}" - html.replace(F("{DEVICE}"), String(mSysCfg->deviceName)); - html.replace(F("{VERSION}"), String(mVersion)); - if(mMain->getWifiApActive()) - html.replace("{IP}", String(F("http://192.168.1.1"))); - else - html.replace("{IP}", (F("http://") + String(WiFi.localIP().toString()))); - - String inv = ""; - Inverter<> *iv; - for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i ++) { - iv = mMain->mSys->getInverterByPos(i); - - inv += F("

Inverter ") + String(i) + "

"; - inv += F(""); - inv += F("serial.u64, HEX); - inv += F("\"/ maxlength=\"12\">"); - - inv += F(""); - inv += F("name); - inv += F("\"/ maxlength=\"") + String(MAX_NAME_LENGTH) + "\">"; - - inv += F(""); - inv += F("powerLimit[0]); - inv += F("\"/ maxlength=\"") + String(6) + "\">"; - - inv += F(""); - inv += F(""); - - inv += F("
"); - for(uint8_t j = 0; j < 4; j++) { - inv += F("chMaxPwr[j]); - inv += F("\"/ maxlength=\"4\">"); - } - inv += F("

"); - for(uint8_t j = 0; j < 4; j++) { - inv += F("chName[j]); - inv += F("\"/ maxlength=\"") + String(MAX_NAME_LENGTH) + "\">"; - } - inv += F("
"); - } - html.replace(F("{INVERTERS}"), String(inv)); - +void web::onSetup(AsyncWebServerRequest *request) { + DPRINTLN(DBG_VERBOSE, F("onSetup")); - // pinout - String pinout; - for(uint8_t i = 0; i < 3; i++) { - pinout += F(""); - pinout += F(""); - } - html.replace(F("{PINOUT}"), String(pinout)); - - - // nrf24l01+ - String rf24; - for(uint8_t i = 0; i <= 3; i++) { - rf24 += F(""); - } - html.replace(F("{RF24}"), String(rf24)); - - - html.replace(F("{INV_INTVL}"), String(mConfig->sendInterval)); - html.replace(F("{INV_RETRIES}"), String(mConfig->maxRetransPerPyld)); - - html.replace(F("{SER_INTVL}"), String(mConfig->serialInterval)); - html.replace(F("{SER_VAL_CB}"), (mConfig->serialShowIv) ? "checked" : ""); - html.replace(F("{SER_DBG_CB}"), (mConfig->serialDebug) ? "checked" : ""); - - html.replace(F("{NTP_ADDR}"), String(mConfig->ntpAddr)); - html.replace(F("{NTP_PORT}"), String(mConfig->ntpPort)); - - html.replace(F("{MQTT_ADDR}"), String(mConfig->mqtt.broker)); - html.replace(F("{MQTT_PORT}"), String(mConfig->mqtt.port)); - html.replace(F("{MQTT_USER}"), String(mConfig->mqtt.user)); - html.replace(F("{MQTT_PWD}"), String(mConfig->mqtt.pwd)); - html.replace(F("{MQTT_TOPIC}"), String(mConfig->mqtt.topic)); - - mWeb->send(200, F("text/html"), html); + AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html"), setup_html, setup_html_len); + response->addHeader(F("Content-Encoding"), "gzip"); + request->send(response); } //----------------------------------------------------------------------------- -void web::showSave(void) { +void web::showSave(AsyncWebServerRequest *request) { DPRINTLN(DBG_VERBOSE, F("showSave")); - if(mWeb->args() > 0) { + if(request->args() > 0) { char buf[20] = {0}; // general - if(mWeb->arg("ssid") != "") - mWeb->arg("ssid").toCharArray(mSysCfg->stationSsid, SSID_LEN); - if(mWeb->arg("pwd") != "{PWD}") - mWeb->arg("pwd").toCharArray(mSysCfg->stationPwd, PWD_LEN); - if(mWeb->arg("device") != "") - mWeb->arg("device").toCharArray(mSysCfg->deviceName, DEVNAME_LEN); + if(request->arg("ssid") != "") + request->arg("ssid").toCharArray(mSysCfg->stationSsid, SSID_LEN); + if(request->arg("pwd") != "{PWD}") + request->arg("pwd").toCharArray(mSysCfg->stationPwd, PWD_LEN); + if(request->arg("device") != "") + request->arg("device").toCharArray(mSysCfg->deviceName, DEVNAME_LEN); // inverter Inverter<> *iv; for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i ++) { iv = mMain->mSys->getInverterByPos(i, false); // address - mWeb->arg("inv" + String(i) + "Addr").toCharArray(buf, 20); + request->arg("inv" + String(i) + "Addr").toCharArray(buf, 20); if(strlen(buf) == 0) memset(buf, 0, 20); iv->serial.u64 = mMain->Serial2u64(buf); - // active power limit - uint16_t actPwrLimit = mWeb->arg("inv" + String(i) + "ActivePowerLimit").toInt(); - uint16_t actPwrLimitControl = mWeb->arg("inv" + String(i) + "PowerLimitControl").toInt(); - if (actPwrLimit != 0xffff && actPwrLimit > 0){ - iv->powerLimit[0] = actPwrLimit; - iv->powerLimit[1] = actPwrLimitControl; - iv->devControlCmd = ActivePowerContr; - iv->devControlRequest = true; - 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 { - 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% - iv->powerLimit[0] = 100; - iv->powerLimit[1] = RelativPersistent; - iv->devControlCmd = ActivePowerContr; - iv->devControlRequest = true; - DPRINTLN(DBG_INFO, F("Power limit for inverter ") + String(iv->id) + F(" set to unlimted")); - } - // name - mWeb->arg("inv" + String(i) + "Name").toCharArray(iv->name, MAX_NAME_LENGTH); + request->arg("inv" + String(i) + "Name").toCharArray(iv->name, MAX_NAME_LENGTH); // max channel power / name for(uint8_t j = 0; j < 4; j++) { - iv->chMaxPwr[j] = mWeb->arg("inv" + String(i) + "ModPwr" + String(j)).toInt() & 0xffff; - mWeb->arg("inv" + String(i) + "ModName" + String(j)).toCharArray(iv->chName[j], MAX_NAME_LENGTH); + iv->chMaxPwr[j] = request->arg("inv" + String(i) + "ModPwr" + String(j)).toInt() & 0xffff; + request->arg("inv" + String(i) + "ModName" + String(j)).toCharArray(iv->chName[j], MAX_NAME_LENGTH); } iv->initialized = true; } - if(mWeb->arg("invInterval") != "") - mConfig->sendInterval = mWeb->arg("invInterval").toInt(); - if(mWeb->arg("invRetry") != "") - mConfig->maxRetransPerPyld = mWeb->arg("invRetry").toInt(); + if(request->arg("invInterval") != "") + mConfig->sendInterval = request->arg("invInterval").toInt(); + if(request->arg("invRetry") != "") + mConfig->maxRetransPerPyld = request->arg("invRetry").toInt(); // pinout uint8_t pin; for(uint8_t i = 0; i < 3; i ++) { - pin = mWeb->arg(String(pinArgNames[i])).toInt(); + pin = request->arg(String(pinArgNames[i])).toInt(); switch(i) { default: mConfig->pinCs = pin; break; case 1: mConfig->pinCe = pin; break; @@ -374,220 +264,74 @@ void web::showSave(void) { } // nrf24 amplifier power - mConfig->amplifierPower = mWeb->arg("rf24Power").toInt() & 0x03; + mConfig->amplifierPower = request->arg("rf24Power").toInt() & 0x03; // ntp - if(mWeb->arg("ntpAddr") != "") { - mWeb->arg("ntpAddr").toCharArray(mConfig->ntpAddr, NTP_ADDR_LEN); - mConfig->ntpPort = mWeb->arg("ntpPort").toInt() & 0xffff; + if(request->arg("ntpAddr") != "") { + request->arg("ntpAddr").toCharArray(mConfig->ntpAddr, NTP_ADDR_LEN); + mConfig->ntpPort = request->arg("ntpPort").toInt() & 0xffff; } // mqtt - if(mWeb->arg("mqttAddr") != "") { - String addr = mWeb->arg("mqttAddr"); + if(request->arg("mqttAddr") != "") { + String addr = request->arg("mqttAddr"); addr.trim(); addr.toCharArray(mConfig->mqtt.broker, MQTT_ADDR_LEN); - mWeb->arg("mqttUser").toCharArray(mConfig->mqtt.user, MQTT_USER_LEN); - mWeb->arg("mqttPwd").toCharArray(mConfig->mqtt.pwd, MQTT_PWD_LEN); - mWeb->arg("mqttTopic").toCharArray(mConfig->mqtt.topic, MQTT_TOPIC_LEN); - mConfig->mqtt.port = mWeb->arg("mqttPort").toInt(); + request->arg("mqttUser").toCharArray(mConfig->mqtt.user, MQTT_USER_LEN); + 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(); } // serial console - if(mWeb->arg("serIntvl") != "") { - mConfig->serialInterval = mWeb->arg("serIntvl").toInt() & 0xffff; + if(request->arg("serIntvl") != "") { + mConfig->serialInterval = request->arg("serIntvl").toInt() & 0xffff; - mConfig->serialDebug = (mWeb->arg("serDbg") == "on"); - mConfig->serialShowIv = (mWeb->arg("serEn") == "on"); + mConfig->serialDebug = (request->arg("serDbg") == "on"); + mConfig->serialShowIv = (request->arg("serEn") == "on"); // Needed to log TX buffers to serial console mMain->mSys->Radio.mSerialDebug = mConfig->serialDebug; } mMain->saveValues(); - if(mWeb->arg("reboot") == "on") - showReboot(); + if(request->arg("reboot") == "on") + onReboot(request); else - mWeb->send(200, F("text/html"), F("Setup saved" + request->send(200, F("text/html"), F("Setup saved" "

saved

")); } } //----------------------------------------------------------------------------- -void web::showStatistics(void) { - DPRINTLN(DBG_VERBOSE, F("web::showStatistics")); - mWeb->send(200, F("text/plain"), mMain->getStatistics()); -} - - -//----------------------------------------------------------------------------- -void web::showVisualization(void) { - DPRINTLN(DBG_VERBOSE, F("web::showVisualization")); - String html = FPSTR(visualization_html); - html.replace(F("{DEVICE}"), mSysCfg->deviceName); - html.replace(F("{VERSION}"), mVersion); - html.replace(F("{TS}"), String(mConfig->sendInterval) + " "); - html.replace(F("{JS_TS}"), String(mConfig->sendInterval * 1000)); - mWeb->send(200, F("text/html"), html); -} - - -//----------------------------------------------------------------------------- -void web::showLiveData(void) { - DPRINTLN(DBG_VERBOSE, F("web::showLiveData")); - - String modHtml, totalModHtml; - float totalYield = 0, totalYieldToday = 0, totalActual = 0; - uint8_t count = 0; - - for (uint8_t id = 0; id < mMain->mSys->getNumInverters(); id++) { - count++; - - 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(fld == 6){ - totalYield += iv->getValue(pos); - } - - if(fld == 7){ - totalYieldToday += iv->getValue(pos); - } +void web::onLive(AsyncWebServerRequest *request) { + DPRINTLN(DBG_VERBOSE, F("onLive")); - if(fld == 2){ - totalActual += iv->getValue(pos); - } - - 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 - } - } - - if(count > 1){ - totalModHtml += F("
" - "
Gesamt"); - - totalModHtml += F("
"); - totalModHtml += F("") + String(totalActual); - totalModHtml += F("W"); - totalModHtml += F("P_AC All"); - totalModHtml += F("
"); - - totalModHtml += F("
"); - totalModHtml += F("") + String(totalYieldToday); - totalModHtml += F("Wh"); - totalModHtml += F("YieldDayAll"); - totalModHtml += F("
"); - - totalModHtml += F("
"); - totalModHtml += F("") + String(totalYield); - totalModHtml += F("kWh"); - totalModHtml += F("YieldTotalAll"); - totalModHtml += F("
"); - - totalModHtml += F("
"); - totalModHtml += F("
"); - mWeb->send(200, F("text/html"), totalModHtml + modHtml); - } else { - mWeb->send(200, F("text/html"), modHtml); - } + AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html"), visualization_html, visualization_html_len); + response->addHeader(F("Content-Encoding"), "gzip"); + request->send(response); } //----------------------------------------------------------------------------- -void web::showJson(void) { - DPRINTLN(DBG_VERBOSE, F("web::showJson")); - mWeb->send(200, F("application/json"), mMain->getJson()); -} - -//----------------------------------------------------------------------------- -void web::showWebApi(void) -{ +void web::showWebApi(AsyncWebServerRequest *request) { DPRINTLN(DBG_VERBOSE, F("web::showWebApi")); - DPRINTLN(DBG_DEBUG, mWeb->arg("plain")); + DPRINTLN(DBG_DEBUG, request->arg("plain")); const size_t capacity = 200; // Use arduinojson.org/assistant to compute the capacity. DynamicJsonDocument response(capacity); // Parse JSON object - deserializeJson(response, mWeb->arg("plain")); + deserializeJson(response, request->arg("plain")); // ToDo: error handling for payload uint8_t iv_id = response["inverter"]; uint8_t cmd = response["cmd"]; Inverter<> *iv = mMain->mSys->getInverterByPos(iv_id); - if (NULL != iv) - { - if (response["tx_request"] == (uint8_t)TX_REQ_INFO) - { + if (NULL != iv) { + 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 || cmd == AlarmUpdate){ + if (cmd == AlarmData || cmd == AlarmUpdate) { // set the AlarmMesIndex for the request from user input iv->alarmMesIndex = response["payload"]; } @@ -597,44 +341,137 @@ void web::showWebApi(void) } - if (response["tx_request"] == (uint8_t)TX_REQ_DEVCONTROL) - { - if (response["cmd"] == (uint8_t)ActivePowerContr) - { + if (response["tx_request"] == (uint8_t)TX_REQ_DEVCONTROL) { + if (response["cmd"] == (uint8_t)ActivePowerContr) { uint16_t webapiPayload = response["payload"]; uint16_t webapiPayload2 = response["payload2"]; - if (webapiPayload > 0 && webapiPayload < 10000) - { + if (webapiPayload > 0 && webapiPayload < 10000) { iv->devControlCmd = ActivePowerContr; iv->powerLimit[0] = webapiPayload; if (webapiPayload2 > 0) - { iv->powerLimit[1] = webapiPayload2; // dev option, no sanity check - } - else - { // if not set, set it to 0x0000 default - iv->powerLimit[1] = AbsolutNonPersistent; // payload will be seted temporay in Watt absolut - } + else // if not set, set it to 0x0000 default + iv->powerLimit[1] = AbsolutNonPersistent; // payload will be seted temporary in Watt absolut if (iv->powerLimit[1] & 0x0001) - { DPRINTLN(DBG_INFO, F("Power limit for inverter ") + String(iv->id) + F(" set to ") + String(iv->powerLimit[0]) + F("% via REST API")); - } else - { DPRINTLN(DBG_INFO, F("Power limit for inverter ") + String(iv->id) + F(" set to ") + String(iv->powerLimit[0]) + F("W via REST API")); - } iv->devControlRequest = true; // queue it in the request loop } } - if (response["cmd"] == (uint8_t)TurnOff){ + if (response["cmd"] == (uint8_t)TurnOff) { iv->devControlCmd = TurnOff; iv->devControlRequest = true; // queue it in the request loop } - if (response["cmd"] == (uint8_t)TurnOn){ + if (response["cmd"] == (uint8_t)TurnOn) { iv->devControlCmd = TurnOn; iv->devControlRequest = true; // queue it in the request loop } + if (response["cmd"] == (uint8_t)CleanState_LockAndAlarm) { + iv->devControlCmd = CleanState_LockAndAlarm; + iv->devControlRequest = true; // queue it in the request loop + } + if (response["cmd"] == (uint8_t)Restart) { + iv->devControlCmd = Restart; + iv->devControlRequest = true; // queue it in the request loop + } + } + } + request->send(200, "text/json", "{success:true}"); +} + + +//----------------------------------------------------------------------------- +void web::onUpdate(AsyncWebServerRequest *request) { + DPRINTLN(DBG_VERBOSE, F("onUpdate")); + + + AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html"), update_html, update_html_len); + response->addHeader(F("Content-Encoding"), "gzip"); + request->send(response); +} + + +//----------------------------------------------------------------------------- +void web::showUpdate(AsyncWebServerRequest *request) { + bool reboot = !Update.hasError(); + + String html = F("UpdateUpdate: "); + if(reboot) + html += "success"; + else + html += "failed"; + html += F("

rebooting ... auto reload after 20s"); + + AsyncWebServerResponse *response = request->beginResponse(200, F("text/html"), html); + response->addHeader("Connection", "close"); + request->send(response); + mMain->mShouldReboot = reboot; +} + + +//----------------------------------------------------------------------------- +void web::showUpdate2(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) { + if(!index) { + Serial.printf("Update Start: %s\n", filename.c_str()); +#ifndef ESP32 + Update.runAsync(true); +#endif + if(!Update.begin((ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000)) { + Update.printError(Serial); } } - mWeb->send(200, "text/json", "{success:true}"); + 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); + } else { + Update.printError(Serial); + } + } +} + + +//----------------------------------------------------------------------------- +void web::onSerial(AsyncWebServerRequest *request) { + DPRINTLN(DBG_VERBOSE, F("onSerial")); + + AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html"), serial_html, serial_html_len); + response->addHeader(F("Content-Encoding"), "gzip"); + request->send(response); +} + + +//----------------------------------------------------------------------------- +void web::serialCb(String msg) { + msg.replace("\r\n", ""); + if(mSerialAddTime) { + if((9 + mSerialBufFill) <= WEB_SERIAL_BUF_SIZE) { + strncpy(&mSerialBuf[mSerialBufFill], mMain->getTimeStr().c_str(), 9); + mSerialBufFill += 9; + } + else { + mSerialBufFill = 0; + mEvts->send("webSerial, buffer overflow!", "serial", millis()); + } + mSerialAddTime = false; + } + + if(msg.endsWith("")) + mSerialAddTime = true; + + uint16_t length = msg.length(); + if((length + mSerialBufFill) <= WEB_SERIAL_BUF_SIZE) { + strncpy(&mSerialBuf[mSerialBufFill], msg.c_str(), length); + mSerialBufFill += length; + } + else { + mSerialBufFill = 0; + mEvts->send("webSerial, buffer overflow!", "serial", millis()); + } + } diff --git a/tools/esp8266/web.h b/tools/esp8266/web.h index ad9aa7c6..a91d9d3a 100644 --- a/tools/esp8266/web.h +++ b/tools/esp8266/web.h @@ -7,56 +7,69 @@ #define __WEB_H__ #include "dbg.h" -#ifdef ESP8266 - #include - #include -#elif defined(ESP32) - #include - #include +#ifdef ESP32 + #include "AsyncTCP.h" + #include "Update.h" +#else + #include "ESPAsyncTCP.h" #endif - +#include "ESPAsyncWebServer.h" #include "app.h" +#include "webApi.h" + +#define WEB_SERIAL_BUF_SIZE 2048 class app; +class webApi; class web { public: - web(app *main, sysConfig_t *sysCfg, config_t *config, char version[]); + web(app *main, sysConfig_t *sysCfg, config_t *config, statistics_t *stat, char version[]); ~web() {} void setup(void); void loop(void); - void showIndex(void); - void showCss(void); - void showFavicon(void); - void showNotFound(void); - void showUptime(void); - void showReboot(void); - void showErase(); - void showFactoryRst(void); - void showSetup(void); - void showSave(void); - - void showStatistics(void); - void showVisualization(void); - void showLiveData(void); - void showJson(void); - void showWebApi(void); + void onConnect(AsyncEventSourceClient *client); + + void onIndex(AsyncWebServerRequest *request); + void onCss(AsyncWebServerRequest *request); + void onApiJs(AsyncWebServerRequest *request); + void onFavicon(AsyncWebServerRequest *request); + void showNotFound(AsyncWebServerRequest *request); + void onReboot(AsyncWebServerRequest *request); + void showErase(AsyncWebServerRequest *request); + void showFactoryRst(AsyncWebServerRequest *request); + void onSetup(AsyncWebServerRequest *request); + void showSave(AsyncWebServerRequest *request); + + void onLive(AsyncWebServerRequest *request); + void showWebApi(AsyncWebServerRequest *request); + + void onUpdate(AsyncWebServerRequest *request); + void showUpdate(AsyncWebServerRequest *request); + void showUpdate2(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final); + + void serialCb(String msg); private: - #ifdef ESP8266 - ESP8266WebServer *mWeb; - ESP8266HTTPUpdateServer *mUpdater; - #elif defined(ESP32) - WebServer *mWeb; - HTTPUpdateServer *mUpdater; - #endif + void onSerial(AsyncWebServerRequest *request); + + AsyncWebServer *mWeb; + AsyncEventSource *mEvts; config_t *mConfig; sysConfig_t *mSysCfg; + statistics_t *mStat; char *mVersion; app *mMain; + webApi *mApi; + + bool mSerialAddTime; + char mSerialBuf[WEB_SERIAL_BUF_SIZE]; + uint16_t mSerialBufFill; + uint32_t mWebSerialTicker; + uint32_t mWebSerialInterval; }; #endif /*__WEB_H__*/ diff --git a/tools/esp8266/webApi.cpp b/tools/esp8266/webApi.cpp new file mode 100644 index 00000000..e7f2e4af --- /dev/null +++ b/tools/esp8266/webApi.cpp @@ -0,0 +1,415 @@ +//----------------------------------------------------------------------------- +// 2022 Ahoy, https://www.mikrocontroller.net/topic/525778 +// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/ +//----------------------------------------------------------------------------- + +#if defined(ESP32) && defined(F) + #undef F + #define F(sl) (sl) +#endif + +#include "webApi.h" + +//----------------------------------------------------------------------------- +webApi::webApi(AsyncWebServer *srv, app *app, sysConfig_t *sysCfg, config_t *config, statistics_t *stat, char version[]) { + mSrv = srv; + mApp = app; + mSysCfg = sysCfg; + mConfig = config; + mStat = stat; + mVersion = version; +} + + +//----------------------------------------------------------------------------- +void webApi::setup(void) { + mSrv->on("/api", HTTP_GET, std::bind(&webApi::onApi, this, std::placeholders::_1)); + mSrv->on("/api", HTTP_POST, std::bind(&webApi::onApiPost, this, std::placeholders::_1)).onBody( + std::bind(&webApi::onApiPostBody, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5)); +} + + +//----------------------------------------------------------------------------- +void webApi::loop(void) { +} + + +//----------------------------------------------------------------------------- +void webApi::onApi(AsyncWebServerRequest *request) { + AsyncJsonResponse* response = new AsyncJsonResponse(false, 8192); + JsonObject root = response->getRoot(); + + Inverter<> *iv = mApp->mSys->getInverterByPos(0, false); + String path = request->url().substring(5); + if(path == "system") getSystem(root); + else if(path == "statistics") getStatistics(root); + else if(path == "inverter/list") getInverterList(root); + else if(path == "index") getIndex(root); + else if(path == "setup") getSetup(root); + else if(path == "live") getLive(root); + else if(path == "record/info") getRecord(root, iv->getRecordStruct(InverterDevInform_All)); + else if(path == "record/alarm") getRecord(root, iv->getRecordStruct(AlarmData)); + else if(path == "record/config") getRecord(root, iv->getRecordStruct(SystemConfigPara)); + else if(path == "record/live") getRecord(root, iv->getRecordStruct(RealTimeRunData_Debug)); + else + getNotFound(root, F("http://") + request->host() + F("/api/")); + + response->addHeader("Access-Control-Allow-Origin", "*"); + response->addHeader("Access-Control-Allow-Headers", "content-type"); + response->setLength(); + request->send(response); +} + + +//----------------------------------------------------------------------------- +void webApi::onApiPost(AsyncWebServerRequest *request) { + DPRINTLN(DBG_VERBOSE, "onApiPost"); +} + + +//----------------------------------------------------------------------------- +void webApi::onApiPostBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) { + DPRINTLN(DBG_VERBOSE, "onApiPostBody"); + DynamicJsonDocument json(200); + AsyncJsonResponse* response = new AsyncJsonResponse(false, 200); + JsonObject root = response->getRoot(); + + DeserializationError err = deserializeJson(json, (const char *)data); + root[F("success")] = (err) ? false : true; + if(!err) { + String path = request->url().substring(5); + if(path == "ctrl") + root[F("success")] = setCtrl(json, root); + else if(path == "setup") + root[F("success")] = setSetup(json, root); + else { + root[F("success")] = false; + root[F("error")] = "Path not found: " + path; + } + } + else { + switch (err.code()) { + case DeserializationError::Ok: break; + case DeserializationError::InvalidInput: root[F("error")] = F("Invalid input"); break; + case DeserializationError::NoMemory: root[F("error")] = F("Not enough memory"); break; + default: root[F("error")] = F("Deserialization failed"); break; + } + } + + response->setLength(); + request->send(response); +} + + +//----------------------------------------------------------------------------- +void webApi::getNotFound(JsonObject obj, String url) { + JsonObject ep = obj.createNestedObject("avail_endpoints"); + ep[F("system")] = url + F("system"); + ep[F("statistics")] = url + F("statistics"); + ep[F("inverter/list")] = url + F("inverter/list"); + ep[F("index")] = url + F("index"); + ep[F("setup")] = url + F("setup"); + ep[F("live")] = url + F("live"); + ep[F("record/info")] = url + F("record/info"); + ep[F("record/alarm")] = url + F("record/alarm"); + ep[F("record/config")] = url + F("record/config"); + ep[F("record/live")] = url + F("record/live"); +} + + +//----------------------------------------------------------------------------- +void webApi::getSystem(JsonObject obj) { + obj[F("ssid")] = mSysCfg->stationSsid; + obj[F("device_name")] = mSysCfg->deviceName; + obj[F("version")] = String(mVersion); + obj[F("build")] = String(AUTO_GIT_HASH); + obj[F("ts_uptime")] = mApp->getUptime(); + obj[F("ts_now")] = mApp->getTimestamp(); + obj[F("wifi_rssi")] = WiFi.RSSI(); +} + + +//----------------------------------------------------------------------------- +void webApi::getStatistics(JsonObject obj) { + obj[F("rx_success")] = mStat->rxSuccess; + obj[F("rx_fail")] = mStat->rxFail; + obj[F("rx_fail_answer")] = mStat->rxFailNoAnser; + obj[F("frame_cnt")] = mStat->frmCnt; + obj[F("tx_cnt")] = mApp->mSys->Radio.mSendCnt; +} + + +//----------------------------------------------------------------------------- +void webApi::getInverterList(JsonObject obj) { + JsonArray invArr = obj.createNestedArray(F("inverter")); + + Inverter<> *iv; + for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i ++) { + iv = mApp->mSys->getInverterByPos(i); + if(NULL != iv) { + JsonObject obj2 = invArr.createNestedObject(); + obj2[F("id")] = i; + obj2[F("name")] = String(iv->name); + obj2[F("serial")] = String(iv->serial.u64, HEX); + obj2[F("channels")] = iv->channels; + obj2[F("version")] = String(iv->fwVersion); + + for(uint8_t j = 0; j < iv->channels; j ++) { + obj2[F("ch_max_power")][j] = iv->chMaxPwr[j]; + obj2[F("ch_name")][j] = iv->chName[j]; + } + } + } + obj[F("interval")] = String(mConfig->sendInterval); + obj[F("retries")] = String(mConfig->maxRetransPerPyld); + obj[F("max_num_inverters")] = MAX_NUM_INVERTERS; +} + + +//----------------------------------------------------------------------------- +void webApi::getMqtt(JsonObject obj) { + obj[F("broker")] = String(mConfig->mqtt.broker); + obj[F("port")] = String(mConfig->mqtt.port); + obj[F("user")] = String(mConfig->mqtt.user); + obj[F("pwd")] = (strlen(mConfig->mqtt.pwd) > 0) ? F("{PWD}") : String(""); + obj[F("topic")] = String(mConfig->mqtt.topic); +} + + +//----------------------------------------------------------------------------- +void webApi::getNtp(JsonObject obj) { + obj[F("addr")] = String(mConfig->ntpAddr); + obj[F("port")] = String(mConfig->ntpPort); +} + + +//----------------------------------------------------------------------------- +void webApi::getPinout(JsonObject obj) { + obj[F("cs")] = mConfig->pinCs; + obj[F("ce")] = mConfig->pinCe; + obj[F("irq")] = mConfig->pinIrq; +} + + +//----------------------------------------------------------------------------- +void webApi::getRadio(JsonObject obj) { + obj[F("power_level")] = mConfig->amplifierPower; +} + + +//----------------------------------------------------------------------------- +void webApi::getSerial(JsonObject obj) { + obj[F("interval")] = (uint16_t)mConfig->serialInterval; + obj[F("show_live_data")] = mConfig->serialShowIv; + obj[F("debug")] = mConfig->serialDebug; +} + + +//----------------------------------------------------------------------------- +void webApi::getIndex(JsonObject obj) { + getSystem(obj.createNestedObject(F("system"))); + getStatistics(obj.createNestedObject(F("statistics"))); + obj["refresh_interval"] = SEND_INTERVAL; + + JsonArray inv = obj.createNestedArray(F("inverter")); + Inverter<> *iv; + for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i ++) { + iv = mApp->mSys->getInverterByPos(i); + if(NULL != iv) { + record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug); + JsonObject invObj = inv.createNestedObject(); + invObj[F("id")] = i; + invObj[F("name")] = String(iv->name); + invObj[F("version")] = String(iv->fwVersion); + invObj[F("is_avail")] = iv->isAvailable(mApp->getTimestamp(), rec); + invObj[F("is_producing")] = iv->isProducing(mApp->getTimestamp(), rec); + invObj[F("ts_last_success")] = iv->getLastTs(rec); + } + } + + JsonArray warn = obj.createNestedArray(F("warnings")); + if(!mApp->mSys->Radio.isChipConnected()) + warn.add(F("your NRF24 module can't be reached, check the wiring and pinout")); + if(!mApp->mqttIsConnected()) + warn.add(F("MQTT is not connected")); + + JsonArray info = obj.createNestedArray(F("infos")); + if(mApp->getRebootRequestState()) + info.add(F("reboot your ESP to apply all your configuration changes!")); + if(!mApp->getSettingsValid()) + info.add(F("your settings are invalid")); + if(mApp->mqttIsConnected()) + info.add(F("MQTT is connected")); +} + + +//----------------------------------------------------------------------------- +void webApi::getSetup(JsonObject obj) { + getSystem(obj.createNestedObject(F("system"))); + getInverterList(obj.createNestedObject(F("inverter"))); + getMqtt(obj.createNestedObject(F("mqtt"))); + getNtp(obj.createNestedObject(F("ntp"))); + getPinout(obj.createNestedObject(F("pinout"))); + getRadio(obj.createNestedObject(F("radio"))); + getSerial(obj.createNestedObject(F("serial"))); +} + + +//----------------------------------------------------------------------------- +void webApi::getLive(JsonObject obj) { + getSystem(obj.createNestedObject(F("system"))); + JsonArray invArr = obj.createNestedArray(F("inverter")); + obj["refresh_interval"] = SEND_INTERVAL; + + 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}; + + Inverter<> *iv; + uint8_t pos; + for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i ++) { + iv = mApp->mSys->getInverterByPos(i); + if(NULL != iv) { + record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug); + JsonObject obj2 = invArr.createNestedObject(); + obj2[F("name")] = String(iv->name); + obj2[F("channels")] = iv->channels; + obj2[F("power_limit_read")] = round3(iv->actPowerLimit); + obj2[F("last_alarm")] = String(iv->lastAlarmMsg); + obj2[F("ts_last_success")] = rec->ts; + + JsonArray ch = obj2.createNestedArray("ch"); + JsonArray ch0 = ch.createNestedArray(); + obj2[F("ch_names")][0] = "AC"; + for (uint8_t fld = 0; fld < sizeof(list); fld++) { + pos = (iv->getPosByChFld(CH0, list[fld], rec)); + ch0[fld] = (0xff != pos) ? round3(iv->getValue(pos, rec)) : 0.0; + obj[F("ch0_fld_units")][fld] = (0xff != pos) ? String(iv->getUnit(pos, rec)) : notAvail; + obj[F("ch0_fld_names")][fld] = (0xff != pos) ? String(iv->getFieldName(pos, rec)) : notAvail; + } + + for(uint8_t j = 1; j <= iv->channels; j ++) { + obj2[F("ch_names")][j] = String(iv->chName[j-1]); + JsonArray cur = ch.createNestedArray(); + for (uint8_t k = 0; k < 6; k++) { + switch(k) { + default: pos = (iv->getPosByChFld(j, FLD_UDC, rec)); break; + case 1: pos = (iv->getPosByChFld(j, FLD_IDC, rec)); break; + case 2: pos = (iv->getPosByChFld(j, FLD_PDC, rec)); break; + case 3: pos = (iv->getPosByChFld(j, FLD_YD, rec)); break; + case 4: pos = (iv->getPosByChFld(j, FLD_YT, rec)); break; + case 5: pos = (iv->getPosByChFld(j, FLD_IRR, rec)); break; + } + cur[k] = (0xff != pos) ? round3(iv->getValue(pos, rec)) : 0.0; + if(1 == j) { + obj[F("fld_units")][k] = (0xff != pos) ? String(iv->getUnit(pos, rec)) : notAvail; + obj[F("fld_names")][k] = (0xff != pos) ? String(iv->getFieldName(pos, rec)) : notAvail; + } + } + } + } + } +} + + +//----------------------------------------------------------------------------- +void webApi::getRecord(JsonObject obj, record_t<> *rec) { + JsonArray invArr = obj.createNestedArray(F("inverter")); + + Inverter<> *iv; + uint8_t pos; + for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i ++) { + iv = mApp->mSys->getInverterByPos(i); + if(NULL != iv) { + JsonArray obj2 = invArr.createNestedArray(); + for(uint8_t j = 0; j < rec->length; j++) { + byteAssign_t *assign = iv->getByteAssign(j, rec); + pos = (iv->getPosByChFld(assign->ch, assign->fieldId, rec)); + obj2[j]["fld"] = (0xff != pos) ? String(iv->getFieldName(pos, rec)) : notAvail; + obj2[j]["unit"] = (0xff != pos) ? String(iv->getUnit(pos, rec)) : notAvail; + obj2[j]["val"] = (0xff != pos) ? String(iv->getValue(pos, rec)) : notAvail; + } + } + } +} + + +//----------------------------------------------------------------------------- +bool webApi::setCtrl(DynamicJsonDocument jsonIn, JsonObject jsonOut) { + uint8_t cmd = jsonIn[F("cmd")]; + + // Todo: num is the inverter number 0-3. For better display in DPRINTLN + uint8_t num = jsonIn[F("inverter")]; + uint8_t tx_request = jsonIn[F("tx_request")]; + + if(TX_REQ_DEVCONTROL == tx_request) + { + DPRINTLN(DBG_INFO, F("devcontrol [") + String(num) + F("], cmd: 0x") + String(cmd, HEX)); + + Inverter<> *iv = getInverter(jsonIn, jsonOut); + JsonArray payload = jsonIn[F("payload")].as(); + + if(NULL != iv) + { + switch (cmd) + { + case TurnOn: + iv->devControlCmd = TurnOn; + iv->devControlRequest = true; + break; + case TurnOff: + iv->devControlCmd = TurnOff; + iv->devControlRequest = true; + break; + case CleanState_LockAndAlarm: + iv->devControlCmd = CleanState_LockAndAlarm; + iv->devControlRequest = true; + break; + case Restart: + iv->devControlCmd = Restart; + iv->devControlRequest = true; + break; + case ActivePowerContr: + iv->devControlCmd = ActivePowerContr; + iv->devControlRequest = true; + iv->powerLimit[0] = payload[0]; + iv->powerLimit[1] = payload[1]; + break; + default: + jsonOut["error"] = "unknown 'cmd' = " + String(cmd); + return false; + } + } else { + return false; + } + } + else { + jsonOut[F("error")] = F("unknown 'tx_request'"); + return false; + } + + return true; +} + + +//----------------------------------------------------------------------------- +bool webApi::setSetup(DynamicJsonDocument jsonIn, JsonObject jsonOut) { + if(F("set_time") == jsonIn[F("cmd")]) + mApp->setTimestamp(jsonIn[F("ts")]); + else if(F("sync_ntp") == jsonIn[F("cmd")]) + mApp->setTimestamp(0); // 0: update ntp flag + else { + jsonOut[F("error")] = F("unknown cmd"); + return false; + } + + return true; +} + + +//----------------------------------------------------------------------------- +Inverter<> *webApi::getInverter(DynamicJsonDocument jsonIn, JsonObject jsonOut) { + uint8_t id = jsonIn[F("inverter")]; + Inverter<> *iv = mApp->mSys->getInverterByPos(id); + if(NULL == iv) + jsonOut[F("error")] = F("inverter index to high: ") + String(id); + return iv; +} diff --git a/tools/esp8266/webApi.h b/tools/esp8266/webApi.h new file mode 100644 index 00000000..e07e7d53 --- /dev/null +++ b/tools/esp8266/webApi.h @@ -0,0 +1,62 @@ +#ifndef __WEB_API_H__ +#define __WEB_API_H__ + +#include "dbg.h" +#ifdef ESP32 + #include "AsyncTCP.h" +#else + #include "ESPAsyncTCP.h" +#endif +#include "ESPAsyncWebServer.h" +#include "AsyncJson.h" +#include "app.h" + + +class app; + +class webApi { + public: + webApi(AsyncWebServer *srv, app *app, sysConfig_t *sysCfg, config_t *config, statistics_t *stat, char version[]); + + void setup(void); + void loop(void); + + private: + void onApi(AsyncWebServerRequest *request); + void onApiPost(AsyncWebServerRequest *request); + void onApiPostBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total); + void getNotFound(JsonObject obj, String url); + + void getSystem(JsonObject obj); + void getStatistics(JsonObject obj); + void getInverterList(JsonObject obj); + void getMqtt(JsonObject obj); + void getNtp(JsonObject obj); + void getPinout(JsonObject obj); + void getRadio(JsonObject obj); + void getSerial(JsonObject obj); + + void getIndex(JsonObject obj); + void getSetup(JsonObject obj); + void getLive(JsonObject obj); + void getRecord(JsonObject obj, record_t<> *rec); + + bool setCtrl(DynamicJsonDocument jsonIn, JsonObject jsonOut); + bool setSetup(DynamicJsonDocument jsonIn, JsonObject jsonOut); + + Inverter<> *getInverter(DynamicJsonDocument jsonIn, JsonObject jsonOut); + + double round3(double value) { + return (int)(value * 1000 + 0.5) / 1000.0; + } + + AsyncWebServer *mSrv; + app *mApp; + + config_t *mConfig; + sysConfig_t *mSysCfg; + statistics_t *mStat; + char *mVersion; +}; + +#endif /*__WEB_API_H__*/