From 35e530c0c9f6f06c00f7a3500d01e5f23741eb77 Mon Sep 17 00:00:00 2001 From: DanielR92 Date: Fri, 29 Dec 2023 19:55:10 +0100 Subject: [PATCH 001/179] Some changes in readme --- Getting_Started.md | 188 ++++++++++++++++++++++----------------------- README.md | 12 +-- ahoy_config.md | 65 ++++++++++++++++ 3 files changed, 166 insertions(+), 99 deletions(-) create mode 100644 ahoy_config.md diff --git a/Getting_Started.md b/Getting_Started.md index f6a89dde..81193ed0 100644 --- a/Getting_Started.md +++ b/Getting_Started.md @@ -1,12 +1,13 @@ + ## Overview -On this page, you'll find detailed instructions on how to wire the module of a Wemos D1 mini or ESP32 to the radio module, as well as how to flash it with the latest firmware. This information will enable you to communicate with compatible inverters. +This page contains detailed instructions on building a module and flashing it with the latest firmware. Following these instructions will allow you to communicate with compatible inverters. You find the full [User_Manual here](User_Manual.md) ## Compatiblity -The following inverters are currently supported out of the box: +Currently, the following inverters are supported: Hoymiles Inverters @@ -20,7 +21,6 @@ Hoymiles Inverters ## Table of Contents -- [Table of Contents](#table-of-contents) - [Overview](#overview) - [Compatiblity](#compatiblity) - [Things needed](#things-needed) @@ -38,7 +38,6 @@ Hoymiles Inverters - [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](#compiling-your-own-version) - - [Optional Configuration before compilation](#optional-configuration-before-compilation) - [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 DTU is very verbose using the Serial Console](#your-ahoy-dtu-is-very-verbose-using-the-serial-console) @@ -56,47 +55,52 @@ Solenso Inverters: ## Things needed -If you're interested in building your own AhoyDTU, you'll need a few things to get started. While we've provided a list of recommended boards below, keep in mind that the maker community is constantly developing new and innovative options that we may not have covered in this readme.. +To build your own AhoyDTU, you only need a few things. Remember that the maker community is always developing new and innovative options that we may not have covered in this readme. + +Start with an ESP8266 or ESP32, and combine it with an NRF24L01+ breakout board. Other ESP boards with at least 4MBytes of ROM may also be suitable. -For optimal performance, we recommend using a Wemos D1 mini or ESP32 along with a NRF24L01+ breakout board as a bare minimum. However, if you have experience working with other ESP boards, any board with at least 4MBytes of ROM may be suitable, depending on your skills. +Make sure to choose an NRF24L01+ module that includes the '+' in its name. This is important because we need the 250kbps features that are only available in the plus-variant. + +**Attention**: The NRF24L01+ can only communicate with the MI/HM/TSUN inverter. For the HMS/HMT it is needed to use a CMT2300A! -Just be sure that the NRF24L01+ module you choose includes the "+" in its name, as we rely on the 250kbps features that are only provided by the plus-variant. | **Parts** | **Price** | | --- | --- | -| D1 ESP8266 Mini WLAN Board Microcontroller | 4,40 Euro | -| NRF24L01+ SMD Modul 2,4 GHz Wi-Fi Funkmodul | 3,45 Euro | -| 100µF / 10V Capacitor Kondensator | 0,15 Euro | -| Jumper Wire Steckbrücken Steckbrett weiblich-weiblich | 2,49 Euro | -| **Total costs** | **10,34 Euro** | +| D1 ESP8266 Mini WLAN Board Microcontroller | 4,40 €| +| *NRF24L01+ SMD Modul 2,4 GHz Wi-Fi Funkmodul (not for HMS/HMT)* | *3,45 €*| +| *CMT2300A 868/915MHz (E49-900M20S)* | *4,59 €* | +| 100µF / 10V Capacitor Kondensator | 0,15 €| +| Jumper Wire Steckbrücken Steckbrett weiblich-weiblich | 2,49 €| +| **Total costs** | **10,34 € / 11,48 €** | -If you're interested in using our sister project OpenDTU or you want to future-proof your setup, we recommend investing in an ESP32 board that features two CPU cores. As Radio you can also use a NRF24L01+ module with an external antenna. While this option may cost a bit more, it will provide superior performance and ensure compatibility with upcoming developments. +To future-proof your setup and use our sister project OpenDTU, we recommend investing in an ESP32 board with two CPU cores. Additionally, you can use a NRF24L01+ module with an external antenna as a radio for superior performance and compatibility with upcoming developments. | **Parts** | **Price** | | --- | --- | -| ESP32 Dev Board NodeMCU WROOM32 WiFi | 7,90 Euro | -| NRF24L01+ PA LNA SMA mit Antenne Long | 4,50 Euro | -| 100µF / 10V Capacitor Kondensator | 0,15 Euro | -| Jumper Wire Steckbrücken Steckbrett weiblich-weiblich | 2,49 Euro | -| **Total costs** | **14,89 Euro** | +| ESP32 Dev Board NodeMCU WROOM32 WiFi | 7,90 €| +| *NRF24L01+ SMD Modul 2,4 GHz Wi-Fi Funkmodul (not for HMS/HMT)* | *3,45 €*| +| *CMT2300A 868/915MHz (E49-900M20S)* | *4,59 €* | +| 100µF / 10V Capacitor Kondensator | 0,15 €| +| Jumper Wire breadboard female-female | 2,49 €| +| **Total costs** | **13,99 € / 15,13 €** | #### There are fake NRF24L01+ Modules out there +Beware of fake NRF24L01+ modules that use rebranded NRF24L01 chips (without the +). +An example of this can be found in Issue #230 (https://github.com/lumapu/ahoy/issues/230). +If you have any additional examples of fake chips, please share them with us and we will add the 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/lumapu/ahoy/issues/230).
-You are welcome to add more examples of faked chips. We will add that information here.
- -Some users reported better connection or longer range through more walls when using the -"E01-ML01DP5" EBYTE 2,4 GHz Wireless Modul nRF24L01 + PA + LNA RF Modul, SMA-K Antenna connector, -which has an eye-catching HF cover. But beware: It comes without the antenna! +#### NRF24L01+ improvements +Users have reported improved connections and longer range through walls when using these modules. +The "E01-ML01DP5" module is a 2.4 GHz wireless module that utilizes the nRF24L01+PA+LNA RF module and features an SMA-K antenna connector. +**The product includes an HF cover, but please note that it does not come with an antenna.** -In any case you should stabilize the Vcc power by a capacitor and don't exceed the Amplifier Power Level "LOW". -Users reporting good connection over 10m through walls / ceilings with Amplifier Power Level "MIN". -It is not always the bigger the better... +To achieve the best results, stabilize the Vcc power by using a capacitor and do not exceed the 'LOW' Amplifier Power Level. +Users have reported good connections over 10m through walls and ceilings when using the Amplifier Power Level 'MIN'. +It's important to remember that bigger is not always better. -Power levels "HIGH" and "MAX" are meant to wirings where the nRF24 is supplied by an extra 3.3 Volt regulator. -The bultin regulator on ESP boards has only low reserves in case WiFi and nRF are sending simultaneously. -If you operate additional interfaces like a display, the reserve is again reduced. +If you are using the NRF24 directly on the ESP board, make sure to set the transmission power to the lowest possible level (this can be adjusted later in the web interface). Using a high transmission power can potentially cause problems. +The ESP board's built-in controller has limited reserves in case both WiFi and nRF are transmitting simultaneously. +If you are using additional interfaces, such as a display, the reserves will be further reduced. ## Wiring things up @@ -117,15 +121,12 @@ Additional, there are 3 pins, which can be set individual: *These pins can be changed from the /setup URL.* #### ESP8266 wiring example on WEMOS D1 - -This is an example wiring using a Wemos D1 mini.
+This is an example wiring using a Wemos D1 mini. ##### Schematic - ![Schematic](https://ahoydtu.de/img/fritzing/esp8266_nrf_sch.png) ##### Symbolic view - ![Symbolic](https://ahoydtu.de/img/fritzing/esp8266_nrf.png) #### ESP8266 wiring example on 30pin Lolin NodeMCU v3 @@ -162,42 +163,34 @@ CE D2 (GPIO4) IRQ D0 (GPIO16 - no IRQ!) ``` -IMPORTANT: From development version 108/release 0.6.0 onwards, also MISO, MOSI, and SCLK -are configurable. On new installations, their defaults are correct for most ESP32 boards. -These pins cannot be configured for ESP82xx boards, as this chip cannot move them elsewhere. +**IMPORTANT**: Starting from development version 108/release 0.6.0, MISO, MOSI, and SCLK are also included. + For most ESP32 boards, the default settings are correct on new installations. +However, it is not possible to configure these pins for ESP82xx boards, as they cannot be moved elsewhere. -If you are upgrading an existing install though, you might see that these pins are set to '0' in the web GUI. -Communication with the NRF module wont work. For upgrading an existing installations, set MISO=19, MOSI=23, SCLK=18 in the settings. -This is the correct default for most ESP32 boards. On ESP82xx, simply saving the settings without changes should suffice. +If you are upgrading an existing installation, you may notice that the pins are set to '0' in the web GUI, which will prevent communication with the NRF module. +To resolve this, set MISO=19, MOSI=23, SCLK=18 in the settings. +This is the correct default for most ESP32 boards. For ESP82xx, simply saving the settings without changes should suffice. Save and reboot. ## 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 of our pre-compiled generic builds. +After preparing your hardware, you must flash the Ahoy DTU Firmware to your board. +You can either create your own firmware using your configuration or use one of our pre-compiled generic builds. + +Are you ready to flash? Then go to next Step here. ### Flash from your browser (easy) The easiest step for you is to flash online. A browser MS Edge or Google Chrome is required. [Here you go](https://ahoydtu.de/web_install/) -### 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.
-Visual Studio Code, AtomIDE and other IDE's support the PlatformIO Addon.
-If you do not want to compile your own build, you can use one of our ready-to-flash binaries. - -##### Optional Configuration before compilation +### Compiling your own Version (expert) +This information is for those who wish to configure and build their own firmware. -- number of supported inverters (set to 3 by default) `config.h` -- DTU radio id `config.h` (default = 1234567801) -- 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. +The code is provided as a PlatformIO project and can be compiled using the PlatformIO Addon. +The PlatformIO Addon is supported by Visual Studio Code, AtomIDE, and other IDEs. +If you do not wish to compile your own build, you can use one of our pre-compiled binaries. #### Using a ready-to-flash binary using nodemcu-pyflasher @@ -217,7 +210,7 @@ This information suits you if you just want to use an easy way. 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! +**! ATTENTION: If you update from a very low version to the newest, please make sure to wipe all flash data!** #### Flashing on Linux with `esptool.py` (ESP32) 1. install [esptool.py](https://docs.espressif.com/projects/esptool/en/latest/esp32/) if you haven't already. @@ -229,51 +222,56 @@ Once your Ahoy DTU is running, you can use the Over The Air (OTA) capabilities t ## Connect to your Ahoy DTU -When everything is wired up and the firmware is flashed, it is time to connect to your Ahoy DTU. +Once everything is wired 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). + Once connected to your computer, you can open a serial console to get additional information. + This can be useful for troubleshooting, as well as simply to get + information about the converted values read from 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 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 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.4.1/](http://192.168.4.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) + After you have successfully flashed and powered up your Ahoy DTU, you can access it from your browser.
+ If your Ahoy DTU was able to log on to the configured WiFi network, it will try to obtain an IP address from your local DHCP server (in most cases this is your router). + + If it cannot connect to your configured network, it will provide its own WiFi network that you can + to for further configuration. + + The WiFi SSID *(the WiFi name)* and password are pre-configured and are set to SSID "`AHOY-DTU`" and password "`esp_8266`" by default. + + The Ahoy DTU will keep this network open for a certain amount of time (default is 60sec). + If nothing connects to it and the time expires, it will retry to connect to the configured network, and so on. + + If you are connected to your local network, just find out the IP address used 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.4.1/](http://192.168.4.1/). + Just open the IP-Address in your browser. + + The web interface has the following capabilities: + +- Live data (values updated every 5 seconds) + Click on the title/name/alarm for more actions. +- Webserial (Debug) +- Settings (System Config, Network, Protection, Inverter, NTP Server, Sunrise/Sunset, MQTT, Display Config) +- Update (Over The Air Update) +- System (status about the modules) ##### HTTP based Pages - 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.4.1/setup](http://192.168.4.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.4.1/setup](http://192.168.4.1/setup) ). | page | use | output | default availability | | ---- | ------ | ------ | ------ | -| /uptime | displays the uptime uf your Ahoy DTU | 0 Days, 01:37:34; now: 2022-08-21 11:13:53 | yes | +| /logout| logout the user from webinterface | | yes | | /reboot | reboots the Ahoy DTU | | yes | +| /system| show system inforamtion | | yes | +| /live | displays the live data | | yes | +| /save | | | yes | | /erase | erases the EEPROM | | yes | | /factory | resets to the factory defaults configured in config.h | | yes | | /setup | opens the setup page | | yes | -| /save | | | yes | -| /cmdstat | show stat from the home page | | yes | -| /visualization | displays the information from your converter | | yes | -| /livedata | displays the live data | | yes | | /metrics | gets live-data for prometheus | prometheus metrics from the livedata | no - enable via config_override.h | | /api | gets configuration and live-data in JSON format | json output from the configuration or livedata | yes | @@ -290,12 +288,14 @@ When everything is wired up and the firmware is flashed, it is time to connect t | `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.7 | GPL-2.0 | -| `espMqttClient` | 1.4.4 | MIT | -| `ArduinoJson` | 6.21.3 | MIT | +| `ESPAsyncWebServer` | 1.2.3 | LGPL-3.0 | +| [ESPAsyncTCP](https://github.com/me-no-dev/ESPAsyncTCP) | 1.2.2 | [LGPL-3.0 license](https://github.com/me-no-dev/ESPAsyncTCP#LGPL-3.0-1-ov-file) | +| [Time](https://github.com/PaulStoffregen/Time) | 1.6.1 | ? | +| [RF24](https://github.com/nRF24/RF24) | 1.4.8 | [GPL-2.0 license](https://github.com/nRF24/RF24#GPL-2.0-1-ov-file) | +| [espMqttClient](https://github.com/bertmelis/espMqttClient) | ? | [MIT license](https://github.com/bertmelis/espMqttClient#MIT-1-ov-file) | +| [ArduinoJson](https://github.com/bblanchon/ArduinoJson) | 6.21.3 | [MIT license](https://github.com/bblanchon/ArduinoJson#MIT-1-ov-file)| +| [GxEPD2](https://github.com/ZinggJM/GxEPD2) | 1.5.2 | [GPL-3.0 license](https://github.com/ZinggJM/GxEPD2#GPL-3.0-1-ov-file)| +| [U8g2_Arduino](https://registry.platformio.org/libraries/olikraus/U8g2) | [2.35.9](https://registry.platformio.org/libraries/olikraus/U8g2/versions) | [BSD-2-Clause](https://spdx.org/licenses/BSD-2-Clause.html) | ## ToDo diff --git a/README.md b/README.md index f774b568..cc85f1a2 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ This work is licensed under a # 🖐 Ahoy! ![Logo](https://github.com/grindylow/ahoy/blob/main/doc/logo1_small.png?raw=true) -This repository offers hardware and software solutions for communicating with Hoymiles inverters via radio. With our system, you can easily obtain real-time values such as power, current, and daily energy. Additionally, you can set parameters like the power limit of your inverter to achieve zero export. You can access these functionalities through our user-friendly web interface, MQTT, or JSON. Whether you're monitoring your solar panel system's performance or fine-tuning its settings, our solutions make it easy to achieve your goals. +This repository provides hardware and software solutions for communicating with Hoymiles inverters via radio. Our system allows you to easily obtain real-time values, such as power, current, and daily energy, as well as set parameters like the power limit of your inverter to achieve zero export. You can access these functionalities through our user-friendly web interface, MQTT, or JSON. Our solutions simplify the process of monitoring and fine-tuning your solar panel system to help you achieve your goals. Table of approaches: @@ -32,9 +32,11 @@ Table of approaches: | [Others, C/C++](tools/nano/NRF24_SendRcv/) | ❌ | ✔️ | ❌ | | ## Getting Started -[Guide how to start with a ESP module](Getting_Started.md) +1. [Guide how to start with a ESP module](Getting_Started.md) -[ESP Webinstaller (Edge / Chrome Browser only)](https://ahoydtu.de/web_install) +2. [ESP Webinstaller (Edge / Chrome Browser only)](https://ahoydtu.de/web_install) + +3. [Ahoy Configuration ](ahoy_config.md) ## Our Website [https://ahoydtu.de](https://ahoydtu.de) @@ -43,11 +45,11 @@ Table of approaches: - [Getting the data into influxDB and visualize them in a Grafana Dashboard](https://grafana.com/grafana/dashboards/16850-pv-power-ahoy/) (thx @Carl) ## Support, Feedback, Information and Discussion -- [Discord Server (~ 3.800 Users)](https://discord.gg/WzhxEY62mB) +- [Discord Server (~ 7.300 Users)](https://discord.gg/WzhxEY62mB) - [The root of development](https://www.mikrocontroller.net/topic/525778) ### Development -If you run into any issues, please feel free to use the issue tracker here on Github. When describing your issue, please be as detailed and precise as possible, and take a moment to consider whether the issue is related to our software. This will help us to provide more effective solutions to your problem. +If you encounter any problems, use the issue tracker on Github. Provide a detailed description of the issue and consider if it is related to our software. This will help us provide effective solutions. **Contributors are always welcome!** diff --git a/ahoy_config.md b/ahoy_config.md new file mode 100644 index 00000000..5c2321de --- /dev/null +++ b/ahoy_config.md @@ -0,0 +1,65 @@ + + +## Ahoy configuration + + So far we have built our own DTU, written a program on it and put it into operation. +But how do I get my data from the inverter? + +To do this, we need to configure the DTU. + +The following steps are required: +1. Set the pinning to communicate with the radio module. +2. Check if Ahoy has a current time +3. Set inverter data + +### 1.) Set the pinning +Once you are in the web interface, you will find the "System Config" sub-item in the Setup area (left). + +This is where you tell the ESP how you connected the radio module. +Note the schematics you saw earlier. - If you haven't noticed them yet, here's another table of connections. + + +#### OpenDTU Fusion (ESP32-S3) +| NRF24 Pin | ESP Pin| +|---------| --------| +| CS (4) | GPIO37 +| CE (3)| GPIO38 +| IRQ (8) | GPIO47 +| SCLK (5)| GPIO36 +| MOSI (6)| GPIO35 +| MISO (7)| GPIO48 + +| CMT2300A | Pin | +|---------| --------| +| CMT| Enabled | +| SCLK| GPIO6 +| SDIO| GPIO5 +| CSB| GPIO4 +| FCSB| GPIO21 +| GPIO3| GPIO8 + +### 2.) Set current time (normal skip this step) +Ahoy needs a current date and time to talk to the inverter. +It works without, but it is recommended to include a time. This allows you to analyze information from the inverter in more detail. +Normally, a date/time should be automatically retrieved from the NTP server. However, it may happen that the firewall of some routers does not allow this. +In the section "Settings -> NTP Server" you can also get the time from your own computer. Or set up your own NTP server. + +### 3.) Set inverter data + +#### add new inverter +Now it's time to place the inverter. This is necessary because it is not the inverter that speaks first, but the DTU (Ahoy). + +Each inverter has its own S.Nr. This also serves as an identity for communication between the DTU and the inverter. + +The S.Nr is a 12-digit number. You can look it up [here (german)](https://github.com/lumapu/ahoy/wiki/Hardware#wie-ist-die-serien-nummer-der-inverter-aufgebaut) for more information. +#### set pv-modules (not necessary) +Click on "Add Inverter" and enter the S.No. and a name. Please keep the name short! +In the upper tab "Inputs" you can enter the data of the solar modules. These are only used directly in Ahoy for calculation and have no influence on the inverter. + +#### set radio parameter (not necessary, only for EU) +In the next tab "Radio" you can adjust the power and other parameters if necessary. However, these should be left as default (EU only). + +#### advanced options (not necessary) +In the "Advanced" section, you can customize more settings. + +# Done - Now check the live site From 8ca0cc5d27db70f0b685b0907067fba466a34668 Mon Sep 17 00:00:00 2001 From: DanielR92 Date: Sat, 30 Dec 2023 10:21:00 +0100 Subject: [PATCH 002/179] move to folder --- Getting_Started.md => manual/Getting_Started.md | 0 User_Manual.md => manual/User_Manual.md | 0 ahoy_config.md => manual/ahoy_config.md | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename Getting_Started.md => manual/Getting_Started.md (100%) rename User_Manual.md => manual/User_Manual.md (100%) rename ahoy_config.md => manual/ahoy_config.md (100%) diff --git a/Getting_Started.md b/manual/Getting_Started.md similarity index 100% rename from Getting_Started.md rename to manual/Getting_Started.md diff --git a/User_Manual.md b/manual/User_Manual.md similarity index 100% rename from User_Manual.md rename to manual/User_Manual.md diff --git a/ahoy_config.md b/manual/ahoy_config.md similarity index 100% rename from ahoy_config.md rename to manual/ahoy_config.md From a993c7a2cf7d513b36d32039012c500622f7b635 Mon Sep 17 00:00:00 2001 From: DanielR92 Date: Sat, 30 Dec 2023 10:40:26 +0100 Subject: [PATCH 003/179] Update ahoy_config.md add some pictures --- manual/ahoy_config.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/manual/ahoy_config.md b/manual/ahoy_config.md index 5c2321de..73427b6f 100644 --- a/manual/ahoy_config.md +++ b/manual/ahoy_config.md @@ -54,6 +54,10 @@ Each inverter has its own S.Nr. This also serves as an identity for communicatio The S.Nr is a 12-digit number. You can look it up [here (german)](https://github.com/lumapu/ahoy/wiki/Hardware#wie-ist-die-serien-nummer-der-inverter-aufgebaut) for more information. #### set pv-modules (not necessary) Click on "Add Inverter" and enter the S.No. and a name. Please keep the name short! +![grafik](https://github.com/DanielR92/ahoy/assets/25644396/b52a2d5d-513c-4895-848a-01ce129f93c1) + +![grafik](https://github.com/DanielR92/ahoy/assets/25644396/b508824f-08a7-4b9c-bc41-29dfee02dced) + In the upper tab "Inputs" you can enter the data of the solar modules. These are only used directly in Ahoy for calculation and have no influence on the inverter. #### set radio parameter (not necessary, only for EU) @@ -62,4 +66,5 @@ In the next tab "Radio" you can adjust the power and other parameters if necessa #### advanced options (not necessary) In the "Advanced" section, you can customize more settings. +Save and reboot. # Done - Now check the live site From f5b0bacbe650f8c1df16fe1d9460ec5e4f588dfc Mon Sep 17 00:00:00 2001 From: lumapu Date: Sat, 30 Dec 2023 15:27:08 +0100 Subject: [PATCH 004/179] 0.8.37 * added grid profiles * format version of grid profile --- src/CHANGES.md | 1002 ++++++++++++++++++++++++++++++- src/defines.h | 2 +- src/web/html/grid_info.json | 199 ++++++ src/web/html/visualization.html | 3 +- 4 files changed, 1202 insertions(+), 4 deletions(-) diff --git a/src/CHANGES.md b/src/CHANGES.md index e67748fd..32efd79b 100644 --- a/src/CHANGES.md +++ b/src/CHANGES.md @@ -1,5 +1,12 @@ -Changelog v0.8.36 +# Development Changes +## 0.8.37 - 2023-12-30 +* added grid profiles +* format version of grid profile + +# RELEASE 0.8.36 - 2023-12-30 + +## 0.8.35 - 2023-12-30 * added dim option for LEDS * changed reload time for opendtufusion after update to 5s * fix default interval and gap for communication @@ -9,4 +16,995 @@ Changelog v0.8.36 * increased maximal number of inverters to 32 for opendtufusion board (ESP32-S3) * fixed crash if CMT inverter is enabled, but CMT isn't configured -full version log: [Development Log](https://github.com/lumapu/ahoy/blob/development03/src/CHANGES.md) +# RELEASE 0.8.34 - 2023-12-29 + +## 0.8.33 - 2023-12-29 +* improved communication thx @rejoe2 + +## 0.8.32 - 2023-12-29 +* fix `start` / `stop` / `restart` commands #1287 +* added message, if profile was not read until now #1300 +* added developer option to use 'syslog-server' instead of 'web-serail' #1299 + +## 0.8.31 - 2023-12-29 +* added class to handle timeouts PR #1298 + +## 0.8.30 - 2023-12-28 +* added info if grid profile was not found +* merge PR #1293 +* merge PR #1295 fix ESP8266 pin settings +* merge PR #1297 fix layout for OLED displays + +## 0.8.29 - 2023-12-27 +* fix MqTT generic topic `comm_disabled` #1265 #1286 +* potential fix of #1285 (reset yield day) +* fix fraction of yield correction #1280 +* fix crash if `getLossRate` was read from inverter #1288 #1290 +* reduce reload time for opendtufusion ethernet variant to 5 seconds +* added basic grid parser +* added ESP32-C3 mini environment #1289 + +## 0.8.28 - 2023-12-23 +* fix bug heuristic +* add version information to clipboard once 'copy' was clicked +* add get loss rate @rejoe2 +* improve communication @rejoe2 + +## 0.8.27 - 2023-12-18 +* fix set power limit #1276 + +## 0.8.26 - 2023-12-17 +* read grid profile as HEX (`live` -> click inverter name -> `show grid profile`) + +## 0.8.25 - 2023-12-17 +* RX channel ID starts with fixed value #1277 +* fix static IP for Ethernet + +## 0.8.24 - 2023-12-16 +* fix NRF communication for opendtufusion ethernet variant + +## 0.8.23 - 2023-12-14 +* heuristics fix #1269 #1270 +* moved `sendInterval` in settings, **important:** *will be reseted to 15s after update to this version* +* try to prevent access to radio classes if they are not activated +* fixed millis in serial log +* changed 'print whole trace' = `false` as default +* added communication loop duration in [ms] to serial console +* don't print Hex-Payload if 'print whole trace' == `false` + +## 0.8.22 - 2023-12-13 +* fix communication state-machine regarding zero export #1267 + +## 0.8.21 - 2023-12-12 +* fix ethernet save inverter parameters #886 +* fix ethernet OTA update #886 +* improved radio statistics, fixed heuristic output for HMS and HMT inverters + +## 0.8.20 - 2023-12-12 +* improved HM communication #1259 #1249 +* fix `loadDefaults` for ethernet builds #1263 +* don't loop through radios which aren't in use #1264 + +## 0.8.19 - 2023-12-11 +* added ms to serial log +* added (debug) option to configure gap between inverter requests + +## 0.8.18 - 2023-12-10 +* copied even more from the original heuristic code #1259 +* added mDNS support #1262 + +## 0.8.17 - 2023-12-10 +* possible fix of NRF with opendtufusion (without ETH) +* small fix in heuristics (if conditions made assignment not comparisson) + +## 0.8.16 - 2023-12-09 +* fix crash if NRF is not enabled +* updated heuristic #1080 #1259 +* fix compile opendtufusion fusion ethernet + +## 0.8.15 - 2023-12-09 +* added support for opendtufusion fusion ethernet shield #886 +* fixed range of HMS / HMT frequencies to 863 to 870 MHz #1238 +* changed `yield effiency` per default to `1.0` #1243 +* small heuristics improvements #1258 +* added class to combine inverter heuristics fields #1258 + +## 0.8.14 - 2023-12-07 +* fixed decimal points for temperature (WebUI) PR #1254 #1251 +* fixed inverter statemachine available state PR #1252 #1253 +* fixed NTP update and sunrise calculation #1240 #886 +* display improvments #1248 #1247 +* fixed overflow in `hmRadio.h` #1244 + +## 0.8.13 - 2023-11-28 +* merge PR #1239 symbolic layout for OLED 128x64 + motion senser functionality +* fix MqTT IP addr for ETH connections PR #1240 +* added ethernet build for fusion board, not tested so far + +## 0.8.12 - 2023-11-20 +* added button `copy to clipboard` to `/serial` + +## 0.8.11 - 2023-11-20 +* improved communication, thx @rejoe2 +* improved heuristics, thx @rejoe2, @Oberfritze +* added option to strip payload of frames to significant area + +## 0.8.10 - 2023-11-19 +* fix Mi and HM inverter communication #1235 +* added privacy mode option #1211 +* changed serial debug option to work without reboot + +## 0.8.9 - 2023-11-19 +* merged PR #1234 +* added new alarm codes +* removed serial interval, was not in use anymore + +## 0.8.8 - 2023-11-16 +* fix ESP8266 save inverter #1232 + +## 0.8.7 - 2023-11-13 +* fix ESP8266 inverter settings #1226 +* send radio statistics via MqTT #1227 +* made night communication inverter depended +* added option to prevent adding values of inverter to total values (MqTT only) #1199 + +## 0.8.6 - 2023-11-12 +* merged PR #1225 +* improved heuristics (prevent update of statitistic during testing) + +## 0.8.5 - 2023-11-12 +* fixed endless loop while switching CMT frequency +* removed obsolete "retries" field from settings #1224 +* fixed crash while defining new invertes #1224 +* fixed default frequency settings +* added default input power to `400` while adding new inverters +* fixed color of wifi RSSI icon #1224 + +## 0.8.4 - 2023-11-10 +* changed MqTT alarm topic, removed retained flag #1212 +* reduce last_success MQTT messages (#1124) +* introduced tabs in WebGUI (inverter settings) +* added inverter-wise power level and frequency + +## 0.8.3 - 2023-11-09 +* fix yield day reset during day #848 +* add total AC Max Power to WebUI +* fix opendtufusion build (GxEPD patch) +* fix null ptr PR #1222 + +## 0.8.2 - 2023-11-08 +* beautified inverter settings in `setup` (preperation for future, settings become more inverter dependent) + +## 0.8.1 - 2023-11-05 +* added tx channel heuristics (per inverter) +* fix statistics counter + +## 0.8.0 - 2023-10-?? +* switched to new communication scheme + +## 0.7.66 - 2023-10-04 +* prepared PA-Level for CMT +* removed settings for number of retransmits, its fixed to `5` now +* added parentheses to have a excactly defined behaviour + +## 0.7.65 - 2023-10-02 +* MI control command review #1197 + +## 0.7.64 - 2023-10-02 +* moved active power control to modal in `live` view (per inverter) by click on current APC state + +## 0.7.63 - 2023-10-01 +* fix NRF24 communication #1200 + +## 0.7.62 - 2023-10-01 +* fix communication to inverters #1198 +* add timeout before payload is tried to process (necessary for HMS/HMT) + +## 0.7.61 - 2023-10-01 +* merged `hmPayload` and `hmsPayload` into single class +* merged generic radio functions into new parent class `radio.h` +* moved radio statistics into the inverter - each inverter has now seperate statistics which can be accessed by click on the footer in `/live` +* fix compiler warnings #1191 +* fix ePaper logo during night time #1151 + +## 0.7.60 - 2023-09-27 +* fixed typos in changelog #1172 +* fixed MqTT manual clientId storage #1174 +* fixed inverter name length in setup #1181 +* added inverter name to the header of alarm list #1181 +* improved code to avoid warning during compilation #1182 +* fix scheduler #1188, #1179 + +## 0.7.59 - 2023-09-20 +* re-add another HM-400 hardware serial number accidentally removed with `0.7.45` (#1169) +* merge PR #1170 +* reduce last_success MQTT messages (#1124) +* add re-request if inverter is known to be online and first try fails +* add alarm reporting to MI (might need review!) +* rebuild MI limiting code closer to DTUSimMI example +* round APC in `W` to an integer #1171 + +## 0.7.58 +* fix ESP8266 save settings issue #1166 + +## 0.7.57 - 2023-09-18 +* fix Alarms are always in queue (since 0.7.56) +* fix display active power control to long for small devices #1165 + +## 0.7.56 - 2023-09-17 +* only request alarms which were not received before #1113 +* added flag if alarm was requested but not received and re-request it #1105 +* merge PR #1163 + +## 0.7.55 - 2023-09-17 +* fix prometheus builds +* fix ESP32 default pinout #1159 +* added `opendtufusion-dev` because of annoying `-DARDUINO_USB_CDC_ON_BOOT=1` flag +* fix display of current power on `index` +* fix OTA, was damaged by version `0.7.51`, need to use webinstaller (from `0.7.51` to `0.7.54`) + +## 0.7.54 - 2023-09-16 +* added active power control in `W` to live view #201, #673 +* updated docu, active power control related #706 +* added current AC-Power to `index` page and removed version #763 +* improved statistic data, moved to entire struct +* removed `/api/statistics` endpoint from REST-API + +## 0.7.53 - 2023-09-16 +* fix ePaper / display night behaviour #1151 +* fix ESP8266 compile error + +## 0.7.52 - 2023-09-16 +* fix CMT configurable pins #1150, #1159 +* update MqTT lib to version `1.4.5` + +## 0.7.51 - 2023-09-16 +* fix CMT configurable pins #1150 +* fix default CMT pins for opendtufusion +* beautified `system` +* changed main loops, fix resets #1125, #1135 + +## 0.7.50 - 2023-09-12 +* moved MqTT info to `system` +* added CMT info for ESP32 devices +* improved CMT settings, now `SCLK` and `SDIO` are configurable #1046, #1150 +* changed `Power-Limit` in live-view to `Active Power Control` +* increase length of update file selector #1132 + +## 0.7.49 - 2023-09-11 +* merge PR: symbolic icons for mono displays, PR #1136 +* merge MI code restructuring PR #1145 +* merge Prometheus PR #1148 +* add option to strip webUI for ESP8266 (reduce code size, add ESP32 special features; `IF_ESP32` directives) +* started to get CMT info into `system` - not finished + +## 0.7.48 - 2023-09-10 +* fix SSD1309 2.42" display pinout +* improved setup page: save and delete of inverters + +## 0.7.47 - 2023-09-07 +* fix boot loop #1140 +* fix regex in `setup` page +* fix MI serial number display `max-module-power` in `setup` #1142 +* renamed `opendtufusionv1` to `opendtufusion` + +## 0.7.46 - 2023-09-04 +* removed `delay` from ePaper +* started improvements of `/system` +* fix LEDs to check all configured inverters +* send loop skip disabled inverters fix +* print generated DTU SN to console +* HW Versions for MI series PR #1133 +* 2.42" display (SSD1309) integration PR #1139 +* update user manual PR #1121 +* add / rename alarm codes PR #1118 +* revert default pin ESP32 for NRF23-CE #1132 +* luminance of display can be changed during runtime #1106 + +## 0.7.45 - 2023-08-29 +* change ePaper text to symbols PR #1131 +* added some invertes to dev info list #1111 + +## 0.7.44 - 2023-08-28 +* fix `last_success` transmitted to often #1124 + +## 0.7.43 - 2023-08-28 +* improved RSSI for NRF24, now it's read per package (and inverter) #1129 +* arranged `heap` related info together in `/system` +* fix display navi during save +* clean up binary output, separated to folders + +## 0.7.42 - 2023-08-27 +* fix ePaper for opendtufusion_v2.x boards (Software SPI) +* add signal strength for NRF24 - PR #1119 +* refactor wifi class to support ESP32 S2 PR #1127 +* update platform for ESP32 to 6.3.2 +* fix opendtufusion LED (were mixed) +* fix `last_success` transmitted to often #1124 +* added ESP32-S3-mini to github actions +* added old Changelog Entries, to have full log of changes + +## 0.7.41 - 2023-08-26 +* merge PR #1117 code spelling fixes #1112 +* alarms were not read after the first day + +## 0.7.40 - 2023-08-21 +* added default pins for opendtu-fusion-v1 board +* fixed hw version display in `live` +* removed development builds, renamed environments in `platform.ini` + +## 0.7.39 - 2023-08-21 +* fix background color of invalid inputs +* add hardware info (click in `live` on inverter name) + +## 0.7.38 - 2023-08-21 +* reset alarms at midnight (if inverter is not available) #1105, #1096 +* add option to reset 'max' values on midnight #1102 +* added default pins for CMT2300A (matching OpenDTU) + +## 0.7.37 - 2023-08-18 +* fix alarm time on WebGui #1099 +* added RSSI info for HMS and HMT inverters (MqTT + REST API) + +# RELEASE 0.7.36 - 2023-08-18 + +## 0.7.35 - 2023-08-17 +* fixed timestamp for alarms send over MqTT +* auto-patch of `AsyncWebServer` #834, #1036 +* Update documentation in Git regarding `ESP8266` default NRF24 pin assignments + +## 0.7.34 - 2023-08-16 +* fixed timezone offset of alarms +* added `AC` and `DC` to `/live` #1098 +* changed `ESP8266` default NRF24 pin assignments (`D3` = `CE` and `D4` = `IRQ`) +* fixed background of modal window for bright color +* fix MI crashes +* fix some lost debug messages +* merged PR #1095, MI fixes for 0.7.x versions +* fix scheduled reboot #1097 +* added vector graphic logo `/doc/logo.svg` +* merge PR #1093, improved Nokia5110 display layout + +## 0.7.33 - 2023-08-15 +* add alarms overview to WebGui #608 +* fix webGui total values #1084 + +## 0.7.32 - 2023-08-14 +* fix colors of live view #1091 + +## 0.7.31 - 2023-08-13 +* fixed docu #1085 +* changed active power limit MqTT messages to QOS2 #1072 +* improved alarm messages, added alarm-id to log #1089 +* trigger power limit read on next day (if inverter was offline meanwhile) +* disabled improv implementation to check if it is related to 'Schwuppdizitaet' +* changed live view to gray once inverter isn't available +* added inverter status to API +* changed sum of totals on WebGui depending on inverter status #1084 +* merge maximum power (AC and DC) from PR #1080 + +## 0.7.30 - 2023-08-10 +* attempt to improve speed / response times (Schwuppdizitaet) #1075 + +## 0.7.29 - 2023-08-09 +* MqTT alarm data was never sent, fixed +* REST API: added alarm data +* REST API: made get record obsolete +* REST API: added power limit acknowledge `/api/inverter/id/[0-x]` #1072 + +## 0.7.28 - 2023-08-08 +* fix MI inverter support #1078 + +## 0.7.27 - 2023-08-08 +* added compile option for ethernet #886 +* fix ePaper configuration, missing `Busy`-Pin #1075 + +# RELEASE 0.7.26 - 2023-08-06 + +* fix MqTT `last_success` + +# RELEASE 0.7.25 - 2023-08-06 + +## 0.7.24 - 2023-08-05 +* merge PR #1069 make MqTT client ID configurable +* fix #1016, general MqTT status depending on inverter state machine +* changed icon for fully available inverter to a filled check mark #1070 +* fixed `last_success` update with MqTT #1068 +* removed `improv` esp-web-installer script, because it is not fully functional at this time + +## 0.7.23 - 2023-08-04 +* merge PR #1056, visualization html +* update MqTT library to 1.4.4 +* update RF24 library to 1.4.7 +* update ArduinoJson library to 6.21.3 +* set minimum invervall for `/live` to 5 seconds + +## 0.7.22 - 2023-08-04 +* attempt to fix homeassistant auto discovery #1066 + +## 0.7.21 - 2023-07-30 +* fix MqTT YieldDay Total goes to 0 several times #1016 + +## 0.7.20 - 2023-07-28 +* merge PR #1048 version and hash in API, fixes #1045 +* fix: no yield day update if yield day reads `0` after inverter reboot (mostly on evening) #848 +* try to fix Wifi override #1047 +* added information after NTP sync to WebUI #1040 + +## 0.7.19 - 2023-07-27 +* next attempt to fix yield day for multiple inverters #1016 +* reduced threshold for inverter state machine from 60min to 15min to go from state `WAS_ON` to `OFF` + +## 0.7.18 - 2023-07-26 +* next attempt to fix yield day for multiple inverters #1016 + +## 0.7.17 - 2023-07-25 +* next attempt to fix yield day for multiple inverters #1016 +* added two more states for the inverter status (also docu) + +## 0.7.16 - 2023-07-24 +* next attempt to fix yield day for multiple inverters #1016 +* fix export settings date #1040 +* fix time on WebUI (timezone was not observed) #913 #1016 + +## 0.7.15 - 2023-07-23 +* add NTP sync interval #1019 +* adjusted range of contrast / luminance setting #1041 +* use only ISO time format in Web-UI #913 + +## 0.7.14 - 2023-07-23 +* fix Contrast for Nokia Display #1041 +* attempt to fix #1016 by improving inverter status +* added option to adjust efficiency for yield (day/total) #1028 + +## 0.7.13 - 2023-07-19 +* merged display PR #1027 +* add date, time and version to export json #1024 + +## 0.7.12 - 2023-07-09 +* added inverter status - state-machine #1016 + +## 0.7.11 - 2023-07-09 +* fix MqTT endless loop #1013 + +## 0.7.10 - 2023-07-08 +* fix MqTT endless loop #1013 + +## 0.7.9 - 2023-07-08 +* added 'improve' functions to set wifi password directly with ESP web tools #1014 +* fixed MqTT publish while applying power limit #1013 +* slightly improved HMT live view (Voltage & Current) + +## 0.7.8 - 2023-07-05 +* fix `YieldDay`, `YieldTotal` and `P_AC` in `TotalValues` #929 +* fix some serial debug prints +* merge PR #1005 which fixes issue #889 +* merge homeassistant PR #963 +* merge PR #890 which gives option for scheduled reboot at midnight (default off) + +## 0.7.7 - 2023-07-03 +* attempt to fix MqTT `YieldDay` in `TotalValues` #927 +* attempt to fix MqTT `YieldDay` and `YieldTotal` even if inverters are not completely available #929 +* fix wrong message 'NRF not connected' if it is disabled #1007 + +## 0.7.6 - 2023-06-17 +* fix display of hidden SSID checkbox +* changed yield correction data type to `double`, now decimal places are supported +* corrected name of 0.91" display in settings +* attempt to fix MqTT zero values only if setting is there #980, #957 +* made AP password configurable #951 +* added option to start without time-sync, eg. for AP-only-mode #951 + +## 0.7.5 - 2023-06-16 +* fix yield day reset on midnight #957 +* improved tickers in `app.cpp` + +## 0.7.4 - 2023-06-15 +* fix MqTT `P_AC` send if inverters are available #987 +* fix assignments for HMS 1CH and 2CH devices +* fixed uptime overflow #990 + +## 0.7.3 - 2023-06-09 +* fix hidden SSID scan #983 +* improved NRF24 missing message on home screen #981 +* fix MqTT publishing only updated values #982 + +## 0.7.2 - 2023-06-08 +* fix HMS-800 and HMS-1000 assignments #981 +* make nrf enabled all the time for ESP8266 +* fix menu item `active` highlight for 'API' and 'Doku' +* fix MqTT totals issue #927, #980 +* reduce maximum number of inverters to 4 for ESP8266, increase to 16 for ESP32 + +## 0.7.1 - 2023-06-05 +* enabled power limit control for HMS / HMT devices +* changed NRF24 lib version back to 1.4.5 because of compile problems for EPS8266 + +## 0.7.0 - 2023-06-04 +* HMS / HMT support for ESP32 devices + +## 0.6.15 - 2023-05-25 +* improved Prometheus Endpoint PR #958 +* fix turn off ePaper only if setting was set #956 +* improved reset values and update MqTT #957 + +## 0.6.14 - 2023-05-21 +* merge PR #902 Mono-Display + +## 0.6.13 - 2023-05-16 +* merge PR #934 (fix JSON API) and #944 (update manual) + +## 0.6.12 - 2023-04-28 +* improved MqTT +* fix menu active item + +## 0.6.11 - 2023-04-27 +* added MqTT class for publishing all values in Arduino `loop` + +## 0.6.10 - HMS +* Version available in `HMS` branch + +# RELEASE 0.6.9 - 2023-04-19 + +## 0.6.8 - 2023-04-19 +* fix #892 `zeroYieldDay` loop was not applied to all channels + +## 0.6.7 - 2023-04-13 +* merge PR #883, improved store of settings and javascript, thx @tastendruecker123 +* support `.` and `,` as floating point separator in setup #881 + +## 0.6.6 - 2023-04-12 +* increased distance for `import` button in mobile view #879 +* changed `led_high_active` to `bool` #879 + +## 0.6.5 - 2023-04-11 +* fix #845 MqTT subscription for `ctrl/power/[IV-ID]` was missing +* merge PR #876, check JSON settings during read for existence +* **NOTE:** incompatible change: renamed `led_high_active` to `act_high`, maybe setting must be changed after update +* merge PR #861 do not send channel metric if channel is disabled + +## 0.6.4 - 2023-04-06 +* merge PR #846, improved NRF24 communication and MI, thx @beegee3 & @rejoe2 +* merge PR #859, fix burger menu height, thx @ThomasPohl + +## 0.6.3 - 2023-04-04 +* fix login, password length was not checked #852 +* merge PR #854 optimize browser caching, thx @tastendruecker123 #828 +* fix WiFi reconnect not working #851 +* updated issue templates #822 + +## 0.6.2 - 2023-04-04 +* fix login from multiple clients #819 +* fix login screen on small displays + +## 0.6.1 - 2023-04-01 +* merge LED fix - LED1 shows MqTT state, LED configurable active high/low #839 +* only publish new inverter data #826 +* potential fix of WiFi hostname during boot up #752 + +# RELEASE 0.6.0 - 2023-03-27 + +## 0.5.110 +* MQTT fix reconnection by new lib version #780 +* add `about` page +* improved documentation regarding SPI pins #814 +* improved documentation (getting started) #815 #816 +* improved MI 4-ch inverter #820 + +## 0.5.109 +* reduced heap fragmentation by optimizing MqTT #768 +* ePaper: centered text thx @knickohr + +## 0.5.108 +* merge: PR SPI pins configurable (ESP32) #807, #806 (requires manual set of MISO=19, MOSI=23, SCLK=18 in GUI for existing installs) +* merge: PR MI serial outputs #809 +* fix: no MQTT `total` sensor for autodiscover if only one inverter was found #805 +* fix: MQTT `total` renamed to `device_name` + `_TOTOL` for better visibility #805 + +## 0.5.107 +* fix: show save message +* fix: removed serial newline for `enqueueCmd` +* Merged improved Prometheus #808 + +## 0.5.106 +* merged MI and debug message changes #804 +* fixed MQTT autodiscover #794, #632 + +## 0.5.105 +* merged MI, thx @rejoe2 #788 +* fixed reboot message #793 + +## 0.5.104 +* further improved save settings +* removed `#` character from ePaper +* fixed saving pinout for `Nokia-Display` +* removed `Reset` Pin for monochrome displays +* improved wifi connection #652 + +## 0.5.103 +* merged MI improvements, thx @rejoe2 #778 +* changed display inverter online message +* merged heap improvements #772 + +## 0.5.102 +* Warning: old exports are not compatible any more! +* fix JSON import #775 +* fix save settings, at least already stored settings are not lost #771 +* further save settings improvements (only store inverters which are existing) +* improved display of settings save return value +* made save settings asynchronous (more heap memory is free) + +## 0.5.101 +* fix SSD1306 +* update documentation +* Update miPayload.h +* Update README.md +* MI - remarks to user manual +* MI - fix AC calc +* MI - fix status msg. analysis + +## 0.5.100 +* fix add inverter `setup.html` #766 +* fix MQTT retained flag for total values #726 +* renamed buttons for import and export `setup.html` +* added serial message `settings saved` + +## 0.5.99 +* fix limit in [User_Manual.md](../User_Manual.md) +* changed `contrast` to `luminance` in `setup.html` +* try to fix SSD1306 display #759 +* only show necessary display pins depending on setting + +## 0.5.98 +* fix SH1106 rotation and turn off during night #756 +* removed MQTT subscription `sync_ntp`, `set_time` with a value of `0` does the same #696 +* simplified MQTT subscription for `limit`. Check [User_Manual.md](../User_Manual.md) for new syntax #696, #713 +* repaired inverter wise limit control +* fix upload settings #686 + +## 0.5.97 +* Attention: re-ordered display types, check your settings! #746 +* improved saving settings of display #747, #746 +* disabled contrast for Nokia display #746 +* added Prometheus as compile option #719, #615 +* update MQTT lib to v1.4.1 +* limit decimal places to 2 in `live` +* added `-DPIO_FRAMEWORK_ARDUINO_MMU_CACHE16_IRAM48` to esp8266 debug build #657 +* a `max-module-power` of `0` disables channel in live view `setup` +* merge MI improvements, get firmware information #753 + +## 0.5.96 +* added Nokia display again for ESP8266 #764 +* changed `var` / `VAr` to SI unit `var` #732 +* fix MQTT retained flags for totals (P_AC, P_DC) #726, #721 + +## 0.5.95 +* merged #742 MI Improvements +* merged #736 remove obsolete JSON Endpoint + +## 0.5.94 +* added ePaper (for ESP32 only!), thx @dAjaY85 #735 +* improved `/live` margins #732 +* renamed `var` to `VAr` #732 + +## 0.5.93 +* improved web API for `live` +* added dark mode option +* converted all forms to reponsive design +* repaired menu with password protection #720, #716, #709 +* merged MI series fixes #729 + +## 0.5.92 +* fix mobile menu +* fix inverters in select `serial.html` #709 + +## 0.5.91 +* improved html and navi, navi is visible even when API dies #660 +* reduced maximum allowed JSON size for API to 6000Bytes #660 +* small fix: output command at `prepareDevInformCmd` #692 +* improved inverter handling #671 + +## 0.5.90 +* merged PR #684, #698, #705 +* webserial minor overflow fix #660 +* web `index.html` improve version information #701 +* fix MQTT sets power limit to zero (0) #692 +* changed `reset at midnight` with timezone #697 + +## 0.5.89 +* reduced heap fragmentation (removed `strtok` completely) #644, #645, #682 +* added part of mac address to MQTT client ID to separate multiple ESPs in same network +* added dictionary for MQTT to reduce heap-fragmentation +* removed `last Alarm` from Live view, because it showed always the same alarm - will change in future + +## 0.5.88 +* MQTT Yield Day zero, next try to fix #671, thx @beegee3 +* added Solenso inverter to supported devices +* improved reconnection of MQTT #650 + +## 0.5.87 +* fix yield total correction as module (inverter input) value #570 +* reneabled instant start communication (once NTP is synced) #674 + +## 0.5.86 +* prevent send devcontrol request during disabled night communication +* changed yield total correction as module (inverter input) value #570 +* MQTT Yield Day zero, next try to fix #671 + +## 0.5.85 +* fix power-limit was not checked for max retransmits #667 +* fix blue LED lights up all the time #672 +* fix installing schedulers if NTP server isn't available +* improved zero values on triggers #671 +* hardcoded MQTT subtopics, because wildcard `#` leads to errors +* rephrased some messages on webif, thx to @Argafal #638 +* fixed 'polling stop message' on `index.html` #639 + +## 0.5.84 +* fix blue LED lights up all the time #672 +* added an instant start communication (once NTP is synced) +* add MI 3rd generation inverters (10x2 serial numbers) +* first decodings of messages from MI 2nd generation inverters + +## 0.5.83 +* fix MQTT publishing, `callback` was set but reset by following `setup()` + +## 0.5.82 +* fixed communication error #652 +* reset values is no bound to MQTT any more, setting moved to `inverter` #649 +* fixed wording on `index.hmtl` #661 + +## 0.5.81 +* started implementation of MI inverters (setup.html, own processing `MiPayload.h`) + +## 0.5.80 +* fixed communication #656 + +## 0.5.79 +* fixed mixed reset flags #648 +* fixed `mCbAlarm` if MQTT is not used #653 +* fixed MQTT `autodiscover` #630 thanks to @antibill51 +* next changes from @beegee many thanks for your contribution! +* replaced `CircularBuffer` by `std::queue` +* reworked `hmRadio.h` completely (interrupts, packaging) +* fix exception while `reboot` +* cleanup MQTT coding + +## 0.5.78 +* further improvements regarding wifi #611, fix connection if only one AP with same SSID is there +* fix endless loop in `zerovalues` #564 +* fix auto discover again #565 +* added total values to autodiscover #630 +* improved zero at midnight #625 + +## 0.5.77 +* fix wrong filename for automatically created manifest (online installer) #620 +* added rotate display feature #619 +* improved Prometheus endpoint #615, thx to @fsck-block +* improved wifi to connect always to strongest RSSI, thx to @beegee3 #611 + +## 0.5.76 +* reduce MQTT retry interval from maximum speed to one second +* fixed homeassistant autodiscovery #565 +* implemented `getNTPTime` improvements #609 partially #611 +* added alarm messages to MQTT #177, #600, #608 + +## 0.5.75 +* fix wakeup issue, once wifi was lost during night the communication didn't start in the morning +* re-enabled FlashStringHelper because of lacking RAM +* complete rewrite of monochrome display class, thx to @dAjaY85 -> displays are now configurable in setup +* fix power limit not possible #607 + +## 0.5.74 +* improved payload handling (retransmit all fragments on CRC error) +* improved `isAvailable`, checks all record structs, inverter becomes available more early because version is check first +* fix tickers were not set if NTP is not available +* disabled annoying `FlashStringHelper` it gives randomly Exceptions during development, feels more stable since then +* moved erase button to the bottom in settings, not nice but more functional +* split `tx_count` to `tx_cnt` and `retransmits` in `system.html` +* fix mqtt retransmit IP address #602 +* added debug infos for `scheduler` (web -> `/debug` as trigger prints list of tickers to serial console) + +## 0.5.73 +* improved payload handling (request / retransmit) #464 +* included alarm ID parse to serial console (in development) + +## 0.5.72 +* repaired system, scheduler was not called any more #596 + +## 0.5.71 +* improved wifi handling and tickers, many thanks to @beegee3 #571 +* fixed YieldTotal correction calculation #589 +* fixed serial output of power limit acknowledge #569 +* reviewed `sendDiscoveryConfig` #565 +* merged PR `Monodisplay`, many thanks to @dAjaY85 #566, Note: (settings are introduced but not able to be modified, will be included in next version) + +## 0.5.70 +* corrected MQTT `comm_disabled` #529 +* fix Prometheus and JSON endpoints (`config_override.h`) #561 +* publish MQTT with fixed interval even if inverter is not available #542 +* added JSON settings upload. NOTE: settings JSON download changed, so only settings should be uploaded starting from version `0.5.70` #551 +* MQTT topic and inverter name have more allowed characters: `[A-Za-z0-9./#$%&=+_-]+`, thx: @Mo Demman +* improved potential issue with `checkTicker`, thx @cbscpe +* MQTT option for reset values on midnight / not avail / communication stop #539 +* small fix in `tickIVCommunication` #534 +* add `YieldTotal` correction, eg. to have the option to zero at year start #512 + +## 0.5.69 +* merged SH1106 1.3" Display, thx @dAjaY85 +* added SH1106 to automatic build +* added IP address to MQTT (version, device and IP are retained and only transmitted once after boot) #556 +* added `set_power_limit` acknowledge MQTT publish #553 +* changed: version, device name are only published via MQTT once after boot +* added `Login` to menu if admin password is set #554 +* added `development` to second changelog link in `index.html` #543 +* added interval for MQTT (as option). With this settings MQTT live data is published in a fixed timing (only if inverter is available) #542, #523 +* added MQTT `comm_disabled` #529 +* changed name of binaries, moved GIT-Sha to the front #538 + +## 0.5.68 +* repaired receive payload +* Powerlimit is transferred immediately to inverter + +## 0.5.67 +* changed calculation of start / stop communication to 1 min after last comm. stop #515 +* moved payload send to `payload.h`, function `ivSend` #515 +* payload: if last frame is missing, request all frames again + +# RELEASE 0.5.66 - 2022-12-30 + +## 0.5.65 +* wifi, code optimization #509 + +## 0.5.64 +* channel name can use any character, not limited any more +* added `/` to MQTT topic and Inverter name +* trigger for `calcSunrise` is now using local time #515 +* fix reconnect timeout for WiFi #509 +* start AP only after boot, not on WiFi connection loss +* improved /system `free_heap` value (measured before JSON-tree is built) + +## 0.5.63 +* fix Update button protection (prevent double click #527) +* optimized scheduler #515 (thx @beegee3) +* potential fix of #526 duplicates in API `/api/record/live` +* added update information to `index.html` + +## 0.5.62 +* fix MQTT `status` update +* removed MQTT `available_text` (can be deducted from `available`) +* enhanced MQTT documentation in `User_Manual.md` +* removed `tickSunset` and `tickSunrise` from MQTT. It's not needed any more because of minute wise check of status (`processIvStatus`) +* changed MQTT topic `status` to nummeric value, check documentation in `User_Manual.md` +* fix regular expression of `setup.html` for inverter name and channel name + +## 0.5.61 +* fix #521 no reconnect at beginning of day +* added immediate (each minute) report of inverter status MQTT #522 +* added protection mask to select which pages should be protected +* update of monochrome display, show values also if nothing changed + +## 0.5.60 +* added regex to inverter name and MQTT topic (setup.html) +* beautified serial.html +* added ticker for wifi loop #515 + +## 0.5.59 +* fix night communication enable +* improved different WiFi connection scenarios (STA WiFi not found, reconnect #509, redirect for AP to configuration) +* increased MQTT user, pwd and topic length to 64 characters + `\0`. (The string end `\0` reduces the available size by one) #516 + +## 0.5.58 +* improved stability +* improved WiFi initial connection - especially if station WiFi is not available +* removed new operators from web.h (reduce dynamic allocation) +* improved sun calculation #515, #505 +* fixed WiFi auto reconnect #509 +* added disable night communication flag to MQTT #505 +* changed MQTT publish of `available` and `available_text` to sunset #468 + +## 0.5.57 +* improved stability +* added icons to index.html, added WiFi-strength symbol on each page +* moved packet stats and sun to system.html +* refactored communication offset (adjustable in minutes now) + +## 0.5.56 +* factory reset formats entire little fs +* renamed sunrise / sunset on index.html to start / stop communication +* show system information only if called directly from menu +* beautified system.html + +## 0.5.55 +* fixed static IP save + +## 0.5.54 +* changed sunrise / sunset calculation, angle is now `-3.5` instead of original `-0.83` +* improved scheduler (removed -1 from `reload`) #483 +* improved reboot flag in `app.h` +* fixed #493 no MQTT payload once display is defined + +## 0.5.53 +* Mono-Display: show values in offline mode #498 +* improved WiFi class #483 +* added communication enable / disable (to test multiple DTUs with the same inverter) +* fix factory reset #495 + +## 0.5.52 +* improved ahoyWifi class +* added interface class for app +* refactored web and webApi -> RestApi +* fix calcSunrise was not called every day +* added MQTT RX counter to index.html +* all values are displayed on /live even if they are 0 +* added MQTT /status to show status over all inverters + +## 0.5.51 +* improved scheduler, @beegee3 #483 +* refactored get NTP time, @beegee3 #483 +* generate `bin.gz` only for 1M device ESP8285 +* fix calcSunrise was not called every day +* increased number of allowed characters for MQTT user, broker and password, @DanielR92 +* added NRF24 info to Systeminfo, @DanielR92 +* added timezone for monochrome displays, @gh-fx2 +* added support for second inverter for monochrome displays, @gh-fx2 + +## 0.5.50 +* fixed scheduler, uptime and timestamp counted too fast +* added / renamed automatically build outputs +* fixed MQTT ESP uptime on reconnect (not zero any more) +* changed uptime on index.html to count each second, synced with ESP each 10 seconds + +## 0.5.49 +* fixed AP mode on brand new ESP modules +* fixed `last_success` MQTT message +* fixed MQTT inverter available status at sunset +* reordered enqueue commands after boot up to prevent same payload length for successive commands +* added automatic build for Nokia5110 and SSD1306 displays (ESP8266) + +## 0.5.48 +* added MQTT message send at sunset +* added monochrome display support +* added `once` and `onceAt` to scheduler to make code cleaner +* improved sunrise / sunset calculation + +## 0.5.47 +* refactored ahoyWifi class: AP is opened on every boot, once station connection is successful the AP will be closed +* improved NTP sync after boot, faster sync +* fix NRF24 details only on valid SPI connection + +## 0.5.46 +* fix sunrise / sunset calculation +* improved setup.html: `reboot on save` is checked as default + +## 0.5.45 +* changed MQTT last will topic from `status` to `mqtt` +* fix sunrise / sunset calculation +* fix time of serial web console + +## 0.5.44 +* marked some MQTT messages as retained +* moved global functions to global location (no duplicates) +* changed index.html interval to static 10 seconds +* fix static IP +* fix NTP with static IP +* print MQTT info only if MQTT was configured + +## 0.5.43 +* updated REST API and MQTT (both of them use the same functionality) +* added ESP-heap information as MQTT message +* changed output name of automatic development build to fixed name (to have a static link from https://ahoydtu.de) +* updated user manual to latest MQTT and API changes + +## 0.5.42 +* fix web logout (auto logout) +* switched MQTT library + +# RELEASE 0.5.41 - 2022-11-22 + +# RELEASE 0.5.28 - 2022-10-30 + +# RELEASE 0.5.17 - 2022-09-06 + +# RELEASE 0.5.16 - 2022-09-04 + diff --git a/src/defines.h b/src/defines.h index 23919899..787e9ccf 100644 --- a/src/defines.h +++ b/src/defines.h @@ -13,7 +13,7 @@ //------------------------------------- #define VERSION_MAJOR 0 #define VERSION_MINOR 8 -#define VERSION_PATCH 36 +#define VERSION_PATCH 37 //------------------------------------- typedef struct { diff --git a/src/web/html/grid_info.json b/src/web/html/grid_info.json index 5dcdda65..ee6dea13 100644 --- a/src/web/html/grid_info.json +++ b/src/web/html/grid_info.json @@ -1,6 +1,8 @@ { "type": [ + {"0x0100": "?"}, {"0x0300": "DE_VDE4105_2018"}, + {"0x0301": "DE_VDE4105_2011"}, {"0x0a00": "DE NF_EN_50549-1:2019"}, {"0x0c00": "AT_TOR_Erzeuger_default"}, {"0x0d04": "France NF_EN_50549-1:2019"}, @@ -94,6 +96,52 @@ } ] }, + { + "0x0008": [ + { + "name": "Nominal Voltage", + "div": 10, + "def": 230, + "unit": "V" + }, + { + "name": "Low Voltage 1", + "div": 10, + "min": 170, + "max": 184, + "def": 184, + "unit": "V" + }, + { + "name": "LV1 Maximum Trip Time", + "div": 10, + "def": 0.1, + "unit": "s" + }, + { + "name": "High Voltage 1", + "div": 10, + "min": 264.5, + "max": 270, + "def": 264.5, + "unit": "V" + }, + { + "name": "HV1 Maximum Trip Time", + "div": 10, + "def": 0.1, + "unit": "s" + }, + { + "name": "10 mins Average High Voltage", + "div": 10, + "min": 253, + "max": 260, + "def": 253, + "unit": "V" + } + ] + }, { "0x000a": [ { @@ -286,6 +334,54 @@ } ] }, + { + "0x1002": [ + { + "name": "Nominal Frequency", + "div": 100, + "def": 50, + "unit": "Hz" + }, + { + "name": "Low Frequency 1", + "div": 100, + "unit": "Hz" + }, + { + "name": "?", + "div": 10 + }, + { + "name": "?", + "div": 100, + "unit": "Hz" + }, + { + "name": "?", + "div": 10, + "unit": "s" + }, + { + "name": "?", + "div": 100, + "unit": "Hz" + }, + { + "name": "?", + "div": 10, + "unit": "s" + }, + { + "name": "?", + "div": 100 + }, + { + "name": "?", + "div": 10, + "unit": "s" + } + ] + }, { "0x1003": [ { @@ -415,6 +511,62 @@ } ] }, + { + "0x3007": [ + { + "name": "Reconnect Time", + "div": 10, + "min": 10, + "max": 300, + "def": 65, + "unit": "s" + }, + { + "name": "Reconnect High Voltage", + "div": 10, + "min": 253, + "max": 264.5, + "def": 264.5, + "unit": "V" + }, + { + "name": "Reconnect Low Voltage", + "div": 10, + "min": 184, + "max": 210, + "def": 184, + "unit": "V" + }, + { + "name": "Reconnect High Frequency", + "div": 100, + "max": 52, + "min": 50.5, + "def": 51.5, + "unit": "Hz" + }, + { + "name": "Reconnect Low Frequency", + "div": 100, + "min": 47, + "max": 49.9, + "def": 47.5, + "unit": "Hz" + }, + { + "name": "Short Interruption Reconnect Time", + "div": 10, + "def": 8, + "unit": "s" + }, + { + "name": "Short Interruption Time", + "div": 10, + "def": 3, + "unit": "s" + } + ] + }, { "0x4000": [ { @@ -435,6 +587,42 @@ } ] }, + { + "0x5000": [ + { + "name": "FW Function Activated", + "div": 1, + "min": 0, + "max": 1, + "def": 1 + }, + { + "name": "Start of Frequency Watt Droop", + "div": 100, + "min": 50.2, + "max": 52, + "def": 50.2, + "unit": "Hz" + }, + { + "name": "FW Droop Slope", + "div": 10, + "min": 16.7, + "max": 100, + "def": 40, + "unit": "Pn%/Hz" + }, + { + "name": "Recovery Ramp Rate", + "div": 100, + "min": 0.1, + "max": 50, + "def": 0.5, + "unit": "Pn%/s" + + } + ] + }, { "0x5001": [ { @@ -563,6 +751,17 @@ } ] }, + { + "0x7000": [ + { + "name": "APC Function Activated", + "div": 1, + "min": 0, + "max": 1, + "def": 1 + } + ] + }, { "0x7002": [ { diff --git a/src/web/html/visualization.html b/src/web/html/visualization.html index d255d1c5..9567504c 100644 --- a/src/web/html/visualization.html +++ b/src/web/html/visualization.html @@ -356,6 +356,7 @@ var glob = {offs:0, grid:obj.grid, info: data} var content = []; var g = getGridType(glob.info.type, getGridIdentifier(glob)) + var v = getGridValue(glob); if(null === g) { if(0 == obj.grid.length) { content.push(ml("div", {class: "row"}, ml("div", {class: "col"}, ml("p", {}, "Profile was not read until now, maybe turned off?")))) @@ -366,7 +367,7 @@ } } else { content.push(ml("div", {class: "row"}, - ml("div", {class: "col my-3"}, ml("h5", {}, g + " (Version " + getGridValue(glob).toString(16) + ")")) + ml("div", {class: "col my-3"}, ml("h5", {}, g + " (Version " + (Math.round(v / 0x1000)) + "." + (Math.round((v & 0x0ff0) / 0x10)) + "." + (v & 0x0F) + ")")) )) while((glob.offs*3) < glob.grid.length) { From 41485a9af3a5bb2e87fa163d46dd705e220a048c Mon Sep 17 00:00:00 2001 From: lumapu Date: Sun, 31 Dec 2023 03:03:14 +0100 Subject: [PATCH 005/179] 0.8.37 * added grid profiles --- src/web/html/grid_info.json | 55 +++++++++++++++++++++++-------------- 1 file changed, 35 insertions(+), 20 deletions(-) diff --git a/src/web/html/grid_info.json b/src/web/html/grid_info.json index ee6dea13..5ac2e0b2 100644 --- a/src/web/html/grid_info.json +++ b/src/web/html/grid_info.json @@ -1,13 +1,24 @@ { "type": [ - {"0x0100": "?"}, + {"0x0100": "CN_NBT32004_2018"}, + {"0x0200": "US_Rule21_240V"}, {"0x0300": "DE_VDE4105_2018"}, {"0x0301": "DE_VDE4105_2011"}, + {"0x0604": "Germany_VDE4105"}, + {"0x0800": "IT_CEI0-21"}, + {"0x0807": "Netherland_EN50438"}, + {"0x0908": "France_VFR2014"}, {"0x0a00": "DE NF_EN_50549-1:2019"}, {"0x0c00": "AT_TOR_Erzeuger_default"}, - {"0x0d04": "France NF_EN_50549-1:2019"}, - {"0x1200": "2.0.4 (EU_EN50438)"}, - {"0x3700": "2.0.0 (CH_NA EEA-NE7–CH2020)"} + {"0x0d00": "FR_VFR2019"}, + {"0x0d04": "NF_EN_50549-1:2019"}, + {"0x1000": "ES_RD1699"}, + {"0x1200": "EU_EN50438"}, + {"0x2600": "BE_C10_26"}, + {"0x2900": "NL_NEN-EN50549-1_2019"}, + {"0x2a00": "PL_PN-EN 50549-1:2019"}, + {"0x3700": "CH_NA EEA-NE7–CH2020"} + {"0xe100": "LN_50Hz"} ], "grp_codes": [ {"0x00": "Voltage H/LVRT"}, @@ -345,39 +356,43 @@ { "name": "Low Frequency 1", "div": 100, + "min": 49.5, + "max": 49.9, + "def": 49.5, "unit": "Hz" }, { - "name": "?", - "div": 10 - }, - { - "name": "?", - "div": 100, - "unit": "Hz" - }, - { - "name": "?", + "name": "LF1 Maximum Trip Time", "div": 10, + "def": 700, "unit": "s" }, { - "name": "?", + "name": "High Frequency 1", "div": 100, + "max": 51, + "min": 50.1, + "def": 50.2, "unit": "Hz" }, { - "name": "?", + "name": "HF1 Maximum Trip time", "div": 10, + "def": 0.1, "unit": "s" }, { - "name": "?", - "div": 100 + "name": "Low Frequency 2", + "div": 100, + "max": 49, + "min": 47.5, + "def": 47.5, + "unit": "Hz" }, { - "name": "?", - "div": 10, + "name": "LF2 Maximum Trip Time", + "div": 100, + "def": 0.1, "unit": "s" } ] From 1b73e493a9608599b7fa40dda6fec1a6b2294cb8 Mon Sep 17 00:00:00 2001 From: lumapu Date: Sun, 31 Dec 2023 13:04:13 +0100 Subject: [PATCH 006/179] 0.8.38 * fix Grid-Profile JSON #1304 --- src/CHANGES.md | 3 +++ src/defines.h | 2 +- src/web/html/grid_info.json | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/CHANGES.md b/src/CHANGES.md index 32efd79b..f6e7445e 100644 --- a/src/CHANGES.md +++ b/src/CHANGES.md @@ -1,5 +1,8 @@ # Development Changes +## 0.8.38 - 2023-12-31 +* fix Grid-Profile JSON #1304 + ## 0.8.37 - 2023-12-30 * added grid profiles * format version of grid profile diff --git a/src/defines.h b/src/defines.h index 787e9ccf..05cdc5e0 100644 --- a/src/defines.h +++ b/src/defines.h @@ -13,7 +13,7 @@ //------------------------------------- #define VERSION_MAJOR 0 #define VERSION_MINOR 8 -#define VERSION_PATCH 37 +#define VERSION_PATCH 38 //------------------------------------- typedef struct { diff --git a/src/web/html/grid_info.json b/src/web/html/grid_info.json index 5ac2e0b2..f14babe3 100644 --- a/src/web/html/grid_info.json +++ b/src/web/html/grid_info.json @@ -17,7 +17,7 @@ {"0x2600": "BE_C10_26"}, {"0x2900": "NL_NEN-EN50549-1_2019"}, {"0x2a00": "PL_PN-EN 50549-1:2019"}, - {"0x3700": "CH_NA EEA-NE7–CH2020"} + {"0x3700": "CH_NA EEA-NE7–CH2020"}, {"0xe100": "LN_50Hz"} ], "grp_codes": [ From 2571e3c9f9b0abcb9033c183f2c3eee93d3a879a Mon Sep 17 00:00:00 2001 From: rejoe2 Date: Sun, 31 Dec 2023 14:24:23 +0100 Subject: [PATCH 007/179] MI - add grid profile request Note: this doesn't deliver entire grid profile infos... --- src/hm/Communication.h | 47 ++++++++++++++++++++++++++++-------------- src/hm/hmInverter.h | 2 ++ 2 files changed, 33 insertions(+), 16 deletions(-) diff --git a/src/hm/Communication.h b/src/hm/Communication.h index 9f96533d..4aa9ddcc 100644 --- a/src/hm/Communication.h +++ b/src/hm/Communication.h @@ -353,6 +353,12 @@ class Communication : public CommQueue<> { miDataDecode(p, q); } else if (p->packet[0] == (0x0f + ALL_FRAMES)) miHwDecode(p, q); + + else if (p->packet[0] == ( 0x10 + ALL_FRAMES)) { + // MI response from get Grid Profile information request + miGPFDecode(p, q); + } + else if ((p->packet[0] == 0x88) || (p->packet[0] == 0x92)) { record_t<> *rec = q->iv->getRecordStruct(RealTimeRunData_Debug); // choose the record structure rec->ts = q->ts; @@ -650,22 +656,31 @@ class Communication : public CommQueue<> { (mCbPayload)(InverterDevInform_Simple, q->iv); q->iv->miMultiParts++; } - //if(q->iv->miMultiParts > 5) - //closeRequest(q->iv, true); - //else - //if(q->iv->miMultiParts < 6) - // mState = States::WAIT; - - /*if (mPayload[iv->id].multi_parts > 5) { - iv->setQueuedCmdFinished(); - mPayload[iv->id].complete = true; - mPayload[iv->id].rxTmo = true; - mPayload[iv->id].requested= false; - iv->radioStatistics.rxSuccess++; - } - if (mHighPrioIv == NULL) - mHighPrioIv = iv; - */ + } + + inline void miGPFDecode(packet_t *p, const queue_s *q) { + record_t<> *rec = q->iv->getRecordStruct(InverterDevInform_Simple); // choose the record structure + rec->ts = q->ts; + + q->iv->setValue(2, rec, (uint32_t) (((p->packet[10] << 8) | p->packet[11]))); //FLD_GRID_PROFILE_CODE + q->iv->setValue(3, rec, (uint32_t) (((p->packet[12] << 8) | p->packet[13]))); //FLD_GRID_PROFILE_VERSION + +/* according to xlsx (different start byte -1!) + Polling Grid-connected Protection Parameter File Command - Receipt + byte[10] ST1 indicates the status of the grid-connected protection file. ST1=1 indicates the default grid-connected protection file, ST=2 indicates that the grid-connected protection file is configured and normal, ST=3 indicates that the grid-connected protection file cannot be recognized, ST=4 indicates that the grid-connected protection file is damaged + byte[11] byte[12] CountryStd variable indicates the national standard code of the grid-connected protection file + byte[13] byte[14] Version indicates the version of the grid-connected protection file + byte[15] byte[16] +*/ + /*if(mSerialDebug) { + DPRINT(DBG_INFO,F("ST1 ")); + DBGPRINTLN(String(p->packet[9])); + DPRINT(DBG_INFO,F("CountryStd ")); + DBGPRINTLN(String((p->packet[10] << 8) + p->packet[11])); + DPRINT(DBG_INFO,F("Version ")); + DBGPRINTLN(String((p->packet[12] << 8) + p->packet[13])); + }*/ + q->iv->miMultiParts = 7; // indicate we are ready } inline void miDataDecode(packet_t *p, const queue_s *q) { diff --git a/src/hm/hmInverter.h b/src/hm/hmInverter.h index 776b9c6f..757562bb 100644 --- a/src/hm/hmInverter.h +++ b/src/hm/hmInverter.h @@ -215,6 +215,8 @@ class Inverter { record_t<> *rec = getRecordStruct(InverterDevInform_Simple); if (getChannelFieldValue(CH0, FLD_PART_NUM, rec) == 0) cb(0x0f, false); // hard- and firmware version for missing HW part nr, delivered by frame 1 + else if((getChannelFieldValue(CH0, FLD_GRID_PROFILE_CODE, rec) == 0) && generalConfig->readGrid) // read grid profile + cb(0x10, false); // legacy GPF command else cb(((type == INV_TYPE_4CH) ? MI_REQ_4CH : MI_REQ_CH1), false); } From 29d2bdefab2021847678f89493bc7ca450c4392d Mon Sep 17 00:00:00 2001 From: Frank Date: Mon, 1 Jan 2024 15:20:14 +0100 Subject: [PATCH 008/179] PROMETHEUS_EP: Add NRF-radio statistics and power-limit metrics --- doc/prometheus_ep_description.md | 21 ++-- src/web/web.h | 165 ++++++++++++++++--------------- 2 files changed, 98 insertions(+), 88 deletions(-) diff --git a/doc/prometheus_ep_description.md b/doc/prometheus_ep_description.md index 711299dd..651cd937 100644 --- a/doc/prometheus_ep_description.md +++ b/doc/prometheus_ep_description.md @@ -1,10 +1,10 @@ # Prometheus Endpoint Metrics available for AhoyDTU device, inverters and channels. -Prometheus metrics provided at `/metrics`. +Prometheus metrics provided at `/metrics`. ## Labels -| Label name | Description | +| Label name | Description | |:-------------|:--------------------------------------| | version | current installed version of AhoyDTU | | image | currently not used | @@ -19,11 +19,21 @@ Prometheus metrics provided at `/metrics`. |----------------------------------------------|---------|----------------------------------------------------------|--------------| | `ahoy_solar_info` | Gauge | Information about the AhoyDTU device | version, image, devicename | | `ahoy_solar_uptime` | Counter | Seconds since boot of the AhoyDTU device | devicename | -| `ahoy_solar_rssi_db` | Gauge | Quality of the Wifi STA connection | devicename | +| `ahoy_solar_freeheap` | Gauge | free heap memory of the AhoyDTU device | devicename | +| `ahoy_solar_wifi_rssi_db` | Gauge | Quality of the Wifi STA connection | devicename | | `ahoy_solar_inverter_info` | Gauge | Information about the configured inverter(s) | name, serial | | `ahoy_solar_inverter_enabled` | Gauge | Is the inverter enabled? | inverter | | `ahoy_solar_inverter_is_available` | Gauge | is the inverter available? | inverter | | `ahoy_solar_inverter_is_producing` | Gauge | Is the inverter producing? | inverter | +| `ahoy_solar_inverter_power_limit_read` | Gauge | Power Limit read from inverter | inverter | +| `ahoy_solar_inverter_power_limit_ack` | Gauge | Power Limit acknowledged by inverter | inverter | +| `ahoy_solar_inverter_max_power` | Gauge | Max Power of inverter | inverter | +| `ahoy_solar_inverter_radio_rx_success` | Counter | NRF24 statistic of inverter | inverter | +| `ahoy_solar_inverter_radio_rx_fail` | Counter | NRF24 statistic of inverter | inverter | +| `ahoy_solar_inverter_radio_rx_fail_answer` | Counter | NRF24 statistic of inverter | inverter | +| `ahoy_solar_inverter_radio_frame_cnt` | Counter | NRF24 statistic of inverter | inverter | +| `ahoy_solar_inverter_radio_tx_cnt` | Counter | NRF24 statistic of inverter | inverter | +| `ahoy_solar_inverter_radio_retransmits` | Counter | NRF24 statistic of inverter | inverter | | `ahoy_solar_U_AC_volt` | Gauge | AC voltage of inverter [V] | inverter | | `ahoy_solar_I_AC_ampere` | Gauge | AC current of inverter [A] | inverter | | `ahoy_solar_P_AC_watt` | Gauge | AC power of inverter [W] | inverter | @@ -46,9 +56,4 @@ Prometheus metrics provided at `/metrics`. | `ahoy_solar_YieldDay_wattHours` | Counter | Energy converted to AC per day [Wh] | inverter, channel | | `ahoy_solar_YieldTotal_kilowattHours` | Counter | Energy converted to AC since reset [kWh] | inverter, channel | | `ahoy_solar_Irradiation_ratio` | Gauge | ratio DC Power over set maximum power per channel [%] | inverter, channel | -| `ahoy_solar_radio_rx_success` | Gauge | NRF24 statistic | | -| `ahoy_solar_radio_rx_fail` | Gauge | NRF24 statistic | | -| `ahoy_solar_radio_rx_fail_answer` | Gauge | NRF24 statistic | | -| `ahoy_solar_radio_frame_cnt` | Gauge | NRF24 statistic | | -| `ahoy_solar_radio_tx_cnt` | Gauge | NRF24 statistic | | diff --git a/src/web/web.h b/src/web/web.h index 1e87547b..990f1983 100644 --- a/src/web/web.h +++ b/src/web/web.h @@ -623,17 +623,45 @@ class Web { #ifdef ENABLE_PROMETHEUS_EP // Note // Prometheus exposition format is defined here: https://github.com/prometheus/docs/blob/main/content/docs/instrumenting/exposition_formats.md - // TODO: Check packetsize for MAX_NUM_INVERTERS. Successfully Tested with 4 Inverters (each with 4 channels) - enum { - metricsStateStart, - metricsStateInverter1, metricsStateInverter2, metricsStateInverter3, metricsStateInverter4, - metricStateRealtimeFieldId, metricStateRealtimeInverterId, + // NOTE: Grouping for fields with channels and totals is currently not working + // TODO: Handle grouping and sorting for independant from channel number + // NOTE: Check packetsize for MAX_NUM_INVERTERS. Successfully Tested with 4 Inverters (each with 4 channels) + const char * metricPrefix = "ahoy_solar_"; + typedef enum { + metricsStateInverterInfo=0, metricsStateInverterEnabled=1, metricsStateInverterAvailable=2, metricsStateInverterProducing=3, + metricsStateInverterPowerLimitRead=4, metricsStateInverterPowerLimitAck=5, metricsStateInverterMaxPower=6, + metricsStateInverterRxSuccess=7, metricsStateInverterRxFail=8, metricsStateInverterRxFailAnswer=9, + metricsStateInverterFrameCnt=10, metricsStateInverterTxCnt=11, metricsStateInverterRetransmits=12, + metricStateRealtimeFieldId=metricsStateInverterRetransmits+1, // ensure that this state follows the last per_inverter state + metricStateRealtimeInverterId, metricsStateAlarmData, + metricsStateStart, metricsStateEnd - } metricsStep; + } MetricStep_t; + MetricStep_t metricsStep; + typedef struct { + const char *type; + const char *format; + const std::function *iv)> valueFunc; + } InverterMetric_t; + InverterMetric_t inverterMetrics[13] = { + { "info", "info{name=\"%s\",serial=\"%12llx\"} 1\n", [](Inverter<> *iv)-> uint64_t {return iv->config->serial.u64;} }, + { "is_enabled", "is_enabled {inverter=\"%s\"} %d\n", [](Inverter<> *iv)-> uint64_t {return iv->config->enabled;} }, + { "is_available", "is_available {inverter=\"%s\"} %d\n", [](Inverter<> *iv)-> uint64_t {return iv->isAvailable();} }, + { "is_producing", "is_producing {inverter=\"%s\"} %d\n", [](Inverter<> *iv)-> uint64_t {return iv->isProducing();} }, + { "power_limit_read", "power_limit_read {inverter=\"%s\"} %d\n", [](Inverter<> *iv)-> uint64_t {return (int64_t)ah::round3(iv->actPowerLimit);} }, + { "power_limit_ack", "power_limit_ack {inverter=\"%s\"} %d\n", [](Inverter<> *iv)-> uint64_t {return (iv->powerLimitAck)?1:0;} }, + { "max_power", "max_power {inverter=\"%s\"} %d\n", [](Inverter<> *iv)-> uint64_t {return iv->getMaxPower();} }, + { "radio_rx_success", "radio_rx_success {inverter=\"%s\"} %d\n", [](Inverter<> *iv)-> uint64_t {return iv->radioStatistics.rxSuccess;} }, + { "radio_rx_fail", "radio_rx_fail {inverter=\"%s\"} %d\n", [](Inverter<> *iv)-> uint64_t {return iv->radioStatistics.rxFail;} }, + { "radio_rx_fail_answer", "radio_rx_fail_answer {inverter=\"%s\"} %d\n", [](Inverter<> *iv)-> uint64_t {return iv->radioStatistics.rxFailNoAnser;} }, + { "radio_frame_cnt", "radio_frame_cnt {inverter=\"%s\"} %d\n", [](Inverter<> *iv)-> uint64_t {return iv->radioStatistics.frmCnt;} }, + { "radio_tx_cnt", "radio_tx_cnt {inverter=\"%s\"} %d\n", [](Inverter<> *iv)-> uint64_t {return iv->radioStatistics.txCnt;} }, + { "radio_retransmits", "radio_retransmits {inverter=\"%s\"} %d\n", [](Inverter<> *iv)-> uint64_t {return iv->radioStatistics.retransmits;} } + }; int metricsInverterId; uint8_t metricsFieldId; - bool metricDeclared; + bool metricDeclared, metricTotalDeclard; void showMetrics(AsyncWebServerRequest *request) { DPRINTLN(DBG_VERBOSE, F("web::showMetrics")); @@ -654,79 +682,58 @@ class Web { // Each step must return at least one character. Otherwise the processing of AsyncWebServerResponse stops. // So several "Info:" blocks are used to keep the transmission going switch (metricsStep) { - case metricsStateStart: // System Info & NRF Statistics : fit to one packet - snprintf(type,sizeof(type),"# TYPE ahoy_solar_info gauge\n"); - snprintf(topic,sizeof(topic),"ahoy_solar_info{version=\"%s\",image=\"\",devicename=\"%s\"} 1\n", + case metricsStateStart: // System Info : fit to one packet + snprintf(type,sizeof(type),"# TYPE %sinfo gauge\n",metricPrefix); + snprintf(topic,sizeof(topic),"%sinfo{version=\"%s\",image=\"\",devicename=\"%s\"} 1\n",metricPrefix, mApp->getVersion(), mConfig->sys.deviceName); metrics = String(type) + String(topic); - snprintf(type,sizeof(type),"# TYPE ahoy_solar_freeheap gauge\n"); - snprintf(topic,sizeof(topic),"ahoy_solar_freeheap{devicename=\"%s\"} %u\n",mConfig->sys.deviceName,ESP.getFreeHeap()); + snprintf(type,sizeof(type),"# TYPE %sfreeheap gauge\n",metricPrefix); + snprintf(topic,sizeof(topic),"%sfreeheap{devicename=\"%s\"} %u\n",metricPrefix,mConfig->sys.deviceName,ESP.getFreeHeap()); metrics += String(type) + String(topic); - snprintf(type,sizeof(type),"# TYPE ahoy_solar_uptime counter\n"); - snprintf(topic,sizeof(topic),"ahoy_solar_uptime{devicename=\"%s\"} %u\n", mConfig->sys.deviceName, mApp->getUptime()); + snprintf(type,sizeof(type),"# TYPE %suptime counter\n",metricPrefix); + snprintf(topic,sizeof(topic),"%suptime{devicename=\"%s\"} %u\n",metricPrefix, mConfig->sys.deviceName, mApp->getUptime()); metrics += String(type) + String(topic); - snprintf(type,sizeof(type),"# TYPE ahoy_solar_wifi_rssi_db gauge\n"); - snprintf(topic,sizeof(topic),"ahoy_solar_wifi_rssi_db{devicename=\"%s\"} %d\n", mConfig->sys.deviceName, WiFi.RSSI()); + snprintf(type,sizeof(type),"# TYPE %swifi_rssi_db gauge\n",metricPrefix); + snprintf(topic,sizeof(topic),"%swifi_rssi_db{devicename=\"%s\"} %d\n",metricPrefix, mConfig->sys.deviceName, WiFi.RSSI()); metrics += String(type) + String(topic); - // NRF Statistics - // @TODO 2023-10-01: the statistic data is now available per inverter - /*stat = mApp->getNrfStatistics(); - metrics += radioStatistic(F("rx_success"), stat->rxSuccess); - metrics += radioStatistic(F("rx_fail"), stat->rxFail); - metrics += radioStatistic(F("rx_fail_answer"), stat->rxFailNoAnser); - metrics += radioStatistic(F("frame_cnt"), stat->frmCnt); - metrics += radioStatistic(F("tx_cnt"), stat->txCnt); - metrics += radioStatistic(F("retrans_cnt"), stat->retransmits);*/ - len = snprintf((char *)buffer,maxLen,"%s",metrics.c_str()); // Next is Inverter information - metricsInverterId = 0; - metricsStep = metricsStateInverter1; + metricsStep = metricsStateInverterInfo; break; - case metricsStateInverter1: // Information about all inverters configured : fit to one packet - metrics = "# TYPE ahoy_solar_inverter_info gauge\n"; - metrics += inverterMetric(topic, sizeof(topic),"ahoy_solar_inverter_info{name=\"%s\",serial=\"%12llx\"} 1\n", - [](Inverter<> *iv,IApp *mApp)-> uint64_t {return iv->config->serial.u64;}); + // Information about all inverters configured : each metric for all inverters must fit to one network packet + case metricsStateInverterInfo: + case metricsStateInverterEnabled: + case metricsStateInverterAvailable: + case metricsStateInverterProducing: + case metricsStateInverterPowerLimitRead: + case metricsStateInverterPowerLimitAck: + case metricsStateInverterMaxPower: + case metricsStateInverterRxSuccess: + case metricsStateInverterRxFail: + case metricsStateInverterRxFailAnswer: + case metricsStateInverterFrameCnt: + case metricsStateInverterTxCnt: + case metricsStateInverterRetransmits: + metrics = "# TYPE ahoy_solar_inverter_" + String(inverterMetrics[metricsStep].type) + " gauge\n"; + metrics += inverterMetric(topic, sizeof(topic),(String("ahoy_solar_inverter_") + inverterMetrics[metricsStep].format).c_str(), inverterMetrics[metricsStep].valueFunc); len = snprintf((char *)buffer,maxLen,"%s",metrics.c_str()); - metricsStep = metricsStateInverter2; - break; - - case metricsStateInverter2: // Information about all inverters configured : fit to one packet - metrics += "# TYPE ahoy_solar_inverter_is_enabled gauge\n"; - metrics += inverterMetric(topic, sizeof(topic),"ahoy_solar_inverter_is_enabled {inverter=\"%s\"} %d\n", - [](Inverter<> *iv,IApp *mApp)-> uint64_t {return iv->config->enabled;}); - - len = snprintf((char *)buffer,maxLen,"%s",metrics.c_str()); - metricsStep = metricsStateInverter3; - break; - - case metricsStateInverter3: // Information about all inverters configured : fit to one packet - metrics += "# TYPE ahoy_solar_inverter_is_available gauge\n"; - metrics += inverterMetric(topic, sizeof(topic),"ahoy_solar_inverter_is_available {inverter=\"%s\"} %d\n", - [](Inverter<> *iv,IApp *mApp)-> uint64_t {return iv->isAvailable();}); - len = snprintf((char *)buffer,maxLen,"%s",metrics.c_str()); - metricsStep = metricsStateInverter4; - break; - - case metricsStateInverter4: // Information about all inverters configured : fit to one packet - metrics += "# TYPE ahoy_solar_inverter_is_producing gauge\n"; - metrics += inverterMetric(topic, sizeof(topic),"ahoy_solar_inverter_is_producing {inverter=\"%s\"} %d\n", - [](Inverter<> *iv,IApp *mApp)-> uint64_t {return iv->isProducing();}); - len = snprintf((char *)buffer,maxLen,"%s",metrics.c_str()); - // Start Realtime Field loop + // ugly hack to increment the enum + metricsStep = static_cast( static_cast(metricsStep) + 1); + // Prepare Realtime Field loop, which may be startet next metricsFieldId = FLD_UDC; - metricsStep = metricStateRealtimeFieldId; break; + case metricStateRealtimeFieldId: // Iterate over all defined fields if (metricsFieldId < FLD_LAST_ALARM_CODE) { metrics = "# Info: processing realtime field #"+String(metricsFieldId)+"\n"; metricDeclared = false; + metricTotalDeclard = false; metricsInverterId = 0; metricsStep = metricStateRealtimeInverterId; @@ -741,7 +748,6 @@ class Web { metrics = ""; if (metricsInverterId < mSys->getNumInverters()) { // process all channels of this inverter - iv = mSys->getInverterByPos(metricsInverterId); if (NULL != iv) { rec = iv->getRecordStruct(RealTimeRunData_Debug); @@ -755,22 +761,27 @@ class Web { std::tie(promUnit, promType) = convertToPromUnits(iv->getUnit(metricsChannelId, rec)); // Declare metric only once if (channel != 0 && !metricDeclared) { - snprintf(type, sizeof(type), "# TYPE ahoy_solar_%s%s %s\n", iv->getFieldName(metricsChannelId, rec), promUnit.c_str(), promType.c_str()); + snprintf(type, sizeof(type), "# TYPE %s%s%s %s\n",metricPrefix, iv->getFieldName(metricsChannelId, rec), promUnit.c_str(), promType.c_str()); metrics += type; metricDeclared = true; } // report value if (0 == channel) { + // Report a _total value if also channel values were reported. Otherwise report without _total char total[7]; total[0] = 0; if (metricDeclared) { - // A declaration and value for channels has been delivered. So declare and deliver a _total metric + // A declaration and value for channels have been delivered. So declare and deliver a _total metric strncpy(total,"_total",sizeof(total)); } - snprintf(type, sizeof(type), "# TYPE ahoy_solar_%s%s%s %s\n", iv->getFieldName(metricsChannelId, rec), promUnit.c_str(), total, promType.c_str()); - metrics += type; - snprintf(topic, sizeof(topic), "ahoy_solar_%s%s%s{inverter=\"%s\"}", iv->getFieldName(metricsChannelId, rec), promUnit.c_str(), total,iv->config->name); + if (!metricTotalDeclard) { + snprintf(type, sizeof(type), "# TYPE %s%s%s%s %s\n",metricPrefix, iv->getFieldName(metricsChannelId, rec), promUnit.c_str(), total, promType.c_str()); + metrics += type; + metricTotalDeclard = true; + } + snprintf(topic, sizeof(topic), "%s%s%s%s{inverter=\"%s\"}",metricPrefix, iv->getFieldName(metricsChannelId, rec), promUnit.c_str(), total,iv->config->name); } else { + // Report (non zero) channel value // Use a fallback channel name (ch0, ch1, ...)if non is given by user char chName[MAX_NAME_LENGTH]; if (iv->config->chName[channel-1][0] != 0) { @@ -778,7 +789,7 @@ class Web { } else { snprintf(chName,sizeof(chName),"ch%1d",channel); } - snprintf(topic, sizeof(topic), "ahoy_solar_%s%s{inverter=\"%s\",channel=\"%s\"}", iv->getFieldName(metricsChannelId, rec), promUnit.c_str(), iv->config->name,chName); + snprintf(topic, sizeof(topic), "%s%s%s{inverter=\"%s\",channel=\"%s\"}",metricPrefix, iv->getFieldName(metricsChannelId, rec), promUnit.c_str(), iv->config->name,chName); } snprintf(val, sizeof(val), " %.3f\n", iv->getValue(metricsChannelId, rec)); metrics += topic; @@ -808,7 +819,7 @@ class Web { case metricsStateAlarmData: // Alarm Info loop : fit to one packet // Perform grouping on metrics according to Prometheus exposition format specification - snprintf(type, sizeof(type),"# TYPE ahoy_solar_%s gauge\n",fields[FLD_LAST_ALARM_CODE]); + snprintf(type, sizeof(type),"# TYPE %s%s gauge\n",metricPrefix,fields[FLD_LAST_ALARM_CODE]); metrics = type; for (metricsInverterId = 0; metricsInverterId < mSys->getNumInverters();metricsInverterId++) { @@ -820,7 +831,7 @@ class Web { alarmChannelId = 0; if (alarmChannelId < rec->length) { std::tie(promUnit, promType) = convertToPromUnits(iv->getUnit(alarmChannelId, rec)); - snprintf(topic, sizeof(topic), "ahoy_solar_%s%s{inverter=\"%s\"}", iv->getFieldName(alarmChannelId, rec), promUnit.c_str(), iv->config->name); + snprintf(topic, sizeof(topic), "%s%s%s{inverter=\"%s\"}",metricPrefix, iv->getFieldName(alarmChannelId, rec), promUnit.c_str(), iv->config->name); snprintf(val, sizeof(val), " %.3f\n", iv->getValue(alarmChannelId, rec)); metrics += topic; metrics += val; @@ -831,11 +842,13 @@ class Web { metricsStep = metricsStateEnd; break; - case metricsStateEnd: default: // end of transmission + DBGPRINT("E: Prometheus: Bad metricsStep="); + DBGPRINTLN(String(metricsStep)); + case metricsStateEnd: len = 0; break; - } + } // switch return len; }); request->send(response); @@ -843,27 +856,19 @@ class Web { // Traverse all inverters and collect the metric via valueFunc - String inverterMetric(char *buffer, size_t len, const char *format, std::function *iv, IApp *mApp)> valueFunc) { + String inverterMetric(char *buffer, size_t len, const char *format, std::function *iv)> valueFunc) { Inverter<> *iv; String metric = ""; for (int metricsInverterId = 0; metricsInverterId < mSys->getNumInverters();metricsInverterId++) { iv = mSys->getInverterByPos(metricsInverterId); if (NULL != iv) { - snprintf(buffer,len,format,iv->config->name, valueFunc(iv,mApp)); + snprintf(buffer,len,format,iv->config->name, valueFunc(iv)); metric += String(buffer); } } return metric; } - String radioStatistic(String statistic, uint32_t value) { - char type[60], topic[80], val[25]; - snprintf(type, sizeof(type), "# TYPE ahoy_solar_radio_%s counter",statistic.c_str()); - snprintf(topic, sizeof(topic), "ahoy_solar_radio_%s",statistic.c_str()); - snprintf(val, sizeof(val), "%d", value); - return ( String(type) + "\n" + String(topic) + " " + String(val) + "\n"); - } - std::pair convertToPromUnits(String shortUnit) { if(shortUnit == "A") return {"_ampere", "gauge"}; if(shortUnit == "V") return {"_volt", "gauge"}; From 2cda39c9f9511eb078a14aad8c096d59687e42d4 Mon Sep 17 00:00:00 2001 From: lumapu Date: Mon, 1 Jan 2024 23:02:14 +0100 Subject: [PATCH 009/179] 0.8.39 * fix MqTT dis_night_comm in the morning #1309 * seperated offset for sunrise and sunset #1308 --- src/CHANGES.md | 4 ++++ src/app.cpp | 21 ++++++++++++--------- src/config/settings.h | 19 +++++++++++++------ src/defines.h | 4 ++-- src/publisher/pubMqtt.h | 10 +++++----- src/web/RestApi.h | 14 ++++++++------ src/web/html/index.html | 38 +++++++++++++++++++------------------- src/web/html/setup.html | 16 +++++++++++----- src/web/web.h | 8 +++++--- 9 files changed, 79 insertions(+), 55 deletions(-) diff --git a/src/CHANGES.md b/src/CHANGES.md index f6e7445e..66056890 100644 --- a/src/CHANGES.md +++ b/src/CHANGES.md @@ -1,5 +1,9 @@ # Development Changes +## 0.8.39 - 2024-01-01 +* fix MqTT dis_night_comm in the morning #1309 +* seperated offset for sunrise and sunset #1308 + ## 0.8.38 - 2023-12-31 * fix Grid-Profile JSON #1304 diff --git a/src/app.cpp b/src/app.cpp index 0bba14da..8e70778a 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -1,5 +1,5 @@ //----------------------------------------------------------------------------- -// 2023 Ahoy, https://ahoydtu.de +// 2024 Ahoy, https://ahoydtu.de // Creative Commons - https://creativecommons.org/licenses/by-nc-sa/4.0/deed //----------------------------------------------------------------------------- @@ -226,15 +226,18 @@ void app::tickCalcSunrise(void) { if (mSunrise == 0) // on boot/reboot calc sun values for current time ah::calculateSunriseSunset(mTimestamp, mCalculatedTimezoneOffset, mConfig->sun.lat, mConfig->sun.lon, &mSunrise, &mSunset); - if (mTimestamp > (mSunset + mConfig->sun.offsetSec)) // current time is past communication stop, calc sun values for next day + if (mTimestamp > (mSunset + mConfig->sun.offsetSecEvening)) // current time is past communication stop, calc sun values for next day ah::calculateSunriseSunset(mTimestamp + 86400, mCalculatedTimezoneOffset, mConfig->sun.lat, mConfig->sun.lon, &mSunrise, &mSunset); tickIVCommunication(); - uint32_t nxtTrig = mSunset + mConfig->sun.offsetSec + 60; // set next trigger to communication stop, +60 for safety that it is certain past communication stop + uint32_t nxtTrig = mSunset + mConfig->sun.offsetSecEvening + 60; // set next trigger to communication stop, +60 for safety that it is certain past communication stop onceAt(std::bind(&app::tickCalcSunrise, this), nxtTrig, "Sunri"); - if (mMqttEnabled) + if (mMqttEnabled) { tickSun(); + nxtTrig = mSunrise - mConfig->sun.offsetSecMorning + 1; // one second safety to trigger correctly + onceAt(std::bind(&app::tickSun, this), nxtTrig, "mqSr"); // trigger on sunrise to update 'dis_night_comm' + } } //----------------------------------------------------------------------------- @@ -251,14 +254,14 @@ void app::tickIVCommunication(void) { iv->commEnabled = !iv->config->disNightCom; // if sun.disNightCom is false, communication is always on if (!iv->commEnabled) { // inverter communication only during the day - if (mTimestamp < (mSunrise - mConfig->sun.offsetSec)) { // current time is before communication start, set next trigger to communication start - nxtTrig = mSunrise - mConfig->sun.offsetSec; + if (mTimestamp < (mSunrise - mConfig->sun.offsetSecMorning)) { // current time is before communication start, set next trigger to communication start + nxtTrig = mSunrise - mConfig->sun.offsetSecMorning; } else { - if (mTimestamp >= (mSunset + mConfig->sun.offsetSec)) { // current time is past communication stop, nothing to do. Next update will be done at midnight by tickCalcSunrise + if (mTimestamp >= (mSunset + mConfig->sun.offsetSecEvening)) { // current time is past communication stop, nothing to do. Next update will be done at midnight by tickCalcSunrise nxtTrig = 0; } else { // current time lies within communication start/stop time, set next trigger to communication stop iv->commEnabled = true; - nxtTrig = mSunset + mConfig->sun.offsetSec; + nxtTrig = mSunset + mConfig->sun.offsetSecEvening; } } if (nxtTrig != 0) @@ -279,7 +282,7 @@ void app::tickIVCommunication(void) { //----------------------------------------------------------------------------- void app::tickSun(void) { // only used and enabled by MQTT (see setup()) - if (!mMqtt.tickerSun(mSunrise, mSunset, mConfig->sun.offsetSec)) + if (!mMqtt.tickerSun(mSunrise, mSunset, mConfig->sun.offsetSecMorning, mConfig->sun.offsetSecEvening)) once(std::bind(&app::tickSun, this), 1, "mqSun"); // MQTT not connected, retry } diff --git a/src/config/settings.h b/src/config/settings.h index fe2ad1b0..c493eb1a 100644 --- a/src/config/settings.h +++ b/src/config/settings.h @@ -1,5 +1,5 @@ //----------------------------------------------------------------------------- -// 2023 Ahoy, https://ahoydtu.de +// 2024 Ahoy, https://ahoydtu.de // Creative Commons - http://creativecommons.org/licenses/by-nc-sa/4.0/deed //----------------------------------------------------------------------------- @@ -30,7 +30,7 @@ * https://arduino-esp8266.readthedocs.io/en/latest/filesystem.html#flash-layout * */ -#define CONFIG_VERSION 7 +#define CONFIG_VERSION 8 #define PROT_MASK_INDEX 0x0001 @@ -106,7 +106,8 @@ typedef struct { typedef struct { float lat; float lon; - uint16_t offsetSec; + uint16_t offsetSecMorning; + uint16_t offsetSecEvening; } cfgSun_t; typedef struct { @@ -420,7 +421,8 @@ class settings { mCfg.sun.lat = 0.0; mCfg.sun.lon = 0.0; - mCfg.sun.offsetSec = 0; + mCfg.sun.offsetSecMorning = 0; + mCfg.sun.offsetSecEvening = 0; mCfg.serial.showIv = false; mCfg.serial.debug = false; @@ -496,6 +498,9 @@ class settings { if(mCfg.configVersion < 7) { mCfg.led.luminance = 255; } + if(mCfg.configVersion < 8) { + mCfg.sun.offsetSecEvening = mCfg.sun.offsetSecMorning; + } } } @@ -625,11 +630,13 @@ class settings { if(set) { obj[F("lat")] = mCfg.sun.lat; obj[F("lon")] = mCfg.sun.lon; - obj[F("offs")] = mCfg.sun.offsetSec; + obj[F("offs")] = mCfg.sun.offsetSecMorning; + obj[F("offsEve")] = mCfg.sun.offsetSecEvening; } else { getVal(obj, F("lat"), &mCfg.sun.lat); getVal(obj, F("lon"), &mCfg.sun.lon); - getVal(obj, F("offs"), &mCfg.sun.offsetSec); + getVal(obj, F("offs"), &mCfg.sun.offsetSecMorning); + getVal(obj, F("offsEve"), &mCfg.sun.offsetSecEvening); } } diff --git a/src/defines.h b/src/defines.h index 05cdc5e0..ad321921 100644 --- a/src/defines.h +++ b/src/defines.h @@ -1,5 +1,5 @@ //----------------------------------------------------------------------------- -// 2023 Ahoy, https://ahoydtu.de +// 2024 Ahoy, https://ahoydtu.de // Creative Commons - https://creativecommons.org/licenses/by-nc-sa/4.0/deed //----------------------------------------------------------------------------- @@ -13,7 +13,7 @@ //------------------------------------- #define VERSION_MAJOR 0 #define VERSION_MINOR 8 -#define VERSION_PATCH 38 +#define VERSION_PATCH 39 //------------------------------------- typedef struct { diff --git a/src/publisher/pubMqtt.h b/src/publisher/pubMqtt.h index e062439c..9834f29a 100644 --- a/src/publisher/pubMqtt.h +++ b/src/publisher/pubMqtt.h @@ -1,5 +1,5 @@ //----------------------------------------------------------------------------- -// 2023 Ahoy, https://ahoydtu.de +// 2024 Ahoy, https://ahoydtu.de // Creative Commons - https://creativecommons.org/licenses/by-nc-sa/4.0/deed //----------------------------------------------------------------------------- @@ -134,14 +134,14 @@ class PubMqtt { #endif } - bool tickerSun(uint32_t sunrise, uint32_t sunset, uint32_t offs) { + bool tickerSun(uint32_t sunrise, uint32_t sunset, uint16_t offsM, uint16_t offsE) { if (!mClient.connected()) return false; publish(subtopics[MQTT_SUNRISE], String(sunrise).c_str(), true); publish(subtopics[MQTT_SUNSET], String(sunset).c_str(), true); - publish(subtopics[MQTT_COMM_START], String(sunrise - offs).c_str(), true); - publish(subtopics[MQTT_COMM_STOP], String(sunset + offs).c_str(), true); + publish(subtopics[MQTT_COMM_START], String(sunrise - offsM).c_str(), true); + publish(subtopics[MQTT_COMM_STOP], String(sunset + offsE).c_str(), true); Inverter<> *iv; for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i++) { @@ -155,7 +155,7 @@ class PubMqtt { snprintf(mSubTopic, 32 + MAX_NAME_LENGTH, "comm_disabled"); - publish(mSubTopic, (((*mUtcTimestamp > (sunset + offs)) || (*mUtcTimestamp < (sunrise - offs))) ? dict[STR_TRUE] : dict[STR_FALSE]), true); + publish(mSubTopic, (((*mUtcTimestamp > (sunset + offsE)) || (*mUtcTimestamp < (sunrise - offsM))) ? dict[STR_TRUE] : dict[STR_FALSE]), true); return true; } diff --git a/src/web/RestApi.h b/src/web/RestApi.h index a74f4f14..c920a8e3 100644 --- a/src/web/RestApi.h +++ b/src/web/RestApi.h @@ -1,5 +1,5 @@ //----------------------------------------------------------------------------- -// 2023 Ahoy, https://ahoydtu.de +// 2024 Ahoy, https://ahoydtu.de // Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/ //----------------------------------------------------------------------------- @@ -600,7 +600,8 @@ class RestApi { void getSun(JsonObject obj) { obj[F("lat")] = mConfig->sun.lat ? String(mConfig->sun.lat, 5) : ""; obj[F("lon")] = mConfig->sun.lat ? String(mConfig->sun.lon, 5) : ""; - obj[F("offs")] = mConfig->sun.offsetSec; + obj[F("offsSr")] = mConfig->sun.offsetSecMorning; + obj[F("offsSs")] = mConfig->sun.offsetSecEvening; } void getPinout(JsonObject obj) { @@ -685,10 +686,11 @@ class RestApi { void getIndex(AsyncWebServerRequest *request, JsonObject obj) { getGeneric(request, obj.createNestedObject(F("generic"))); - obj[F("ts_now")] = mApp->getTimestamp(); - obj[F("ts_sunrise")] = mApp->getSunrise(); - obj[F("ts_sunset")] = mApp->getSunset(); - obj[F("ts_offset")] = mConfig->sun.offsetSec; + obj[F("ts_now")] = mApp->getTimestamp(); + obj[F("ts_sunrise")] = mApp->getSunrise(); + obj[F("ts_sunset")] = mApp->getSunset(); + obj[F("ts_offsSr")] = mConfig->sun.offsetSecMorning; + obj[F("ts_offsSs")] = mConfig->sun.offsetSecEvening; JsonArray inv = obj.createNestedArray(F("inverter")); Inverter<> *iv; diff --git a/src/web/html/index.html b/src/web/html/index.html index baa70742..3ac72e89 100644 --- a/src/web/html/index.html +++ b/src/web/html/index.html @@ -45,12 +45,12 @@ function apiCb(obj) { var e = document.getElementById("apiResult"); - if(obj["success"]) { + if(obj.success) { e.innerHTML = " command executed"; getAjax("/api/index", parse); } else - e.innerHTML = " Error: " + obj["error"]; + e.innerHTML = " Error: " + obj.error; } function setTime() { @@ -68,9 +68,9 @@ } function parseSys(obj) { - ts = obj["ts_now"]; - var date = new Date(obj["ts_now"] * 1000); - var up = obj["generic"]["ts_uptime"]; + ts = obj.ts_now; + var date = new Date(obj.ts_now * 1000); + var up = obj.generic["ts_uptime"]; var days = parseInt(up / 86400) % 365; var hrs = parseInt(up / 3600) % 24; var min = parseInt(up / 60) % 60; @@ -83,8 +83,8 @@ + ("0"+min).substr(-2) + ":" + ("0"+sec).substr(-2); var dSpan = document.getElementById("date"); - if(0 != obj["ts_now"]) { - if(obj["ts_now"] < 1680000000) + if(0 != obj.ts_now) { + if(obj.ts_now < 1680000000) setTime(); else dSpan.innerHTML = toIsoDateStr(date); @@ -98,18 +98,18 @@ e.addEventListener("click", setTime); } - if(obj["disNightComm"]) { - if(((obj["ts_sunrise"] - obj["ts_offset"]) < obj["ts_now"]) - && ((obj["ts_sunset"] + obj["ts_offset"]) > obj["ts_now"])) { - commInfo = "Polling inverter(s), will pause at sunset " + (new Date((obj["ts_sunset"] + obj["ts_offset"]) * 1000).toLocaleString('de-DE')); + if(obj.disNightComm) { + if(((obj.ts_sunrise - obj.ts_offsSr) < obj.ts_now) + && ((obj.ts_sunset + obj.ts_offsSs) > obj.ts_now)) { + commInfo = "Polling inverter(s), will pause at sunset " + (new Date((obj.ts_sunset + obj.ts_offsSs) * 1000).toLocaleString('de-DE')); } else { commInfo = "Night time, inverter polling disabled, "; - if(obj["ts_now"] > (obj["ts_sunrise"] - obj["ts_offset"])) { - commInfo += "paused at " + (new Date((obj["ts_sunset"] + obj["ts_offset"]) * 1000).toLocaleString('de-DE')); + if(obj.ts_now > (obj.ts_sunrise - obj.ts_offsSr)) { + commInfo += "paused at " + (new Date((obj.ts_sunset + obj.ts_offsSs) * 1000).toLocaleString('de-DE')); } else { - commInfo += "will start polling at " + (new Date((obj["ts_sunrise"] - obj["ts_offset"]) * 1000).toLocaleString('de-DE')); + commInfo += "will start polling at " + (new Date((obj.ts_sunrise - obj.ts_offsSr) * 1000).toLocaleString('de-DE')); } } } @@ -190,11 +190,11 @@ function parse(obj) { if(null != obj) { if(exeOnce) - parseNav(obj["generic"]); - parseGeneric(obj["generic"]); + parseNav(obj.generic); + parseGeneric(obj.generic); parseSys(obj); - parseIv(obj["inverter"], obj.ts_now); - parseWarn(obj["warnings"]); + parseIv(obj.inverter, obj.ts_now); + parseWarn(obj.warnings); if(exeOnce) { window.setInterval("tick()", 1000); exeOnce = false; @@ -210,7 +210,7 @@ } function parseRelease(obj) { - release = obj["name"].substring(6); + release = obj.name.substring(6); getAjax("/api/index", parse); } diff --git a/src/web/html/setup.html b/src/web/html/setup.html index f172f925..aef43dcf 100644 --- a/src/web/html/setup.html +++ b/src/web/html/setup.html @@ -227,8 +227,12 @@
-
Offset (pre sunrise, post sunset)
-
+
Offset (sunrise)
+
+
+
+
Offset (sunset)
+
@@ -889,9 +893,11 @@ function parseSun(obj) { document.getElementsByName("sunLat")[0].value = obj["lat"]; document.getElementsByName("sunLon")[0].value = obj["lon"]; - const sel = document.getElementsByName("sunOffs")[0]; - for(var i = 0; i <= 60; i++) { - sel.appendChild(opt(i, i + " minutes", (i == (obj["offs"] / 60)))); + for(p of [["sunOffsSr", "offsSr"], ["sunOffsSs", "offsSs"]]) { + const sel = document.getElementsByName(p[0])[0]; + for(var i = 0; i <= 60; i++) { + sel.appendChild(opt(i, i + " minutes", (i == (obj[p[1]] / 60)))); + } } } diff --git a/src/web/web.h b/src/web/web.h index 1e87547b..d10fa62d 100644 --- a/src/web/web.h +++ b/src/web/web.h @@ -1,5 +1,5 @@ //----------------------------------------------------------------------------- -// 2022 Ahoy, https://www.mikrocontroller.net/topic/525778 +// 2024 Ahoy, https://www.mikrocontroller.net/topic/525778 // Creative Commons - http://creativecommons.org/licenses/by-nc-sa/4.0/deed //----------------------------------------------------------------------------- @@ -541,11 +541,13 @@ class Web { if (request->arg("sunLat") == "" || (request->arg("sunLon") == "")) { mConfig->sun.lat = 0.0; mConfig->sun.lon = 0.0; - mConfig->sun.offsetSec = 0; + mConfig->sun.offsetSecMorning = 0; + mConfig->sun.offsetSecEvening = 0; } else { mConfig->sun.lat = request->arg("sunLat").toFloat(); mConfig->sun.lon = request->arg("sunLon").toFloat(); - mConfig->sun.offsetSec = request->arg("sunOffs").toInt() * 60; + mConfig->sun.offsetSecMorning = request->arg("sunOffsSr").toInt() * 60; + mConfig->sun.offsetSecEvening = request->arg("sunOffsSs").toInt() * 60; } // mqtt From ea29e49c93cdb1fa017886b441ce0a23d9a73a4e Mon Sep 17 00:00:00 2001 From: lumapu Date: Tue, 2 Jan 2024 00:21:06 +0100 Subject: [PATCH 010/179] 0.8.39 * fix MqTT dis_night_comm in the morning #1309 #1286 * seperated offset for sunrise and sunset #1308 * **BREAKING CHANGE**: powerlimit (active power control) now has one decimal place (MqTT / API) #1199 --- src/CHANGES.md | 3 ++- src/hm/Communication.h | 4 ++-- src/hm/hmDefines.h | 4 ++-- src/hm/hmInverter.h | 8 ++++---- src/hm/hmRadio.h | 10 +++++----- src/hms/hmsRadio.h | 10 +++++----- src/web/html/visualization.html | 4 ++-- 7 files changed, 22 insertions(+), 21 deletions(-) diff --git a/src/CHANGES.md b/src/CHANGES.md index 66056890..67e94621 100644 --- a/src/CHANGES.md +++ b/src/CHANGES.md @@ -1,8 +1,9 @@ # Development Changes ## 0.8.39 - 2024-01-01 -* fix MqTT dis_night_comm in the morning #1309 +* fix MqTT dis_night_comm in the morning #1309 #1286 * seperated offset for sunrise and sunset #1308 +* **BREAKING CHANGE**: powerlimit (active power control) now has one decimal place (MqTT / API) #1199 ## 0.8.38 - 2023-12-31 * fix Grid-Profile JSON #1304 diff --git a/src/hm/Communication.h b/src/hm/Communication.h index 9f96533d..79f5d34a 100644 --- a/src/hm/Communication.h +++ b/src/hm/Communication.h @@ -1,5 +1,5 @@ //----------------------------------------------------------------------------- -// 2023 Ahoy, https://github.com/lumpapu/ahoy +// 2024 Ahoy, https://github.com/lumpapu/ahoy // Creative Commons - http://creativecommons.org/licenses/by-nc-sa/4.0/deed //----------------------------------------------------------------------------- @@ -392,7 +392,7 @@ class Communication : public CommQueue<> { DBGPRINT(F("has ")); if(!accepted) DBGPRINT(F("not ")); DBGPRINT(F("accepted power limit set point ")); - DBGPRINT(String(q->iv->powerLimit[0])); + DBGPRINT(String(q->iv->powerLimit[0]/10)); DBGPRINT(F(" with PowerLimitControl ")); DBGPRINTLN(String(q->iv->powerLimit[1])); q->iv->actPowerLimit = 0xffff; // unknown, readback current value diff --git a/src/hm/hmDefines.h b/src/hm/hmDefines.h index 55259289..a2a2d6b4 100644 --- a/src/hm/hmDefines.h +++ b/src/hm/hmDefines.h @@ -1,6 +1,6 @@ //----------------------------------------------------------------------------- -// 2023 Ahoy, https://github.com/lumpapu/ahoy -// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/ +// 2024 Ahoy, https://github.com/lumpapu/ahoy +// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/4.0/deed //----------------------------------------------------------------------------- #ifndef __HM_DEFINES_H__ diff --git a/src/hm/hmInverter.h b/src/hm/hmInverter.h index 776b9c6f..fa8e1b87 100644 --- a/src/hm/hmInverter.h +++ b/src/hm/hmInverter.h @@ -1,6 +1,6 @@ //----------------------------------------------------------------------------- -// 2023 Ahoy, https://www.mikrocontroller.net/topic/525778 -// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/ +// 2024 Ahoy, https://www.mikrocontroller.net/topic/525778 +// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/4.0/deed //----------------------------------------------------------------------------- #ifndef __HM_INVERTER_H__ @@ -110,7 +110,7 @@ class Inverter { uint8_t id; // unique id uint8_t type; // integer which refers to inverter type uint16_t alarmMesIndex; // Last recorded Alarm Message Index - uint16_t powerLimit[2]; // limit power output + uint16_t powerLimit[2]; // limit power output (multiplied by 10) float actPowerLimit; // actual power limit bool powerLimitAck; // acknowledged power limit (default: false) uint8_t devControlCmd; // carries the requested cmd @@ -152,7 +152,7 @@ class Inverter { Inverter() { ivGen = IV_HM; - powerLimit[0] = 0xffff; // 65535 W Limit -> unlimited + powerLimit[0] = 0xffff; // 6553.5 W Limit -> unlimited powerLimit[1] = AbsolutNonPersistent; // default power limit setting powerLimitAck = false; actPowerLimit = 0xffff; // init feedback from inverter to -1 diff --git a/src/hm/hmRadio.h b/src/hm/hmRadio.h index 0754f83c..6539dd21 100644 --- a/src/hm/hmRadio.h +++ b/src/hm/hmRadio.h @@ -1,5 +1,5 @@ //----------------------------------------------------------------------------- -// 2023 Ahoy, https://github.com/lumpapu/ahoy +// 2024 Ahoy, https://github.com/lumpapu/ahoy // Creative Commons - http://creativecommons.org/licenses/by-nc-sa/4.0/deed //----------------------------------------------------------------------------- @@ -177,10 +177,10 @@ class HmRadio : public Radio { mTxBuf[cnt++] = cmd; // cmd -> 0 on, 1 off, 2 restart, 11 active power, 12 reactive power, 13 power factor mTxBuf[cnt++] = 0x00; if(cmd >= ActivePowerContr && cmd <= PFSet) { // ActivePowerContr, ReactivePowerContr, PFSet - mTxBuf[cnt++] = ((data[0] * 10) >> 8) & 0xff; // power limit - mTxBuf[cnt++] = ((data[0] * 10) ) & 0xff; // power limit - mTxBuf[cnt++] = ((data[1] ) >> 8) & 0xff; // setting for persistens handlings - mTxBuf[cnt++] = ((data[1] ) ) & 0xff; // setting for persistens handling + mTxBuf[cnt++] = (data[0] >> 8) & 0xff; // power limit, multiplied by 10 (because of fraction) + mTxBuf[cnt++] = (data[0] ) & 0xff; // power limit + mTxBuf[cnt++] = (data[1] >> 8) & 0xff; // setting for persistens handlings + mTxBuf[cnt++] = (data[1] ) & 0xff; // setting for persistens handling } } else { //MI 2nd gen. specific uint16_t powerMax = ((iv->powerLimit[1] == RelativNonPersistent) ? 0 : iv->getMaxPower()); diff --git a/src/hms/hmsRadio.h b/src/hms/hmsRadio.h index d2779012..6b502816 100644 --- a/src/hms/hmsRadio.h +++ b/src/hms/hmsRadio.h @@ -1,5 +1,5 @@ //----------------------------------------------------------------------------- -// 2023 Ahoy, https://github.com/lumpapu/ahoy +// 2024 Ahoy, https://github.com/lumpapu/ahoy // Creative Commons - https://creativecommons.org/licenses/by-nc-sa/4.0/deed //----------------------------------------------------------------------------- @@ -50,10 +50,10 @@ class CmtRadio : public Radio { mTxBuf[cnt++] = cmd; // cmd -> 0 on, 1 off, 2 restart, 11 active power, 12 reactive power, 13 power factor mTxBuf[cnt++] = 0x00; if(cmd >= ActivePowerContr && cmd <= PFSet) { // ActivePowerContr, ReactivePowerContr, PFSet - mTxBuf[cnt++] = ((data[0] * 10) >> 8) & 0xff; // power limit - mTxBuf[cnt++] = ((data[0] * 10) ) & 0xff; // power limit - mTxBuf[cnt++] = ((data[1] ) >> 8) & 0xff; // setting for persistens handlings - mTxBuf[cnt++] = ((data[1] ) ) & 0xff; // setting for persistens handling + mTxBuf[cnt++] = (data[0] >> 8) & 0xff; // power limit, multiplied by 10 (because of fraction) + mTxBuf[cnt++] = (data[0] ) & 0xff; // power limit + mTxBuf[cnt++] = (data[1] >> 8) & 0xff; // setting for persistens handlings + mTxBuf[cnt++] = (data[1] ) & 0xff; // setting for persistens handling } sendPacket(iv, cnt, isRetransmit); diff --git a/src/web/html/visualization.html b/src/web/html/visualization.html index 9567504c..d8896e14 100644 --- a/src/web/html/visualization.html +++ b/src/web/html/visualization.html @@ -399,7 +399,7 @@ var html = ml("div", {}, [ ml("div", {class: "row mb-3"}, [ ml("div", {class: "col-12 col-sm-5 my-2"}, "Limit Value"), - ml("div", {class: "col-8 col-sm-5"}, ml("input", {name: "limit", type: "number"}, "")), + ml("div", {class: "col-8 col-sm-5"}, ml("input", {name: "limit", type: "number", step: "0.1", min: 1}, "")), ml("div", {class: "col-4 col-sm-2"}, sel("type", opt, "pct")) ]), ml("div", {class: "row mb-3"}, [ @@ -450,7 +450,7 @@ var obj = new Object(); obj.id = id; obj.cmd = cmd; - obj.val = val; + obj.val = Math.round(val*10); getAjax("/api/ctrl", ctrlCb, "POST", JSON.stringify(obj)); } From 54b9e2f3ea6532df551edbf92c68e78cd40fb2c5 Mon Sep 17 00:00:00 2001 From: lumapu Date: Tue, 2 Jan 2024 00:29:36 +0100 Subject: [PATCH 011/179] 0.8.39 * merge Prometheus metrics fix #1310 * merge MI grid profile request #1306 --- src/hm/Communication.h | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/src/hm/Communication.h b/src/hm/Communication.h index 7802bf32..53106524 100644 --- a/src/hm/Communication.h +++ b/src/hm/Communication.h @@ -351,10 +351,9 @@ class Communication : public CommQueue<> { // small MI or MI 1500 data responses to 0x09, 0x11, 0x36, 0x37, 0x38 and 0x39 //mPayload[iv->id].txId = p->packet[0]; miDataDecode(p, q); - } else if (p->packet[0] == (0x0f + ALL_FRAMES)) + } else if (p->packet[0] == (0x0f + ALL_FRAMES)) { miHwDecode(p, q); - - else if (p->packet[0] == ( 0x10 + ALL_FRAMES)) { + } else if (p->packet[0] == ( 0x10 + ALL_FRAMES)) { // MI response from get Grid Profile information request miGPFDecode(p, q); } @@ -665,13 +664,13 @@ class Communication : public CommQueue<> { q->iv->setValue(2, rec, (uint32_t) (((p->packet[10] << 8) | p->packet[11]))); //FLD_GRID_PROFILE_CODE q->iv->setValue(3, rec, (uint32_t) (((p->packet[12] << 8) | p->packet[13]))); //FLD_GRID_PROFILE_VERSION -/* according to xlsx (different start byte -1!) - Polling Grid-connected Protection Parameter File Command - Receipt - byte[10] ST1 indicates the status of the grid-connected protection file. ST1=1 indicates the default grid-connected protection file, ST=2 indicates that the grid-connected protection file is configured and normal, ST=3 indicates that the grid-connected protection file cannot be recognized, ST=4 indicates that the grid-connected protection file is damaged - byte[11] byte[12] CountryStd variable indicates the national standard code of the grid-connected protection file - byte[13] byte[14] Version indicates the version of the grid-connected protection file - byte[15] byte[16] -*/ + /* according to xlsx (different start byte -1!) + Polling Grid-connected Protection Parameter File Command - Receipt + byte[10] ST1 indicates the status of the grid-connected protection file. ST1=1 indicates the default grid-connected protection file, ST=2 indicates that the grid-connected protection file is configured and normal, ST=3 indicates that the grid-connected protection file cannot be recognized, ST=4 indicates that the grid-connected protection file is damaged + byte[11] byte[12] CountryStd variable indicates the national standard code of the grid-connected protection file + byte[13] byte[14] Version indicates the version of the grid-connected protection file + byte[15] byte[16] + */ /*if(mSerialDebug) { DPRINT(DBG_INFO,F("ST1 ")); DBGPRINTLN(String(p->packet[9])); From 27ad75b7d39fcc38d21f28885783fa4efed1c49d Mon Sep 17 00:00:00 2001 From: lumapu Date: Tue, 2 Jan 2024 00:45:42 +0100 Subject: [PATCH 012/179] 0.8.39 * merge update documentation / readme #1305 --- doc/screenshots/inverterSettings.png | Bin 0 -> 22006 bytes doc/screenshots/settings.png | Bin 0 -> 95179 bytes manual/Getting_Started.md | 3 +-- manual/User_Manual.md | 19 ++++++++++--------- manual/ahoy_config.md | 27 ++++++++++++++------------- src/CHANGES.md | 3 +++ 6 files changed, 28 insertions(+), 24 deletions(-) create mode 100644 doc/screenshots/inverterSettings.png create mode 100644 doc/screenshots/settings.png diff --git a/doc/screenshots/inverterSettings.png b/doc/screenshots/inverterSettings.png new file mode 100644 index 0000000000000000000000000000000000000000..ef9484c166d00ef7df2bcee0562e093902dedac9 GIT binary patch literal 22006 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

|Rb3|~#t-{|@of>C% z1#%}_=zm4Q4SQFN`>qig__WJ1!2k9}?N{v+xz{BO7gd|1mQQ30|RRy;(SUpT_cCQa|?HVnRt4m!t%< z)g}zx;D{E1Y}S{Q7Ei!R#cvDN0XOWWRZts|=pbo6#tPKvmEQIH8i$S1!?vc;=Ow3` zONK)*TEEu$NWI&bjkK0~bmX)!gBq>fV9nHRrW2i@IahzU6@RWcBr@?g3B1;daAaYO z$iZI3`1oy;w?V_{v3$_zK+PdLO^!6TypMcwJiDL!q)vH9q%HkSLmysr@rd5MNjd~% z-LXQ`nJ)eP4vOu0r_FTQ<9_%3oK$!@{8n=O_?WuFWcy6~JLdz&ub!OU8g0?8 z1W1;Hkk0I@xZ`D~WPC$}`tAnpEqF1y2RW6=Z;yN$+E~KJ2}b=EV{wt;m6*L%g9PIN zeF!90H@~)u@}SFFO<7|QonP<|6m?3t?ve=VU&Vi=0_{j%!X4 zTjW^v66@daj@scaD>3z1&I$sxTWEn-*t&>9L>4nPRBX1bN?}ZP;2hN<$GSbmZsp2_ z!PUX|nh~m4RAlKyf|S)$!CeP0C9fL0MSZ4rQm|hHYOxg4A+1E>q!jt%vKkil@oHtf z%_CIP&}a+k9Aj0e;fhaC^=&eM&@Hj0Qnp$;C=zprlqeS6mw9UK@nz^zQ3fnu3rXLA z$Bz~ixeWD2lgZtZYOS{QAIdD}BP%gaQq984zb(pajcrvP$x=C%zQ;_vJv>#Q%XK`n zs`5lBHbjFgycJrDNu@#zy9(nw5t+>AF%Ulfr@zAA=bUrvW*jdPD6wkBS>u|;#3k8t zU{50r0%&%ZGs3Yg8%M4{JszZb7I;pbygB#P4WH($KzPgai*Z~CLuSS_%hXv_#t1Yy zVMUy=>Irw#iQKOE$|Vc=nVL^=lCmhKU6!k&KSRR*Xp>Ge940iK(Q?aMp&2*W?m?86 zcs>AlGg~{y5HPwCv1|#AdH|h=jH_N&M1MIO(W0gWHDq;{_gtL1RH^a7h16+o#5FnL zz`OwWGz{f`cy=Hve_cb@Rd$a`TU4g+7uVGT#Qll&ua}%&>_Yi&l~>B1#fkMpV#CQO zqTD`0=;;Ap%Js9QLI;Mva8s+D2pgd%w2+j%8thx=T(GMS$qjsl{?Uk05 zepDMh)r!jPO=uNb)tjGr!VE6fdnok>X^Y^}*W2OIsAs)^iY6-)207fCux-^aHBs0t zi#s=wbmgm&wAvACdRVfZ;L^uy0uO$?xD~~EkVVDxtnI06E4*zVm9Xg$TBN4`ebF6hK8`oQaYdg_=#B`#P@R&P%#g9hBx}a6 zm~cvuf7Ym9;H}6B0bnET-2j5Iuw21QZD*S& zQX=_*ZtjdZwk{~{_r&gguSFC&_3K!u_M1p~0iGQ>6u zYQwv>zAnCP4*E59?E5Qy5j(*-1De&j!4EK#o#}{|e$%Rk;fK$BtPPg=HZ$XL@uG}? zP2X(N=aGF70V=J8)H7aI5)>q8qx=|c{=4!OTH(njs&hc@sy9hK94DpwL!Pd@UG=C# nl#|+Q9t5f6-sUU5AaSacDnS7}SFV%zQ-($QE;s)Kt^qrq literal 0 HcmV?d00001 diff --git a/doc/screenshots/settings.png b/doc/screenshots/settings.png new file mode 100644 index 0000000000000000000000000000000000000000..179a5d077e428858f2fb1bd6f542acf8ea461ce4 GIT binary patch literal 95179 zcmb@uWmKHO)-?zOcWIz;YuqKcd*f~i4ha(69fG@Sa3{DE+${<2E+J^J5L~8t-y3r0 zoB1_s{;*j6R9Dq=&Z(-i_u19qDoQeF$b`sHP*7-KSxGf0s8?VpC>R4oc*rNlt4i}w zP~=cxNihv~gQHah9e~U>{OJ|%F?vi)z6rjU_E86IZ9gGS^lB;nWmv~m75f}lv`|Xv zHVRK;llkVu%%}$Oar91KIF-}hR+VRnm-DURBe0o;A@r)%-iz+z} zqQwR9j{$wffdLE8Oo~e(6dau>G_s063}Cnjh9ji#?ULh7?Gh^Zrgqk29FY5;$K6LT zsNi?ByKw6|{uKMs`|gx4U&|H!2?tEzup6Lq((|XFFL~oXU-HuFjX2~ZLoO%MFW`_u z!Kt`*wEr3Exm%+}NS7(A!WFCKkbk^M|4sw)>>GJ^oEf~C0p%xHU_u>I!oc(N{xg;P zL?{XDoY)nr?zkkcrHI!>FJpayOpP9&$Dp&EUqY?Z+HsBTWsbv0&>@AHD!Z@O6(H09 zzT%Ge@+h~v3)@KcR{O0WuMAurJ7}Q4f8%U>Xky=)&r`mg_g_1Ib0l5?fk29px}~7T z8`^*m$l%S*&C|46B`JPih8j`TgH|Fz`*PIcIfLsCYD6Lh9KECsU&NI)kQ41-4_ z%(dU_{j&E{SM$XZqR>u=vQN2>uAT|l4gO⋙8kh@~5>(X;d4I@O7L1M{U>l`|s2x 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 should be set to `1` = `ON` and `0` = `OFF` } ``` +**beginning from verson `0.8.39` the wattage and percentage has one decimal place!** -### Power Limit relative persistent [%] +### Power Limit (active power control) relative persistent [%] ```json { @@ -205,10 +206,10 @@ The `` should be set to `1` = `ON` and `0` = `OFF` "val": } ``` -The `VALUE` represents a percent number in a range of `[2 .. 100]` +The `VALUE` represents a percent number in a range of `[2.0 .. 100.0]` -### Power Limit absolute persistent [Watts] +### Power Limit (active power control) absolute persistent [Watts] ```json { @@ -217,10 +218,10 @@ The `VALUE` represents a percent number in a range of `[2 .. 100]` "val": } ``` -The `VALUE` represents watts in a range of `[0 .. 65535]` +The `VALUE` represents watts in a range of `[1.0 .. 6553.5]` -### Power Limit relative non persistent [%] +### Power Limit (active power control) relative non persistent [%] ```json { @@ -229,10 +230,10 @@ The `VALUE` represents watts in a range of `[0 .. 65535]` "val": } ``` -The `VALUE` represents a percent number in a range of `[2 .. 100]` +The `VALUE` represents a percent number in a range of `[2.0 .. 100.0]` -### Power Limit absolute non persistent [Watts] +### Power Limit (active power control) absolute non persistent [Watts] ```json { @@ -241,7 +242,7 @@ The `VALUE` represents a percent number in a range of `[2 .. 100]` "val": } ``` -The `VALUE` represents watts in a range of `[0 .. 65535]` +The `VALUE` represents watts in a range of `[1.0 .. 6553.5]` @@ -328,7 +329,7 @@ Send Power Limit: - If the DC voltage is missing for a few seconds, the microcontroller in the inverter goes off and forgets everything that was temporary/non-persistent in the RAM: YieldDay, error memory, non-persistent limit. ### Update your AHOY-DTU Firmware To update your AHOY-DTU, you have to download the latest firmware package. -Here are the [latest stable releases](https://github.com/lumapu/ahoy/releases/) and [latest development builds](https://nightly.link/lumapu/ahoy/workflows/compile_development/development03/ahoydtu_dev.zip) available for download. +Here are the [latest stable releases](https://github.com/lumapu/ahoy/releases/) and [latest development builds](https://fw.ahoydtu.de/dev) available for download. As soon as you have downloaded the firmware package, unzip it. On the WebUI, navigate to Update and press on select firmware file. From the unzipped files, select the right .bin file for your hardware and needs. - If you use an ESP8266, select the file ending with esp8266.bin diff --git a/manual/ahoy_config.md b/manual/ahoy_config.md index 73427b6f..1971848f 100644 --- a/manual/ahoy_config.md +++ b/manual/ahoy_config.md @@ -1,19 +1,18 @@ - +# Ahoy configuration -## Ahoy configuration +## Prerequists +You have build your own hardware (or purchased one). The firmware is already loaded on the ESP and the WebUI is accessible from your browser. - So far we have built our own DTU, written a program on it and put it into operation. +## Start But how do I get my data from the inverter? -To do this, we need to configure the DTU. - The following steps are required: 1. Set the pinning to communicate with the radio module. 2. Check if Ahoy has a current time -3. Set inverter data +3. Configure the inverter data (e.g. serialnumber) ### 1.) Set the pinning -Once you are in the web interface, you will find the "System Config" sub-item in the Setup area (left). +Once you are in the web interface, you will find the "System Config" sub-item in the Setup area. This is where you tell the ESP how you connected the radio module. Note the schematics you saw earlier. - If you haven't noticed them yet, here's another table of connections. @@ -38,7 +37,7 @@ Note the schematics you saw earlier. - If you haven't noticed them yet, here's a | FCSB| GPIO21 | GPIO3| GPIO8 -### 2.) Set current time (normal skip this step) +### 2.) Set current time (standard: skip this step) Ahoy needs a current date and time to talk to the inverter. It works without, but it is recommended to include a time. This allows you to analyze information from the inverter in more detail. Normally, a date/time should be automatically retrieved from the NTP server. However, it may happen that the firewall of some routers does not allow this. @@ -51,20 +50,22 @@ Now it's time to place the inverter. This is necessary because it is not the inv Each inverter has its own S.Nr. This also serves as an identity for communication between the DTU and the inverter. -The S.Nr is a 12-digit number. You can look it up [here (german)](https://github.com/lumapu/ahoy/wiki/Hardware#wie-ist-die-serien-nummer-der-inverter-aufgebaut) for more information. +The S.Nr is a 12-digit number. Check [here (german)](https://github.com/lumapu/ahoy/wiki/Hardware#wie-ist-die-serien-nummer-der-inverter-aufgebaut) for more information. + #### set pv-modules (not necessary) Click on "Add Inverter" and enter the S.No. and a name. Please keep the name short! -![grafik](https://github.com/DanielR92/ahoy/assets/25644396/b52a2d5d-513c-4895-848a-01ce129f93c1) +![grafik](https://github.com/lumapu/ahoy/doc/screenshots/settings.png) -![grafik](https://github.com/DanielR92/ahoy/assets/25644396/b508824f-08a7-4b9c-bc41-29dfee02dced) +![grafik](https://github.com/lumapu/ahoy/doc/screenshots/inverterSettings.png) In the upper tab "Inputs" you can enter the data of the solar modules. These are only used directly in Ahoy for calculation and have no influence on the inverter. #### set radio parameter (not necessary, only for EU) In the next tab "Radio" you can adjust the power and other parameters if necessary. However, these should be left as default (EU only). -#### advanced options (not necessary) +#### advanced options (not necessary to be changed) In the "Advanced" section, you can customize more settings. Save and reboot. -# Done - Now check the live site + +## ✅ Done - Now check the live site diff --git a/src/CHANGES.md b/src/CHANGES.md index 67e94621..a29901ac 100644 --- a/src/CHANGES.md +++ b/src/CHANGES.md @@ -4,6 +4,9 @@ * fix MqTT dis_night_comm in the morning #1309 #1286 * seperated offset for sunrise and sunset #1308 * **BREAKING CHANGE**: powerlimit (active power control) now has one decimal place (MqTT / API) #1199 +* merge Prometheus metrics fix #1310 +* merge MI grid profile request #1306 +* merge update documentation / readme #1305 ## 0.8.38 - 2023-12-31 * fix Grid-Profile JSON #1304 From 40097aba1807241e910eabb4ee4fca0552a909b8 Mon Sep 17 00:00:00 2001 From: lumapu Date: Tue, 2 Jan 2024 01:03:40 +0100 Subject: [PATCH 013/179] 0.8.39 * add `getLossRate` to radio statistics and to MqTT #1199 --- src/CHANGES.md | 1 + src/defines.h | 4 ++++ src/hm/Communication.h | 2 +- src/hm/hmInverter.h | 35 +++++++++++++++-------------------- src/hm/hmRadio.h | 2 +- src/hms/hmsRadio.h | 2 +- src/publisher/pubMqttIvData.h | 10 +++++++--- 7 files changed, 30 insertions(+), 26 deletions(-) diff --git a/src/CHANGES.md b/src/CHANGES.md index a29901ac..c83130c8 100644 --- a/src/CHANGES.md +++ b/src/CHANGES.md @@ -7,6 +7,7 @@ * merge Prometheus metrics fix #1310 * merge MI grid profile request #1306 * merge update documentation / readme #1305 +* add `getLossRate` to radio statistics and to MqTT #1199 ## 0.8.38 - 2023-12-31 * fix Grid-Profile JSON #1304 diff --git a/src/defines.h b/src/defines.h index ad321921..d5aa04c2 100644 --- a/src/defines.h +++ b/src/defines.h @@ -103,6 +103,10 @@ typedef struct { uint32_t frmCnt; uint32_t txCnt; uint32_t retransmits; + uint16_t ivRxCnt; // last iv rx frames (from GetLossRate) + uint16_t ivTxCnt; // last iv tx frames (from GetLossRate) + uint16_t dtuRxCnt; // current DTU rx frames (since last GetLossRate) + uint16_t dtuTxCnt; // current DTU tx frames (since last GetLossRate) } statistics_t; #endif /*__DEFINES_H__*/ diff --git a/src/hm/Communication.h b/src/hm/Communication.h index 53106524..a448884e 100644 --- a/src/hm/Communication.h +++ b/src/hm/Communication.h @@ -151,7 +151,7 @@ class Communication : public CommQueue<> { if(validateIvSerial(&p->packet[1], q->iv)) { q->iv->radioStatistics.frmCnt++; - q->iv->mDtuRxCnt++; + q->iv->radioStatistics.dtuRxCnt++; if (p->packet[0] == (TX_REQ_INFO + ALL_FRAMES)) { // response from get information command if(parseFrame(p)) diff --git a/src/hm/hmInverter.h b/src/hm/hmInverter.h index 91b8480e..37a469a7 100644 --- a/src/hm/hmInverter.h +++ b/src/hm/hmInverter.h @@ -141,12 +141,6 @@ class Inverter { uint8_t curCmtFreq; // current used CMT frequency, used to check if freq. was changed during runtime bool commEnabled; // 'pause night communication' sets this field to false - uint16_t mIvRxCnt; // last iv rx frames (from GetLossRate) - uint16_t mIvTxCnt; // last iv tx frames (from GetLossRate) - uint16_t mDtuRxCnt; // cur dtu rx frames (since last GetLossRate) - uint16_t mDtuTxCnt; // cur dtu tx frames (since last getLoassRate) - uint8_t mGetLossInterval; // request iv every AHOY_GET_LOSS_INTERVAL RealTimeRunData_Debu - static uint32_t *timestamp; // system timestamp static cfgInst_t *generalConfig; // general inverter configuration from setup @@ -171,10 +165,6 @@ class Inverter { mIsSingleframeReq = false; radio = NULL; commEnabled = true; - mIvRxCnt = 0; - mIvTxCnt = 0; - mDtuRxCnt = 0; - mDtuTxCnt = 0; memset(&radioStatistics, 0, sizeof(statistics_t)); memset(heuristics.txRfQuality, -6, 5); @@ -605,21 +595,25 @@ class Inverter { uint16_t rxCnt = (pyld[0] << 8) + pyld[1]; uint16_t txCnt = (pyld[2] << 8) + pyld[3]; - if (mIvRxCnt || mIvTxCnt) { // there was successful GetLossRate in the past + if (radioStatistics.ivRxCnt || radioStatistics.ivTxCnt) { // there was successful GetLossRate in the past DPRINT_IVID(DBG_INFO, id); - DBGPRINTLN("Inv loss: " + - String (mDtuTxCnt - (rxCnt - mIvRxCnt)) + " of " + - String (mDtuTxCnt) + ", DTU loss: " + - String (txCnt - mIvTxCnt - mDtuRxCnt) + " of " + - String (txCnt - mIvTxCnt)); + DBGPRINT(F("Inv loss: ")); + DBGPRINT(String (radioStatistics.dtuTxCnt - (rxCnt - radioStatistics.ivRxCnt))); + DBGPRINT(F(" of ")); + DBGPRINT(String (radioStatistics.dtuTxCnt)); + DBGPRINT(F(", DTU loss: ")); + DBGPRINT(String (txCnt - radioStatistics.ivTxCnt - radioStatistics.dtuRxCnt)); + DBGPRINT(F(" of ")); + DBGPRINTLN(String (txCnt - radioStatistics.ivTxCnt)); } - mIvRxCnt = rxCnt; - mIvTxCnt = txCnt; - mDtuRxCnt = 0; // start new interval - mDtuTxCnt = 0; // start new interval + radioStatistics.ivRxCnt = rxCnt; + radioStatistics.ivTxCnt = txCnt; + radioStatistics.dtuRxCnt = 0; // start new interval + radioStatistics.dtuTxCnt = 0; // start new interval return true; } + return false; } @@ -811,6 +805,7 @@ class Inverter { bool mDevControlRequest; // true if change needed uint8_t mGridLen = 0; uint8_t mGridProfile[MAX_GRID_LENGTH]; + uint8_t mGetLossInterval; // request iv every AHOY_GET_LOSS_INTERVAL RealTimeRunData_Debug }; template diff --git a/src/hm/hmRadio.h b/src/hm/hmRadio.h index 6539dd21..c7b9581c 100644 --- a/src/hm/hmRadio.h +++ b/src/hm/hmRadio.h @@ -339,7 +339,7 @@ class HmRadio : public Radio { mMillis = millis(); mLastIv = iv; - iv->mDtuTxCnt++; + iv->radioStatistics.dtuTxCnt++; } uint64_t getIvId(Inverter<> *iv) { diff --git a/src/hms/hmsRadio.h b/src/hms/hmsRadio.h index 6b502816..3b3893ac 100644 --- a/src/hms/hmsRadio.h +++ b/src/hms/hmsRadio.h @@ -112,7 +112,7 @@ class CmtRadio : public Radio { if(CMT_ERR_RX_IN_FIFO == status) mIrqRcvd = true; } - iv->mDtuTxCnt++; + iv->radioStatistics.dtuTxCnt++; } uint64_t getIvId(Inverter<> *iv) { diff --git a/src/publisher/pubMqttIvData.h b/src/publisher/pubMqttIvData.h index 9a364ec9..3a38a5ed 100644 --- a/src/publisher/pubMqttIvData.h +++ b/src/publisher/pubMqttIvData.h @@ -195,12 +195,16 @@ class PubMqttIvData { inline void sendRadioStat(uint8_t start) { snprintf(mSubTopic, 32 + MAX_NAME_LENGTH, "%s/radio_stat", mIv->config->name); - snprintf(mVal, 100, "{\"tx\":%d,\"success\":%d,\"fail\":%d,\"no_answer\":%d,\"retransmits\":%d}", + snprintf(mVal, 140, "{\"tx\":%d,\"success\":%d,\"fail\":%d,\"no_answer\":%d,\"retransmits\":%d,\"lossIvRx\":%d,\"lossIvTx\":%d,\"lossDtuRx\":%d,\"lossDtuTx\":%d}", mIv->radioStatistics.txCnt, mIv->radioStatistics.rxSuccess, mIv->radioStatistics.rxFail, mIv->radioStatistics.rxFailNoAnser, - mIv->radioStatistics.retransmits); + mIv->radioStatistics.retransmits, + mIv->radioStatistics.ivRxCnt, + mIv->radioStatistics.ivTxCnt, + mIv->radioStatistics.dtuRxCnt, + mIv->radioStatistics.dtuTxCnt); mPublish(mSubTopic, mVal, false, QOS_0); } @@ -263,7 +267,7 @@ class PubMqttIvData { bool mRTRDataHasBeenSent; char mSubTopic[32 + MAX_NAME_LENGTH + 1]; - char mVal[100]; + char mVal[140]; bool mZeroValues; // makes sure that yield day is sent even if no inverter is online std::queue *mSendList; From 3dd4997094ef4698bc60ff31d0dce6c97c39f0b4 Mon Sep 17 00:00:00 2001 From: lumapu Date: Tue, 2 Jan 2024 14:43:33 +0100 Subject: [PATCH 014/179] 0.8.40 * fix display of sunrise and sunset in `/system` #1308 * fix MqTT set power limit #1313 --- src/CHANGES.md | 6 +++++- src/defines.h | 2 +- src/hm/Communication.h | 2 +- src/hm/hmInverter.h | 2 +- src/publisher/pubMqtt.h | 12 +++++++----- src/web/html/system.html | 4 ++-- 6 files changed, 17 insertions(+), 11 deletions(-) diff --git a/src/CHANGES.md b/src/CHANGES.md index c83130c8..bbede74c 100644 --- a/src/CHANGES.md +++ b/src/CHANGES.md @@ -1,9 +1,13 @@ # Development Changes +## 0.8.40 - 2024-01-02 +* fix display of sunrise and sunset in `/system` #1308 +* fix MqTT set power limit #1313 + ## 0.8.39 - 2024-01-01 * fix MqTT dis_night_comm in the morning #1309 #1286 * seperated offset for sunrise and sunset #1308 -* **BREAKING CHANGE**: powerlimit (active power control) now has one decimal place (MqTT / API) #1199 +* powerlimit (active power control) now has one decimal place (MqTT / API) #1199 * merge Prometheus metrics fix #1310 * merge MI grid profile request #1306 * merge update documentation / readme #1305 diff --git a/src/defines.h b/src/defines.h index d5aa04c2..26ddf237 100644 --- a/src/defines.h +++ b/src/defines.h @@ -13,7 +13,7 @@ //------------------------------------- #define VERSION_MAJOR 0 #define VERSION_MINOR 8 -#define VERSION_PATCH 39 +#define VERSION_PATCH 40 //------------------------------------- typedef struct { diff --git a/src/hm/Communication.h b/src/hm/Communication.h index a448884e..d44e860f 100644 --- a/src/hm/Communication.h +++ b/src/hm/Communication.h @@ -397,7 +397,7 @@ class Communication : public CommQueue<> { DBGPRINT(F("has ")); if(!accepted) DBGPRINT(F("not ")); DBGPRINT(F("accepted power limit set point ")); - DBGPRINT(String(q->iv->powerLimit[0]/10)); + DBGPRINT(String((float)q->iv->powerLimit[0]/10.0)); DBGPRINT(F(" with PowerLimitControl ")); DBGPRINTLN(String(q->iv->powerLimit[1])); q->iv->actPowerLimit = 0xffff; // unknown, readback current value diff --git a/src/hm/hmInverter.h b/src/hm/hmInverter.h index 37a469a7..25f575e5 100644 --- a/src/hm/hmInverter.h +++ b/src/hm/hmInverter.h @@ -805,7 +805,7 @@ class Inverter { bool mDevControlRequest; // true if change needed uint8_t mGridLen = 0; uint8_t mGridProfile[MAX_GRID_LENGTH]; - uint8_t mGetLossInterval; // request iv every AHOY_GET_LOSS_INTERVAL RealTimeRunData_Debug + uint8_t mGetLossInterval; // request iv every AHOY_GET_LOSS_INTERVAL RealTimeRunData_Debug }; template diff --git a/src/publisher/pubMqtt.h b/src/publisher/pubMqtt.h index 9834f29a..76ec452f 100644 --- a/src/publisher/pubMqtt.h +++ b/src/publisher/pubMqtt.h @@ -310,15 +310,18 @@ class PubMqtt { char *pyld = new char[len + 1]; strncpy(pyld, (const char*)payload, len); pyld[len] = '\0'; - root[F("val")] = atoi(pyld); + if(NULL == strstr(topic, "limit")) + root[F("val")] = atoi(pyld); + else + root[F("val")] = (int)(atof(pyld) * 10.0f); + if(pyld[len-1] == 'W') limitAbs = true; delete[] pyld; } const char *p = topic + strlen(mCfgMqtt->topic); - uint8_t pos = 0; - uint8_t elm = 0; + uint8_t pos = 0, elm = 0; char tmp[30]; while(1) { @@ -333,8 +336,7 @@ class PubMqtt { root[F("cmd")] = F("limit_nonpersistent_absolute"); else root[F("cmd")] = F("limit_nonpersistent_relative"); - } - else + } else root[F("cmd")] = String(tmp); break; case 3: root[F("id")] = atoi(tmp); break; diff --git a/src/web/html/system.html b/src/web/html/system.html index eb25d5fa..0415864b 100644 --- a/src/web/html/system.html +++ b/src/web/html/system.html @@ -108,8 +108,8 @@ ml("tbody", {}, [ tr("Sunrise", new Date(obj.ts_sunrise * 1000).toLocaleString('de-DE')), tr("Sunset", new Date(obj.ts_sunset * 1000).toLocaleString('de-DE')), - tr("Communication start", new Date((obj.ts_sunrise - obj.ts_offset) * 1000).toLocaleString('de-DE')), - tr("Communication stop", new Date((obj.ts_sunset + obj.ts_offset) * 1000).toLocaleString('de-DE')), + tr("Communication start", new Date((obj.ts_sunrise - obj.ts_offsSr) * 1000).toLocaleString('de-DE')), + tr("Communication stop", new Date((obj.ts_sunset + obj.ts_offsSs) * 1000).toLocaleString('de-DE')), tr("Night behaviour", badge(obj.disNightComm, ((obj.disNightComm) ? "not" : "") + " communicating", "warning")) ]) ) From 938c42925f8022bfc13441b6f9b4cf98c2251356 Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 2 Jan 2024 14:58:19 +0100 Subject: [PATCH 015/179] PROMETHEUS_EP: add `LossRate` radio statistics to metric bugfix: use specific metric types for each inverter metric --- doc/prometheus_ep_description.md | 6 ++- src/web/web.h | 93 +++++++++++++++++++------------- 2 files changed, 60 insertions(+), 39 deletions(-) diff --git a/doc/prometheus_ep_description.md b/doc/prometheus_ep_description.md index 651cd937..6b4ce56c 100644 --- a/doc/prometheus_ep_description.md +++ b/doc/prometheus_ep_description.md @@ -25,7 +25,7 @@ Prometheus metrics provided at `/metrics`. | `ahoy_solar_inverter_enabled` | Gauge | Is the inverter enabled? | inverter | | `ahoy_solar_inverter_is_available` | Gauge | is the inverter available? | inverter | | `ahoy_solar_inverter_is_producing` | Gauge | Is the inverter producing? | inverter | -| `ahoy_solar_inverter_power_limit_read` | Gauge | Power Limit read from inverter | inverter | +| `ahoy_solar_inverter_power_limit_read` | Gauge | Power Limit read from inverter. Defaults to 65535 | inverter | | `ahoy_solar_inverter_power_limit_ack` | Gauge | Power Limit acknowledged by inverter | inverter | | `ahoy_solar_inverter_max_power` | Gauge | Max Power of inverter | inverter | | `ahoy_solar_inverter_radio_rx_success` | Counter | NRF24 statistic of inverter | inverter | @@ -34,6 +34,10 @@ Prometheus metrics provided at `/metrics`. | `ahoy_solar_inverter_radio_frame_cnt` | Counter | NRF24 statistic of inverter | inverter | | `ahoy_solar_inverter_radio_tx_cnt` | Counter | NRF24 statistic of inverter | inverter | | `ahoy_solar_inverter_radio_retransmits` | Counter | NRF24 statistic of inverter | inverter | +| `ahoy_solar_inverter_radio_iv_rx_cnt` | Counter | NRF24 statistic of inverter | inverter | +| `ahoy_solar_inverter_radio_iv_tx_cnt` | Counter | NRF24 statistic of inverter | inverter | +| `ahoy_solar_inverter_radio_dtu_rx_cnt` | Counter | NRF24 statistic of inverter | inverter | +| `ahoy_solar_inverter_radio_dtu_rx_cnt` | Counter | NRF24 statistic of inverter | inverter | | `ahoy_solar_U_AC_volt` | Gauge | AC voltage of inverter [V] | inverter | | `ahoy_solar_I_AC_ampere` | Gauge | AC current of inverter [A] | inverter | | `ahoy_solar_P_AC_watt` | Gauge | AC power of inverter [W] | inverter | diff --git a/src/web/web.h b/src/web/web.h index 3ed0353d..b76dbf70 100644 --- a/src/web/web.h +++ b/src/web/web.h @@ -628,13 +628,16 @@ class Web { // NOTE: Grouping for fields with channels and totals is currently not working // TODO: Handle grouping and sorting for independant from channel number // NOTE: Check packetsize for MAX_NUM_INVERTERS. Successfully Tested with 4 Inverters (each with 4 channels) - const char * metricPrefix = "ahoy_solar_"; + const char * metricConstPrefix = "ahoy_solar_"; + const char * metricConstInverterFormat = " {inverter=\"%s\"} %d\n"; typedef enum { - metricsStateInverterInfo=0, metricsStateInverterEnabled=1, metricsStateInverterAvailable=2, metricsStateInverterProducing=3, - metricsStateInverterPowerLimitRead=4, metricsStateInverterPowerLimitAck=5, metricsStateInverterMaxPower=6, - metricsStateInverterRxSuccess=7, metricsStateInverterRxFail=8, metricsStateInverterRxFailAnswer=9, - metricsStateInverterFrameCnt=10, metricsStateInverterTxCnt=11, metricsStateInverterRetransmits=12, - metricStateRealtimeFieldId=metricsStateInverterRetransmits+1, // ensure that this state follows the last per_inverter state + metricsStateInverterInfo=0, metricsStateInverterEnabled=1, metricsStateInverterAvailable=2, + metricsStateInverterProducing=3, metricsStateInverterPowerLimitRead=4, metricsStateInverterPowerLimitAck=5, + metricsStateInverterMaxPower=6, metricsStateInverterRxSuccess=7, metricsStateInverterRxFail=8, + metricsStateInverterRxFailAnswer=9, metricsStateInverterFrameCnt=10, metricsStateInverterTxCnt=11, + metricsStateInverterRetransmits=12, metricsStateInverterIvRxCnt=13, metricsStateInverterIvTxCnt=14, + metricsStateInverterDtuRxCnt=15, metricsStateInverterDtuTxCnt=16, + metricStateRealtimeFieldId=metricsStateInverterDtuTxCnt+1, // ensure that this state follows the last per_inverter state metricStateRealtimeInverterId, metricsStateAlarmData, metricsStateStart, @@ -642,24 +645,29 @@ class Web { } MetricStep_t; MetricStep_t metricsStep; typedef struct { + const char *topic; const char *type; const char *format; const std::function *iv)> valueFunc; } InverterMetric_t; - InverterMetric_t inverterMetrics[13] = { - { "info", "info{name=\"%s\",serial=\"%12llx\"} 1\n", [](Inverter<> *iv)-> uint64_t {return iv->config->serial.u64;} }, - { "is_enabled", "is_enabled {inverter=\"%s\"} %d\n", [](Inverter<> *iv)-> uint64_t {return iv->config->enabled;} }, - { "is_available", "is_available {inverter=\"%s\"} %d\n", [](Inverter<> *iv)-> uint64_t {return iv->isAvailable();} }, - { "is_producing", "is_producing {inverter=\"%s\"} %d\n", [](Inverter<> *iv)-> uint64_t {return iv->isProducing();} }, - { "power_limit_read", "power_limit_read {inverter=\"%s\"} %d\n", [](Inverter<> *iv)-> uint64_t {return (int64_t)ah::round3(iv->actPowerLimit);} }, - { "power_limit_ack", "power_limit_ack {inverter=\"%s\"} %d\n", [](Inverter<> *iv)-> uint64_t {return (iv->powerLimitAck)?1:0;} }, - { "max_power", "max_power {inverter=\"%s\"} %d\n", [](Inverter<> *iv)-> uint64_t {return iv->getMaxPower();} }, - { "radio_rx_success", "radio_rx_success {inverter=\"%s\"} %d\n", [](Inverter<> *iv)-> uint64_t {return iv->radioStatistics.rxSuccess;} }, - { "radio_rx_fail", "radio_rx_fail {inverter=\"%s\"} %d\n", [](Inverter<> *iv)-> uint64_t {return iv->radioStatistics.rxFail;} }, - { "radio_rx_fail_answer", "radio_rx_fail_answer {inverter=\"%s\"} %d\n", [](Inverter<> *iv)-> uint64_t {return iv->radioStatistics.rxFailNoAnser;} }, - { "radio_frame_cnt", "radio_frame_cnt {inverter=\"%s\"} %d\n", [](Inverter<> *iv)-> uint64_t {return iv->radioStatistics.frmCnt;} }, - { "radio_tx_cnt", "radio_tx_cnt {inverter=\"%s\"} %d\n", [](Inverter<> *iv)-> uint64_t {return iv->radioStatistics.txCnt;} }, - { "radio_retransmits", "radio_retransmits {inverter=\"%s\"} %d\n", [](Inverter<> *iv)-> uint64_t {return iv->radioStatistics.retransmits;} } + InverterMetric_t inverterMetrics[17] = { + { "info", "gauge", " {name=\"%s\",serial=\"%12llx\"} 1\n", [](Inverter<> *iv)-> uint64_t {return iv->config->serial.u64;} }, + { "is_enabled", "gauge", metricConstInverterFormat, [](Inverter<> *iv)-> uint64_t {return iv->config->enabled;} }, + { "is_available", "gauge", metricConstInverterFormat, [](Inverter<> *iv)-> uint64_t {return iv->isAvailable();} }, + { "is_producing", "gauge", metricConstInverterFormat, [](Inverter<> *iv)-> uint64_t {return iv->isProducing();} }, + { "power_limit_read", "gauge", metricConstInverterFormat, [](Inverter<> *iv)-> uint64_t {return (int64_t)ah::round3(iv->actPowerLimit);} }, + { "power_limit_ack", "gauge", metricConstInverterFormat, [](Inverter<> *iv)-> uint64_t {return (iv->powerLimitAck)?1:0;} }, + { "max_power", "gauge", metricConstInverterFormat, [](Inverter<> *iv)-> uint64_t {return iv->getMaxPower();} }, + { "radio_rx_success", "counter" ,metricConstInverterFormat, [](Inverter<> *iv)-> uint64_t {return iv->radioStatistics.rxSuccess;} }, + { "radio_rx_fail", "counter" ,metricConstInverterFormat, [](Inverter<> *iv)-> uint64_t {return iv->radioStatistics.rxFail;} }, + { "radio_rx_fail_answer", "counter" ,metricConstInverterFormat, [](Inverter<> *iv)-> uint64_t {return iv->radioStatistics.rxFailNoAnser;} }, + { "radio_frame_cnt", "counter" ,metricConstInverterFormat, [](Inverter<> *iv)-> uint64_t {return iv->radioStatistics.frmCnt;} }, + { "radio_tx_cnt", "counter" ,metricConstInverterFormat, [](Inverter<> *iv)-> uint64_t {return iv->radioStatistics.txCnt;} }, + { "radio_retransmits", "counter" ,metricConstInverterFormat, [](Inverter<> *iv)-> uint64_t {return iv->radioStatistics.retransmits;} }, + { "radio_iv_rx_cnt", "counter" ,metricConstInverterFormat, [](Inverter<> *iv)-> uint64_t {return iv->radioStatistics.ivRxCnt;} }, + { "radio_iv_tx_cnt", "counter" ,metricConstInverterFormat, [](Inverter<> *iv)-> uint64_t {return iv->radioStatistics.ivTxCnt;} }, + { "radio_dtu_rx_cnt", "counter" ,metricConstInverterFormat, [](Inverter<> *iv)-> uint64_t {return iv->radioStatistics.dtuRxCnt;} }, + { "radio_dtu_tx_cnt", "counter" ,metricConstInverterFormat, [](Inverter<> *iv)-> uint64_t {return iv->radioStatistics.dtuTxCnt;} } }; int metricsInverterId; uint8_t metricsFieldId; @@ -685,21 +693,21 @@ class Web { // So several "Info:" blocks are used to keep the transmission going switch (metricsStep) { case metricsStateStart: // System Info : fit to one packet - snprintf(type,sizeof(type),"# TYPE %sinfo gauge\n",metricPrefix); - snprintf(topic,sizeof(topic),"%sinfo{version=\"%s\",image=\"\",devicename=\"%s\"} 1\n",metricPrefix, + snprintf(type,sizeof(type),"# TYPE %sinfo gauge\n",metricConstPrefix); + snprintf(topic,sizeof(topic),"%sinfo{version=\"%s\",image=\"\",devicename=\"%s\"} 1\n",metricConstPrefix, mApp->getVersion(), mConfig->sys.deviceName); metrics = String(type) + String(topic); - snprintf(type,sizeof(type),"# TYPE %sfreeheap gauge\n",metricPrefix); - snprintf(topic,sizeof(topic),"%sfreeheap{devicename=\"%s\"} %u\n",metricPrefix,mConfig->sys.deviceName,ESP.getFreeHeap()); + snprintf(type,sizeof(type),"# TYPE %sfreeheap gauge\n",metricConstPrefix); + snprintf(topic,sizeof(topic),"%sfreeheap{devicename=\"%s\"} %u\n",metricConstPrefix,mConfig->sys.deviceName,ESP.getFreeHeap()); metrics += String(type) + String(topic); - snprintf(type,sizeof(type),"# TYPE %suptime counter\n",metricPrefix); - snprintf(topic,sizeof(topic),"%suptime{devicename=\"%s\"} %u\n",metricPrefix, mConfig->sys.deviceName, mApp->getUptime()); + snprintf(type,sizeof(type),"# TYPE %suptime counter\n",metricConstPrefix); + snprintf(topic,sizeof(topic),"%suptime{devicename=\"%s\"} %u\n",metricConstPrefix, mConfig->sys.deviceName, mApp->getUptime()); metrics += String(type) + String(topic); - snprintf(type,sizeof(type),"# TYPE %swifi_rssi_db gauge\n",metricPrefix); - snprintf(topic,sizeof(topic),"%swifi_rssi_db{devicename=\"%s\"} %d\n",metricPrefix, mConfig->sys.deviceName, WiFi.RSSI()); + snprintf(type,sizeof(type),"# TYPE %swifi_rssi_db gauge\n",metricConstPrefix); + snprintf(topic,sizeof(topic),"%swifi_rssi_db{devicename=\"%s\"} %d\n",metricConstPrefix, mConfig->sys.deviceName, WiFi.RSSI()); metrics += String(type) + String(topic); len = snprintf((char *)buffer,maxLen,"%s",metrics.c_str()); @@ -721,8 +729,15 @@ class Web { case metricsStateInverterFrameCnt: case metricsStateInverterTxCnt: case metricsStateInverterRetransmits: - metrics = "# TYPE ahoy_solar_inverter_" + String(inverterMetrics[metricsStep].type) + " gauge\n"; - metrics += inverterMetric(topic, sizeof(topic),(String("ahoy_solar_inverter_") + inverterMetrics[metricsStep].format).c_str(), inverterMetrics[metricsStep].valueFunc); + case metricsStateInverterIvRxCnt: + case metricsStateInverterIvTxCnt: + case metricsStateInverterDtuRxCnt: + case metricsStateInverterDtuTxCnt: + metrics = "# TYPE ahoy_solar_inverter_" + String(inverterMetrics[metricsStep].topic) + " " + String(inverterMetrics[metricsStep].type) + "\n"; + metrics += inverterMetric(topic, sizeof(topic), + (String("ahoy_solar_inverter_") + inverterMetrics[metricsStep].topic + + inverterMetrics[metricsStep].format).c_str(), + inverterMetrics[metricsStep].valueFunc); len = snprintf((char *)buffer,maxLen,"%s",metrics.c_str()); // ugly hack to increment the enum metricsStep = static_cast( static_cast(metricsStep) + 1); @@ -763,7 +778,7 @@ class Web { std::tie(promUnit, promType) = convertToPromUnits(iv->getUnit(metricsChannelId, rec)); // Declare metric only once if (channel != 0 && !metricDeclared) { - snprintf(type, sizeof(type), "# TYPE %s%s%s %s\n",metricPrefix, iv->getFieldName(metricsChannelId, rec), promUnit.c_str(), promType.c_str()); + snprintf(type, sizeof(type), "# TYPE %s%s%s %s\n",metricConstPrefix, iv->getFieldName(metricsChannelId, rec), promUnit.c_str(), promType.c_str()); metrics += type; metricDeclared = true; } @@ -777,11 +792,11 @@ class Web { strncpy(total,"_total",sizeof(total)); } if (!metricTotalDeclard) { - snprintf(type, sizeof(type), "# TYPE %s%s%s%s %s\n",metricPrefix, iv->getFieldName(metricsChannelId, rec), promUnit.c_str(), total, promType.c_str()); + snprintf(type, sizeof(type), "# TYPE %s%s%s%s %s\n",metricConstPrefix, iv->getFieldName(metricsChannelId, rec), promUnit.c_str(), total, promType.c_str()); metrics += type; metricTotalDeclard = true; } - snprintf(topic, sizeof(topic), "%s%s%s%s{inverter=\"%s\"}",metricPrefix, iv->getFieldName(metricsChannelId, rec), promUnit.c_str(), total,iv->config->name); + snprintf(topic, sizeof(topic), "%s%s%s%s{inverter=\"%s\"}",metricConstPrefix, iv->getFieldName(metricsChannelId, rec), promUnit.c_str(), total,iv->config->name); } else { // Report (non zero) channel value // Use a fallback channel name (ch0, ch1, ...)if non is given by user @@ -791,7 +806,7 @@ class Web { } else { snprintf(chName,sizeof(chName),"ch%1d",channel); } - snprintf(topic, sizeof(topic), "%s%s%s{inverter=\"%s\",channel=\"%s\"}",metricPrefix, iv->getFieldName(metricsChannelId, rec), promUnit.c_str(), iv->config->name,chName); + snprintf(topic, sizeof(topic), "%s%s%s{inverter=\"%s\",channel=\"%s\"}",metricConstPrefix, iv->getFieldName(metricsChannelId, rec), promUnit.c_str(), iv->config->name,chName); } snprintf(val, sizeof(val), " %.3f\n", iv->getValue(metricsChannelId, rec)); metrics += topic; @@ -800,12 +815,14 @@ class Web { } } if (metrics.length() < 1) { - metrics = "# Info: Field #"+String(metricsFieldId)+" not available for inverter #"+String(metricsInverterId)+". Skipping remaining inverters\n"; + metrics = "# Info: Field #"+String(metricsFieldId)+" (" + fields[metricsFieldId] + + ") not available for inverter #"+String(metricsInverterId)+". Skipping remaining inverters\n"; metricsFieldId++; // Process next field Id metricsStep = metricStateRealtimeFieldId; } } else { - metrics = "# Info: No data for field #"+String(metricsFieldId)+" of inverter #"+String(metricsInverterId)+". Skipping remaining inverters\n"; + metrics = "# Info: No data for field #"+String(metricsFieldId)+ " (" + fields[metricsFieldId] + + ") of inverter #"+String(metricsInverterId)+". Skipping remaining inverters\n"; metricsFieldId++; // Process next field Id metricsStep = metricStateRealtimeFieldId; } @@ -821,7 +838,7 @@ class Web { case metricsStateAlarmData: // Alarm Info loop : fit to one packet // Perform grouping on metrics according to Prometheus exposition format specification - snprintf(type, sizeof(type),"# TYPE %s%s gauge\n",metricPrefix,fields[FLD_LAST_ALARM_CODE]); + snprintf(type, sizeof(type),"# TYPE %s%s gauge\n",metricConstPrefix,fields[FLD_LAST_ALARM_CODE]); metrics = type; for (metricsInverterId = 0; metricsInverterId < mSys->getNumInverters();metricsInverterId++) { @@ -833,7 +850,7 @@ class Web { alarmChannelId = 0; if (alarmChannelId < rec->length) { std::tie(promUnit, promType) = convertToPromUnits(iv->getUnit(alarmChannelId, rec)); - snprintf(topic, sizeof(topic), "%s%s%s{inverter=\"%s\"}",metricPrefix, iv->getFieldName(alarmChannelId, rec), promUnit.c_str(), iv->config->name); + snprintf(topic, sizeof(topic), "%s%s%s{inverter=\"%s\"}",metricConstPrefix, iv->getFieldName(alarmChannelId, rec), promUnit.c_str(), iv->config->name); snprintf(val, sizeof(val), " %.3f\n", iv->getValue(alarmChannelId, rec)); metrics += topic; metrics += val; From f2e1e536f52a7f21efddde1f505a53c0aff29601 Mon Sep 17 00:00:00 2001 From: lumapu Date: Tue, 2 Jan 2024 15:12:24 +0100 Subject: [PATCH 016/179] 0.8.41 * fix display timeout (OLED) to 60s * change offs to signed value --- src/CHANGES.md | 4 ++++ src/app.cpp | 6 +++--- src/config/settings.h | 8 ++++---- src/defines.h | 2 +- src/plugins/Display/Display_Mono.h | 2 +- src/publisher/pubMqtt.h | 6 +++--- src/web/html/setup.html | 2 +- src/web/html/system.html | 2 +- 8 files changed, 18 insertions(+), 14 deletions(-) diff --git a/src/CHANGES.md b/src/CHANGES.md index bbede74c..43e58613 100644 --- a/src/CHANGES.md +++ b/src/CHANGES.md @@ -1,5 +1,9 @@ # Development Changes +## 0.8.41 - 2024-01-02 +* fix display timeout (OLED) to 60s +* change offs to signed value + ## 0.8.40 - 2024-01-02 * fix display of sunrise and sunset in `/system` #1308 * fix MqTT set power limit #1313 diff --git a/src/app.cpp b/src/app.cpp index 8e70778a..cd13050b 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -235,7 +235,7 @@ void app::tickCalcSunrise(void) { onceAt(std::bind(&app::tickCalcSunrise, this), nxtTrig, "Sunri"); if (mMqttEnabled) { tickSun(); - nxtTrig = mSunrise - mConfig->sun.offsetSecMorning + 1; // one second safety to trigger correctly + nxtTrig = mSunrise + mConfig->sun.offsetSecMorning + 1; // one second safety to trigger correctly onceAt(std::bind(&app::tickSun, this), nxtTrig, "mqSr"); // trigger on sunrise to update 'dis_night_comm' } } @@ -254,8 +254,8 @@ void app::tickIVCommunication(void) { iv->commEnabled = !iv->config->disNightCom; // if sun.disNightCom is false, communication is always on if (!iv->commEnabled) { // inverter communication only during the day - if (mTimestamp < (mSunrise - mConfig->sun.offsetSecMorning)) { // current time is before communication start, set next trigger to communication start - nxtTrig = mSunrise - mConfig->sun.offsetSecMorning; + if (mTimestamp < (mSunrise + mConfig->sun.offsetSecMorning)) { // current time is before communication start, set next trigger to communication start + nxtTrig = mSunrise + mConfig->sun.offsetSecMorning; } else { if (mTimestamp >= (mSunset + mConfig->sun.offsetSecEvening)) { // current time is past communication stop, nothing to do. Next update will be done at midnight by tickCalcSunrise nxtTrig = 0; diff --git a/src/config/settings.h b/src/config/settings.h index c493eb1a..5cecd46a 100644 --- a/src/config/settings.h +++ b/src/config/settings.h @@ -106,8 +106,8 @@ typedef struct { typedef struct { float lat; float lon; - uint16_t offsetSecMorning; - uint16_t offsetSecEvening; + int16_t offsetSecMorning; + int16_t offsetSecEvening; } cfgSun_t; typedef struct { @@ -635,8 +635,8 @@ class settings { } else { getVal(obj, F("lat"), &mCfg.sun.lat); getVal(obj, F("lon"), &mCfg.sun.lon); - getVal(obj, F("offs"), &mCfg.sun.offsetSecMorning); - getVal(obj, F("offsEve"), &mCfg.sun.offsetSecEvening); + getVal(obj, F("offs"), &mCfg.sun.offsetSecMorning); + getVal(obj, F("offsEve"), &mCfg.sun.offsetSecEvening); } } diff --git a/src/defines.h b/src/defines.h index 26ddf237..9ee93c17 100644 --- a/src/defines.h +++ b/src/defines.h @@ -13,7 +13,7 @@ //------------------------------------- #define VERSION_MAJOR 0 #define VERSION_MINOR 8 -#define VERSION_PATCH 40 +#define VERSION_PATCH 41 //------------------------------------- typedef struct { diff --git a/src/plugins/Display/Display_Mono.h b/src/plugins/Display/Display_Mono.h index 3e998b6d..322e991e 100644 --- a/src/plugins/Display/Display_Mono.h +++ b/src/plugins/Display/Display_Mono.h @@ -80,7 +80,7 @@ class DisplayMono { uint8_t mExtra; int8_t mPixelshift=0; - TimeMonitor mDisplayTime = TimeMonitor(1000 * 15, true); + TimeMonitor mDisplayTime = TimeMonitor(1000 * DISP_DEFAULT_TIMEOUT, true); bool mDisplayActive = true; // always start with display on char mFmtText[DISP_FMT_TEXT_LEN]; diff --git a/src/publisher/pubMqtt.h b/src/publisher/pubMqtt.h index 76ec452f..a1efa5fb 100644 --- a/src/publisher/pubMqtt.h +++ b/src/publisher/pubMqtt.h @@ -134,13 +134,13 @@ class PubMqtt { #endif } - bool tickerSun(uint32_t sunrise, uint32_t sunset, uint16_t offsM, uint16_t offsE) { + bool tickerSun(uint32_t sunrise, uint32_t sunset, int16_t offsM, int16_t offsE) { if (!mClient.connected()) return false; publish(subtopics[MQTT_SUNRISE], String(sunrise).c_str(), true); publish(subtopics[MQTT_SUNSET], String(sunset).c_str(), true); - publish(subtopics[MQTT_COMM_START], String(sunrise - offsM).c_str(), true); + publish(subtopics[MQTT_COMM_START], String(sunrise + offsM).c_str(), true); publish(subtopics[MQTT_COMM_STOP], String(sunset + offsE).c_str(), true); Inverter<> *iv; @@ -155,7 +155,7 @@ class PubMqtt { snprintf(mSubTopic, 32 + MAX_NAME_LENGTH, "comm_disabled"); - publish(mSubTopic, (((*mUtcTimestamp > (sunset + offsE)) || (*mUtcTimestamp < (sunrise - offsM))) ? dict[STR_TRUE] : dict[STR_FALSE]), true); + publish(mSubTopic, (((*mUtcTimestamp > (sunset + offsE)) || (*mUtcTimestamp < (sunrise + offsM))) ? dict[STR_TRUE] : dict[STR_FALSE]), true); return true; } diff --git a/src/web/html/setup.html b/src/web/html/setup.html index aef43dcf..aa52ac33 100644 --- a/src/web/html/setup.html +++ b/src/web/html/setup.html @@ -895,7 +895,7 @@ document.getElementsByName("sunLon")[0].value = obj["lon"]; for(p of [["sunOffsSr", "offsSr"], ["sunOffsSs", "offsSs"]]) { const sel = document.getElementsByName(p[0])[0]; - for(var i = 0; i <= 60; i++) { + for(var i = -60; i <= 60; i++) { sel.appendChild(opt(i, i + " minutes", (i == (obj[p[1]] / 60)))); } } diff --git a/src/web/html/system.html b/src/web/html/system.html index 0415864b..035f30ff 100644 --- a/src/web/html/system.html +++ b/src/web/html/system.html @@ -108,7 +108,7 @@ ml("tbody", {}, [ tr("Sunrise", new Date(obj.ts_sunrise * 1000).toLocaleString('de-DE')), tr("Sunset", new Date(obj.ts_sunset * 1000).toLocaleString('de-DE')), - tr("Communication start", new Date((obj.ts_sunrise - obj.ts_offsSr) * 1000).toLocaleString('de-DE')), + tr("Communication start", new Date((obj.ts_sunrise + obj.ts_offsSr) * 1000).toLocaleString('de-DE')), tr("Communication stop", new Date((obj.ts_sunset + obj.ts_offsSs) * 1000).toLocaleString('de-DE')), tr("Night behaviour", badge(obj.disNightComm, ((obj.disNightComm) ? "not" : "") + " communicating", "warning")) ]) From 4ae4c167666bba2f390ffd8876a00a172b860fec Mon Sep 17 00:00:00 2001 From: Gerald <34278535+GHolli@users.noreply.github.com> Date: Tue, 2 Jan 2024 16:21:04 +0100 Subject: [PATCH 017/179] Add space between values and units. --- src/web/html/visualization.html | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/web/html/visualization.html b/src/web/html/visualization.html index d255d1c5..b027ef95 100644 --- a/src/web/html/visualization.html +++ b/src/web/html/visualization.html @@ -106,7 +106,7 @@ if(65535 != obj.power_limit_read) { pwrLimit = obj.power_limit_read + " %"; if(0 != obj.max_pwr) - pwrLimit += ", " + Math.round(obj.max_pwr * obj.power_limit_read / 100) + "W"; + pwrLimit += ", " + Math.round(obj.max_pwr * obj.power_limit_read / 100) + " W"; } return ml("div", {class: "row mt-2"}, ml("div", {class: "col"}, [ @@ -192,9 +192,9 @@ if(obj.rssi > -127) { if(obj.generation < 2) - ageInfo += " (RSSI: " + ((obj.rssi == -64) ? ">=" : "<") + " -64dBm)"; + ageInfo += " (RSSI: " + ((obj.rssi == -64) ? ">=" : "<") + " -64 dBm)"; else - ageInfo += " (RSSI: " + obj.rssi + "dBm)"; + ageInfo += " (RSSI: " + obj.rssi + " dBm)"; } return ml("div", {class: "mb-5"}, [ @@ -383,9 +383,9 @@ var html = ml("table", {class: "table"}, [ ml("tbody", {}, [ tr2(["TX count", obj.tx_cnt, ""]), - tr2(["RX success", obj.rx_success, String(Math.round(obj.rx_success / obj.tx_cnt * 10000) / 100) + "%"]), - tr2(["RX fail", obj.rx_fail, String(Math.round(obj.rx_fail / obj.tx_cnt * 10000) / 100) + "%"]), - tr2(["RX no answer", obj.rx_fail_answer, String(Math.round(obj.rx_fail_answer / obj.tx_cnt * 10000) / 100) + "%"]), + tr2(["RX success", obj.rx_success, String(Math.round(obj.rx_success / obj.tx_cnt * 10000) / 100) + " %"]), + tr2(["RX fail", obj.rx_fail, String(Math.round(obj.rx_fail / obj.tx_cnt * 10000) / 100) + " %"]), + tr2(["RX no answer", obj.rx_fail_answer, String(Math.round(obj.rx_fail_answer / obj.tx_cnt * 10000) / 100) + " %"]), tr2(["RX fragments", obj.frame_cnt, ""]), tr2(["TX retransmits", obj.retransmits, ""]) ]) From cd9cc976e76b77cd47b4acbd79b01687d72678d9 Mon Sep 17 00:00:00 2001 From: lumapu Date: Wed, 3 Jan 2024 00:14:10 +0100 Subject: [PATCH 018/179] 0.8.42 * add LED to display whether it's night time or not. Can be reused as output to control battery system #1308 --- src/CHANGES.md | 3 +++ src/app.cpp | 32 ++++++++++++++++++-------------- src/config/config.h | 3 +++ src/config/settings.h | 18 ++++++++++-------- src/defines.h | 2 +- src/web/RestApi.h | 5 +++-- src/web/html/setup.html | 2 +- src/web/web.h | 23 ++++++++++++----------- 8 files changed, 51 insertions(+), 37 deletions(-) diff --git a/src/CHANGES.md b/src/CHANGES.md index 43e58613..1539fd5c 100644 --- a/src/CHANGES.md +++ b/src/CHANGES.md @@ -1,5 +1,8 @@ # Development Changes +## 0.8.42 - 2024-01-02 +* add LED to display whether it's night time or not. Can be reused as output to control battery system #1308 + ## 0.8.41 - 2024-01-02 * fix display timeout (OLED) to 60s * change offs to signed value diff --git a/src/app.cpp b/src/app.cpp index cd13050b..6bce1709 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -456,14 +456,11 @@ void app::mqttSubRxCb(JsonObject obj) { //----------------------------------------------------------------------------- void app::setupLed(void) { uint8_t led_off = (mConfig->led.high_active) ? 0 : 255; - - if (mConfig->led.led0 != DEF_PIN_OFF) { - pinMode(mConfig->led.led0, OUTPUT); - analogWrite(mConfig->led.led0, led_off); - } - if (mConfig->led.led1 != DEF_PIN_OFF) { - pinMode(mConfig->led.led1, OUTPUT); - analogWrite(mConfig->led.led1, led_off); + for(uint8_t i = 0; i < 3; i ++) { + if (mConfig->led.led[i] != DEF_PIN_OFF) { + pinMode(mConfig->led.led[i], OUTPUT); + analogWrite(mConfig->led.led[i], led_off); + } } } @@ -472,27 +469,34 @@ void app::updateLed(void) { uint8_t led_off = (mConfig->led.high_active) ? 0 : 255; uint8_t led_on = (mConfig->led.high_active) ? (mConfig->led.luminance) : (255-mConfig->led.luminance); - if (mConfig->led.led0 != DEF_PIN_OFF) { + if (mConfig->led.led[0] != DEF_PIN_OFF) { Inverter<> *iv; for (uint8_t id = 0; id < mSys.getNumInverters(); id++) { iv = mSys.getInverterByPos(id); if (NULL != iv) { if (iv->isProducing()) { // turn on when at least one inverter is producing - analogWrite(mConfig->led.led0, led_on); + analogWrite(mConfig->led.led[0], led_on); break; } else if(iv->config->enabled) - analogWrite(mConfig->led.led0, led_off); + analogWrite(mConfig->led.led[0], led_off); } } } - if (mConfig->led.led1 != DEF_PIN_OFF) { + if (mConfig->led.led[1] != DEF_PIN_OFF) { if (getMqttIsConnected()) { - analogWrite(mConfig->led.led1, led_on); + analogWrite(mConfig->led.led[1], led_on); } else { - analogWrite(mConfig->led.led1, led_off); + analogWrite(mConfig->led.led[1], led_off); } } + + if (mConfig->led.led[2] != DEF_PIN_OFF) { + if((mTimestamp > (mSunset + mConfig->sun.offsetSecEvening)) || (mTimestamp < (mSunrise + mConfig->sun.offsetSecMorning))) + analogWrite(mConfig->led.led[2], led_on); + else + analogWrite(mConfig->led.led[2], led_off); + } } diff --git a/src/config/config.h b/src/config/config.h index 2cb9bcd3..fb05a1cc 100644 --- a/src/config/config.h +++ b/src/config/config.h @@ -142,6 +142,9 @@ #ifndef DEF_LED1 #define DEF_LED1 DEF_PIN_OFF #endif +#ifndef DEF_LED2 + #define DEF_LED2 DEF_PIN_OFF +#endif #ifdef LED_ACTIVE_HIGH #define LED_HIGH_ACTIVE true #else diff --git a/src/config/settings.h b/src/config/settings.h index 5cecd46a..476f9fbf 100644 --- a/src/config/settings.h +++ b/src/config/settings.h @@ -118,8 +118,7 @@ typedef struct { } cfgSerial_t; typedef struct { - uint8_t led0; // first LED pin - uint8_t led1; // second LED pin + uint8_t led[3]; // LED pins bool high_active; // determines if LEDs are high or low active uint8_t luminance; // luminance of LED } cfgLed_t; @@ -453,8 +452,9 @@ class settings { mCfg.inst.iv[i].add2Total = true; } - mCfg.led.led0 = DEF_LED0; - mCfg.led.led1 = DEF_LED1; + mCfg.led.led[0] = DEF_LED0; + mCfg.led.led[1] = DEF_LED1; + mCfg.led.led[2] = DEF_LED2; mCfg.led.high_active = LED_HIGH_ACTIVE; mCfg.led.luminance = 255; @@ -677,13 +677,15 @@ class settings { void jsonLed(JsonObject obj, bool set = false) { if(set) { - obj[F("0")] = mCfg.led.led0; - obj[F("1")] = mCfg.led.led1; + obj[F("0")] = mCfg.led.led[0]; + obj[F("1")] = mCfg.led.led[1]; + obj[F("2")] = mCfg.led.led[2]; obj[F("act_high")] = mCfg.led.high_active; obj[F("lum")] = mCfg.led.luminance; } else { - getVal(obj, F("0"), &mCfg.led.led0); - getVal(obj, F("1"), &mCfg.led.led1); + getVal(obj, F("0"), &mCfg.led.led[0]); + getVal(obj, F("1"), &mCfg.led.led[1]); + getVal(obj, F("2"), &mCfg.led.led[2]); getVal(obj, F("act_high"), &mCfg.led.high_active); getVal(obj, F("lum"), &mCfg.led.luminance); } diff --git a/src/defines.h b/src/defines.h index 9ee93c17..56bdf5e6 100644 --- a/src/defines.h +++ b/src/defines.h @@ -13,7 +13,7 @@ //------------------------------------- #define VERSION_MAJOR 0 #define VERSION_MINOR 8 -#define VERSION_PATCH 41 +#define VERSION_PATCH 42 //------------------------------------- typedef struct { diff --git a/src/web/RestApi.h b/src/web/RestApi.h index c920a8e3..7541e095 100644 --- a/src/web/RestApi.h +++ b/src/web/RestApi.h @@ -611,8 +611,9 @@ class RestApi { obj[F("sclk")] = mConfig->nrf.pinSclk; obj[F("mosi")] = mConfig->nrf.pinMosi; obj[F("miso")] = mConfig->nrf.pinMiso; - obj[F("led0")] = mConfig->led.led0; - obj[F("led1")] = mConfig->led.led1; + obj[F("led0")] = mConfig->led.led[0]; + obj[F("led1")] = mConfig->led.led[1]; + obj[F("led2")] = mConfig->led.led[2]; obj[F("led_high_active")] = mConfig->led.high_active; obj[F("led_lum")] = mConfig->led.luminance; } diff --git a/src/web/html/setup.html b/src/web/html/setup.html index aa52ac33..a103cf15 100644 --- a/src/web/html/setup.html +++ b/src/web/html/setup.html @@ -909,7 +909,7 @@ if ("ESP32-S3" == system.chip_model) pinList = esp32s3pins; else if("ESP32-C3" == system["chip_model"]) pinList = esp32c3pins; /*ENDIF_ESP32*/ - pins = [['led0', 'pinLed0', 'At least one inverter is producing'], ['led1', 'pinLed1', 'MqTT connected']]; + pins = [['led0', 'pinLed0', 'At least one inverter is producing'], ['led1', 'pinLed1', 'MqTT connected'], ['led2', 'pinLed2', 'Night time']]; for(p of pins) { e.append( ml("div", {class: "row mb-3"}, [ diff --git a/src/web/web.h b/src/web/web.h index 3ed0353d..3df40839 100644 --- a/src/web/web.h +++ b/src/web/web.h @@ -39,7 +39,7 @@ #define WEB_SERIAL_BUF_SIZE 2048 -const char* const pinArgNames[] = {"pinCs", "pinCe", "pinIrq", "pinSclk", "pinMosi", "pinMiso", "pinLed0", "pinLed1", "pinLedHighActive", "pinLedLum", "pinCmtSclk", "pinSdio", "pinCsb", "pinFcsb", "pinGpio3"}; +const char* const pinArgNames[] = {"pinCs", "pinCe", "pinIrq", "pinSclk", "pinMosi", "pinMiso", "pinLed0", "pinLed1", "pinLed2", "pinLedHighActive", "pinLedLum", "pinCmtSclk", "pinSdio", "pinCsb", "pinFcsb", "pinGpio3"}; template class Web { @@ -506,7 +506,7 @@ class Web { // pinout uint8_t pin; - for (uint8_t i = 0; i < 15; i++) { + for (uint8_t i = 0; i < 16; i++) { pin = request->arg(String(pinArgNames[i])).toInt(); switch(i) { case 0: mConfig->nrf.pinCs = ((pin != 0xff) ? pin : DEF_NRF_CS_PIN); break; @@ -515,15 +515,16 @@ class Web { case 3: mConfig->nrf.pinSclk = ((pin != 0xff) ? pin : DEF_NRF_SCLK_PIN); break; case 4: mConfig->nrf.pinMosi = ((pin != 0xff) ? pin : DEF_NRF_MOSI_PIN); break; case 5: mConfig->nrf.pinMiso = ((pin != 0xff) ? pin : DEF_NRF_MISO_PIN); break; - case 6: mConfig->led.led0 = pin; break; - case 7: mConfig->led.led1 = pin; break; - case 8: mConfig->led.high_active = pin; break; // this is not really a pin but a polarity, but handling it close to here makes sense - case 9: mConfig->led.luminance = pin; break; // this is not really a pin but a polarity, but handling it close to here makes sense - case 10: mConfig->cmt.pinSclk = pin; break; - case 11: mConfig->cmt.pinSdio = pin; break; - case 12: mConfig->cmt.pinCsb = pin; break; - case 13: mConfig->cmt.pinFcsb = pin; break; - case 14: mConfig->cmt.pinIrq = pin; break; + case 6: mConfig->led.led[0] = pin; break; + case 7: mConfig->led.led[1] = pin; break; + case 8: mConfig->led.led[2] = pin; break; + case 9: mConfig->led.high_active = pin; break; // this is not really a pin but a polarity, but handling it close to here makes sense + case 10: mConfig->led.luminance = pin; break; // this is not really a pin but a polarity, but handling it close to here makes sense + case 11: mConfig->cmt.pinSclk = pin; break; + case 12: mConfig->cmt.pinSdio = pin; break; + case 13: mConfig->cmt.pinCsb = pin; break; + case 14: mConfig->cmt.pinFcsb = pin; break; + case 15: mConfig->cmt.pinIrq = pin; break; } } From 9ad119b39ea1ca6843a9496fcfe456fa4c50dc50 Mon Sep 17 00:00:00 2001 From: lumapu Date: Wed, 3 Jan 2024 00:20:22 +0100 Subject: [PATCH 019/179] 0.8.42 * merge PR: beautifiying typography, added spaces between value and unit for `/visualization` #1314 * meger PR: Prometheus add `getLossRate` and bugfixing #1315 --- src/CHANGES.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/CHANGES.md b/src/CHANGES.md index 1539fd5c..20a6f745 100644 --- a/src/CHANGES.md +++ b/src/CHANGES.md @@ -2,6 +2,8 @@ ## 0.8.42 - 2024-01-02 * add LED to display whether it's night time or not. Can be reused as output to control battery system #1308 +* merge PR: beautifiying typography, added spaces between value and unit for `/visualization` #1314 +* meger PR: Prometheus add `getLossRate` and bugfixing #1315 ## 0.8.41 - 2024-01-02 * fix display timeout (OLED) to 60s From dd0bec6fc0d785757778f4fbede8698fe5c84f52 Mon Sep 17 00:00:00 2001 From: lumapu Date: Wed, 3 Jan 2024 00:21:49 +0100 Subject: [PATCH 020/179] 0.8.42 --- src/CHANGES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/CHANGES.md b/src/CHANGES.md index 20a6f745..ba3c661b 100644 --- a/src/CHANGES.md +++ b/src/CHANGES.md @@ -3,7 +3,7 @@ ## 0.8.42 - 2024-01-02 * add LED to display whether it's night time or not. Can be reused as output to control battery system #1308 * merge PR: beautifiying typography, added spaces between value and unit for `/visualization` #1314 -* meger PR: Prometheus add `getLossRate` and bugfixing #1315 +* merge PR: Prometheus add `getLossRate` and bugfixing #1315 ## 0.8.41 - 2024-01-02 * fix display timeout (OLED) to 60s From 0d10d19b30ce8d48d6215bda352821fb8c3b544a Mon Sep 17 00:00:00 2001 From: lumapu Date: Wed, 3 Jan 2024 01:29:46 +0100 Subject: [PATCH 021/179] 0.8.42 * add loss rate to `/visualization` in the statistics window * corrected `getLossRate` infos for MqTT and prometheus * added information about working IRQ for NRF24 and CMT2300A to `/system` --- src/CHANGES.md | 3 +++ src/defines.h | 8 ++++---- src/hm/Communication.h | 2 +- src/hm/hmInverter.h | 29 ++++++++++++++++++++--------- src/hm/hmRadio.h | 2 +- src/hm/radio.h | 6 ++++++ src/hms/hmsRadio.h | 2 +- src/publisher/pubMqttIvData.h | 10 +++++----- src/web/RestApi.h | 6 ++++++ src/web/html/system.html | 10 ++++++++++ src/web/html/visualization.html | 4 +++- src/web/web.h | 8 ++++---- 12 files changed, 64 insertions(+), 26 deletions(-) diff --git a/src/CHANGES.md b/src/CHANGES.md index ba3c661b..a2be1750 100644 --- a/src/CHANGES.md +++ b/src/CHANGES.md @@ -4,6 +4,9 @@ * add LED to display whether it's night time or not. Can be reused as output to control battery system #1308 * merge PR: beautifiying typography, added spaces between value and unit for `/visualization` #1314 * merge PR: Prometheus add `getLossRate` and bugfixing #1315 +* add loss rate to `/visualization` in the statistics window +* corrected `getLossRate` infos for MqTT and prometheus +* added information about working IRQ for NRF24 and CMT2300A to `/system` ## 0.8.41 - 2024-01-02 * fix display timeout (OLED) to 60s diff --git a/src/defines.h b/src/defines.h index 56bdf5e6..4b3482a2 100644 --- a/src/defines.h +++ b/src/defines.h @@ -103,10 +103,10 @@ typedef struct { uint32_t frmCnt; uint32_t txCnt; uint32_t retransmits; - uint16_t ivRxCnt; // last iv rx frames (from GetLossRate) - uint16_t ivTxCnt; // last iv tx frames (from GetLossRate) - uint16_t dtuRxCnt; // current DTU rx frames (since last GetLossRate) - uint16_t dtuTxCnt; // current DTU tx frames (since last GetLossRate) + uint16_t ivLoss; // lost frames (from GetLossRate) + uint16_t ivSent; // sent frames (from GetLossRate) + uint16_t dtuLoss; // current DTU lost frames (since last GetLossRate) + uint16_t dtuSent; // current DTU sent frames (since last GetLossRate) } statistics_t; #endif /*__DEFINES_H__*/ diff --git a/src/hm/Communication.h b/src/hm/Communication.h index d44e860f..e31984b6 100644 --- a/src/hm/Communication.h +++ b/src/hm/Communication.h @@ -151,7 +151,7 @@ class Communication : public CommQueue<> { if(validateIvSerial(&p->packet[1], q->iv)) { q->iv->radioStatistics.frmCnt++; - q->iv->radioStatistics.dtuRxCnt++; + q->iv->mDtuRxCnt++; if (p->packet[0] == (TX_REQ_INFO + ALL_FRAMES)) { // response from get information command if(parseFrame(p)) diff --git a/src/hm/hmInverter.h b/src/hm/hmInverter.h index 25f575e5..57de135f 100644 --- a/src/hm/hmInverter.h +++ b/src/hm/hmInverter.h @@ -595,22 +595,27 @@ class Inverter { uint16_t rxCnt = (pyld[0] << 8) + pyld[1]; uint16_t txCnt = (pyld[2] << 8) + pyld[3]; - if (radioStatistics.ivRxCnt || radioStatistics.ivTxCnt) { // there was successful GetLossRate in the past + if (mIvRxCnt || mIvTxCnt) { // there was successful GetLossRate in the past + radioStatistics.ivLoss = mDtuTxCnt - (rxCnt - mIvRxCnt); + radioStatistics.ivSent = mDtuTxCnt; + radioStatistics.dtuLoss = txCnt - mIvTxCnt - mDtuRxCnt; + radioStatistics.dtuSent = txCnt - mIvTxCnt; + DPRINT_IVID(DBG_INFO, id); DBGPRINT(F("Inv loss: ")); - DBGPRINT(String (radioStatistics.dtuTxCnt - (rxCnt - radioStatistics.ivRxCnt))); + DBGPRINT(String(radioStatistics.ivLoss)); DBGPRINT(F(" of ")); - DBGPRINT(String (radioStatistics.dtuTxCnt)); + DBGPRINT(String(radioStatistics.ivSent)); DBGPRINT(F(", DTU loss: ")); - DBGPRINT(String (txCnt - radioStatistics.ivTxCnt - radioStatistics.dtuRxCnt)); + DBGPRINT(String(radioStatistics.dtuLoss)); DBGPRINT(F(" of ")); - DBGPRINTLN(String (txCnt - radioStatistics.ivTxCnt)); + DBGPRINTLN(String(radioStatistics.dtuSent)); } - radioStatistics.ivRxCnt = rxCnt; - radioStatistics.ivTxCnt = txCnt; - radioStatistics.dtuRxCnt = 0; // start new interval - radioStatistics.dtuTxCnt = 0; // start new interval + mIvRxCnt = rxCnt; + mIvTxCnt = txCnt; + mDtuRxCnt = 0; // start new interval + mDtuTxCnt = 0; // start new interval return true; } @@ -806,6 +811,12 @@ class Inverter { uint8_t mGridLen = 0; uint8_t mGridProfile[MAX_GRID_LENGTH]; uint8_t mGetLossInterval; // request iv every AHOY_GET_LOSS_INTERVAL RealTimeRunData_Debug + uint16_t mIvRxCnt = 0; + uint16_t mIvTxCnt = 0; + + public: + uint16_t mDtuRxCnt = 0; + uint16_t mDtuTxCnt = 0; }; template diff --git a/src/hm/hmRadio.h b/src/hm/hmRadio.h index c7b9581c..6539dd21 100644 --- a/src/hm/hmRadio.h +++ b/src/hm/hmRadio.h @@ -339,7 +339,7 @@ class HmRadio : public Radio { mMillis = millis(); mLastIv = iv; - iv->radioStatistics.dtuTxCnt++; + iv->mDtuTxCnt++; } uint64_t getIvId(Inverter<> *iv) { diff --git a/src/hm/radio.h b/src/hm/radio.h index 2fe4f640..1ef32e05 100644 --- a/src/hm/radio.h +++ b/src/hm/radio.h @@ -14,6 +14,8 @@ #include "../utils/dbg.h" #include "../utils/crc.h" +enum { IRQ_UNKNOWN = 0, IRQ_OK, IRQ_ERROR }; + // forward declaration of class template class Inverter; @@ -30,6 +32,7 @@ class Radio { void handleIntr(void) { mIrqRcvd = true; + mIrqOk = IRQ_OK; } void sendCmdPacket(Inverter<> *iv, uint8_t mid, uint8_t pid, bool isRetransmit, bool appendCrc16=true) { @@ -65,6 +68,7 @@ class Radio { public: std::queue mBufCtrl; + uint8_t mIrqOk = IRQ_UNKNOWN; protected: virtual void sendPacket(Inverter<> *iv, uint8_t len, bool isRetransmit, bool appendCrc16=true) = 0; @@ -77,6 +81,8 @@ class Radio { CP_U32_LittleEndian(&mTxBuf[5], mDtuSn); mTxBuf[9] = pid; memset(&mTxBuf[10], 0x00, (MAX_RF_PAYLOAD_SIZE-10)); + if(IRQ_UNKNOWN == mIrqOk) + mIrqOk = IRQ_ERROR; } void updateCrcs(uint8_t *len, bool appendCrc16=true) { diff --git a/src/hms/hmsRadio.h b/src/hms/hmsRadio.h index 3b3893ac..6b502816 100644 --- a/src/hms/hmsRadio.h +++ b/src/hms/hmsRadio.h @@ -112,7 +112,7 @@ class CmtRadio : public Radio { if(CMT_ERR_RX_IN_FIFO == status) mIrqRcvd = true; } - iv->radioStatistics.dtuTxCnt++; + iv->mDtuTxCnt++; } uint64_t getIvId(Inverter<> *iv) { diff --git a/src/publisher/pubMqttIvData.h b/src/publisher/pubMqttIvData.h index 3a38a5ed..c645c70b 100644 --- a/src/publisher/pubMqttIvData.h +++ b/src/publisher/pubMqttIvData.h @@ -1,5 +1,5 @@ //----------------------------------------------------------------------------- -// 2023 Ahoy, https://ahoydtu.de +// 2024 Ahoy, https://ahoydtu.de // Creative Commons - https://creativecommons.org/licenses/by-nc-sa/4.0/deed //----------------------------------------------------------------------------- @@ -201,10 +201,10 @@ class PubMqttIvData { mIv->radioStatistics.rxFail, mIv->radioStatistics.rxFailNoAnser, mIv->radioStatistics.retransmits, - mIv->radioStatistics.ivRxCnt, - mIv->radioStatistics.ivTxCnt, - mIv->radioStatistics.dtuRxCnt, - mIv->radioStatistics.dtuTxCnt); + mIv->radioStatistics.ivLoss, + mIv->radioStatistics.ivSent, + mIv->radioStatistics.dtuLoss, + mIv->radioStatistics.dtuSent); mPublish(mSubTopic, mVal, false, QOS_0); } diff --git a/src/web/RestApi.h b/src/web/RestApi.h index 7541e095..03275eed 100644 --- a/src/web/RestApi.h +++ b/src/web/RestApi.h @@ -405,6 +405,10 @@ class RestApi { obj[F("frame_cnt")] = iv->radioStatistics.frmCnt; obj[F("tx_cnt")] = iv->radioStatistics.txCnt; obj[F("retransmits")] = iv->radioStatistics.retransmits; + obj[F("ivLoss")] = iv->radioStatistics.ivLoss; + obj[F("ivSent")] = iv->radioStatistics.ivSent; + obj[F("dtuLoss")] = iv->radioStatistics.dtuLoss; + obj[F("dtuSent")] = iv->radioStatistics.dtuSent; } void getIvPowerLimitAck(JsonObject obj, uint8_t id) { @@ -633,6 +637,7 @@ class RestApi { if(mConfig->cmt.enabled) { obj[F("isconnected")] = mRadioCmt->isChipConnected(); obj[F("sn")] = String(mRadioCmt->getDTUSn(), HEX); + obj[F("irqOk")] = mRadioCmt->mIrqOk; } } #endif @@ -643,6 +648,7 @@ class RestApi { obj[F("isconnected")] = mRadioNrf->isChipConnected(); obj[F("dataRate")] = mRadioNrf->getDataRate(); obj[F("sn")] = String(mRadioNrf->getDTUSn(), HEX); + obj[F("irqOk")] = mRadioNrf->mIrqOk; } } diff --git a/src/web/html/system.html b/src/web/html/system.html index 035f30ff..f400db41 100644 --- a/src/web/html/system.html +++ b/src/web/html/system.html @@ -44,12 +44,21 @@ return ml("div", {class: "head p-2 mt-3"}, ml("div", {class: "row"}, ml("div", {class: "col a-c"}, text))) } + function irqBadge(state) { + switch(state) { + case 0: return badge(false, "unknown", "warning"); break; + case 1: return badge(true, "true"); break; + default: return badge(false, "false"); break; + } + } + function parseRadio(obj) { const dr = ["1 M", "2 M", "250 k"] if(obj.radioNrf.en) { lines = [ tr("NRF24L01", badge(obj.radioNrf.isconnected, ((obj.radioNrf.isconnected) ? "" : "not ") + "connected")), + tr("Interrupt Pin working", irqBadge(obj.radioNrf.irqOk)), tr("NRF24 Data Rate", dr[obj.radioNrf.dataRate] + "bps"), tr("DTU Radio ID", obj.radioNrf.sn) ]; @@ -67,6 +76,7 @@ if(obj.radioCmt.en) { cmt = [ tr("CMT2300A", badge(obj.radioCmt.isconnected, ((obj.radioCmt.isconnected) ? "" : "not ") + "connected")), + tr("Interrupt Pin working", irqBadge(obj.radioCmt.irqOk)), tr("DTU Radio ID", obj.radioCmt.sn) ]; } else diff --git a/src/web/html/visualization.html b/src/web/html/visualization.html index 88bbdc9b..ec6596bb 100644 --- a/src/web/html/visualization.html +++ b/src/web/html/visualization.html @@ -388,7 +388,9 @@ tr2(["RX fail", obj.rx_fail, String(Math.round(obj.rx_fail / obj.tx_cnt * 10000) / 100) + " %"]), tr2(["RX no answer", obj.rx_fail_answer, String(Math.round(obj.rx_fail_answer / obj.tx_cnt * 10000) / 100) + " %"]), tr2(["RX fragments", obj.frame_cnt, ""]), - tr2(["TX retransmits", obj.retransmits, ""]) + tr2(["TX retransmits", obj.retransmits, ""]), + tr2(["Inverter loss rate", "lost " + obj.ivLoss + " of " + obj.ivSent, String(Math.round(obj.ivLoss / obj.ivSent * 10000) / 100) + " %"]), + tr2(["DTU loss rate", "lost " + obj.dtuLoss + " of " + obj.dtuSent, String(Math.round(obj.dtuLoss / obj.dtuSent * 10000) / 100) + " %"]) ]) ]) modal("Radio statistics for inverter " + obj.name, ml("div", {}, html)) diff --git a/src/web/web.h b/src/web/web.h index 2bcc031a..ea25e5ff 100644 --- a/src/web/web.h +++ b/src/web/web.h @@ -665,10 +665,10 @@ class Web { { "radio_frame_cnt", "counter" ,metricConstInverterFormat, [](Inverter<> *iv)-> uint64_t {return iv->radioStatistics.frmCnt;} }, { "radio_tx_cnt", "counter" ,metricConstInverterFormat, [](Inverter<> *iv)-> uint64_t {return iv->radioStatistics.txCnt;} }, { "radio_retransmits", "counter" ,metricConstInverterFormat, [](Inverter<> *iv)-> uint64_t {return iv->radioStatistics.retransmits;} }, - { "radio_iv_rx_cnt", "counter" ,metricConstInverterFormat, [](Inverter<> *iv)-> uint64_t {return iv->radioStatistics.ivRxCnt;} }, - { "radio_iv_tx_cnt", "counter" ,metricConstInverterFormat, [](Inverter<> *iv)-> uint64_t {return iv->radioStatistics.ivTxCnt;} }, - { "radio_dtu_rx_cnt", "counter" ,metricConstInverterFormat, [](Inverter<> *iv)-> uint64_t {return iv->radioStatistics.dtuRxCnt;} }, - { "radio_dtu_tx_cnt", "counter" ,metricConstInverterFormat, [](Inverter<> *iv)-> uint64_t {return iv->radioStatistics.dtuTxCnt;} } + { "radio_iv_loss_cnt", "counter" ,metricConstInverterFormat, [](Inverter<> *iv)-> uint64_t {return iv->radioStatistics.ivLoss;} }, + { "radio_iv_sent_cnt", "counter" ,metricConstInverterFormat, [](Inverter<> *iv)-> uint64_t {return iv->radioStatistics.ivSent;} }, + { "radio_dtu_loss_cnt", "counter" ,metricConstInverterFormat, [](Inverter<> *iv)-> uint64_t {return iv->radioStatistics.dtuLoss;} }, + { "radio_dtu_sent_cnt", "counter" ,metricConstInverterFormat, [](Inverter<> *iv)-> uint64_t {return iv->radioStatistics.dtuSent;} } }; int metricsInverterId; uint8_t metricsFieldId; From 31ecb9620fbd188c7bad50c9828d029e7e7b8f7a Mon Sep 17 00:00:00 2001 From: lumapu Date: Fri, 5 Jan 2024 01:24:38 +0100 Subject: [PATCH 022/179] 0.8.43 * fix display of sunrise in `/system` #1308 * fix overflow of `getLossRate` calculation #1318 * improved MqTT by marking sent data and improved `last_success` resends #1319 * added timestamp for `max ac power` as tooltip #1324 #1123 #1199 * repaired Power-limit acknowledge #1322 --- src/CHANGES.md | 7 +++ src/app.cpp | 1 + src/app.h | 4 -- src/appInterface.h | 1 - src/defines.h | 2 +- src/hm/Communication.h | 7 +++ src/hm/hmInverter.h | 75 +++++++++++++++++++++------------ src/publisher/pubMqttIvData.h | 32 +++++--------- src/web/RestApi.h | 1 + src/web/html/index.html | 6 +-- src/web/html/setup.html | 2 +- src/web/html/style.css | 35 +++++---------- src/web/html/visualization.html | 11 +++-- 13 files changed, 97 insertions(+), 87 deletions(-) diff --git a/src/CHANGES.md b/src/CHANGES.md index a2be1750..ca1f5332 100644 --- a/src/CHANGES.md +++ b/src/CHANGES.md @@ -1,5 +1,12 @@ # Development Changes +## 0.8.43 - 2024-01-04 +* fix display of sunrise in `/system` #1308 +* fix overflow of `getLossRate` calculation #1318 +* improved MqTT by marking sent data and improved `last_success` resends #1319 +* added timestamp for `max ac power` as tooltip #1324 #1123 #1199 +* repaired Power-limit acknowledge #1322 + ## 0.8.42 - 2024-01-02 * add LED to display whether it's night time or not. Can be reused as output to control battery system #1308 * merge PR: beautifiying typography, added spaces between value and unit for `/visualization` #1314 diff --git a/src/app.cpp b/src/app.cpp index 6bce1709..6a93f96c 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -52,6 +52,7 @@ void app::setup() { mCommunication.setup(&mTimestamp, &mConfig->serial.debug, &mConfig->serial.privacyLog, &mConfig->serial.printWholeTrace, &mConfig->inst.gapMs); mCommunication.addPayloadListener(std::bind(&app::payloadEventListener, this, std::placeholders::_1, std::placeholders::_2)); + mCommunication.addPowerLimitAckListener([this] (Inverter<> *iv) { mMqtt.setPowerLimitAck(iv); }); mSys.setup(&mTimestamp, &mConfig->inst); for (uint8_t i = 0; i < MAX_NUM_INVERTERS; i++) { initInverter(i); diff --git a/src/app.h b/src/app.h index a24cccb3..8d6fafbe 100644 --- a/src/app.h +++ b/src/app.h @@ -180,10 +180,6 @@ class app : public IApp, public ah::Scheduler { once(std::bind(&PubMqttType::sendDiscoveryConfig, &mMqtt), 1, "disCf"); } - void setMqttPowerLimitAck(Inverter<> *iv) { - mMqtt.setPowerLimitAck(iv); - } - bool getMqttIsConnected() { return mMqtt.isConnected(); } diff --git a/src/appInterface.h b/src/appInterface.h index 34dc5ddc..9d633765 100644 --- a/src/appInterface.h +++ b/src/appInterface.h @@ -52,7 +52,6 @@ class IApp { virtual bool getRebootRequestState() = 0; virtual bool getSettingsValid() = 0; virtual void setMqttDiscoveryFlag() = 0; - virtual void setMqttPowerLimitAck(Inverter<> *iv) = 0; virtual bool getMqttIsConnected() = 0; virtual bool getNrfEnabled() = 0; diff --git a/src/defines.h b/src/defines.h index 4b3482a2..21564fdb 100644 --- a/src/defines.h +++ b/src/defines.h @@ -13,7 +13,7 @@ //------------------------------------- #define VERSION_MAJOR 0 #define VERSION_MINOR 8 -#define VERSION_PATCH 42 +#define VERSION_PATCH 43 //------------------------------------- typedef struct { diff --git a/src/hm/Communication.h b/src/hm/Communication.h index e31984b6..99c8f382 100644 --- a/src/hm/Communication.h +++ b/src/hm/Communication.h @@ -19,6 +19,7 @@ #define MAX_BUFFER 250 typedef std::function *)> payloadListenerType; +typedef std::function *)> powerLimitAckListenerType; typedef std::function *)> alarmListenerType; class Communication : public CommQueue<> { @@ -40,6 +41,10 @@ class Communication : public CommQueue<> { mCbPayload = cb; } + void addPowerLimitAckListener(powerLimitAckListenerType cb) { + mCbPwrAck = cb; + } + void addAlarmListener(alarmListenerType cb) { mCbAlarm = cb; } @@ -401,6 +406,7 @@ class Communication : public CommQueue<> { DBGPRINT(F(" with PowerLimitControl ")); DBGPRINTLN(String(q->iv->powerLimit[1])); q->iv->actPowerLimit = 0xffff; // unknown, readback current value + (mCbPwrAck)(q->iv); return accepted; } @@ -921,6 +927,7 @@ class Communication : public CommQueue<> { uint8_t mMaxFrameId; uint8_t mPayload[MAX_BUFFER]; payloadListenerType mCbPayload = NULL; + powerLimitAckListenerType mCbPwrAck = NULL; alarmListenerType mCbAlarm = NULL; Heuristic mHeu; uint32_t mLastEmptyQueueMillis = 0; diff --git a/src/hm/hmInverter.h b/src/hm/hmInverter.h index 57de135f..1d38c23f 100644 --- a/src/hm/hmInverter.h +++ b/src/hm/hmInverter.h @@ -64,13 +64,28 @@ struct calcFunc_t { func_t* func; // function pointer }; +enum class MqttSentStatus : uint8_t { + NEW_DATA, + LAST_SUCCESS_SENT, + DATA_SENT +}; + +enum class InverterStatus : uint8_t { + OFF, + STARTING, + PRODUCING, + WAS_PRODUCING, + WAS_ON +}; + template struct record_t { - byteAssign_t* assign; // assignment of bytes in payload - uint8_t length; // length of the assignment list - T *record; // data pointer - uint32_t ts; // timestamp of last received payload - uint8_t pyldLen; // expected payload length for plausibility check + byteAssign_t* assign; // assignment of bytes in payload + uint8_t length; // length of the assignment list + T *record; // data pointer + uint32_t ts; // timestamp of last received payload + uint8_t pyldLen; // expected payload length for plausibility check + MqttSentStatus mqttSentStatus; // indicates the current MqTT sent status }; struct alarm_t { @@ -94,14 +109,6 @@ const calcFunc_t calcFunctions[] = { { CALC_MPDC_CH, &calcMaxPowerDc } }; -enum class InverterStatus : uint8_t { - OFF, - STARTING, - PRODUCING, - WAS_PRODUCING, - WAS_ON -}; - template class Inverter { public: @@ -124,26 +131,28 @@ class Inverter { bool isConnected; // shows if inverter was successfully identified (fw version and hardware info) InverterStatus status; // indicates the current inverter status std::array lastAlarm; // holds last 10 alarms - uint8_t alarmNxtWrPos; // indicates the position in array (rolling buffer) + int8_t rssi; // RSSI uint16_t alarmCnt; // counts the total number of occurred alarms uint16_t alarmLastId; // lastId which was received - int8_t rssi; // RSSI + uint8_t mCmd; // holds the command to send + bool mGotFragment; // shows if inverter has sent at least one fragment uint8_t miMultiParts; // helper info for MI multiframe msgs uint8_t outstandingFrames; // helper info to count difference between expected and received frames - bool mGotFragment; // shows if inverter has sent at least one fragment uint8_t curFrmCnt; // count received frames in current loop bool mGotLastMsg; // shows if inverter has already finished transmission cycle - uint8_t mCmd; // holds the command to send bool mIsSingleframeReq; // indicates this is a missing single frame request Radio *radio; // pointer to associated radio class statistics_t radioStatistics; // information about transmitted, failed, ... packets HeuristicInv heuristics; // heuristic information / logic uint8_t curCmtFreq; // current used CMT frequency, used to check if freq. was changed during runtime bool commEnabled; // 'pause night communication' sets this field to false + uint32_t tsMaxAcPower; // holds the timestamp when the MaxAC power was seen static uint32_t *timestamp; // system timestamp static cfgInst_t *generalConfig; // general inverter configuration from setup + public: + Inverter() { ivGen = IV_HM; powerLimit[0] = 0xffff; // 6553.5 W Limit -> unlimited @@ -155,7 +164,6 @@ class Inverter { alarmMesIndex = 0; isConnected = false; status = InverterStatus::OFF; - alarmNxtWrPos = 0; alarmCnt = 0; alarmLastId = 0; rssi = -127; @@ -165,6 +173,7 @@ class Inverter { mIsSingleframeReq = false; radio = NULL; commEnabled = true; + tsMaxAcPower = 0; memset(&radioStatistics, 0, sizeof(statistics_t)); memset(heuristics.txRfQuality, -6, 5); @@ -310,11 +319,11 @@ class Inverter { rec->record[pos] = (REC_TYP)(val); } } + rec->mqttSentStatus = MqttSentStatus::NEW_DATA; } 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]) { @@ -498,6 +507,7 @@ class Inverter { DPRINTLN(DBG_VERBOSE, F("hmInverter.h:initAssignment")); rec->ts = 0; rec->length = 0; + rec->mqttSentStatus = MqttSentStatus::DATA_SENT; // nothing new to transmit switch (cmd) { case RealTimeRunData_Debug: if (INV_TYPE_1CH == type) { @@ -582,7 +592,7 @@ class Inverter { void resetAlarms() { lastAlarm.fill({0, 0, 0}); - alarmNxtWrPos = 0; + mAlarmNxtWrPos = 0; alarmCnt = 0; alarmLastId = 0; @@ -596,10 +606,18 @@ class Inverter { uint16_t txCnt = (pyld[2] << 8) + pyld[3]; if (mIvRxCnt || mIvTxCnt) { // there was successful GetLossRate in the past - radioStatistics.ivLoss = mDtuTxCnt - (rxCnt - mIvRxCnt); radioStatistics.ivSent = mDtuTxCnt; - radioStatistics.dtuLoss = txCnt - mIvTxCnt - mDtuRxCnt; - radioStatistics.dtuSent = txCnt - mIvTxCnt; + if (rxCnt < mIvRxCnt) // overflow + radioStatistics.ivLoss = radioStatistics.ivSent - (rxCnt + ((uint16_t)65535 - mIvRxCnt) + 1); + else + radioStatistics.ivLoss = radioStatistics.ivSent - (rxCnt - mIvRxCnt); + + if (txCnt < mIvTxCnt) // overflow + radioStatistics.dtuSent = txCnt + ((uint16_t)65535 - mIvTxCnt) + 1; + else + radioStatistics.dtuSent = txCnt - mIvTxCnt; + + radioStatistics.dtuLoss = radioStatistics.dtuSent - mDtuRxCnt; DPRINT_IVID(DBG_INFO, id); DBGPRINT(F("Inv loss: ")); @@ -790,9 +808,9 @@ class Inverter { private: inline void addAlarm(uint16_t code, uint32_t start, uint32_t end) { - lastAlarm[alarmNxtWrPos] = alarm_t(code, start, end); - if(++alarmNxtWrPos >= 10) // rolling buffer - alarmNxtWrPos = 0; + lastAlarm[mAlarmNxtWrPos] = alarm_t(code, start, end); + if(++mAlarmNxtWrPos >= 10) // rolling buffer + mAlarmNxtWrPos = 0; } void toRadioId(void) { @@ -813,6 +831,7 @@ class Inverter { uint8_t mGetLossInterval; // request iv every AHOY_GET_LOSS_INTERVAL RealTimeRunData_Debug uint16_t mIvRxCnt = 0; uint16_t mIvTxCnt = 0; + uint8_t mAlarmNxtWrPos = 0; // indicates the position in array (rolling buffer) public: uint16_t mDtuRxCnt = 0; @@ -948,8 +967,10 @@ static T calcMaxPowerDc(Inverter<> *iv, uint8_t arg0) { dcMaxPower = iv->getValue(i, rec); } } - if(dcPower > dcMaxPower) + if(dcPower > dcMaxPower) { + iv->tsMaxAcPower = *iv->timestamp; return dcPower; + } } return dcMaxPower; } diff --git a/src/publisher/pubMqttIvData.h b/src/publisher/pubMqttIvData.h index c645c70b..61344ede 100644 --- a/src/publisher/pubMqttIvData.h +++ b/src/publisher/pubMqttIvData.h @@ -28,7 +28,6 @@ class PubMqttIvData { mState = IDLE; mZeroValues = false; - memset(mIvLastRTRpub, 0, MAX_NUM_INVERTERS * sizeof(uint32_t)); mRTRDataHasBeenSent = false; mTable[IDLE] = &PubMqttIvData::stateIdle; @@ -102,7 +101,7 @@ class PubMqttIvData { mPos = 0; if(found) { record_t<> *rec = mIv->getRecordStruct(mCmd); - if((RealTimeRunData_Debug == mCmd) && mIv->getLastTs(rec) != 0 ) { //workaround for startup. Suspect, mCmd might cause to much messages.... + if(MqttSentStatus::NEW_DATA == rec->mqttSentStatus) { snprintf(mSubTopic, 32 + MAX_NAME_LENGTH, "%s/last_success", mIv->config->name); snprintf(mVal, 40, "%d", mIv->getLastTs(rec)); mPublish(mSubTopic, mVal, true, QOS_0); @@ -112,13 +111,14 @@ class PubMqttIvData { snprintf(mVal, 40, "%d", mIv->rssi); mPublish(mSubTopic, mVal, false, QOS_0); } + rec->mqttSentStatus = MqttSentStatus::LAST_SUCCESS_SENT; } mIv->isProducing(); // recalculate status mState = SEND_DATA; - } else if(mSendTotals && mTotalFound) + } else if(mSendTotals && mTotalFound) { mState = SEND_TOTALS; - else { + } else { mSendList->pop(); mZeroValues = false; mState = START; @@ -132,12 +132,8 @@ class PubMqttIvData { DPRINT(DBG_WARN, "unknown record to publish!"); return; } - uint32_t lastTs = mIv->getLastTs(rec); - bool pubData = (lastTs > 0); - if (mCmd == RealTimeRunData_Debug) - pubData &= (lastTs != mIvLastRTRpub[mIv->id]); - if (pubData) { + if (MqttSentStatus::LAST_SUCCESS_SENT == rec->mqttSentStatus) { if(mPos < rec->length) { bool retained = false; if (mCmd == RealTimeRunData_Debug) { @@ -172,21 +168,16 @@ class PubMqttIvData { } else mAllTotalFound = false; } - } else - mIvLastRTRpub[mIv->id] = lastTs; - - uint8_t qos = QOS_0; - if(FLD_ACT_ACTIVE_PWR_LIMIT == rec->assign[mPos].fieldId) - qos = QOS_2; - - if((mIvSend == mIv) || (NULL == mIvSend)) { // send only updated values, or all if the inverter is NULL - snprintf(mSubTopic, 32 + MAX_NAME_LENGTH, "%s/ch%d/%s", mIv->config->name, rec->assign[mPos].ch, fields[rec->assign[mPos].fieldId]); - snprintf(mVal, 40, "%g", ah::round3(mIv->getValue(mPos, rec))); - mPublish(mSubTopic, mVal, retained, qos); } + + uint8_t qos = (FLD_ACT_ACTIVE_PWR_LIMIT == rec->assign[mPos].fieldId) ? QOS_2 : QOS_0; + snprintf(mSubTopic, 32 + MAX_NAME_LENGTH, "%s/ch%d/%s", mIv->config->name, rec->assign[mPos].ch, fields[rec->assign[mPos].fieldId]); + snprintf(mVal, 40, "%g", ah::round3(mIv->getValue(mPos, rec))); + mPublish(mSubTopic, mVal, retained, qos); mPos++; } else { sendRadioStat(rec->length); + rec->mqttSentStatus = MqttSentStatus::DATA_SENT; mState = FIND_NXT_IV; } } else @@ -263,7 +254,6 @@ class PubMqttIvData { Inverter<> *mIv, *mIvSend; uint8_t mPos; - uint32_t mIvLastRTRpub[MAX_NUM_INVERTERS]; bool mRTRDataHasBeenSent; char mSubTopic[32 + MAX_NAME_LENGTH + 1]; diff --git a/src/web/RestApi.h b/src/web/RestApi.h index 03275eed..2b341441 100644 --- a/src/web/RestApi.h +++ b/src/web/RestApi.h @@ -485,6 +485,7 @@ class RestApi { obj[F("status")] = (uint8_t)iv->getStatus(); obj[F("alarm_cnt")] = iv->alarmCnt; obj[F("rssi")] = iv->rssi; + obj[F("ts_max_ac_pwr")] = iv->tsMaxAcPower; JsonArray ch = obj.createNestedArray("ch"); diff --git a/src/web/html/index.html b/src/web/html/index.html index 3ac72e89..cfdaeee6 100644 --- a/src/web/html/index.html +++ b/src/web/html/index.html @@ -99,17 +99,17 @@ } if(obj.disNightComm) { - if(((obj.ts_sunrise - obj.ts_offsSr) < obj.ts_now) + if(((obj.ts_sunrise + obj.ts_offsSr) < obj.ts_now) && ((obj.ts_sunset + obj.ts_offsSs) > obj.ts_now)) { commInfo = "Polling inverter(s), will pause at sunset " + (new Date((obj.ts_sunset + obj.ts_offsSs) * 1000).toLocaleString('de-DE')); } else { commInfo = "Night time, inverter polling disabled, "; - if(obj.ts_now > (obj.ts_sunrise - obj.ts_offsSr)) { + if(obj.ts_now > (obj.ts_sunrise + obj.ts_offsSr)) { commInfo += "paused at " + (new Date((obj.ts_sunset + obj.ts_offsSs) * 1000).toLocaleString('de-DE')); } else { - commInfo += "will start polling at " + (new Date((obj.ts_sunrise - obj.ts_offsSr) * 1000).toLocaleString('de-DE')); + commInfo += "will start polling at " + (new Date((obj.ts_sunrise + obj.ts_offsSr) * 1000).toLocaleString('de-DE')); } } } diff --git a/src/web/html/setup.html b/src/web/html/setup.html index a103cf15..782c23be 100644 --- a/src/web/html/setup.html +++ b/src/web/html/setup.html @@ -765,7 +765,7 @@ ml("div", {class: "col-2"}, cbDisNightCom) ]), ml("div", {class: "row mb-3"}, [ - ml("div", {class: "col-10"}, "Include inverter to sum of total (should be checked by default)"), + ml("div", {class: "col-10"}, "Include inverter to sum of total (should be checked by default, MqTT only)"), ml("div", {class: "col-2"}, cbAddTotal) ]) ]), diff --git a/src/web/html/style.css b/src/web/html/style.css index b31ae7c9..e7de68f4 100644 --- a/src/web/html/style.css +++ b/src/web/html/style.css @@ -650,39 +650,24 @@ div.hr { } -.css-tooltip{ +.tooltip{ position: relative; } -.css-tooltip:hover:after{ - content:attr(data-tooltip); - background:#000; - padding:5px; - border-radius:3px; +.tooltip:hover:after { + content: attr(data); + background: var(--nav-active); + padding: 5px; + border-radius: 3px; display: inline-block; position: absolute; transform: translate(-50%,-100%); margin:0 auto; - color:#FFF; - min-width:100px; - min-width:150px; - top:-5px; + color: var(--fg2); + min-width: 100px; + top: -5px; left: 50%; text-align:center; -} -.css-tooltip:hover:before { - top:-5px; - left: 50%; - border: solid transparent; - content: " "; - height: 0; - width: 0; - position: absolute; - pointer-events: none; - border-color: rgba(0, 0, 0, 0); - border-top-color: #000; - border-width: 5px; - margin-left: -5px; - transform: translate(0,0px); + font-size: 1rem; } #modal { diff --git a/src/web/html/visualization.html b/src/web/html/visualization.html index ec6596bb..3ae8ff0b 100644 --- a/src/web/html/visualization.html +++ b/src/web/html/visualization.html @@ -45,13 +45,14 @@ ]); } - function numMid(val, unit, des) { + function numMid(val, unit, des, opt={class: "fs-6"}) { return ml("div", {class: "col-6 col-sm-4 col-md-3 mb-2"}, [ ml("div", {class: "row"}, ml("div", {class: "col"}, [ - ml("span", {class: "fs-6"}, String(Math.round(val * 100) / 100)), + ml("span", opt, String(Math.round(val * 100) / 100)), ml("span", {class: "fs-8 mx-1"}, unit) - ])), + ]) + ), ml("div", {class: "row"}, ml("div", {class: "col"}, ml("span", {class: "fs-9"}, des) @@ -108,6 +109,8 @@ if(0 != obj.max_pwr) pwrLimit += ", " + Math.round(obj.max_pwr * obj.power_limit_read / 100) + " W"; } + + var maxAcPwr = toIsoDateStr(new Date(obj.ts_max_ac_pwr * 1000)); return ml("div", {class: "row mt-2"}, ml("div", {class: "col"}, [ ml("div", {class: "p-2 " + clh}, @@ -133,7 +136,7 @@ ]), ml("div", {class: "hr"}), ml("div", {class: "row mt-2"},[ - numMid(obj.ch[0][11], "W", "Max AC Power"), + numMid(obj.ch[0][11], "W", "Max AC Power", {class: "fs-6 tooltip", data: maxAcPwr}), numMid(obj.ch[0][8], "W", "DC Power"), numMid(obj.ch[0][0], "V", "AC Voltage"), numMid(obj.ch[0][1], "A", "AC Current"), From 21d77f58cac1077229e88187a1223118a691b065 Mon Sep 17 00:00:00 2001 From: lumapu Date: Fri, 5 Jan 2024 01:33:26 +0100 Subject: [PATCH 023/179] 0.8.43 * fix `max_power` in `/visualization` was set to `0` after sunset --- src/CHANGES.md | 1 + src/web/html/visualization.html | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/CHANGES.md b/src/CHANGES.md index ca1f5332..c416fd80 100644 --- a/src/CHANGES.md +++ b/src/CHANGES.md @@ -6,6 +6,7 @@ * improved MqTT by marking sent data and improved `last_success` resends #1319 * added timestamp for `max ac power` as tooltip #1324 #1123 #1199 * repaired Power-limit acknowledge #1322 +* fix `max_power` in `/visualization` was set to `0` after sunset ## 0.8.42 - 2024-01-02 * add LED to display whether it's night time or not. Can be reused as output to control battery system #1308 diff --git a/src/web/html/visualization.html b/src/web/html/visualization.html index 3ae8ff0b..27f5da35 100644 --- a/src/web/html/visualization.html +++ b/src/web/html/visualization.html @@ -92,10 +92,10 @@ function ivHead(obj) { if(0 != obj.status) { // only add totals if inverter is online total[0] += obj.ch[0][2]; // P_AC - total[3] += obj.ch[0][11]; // MAX P_AC total[4] += obj.ch[0][8]; // P_DC total[5] += obj.ch[0][10]; // Q_AC } + total[3] += obj.ch[0][11]; // MAX P_AC total[1] += obj.ch[0][7]; // YieldDay total[2] += obj.ch[0][6]; // YieldTotal From b4ba35e6ab4f452ebf451789b4cb4c02968b084a Mon Sep 17 00:00:00 2001 From: lumapu Date: Fri, 5 Jan 2024 14:08:38 +0100 Subject: [PATCH 024/179] 0.8.44 * fix MqTT transmission of data #1326 * live data is read much earlier / faster and more often --- src/CHANGES.md | 4 ++++ src/defines.h | 2 +- src/hm/Communication.h | 1 + src/hm/hmInverter.h | 48 ++++++++++++++++++++++++------------------ 4 files changed, 33 insertions(+), 22 deletions(-) diff --git a/src/CHANGES.md b/src/CHANGES.md index c416fd80..9dc10a72 100644 --- a/src/CHANGES.md +++ b/src/CHANGES.md @@ -1,5 +1,9 @@ # Development Changes +## 0.8.44 - 2024-01-05 +* fix MqTT transmission of data #1326 +* live data is read much earlier / faster and more often + ## 0.8.43 - 2024-01-04 * fix display of sunrise in `/system` #1308 * fix overflow of `getLossRate` calculation #1318 diff --git a/src/defines.h b/src/defines.h index 21564fdb..f0c9731a 100644 --- a/src/defines.h +++ b/src/defines.h @@ -13,7 +13,7 @@ //------------------------------------- #define VERSION_MAJOR 0 #define VERSION_MINOR 8 -#define VERSION_PATCH 43 +#define VERSION_PATCH 44 //------------------------------------- typedef struct { diff --git a/src/hm/Communication.h b/src/hm/Communication.h index 99c8f382..219cb92e 100644 --- a/src/hm/Communication.h +++ b/src/hm/Communication.h @@ -498,6 +498,7 @@ class Communication : public CommQueue<> { for (uint8_t i = 0; i < rec->length; i++) { q->iv->addValue(i, mPayload, rec); } + rec->mqttSentStatus = MqttSentStatus::NEW_DATA; q->iv->rssi = rssi; q->iv->doCalculations(); diff --git a/src/hm/hmInverter.h b/src/hm/hmInverter.h index 1d38c23f..a5834d9a 100644 --- a/src/hm/hmInverter.h +++ b/src/hm/hmInverter.h @@ -186,28 +186,33 @@ class Inverter { if(mDevControlRequest) { cb(devControlCmd, true); mDevControlRequest = false; - } else if (IV_MI != ivGen) { + } else if (IV_MI != ivGen) { // HM / HMS / HMT mGetLossInterval++; - if((alarmLastId != alarmMesIndex) && (alarmMesIndex != 0)) - cb(AlarmData, false); // get last alarms - else if(0 == getFwVersion()) - cb(InverterDevInform_All, false); // get firmware version - else if(0 == getHwVersion()) - cb(InverterDevInform_Simple, false); // get hardware version - else if(actPowerLimit == 0xffff) - cb(SystemConfigPara, false); // power limit info - else if(InitDataState != devControlCmd) { - cb(devControlCmd, false); // custom command which was received by API - devControlCmd = InitDataState; - mGetLossInterval = 1; - } else if((0 == mGridLen) && generalConfig->readGrid) { // read grid profile - cb(GridOnProFilePara, false); - } else if (mGetLossInterval > AHOY_GET_LOSS_INTERVAL) { // get loss rate - mGetLossInterval = 1; - cb(GetLossRate, false); - } else + if(mNextLive) cb(RealTimeRunData_Debug, false); // get live data - } else { + else { + mNextLive = true; + if(actPowerLimit == 0xffff) + cb(SystemConfigPara, false); // power limit info + else if(InitDataState != devControlCmd) { + cb(devControlCmd, false); // custom command which was received by API + devControlCmd = InitDataState; + mGetLossInterval = 1; + } else if((alarmLastId != alarmMesIndex) && (alarmMesIndex != 0)) + cb(AlarmData, false); // get last alarms + else if(0 == getFwVersion()) + cb(InverterDevInform_All, false); // get firmware version + else if(0 == getHwVersion()) + cb(InverterDevInform_Simple, false); // get hardware version + else if((0 == mGridLen) && generalConfig->readGrid) { // read grid profile + cb(GridOnProFilePara, false); + } else if (mGetLossInterval > AHOY_GET_LOSS_INTERVAL) { // get loss rate + mGetLossInterval = 1; + cb(GetLossRate, false); + } else + cb(RealTimeRunData_Debug, false); // get live data + } + } else { // MI if(0 == getFwVersion()) cb(0x0f, false); // get firmware version; for MI, this makes part of polling the device software and hardware version number else { @@ -319,10 +324,10 @@ class Inverter { rec->record[pos] = (REC_TYP)(val); } } - rec->mqttSentStatus = MqttSentStatus::NEW_DATA; } if(rec == &recordMeas) { + mNextLive = false; // live data received 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) { @@ -832,6 +837,7 @@ class Inverter { uint16_t mIvRxCnt = 0; uint16_t mIvTxCnt = 0; uint8_t mAlarmNxtWrPos = 0; // indicates the position in array (rolling buffer) + bool mNextLive = true; // first read live data after booting up then version etc. public: uint16_t mDtuRxCnt = 0; From 617cf0a92a3e303000179e6946b018da4f3d6d09 Mon Sep 17 00:00:00 2001 From: lumapu Date: Fri, 5 Jan 2024 17:19:53 +0100 Subject: [PATCH 025/179] 0.8.45 * fix MqTT total values #1326 --- src/CHANGES.md | 5 ++- src/defines.h | 2 +- src/publisher/pubMqttIvData.h | 75 ++++++++++++++++++----------------- 3 files changed, 43 insertions(+), 39 deletions(-) diff --git a/src/CHANGES.md b/src/CHANGES.md index 9dc10a72..ab0963d7 100644 --- a/src/CHANGES.md +++ b/src/CHANGES.md @@ -1,8 +1,11 @@ # Development Changes +## 0.8.45 - 2024-01-05 +* fix MqTT total values #1326 + ## 0.8.44 - 2024-01-05 * fix MqTT transmission of data #1326 -* live data is read much earlier / faster and more often +* live data is read much earlier / faster and more often #1272 ## 0.8.43 - 2024-01-04 * fix display of sunrise in `/system` #1308 diff --git a/src/defines.h b/src/defines.h index f0c9731a..4d3d7fdd 100644 --- a/src/defines.h +++ b/src/defines.h @@ -13,7 +13,7 @@ //------------------------------------- #define VERSION_MAJOR 0 #define VERSION_MINOR 8 -#define VERSION_PATCH 44 +#define VERSION_PATCH 45 //------------------------------------- typedef struct { diff --git a/src/publisher/pubMqttIvData.h b/src/publisher/pubMqttIvData.h index 61344ede..a7deadfb 100644 --- a/src/publisher/pubMqttIvData.h +++ b/src/publisher/pubMqttIvData.h @@ -133,55 +133,56 @@ class PubMqttIvData { return; } - if (MqttSentStatus::LAST_SUCCESS_SENT == rec->mqttSentStatus) { - if(mPos < rec->length) { - bool retained = false; - if (mCmd == RealTimeRunData_Debug) { - if((FLD_YT == rec->assign[mPos].fieldId) || (FLD_YD == rec->assign[mPos].fieldId)) - retained = true; - - // calculate total values for RealTimeRunData_Debug - if (CH0 == rec->assign[mPos].ch) { - if(mIv->getStatus() > InverterStatus::STARTING) { - if(mIv->config->add2Total) { - mTotalFound = true; - switch (rec->assign[mPos].fieldId) { - case FLD_PAC: - mTotal[0] += mIv->getValue(mPos, rec); - break; - case FLD_YT: - mTotal[1] += mIv->getValue(mPos, rec); - break; - case FLD_YD: { - float val = mIv->getValue(mPos, rec); - if(0 == val) // inverter restarted during day - mSendTotalYd = false; - else - mTotal[2] += val; - break; - } - case FLD_PDC: - mTotal[3] += mIv->getValue(mPos, rec); - break; + if(mPos < rec->length) { + bool retained = false; + if (mCmd == RealTimeRunData_Debug) { + if((FLD_YT == rec->assign[mPos].fieldId) || (FLD_YD == rec->assign[mPos].fieldId)) + retained = true; + + // calculate total values for RealTimeRunData_Debug + if (CH0 == rec->assign[mPos].ch) { + if(mIv->getStatus() > InverterStatus::STARTING) { + if(mIv->config->add2Total) { + mTotalFound = true; + switch (rec->assign[mPos].fieldId) { + case FLD_PAC: + mTotal[0] += mIv->getValue(mPos, rec); + break; + case FLD_YT: + mTotal[1] += mIv->getValue(mPos, rec); + break; + case FLD_YD: { + float val = mIv->getValue(mPos, rec); + if(0 == val) // inverter restarted during day + mSendTotalYd = false; + else + mTotal[2] += val; + break; } + case FLD_PDC: + mTotal[3] += mIv->getValue(mPos, rec); + break; } - } else - mAllTotalFound = false; - } + } + } else + mAllTotalFound = false; } + } + if (MqttSentStatus::LAST_SUCCESS_SENT == rec->mqttSentStatus) { uint8_t qos = (FLD_ACT_ACTIVE_PWR_LIMIT == rec->assign[mPos].fieldId) ? QOS_2 : QOS_0; snprintf(mSubTopic, 32 + MAX_NAME_LENGTH, "%s/ch%d/%s", mIv->config->name, rec->assign[mPos].ch, fields[rec->assign[mPos].fieldId]); snprintf(mVal, 40, "%g", ah::round3(mIv->getValue(mPos, rec))); mPublish(mSubTopic, mVal, retained, qos); - mPos++; - } else { + } + mPos++; + } else { + if (MqttSentStatus::LAST_SUCCESS_SENT == rec->mqttSentStatus) { sendRadioStat(rec->length); rec->mqttSentStatus = MqttSentStatus::DATA_SENT; - mState = FIND_NXT_IV; } - } else mState = FIND_NXT_IV; + } } inline void sendRadioStat(uint8_t start) { From 5ca26895a1147417411eb59b9fd1ea9482ef3c12 Mon Sep 17 00:00:00 2001 From: lumapu Date: Sat, 6 Jan 2024 02:58:19 +0100 Subject: [PATCH 026/179] 0.8.45 * start implementing a wizard for initial (WiFi) configuration #1199 --- src/CHANGES.md | 1 + src/app.h | 12 ++++++ src/appInterface.h | 3 ++ src/web/RestApi.h | 12 +++++- src/web/html/style.css | 17 ++++++++ src/web/html/wizard.html | 87 ++++++++++++++++++++++++++++++++++++++++ src/web/web.h | 11 ++++- src/wifi/ahoywifi.cpp | 15 ++++--- src/wifi/ahoywifi.h | 9 ++++- 9 files changed, 158 insertions(+), 9 deletions(-) create mode 100644 src/web/html/wizard.html diff --git a/src/CHANGES.md b/src/CHANGES.md index ab0963d7..34f9aacc 100644 --- a/src/CHANGES.md +++ b/src/CHANGES.md @@ -2,6 +2,7 @@ ## 0.8.45 - 2024-01-05 * fix MqTT total values #1326 +* start implementing a wizard for initial (WiFi) configuration #1199 ## 0.8.44 - 2024-01-05 * fix MqTT transmission of data #1326 diff --git a/src/app.h b/src/app.h index 8d6fafbe..41ff008b 100644 --- a/src/app.h +++ b/src/app.h @@ -150,6 +150,18 @@ class app : public IApp, public ah::Scheduler { return mWifi.getAvailNetworks(obj); } + void setupStation(void) { + mWifi.setupStation(); + } + + void setStopApAllowedMode(bool allowed) { + mWifi.setStopApAllowedMode(allowed); + } + + String getStationIp(void) { + return mWifi.getStationIp(); + } + #endif /* !defined(ETHERNET) */ void setRebootFlag() { diff --git a/src/appInterface.h b/src/appInterface.h index 9d633765..76c5e935 100644 --- a/src/appInterface.h +++ b/src/appInterface.h @@ -37,6 +37,9 @@ class IApp { #if !defined(ETHERNET) virtual void scanAvailNetworks() = 0; virtual bool getAvailNetworks(JsonObject obj) = 0; + virtual void setupStation(void) = 0; + virtual void setStopApAllowedMode(bool allowed) = 0; + virtual String getStationIp(void) = 0; #endif /* defined(ETHERNET) */ virtual uint32_t getUptime() = 0; diff --git a/src/web/RestApi.h b/src/web/RestApi.h index 2b341441..cea845a2 100644 --- a/src/web/RestApi.h +++ b/src/web/RestApi.h @@ -100,6 +100,7 @@ class RestApi { else if(path == "setup") getSetup(request, root); #if !defined(ETHERNET) else if(path == "setup/networks") getNetworks(root); + else if(path == "setup/getip") getWifiIp(root); #endif /* !defined(ETHERNET) */ else if(path == "live") getLive(request,root); else { @@ -754,6 +755,9 @@ class RestApi { void getNetworks(JsonObject obj) { mApp->getAvailNetworks(obj); } + void getWifiIp(JsonObject obj) { + obj[F("ip")] = mApp->getStationIp(); + } #endif /* !defined(ETHERNET) */ void getLive(AsyncWebServerRequest *request, JsonObject obj) { @@ -834,7 +838,13 @@ class RestApi { mTimezoneOffset = jsonIn[F("val")]; else if(F("discovery_cfg") == jsonIn[F("cmd")]) mApp->setMqttDiscoveryFlag(); // for homeassistant - else if(F("save_iv") == jsonIn[F("cmd")]) { + else if(F("save_wifi") == jsonIn[F("cmd")]) { + snprintf(mConfig->sys.stationSsid, SSID_LEN, "%s", jsonIn[F("ssid")].as()); + snprintf(mConfig->sys.stationPwd, PWD_LEN, "%s", jsonIn[F("pwd")].as()); + mApp->saveSettings(false); // without reboot + mApp->setStopApAllowedMode(false); + mApp->setupStation(); + } else if(F("save_iv") == jsonIn[F("cmd")]) { Inverter<> *iv = mSys->getInverterByPos(jsonIn[F("id")], false); iv->config->enabled = jsonIn[F("en")]; iv->config->serial.u64 = jsonIn[F("ser")]; diff --git a/src/web/html/style.css b/src/web/html/style.css index e7de68f4..c9f9fbf7 100644 --- a/src/web/html/style.css +++ b/src/web/html/style.css @@ -814,3 +814,20 @@ ul { background-color: var(--input-bg); color: var(--fg); } + +.d-flex { + display: flex !important; +} + +.jc { + justify-content: center !important; +} + +.aic { + align-items: center !important; +} + +.container { + height: 100%; + overflow: auto; +} diff --git a/src/web/html/wizard.html b/src/web/html/wizard.html new file mode 100644 index 00000000..1ebfb65f --- /dev/null +++ b/src/web/html/wizard.html @@ -0,0 +1,87 @@ + + + + Setup Wizard + {#HTML_HEADER} + + +

+ + + diff --git a/src/web/web.h b/src/web/web.h index ea25e5ff..eae106a9 100644 --- a/src/web/web.h +++ b/src/web/web.h @@ -36,6 +36,7 @@ #include "html/h/update_html.h" #include "html/h/visualization_html.h" #include "html/h/about_html.h" +#include "html/h/wizard_html.h" #define WEB_SERIAL_BUF_SIZE 2048 @@ -77,6 +78,7 @@ class Web { mWeb.on("/factorytrue", HTTP_ANY, std::bind(&Web::showHtml, this, std::placeholders::_1)); mWeb.on("/setup", HTTP_GET, std::bind(&Web::onSetup, this, std::placeholders::_1)); + mWeb.on("/wizard", HTTP_GET, std::bind(&Web::onWizard, this, std::placeholders::_1)); mWeb.on("/save", HTTP_POST, std::bind(&Web::showSave, this, std::placeholders::_1)); mWeb.on("/live", HTTP_ANY, std::bind(&Web::onLive, this, std::placeholders::_1)); @@ -422,7 +424,7 @@ class Web { void showNotFound(AsyncWebServerRequest *request) { checkProtection(request); - request->redirect("/setup"); + request->redirect("/wizard"); } void onReboot(AsyncWebServerRequest *request) { @@ -444,6 +446,13 @@ class Web { getPage(request, PROT_MASK_SETUP, setup_html, setup_html_len); } + void onWizard(AsyncWebServerRequest *request) { + AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html; charset=UTF-8"), wizard_html, wizard_html_len); + response->addHeader(F("Content-Encoding"), "gzip"); + response->addHeader(F("content-type"), "text/html; charset=UTF-8"); + request->send(response); + } + void showSave(AsyncWebServerRequest *request) { DPRINTLN(DBG_VERBOSE, F("showSave")); diff --git a/src/wifi/ahoywifi.cpp b/src/wifi/ahoywifi.cpp index 02f8d252..4ab7ee92 100644 --- a/src/wifi/ahoywifi.cpp +++ b/src/wifi/ahoywifi.cpp @@ -40,6 +40,7 @@ void ahoywifi::setup(settings_t *config, uint32_t *utcTimestamp, appWifiCb cb) { mCnt = 0; mScanActive = false; mScanCnt = 0; + mStopApAllowed = true; #if defined(ESP8266) wifiConnectHandler = WiFi.onStationModeConnected(std::bind(&ahoywifi::onConnect, this, std::placeholders::_1)); @@ -94,7 +95,7 @@ void ahoywifi::tickWifiLoop() { #endif return; case IN_AP_MODE: - if (WiFi.softAPgetStationNum() == 0) { + if ((WiFi.softAPgetStationNum() == 0) || (!mStopApAllowed)) { mCnt = 0; mDns.stop(); WiFi.mode(WIFI_AP_STA); @@ -105,7 +106,7 @@ void ahoywifi::tickWifiLoop() { } break; case DISCONNECTED: - if (WiFi.softAPgetStationNum() > 0) { + if ((WiFi.softAPgetStationNum() > 0) && (mStopApAllowed)) { mStaConn = IN_AP_MODE; // first time switch to AP Mode if (mScanActive) { @@ -182,10 +183,12 @@ void ahoywifi::tickWifiLoop() { break; case GOT_IP: welcome(WiFi.localIP().toString(), F(" (Station)")); - WiFi.softAPdisconnect(); - WiFi.mode(WIFI_STA); - DBGPRINTLN(F("[WiFi] AP disabled")); - delay(100); + if(mStopApAllowed) { + WiFi.softAPdisconnect(); + WiFi.mode(WIFI_STA); + DBGPRINTLN(F("[WiFi] AP disabled")); + delay(100); + } mAppWifiCb(true); mGotDisconnect = false; mStaConn = IN_STA_MODE; diff --git a/src/wifi/ahoywifi.h b/src/wifi/ahoywifi.h index e44d6858..36837600 100644 --- a/src/wifi/ahoywifi.h +++ b/src/wifi/ahoywifi.h @@ -28,6 +28,13 @@ class ahoywifi { bool getNtpTime(void); void scanAvailNetworks(void); bool getAvailNetworks(JsonObject obj); + void setStopApAllowedMode(bool allowed) { + mStopApAllowed = allowed; + } + String getStationIp(void) { + return WiFi.localIP().toString(); + } + void setupStation(void); private: typedef enum WiFiStatus { @@ -43,7 +50,6 @@ class ahoywifi { void setupWifi(bool startAP); void setupAp(void); - void setupStation(void); void sendNTPpacket(IPAddress& address); void sortRSSI(int *sort, int n); bool getBSSIDs(void); @@ -78,6 +84,7 @@ class ahoywifi { bool mScanActive; bool mGotDisconnect; std::list mBSSIDList; + bool mStopApAllowed; }; #endif /*__AHOYWIFI_H__*/ From 8156cd9d30685ee661134f707857b294f17767df Mon Sep 17 00:00:00 2001 From: Frank Date: Sat, 6 Jan 2024 09:23:29 +0100 Subject: [PATCH 027/179] PROMETHEUS_EP: Update documentation acc. to v0.8.42 --- doc/prometheus_ep_description.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/prometheus_ep_description.md b/doc/prometheus_ep_description.md index 6b4ce56c..4b266da4 100644 --- a/doc/prometheus_ep_description.md +++ b/doc/prometheus_ep_description.md @@ -34,10 +34,10 @@ Prometheus metrics provided at `/metrics`. | `ahoy_solar_inverter_radio_frame_cnt` | Counter | NRF24 statistic of inverter | inverter | | `ahoy_solar_inverter_radio_tx_cnt` | Counter | NRF24 statistic of inverter | inverter | | `ahoy_solar_inverter_radio_retransmits` | Counter | NRF24 statistic of inverter | inverter | -| `ahoy_solar_inverter_radio_iv_rx_cnt` | Counter | NRF24 statistic of inverter | inverter | -| `ahoy_solar_inverter_radio_iv_tx_cnt` | Counter | NRF24 statistic of inverter | inverter | -| `ahoy_solar_inverter_radio_dtu_rx_cnt` | Counter | NRF24 statistic of inverter | inverter | -| `ahoy_solar_inverter_radio_dtu_rx_cnt` | Counter | NRF24 statistic of inverter | inverter | +| `ahoy_solar_inverter_radio_iv_loss_cnt` | Counter | NRF24 statistic of inverter | inverter | +| `ahoy_solar_inverter_radio_iv_sent_cnt` | Counter | NRF24 statistic of inverter | inverter | +| `ahoy_solar_inverter_radio_dtu_loss_cnt` | Counter | NRF24 statistic of inverter | inverter | +| `ahoy_solar_inverter_radio_dtu_sent_cnt` | Counter | NRF24 statistic of inverter | inverter | | `ahoy_solar_U_AC_volt` | Gauge | AC voltage of inverter [V] | inverter | | `ahoy_solar_I_AC_ampere` | Gauge | AC current of inverter [A] | inverter | | `ahoy_solar_P_AC_watt` | Gauge | AC power of inverter [W] | inverter | From 1aa14ebf0871d89b3f5527ac870d702ea0181d5c Mon Sep 17 00:00:00 2001 From: lumapu Date: Sat, 6 Jan 2024 10:07:41 +0100 Subject: [PATCH 028/179] 0.8.45 fix compile --- src/web/RestApi.h | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/web/RestApi.h b/src/web/RestApi.h index cea845a2..de4fe8e3 100644 --- a/src/web/RestApi.h +++ b/src/web/RestApi.h @@ -838,13 +838,16 @@ class RestApi { mTimezoneOffset = jsonIn[F("val")]; else if(F("discovery_cfg") == jsonIn[F("cmd")]) mApp->setMqttDiscoveryFlag(); // for homeassistant + #if !defined(ETHERNET) else if(F("save_wifi") == jsonIn[F("cmd")]) { snprintf(mConfig->sys.stationSsid, SSID_LEN, "%s", jsonIn[F("ssid")].as()); snprintf(mConfig->sys.stationPwd, PWD_LEN, "%s", jsonIn[F("pwd")].as()); mApp->saveSettings(false); // without reboot mApp->setStopApAllowedMode(false); mApp->setupStation(); - } else if(F("save_iv") == jsonIn[F("cmd")]) { + } + #endif /* !defined(ETHERNET */ + else if(F("save_iv") == jsonIn[F("cmd")]) { Inverter<> *iv = mSys->getInverterByPos(jsonIn[F("id")], false); iv->config->enabled = jsonIn[F("en")]; iv->config->serial.u64 = jsonIn[F("ser")]; From dd225bdf79878baacef641aadee35ce1e84071b0 Mon Sep 17 00:00:00 2001 From: lumapu Date: Sat, 6 Jan 2024 14:05:49 +0100 Subject: [PATCH 029/179] 0.8.46 * improved communication --- src/CHANGES.md | 3 ++ src/defines.h | 2 +- src/hm/Communication.h | 2 +- src/hm/hmInverter.h | 48 ++++++++++++++---------------- src/plugins/Display/Display_data.h | 26 ++++++++-------- 5 files changed, 41 insertions(+), 40 deletions(-) diff --git a/src/CHANGES.md b/src/CHANGES.md index 34f9aacc..9313dbee 100644 --- a/src/CHANGES.md +++ b/src/CHANGES.md @@ -1,5 +1,8 @@ # Development Changes +## 0.8.46 - 2024-01-06 +* improved communication + ## 0.8.45 - 2024-01-05 * fix MqTT total values #1326 * start implementing a wizard for initial (WiFi) configuration #1199 diff --git a/src/defines.h b/src/defines.h index 4d3d7fdd..fcd3bbf7 100644 --- a/src/defines.h +++ b/src/defines.h @@ -13,7 +13,7 @@ //------------------------------------- #define VERSION_MAJOR 0 #define VERSION_MINOR 8 -#define VERSION_PATCH 45 +#define VERSION_PATCH 46 //------------------------------------- typedef struct { diff --git a/src/hm/Communication.h b/src/hm/Communication.h index 219cb92e..f69d19c7 100644 --- a/src/hm/Communication.h +++ b/src/hm/Communication.h @@ -118,7 +118,7 @@ class Communication : public CommQueue<> { mIsRetransmit = false; setAttempt(); if((q->cmd == AlarmData) || (q->cmd == GridOnProFilePara)) - incrAttempt(q->cmd == AlarmData? 5 : 3); + incrAttempt(15); mState = States::WAIT; break; diff --git a/src/hm/hmInverter.h b/src/hm/hmInverter.h index a5834d9a..4e714cfd 100644 --- a/src/hm/hmInverter.h +++ b/src/hm/hmInverter.h @@ -191,19 +191,18 @@ class Inverter { if(mNextLive) cb(RealTimeRunData_Debug, false); // get live data else { - mNextLive = true; if(actPowerLimit == 0xffff) cb(SystemConfigPara, false); // power limit info else if(InitDataState != devControlCmd) { cb(devControlCmd, false); // custom command which was received by API devControlCmd = InitDataState; mGetLossInterval = 1; - } else if((alarmLastId != alarmMesIndex) && (alarmMesIndex != 0)) - cb(AlarmData, false); // get last alarms - else if(0 == getFwVersion()) + } else if(0 == getFwVersion()) cb(InverterDevInform_All, false); // get firmware version else if(0 == getHwVersion()) cb(InverterDevInform_Simple, false); // get hardware version + else if((alarmLastId != alarmMesIndex) && (alarmMesIndex != 0)) + cb(AlarmData, false); // get last alarms else if((0 == mGridLen) && generalConfig->readGrid) { // read grid profile cb(GridOnProFilePara, false); } else if (mGetLossInterval > AHOY_GET_LOSS_INTERVAL) { // get loss rate @@ -339,28 +338,27 @@ class Inverter { } } } - else if (rec->assign == InfoAssignment) { - DPRINTLN(DBG_DEBUG, "add info"); - // eg. fw version ... - isConnected = true; - } - else if (rec->assign == SimpleInfoAssignment) { - DPRINTLN(DBG_DEBUG, "add simple info"); - // eg. hw version ... - } - else if (rec->assign == SystemConfigParaAssignment) { - DPRINTLN(DBG_DEBUG, "add config"); - if (getPosByChFld(0, FLD_ACT_ACTIVE_PWR_LIMIT, rec) == pos){ - actPowerLimit = rec->record[pos]; - DPRINT(DBG_DEBUG, F("Inverter actual power limit: ")); - DPRINTLN(DBG_DEBUG, String(actPowerLimit, 1)); - } - } - else if (rec->assign == AlarmDataAssignment) { - DPRINTLN(DBG_DEBUG, "add alarm"); + else { + mNextLive = true; + if (rec->assign == InfoAssignment) { + DPRINTLN(DBG_DEBUG, "add info"); + // eg. fw version ... + isConnected = true; + } else if (rec->assign == SimpleInfoAssignment) { + DPRINTLN(DBG_DEBUG, "add simple info"); + // eg. hw version ... + } else if (rec->assign == SystemConfigParaAssignment) { + DPRINTLN(DBG_DEBUG, "add config"); + if (getPosByChFld(0, FLD_ACT_ACTIVE_PWR_LIMIT, rec) == pos){ + actPowerLimit = rec->record[pos]; + DPRINT(DBG_DEBUG, F("Inverter actual power limit: ")); + DPRINTLN(DBG_DEBUG, String(actPowerLimit, 1)); + } + } else if (rec->assign == AlarmDataAssignment) { + DPRINTLN(DBG_DEBUG, "add alarm"); + } else + DPRINTLN(DBG_WARN, F("add with unknown assignment")); } - else - DPRINTLN(DBG_WARN, F("add with unknown assignment")); } else DPRINTLN(DBG_ERROR, F("addValue: assignment not found with cmd 0x")); diff --git a/src/plugins/Display/Display_data.h b/src/plugins/Display/Display_data.h index a400377d..a7a7ecee 100644 --- a/src/plugins/Display/Display_data.h +++ b/src/plugins/Display/Display_data.h @@ -4,19 +4,19 @@ #define __DISPLAY_DATA__ struct DisplayData { - const char *version=nullptr; - float totalPower=0.0f; // indicate current power (W) - float totalYieldDay=0.0f; // indicate day yield (Wh) - float totalYieldTotal=0.0f; // indicate total yield (kWh) - uint32_t utcTs=0; // indicate absolute timestamp (utc unix time). 0 = time is not synchonized - uint8_t nrProducing=0; // indicate number of producing inverters - uint8_t nrSleeping=0; // indicate number of sleeping inverters - bool WifiSymbol = false; // indicate if WiFi is connected - bool RadioSymbol = false; // indicate if radio module is connecting and working - bool MQTTSymbol = false; // indicate if MQTT is connected - int8_t WifiRSSI=SCHAR_MIN; // indicate RSSI value for WiFi - int8_t RadioRSSI=SCHAR_MIN; // indicate RSSI value for radio - IPAddress ipAddress; // indicate ip adress of ahoy + const char *version=nullptr; + float totalPower=0.0f; // indicate current power (W) + float totalYieldDay=0.0f; // indicate day yield (Wh) + float totalYieldTotal=0.0f; // indicate total yield (kWh) + uint32_t utcTs=0; // indicate absolute timestamp (utc unix time). 0 = time is not synchonized + uint8_t nrProducing=0; // indicate number of producing inverters + uint8_t nrSleeping=0; // indicate number of sleeping inverters + bool WifiSymbol = false; // indicate if WiFi is connected + bool RadioSymbol = false; // indicate if radio module is connecting and working + bool MQTTSymbol = false; // indicate if MQTT is connected + int8_t WifiRSSI=SCHAR_MIN; // indicate RSSI value for WiFi + int8_t RadioRSSI=SCHAR_MIN; // indicate RSSI value for radio + IPAddress ipAddress; // indicate ip adress of ahoy }; #endif /*__DISPLAY_DATA__*/ From 7a34b7e6167d8d62984371d71ce61cdf280c66a8 Mon Sep 17 00:00:00 2001 From: lumapu Date: Sat, 6 Jan 2024 17:45:53 +0100 Subject: [PATCH 030/179] 0.8.47 * reduce GxEPD2 lib to compile faster * upgraded GxEPD2 lib to `1.5.3` * updated espressif32 platform to `6.5.0` * updated U8g2 to `2.35.9` * started to convert deprecated functions of new ArduinoJson `7.0.0` --- patches/GxEPD2_SW_SPI.patch | 12 ++++---- scripts/reduceGxEPD2.py | 41 ++++++++++++++++++++++++++++ src/CHANGES.md | 7 +++++ src/config/settings.h | 20 +++++++------- src/defines.h | 2 +- src/platformio.ini | 32 +++++++++++----------- src/plugins/Display/Display_ePaper.h | 2 -- 7 files changed, 82 insertions(+), 34 deletions(-) create mode 100644 scripts/reduceGxEPD2.py diff --git a/patches/GxEPD2_SW_SPI.patch b/patches/GxEPD2_SW_SPI.patch index 9697eec8..dc3fa9ca 100644 --- a/patches/GxEPD2_SW_SPI.patch +++ b/patches/GxEPD2_SW_SPI.patch @@ -1,5 +1,5 @@ diff --git a/src/GxEPD2_EPD.cpp b/src/GxEPD2_EPD.cpp -index 1588444..592869b 100644 +index 8df8bef..91d7f49 100644 --- a/src/GxEPD2_EPD.cpp +++ b/src/GxEPD2_EPD.cpp @@ -19,9 +19,9 @@ @@ -71,7 +71,7 @@ index 1588444..592869b 100644 void GxEPD2_EPD::_reset() { if (_rst >= 0) -@@ -174,115 +169,201 @@ void GxEPD2_EPD::_waitWhileBusy(const char* comment, uint16_t busy_time) +@@ -174,115 +171,201 @@ void GxEPD2_EPD::_waitWhileBusy(const char* comment, uint16_t busy_time) void GxEPD2_EPD::_writeCommand(uint8_t c) { @@ -304,7 +304,7 @@ index 1588444..592869b 100644 + _endTransaction(); } diff --git a/src/GxEPD2_EPD.h b/src/GxEPD2_EPD.h -index ef2318f..50aa961 100644 +index 34c1145..c480b7d 100644 --- a/src/GxEPD2_EPD.h +++ b/src/GxEPD2_EPD.h @@ -8,6 +8,10 @@ @@ -334,7 +334,7 @@ index ef2318f..50aa961 100644 protected: void _reset(); void _waitWhileBusy(const char* comment = 0, uint16_t busy_time = 5000); -@@ -111,9 +115,14 @@ class GxEPD2_EPD +@@ -111,17 +115,22 @@ class GxEPD2_EPD void _startTransfer(); void _transfer(uint8_t value); void _endTransfer(); @@ -351,7 +351,9 @@ index ef2318f..50aa961 100644 bool _diag_enabled, _pulldown_rst_mode; - SPIClass* _pSPIx; SPISettings _spi_settings; -@@ -123,5 +124,5 @@ class GxEPD2_EPD + bool _initial_write, _initial_refresh; + bool _power_is_on, _using_partial_mode, _hibernating; + bool _init_display_done; uint16_t _reset_duration; - void (*_busy_callback)(const void*); + void (*_busy_callback)(const void*); diff --git a/scripts/reduceGxEPD2.py b/scripts/reduceGxEPD2.py new file mode 100644 index 00000000..f05e5c3b --- /dev/null +++ b/scripts/reduceGxEPD2.py @@ -0,0 +1,41 @@ +import os +import subprocess +import glob +Import("env") + +def rmDirWithFiles(path): + if os.path.isdir(path): + for f in glob.glob(path + "/*"): + os.remove(f) + os.rmdir(path) + +def clean(libName): + # save current wd + start = os.getcwd() + + if os.path.exists('.pio/libdeps/' + env['PIOENV'] + '/' + libName) == False: + print("path '" + '.pio/libdeps/' + env['PIOENV'] + '/' + libName + "' does not exist") + return + + os.chdir('.pio/libdeps/' + env['PIOENV'] + '/' + libName) + os.chdir('src/') + types = ('epd/*.h', 'epd/*.cpp') # the tuple of file types + files = [] + for t in types: + files.extend(glob.glob(t)) + + for f in files: + if f.count('GxEPD2_150_BN') == 0: + os.remove(f) + + rmDirWithFiles("epd3c") + rmDirWithFiles("epd4c") + rmDirWithFiles("epd7c") + rmDirWithFiles("gdeq") + rmDirWithFiles("gdey") + rmDirWithFiles("it8951") + + os.chdir(start) + + +clean("GxEPD2") diff --git a/src/CHANGES.md b/src/CHANGES.md index 9313dbee..e91179f7 100644 --- a/src/CHANGES.md +++ b/src/CHANGES.md @@ -1,5 +1,12 @@ # Development Changes +## 0.8.47 - 2024-01-06 +* reduce GxEPD2 lib to compile faster +* upgraded GxEPD2 lib to `1.5.3` +* updated espressif32 platform to `6.5.0` +* updated U8g2 to `2.35.9` +* started to convert deprecated functions of new ArduinoJson `7.0.0` + ## 0.8.46 - 2024-01-06 * improved communication diff --git a/src/config/settings.h b/src/config/settings.h index 476f9fbf..8ce71167 100644 --- a/src/config/settings.h +++ b/src/config/settings.h @@ -308,18 +308,18 @@ class settings { DynamicJsonDocument json(MAX_ALLOWED_BUF_SIZE); JsonObject root = json.to(); json[F("version")] = CONFIG_VERSION; - jsonNetwork(root.createNestedObject(F("wifi")), true); - jsonNrf(root.createNestedObject(F("nrf")), true); + jsonNetwork(root[F("wifi")].to(), true); + jsonNrf(root[F("nrf")].to(), true); #if defined(ESP32) - jsonCmt(root.createNestedObject(F("cmt")), true); + jsonCmt(root[F("cmt")].to(), true); #endif - jsonNtp(root.createNestedObject(F("ntp")), true); - jsonSun(root.createNestedObject(F("sun")), true); - jsonSerial(root.createNestedObject(F("serial")), true); - jsonMqtt(root.createNestedObject(F("mqtt")), true); - jsonLed(root.createNestedObject(F("led")), true); - jsonPlugin(root.createNestedObject(F("plugin")), true); - jsonInst(root.createNestedObject(F("inst")), true); + jsonNtp(root[F("ntp")].to(), true); + jsonSun(root[F("sun")].to(), true); + jsonSerial(root[F("serial")].to(), true); + jsonMqtt(root[F("mqtt")].to(), true); + jsonLed(root[F("led")].to(), true); + jsonPlugin(root[F("plugin")].to(), true); + jsonInst(root[F("inst")].to(), true); DPRINT(DBG_INFO, F("memory usage: ")); DBGPRINTLN(String(json.memoryUsage())); diff --git a/src/defines.h b/src/defines.h index fcd3bbf7..87b4101d 100644 --- a/src/defines.h +++ b/src/defines.h @@ -13,7 +13,7 @@ //------------------------------------- #define VERSION_MAJOR 0 #define VERSION_MINOR 8 -#define VERSION_PATCH 46 +#define VERSION_PATCH 47 //------------------------------------- typedef struct { diff --git a/src/platformio.ini b/src/platformio.ini index 2b4b25cf..9b204f2e 100644 --- a/src/platformio.ini +++ b/src/platformio.ini @@ -22,6 +22,7 @@ extra_scripts = pre:../scripts/auto_firmware_version.py pre:../scripts/convertHtml.py pre:../scripts/applyPatches.py + pre:../scripts/reduceGxEPD2.py lib_deps = https://github.com/yubox-node-org/ESPAsyncWebServer @@ -30,8 +31,8 @@ lib_deps = https://github.com/bertmelis/espMqttClient#v1.5.0 bblanchon/ArduinoJson @ ^6.21.3 https://github.com/JChristensen/Timezone @ ^1.2.4 - olikraus/U8g2 @ ^2.35.7 - https://github.com/zinggjm/GxEPD2 @ ^1.5.2 + olikraus/U8g2 @ ^2.35.9 + https://github.com/zinggjm/GxEPD2 @ ^1.5.3 build_flags = -std=c++17 -std=gnu++17 @@ -71,7 +72,7 @@ monitor_filters = esp8266_exception_decoder [env:esp32-wroom32] -platform = espressif32@6.4.0 +platform = espressif32@6.5.0 board = lolin_d32 build_flags = ${env.build_flags} -DUSE_HSPI_FOR_EPD @@ -79,7 +80,7 @@ monitor_filters = esp32_exception_decoder [env:esp32-wroom32-prometheus] -platform = espressif32@6.4.0 +platform = espressif32@6.5.0 board = lolin_d32 build_flags = ${env.build_flags} -DUSE_HSPI_FOR_EPD @@ -98,8 +99,8 @@ lib_deps = https://github.com/bertmelis/espMqttClient#v1.5.0 bblanchon/ArduinoJson @ ^6.21.3 https://github.com/JChristensen/Timezone @ ^1.2.4 - olikraus/U8g2 @ ^2.35.7 - zinggjm/GxEPD2 @ ^1.5.2 + olikraus/U8g2 @ ^2.35.9 + https://github.com/zinggjm/GxEPD2 @ ^1.5.3 build_flags = ${env.build_flags} -D ETHERNET -DRELEASE @@ -110,7 +111,7 @@ monitor_filters = esp32_exception_decoder [env:esp32-s2-mini] -platform = espressif32@6.4.0 +platform = espressif32@6.5.0 board = lolin_s2_mini build_flags = ${env.build_flags} -DUSE_HSPI_FOR_EPD @@ -124,7 +125,7 @@ monitor_filters = esp32_exception_decoder [env:esp32-c3-mini] -platform = espressif32@6.4.0 +platform = espressif32@6.5.0 board = lolin_c3_mini build_flags = ${env.build_flags} -DUSE_HSPI_FOR_EPD @@ -139,7 +140,7 @@ monitor_filters = [env:opendtufusion] -platform = espressif32@6.4.0 +platform = espressif32@6.5.0 board = esp32-s3-devkitc-1 upload_protocol = esp-builtin build_flags = ${env.build_flags} @@ -162,7 +163,7 @@ monitor_filters = esp32_exception_decoder, colorize [env:opendtufusion-ethernet] -platform = espressif32@6.4.0 +platform = espressif32@6.5.0 board = esp32-s3-devkitc-1 lib_deps = khoih-prog/AsyncWebServer_ESP32_W5500 @@ -172,13 +173,12 @@ lib_deps = https://github.com/bertmelis/espMqttClient#v1.5.0 bblanchon/ArduinoJson @ ^6.21.3 https://github.com/JChristensen/Timezone @ ^1.2.4 - olikraus/U8g2 @ ^2.35.7 - zinggjm/GxEPD2 @ ^1.5.2 + olikraus/U8g2 @ ^2.35.9 + https://github.com/zinggjm/GxEPD2 @ ^1.5.3 upload_protocol = esp-builtin build_flags = ${env.build_flags} -DETHERNET -DSPI_HAL - -DUSE_HSPI_FOR_EPD -DDEF_ETH_CS_PIN=42 -DDEF_ETH_SCK_PIN=39 -DDEF_ETH_MISO_PIN=41 @@ -205,7 +205,7 @@ monitor_filters = esp32_exception_decoder, colorize [env:opendtufusion-dev] -platform = espressif32@6.4.0 +platform = espressif32@6.5.0 board = esp32-s3-devkitc-1 lib_deps = https://github.com/yubox-node-org/ESPAsyncWebServer @@ -214,8 +214,8 @@ lib_deps = https://github.com/bertmelis/espMqttClient#v1.5.0 bblanchon/ArduinoJson @ ^6.21.3 https://github.com/JChristensen/Timezone @ ^1.2.4 - olikraus/U8g2 @ ^2.35.7 - https://github.com/zinggjm/GxEPD2 @ ^1.5.2 + olikraus/U8g2 @ ^2.35.9 + https://github.com/zinggjm/GxEPD2 @ ^1.5.3 upload_protocol = esp-builtin build_flags = ${env.build_flags} -DDEF_NRF_CS_PIN=37 diff --git a/src/plugins/Display/Display_ePaper.h b/src/plugins/Display/Display_ePaper.h index c9a0fbf5..0e077601 100644 --- a/src/plugins/Display/Display_ePaper.h +++ b/src/plugins/Display/Display_ePaper.h @@ -9,11 +9,9 @@ // enable GxEPD2_GFX base class #define ENABLE_GxEPD2_GFX 1 -#include #include #include -#include // FreeFonts from Adafruit_GFX #include #include From aa6a022214a5a83e33c3f0d7b2dc1bee8301ba33 Mon Sep 17 00:00:00 2001 From: Patrick Amrhein Date: Sat, 6 Jan 2024 19:45:18 +0100 Subject: [PATCH 031/179] Add defines for Retry ATTEMPS --- src/hm/CommQueue.h | 18 ++++++++++++++---- src/hm/Communication.h | 12 ++++++------ 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/src/hm/CommQueue.h b/src/hm/CommQueue.h index bb815a7e..eb770bb0 100644 --- a/src/hm/CommQueue.h +++ b/src/hm/CommQueue.h @@ -11,6 +11,10 @@ #include "hmInverter.h" #include "../utils/dbg.h" +#define DEFAULT_ATTEMPS 10 +#define MORE_ATTEMPS_ALARMDATA 15 +#define MORE_ATTEMPS_GRIDONPROFILEPARA 15 + template class CommQueue { public: @@ -44,11 +48,12 @@ class CommQueue { Inverter<> *iv; uint8_t cmd; uint8_t attempts; + uint8_t attemptsMax; uint32_t ts; bool isDevControl; queue_s() {} queue_s(Inverter<> *i, uint8_t c, bool dev) : - iv(i), cmd(c), attempts(5), ts(0), isDevControl(dev) {} + iv(i), cmd(c), attempts(DEFAULT_ATTEMPS), attemptsMax(DEFAULT_ATTEMPS), ts(0), isDevControl(dev) {} }; protected: @@ -59,8 +64,10 @@ class CommQueue { void add(const queue_s *q, bool rstAttempts = false) { mQueue[mWrPtr] = *q; - if(rstAttempts) - mQueue[mWrPtr].attempts = 5; + if(rstAttempts) { + mQueue[mWrPtr].attempts = DEFAULT_ATTEMPS; + mQueue[mWrPtr].attemptsMax = DEFAULT_ATTEMPS; + } inc(&mWrPtr); } @@ -79,7 +86,8 @@ class CommQueue { void cmdDone(bool keep = false) { if(keep) { - mQueue[mRdPtr].attempts = 5; + mQueue[mRdPtr].attempts = DEFAULT_ATTEMPS; + mQueue[mRdPtr].attemptsMax = DEFAULT_ATTEMPS; add(mQueue[mRdPtr]); // add to the end again } inc(&mRdPtr); @@ -96,6 +104,8 @@ class CommQueue { void incrAttempt(uint8_t attempts = 1) { mQueue[mRdPtr].attempts += attempts; + if (mQueue[mRdPtr].attempts > mQueue[mRdPtr].attemptsMax) + mQueue[mRdPtr].attemptsMax = mQueue[mRdPtr].attempts; } void inc(uint8_t *ptr) { diff --git a/src/hm/Communication.h b/src/hm/Communication.h index f69d19c7..3c7c82a5 100644 --- a/src/hm/Communication.h +++ b/src/hm/Communication.h @@ -118,8 +118,8 @@ class Communication : public CommQueue<> { mIsRetransmit = false; setAttempt(); if((q->cmd == AlarmData) || (q->cmd == GridOnProFilePara)) - incrAttempt(15); - + incrAttempt(q->cmd == AlarmData? MORE_ATTEMPS_ALARMDATA : MORE_ATTEMPS_GRIDONPROFILEPARA); +/// statt 5:3 mState = States::WAIT; break; @@ -194,7 +194,7 @@ class Communication : public CommQueue<> { return; } } else { - mHeu.evalTxChQuality(q->iv, true, (4 - q->attempts), q->iv->curFrmCnt); + mHeu.evalTxChQuality(q->iv, true, (q->attemptsMax - 1 - q->attempts), q->iv->curFrmCnt); if(((q->cmd == 0x39) && (q->iv->type == INV_TYPE_4CH)) || ((q->cmd == MI_REQ_CH2) && (q->iv->type == INV_TYPE_2CH)) || ((q->cmd == MI_REQ_CH1) && (q->iv->type == INV_TYPE_1CH))) { @@ -529,7 +529,7 @@ class Communication : public CommQueue<> { private: void closeRequest(const queue_s *q, bool crcPass) { - mHeu.evalTxChQuality(q->iv, crcPass, (4 - q->attempts), q->iv->curFrmCnt); + mHeu.evalTxChQuality(q->iv, crcPass, (q->attemptsMax - 1 - q->attempts), q->iv->curFrmCnt); if(crcPass) q->iv->radioStatistics.rxSuccess++; else if(q->iv->mGotFragment) @@ -730,7 +730,7 @@ class Communication : public CommQueue<> { miStsConsolidate(q, datachan, rec, p->packet[23], p->packet[24]); if (p->packet[0] < (0x39 + ALL_FRAMES) ) { - mHeu.evalTxChQuality(q->iv, true, (4 - q->attempts), 1); + mHeu.evalTxChQuality(q->iv, true, (q->attemptsMax - 1 - q->attempts), 1); miNextRequest((p->packet[0] - ALL_FRAMES + 1), q); } else { q->iv->miMultiParts = 7; // indicate we are ready @@ -739,7 +739,7 @@ class Communication : public CommQueue<> { } else if((p->packet[0] == (MI_REQ_CH1 + ALL_FRAMES)) && (q->iv->type == INV_TYPE_2CH)) { //addImportant(q->iv, MI_REQ_CH2); miNextRequest(MI_REQ_CH2, q); - mHeu.evalTxChQuality(q->iv, true, (4 - q->attempts), q->iv->curFrmCnt); + mHeu.evalTxChQuality(q->iv, true, (q->attemptsMax - 1 - q->attempts), q->iv->curFrmCnt); //use also miMultiParts here for better statistics? //mHeu.setGotFragment(q->iv); } else { // first data msg for 1ch, 2nd for 2ch From b765916a2af3f0229eeae1da8d93277566c6dfb4 Mon Sep 17 00:00:00 2001 From: lumapu Date: Sun, 7 Jan 2024 02:36:19 +0100 Subject: [PATCH 032/179] 0.8.47 * started to have german translations of all variants (environments) #925 #1199 --- .github/workflows/compile_development.yml | 23 +- scripts/convertHtml.py | 39 +- scripts/getVersion.py | 54 + src/CHANGES.md | 1 + src/config/config.h | 4 +- src/platformio.ini | 144 ++- src/web/html/includes/header.html | 2 +- src/web/html/includes/nav.html | 10 +- src/web/html/index.html | 53 +- src/web/html/save.html | 8 +- src/web/html/setup.html | 288 +++-- src/web/html/system.html | 36 +- src/web/html/update.html | 8 +- src/web/html/visualization.html | 136 +-- src/web/html/wizard.html | 28 +- src/web/lang.json | 1334 +++++++++++++++++++++ 16 files changed, 1869 insertions(+), 299 deletions(-) create mode 100644 src/web/lang.json diff --git a/.github/workflows/compile_development.yml b/.github/workflows/compile_development.yml index 6eba4879..4b688fca 100644 --- a/.github/workflows/compile_development.yml +++ b/.github/workflows/compile_development.yml @@ -43,7 +43,28 @@ jobs: pip install --upgrade platformio - name: Run PlatformIO - run: pio run -d src --environment esp8266 --environment esp8266-prometheus --environment esp8285 --environment esp32-wroom32 --environment esp32-wroom32-prometheus --environment esp32-wroom32-ethernet --environment esp32-s2-mini --environment esp32-c3-mini --environment opendtufusion --environment opendtufusion-ethernet + run: > + pio run -d src + --environment esp8266 + --environment esp8266-prometheus + --environment esp8285 + --environment esp32-wroom32 + --environment esp32-wroom32-prometheus + --environment esp32-wroom32-ethernet + --environment esp32-s2-mini + --environment esp32-c3-mini + --environment opendtufusion + --environment opendtufusion-ethernet + --environment esp8266-de + --environment esp8266-prometheus-de + --environment esp8285-de + --environment esp32-wroom32-de + --environment esp32-wroom32-prometheus-de + --environment esp32-wroom32-ethernet-de + --environment esp32-s2-mini-de + --environment esp32-c3-mini-de + --environment opendtufusion-de + --environment opendtufusion-ethernet-de - name: Copy boot_app0.bin run: cp ~/.platformio/packages/framework-arduinoespressif32/tools/partitions/boot_app0.bin src/.pio/build/opendtufusion/ota.bin diff --git a/scripts/convertHtml.py b/scripts/convertHtml.py index 6eaa92a3..4e01a875 100644 --- a/scripts/convertHtml.py +++ b/scripts/convertHtml.py @@ -3,6 +3,7 @@ import os import gzip import glob import shutil +import json from datetime import date from pathlib import Path import subprocess @@ -33,7 +34,7 @@ def readVersion(path): ver += line[p+13:].rstrip() + "." return ver[:-1] -def htmlParts(file, header, nav, footer, version): +def htmlParts(file, header, nav, footer, version, lang): p = ""; f = open(file, "r") lines = f.readlines() @@ -64,6 +65,8 @@ def htmlParts(file, header, nav, footer, version): # remove if - endif ESP32 p = checkIf(p) + p = translate(file, p, lang) + p = translate("general", p, lang) # menu / header / footer f = open("tmp/" + file, "w") f.write(p); @@ -94,7 +97,30 @@ def checkIf(data): return data -def convert2Header(inFile, version): +def findLang(file): + with open('../lang.json') as j: + lang = json.load(j) + + for l in lang["files"]: + if l["name"] == file: + return l + + return None + +def translate(file, data, lang="de"): + json = findLang(file) + + if None != json: + matches = re.findall(r'\{\#([A-Z0-9_]+)\}', data) + for x in matches: + for e in json["list"]: + if x == e["token"]: + #print("replace " + "{#" + x + "}" + " with " + e[lang]) + data = data.replace("{#" + x + "}", e[lang]) + return data + + +def convert2Header(inFile, version, lang): fileType = inFile.split(".")[1] define = inFile.split(".")[0].upper() define2 = inFile.split(".")[1].upper() @@ -114,7 +140,7 @@ def convert2Header(inFile, version): f.close() else: if fileType == "html": - data = htmlParts(inFile, "includes/header.html", "includes/nav.html", "includes/footer.html", version) + data = htmlParts(inFile, "includes/header.html", "includes/nav.html", "includes/footer.html", version, lang) else: f = open(inFile, "r") data = f.read() @@ -169,6 +195,11 @@ Path("tmp").mkdir(exist_ok=True) # created to check if webpages are valid with a shutil.copyfile("style.css", "tmp/style.css") version = readVersion("../../defines.h") +# get language from environment +lang = "en" +if env['PIOENV'][-3:] == "-de": + lang = "de" + # go throw the array for val in files_grabbed: - convert2Header(val, version) + convert2Header(val, version, lang) diff --git a/scripts/getVersion.py b/scripts/getVersion.py index cdb39ae8..ce34d26e 100644 --- a/scripts/getVersion.py +++ b/scripts/getVersion.py @@ -59,6 +59,7 @@ def readVersion(path, infile): os.mkdir(path + "firmware/ESP32-S3-ETH/") sha = os.getenv("SHA",default="sha") +## ENGLISH VERSIONS versionout = version[:-1] + "_" + sha + "_esp8266.bin" src = path + ".pio/build/esp8266/firmware.bin" dst = path + "firmware/ESP8266/" + versionout @@ -110,6 +111,59 @@ def readVersion(path, infile): dst = path + "firmware/ESP32-S3-ETH/" + versionout os.rename(src, dst) +## GERMAN VERSIONS + versionout = version[:-1] + "_" + sha + "_esp8266-de.bin" + src = path + ".pio/build/esp8266-de/firmware.bin" + dst = path + "firmware/ESP8266/" + versionout + os.rename(src, dst) + + versionout = version[:-1] + "_" + sha + "_esp8266_prometheus-de.bin" + src = path + ".pio/build/esp8266-prometheus-de/firmware.bin" + dst = path + "firmware/ESP8266/" + versionout + os.rename(src, dst) + + versionout = version[:-1] + "_" + sha + "_esp8285-de.bin" + src = path + ".pio/build/esp8285-de/firmware.bin" + dst = path + "firmware/ESP8285/" + versionout + os.rename(src, dst) + gzip_bin(dst, dst + ".gz") + + versionout = version[:-1] + "_" + sha + "_esp32-de.bin" + src = path + ".pio/build/esp32-wroom32-de/firmware.bin" + dst = path + "firmware/ESP32/" + versionout + os.rename(src, dst) + + versionout = version[:-1] + "_" + sha + "_esp32_prometheus-de.bin" + src = path + ".pio/build/esp32-wroom32-prometheus-de/firmware.bin" + dst = path + "firmware/ESP32/" + versionout + os.rename(src, dst) + + versionout = version[:-1] + "_" + sha + "_esp32_ethernet-de.bin" + src = path + ".pio/build/esp32-wroom32-ethernet-de/firmware.bin" + dst = path + "firmware/ESP32/" + versionout + os.rename(src, dst) + + versionout = version[:-1] + "_" + sha + "_esp32s2-mini-de.bin" + src = path + ".pio/build/esp32-s2-mini-de/firmware.bin" + dst = path + "firmware/ESP32-S2/" + versionout + os.rename(src, dst) + + versionout = version[:-1] + "_" + sha + "_esp32c3-mini-de.bin" + src = path + ".pio/build/esp32-c3-mini-de/firmware.bin" + dst = path + "firmware/ESP32-C3/" + versionout + os.rename(src, dst) + + versionout = version[:-1] + "_" + sha + "_esp32s3-de.bin" + src = path + ".pio/build/opendtufusion-de/firmware.bin" + dst = path + "firmware/ESP32-S3/" + versionout + os.rename(src, dst) + + versionout = version[:-1] + "_" + sha + "_esp32s3_ethernet-de.bin" + src = path + ".pio/build/opendtufusion-ethernet-de/firmware.bin" + dst = path + "firmware/ESP32-S3-ETH/" + versionout + os.rename(src, dst) + +## BOOTLOADER AND PARTITIONS # other ESP32 bin files src = path + ".pio/build/esp32-wroom32/" dst = path + "firmware/ESP32/" diff --git a/src/CHANGES.md b/src/CHANGES.md index e91179f7..c403f8b0 100644 --- a/src/CHANGES.md +++ b/src/CHANGES.md @@ -6,6 +6,7 @@ * updated espressif32 platform to `6.5.0` * updated U8g2 to `2.35.9` * started to convert deprecated functions of new ArduinoJson `7.0.0` +* started to have german translations of all variants (environments) #925 #1199 ## 0.8.46 - 2024-01-06 * improved communication diff --git a/src/config/config.h b/src/config/config.h index fb05a1cc..40cb5b76 100644 --- a/src/config/config.h +++ b/src/config/config.h @@ -1,6 +1,6 @@ //----------------------------------------------------------------------------- -// 2022 Ahoy, https://www.mikrocontroller.net/topic/525778 -// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/ +// 2024 Ahoy, https://www.mikrocontroller.net/topic/525778 +// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/4.0/deed //----------------------------------------------------------------------------- #ifndef __CONFIG_H__ diff --git a/src/platformio.ini b/src/platformio.ini index 9b204f2e..e9eb282b 100644 --- a/src/platformio.ini +++ b/src/platformio.ini @@ -50,6 +50,16 @@ build_flags = ${env.build_flags} monitor_filters = esp8266_exception_decoder +[env:esp8266-de] +platform = espressif8266 +board = esp12e +board_build.f_cpu = 80000000L +build_flags = ${env.build_flags} + -DEMC_MIN_FREE_MEMORY=4096 + -DLANG_DE + ;-Wl,-Map,output.map +monitor_filters = + esp8266_exception_decoder [env:esp8266-prometheus] platform = espressif8266 @@ -61,6 +71,17 @@ build_flags = ${env.build_flags} monitor_filters = esp8266_exception_decoder +[env:esp8266-prometheus-de] +platform = espressif8266 +board = esp12e +board_build.f_cpu = 80000000L +build_flags = ${env.build_flags} + -DENABLE_PROMETHEUS_EP + -DEMC_MIN_FREE_MEMORY=4096 + -DLANG_DE +monitor_filters = + esp8266_exception_decoder + [env:esp8285] platform = espressif8266 board = esp8285 @@ -71,6 +92,17 @@ build_flags = ${env.build_flags} monitor_filters = esp8266_exception_decoder +[env:esp8285-de] +platform = espressif8266 +board = esp8285 +board_build.ldscript = eagle.flash.1m64.ld +board_build.f_cpu = 80000000L +build_flags = ${env.build_flags} + -DEMC_MIN_FREE_MEMORY=4096 + -DLANG_DE +monitor_filters = + esp8266_exception_decoder + [env:esp32-wroom32] platform = espressif32@6.5.0 board = lolin_d32 @@ -79,6 +111,15 @@ build_flags = ${env.build_flags} monitor_filters = esp32_exception_decoder +[env:esp32-wroom32-de] +platform = espressif32@6.5.0 +board = lolin_d32 +build_flags = ${env.build_flags} + -DUSE_HSPI_FOR_EPD + -DLANG_DE +monitor_filters = + esp32_exception_decoder + [env:esp32-wroom32-prometheus] platform = espressif32@6.5.0 board = lolin_d32 @@ -88,6 +129,16 @@ build_flags = ${env.build_flags} monitor_filters = esp32_exception_decoder +[env:esp32-wroom32-prometheus-de] +platform = espressif32@6.5.0 +board = lolin_d32 +build_flags = ${env.build_flags} + -DUSE_HSPI_FOR_EPD + -DENABLE_PROMETHEUS_EP + -DLANG_DE +monitor_filters = + esp32_exception_decoder + [env:esp32-wroom32-ethernet] platform = espressif32 board = esp32dev @@ -110,6 +161,29 @@ build_flags = ${env.build_flags} monitor_filters = esp32_exception_decoder +[env:esp32-wroom32-ethernet-de] +platform = espressif32 +board = esp32dev +lib_deps = + khoih-prog/AsyncWebServer_ESP32_W5500 + khoih-prog/AsyncUDP_ESP32_W5500 + nrf24/RF24 @ ^1.4.8 + paulstoffregen/Time @ ^1.6.1 + https://github.com/bertmelis/espMqttClient#v1.5.0 + bblanchon/ArduinoJson @ ^6.21.3 + https://github.com/JChristensen/Timezone @ ^1.2.4 + olikraus/U8g2 @ ^2.35.9 + https://github.com/zinggjm/GxEPD2 @ ^1.5.3 +build_flags = ${env.build_flags} + -D ETHERNET + -DRELEASE + -DUSE_HSPI_FOR_EPD + -DLANG_DE + -DLOG_LOCAL_LEVEL=ESP_LOG_INFO + -DDEBUG_LEVEL=DBG_INFO +monitor_filters = + esp32_exception_decoder + [env:esp32-s2-mini] platform = espressif32@6.5.0 board = lolin_s2_mini @@ -124,6 +198,21 @@ build_flags = ${env.build_flags} monitor_filters = esp32_exception_decoder +[env:esp32-s2-mini-de] +platform = espressif32@6.5.0 +board = lolin_s2_mini +build_flags = ${env.build_flags} + -DUSE_HSPI_FOR_EPD + -DDEF_NRF_CS_PIN=12 + -DDEF_NRF_CE_PIN=3 + -DDEF_NRF_IRQ_PIN=5 + -DDEF_NRF_MISO_PIN=9 + -DDEF_NRF_MOSI_PIN=11 + -DDEF_NRF_SCLK_PIN=7 + -DLANG_DE +monitor_filters = + esp32_exception_decoder + [env:esp32-c3-mini] platform = espressif32@6.5.0 board = lolin_c3_mini @@ -138,6 +227,20 @@ build_flags = ${env.build_flags} monitor_filters = esp32_exception_decoder +[env:esp32-c3-mini-de] +platform = espressif32@6.5.0 +board = lolin_c3_mini +build_flags = ${env.build_flags} + -DUSE_HSPI_FOR_EPD + -DDEF_NRF_CS_PIN=5 + -DDEF_NRF_CE_PIN=0 + -DDEF_NRF_IRQ_PIN=1 + -DDEF_NRF_MISO_PIN=3 + -DDEF_NRF_MOSI_PIN=4 + -DDEF_NRF_SCLK_PIN=2 + -DLANG_DE +monitor_filters = + esp32_exception_decoder [env:opendtufusion] platform = espressif32@6.5.0 @@ -162,6 +265,30 @@ build_flags = ${env.build_flags} monitor_filters = esp32_exception_decoder, colorize +[env:opendtufusion-de] +platform = espressif32@6.5.0 +board = esp32-s3-devkitc-1 +upload_protocol = esp-builtin +build_flags = ${env.build_flags} + -DDEF_NRF_CS_PIN=37 + -DDEF_NRF_CE_PIN=38 + -DDEF_NRF_IRQ_PIN=47 + -DDEF_NRF_MISO_PIN=48 + -DDEF_NRF_MOSI_PIN=35 + -DDEF_NRF_SCLK_PIN=36 + -DDEF_CMT_CSB=4 + -DDEF_CMT_FCSB=21 + -DDEF_CMT_IRQ=8 + -DDEF_CMT_SDIO=5 + -DDEF_CMT_SCLK=6 + -DDEF_LED0=18 + -DDEF_LED1=17 + -DLED_ACTIVE_HIGH + -DARDUINO_USB_MODE=1 + -DLANG_DE +monitor_filters = + esp32_exception_decoder, colorize + [env:opendtufusion-ethernet] platform = espressif32@6.5.0 board = esp32-s3-devkitc-1 @@ -204,11 +331,12 @@ build_flags = ${env.build_flags} monitor_filters = esp32_exception_decoder, colorize -[env:opendtufusion-dev] +[env:opendtufusion-ethernet-de] platform = espressif32@6.5.0 board = esp32-s3-devkitc-1 lib_deps = - https://github.com/yubox-node-org/ESPAsyncWebServer + khoih-prog/AsyncWebServer_ESP32_W5500 + khoih-prog/AsyncUDP_ESP32_W5500 https://github.com/nrf24/RF24 @ ^1.4.8 paulstoffregen/Time @ ^1.6.1 https://github.com/bertmelis/espMqttClient#v1.5.0 @@ -218,6 +346,14 @@ lib_deps = https://github.com/zinggjm/GxEPD2 @ ^1.5.3 upload_protocol = esp-builtin build_flags = ${env.build_flags} + -DETHERNET + -DSPI_HAL + -DDEF_ETH_CS_PIN=42 + -DDEF_ETH_SCK_PIN=39 + -DDEF_ETH_MISO_PIN=41 + -DDEF_ETH_MOSI_PIN=40 + -DDEF_ETH_IRQ_PIN=44 + -DDEF_ETH_RST_PIN=43 -DDEF_NRF_CS_PIN=37 -DDEF_NRF_CE_PIN=38 -DDEF_NRF_IRQ_PIN=47 @@ -233,7 +369,7 @@ build_flags = ${env.build_flags} -DDEF_LED1=17 -DLED_ACTIVE_HIGH -DARDUINO_USB_MODE=1 - -DARDUINO_USB_CDC_ON_BOOT=1 - -DSPI_HAL + #-DARDUINO_USB_CDC_ON_BOOT=1 + -DLANG_DE monitor_filters = esp32_exception_decoder, colorize diff --git a/src/web/html/includes/header.html b/src/web/html/includes/header.html index ab3b0545..c363304c 100644 --- a/src/web/html/includes/header.html +++ b/src/web/html/includes/header.html @@ -1,6 +1,6 @@ - + diff --git a/src/web/html/includes/nav.html b/src/web/html/includes/nav.html index 91de5047..c05099b7 100644 --- a/src/web/html/includes/nav.html +++ b/src/web/html/includes/nav.html @@ -6,16 +6,16 @@
- Live - Webserial - Settings + {#NAV_LIVE} + {#NAV_WEBSERIAL} + {#NAV_SETTINGS} Update System REST API - Documentation - About + {#NAV_DOCUMENTATION} + {#NAV_ABOUT} Login Logout diff --git a/src/web/html/index.html b/src/web/html/index.html index cfdaeee6..6ad2c787 100644 --- a/src/web/html/index.html +++ b/src/web/html/index.html @@ -20,18 +20,15 @@

-

Support this project:

+

{#SUPPORT}:

-

- This project was started from this discussion. (Mikrocontroller.net) -

@@ -46,11 +43,11 @@ function apiCb(obj) { var e = document.getElementById("apiResult"); if(obj.success) { - e.innerHTML = " command executed"; + e.innerHTML = " {#COMMAND_EXE}"; getAjax("/api/index", parse); } else - e.innerHTML = " Error: " + obj.error; + e.innerHTML = " {#ERROR}: " + obj.error; } function setTime() { @@ -92,7 +89,7 @@ else { dSpan.innerHTML = ""; var e = inp("set", "sync from browser", 0, ["btn"], "set", "button"); - dSpan.appendChild(span("NTP timeserver unreachable. ")); + dSpan.appendChild(span("{#NTP_UNREACH}. ")); dSpan.appendChild(e); dSpan.appendChild(span("", ["span"], "apiResult")); e.addEventListener("click", setTime); @@ -101,15 +98,15 @@ if(obj.disNightComm) { if(((obj.ts_sunrise + obj.ts_offsSr) < obj.ts_now) && ((obj.ts_sunset + obj.ts_offsSs) > obj.ts_now)) { - commInfo = "Polling inverter(s), will pause at sunset " + (new Date((obj.ts_sunset + obj.ts_offsSs) * 1000).toLocaleString('de-DE')); + commInfo = "{#POLLING_STOP} " + (new Date((obj.ts_sunset + obj.ts_offsSs) * 1000).toLocaleString('de-DE')); } else { - commInfo = "Night time, inverter polling disabled, "; + commInfo = "{#NIGHT_TIME}, "; if(obj.ts_now > (obj.ts_sunrise + obj.ts_offsSr)) { - commInfo += "paused at " + (new Date((obj.ts_sunset + obj.ts_offsSs) * 1000).toLocaleString('de-DE')); + commInfo += "{#PAUSED_AT} " + (new Date((obj.ts_sunset + obj.ts_offsSs) * 1000).toLocaleString('de-DE')); } else { - commInfo += "will start polling at " + (new Date((obj.ts_sunrise + obj.ts_offsSr) * 1000).toLocaleString('de-DE')); + commInfo += "{#START_AT} " + (new Date((obj.ts_sunrise + obj.ts_offsSr) * 1000).toLocaleString('de-DE')); } } } @@ -124,33 +121,33 @@ if(false == i["enabled"]) { icon = iconWarn; cl = "icon-warn"; - avail = "disabled"; + avail = "{#DISABLED}"; } else if((false == i["is_avail"]) || (0 == ts)) { icon = iconInfo; cl = "icon-info"; - avail = "not yet available"; + avail = "{#NOT_YET_AVAIL}"; } else if(0 == i["ts_last_success"]) { - avail = "available but no data was received until now"; + avail = "{#AVAIL_NO_DATA}"; } else { - avail = "available and is "; + avail = "{#AVAIL} "; if(false == i["is_producing"]) - avail += "not producing"; + avail += "{#NOT_PRODUCING}"; else { icon = iconSuccessFull; - avail += "producing " + i.cur_pwr + "W"; + avail += "{#PRODUCING} " + i.cur_pwr + "W"; } } p.append( svg(icon, 30, 30, "icon " + cl), - span("Inverter #" + i["id"] + ": " + i["name"] + " is " + avail), + span("{#INVERTER} #" + i["id"] + ": " + i["name"] + " {#IS} " + avail), br() ); if(false == i["is_avail"]) { if(i["ts_last_success"] > 0) { var date = new Date(i["ts_last_success"] * 1000); - p.append(span("-> last successful transmission: " + toIsoDateStr(date)), br()); + p.append(span("-> {#LAST_SUCCESS}: " + toIsoDateStr(date)), br()); } } } @@ -168,11 +165,11 @@ if(null != release) { if(getVerInt("{#VERSION}") < getVerInt(release)) - p.append(svg(iconInfo, 30, 30, "icon icon-info"), span("Update available, current released version: " + release), br()); + p.append(svg(iconInfo, 30, 30, "icon icon-info"), span("{#UPDATE_AVAIL}: " + release), br()); else if(getVerInt("{#VERSION}") > getVerInt(release)) - p.append(svg(iconInfo, 30, 30, "icon icon-info"), span("You are using development version {#VERSION}. In case of issues you may want to try the current stable release: " + release), br()); + p.append(svg(iconInfo, 30, 30, "icon icon-info"), span("{#USING_DEV_VERSION} {#VERSION}. {#DEV_ISSUE_RELEASE_VERSION}: " + release), br()); else - p.append(svg(iconInfo, 30, 30, "icon icon-info"), span("You are using the current stable release: " + release), br()); + p.append(svg(iconInfo, 30, 30, "icon icon-info"), span("{#RELEASE_INSTALLED}: " + release), br()); } document.getElementById("warn_info").replaceChildren(p); diff --git a/src/web/html/save.html b/src/web/html/save.html index 9b5b4864..e5d5a67d 100644 --- a/src/web/html/save.html +++ b/src/web/html/save.html @@ -8,7 +8,7 @@ {#HTML_NAV}
-
Saving settings...
+
{#SAVE_SETTINGS}
{#HTML_FOOTER} @@ -31,15 +31,15 @@ var meta = document.createElement('meta'); meta.httpEquiv = "refresh" if(!obj.reboot) { - html = "Settings successfully saved. Automatic page reload in 3 seconds."; + html = "{#SUCCESS_SAVED_RELOAD}"; meta.content = 3; } else { - html = "Settings successfully saved. Rebooting. Automatic redirect in " + obj.reload + " seconds."; + html = "{#SUCCESS_SAVED_REBOOT} " + obj.reload + " {#SECONDS}."; meta.content = obj.reload + "; URL=/"; } document.getElementsByTagName('head')[0].appendChild(meta); } else { - html = "Failed saving settings."; + html = "{#FAILED_SAVE}."; } } document.getElementById("html").innerHTML = html; diff --git a/src/web/html/setup.html b/src/web/html/setup.html index 782c23be..4903db16 100644 --- a/src/web/html/setup.html +++ b/src/web/html/setup.html @@ -1,7 +1,7 @@ - Setup + {#SETTINGS} {#HTML_HEADER} @@ -10,77 +10,74 @@
- +
- Device Host Name + {#DEVICE_NAME}
-
Device Name
+
{#DEVICE_NAME}
-
Reboot Ahoy at midnight
+
{#REBOOT_AT_MIDNIGHT}
-
Dark Mode
+
{#DARK_MODE}
-
(empty browser cache or use CTRL + F5 after reboot to apply this setting)
+
{#DARK_MODE_NOTE}
- System Config + {#PINOUT_CONFIGURATION}

Status LEDs

-

Radio (NRF24L01+)

+

{#RADIO} (NRF24L01+)

-

Radio (CMT2300A)

-
(ESP32 only)
+

{#RADIO} (CMT2300A)

+
-

Serial Console

+

{#SERIAL_CONSOLE}

-
print inverter data
+
{#LOG_PRINT_INVERTER_DATA}
-
Serial Debug
+
{#LOG_SERIAL_DEBUG}
-
Privacy Mode
+
{#LOG_PRIVACY_MODE}
-
Print whole traces in Log
+
{#LOG_PRINT_TRACES}
- +
WiFi
-
AP Password (min. length: 8)
+
{#AP_PWD}
- -

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

-
-
Search Networks
-
+
{#SEARCH_NETWORKS}
+
-
Avail Networks
+
{#AVAIL_NETWORKS}
@@ -89,26 +86,25 @@
-
SSID is hidden
+
{#SSID_HIDDEN}
-
Password
+
{#PASSWORD}
- Static IP (optional) + {#STATIC_IP}

- Leave fields blank for DHCP
- The following fields are parsed in this format: 192.168.4.1 + {#NETWORK_HINT_BLANK}

-
IP Address
+
{#IP_ADDRESS}
-
Submask
+
{#SUBMASK}
@@ -126,58 +122,58 @@
- +
- Protection + {#PROTECTION}
-
Admin Password
+
{#ADMIN_PASSWORD}
-

Select pages which should be protected by password

+

{#PROTECTION_NOTE}

- +
- Inverter + {#INVERTER}
-
Interval [s]
+
{#INTERVAL}
-
Inverter Gap [ms]
+
{#INV_GAP}
-
Reset values and YieldDay at midnight
+
{#INV_RESET_MIDNIGHT}
-
Reset values when inverter polling pauses at sunset
+
{#INV_PAUSE_SUNSET}
-
Reset values when inverter status is 'not available'
+
{#INV_RESET_NOT_AVAIL}
-
Reset 'max' values at midnight
+
{#INV_RESET_MAX_MIDNIGHT}
-
Start without time sync (useful in AP-Only-Mode)
+
{#INV_START_WITHOUT_TIME}
-
Read Grid Profile
+
{#INV_READ_GRID_PROFILE}
-
Yield Efficiency (Standard 1.0)
+
{#INV_YIELD_EFF}
@@ -196,42 +192,42 @@
-
NTP Interval (in Minutes, min. 5 Minutes)
+
{#NTP_INTERVAL}
-
set System time
+
{#NTP_SET_SYS_TIME}
- -
+ +
-
System Time
+
{#NTP_SYS_TIME}
- +
- Sunrise & Sunset + {#SUNRISE_SUNSET}
-
Latitude (decimal)
+
{#LATITUDE}
-
Longitude (decimal)
+
{#LONGITUDE}
-
Offset (sunrise)
+
{#OFFSET_SUNRISE}
-
Offset (sunset)
+
{#OFFSET_SUNSET}
@@ -254,20 +250,20 @@
-
Username (optional)
+
{#MQTT_USER}
-
Password (optional)
+
{MQTT_PASSWORD}
Topic
-

Send Inverter data in a fixed interval, even if there is no change. A value of '0' disables the fixed interval. The data is published once it was successfully received from inverter. (default: 0)

+

{#MQTT_NOTE}

-
Interval [s]
+
{#INTERVAL}
@@ -280,29 +276,29 @@
- +
- Display Config + {#DISPLAY_CONFIG}
-
Turn off while inverters are offline
+
{#DISP_OFF_INV}
-
Luminance
+
{#DISP_LUMINANCE}
-

Pinout

+

{#DISP_PINOUT}

-
Reboot device after successful save
+
{#BTN_REBOOT_SUCCESSFUL_SAVE}
@@ -311,11 +307,11 @@
- ERASE SETTINGS (not WiFi) + {#BTN_ERASE}
- Import / Export JSON Settings + {#IM_EXPORT}
-
Import
+
{#IMPORT}
@@ -326,9 +322,9 @@
-
Export
+
{#EXPORT}
- Export settings (JSON file) (only values, passwords will be removed!) + {#BTN_EXPORT} {#EXPORT_NOTE}
@@ -341,7 +337,7 @@ var ts = 0; var esp8266pins = [ - [255, "off / default"], + [255, "{#PIN_OFF}"], [0, "D3 (GPIO0)"], [1, "TX (GPIO1)"], [2, "D4 (GPIO2)"], @@ -358,12 +354,12 @@ [13, "D7 (GPIO13)"], [14, "D5 (GPIO14)"], [15, "D8 (GPIO15)"], - [16, "D0 (GPIO16 - no IRQ!)"] + [16, "D0 (GPIO16 - {#PIN_NO_IRQ})"] ]; /*IF_ESP32*/ var esp32pins = [ - [255, "off / default"], + [255, "{#PIN_OFF}"], [0, "GPIO0"], [1, "TX (GPIO1)"], [2, "GPIO2 (LED)"], @@ -386,14 +382,14 @@ [27, "GPIO27"], [32, "GPIO32"], [33, "GPIO33"], - [34, "GPIO34 (in only)"], - [35, "GPIO35 (in only)"], - [36, "VP (GPIO36, in only)"], - [39, "VN (GPIO39, in only)"] + [34, "GPIO34 ({#PIN_INPUT_ONLY})"], + [35, "GPIO35 ({#PIN_INPUT_ONLY})"], + [36, "VP (GPIO36, {#PIN_INPUT_ONLY})"], + [39, "VN (GPIO39, {#PIN_INPUT_ONLY})"] ]; var esp32s3pins = [ [255, "off / default"], - [0, "GPIO0 (DONT USE - BOOT)"], + [0, "GPIO0 ({#PIN_DONT_USE} - BOOT)"], [1, "GPIO1"], [2, "GPIO2"], [3, "GPIO3"], @@ -412,16 +408,16 @@ [16, "GPIO16"], [17, "GPIO17"], [18, "GPIO18"], - [19, "GPIO19 (DONT USE - USB-)"], - [20, "GPIO20 (DONT USE - USB+)"], + [19, "GPIO19 ({#PIN_DONT_USE} - USB-)"], + [20, "GPIO20 ({#PIN_DONT_USE} - USB+)"], [21, "GPIO21"], - [26, "GPIO26 (PSRAM - not available)"], - [27, "GPIO27 (FLASH - not available)"], - [28, "GPIO28 (FLASH - not available)"], - [29, "GPIO29 (FLASH - not available)"], - [30, "GPIO30 (FLASH - not available)"], - [31, "GPIO31 (FLASH - not available)"], - [32, "GPIO32 (FLASH - not available)"], + [26, "GPIO26 (PSRAM - {#PIN_NOT_AVAIL})"], + [27, "GPIO27 (FLASH - {#PIN_NOT_AVAIL})"], + [28, "GPIO28 (FLASH - {#PIN_NOT_AVAIL})"], + [29, "GPIO29 (FLASH - {#PIN_NOT_AVAIL})"], + [30, "GPIO30 (FLASH - {#PIN_NOT_AVAIL})"], + [31, "GPIO31 (FLASH - {#PIN_NOT_AVAIL})"], + [32, "GPIO32 (FLASH - {#PIN_NOT_AVAIL})"], [33, "GPIO33 (not exposed on WROOM modules)"], [34, "GPIO34 (not exposed on WROOM modules)"], [35, "GPIO35"], @@ -434,8 +430,8 @@ [42, "GPIO42"], [43, "GPIO43"], [44, "GPIO44"], - [45, "GPIO45 (DONT USE - STRAPPING PIN)"], - [46, "GPIO46 (DONT USE - STRAPPING PIN)"], + [45, "GPIO45 ({#PIN_DONT_USE} - STRAPPING PIN)"], + [46, "GPIO46 ({#PIN_DONT_USE} - STRAPPING PIN)"], [47, "GPIO47"], [48, "GPIO48"], ]; @@ -459,17 +455,17 @@ [15, "GPIO15 (PSRAM/FLASH)"], [16, "GPIO16 (PSRAM/FLASH)"], [17, "GPIO17 (PSRAM/FLASH)"], - [18, "GPIO18 (DONT USE - USB-)"], - [19, "GPIO19 (DONT USE - USB+)"], + [18, "GPIO18 ({#PIN_DONT_USE} - USB-)"], + [19, "GPIO19 ({#PIN_DONT_USE} - USB+)"], [20, "GPIO20 (RX)"], [21, "GPIO21 (TX)"], ]; /*ENDIF_ESP32*/ var nrfPa = [ - [0, "MIN (recommended)"], + [0, "MIN ({#PIN_RECOMMENDED})"], [1, "LOW"], [2, "HIGH"], - [3, "MAX (experimental)"] + [3, "MAX ({#PIN_EXPERIMENTAL})"] ]; var esp32cmtPa = []; var esp32cmtFreq = []; @@ -487,8 +483,8 @@ } /*ENDIF_ESP32*/ var led_high_active = [ - [0, "low active"], - [1, "high active"], + [0, "{#PIN_LOW_ACTIVE}"], + [1, "{#PIN_HIGH_ACTIVE}"], ]; window.onload = function() { @@ -513,31 +509,31 @@ var e = document.getElementById("networks"); selDelAllOpt(e); if(obj["success"]) - e.appendChild(opt("-1", "scanning ...")) + e.appendChild(opt("-1", "{#NETWORK_SCANNING}")) else - e.appendChild(opt("-1", "Error: " + obj["error"])); + e.appendChild(opt("-1", "{#ERROR} " + obj["error"])); } function apiCbNtp(obj) { var e = document.getElementById("apiResultNtp"); if(obj["success"]) - e.innerHTML = "command executed, set new time ..."; + e.innerHTML = "{#NTP_COMMAND_EXE}"; else - e.innerHTML = "Error: " + obj["error"]; + e.innerHTML = "{#ERROR} " + obj["error"]; } function apiCbNtp2(obj) { var e = document.getElementById("apiResultNtp"); var date = new Date(obj["ts_now"] * 1000); - e.innerHTML = "synced at: " + toIsoDateStr(date) + ", difference: " + (ts - obj["ts_now"]) + "ms"; + e.innerHTML = "{#NTP_SYNCED_AT}: " + toIsoDateStr(date) + ", {#NTP_DIFF}: " + (ts - obj["ts_now"]) + "ms"; } function apiCbMqtt(obj) { var e = document.getElementById("apiResultMqtt"); if(obj["success"]) - e.innerHTML = "command executed"; + e.innerHTML = "{#MQTT_EXE}"; else - e.innerHTML = "Error: " + obj["error"]; + e.innerHTML = "{#ERROR} " + obj["error"]; } function setTime() { @@ -572,7 +568,7 @@ function hide() { document.getElementById("form").submit(); var e = document.getElementById("content"); - e.replaceChildren(span("upload started")); + e.replaceChildren(span("{#IMPORT_UPLOAD_STARTED}")); } function delIv() { @@ -626,7 +622,7 @@ if(!obj["pwd_set"]) e.value = ""; var d = document.getElementById("prot_mask"); - var a = ["Index", "Live", "Webserial", "Settings", "Update", "System"]; + var a = ["Index", "{#NAV_LIVE}", "{#NAV_WEBSERIAL}", "{#NAV_SETTINGS}", "Update", "System"]; var el = []; for(var i = 0; i < 6; i++) { var chk = ((obj["prot_mask"] & (1 << i)) == (1 << i)); @@ -657,13 +653,13 @@ ml("th", {style: "width: 10%; text-align: center;"}, ""), ml("th", {}, "Name"), ml("th", {}, "Serial"), - ml("th", {style: "width: 10%; text-align: center;"}, "Edit"), - ml("th", {style: "width: 10%; text-align: center;"}, "Delete") + ml("th", {style: "width: 10%; text-align: center;"}, "{#INV_EDIT}"), + ml("th", {style: "width: 10%; text-align: center;"}, "{#INV_DELETE}") ])); for(let i = 0; i < obj.inverter.length; i++) { lines.push(ml("tr", {}, [ - ml("td", {}, badge(obj.inverter[i].enabled, (obj.inverter[i].enabled) ? "enabled" : "disabled")), + ml("td", {}, badge(obj.inverter[i].enabled, (obj.inverter[i].enabled) ? "{#ENABLED}" : "{#DISABLED}")), ml("td", {}, obj.inverter[i].name), ml("td", {}, String(obj.inverter[i].serial)), ml("td", {style: "text-align: center;", onclick: function() {ivModal(obj.inverter[i]);}}, svg(iconGear, 25, 25, "icon icon-fg pointer")), @@ -686,7 +682,7 @@ e.innerHTML = ""; // remove all childs e.append(ml("table", {class: "table"}, ml("tbody", {}, lines))); if(obj.max_num_inverters > obj.inverter.length) - e.append(ml("div", {class: "row my-3"}, ml("div", {class: "col a-r"}, ml("input", {type: "button", value: "add Inverter", class: "btn", onclick: function() { ivModal(add); }}, null)))); + e.append(ml("div", {class: "row my-3"}, ml("div", {class: "col a-r"}, ml("input", {type: "button", value: "{#BTN_INV_ADD}", class: "btn", onclick: function() { ivModal(add); }}, null)))); ivGlob(obj); } @@ -694,10 +690,10 @@ function ivModal(obj) { var lines = []; lines.push(ml("tr", {}, [ - ml("th", {style: "width: 10%;"}, "Input"), - ml("th", {}, "Max Module Power [Wp]"), + ml("th", {style: "width: 10%;"}, "{#INV_INPUT}"), + ml("th", {}, "{#INV_MAX_MODULE_POWER} [Wp]"), ml("th", {}, "Name (optional)"), - ml("th", {}, "Yield Correction [kWh] (optional)") + ml("th", {}, "{#INV_YIELD_CORR} [kWh] (optional)") ])); for(let i = 0; i < 6; i++) { @@ -718,14 +714,14 @@ var ser = ml("input", {name: "ser", class: "text", type: "number", max: 138999999999, value: obj.serial}, null); var html = ml("div", {}, [ - tabs(["General", "Inputs", "Radio", "Advanced"]), - ml("div", {id: "divGeneral", class: "tab-content"}, [ + tabs(["{#TAB_GENERAL}", "{#TAB_INPUTS}", "{#TAB_RADIO}", "{#TAB_ADVANCED}"]), + ml("div", {id: "div{#TAB_GENERAL}", class: "tab-content"}, [ ml("div", {class: "row mb-3"}, [ - ml("div", {class: "col-2"}, "Enable"), + ml("div", {class: "col-2"}, "{#INV_ENABLE}"), ml("div", {class: "col-10"}, cbEn) ]), ml("div", {class: "row mb-3"}, [ - ml("div", {class: "col-2 mt-2"}, "Serial"), + ml("div", {class: "col-2 mt-2"}, "{#INV_SERIAL}"), ml("div", {class: "col-10"}, ser) ]), ml("div", {class: "row mb-3"}, [ @@ -733,45 +729,45 @@ ml("div", {class: "col-10"}, ml("input", {name: "name", class: "text", type: "text", value: obj.name}, null)) ]) ]), - ml("div", {id: "divInputs", class: "tab-content hide"}, [ + ml("div", {id: "div{#TAB_INPUTS}", class: "tab-content hide"}, [ ml("div", {class: "row mb-3"}, ml("table", {class: "table"}, ml("tbody", {}, lines) ) ) ]), - ml("div", {id: "divRadio", class: "tab-content hide"}, [ + ml("div", {id: "div{#TAB_RADIO}", class: "tab-content hide"}, [ ml("input", {type: "hidden", name: "isnrf"}, null), ml("div", {id: "setcmt"}, [ ml("div", {class: "row mb-3"}, [ - ml("div", {class: "col-3 mt-2"}, "Frequency"), + ml("div", {class: "col-3 mt-2"}, "{#INV_FREQUENCY}"), ml("div", {class: "col-9"}, sel("freq", esp32cmtFreq, obj.freq)) ]), ml("div", {class: "row mb-3"}, [ - ml("div", {class: "col-3 mt-2"}, "Power Level"), + ml("div", {class: "col-3 mt-2"}, "{#INV_POWER_LEVEL}"), ml("div", {class: "col-9"}, sel("cmtpa", esp32cmtPa, obj.pa)) ]), ]), ml("div", {id: "setnrf"}, ml("div", {class: "row mb-3"}, [ - ml("div", {class: "col-3 mt-2"}, "Power Level"), + ml("div", {class: "col-3 mt-2"}, "{#INV_POWER_LEVEL}"), ml("div", {class: "col-9"}, sel("nrfpa", nrfPa, obj.pa)) ]), ), ]), - ml("div", {id: "divAdvanced", class: "tab-content hide"}, [ + ml("div", {id: "div{#TAB_ADVANCED}", class: "tab-content hide"}, [ ml("div", {class: "row mb-3"}, [ - ml("div", {class: "col-10"}, "Pause communication during night (lat. and lon. need to be set)"), + ml("div", {class: "col-10"}, "{#INV_PAUSE_DURING_NIGHT}"), ml("div", {class: "col-2"}, cbDisNightCom) ]), ml("div", {class: "row mb-3"}, [ - ml("div", {class: "col-10"}, "Include inverter to sum of total (should be checked by default, MqTT only)"), + ml("div", {class: "col-10"}, "{#INV_INCLUDE_MQTT_SUM}"), ml("div", {class: "col-2"}, cbAddTotal) ]) ]), ml("div", {class: "row mt-5"}, [ ml("div", {class: "col-8", id: "res"}, ""), - ml("div", {class: "col-4 a-r"}, ml("input", {type: "button", value: "save", class: "btn", onclick: function() { ivSave(); }}, null)) + ml("div", {class: "col-4 a-r"}, ml("input", {type: "button", value: "{#BTN_SAVE}", class: "btn", onclick: function() { ivSave(); }}, null)) ]) ]); @@ -808,7 +804,7 @@ }) }); - modal("Edit inverter " + obj.name, html); + modal("{#INV_EDIT_MODAL}: " + obj.name, html); ser.dispatchEvent(new Event('change')); function ivSave() { @@ -839,7 +835,7 @@ function cb(obj2) { var e = document.getElementById("res"); if(!obj2.success) - e.innerHTML = "error: " + obj2.error; + e.innerHTML = "{#ERROR}" + obj2.error; else { modalClose(); getAjax("/api/inverter/list", parseIv); @@ -849,10 +845,10 @@ function ivDel(obj) { var html = ml("div", {class: "row"}, [ - ml("div", {class: "col-9"}, "do you realy want to delete inverter " + obj.name + "?"), - ml("div", {class: "col-3 a-r"}, ml("div", {class: "col-4 a-r"}, ml("input", {type: "button", value: "yes", class: "btn", onclick: function() { del(); }}, null))) + ml("div", {class: "col-9"}, "{#INV_DELETE_SURE} (" + obj.name + ")"), + ml("div", {class: "col-3 a-r"}, ml("div", {class: "col-4 a-r"}, ml("input", {type: "button", value: "{#BTN_YES}", class: "btn", onclick: function() { del(); }}, null))) ]); - modal("Delete inverter " + obj.name, html); + modal("{#INV_DELETE_MODAL}: " + obj.name, html); function del() { var o = new Object(); @@ -896,7 +892,7 @@ for(p of [["sunOffsSr", "offsSr"], ["sunOffsSs", "offsSs"]]) { const sel = document.getElementsByName(p[0])[0]; for(var i = -60; i <= 60; i++) { - sel.appendChild(opt(i, i + " minutes", (i == (obj[p[1]] / 60)))); + sel.appendChild(opt(i, i + " {#NTP_MINUTES}", (i == (obj[p[1]] / 60)))); } } } @@ -909,7 +905,7 @@ if ("ESP32-S3" == system.chip_model) pinList = esp32s3pins; else if("ESP32-C3" == system["chip_model"]) pinList = esp32c3pins; /*ENDIF_ESP32*/ - pins = [['led0', 'pinLed0', 'At least one inverter is producing'], ['led1', 'pinLed1', 'MqTT connected'], ['led2', 'pinLed2', 'Night time']]; + pins = [['led0', 'pinLed0', '{#LED_AT_LEAST_ONE_PRODUCING}'], ['led1', 'pinLed1', '{#LED_MQTT_CONNECTED}'], ['led2', 'pinLed2', '{#LED_NIGHT_TIME}']]; for(p of pins) { e.append( ml("div", {class: "row mb-3"}, [ @@ -922,13 +918,13 @@ } e.append( ml("div", { class: "row mb-3" }, [ - ml("div", { class: "col-12 col-sm-3 my-2" }, "LED polarity"), + ml("div", { class: "col-12 col-sm-3 my-2" }, "{#LED_POLARITY}"), ml("div", { class: "col-12 col-sm-9" }, sel('pinLedHighActive', led_high_active, obj.led_high_active) ) ]), ml("div", { class: "row mb-3" }, [ - ml("div", { class: "col-12 col-sm-3 my-2" }, "LED luminance (0-255)"), + ml("div", { class: "col-12 col-sm-3 my-2" }, "{#LED_LUMINANCE} (0-255)"), ml("div", { class: "col-12 col-sm-9" }, ml("input", {class: "text", type: "number", name: "pinLedLum", value: obj.led_lum, min: 0, max: 255}, null)) ]) ) @@ -948,7 +944,7 @@ e.replaceChildren ( ml("div", {class: "row mb-3"}, [ - ml("div", {class: "col-8 col-sm-3 my-2"}, "NRF24 Enable"), + ml("div", {class: "col-8 col-sm-3 my-2"}, "{#NRF24_ENABLE}"), ml("div", {class: "col-4 col-sm-9"}, en) ]) ); @@ -982,7 +978,7 @@ e.replaceChildren ( ml("div", {class: "row mb-3"}, [ - ml("div", {class: "col-8 col-sm-3 my-2"}, "CMT2300A Enable"), + ml("div", {class: "col-8 col-sm-3 my-2"}, "{#CMT_ENABLE}"), ml("div", {class: "col-4 col-sm-9"}, en) ]) ); @@ -1033,7 +1029,7 @@ ); } // keep display types grouped - var opts = [[0, "None"], + var opts = [[0, "{#DISP_NONE}"], [2, "SH1106 128x64 (1.3\")"], [5, "SSD1306 64x48 (0.66\" Wemos OLED Shield)"], [4, "SSD1306 128x32 (0.91\")"], @@ -1046,7 +1042,7 @@ var dtype_sel = sel("disp_typ", opts, obj["disp_typ"]); document.getElementById("dispType").append( ml("div", {class: "row mb-3"}, [ - ml("div", {class: "col-12 col-sm-3 my-2"}, "Type"), + ml("div", {class: "col-12 col-sm-3 my-2"}, "{#DISP_TYPE}"), ml("div", {class: "col-12 col-sm-9"}, dtype_sel) ]) ); @@ -1057,22 +1053,22 @@ /*ENDIF_ESP32*/ document.getElementById("dispRot").append( ml("div", {class: "row mb-3"}, [ - ml("div", {class: "col-12 col-sm-3 my-2"}, "Rotation"), + ml("div", {class: "col-12 col-sm-3 my-2"}, "{#DISP_ROTATION}"), ml("div", {class: "col-12 col-sm-9"}, sel("disp_rot", opts, obj["disp_rot"])) ]) ); - var opts1 = [[0, "off"], [1, "pixel shift"], [2, "motion sensor"]]; + var opts1 = [[0, "{#DISP_SCREENSAVER_OFF}"], [1, "{#DISP_PIXEL_SHIFT}"], [2, "{#DISP_MOTION_SENS}"]]; var screensaver_sel = sel("disp_screensaver", opts1, obj["disp_screensaver"]); screensaver_sel.id = 'disp_screensaver'; document.getElementById("screenSaver").append( ml("div", {class: "row mb-3"}, [ - ml("div", {class: "col-12 col-sm-3 my-2"}, "Screensaver (OLED only)"), + ml("div", {class: "col-12 col-sm-3 my-2"}, "{#DISP_SCREENSAVER}"), ml("div", {class: "col-12 col-sm-9"}, screensaver_sel) ]) ); - var esp8266pirpins = [[255, "off / default"], + var esp8266pirpins = [[255, "{#PIN_OFF}"], [17, "A0"]]; document.getElementById("pirPin").append( ml("div", {class: "row mb-3"}, [ @@ -1155,12 +1151,12 @@ var s = document.getElementById("networks"); selDelAllOpt(s); if(root["networks"].length > 0) { - s.appendChild(opt("-1", "please select network")); + s.appendChild(opt("-1", "{#NETWORK_PLEASE_SELECT}")); for(i = 0; i < root["networks"].length; i++) { s.appendChild(opt(root["networks"][i]["ssid"], root["networks"][i]["ssid"] + " (" + root["networks"][i]["rssi"] + " dBm)")); } } else - s.appendChild(opt("-1", "no network found")); + s.appendChild(opt("-1", "{#NO_NETWORK_FOUND}")); } function selNet() { diff --git a/src/web/html/system.html b/src/web/html/system.html index f400db41..16c6343e 100644 --- a/src/web/html/system.html +++ b/src/web/html/system.html @@ -46,7 +46,7 @@ function irqBadge(state) { switch(state) { - case 0: return badge(false, "unknown", "warning"); break; + case 0: return badge(false, "{#UNKNOWN}", "warning"); break; case 1: return badge(true, "true"); break; default: return badge(false, "false"); break; } @@ -57,16 +57,16 @@ if(obj.radioNrf.en) { lines = [ - tr("NRF24L01", badge(obj.radioNrf.isconnected, ((obj.radioNrf.isconnected) ? "" : "not ") + "connected")), - tr("Interrupt Pin working", irqBadge(obj.radioNrf.irqOk)), - tr("NRF24 Data Rate", dr[obj.radioNrf.dataRate] + "bps"), + tr("NRF24L01", badge(obj.radioNrf.isconnected, ((obj.radioNrf.isconnected) ? "" : "{#NOT} ") + "{#CONNECTED}")), + tr("{#IRQ_WORKING}", irqBadge(obj.radioNrf.irqOk)), + tr("{#NRF24_DATA_RATE}", dr[obj.radioNrf.dataRate] + "bps"), tr("DTU Radio ID", obj.radioNrf.sn) ]; } else - lines = [tr("NRF24L01", badge(false, "not enabled"))]; + lines = [tr("NRF24L01", badge(false, "{#NOT_ENABLED}"))]; document.getElementById("info").append( - headline("Radio NRF"), + headline("{#NRF24_RADIO}"), ml("table", {class: "table"}, ml("tbody", {}, lines) ) @@ -75,15 +75,15 @@ /*IF_ESP32*/ if(obj.radioCmt.en) { cmt = [ - tr("CMT2300A", badge(obj.radioCmt.isconnected, ((obj.radioCmt.isconnected) ? "" : "not ") + "connected")), - tr("Interrupt Pin working", irqBadge(obj.radioCmt.irqOk)), + tr("CMT2300A", badge(obj.radioCmt.isconnected, ((obj.radioCmt.isconnected) ? "" : "{#NOT} ") + "{#CONNECTED}")), + tr("{#IRQ_WORKING}", irqBadge(obj.radioCmt.irqOk)), tr("DTU Radio ID", obj.radioCmt.sn) ]; } else - cmt = [tr("CMT2300A", badge(false, "not enabled"))]; + cmt = [tr("CMT2300A", badge(false, "{#NOT_ENABLED}"))]; document.getElementById("info").append( - headline("Radio CMT"), + headline("{#CMT_RADIO}"), ml("table", {class: "table"}, ml("tbody", {}, cmt) ) @@ -94,13 +94,13 @@ function parseMqtt(obj) { if(obj.enabled) { lines = [ - tr("connected", badge(obj.connected, ((obj.connected) ? "true" : "false"))), + tr("{#CONNECTED}", badge(obj.connected, ((obj.connected) ? "{#TRUE}" : "{#FALSE}"))), tr("#TX", obj.tx_cnt), tr("#RX", obj.rx_cnt) ]; } else - lines = tr("enabled", badge(false, "false")); + lines = tr("{#ENABLED}", badge(false, "{#FALSE}")); document.getElementById("info").append( headline("MqTT"), @@ -113,14 +113,14 @@ function parseIndex(obj) { if(obj.ts_sunrise > 0) { document.getElementById("info").append( - headline("Sun"), + headline("{#SUN}"), ml("table", {class: "table"}, ml("tbody", {}, [ - tr("Sunrise", new Date(obj.ts_sunrise * 1000).toLocaleString('de-DE')), - tr("Sunset", new Date(obj.ts_sunset * 1000).toLocaleString('de-DE')), - tr("Communication start", new Date((obj.ts_sunrise + obj.ts_offsSr) * 1000).toLocaleString('de-DE')), - tr("Communication stop", new Date((obj.ts_sunset + obj.ts_offsSs) * 1000).toLocaleString('de-DE')), - tr("Night behaviour", badge(obj.disNightComm, ((obj.disNightComm) ? "not" : "") + " communicating", "warning")) + tr("{#SUNRISE}", new Date(obj.ts_sunrise * 1000).toLocaleString('de-DE')), + tr("{#SUNSET}", new Date(obj.ts_sunset * 1000).toLocaleString('de-DE')), + tr("{#COMMUNICATION_START}", new Date((obj.ts_sunrise + obj.ts_offsSr) * 1000).toLocaleString('de-DE')), + tr("{#COMMUNICATION_STTOP}", new Date((obj.ts_sunset + obj.ts_offsSs) * 1000).toLocaleString('de-DE')), + tr("{#NIGHT_BEHAVE}", badge(obj.disNightComm, ((obj.disNightComm) ? "{#NOT}" : "") + " {#COMMUNICATING}", "warning")) ]) ) ); diff --git a/src/web/html/update.html b/src/web/html/update.html index 4e56b1ad..f0e39fe7 100644 --- a/src/web/html/update.html +++ b/src/web/html/update.html @@ -9,14 +9,14 @@
- Select firmware file (*.bin) + {#SELECT_FILE} (*.bin) - +
@@ -31,7 +31,7 @@ function hide() { document.getElementById("form").submit(); var e = document.getElementById("content"); - e.replaceChildren(span("update started")); + e.replaceChildren(span("{#UPDATE_STARTED}")); } getAjax("/api/generic", parseGeneric); diff --git a/src/web/html/visualization.html b/src/web/html/visualization.html index 27f5da35..8099539c 100644 --- a/src/web/html/visualization.html +++ b/src/web/html/visualization.html @@ -10,7 +10,7 @@
-

Every seconds the values are updated

+

{#EVERY} {#UPDATE_SECS}

{#HTML_FOOTER} @@ -70,20 +70,20 @@ ml("div", {class: "col"}, [ ml("div", {class: "p-2 total-h"}, ml("div", {class: "row"}, - ml("div", {class: "col mx-2 mx-md-1"}, "TOTAL") + ml("div", {class: "col mx-2 mx-md-1"}, "{#TOTAL}") ), ), ml("div", {class: "p-2 total-bg"}, [ ml("div", {class: "row"}, [ - numBig(total[0], "W", "AC Power"), - numBig(total[1], "Wh", "Yield Day"), - numBig(total[2], "kWh", "Yield Total") + numBig(total[0], "W", "{#AC_POWER}"), + numBig(total[1], "Wh", "{#YIELD_DAY}"), + numBig(total[2], "kWh", "{#YIELD_TOTAL}") ]), ml("div", {class: "hr"}), ml("div", {class: "row"}, [ - numMid(total[3], "W", "Max Power"), - numMid(total[4], "W", "DC Power"), - numMid(total[5], "var", "Reactive Power") + numMid(total[3], "W", "{#MAX_POWER}"), + numMid(total[4], "W", "{#DC_POWER}"), + numMid(total[5], "var", "{#REACTIVE_POWER}") ]) ]) ]) @@ -119,31 +119,31 @@ getAjax("/api/inverter/version/" + obj.id, parseIvVersion); }}, obj.name)), ml("div", {class: "col a-c", onclick: function() {limitModal(obj)}}, [ - ml("span", {class: "d-none d-sm-block pointer"}, "Active Power Control: " + pwrLimit), - ml("span", {class: "d-block d-sm-none pointer"}, "APC: " + pwrLimit) + ml("span", {class: "d-none d-sm-block pointer"}, "{#ACTIVE_POWER_CONTROL}: " + pwrLimit), + ml("span", {class: "d-block d-sm-none pointer"}, "{#APC}: " + pwrLimit) ]), ml("div", {class: "col a-c"}, ml("span", { class: "pointer", onclick: function() { getAjax("/api/inverter/alarm/" + obj.id, parseIvAlarm); - }}, ("Alarms: " + obj.alarm_cnt))), + }}, ("{#ALARMS}: " + obj.alarm_cnt))), ml("div", {class: "col a-r mx-2 mx-md-1"}, String(obj.ch[0][5].toFixed(1)) + t.innerText) ]) ), ml("div", {class: "p-2 " + clbg}, [ ml("div", {class: "row"},[ - numBig(obj.ch[0][2], "W", "AC Power"), - numBig(obj.ch[0][7], "Wh", "Yield Day"), - numBig(obj.ch[0][6], "kWh", "Yield Total") + numBig(obj.ch[0][2], "W", "{#AC_POWER}"), + numBig(obj.ch[0][7], "Wh", "{#YIELD_DAY}"), + numBig(obj.ch[0][6], "kWh", "{#YIELD_TOTAL}") ]), ml("div", {class: "hr"}), ml("div", {class: "row mt-2"},[ - numMid(obj.ch[0][11], "W", "Max AC Power", {class: "fs-6 tooltip", data: maxAcPwr}), - numMid(obj.ch[0][8], "W", "DC Power"), - numMid(obj.ch[0][0], "V", "AC Voltage"), - numMid(obj.ch[0][1], "A", "AC Current"), - numMid(obj.ch[0][3], "Hz", "Frequency"), - numMid(obj.ch[0][9], "%", "Efficiency"), - numMid(obj.ch[0][10], "var", "Reactive Power"), - numMid(obj.ch[0][4], "", "Power Factor") + numMid(obj.ch[0][11], "W", "{#MAX_AC_POWER}", {class: "fs-6 tooltip", data: maxAcPwr}), + numMid(obj.ch[0][8], "W", "{#DC_POWER}"), + numMid(obj.ch[0][0], "V", "{#DC_VOLTAGE}"), + numMid(obj.ch[0][1], "A", "{#AC_CURRENT}"), + numMid(obj.ch[0][3], "Hz", "{#FREQUENCY}"), + numMid(obj.ch[0][9], "%", "{#EFFICIENCY}"), + numMid(obj.ch[0][10], "var", "{#REACTIVE_POWER}"), + numMid(obj.ch[0][4], "", "{#POWER_FACTOR}") ]) ]) ]) @@ -172,26 +172,26 @@ ml("div", {class: "p-2 a-c " + clh}, name), ml("div", {class: "p-2 " + clbg}, [ ml("div", {class: "row"}, [ - numCh(vals[2], units[2], "DC Power"), - numCh(vals[6], units[2], "Max Power"), - numCh(vals[5], units[5], "Irradiation"), - numCh(vals[3], units[3], "Yield Day"), - numCh(vals[4], units[4], "Yield Total"), - numCh(vals[0], units[0], "DC Voltage"), - numCh(vals[1], units[1], "DC Current") + numCh(vals[2], units[2], "{#DC_POWER}"), + numCh(vals[6], units[2], "{#MAX_POWER}"), + numCh(vals[5], units[5], "{#IRRADIATION}"), + numCh(vals[3], units[3], "{#YIELD_DAY}"), + numCh(vals[4], units[4], "{#YIELD_TOTAL}"), + numCh(vals[0], units[0], "{#DC_VOLTAGE}"), + numCh(vals[1], units[1], "{#DC_CURRENT}") ]) ]) ]); } function tsInfo(obj) { - var ageInfo = "Last received data requested at: "; + var ageInfo = "{#LAST_RECEIVED}: "; if(obj.ts_last_success > 0) { var date = new Date(obj.ts_last_success * 1000); ageInfo += toIsoDateStr(date); } else - ageInfo += "nothing received"; + ageInfo += "{#NOTHING_RECEIVED}"; if(obj.rssi > -127) { if(obj.generation < 2) @@ -252,10 +252,10 @@ var offs = new Date().getTimezoneOffset() * -60; html.push( ml("div", {class: "row"}, [ - ml("div", {class: "col"}, ml("strong", {}, "Event")), + ml("div", {class: "col"}, ml("strong", {}, "{#EVENT}")), ml("div", {class: "col"}, ml("strong", {}, "ID")), ml("div", {class: "col"}, ml("strong", {}, "Start")), - ml("div", {class: "col"}, ml("strong", {}, "End")) + ml("div", {class: "col"}, ml("strong", {}, "{#END}")) ]) ); @@ -271,7 +271,7 @@ ); } } - modal("Alarms of inverter " + obj.iv_name, ml("div", {}, html)); + modal("{#ALARMS_MODAL}: " + obj.iv_name, ml("div", {}, html)); } function parseIvVersion(obj) { @@ -283,7 +283,7 @@ case 3: model = "HMT-"; break; default: model = "???-"; break; } - model += String(obj.max_pwr) + " (Serial: " + obj.serial + ")"; + model += String(obj.max_pwr) + " ({#SERIAL}: " + obj.serial + ")"; var html = ml("table", {class: "table"}, [ @@ -291,15 +291,15 @@ tr("Model", model), tr("Firmware Version / Build", String(obj.fw_ver) + " (build: " + String(obj.fw_date) + " " + String(obj.fw_time) + ")"), tr("Hardware Version / Build", (obj.hw_ver/100).toFixed(2) + " (build: " + String(obj.prod_cw) + "/" + String(obj.prod_year) + ")"), - tr("Hardware Number", obj.part_num.toString(16)), + tr("{#HW_NUMBER}", obj.part_num.toString(16)), tr("Bootloader Version", (obj.boot_ver/100).toFixed(2)), - tr("Grid Profile", ml("input", {type: "button", value: "show", class: "btn", onclick: function() { + tr("Grid Profile", ml("input", {type: "button", value: "{#BTN_SHOW}", class: "btn", onclick: function() { modalClose(); getAjax("/api/inverter/grid/" + obj.id, showGridProfile); }}, null)) ]) ]) - modal("Info for inverter " + obj.name, ml("div", {}, html)) + modal("{#INV_INFO}: " + obj.name, ml("div", {}, html)) } function getGridValue(g) { @@ -332,9 +332,9 @@ )) content.push(ml("div", {class: "row my-2"}, [ ml("div", {class: "col-4"}, ml("b", {}, "Name")), - ml("div", {class: "col-3"}, ml("b", {}, "Value")), - ml("div", {class: "col-3"}, ml("b", {}, "Range")), - ml("div", {class: "col-2"}, ml("b", {}, "Default")) + ml("div", {class: "col-3"}, ml("b", {}, "{#VALUE}")), + ml("div", {class: "col-3"}, ml("b", {}, "{#RANGE}")), + ml("div", {class: "col-2"}, ml("b", {}, "{#DEFAULT}")) ])) for(e of g.info.group) { if(Array.isArray(e[id])) { @@ -362,10 +362,10 @@ var v = getGridValue(glob); if(null === g) { if(0 == obj.grid.length) { - content.push(ml("div", {class: "row"}, ml("div", {class: "col"}, ml("p", {}, "Profile was not read until now, maybe turned off?")))) + content.push(ml("div", {class: "row"}, ml("div", {class: "col"}, ml("p", {}, "{#PROFILE_NOT_READ}?")))) } else { - content.push(ml("div", {class: "row"}, ml("div", {class: "col"}, ml("h5", {}, "Unknown Profile")))) - content.push(ml("div", {class: "row"}, ml("div", {class: "col"}, ml("p", {}, "Please open a new issue at https://github.com/lumapu/ahoy and copy the raw data into it.")))) + content.push(ml("div", {class: "row"}, ml("div", {class: "col"}, ml("h5", {}, "{#UNKNOWN_PROFILE}")))) + content.push(ml("div", {class: "row"}, ml("div", {class: "col"}, ml("p", {}, "{#OPEN_ISSUE}.")))) content.push(ml("div", {class: "row"}, ml("div", {class: "col my-2"}, ml("pre", {}, obj.grid)))) } } else { @@ -378,7 +378,7 @@ } } - modal("Grid Profile for inverter " + obj.name, ml("div", {}, ml("div", {class: "col mb-2"}, [...content]))) + modal("{#PROFILE_MODAL}: " + obj.name, ml("div", {}, ml("div", {class: "col mb-2"}, [...content]))) }) } @@ -386,56 +386,56 @@ function parseIvRadioStats(obj) { var html = ml("table", {class: "table"}, [ ml("tbody", {}, [ - tr2(["TX count", obj.tx_cnt, ""]), - tr2(["RX success", obj.rx_success, String(Math.round(obj.rx_success / obj.tx_cnt * 10000) / 100) + " %"]), - tr2(["RX fail", obj.rx_fail, String(Math.round(obj.rx_fail / obj.tx_cnt * 10000) / 100) + " %"]), - tr2(["RX no answer", obj.rx_fail_answer, String(Math.round(obj.rx_fail_answer / obj.tx_cnt * 10000) / 100) + " %"]), - tr2(["RX fragments", obj.frame_cnt, ""]), - tr2(["TX retransmits", obj.retransmits, ""]), - tr2(["Inverter loss rate", "lost " + obj.ivLoss + " of " + obj.ivSent, String(Math.round(obj.ivLoss / obj.ivSent * 10000) / 100) + " %"]), - tr2(["DTU loss rate", "lost " + obj.dtuLoss + " of " + obj.dtuSent, String(Math.round(obj.dtuLoss / obj.dtuSent * 10000) / 100) + " %"]) + tr2(["{#TX_COUNT}", obj.tx_cnt, ""]), + tr2(["{#RX_SUCCESS}", obj.rx_success, String(Math.round(obj.rx_success / obj.tx_cnt * 10000) / 100) + " %"]), + tr2(["{#RX_FAIL}", obj.rx_fail, String(Math.round(obj.rx_fail / obj.tx_cnt * 10000) / 100) + " %"]), + tr2(["{#RX_NO_ANSWER}", obj.rx_fail_answer, String(Math.round(obj.rx_fail_answer / obj.tx_cnt * 10000) / 100) + " %"]), + tr2(["{#RX_FRAGMENTS}", obj.frame_cnt, ""]), + tr2(["{#TX_RETRANSMITS}", obj.retransmits, ""]), + tr2(["{#INV_LOSS_RATE}", "{#LOST_1} " + obj.ivLoss + " {#LOST_2} " + obj.ivSent + " {#LOST_3}", String(Math.round(obj.ivLoss / obj.ivSent * 10000) / 100) + " %"]), + tr2(["{#DTU_LOSS_RATE}", "{#LOST_1} " + obj.dtuLoss + " {#LOST_2} " + obj.dtuSent + " {#LOST_3}", String(Math.round(obj.dtuLoss / obj.dtuSent * 10000) / 100) + " %"]) ]) ]) - modal("Radio statistics for inverter " + obj.name, ml("div", {}, html)) + modal("{#RADIO_STAT_MODAL}: " + obj.name, ml("div", {}, html)) } function limitModal(obj) { var opt = [["pct", "%"], ["watt", "W"]]; var html = ml("div", {}, [ ml("div", {class: "row mb-3"}, [ - ml("div", {class: "col-12 col-sm-5 my-2"}, "Limit Value"), + ml("div", {class: "col-12 col-sm-5 my-2"}, "{#LIMIT_VALUE}"), ml("div", {class: "col-8 col-sm-5"}, ml("input", {name: "limit", type: "number", step: "0.1", min: 1}, "")), ml("div", {class: "col-4 col-sm-2"}, sel("type", opt, "pct")) ]), ml("div", {class: "row mb-3"}, [ - ml("div", {class: "col-8 col-sm-5"}, "Keep limit over inverter restart"), + ml("div", {class: "col-8 col-sm-5"}, "{#KEEP_LIMIT}"), ml("div", {class: "col-4 col-sm-7"}, ml("input", {type: "checkbox", name: "keep"})) ]), ml("div", {class: "row my-3"}, - ml("div", {class: "col a-r"}, ml("input", {type: "button", value: "Apply", class: "btn", onclick: function() { + ml("div", {class: "col a-r"}, ml("input", {type: "button", value: "{#BTN_APPLY}", class: "btn", onclick: function() { applyLimit(obj.id); }}, null)) ), ml("div", {class: "row my-4"}, [ - ml("div", {class: "col-12 col-sm-5 my-2"}, "Control"), + ml("div", {class: "col-12 col-sm-5 my-2"}, "{#CONTROL}"), ml("div", {class: "col col-sm-7 a-r"}, [ - ml("input", {type: "button", value: "restart", class: "btn", onclick: function() { + ml("input", {type: "button", value: "{#RESTART}", class: "btn", onclick: function() { applyCtrl(obj.id, "restart"); }}, null), - ml("input", {type: "button", value: "turn off", class: "btn mx-1", onclick: function() { + ml("input", {type: "button", value: "{#TURN_OFF}", class: "btn mx-1", onclick: function() { applyCtrl(obj.id, "power", 0); }}, null), - ml("input", {type: "button", value: "turn on", class: "btn", onclick: function() { + ml("input", {type: "button", value: "{#TURN_ON}", class: "btn", onclick: function() { applyCtrl(obj.id, "power", 1); }}, null) ]) ]), ml("div", {class: "row mt-1"}, [ - ml("div", {class: "col-12 col-sm-5 my-2"}, "Result"), + ml("div", {class: "col-12 col-sm-5 my-2"}, "{#RESULT}"), ml("div", {class: "col-sm-7 my-2"}, ml("span", {name: "pwrres"}, "-")) ]) ]); - modal("Active Power Control for inverter " + obj.name, html); + modal("{#POWER_LIMIT_MODAL}: " + obj.name, html); } function applyLimit(id) { @@ -470,19 +470,19 @@ function ctrlCb(obj) { var e = document.getElementsByName("pwrres")[0]; if(obj.success) { - e.innerHTML = "received command, waiting for inverter acknowledge ..."; + e.innerHTML = "{#CMD_RECEIVED_WAIT_ACK}"; tPwrAck = window.setInterval("getAjax('/api/inverter/pwrack/" + obj.id + "', updatePwrAck)", 1000); } else - e.innerHTML = "Error: " + obj["error"]; + e.innerHTML = "{#ERROR}: " + obj["error"]; } function ctrlCb2(obj) { var e = document.getElementsByName("pwrres")[0]; if(obj.success) - e.innerHTML = "command received"; + e.innerHTML = "{#COMMAND_RECEIVED}"; else - e.innerHTML = "Error: " + obj["error"]; + e.innerHTML = "{#ERROR}: " + obj["error"]; } function updatePwrAck(obj) { @@ -492,7 +492,7 @@ clearInterval(tPwrAck); if(null == e) return; - e.innerHTML = "inverter acknowledged active power control command"; + e.innerHTML = "{#INV_ACK}"; } function parse(obj) { diff --git a/src/web/html/wizard.html b/src/web/html/wizard.html index 1ebfb65f..674823b2 100644 --- a/src/web/html/wizard.html +++ b/src/web/html/wizard.html @@ -24,23 +24,23 @@ function wifi() { return ml("div", {}, [ - ml("div", {class: "row my-5"}, ml("div", {class: "col"}, ml("span", {class: "fs-1"}, "Welcome to AhoyDTU"))), - ml("div", {class: "row"}, ml("div", {class: "col"}, ml("span", {class: "fs-5"}, "Network Setup"))), - sect("Choose your WiFi Network", ml("select", {id: "net", onchange: () => {if(found) clearInterval(v)}}, ml("option", {value: "-1"}, "---"))), - sect("... or name it manually", ml("input", {id: "man", type: "text"})), - sect("WiFi Password", ml("input", {id: "pwd", type: "password"})), - ml("div", {class: "row my-4"}, ml("div", {class: "col a-r"}, ml("input", {type: "button", class:"btn", value: "next", onclick: () => {saveWifi()}}, null))), - ml("div", {class: "row mt-5"}, ml("div", {class: "col a-c"}, ml("a", {href: "http://192.168.4.1/"}, "stop wizard"))) + ml("div", {class: "row my-5"}, ml("div", {class: "col"}, ml("span", {class: "fs-1"}, "{#WELCOME}"))), + ml("div", {class: "row"}, ml("div", {class: "col"}, ml("span", {class: "fs-5"}, "{#NETWORK_SETUP}"))), + sect("{#CHOOSE_WIFI}", ml("select", {id: "net", onchange: () => {if(found) clearInterval(v)}}, ml("option", {value: "-1"}, "---"))), + sect("{#WIFI_MANUAL}", ml("input", {id: "man", type: "text"})), + sect("{#WIFI_PASSWORD}", ml("input", {id: "pwd", type: "password"})), + ml("div", {class: "row my-4"}, ml("div", {class: "col a-r"}, ml("input", {type: "button", class:"btn", value: "{#BTN_NEXT}", onclick: () => {saveWifi()}}, null))), + ml("div", {class: "row mt-5"}, ml("div", {class: "col a-c"}, ml("a", {href: "http://192.168.4.1/"}, "{#STOP_WIZARD}"))) ]) } function checkWifi() { c.replaceChildren( - ml("div", {class: "row my-5"}, ml("div", {class: "col"}, ml("span", {class: "fs-1"}, "Welcome to AhoyDTU"))), - ml("div", {class: "row"}, ml("div", {class: "col"}, ml("span", {class: "fs-5"}, "Test Connection"))), - sect("AhoyDTU is trying to connect to your WiFi", ml("span", {id: "state"}, "connecting ...")), - ml("div", {class: "row my-4"}, ml("div", {class: "col a-r"}, ml("input", {type: "button", class:"btn hide", id: "btn", value: "Finish", onclick: () => {redirect()}}, null))), - ml("div", {class: "row mt-5"}, ml("div", {class: "col a-c"}, ml("a", {href: "http://192.168.4.1/"}, "stop wizard"))) + ml("div", {class: "row my-5"}, ml("div", {class: "col"}, ml("span", {class: "fs-1"}, "{#WELCOME}"))), + ml("div", {class: "row"}, ml("div", {class: "col"}, ml("span", {class: "fs-5"}, "{#TEST_CONNECTION}"))), + sect("{#TRY_TO_CONNECT}", ml("span", {id: "state"}, "{#CONNECTING}")), + ml("div", {class: "row my-4"}, ml("div", {class: "col a-r"}, ml("input", {type: "button", class:"btn hide", id: "btn", value: "{#BTN_FINISH}", onclick: () => {redirect()}}, null))), + ml("div", {class: "row mt-5"}, ml("div", {class: "col a-c"}, ml("a", {href: "http://192.168.4.1/"}, "{#STOP_WIZARD}"))) ) v = setInterval(() => {getAjax('/api/setup/getip', printIp)}, 2500); } @@ -53,7 +53,7 @@ if("0.0.0.0" != obj["ip"]) { clearInterval(v) setHide("btn", false) - document.getElementById("state").innerHTML = "success, got following IP in your network: " + obj.ip + document.getElementById("state").innerHTML = "{#NETWORK_SUCCESS}" + obj.ip } } @@ -68,7 +68,7 @@ var e = document.getElementById("net"); if(obj.networks.length > 0) { var a = [] - a.push(ml("option", {value: -1}, obj.networks.length + " Network(s) found")) + a.push(ml("option", {value: -1}, obj.networks.length + " {#NUM_NETWORKS_FOUND}")) for(n of obj.networks) { a.push(ml("option", {value: n.ssid}, n.ssid + " (" + n.rssi + "dBm)")) found = true; diff --git a/src/web/lang.json b/src/web/lang.json new file mode 100644 index 00000000..d6c76ffc --- /dev/null +++ b/src/web/lang.json @@ -0,0 +1,1334 @@ +{ + "files": [ + { + "name": "general", + "list": [ + { + "token": "NAV_LIVE", + "en": "Live", + "de": "Daten" + }, + { + "token": "NAV_WEBSERIAL", + "en": "Webserial", + "de": "Web Konsole" + }, + { + "token": "NAV_SETTINGS", + "en": "Settings", + "de": "Einstellungen" + }, + { + "token": "NAV_DOCUMENTATION", + "en": "Documentation", + "de": "Dokumentation" + }, + { + "token": "NAV_ABOUT", + "en": "About", + "de": "Über" + } + ] + }, + { + "name": "wizard.html", + "list": [ + { + "token": "WELCOME", + "en": "Welcome to AhoyDTU", + "de": "Willkommen zu AhoyDTU" + }, + { + "token": "NETWORK_SETUP", + "en": "Network Setup", + "de": "Netzwerkeinstellung" + }, + { + "token": "CHOOSE_WIFI", + "en": "Choose your WiFi Network", + "de": "Wähle dein WiFi Netzwerk aus" + }, + { + "token": "WIFI_MANUAL", + "en": "... or name it manually", + "de": "... oder nenne es hier" + }, + { + "token": "WIFI_PASSWORD", + "en": "WiFi Password", + "de": "WiFi Passwort" + }, + { + "token": "STOP_WIZARD", + "en": "stop wizard", + "de": "Einrichtung beenden" + }, + { + "token": "BTN_NEXT", + "en": "next >>", + "de": "prüfen >>" + }, + { + "token": "TEST_CONNECTION", + "en": "Test Connection", + "de": "Verbindung wird überprüft" + }, + { + "token": "TRY_TO_CONNECT", + "en": "AhoyDTU is trying to connect to your WiFi", + "de": "AhoyDTU versucht eine Verindung mit deinem Netzwerk herzustellen" + }, + { + "token": "CONNECTING", + "en": "connecting ...", + "de": "verbinde ..." + }, + { + "token": "NETWORK_SUCCESS", + "en": "success, got following IP in your network: ", + "de": "Verindung erfolgreich. AhoyDTU hat die folgende IP bekommen: " + }, + { + "token": "BTN_FINISH", + "en": "Finish >>", + "de": "Fertig >>" + }, + { + "token": "NUM_NETWORKS_FOUND", + "en": "Network(s) found", + "de": "Netzwerk(e) gefunden" + } + ] + }, + { + "name": "setup.html", + "list": [ + { + "token": "SETTINGS", + "en": "Settings", + "de": "Einstellungen" + }, + { + "token": "SYSTEM_CONFIG", + "en": "System Config", + "de": "Systemkonfiguration" + }, + { + "token": "DEVICE_NAME", + "en": "Device Host Name", + "de": "Name der DTU" + }, + { + "token": "REBOOT_AT_MIDNIGHT", + "en": "Reboot Ahoy at midnight", + "de": "um Mitternacht neu starten" + }, + { + "token": "DARK_MODE", + "en": "Dark Mode", + "de": "dunkler Modus" + }, + { + "token": "DARK_MODE_NOTE", + "en": "(empty browser cache or use CTRL + F5 after reboot to apply this setting)", + "de": "(der Browser-Cache muss geleert oder STRG + F5 gedrückt werden, um diese Einstellung zu aktivieren)" + }, + { + "token": "PINOUT_CONFIGURATION", + "en": "Pinout Configuration", + "de": "Anschlusseinstellungen" + }, + { + "token": "RADIO", + "en": "Radio", + "de": "Funkmodul" + }, + { + "token": "SERIAL_CONSOLE", + "en": "Serial console", + "de": "Serielle Konsole" + }, + { + "token": "LOG_PRINT_INVERTER_DATA", + "en": "print inverter data", + "de": "Livedaten ausgeben" + }, + { + "token": "LOG_SERIAL_DEBUG", + "en": "Serial Debug", + "de": "Entwicklerinformationen ausgeben" + }, + { + "token": "LOG_PRIVACY_MODE", + "en": "Privacy Mode", + "de": "Privatsphärenmodus" + }, + { + "token": "LOG_PRINT_TRACES", + "en": "Print whole traces in Log", + "de": "alle Informationen in Log schreiben" + }, + { + "token": "NETWORK", + "en": "Network", + "de": "Netzwerk" + }, + { + "token": "AP_PWD", + "en": "AP Password (min. length: 8)", + "de": "AP Passwort (min. Länge: 8 Zeichen)" + }, + { + "token": "SEARCH_NETWORKS", + "en": "Search Networks", + "de": "Netzwerke suchen" + }, + { + "token": "BTN_SCAN", + "en": "scan", + "de": "Suche starten" + }, + { + "token": "AVAIL_NETWORKS", + "en": "Avail Networks", + "de": "Verfügbare Netzwerke" + }, + { + "token": "NETWORK_NOT_SCANNED", + "en": "not scanned", + "de": "nicht gesucht" + }, + { + "token": "SSID_HIDDEN", + "en": "SSID is hidden", + "de": "SSID ist versteckt" + }, + { + "token": "PASSWORD", + "en": "Password", + "de": "Passwort" + }, + { + "token": "STATIC_IP", + "en": "Static IP (optional)", + "de": "Statische IP (optional)" + }, + { + "token": "NETWORK_HINT_BLANK", + "en": "Leave fields blank for DHCP
The following fields are parsed in this format: 192.168.4.1", + "de": "Felder leer lassen um in den DHCP Modus zu wechseln.
Das Format der IP Adressen ist: 192.168.4.1" + }, + { + "token": "IP_ADDRESS", + "en": "IP Address", + "de": "IP Adresse" + }, + { + "token": "SUBMASK", + "en": "Submask", + "de": "Subnetzmaske" + }, + { + "token": "PROTECTION", + "en": "Protection", + "de": "Zugriffsschutz" + }, + { + "token": "ADMIN_PASSWORD", + "en": "Admin Password", + "de": "Administratorpasswort" + }, + { + "token": "PROTECTION_NOTE", + "en": "Select pages which should be protected by password", + "de": "Auswählen, welche Bereiche passwortgeschützt werden sollen" + }, + { + "token": "INVERTER", + "en": "Inverter", + "de": "Wechselrichter" + }, + { + "token": "INTERVAL", + "en": "Interval [s]", + "de": "Intervall [s]" + }, + { + "token": "INV_GAP", + "en": "Communication Gap [ms]", + "de": "Kommunikationslücke" + }, + { + "token": "INV_RESET_MIDNIGHT", + "en": "Reset values and YieldDay at midnight", + "de": "Werte und Gesamtertrag um Mitternacht zurücksetzen" + }, + { + "token": "INV_PAUSE_SUNSET", + "en": "Reset values at sunset", + "de": "Werte bei Sonnenuntergang zurücksetzen" + }, + { + "token": "INV_RESET_NOT_AVAIL", + "en": "Reset values when inverter status is 'not available'", + "de": "Werte zurücksetzen, sobald der Wechselrichter nicht erreichbar ist" + }, + { + "token": "INV_RESET_MAX_MIDNIGHT", + "en": "Reset 'max' values at midnight", + "de": "Maximalwerte bei Sonnenuntergang zurücksetzen" + }, + { + "token": "INV_START_WITHOUT_TIME", + "en": "Start without time sync (useful in AP-Only-Mode)", + "de": "Kommunikation starten ohne gültige Zeit (sinnvoll im AP Modus)" + }, + { + "token": "INV_READ_GRID_PROFILE", + "en": "Read Grid Profile", + "de": "Grid-Profil auslesen" + }, + { + "token": "INV_YIELD_EFF", + "en": "Yield Efficiency (default 1.0)", + "de": "Ertragseffizienz (Standard 1.0)" + }, + { + "token": "NTP_INTERVAL", + "en": "NTP Interval (in minutes, min. 5 minutes)", + "de": "NTP Intervall (in Minuten, min. 5 Minuten)" + }, + { + "token": "NTP_SET_SYS_TIME", + "en": "set system time", + "de": "Systemzeit setzten" + }, + { + "token": "BTN_FROM_BROWSER", + "en": "from browser", + "de": "vom Browser übernehmen" + }, + { + "token": "BTN_SYNC_NTP", + "en": "sync NTP", + "de": "NTP syncchronisieren" + }, + { + "token": "NTP_SYS_TIME", + "en": "System Time", + "de": "Systemzeit" + }, + { + "token": "SUNRISE_SUNSET", + "en": "Sunrise & Sunset", + "de": "Sonnenaufgang & -untergang" + }, + { + "token": "LATITUDE", + "en": "Latitude (decimal)", + "de": "Breitengrad (dezimal)" + }, + { + "token": "LONGITUDE", + "en": "Longitude (decimal)", + "de": "Längengrad (dezimal)" + }, + { + "token": "OFFSET_SUNRISE", + "en": "Offset (sunrise)", + "de": "Zeitversatz (Sonnenaufgang)" + }, + { + "token": "OFFSET_SUNSET", + "en": "Offset (sunset)", + "de": "Zeitversatz (Sonnenuntergang)" + }, + { + "token": "MQTT_USER", + "en": "Username (optional)", + "de": "Benutzername (optional)" + }, + { + "token": "MQTT_PASSWORD", + "en": "Password (optional)", + "de": "Passwort (optional)" + }, + { + "token": "MQTT_NOTE", + "en": "Send Inverter data in a fixed interval, even if there is no change. A value of '0' disables the fixed interval. The data is published once it was successfully received from inverter. (default: 0)", + "de": "Wechselrichterdaten in fixem Intervall schicken, auch wenn es keine Änderung gab. Ein Wert von '0' deaktiviert das fixe Intervall, die Wechselrichterdaten werden übertragen, sobald neue zur Verfügung stehen. (Standard: 0)" + }, + { + "token": "DISPLAY_CONFIG", + "en": "Display Config", + "de": "Display Konfiguration" + }, + { + "token": "DISP_OFF_INV", + "en": "Turn off while inverters are offline", + "de": "schalte das Display aus, während die Wechselrichter aus sind" + }, + { + "token": "DISP_LUMINANCE", + "en": "Luminance", + "de": "Helligkeit" + }, + { + "token": "DISP_PINOUT", + "en": "Pinout", + "de": "Anschlusseinstellungen" + }, + { + "token": "BTN_REBOOT_SUCCESSFUL_SAVE", + "en": "Reboot device after successful save", + "de": "Nach erfolgreichem Speichern AhoyDTU neu starten" + }, + { + "token": "BTN_ERASE", + "en": "ERASE SETTINGS (not WiFi)", + "de": "Einstellungen zurücksetzen (nicht WiFi)" + }, + { + "token": "IM_EXPORT", + "en": "Import / Export JSON Settings", + "de": "Import / Export JSON Einstellungen" + }, + { + "token": "IMPORT", + "en": "Import", + "de": "Import" + }, + { + "token": "EXPORT", + "en": "Export", + "de": "Export" + }, + { + "token": "BTN_EXPORT", + "en": "Export settings (JSON file)", + "de": "Einstellungen exportieren (JSON Datei)" + }, + { + "token": "EXPORT_NOTE", + "en": "(only values, passwords will be skipped!)", + "de": "(nur Werte, Passwörter werden nicht mit exportiert!)" + }, + { + "token": "PIN_OFF", + "en": "off / default", + "de": "aus / Standard" + }, + { + "token": "PIN_NO_IRQ", + "en": "no IRQ!", + "de": "kein Interrupt!" + }, + { + "token": "PIN_INPUT_ONLY", + "en": "in only", + "de": "nur Eingang" + }, + { + "token": "PIN_DONT_USE", + "en": "DONT USE", + "de": "nicht benutzen" + }, + { + "token": "PIN_NOT_AVAIL", + "en": "not available", + "de": "nicht verfügbar" + }, + { + "token": "PIN_RECOMMENDED", + "en": "recommended", + "de": "empfohlen" + }, + { + "token": "PIN_EXPERIMENTAL", + "en": "experimental", + "de": "experimentell" + }, + { + "token": "PIN_LOW_ACTIVE", + "en": "low active", + "de": "low aktiv" + }, + { + "token": "PIN_HIGH_ACTIVE", + "en": "high active", + "de": "high aktiv" + }, + { + "token": "NETWORK_SCANNING", + "en": "scanning ...", + "de": "suche ..." + }, + { + "token": "ERROR", + "en": "Error:", + "de": "Fehler:" + }, + { + "token": "NTP_COMMAND_EXE", + "en": "command executed, set new time ...", + "de": "Befehl ausgeführt, setze neue Zeit ..." + }, + { + "token": "NTP_SYNCED_AT", + "en": "synced at", + "de": "syncchronisiert um" + }, + { + "token": "NTP_DIFF", + "en": "difference", + "de": "Unterschied" + }, + { + "token": "MQTT_EXE", + "en": "command executed", + "de": "Befehl ausgeführt" + }, + { + "token": "IMPORT_UPLOAD_STARTED", + "en": "upload started", + "de": "hochladen gestartet" + }, + { + "token": "INV_EDIT", + "en": "Edit", + "de": "Bearbeiten" + }, + { + "token": "INV_DELETE", + "en": "Delete", + "de": "Löschen" + }, + { + "token": "ENABLED", + "en": "enabled", + "de": "aktiviert" + }, + { + "token": "DISABLED", + "en": "disabled", + "de": "deaktiviert" + }, + { + "token": "BTN_INV_ADD", + "en": "add Inverter", + "de": "Wechselrichter hinzufuegen" + }, + { + "token": "INV_INPUT", + "en": "Input", + "de": "Eingang" + }, + { + "token": "INV_MAX_MODULE_POWER", + "en": "Max Module Power", + "de": "Maximale Panelleistung" + }, + { + "token": "INV_YIELD_CORR", + "en": "Yield Correction", + "de": "Ertragskorrektur" + }, + { + "token": "TAB_GENERAL", + "en": "General", + "de": "Allgemein" + }, + { + "token": "TAB_INPUTS", + "en": "Inputs", + "de": "Eingaenge" + }, + { + "token": "TAB_RADIO", + "en": "Radio", + "de": "Funkmodul" + }, + { + "token": "TAB_ADVANCED", + "en": "Advanced", + "de": "Erweitert" + }, + { + "token": "INV_ENABLE", + "en": "Enabled", + "de": "aktiviert" + }, + { + "token": "INV_SERIAL", + "en": "Serial", + "de": "Seriennummer" + }, + { + "token": "INV_FREQUENCY", + "en": "Frequency", + "de": "Frequenz" + }, + { + "token": "INV_POWER_LEVEL", + "en": "Power Level", + "de": "Sendeleistung" + }, + { + "token": "INV_PAUSE_DURING_NIGHT", + "en": "Pause communication during night (lat. and lon. need to be set)", + "de": "Kommunikation während der Nacht pausieren (Breiten- und Längengrad müssen gesetzt sein" + }, + { + "token": "INV_INCLUDE_MQTT_SUM", + "en": "Include inverter to sum of total (should be checked by default, MqTT only)", + "de": "Wechselrichter in Liste der aufzuaddierenden Wechselrichter aufnehmen (nur MqTT)" + }, + { + "token": "BTN_SAVE", + "en": "save", + "de": "speichern" + }, + { + "token": "INV_EDIT_MODAL", + "en": "Edit inverter", + "de": "Wechselrichter editieren" + }, + { + "token": "INV_DELETE_SURE", + "en": "do you realy want to delete inverter?", + "de": "Willst du den Wechselrichter wirklich löschen?" + }, + { + "token": "BTN_YES", + "en": "yes", + "de": "ja" + }, + { + "token": "INV_DELETE_MODAL", + "en": "Delete inverter", + "de": "Wechselrichter löschen " + }, + { + "token": "NTP_MINUTES", + "en": "minutes", + "de": "Minuten" + }, + { + "token": "LED_AT_LEAST_ONE_PRODUCING", + "en": "At least one inverter is producing", + "de": "mindestens ein Wechselrichter produziert" + }, + { + "token": "LED_MQTT_CONNECTED", + "en": "MqTT connected", + "de": "MqTT verbunden" + }, + { + "token": "LED_NIGHT_TIME", + "en": "Night time", + "de": "Nachtschaltung" + }, + { + "token": "LED_POLARITY", + "en": "LED polarity", + "de": "LED Polarität" + }, + { + "token": "LED_LUMINANCE", + "en": "LED luminance", + "de": "LED Helligkeit" + }, + { + "token": "NRF24_ENABLE", + "en": "NRF24 radio enable", + "de": "NRF24 Funkmodul aktivieren" + }, + { + "token": "CMT_ENABLE", + "en": "CMT2300A radio enable", + "de": "CMT2300A Funkmodul aktivieren" + }, + { + "token": "DISP_NONE", + "en": "None", + "de": "keins" + }, + { + "token": "DISP_TYPE", + "en": "Type", + "de": "Typ" + }, + { + "token": "DISP_ROTATION", + "en": "Rotation", + "de": "Drehung" + }, + { + "token": "DISP_SCREENSAVER_OFF", + "en": "off", + "de": "aus" + }, + { + "token": "DISP_PIXEL_SHIFT", + "en": "pixel shift", + "de": "Bildversatz" + }, + { + "token": "DISP_MOTION_SENS", + "en": "motion sensor", + "de": "Bewegungssensor" + }, + { + "token": "DISP_SCREENSAVER", + "en": "Screensaver (OLED only)", + "de": "Bildschrimschoner (nur OLED)" + }, + { + "token": "NETWORK_PLEASE_SELECT", + "en": "please select network", + "de": "bitte Netzwerk auswählen" + }, + { + "token": "NO_NETWORK_FOUND", + "en": "no network found", + "de": "kein Netzwerk gefunden" + } + ] + }, + { + "name": "system.html", + "list": [ + { + "token": "UNKNOWN", + "en": "unknown", + "de": "unbekannt" + }, + { + "token": "CONNECTED", + "en": "connected", + "de": "verbunden" + }, + { + "token": "NOT", + "en": "not", + "de": "nicht" + }, + { + "token": "IRQ_WORKING", + "en": "Interrupt Pin working", + "de": "Interrupt Pin funktoniert" + }, + { + "token": "NRF24_DATA_RATE", + "en": "NRF24 Data Rate", + "de": "NRF24 Geschwindigkeit" + }, + { + "token": "NOT_ENABLED", + "en": "not enabled", + "de": "nicht aktiviert" + }, + { + "token": "NRF24_RADIO", + "en": "Radio NRF24", + "de": "NRF24 Funkmodul" + }, + { + "token": "CMT_RADIO", + "en": "Radio CMT", + "de": "CMT2300A Funkmodul" + }, + { + "token": "TRUE", + "en": "true", + "de": "ja" + }, + { + "token": "FALSE", + "en": "false", + "de": "nein" + }, + { + "token": "ENABLED", + "en": "enabled", + "de": "aktiviert" + }, + { + "token": "SUN", + "en": "Sun", + "de": "Sonne" + }, + { + "token": "SUNRISE", + "en": "sunrise", + "de": "Sonnenaufgang" + }, + { + "token": "SUNSET", + "en": "sunset", + "de": "Sonnenuntergang" + }, + { + "token": "COMMUNICATION_START", + "en": "Communication start", + "de": "Start der Kommunikation" + }, + { + "token": "COMMUNICATION_STOP", + "en": "Communication stop", + "de": "Ende der Kommunikation" + }, + { + "token": "NIGHT_BEHAVE", + "en": "Night behaviour", + "de": "Verhalten bei Nacht" + }, + { + "token": "COMMUNICATING", + "en": "communicating", + "de": "aktiv" + } + ] + }, + { + "name": "index.html", + "list": [ + { + "token": "SUPPORT", + "en": "Support this project", + "de": "Dieses Projekt unterstützen" + }, + { + "token": "CHANGELOG", + "en": "Changelog", + "de": "Änderungshistorie" + }, + { + "token": "DISCUSS", + "en": "Discuss with us on", + "de": "Diskutiere mit uns bei" + }, + { + "token": "REPORT", + "en": "Report", + "de": "Melde " + }, + { + "token": "ISSUES", + "en": "Issues", + "de": "Fehler " + }, + { + "token": "CONTRIBUTE", + "en": "Contribute to", + "de": "Unterstütze bei" + }, + { + "token": "DOCUMENTATION", + "en": "documentation", + "de": "der Dokumentation" + }, + { + "token": "DEV_FIRMWARE", + "en": "development firmware", + "de": "Entwicklerversionen der Firmware" + }, + { + "token": "DEV_CHANGELOG", + "en": "Development Changelog", + "de": "Änderungshistorie der Entwicklerversionen" + }, + { + "token": "DON_MAKE", + "en": "make a", + "de": "Unterstüze mit" + }, + { + "token": "DON_MAKE", + "en": "make a", + "de": "Unterstüze z.B. mit" + }, + { + "token": "DONATION", + "en": "donation", + "de": "Paypal" + }, + { + "token": "COMMAND_EXE", + "en": "command executed", + "de": "Befehl ausgeführt" + }, + { + "token": "ERROR", + "en": "Error", + "de": "Fehler" + }, + { + "token": "NTP_UNREACH", + "en": "NTP timeserver unreachable", + "de": "NTP Zeitserver nicht erreichbar" + }, + { + "token": "POLLING_STOP", + "en": "Polling inverter(s), will pause at sunset", + "de": "Abfrage der Wechselrichter wird zum Sonnenuntergang stoppen" + }, + { + "token": "NIGHT_TIME", + "en": "Night time, inverter polling disabled", + "de": "Nacht, Wechselrichterabfrage deaktivert" + }, + { + "token": "PAUSED_AT", + "en": "paused at", + "de": "pausiert um" + }, + { + "token": "START_AT", + "en": "will start polling at", + "de": "wird starten um" + }, + { + "token": "DISABLED", + "en": "disabled", + "de": "deaktiviert" + }, + { + "token": "NOT_YET_AVAIL", + "en": "not yet available", + "de": "gerade nicht verfügbar" + }, + { + "token": "AVAIL", + "en": "available and is", + "de": "verfügbar und ist" + }, + { + "token": "AVAIL_NO_DATA", + "en": "available but no data was received until now", + "de": "verfügbar, aber bis jetzt keine Daten empfangen" + }, + { + "token": "NOT_PRODUCING", + "en": "not producing", + "de": "produziert nicht" + }, + { + "token": "PRODUCING", + "en": "producing", + "de": "produziert" + }, + { + "token": "INVERTER", + "en": "Inverter", + "de": "Wechselrichter" + }, + { + "token": "IS", + "en": "is", + "de": "ist" + }, + { + "token": "LAST_SUCCESS", + "en": "last successful transmission", + "de": "letzte erfolgreiche Übertragung" + }, + { + "token": "UPDATE_AVAIL", + "en": "Update available, current released version", + "de": "Update verfügbar, aktuell veröffentlichte Version" + }, + { + "token": "USING_DEV_VERSION", + "en": "You are using development version", + "de": "Du verwendest eine Entwicklerversion" + }, + { + "token": "DEV_ISSUE_RELEASE_VERSION", + "en": "In case of issues you may want to try the current stable release", + "de": "Wenn du Fehler feststellst solltest du die aktuelle Releaseversion verwenden" + }, + { + "token": "RELEASE_INSTALLED", + "en": "You are using the current stable release", + "de": "Du verwendest das aktuelle Release" + } + ] + }, + { + "name": "update.html", + "list": [ + { + "token": "SELECT_FILE", + "en": "Select firmware file", + "de": "Firmware Datei auswählen" + }, + { + "token": "BTN_UPDATE", + "en": "Update", + "de": "aktualisieren" + }, + { + "token": "BTN_UPDATE", + "en": "Update", + "de": "aktualisieren" + }, + { + "token": "DOWNLOADS", + "en": "Download latest Release and Development versions (without login)", + "de": "Lade die letzte Releaseversion oder Entwicklerversion herunter (ohne Login)" + }, + { + "token": "UPDATE_STARTED", + "en": "update started", + "de": "Aktualisierung gestartet" + } + ] + }, + { + "name": "visualization.html", + "list": [ + { + "token": "EVERY", + "en": "Every", + "de": "Alle" + }, + { + "token": "UPDATE_SECS", + "en": "seconds the values are updated", + "de": "Sekunden werden die Daten aktualisiert" + }, + { + "token": "TOTAL", + "en": "TOTAL", + "de": "GESAMT" + }, + { + "token": "AC_POWER", + "en": "AC Power", + "de": "AC Leistung" + }, + { + "token": "YIELD_DAY", + "en": "Yield Day", + "de": "Tagesertrag" + }, + { + "token": "YIELD_TOTAL", + "en": "Yield Total", + "de": "Gesamtertrag" + }, + { + "token": "MAX_POWER", + "en": "Max Power", + "de": "Maximale Leistung" + }, + { + "token": "DC_POWER", + "en": "DC Power", + "de": "DC Leistung" + }, + { + "token": "REACTIVE_POWER", + "en": "Reactive Power", + "de": "Blindleistung" + }, + { + "token": "ACTIVE_POWER_CONTROL", + "en": "Active Power Control", + "de": "Leistungsbegrenzung" + }, + { + "token": "APC", + "en": "APC", + "de": "Begr." + }, + { + "token": "ALARMS", + "en": "Alarms", + "de": "Meldungen" + }, + { + "token": "MAX_AC_POWER", + "en": "Max AC Power", + "de": "Max AC Leistung" + }, + { + "token": "DC_POWER", + "en": "DC Power", + "de": "DC Leistung" + }, + { + "token": "DC_VOLTAGE", + "en": "DC Voltage", + "de": "DC Spannung" + }, + { + "token": "AC_CURRENT", + "en": "AC Current", + "de": "AC Strom" + }, + { + "token": "FREQUENCY", + "en": "Frequency", + "de": "Frequenz" + }, + { + "token": "EFFICIENCY", + "en": "Efficiency", + "de": "Effizienz" + }, + { + "token": "POWER_FACTOR", + "en": "Power Factor", + "de": "Leistungsfaktor" + }, + { + "token": "IRRADIATION", + "en": "Irradiation", + "de": "Einstrahlung" + }, + { + "token": "DC_CURRENT", + "en": "DC Current", + "de": "DC Strom" + }, + { + "token": "LAST_RECEIVED", + "en": "Last received data requested at", + "de": "Zuletzt empfangene Daten wurden angefragt um" + }, + { + "token": "NOTHING_RECEIVED", + "en": "nothing received", + "de": "nichts empfangen" + }, + { + "token": "ALARMS_MODAL", + "en": "Alarms of inverter", + "de": "Meldungen von Wechselrichter" + }, + { + "token": "SERIAL", + "en": "Serial", + "de": "Seriennummer" + }, + { + "token": "BTN_SHOW", + "en": "show", + "de": "anzeigen" + }, + { + "token": "EVENT", + "en": "Event", + "de": "Ereignis" + }, + { + "token": "END", + "en": "End", + "de": "Ende" + }, + { + "token": "HW_NUMBER", + "en": "Hardware Number", + "de": "Hardware Nummer" + }, + { + "token": "INV_INFO", + "en": "Info for inverter", + "de": "Info für Wechselrichter" + }, + { + "token": "VALUE", + "en": "Value", + "de": "Wert" + }, + { + "token": "RANGE", + "en": "Range", + "de": "Bereich" + }, + { + "token": "DEFAULT", + "en": "Default", + "de": "Standard" + }, + { + "token": "PROFILE_NOT_READ", + "en": "Profile was not read until now, maybe turned off", + "de": "Grid-Profil wurde bis jetzt noch nicht gelesen oder das Auslesen ist nicht aktiv" + }, + { + "token": "UNKNOWN_PROFILE", + "en": "Unknown Profile", + "de": "Unbekanntes Profil" + }, + { + "token": "OPEN_ISSUE", + "en": "Please open a new issue at https://github.com/lumapu/ahoy and copy the raw data into it", + "de": "Bitte erstelle einen neuen Issue auf https://github.com/lumapu/ahoy und kopiere die Rohdaten hinein" + }, + { + "token": "PROFILE_MODAL", + "en": "Grid Profile for inverter", + "de": "Grid-Profil für Wechselrichter" + }, + { + "token": "TX_COUNT", + "en": "TX count", + "de": "Sendezähler" + }, + { + "token": "RX_SUCCESS", + "en": "RX success", + "de": "erfolgreicher Empfang" + }, + { + "token": "RX_FAIL", + "en": "RX fail", + "de": "fehlgeschlagener Empfang" + }, + { + "token": "RX_NO_ANSWER", + "en": "RX no answer", + "de": "keine Antwort" + }, + { + "token": "RX_FRAGMENTS", + "en": "RX fragments", + "de": "empfangene Fragmente" + }, + { + "token": "TX_RETRANSMITS", + "en": "TX retransmits", + "de": "erneute Sendeversuche" + }, + { + "token": "INV_LOSS_RATE", + "en": "Inverter loss rate", + "de": "Wechselrichter Empfangsqualität" + }, + { + "token": "DTU_LOSS_RATE", + "en": "DTU loss rate", + "de": "DTU Empfangsqualität" + }, + { + "token": "RADIO_STAT_MODAL", + "en": "Radio statistics for inverter", + "de": "Funkstatistik für Wechselrichter" + }, + { + "token": "LOST_1", + "en": "lost", + "de": "" + }, + { + "token": "LOST_2", + "en": "of", + "de": "von" + }, + { + "token": "LOST_3", + "en": "", + "de": "verloren" + }, + { + "token": "LIMIT_VALUE", + "en": "Limit Value", + "de": "Wert der Leistungsbegrenzung" + }, + { + "token": "KEEP_LIMIT", + "en": "Keep limit over inverter restart", + "de": "Leistungsbegrenzung dauerhaft speichern (im Wechselrichter)" + }, + { + "token": "BTN_APPLY", + "en": "Apply", + "de": "anwenden" + }, + { + "token": "CONTROL", + "en": "Control", + "de": "Ansteuern" + }, + { + "token": "RESTART", + "en": "restart", + "de": "neustarten" + }, + { + "token": "TURN_OFF", + "en": "turn off", + "de": "ausschalten" + }, + { + "token": "TURN_ON", + "en": "turn on", + "de": "anschalten" + }, + { + "token": "RESULT", + "en": "Result", + "de": "Status" + }, + { + "token": "POWER_LIMIT_MODAL", + "en": "Active Power Control for inverter", + "de": "Leistungsbegrenzung für Wechselrichter" + }, + { + "token": "CMD_RECEIVED_WAIT_ACK", + "en": "received command, waiting for inverter acknowledge ...", + "de": "Befehl erhalten, warte auf Bestäigung von Wechselrichter ..." + }, + { + "token": "COMMAND_RECEIVED", + "en": "command received", + "de": "Befehl erhalten" + }, + { + "token": "ERROR", + "en": "Error", + "de": "Fehler" + }, + { + "token": "INV_ACK", + "en": "inverter acknowledged active power control command", + "de": "Wechselrichter hat die Leistungsbegrenzung akzeptiert" + } + ] + }, + { + "name": "save.html", + "list": [ + { + "token": "SAVE_SETTINGS", + "en": "Saving settings...", + "de": "Einstellungen werden gespeichert ..." + }, + { + "token": "SUCCESS_SAVED_RELOAD", + "en": "Settings successfully saved. Automatic page reload in 3 seconds.", + "de": "Einstellungen erfolgreich gespeichert. Automatische Weiterleitung in 3 Sekunden" + }, + { + "token": "SUCCESS_SAVED_REBOOT", + "en": "Settings successfully saved. Rebooting. Automatic redirect in", + "de": "Einstellungen erfolgreich gespeichert. Automatische Weiterleitung nach Reboot in" + }, + { + "token": "SECONDS", + "en": "seconds", + "de": "Sekunden" + }, + { + "token": "FAILED_SAVE", + "en": "Failed saving settings", + "de": "Fehler beim Speichern" + } + ] + } + ] +} From 135a1f8032fba3c42cf965b7eb63fabdc320b78b Mon Sep 17 00:00:00 2001 From: lumapu Date: Sun, 7 Jan 2024 02:42:42 +0100 Subject: [PATCH 033/179] 0.8.47 * merge PR: add defines for retry attempts #1329 --- src/CHANGES.md | 1 + src/hm/Communication.h | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/src/CHANGES.md b/src/CHANGES.md index c403f8b0..cddfc86a 100644 --- a/src/CHANGES.md +++ b/src/CHANGES.md @@ -7,6 +7,7 @@ * updated U8g2 to `2.35.9` * started to convert deprecated functions of new ArduinoJson `7.0.0` * started to have german translations of all variants (environments) #925 #1199 +* merge PR: add defines for retry attempts #1329 ## 0.8.46 - 2024-01-06 * improved communication diff --git a/src/hm/Communication.h b/src/hm/Communication.h index 3c7c82a5..d0878dd5 100644 --- a/src/hm/Communication.h +++ b/src/hm/Communication.h @@ -119,7 +119,6 @@ class Communication : public CommQueue<> { setAttempt(); if((q->cmd == AlarmData) || (q->cmd == GridOnProFilePara)) incrAttempt(q->cmd == AlarmData? MORE_ATTEMPS_ALARMDATA : MORE_ATTEMPS_GRIDONPROFILEPARA); -/// statt 5:3 mState = States::WAIT; break; From 954b4ff7064abd02dd10cc19de648f491fcbebb8 Mon Sep 17 00:00:00 2001 From: you69man Date: Tue, 2 Jan 2024 11:36:14 +0100 Subject: [PATCH 034/179] implementation of power graph --- src/plugins/Display/Display.h | 3 + src/plugins/Display/Display_Mono.h | 145 ++++++++++++++++++++++ src/plugins/Display/Display_Mono_128X64.h | 74 ++++++----- src/plugins/Display/Display_Mono_84X48.h | 78 +++++++----- src/plugins/Display/Display_data.h | 2 + 5 files changed, 240 insertions(+), 62 deletions(-) diff --git a/src/plugins/Display/Display.h b/src/plugins/Display/Display.h index 0cfbd710..387a6a0d 100644 --- a/src/plugins/Display/Display.h +++ b/src/plugins/Display/Display.h @@ -165,6 +165,9 @@ class Display { else mDisplayData.utcTs = 0; + mDisplayData.pGraphStartTime = mApp->getSunrise(); + mDisplayData.pGraphEndTime = mApp->getSunset(); + if (mMono ) { mMono->disp(); } diff --git a/src/plugins/Display/Display_Mono.h b/src/plugins/Display/Display_Mono.h index 322e991e..1d925849 100644 --- a/src/plugins/Display/Display_Mono.h +++ b/src/plugins/Display/Display_Mono.h @@ -60,12 +60,23 @@ class DisplayMono { mLuminance = lum; mDisplay->setContrast(mLuminance); } + + monoMaintainDispSwitchState(); } protected: U8G2* mDisplay; DisplayData *mDisplayData; + float *mPgData=nullptr; + uint8_t mPgWidth=0; + uint8_t mPgHeight=0; + float mPgMaxPwr=0.0; +// float mPgMaxAvailPower = 0.0; + uint32_t mPgPeriod=0; // seconds + uint32_t mPgTimeOfDay=0; + uint8_t mPgLastPos=0; + uint8_t mType; uint16_t mDispWidth; uint16_t mDispHeight; @@ -81,9 +92,16 @@ class DisplayMono { uint8_t mExtra; int8_t mPixelshift=0; TimeMonitor mDisplayTime = TimeMonitor(1000 * DISP_DEFAULT_TIMEOUT, true); + TimeMonitor mDispSwitchTime = TimeMonitor(10000, true); + uint8_t mDispSwitchState = 0; bool mDisplayActive = true; // always start with display on char mFmtText[DISP_FMT_TEXT_LEN]; + enum _dispSwitchState { + d_POWER_TEXT = 0, + d_POWER_GRAPH = 1, + }; + // Common initialization function to be called by subclasses void monoInit(U8G2* display, uint8_t type, DisplayData *displayData) { mDisplay = display; @@ -97,6 +115,133 @@ class DisplayMono { mDispHeight = mDisplay->getDisplayHeight(); } + void monoMaintainDispSwitchState(void) { + switch(mDispSwitchState) { + case d_POWER_TEXT: + if (mDispSwitchTime.isTimeout()) { + mDispSwitchState = d_POWER_GRAPH; + mDispSwitchTime.startTimeMonitor(5000); + } + break; + case d_POWER_GRAPH: + if (mDispSwitchTime.isTimeout()) { + mDispSwitchState = d_POWER_TEXT; + mDispSwitchTime.startTimeMonitor(10000); + } + break; + } + } + + void initPowerGraph(uint8_t width, uint8_t height) { + mPgWidth = width; + mPgHeight = height; + mPgData = new float[mPgWidth]; + //memset(mPgData, 0, mPgWidth); + resetPowerGraph(); +/* + Inverter<> *iv; + mPgMaxAvailPower = 0; + uint8_t nInv = mSys->getNumInverters(); + for (uint8_t i = 0; i < nInv; i++) { + iv = mSys->getInverterByPos(i); + if (iv == NULL) + continue; + for (uint8_t ch = 0; ch < 6; ch++) { + mPgMaxAvailPower += iv->config->chMaxPwr[ch]; + } + } + DBGPRINTLN("max. Power = " + String(mPgMaxAvailPower));*/ + } + + void resetPowerGraph() { + if (mPgData != nullptr) { + mPgMaxPwr = 0.0; + mPgLastPos = 0; + for (uint8_t i = 0; i < mPgWidth; i++) + mPgData[i] = 0.0; + } + } + + uint8_t sss2pgpos(uint seconds_since_start) { + return(seconds_since_start * (mPgWidth - 1) / (mDisplayData->pGraphEndTime - mDisplayData->pGraphStartTime)); + } + + void calcPowerGraphValues() { + mPgPeriod = mDisplayData->pGraphEndTime - mDisplayData->pGraphStartTime; // length of power graph for scaling of x-axis + uint32_t oldTimeOfDay = mPgTimeOfDay; + mPgTimeOfDay = (mDisplayData->utcTs > mDisplayData->pGraphStartTime) ? mDisplayData->utcTs - mDisplayData->pGraphStartTime : 0; // current time of day with respect to current sunrise time + if (oldTimeOfDay > mPgTimeOfDay) // new day -> reset old data + resetPowerGraph(); + mPgLastPos = std::min((uint8_t) (mPgTimeOfDay * (mPgWidth - 1) / mPgPeriod), (uint8_t) (mPgWidth - 1)); // current datapoint based on currenct time of day + } + + void addPowerGraphEntry(float val) { + if (mDisplayData->utcTs > 0) { // precondition: utc time available + calcPowerGraphValues(); + //mPgData[mPgLastPos] = std::max(mPgData[mPgLastPos], (uint8_t) (val * 255.0 / mPgMaxAvailPower)); // normalizing of data to 0-255 + mPgData[mPgLastPos] = std::max(mPgData[mPgLastPos], val); + mPgMaxPwr = std::max(mPgMaxPwr, val); // max value of stored data for scaling of y-axis + } + } + + uint8_t getPowerGraphXpos(uint8_t p) { // + if ((p <= mPgLastPos) && (mPgLastPos > 0)) + return((p * (mPgWidth - 1)) / mPgLastPos); // scaling of x-axis + else + return(0); + } + + uint8_t getPowerGraphYpos(uint8_t p) { + if (p < mPgWidth) + //return(((uint32_t) mPgData[p] * (uint32_t) mPgMaxAvailPower) * (uint32_t) mPgHeight / mPgMaxPwr / 255); // scaling of normalized data (0-255) to graph height + return((mPgData[p] * (uint32_t) mPgHeight / mPgMaxPwr)); // scaling of data to graph height + else + return(0); + } + + void plotPowerGraph(uint8_t xoff, uint8_t yoff) { + // draw axes + mDisplay->drawLine(xoff, yoff, xoff, yoff - mPgHeight); // vertical axis + mDisplay->drawLine(xoff, yoff, xoff + mPgWidth, yoff); // horizontal axis + + // draw X scale + tmElements_t tm; + breakTime(mDisplayData->pGraphEndTime, tm); + uint8_t endHourPg = tm.Hour; + breakTime(mDisplayData->utcTs, tm); + uint8_t endHour = std::min(endHourPg, tm.Hour); + breakTime(mDisplayData->pGraphStartTime, tm); + tm.Hour += 1; + tm.Minute = 0; + tm.Second = 0; + for (; tm.Hour <= endHour; tm.Hour++) { + uint8_t x_pos_screen = getPowerGraphXpos(sss2pgpos((uint32_t) makeTime(tm) - mDisplayData->pGraphStartTime)); // scale horizontal axis + mDisplay->drawPixel(xoff + x_pos_screen, yoff - 1); + } + + // draw Y scale + uint16_t scale_y = 10; + uint32_t maxpwr_int = static_cast(std::round(mPgMaxPwr)); + if (maxpwr_int > 100) + scale_y = 100; + for (uint32_t i = scale_y; i <= maxpwr_int; i += scale_y) { + uint8_t ypos = yoff - static_cast(std::round(i * (float) mPgHeight / mPgMaxPwr)); // scale vertical axis + mDisplay->drawPixel(xoff + 1, ypos); + } + + // draw curve + for (uint8_t i = 1; i <= mPgLastPos; i++) { + mDisplay->drawLine(xoff + getPowerGraphXpos(i - 1), yoff - getPowerGraphYpos(i - 1), + xoff + getPowerGraphXpos(i), yoff - getPowerGraphYpos(i)); + } + + // print max power value + mDisplay->setFont(u8g2_font_4x6_tr); + snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%dW", static_cast(std::round(mPgMaxPwr))); + mDisplay->drawStr(xoff + 3, yoff - mPgHeight + 5, mFmtText); + } + + // pixelshift screensaver with wipe effect void calcPixelShift(int range) { int8_t mod = (millis() / 10000) % ((range >> 1) << 2); mPixelshift = mScreenSaver == 1 ? ((mod < range) ? mod - (range >> 1) : -(mod - range - (range >> 1) + 1)) : 0; diff --git a/src/plugins/Display/Display_Mono_128X64.h b/src/plugins/Display/Display_Mono_128X64.h index afb581dd..8f762dde 100644 --- a/src/plugins/Display/Display_Mono_128X64.h +++ b/src/plugins/Display/Display_Mono_128X64.h @@ -34,6 +34,8 @@ class DisplayMono128X64 : public DisplayMono { } calcLinePositions(); + initPowerGraph(mDispWidth - 20, mLineYOffsets[4] - mLineYOffsets[1]); + printText("Ahoy!", l_Ahoy, 0xff); printText("ahoydtu.de", l_Website, 0xff); printText(mDisplayData->version, l_Version, 0xff); @@ -61,23 +63,16 @@ class DisplayMono128X64 : public DisplayMono { // calculate current pixelshift for pixelshift screensaver calcPixelShift(pixelShiftRange); - // print total power + // add new power data to power graph if (mDisplayData->nrProducing > 0) { - if (mDisplayData->totalPower > 9999.0) - snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%.2f kW", (mDisplayData->totalPower / 1000.0)); - else - snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%.0f W", mDisplayData->totalPower); - - printText(mFmtText, l_TotalPower, 0xff); - } else { - printText("offline", l_TotalPower, 0xff); + addPowerGraphEntry(mDisplayData->totalPower); } // print Date and time if (0 != mDisplayData->utcTs) printText(ah::getDateTimeStrShort(gTimezone.toLocal(mDisplayData->utcTs)).c_str(), l_Time, 0xff); - // dynamic status bar, alternatively: + // alternatively: // print ip address if (!(mExtra % 5) && (mDisplayData->ipAddress)) { snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%s", (mDisplayData->ipAddress).toString().c_str()); @@ -115,22 +110,41 @@ class DisplayMono128X64 : public DisplayMono { mDisplay->drawStr(pos + moon_pos + mPixelshift, mLineYOffsets[l_Status], "H"); // moon symbol } - // print yields - mDisplay->setFont(u8g2_font_ncenB10_symbols10_ahoy); - mDisplay->drawStr(16 + mPixelshift, mLineYOffsets[l_YieldDay], "I"); // day symbol - mDisplay->drawStr(16 + mPixelshift, mLineYOffsets[l_YieldTotal], "D"); // total symbol + if (mDispSwitchState == d_POWER_TEXT) { - if (mDisplayData->totalYieldDay > 9999.0) - snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%.2f kWh", mDisplayData->totalYieldDay / 1000.0); - else - snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%.0f Wh", mDisplayData->totalYieldDay); - printText(mFmtText, l_YieldDay, 0xff); + // print total power + if (mDisplayData->nrProducing > 0) { + if (mDisplayData->totalPower > 9999.0) + snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%.2f kW", (mDisplayData->totalPower / 1000.0)); + else + snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%.0f W", mDisplayData->totalPower); - if (mDisplayData->totalYieldTotal > 9999.0) - snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%.2f MWh", mDisplayData->totalYieldTotal / 1000.0); - else - snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%.0f kWh", mDisplayData->totalYieldTotal); - printText(mFmtText, l_YieldTotal, 0xff); + printText(mFmtText, l_TotalPower, 0xff); + } else { + printText("offline", l_TotalPower, 0xff); + } + + // print yields + mDisplay->setFont(u8g2_font_ncenB10_symbols10_ahoy); + mDisplay->drawStr(16 + mPixelshift, mLineYOffsets[l_YieldDay], "I"); // day symbol + mDisplay->drawStr(16 + mPixelshift, mLineYOffsets[l_YieldTotal], "D"); // total symbol + + if (mDisplayData->totalYieldDay > 9999.0) + snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%.2f kWh", mDisplayData->totalYieldDay / 1000.0); + else + snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%.0f Wh", mDisplayData->totalYieldDay); + printText(mFmtText, l_YieldDay, 0xff); + + if (mDisplayData->totalYieldTotal > 9999.0) + snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%.2f MWh", mDisplayData->totalYieldTotal / 1000.0); + else + snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%.0f kWh", mDisplayData->totalYieldTotal); + printText(mFmtText, l_YieldTotal, 0xff); + + } else { + // plot power graph + plotPowerGraph(10 + mPixelshift, mLineYOffsets[4] - 1); + } // draw dynamic RSSI bars int xoffs; @@ -142,10 +156,11 @@ class DisplayMono128X64 : public DisplayMono { for (int i = 0; i < 4; i++) { int radio_rssi_threshold = -60 - i * 10; int wifi_rssi_threshold = -60 - i * 10; + uint8_t barwidth = std::min(4 - i, 3); if (mDisplayData->RadioRSSI > radio_rssi_threshold) - mDisplay->drawBox(xoffs + mPixelshift, 8 + (rssi_bar_height + 1) * i, 4 - i, rssi_bar_height); + mDisplay->drawBox(xoffs + mPixelshift, 8 + (rssi_bar_height + 1) * i, barwidth, rssi_bar_height); if (mDisplayData->WifiRSSI > wifi_rssi_threshold) - mDisplay->drawBox(mDispWidth - 4 - xoffs + mPixelshift + i, 8 + (rssi_bar_height + 1) * i, 4 - i, rssi_bar_height); + mDisplay->drawBox(mDispWidth - barwidth - xoffs + mPixelshift, 8 + (rssi_bar_height + 1) * i, barwidth, rssi_bar_height); } // draw dynamic antenna and WiFi symbols mDisplay->setFont(u8g2_font_ncenB10_symbols10_ahoy); @@ -160,9 +175,6 @@ class DisplayMono128X64 : public DisplayMono { mDisplay->drawStr(mDispWidth - mDisplay->getStrWidth(sym) - xoffs + mPixelshift, mLineYOffsets[l_RSSI], sym); mDisplay->sendBuffer(); - - mDisplay->sendBuffer(); - mExtra++; } @@ -198,8 +210,8 @@ class DisplayMono128X64 : public DisplayMono { mLineYOffsets[i] = yOff; dsc = mDisplay->getDescent(); yOff -= dsc; - if (l_Time==i) // prevent time and status line to touch - yOff+=1; // -> one pixels space + if (l_Time == i) // prevent time and status line to touch + yOff++; // -> one pixels space i++; } while(l_MAX_LINES>i); } diff --git a/src/plugins/Display/Display_Mono_84X48.h b/src/plugins/Display/Display_Mono_84X48.h index 7e5c157f..7f0281ed 100644 --- a/src/plugins/Display/Display_Mono_84X48.h +++ b/src/plugins/Display/Display_Mono_84X48.h @@ -5,7 +5,6 @@ #pragma once #include "Display_Mono.h" -#include "../../utils/dbg.h" class DisplayMono84X48 : public DisplayMono { public: @@ -23,6 +22,9 @@ class DisplayMono84X48 : public DisplayMono { u8g2_cb_t *rot = (u8g2_cb_t *)((rotation != 0x00) ? U8G2_R2 : U8G2_R0); monoInit(new U8G2_PCD8544_84X48_F_4W_SW_SPI(rot, clock, data, cs, dc, reset), type, displayData); calcLinePositions(); + + initPowerGraph(mDispWidth - 16, mLineYOffsets[4] - mLineYOffsets[1] - 2); + printText("Ahoy!", l_Ahoy, 0xff); printText("ahoydtu.de", l_Website, 0xff); printText(mDisplayData->version, l_Version, 0xff); @@ -45,16 +47,9 @@ class DisplayMono84X48 : public DisplayMono { mDisplay->drawPixel(mDispWidth-1, mDispHeight-1); */ - // print total power + // add new power data to power graph if (mDisplayData->nrProducing > 0) { - if (mDisplayData->totalPower > 9999.0) - snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%.2f kW", (mDisplayData->totalPower / 1000.0)); - else - snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%.0f W", mDisplayData->totalPower); - - printText(mFmtText, l_TotalPower, 0xff); - } else { - printText("offline", l_TotalPower, 0xff); + addPowerGraphEntry(mDisplayData->totalPower); } // print Date and time @@ -80,31 +75,51 @@ class DisplayMono84X48 : public DisplayMono { printText(mFmtText, l_Status, 0xff); } - // print yields - printText("\x88", l_YieldDay, 10); // day symbol - printText("\x83", l_YieldTotal, 10); // total symbol + if (mDispSwitchState == d_POWER_TEXT) { - if (mDisplayData->totalYieldDay > 9999.0) - snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%.2f kWh", mDisplayData->totalYieldDay / 1000.0); - else - snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%.0f Wh", mDisplayData->totalYieldDay); - printText(mFmtText, l_YieldDay, 0xff); + // print total power + if (mDisplayData->nrProducing > 0) { + if (mDisplayData->totalPower > 9999.0) + snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%.2f kW", (mDisplayData->totalPower / 1000.0)); + else + snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%.0f W", mDisplayData->totalPower); - if (mDisplayData->totalYieldTotal > 9999.0) - snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%.2f MWh", mDisplayData->totalYieldTotal / 1000.0); - else - snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%.0f kWh", mDisplayData->totalYieldTotal); - printText(mFmtText, l_YieldTotal, 0xff); + printText(mFmtText, l_TotalPower, 0xff); + } else { + printText("offline", l_TotalPower, 0xff); + } + + // print day yield + printText("\x88", l_YieldDay, 10); // day symbol + if (mDisplayData->totalYieldDay > 9999.0) + snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%.2f kWh", mDisplayData->totalYieldDay / 1000.0); + else + snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%.0f Wh", mDisplayData->totalYieldDay); + printText(mFmtText, l_YieldDay, 0xff); + + // print total yield + printText("\x83", l_YieldTotal, 10); // total symbol + if (mDisplayData->totalYieldTotal > 9999.0) + snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%.2f MWh", mDisplayData->totalYieldTotal / 1000.0); + else + snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%.0f kWh", mDisplayData->totalYieldTotal); + printText(mFmtText, l_YieldTotal, 0xff); + + } else { + // plot power graph + plotPowerGraph(8, mLineYOffsets[4] - 1); + } - // draw dynamic Nokia RSSI bars + // draw dynamic RSSI bars int rssi_bar_height = 7; - for (int i=0; i<4;i++) { - int radio_rssi_threshold = -60 - i*10; // radio rssi not yet tested in reality! - int wifi_rssi_threshold = -60 - i*10; + for (int i = 0; i < 4; i++) { + int radio_rssi_threshold = -60 - i * 10; + int wifi_rssi_threshold = -60 - i * 10; + uint8_t barwidth = std::min(4 - i, 3); if (mDisplayData->RadioRSSI > radio_rssi_threshold) - mDisplay->drawBox(0, 8+(rssi_bar_height+1)*i, 4-i,rssi_bar_height); + mDisplay->drawBox(0, 8 + (rssi_bar_height + 1) * i, barwidth, rssi_bar_height); if (mDisplayData->WifiRSSI > wifi_rssi_threshold) - mDisplay->drawBox(mDispWidth-4+i, 8+(rssi_bar_height+1)*i, 4-i,rssi_bar_height); + mDisplay->drawBox(mDispWidth - barwidth, 8 + (rssi_bar_height + 1) * i, barwidth, rssi_bar_height); } // draw dynamic antenna and WiFi symbols @@ -150,7 +165,7 @@ class DisplayMono84X48 : public DisplayMono { yOff += asc; mLineYOffsets[i] = yOff; dsc = mDisplay->getDescent(); - if (l_TotalPower!=i) // power line needs no descent spacing + if (l_TotalPower != i) // power line needs no descent spacing yOff -= dsc; yOff++; // instead lets spend one pixel space between all lines i++; @@ -158,7 +173,8 @@ class DisplayMono84X48 : public DisplayMono { } inline void setLineFont(uint8_t line) { - if ((line == l_TotalPower) || (line == l_Ahoy)) + if ((line == l_TotalPower) || + (line == l_Ahoy)) mDisplay->setFont(u8g2_font_logisoso16_tr); else mDisplay->setFont(u8g2_font_5x8_symbols_ahoy); diff --git a/src/plugins/Display/Display_data.h b/src/plugins/Display/Display_data.h index a7a7ecee..2017403a 100644 --- a/src/plugins/Display/Display_data.h +++ b/src/plugins/Display/Display_data.h @@ -9,6 +9,8 @@ struct DisplayData { float totalYieldDay=0.0f; // indicate day yield (Wh) float totalYieldTotal=0.0f; // indicate total yield (kWh) uint32_t utcTs=0; // indicate absolute timestamp (utc unix time). 0 = time is not synchonized + uint32_t pGraphStartTime=0; // starttime for power graph (e.g. sunRise) + uint32_t pGraphEndTime=0; // starttime for power graph (e.g. sunSet) uint8_t nrProducing=0; // indicate number of producing inverters uint8_t nrSleeping=0; // indicate number of sleeping inverters bool WifiSymbol = false; // indicate if WiFi is connected From 4496981b3b9eba17231eb0c83052f745f6e21d89 Mon Sep 17 00:00:00 2001 From: you69man Date: Sat, 6 Jan 2024 20:39:17 +0100 Subject: [PATCH 035/179] add option for show ratio --- src/config/settings.h | 4 ++++ src/plugins/Display/Display.h | 8 ++++--- src/plugins/Display/Display_Mono.h | 27 ++++++++++++++++------- src/plugins/Display/Display_Mono_128X32.h | 3 ++- src/plugins/Display/Display_Mono_128X64.h | 3 ++- src/plugins/Display/Display_Mono_64X48.h | 3 ++- src/plugins/Display/Display_Mono_84X48.h | 3 ++- src/web/RestApi.h | 25 +++++++++++---------- src/web/html/setup.html | 11 +++++++++ src/web/web.h | 1 + 10 files changed, 61 insertions(+), 27 deletions(-) diff --git a/src/config/settings.h b/src/config/settings.h index 8ce71167..83e2eaa3 100644 --- a/src/config/settings.h +++ b/src/config/settings.h @@ -165,6 +165,7 @@ typedef struct { uint8_t type; bool pwrSaveAtIvOffline; uint8_t screenSaver; + uint8_t graph_ratio; uint8_t rot; //uint16_t wakeUp; //uint16_t sleepAt; @@ -461,6 +462,7 @@ class settings { mCfg.plugin.display.pwrSaveAtIvOffline = false; mCfg.plugin.display.contrast = 60; mCfg.plugin.display.screenSaver = 1; // default: 1 .. pixelshift for OLED for downward compatibility + mCfg.plugin.display.graph_ratio = 50; mCfg.plugin.display.rot = 0; mCfg.plugin.display.disp_data = DEF_PIN_OFF; // SDA mCfg.plugin.display.disp_clk = DEF_PIN_OFF; // SCL @@ -697,6 +699,7 @@ class settings { disp[F("type")] = mCfg.plugin.display.type; disp[F("pwrSafe")] = (bool)mCfg.plugin.display.pwrSaveAtIvOffline; disp[F("screenSaver")] = mCfg.plugin.display.screenSaver; + disp[F("graph_ratio")] = mCfg.plugin.display.graph_ratio; disp[F("rotation")] = mCfg.plugin.display.rot; //disp[F("wake")] = mCfg.plugin.display.wakeUp; //disp[F("sleep")] = mCfg.plugin.display.sleepAt; @@ -713,6 +716,7 @@ class settings { getVal(disp, F("type"), &mCfg.plugin.display.type); getVal(disp, F("pwrSafe"), &mCfg.plugin.display.pwrSaveAtIvOffline); getVal(disp, F("screenSaver"), &mCfg.plugin.display.screenSaver); + getVal(disp, F("graph_ratio"), &mCfg.plugin.display.graph_ratio); getVal(disp, F("rotation"), &mCfg.plugin.display.rot); //mCfg.plugin.display.wakeUp = disp[F("wake")]; //mCfg.plugin.display.sleepAt = disp[F("sleep")]; diff --git a/src/plugins/Display/Display.h b/src/plugins/Display/Display.h index 387a6a0d..0b06cc58 100644 --- a/src/plugins/Display/Display.h +++ b/src/plugins/Display/Display.h @@ -54,7 +54,7 @@ class Display { default: mMono = NULL; break; } if(mMono) { - mMono->config(mCfg->pwrSaveAtIvOffline, mCfg->screenSaver, mCfg->contrast); + mMono->config(mCfg->pwrSaveAtIvOffline, mCfg->screenSaver, mCfg->contrast, mCfg->graph_ratio); mMono->init(mCfg->type, mCfg->rot, mCfg->disp_cs, mCfg->disp_dc, 0xff, mCfg->disp_clk, mCfg->disp_data, &mDisplayData); } @@ -75,10 +75,12 @@ class Display { } void tickerSecond() { + bool request_refresh = false; + if (mMono != NULL) - mMono->loop(mCfg->contrast, motionSensorActive()); + request_refresh = mMono->loop(mCfg->contrast, motionSensorActive()); - if (mNewPayload || (((++mLoopCnt) % 5) == 0)) { + if (mNewPayload || (((++mLoopCnt) % 5) == 0) || request_refresh) { DataScreen(); mNewPayload = false; mLoopCnt = 0; diff --git a/src/plugins/Display/Display_Mono.h b/src/plugins/Display/Display_Mono.h index 1d925849..76447ce3 100644 --- a/src/plugins/Display/Display_Mono.h +++ b/src/plugins/Display/Display_Mono.h @@ -26,12 +26,12 @@ class DisplayMono { DisplayMono() {}; virtual void init(uint8_t type, uint8_t rot, uint8_t cs, uint8_t dc, uint8_t reset, uint8_t clock, uint8_t data, DisplayData *displayData) = 0; - virtual void config(bool enPowerSave, uint8_t screenSaver, uint8_t lum) = 0; + virtual void config(bool enPowerSave, uint8_t screenSaver, uint8_t lum, uint8_t graph_ratio) = 0; virtual void disp(void) = 0; // Common loop function, manages display on/off functions for powersave and screensaver with motionsensor // can be overridden by subclasses - virtual void loop(uint8_t lum, bool motion) { + virtual bool loop(uint8_t lum, bool motion) { bool dispConditions = (!mEnPowerSave || (mDisplayData->nrProducing > 0)) && ((mScreenSaver != 2) || motion); // screensaver 2 .. motionsensor @@ -61,7 +61,7 @@ class DisplayMono { mDisplay->setContrast(mLuminance); } - monoMaintainDispSwitchState(); + return(monoMaintainDispSwitchState()); } protected: @@ -84,6 +84,7 @@ class DisplayMono { bool mEnPowerSave; uint8_t mScreenSaver = 1; // 0 .. off; 1 .. pixelShift; 2 .. motionsensor uint8_t mLuminance; + uint8_t mGraphRatio; uint8_t mLoopCnt; uint8_t mLineXOffsets[5] = {}; @@ -92,8 +93,8 @@ class DisplayMono { uint8_t mExtra; int8_t mPixelshift=0; TimeMonitor mDisplayTime = TimeMonitor(1000 * DISP_DEFAULT_TIMEOUT, true); - TimeMonitor mDispSwitchTime = TimeMonitor(10000, true); - uint8_t mDispSwitchState = 0; + TimeMonitor mDispSwitchTime = TimeMonitor(); + uint8_t mDispSwitchState; bool mDisplayActive = true; // always start with display on char mFmtText[DISP_FMT_TEXT_LEN]; @@ -113,23 +114,33 @@ class DisplayMono { mDisplay->clearBuffer(); mDispWidth = mDisplay->getDisplayWidth(); mDispHeight = mDisplay->getDisplayHeight(); + mDispSwitchTime.stopTimeMonitor(); + mDispSwitchState = d_POWER_TEXT; + if (mGraphRatio == 100) // if graph ratio is 100% start in graph mode + mDispSwitchState = d_POWER_GRAPH; + else if (mGraphRatio != 0) + mDispSwitchTime.startTimeMonitor(150 * (100 - mGraphRatio)); // start time monitor only if ratio is neither 0 nor 100 } - void monoMaintainDispSwitchState(void) { + bool monoMaintainDispSwitchState(void) { + bool change = false; switch(mDispSwitchState) { case d_POWER_TEXT: if (mDispSwitchTime.isTimeout()) { mDispSwitchState = d_POWER_GRAPH; - mDispSwitchTime.startTimeMonitor(5000); + mDispSwitchTime.startTimeMonitor(150 * mGraphRatio); // mGraphRatio: 0-100 Gesamtperiode 15000 ms + change = true; } break; case d_POWER_GRAPH: if (mDispSwitchTime.isTimeout()) { mDispSwitchState = d_POWER_TEXT; - mDispSwitchTime.startTimeMonitor(10000); + mDispSwitchTime.startTimeMonitor(150 * (100 - mGraphRatio)); + change = true; } break; } + return change; } void initPowerGraph(uint8_t width, uint8_t height) { diff --git a/src/plugins/Display/Display_Mono_128X32.h b/src/plugins/Display/Display_Mono_128X32.h index fa0cacdf..edc79b75 100644 --- a/src/plugins/Display/Display_Mono_128X32.h +++ b/src/plugins/Display/Display_Mono_128X32.h @@ -12,10 +12,11 @@ class DisplayMono128X32 : public DisplayMono { mExtra = 0; } - void config(bool enPowerSave, uint8_t screenSaver, uint8_t lum) { + void config(bool enPowerSave, uint8_t screenSaver, uint8_t lum, uint8_t graph_ratio) { mEnPowerSave = enPowerSave; mScreenSaver = screenSaver; mLuminance = lum; + mGraphRatio = graph_ratio; } void init(uint8_t type, uint8_t rotation, uint8_t cs, uint8_t dc, uint8_t reset, uint8_t clock, uint8_t data, DisplayData *displayData) { diff --git a/src/plugins/Display/Display_Mono_128X64.h b/src/plugins/Display/Display_Mono_128X64.h index 8f762dde..c578f9a4 100644 --- a/src/plugins/Display/Display_Mono_128X64.h +++ b/src/plugins/Display/Display_Mono_128X64.h @@ -12,10 +12,11 @@ class DisplayMono128X64 : public DisplayMono { mExtra = 0; } - void config(bool enPowerSave, uint8_t screenSaver, uint8_t lum) { + void config(bool enPowerSave, uint8_t screenSaver, uint8_t lum, uint8_t graph_ratio) { mEnPowerSave = enPowerSave; mScreenSaver = screenSaver; mLuminance = lum; + mGraphRatio = graph_ratio; } void init(uint8_t type, uint8_t rotation, uint8_t cs, uint8_t dc, uint8_t reset, uint8_t clock, uint8_t data, DisplayData *displayData) { diff --git a/src/plugins/Display/Display_Mono_64X48.h b/src/plugins/Display/Display_Mono_64X48.h index 68cac96f..5d1bf6ac 100644 --- a/src/plugins/Display/Display_Mono_64X48.h +++ b/src/plugins/Display/Display_Mono_64X48.h @@ -12,10 +12,11 @@ class DisplayMono64X48 : public DisplayMono { mExtra = 0; } - void config(bool enPowerSave, uint8_t screenSaver, uint8_t lum) { + void config(bool enPowerSave, uint8_t screenSaver, uint8_t lum, uint8_t graph_ratio) { mEnPowerSave = enPowerSave; mScreenSaver = screenSaver; mLuminance = lum; + mGraphRatio = graph_ratio; } void init(uint8_t type, uint8_t rotation, uint8_t cs, uint8_t dc, uint8_t reset, uint8_t clock, uint8_t data, DisplayData *displayData) { diff --git a/src/plugins/Display/Display_Mono_84X48.h b/src/plugins/Display/Display_Mono_84X48.h index 7f0281ed..a996f52d 100644 --- a/src/plugins/Display/Display_Mono_84X48.h +++ b/src/plugins/Display/Display_Mono_84X48.h @@ -12,10 +12,11 @@ class DisplayMono84X48 : public DisplayMono { mExtra = 0; } - void config(bool enPowerSave, uint8_t screenSaver, uint8_t lum) { + void config(bool enPowerSave, uint8_t screenSaver, uint8_t lum, uint8_t graph_ratio) { mEnPowerSave = enPowerSave; mScreenSaver = screenSaver; mLuminance = lum; + mGraphRatio = graph_ratio; } void init(uint8_t type, uint8_t rotation, uint8_t cs, uint8_t dc, uint8_t reset, uint8_t clock, uint8_t data, DisplayData *displayData) { diff --git a/src/web/RestApi.h b/src/web/RestApi.h index de4fe8e3..7ef4349b 100644 --- a/src/web/RestApi.h +++ b/src/web/RestApi.h @@ -671,18 +671,19 @@ class RestApi { } void getDisplay(JsonObject obj) { - obj[F("disp_typ")] = (uint8_t)mConfig->plugin.display.type; - obj[F("disp_pwr")] = (bool)mConfig->plugin.display.pwrSaveAtIvOffline; - obj[F("disp_screensaver")] = (uint8_t)mConfig->plugin.display.screenSaver; - obj[F("disp_rot")] = (uint8_t)mConfig->plugin.display.rot; - obj[F("disp_cont")] = (uint8_t)mConfig->plugin.display.contrast; - obj[F("disp_clk")] = (mConfig->plugin.display.type == 0) ? DEF_PIN_OFF : mConfig->plugin.display.disp_clk; - obj[F("disp_data")] = (mConfig->plugin.display.type == 0) ? DEF_PIN_OFF : mConfig->plugin.display.disp_data; - obj[F("disp_cs")] = (mConfig->plugin.display.type < 3) ? DEF_PIN_OFF : mConfig->plugin.display.disp_cs; - obj[F("disp_dc")] = (mConfig->plugin.display.type < 3) ? DEF_PIN_OFF : mConfig->plugin.display.disp_dc; - obj[F("disp_rst")] = (mConfig->plugin.display.type < 3) ? DEF_PIN_OFF : mConfig->plugin.display.disp_reset; - obj[F("disp_bsy")] = (mConfig->plugin.display.type < 10) ? DEF_PIN_OFF : mConfig->plugin.display.disp_busy; - obj[F("pir_pin")] = mConfig->plugin.display.pirPin; + obj[F("disp_typ")] = (uint8_t)mConfig->plugin.display.type; + obj[F("disp_pwr")] = (bool)mConfig->plugin.display.pwrSaveAtIvOffline; + obj[F("disp_screensaver")] = (uint8_t)mConfig->plugin.display.screenSaver; + obj[F("disp_rot")] = (uint8_t)mConfig->plugin.display.rot; + obj[F("disp_cont")] = (uint8_t)mConfig->plugin.display.contrast; + obj[F("disp_graph_ratio")] = (uint8_t)mConfig->plugin.display.graph_ratio; + obj[F("disp_clk")] = (mConfig->plugin.display.type == 0) ? DEF_PIN_OFF : mConfig->plugin.display.disp_clk; + obj[F("disp_data")] = (mConfig->plugin.display.type == 0) ? DEF_PIN_OFF : mConfig->plugin.display.disp_data; + obj[F("disp_cs")] = (mConfig->plugin.display.type < 3) ? DEF_PIN_OFF : mConfig->plugin.display.disp_cs; + obj[F("disp_dc")] = (mConfig->plugin.display.type < 3) ? DEF_PIN_OFF : mConfig->plugin.display.disp_dc; + obj[F("disp_rst")] = (mConfig->plugin.display.type < 3) ? DEF_PIN_OFF : mConfig->plugin.display.disp_reset; + obj[F("disp_bsy")] = (mConfig->plugin.display.type < 10) ? DEF_PIN_OFF : mConfig->plugin.display.disp_busy; + obj[F("pir_pin")] = mConfig->plugin.display.pirPin; } void getMqttInfo(JsonObject obj) { diff --git a/src/web/html/setup.html b/src/web/html/setup.html index 4903db16..4759f893 100644 --- a/src/web/html/setup.html +++ b/src/web/html/setup.html @@ -294,6 +294,15 @@

{#DISP_PINOUT}

+

Graph options

+
+
Graph Size
+
+
+
+
Show ratio (0-100 %)
+
+
@@ -1087,6 +1096,8 @@ hideDispPins(pins, parseInt(dtype_sel.value)) }); + document.getElementsByName("disp_graph_ratio")[0].value = obj["disp_graph_ratio"]; + hideDispPins(pins, obj.disp_typ); } diff --git a/src/web/web.h b/src/web/web.h index eae106a9..1b4f5702 100644 --- a/src/web/web.h +++ b/src/web/web.h @@ -584,6 +584,7 @@ class Web { // display mConfig->plugin.display.pwrSaveAtIvOffline = (request->arg("disp_pwr") == "on"); mConfig->plugin.display.screenSaver = request->arg("disp_screensaver").toInt(); + mConfig->plugin.display.graph_ratio = request->arg("disp_graph_ratio").toInt(); mConfig->plugin.display.rot = request->arg("disp_rot").toInt(); mConfig->plugin.display.type = request->arg("disp_typ").toInt(); mConfig->plugin.display.contrast = (mConfig->plugin.display.type == 0) ? 60 : request->arg("disp_cont").toInt(); From c82cc1c77ec2b4df3678d8bb7a829d0d03025408 Mon Sep 17 00:00:00 2001 From: you69man Date: Sat, 6 Jan 2024 22:15:29 +0100 Subject: [PATCH 036/179] add option for graph size --- src/config/settings.h | 4 + src/plugins/Display/Display.h | 2 +- src/plugins/Display/Display_Mono.h | 5 +- src/plugins/Display/Display_Mono_128X32.h | 3 +- src/plugins/Display/Display_Mono_128X64.h | 135 ++++++++++++++-------- src/plugins/Display/Display_Mono_64X48.h | 3 +- src/plugins/Display/Display_Mono_84X48.h | 84 ++++++++++---- src/web/RestApi.h | 1 + src/web/html/setup.html | 15 ++- src/web/web.h | 1 + 10 files changed, 172 insertions(+), 81 deletions(-) diff --git a/src/config/settings.h b/src/config/settings.h index 83e2eaa3..28739db3 100644 --- a/src/config/settings.h +++ b/src/config/settings.h @@ -166,6 +166,7 @@ typedef struct { bool pwrSaveAtIvOffline; uint8_t screenSaver; uint8_t graph_ratio; + uint8_t graph_size; uint8_t rot; //uint16_t wakeUp; //uint16_t sleepAt; @@ -463,6 +464,7 @@ class settings { mCfg.plugin.display.contrast = 60; mCfg.plugin.display.screenSaver = 1; // default: 1 .. pixelshift for OLED for downward compatibility mCfg.plugin.display.graph_ratio = 50; + mCfg.plugin.display.graph_size = 2; mCfg.plugin.display.rot = 0; mCfg.plugin.display.disp_data = DEF_PIN_OFF; // SDA mCfg.plugin.display.disp_clk = DEF_PIN_OFF; // SCL @@ -700,6 +702,7 @@ class settings { disp[F("pwrSafe")] = (bool)mCfg.plugin.display.pwrSaveAtIvOffline; disp[F("screenSaver")] = mCfg.plugin.display.screenSaver; disp[F("graph_ratio")] = mCfg.plugin.display.graph_ratio; + disp[F("graph_size")] = mCfg.plugin.display.graph_size; disp[F("rotation")] = mCfg.plugin.display.rot; //disp[F("wake")] = mCfg.plugin.display.wakeUp; //disp[F("sleep")] = mCfg.plugin.display.sleepAt; @@ -717,6 +720,7 @@ class settings { getVal(disp, F("pwrSafe"), &mCfg.plugin.display.pwrSaveAtIvOffline); getVal(disp, F("screenSaver"), &mCfg.plugin.display.screenSaver); getVal(disp, F("graph_ratio"), &mCfg.plugin.display.graph_ratio); + getVal(disp, F("graph_size"), &mCfg.plugin.display.graph_size); getVal(disp, F("rotation"), &mCfg.plugin.display.rot); //mCfg.plugin.display.wakeUp = disp[F("wake")]; //mCfg.plugin.display.sleepAt = disp[F("sleep")]; diff --git a/src/plugins/Display/Display.h b/src/plugins/Display/Display.h index 0b06cc58..6c8bb9cb 100644 --- a/src/plugins/Display/Display.h +++ b/src/plugins/Display/Display.h @@ -54,7 +54,7 @@ class Display { default: mMono = NULL; break; } if(mMono) { - mMono->config(mCfg->pwrSaveAtIvOffline, mCfg->screenSaver, mCfg->contrast, mCfg->graph_ratio); + mMono->config(mCfg->pwrSaveAtIvOffline, mCfg->screenSaver, mCfg->contrast, mCfg->graph_ratio, mCfg->graph_size); mMono->init(mCfg->type, mCfg->rot, mCfg->disp_cs, mCfg->disp_dc, 0xff, mCfg->disp_clk, mCfg->disp_data, &mDisplayData); } diff --git a/src/plugins/Display/Display_Mono.h b/src/plugins/Display/Display_Mono.h index 76447ce3..c5aa2ed3 100644 --- a/src/plugins/Display/Display_Mono.h +++ b/src/plugins/Display/Display_Mono.h @@ -26,7 +26,7 @@ class DisplayMono { DisplayMono() {}; virtual void init(uint8_t type, uint8_t rot, uint8_t cs, uint8_t dc, uint8_t reset, uint8_t clock, uint8_t data, DisplayData *displayData) = 0; - virtual void config(bool enPowerSave, uint8_t screenSaver, uint8_t lum, uint8_t graph_ratio) = 0; + virtual void config(bool enPowerSave, uint8_t screenSaver, uint8_t lum, uint8_t graph_ratio, uint8_t graph_size) = 0; virtual void disp(void) = 0; // Common loop function, manages display on/off functions for powersave and screensaver with motionsensor @@ -85,6 +85,7 @@ class DisplayMono { uint8_t mScreenSaver = 1; // 0 .. off; 1 .. pixelShift; 2 .. motionsensor uint8_t mLuminance; uint8_t mGraphRatio; + uint8_t mGraphSize; uint8_t mLoopCnt; uint8_t mLineXOffsets[5] = {}; @@ -119,7 +120,7 @@ class DisplayMono { if (mGraphRatio == 100) // if graph ratio is 100% start in graph mode mDispSwitchState = d_POWER_GRAPH; else if (mGraphRatio != 0) - mDispSwitchTime.startTimeMonitor(150 * (100 - mGraphRatio)); // start time monitor only if ratio is neither 0 nor 100 + mDispSwitchTime.startTimeMonitor(150 * (100 - mGraphRatio)); // start display mode change only if ratio is neither 0 nor 100 } bool monoMaintainDispSwitchState(void) { diff --git a/src/plugins/Display/Display_Mono_128X32.h b/src/plugins/Display/Display_Mono_128X32.h index edc79b75..1b3927b3 100644 --- a/src/plugins/Display/Display_Mono_128X32.h +++ b/src/plugins/Display/Display_Mono_128X32.h @@ -12,11 +12,12 @@ class DisplayMono128X32 : public DisplayMono { mExtra = 0; } - void config(bool enPowerSave, uint8_t screenSaver, uint8_t lum, uint8_t graph_ratio) { + void config(bool enPowerSave, uint8_t screenSaver, uint8_t lum, uint8_t graph_ratio, uint8_t graph_size) { mEnPowerSave = enPowerSave; mScreenSaver = screenSaver; mLuminance = lum; mGraphRatio = graph_ratio; + mGraphSize = graph_size; } void init(uint8_t type, uint8_t rotation, uint8_t cs, uint8_t dc, uint8_t reset, uint8_t clock, uint8_t data, DisplayData *displayData) { diff --git a/src/plugins/Display/Display_Mono_128X64.h b/src/plugins/Display/Display_Mono_128X64.h index c578f9a4..32459c89 100644 --- a/src/plugins/Display/Display_Mono_128X64.h +++ b/src/plugins/Display/Display_Mono_128X64.h @@ -12,11 +12,12 @@ class DisplayMono128X64 : public DisplayMono { mExtra = 0; } - void config(bool enPowerSave, uint8_t screenSaver, uint8_t lum, uint8_t graph_ratio) { + void config(bool enPowerSave, uint8_t screenSaver, uint8_t lum, uint8_t graph_ratio, uint8_t graph_size) { mEnPowerSave = enPowerSave; mScreenSaver = screenSaver; mLuminance = lum; mGraphRatio = graph_ratio; + mGraphSize = graph_size; } void init(uint8_t type, uint8_t rotation, uint8_t cs, uint8_t dc, uint8_t reset, uint8_t clock, uint8_t data, DisplayData *displayData) { @@ -35,7 +36,33 @@ class DisplayMono128X64 : public DisplayMono { } calcLinePositions(); - initPowerGraph(mDispWidth - 20, mLineYOffsets[4] - mLineYOffsets[1]); + switch(mGraphSize) { // var opts2 = [[0, "Line 1 - 2"], [1, "Line 2 - 3"], [2, "Line 1 - 3"], [3, "Line 2 - 4"], [4, "Line 1 - 4"]]; + case 0: + graph_first_line = 1; + graph_last_line = 2; + break; + case 1: + graph_first_line = 2; + graph_last_line = 3; + break; + case 2: + graph_first_line = 1; + graph_last_line = 3; + break; + case 3: + graph_first_line = 2; + graph_last_line = 4; + break; + case 4: + default: + graph_first_line = 1; + graph_last_line = 4; + break; + } + + widthShrink = (mScreenSaver == 1) ? pixelShiftRange : 0; // shrink graphwidth for pixelshift screensaver + + initPowerGraph(mDispWidth - 22 - widthShrink, mLineYOffsets[graph_last_line] - mLineYOffsets[graph_first_line - 1] - 2); printText("Ahoy!", l_Ahoy, 0xff); printText("ahoydtu.de", l_Website, 0xff); @@ -73,46 +100,47 @@ class DisplayMono128X64 : public DisplayMono { if (0 != mDisplayData->utcTs) printText(ah::getDateTimeStrShort(gTimezone.toLocal(mDisplayData->utcTs)).c_str(), l_Time, 0xff); - // alternatively: - // print ip address - if (!(mExtra % 5) && (mDisplayData->ipAddress)) { - snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%s", (mDisplayData->ipAddress).toString().c_str()); - printText(mFmtText, l_Status, 0xff); - } - // print status of inverters - else { - sun_pos = -1; - moon_pos = -1; - setLineFont(l_Status); - if (0 == mDisplayData->nrSleeping + mDisplayData->nrProducing) - snprintf(mFmtText, DISP_FMT_TEXT_LEN, "no inverter"); - else if (0 == mDisplayData->nrSleeping) { - snprintf(mFmtText, DISP_FMT_TEXT_LEN, " "); - sun_pos = 0; - } - else if (0 == mDisplayData->nrProducing) { - snprintf(mFmtText, DISP_FMT_TEXT_LEN, " "); - moon_pos = 0; + if (showLine(l_Status)) { + // alternatively: + // print ip address + if (!(mExtra % 5) && (mDisplayData->ipAddress)) { + snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%s", (mDisplayData->ipAddress).toString().c_str()); + printText(mFmtText, l_Status, 0xff); } + // print status of inverters else { - snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%2d", mDisplayData->nrProducing); - sun_pos = mDisplay->getStrWidth(mFmtText) + 1; - snprintf(mFmtText+2, DISP_FMT_TEXT_LEN, " %2d", mDisplayData->nrSleeping); - moon_pos = mDisplay->getStrWidth(mFmtText) + 1; - snprintf(mFmtText+7, DISP_FMT_TEXT_LEN, " "); + sun_pos = -1; + moon_pos = -1; + setLineFont(l_Status); + if (0 == mDisplayData->nrSleeping + mDisplayData->nrProducing) + snprintf(mFmtText, DISP_FMT_TEXT_LEN, "no inverter"); + else if (0 == mDisplayData->nrSleeping) { + snprintf(mFmtText, DISP_FMT_TEXT_LEN, " "); + sun_pos = 0; + } + else if (0 == mDisplayData->nrProducing) { + snprintf(mFmtText, DISP_FMT_TEXT_LEN, " "); + moon_pos = 0; + } + else { + snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%2d", mDisplayData->nrProducing); + sun_pos = mDisplay->getStrWidth(mFmtText) + 1; + snprintf(mFmtText+2, DISP_FMT_TEXT_LEN, " %2d", mDisplayData->nrSleeping); + moon_pos = mDisplay->getStrWidth(mFmtText) + 1; + snprintf(mFmtText+7, DISP_FMT_TEXT_LEN, " "); + } + printText(mFmtText, l_Status, 0xff); + + pos = (mDispWidth - mDisplay->getStrWidth(mFmtText)) / 2; + mDisplay->setFont(u8g2_font_ncenB08_symbols8_ahoy); + if (sun_pos!=-1) + mDisplay->drawStr(pos + sun_pos + mPixelshift, mLineYOffsets[l_Status], "G"); // sun symbol + if (moon_pos!=-1) + mDisplay->drawStr(pos + moon_pos + mPixelshift, mLineYOffsets[l_Status], "H"); // moon symbol } - printText(mFmtText, l_Status, 0xff); - - pos = (mDispWidth - mDisplay->getStrWidth(mFmtText)) / 2; - mDisplay->setFont(u8g2_font_ncenB08_symbols8_ahoy); - if (sun_pos!=-1) - mDisplay->drawStr(pos + sun_pos + mPixelshift, mLineYOffsets[l_Status], "G"); // sun symbol - if (moon_pos!=-1) - mDisplay->drawStr(pos + moon_pos + mPixelshift, mLineYOffsets[l_Status], "H"); // moon symbol } - if (mDispSwitchState == d_POWER_TEXT) { - + if (showLine(l_TotalPower)) { // print total power if (mDisplayData->nrProducing > 0) { if (mDisplayData->totalPower > 9999.0) @@ -124,8 +152,10 @@ class DisplayMono128X64 : public DisplayMono { } else { printText("offline", l_TotalPower, 0xff); } + } - // print yields + if (showLine(l_YieldDay)) { + // print day yield mDisplay->setFont(u8g2_font_ncenB10_symbols10_ahoy); mDisplay->drawStr(16 + mPixelshift, mLineYOffsets[l_YieldDay], "I"); // day symbol mDisplay->drawStr(16 + mPixelshift, mLineYOffsets[l_YieldTotal], "D"); // total symbol @@ -135,45 +165,44 @@ class DisplayMono128X64 : public DisplayMono { else snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%.0f Wh", mDisplayData->totalYieldDay); printText(mFmtText, l_YieldDay, 0xff); + } + if (showLine(l_YieldTotal)) { + // print total yield if (mDisplayData->totalYieldTotal > 9999.0) snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%.2f MWh", mDisplayData->totalYieldTotal / 1000.0); else snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%.0f kWh", mDisplayData->totalYieldTotal); printText(mFmtText, l_YieldTotal, 0xff); + } - } else { + if (mDispSwitchState == d_POWER_GRAPH) { // plot power graph - plotPowerGraph(10 + mPixelshift, mLineYOffsets[4] - 1); + plotPowerGraph((mDispWidth - mPgWidth) / 2 + mPixelshift, mLineYOffsets[graph_last_line] - 1); } // draw dynamic RSSI bars - int xoffs; - if (mScreenSaver == 1) // shrink screenwidth for pixelshift screensaver - xoffs = pixelShiftRange/2; - else - xoffs = 0; int rssi_bar_height = 9; for (int i = 0; i < 4; i++) { int radio_rssi_threshold = -60 - i * 10; int wifi_rssi_threshold = -60 - i * 10; uint8_t barwidth = std::min(4 - i, 3); if (mDisplayData->RadioRSSI > radio_rssi_threshold) - mDisplay->drawBox(xoffs + mPixelshift, 8 + (rssi_bar_height + 1) * i, barwidth, rssi_bar_height); + mDisplay->drawBox(widthShrink / 2 + mPixelshift, 8 + (rssi_bar_height + 1) * i, barwidth, rssi_bar_height); if (mDisplayData->WifiRSSI > wifi_rssi_threshold) - mDisplay->drawBox(mDispWidth - barwidth - xoffs + mPixelshift, 8 + (rssi_bar_height + 1) * i, barwidth, rssi_bar_height); + mDisplay->drawBox(mDispWidth - barwidth - widthShrink / 2 + mPixelshift, 8 + (rssi_bar_height + 1) * i, barwidth, rssi_bar_height); } // draw dynamic antenna and WiFi symbols mDisplay->setFont(u8g2_font_ncenB10_symbols10_ahoy); char sym[]=" "; sym[0] = mDisplayData->RadioSymbol?'A':'E'; // NRF - mDisplay->drawStr(xoffs + mPixelshift, mLineYOffsets[l_RSSI], sym); + mDisplay->drawStr(widthShrink / 2 + mPixelshift, mLineYOffsets[l_RSSI], sym); if (mDisplayData->MQTTSymbol) sym[0] = 'J'; // MQTT else sym[0] = mDisplayData->WifiSymbol?'B':'F'; // Wifi - mDisplay->drawStr(mDispWidth - mDisplay->getStrWidth(sym) - xoffs + mPixelshift, mLineYOffsets[l_RSSI], sym); + mDisplay->drawStr(mDispWidth - mDisplay->getStrWidth(sym) - widthShrink / 2 + mPixelshift, mLineYOffsets[l_RSSI], sym); mDisplay->sendBuffer(); mExtra++; @@ -197,7 +226,11 @@ class DisplayMono128X64 : public DisplayMono { l_MAX_LINES = 5, }; + uint8_t graph_first_line; + uint8_t graph_last_line; + const uint8_t pixelShiftRange = 11; // number of pixels to shift from left to right (centered -> must be odd!) + uint8_t widthShrink; void calcLinePositions() { uint8_t yOff = 0; @@ -239,4 +272,8 @@ class DisplayMono128X64 : public DisplayMono { dispX += mPixelshift; mDisplay->drawStr(dispX, mLineYOffsets[line], text); } + + bool showLine(uint8_t line) { + return ((mDispSwitchState == d_POWER_TEXT) || ((line < graph_first_line) || (line > graph_last_line))); + } }; diff --git a/src/plugins/Display/Display_Mono_64X48.h b/src/plugins/Display/Display_Mono_64X48.h index 5d1bf6ac..e55f5ac3 100644 --- a/src/plugins/Display/Display_Mono_64X48.h +++ b/src/plugins/Display/Display_Mono_64X48.h @@ -12,11 +12,12 @@ class DisplayMono64X48 : public DisplayMono { mExtra = 0; } - void config(bool enPowerSave, uint8_t screenSaver, uint8_t lum, uint8_t graph_ratio) { + void config(bool enPowerSave, uint8_t screenSaver, uint8_t lum, uint8_t graph_ratio, uint8_t graph_size) { mEnPowerSave = enPowerSave; mScreenSaver = screenSaver; mLuminance = lum; mGraphRatio = graph_ratio; + mGraphSize = graph_size; } void init(uint8_t type, uint8_t rotation, uint8_t cs, uint8_t dc, uint8_t reset, uint8_t clock, uint8_t data, DisplayData *displayData) { diff --git a/src/plugins/Display/Display_Mono_84X48.h b/src/plugins/Display/Display_Mono_84X48.h index a996f52d..ee2aebd5 100644 --- a/src/plugins/Display/Display_Mono_84X48.h +++ b/src/plugins/Display/Display_Mono_84X48.h @@ -12,11 +12,12 @@ class DisplayMono84X48 : public DisplayMono { mExtra = 0; } - void config(bool enPowerSave, uint8_t screenSaver, uint8_t lum, uint8_t graph_ratio) { + void config(bool enPowerSave, uint8_t screenSaver, uint8_t lum, uint8_t graph_ratio, uint8_t graph_size) { mEnPowerSave = enPowerSave; mScreenSaver = screenSaver; mLuminance = lum; mGraphRatio = graph_ratio; + mGraphSize = graph_size; } void init(uint8_t type, uint8_t rotation, uint8_t cs, uint8_t dc, uint8_t reset, uint8_t clock, uint8_t data, DisplayData *displayData) { @@ -24,7 +25,31 @@ class DisplayMono84X48 : public DisplayMono { monoInit(new U8G2_PCD8544_84X48_F_4W_SW_SPI(rot, clock, data, cs, dc, reset), type, displayData); calcLinePositions(); - initPowerGraph(mDispWidth - 16, mLineYOffsets[4] - mLineYOffsets[1] - 2); + switch(mGraphSize) { // var opts2 = [[0, "Line 1 - 2"], [1, "Line 2 - 3"], [2, "Line 1 - 3"], [3, "Line 2 - 4"], [4, "Line 1 - 4"]]; + case 0: + graph_first_line = 1; + graph_last_line = 2; + break; + case 1: + graph_first_line = 2; + graph_last_line = 3; + break; + case 2: + graph_first_line = 1; + graph_last_line = 3; + break; + case 3: + graph_first_line = 2; + graph_last_line = 4; + break; + case 4: + default: + graph_first_line = 1; + graph_last_line = 4; + break; + } + + initPowerGraph(mDispWidth - 16, mLineYOffsets[graph_last_line] - mLineYOffsets[graph_first_line - 1] - 2); printText("Ahoy!", l_Ahoy, 0xff); printText("ahoydtu.de", l_Website, 0xff); @@ -57,27 +82,28 @@ class DisplayMono84X48 : public DisplayMono { if (0 != mDisplayData->utcTs) printText(ah::getDateTimeStrShort(gTimezone.toLocal(mDisplayData->utcTs)).c_str(), l_Time, 0xff); - // alternatively: - // print ip address - if (!(mExtra % 5) && (mDisplayData->ipAddress)) { - snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%s", (mDisplayData->ipAddress).toString().c_str()); - printText(mFmtText, l_Status, 0xff); - } - // print status of inverters - else { - if (0 == mDisplayData->nrSleeping + mDisplayData->nrProducing) - snprintf(mFmtText, DISP_FMT_TEXT_LEN, "no inverter"); - else if (0 == mDisplayData->nrSleeping) - snprintf(mFmtText, DISP_FMT_TEXT_LEN, "\x86"); // sun symbol - else if (0 == mDisplayData->nrProducing) - snprintf(mFmtText, DISP_FMT_TEXT_LEN, "\x87"); // moon symbol - else - snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%d\x86 %d\x87", mDisplayData->nrProducing, mDisplayData->nrSleeping); - printText(mFmtText, l_Status, 0xff); + if (showLine(l_Status)) { + // alternatively: + // print ip address + if (!(mExtra % 5) && (mDisplayData->ipAddress)) { + snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%s", (mDisplayData->ipAddress).toString().c_str()); + printText(mFmtText, l_Status, 0xff); + } + // print status of inverters + else { + if (0 == mDisplayData->nrSleeping + mDisplayData->nrProducing) + snprintf(mFmtText, DISP_FMT_TEXT_LEN, "no inverter"); + else if (0 == mDisplayData->nrSleeping) + snprintf(mFmtText, DISP_FMT_TEXT_LEN, "\x86"); // sun symbol + else if (0 == mDisplayData->nrProducing) + snprintf(mFmtText, DISP_FMT_TEXT_LEN, "\x87"); // moon symbol + else + snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%d\x86 %d\x87", mDisplayData->nrProducing, mDisplayData->nrSleeping); + printText(mFmtText, l_Status, 0xff); + } } - if (mDispSwitchState == d_POWER_TEXT) { - + if (showLine(l_TotalPower)) { // print total power if (mDisplayData->nrProducing > 0) { if (mDisplayData->totalPower > 9999.0) @@ -89,7 +115,9 @@ class DisplayMono84X48 : public DisplayMono { } else { printText("offline", l_TotalPower, 0xff); } + } + if (showLine(l_YieldDay)) { // print day yield printText("\x88", l_YieldDay, 10); // day symbol if (mDisplayData->totalYieldDay > 9999.0) @@ -97,7 +125,9 @@ class DisplayMono84X48 : public DisplayMono { else snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%.0f Wh", mDisplayData->totalYieldDay); printText(mFmtText, l_YieldDay, 0xff); + } + if (showLine(l_YieldTotal)) { // print total yield printText("\x83", l_YieldTotal, 10); // total symbol if (mDisplayData->totalYieldTotal > 9999.0) @@ -105,10 +135,11 @@ class DisplayMono84X48 : public DisplayMono { else snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%.0f kWh", mDisplayData->totalYieldTotal); printText(mFmtText, l_YieldTotal, 0xff); + } - } else { + if (mDispSwitchState == d_POWER_GRAPH) { // plot power graph - plotPowerGraph(8, mLineYOffsets[4] - 1); + plotPowerGraph(8, mLineYOffsets[graph_last_line] - 1); } // draw dynamic RSSI bars @@ -155,6 +186,9 @@ class DisplayMono84X48 : public DisplayMono { l_MAX_LINES = 5, }; + uint8_t graph_first_line; + uint8_t graph_last_line; + void calcLinePositions() { uint8_t yOff = 0; uint8_t i = 0; @@ -191,6 +225,10 @@ class DisplayMono84X48 : public DisplayMono { dispX = col; mDisplay->drawStr(dispX, mLineYOffsets[line], text); } + + bool showLine(uint8_t line) { + return ((mDispSwitchState == d_POWER_TEXT) || ((line < graph_first_line) || (line > graph_last_line))); + } }; diff --git a/src/web/RestApi.h b/src/web/RestApi.h index 7ef4349b..3d4a6c0d 100644 --- a/src/web/RestApi.h +++ b/src/web/RestApi.h @@ -677,6 +677,7 @@ class RestApi { obj[F("disp_rot")] = (uint8_t)mConfig->plugin.display.rot; obj[F("disp_cont")] = (uint8_t)mConfig->plugin.display.contrast; obj[F("disp_graph_ratio")] = (uint8_t)mConfig->plugin.display.graph_ratio; + obj[F("disp_graph_size")] = (uint8_t)mConfig->plugin.display.graph_size; obj[F("disp_clk")] = (mConfig->plugin.display.type == 0) ? DEF_PIN_OFF : mConfig->plugin.display.disp_clk; obj[F("disp_data")] = (mConfig->plugin.display.type == 0) ? DEF_PIN_OFF : mConfig->plugin.display.disp_data; obj[F("disp_cs")] = (mConfig->plugin.display.type < 3) ? DEF_PIN_OFF : mConfig->plugin.display.disp_cs; diff --git a/src/web/html/setup.html b/src/web/html/setup.html index 4759f893..6d0b3710 100644 --- a/src/web/html/setup.html +++ b/src/web/html/setup.html @@ -295,10 +295,7 @@

Graph options

-
-
Graph Size
-
-
+
Show ratio (0-100 %)
@@ -1098,6 +1095,16 @@ document.getElementsByName("disp_graph_ratio")[0].value = obj["disp_graph_ratio"]; + var opts2 = [[0, "Line 1 - 2"], [1, "Line 2 - 3"], [2, "Line 1 - 3"], [3, "Line 2 - 4"], [4, "Line 1 - 4"]]; + var graph_size_sel = sel("disp_graph_size", opts2, obj["disp_graph_size"]); + graph_size_sel.id = 'disp_graph_size'; + document.getElementById("graphSize").append( + ml("div", {class: "row mb-3"}, [ + ml("div", {class: "col-12 col-sm-3 my-2"}, "Graph size"), + ml("div", {class: "col-12 col-sm-9"}, graph_size_sel) + ]) + ); + hideDispPins(pins, obj.disp_typ); } diff --git a/src/web/web.h b/src/web/web.h index 1b4f5702..38ecf171 100644 --- a/src/web/web.h +++ b/src/web/web.h @@ -585,6 +585,7 @@ class Web { mConfig->plugin.display.pwrSaveAtIvOffline = (request->arg("disp_pwr") == "on"); mConfig->plugin.display.screenSaver = request->arg("disp_screensaver").toInt(); mConfig->plugin.display.graph_ratio = request->arg("disp_graph_ratio").toInt(); + mConfig->plugin.display.graph_size = request->arg("disp_graph_size").toInt(); mConfig->plugin.display.rot = request->arg("disp_rot").toInt(); mConfig->plugin.display.type = request->arg("disp_typ").toInt(); mConfig->plugin.display.contrast = (mConfig->plugin.display.type == 0) ? 60 : request->arg("disp_cont").toInt(); From 17df96ed680f609b351b3f2a2c693dbd6844c31f Mon Sep 17 00:00:00 2001 From: lumapu Date: Sun, 7 Jan 2024 12:18:40 +0100 Subject: [PATCH 037/179] github action test --- .github/workflows/compile_development.yml | 102 +++++++++++++++++----- src/utils/helper.cpp | 2 +- 2 files changed, 79 insertions(+), 25 deletions(-) diff --git a/.github/workflows/compile_development.yml b/.github/workflows/compile_development.yml index 4b688fca..37d3950b 100644 --- a/.github/workflows/compile_development.yml +++ b/.github/workflows/compile_development.yml @@ -5,14 +5,85 @@ on: branches: development* paths-ignore: - '**.md' # Do no build on *.md changes + jobs: - build: + check: runs-on: ubuntu-latest + if: github.repository == 'lumapu/ahoy' && github.ref_name == 'development03' + continue-on-error: true + steps: + - uses: actions/checkout@v3 + build-en: + needs: check + runs-on: ubuntu-latest + continue-on-error: true + strategy: + matrix: + variant: + - esp8266 + - esp8266-prometheus + - esp8285 + - esp32-wroom32 + - esp32-wroom32-prometheus + - esp32-wroom32-ethernet + - esp32-s2-mini + - esp32-c3-mini + - opendtufusion + - opendtufusion-ethernet steps: - uses: actions/checkout@v3 + - uses: benjlevesque/short-sha@v2.1 + id: short-sha + with: + length: 7 + + - name: Cache Pip + uses: actions/cache@v3 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} + restore-keys: | + ${{ runner.os }}-pip- + + - name: Cache PlatformIO + uses: actions/cache@v3 with: - ref: development03 + path: ~/.platformio + key: ${{ runner.os }}-${{ hashFiles('**/lockfiles') }} + + - name: Setup Python + uses: actions/setup-python@v4.3.0 + with: + python-version: "3.x" + + - name: Install PlatformIO + run: | + python -m pip install setuptools --upgrade pip + pip install --upgrade platformio + + - name: Run PlatformIO + run: pio run -d src -e ${{ matrix.variant }} + + build-de: + needs: check + runs-on: ubuntu-latest + continue-on-error: true + strategy: + matrix: + variant: + - esp8266-de + - esp8266-prometheus-de + - esp8285-de + - esp32-wroom32-de + - esp32-wroom32-prometheus-de + - esp32-wroom32-ethernet-de + - esp32-s2-mini-de + - esp32-c3-mini-de + - opendtufusion-de + - opendtufusion-ethernet-de + steps: + - uses: actions/checkout@v3 - uses: benjlevesque/short-sha@v2.1 id: short-sha with: @@ -43,29 +114,12 @@ jobs: pip install --upgrade platformio - name: Run PlatformIO - run: > - pio run -d src - --environment esp8266 - --environment esp8266-prometheus - --environment esp8285 - --environment esp32-wroom32 - --environment esp32-wroom32-prometheus - --environment esp32-wroom32-ethernet - --environment esp32-s2-mini - --environment esp32-c3-mini - --environment opendtufusion - --environment opendtufusion-ethernet - --environment esp8266-de - --environment esp8266-prometheus-de - --environment esp8285-de - --environment esp32-wroom32-de - --environment esp32-wroom32-prometheus-de - --environment esp32-wroom32-ethernet-de - --environment esp32-s2-mini-de - --environment esp32-c3-mini-de - --environment opendtufusion-de - --environment opendtufusion-ethernet-de + run: pio run -d src -e ${{ matrix.variant }} + deploy: + needs: [build-en, build-de] + runs-on: ubuntu-latest + steps: - name: Copy boot_app0.bin run: cp ~/.platformio/packages/framework-arduinoespressif32/tools/partitions/boot_app0.bin src/.pio/build/opendtufusion/ota.bin diff --git a/src/utils/helper.cpp b/src/utils/helper.cpp index 9aea1054..5d68adc4 100644 --- a/src/utils/helper.cpp +++ b/src/utils/helper.cpp @@ -76,7 +76,7 @@ namespace ah { sprintf(str, "n/a"); else { t = (t + (millis() % 1000)) / 1000; - sprintf(str, "%02d:%02d:%02d.%03d", hour(t), minute(t), second(t), millis() % 1000); + sprintf(str, "%02d:%02d:%02d.%03d", hour(t), minute(t), second(t), (uint16_t)(millis() % 1000)); } return String(str); } From 72d678e32b4dad00df3c1d3d8d9514f6131e2c95 Mon Sep 17 00:00:00 2001 From: lumapu Date: Sun, 7 Jan 2024 12:30:26 +0100 Subject: [PATCH 038/179] github action test --- .github/workflows/compile_development.yml | 48 ++++++++++++++++------- 1 file changed, 34 insertions(+), 14 deletions(-) diff --git a/.github/workflows/compile_development.yml b/.github/workflows/compile_development.yml index 37d3950b..e3fa489d 100644 --- a/.github/workflows/compile_development.yml +++ b/.github/workflows/compile_development.yml @@ -65,6 +65,13 @@ jobs: - name: Run PlatformIO run: pio run -d src -e ${{ matrix.variant }} + - name: Create Artifact + uses: actions/upload-artifact@v3 + with: + name: ahoydtu_dev + path: | + src/firmware/* + build-de: needs: check runs-on: ubuntu-latest @@ -116,12 +123,25 @@ jobs: - name: Run PlatformIO run: pio run -d src -e ${{ matrix.variant }} + - name: Create Artifact + uses: actions/upload-artifact@v3 + with: + name: ahoydtu_dev + path: | + src/firmware/* + deploy: needs: [build-en, build-de] runs-on: ubuntu-latest steps: - - name: Copy boot_app0.bin - run: cp ~/.platformio/packages/framework-arduinoespressif32/tools/partitions/boot_app0.bin src/.pio/build/opendtufusion/ota.bin + #- name: Copy boot_app0.bin + # run: cp ~/.platformio/packages/framework-arduinoespressif32/tools/partitions/boot_app0.bin src/.pio/build/opendtufusion/ota.bin + + - name: Get Artifacts + - uses: actions/download-artifact@master + with: + name: ahoydtu_dev + path: src/firmware/* - name: Rename Binary files id: rename-binary-files @@ -135,18 +155,18 @@ jobs: env: VERSION: ${{ steps.rename-binary-files.outputs.name }} - - name: Create Manifest - working-directory: src - run: python ../scripts/buildManifest.py - - - name: Create Artifact - uses: actions/upload-artifact@v3 - with: - name: ahoydtu_dev - path: | - src/firmware/* - src/User_Manual.md - src/install.html +# - name: Create Manifest +# working-directory: src +# run: python ../scripts/buildManifest.py +# +# - name: Create Artifact +# uses: actions/upload-artifact@v3 +# with: +# name: ahoydtu_dev +# path: | +# src/firmware/* +# src/User_Manual.md +# src/install.html - name: Rename firmware directory run: mv src/firmware src/${{ steps.rename-binary-files.outputs.name }} From c66089cb47d581826e832f7c2025812a253e9a58 Mon Sep 17 00:00:00 2001 From: lumapu Date: Sun, 7 Jan 2024 12:31:49 +0100 Subject: [PATCH 039/179] github action test --- .github/workflows/compile_development.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/compile_development.yml b/.github/workflows/compile_development.yml index e3fa489d..197d1e37 100644 --- a/.github/workflows/compile_development.yml +++ b/.github/workflows/compile_development.yml @@ -138,10 +138,10 @@ jobs: # run: cp ~/.platformio/packages/framework-arduinoespressif32/tools/partitions/boot_app0.bin src/.pio/build/opendtufusion/ota.bin - name: Get Artifacts - - uses: actions/download-artifact@master - with: - name: ahoydtu_dev - path: src/firmware/* + uses: actions/download-artifact@master + with: + name: ahoydtu_dev + path: src/firmware/* - name: Rename Binary files id: rename-binary-files From 8d81db44d2cb0893f73e66163bc6f1295ba59287 Mon Sep 17 00:00:00 2001 From: kscholty Date: Sun, 7 Jan 2024 18:07:19 +0100 Subject: [PATCH 040/179] Added better pin selection for S2 (mini) and improved default pins for S2 and C3 --- src/platformio.ini | 20 ++++++++++++++++++++ src/web/html/setup.html | 14 +++++++------- 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/src/platformio.ini b/src/platformio.ini index e9eb282b..a2397596 100644 --- a/src/platformio.ini +++ b/src/platformio.ini @@ -195,6 +195,11 @@ build_flags = ${env.build_flags} -DDEF_NRF_MISO_PIN=9 -DDEF_NRF_MOSI_PIN=11 -DDEF_NRF_SCLK_PIN=7 + -DDEF_CMT_CSB=16 + -DDEF_CMT_FCSB=18 + -DDEF_CMT_IRQ=33 + -DDEF_CMT_SDIO=35 + -DDEF_CMT_SCLK=37 monitor_filters = esp32_exception_decoder @@ -209,6 +214,11 @@ build_flags = ${env.build_flags} -DDEF_NRF_MISO_PIN=9 -DDEF_NRF_MOSI_PIN=11 -DDEF_NRF_SCLK_PIN=7 + -DDEF_CMT_CSB=16 + -DDEF_CMT_FCSB=18 + -DDEF_CMT_IRQ=33 + -DDEF_CMT_SDIO=35 + -DDEF_CMT_SCLK=37 -DLANG_DE monitor_filters = esp32_exception_decoder @@ -224,6 +234,11 @@ build_flags = ${env.build_flags} -DDEF_NRF_MISO_PIN=3 -DDEF_NRF_MOSI_PIN=4 -DDEF_NRF_SCLK_PIN=2 + -DDEF_CMT_CSB=255 + -DDEF_CMT_FCSB=255 + -DDEF_CMT_IRQ=255 + -DDEF_CMT_SDIO=255 + -DDEF_CMT_SCLK=255 monitor_filters = esp32_exception_decoder @@ -238,6 +253,11 @@ build_flags = ${env.build_flags} -DDEF_NRF_MISO_PIN=3 -DDEF_NRF_MOSI_PIN=4 -DDEF_NRF_SCLK_PIN=2 + -DDEF_CMT_CSB=255 + -DDEF_CMT_FCSB=255 + -DDEF_CMT_IRQ=255 + -DDEF_CMT_SDIO=255 + -DDEF_CMT_SCLK=255 -DLANG_DE monitor_filters = esp32_exception_decoder diff --git a/src/web/html/setup.html b/src/web/html/setup.html index 4903db16..c503339b 100644 --- a/src/web/html/setup.html +++ b/src/web/html/setup.html @@ -387,7 +387,7 @@ [36, "VP (GPIO36, {#PIN_INPUT_ONLY})"], [39, "VN (GPIO39, {#PIN_INPUT_ONLY})"] ]; - var esp32s3pins = [ + var esp32sXpins = [ [255, "off / default"], [0, "GPIO0 ({#PIN_DONT_USE} - BOOT)"], [1, "GPIO1"], @@ -418,8 +418,8 @@ [30, "GPIO30 (FLASH - {#PIN_NOT_AVAIL})"], [31, "GPIO31 (FLASH - {#PIN_NOT_AVAIL})"], [32, "GPIO32 (FLASH - {#PIN_NOT_AVAIL})"], - [33, "GPIO33 (not exposed on WROOM modules)"], - [34, "GPIO34 (not exposed on WROOM modules)"], + [33, "GPIO33 (not exposed on S3-WROOM modules)"], + [34, "GPIO34 (not exposed on S3-WROOM modules)"], [35, "GPIO35"], [36, "GPIO36"], [37, "GPIO37"], @@ -902,7 +902,7 @@ var pinList = esp8266pins; /*IF_ESP32*/ var pinList = esp32pins; - if ("ESP32-S3" == system.chip_model) pinList = esp32s3pins; + if ("ESP32-S3" == system.chip_model || "ESP32-S2" == system.chip_model) pinList = esp32sXpins; else if("ESP32-C3" == system["chip_model"]) pinList = esp32c3pins; /*ENDIF_ESP32*/ pins = [['led0', 'pinLed0', '{#LED_AT_LEAST_ONE_PRODUCING}'], ['led1', 'pinLed1', '{#LED_MQTT_CONNECTED}'], ['led2', 'pinLed2', '{#LED_NIGHT_TIME}']]; @@ -938,7 +938,7 @@ var pinList = esp8266pins; /*IF_ESP32*/ var pinList = esp32pins; - if ("ESP32-S3" == system.chip_model) pinList = esp32s3pins; + if ("ESP32-S3" == system.chip_model || "ESP32-S2" == system.chip_model) pinList = esp32sXpins; else if("ESP32-C3" == system["chip_model"]) pinList = esp32c3pins; /*ENDIF_ESP32*/ @@ -971,7 +971,7 @@ var e = document.getElementById("cmt"); var en = inp("cmtEnable", null, null, ["cb"], "cmtEnable", "checkbox"); var pinList = esp32pins; - if ("ESP32-S3" == system["chip_model"]) pinList = esp32s3pins; + if ("ESP32-S3" == system.chip_model || "ESP32-S2" == system.chip_model) pinList = esp32sXpins; else if("ESP32-C3" == system["chip_model"]) pinList = esp32c3pins; en.checked = obj["en"]; @@ -1005,7 +1005,7 @@ var pinList = esp8266pins; /*IF_ESP32*/ var pinList = esp32pins; - if ("ESP32-S3" == system.chip_model) pinList = esp32s3pins; + if ("ESP32-S3" == system.chip_model || "ESP32-S2" == system.chip_model) pinList = esp32sXpins; else if("ESP32-C3" == system["chip_model"]) pinList = esp32c3pins; /*ENDIF_ESP32*/ From c9135e06e1bd25029fc06ec075fa29d10c7e0ec0 Mon Sep 17 00:00:00 2001 From: lumapu Date: Sun, 7 Jan 2024 20:30:52 +0100 Subject: [PATCH 041/179] github action test --- .github/workflows/compile_development.yml | 23 +-- scripts/getVersion.py | 187 +++++----------------- 2 files changed, 50 insertions(+), 160 deletions(-) diff --git a/.github/workflows/compile_development.yml b/.github/workflows/compile_development.yml index 197d1e37..d20c4627 100644 --- a/.github/workflows/compile_development.yml +++ b/.github/workflows/compile_development.yml @@ -65,6 +65,9 @@ jobs: - name: Run PlatformIO run: pio run -d src -e ${{ matrix.variant }} + - name: Rename Firmware + run: python ../scripts/getVersion.py ${{ matrix.variant }} >> $GITHUB_OUTPUT + - name: Create Artifact uses: actions/upload-artifact@v3 with: @@ -123,12 +126,15 @@ jobs: - name: Run PlatformIO run: pio run -d src -e ${{ matrix.variant }} + - name: Rename Firmware + run: python ../scripts/getVersion.py ${{ matrix.variant }} >> $GITHUB_OUTPUT + - name: Create Artifact uses: actions/upload-artifact@v3 with: name: ahoydtu_dev path: | - src/firmware/* + firmware/* deploy: needs: [build-en, build-de] @@ -141,19 +147,18 @@ jobs: uses: actions/download-artifact@master with: name: ahoydtu_dev - path: src/firmware/* + path: firmware/* - - name: Rename Binary files - id: rename-binary-files - working-directory: src - run: python ../scripts/getVersion.py >> $GITHUB_OUTPUT + - name: Get Version from code + id: version_name + run: python ../scripts/getVersion.py ${{ matrix.variant }} >> $GITHUB_OUTPUT - name: Set Version uses: cschleiden/replace-tokens@v1 with: files: tools/esp8266/User_Manual.md env: - VERSION: ${{ steps.rename-binary-files.outputs.name }} + VERSION: ${{ steps.version_name.outputs.name }} # - name: Create Manifest # working-directory: src @@ -169,12 +174,12 @@ jobs: # src/install.html - name: Rename firmware directory - run: mv src/firmware src/${{ steps.rename-binary-files.outputs.name }} + run: mv src/firmware src/${{ steps.version_name.outputs.name }} - name: Deploy uses: nogsantos/scp-deploy@master with: - src: src/${{ steps.rename-binary-files.outputs.name }}/ + src: src/${{ steps.version_name.outputs.name }}/ host: ${{ secrets.FW_SSH_HOST }} remote: ${{ secrets.FW_SSH_DIR }}/dev port: ${{ secrets.FW_SSH_PORT }} diff --git a/scripts/getVersion.py b/scripts/getVersion.py index ce34d26e..8d6d1cf8 100644 --- a/scripts/getVersion.py +++ b/scripts/getVersion.py @@ -2,6 +2,7 @@ import os import shutil import gzip from datetime import date +import sys def genOtaBin(path): arr = [] @@ -32,8 +33,8 @@ def gzip_bin(bin_file, gzip_file): with gzip.open(gzip_file, "wb", compresslevel = 9) as f: shutil.copyfileobj(fp, f) -def readVersion(path, infile): - f = open(path + infile, "r") +def getVersion(path_define): + f = open(path_define, "r") lines = f.readlines() f.close() @@ -48,160 +49,44 @@ def readVersion(path, infile): if(p != -1): version += line[p+13:].rstrip() + "." versionnumber += line[p+13:].rstrip() + "." - - os.mkdir(path + "firmware/") - os.mkdir(path + "firmware/ESP8266/") - os.mkdir(path + "firmware/ESP8285/") - os.mkdir(path + "firmware/ESP32/") - os.mkdir(path + "firmware/ESP32-S2/") - os.mkdir(path + "firmware/ESP32-S3/") - os.mkdir(path + "firmware/ESP32-C3/") - os.mkdir(path + "firmware/ESP32-S3-ETH/") - sha = os.getenv("SHA",default="sha") -## ENGLISH VERSIONS - versionout = version[:-1] + "_" + sha + "_esp8266.bin" - src = path + ".pio/build/esp8266/firmware.bin" - dst = path + "firmware/ESP8266/" + versionout - os.rename(src, dst) + return [version, versionnumber] - versionout = version[:-1] + "_" + sha + "_esp8266_prometheus.bin" - src = path + ".pio/build/esp8266-prometheus/firmware.bin" - dst = path + "firmware/ESP8266/" + versionout - os.rename(src, dst) +def renameFw(path_define, env): + version = getVersion(path_define)[0] - versionout = version[:-1] + "_" + sha + "_esp8285.bin" - src = path + ".pio/build/esp8285/firmware.bin" - dst = path + "firmware/ESP8285/" + versionout - os.rename(src, dst) - gzip_bin(dst, dst + ".gz") - - versionout = version[:-1] + "_" + sha + "_esp32.bin" - src = path + ".pio/build/esp32-wroom32/firmware.bin" - dst = path + "firmware/ESP32/" + versionout - os.rename(src, dst) - - versionout = version[:-1] + "_" + sha + "_esp32_prometheus.bin" - src = path + ".pio/build/esp32-wroom32-prometheus/firmware.bin" - dst = path + "firmware/ESP32/" + versionout - os.rename(src, dst) - - versionout = version[:-1] + "_" + sha + "_esp32_ethernet.bin" - src = path + ".pio/build/esp32-wroom32-ethernet/firmware.bin" - dst = path + "firmware/ESP32/" + versionout - os.rename(src, dst) - - versionout = version[:-1] + "_" + sha + "_esp32s2-mini.bin" - src = path + ".pio/build/esp32-s2-mini/firmware.bin" - dst = path + "firmware/ESP32-S2/" + versionout - os.rename(src, dst) - - versionout = version[:-1] + "_" + sha + "_esp32c3-mini.bin" - src = path + ".pio/build/esp32-c3-mini/firmware.bin" - dst = path + "firmware/ESP32-C3/" + versionout - os.rename(src, dst) - - versionout = version[:-1] + "_" + sha + "_esp32s3.bin" - src = path + ".pio/build/opendtufusion/firmware.bin" - dst = path + "firmware/ESP32-S3/" + versionout - os.rename(src, dst) - - versionout = version[:-1] + "_" + sha + "_esp32s3_ethernet.bin" - src = path + ".pio/build/opendtufusion-ethernet/firmware.bin" - dst = path + "firmware/ESP32-S3-ETH/" + versionout - os.rename(src, dst) - -## GERMAN VERSIONS - versionout = version[:-1] + "_" + sha + "_esp8266-de.bin" - src = path + ".pio/build/esp8266-de/firmware.bin" - dst = path + "firmware/ESP8266/" + versionout - os.rename(src, dst) - - versionout = version[:-1] + "_" + sha + "_esp8266_prometheus-de.bin" - src = path + ".pio/build/esp8266-prometheus-de/firmware.bin" - dst = path + "firmware/ESP8266/" + versionout - os.rename(src, dst) - - versionout = version[:-1] + "_" + sha + "_esp8285-de.bin" - src = path + ".pio/build/esp8285-de/firmware.bin" - dst = path + "firmware/ESP8285/" + versionout - os.rename(src, dst) - gzip_bin(dst, dst + ".gz") - - versionout = version[:-1] + "_" + sha + "_esp32-de.bin" - src = path + ".pio/build/esp32-wroom32-de/firmware.bin" - dst = path + "firmware/ESP32/" + versionout - os.rename(src, dst) - - versionout = version[:-1] + "_" + sha + "_esp32_prometheus-de.bin" - src = path + ".pio/build/esp32-wroom32-prometheus-de/firmware.bin" - dst = path + "firmware/ESP32/" + versionout - os.rename(src, dst) - - versionout = version[:-1] + "_" + sha + "_esp32_ethernet-de.bin" - src = path + ".pio/build/esp32-wroom32-ethernet-de/firmware.bin" - dst = path + "firmware/ESP32/" + versionout - os.rename(src, dst) - - versionout = version[:-1] + "_" + sha + "_esp32s2-mini-de.bin" - src = path + ".pio/build/esp32-s2-mini-de/firmware.bin" - dst = path + "firmware/ESP32-S2/" + versionout - os.rename(src, dst) - - versionout = version[:-1] + "_" + sha + "_esp32c3-mini-de.bin" - src = path + ".pio/build/esp32-c3-mini-de/firmware.bin" - dst = path + "firmware/ESP32-C3/" + versionout - os.rename(src, dst) - - versionout = version[:-1] + "_" + sha + "_esp32s3-de.bin" - src = path + ".pio/build/opendtufusion-de/firmware.bin" - dst = path + "firmware/ESP32-S3/" + versionout - os.rename(src, dst) - - versionout = version[:-1] + "_" + sha + "_esp32s3_ethernet-de.bin" - src = path + ".pio/build/opendtufusion-ethernet-de/firmware.bin" - dst = path + "firmware/ESP32-S3-ETH/" + versionout - os.rename(src, dst) - -## BOOTLOADER AND PARTITIONS - # other ESP32 bin files - src = path + ".pio/build/esp32-wroom32/" - dst = path + "firmware/ESP32/" - os.rename(src + "bootloader.bin", dst + "bootloader.bin") - os.rename(src + "partitions.bin", dst + "partitions.bin") - genOtaBin(dst) - - # other ESP32-S2 bin files - src = path + ".pio/build/esp32-s2-mini/" - dst = path + "firmware/ESP32-S2/" - os.rename(src + "bootloader.bin", dst + "bootloader.bin") - os.rename(src + "partitions.bin", dst + "partitions.bin") - genOtaBin(dst) + os.mkdir("firmware/") + fwDir = "" + if env[:7] == "esp8266": + fwDir = "ESP8266/" + elif env[:7] == "esp8285": + fwDir = "ESP8285/" + elif env[:7] == "esp32-w": + fwDir = "ESP32/" + elif env[:8] == "esp32-s2": + fwDir = "ESP32-S2/" + elif env[:4] == "open": + fwDir = "ESP32-S3/" + elif env[:8] == "esp32-c3": + fwDir = "ESP32-C3/" + os.mkdir("firmware/" + fwDir) + sha = os.getenv("SHA",default="sha") - # other ESP32-C3 bin files - src = path + ".pio/build/esp32-c3-mini/" - dst = path + "firmware/ESP32-C3/" - os.rename(src + "bootloader.bin", dst + "bootloader.bin") - os.rename(src + "partitions.bin", dst + "partitions.bin") - genOtaBin(dst) + dst = "firmware/" + fwDir + fname = version[:-1] + "_" + sha + "_" + env + ".bin" - # other ESP32-S3 bin files - src = path + ".pio/build/opendtufusion/" - dst = path + "firmware/ESP32-S3/" - os.rename(src + "bootloader.bin", dst + "bootloader.bin") - os.rename(src + "partitions.bin", dst + "partitions.bin") - genOtaBin(dst) + os.rename(".pio/build/" + env + "/firmware.bin", dst + fname) - # other ESP32-S3-Eth bin files - src = path + ".pio/build/opendtufusion-ethernet/" - dst = path + "firmware/ESP32-S3-ETH/" - os.rename(src + "bootloader.bin", dst + "bootloader.bin") - os.rename(src + "partitions.bin", dst + "partitions.bin") - genOtaBin(dst) + if env[:5] == "esp32": + os.rename(".pio/build/" + env + "/bootloader.bin", dst + "bootloader.bin") + os.rename(".pio/build/" + env + "/partitions.bin", dst + "partitions.bin") + genOtaBin(dst) - os.rename("../scripts/gh-action-dev-build-flash.html", path + "install.html") + if env[:7] == "esp8285": + gzip_bin(dst + fname, dst + fname[:-4] + ".gz") - print("name=" + versionnumber[:-1] ) - - -readVersion("", "defines.h") +if len(sys.argv) == 1: + print("name=" + getVersion("src/defines.h")[1][:-1]) +else: + # arg1: environment + renameFw("src/defines.h", sys.argv[1]) From 585fd5ca7e6010b6c4cc96f4794a7d131414f6f8 Mon Sep 17 00:00:00 2001 From: lumapu Date: Sun, 7 Jan 2024 20:34:45 +0100 Subject: [PATCH 042/179] github action test --- .github/workflows/compile_development.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/compile_development.yml b/.github/workflows/compile_development.yml index d20c4627..c00029a2 100644 --- a/.github/workflows/compile_development.yml +++ b/.github/workflows/compile_development.yml @@ -66,7 +66,7 @@ jobs: run: pio run -d src -e ${{ matrix.variant }} - name: Rename Firmware - run: python ../scripts/getVersion.py ${{ matrix.variant }} >> $GITHUB_OUTPUT + run: python scripts/getVersion.py ${{ matrix.variant }} >> $GITHUB_OUTPUT - name: Create Artifact uses: actions/upload-artifact@v3 @@ -127,7 +127,7 @@ jobs: run: pio run -d src -e ${{ matrix.variant }} - name: Rename Firmware - run: python ../scripts/getVersion.py ${{ matrix.variant }} >> $GITHUB_OUTPUT + run: python scripts/getVersion.py ${{ matrix.variant }} >> $GITHUB_OUTPUT - name: Create Artifact uses: actions/upload-artifact@v3 @@ -151,7 +151,7 @@ jobs: - name: Get Version from code id: version_name - run: python ../scripts/getVersion.py ${{ matrix.variant }} >> $GITHUB_OUTPUT + run: python scripts/getVersion.py ${{ matrix.variant }} >> $GITHUB_OUTPUT - name: Set Version uses: cschleiden/replace-tokens@v1 From 7f425e094a1335eb657ad5e35f250d1f9b47de43 Mon Sep 17 00:00:00 2001 From: lumapu Date: Sun, 7 Jan 2024 20:40:25 +0100 Subject: [PATCH 043/179] github action test --- scripts/getVersion.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/getVersion.py b/scripts/getVersion.py index 8d6d1cf8..a60a772d 100644 --- a/scripts/getVersion.py +++ b/scripts/getVersion.py @@ -75,11 +75,11 @@ def renameFw(path_define, env): dst = "firmware/" + fwDir fname = version[:-1] + "_" + sha + "_" + env + ".bin" - os.rename(".pio/build/" + env + "/firmware.bin", dst + fname) + os.rename("src/.pio/build/" + env + "/firmware.bin", dst + fname) if env[:5] == "esp32": - os.rename(".pio/build/" + env + "/bootloader.bin", dst + "bootloader.bin") - os.rename(".pio/build/" + env + "/partitions.bin", dst + "partitions.bin") + os.rename("src/.pio/build/" + env + "/bootloader.bin", dst + "bootloader.bin") + os.rename("src/.pio/build/" + env + "/partitions.bin", dst + "partitions.bin") genOtaBin(dst) if env[:7] == "esp8285": From 63d15f217f1f40542623b3a3ee675c4ab026c9ad Mon Sep 17 00:00:00 2001 From: lumapu Date: Sun, 7 Jan 2024 20:45:31 +0100 Subject: [PATCH 044/179] github action test --- .github/workflows/compile_development.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/compile_development.yml b/.github/workflows/compile_development.yml index c00029a2..47976029 100644 --- a/.github/workflows/compile_development.yml +++ b/.github/workflows/compile_development.yml @@ -73,7 +73,7 @@ jobs: with: name: ahoydtu_dev path: | - src/firmware/* + firmware/* build-de: needs: check From 57ba359fa33a9dcb28a4bffbd64bfd5491d5ee3b Mon Sep 17 00:00:00 2001 From: lumapu Date: Sun, 7 Jan 2024 20:55:15 +0100 Subject: [PATCH 045/179] github action test --- .github/workflows/compile_development.yml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/.github/workflows/compile_development.yml b/.github/workflows/compile_development.yml index 47976029..6d7991c0 100644 --- a/.github/workflows/compile_development.yml +++ b/.github/workflows/compile_development.yml @@ -145,9 +145,6 @@ jobs: - name: Get Artifacts uses: actions/download-artifact@master - with: - name: ahoydtu_dev - path: firmware/* - name: Get Version from code id: version_name @@ -174,12 +171,12 @@ jobs: # src/install.html - name: Rename firmware directory - run: mv src/firmware src/${{ steps.version_name.outputs.name }} + run: mv firmware ${{ steps.version_name.outputs.name }} - name: Deploy uses: nogsantos/scp-deploy@master with: - src: src/${{ steps.version_name.outputs.name }}/ + src: ${{ steps.version_name.outputs.name }}/ host: ${{ secrets.FW_SSH_HOST }} remote: ${{ secrets.FW_SSH_DIR }}/dev port: ${{ secrets.FW_SSH_PORT }} From 4b3a670294004ac9b47edb22a0469ba8e8960d86 Mon Sep 17 00:00:00 2001 From: lumapu Date: Sun, 7 Jan 2024 21:00:12 +0100 Subject: [PATCH 046/179] github action test --- .github/workflows/compile_development.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/compile_development.yml b/.github/workflows/compile_development.yml index 6d7991c0..0725cb2c 100644 --- a/.github/workflows/compile_development.yml +++ b/.github/workflows/compile_development.yml @@ -140,6 +140,7 @@ jobs: needs: [build-en, build-de] runs-on: ubuntu-latest steps: + - uses: actions/checkout@v3 #- name: Copy boot_app0.bin # run: cp ~/.platformio/packages/framework-arduinoespressif32/tools/partitions/boot_app0.bin src/.pio/build/opendtufusion/ota.bin From b70c9bfe26cfb2d53c2311a78f946eb9831de999 Mon Sep 17 00:00:00 2001 From: lumapu Date: Sun, 7 Jan 2024 21:09:38 +0100 Subject: [PATCH 047/179] github action test --- .github/workflows/compile_development.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/compile_development.yml b/.github/workflows/compile_development.yml index 0725cb2c..61a060e8 100644 --- a/.github/workflows/compile_development.yml +++ b/.github/workflows/compile_development.yml @@ -145,7 +145,12 @@ jobs: # run: cp ~/.platformio/packages/framework-arduinoespressif32/tools/partitions/boot_app0.bin src/.pio/build/opendtufusion/ota.bin - name: Get Artifacts - uses: actions/download-artifact@master + uses: actions/download-artifact@v4 + path: firmware + with: + name: ahoydtu_dev + - name: Display structure of downloaded files + run: ls -R - name: Get Version from code id: version_name From 78c37625388fd579f16cddb8f546596049819533 Mon Sep 17 00:00:00 2001 From: lumapu Date: Sun, 7 Jan 2024 21:10:59 +0100 Subject: [PATCH 048/179] github action test --- .github/workflows/compile_development.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/compile_development.yml b/.github/workflows/compile_development.yml index 61a060e8..9cc1f530 100644 --- a/.github/workflows/compile_development.yml +++ b/.github/workflows/compile_development.yml @@ -146,9 +146,9 @@ jobs: - name: Get Artifacts uses: actions/download-artifact@v4 - path: firmware with: name: ahoydtu_dev + path: firmware - name: Display structure of downloaded files run: ls -R From fabe410967c5ec8abf3b9f4d2f06cd375ae2bcca Mon Sep 17 00:00:00 2001 From: lumapu Date: Sun, 7 Jan 2024 21:26:05 +0100 Subject: [PATCH 049/179] github action test --- .github/workflows/compile_development.yml | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/.github/workflows/compile_development.yml b/.github/workflows/compile_development.yml index 9cc1f530..a4fd3c57 100644 --- a/.github/workflows/compile_development.yml +++ b/.github/workflows/compile_development.yml @@ -69,11 +69,10 @@ jobs: run: python scripts/getVersion.py ${{ matrix.variant }} >> $GITHUB_OUTPUT - name: Create Artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: ahoydtu_dev - path: | - firmware/* + path: firmware/* build-de: needs: check @@ -130,11 +129,10 @@ jobs: run: python scripts/getVersion.py ${{ matrix.variant }} >> $GITHUB_OUTPUT - name: Create Artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: ahoydtu_dev - path: | - firmware/* + path: firmware/* deploy: needs: [build-en, build-de] From 9889cc334d4a1a4c39a70293c9886551882f3b8a Mon Sep 17 00:00:00 2001 From: lumapu Date: Sun, 7 Jan 2024 21:31:00 +0100 Subject: [PATCH 050/179] github action test --- .github/workflows/compile_development.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/compile_development.yml b/.github/workflows/compile_development.yml index a4fd3c57..364a5962 100644 --- a/.github/workflows/compile_development.yml +++ b/.github/workflows/compile_development.yml @@ -131,7 +131,7 @@ jobs: - name: Create Artifact uses: actions/upload-artifact@v4 with: - name: ahoydtu_dev + name: dev-${{ matrix.variant }} path: firmware/* deploy: @@ -144,9 +144,6 @@ jobs: - name: Get Artifacts uses: actions/download-artifact@v4 - with: - name: ahoydtu_dev - path: firmware - name: Display structure of downloaded files run: ls -R From 6677116badef88874795f8659cf8cceb4ad23d55 Mon Sep 17 00:00:00 2001 From: lumapu Date: Sun, 7 Jan 2024 21:42:52 +0100 Subject: [PATCH 051/179] github action test --- .github/workflows/compile_development.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/compile_development.yml b/.github/workflows/compile_development.yml index 364a5962..2e3dcb31 100644 --- a/.github/workflows/compile_development.yml +++ b/.github/workflows/compile_development.yml @@ -71,7 +71,7 @@ jobs: - name: Create Artifact uses: actions/upload-artifact@v4 with: - name: ahoydtu_dev + name: dev-${{ matrix.variant }} path: firmware/* build-de: @@ -144,8 +144,11 @@ jobs: - name: Get Artifacts uses: actions/download-artifact@v4 + with: + merge-multiple: true + path: firmware - name: Display structure of downloaded files - run: ls -R + run: ls -R firmware - name: Get Version from code id: version_name From 3c5be9ae35470169f04842c4b1ece9067a59efd5 Mon Sep 17 00:00:00 2001 From: lumapu Date: Sun, 7 Jan 2024 22:25:59 +0100 Subject: [PATCH 052/179] 0.8.48 * merge PR: pin selection for ESP-32 S2 #1334 * merge PR: enhancement: power graph display option #1330 --- .github/workflows/compile_development.yml | 15 - src/CHANGES.md | 4 + src/defines.h | 2 +- src/plugins/Display/Display_Mono.h | 459 +++++++++++----------- src/plugins/Display/Display_Mono_128X64.h | 4 +- src/plugins/Display/Display_Mono_84X48.h | 4 +- 6 files changed, 238 insertions(+), 250 deletions(-) diff --git a/.github/workflows/compile_development.yml b/.github/workflows/compile_development.yml index 2e3dcb31..7eaf9b34 100644 --- a/.github/workflows/compile_development.yml +++ b/.github/workflows/compile_development.yml @@ -147,8 +147,6 @@ jobs: with: merge-multiple: true path: firmware - - name: Display structure of downloaded files - run: ls -R firmware - name: Get Version from code id: version_name @@ -161,19 +159,6 @@ jobs: env: VERSION: ${{ steps.version_name.outputs.name }} -# - name: Create Manifest -# working-directory: src -# run: python ../scripts/buildManifest.py -# -# - name: Create Artifact -# uses: actions/upload-artifact@v3 -# with: -# name: ahoydtu_dev -# path: | -# src/firmware/* -# src/User_Manual.md -# src/install.html - - name: Rename firmware directory run: mv firmware ${{ steps.version_name.outputs.name }} diff --git a/src/CHANGES.md b/src/CHANGES.md index cddfc86a..5073a2c9 100644 --- a/src/CHANGES.md +++ b/src/CHANGES.md @@ -1,5 +1,9 @@ # Development Changes +## 0.8.48 - 2024-01-07 +* merge PR: pin selection for ESP-32 S2 #1334 +* merge PR: enhancement: power graph display option #1330 + ## 0.8.47 - 2024-01-06 * reduce GxEPD2 lib to compile faster * upgraded GxEPD2 lib to `1.5.3` diff --git a/src/defines.h b/src/defines.h index 87b4101d..bd5cc7ff 100644 --- a/src/defines.h +++ b/src/defines.h @@ -13,7 +13,7 @@ //------------------------------------- #define VERSION_MAJOR 0 #define VERSION_MINOR 8 -#define VERSION_PATCH 47 +#define VERSION_PATCH 48 //------------------------------------- typedef struct { diff --git a/src/plugins/Display/Display_Mono.h b/src/plugins/Display/Display_Mono.h index c5aa2ed3..c7235815 100644 --- a/src/plugins/Display/Display_Mono.h +++ b/src/plugins/Display/Display_Mono.h @@ -22,256 +22,255 @@ #include "../../utils/timemonitor.h" class DisplayMono { - public: - DisplayMono() {}; - - virtual void init(uint8_t type, uint8_t rot, uint8_t cs, uint8_t dc, uint8_t reset, uint8_t clock, uint8_t data, DisplayData *displayData) = 0; - virtual void config(bool enPowerSave, uint8_t screenSaver, uint8_t lum, uint8_t graph_ratio, uint8_t graph_size) = 0; - virtual void disp(void) = 0; - - // Common loop function, manages display on/off functions for powersave and screensaver with motionsensor - // can be overridden by subclasses - virtual bool loop(uint8_t lum, bool motion) { - - bool dispConditions = (!mEnPowerSave || (mDisplayData->nrProducing > 0)) && - ((mScreenSaver != 2) || motion); // screensaver 2 .. motionsensor - - if (mDisplayActive) { - if (!dispConditions) { - if (mDisplayTime.isTimeout()) { // switch display off after timeout - mDisplayActive = false; - mDisplay->setPowerSave(true); - DBGPRINTLN("**** Display off ****"); + public: + DisplayMono() {}; + + virtual void init(uint8_t type, uint8_t rot, uint8_t cs, uint8_t dc, uint8_t reset, uint8_t clock, uint8_t data, DisplayData *displayData) = 0; + virtual void config(bool enPowerSave, uint8_t screenSaver, uint8_t lum, uint8_t graph_ratio, uint8_t graph_size) = 0; + virtual void disp(void) = 0; + + // Common loop function, manages display on/off functions for powersave and screensaver with motionsensor + // can be overridden by subclasses + virtual bool loop(uint8_t lum, bool motion) { + + bool dispConditions = (!mEnPowerSave || (mDisplayData->nrProducing > 0)) && + ((mScreenSaver != 2) || motion); // screensaver 2 .. motionsensor + + if (mDisplayActive) { + if (!dispConditions) { + if (mDisplayTime.isTimeout()) { // switch display off after timeout + mDisplayActive = false; + mDisplay->setPowerSave(true); + DBGPRINTLN("**** Display off ****"); + } } + else + mDisplayTime.reStartTimeMonitor(); // keep display on } - else - mDisplayTime.reStartTimeMonitor(); // keep display on - } - else { - if (dispConditions) { - mDisplayActive = true; - mDisplayTime.reStartTimeMonitor(); // switch display on - mDisplay->setPowerSave(false); - DBGPRINTLN("**** Display on ****"); + else { + if (dispConditions) { + mDisplayActive = true; + mDisplayTime.reStartTimeMonitor(); // switch display on + mDisplay->setPowerSave(false); + DBGPRINTLN("**** Display on ****"); + } + } + + if(mLuminance != lum) { + mLuminance = lum; + mDisplay->setContrast(mLuminance); } - } - if(mLuminance != lum) { - mLuminance = lum; + return(monoMaintainDispSwitchState()); + } + + protected: + enum class DispSwitchState { + TEXT, + GRAPH + }; + + protected: + U8G2* mDisplay; + DisplayData *mDisplayData; + + float *mPgData = nullptr; + uint8_t mPgWidth = 0; + uint8_t mPgHeight = 0; + float mPgMaxPwr = 0.0; + uint32_t mPgPeriod = 0; // seconds + uint32_t mPgTimeOfDay = 0; + uint8_t mPgLastPos = 0; + + uint8_t mType; + uint16_t mDispWidth; + uint16_t mDispHeight; + + bool mEnPowerSave; + uint8_t mScreenSaver = 1; // 0 .. off; 1 .. pixelShift; 2 .. motionsensor + uint8_t mLuminance; + uint8_t mGraphRatio; + uint8_t mGraphSize; + + uint8_t mLoopCnt; + uint8_t mLineXOffsets[5] = {}; + uint8_t mLineYOffsets[5] = {}; + + uint8_t mExtra; + int8_t mPixelshift=0; + TimeMonitor mDisplayTime = TimeMonitor(1000 * DISP_DEFAULT_TIMEOUT, true); + TimeMonitor mDispSwitchTime = TimeMonitor(); + DispSwitchState mDispSwitchState = DispSwitchState::TEXT; + bool mDisplayActive = true; // always start with display on + char mFmtText[DISP_FMT_TEXT_LEN]; + + // Common initialization function to be called by subclasses + void monoInit(U8G2* display, uint8_t type, DisplayData *displayData) { + mDisplay = display; + mType = type; + mDisplayData = displayData; + mDisplay->begin(); + mDisplay->setPowerSave(false); // always start with display on mDisplay->setContrast(mLuminance); - } - - return(monoMaintainDispSwitchState()); - } - - protected: - U8G2* mDisplay; - DisplayData *mDisplayData; - - float *mPgData=nullptr; - uint8_t mPgWidth=0; - uint8_t mPgHeight=0; - float mPgMaxPwr=0.0; -// float mPgMaxAvailPower = 0.0; - uint32_t mPgPeriod=0; // seconds - uint32_t mPgTimeOfDay=0; - uint8_t mPgLastPos=0; - - uint8_t mType; - uint16_t mDispWidth; - uint16_t mDispHeight; - - bool mEnPowerSave; - uint8_t mScreenSaver = 1; // 0 .. off; 1 .. pixelShift; 2 .. motionsensor - uint8_t mLuminance; - uint8_t mGraphRatio; - uint8_t mGraphSize; - - uint8_t mLoopCnt; - uint8_t mLineXOffsets[5] = {}; - uint8_t mLineYOffsets[5] = {}; - - uint8_t mExtra; - int8_t mPixelshift=0; - TimeMonitor mDisplayTime = TimeMonitor(1000 * DISP_DEFAULT_TIMEOUT, true); - TimeMonitor mDispSwitchTime = TimeMonitor(); - uint8_t mDispSwitchState; - bool mDisplayActive = true; // always start with display on - char mFmtText[DISP_FMT_TEXT_LEN]; - - enum _dispSwitchState { - d_POWER_TEXT = 0, - d_POWER_GRAPH = 1, - }; - - // Common initialization function to be called by subclasses - void monoInit(U8G2* display, uint8_t type, DisplayData *displayData) { - mDisplay = display; - mType = type; - mDisplayData = displayData; - mDisplay->begin(); - mDisplay->setPowerSave(false); // always start with display on - mDisplay->setContrast(mLuminance); - mDisplay->clearBuffer(); - mDispWidth = mDisplay->getDisplayWidth(); - mDispHeight = mDisplay->getDisplayHeight(); - mDispSwitchTime.stopTimeMonitor(); - mDispSwitchState = d_POWER_TEXT; - if (mGraphRatio == 100) // if graph ratio is 100% start in graph mode - mDispSwitchState = d_POWER_GRAPH; - else if (mGraphRatio != 0) - mDispSwitchTime.startTimeMonitor(150 * (100 - mGraphRatio)); // start display mode change only if ratio is neither 0 nor 100 - } - - bool monoMaintainDispSwitchState(void) { - bool change = false; - switch(mDispSwitchState) { - case d_POWER_TEXT: - if (mDispSwitchTime.isTimeout()) { - mDispSwitchState = d_POWER_GRAPH; - mDispSwitchTime.startTimeMonitor(150 * mGraphRatio); // mGraphRatio: 0-100 Gesamtperiode 15000 ms - change = true; - } - break; - case d_POWER_GRAPH: - if (mDispSwitchTime.isTimeout()) { - mDispSwitchState = d_POWER_TEXT; - mDispSwitchTime.startTimeMonitor(150 * (100 - mGraphRatio)); - change = true; - } - break; - } - return change; - } - - void initPowerGraph(uint8_t width, uint8_t height) { - mPgWidth = width; - mPgHeight = height; - mPgData = new float[mPgWidth]; - //memset(mPgData, 0, mPgWidth); - resetPowerGraph(); + mDisplay->clearBuffer(); + mDispWidth = mDisplay->getDisplayWidth(); + mDispHeight = mDisplay->getDisplayHeight(); + mDispSwitchTime.stopTimeMonitor(); + if (mGraphRatio == 100) // if graph ratio is 100% start in graph mode + mDispSwitchState = DispSwitchState::GRAPH; + else if (mGraphRatio != 0) + mDispSwitchTime.startTimeMonitor(150 * (100 - mGraphRatio)); // start display mode change only if ratio is neither 0 nor 100 + } + + bool monoMaintainDispSwitchState(void) { + bool change = false; + switch(mDispSwitchState) { + case DispSwitchState::TEXT: + if (mDispSwitchTime.isTimeout()) { + mDispSwitchState = DispSwitchState::GRAPH; + mDispSwitchTime.startTimeMonitor(150 * mGraphRatio); // mGraphRatio: 0-100 Gesamtperiode 15000 ms + change = true; + } + break; + case DispSwitchState::GRAPH: + if (mDispSwitchTime.isTimeout()) { + mDispSwitchState = DispSwitchState::TEXT; + mDispSwitchTime.startTimeMonitor(150 * (100 - mGraphRatio)); + change = true; + } + break; + } + return change; + } + + void initPowerGraph(uint8_t width, uint8_t height) { + mPgWidth = width; + mPgHeight = height; + mPgData = new float[mPgWidth]; + //memset(mPgData, 0, mPgWidth); + resetPowerGraph(); /* - Inverter<> *iv; - mPgMaxAvailPower = 0; - uint8_t nInv = mSys->getNumInverters(); - for (uint8_t i = 0; i < nInv; i++) { - iv = mSys->getInverterByPos(i); - if (iv == NULL) - continue; - for (uint8_t ch = 0; ch < 6; ch++) { - mPgMaxAvailPower += iv->config->chMaxPwr[ch]; + Inverter<> *iv; + mPgMaxAvailPower = 0; + uint8_t nInv = mSys->getNumInverters(); + for (uint8_t i = 0; i < nInv; i++) { + iv = mSys->getInverterByPos(i); + if (iv == NULL) + continue; + for (uint8_t ch = 0; ch < 6; ch++) { + mPgMaxAvailPower += iv->config->chMaxPwr[ch]; + } } - } - DBGPRINTLN("max. Power = " + String(mPgMaxAvailPower));*/ - } - - void resetPowerGraph() { - if (mPgData != nullptr) { - mPgMaxPwr = 0.0; - mPgLastPos = 0; - for (uint8_t i = 0; i < mPgWidth; i++) - mPgData[i] = 0.0; + DBGPRINTLN("max. Power = " + String(mPgMaxAvailPower));*/ } - } - uint8_t sss2pgpos(uint seconds_since_start) { - return(seconds_since_start * (mPgWidth - 1) / (mDisplayData->pGraphEndTime - mDisplayData->pGraphStartTime)); - } + void resetPowerGraph() { + if (mPgData != nullptr) { + mPgMaxPwr = 0.0; + mPgLastPos = 0; + for (uint8_t i = 0; i < mPgWidth; i++) + mPgData[i] = 0.0; + } + } - void calcPowerGraphValues() { - mPgPeriod = mDisplayData->pGraphEndTime - mDisplayData->pGraphStartTime; // length of power graph for scaling of x-axis - uint32_t oldTimeOfDay = mPgTimeOfDay; - mPgTimeOfDay = (mDisplayData->utcTs > mDisplayData->pGraphStartTime) ? mDisplayData->utcTs - mDisplayData->pGraphStartTime : 0; // current time of day with respect to current sunrise time - if (oldTimeOfDay > mPgTimeOfDay) // new day -> reset old data - resetPowerGraph(); - mPgLastPos = std::min((uint8_t) (mPgTimeOfDay * (mPgWidth - 1) / mPgPeriod), (uint8_t) (mPgWidth - 1)); // current datapoint based on currenct time of day - } - - void addPowerGraphEntry(float val) { - if (mDisplayData->utcTs > 0) { // precondition: utc time available - calcPowerGraphValues(); - //mPgData[mPgLastPos] = std::max(mPgData[mPgLastPos], (uint8_t) (val * 255.0 / mPgMaxAvailPower)); // normalizing of data to 0-255 - mPgData[mPgLastPos] = std::max(mPgData[mPgLastPos], val); - mPgMaxPwr = std::max(mPgMaxPwr, val); // max value of stored data for scaling of y-axis - } - } - - uint8_t getPowerGraphXpos(uint8_t p) { // - if ((p <= mPgLastPos) && (mPgLastPos > 0)) - return((p * (mPgWidth - 1)) / mPgLastPos); // scaling of x-axis - else - return(0); - } - - uint8_t getPowerGraphYpos(uint8_t p) { - if (p < mPgWidth) - //return(((uint32_t) mPgData[p] * (uint32_t) mPgMaxAvailPower) * (uint32_t) mPgHeight / mPgMaxPwr / 255); // scaling of normalized data (0-255) to graph height - return((mPgData[p] * (uint32_t) mPgHeight / mPgMaxPwr)); // scaling of data to graph height - else - return(0); - } - - void plotPowerGraph(uint8_t xoff, uint8_t yoff) { - // draw axes - mDisplay->drawLine(xoff, yoff, xoff, yoff - mPgHeight); // vertical axis - mDisplay->drawLine(xoff, yoff, xoff + mPgWidth, yoff); // horizontal axis - - // draw X scale - tmElements_t tm; - breakTime(mDisplayData->pGraphEndTime, tm); - uint8_t endHourPg = tm.Hour; - breakTime(mDisplayData->utcTs, tm); - uint8_t endHour = std::min(endHourPg, tm.Hour); - breakTime(mDisplayData->pGraphStartTime, tm); - tm.Hour += 1; - tm.Minute = 0; - tm.Second = 0; - for (; tm.Hour <= endHour; tm.Hour++) { - uint8_t x_pos_screen = getPowerGraphXpos(sss2pgpos((uint32_t) makeTime(tm) - mDisplayData->pGraphStartTime)); // scale horizontal axis - mDisplay->drawPixel(xoff + x_pos_screen, yoff - 1); + uint8_t sss2pgpos(uint seconds_since_start) { + return(seconds_since_start * (mPgWidth - 1) / (mDisplayData->pGraphEndTime - mDisplayData->pGraphStartTime)); } - // draw Y scale - uint16_t scale_y = 10; - uint32_t maxpwr_int = static_cast(std::round(mPgMaxPwr)); - if (maxpwr_int > 100) - scale_y = 100; - for (uint32_t i = scale_y; i <= maxpwr_int; i += scale_y) { - uint8_t ypos = yoff - static_cast(std::round(i * (float) mPgHeight / mPgMaxPwr)); // scale vertical axis - mDisplay->drawPixel(xoff + 1, ypos); + void calcPowerGraphValues() { + mPgPeriod = mDisplayData->pGraphEndTime - mDisplayData->pGraphStartTime; // length of power graph for scaling of x-axis + uint32_t oldTimeOfDay = mPgTimeOfDay; + mPgTimeOfDay = (mDisplayData->utcTs > mDisplayData->pGraphStartTime) ? mDisplayData->utcTs - mDisplayData->pGraphStartTime : 0; // current time of day with respect to current sunrise time + if (oldTimeOfDay > mPgTimeOfDay) // new day -> reset old data + resetPowerGraph(); + mPgLastPos = std::min((uint8_t) (mPgTimeOfDay * (mPgWidth - 1) / mPgPeriod), (uint8_t) (mPgWidth - 1)); // current datapoint based on currenct time of day + } + + void addPowerGraphEntry(float val) { + if (mDisplayData->utcTs > 0) { // precondition: utc time available + calcPowerGraphValues(); + //mPgData[mPgLastPos] = std::max(mPgData[mPgLastPos], (uint8_t) (val * 255.0 / mPgMaxAvailPower)); // normalizing of data to 0-255 + mPgData[mPgLastPos] = std::max(mPgData[mPgLastPos], val); + mPgMaxPwr = std::max(mPgMaxPwr, val); // max value of stored data for scaling of y-axis + } } - // draw curve - for (uint8_t i = 1; i <= mPgLastPos; i++) { - mDisplay->drawLine(xoff + getPowerGraphXpos(i - 1), yoff - getPowerGraphYpos(i - 1), - xoff + getPowerGraphXpos(i), yoff - getPowerGraphYpos(i)); + uint8_t getPowerGraphXpos(uint8_t p) { // + if ((p <= mPgLastPos) && (mPgLastPos > 0)) + return((p * (mPgWidth - 1)) / mPgLastPos); // scaling of x-axis + else + return(0); } - // print max power value - mDisplay->setFont(u8g2_font_4x6_tr); - snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%dW", static_cast(std::round(mPgMaxPwr))); - mDisplay->drawStr(xoff + 3, yoff - mPgHeight + 5, mFmtText); - } - - // pixelshift screensaver with wipe effect - void calcPixelShift(int range) { - int8_t mod = (millis() / 10000) % ((range >> 1) << 2); - mPixelshift = mScreenSaver == 1 ? ((mod < range) ? mod - (range >> 1) : -(mod - range - (range >> 1) + 1)) : 0; - } + uint8_t getPowerGraphYpos(uint8_t p) { + if (p < mPgWidth) + //return(((uint32_t) mPgData[p] * (uint32_t) mPgMaxAvailPower) * (uint32_t) mPgHeight / mPgMaxPwr / 255); // scaling of normalized data (0-255) to graph height + return((mPgData[p] * (uint32_t) mPgHeight / mPgMaxPwr)); // scaling of data to graph height + else + return(0); + } + + void plotPowerGraph(uint8_t xoff, uint8_t yoff) { + // draw axes + mDisplay->drawLine(xoff, yoff, xoff, yoff - mPgHeight); // vertical axis + mDisplay->drawLine(xoff, yoff, xoff + mPgWidth, yoff); // horizontal axis + + // draw X scale + tmElements_t tm; + breakTime(mDisplayData->pGraphEndTime, tm); + uint8_t endHourPg = tm.Hour; + breakTime(mDisplayData->utcTs, tm); + uint8_t endHour = std::min(endHourPg, tm.Hour); + breakTime(mDisplayData->pGraphStartTime, tm); + tm.Hour += 1; + tm.Minute = 0; + tm.Second = 0; + for (; tm.Hour <= endHour; tm.Hour++) { + uint8_t x_pos_screen = getPowerGraphXpos(sss2pgpos((uint32_t) makeTime(tm) - mDisplayData->pGraphStartTime)); // scale horizontal axis + mDisplay->drawPixel(xoff + x_pos_screen, yoff - 1); + } + + // draw Y scale + uint16_t scale_y = 10; + uint32_t maxpwr_int = static_cast(std::round(mPgMaxPwr)); + if (maxpwr_int > 100) + scale_y = 100; + for (uint32_t i = scale_y; i <= maxpwr_int; i += scale_y) { + uint8_t ypos = yoff - static_cast(std::round(i * (float) mPgHeight / mPgMaxPwr)); // scale vertical axis + mDisplay->drawPixel(xoff + 1, ypos); + } + + // draw curve + for (uint8_t i = 1; i <= mPgLastPos; i++) { + mDisplay->drawLine(xoff + getPowerGraphXpos(i - 1), yoff - getPowerGraphYpos(i - 1), + xoff + getPowerGraphXpos(i), yoff - getPowerGraphYpos(i)); + } + + // print max power value + mDisplay->setFont(u8g2_font_4x6_tr); + snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%dW", static_cast(std::round(mPgMaxPwr))); + mDisplay->drawStr(xoff + 3, yoff - mPgHeight + 5, mFmtText); + } + + // pixelshift screensaver with wipe effect + void calcPixelShift(int range) { + int8_t mod = (millis() / 10000) % ((range >> 1) << 2); + mPixelshift = mScreenSaver == 1 ? ((mod < range) ? mod - (range >> 1) : -(mod - range - (range >> 1) + 1)) : 0; + } }; /* adapted 5x8 Font for low-res displays with symbols Symbols: - \x80 ... antenna - \x81 ... WiFi - \x82 ... suncurve - \x83 ... sum/sigma - \x84 ... antenna crossed - \x85 ... WiFi crossed - \x86 ... sun - \x87 ... moon - \x88 ... calendar/day - \x89 ... MQTT */ + \x80 ... antenna + \x81 ... WiFi + \x82 ... suncurve + \x83 ... sum/sigma + \x84 ... antenna crossed + \x85 ... WiFi crossed + \x86 ... sun + \x87 ... moon + \x88 ... calendar/day + \x89 ... MQTT */ const uint8_t u8g2_font_5x8_symbols_ahoy[1049] U8G2_FONT_SECTION("u8g2_font_5x8_symbols_ahoy") = "j\0\3\2\4\4\3\4\5\10\10\0\377\6\377\6\0\1\61\2b\4\0 \5\0\304\11!\7a\306" "\212!\11\42\7\63\335\212\304\22#\16u\304\232R\222\14JePJI\2$\14u\304\252l\251m" diff --git a/src/plugins/Display/Display_Mono_128X64.h b/src/plugins/Display/Display_Mono_128X64.h index 32459c89..c8338691 100644 --- a/src/plugins/Display/Display_Mono_128X64.h +++ b/src/plugins/Display/Display_Mono_128X64.h @@ -176,7 +176,7 @@ class DisplayMono128X64 : public DisplayMono { printText(mFmtText, l_YieldTotal, 0xff); } - if (mDispSwitchState == d_POWER_GRAPH) { + if (mDispSwitchState == DispSwitchState::GRAPH) { // plot power graph plotPowerGraph((mDispWidth - mPgWidth) / 2 + mPixelshift, mLineYOffsets[graph_last_line] - 1); } @@ -274,6 +274,6 @@ class DisplayMono128X64 : public DisplayMono { } bool showLine(uint8_t line) { - return ((mDispSwitchState == d_POWER_TEXT) || ((line < graph_first_line) || (line > graph_last_line))); + return ((mDispSwitchState == DispSwitchState::TEXT) || ((line < graph_first_line) || (line > graph_last_line))); } }; diff --git a/src/plugins/Display/Display_Mono_84X48.h b/src/plugins/Display/Display_Mono_84X48.h index ee2aebd5..b195e17c 100644 --- a/src/plugins/Display/Display_Mono_84X48.h +++ b/src/plugins/Display/Display_Mono_84X48.h @@ -137,7 +137,7 @@ class DisplayMono84X48 : public DisplayMono { printText(mFmtText, l_YieldTotal, 0xff); } - if (mDispSwitchState == d_POWER_GRAPH) { + if (mDispSwitchState == DispSwitchState::GRAPH) { // plot power graph plotPowerGraph(8, mLineYOffsets[graph_last_line] - 1); } @@ -227,7 +227,7 @@ class DisplayMono84X48 : public DisplayMono { } bool showLine(uint8_t line) { - return ((mDispSwitchState == d_POWER_TEXT) || ((line < graph_first_line) || (line > graph_last_line))); + return ((mDispSwitchState == DispSwitchState::TEXT) || ((line < graph_first_line) || (line > graph_last_line))); } }; From 2b4de00a894141048d94b2d713689f62753df1c8 Mon Sep 17 00:00:00 2001 From: VArt67 <132200455+VArt67@users.noreply.github.com> Date: Sun, 7 Jan 2024 23:25:50 +0100 Subject: [PATCH 053/179] * Added history charts to web and to Display_Mono_128x64 --- src/app.cpp | 10 +- src/app.h | 6 + src/appInterface.h | 4 + src/config/config.h | 3 + src/config/settings.h | 7 +- src/plugins/Display/Display.h | 18 +- src/plugins/Display/Display_Mono.h | 8 + src/plugins/Display/Display_Mono_128X64.h | 261 ++++++++++++++-------- src/plugins/Display/Display_data.h | 2 + src/plugins/history.cpp | 94 ++++++++ src/plugins/history.h | 86 +++++++ src/web/RestApi.h | 121 +++++++++- src/web/html/api.js | 2 +- src/web/html/history.html | 135 +++++++++++ src/web/html/includes/nav.html | 1 + src/web/html/style.css | 20 ++ src/web/web.h | 10 +- 17 files changed, 691 insertions(+), 97 deletions(-) create mode 100644 src/plugins/history.cpp create mode 100644 src/plugins/history.h create mode 100644 src/web/html/history.html diff --git a/src/app.cpp b/src/app.cpp index 0bba14da..9ec3d86c 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -7,7 +7,7 @@ #include "app.h" #include "utils/sun.h" - +#include "plugins/history.h" //----------------------------------------------------------------------------- app::app() : ah::Scheduler {} {} @@ -91,6 +91,11 @@ void app::setup() { #endif #endif + mTotalPowerHistory = new TotalPowerHistory(); + mTotalPowerHistory->setup(this, &mSys, mConfig); + mYieldDayHistory = new YieldDayHistory(); + mYieldDayHistory->setup(this, &mSys, mConfig); + mPubSerial.setup(mConfig, &mSys, &mTimestamp); #if !defined(ETHERNET) @@ -148,6 +153,9 @@ void app::regularTickers(void) { #if !defined(ETHERNET) //everySec([this]() { mImprov.tickSerial(); }, "impro"); #endif + + everySec(std::bind(&TotalPowerHistory::tickerSecond, mTotalPowerHistory), "totalPowerHistory"); + everySec(std::bind(&YieldDayHistory::tickerSecond, mYieldDayHistory), "yieldDayHistory"); } #if defined(ETHERNET) diff --git a/src/app.h b/src/app.h index a24cccb3..029f0f1e 100644 --- a/src/app.h +++ b/src/app.h @@ -55,6 +55,7 @@ typedef PubSerial PubSerialType; #include "plugins/Display/Display_data.h" typedef Display DisplayType; #endif +#include "plugins/history.h" class app : public IApp, public ah::Scheduler { public: @@ -243,6 +244,9 @@ class app : public IApp, public ah::Scheduler { Scheduler::setTimestamp(newTime); } + TotalPowerHistory *getTotalPowerHistoryPtr() { return mTotalPowerHistory; }; + YieldDayHistory *getYieldDayHistoryPtr() { return mYieldDayHistory; }; + private: #define CHECK_AVAIL true #define SKIP_YIELD_DAY true @@ -350,6 +354,8 @@ class app : public IApp, public ah::Scheduler { DisplayType mDisplay; DisplayData mDispData; #endif + TotalPowerHistory *mTotalPowerHistory; + YieldDayHistory *mYieldDayHistory; }; #endif /*__APP_H__*/ diff --git a/src/appInterface.h b/src/appInterface.h index 34dc5ddc..89d52d9e 100644 --- a/src/appInterface.h +++ b/src/appInterface.h @@ -14,6 +14,8 @@ #include "ESPAsyncWebServer.h" #endif +class TotalPowerHistory; +class YieldDayHistory; //#include "hms/hmsRadio.h" #if defined(ESP32) //typedef CmtRadio> CmtRadioType; @@ -63,6 +65,8 @@ class IApp { virtual bool getProtection(AsyncWebServerRequest *request) = 0; + virtual TotalPowerHistory *getTotalPowerHistoryPtr() = 0; + virtual YieldDayHistory *getYieldDayHistoryPtr() = 0; virtual void* getRadioObj(bool nrf) = 0; }; diff --git a/src/config/config.h b/src/config/config.h index 2cb9bcd3..393c78e3 100644 --- a/src/config/config.h +++ b/src/config/config.h @@ -36,6 +36,9 @@ // CONFIGURATION - COMPILE TIME //------------------------------------- +// Draw power chart in MONO-Display +#define DISPLAY_CHART 1 + // ethernet #if defined(ETHERNET) diff --git a/src/config/settings.h b/src/config/settings.h index fe2ad1b0..ec61d733 100644 --- a/src/config/settings.h +++ b/src/config/settings.h @@ -41,6 +41,7 @@ #define PROT_MASK_SYSTEM 0x0020 #define PROT_MASK_API 0x0040 #define PROT_MASK_MQTT 0x0080 +#define PROT_MASK_HISTORY 0x0100 #define DEF_PROT_INDEX 0x0001 #define DEF_PROT_LIVE 0x0000 @@ -50,7 +51,7 @@ #define DEF_PROT_SYSTEM 0x0020 #define DEF_PROT_API 0x0000 #define DEF_PROT_MQTT 0x0000 - +#define DEF_PROT_HISTORY 0x0000 typedef struct { uint8_t ip[4]; // ip address @@ -373,7 +374,7 @@ class settings { // erase all settings and reset to default memset(&mCfg, 0, sizeof(settings_t)); mCfg.sys.protectionMask = DEF_PROT_INDEX | DEF_PROT_LIVE | DEF_PROT_SERIAL | DEF_PROT_SETUP - | DEF_PROT_UPDATE | DEF_PROT_SYSTEM | DEF_PROT_API | DEF_PROT_MQTT; + | DEF_PROT_UPDATE | DEF_PROT_SYSTEM | DEF_PROT_API | DEF_PROT_MQTT | DEF_PROT_HISTORY; mCfg.sys.darkMode = false; mCfg.sys.schedReboot = false; // restore temp settings @@ -546,7 +547,7 @@ class settings { if(mCfg.sys.protectionMask == 0) mCfg.sys.protectionMask = DEF_PROT_INDEX | DEF_PROT_LIVE | DEF_PROT_SERIAL | DEF_PROT_SETUP - | DEF_PROT_UPDATE | DEF_PROT_SYSTEM | DEF_PROT_API | DEF_PROT_MQTT; + | DEF_PROT_UPDATE | DEF_PROT_SYSTEM | DEF_PROT_API | DEF_PROT_MQTT | DEF_PROT_HISTORY; } } diff --git a/src/plugins/Display/Display.h b/src/plugins/Display/Display.h index 0cfbd710..2f0a94bf 100644 --- a/src/plugins/Display/Display.h +++ b/src/plugins/Display/Display.h @@ -99,7 +99,8 @@ class Display { uint8_t nrprod = 0; uint8_t nrsleep = 0; - int8_t minQAllInv = 4; + uint8_t nrAvailable = 0; + int8_t minQAllInv = 4; Inverter<> *iv; record_t<> *rec; @@ -114,6 +115,8 @@ class Display { nrprod++; else nrsleep++; + if (iv->isAvailable()) + nrAvailable++; rec = iv->getRecordStruct(RealTimeRunData_Debug); @@ -141,6 +144,7 @@ class Display { // prepare display data mDisplayData.nrProducing = nrprod; mDisplayData.nrSleeping = nrsleep; + mDisplayData.nrAvailable = nrAvailable; mDisplayData.totalPower = totalPower; mDisplayData.totalYieldDay = totalYieldDay; mDisplayData.totalYieldTotal = totalYieldTotal; @@ -165,7 +169,17 @@ class Display { else mDisplayData.utcTs = 0; - if (mMono ) { + const uint32_t sunriseTime = mApp->getSunrise(); + if (mDisplayData.utcTs == 0) + mDisplayData.sunIsShining = true; // Start with sunshine :-) + else { + mDisplayData.sunIsShining = false; + // new sunrise is calculated after sunset + user-offset + if (utc > sunriseTime) + mDisplayData.sunIsShining = true; + } + + if (mMono) { mMono->disp(); } #if defined(ESP32) && !defined(ETHERNET) diff --git a/src/plugins/Display/Display_Mono.h b/src/plugins/Display/Display_Mono.h index 3e998b6d..f8c81914 100644 --- a/src/plugins/Display/Display_Mono.h +++ b/src/plugins/Display/Display_Mono.h @@ -4,6 +4,7 @@ //----------------------------------------------------------------------------- #pragma once +#include "config/config.h" #include #define DISP_DEFAULT_TIMEOUT 60 // in seconds #define DISP_FMT_TEXT_LEN 32 @@ -101,6 +102,13 @@ class DisplayMono { int8_t mod = (millis() / 10000) % ((range >> 1) << 2); mPixelshift = mScreenSaver == 1 ? ((mod < range) ? mod - (range >> 1) : -(mod - range - (range >> 1) + 1)) : 0; } + +#ifdef DISPLAY_CHART +#define DISP_WATT_ARR_LENGTH 128 // Number of WATT history values + float m_wattArr[DISP_WATT_ARR_LENGTH + 1]; // ring buffer for watt history + uint16_t m_wattListIdx; // index for next Element to write into WattArr + void drawPowerChart(); +#endif }; /* adapted 5x8 Font for low-res displays with symbols diff --git a/src/plugins/Display/Display_Mono_128X64.h b/src/plugins/Display/Display_Mono_128X64.h index afb581dd..88c72979 100644 --- a/src/plugins/Display/Display_Mono_128X64.h +++ b/src/plugins/Display/Display_Mono_128X64.h @@ -9,6 +9,12 @@ class DisplayMono128X64 : public DisplayMono { public: DisplayMono128X64() : DisplayMono() { +#ifdef DISPLAY_CHART + for (uint16_t i = 0; i < DISP_WATT_ARR_LENGTH; i++) + m_wattArr[i] = 0.0; + m_wattListIdx = 0; + mDrawChart = false; +#endif mExtra = 0; } @@ -41,7 +47,6 @@ class DisplayMono128X64 : public DisplayMono { } void disp(void) { - uint8_t pos, sun_pos, moon_pos; mDisplay->clearBuffer(); @@ -61,109 +66,149 @@ class DisplayMono128X64 : public DisplayMono { // calculate current pixelshift for pixelshift screensaver calcPixelShift(pixelShiftRange); - // print total power - if (mDisplayData->nrProducing > 0) { - if (mDisplayData->totalPower > 9999.0) - snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%.2f kW", (mDisplayData->totalPower / 1000.0)); - else - snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%.0f W", mDisplayData->totalPower); - - printText(mFmtText, l_TotalPower, 0xff); - } else { - printText("offline", l_TotalPower, 0xff); +#ifdef DISPLAY_CHART + static uint32_t dataUpdateTime = mDisplayData->utcTs + 60; // update chart every minute + if (mDisplayData->utcTs >= dataUpdateTime) + { + dataUpdateTime = mDisplayData->utcTs + 60; // next minute + m_wattArr[m_wattListIdx] = mDisplayData->totalPower; + m_wattListIdx = (m_wattListIdx + 1) % (DISP_WATT_ARR_LENGTH); } - // print Date and time - if (0 != mDisplayData->utcTs) - printText(ah::getDateTimeStrShort(gTimezone.toLocal(mDisplayData->utcTs)).c_str(), l_Time, 0xff); + if (mDrawChart && mDisplayData->sunIsShining && (mDisplayData->nrAvailable > 0)) + { + // print total power + if (mDisplayData->nrProducing > 0) { + if (mDisplayData->totalPower > 9999.0) + snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%.2f kW", (mDisplayData->totalPower / 1000.0)); + else + snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%.0f W", mDisplayData->totalPower); + printText(mFmtText, l_Time, 10); + } else { + printText("offline", l_Time, 0xff); + } + + snprintf(mFmtText, DISP_FMT_TEXT_LEN, "today: %4.0f Wh", mDisplayData->totalYieldDay); + printText(mFmtText, l_Status, 10); + + drawPowerChart(); - // dynamic status bar, alternatively: - // print ip address - if (!(mExtra % 5) && (mDisplayData->ipAddress)) { - snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%s", (mDisplayData->ipAddress).toString().c_str()); - printText(mFmtText, l_Status, 0xff); } - // print status of inverters - else { - sun_pos = -1; - moon_pos = -1; - setLineFont(l_Status); - if (0 == mDisplayData->nrSleeping + mDisplayData->nrProducing) - snprintf(mFmtText, DISP_FMT_TEXT_LEN, "no inverter"); - else if (0 == mDisplayData->nrSleeping) { - snprintf(mFmtText, DISP_FMT_TEXT_LEN, " "); - sun_pos = 0; + else +#endif + { + // print total power + if (mDisplayData->nrProducing > 0) { + if (mDisplayData->totalPower > 9999.0) + snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%.2f kW", (mDisplayData->totalPower / 1000.0)); + else + snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%.0f W", mDisplayData->totalPower); + + printText(mFmtText, l_TotalPower, 0xff); + } else { + printText("offline", l_TotalPower, 0xff); + } + + // print Date and time + if (0 != mDisplayData->utcTs) + printText(ah::getDateTimeStrShort(gTimezone.toLocal(mDisplayData->utcTs)).c_str(), l_Time, 0xff); + + // dynamic status bar, alternatively: + // print ip address + if (!(mExtra % 5) && (mDisplayData->ipAddress)) { + snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%s", (mDisplayData->ipAddress).toString().c_str()); + printText(mFmtText, l_Status, 0xff); + } + // print status of inverters + else { + uint8_t pos, sun_pos, moon_pos; + sun_pos = -1; + moon_pos = -1; + setLineFont(l_Status); + if (0 == mDisplayData->nrSleeping + mDisplayData->nrProducing) + snprintf(mFmtText, DISP_FMT_TEXT_LEN, "no inverter"); + else if (0 == mDisplayData->nrSleeping) { + snprintf(mFmtText, DISP_FMT_TEXT_LEN, " "); + sun_pos = 0; } else if (0 == mDisplayData->nrProducing) { - snprintf(mFmtText, DISP_FMT_TEXT_LEN, " "); - moon_pos = 0; + snprintf(mFmtText, DISP_FMT_TEXT_LEN, " "); + moon_pos = 0; } else { - snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%2d", mDisplayData->nrProducing); - sun_pos = mDisplay->getStrWidth(mFmtText) + 1; - snprintf(mFmtText+2, DISP_FMT_TEXT_LEN, " %2d", mDisplayData->nrSleeping); - moon_pos = mDisplay->getStrWidth(mFmtText) + 1; - snprintf(mFmtText+7, DISP_FMT_TEXT_LEN, " "); + snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%2d", mDisplayData->nrProducing); + sun_pos = mDisplay->getStrWidth(mFmtText) + 1; + snprintf(mFmtText + 2, DISP_FMT_TEXT_LEN, " %2d", mDisplayData->nrSleeping); + moon_pos = mDisplay->getStrWidth(mFmtText) + 1; + snprintf(mFmtText + 7, DISP_FMT_TEXT_LEN, " "); + } + printText(mFmtText, l_Status, 0xff); + + pos = (mDispWidth - mDisplay->getStrWidth(mFmtText)) / 2; + mDisplay->setFont(u8g2_font_ncenB08_symbols8_ahoy); + if (sun_pos != -1) + mDisplay->drawStr(pos + sun_pos + mPixelshift, mLineYOffsets[l_Status], "G"); // sun symbol + if (moon_pos != -1) + mDisplay->drawStr(pos + moon_pos + mPixelshift, mLineYOffsets[l_Status], "H"); // moon symbol } - printText(mFmtText, l_Status, 0xff); - - pos = (mDispWidth - mDisplay->getStrWidth(mFmtText)) / 2; - mDisplay->setFont(u8g2_font_ncenB08_symbols8_ahoy); - if (sun_pos!=-1) - mDisplay->drawStr(pos + sun_pos + mPixelshift, mLineYOffsets[l_Status], "G"); // sun symbol - if (moon_pos!=-1) - mDisplay->drawStr(pos + moon_pos + mPixelshift, mLineYOffsets[l_Status], "H"); // moon symbol - } - // print yields - mDisplay->setFont(u8g2_font_ncenB10_symbols10_ahoy); - mDisplay->drawStr(16 + mPixelshift, mLineYOffsets[l_YieldDay], "I"); // day symbol - mDisplay->drawStr(16 + mPixelshift, mLineYOffsets[l_YieldTotal], "D"); // total symbol + // print yields + mDisplay->setFont(u8g2_font_ncenB10_symbols10_ahoy); + mDisplay->drawStr(16 + mPixelshift, mLineYOffsets[l_YieldDay], "I"); // day symbol + mDisplay->drawStr(16 + mPixelshift, mLineYOffsets[l_YieldTotal], "D"); // total symbol - if (mDisplayData->totalYieldDay > 9999.0) - snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%.2f kWh", mDisplayData->totalYieldDay / 1000.0); - else - snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%.0f Wh", mDisplayData->totalYieldDay); - printText(mFmtText, l_YieldDay, 0xff); + if (mDisplayData->totalYieldDay > 9999.0) + snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%.2f kWh", mDisplayData->totalYieldDay / 1000.0); + else + snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%.0f Wh", mDisplayData->totalYieldDay); + printText(mFmtText, l_YieldDay, 0xff); - if (mDisplayData->totalYieldTotal > 9999.0) - snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%.2f MWh", mDisplayData->totalYieldTotal / 1000.0); - else - snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%.0f kWh", mDisplayData->totalYieldTotal); - printText(mFmtText, l_YieldTotal, 0xff); + if (mDisplayData->totalYieldTotal > 9999.0) + snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%.2f MWh", mDisplayData->totalYieldTotal / 1000.0); + else + snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%.0f kWh", mDisplayData->totalYieldTotal); + printText(mFmtText, l_YieldTotal, 0xff); - // draw dynamic RSSI bars - int xoffs; - if (mScreenSaver == 1) // shrink screenwidth for pixelshift screensaver - xoffs = pixelShiftRange/2; - else - xoffs = 0; - int rssi_bar_height = 9; - for (int i = 0; i < 4; i++) { - int radio_rssi_threshold = -60 - i * 10; - int wifi_rssi_threshold = -60 - i * 10; - if (mDisplayData->RadioRSSI > radio_rssi_threshold) - mDisplay->drawBox(xoffs + mPixelshift, 8 + (rssi_bar_height + 1) * i, 4 - i, rssi_bar_height); - if (mDisplayData->WifiRSSI > wifi_rssi_threshold) - mDisplay->drawBox(mDispWidth - 4 - xoffs + mPixelshift + i, 8 + (rssi_bar_height + 1) * i, 4 - i, rssi_bar_height); - } - // draw dynamic antenna and WiFi symbols - mDisplay->setFont(u8g2_font_ncenB10_symbols10_ahoy); - char sym[]=" "; - sym[0] = mDisplayData->RadioSymbol?'A':'E'; // NRF - mDisplay->drawStr(xoffs + mPixelshift, mLineYOffsets[l_RSSI], sym); - - if (mDisplayData->MQTTSymbol) - sym[0] = 'J'; // MQTT - else - sym[0] = mDisplayData->WifiSymbol?'B':'F'; // Wifi - mDisplay->drawStr(mDispWidth - mDisplay->getStrWidth(sym) - xoffs + mPixelshift, mLineYOffsets[l_RSSI], sym); - mDisplay->sendBuffer(); + // draw dynamic RSSI bars + int xoffs; + if (mScreenSaver == 1) // shrink screenwidth for pixelshift screensaver + xoffs = pixelShiftRange / 2; + else + xoffs = 0; + int rssi_bar_height = 9; + for (int i = 0; i < 4; i++) { + int radio_rssi_threshold = -60 - i * 10; + int wifi_rssi_threshold = -60 - i * 10; + if (mDisplayData->RadioRSSI > radio_rssi_threshold) + mDisplay->drawBox(xoffs + mPixelshift, 8 + (rssi_bar_height + 1) * i, 4 - i, rssi_bar_height); + if (mDisplayData->WifiRSSI > wifi_rssi_threshold) + mDisplay->drawBox(mDispWidth - 4 - xoffs + mPixelshift + i, 8 + (rssi_bar_height + 1) * i, 4 - i, rssi_bar_height); + } + // draw dynamic antenna and WiFi symbols + mDisplay->setFont(u8g2_font_ncenB10_symbols10_ahoy); + char sym[] = " "; + sym[0] = mDisplayData->RadioSymbol ? 'A' : 'E'; // NRF + mDisplay->drawStr(xoffs + mPixelshift, mLineYOffsets[l_RSSI], sym); + if (mDisplayData->MQTTSymbol) + sym[0] = 'J'; // MQTT + else + sym[0] = mDisplayData->WifiSymbol ? 'B' : 'F'; // Wifi + mDisplay->drawStr(mDispWidth - mDisplay->getStrWidth(sym) - xoffs + mPixelshift, mLineYOffsets[l_RSSI], sym); + mDisplay->sendBuffer(); + } mDisplay->sendBuffer(); - + mExtra++; + +#ifdef DISPLAY_CHART + static uint32_t switchDisplayTime = mDisplayData->utcTs + 20; + if (mDisplayData->utcTs >= switchDisplayTime) { + switchDisplayTime = mDisplayData->utcTs + 20; + mDrawChart = !mDrawChart; + } +#endif } private: @@ -226,4 +271,46 @@ class DisplayMono128X64 : public DisplayMono { dispX += mPixelshift; mDisplay->drawStr(dispX, mLineYOffsets[line], text); } + +#ifdef DISPLAY_CHART + bool mDrawChart ; + + void drawPowerChart() { + const int hight = 40; // chart hight + + // Clear area + // mDisplay->draw_rectangle(0, 63 - hight, DISP_WATT_ARR_LENGTH, 63, OLED::SOLID, OLED::BLACK); + mDisplay->setDrawColor(0); + mDisplay->drawBox(0, 63 - hight, DISP_WATT_ARR_LENGTH, hight); + mDisplay->setDrawColor(1); + + // Get max value for scaling + float maxValue = 0.0; + for (int i = 0; i < DISP_WATT_ARR_LENGTH; i++) { + float fValue = m_wattArr[i]; + if (fValue > maxValue) + maxValue = fValue; + } + // calc divider to fit into chart hight + int divider = round(maxValue / (float)hight); + if (divider < 1) + divider = 1; + + // draw chart bars + // Start display of data right behind last written data + uint16_t idx = m_wattListIdx; + for (uint16_t i = 0; i < DISP_WATT_ARR_LENGTH; i++) { + float fValue = m_wattArr[idx]; + int iValue = roundf(fValue); + iValue /= divider; + if (iValue > hight) + iValue = hight; + // mDisplay->draw_line(i, 63 - iValue, i, 63); + // mDisplay->drawVLine(i, 63 - iValue, iValue); + if (iValue>0) + mDisplay->drawLine(i, 63 - iValue, i, 63); + idx = (idx + 1) % (DISP_WATT_ARR_LENGTH); + } + } +#endif }; diff --git a/src/plugins/Display/Display_data.h b/src/plugins/Display/Display_data.h index a400377d..1ccc5f7d 100644 --- a/src/plugins/Display/Display_data.h +++ b/src/plugins/Display/Display_data.h @@ -11,12 +11,14 @@ struct DisplayData { uint32_t utcTs=0; // indicate absolute timestamp (utc unix time). 0 = time is not synchonized uint8_t nrProducing=0; // indicate number of producing inverters uint8_t nrSleeping=0; // indicate number of sleeping inverters + uint8_t nrAvailable=0; // number of available (comunicating) inverters bool WifiSymbol = false; // indicate if WiFi is connected bool RadioSymbol = false; // indicate if radio module is connecting and working bool MQTTSymbol = false; // indicate if MQTT is connected int8_t WifiRSSI=SCHAR_MIN; // indicate RSSI value for WiFi int8_t RadioRSSI=SCHAR_MIN; // indicate RSSI value for radio IPAddress ipAddress; // indicate ip adress of ahoy + bool sunIsShining; // indicate if time is between sunrise and sunset }; #endif /*__DISPLAY_DATA__*/ diff --git a/src/plugins/history.cpp b/src/plugins/history.cpp new file mode 100644 index 00000000..451d3e1e --- /dev/null +++ b/src/plugins/history.cpp @@ -0,0 +1,94 @@ + +#include "plugins/history.h" + +#include "appInterface.h" +#include "config/config.h" +#include "utils/dbg.h" + +void TotalPowerHistory::setup(IApp *app, HmSystemType *sys, settings_t *config) { + mApp = app; + mSys = sys; + mConfig = config; + mRefreshCycle = mConfig->inst.sendInterval; + mMaximumDay = 0; + + // Debug + //for (uint16_t i = 0; i < HISTORY_DATA_ARR_LENGTH *1.5; i++) { + // addValue(i); + //} +} + +void TotalPowerHistory::tickerSecond() { + ++mLoopCnt; + if ((mLoopCnt % mRefreshCycle) == 0) { + //DPRINTLN(DBG_DEBUG,F("TotalPowerHistory::tickerSecond > refreshCycle" + String(mRefreshCycle) + "|" + String(mLoopCnt) + "|" + String(mRefreshCycle % mLoopCnt)); + mLoopCnt = 0; + float totalPower = 0; + float totalPowerDay = 0; + Inverter<> *iv; + record_t<> *rec; + for (uint8_t i = 0; i < mSys->getNumInverters(); i++) { + iv = mSys->getInverterByPos(i); + rec = iv->getRecordStruct(RealTimeRunData_Debug); + if (iv == NULL) + continue; + totalPower += iv->getChannelFieldValue(CH0, FLD_PAC, rec); + totalPowerDay += iv->getChannelFieldValue(CH0, FLD_MP, rec); + } + if (totalPower > 0) { + uint16_t iTotalPower = roundf(totalPower); + DPRINTLN(DBG_DEBUG, F("[TotalPowerHistory]: addValue(iTotalPower)=") + String(iTotalPower)); + addValue(iTotalPower); + } + if (totalPowerDay > 0) { + mMaximumDay = roundf(totalPowerDay); + } + } +} + +void YieldDayHistory::setup(IApp *app, HmSystemType *sys, settings_t *config) { + mApp = app; + mSys = sys; + mConfig = config; + mRefreshCycle = 60; // every minute + mDayStored = false; +}; + +void YieldDayHistory::tickerSecond() { + ++mLoopCnt; + if ((mLoopCnt % mRefreshCycle) == 0) { + mLoopCnt = 0; + // check for sunset. if so store yield of day once + uint32_t sunsetTime = mApp->getSunset(); + uint32_t sunriseTime = mApp->getSunrise(); + uint32_t currentTime = mApp->getTimestamp(); + DPRINTLN(DBG_DEBUG,F("[YieldDayHistory] current | rise | set -> ") + String(currentTime) + " | " + String(sunriseTime) + " | " + String(sunsetTime)); + + if (currentTime > sunsetTime) { + if (!mDayStored) { + DPRINTLN(DBG_DEBUG,F("currentTime > sunsetTime ") + String(currentTime) + " > " + String(sunsetTime)); + float totalYieldDay = -0.1; + Inverter<> *iv; + record_t<> *rec; + for (uint8_t i = 0; i < mSys->getNumInverters(); i++) { + iv = mSys->getInverterByPos(i); + rec = iv->getRecordStruct(RealTimeRunData_Debug); + if (iv == NULL) + continue; + totalYieldDay += iv->getChannelFieldValue(CH0, FLD_YD, rec); + } + if (totalYieldDay > 0) { + uint16_t iTotalYieldDay = roundf(totalYieldDay); + DPRINTLN(DBG_DEBUG,F("addValue(iTotalYieldDay)=") + String(iTotalYieldDay)); + addValue(iTotalYieldDay); + mDayStored = true; + } + } + } else { + if (currentTime > sunriseTime) { + DPRINTLN(DBG_DEBUG,F("currentTime > sunriseTime ") + String(currentTime) + " > " + String(sunriseTime)); + mDayStored = false; + } + } + } +} \ No newline at end of file diff --git a/src/plugins/history.h b/src/plugins/history.h new file mode 100644 index 00000000..f6485b17 --- /dev/null +++ b/src/plugins/history.h @@ -0,0 +1,86 @@ +#ifndef __HISTORY_DATA_H__ +#define __HISTORY_DATA_H__ + +#include "utils/helper.h" +#include "defines.h" +#include "hm/hmSystem.h" + +typedef HmSystem HmSystemType; +class IApp; + +#define HISTORY_DATA_ARR_LENGTH 256 + +class HistoryData { + public: + HistoryData() { + for (int i = 0; i < HISTORY_DATA_ARR_LENGTH; i++) + m_dataArr[i] = 0; + m_listIdx = 0; + m_dispIdx = 0; + m_wrapped = false; + }; + void addValue(uint16_t value) + { + if (m_wrapped) // after 1st time array wrap we have to increas the display index + m_dispIdx = (m_listIdx + 1) % (HISTORY_DATA_ARR_LENGTH); + m_dataArr[m_listIdx] = value; + m_listIdx = (m_listIdx + 1) % (HISTORY_DATA_ARR_LENGTH); + if (m_listIdx == 0) + m_wrapped = true; + }; + + uint16_t valueAt(int i){ + uint16_t idx = m_dispIdx + i; + idx = idx % HISTORY_DATA_ARR_LENGTH; + uint16_t value = m_dataArr[idx]; + return value; + }; + + private: + uint16_t m_dataArr[HISTORY_DATA_ARR_LENGTH + 1]; // ring buffer for watt history + uint16_t m_listIdx; // index for next Element to write into WattArr + uint16_t m_dispIdx; // index for 1st Element to display from WattArr + bool m_wrapped; +}; + +class TotalPowerHistory : public HistoryData { + public: + TotalPowerHistory() : HistoryData() { + mLoopCnt = 0; + }; + + void setup(IApp *app, HmSystemType *sys, settings_t *config); + void tickerSecond(); + uint16_t getMaximumDay() { return mMaximumDay; } + + private: + IApp *mApp; + HmSystemType *mSys; + settings *mSettings; + settings_t *mConfig; + uint16_t mRefreshCycle; + uint16_t mLoopCnt; + + uint16_t mMaximumDay; +}; + +class YieldDayHistory : public HistoryData { + public: + YieldDayHistory() : HistoryData(){ + mLoopCnt = 0; + }; + + void setup(IApp *app, HmSystemType *sys, settings_t *config); + void tickerSecond(); + + private: + IApp *mApp; + HmSystemType *mSys; + settings *mSettings; + settings_t *mConfig; + uint16_t mRefreshCycle; + uint16_t mLoopCnt; + bool mDayStored; +}; + +#endif \ No newline at end of file diff --git a/src/web/RestApi.h b/src/web/RestApi.h index a74f4f14..45c2670d 100644 --- a/src/web/RestApi.h +++ b/src/web/RestApi.h @@ -22,6 +22,8 @@ #include "ESPAsyncWebServer.h" #endif +#include "plugins/history.h" + #if defined(F) && defined(ESP32) #undef F #define F(sl) (sl) @@ -50,8 +52,10 @@ class RestApi { mRadioCmt = (CmtRadio<>*)mApp->getRadioObj(false); #endif mConfig = config; - mSrv->on("/api", HTTP_POST, std::bind(&RestApi::onApiPost, this, std::placeholders::_1)).onBody( - std::bind(&RestApi::onApiPostBody, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5)); + mSrv->on("/api/insertYieldDayHistory", HTTP_POST, std::bind(&RestApi::onApiPost, this, std::placeholders::_1), + std::bind(&RestApi::onApiPostYieldDHistory, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5, std::placeholders::_6)); + mSrv->on("/api", HTTP_POST, std::bind(&RestApi::onApiPost, this, std::placeholders::_1)) + .onBody(std::bind(&RestApi::onApiPostBody, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5)); mSrv->on("/api", HTTP_GET, std::bind(&RestApi::onApi, this, std::placeholders::_1)); mSrv->on("/get_setup", HTTP_GET, std::bind(&RestApi::onDwnldSetup, this, std::placeholders::_1)); @@ -102,6 +106,8 @@ class RestApi { else if(path == "setup/networks") getNetworks(root); #endif /* !defined(ETHERNET) */ else if(path == "live") getLive(request,root); + else if (path == "powerHistory") getPowerHistory(request, root); + else if (path == "yieldDayHistory") getYieldDayHistory(request, root); else { if(path.substring(0, 12) == "inverter/id/") getInverter(root, request->url().substring(17).toInt()); @@ -136,6 +142,83 @@ class RestApi { #endif } + void onApiPostYieldDHistory(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, size_t final) + { + uint32_t total = request->contentLength(); + DPRINTLN(DBG_DEBUG, "[onApiPostYieldDHistory ] " + filename + " index:" + index + " len:" + len + " total:" + total + " final:" + final); + + if (0 == index) { + if (NULL != mTmpBuf) + delete[] mTmpBuf; + mTmpBuf = new uint8_t[total + 1]; + mTmpSize = total; + } + if (mTmpSize >= (len + index)) + memcpy(&mTmpBuf[index], data, len); + + if (!final) + return; // not last frame - nothing to do + + mTmpSize = len + index ; // correct the total size + mTmpBuf[mTmpSize] = 0; + +#ifndef ESP32 + DynamicJsonDocument json(ESP.getMaxFreeBlockSize() - 512); // need some memory on heap +#else + DynamicJsonDocument json(12000)); // does this work? I have no ESP32 :-( +#endif + DeserializationError err = deserializeJson(json, (const char *)mTmpBuf, mTmpSize); + json.shrinkToFit(); + JsonObject obj = json.as(); + + + // Debugging + // mTmpBuf[mTmpSize] = 0; + // DPRINTLN(DBG_DEBUG, (const char *)mTmpBuf); + + if (!err && obj) + { + // insert data into yieldDayHistory object + HistoryData *p; + if (obj["maximumDay"]>0) // this is power history data + p = mApp->getTotalPowerHistoryPtr(); + else + p = mApp->getYieldDayHistoryPtr(); + + size_t cnt = obj[F("value")].size(); + DPRINTLN(DBG_DEBUG, "ArraySize: " + String(cnt)); + + for (uint16_t i = 0; i < cnt; i++) { + uint16_t val = obj[F("value")][i]; + p->addValue(val); + // DPRINT(DBG_VERBOSE, "value " + String(i) + ": " + String(val) + ", "); + } + } + else + { + switch (err.code()) { + case DeserializationError::Ok: + break; + case DeserializationError::IncompleteInput: + DPRINTLN(DBG_DEBUG, F("Incomplete input")); + break; + case DeserializationError::InvalidInput: + DPRINTLN(DBG_DEBUG, F("Invalid input")); + break; + case DeserializationError::NoMemory: + DPRINTLN(DBG_DEBUG, F("Not enough memory ") + String(json.capacity()) + " bytes"); + break; + default: + DPRINTLN(DBG_DEBUG, F("Deserialization failed")); + break; + } + } + + request->send(204); // Success with no page load + delete[] mTmpBuf; + mTmpBuf = NULL; + } + void onApiPostBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) { DPRINTLN(DBG_VERBOSE, "onApiPostBody"); @@ -196,6 +279,8 @@ class RestApi { ep[F("setup")] = url + F("setup"); ep[F("system")] = url + F("system"); ep[F("live")] = url + F("live"); + ep[F("powerHistory")] = url + F("powerHistory"); + ep[F("yieldDayHistory")] = url + F("yieldDayHistory"); } @@ -769,6 +854,38 @@ class RestApi { } } + void getPowerHistory(AsyncWebServerRequest *request, JsonObject obj) { + getGeneric(request, obj.createNestedObject(F("generic"))); + obj[F("refresh")] = mConfig->inst.sendInterval; + obj[F("datapoints")] = HISTORY_DATA_ARR_LENGTH; + uint16_t maximum = 0; + TotalPowerHistory *p = mApp->getTotalPowerHistoryPtr(); + for (uint16_t fld = 0; fld < HISTORY_DATA_ARR_LENGTH; fld++) { + uint16_t value = p->valueAt(fld); + obj[F("value")][fld] = value; + if (value > maximum) + maximum = value; + } + obj[F("maximum")] = maximum; + obj[F("maximumDay")] = p->getMaximumDay(); + } + + void getYieldDayHistory(AsyncWebServerRequest *request, JsonObject obj) { + getGeneric(request, obj.createNestedObject(F("generic"))); + obj[F("refresh")] = 86400; // 1 day + obj[F("datapoints")] = HISTORY_DATA_ARR_LENGTH; + uint16_t maximum = 0; + YieldDayHistory *p = mApp->getYieldDayHistoryPtr(); + for (uint16_t fld = 0; fld < HISTORY_DATA_ARR_LENGTH; fld++) { + uint16_t value = p->valueAt(fld); + obj[F("value")][fld] = value; + if (value > maximum) + maximum = value; + } + obj[F("maximum")] = maximum; + } + + bool setCtrl(JsonObject jsonIn, JsonObject jsonOut) { Inverter<> *iv = mSys->getInverterByPos(jsonIn[F("id")]); bool accepted = true; diff --git a/src/web/html/api.js b/src/web/html/api.js index b059cc69..3b8b266d 100644 --- a/src/web/html/api.js +++ b/src/web/html/api.js @@ -84,7 +84,7 @@ function topnav() { } function parseNav(obj) { - for(i = 0; i < 11; i++) { + for(i = 0; i < 12; i++) { if(i == 2) continue; var l = document.getElementById("nav"+i); diff --git a/src/web/html/history.html b/src/web/html/history.html new file mode 100644 index 00000000..5b6c1fb5 --- /dev/null +++ b/src/web/html/history.html @@ -0,0 +1,135 @@ + + + + + History + {#HTML_HEADER} + + + + + + + {#HTML_NAV} +
+
+

Total Power history

+
+
+

+ Maximum day: W. Last value: W.
+ Maximum graphics: W. Updated every seconds

+
+

Yield per day history

+
+
+

+ Maximum value: Wh
+ Updated every seconds

+
+ +

Insert data into Yield per day history

+
+ Insert data (*.json) i.e. from a saved "/api/yieldDayHistory" call +
+ + +
+
+

+
+
+ {#HTML_FOOTER} + + + + + + \ No newline at end of file diff --git a/src/web/html/includes/nav.html b/src/web/html/includes/nav.html index 91de5047..2f13c2e8 100644 --- a/src/web/html/includes/nav.html +++ b/src/web/html/includes/nav.html @@ -7,6 +7,7 @@
Live + History Webserial Settings diff --git a/src/web/html/style.css b/src/web/html/style.css index b31ae7c9..14a1b002 100644 --- a/src/web/html/style.css +++ b/src/web/html/style.css @@ -33,6 +33,26 @@ textarea { color: var(--fg2); } +svg rect {fill: #0000AA;} +svg.chart { + background: #f2f2f2; + border: 2px solid gray; + padding: 1px; +} + +div.chartDivContainer { + padding: 1px; + margin: 1px; +} +div.chartdivContainer span { + color: var(--fg2); +} +div.chartDiv { + padding: 0px; + margin: 0px; +} + + .topnav { background-color: var(--nav-bg); position: fixed; diff --git a/src/web/web.h b/src/web/web.h index 1e87547b..38c927f7 100644 --- a/src/web/web.h +++ b/src/web/web.h @@ -36,6 +36,7 @@ #include "html/h/update_html.h" #include "html/h/visualization_html.h" #include "html/h/about_html.h" +#include "html/h/history_html.h" #define WEB_SERIAL_BUF_SIZE 2048 @@ -80,6 +81,7 @@ class Web { mWeb.on("/save", HTTP_POST, std::bind(&Web::showSave, this, std::placeholders::_1)); mWeb.on("/live", HTTP_ANY, std::bind(&Web::onLive, this, std::placeholders::_1)); + mWeb.on("/history", HTTP_ANY, std::bind(&Web::onHistory, this, std::placeholders::_1)); #ifdef ENABLE_PROMETHEUS_EP mWeb.on("/metrics", HTTP_ANY, std::bind(&Web::showMetrics, this, std::placeholders::_1)); @@ -249,6 +251,8 @@ class Web { request->redirect(F("/index")); else if ((mConfig->sys.protectionMask & PROT_MASK_LIVE) != PROT_MASK_LIVE) request->redirect(F("/live")); + else if ((mConfig->sys.protectionMask & PROT_MASK_HISTORY) != PROT_MASK_HISTORY) + request->redirect(F("/history")); else if ((mConfig->sys.protectionMask & PROT_MASK_SERIAL) != PROT_MASK_SERIAL) request->redirect(F("/serial")); else if ((mConfig->sys.protectionMask & PROT_MASK_SYSTEM) != PROT_MASK_SYSTEM) @@ -264,7 +268,7 @@ class Web { } } - void getPage(AsyncWebServerRequest *request, uint8_t mask, const uint8_t *zippedHtml, uint32_t len) { + void getPage(AsyncWebServerRequest *request, uint16_t mask, const uint8_t *zippedHtml, uint32_t len) { if (CHECK_MASK(mConfig->sys.protectionMask, mask)) checkProtection(request); @@ -594,6 +598,10 @@ class Web { getPage(request, PROT_MASK_LIVE, visualization_html, visualization_html_len); } + void onHistory(AsyncWebServerRequest *request) { + getPage(request, PROT_MASK_HISTORY, history_html, history_html_len); + } + void onAbout(AsyncWebServerRequest *request) { AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html; charset=UTF-8"), about_html, about_html_len); response->addHeader(F("Content-Encoding"), "gzip"); From 3aa4751689a68a99044fac9686c66a89172ed7f3 Mon Sep 17 00:00:00 2001 From: lumapu Date: Mon, 8 Jan 2024 21:54:27 +0100 Subject: [PATCH 054/179] 0.8.49 * fix send total values if inverter state is different from `OFF` #1331 * fix german language issues #1335 --- src/CHANGES.md | 4 ++++ src/defines.h | 2 +- src/publisher/pubMqttIvData.h | 2 +- src/web/html/system.html | 6 +++--- src/web/lang.json | 6 +++--- 5 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/CHANGES.md b/src/CHANGES.md index 5073a2c9..a11a4e2b 100644 --- a/src/CHANGES.md +++ b/src/CHANGES.md @@ -1,5 +1,9 @@ # Development Changes +## 0.8.49 - 2024-01-08 +* fix send total values if inverter state is different from `OFF` #1331 +* fix german language issues #1335 + ## 0.8.48 - 2024-01-07 * merge PR: pin selection for ESP-32 S2 #1334 * merge PR: enhancement: power graph display option #1330 diff --git a/src/defines.h b/src/defines.h index bd5cc7ff..b205297f 100644 --- a/src/defines.h +++ b/src/defines.h @@ -13,7 +13,7 @@ //------------------------------------- #define VERSION_MAJOR 0 #define VERSION_MINOR 8 -#define VERSION_PATCH 48 +#define VERSION_PATCH 49 //------------------------------------- typedef struct { diff --git a/src/publisher/pubMqttIvData.h b/src/publisher/pubMqttIvData.h index a7deadfb..0a59fdd6 100644 --- a/src/publisher/pubMqttIvData.h +++ b/src/publisher/pubMqttIvData.h @@ -141,7 +141,7 @@ class PubMqttIvData { // calculate total values for RealTimeRunData_Debug if (CH0 == rec->assign[mPos].ch) { - if(mIv->getStatus() > InverterStatus::STARTING) { + if(mIv->getStatus() != InverterStatus::OFF) { if(mIv->config->add2Total) { mTotalFound = true; switch (rec->assign[mPos].fieldId) { diff --git a/src/web/html/system.html b/src/web/html/system.html index 16c6343e..ab0b5289 100644 --- a/src/web/html/system.html +++ b/src/web/html/system.html @@ -47,8 +47,8 @@ function irqBadge(state) { switch(state) { case 0: return badge(false, "{#UNKNOWN}", "warning"); break; - case 1: return badge(true, "true"); break; - default: return badge(false, "false"); break; + case 1: return badge(true, "{#TRUE}"); break; + default: return badge(false, "{#FALSE}"); break; } } @@ -119,7 +119,7 @@ tr("{#SUNRISE}", new Date(obj.ts_sunrise * 1000).toLocaleString('de-DE')), tr("{#SUNSET}", new Date(obj.ts_sunset * 1000).toLocaleString('de-DE')), tr("{#COMMUNICATION_START}", new Date((obj.ts_sunrise + obj.ts_offsSr) * 1000).toLocaleString('de-DE')), - tr("{#COMMUNICATION_STTOP}", new Date((obj.ts_sunset + obj.ts_offsSs) * 1000).toLocaleString('de-DE')), + tr("{#COMMUNICATION_STOP}", new Date((obj.ts_sunset + obj.ts_offsSs) * 1000).toLocaleString('de-DE')), tr("{#NIGHT_BEHAVE}", badge(obj.disNightComm, ((obj.disNightComm) ? "{#NOT}" : "") + " {#COMMUNICATING}", "warning")) ]) ) diff --git a/src/web/lang.json b/src/web/lang.json index d6c76ffc..c7243728 100644 --- a/src/web/lang.json +++ b/src/web/lang.json @@ -876,7 +876,7 @@ { "token": "NIGHT_TIME", "en": "Night time, inverter polling disabled", - "de": "Nacht, Wechselrichterabfrage deaktivert" + "de": "Wechselrichterabfrage deaktivert (Nacht)" }, { "token": "PAUSED_AT", @@ -886,7 +886,7 @@ { "token": "START_AT", "en": "will start polling at", - "de": "wird starten um" + "de": "Abfrage startet am" }, { "token": "DISABLED", @@ -901,7 +901,7 @@ { "token": "AVAIL", "en": "available and is", - "de": "verfügbar und ist" + "de": "verfügbar und" }, { "token": "AVAIL_NO_DATA", From e88809c7955d286b564c4063aa5da89224e7b0c3 Mon Sep 17 00:00:00 2001 From: you69man Date: Mon, 8 Jan 2024 23:46:19 +0100 Subject: [PATCH 055/179] disable graph per default --- src/config/settings.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config/settings.h b/src/config/settings.h index 28739db3..2da326cc 100644 --- a/src/config/settings.h +++ b/src/config/settings.h @@ -463,7 +463,7 @@ class settings { mCfg.plugin.display.pwrSaveAtIvOffline = false; mCfg.plugin.display.contrast = 60; mCfg.plugin.display.screenSaver = 1; // default: 1 .. pixelshift for OLED for downward compatibility - mCfg.plugin.display.graph_ratio = 50; + mCfg.plugin.display.graph_ratio = 0; mCfg.plugin.display.graph_size = 2; mCfg.plugin.display.rot = 0; mCfg.plugin.display.disp_data = DEF_PIN_OFF; // SDA From 08dc6d21947d86fd762c9b26f8eb8a67bc66dbed Mon Sep 17 00:00:00 2001 From: you69man Date: Mon, 8 Jan 2024 23:51:56 +0100 Subject: [PATCH 056/179] pass full display config to Display_Mono classes for better maintainability --- src/plugins/Display/Display.h | 7 ++-- src/plugins/Display/Display_Mono.h | 46 +++++++++-------------- src/plugins/Display/Display_Mono_128X32.h | 14 +++---- src/plugins/Display/Display_Mono_128X64.h | 24 +++++------- src/plugins/Display/Display_Mono_64X48.h | 14 +++---- src/plugins/Display/Display_Mono_84X48.h | 17 ++++----- 6 files changed, 48 insertions(+), 74 deletions(-) diff --git a/src/plugins/Display/Display.h b/src/plugins/Display/Display.h index 6c8bb9cb..0ce49c33 100644 --- a/src/plugins/Display/Display.h +++ b/src/plugins/Display/Display.h @@ -50,12 +50,11 @@ class Display { mEpaper.init(mCfg->type, mCfg->disp_cs, mCfg->disp_dc, mCfg->disp_reset, mCfg->disp_busy, mCfg->disp_clk, mCfg->disp_data, mUtcTs, mDisplayData.version); break; #endif - default: mMono = NULL; break; } if(mMono) { - mMono->config(mCfg->pwrSaveAtIvOffline, mCfg->screenSaver, mCfg->contrast, mCfg->graph_ratio, mCfg->graph_size); - mMono->init(mCfg->type, mCfg->rot, mCfg->disp_cs, mCfg->disp_dc, 0xff, mCfg->disp_clk, mCfg->disp_data, &mDisplayData); + mMono->config(mCfg); + mMono->init(&mDisplayData); } // setup PIR pin for motion sensor @@ -78,7 +77,7 @@ class Display { bool request_refresh = false; if (mMono != NULL) - request_refresh = mMono->loop(mCfg->contrast, motionSensorActive()); + request_refresh = mMono->loop(motionSensorActive()); if (mNewPayload || (((++mLoopCnt) % 5) == 0) || request_refresh) { DataScreen(); diff --git a/src/plugins/Display/Display_Mono.h b/src/plugins/Display/Display_Mono.h index c7235815..785f1334 100644 --- a/src/plugins/Display/Display_Mono.h +++ b/src/plugins/Display/Display_Mono.h @@ -25,23 +25,22 @@ class DisplayMono { public: DisplayMono() {}; - virtual void init(uint8_t type, uint8_t rot, uint8_t cs, uint8_t dc, uint8_t reset, uint8_t clock, uint8_t data, DisplayData *displayData) = 0; - virtual void config(bool enPowerSave, uint8_t screenSaver, uint8_t lum, uint8_t graph_ratio, uint8_t graph_size) = 0; + virtual void init(DisplayData *displayData) = 0; + virtual void config(display_t *cfg) = 0; virtual void disp(void) = 0; // Common loop function, manages display on/off functions for powersave and screensaver with motionsensor // can be overridden by subclasses - virtual bool loop(uint8_t lum, bool motion) { + virtual bool loop(bool motion) { - bool dispConditions = (!mEnPowerSave || (mDisplayData->nrProducing > 0)) && - ((mScreenSaver != 2) || motion); // screensaver 2 .. motionsensor + bool dispConditions = (!mCfg->pwrSaveAtIvOffline || (mDisplayData->nrProducing > 0)) && + ((mCfg->screenSaver != 2) || motion); // screensaver 2 .. motionsensor if (mDisplayActive) { if (!dispConditions) { if (mDisplayTime.isTimeout()) { // switch display off after timeout mDisplayActive = false; mDisplay->setPowerSave(true); - DBGPRINTLN("**** Display off ****"); } } else @@ -52,16 +51,15 @@ class DisplayMono { mDisplayActive = true; mDisplayTime.reStartTimeMonitor(); // switch display on mDisplay->setPowerSave(false); - DBGPRINTLN("**** Display on ****"); } } - if(mLuminance != lum) { - mLuminance = lum; + if(mLuminance != mCfg->contrast) { + mLuminance = mCfg->contrast; mDisplay->setContrast(mLuminance); } - return(monoMaintainDispSwitchState()); + return(monoMaintainDispSwitchState()); // return flag, if display content should be updated immediately } protected: @@ -71,7 +69,8 @@ class DisplayMono { }; protected: - U8G2* mDisplay; + display_t *mCfg; + U8G2 *mDisplay; DisplayData *mDisplayData; float *mPgData = nullptr; @@ -82,17 +81,10 @@ class DisplayMono { uint32_t mPgTimeOfDay = 0; uint8_t mPgLastPos = 0; - uint8_t mType; uint16_t mDispWidth; uint16_t mDispHeight; - - bool mEnPowerSave; - uint8_t mScreenSaver = 1; // 0 .. off; 1 .. pixelShift; 2 .. motionsensor uint8_t mLuminance; - uint8_t mGraphRatio; - uint8_t mGraphSize; - uint8_t mLoopCnt; uint8_t mLineXOffsets[5] = {}; uint8_t mLineYOffsets[5] = {}; @@ -105,9 +97,8 @@ class DisplayMono { char mFmtText[DISP_FMT_TEXT_LEN]; // Common initialization function to be called by subclasses - void monoInit(U8G2* display, uint8_t type, DisplayData *displayData) { + void monoInit(U8G2* display, DisplayData *displayData) { mDisplay = display; - mType = type; mDisplayData = displayData; mDisplay->begin(); mDisplay->setPowerSave(false); // always start with display on @@ -116,10 +107,10 @@ class DisplayMono { mDispWidth = mDisplay->getDisplayWidth(); mDispHeight = mDisplay->getDisplayHeight(); mDispSwitchTime.stopTimeMonitor(); - if (mGraphRatio == 100) // if graph ratio is 100% start in graph mode + if (mCfg->graph_ratio == 100) // if graph ratio is 100% start in graph mode mDispSwitchState = DispSwitchState::GRAPH; - else if (mGraphRatio != 0) - mDispSwitchTime.startTimeMonitor(150 * (100 - mGraphRatio)); // start display mode change only if ratio is neither 0 nor 100 + else if (mCfg->graph_ratio != 0) + mDispSwitchTime.startTimeMonitor(150 * (100 - mCfg->graph_ratio)); // start display mode change only if ratio is neither 0 nor 100 } bool monoMaintainDispSwitchState(void) { @@ -128,14 +119,14 @@ class DisplayMono { case DispSwitchState::TEXT: if (mDispSwitchTime.isTimeout()) { mDispSwitchState = DispSwitchState::GRAPH; - mDispSwitchTime.startTimeMonitor(150 * mGraphRatio); // mGraphRatio: 0-100 Gesamtperiode 15000 ms + mDispSwitchTime.startTimeMonitor(150 * mCfg->graph_ratio); // mGraphRatio: 0-100 Gesamtperiode 15000 ms change = true; } break; case DispSwitchState::GRAPH: if (mDispSwitchTime.isTimeout()) { mDispSwitchState = DispSwitchState::TEXT; - mDispSwitchTime.startTimeMonitor(150 * (100 - mGraphRatio)); + mDispSwitchTime.startTimeMonitor(150 * (100 - mCfg->graph_ratio)); change = true; } break; @@ -147,7 +138,6 @@ class DisplayMono { mPgWidth = width; mPgHeight = height; mPgData = new float[mPgWidth]; - //memset(mPgData, 0, mPgWidth); resetPowerGraph(); /* Inverter<> *iv; @@ -195,7 +185,7 @@ class DisplayMono { } } - uint8_t getPowerGraphXpos(uint8_t p) { // + uint8_t getPowerGraphXpos(uint8_t p) { if ((p <= mPgLastPos) && (mPgLastPos > 0)) return((p * (mPgWidth - 1)) / mPgLastPos); // scaling of x-axis else @@ -255,7 +245,7 @@ class DisplayMono { // pixelshift screensaver with wipe effect void calcPixelShift(int range) { int8_t mod = (millis() / 10000) % ((range >> 1) << 2); - mPixelshift = mScreenSaver == 1 ? ((mod < range) ? mod - (range >> 1) : -(mod - range - (range >> 1) + 1)) : 0; + mPixelshift = mCfg->screenSaver == 1 ? ((mod < range) ? mod - (range >> 1) : -(mod - range - (range >> 1) + 1)) : 0; } }; diff --git a/src/plugins/Display/Display_Mono_128X32.h b/src/plugins/Display/Display_Mono_128X32.h index 1b3927b3..6ab21a6b 100644 --- a/src/plugins/Display/Display_Mono_128X32.h +++ b/src/plugins/Display/Display_Mono_128X32.h @@ -12,17 +12,13 @@ class DisplayMono128X32 : public DisplayMono { mExtra = 0; } - void config(bool enPowerSave, uint8_t screenSaver, uint8_t lum, uint8_t graph_ratio, uint8_t graph_size) { - mEnPowerSave = enPowerSave; - mScreenSaver = screenSaver; - mLuminance = lum; - mGraphRatio = graph_ratio; - mGraphSize = graph_size; + void config(display_t *cfg) { + mCfg = cfg; } - void init(uint8_t type, uint8_t rotation, uint8_t cs, uint8_t dc, uint8_t reset, uint8_t clock, uint8_t data, DisplayData *displayData) { - u8g2_cb_t *rot = (u8g2_cb_t *)((rotation != 0x00) ? U8G2_R2 : U8G2_R0); - monoInit(new U8G2_SSD1306_128X32_UNIVISION_F_HW_I2C(rot, reset, clock, data), type, displayData); + void init(DisplayData *displayData) { + u8g2_cb_t *rot = (u8g2_cb_t *)((mCfg->rot != 0x00) ? U8G2_R2 : U8G2_R0); + monoInit(new U8G2_SSD1306_128X32_UNIVISION_F_HW_I2C(rot, 0xff, mCfg->disp_clk, mCfg->disp_data), displayData); calcLinePositions(); printText("Ahoy!", 0); printText("ahoydtu.de", 2); diff --git a/src/plugins/Display/Display_Mono_128X64.h b/src/plugins/Display/Display_Mono_128X64.h index c8338691..b4c1cfe4 100644 --- a/src/plugins/Display/Display_Mono_128X64.h +++ b/src/plugins/Display/Display_Mono_128X64.h @@ -12,31 +12,27 @@ class DisplayMono128X64 : public DisplayMono { mExtra = 0; } - void config(bool enPowerSave, uint8_t screenSaver, uint8_t lum, uint8_t graph_ratio, uint8_t graph_size) { - mEnPowerSave = enPowerSave; - mScreenSaver = screenSaver; - mLuminance = lum; - mGraphRatio = graph_ratio; - mGraphSize = graph_size; + void config(display_t *cfg) { + mCfg = cfg; } - void init(uint8_t type, uint8_t rotation, uint8_t cs, uint8_t dc, uint8_t reset, uint8_t clock, uint8_t data, DisplayData *displayData) { - u8g2_cb_t *rot = (u8g2_cb_t *)((rotation != 0x00) ? U8G2_R2 : U8G2_R0); - switch (type) { + void init(DisplayData *displayData) { + u8g2_cb_t *rot = (u8g2_cb_t *)(( mCfg->rot != 0x00) ? U8G2_R2 : U8G2_R0); + switch (mCfg->type) { case 1: - monoInit(new U8G2_SSD1306_128X64_NONAME_F_HW_I2C(rot, reset, clock, data), type, displayData); + monoInit(new U8G2_SSD1306_128X64_NONAME_F_HW_I2C(rot, 0xff, mCfg->disp_clk, mCfg->disp_data), displayData); break; case 2: - monoInit(new U8G2_SH1106_128X64_NONAME_F_HW_I2C(rot, reset, clock, data), type, displayData); + monoInit(new U8G2_SH1106_128X64_NONAME_F_HW_I2C(rot, 0xff, mCfg->disp_clk, mCfg->disp_data), displayData); break; case 6: default: - monoInit(new U8G2_SSD1309_128X64_NONAME0_F_HW_I2C(rot, reset, clock, data), type, displayData); + monoInit(new U8G2_SSD1309_128X64_NONAME0_F_HW_I2C(rot, 0xff, mCfg->disp_clk, mCfg->disp_data), displayData); break; } calcLinePositions(); - switch(mGraphSize) { // var opts2 = [[0, "Line 1 - 2"], [1, "Line 2 - 3"], [2, "Line 1 - 3"], [3, "Line 2 - 4"], [4, "Line 1 - 4"]]; + switch(mCfg->graph_size) { // var opts2 = [[0, "Line 1 - 2"], [1, "Line 2 - 3"], [2, "Line 1 - 3"], [3, "Line 2 - 4"], [4, "Line 1 - 4"]]; case 0: graph_first_line = 1; graph_last_line = 2; @@ -60,7 +56,7 @@ class DisplayMono128X64 : public DisplayMono { break; } - widthShrink = (mScreenSaver == 1) ? pixelShiftRange : 0; // shrink graphwidth for pixelshift screensaver + widthShrink = (mCfg->screenSaver == 1) ? pixelShiftRange : 0; // shrink graphwidth for pixelshift screensaver initPowerGraph(mDispWidth - 22 - widthShrink, mLineYOffsets[graph_last_line] - mLineYOffsets[graph_first_line - 1] - 2); diff --git a/src/plugins/Display/Display_Mono_64X48.h b/src/plugins/Display/Display_Mono_64X48.h index e55f5ac3..68aa3cc4 100644 --- a/src/plugins/Display/Display_Mono_64X48.h +++ b/src/plugins/Display/Display_Mono_64X48.h @@ -12,18 +12,14 @@ class DisplayMono64X48 : public DisplayMono { mExtra = 0; } - void config(bool enPowerSave, uint8_t screenSaver, uint8_t lum, uint8_t graph_ratio, uint8_t graph_size) { - mEnPowerSave = enPowerSave; - mScreenSaver = screenSaver; - mLuminance = lum; - mGraphRatio = graph_ratio; - mGraphSize = graph_size; + void config(display_t *cfg) { + mCfg = cfg; } - void init(uint8_t type, uint8_t rotation, uint8_t cs, uint8_t dc, uint8_t reset, uint8_t clock, uint8_t data, DisplayData *displayData) { - u8g2_cb_t *rot = (u8g2_cb_t *)((rotation != 0x00) ? U8G2_R2 : U8G2_R0); + void init(DisplayData *displayData) { + u8g2_cb_t *rot = (u8g2_cb_t *)((mCfg->rot != 0x00) ? U8G2_R2 : U8G2_R0); // Wemos OLed Shield is not defined in u8 lib -> use nearest compatible - monoInit(new U8G2_SSD1306_64X48_ER_F_HW_I2C(rot, reset, clock, data), type, displayData); + monoInit(new U8G2_SSD1306_64X48_ER_F_HW_I2C(rot, 0xff, mCfg->disp_clk, mCfg->disp_data), displayData); calcLinePositions(); printText("Ahoy!", 0); diff --git a/src/plugins/Display/Display_Mono_84X48.h b/src/plugins/Display/Display_Mono_84X48.h index b195e17c..51c5eafe 100644 --- a/src/plugins/Display/Display_Mono_84X48.h +++ b/src/plugins/Display/Display_Mono_84X48.h @@ -12,20 +12,17 @@ class DisplayMono84X48 : public DisplayMono { mExtra = 0; } - void config(bool enPowerSave, uint8_t screenSaver, uint8_t lum, uint8_t graph_ratio, uint8_t graph_size) { - mEnPowerSave = enPowerSave; - mScreenSaver = screenSaver; - mLuminance = lum; - mGraphRatio = graph_ratio; - mGraphSize = graph_size; + void config(display_t *cfg) { + mCfg = cfg; } - void init(uint8_t type, uint8_t rotation, uint8_t cs, uint8_t dc, uint8_t reset, uint8_t clock, uint8_t data, DisplayData *displayData) { - u8g2_cb_t *rot = (u8g2_cb_t *)((rotation != 0x00) ? U8G2_R2 : U8G2_R0); - monoInit(new U8G2_PCD8544_84X48_F_4W_SW_SPI(rot, clock, data, cs, dc, reset), type, displayData); + void init(DisplayData *displayData) { + u8g2_cb_t *rot = (u8g2_cb_t *)((mCfg->rot != 0x00) ? U8G2_R2 : U8G2_R0); + + monoInit(new U8G2_PCD8544_84X48_F_4W_SW_SPI(rot, mCfg->disp_clk, mCfg->disp_data, mCfg->disp_cs, mCfg->disp_dc, 0xff), displayData); calcLinePositions(); - switch(mGraphSize) { // var opts2 = [[0, "Line 1 - 2"], [1, "Line 2 - 3"], [2, "Line 1 - 3"], [3, "Line 2 - 4"], [4, "Line 1 - 4"]]; + switch(mCfg->graph_size) { // var opts2 = [[0, "Line 1 - 2"], [1, "Line 2 - 3"], [2, "Line 1 - 3"], [3, "Line 2 - 4"], [4, "Line 1 - 4"]]; case 0: graph_first_line = 1; graph_last_line = 2; From c9c4f20ec3f4292455aee92198a826cf664c1756 Mon Sep 17 00:00:00 2001 From: you69man Date: Mon, 8 Jan 2024 23:53:48 +0100 Subject: [PATCH 057/179] add logic to settings to hide useless options depending on display type, and respect i18n --- src/web/html/setup.html | 56 ++++++++++++++++++++++++----------------- src/web/lang.json | 52 +++++++++++++++++++++++++++++++++----- 2 files changed, 79 insertions(+), 29 deletions(-) diff --git a/src/web/html/setup.html b/src/web/html/setup.html index da9785ee..00b0477c 100644 --- a/src/web/html/setup.html +++ b/src/web/html/setup.html @@ -287,18 +287,20 @@
-
+
{#DISP_LUMINANCE}

{#DISP_PINOUT}

-

Graph options

-
-
-
Show ratio (0-100 %)
-
+
+

{#GRAPH_OPTIONS}

+
+
+
{#GRAPH_SHOW_RATIO}
+
+
@@ -1095,12 +1097,12 @@ document.getElementsByName("disp_graph_ratio")[0].value = obj["disp_graph_ratio"]; - var opts2 = [[0, "Line 1 - 2"], [1, "Line 2 - 3"], [2, "Line 1 - 3"], [3, "Line 2 - 4"], [4, "Line 1 - 4"]]; + var opts2 = [[0, "{#GRAPH_LINES_1_2}"], [1, "{#GRAPH_LINES_2_3}"], [2, "{#GRAPH_LINES_1_3}"], [3, "{#GRAPH_LINES_2_4}"], [4, "{#GRAPH_LINES_1_4}"]]; var graph_size_sel = sel("disp_graph_size", opts2, obj["disp_graph_size"]); graph_size_sel.id = 'disp_graph_size'; document.getElementById("graphSize").append( ml("div", {class: "row mb-3"}, [ - ml("div", {class: "col-12 col-sm-3 my-2"}, "Graph size"), + ml("div", {class: "col-12 col-sm-3 my-2"}, "{#GRAPH_POSITION}"), ml("div", {class: "col-12 col-sm-9"}, graph_size_sel) ]) ); @@ -1113,14 +1115,14 @@ // It depends on fix pin array (see var pins) // var pins = [['clock', 'disp_clk'], ['data', 'disp_data'], ['cs', 'disp_cs'], ['dc', 'disp_dc'], ['reset', 'disp_rst']]; const pinMap = new Map([ - [0, [0,0,0,0,0,0]], //none - [1, [1,1,0,0,0,0]], //SSD1306_128X64 - [2, [1,1,0,0,0,0]], //SH1106_128X64 - [3, [1,1,1,1,0,0]], //PCD8544_84X48 /nokia5110 - [4, [1,1,0,0,0,0]], //SSD1306_128X32 - [5, [1,1,0,0,0,0]], //SSD1306_128x64 - [6, [1,1,0,0,0,0]], //SSD1309_128x64 - [10, [1,1,1,1,1,1]] //ePaper + [0, [0,0,0,0,0,0]], //none + [1, [1,1,0,0,0,0]], //SSD1306_128X64 + [2, [1,1,0,0,0,0]], //SH1106_128X64 + [3, [1,1,1,1,0,0]], //PCD8544_84X48 /nokia5110 + [4, [1,1,0,0,0,0]], //SSD1306_128X32 + [5, [1,1,0,0,0,0]], //SSD1306_64X48 + [6, [1,1,0,0,0,0]], //SSD1309_128x64 + [10, [1,1,1,1,1,1]] //ePaper ]) for(var i = 0; i < pins.length; i++) { var cl = document.getElementById("row_" + pins[i][1]).classList; @@ -1132,14 +1134,22 @@ } } - var screenSaver = document.getElementById("disp_screensaver").value; + const optionsMap = new Map([ // options: [Graph, Luminance, Screensaver] + [0, [0,0,0]], //none + [1, [1,1,1]], //SSD1306_128X64 + [2, [1,1,1]], //SH1106_128X64 + [3, [1,1,0]], //PCD8544_84X48 /nokia5110 + [4, [0,1,1]], //SSD1306_128X32 + [5, [0,1,1]], //SSD1306_64X48 + [6, [1,1,1]], //SSD1309_128x64 + [10, [0,0,0]] //ePaper + ]) - if (2==screenSaver) { // show pir pin only for motion screensaver - setHide("pirPin", false); - } - else { // no pir pin for all others - setHide("pirPin", true); - } + var screenSaver = document.getElementById("disp_screensaver").value; + setHide("graphOptions", !optionsMap.get(dispType)[0]); + setHide("luminanceOption", !optionsMap.get(dispType)[1]); + setHide("screenSaver", !optionsMap.get(dispType)[2]); + setHide("pirPin", !(optionsMap.get(dispType)[2] && (screenSaver==2))); // show pir pin only for motion screensaver } function tick() { diff --git a/src/web/lang.json b/src/web/lang.json index d6c76ffc..4663949d 100644 --- a/src/web/lang.json +++ b/src/web/lang.json @@ -301,7 +301,7 @@ { "token": "NTP_SET_SYS_TIME", "en": "set system time", - "de": "Systemzeit setzten" + "de": "Systemzeit setzen" }, { "token": "BTN_FROM_BROWSER", @@ -311,7 +311,7 @@ { "token": "BTN_SYNC_NTP", "en": "sync NTP", - "de": "NTP syncchronisieren" + "de": "NTP synchronisieren" }, { "token": "NTP_SYS_TIME", @@ -366,7 +366,7 @@ { "token": "DISP_OFF_INV", "en": "Turn off while inverters are offline", - "de": "schalte das Display aus, während die Wechselrichter aus sind" + "de": "Schalte das Display aus, während die Wechselrichter aus sind" }, { "token": "DISP_LUMINANCE", @@ -378,6 +378,46 @@ "en": "Pinout", "de": "Anschlusseinstellungen" }, + { + "token": "GRAPH_OPTIONS", + "en": "Graph options", + "de": "Graph Einstellungen" + }, + { + "token": "GRAPH_SHOW_RATIO", + "en": "Graph show ratio (0-100%)", + "de": "Graph Anzeigeverhältnis (0-100%)" + }, + { + "token": "GRAPH_POSITION", + "en": "Graph pos (from/to)", + "de": "Graph Position (von/bis)" + }, + { + "token": "GRAPH_LINES_1_2", + "en": "Line 1-2", + "de": "Zeile 1-2" + }, + { + "token": "GRAPH_LINES_2_3", + "en": "Line 2-3", + "de": "Zeile 2-3" + }, + { + "token": "GRAPH_LINES_1_3", + "en": "Line 1-3", + "de": "Zeile 1-3" + }, + { + "token": "GRAPH_LINES_2_4", + "en": "Line 2-4", + "de": "Zeile 2-4" + }, + { + "token": "GRAPH_LINES_1_4", + "en": "Line 1-4", + "de": "Zeile 1-4" + }, { "token": "BTN_REBOOT_SUCCESSFUL_SAVE", "en": "Reboot device after successful save", @@ -476,7 +516,7 @@ { "token": "NTP_SYNCED_AT", "en": "synced at", - "de": "syncchronisiert um" + "de": "synchronisiert um" }, { "token": "NTP_DIFF", @@ -491,7 +531,7 @@ { "token": "IMPORT_UPLOAD_STARTED", "en": "upload started", - "de": "hochladen gestartet" + "de": "Hochladen gestartet" }, { "token": "INV_EDIT", @@ -681,7 +721,7 @@ { "token": "DISP_SCREENSAVER", "en": "Screensaver (OLED only)", - "de": "Bildschrimschoner (nur OLED)" + "de": "Bildschirmschoner (nur OLED)" }, { "token": "NETWORK_PLEASE_SELECT", From cc9ba1b808811f3005371c029a86f0f6135c9af9 Mon Sep 17 00:00:00 2001 From: lumapu Date: Tue, 9 Jan 2024 00:11:36 +0100 Subject: [PATCH 058/179] improved, but currently compiles with errors --- src/app.cpp | 10 +- src/app.h | 18 +- src/appInterface.h | 14 +- src/config/config.h | 3 - src/config/settings.h | 1 + src/plugins/Display/Display.h | 18 +- src/plugins/Display/Display_Mono.h | 8 - src/plugins/Display/Display_Mono_128X64.h | 261 ++++++++-------------- src/plugins/Display/Display_data.h | 2 - src/plugins/history.cpp | 94 -------- src/plugins/history.h | 187 +++++++++------- src/web/RestApi.h | 109 ++------- 12 files changed, 234 insertions(+), 491 deletions(-) delete mode 100644 src/plugins/history.cpp diff --git a/src/app.cpp b/src/app.cpp index 9ec3d86c..5a31a1d0 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -7,7 +7,7 @@ #include "app.h" #include "utils/sun.h" -#include "plugins/history.h" + //----------------------------------------------------------------------------- app::app() : ah::Scheduler {} {} @@ -91,10 +91,7 @@ void app::setup() { #endif #endif - mTotalPowerHistory = new TotalPowerHistory(); - mTotalPowerHistory->setup(this, &mSys, mConfig); - mYieldDayHistory = new YieldDayHistory(); - mYieldDayHistory->setup(this, &mSys, mConfig); + mHistory.setup(this, &mSys, mConfig, &mTimestamp); mPubSerial.setup(mConfig, &mSys, &mTimestamp); @@ -154,8 +151,7 @@ void app::regularTickers(void) { //everySec([this]() { mImprov.tickSerial(); }, "impro"); #endif - everySec(std::bind(&TotalPowerHistory::tickerSecond, mTotalPowerHistory), "totalPowerHistory"); - everySec(std::bind(&YieldDayHistory::tickerSecond, mYieldDayHistory), "yieldDayHistory"); + everySec(std::bind(&HistoryType::tickerSecond, mHistory), "hist"); } #if defined(ETHERNET) diff --git a/src/app.h b/src/app.h index 029f0f1e..4708ee5a 100644 --- a/src/app.h +++ b/src/app.h @@ -24,6 +24,7 @@ #include "utils/scheduler.h" #include "utils/syslog.h" #include "web/RestApi.h" +#include "plugins/history.h" #include "web/web.h" #include "hm/Communication.h" #if defined(ETHERNET) @@ -35,6 +36,7 @@ #include // position is relevant since version 1.4.7 of this library + // convert degrees and radians for sun calculation #define SIN(x) (sin(radians(x))) #define COS(x) (cos(radians(x))) @@ -42,12 +44,11 @@ #define ACOS(x) (degrees(acos(x))) typedef HmSystem HmSystemType; -#ifdef ESP32 -#endif typedef Web WebType; typedef RestApi RestApiType; typedef PubMqtt PubMqttType; typedef PubSerial PubSerialType; +typedef HistoryData HistoryType; // PLUGINS #if defined(PLUGIN_DISPLAY) @@ -55,7 +56,6 @@ typedef PubSerial PubSerialType; #include "plugins/Display/Display_data.h" typedef Display DisplayType; #endif -#include "plugins/history.h" class app : public IApp, public ah::Scheduler { public: @@ -244,8 +244,13 @@ class app : public IApp, public ah::Scheduler { Scheduler::setTimestamp(newTime); } - TotalPowerHistory *getTotalPowerHistoryPtr() { return mTotalPowerHistory; }; - YieldDayHistory *getYieldDayHistoryPtr() { return mYieldDayHistory; }; + uint16_t getHistoryValue(HistoryType type, uint16_t i) { + return mHistory.valueAt(type, i); + } + + uint16_t getHistoryMaxDay() { + return mHistory.getMaximumDay(); + } private: #define CHECK_AVAIL true @@ -354,8 +359,7 @@ class app : public IApp, public ah::Scheduler { DisplayType mDisplay; DisplayData mDispData; #endif - TotalPowerHistory *mTotalPowerHistory; - YieldDayHistory *mYieldDayHistory; + HistoryType mHistory; }; #endif /*__APP_H__*/ diff --git a/src/appInterface.h b/src/appInterface.h index 89d52d9e..a9c67147 100644 --- a/src/appInterface.h +++ b/src/appInterface.h @@ -8,19 +8,13 @@ #include "defines.h" #include "hm/hmSystem.h" +#include "plugins/history.h" #if defined(ETHERNET) #include "AsyncWebServer_ESP32_W5500.h" #else #include "ESPAsyncWebServer.h" #endif -class TotalPowerHistory; -class YieldDayHistory; -//#include "hms/hmsRadio.h" -#if defined(ESP32) -//typedef CmtRadio> CmtRadioType; -#endif - // abstract interface to App. Make members of App accessible from child class // like web or API without forward declaration class IApp { @@ -65,10 +59,10 @@ class IApp { virtual bool getProtection(AsyncWebServerRequest *request) = 0; - virtual TotalPowerHistory *getTotalPowerHistoryPtr() = 0; - virtual YieldDayHistory *getYieldDayHistoryPtr() = 0; - virtual void* getRadioObj(bool nrf) = 0; + virtual uint16_t getHistoryValue(uint8_t type, uint16_t i) = 0; + virtual uint16_t getHistoryMaxDay() = 0; + virtual void* getRadioObj(bool nrf) = 0; }; #endif /*__IAPP_H__*/ diff --git a/src/config/config.h b/src/config/config.h index 393c78e3..2cb9bcd3 100644 --- a/src/config/config.h +++ b/src/config/config.h @@ -36,9 +36,6 @@ // CONFIGURATION - COMPILE TIME //------------------------------------- -// Draw power chart in MONO-Display -#define DISPLAY_CHART 1 - // ethernet #if defined(ETHERNET) diff --git a/src/config/settings.h b/src/config/settings.h index ec61d733..f4a083f8 100644 --- a/src/config/settings.h +++ b/src/config/settings.h @@ -53,6 +53,7 @@ #define DEF_PROT_MQTT 0x0000 #define DEF_PROT_HISTORY 0x0000 + typedef struct { uint8_t ip[4]; // ip address uint8_t mask[4]; // sub mask diff --git a/src/plugins/Display/Display.h b/src/plugins/Display/Display.h index 2f0a94bf..0cfbd710 100644 --- a/src/plugins/Display/Display.h +++ b/src/plugins/Display/Display.h @@ -99,8 +99,7 @@ class Display { uint8_t nrprod = 0; uint8_t nrsleep = 0; - uint8_t nrAvailable = 0; - int8_t minQAllInv = 4; + int8_t minQAllInv = 4; Inverter<> *iv; record_t<> *rec; @@ -115,8 +114,6 @@ class Display { nrprod++; else nrsleep++; - if (iv->isAvailable()) - nrAvailable++; rec = iv->getRecordStruct(RealTimeRunData_Debug); @@ -144,7 +141,6 @@ class Display { // prepare display data mDisplayData.nrProducing = nrprod; mDisplayData.nrSleeping = nrsleep; - mDisplayData.nrAvailable = nrAvailable; mDisplayData.totalPower = totalPower; mDisplayData.totalYieldDay = totalYieldDay; mDisplayData.totalYieldTotal = totalYieldTotal; @@ -169,17 +165,7 @@ class Display { else mDisplayData.utcTs = 0; - const uint32_t sunriseTime = mApp->getSunrise(); - if (mDisplayData.utcTs == 0) - mDisplayData.sunIsShining = true; // Start with sunshine :-) - else { - mDisplayData.sunIsShining = false; - // new sunrise is calculated after sunset + user-offset - if (utc > sunriseTime) - mDisplayData.sunIsShining = true; - } - - if (mMono) { + if (mMono ) { mMono->disp(); } #if defined(ESP32) && !defined(ETHERNET) diff --git a/src/plugins/Display/Display_Mono.h b/src/plugins/Display/Display_Mono.h index f8c81914..3e998b6d 100644 --- a/src/plugins/Display/Display_Mono.h +++ b/src/plugins/Display/Display_Mono.h @@ -4,7 +4,6 @@ //----------------------------------------------------------------------------- #pragma once -#include "config/config.h" #include #define DISP_DEFAULT_TIMEOUT 60 // in seconds #define DISP_FMT_TEXT_LEN 32 @@ -102,13 +101,6 @@ class DisplayMono { int8_t mod = (millis() / 10000) % ((range >> 1) << 2); mPixelshift = mScreenSaver == 1 ? ((mod < range) ? mod - (range >> 1) : -(mod - range - (range >> 1) + 1)) : 0; } - -#ifdef DISPLAY_CHART -#define DISP_WATT_ARR_LENGTH 128 // Number of WATT history values - float m_wattArr[DISP_WATT_ARR_LENGTH + 1]; // ring buffer for watt history - uint16_t m_wattListIdx; // index for next Element to write into WattArr - void drawPowerChart(); -#endif }; /* adapted 5x8 Font for low-res displays with symbols diff --git a/src/plugins/Display/Display_Mono_128X64.h b/src/plugins/Display/Display_Mono_128X64.h index 88c72979..afb581dd 100644 --- a/src/plugins/Display/Display_Mono_128X64.h +++ b/src/plugins/Display/Display_Mono_128X64.h @@ -9,12 +9,6 @@ class DisplayMono128X64 : public DisplayMono { public: DisplayMono128X64() : DisplayMono() { -#ifdef DISPLAY_CHART - for (uint16_t i = 0; i < DISP_WATT_ARR_LENGTH; i++) - m_wattArr[i] = 0.0; - m_wattListIdx = 0; - mDrawChart = false; -#endif mExtra = 0; } @@ -47,6 +41,7 @@ class DisplayMono128X64 : public DisplayMono { } void disp(void) { + uint8_t pos, sun_pos, moon_pos; mDisplay->clearBuffer(); @@ -66,149 +61,109 @@ class DisplayMono128X64 : public DisplayMono { // calculate current pixelshift for pixelshift screensaver calcPixelShift(pixelShiftRange); -#ifdef DISPLAY_CHART - static uint32_t dataUpdateTime = mDisplayData->utcTs + 60; // update chart every minute - if (mDisplayData->utcTs >= dataUpdateTime) - { - dataUpdateTime = mDisplayData->utcTs + 60; // next minute - m_wattArr[m_wattListIdx] = mDisplayData->totalPower; - m_wattListIdx = (m_wattListIdx + 1) % (DISP_WATT_ARR_LENGTH); - } - - if (mDrawChart && mDisplayData->sunIsShining && (mDisplayData->nrAvailable > 0)) - { - // print total power - if (mDisplayData->nrProducing > 0) { - if (mDisplayData->totalPower > 9999.0) - snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%.2f kW", (mDisplayData->totalPower / 1000.0)); - else - snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%.0f W", mDisplayData->totalPower); - printText(mFmtText, l_Time, 10); - } else { - printText("offline", l_Time, 0xff); - } - - snprintf(mFmtText, DISP_FMT_TEXT_LEN, "today: %4.0f Wh", mDisplayData->totalYieldDay); - printText(mFmtText, l_Status, 10); - - drawPowerChart(); + // print total power + if (mDisplayData->nrProducing > 0) { + if (mDisplayData->totalPower > 9999.0) + snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%.2f kW", (mDisplayData->totalPower / 1000.0)); + else + snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%.0f W", mDisplayData->totalPower); + printText(mFmtText, l_TotalPower, 0xff); + } else { + printText("offline", l_TotalPower, 0xff); } - else -#endif - { - // print total power - if (mDisplayData->nrProducing > 0) { - if (mDisplayData->totalPower > 9999.0) - snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%.2f kW", (mDisplayData->totalPower / 1000.0)); - else - snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%.0f W", mDisplayData->totalPower); - printText(mFmtText, l_TotalPower, 0xff); - } else { - printText("offline", l_TotalPower, 0xff); - } + // print Date and time + if (0 != mDisplayData->utcTs) + printText(ah::getDateTimeStrShort(gTimezone.toLocal(mDisplayData->utcTs)).c_str(), l_Time, 0xff); - // print Date and time - if (0 != mDisplayData->utcTs) - printText(ah::getDateTimeStrShort(gTimezone.toLocal(mDisplayData->utcTs)).c_str(), l_Time, 0xff); - - // dynamic status bar, alternatively: - // print ip address - if (!(mExtra % 5) && (mDisplayData->ipAddress)) { - snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%s", (mDisplayData->ipAddress).toString().c_str()); - printText(mFmtText, l_Status, 0xff); - } - // print status of inverters - else { - uint8_t pos, sun_pos, moon_pos; - sun_pos = -1; - moon_pos = -1; - setLineFont(l_Status); - if (0 == mDisplayData->nrSleeping + mDisplayData->nrProducing) - snprintf(mFmtText, DISP_FMT_TEXT_LEN, "no inverter"); - else if (0 == mDisplayData->nrSleeping) { - snprintf(mFmtText, DISP_FMT_TEXT_LEN, " "); - sun_pos = 0; + // dynamic status bar, alternatively: + // print ip address + if (!(mExtra % 5) && (mDisplayData->ipAddress)) { + snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%s", (mDisplayData->ipAddress).toString().c_str()); + printText(mFmtText, l_Status, 0xff); + } + // print status of inverters + else { + sun_pos = -1; + moon_pos = -1; + setLineFont(l_Status); + if (0 == mDisplayData->nrSleeping + mDisplayData->nrProducing) + snprintf(mFmtText, DISP_FMT_TEXT_LEN, "no inverter"); + else if (0 == mDisplayData->nrSleeping) { + snprintf(mFmtText, DISP_FMT_TEXT_LEN, " "); + sun_pos = 0; } else if (0 == mDisplayData->nrProducing) { - snprintf(mFmtText, DISP_FMT_TEXT_LEN, " "); - moon_pos = 0; + snprintf(mFmtText, DISP_FMT_TEXT_LEN, " "); + moon_pos = 0; } else { - snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%2d", mDisplayData->nrProducing); - sun_pos = mDisplay->getStrWidth(mFmtText) + 1; - snprintf(mFmtText + 2, DISP_FMT_TEXT_LEN, " %2d", mDisplayData->nrSleeping); - moon_pos = mDisplay->getStrWidth(mFmtText) + 1; - snprintf(mFmtText + 7, DISP_FMT_TEXT_LEN, " "); - } - printText(mFmtText, l_Status, 0xff); - - pos = (mDispWidth - mDisplay->getStrWidth(mFmtText)) / 2; - mDisplay->setFont(u8g2_font_ncenB08_symbols8_ahoy); - if (sun_pos != -1) - mDisplay->drawStr(pos + sun_pos + mPixelshift, mLineYOffsets[l_Status], "G"); // sun symbol - if (moon_pos != -1) - mDisplay->drawStr(pos + moon_pos + mPixelshift, mLineYOffsets[l_Status], "H"); // moon symbol + snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%2d", mDisplayData->nrProducing); + sun_pos = mDisplay->getStrWidth(mFmtText) + 1; + snprintf(mFmtText+2, DISP_FMT_TEXT_LEN, " %2d", mDisplayData->nrSleeping); + moon_pos = mDisplay->getStrWidth(mFmtText) + 1; + snprintf(mFmtText+7, DISP_FMT_TEXT_LEN, " "); } + printText(mFmtText, l_Status, 0xff); + + pos = (mDispWidth - mDisplay->getStrWidth(mFmtText)) / 2; + mDisplay->setFont(u8g2_font_ncenB08_symbols8_ahoy); + if (sun_pos!=-1) + mDisplay->drawStr(pos + sun_pos + mPixelshift, mLineYOffsets[l_Status], "G"); // sun symbol + if (moon_pos!=-1) + mDisplay->drawStr(pos + moon_pos + mPixelshift, mLineYOffsets[l_Status], "H"); // moon symbol + } - // print yields - mDisplay->setFont(u8g2_font_ncenB10_symbols10_ahoy); - mDisplay->drawStr(16 + mPixelshift, mLineYOffsets[l_YieldDay], "I"); // day symbol - mDisplay->drawStr(16 + mPixelshift, mLineYOffsets[l_YieldTotal], "D"); // total symbol + // print yields + mDisplay->setFont(u8g2_font_ncenB10_symbols10_ahoy); + mDisplay->drawStr(16 + mPixelshift, mLineYOffsets[l_YieldDay], "I"); // day symbol + mDisplay->drawStr(16 + mPixelshift, mLineYOffsets[l_YieldTotal], "D"); // total symbol - if (mDisplayData->totalYieldDay > 9999.0) - snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%.2f kWh", mDisplayData->totalYieldDay / 1000.0); - else - snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%.0f Wh", mDisplayData->totalYieldDay); - printText(mFmtText, l_YieldDay, 0xff); - - if (mDisplayData->totalYieldTotal > 9999.0) - snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%.2f MWh", mDisplayData->totalYieldTotal / 1000.0); - else - snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%.0f kWh", mDisplayData->totalYieldTotal); - printText(mFmtText, l_YieldTotal, 0xff); + if (mDisplayData->totalYieldDay > 9999.0) + snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%.2f kWh", mDisplayData->totalYieldDay / 1000.0); + else + snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%.0f Wh", mDisplayData->totalYieldDay); + printText(mFmtText, l_YieldDay, 0xff); - // draw dynamic RSSI bars - int xoffs; - if (mScreenSaver == 1) // shrink screenwidth for pixelshift screensaver - xoffs = pixelShiftRange / 2; - else - xoffs = 0; - int rssi_bar_height = 9; - for (int i = 0; i < 4; i++) { - int radio_rssi_threshold = -60 - i * 10; - int wifi_rssi_threshold = -60 - i * 10; - if (mDisplayData->RadioRSSI > radio_rssi_threshold) - mDisplay->drawBox(xoffs + mPixelshift, 8 + (rssi_bar_height + 1) * i, 4 - i, rssi_bar_height); - if (mDisplayData->WifiRSSI > wifi_rssi_threshold) - mDisplay->drawBox(mDispWidth - 4 - xoffs + mPixelshift + i, 8 + (rssi_bar_height + 1) * i, 4 - i, rssi_bar_height); - } - // draw dynamic antenna and WiFi symbols - mDisplay->setFont(u8g2_font_ncenB10_symbols10_ahoy); - char sym[] = " "; - sym[0] = mDisplayData->RadioSymbol ? 'A' : 'E'; // NRF - mDisplay->drawStr(xoffs + mPixelshift, mLineYOffsets[l_RSSI], sym); + if (mDisplayData->totalYieldTotal > 9999.0) + snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%.2f MWh", mDisplayData->totalYieldTotal / 1000.0); + else + snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%.0f kWh", mDisplayData->totalYieldTotal); + printText(mFmtText, l_YieldTotal, 0xff); - if (mDisplayData->MQTTSymbol) - sym[0] = 'J'; // MQTT - else - sym[0] = mDisplayData->WifiSymbol ? 'B' : 'F'; // Wifi - mDisplay->drawStr(mDispWidth - mDisplay->getStrWidth(sym) - xoffs + mPixelshift, mLineYOffsets[l_RSSI], sym); - mDisplay->sendBuffer(); + // draw dynamic RSSI bars + int xoffs; + if (mScreenSaver == 1) // shrink screenwidth for pixelshift screensaver + xoffs = pixelShiftRange/2; + else + xoffs = 0; + int rssi_bar_height = 9; + for (int i = 0; i < 4; i++) { + int radio_rssi_threshold = -60 - i * 10; + int wifi_rssi_threshold = -60 - i * 10; + if (mDisplayData->RadioRSSI > radio_rssi_threshold) + mDisplay->drawBox(xoffs + mPixelshift, 8 + (rssi_bar_height + 1) * i, 4 - i, rssi_bar_height); + if (mDisplayData->WifiRSSI > wifi_rssi_threshold) + mDisplay->drawBox(mDispWidth - 4 - xoffs + mPixelshift + i, 8 + (rssi_bar_height + 1) * i, 4 - i, rssi_bar_height); } + // draw dynamic antenna and WiFi symbols + mDisplay->setFont(u8g2_font_ncenB10_symbols10_ahoy); + char sym[]=" "; + sym[0] = mDisplayData->RadioSymbol?'A':'E'; // NRF + mDisplay->drawStr(xoffs + mPixelshift, mLineYOffsets[l_RSSI], sym); + + if (mDisplayData->MQTTSymbol) + sym[0] = 'J'; // MQTT + else + sym[0] = mDisplayData->WifiSymbol?'B':'F'; // Wifi + mDisplay->drawStr(mDispWidth - mDisplay->getStrWidth(sym) - xoffs + mPixelshift, mLineYOffsets[l_RSSI], sym); + mDisplay->sendBuffer(); + mDisplay->sendBuffer(); - - mExtra++; -#ifdef DISPLAY_CHART - static uint32_t switchDisplayTime = mDisplayData->utcTs + 20; - if (mDisplayData->utcTs >= switchDisplayTime) { - switchDisplayTime = mDisplayData->utcTs + 20; - mDrawChart = !mDrawChart; - } -#endif + mExtra++; } private: @@ -271,46 +226,4 @@ class DisplayMono128X64 : public DisplayMono { dispX += mPixelshift; mDisplay->drawStr(dispX, mLineYOffsets[line], text); } - -#ifdef DISPLAY_CHART - bool mDrawChart ; - - void drawPowerChart() { - const int hight = 40; // chart hight - - // Clear area - // mDisplay->draw_rectangle(0, 63 - hight, DISP_WATT_ARR_LENGTH, 63, OLED::SOLID, OLED::BLACK); - mDisplay->setDrawColor(0); - mDisplay->drawBox(0, 63 - hight, DISP_WATT_ARR_LENGTH, hight); - mDisplay->setDrawColor(1); - - // Get max value for scaling - float maxValue = 0.0; - for (int i = 0; i < DISP_WATT_ARR_LENGTH; i++) { - float fValue = m_wattArr[i]; - if (fValue > maxValue) - maxValue = fValue; - } - // calc divider to fit into chart hight - int divider = round(maxValue / (float)hight); - if (divider < 1) - divider = 1; - - // draw chart bars - // Start display of data right behind last written data - uint16_t idx = m_wattListIdx; - for (uint16_t i = 0; i < DISP_WATT_ARR_LENGTH; i++) { - float fValue = m_wattArr[idx]; - int iValue = roundf(fValue); - iValue /= divider; - if (iValue > hight) - iValue = hight; - // mDisplay->draw_line(i, 63 - iValue, i, 63); - // mDisplay->drawVLine(i, 63 - iValue, iValue); - if (iValue>0) - mDisplay->drawLine(i, 63 - iValue, i, 63); - idx = (idx + 1) % (DISP_WATT_ARR_LENGTH); - } - } -#endif }; diff --git a/src/plugins/Display/Display_data.h b/src/plugins/Display/Display_data.h index 1ccc5f7d..a400377d 100644 --- a/src/plugins/Display/Display_data.h +++ b/src/plugins/Display/Display_data.h @@ -11,14 +11,12 @@ struct DisplayData { uint32_t utcTs=0; // indicate absolute timestamp (utc unix time). 0 = time is not synchonized uint8_t nrProducing=0; // indicate number of producing inverters uint8_t nrSleeping=0; // indicate number of sleeping inverters - uint8_t nrAvailable=0; // number of available (comunicating) inverters bool WifiSymbol = false; // indicate if WiFi is connected bool RadioSymbol = false; // indicate if radio module is connecting and working bool MQTTSymbol = false; // indicate if MQTT is connected int8_t WifiRSSI=SCHAR_MIN; // indicate RSSI value for WiFi int8_t RadioRSSI=SCHAR_MIN; // indicate RSSI value for radio IPAddress ipAddress; // indicate ip adress of ahoy - bool sunIsShining; // indicate if time is between sunrise and sunset }; #endif /*__DISPLAY_DATA__*/ diff --git a/src/plugins/history.cpp b/src/plugins/history.cpp deleted file mode 100644 index 451d3e1e..00000000 --- a/src/plugins/history.cpp +++ /dev/null @@ -1,94 +0,0 @@ - -#include "plugins/history.h" - -#include "appInterface.h" -#include "config/config.h" -#include "utils/dbg.h" - -void TotalPowerHistory::setup(IApp *app, HmSystemType *sys, settings_t *config) { - mApp = app; - mSys = sys; - mConfig = config; - mRefreshCycle = mConfig->inst.sendInterval; - mMaximumDay = 0; - - // Debug - //for (uint16_t i = 0; i < HISTORY_DATA_ARR_LENGTH *1.5; i++) { - // addValue(i); - //} -} - -void TotalPowerHistory::tickerSecond() { - ++mLoopCnt; - if ((mLoopCnt % mRefreshCycle) == 0) { - //DPRINTLN(DBG_DEBUG,F("TotalPowerHistory::tickerSecond > refreshCycle" + String(mRefreshCycle) + "|" + String(mLoopCnt) + "|" + String(mRefreshCycle % mLoopCnt)); - mLoopCnt = 0; - float totalPower = 0; - float totalPowerDay = 0; - Inverter<> *iv; - record_t<> *rec; - for (uint8_t i = 0; i < mSys->getNumInverters(); i++) { - iv = mSys->getInverterByPos(i); - rec = iv->getRecordStruct(RealTimeRunData_Debug); - if (iv == NULL) - continue; - totalPower += iv->getChannelFieldValue(CH0, FLD_PAC, rec); - totalPowerDay += iv->getChannelFieldValue(CH0, FLD_MP, rec); - } - if (totalPower > 0) { - uint16_t iTotalPower = roundf(totalPower); - DPRINTLN(DBG_DEBUG, F("[TotalPowerHistory]: addValue(iTotalPower)=") + String(iTotalPower)); - addValue(iTotalPower); - } - if (totalPowerDay > 0) { - mMaximumDay = roundf(totalPowerDay); - } - } -} - -void YieldDayHistory::setup(IApp *app, HmSystemType *sys, settings_t *config) { - mApp = app; - mSys = sys; - mConfig = config; - mRefreshCycle = 60; // every minute - mDayStored = false; -}; - -void YieldDayHistory::tickerSecond() { - ++mLoopCnt; - if ((mLoopCnt % mRefreshCycle) == 0) { - mLoopCnt = 0; - // check for sunset. if so store yield of day once - uint32_t sunsetTime = mApp->getSunset(); - uint32_t sunriseTime = mApp->getSunrise(); - uint32_t currentTime = mApp->getTimestamp(); - DPRINTLN(DBG_DEBUG,F("[YieldDayHistory] current | rise | set -> ") + String(currentTime) + " | " + String(sunriseTime) + " | " + String(sunsetTime)); - - if (currentTime > sunsetTime) { - if (!mDayStored) { - DPRINTLN(DBG_DEBUG,F("currentTime > sunsetTime ") + String(currentTime) + " > " + String(sunsetTime)); - float totalYieldDay = -0.1; - Inverter<> *iv; - record_t<> *rec; - for (uint8_t i = 0; i < mSys->getNumInverters(); i++) { - iv = mSys->getInverterByPos(i); - rec = iv->getRecordStruct(RealTimeRunData_Debug); - if (iv == NULL) - continue; - totalYieldDay += iv->getChannelFieldValue(CH0, FLD_YD, rec); - } - if (totalYieldDay > 0) { - uint16_t iTotalYieldDay = roundf(totalYieldDay); - DPRINTLN(DBG_DEBUG,F("addValue(iTotalYieldDay)=") + String(iTotalYieldDay)); - addValue(iTotalYieldDay); - mDayStored = true; - } - } - } else { - if (currentTime > sunriseTime) { - DPRINTLN(DBG_DEBUG,F("currentTime > sunriseTime ") + String(currentTime) + " > " + String(sunriseTime)); - mDayStored = false; - } - } - } -} \ No newline at end of file diff --git a/src/plugins/history.h b/src/plugins/history.h index f6485b17..f068a127 100644 --- a/src/plugins/history.h +++ b/src/plugins/history.h @@ -1,86 +1,123 @@ +//----------------------------------------------------------------------------- +// 2024 Ahoy, https://ahoydtu.de +// Creative Commons - https://creativecommons.org/licenses/by-nc-sa/4.0/deed +//----------------------------------------------------------------------------- + #ifndef __HISTORY_DATA_H__ #define __HISTORY_DATA_H__ -#include "utils/helper.h" -#include "defines.h" -#include "hm/hmSystem.h" - -typedef HmSystem HmSystemType; -class IApp; +#include +#include "../appInterface.h" +#include "../hm/hmSystem.h" +#include "../utils/helper.h" #define HISTORY_DATA_ARR_LENGTH 256 -class HistoryData { - public: - HistoryData() { - for (int i = 0; i < HISTORY_DATA_ARR_LENGTH; i++) - m_dataArr[i] = 0; - m_listIdx = 0; - m_dispIdx = 0; - m_wrapped = false; - }; - void addValue(uint16_t value) - { - if (m_wrapped) // after 1st time array wrap we have to increas the display index - m_dispIdx = (m_listIdx + 1) % (HISTORY_DATA_ARR_LENGTH); - m_dataArr[m_listIdx] = value; - m_listIdx = (m_listIdx + 1) % (HISTORY_DATA_ARR_LENGTH); - if (m_listIdx == 0) - m_wrapped = true; - }; - - uint16_t valueAt(int i){ - uint16_t idx = m_dispIdx + i; - idx = idx % HISTORY_DATA_ARR_LENGTH; - uint16_t value = m_dataArr[idx]; - return value; - }; - - private: - uint16_t m_dataArr[HISTORY_DATA_ARR_LENGTH + 1]; // ring buffer for watt history - uint16_t m_listIdx; // index for next Element to write into WattArr - uint16_t m_dispIdx; // index for 1st Element to display from WattArr - bool m_wrapped; +enum class HistoryStorageType : uint8_t { + POWER, + YIELD }; -class TotalPowerHistory : public HistoryData { - public: - TotalPowerHistory() : HistoryData() { - mLoopCnt = 0; - }; - - void setup(IApp *app, HmSystemType *sys, settings_t *config); - void tickerSecond(); - uint16_t getMaximumDay() { return mMaximumDay; } - - private: - IApp *mApp; - HmSystemType *mSys; - settings *mSettings; - settings_t *mConfig; - uint16_t mRefreshCycle; - uint16_t mLoopCnt; - - uint16_t mMaximumDay; -}; +template +class HistoryData { + private: + struct storage_t { + uint16_t refreshCycle; + uint16_t loopCnt; + uint16_t listIdx; // index for next Element to write into WattArr + uint16_t dispIdx; // index for 1st Element to display from WattArr + bool wrapped; + // ring buffer for watt history + std::array data; + + void reset() { + loopCnt = 0; + listIdx = 0; + dispIdx = 0; + wrapped = false; + for(uint16_t i = 0; i < (HISTORY_DATA_ARR_LENGTH + 1); i++) { + data[i] = 0; + } + } + }; + + public: + void setup(IApp *app, HMSYSTEM *sys, settings_t *config, uint32_t *ts) { + mApp = app; + mSys = sys; + mConfig = config; + mTs = ts; + + mCurPwr.reset(); + mCurPwr.refreshCycle = mConfig->inst.sendInterval; + mYieldDay.reset(); + mYieldDay.refreshCycle = 60; + } + + void tickerSecond() { + Inverter<> *iv; + record_t<> *rec; + float curPwr = 0; + float maxPwr = 0; + float yldDay = -0.1; + for (uint8_t i = 0; i < mSys->getNumInverters(); i++) { + iv = mSys->getInverterByPos(i); + rec = iv->getRecordStruct(RealTimeRunData_Debug); + if (iv == NULL) + continue; + curPwr += iv->getChannelFieldValue(CH0, FLD_PAC, rec); + maxPwr += iv->getChannelFieldValue(CH0, FLD_MP, rec); + yldDay += iv->getChannelFieldValue(CH0, FLD_YD, rec); + } + + if ((++mCurPwr.loopCnt % mCurPwr.refreshCycle) == 0) { + mCurPwr.loopCnt = 0; + if (curPwr > 0) + addValue(&mCurPwr, roundf(curPwr)); + if (maxPwr > 0) + mMaximumDay = roundf(maxPwr); + } + + if (*mTs > mApp->getSunset()) { + if ((!mDayStored) && (yldDay > 0)) { + addValue(&mYieldDay, roundf(yldDay)); + mDayStored = true; + } + } else if (*mTs > mApp->getSunrise()) + mDayStored = false; + } + + uint16_t valueAt(HistoryStorageType type, uint16_t i) { + settings_t *s = (HistoryStorageType::POWER == type) ? &mCurPwr : &mYieldDay; + uint16_t idx = (s->dispIdx + i) % HISTORY_DATA_ARR_LENGTH; + return s->data[idx]; + } + + uint16_t getMaximumDay() { + return mMaximumDay; + } + + private: + void addValue(storage_t *s, uint16_t value) { + if (s->wrapped) // after 1st time array wrap we have to increase the display index + s->dispIdx = (s->listIdx + 1) % (HISTORY_DATA_ARR_LENGTH); + s->data[s->listIdx] = value; + s->listIdx = (s->listIdx + 1) % (HISTORY_DATA_ARR_LENGTH); + if (s->listIdx == 0) + s->wrapped = true; + } + + private: + IApp *mApp; + HMSYSTEM *mSys; + settings *mSettings; + settings_t *mConfig; + uint32_t *mTs; -class YieldDayHistory : public HistoryData { - public: - YieldDayHistory() : HistoryData(){ - mLoopCnt = 0; - }; - - void setup(IApp *app, HmSystemType *sys, settings_t *config); - void tickerSecond(); - - private: - IApp *mApp; - HmSystemType *mSys; - settings *mSettings; - settings_t *mConfig; - uint16_t mRefreshCycle; - uint16_t mLoopCnt; - bool mDayStored; + storage_t mCurPwr; + storage_t mYieldDay; + bool mDayStored = false; + uint16_t mMaximumDay = 0; }; -#endif \ No newline at end of file +#endif diff --git a/src/web/RestApi.h b/src/web/RestApi.h index 45c2670d..254b0ac4 100644 --- a/src/web/RestApi.h +++ b/src/web/RestApi.h @@ -52,10 +52,8 @@ class RestApi { mRadioCmt = (CmtRadio<>*)mApp->getRadioObj(false); #endif mConfig = config; - mSrv->on("/api/insertYieldDayHistory", HTTP_POST, std::bind(&RestApi::onApiPost, this, std::placeholders::_1), - std::bind(&RestApi::onApiPostYieldDHistory, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5, std::placeholders::_6)); - mSrv->on("/api", HTTP_POST, std::bind(&RestApi::onApiPost, this, std::placeholders::_1)) - .onBody(std::bind(&RestApi::onApiPostBody, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5)); + mSrv->on("/api", HTTP_POST, std::bind(&RestApi::onApiPost, this, std::placeholders::_1)).onBody( + std::bind(&RestApi::onApiPostBody, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5)); mSrv->on("/api", HTTP_GET, std::bind(&RestApi::onApi, this, std::placeholders::_1)); mSrv->on("/get_setup", HTTP_GET, std::bind(&RestApi::onDwnldSetup, this, std::placeholders::_1)); @@ -142,83 +140,6 @@ class RestApi { #endif } - void onApiPostYieldDHistory(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, size_t final) - { - uint32_t total = request->contentLength(); - DPRINTLN(DBG_DEBUG, "[onApiPostYieldDHistory ] " + filename + " index:" + index + " len:" + len + " total:" + total + " final:" + final); - - if (0 == index) { - if (NULL != mTmpBuf) - delete[] mTmpBuf; - mTmpBuf = new uint8_t[total + 1]; - mTmpSize = total; - } - if (mTmpSize >= (len + index)) - memcpy(&mTmpBuf[index], data, len); - - if (!final) - return; // not last frame - nothing to do - - mTmpSize = len + index ; // correct the total size - mTmpBuf[mTmpSize] = 0; - -#ifndef ESP32 - DynamicJsonDocument json(ESP.getMaxFreeBlockSize() - 512); // need some memory on heap -#else - DynamicJsonDocument json(12000)); // does this work? I have no ESP32 :-( -#endif - DeserializationError err = deserializeJson(json, (const char *)mTmpBuf, mTmpSize); - json.shrinkToFit(); - JsonObject obj = json.as(); - - - // Debugging - // mTmpBuf[mTmpSize] = 0; - // DPRINTLN(DBG_DEBUG, (const char *)mTmpBuf); - - if (!err && obj) - { - // insert data into yieldDayHistory object - HistoryData *p; - if (obj["maximumDay"]>0) // this is power history data - p = mApp->getTotalPowerHistoryPtr(); - else - p = mApp->getYieldDayHistoryPtr(); - - size_t cnt = obj[F("value")].size(); - DPRINTLN(DBG_DEBUG, "ArraySize: " + String(cnt)); - - for (uint16_t i = 0; i < cnt; i++) { - uint16_t val = obj[F("value")][i]; - p->addValue(val); - // DPRINT(DBG_VERBOSE, "value " + String(i) + ": " + String(val) + ", "); - } - } - else - { - switch (err.code()) { - case DeserializationError::Ok: - break; - case DeserializationError::IncompleteInput: - DPRINTLN(DBG_DEBUG, F("Incomplete input")); - break; - case DeserializationError::InvalidInput: - DPRINTLN(DBG_DEBUG, F("Invalid input")); - break; - case DeserializationError::NoMemory: - DPRINTLN(DBG_DEBUG, F("Not enough memory ") + String(json.capacity()) + " bytes"); - break; - default: - DPRINTLN(DBG_DEBUG, F("Deserialization failed")); - break; - } - } - - request->send(204); // Success with no page load - delete[] mTmpBuf; - mTmpBuf = NULL; - } - void onApiPostBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) { DPRINTLN(DBG_VERBOSE, "onApiPostBody"); @@ -858,31 +779,29 @@ class RestApi { getGeneric(request, obj.createNestedObject(F("generic"))); obj[F("refresh")] = mConfig->inst.sendInterval; obj[F("datapoints")] = HISTORY_DATA_ARR_LENGTH; - uint16_t maximum = 0; - TotalPowerHistory *p = mApp->getTotalPowerHistoryPtr(); + uint16_t max = 0; for (uint16_t fld = 0; fld < HISTORY_DATA_ARR_LENGTH; fld++) { - uint16_t value = p->valueAt(fld); + uint16_t value = mApp->getHistoryValue(HistoryStorageType::POWER, fld); obj[F("value")][fld] = value; - if (value > maximum) - maximum = value; + if (value > max) + max = value; } - obj[F("maximum")] = maximum; - obj[F("maximumDay")] = p->getMaximumDay(); + obj[F("max")] = max; + obj[F("maxDay")] = mApp->getHistoryMaxDay(); } void getYieldDayHistory(AsyncWebServerRequest *request, JsonObject obj) { getGeneric(request, obj.createNestedObject(F("generic"))); obj[F("refresh")] = 86400; // 1 day obj[F("datapoints")] = HISTORY_DATA_ARR_LENGTH; - uint16_t maximum = 0; - YieldDayHistory *p = mApp->getYieldDayHistoryPtr(); + uint16_t max = 0; for (uint16_t fld = 0; fld < HISTORY_DATA_ARR_LENGTH; fld++) { - uint16_t value = p->valueAt(fld); + uint16_t value = mApp->getHistoryValue(HistoryStorageType::YIELD, fld); obj[F("value")][fld] = value; - if (value > maximum) - maximum = value; - } - obj[F("maximum")] = maximum; + if (value > max) + max = value; + } + obj[F("max")] = max; } From e23daad910cfa69d90797cff23312ab39a1413ff Mon Sep 17 00:00:00 2001 From: rejoe2 Date: Tue, 9 Jan 2024 10:10:39 +0100 Subject: [PATCH 059/179] MI - add "get loss logic" * as we don't have info from inverter side, this is just dull tx/rx statistics from the recent cycle... --- src/hm/Communication.h | 39 ++++++++++++++++++++++++++++----------- src/hm/hmInverter.h | 25 ++++++++++++++++--------- src/hm/radio.h | 6 ++++-- 3 files changed, 48 insertions(+), 22 deletions(-) diff --git a/src/hm/Communication.h b/src/hm/Communication.h index d0878dd5..5c1b1f21 100644 --- a/src/hm/Communication.h +++ b/src/hm/Communication.h @@ -141,6 +141,8 @@ class Communication : public CommQueue<> { q->iv->radio->switchFrequency(q->iv, HOY_BOOT_FREQ_KHZ, (q->iv->config->frequency*FREQ_STEP_KHZ + HOY_BASE_FREQ_KHZ)); mWaitTime.startTimeMonitor(1000); } + if(IV_MI == q->iv->ivGen) + q->iv->mIvTxCnt++; } closeRequest(q, false); break; @@ -733,17 +735,16 @@ class Communication : public CommQueue<> { miNextRequest((p->packet[0] - ALL_FRAMES + 1), q); } else { q->iv->miMultiParts = 7; // indicate we are ready - //miComplete(q->iv); } } else if((p->packet[0] == (MI_REQ_CH1 + ALL_FRAMES)) && (q->iv->type == INV_TYPE_2CH)) { //addImportant(q->iv, MI_REQ_CH2); miNextRequest(MI_REQ_CH2, q); mHeu.evalTxChQuality(q->iv, true, (q->attemptsMax - 1 - q->attempts), q->iv->curFrmCnt); - //use also miMultiParts here for better statistics? - //mHeu.setGotFragment(q->iv); - } else { // first data msg for 1ch, 2nd for 2ch + q->iv->mIvRxCnt++; // statistics workaround... + + } else { // first data msg for 1ch, 2nd for 2ch q->iv->miMultiParts += 6; // indicate we are ready - //miComplete(q->iv); + } } @@ -757,13 +758,9 @@ class Communication : public CommQueue<> { DBGHEXLN(cmd); } - if(q->iv->miMultiParts == 7) { - //mHeu.setGotAll(q->iv); + if(q->iv->miMultiParts == 7) q->iv->radioStatistics.rxSuccess++; - } else - //mHeu.setGotFragment(q->iv); - /*iv->radioStatistics.rxFail++; // got no complete payload*/ - //q->iv->radioStatistics.retransmits++; + q->iv->radio->sendCmdPacket(q->iv, cmd, 0x00, true); mWaitTime.startTimeMonitor(MI_TIMEOUT); @@ -871,6 +868,26 @@ class Communication : public CommQueue<> { DPRINT_IVID(DBG_INFO, iv->id); DBGPRINTLN(F("got all data msgs")); } + + if (iv->mGetLossInterval >= AHOY_GET_LOSS_INTERVAL) { // initially mIvRxCnt = mIvTxCnt = 0 + iv->mGetLossInterval = 1; + iv->radioStatistics.ivSent = iv->mIvRxCnt + iv->mDtuTxCnt; // iv->mIvRxCnt is the nr. of additional answer frames, default we expect one frame per request + iv->radioStatistics.ivLoss = iv->radioStatistics.ivSent - iv->mDtuRxCnt; // this is what we didn't receive + iv->radioStatistics.dtuLoss = iv->mIvTxCnt; // this is somehow the requests w/o answers in that periode + iv->radioStatistics.dtuSent = iv->mDtuTxCnt; + if (mSerialDebug) { + DPRINT_IVID(DBG_INFO, iv->id); + DBGPRINTLN("DTU loss: " + + String (iv->radioStatistics.ivLoss) + "/" + + String (iv->radioStatistics.ivSent) + " frames for " + + String (iv->radioStatistics.dtuSent) + " requests"); + } + iv->mIvRxCnt = 0; // start new interval, iVRxCnt is abused to collect additional possible frames + iv->mIvTxCnt = 0; // start new interval, iVTxCnt is abused to collect nr. of unanswered requests + iv->mDtuRxCnt = 0; // start new interval + iv->mDtuTxCnt = 0; // start new interval + } + record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug); iv->setValue(iv->getPosByChFld(0, FLD_YD, rec), rec, calcYieldDayCh0(iv,0)); diff --git a/src/hm/hmInverter.h b/src/hm/hmInverter.h index 4e714cfd..aeb40c19 100644 --- a/src/hm/hmInverter.h +++ b/src/hm/hmInverter.h @@ -212,16 +212,22 @@ class Inverter { cb(RealTimeRunData_Debug, false); // get live data } } else { // MI - if(0 == getFwVersion()) + if(0 == getFwVersion()) { + mIvRxCnt +=2; cb(0x0f, false); // get firmware version; for MI, this makes part of polling the device software and hardware version number - else { + } else { record_t<> *rec = getRecordStruct(InverterDevInform_Simple); - if (getChannelFieldValue(CH0, FLD_PART_NUM, rec) == 0) + if (getChannelFieldValue(CH0, FLD_PART_NUM, rec) == 0) { cb(0x0f, false); // hard- and firmware version for missing HW part nr, delivered by frame 1 - else if((getChannelFieldValue(CH0, FLD_GRID_PROFILE_CODE, rec) == 0) && generalConfig->readGrid) // read grid profile + mIvRxCnt +=2; + } else if((getChannelFieldValue(CH0, FLD_GRID_PROFILE_CODE, rec) == 0) && generalConfig->readGrid) // read grid profile cb(0x10, false); // legacy GPF command - else + else { cb(((type == INV_TYPE_4CH) ? MI_REQ_4CH : MI_REQ_CH1), false); + mGetLossInterval++; + if (type != INV_TYPE_4CH) + mIvRxCnt++; // statistics workaround... + } } } } @@ -619,7 +625,7 @@ class Inverter { radioStatistics.dtuSent = txCnt + ((uint16_t)65535 - mIvTxCnt) + 1; else radioStatistics.dtuSent = txCnt - mIvTxCnt; - + radioStatistics.dtuLoss = radioStatistics.dtuSent - mDtuRxCnt; DPRINT_IVID(DBG_INFO, id); @@ -831,15 +837,16 @@ class Inverter { bool mDevControlRequest; // true if change needed uint8_t mGridLen = 0; uint8_t mGridProfile[MAX_GRID_LENGTH]; - uint8_t mGetLossInterval; // request iv every AHOY_GET_LOSS_INTERVAL RealTimeRunData_Debug - uint16_t mIvRxCnt = 0; - uint16_t mIvTxCnt = 0; uint8_t mAlarmNxtWrPos = 0; // indicates the position in array (rolling buffer) bool mNextLive = true; // first read live data after booting up then version etc. public: uint16_t mDtuRxCnt = 0; uint16_t mDtuTxCnt = 0; + uint8_t mGetLossInterval = 0; // request iv every AHOY_GET_LOSS_INTERVAL RealTimeRunData_Debug + uint16_t mIvRxCnt = 0; + uint16_t mIvTxCnt = 0; + }; template diff --git a/src/hm/radio.h b/src/hm/radio.h index 1ef32e05..e5eda128 100644 --- a/src/hm/radio.h +++ b/src/hm/radio.h @@ -42,8 +42,10 @@ class Radio { void prepareDevInformCmd(Inverter<> *iv, uint8_t cmd, uint32_t ts, uint16_t alarmMesId, bool isRetransmit, uint8_t reqfld=TX_REQ_INFO) { // might not be necessary to add additional arg. if(IV_MI == getIvGen(iv)) { - DPRINT(DBG_DEBUG, F("legacy cmd 0x")); - DPRINTLN(DBG_DEBUG,String(cmd, HEX)); + if(*mSerialDebug) { + DPRINT(DBG_DEBUG, F("legacy cmd 0x")); + DPRINTLN(DBG_DEBUG,String(cmd, HEX)); + } sendCmdPacket(iv, cmd, cmd, false, false); return; } From 3a8f37fd8eeb0c360949a476a7a3f998d7b85dee Mon Sep 17 00:00:00 2001 From: lumapu Date: Tue, 9 Jan 2024 20:37:10 +0100 Subject: [PATCH 060/179] fix compile errors --- src/app.h | 4 ++-- src/appInterface.h | 1 - src/plugins/history.h | 2 +- src/web/RestApi.h | 4 ++-- 4 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/app.h b/src/app.h index 4708ee5a..237b4034 100644 --- a/src/app.h +++ b/src/app.h @@ -244,8 +244,8 @@ class app : public IApp, public ah::Scheduler { Scheduler::setTimestamp(newTime); } - uint16_t getHistoryValue(HistoryType type, uint16_t i) { - return mHistory.valueAt(type, i); + uint16_t getHistoryValue(uint8_t type, uint16_t i) { + return mHistory.valueAt((HistoryStorageType)type, i); } uint16_t getHistoryMaxDay() { diff --git a/src/appInterface.h b/src/appInterface.h index a9c67147..a2831c9b 100644 --- a/src/appInterface.h +++ b/src/appInterface.h @@ -8,7 +8,6 @@ #include "defines.h" #include "hm/hmSystem.h" -#include "plugins/history.h" #if defined(ETHERNET) #include "AsyncWebServer_ESP32_W5500.h" #else diff --git a/src/plugins/history.h b/src/plugins/history.h index f068a127..9ed7860d 100644 --- a/src/plugins/history.h +++ b/src/plugins/history.h @@ -88,7 +88,7 @@ class HistoryData { } uint16_t valueAt(HistoryStorageType type, uint16_t i) { - settings_t *s = (HistoryStorageType::POWER == type) ? &mCurPwr : &mYieldDay; + storage_t *s = (HistoryStorageType::POWER == type) ? &mCurPwr : &mYieldDay; uint16_t idx = (s->dispIdx + i) % HISTORY_DATA_ARR_LENGTH; return s->data[idx]; } diff --git a/src/web/RestApi.h b/src/web/RestApi.h index 254b0ac4..6161fd90 100644 --- a/src/web/RestApi.h +++ b/src/web/RestApi.h @@ -781,7 +781,7 @@ class RestApi { obj[F("datapoints")] = HISTORY_DATA_ARR_LENGTH; uint16_t max = 0; for (uint16_t fld = 0; fld < HISTORY_DATA_ARR_LENGTH; fld++) { - uint16_t value = mApp->getHistoryValue(HistoryStorageType::POWER, fld); + uint16_t value = mApp->getHistoryValue((uint8_t)HistoryStorageType::POWER, fld); obj[F("value")][fld] = value; if (value > max) max = value; @@ -796,7 +796,7 @@ class RestApi { obj[F("datapoints")] = HISTORY_DATA_ARR_LENGTH; uint16_t max = 0; for (uint16_t fld = 0; fld < HISTORY_DATA_ARR_LENGTH; fld++) { - uint16_t value = mApp->getHistoryValue(HistoryStorageType::YIELD, fld); + uint16_t value = mApp->getHistoryValue((uint8_t)HistoryStorageType::YIELD, fld); obj[F("value")][fld] = value; if (value > max) max = value; From fa2028e4798897e0fc859200a9814730332bc16b Mon Sep 17 00:00:00 2001 From: lumapu Date: Tue, 9 Jan 2024 22:12:06 +0100 Subject: [PATCH 061/179] 0.8.50 * merge PR: added history charts to web #1336 --- src/CHANGES.md | 3 + src/defines.h | 2 +- src/web/RestApi.h | 2 - src/web/html/history.html | 222 ++++++++++++++------------------ src/web/html/save.html | 2 +- src/web/html/serial.html | 2 +- src/web/html/style.css | 12 +- src/web/html/visualization.html | 2 +- src/web/html/wizard.html | 2 +- src/web/lang.json | 52 +++++++- 10 files changed, 163 insertions(+), 138 deletions(-) diff --git a/src/CHANGES.md b/src/CHANGES.md index a11a4e2b..fd02a030 100644 --- a/src/CHANGES.md +++ b/src/CHANGES.md @@ -1,5 +1,8 @@ # Development Changes +## 0.8.50 - 2024-01-09 +* merge PR: added history charts to web #1336 + ## 0.8.49 - 2024-01-08 * fix send total values if inverter state is different from `OFF` #1331 * fix german language issues #1335 diff --git a/src/defines.h b/src/defines.h index b205297f..9d0e97c9 100644 --- a/src/defines.h +++ b/src/defines.h @@ -13,7 +13,7 @@ //------------------------------------- #define VERSION_MAJOR 0 #define VERSION_MINOR 8 -#define VERSION_PATCH 49 +#define VERSION_PATCH 50 //------------------------------------- typedef struct { diff --git a/src/web/RestApi.h b/src/web/RestApi.h index 26ca571f..96a4c5e4 100644 --- a/src/web/RestApi.h +++ b/src/web/RestApi.h @@ -794,7 +794,6 @@ class RestApi { void getPowerHistory(AsyncWebServerRequest *request, JsonObject obj) { getGeneric(request, obj.createNestedObject(F("generic"))); obj[F("refresh")] = mConfig->inst.sendInterval; - obj[F("datapoints")] = HISTORY_DATA_ARR_LENGTH; uint16_t max = 0; for (uint16_t fld = 0; fld < HISTORY_DATA_ARR_LENGTH; fld++) { uint16_t value = mApp->getHistoryValue((uint8_t)HistoryStorageType::POWER, fld); @@ -809,7 +808,6 @@ class RestApi { void getYieldDayHistory(AsyncWebServerRequest *request, JsonObject obj) { getGeneric(request, obj.createNestedObject(F("generic"))); obj[F("refresh")] = 86400; // 1 day - obj[F("datapoints")] = HISTORY_DATA_ARR_LENGTH; uint16_t max = 0; for (uint16_t fld = 0; fld < HISTORY_DATA_ARR_LENGTH; fld++) { uint16_t value = mApp->getHistoryValue((uint8_t)HistoryStorageType::YIELD, fld); diff --git a/src/web/html/history.html b/src/web/html/history.html index 5b6c1fb5..d0f33755 100644 --- a/src/web/html/history.html +++ b/src/web/html/history.html @@ -1,135 +1,113 @@ + + {#NAV_HISTORY} + {#HTML_HEADER} + + - - History - {#HTML_HEADER} - - + - - - - {#HTML_NAV} -
-
-

Total Power history

-
-
-

- Maximum day: W. Last value: W.
- Maximum graphics: W. Updated every seconds

-
-

Yield per day history

-
-
-

- Maximum value: Wh
- Updated every seconds

+ + {#HTML_NAV} +
+
+

{#TOTAL_POWER}

+
+
+

+ {#MAX_DAY}: W. {#LAST_VALUE}: W.
+ {#MAXIMUM}: W. {#UPDATED} {#SECONDS} +

+
+

{#TOTAL_YIELD_PER_DAY}

+
+
+

+ {#MAXIMUM}: Wh
+ {#UPDATED} {#SECONDS} +

+
+
+ {#HTML_FOOTER} -

Insert data into Yield per day history

-
- Insert data (*.json) i.e. from a saved "/api/yieldDayHistory" call -
- - -
-
-

-
-
- {#HTML_FOOTER} + + function parsePowerHistory(obj){ + if (null != obj) { + parseHistory(obj,"pwr", pwrExeOnce) + document.getElementById("pwrLast").innerHTML = mLastValue + document.getElementById("pwrMaxDay").innerHTML = obj.maxDay + } + if (pwrExeOnce) { + pwrExeOnce = false; + window.setInterval("getAjax('/api/powerHistory', parsePowerHistory)", mRefresh * 1000); - + setTimeout(() => { + getAjax("/api/yieldDayHistory", parseYieldDayHistory); + } , 20); + } + } + function parseYieldDayHistory(obj) { + if (null != obj) { + parseNav(obj.generic); + parseHistory(obj, "yd", ydExeOnce) + } + if (ydExeOnce) { + ydExeOnce = false; + window.setInterval("getAjax('/api/yieldDayHistory', parseYieldDayHistory)", mRefresh * 500); + } + } - \ No newline at end of file + getAjax("/api/powerHistory", parsePowerHistory); + + + diff --git a/src/web/html/save.html b/src/web/html/save.html index e5d5a67d..bfc9a12a 100644 --- a/src/web/html/save.html +++ b/src/web/html/save.html @@ -1,7 +1,7 @@ - Save + {#NAV_SAVE} {#HTML_HEADER} diff --git a/src/web/html/serial.html b/src/web/html/serial.html index ef7aa4c3..25940eb0 100644 --- a/src/web/html/serial.html +++ b/src/web/html/serial.html @@ -1,7 +1,7 @@ - Serial Console + {#NAV_WEBSERIAL} {#HTML_HEADER} diff --git a/src/web/html/style.css b/src/web/html/style.css index 3ad91a8b..ef581ffa 100644 --- a/src/web/html/style.css +++ b/src/web/html/style.css @@ -36,21 +36,17 @@ textarea { svg rect {fill: #0000AA;} svg.chart { background: #f2f2f2; - border: 2px solid gray; - padding: 1px; + border: 2px solid gray; + padding: 1px; } div.chartDivContainer { - padding: 1px; - margin: 1px; + padding: 1px; + margin: 1px; } div.chartdivContainer span { color: var(--fg2); } -div.chartDiv { - padding: 0px; - margin: 0px; -} .topnav { diff --git a/src/web/html/visualization.html b/src/web/html/visualization.html index 8099539c..15b0c7b5 100644 --- a/src/web/html/visualization.html +++ b/src/web/html/visualization.html @@ -1,7 +1,7 @@ - Live + {#NAV_LIVE} {#HTML_HEADER} diff --git a/src/web/html/wizard.html b/src/web/html/wizard.html index 674823b2..3df44dc4 100644 --- a/src/web/html/wizard.html +++ b/src/web/html/wizard.html @@ -1,7 +1,7 @@ - Setup Wizard + {#NAV_WIZARD} {#HTML_HEADER} diff --git a/src/web/lang.json b/src/web/lang.json index 9f1b9e5e..cb8ab8d6 100644 --- a/src/web/lang.json +++ b/src/web/lang.json @@ -3,13 +3,18 @@ { "name": "general", "list": [ + { + "token": "NAV_WIZARD", + "en": "Setup Wizard", + "de": "Daten" + }, { "token": "NAV_LIVE", "en": "Live", "de": "Daten" }, { - "token": "{#NAV_HISTORY}", + "token": "NAV_HISTORY", "en": "History", "de": "Verlauf" }, @@ -28,6 +33,11 @@ "en": "Documentation", "de": "Dokumentation" }, + { + "token": "NAV_SAVE", + "en": "save", + "de": "speichern" + }, { "token": "NAV_ABOUT", "en": "About", @@ -1334,6 +1344,46 @@ "de": "Fehler beim Speichern" } ] + }, + { + "name": "history.html", + "list": [ + { + "token": "TOTAL_POWER", + "en": "Total Power", + "de": "Gesamtleistung" + }, + { + "token": "TOTAL_YIELD_PER_DAY", + "en": "Total Yield per day", + "de": "Gesamtertrag pro Tag" + }, + { + "token": "MAX_DAY", + "en": "maximum day", + "de": "Tagesmaximum" + }, + { + "token": "LAST_VALUE", + "en": "last value", + "de": "letzter Wert" + }, + { + "token": "MAXIMUM", + "en": "maximum value", + "de": "Maximalwert" + }, + { + "token": "UPDATED", + "en": "Updated every", + "de": "aktualisiert alle" + }, + { + "token": "SECONDS", + "en": "seconds", + "de": "Sekunden" + } + ] } ] } From 2a01238bff8fb569ca3130fe7a3b7b50b8e49155 Mon Sep 17 00:00:00 2001 From: lumapu Date: Tue, 9 Jan 2024 22:23:16 +0100 Subject: [PATCH 062/179] small fix --- src/hm/Communication.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/hm/Communication.h b/src/hm/Communication.h index 5c1b1f21..cf0d0054 100644 --- a/src/hm/Communication.h +++ b/src/hm/Communication.h @@ -140,8 +140,7 @@ class Communication : public CommQueue<> { if((IV_HMS == q->iv->ivGen) || (IV_HMT == q->iv->ivGen)) { q->iv->radio->switchFrequency(q->iv, HOY_BOOT_FREQ_KHZ, (q->iv->config->frequency*FREQ_STEP_KHZ + HOY_BASE_FREQ_KHZ)); mWaitTime.startTimeMonitor(1000); - } - if(IV_MI == q->iv->ivGen) + } else if(IV_MI == q->iv->ivGen) q->iv->mIvTxCnt++; } closeRequest(q, false); From 281da4f5762793000b6b22289dbf4f2bce104b1d Mon Sep 17 00:00:00 2001 From: lumapu Date: Tue, 9 Jan 2024 22:31:03 +0100 Subject: [PATCH 063/179] 0.8.50 * merge PR: small display changes #1339 * merge PR: MI - add "get loss logic" #1341 --- src/CHANGES.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/CHANGES.md b/src/CHANGES.md index fd02a030..a15ba6ca 100644 --- a/src/CHANGES.md +++ b/src/CHANGES.md @@ -2,6 +2,10 @@ ## 0.8.50 - 2024-01-09 * merge PR: added history charts to web #1336 +* merge PR: small display changes #1339 +* merge PR: MI - add "get loss logic" #1341 +* translated `/history` +* fix translations in title of documents ## 0.8.49 - 2024-01-08 * fix send total values if inverter state is different from `OFF` #1331 From ce6013d6a0d2cab8aa0a3a69c18a8ec3fb178406 Mon Sep 17 00:00:00 2001 From: lumapu Date: Tue, 9 Jan 2024 22:40:02 +0100 Subject: [PATCH 064/179] 0.8.50 * small changes --- src/plugins/Display/Display_Mono.h | 83 +++++++++++++++--------------- src/web/lang.json | 4 +- 2 files changed, 44 insertions(+), 43 deletions(-) diff --git a/src/plugins/Display/Display_Mono.h b/src/plugins/Display/Display_Mono.h index 785f1334..62d5cc3a 100644 --- a/src/plugins/Display/Display_Mono.h +++ b/src/plugins/Display/Display_Mono.h @@ -155,16 +155,17 @@ class DisplayMono { } void resetPowerGraph() { - if (mPgData != nullptr) { - mPgMaxPwr = 0.0; - mPgLastPos = 0; - for (uint8_t i = 0; i < mPgWidth; i++) - mPgData[i] = 0.0; - } + if (mPgData != nullptr) { + mPgMaxPwr = 0.0; + mPgLastPos = 0; + for (uint8_t i = 0; i < mPgWidth; i++) { + mPgData[i] = 0.0; + } + } } uint8_t sss2pgpos(uint seconds_since_start) { - return(seconds_since_start * (mPgWidth - 1) / (mDisplayData->pGraphEndTime - mDisplayData->pGraphStartTime)); + return(seconds_since_start * (mPgWidth - 1) / (mDisplayData->pGraphEndTime - mDisplayData->pGraphStartTime)); } void calcPowerGraphValues() { @@ -186,60 +187,60 @@ class DisplayMono { } uint8_t getPowerGraphXpos(uint8_t p) { - if ((p <= mPgLastPos) && (mPgLastPos > 0)) + if ((p <= mPgLastPos) && (mPgLastPos > 0)) return((p * (mPgWidth - 1)) / mPgLastPos); // scaling of x-axis - else + else return(0); } uint8_t getPowerGraphYpos(uint8_t p) { - if (p < mPgWidth) - //return(((uint32_t) mPgData[p] * (uint32_t) mPgMaxAvailPower) * (uint32_t) mPgHeight / mPgMaxPwr / 255); // scaling of normalized data (0-255) to graph height + if (p < mPgWidth) return((mPgData[p] * (uint32_t) mPgHeight / mPgMaxPwr)); // scaling of data to graph height - else + else return(0); } void plotPowerGraph(uint8_t xoff, uint8_t yoff) { - // draw axes - mDisplay->drawLine(xoff, yoff, xoff, yoff - mPgHeight); // vertical axis - mDisplay->drawLine(xoff, yoff, xoff + mPgWidth, yoff); // horizontal axis - - // draw X scale - tmElements_t tm; - breakTime(mDisplayData->pGraphEndTime, tm); - uint8_t endHourPg = tm.Hour; - breakTime(mDisplayData->utcTs, tm); - uint8_t endHour = std::min(endHourPg, tm.Hour); - breakTime(mDisplayData->pGraphStartTime, tm); - tm.Hour += 1; - tm.Minute = 0; - tm.Second = 0; - for (; tm.Hour <= endHour; tm.Hour++) { + // draw axes + mDisplay->drawLine(xoff, yoff, xoff, yoff - mPgHeight); // vertical axis + mDisplay->drawLine(xoff, yoff, xoff + mPgWidth, yoff); // horizontal axis + + // draw X scale + tmElements_t tm; + breakTime(mDisplayData->pGraphEndTime, tm); + uint8_t endHourPg = tm.Hour; + breakTime(mDisplayData->utcTs, tm); + uint8_t endHour = std::min(endHourPg, tm.Hour); + breakTime(mDisplayData->pGraphStartTime, tm); + tm.Hour += 1; + tm.Minute = 0; + tm.Second = 0; + for (; tm.Hour <= endHour; tm.Hour++) { uint8_t x_pos_screen = getPowerGraphXpos(sss2pgpos((uint32_t) makeTime(tm) - mDisplayData->pGraphStartTime)); // scale horizontal axis mDisplay->drawPixel(xoff + x_pos_screen, yoff - 1); - } + } - // draw Y scale - uint16_t scale_y = 10; - uint32_t maxpwr_int = static_cast(std::round(mPgMaxPwr)); - if (maxpwr_int > 100) + // draw Y scale + uint16_t scale_y = 10; + uint32_t maxpwr_int = static_cast(std::round(mPgMaxPwr)); + if (maxpwr_int > 100) scale_y = 100; - for (uint32_t i = scale_y; i <= maxpwr_int; i += scale_y) { + + for (uint32_t i = scale_y; i <= maxpwr_int; i += scale_y) { uint8_t ypos = yoff - static_cast(std::round(i * (float) mPgHeight / mPgMaxPwr)); // scale vertical axis mDisplay->drawPixel(xoff + 1, ypos); - } + } - // draw curve - for (uint8_t i = 1; i <= mPgLastPos; i++) { + // draw curve + for (uint8_t i = 1; i <= mPgLastPos; i++) { mDisplay->drawLine(xoff + getPowerGraphXpos(i - 1), yoff - getPowerGraphYpos(i - 1), xoff + getPowerGraphXpos(i), yoff - getPowerGraphYpos(i)); - } + } - // print max power value - mDisplay->setFont(u8g2_font_4x6_tr); - snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%dW", static_cast(std::round(mPgMaxPwr))); - mDisplay->drawStr(xoff + 3, yoff - mPgHeight + 5, mFmtText); + // print max power value + mDisplay->setFont(u8g2_font_4x6_tr); + snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%dW", static_cast(std::round(mPgMaxPwr))); + mDisplay->drawStr(xoff + 3, yoff - mPgHeight + 5, mFmtText); } // pixelshift screensaver with wipe effect diff --git a/src/web/lang.json b/src/web/lang.json index 8868148d..35e99b43 100644 --- a/src/web/lang.json +++ b/src/web/lang.json @@ -1261,12 +1261,12 @@ { "token": "INV_LOSS_RATE", "en": "Inverter loss rate", - "de": "Wechselrichter Empfangsqualität" + "de": "Wechselrichter Verlustrate" }, { "token": "DTU_LOSS_RATE", "en": "DTU loss rate", - "de": "DTU Empfangsqualität" + "de": "DTU Verlustrate" }, { "token": "RADIO_STAT_MODAL", From d2bf74ed3911bdae0839b4a7282da8d26278ed9b Mon Sep 17 00:00:00 2001 From: lumapu Date: Tue, 9 Jan 2024 23:09:08 +0100 Subject: [PATCH 065/179] 0.8.50 * added translations for error messages #1343 --- src/CHANGES.md | 1 + src/web/RestApi.h | 37 +++++++++++------------ src/web/lang.h | 75 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 93 insertions(+), 20 deletions(-) create mode 100644 src/web/lang.h diff --git a/src/CHANGES.md b/src/CHANGES.md index a15ba6ca..fd8b2ec7 100644 --- a/src/CHANGES.md +++ b/src/CHANGES.md @@ -6,6 +6,7 @@ * merge PR: MI - add "get loss logic" #1341 * translated `/history` * fix translations in title of documents +* added translations for error messages #1343 ## 0.8.49 - 2024-01-08 * fix send total values if inverter state is different from `OFF` #1331 diff --git a/src/web/RestApi.h b/src/web/RestApi.h index 96a4c5e4..1ed55a93 100644 --- a/src/web/RestApi.h +++ b/src/web/RestApi.h @@ -15,6 +15,7 @@ #include "../appInterface.h" #include "../hm/hmSystem.h" #include "../utils/helper.h" +#include "lang.h" #include "AsyncJson.h" #if defined(ETHERNET) #include "AsyncWebServer_ESP32_W5500.h" @@ -172,15 +173,15 @@ class RestApi { root[F("success")] = setSetup(obj, root); else { root[F("success")] = false; - root[F("error")] = "Path not found: " + path; + root[F("error")] = F(PATH_NOT_FOUND) + path; } } else { switch (err.code()) { case DeserializationError::Ok: break; - case DeserializationError::IncompleteInput: root[F("error")] = F("Incomplete input"); 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; + case DeserializationError::IncompleteInput: root[F("error")] = F(INCOMPLETE_INPUT); break; + case DeserializationError::InvalidInput: root[F("error")] = F(INVALID_INPUT); break; + case DeserializationError::NoMemory: root[F("error")] = F(NOT_ENOUGH_MEM); break; + default: root[F("error")] = F(DESER_FAILED); break; } } @@ -402,7 +403,7 @@ class RestApi { void getIvStatistis(JsonObject obj, uint8_t id) { Inverter<> *iv = mSys->getInverterByPos(id); if(NULL == iv) { - obj[F("error")] = F("inverter not found!"); + obj[F("error")] = F(INV_NOT_FOUND); return; } obj[F("name")] = String(iv->config->name); @@ -421,7 +422,7 @@ class RestApi { void getIvPowerLimitAck(JsonObject obj, uint8_t id) { Inverter<> *iv = mSys->getInverterByPos(id); if(NULL == iv) { - obj[F("error")] = F("inverter not found!"); + obj[F("error")] = F(INV_NOT_FOUND); return; } obj["ack"] = (bool)iv->powerLimitAck; @@ -474,7 +475,7 @@ class RestApi { void getInverter(JsonObject obj, uint8_t id) { Inverter<> *iv = mSys->getInverterByPos(id); if(NULL == iv) { - obj[F("error")] = F("inverter not found!"); + obj[F("error")] = F(INV_NOT_FOUND); return; } @@ -537,7 +538,7 @@ class RestApi { void getIvAlarms(JsonObject obj, uint8_t id) { Inverter<> *iv = mSys->getInverterByPos(id); if(NULL == iv) { - obj[F("error")] = F("inverter not found!"); + obj[F("error")] = F(INV_NOT_FOUND); return; } @@ -560,7 +561,7 @@ class RestApi { void getIvVersion(JsonObject obj, uint8_t id) { Inverter<> *iv = mSys->getInverterByPos(id); if(NULL == iv) { - obj[F("error")] = F("inverter not found!"); + obj[F("error")] = F(INV_NOT_FOUND); return; } @@ -732,14 +733,10 @@ class RestApi { obj[F("disNightComm")] = disNightCom; JsonArray warn = obj.createNestedArray(F("warnings")); - if(!mRadioNrf->isChipConnected() && mConfig->nrf.enabled) - warn.add(F("your NRF24 module can't be reached, check the wiring, pinout and enable")); - if(!mApp->getSettingsValid()) - warn.add(F("your settings are invalid")); if(mApp->getRebootRequestState()) - warn.add(F("reboot your ESP to apply all your configuration changes")); + warn.add(F(REBOOT_ESP_APPLY_CHANGES)); if(0 == mApp->getTimestamp()) - warn.add(F("time not set. No communication to inverter possible")); + warn.add(F(TIME_NOT_SET)); } void getSetup(AsyncWebServerRequest *request, JsonObject obj) { @@ -823,7 +820,7 @@ class RestApi { Inverter<> *iv = mSys->getInverterByPos(jsonIn[F("id")]); bool accepted = true; if(NULL == iv) { - jsonOut[F("error")] = F("inverter index invalid: ") + jsonIn[F("id")].as(); + jsonOut[F("error")] = F(INV_INDEX_INVALID) + jsonIn[F("id")].as(); return false; } jsonOut[F("id")] = jsonIn[F("id")]; @@ -848,12 +845,12 @@ class RestApi { DPRINTLN(DBG_INFO, F("dev cmd")); iv->setDevCommand(jsonIn[F("val")].as()); } else { - jsonOut[F("error")] = F("unknown cmd: '") + jsonIn["cmd"].as() + "'"; + jsonOut[F("error")] = F(UNKNOWN_CMD) + jsonIn["cmd"].as() + "'"; return false; } if(!accepted) { - jsonOut[F("error")] = F("inverter does not accept dev control request at this moment"); + jsonOut[F("error")] = F(INV_DOES_NOT_ACCEPT_LIMIT_AT_MOMENT); return false; } @@ -902,7 +899,7 @@ class RestApi { iv->config->add2Total = jsonIn[F("add2total")]; mApp->saveSettings(false); // without reboot } else { - jsonOut[F("error")] = F("unknown cmd"); + jsonOut[F("error")] = F(UNKNOWN_CMD); return false; } diff --git a/src/web/lang.h b/src/web/lang.h new file mode 100644 index 00000000..cb955110 --- /dev/null +++ b/src/web/lang.h @@ -0,0 +1,75 @@ +//----------------------------------------------------------------------------- +// 2024 Ahoy, https://ahoydtu.de +// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/4.0/deed +//----------------------------------------------------------------------------- + +#ifndef __LANG_H__ +#define __LANG_H__ + +#ifdef LANG_DE + #define REBOOT_ESP_APPLY_CHANGES "starte AhoyDTU neu, um die Änderungen zu speichern" +#else /*LANG_EN*/ + #define REBOOT_ESP_APPLY_CHANGES "reboot AhoyDTU to apply all your configuration changes"; +#endif + +#ifdef LANG_DE + #define TIME_NOT_SET "keine gültige Zeit, daher keine Kommunikation zum Wechselrichter möglich" +#else /*LANG_EN*/ + #define TIME_NOT_SET "time not set. No communication to inverter possible"; +#endif + +#ifdef LANG_DE + #define INV_INDEX_INVALID "Wechselrichterindex ungültig; " +#else /*LANG_EN*/ + #define INV_INDEX_INVALID "inverter index invalid: " +#endif + +#ifdef LANG_DE + #define UNKNOWN_CMD "unbekanntes Kommando: '" +#else /*LANG_EN*/ + #define UNKNOWN_CMD "unknown cmd: '" +#endif + +#ifdef LANG_DE + #define INV_DOES_NOT_ACCEPT_LIMIT_AT_MOMENT "Leistungsbegrenzung / Ansteuerung aktuell nicht möglich" +#else /*LANG_EN*/ + #define INV_DOES_NOT_ACCEPT_LIMIT_AT_MOMENT "inverter does not accept dev control request at this moment" +#endif + +#ifdef LANG_DE + #define PATH_NOT_FOUND "Pfad nicht gefunden: " +#else /*LANG_EN*/ + #define PATH_NOT_FOUND "Path not found: " +#endif + +#ifdef LANG_DE + #define INCOMPLETE_INPUT "Unvollständige Eingabe" +#else /*LANG_EN*/ + #define INCOMPLETE_INPUT "Incomplete input" +#endif + +#ifdef LANG_DE + #define INVALID_INPUT "Ungültige Eingabe" +#else /*LANG_EN*/ + #define INVALID_INPUT "Invalid input" +#endif + +#ifdef LANG_DE + #define NOT_ENOUGH_MEM "nicht genügend Speicher" +#else /*LANG_EN*/ + #define NOT_ENOUGH_MEM "Not enough memory" +#endif + +#ifdef LANG_DE + #define DESER_FAILED "Deserialisierung fehlgeschlagen" +#else /*LANG_EN*/ + #define DESER_FAILED "Deserialization failed" +#endif + +#ifdef LANG_DE + #define INV_NOT_FOUND "Wechselrichter nicht gefunden!" +#else /*LANG_EN*/ + #define INV_NOT_FOUND "inverter not found!" +#endif + +#endif /*__LANG_H__*/ From e4cb948be48a6b40b9bbe37865fc43ba0a4fd903 Mon Sep 17 00:00:00 2001 From: lumapu Date: Tue, 9 Jan 2024 23:15:28 +0100 Subject: [PATCH 066/179] 0.8.50 fix English versions --- src/web/lang.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/web/lang.h b/src/web/lang.h index cb955110..a82c7888 100644 --- a/src/web/lang.h +++ b/src/web/lang.h @@ -9,13 +9,13 @@ #ifdef LANG_DE #define REBOOT_ESP_APPLY_CHANGES "starte AhoyDTU neu, um die Änderungen zu speichern" #else /*LANG_EN*/ - #define REBOOT_ESP_APPLY_CHANGES "reboot AhoyDTU to apply all your configuration changes"; + #define REBOOT_ESP_APPLY_CHANGES "reboot AhoyDTU to apply all your configuration changes" #endif #ifdef LANG_DE #define TIME_NOT_SET "keine gültige Zeit, daher keine Kommunikation zum Wechselrichter möglich" #else /*LANG_EN*/ - #define TIME_NOT_SET "time not set. No communication to inverter possible"; + #define TIME_NOT_SET "time not set. No communication to inverter possible" #endif #ifdef LANG_DE From 76fa30503d4a3a5096a01b42ee6fa5d26173a396 Mon Sep 17 00:00:00 2001 From: lumapu Date: Wed, 10 Jan 2024 23:53:24 +0100 Subject: [PATCH 067/179] 0.8.51 * fix translation #1346 * further improve sending active power control command faster #1332 * added history protection mask --- ahoy.code-workspace | 45 -------------------------------- scripts/auto_firmware_version.py | 3 ++- src/CHANGES.md | 5 ++++ src/app.cpp | 2 +- src/app.h | 4 +++ src/appInterface.h | 5 ++-- src/config/settings.h | 8 +++--- src/defines.h | 2 +- src/hm/hmInverter.h | 7 +++-- src/hm/hmSystem.h | 10 ++++--- src/utils/scheduler.h | 2 +- src/web/RestApi.h | 3 ++- src/web/html/index.html | 2 +- src/web/html/setup.html | 6 ++--- src/web/lang.json | 6 ++--- 15 files changed, 41 insertions(+), 69 deletions(-) delete mode 100644 ahoy.code-workspace diff --git a/ahoy.code-workspace b/ahoy.code-workspace deleted file mode 100644 index 20d5909e..00000000 --- a/ahoy.code-workspace +++ /dev/null @@ -1,45 +0,0 @@ -{ - "folders": [ - { - "path": "." - }, - { - "path": "src" - } - ], - "settings": { - "files.associations": { - "algorithm": "cpp", - "array": "cpp", - "chrono": "cpp", - "deque": "cpp", - "format": "cpp", - "forward_list": "cpp", - "functional": "cpp", - "initializer_list": "cpp", - "iterator": "cpp", - "list": "cpp", - "memory": "cpp", - "queue": "cpp", - "random": "cpp", - "regex": "cpp", - "vector": "cpp", - "xhash": "cpp", - "xlocmon": "cpp", - "xlocnum": "cpp", - "xmemory": "cpp", - "xstring": "cpp", - "xtree": "cpp", - "xutility": "cpp", - "*.tcc": "cpp", - "string": "cpp", - "unordered_map": "cpp", - "unordered_set": "cpp", - "string_view": "cpp", - "sstream": "cpp", - "istream": "cpp", - "ostream": "cpp" - }, - "editor.formatOnSave": false - } -} \ No newline at end of file diff --git a/scripts/auto_firmware_version.py b/scripts/auto_firmware_version.py index c4ab270d..75bf7379 100644 --- a/scripts/auto_firmware_version.py +++ b/scripts/auto_firmware_version.py @@ -21,7 +21,8 @@ def get_firmware_specifier_build_flag(): except: build_version = "g0000000" - build_flag = "-D AUTO_GIT_HASH=\\\"" + build_version[1:] + "\\\"" + build_flag = "-D AUTO_GIT_HASH=\\\"" + build_version[1:] + "\\\" " + build_flag += "-DENV_NAME=\\\"" + env["PIOENV"] + "\\\" "; print ("Firmware Revision: " + build_version) return (build_flag) diff --git a/src/CHANGES.md b/src/CHANGES.md index fd8b2ec7..89442499 100644 --- a/src/CHANGES.md +++ b/src/CHANGES.md @@ -1,5 +1,10 @@ # Development Changes +## 0.8.51 - 2024-01-10 +* fix translation #1346 +* further improve sending active power control command faster #1332 +* added history protection mask + ## 0.8.50 - 2024-01-09 * merge PR: added history charts to web #1336 * merge PR: small display changes #1339 diff --git a/src/app.cpp b/src/app.cpp index cae48149..88135099 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -53,7 +53,7 @@ void app::setup() { mCommunication.setup(&mTimestamp, &mConfig->serial.debug, &mConfig->serial.privacyLog, &mConfig->serial.printWholeTrace, &mConfig->inst.gapMs); mCommunication.addPayloadListener(std::bind(&app::payloadEventListener, this, std::placeholders::_1, std::placeholders::_2)); mCommunication.addPowerLimitAckListener([this] (Inverter<> *iv) { mMqtt.setPowerLimitAck(iv); }); - mSys.setup(&mTimestamp, &mConfig->inst); + mSys.setup(&mTimestamp, &mConfig->inst, this); for (uint8_t i = 0; i < MAX_NUM_INVERTERS; i++) { initInverter(i); } diff --git a/src/app.h b/src/app.h index a7a88609..a4e51113 100644 --- a/src/app.h +++ b/src/app.h @@ -307,6 +307,10 @@ class app : public IApp, public ah::Scheduler { #endif /* defined(ETHERNET) */ void updateNtp(void); + void triggerTickSend() { + once(std::bind(&app::tickSend, this), 0, "tSend"); + } + void tickCalcSunrise(void); void tickIVCommunication(void); void tickSun(void); diff --git a/src/appInterface.h b/src/appInterface.h index b812f5fc..5feccd3c 100644 --- a/src/appInterface.h +++ b/src/appInterface.h @@ -1,5 +1,5 @@ //----------------------------------------------------------------------------- -// 2022 Ahoy, https://ahoydtu.de +// 2024 Ahoy, https://ahoydtu.de // Creative Commons - https://creativecommons.org/licenses/by-nc-sa/4.0/deed //----------------------------------------------------------------------------- @@ -7,7 +7,6 @@ #define __IAPP_H__ #include "defines.h" -#include "hm/hmSystem.h" #if defined(ETHERNET) #include "AsyncWebServer_ESP32_W5500.h" #else @@ -47,6 +46,8 @@ class IApp { virtual void getSchedulerInfo(uint8_t *max) = 0; virtual void getSchedulerNames() = 0; + virtual void triggerTickSend() = 0; + virtual bool getRebootRequestState() = 0; virtual bool getSettingsValid() = 0; virtual void setMqttDiscoveryFlag() = 0; diff --git a/src/config/settings.h b/src/config/settings.h index b8d0d1e9..0e690ede 100644 --- a/src/config/settings.h +++ b/src/config/settings.h @@ -39,9 +39,9 @@ #define PROT_MASK_SETUP 0x0008 #define PROT_MASK_UPDATE 0x0010 #define PROT_MASK_SYSTEM 0x0020 -#define PROT_MASK_API 0x0040 -#define PROT_MASK_MQTT 0x0080 -#define PROT_MASK_HISTORY 0x0100 +#define PROT_MASK_HISTORY 0x0040 +#define PROT_MASK_API 0x0080 +#define PROT_MASK_MQTT 0x0100 #define DEF_PROT_INDEX 0x0001 #define DEF_PROT_LIVE 0x0000 @@ -49,9 +49,9 @@ #define DEF_PROT_SETUP 0x0008 #define DEF_PROT_UPDATE 0x0010 #define DEF_PROT_SYSTEM 0x0020 +#define DEF_PROT_HISTORY 0x0000 #define DEF_PROT_API 0x0000 #define DEF_PROT_MQTT 0x0000 -#define DEF_PROT_HISTORY 0x0000 typedef struct { diff --git a/src/defines.h b/src/defines.h index 9d0e97c9..91fee041 100644 --- a/src/defines.h +++ b/src/defines.h @@ -13,7 +13,7 @@ //------------------------------------- #define VERSION_MAJOR 0 #define VERSION_MINOR 8 -#define VERSION_PATCH 50 +#define VERSION_PATCH 51 //------------------------------------- typedef struct { diff --git a/src/hm/hmInverter.h b/src/hm/hmInverter.h index aeb40c19..4b2cc40d 100644 --- a/src/hm/hmInverter.h +++ b/src/hm/hmInverter.h @@ -1,5 +1,5 @@ //----------------------------------------------------------------------------- -// 2024 Ahoy, https://www.mikrocontroller.net/topic/525778 +// 2024 Ahoy, https://ahoydtu.de // Creative Commons - http://creativecommons.org/licenses/by-nc-sa/4.0/deed //----------------------------------------------------------------------------- @@ -14,6 +14,7 @@ #define MAX_GRID_LENGTH 150 #include "hmDefines.h" +#include "../appInterface.h" #include "HeuristicInv.h" #include "../hms/hmsDefines.h" #include @@ -148,8 +149,9 @@ class Inverter { bool commEnabled; // 'pause night communication' sets this field to false uint32_t tsMaxAcPower; // holds the timestamp when the MaxAC power was seen - static uint32_t *timestamp; // system timestamp + static uint32_t *timestamp; // system timestamp static cfgInst_t *generalConfig; // general inverter configuration from setup + static IApp *app; // pointer to app interface public: @@ -286,6 +288,7 @@ class Inverter { if(isConnected) { mDevControlRequest = true; devControlCmd = cmd; + app->triggerTickSend(); } return isConnected; } diff --git a/src/hm/hmSystem.h b/src/hm/hmSystem.h index b86f8d08..6ff741bf 100644 --- a/src/hm/hmSystem.h +++ b/src/hm/hmSystem.h @@ -1,11 +1,12 @@ //----------------------------------------------------------------------------- -// 2023 Ahoy, https://github.com/lumpapu/ahoy -// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/ +// 2024 Ahoy, https://ahoydtu.de +// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/4.0/deed //----------------------------------------------------------------------------- #ifndef __HM_SYSTEM_H__ #define __HM_SYSTEM_H__ +#include "../appInterface.h" #include "hmInverter.h" #include @@ -14,9 +15,10 @@ class HmSystem { public: HmSystem() {} - void setup(uint32_t *timestamp, cfgInst_t *config) { - mInverter[0].timestamp = timestamp; + void setup(uint32_t *timestamp, cfgInst_t *config, IApp *app) { + mInverter[0].timestamp = timestamp; mInverter[0].generalConfig = config; + mInverter[0].app = app; } void addInverter(uint8_t id, std::function *iv)> cb) { diff --git a/src/utils/scheduler.h b/src/utils/scheduler.h index 23af29f8..9a01cda8 100644 --- a/src/utils/scheduler.h +++ b/src/utils/scheduler.h @@ -121,7 +121,7 @@ namespace ah { uint16_t mTsMillis; private: - inline uint8_t addTicker(scdCb c, uint32_t timeout, uint32_t reload, bool isTimestamp, const char *name) { + uint8_t addTicker(scdCb c, uint32_t timeout, uint32_t reload, bool isTimestamp, const char *name) { for (uint8_t i = 0; i < MAX_NUM_TICKER; i++) { if (!mTickerInUse[i]) { mTickerInUse[i] = true; diff --git a/src/web/RestApi.h b/src/web/RestApi.h index 1ed55a93..73216ed5 100644 --- a/src/web/RestApi.h +++ b/src/web/RestApi.h @@ -1,6 +1,6 @@ //----------------------------------------------------------------------------- // 2024 Ahoy, https://ahoydtu.de -// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/ +// Creative Commons - https://creativecommons.org/licenses/by-nc-sa/4.0/deed //----------------------------------------------------------------------------- #ifndef __WEB_API_H__ @@ -255,6 +255,7 @@ class RestApi { obj[F("ts_now")] = mApp->getTimestamp(); obj[F("version")] = String(mApp->getVersion()); obj[F("build")] = String(AUTO_GIT_HASH); + obj[F("env")] = String(ENV_NAME); obj[F("menu_prot")] = mApp->getProtection(request); obj[F("menu_mask")] = (uint16_t)(mConfig->sys.protectionMask ); obj[F("menu_protEn")] = (bool) (strlen(mConfig->sys.adminPwd) > 0); diff --git a/src/web/html/index.html b/src/web/html/index.html index 6ad2c787..99a42089 100644 --- a/src/web/html/index.html +++ b/src/web/html/index.html @@ -26,7 +26,7 @@
  • {#DISCUSS} Discord
  • {#REPORT} {#ISSUES}
  • {#CONTRIBUTE} {#DOCUMENTATION}
  • -
  • Download & Test {#DEV_FIRMWARE}, {#DEV_CHANGELOG}
  • +
  • Download & Test {#DEV_FIRMWARE}, {#DEV_CHANGELOG}
  • {#DON_MAKE} {#DONATION}
  • diff --git a/src/web/html/setup.html b/src/web/html/setup.html index 00b0477c..62c52b3a 100644 --- a/src/web/html/setup.html +++ b/src/web/html/setup.html @@ -254,7 +254,7 @@
    -
    {MQTT_PASSWORD}
    +
    {#MQTT_PASSWORD}
    @@ -630,9 +630,9 @@ if(!obj["pwd_set"]) e.value = ""; var d = document.getElementById("prot_mask"); - var a = ["Index", "{#NAV_LIVE}", "{#NAV_WEBSERIAL}", "{#NAV_SETTINGS}", "Update", "System"]; + var a = ["Index", "{#NAV_LIVE}", "{#NAV_WEBSERIAL}", "{#NAV_SETTINGS}", "Update", "System", "{#NAV_HISTORY}"]; var el = []; - for(var i = 0; i < 6; i++) { + for(var i = 0; i < 7; i++) { var chk = ((obj["prot_mask"] & (1 << i)) == (1 << i)); el.push(mlCb("protMask" + i, a[i], chk)) } diff --git a/src/web/lang.json b/src/web/lang.json index 35e99b43..42c5e98a 100644 --- a/src/web/lang.json +++ b/src/web/lang.json @@ -136,7 +136,7 @@ { "token": "REBOOT_AT_MIDNIGHT", "en": "Reboot Ahoy at midnight", - "de": "um Mitternacht neu starten" + "de": "mitternachts neu starten" }, { "token": "DARK_MODE", @@ -291,7 +291,7 @@ { "token": "INV_RESET_MAX_MIDNIGHT", "en": "Reset 'max' values at midnight", - "de": "Maximalwerte bei Sonnenuntergang zurücksetzen" + "de": "Maximalwerte mitternachts zurücksetzen" }, { "token": "INV_START_WITHOUT_TIME", @@ -1096,7 +1096,7 @@ { "token": "APC", "en": "APC", - "de": "Begr." + "de": "Limit" }, { "token": "ALARMS", From 9a8d8560db5b31087735caaadd3be6c13e0759e0 Mon Sep 17 00:00:00 2001 From: you69man Date: Wed, 10 Jan 2024 22:57:47 +0100 Subject: [PATCH 068/179] possibly fix power graph problems --- src/plugins/Display/Display_Mono.h | 9 +++++++-- src/plugins/Display/Display_Mono_128X64.h | 5 +++-- src/plugins/Display/Display_Mono_84X48.h | 5 +++-- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/plugins/Display/Display_Mono.h b/src/plugins/Display/Display_Mono.h index 62d5cc3a..bd3ee30e 100644 --- a/src/plugins/Display/Display_Mono.h +++ b/src/plugins/Display/Display_Mono.h @@ -119,7 +119,7 @@ class DisplayMono { case DispSwitchState::TEXT: if (mDispSwitchTime.isTimeout()) { mDispSwitchState = DispSwitchState::GRAPH; - mDispSwitchTime.startTimeMonitor(150 * mCfg->graph_ratio); // mGraphRatio: 0-100 Gesamtperiode 15000 ms + mDispSwitchTime.startTimeMonitor(150 * mCfg->graph_ratio); // graph_ratio: 0-100 Gesamtperiode 15000 ms change = true; } break; @@ -135,6 +135,7 @@ class DisplayMono { } void initPowerGraph(uint8_t width, uint8_t height) { + DBGPRINTLN("---- Init Power Graph ----"); mPgWidth = width; mPgHeight = height; mPgData = new float[mPgWidth]; @@ -205,6 +206,10 @@ class DisplayMono { mDisplay->drawLine(xoff, yoff, xoff, yoff - mPgHeight); // vertical axis mDisplay->drawLine(xoff, yoff, xoff + mPgWidth, yoff); // horizontal axis + // do not draw as long as time is not set correctly and no data was received + if ((mDisplayData->pGraphStartTime == 0) || (mDisplayData->pGraphEndTime == 0) || (mDisplayData->utcTs < 1) || (mPgMaxPwr < 1) || (mPgLastPos < 1)) + return; + // draw X scale tmElements_t tm; breakTime(mDisplayData->pGraphEndTime, tm); @@ -234,7 +239,7 @@ class DisplayMono { // draw curve for (uint8_t i = 1; i <= mPgLastPos; i++) { mDisplay->drawLine(xoff + getPowerGraphXpos(i - 1), yoff - getPowerGraphYpos(i - 1), - xoff + getPowerGraphXpos(i), yoff - getPowerGraphYpos(i)); + xoff + getPowerGraphXpos(i), yoff - getPowerGraphYpos(i)); } // print max power value diff --git a/src/plugins/Display/Display_Mono_128X64.h b/src/plugins/Display/Display_Mono_128X64.h index b4c1cfe4..2d5c13c2 100644 --- a/src/plugins/Display/Display_Mono_128X64.h +++ b/src/plugins/Display/Display_Mono_128X64.h @@ -58,7 +58,8 @@ class DisplayMono128X64 : public DisplayMono { widthShrink = (mCfg->screenSaver == 1) ? pixelShiftRange : 0; // shrink graphwidth for pixelshift screensaver - initPowerGraph(mDispWidth - 22 - widthShrink, mLineYOffsets[graph_last_line] - mLineYOffsets[graph_first_line - 1] - 2); + if (mCfg->graph_ratio > 0) + initPowerGraph(mDispWidth - 22 - widthShrink, mLineYOffsets[graph_last_line] - mLineYOffsets[graph_first_line - 1] - 2); printText("Ahoy!", l_Ahoy, 0xff); printText("ahoydtu.de", l_Website, 0xff); @@ -172,7 +173,7 @@ class DisplayMono128X64 : public DisplayMono { printText(mFmtText, l_YieldTotal, 0xff); } - if (mDispSwitchState == DispSwitchState::GRAPH) { + if ((mCfg->graph_ratio > 0) && (mDispSwitchState == DispSwitchState::GRAPH)) { // plot power graph plotPowerGraph((mDispWidth - mPgWidth) / 2 + mPixelshift, mLineYOffsets[graph_last_line] - 1); } diff --git a/src/plugins/Display/Display_Mono_84X48.h b/src/plugins/Display/Display_Mono_84X48.h index 51c5eafe..d9f1d98d 100644 --- a/src/plugins/Display/Display_Mono_84X48.h +++ b/src/plugins/Display/Display_Mono_84X48.h @@ -46,7 +46,8 @@ class DisplayMono84X48 : public DisplayMono { break; } - initPowerGraph(mDispWidth - 16, mLineYOffsets[graph_last_line] - mLineYOffsets[graph_first_line - 1] - 2); + if (mCfg->graph_ratio > 0) + initPowerGraph(mDispWidth - 16, mLineYOffsets[graph_last_line] - mLineYOffsets[graph_first_line - 1] - 2); printText("Ahoy!", l_Ahoy, 0xff); printText("ahoydtu.de", l_Website, 0xff); @@ -134,7 +135,7 @@ class DisplayMono84X48 : public DisplayMono { printText(mFmtText, l_YieldTotal, 0xff); } - if (mDispSwitchState == DispSwitchState::GRAPH) { + if ((mCfg->graph_ratio > 0) && (mDispSwitchState == DispSwitchState::GRAPH)) { // plot power graph plotPowerGraph(8, mLineYOffsets[graph_last_line] - 1); } From ed3e93274e4854bec49b0d1da4430d1e009f4df9 Mon Sep 17 00:00:00 2001 From: lumapu Date: Thu, 11 Jan 2024 00:20:55 +0100 Subject: [PATCH 069/179] 0.8.51 * added history protection mask * merge PR: display graph improvements #1347 --- src/CHANGES.md | 1 + src/hm/hmInverter.h | 4 ++-- src/hm/hmSystem.h | 2 +- src/plugins/Display/Display_Mono.h | 4 ++-- src/web/RestApi.h | 2 ++ 5 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/CHANGES.md b/src/CHANGES.md index 89442499..ca123e3b 100644 --- a/src/CHANGES.md +++ b/src/CHANGES.md @@ -4,6 +4,7 @@ * fix translation #1346 * further improve sending active power control command faster #1332 * added history protection mask +* merge PR: display graph improvements #1347 ## 0.8.50 - 2024-01-09 * merge PR: added history charts to web #1336 diff --git a/src/hm/hmInverter.h b/src/hm/hmInverter.h index 4b2cc40d..1f623d85 100644 --- a/src/hm/hmInverter.h +++ b/src/hm/hmInverter.h @@ -151,7 +151,7 @@ class Inverter { static uint32_t *timestamp; // system timestamp static cfgInst_t *generalConfig; // general inverter configuration from setup - static IApp *app; // pointer to app interface + //static IApp *app; // pointer to app interface public: @@ -288,7 +288,7 @@ class Inverter { if(isConnected) { mDevControlRequest = true; devControlCmd = cmd; - app->triggerTickSend(); + //app->triggerTickSend(); // done in RestApi.h, because of "chicken-and-egg problem ;-)" } return isConnected; } diff --git a/src/hm/hmSystem.h b/src/hm/hmSystem.h index 6ff741bf..8f997104 100644 --- a/src/hm/hmSystem.h +++ b/src/hm/hmSystem.h @@ -18,7 +18,7 @@ class HmSystem { void setup(uint32_t *timestamp, cfgInst_t *config, IApp *app) { mInverter[0].timestamp = timestamp; mInverter[0].generalConfig = config; - mInverter[0].app = app; + //mInverter[0].app = app; } void addInverter(uint8_t id, std::function *iv)> cb) { diff --git a/src/plugins/Display/Display_Mono.h b/src/plugins/Display/Display_Mono.h index bd3ee30e..185e066e 100644 --- a/src/plugins/Display/Display_Mono.h +++ b/src/plugins/Display/Display_Mono.h @@ -135,7 +135,7 @@ class DisplayMono { } void initPowerGraph(uint8_t width, uint8_t height) { - DBGPRINTLN("---- Init Power Graph ----"); + DBGPRINTLN(F("---- Init Power Graph ----")); mPgWidth = width; mPgHeight = height; mPgData = new float[mPgWidth]; @@ -207,7 +207,7 @@ class DisplayMono { mDisplay->drawLine(xoff, yoff, xoff + mPgWidth, yoff); // horizontal axis // do not draw as long as time is not set correctly and no data was received - if ((mDisplayData->pGraphStartTime == 0) || (mDisplayData->pGraphEndTime == 0) || (mDisplayData->utcTs < 1) || (mPgMaxPwr < 1) || (mPgLastPos < 1)) + if ((mDisplayData->pGraphStartTime == 0) || (mDisplayData->pGraphEndTime == 0) || (mDisplayData->utcTs != 0) || (mPgMaxPwr != 0) || (mPgLastPos != 0)) return; // draw X scale diff --git a/src/web/RestApi.h b/src/web/RestApi.h index 73216ed5..8148eb20 100644 --- a/src/web/RestApi.h +++ b/src/web/RestApi.h @@ -842,6 +842,8 @@ class RestApi { iv->powerLimit[1] = AbsolutNonPersistent; accepted = iv->setDevControlRequest(ActivePowerContr); + if(accepted) + mApp->triggerTickSend(); } else if(F("dev") == jsonIn[F("cmd")]) { DPRINTLN(DBG_INFO, F("dev cmd")); iv->setDevCommand(jsonIn[F("val")].as()); From 9f39e5c1509c2ef264fb1d2ca68b9aa1c3b11bf4 Mon Sep 17 00:00:00 2001 From: lumapu Date: Thu, 11 Jan 2024 00:42:09 +0100 Subject: [PATCH 070/179] 0.8.51 fix display (wrong correction) fix dependency (GxEPD2) --- src/platformio.ini | 10 +++++----- src/plugins/Display/Display_Mono.h | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/platformio.ini b/src/platformio.ini index a2397596..89040215 100644 --- a/src/platformio.ini +++ b/src/platformio.ini @@ -32,7 +32,7 @@ lib_deps = bblanchon/ArduinoJson @ ^6.21.3 https://github.com/JChristensen/Timezone @ ^1.2.4 olikraus/U8g2 @ ^2.35.9 - https://github.com/zinggjm/GxEPD2 @ ^1.5.3 + https://github.com/zinggjm/GxEPD2#1.5.3 build_flags = -std=c++17 -std=gnu++17 @@ -151,7 +151,7 @@ lib_deps = bblanchon/ArduinoJson @ ^6.21.3 https://github.com/JChristensen/Timezone @ ^1.2.4 olikraus/U8g2 @ ^2.35.9 - https://github.com/zinggjm/GxEPD2 @ ^1.5.3 + https://github.com/zinggjm/GxEPD2#1.5.3 build_flags = ${env.build_flags} -D ETHERNET -DRELEASE @@ -173,7 +173,7 @@ lib_deps = bblanchon/ArduinoJson @ ^6.21.3 https://github.com/JChristensen/Timezone @ ^1.2.4 olikraus/U8g2 @ ^2.35.9 - https://github.com/zinggjm/GxEPD2 @ ^1.5.3 + https://github.com/zinggjm/GxEPD2#1.5.3 build_flags = ${env.build_flags} -D ETHERNET -DRELEASE @@ -321,7 +321,7 @@ lib_deps = bblanchon/ArduinoJson @ ^6.21.3 https://github.com/JChristensen/Timezone @ ^1.2.4 olikraus/U8g2 @ ^2.35.9 - https://github.com/zinggjm/GxEPD2 @ ^1.5.3 + https://github.com/zinggjm/GxEPD2#1.5.3 upload_protocol = esp-builtin build_flags = ${env.build_flags} -DETHERNET @@ -363,7 +363,7 @@ lib_deps = bblanchon/ArduinoJson @ ^6.21.3 https://github.com/JChristensen/Timezone @ ^1.2.4 olikraus/U8g2 @ ^2.35.9 - https://github.com/zinggjm/GxEPD2 @ ^1.5.3 + https://github.com/zinggjm/GxEPD2#1.5.3 upload_protocol = esp-builtin build_flags = ${env.build_flags} -DETHERNET diff --git a/src/plugins/Display/Display_Mono.h b/src/plugins/Display/Display_Mono.h index 185e066e..008f7378 100644 --- a/src/plugins/Display/Display_Mono.h +++ b/src/plugins/Display/Display_Mono.h @@ -207,7 +207,7 @@ class DisplayMono { mDisplay->drawLine(xoff, yoff, xoff + mPgWidth, yoff); // horizontal axis // do not draw as long as time is not set correctly and no data was received - if ((mDisplayData->pGraphStartTime == 0) || (mDisplayData->pGraphEndTime == 0) || (mDisplayData->utcTs != 0) || (mPgMaxPwr != 0) || (mPgLastPos != 0)) + if ((mDisplayData->pGraphStartTime == 0) || (mDisplayData->pGraphEndTime == 0) || (mDisplayData->utcTs < 1) || (mPgMaxPwr < 1) || (mPgLastPos < 1)) return; // draw X scale From 455d29a6fa807ac1f2abda8b35fcd36259b41d89 Mon Sep 17 00:00:00 2001 From: lumapu Date: Fri, 12 Jan 2024 00:00:52 +0100 Subject: [PATCH 071/179] 0.8.52 * possible fix of 'division by zero' #1345 * fix lang #1348 #1346 * fix timestamp `max AC power` #1324 * fix stylesheet overlay `max AC power` #1324 * fix download link #1340 * fix history graph * try to fix #1331 --- src/CHANGES.md | 9 +++++++++ src/defines.h | 2 +- src/hm/hmInverter.h | 8 ++++---- src/plugins/Display/Display_Mono.h | 15 +++++++++------ src/plugins/Display/Display_Mono_128X32.h | 4 ++-- src/plugins/Display/Display_Mono_128X64.h | 8 ++++---- src/plugins/Display/Display_Mono_64X48.h | 2 +- src/plugins/Display/Display_Mono_84X48.h | 2 +- src/plugins/history.h | 18 ++++++++++-------- src/publisher/pubMqttIvData.h | 2 +- src/web/html/history.html | 12 ++++++++---- src/web/html/index.html | 2 +- src/web/html/setup.html | 4 ++-- src/web/html/style.css | 2 +- src/web/html/visualization.html | 4 ++-- src/web/lang.json | 15 +++++++++++++++ 16 files changed, 71 insertions(+), 38 deletions(-) diff --git a/src/CHANGES.md b/src/CHANGES.md index ca123e3b..34ea7b72 100644 --- a/src/CHANGES.md +++ b/src/CHANGES.md @@ -1,5 +1,14 @@ # Development Changes +## 0.8.52 - 2024-01-11 +* possible fix of 'division by zero' #1345 +* fix lang #1348 #1346 +* fix timestamp `max AC power` #1324 +* fix stylesheet overlay `max AC power` #1324 +* fix download link #1340 +* fix history graph +* try to fix #1331 + ## 0.8.51 - 2024-01-10 * fix translation #1346 * further improve sending active power control command faster #1332 diff --git a/src/defines.h b/src/defines.h index 91fee041..e6a1d147 100644 --- a/src/defines.h +++ b/src/defines.h @@ -13,7 +13,7 @@ //------------------------------------- #define VERSION_MAJOR 0 #define VERSION_MINOR 8 -#define VERSION_PATCH 51 +#define VERSION_PATCH 52 //------------------------------------- typedef struct { diff --git a/src/hm/hmInverter.h b/src/hm/hmInverter.h index 1f623d85..7bbf1019 100644 --- a/src/hm/hmInverter.h +++ b/src/hm/hmInverter.h @@ -961,8 +961,10 @@ static T calcMaxPowerAcCh0(Inverter<> *iv, uint8_t arg0) { acMaxPower = iv->getValue(i, rec); } } - if(acPower > acMaxPower) + if(acPower > acMaxPower) { + iv->tsMaxAcPower = *iv->timestamp; return acPower; + } } return acMaxPower; } @@ -981,10 +983,8 @@ static T calcMaxPowerDc(Inverter<> *iv, uint8_t arg0) { dcMaxPower = iv->getValue(i, rec); } } - if(dcPower > dcMaxPower) { - iv->tsMaxAcPower = *iv->timestamp; + if(dcPower > dcMaxPower) return dcPower; - } } return dcMaxPower; } diff --git a/src/plugins/Display/Display_Mono.h b/src/plugins/Display/Display_Mono.h index 008f7378..a0d46d11 100644 --- a/src/plugins/Display/Display_Mono.h +++ b/src/plugins/Display/Display_Mono.h @@ -166,7 +166,10 @@ class DisplayMono { } uint8_t sss2pgpos(uint seconds_since_start) { - return(seconds_since_start * (mPgWidth - 1) / (mDisplayData->pGraphEndTime - mDisplayData->pGraphStartTime)); + uint32_t diff = (mDisplayData->pGraphEndTime - mDisplayData->pGraphStartTime); + if(diff) + return (seconds_since_start * (mPgWidth - 1) / diff); + return 0; } void calcPowerGraphValues() { @@ -175,6 +178,8 @@ class DisplayMono { mPgTimeOfDay = (mDisplayData->utcTs > mDisplayData->pGraphStartTime) ? mDisplayData->utcTs - mDisplayData->pGraphStartTime : 0; // current time of day with respect to current sunrise time if (oldTimeOfDay > mPgTimeOfDay) // new day -> reset old data resetPowerGraph(); + if(0 == mPgPeriod) + mPgPeriod = 1; mPgLastPos = std::min((uint8_t) (mPgTimeOfDay * (mPgWidth - 1) / mPgPeriod), (uint8_t) (mPgWidth - 1)); // current datapoint based on currenct time of day } @@ -190,15 +195,13 @@ class DisplayMono { uint8_t getPowerGraphXpos(uint8_t p) { if ((p <= mPgLastPos) && (mPgLastPos > 0)) return((p * (mPgWidth - 1)) / mPgLastPos); // scaling of x-axis - else - return(0); + return 0; } uint8_t getPowerGraphYpos(uint8_t p) { - if (p < mPgWidth) + if ((p < mPgWidth) && (mPgMaxPwr > 0)) return((mPgData[p] * (uint32_t) mPgHeight / mPgMaxPwr)); // scaling of data to graph height - else - return(0); + return 0; } void plotPowerGraph(uint8_t xoff, uint8_t yoff) { diff --git a/src/plugins/Display/Display_Mono_128X32.h b/src/plugins/Display/Display_Mono_128X32.h index 6ab21a6b..e904769f 100644 --- a/src/plugins/Display/Display_Mono_128X32.h +++ b/src/plugins/Display/Display_Mono_128X32.h @@ -1,5 +1,5 @@ //----------------------------------------------------------------------------- -// 2023 Ahoy, https://ahoydtu.de +// 2024 Ahoy, https://ahoydtu.de // Creative Commons - https://creativecommons.org/licenses/by-nc-sa/4.0/deed //----------------------------------------------------------------------------- @@ -107,7 +107,7 @@ class DisplayMono128X32 : public DisplayMono { void printText(const char *text, uint8_t line) { setFont(line); - uint8_t dispX = mLineXOffsets[line] + pixelShiftRange / 2 + mPixelshift; + uint8_t dispX = mLineXOffsets[line] + (pixelShiftRange / 2) + mPixelshift; if (isTwoRowLine(line)) { String stringText = String(text); diff --git a/src/plugins/Display/Display_Mono_128X64.h b/src/plugins/Display/Display_Mono_128X64.h index 2d5c13c2..34f35834 100644 --- a/src/plugins/Display/Display_Mono_128X64.h +++ b/src/plugins/Display/Display_Mono_128X64.h @@ -193,13 +193,13 @@ class DisplayMono128X64 : public DisplayMono { mDisplay->setFont(u8g2_font_ncenB10_symbols10_ahoy); char sym[]=" "; sym[0] = mDisplayData->RadioSymbol?'A':'E'; // NRF - mDisplay->drawStr(widthShrink / 2 + mPixelshift, mLineYOffsets[l_RSSI], sym); + mDisplay->drawStr((widthShrink / 2) + mPixelshift, mLineYOffsets[l_RSSI], sym); if (mDisplayData->MQTTSymbol) sym[0] = 'J'; // MQTT else sym[0] = mDisplayData->WifiSymbol?'B':'F'; // Wifi - mDisplay->drawStr(mDispWidth - mDisplay->getStrWidth(sym) - widthShrink / 2 + mPixelshift, mLineYOffsets[l_RSSI], sym); + mDisplay->drawStr(mDispWidth - mDisplay->getStrWidth(sym) - (widthShrink / 2) + mPixelshift, mLineYOffsets[l_RSSI], sym); mDisplay->sendBuffer(); mExtra++; @@ -241,8 +241,8 @@ class DisplayMono128X64 : public DisplayMono { mLineYOffsets[i] = yOff; dsc = mDisplay->getDescent(); yOff -= dsc; - if (l_Time == i) // prevent time and status line to touch - yOff++; // -> one pixels space + if (l_Time == i) // prevent time and status line to touch + yOff++; // -> one pixels space i++; } while(l_MAX_LINES>i); } diff --git a/src/plugins/Display/Display_Mono_64X48.h b/src/plugins/Display/Display_Mono_64X48.h index 68aa3cc4..a4ddc6ad 100644 --- a/src/plugins/Display/Display_Mono_64X48.h +++ b/src/plugins/Display/Display_Mono_64X48.h @@ -96,7 +96,7 @@ class DisplayMono64X48 : public DisplayMono { } void printText(const char *text, uint8_t line) { - uint8_t dispX = mLineXOffsets[line] + pixelShiftRange/2 + mPixelshift; + uint8_t dispX = mLineXOffsets[line] + pixelShiftRange / 2 + mPixelshift; setFont(line); mDisplay->drawStr(dispX, mLineYOffsets[line], text); diff --git a/src/plugins/Display/Display_Mono_84X48.h b/src/plugins/Display/Display_Mono_84X48.h index d9f1d98d..175fa17e 100644 --- a/src/plugins/Display/Display_Mono_84X48.h +++ b/src/plugins/Display/Display_Mono_84X48.h @@ -1,5 +1,5 @@ //----------------------------------------------------------------------------- -// 2023 Ahoy, https://ahoydtu.de +// 2024 Ahoy, https://ahoydtu.de // Creative Commons - https://creativecommons.org/licenses/by-nc-sa/4.0/deed //----------------------------------------------------------------------------- diff --git a/src/plugins/history.h b/src/plugins/history.h index 9ed7860d..c4d3ab35 100644 --- a/src/plugins/history.h +++ b/src/plugins/history.h @@ -28,7 +28,7 @@ class HistoryData { uint16_t dispIdx; // index for 1st Element to display from WattArr bool wrapped; // ring buffer for watt history - std::array data; + std::array data; void reset() { loopCnt = 0; @@ -78,13 +78,15 @@ class HistoryData { mMaximumDay = roundf(maxPwr); } - if (*mTs > mApp->getSunset()) { - if ((!mDayStored) && (yldDay > 0)) { - addValue(&mYieldDay, roundf(yldDay)); - mDayStored = true; - } - } else if (*mTs > mApp->getSunrise()) - mDayStored = false; + if((++mYieldDay.loopCnt % mYieldDay.refreshCycle) == 0) { + if (*mTs > mApp->getSunset()) { + if ((!mDayStored) && (yldDay > 0)) { + addValue(&mYieldDay, roundf(yldDay)); + mDayStored = true; + } + } else if (*mTs > mApp->getSunrise()) + mDayStored = false; + } } uint16_t valueAt(HistoryStorageType type, uint16_t i) { diff --git a/src/publisher/pubMqttIvData.h b/src/publisher/pubMqttIvData.h index 0a59fdd6..9a76d646 100644 --- a/src/publisher/pubMqttIvData.h +++ b/src/publisher/pubMqttIvData.h @@ -141,7 +141,7 @@ class PubMqttIvData { // calculate total values for RealTimeRunData_Debug if (CH0 == rec->assign[mPos].ch) { - if(mIv->getStatus() != InverterStatus::OFF) { + if(mIv->getStatus() > InverterStatus::OFF) { if(mIv->config->add2Total) { mTotalFound = true; switch (rec->assign[mPos].fieldId) { diff --git a/src/web/html/history.html b/src/web/html/history.html index d0f33755..975a02ed 100644 --- a/src/web/html/history.html +++ b/src/web/html/history.html @@ -44,19 +44,23 @@ function parseHistory(obj, namePrefix, execOnce) { mRefresh = obj.refresh var data = Object.assign({}, obj.value) - var numDataPts = data.length + numDataPts = Object.keys(data).length if (true == execOnce) { - let s = svg(null, (numDataPts + 2) * 2, mChartHeight, "chart"); + let s = document.createElementNS(svgns, "svg"); + s.setAttribute("class", "chart"); + s.setAttribute("width", (numDataPts + 2) * 2); + s.setAttribute("height", mChartHeight); s.setAttribute("role", "img"); + let g = document.createElementNS(svgns, "g"); s.appendChild(g); for (var i = 0; i < numDataPts; i++) { val = data[i]; let rect = document.createElementNS(svgns, "rect"); rect.setAttribute("id", namePrefix+"Rect" + i); - rect.setAttribute("x", String(i * 2) + ""); - rect.setAttribute("width", String(2) + ""); + rect.setAttribute("x", i * 2); + rect.setAttribute("width", 2); g.appendChild(rect); } document.getElementById(namePrefix+"Chart").appendChild(s); diff --git a/src/web/html/index.html b/src/web/html/index.html index 99a42089..e7b7afc4 100644 --- a/src/web/html/index.html +++ b/src/web/html/index.html @@ -26,7 +26,7 @@
  • {#DISCUSS} Discord
  • {#REPORT} {#ISSUES}
  • {#CONTRIBUTE} {#DOCUMENTATION}
  • -
  • Download & Test {#DEV_FIRMWARE}, {#DEV_CHANGELOG}
  • +
  • Download & Test {#DEV_FIRMWARE}, {#DEV_CHANGELOG}
  • {#DON_MAKE} {#DONATION}
  • diff --git a/src/web/html/setup.html b/src/web/html/setup.html index 62c52b3a..e75faf5b 100644 --- a/src/web/html/setup.html +++ b/src/web/html/setup.html @@ -269,7 +269,7 @@
    Discovery Config (homeassistant)
    - +
    @@ -309,7 +309,7 @@
    {#BTN_REBOOT_SUCCESSFUL_SAVE}
    - +
    diff --git a/src/web/html/style.css b/src/web/html/style.css index ef581ffa..395fcb99 100644 --- a/src/web/html/style.css +++ b/src/web/html/style.css @@ -666,7 +666,7 @@ div.hr { } -.tooltip{ +.tooltip:hover { position: relative; } .tooltip:hover:after { diff --git a/src/web/html/visualization.html b/src/web/html/visualization.html index 15b0c7b5..b99501f3 100644 --- a/src/web/html/visualization.html +++ b/src/web/html/visualization.html @@ -138,7 +138,7 @@ ml("div", {class: "row mt-2"},[ numMid(obj.ch[0][11], "W", "{#MAX_AC_POWER}", {class: "fs-6 tooltip", data: maxAcPwr}), numMid(obj.ch[0][8], "W", "{#DC_POWER}"), - numMid(obj.ch[0][0], "V", "{#DC_VOLTAGE}"), + numMid(obj.ch[0][0], "V", "{#AC_VOLTAGE}"), numMid(obj.ch[0][1], "A", "{#AC_CURRENT}"), numMid(obj.ch[0][3], "Hz", "{#FREQUENCY}"), numMid(obj.ch[0][9], "%", "{#EFFICIENCY}"), @@ -362,7 +362,7 @@ var v = getGridValue(glob); if(null === g) { if(0 == obj.grid.length) { - content.push(ml("div", {class: "row"}, ml("div", {class: "col"}, ml("p", {}, "{#PROFILE_NOT_READ}?")))) + content.push(ml("div", {class: "row"}, ml("div", {class: "col"}, ml("p", {}, "{#PROFILE_NOT_READ}")))) } else { content.push(ml("div", {class: "row"}, ml("div", {class: "col"}, ml("h5", {}, "{#UNKNOWN_PROFILE}")))) content.push(ml("div", {class: "row"}, ml("div", {class: "col"}, ml("p", {}, "{#OPEN_ISSUE}.")))) diff --git a/src/web/lang.json b/src/web/lang.json index 42c5e98a..6717306d 100644 --- a/src/web/lang.json +++ b/src/web/lang.json @@ -433,6 +433,16 @@ "en": "Line 1-4", "de": "Zeile 1-4" }, + { + "token": "BTN_SAVE", + "en": "save", + "de": "speichern" + }, + { + "token": "BTN_SEND", + "en": "send", + "de": "senden" + }, { "token": "BTN_REBOOT_SUCCESSFUL_SAVE", "en": "Reboot device after successful save", @@ -1118,6 +1128,11 @@ "en": "DC Voltage", "de": "DC Spannung" }, + { + "token": "AC_VOLTAGE", + "en": "AC Voltage", + "de": "Netzspannung" + }, { "token": "AC_CURRENT", "en": "AC Current", From ca6ebfe0fe95d8cfd95956243e4f94f43070de90 Mon Sep 17 00:00:00 2001 From: lumapu Date: Fri, 12 Jan 2024 21:42:22 +0100 Subject: [PATCH 072/179] 0.8.53 * fix history graph * fix MqTT yield day #1331 --- src/CHANGES.md | 4 ++++ src/app.cpp | 16 ++++++++++------ src/app.h | 1 + src/defines.h | 2 +- src/publisher/pubMqtt.h | 15 +++++---------- src/publisher/pubMqttIvData.h | 35 ++++++++++++++++++----------------- 6 files changed, 39 insertions(+), 34 deletions(-) diff --git a/src/CHANGES.md b/src/CHANGES.md index 34ea7b72..3fb87fce 100644 --- a/src/CHANGES.md +++ b/src/CHANGES.md @@ -1,5 +1,9 @@ # Development Changes +## 0.8.53 - 2024-01-12 +* fix history graph +* fix MqTT yield day #1331 + ## 0.8.52 - 2024-01-11 * possible fix of 'division by zero' #1345 * fix lang #1348 #1346 diff --git a/src/app.cpp b/src/app.cpp index 88135099..97afe16f 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -152,7 +152,7 @@ void app::regularTickers(void) { //everySec([this]() { mImprov.tickSerial(); }, "impro"); #endif - everySec(std::bind(&HistoryType::tickerSecond, mHistory), "hist"); + everySec(std::bind(&HistoryType::tickerSecond, &mHistory), "hist"); } #if defined(ETHERNET) @@ -241,7 +241,7 @@ void app::tickCalcSunrise(void) { if (mMqttEnabled) { tickSun(); nxtTrig = mSunrise + mConfig->sun.offsetSecMorning + 1; // one second safety to trigger correctly - onceAt(std::bind(&app::tickSun, this), nxtTrig, "mqSr"); // trigger on sunrise to update 'dis_night_comm' + onceAt(std::bind(&app::tickSunrise, this), nxtTrig, "mqSr"); // trigger on sunrise to update 'dis_night_comm' } } @@ -291,6 +291,13 @@ void app::tickSun(void) { once(std::bind(&app::tickSun, this), 1, "mqSun"); // MQTT not connected, retry } +//----------------------------------------------------------------------------- +void app::tickSunrise(void) { + // only used and enabled by MQTT (see setup()) + if (!mMqtt.tickerSun(mSunrise, mSunset, mConfig->sun.offsetSecMorning, mConfig->sun.offsetSecEvening, true)) + once(std::bind(&app::tickSun, this), 1, "mqSun"); // MQTT not connected, retry +} + //----------------------------------------------------------------------------- void app::tickZeroValues(void) { zeroIvValues(!CHECK_AVAIL, SKIP_YIELD_DAY); @@ -423,11 +430,8 @@ void app:: zeroIvValues(bool checkAvail, bool skipYieldDay) { changed = true; } - if(changed) { - if(mMqttEnabled && !skipYieldDay) - mMqtt.setZeroValuesEnable(); + if(changed) payloadEventListener(RealTimeRunData_Debug, NULL); - } } //----------------------------------------------------------------------------- diff --git a/src/app.h b/src/app.h index a4e51113..04a7f1cc 100644 --- a/src/app.h +++ b/src/app.h @@ -314,6 +314,7 @@ class app : public IApp, public ah::Scheduler { void tickCalcSunrise(void); void tickIVCommunication(void); void tickSun(void); + void tickSunrise(void); void tickComm(void); void tickSend(void); void tickMinute(void); diff --git a/src/defines.h b/src/defines.h index e6a1d147..0fb35f2f 100644 --- a/src/defines.h +++ b/src/defines.h @@ -13,7 +13,7 @@ //------------------------------------- #define VERSION_MAJOR 0 #define VERSION_MINOR 8 -#define VERSION_PATCH 52 +#define VERSION_PATCH 53 //------------------------------------- typedef struct { diff --git a/src/publisher/pubMqtt.h b/src/publisher/pubMqtt.h index a1efa5fb..e36338c9 100644 --- a/src/publisher/pubMqtt.h +++ b/src/publisher/pubMqtt.h @@ -43,7 +43,6 @@ class PubMqtt { memset(mLastIvState, (uint8_t)InverterStatus::OFF, MAX_NUM_INVERTERS); memset(mIvLastRTRpub, 0, MAX_NUM_INVERTERS * 4); mLastAnyAvail = false; - mZeroValues = false; } ~PubMqtt() { } @@ -134,7 +133,7 @@ class PubMqtt { #endif } - bool tickerSun(uint32_t sunrise, uint32_t sunset, int16_t offsM, int16_t offsE) { + bool tickerSun(uint32_t sunrise, uint32_t sunset, int16_t offsM, int16_t offsE, bool isSunrise = false) { if (!mClient.connected()) return false; @@ -153,10 +152,12 @@ class PubMqtt { publish(mSubTopic, ((iv->commEnabled) ? dict[STR_TRUE] : dict[STR_FALSE]), true); } - snprintf(mSubTopic, 32 + MAX_NAME_LENGTH, "comm_disabled"); publish(mSubTopic, (((*mUtcTimestamp > (sunset + offsE)) || (*mUtcTimestamp < (sunrise + offsM))) ? dict[STR_TRUE] : dict[STR_FALSE]), true); + if(isSunrise) + mSendIvData.resetYieldDay(); + return true; } @@ -239,10 +240,6 @@ class PubMqtt { } } - void setZeroValuesEnable(void) { - mZeroValues = true; - } - private: void onConnect(bool sessionPreset) { DPRINTLN(DBG_INFO, F("MQTT connected")); @@ -592,8 +589,7 @@ class PubMqtt { if(mSendList.empty()) return; - mSendIvData.start(mZeroValues); - mZeroValues = false; + mSendIvData.start(); mLastAnyAvail = anyAvail; } @@ -612,7 +608,6 @@ class PubMqtt { std::array mSendAlarm{}; subscriptionCb mSubscriptionCb; bool mLastAnyAvail; - bool mZeroValues; InverterStatus mLastIvState[MAX_NUM_INVERTERS]; uint32_t mIvLastRTRpub[MAX_NUM_INVERTERS]; uint16_t mIntervalTimeout; diff --git a/src/publisher/pubMqttIvData.h b/src/publisher/pubMqttIvData.h index 9a76d646..c6fc5d10 100644 --- a/src/publisher/pubMqttIvData.h +++ b/src/publisher/pubMqttIvData.h @@ -22,11 +22,11 @@ template class PubMqttIvData { public: void setup(HMSYSTEM *sys, uint32_t *utcTs, std::queue *sendList) { - mSys = sys; - mUtcTimestamp = utcTs; - mSendList = sendList; - mState = IDLE; - mZeroValues = false; + mSys = sys; + mUtcTimestamp = utcTs; + mSendList = sendList; + mState = IDLE; + mYldTotalStore = 0; mRTRDataHasBeenSent = false; @@ -42,11 +42,14 @@ class PubMqttIvData { yield(); } - bool start(bool zeroValues = false) { + void resetYieldDay() { + mYldTotalStore = 0; + } + + bool start() { if(IDLE != mState) return false; - mZeroValues = zeroValues; mRTRDataHasBeenSent = false; mState = START; return true; @@ -117,10 +120,14 @@ class PubMqttIvData { mIv->isProducing(); // recalculate status mState = SEND_DATA; } else if(mSendTotals && mTotalFound) { + if(mYldTotalStore > mTotal[2]) + mSendTotalYd = false; // don't send yield total if last value was greater + else + mYldTotalStore = mTotal[2]; + mState = SEND_TOTALS; } else { mSendList->pop(); - mZeroValues = false; mState = START; } } @@ -141,7 +148,7 @@ class PubMqttIvData { // calculate total values for RealTimeRunData_Debug if (CH0 == rec->assign[mPos].ch) { - if(mIv->getStatus() > InverterStatus::OFF) { + if(mIv->getStatus() != InverterStatus::OFF) { if(mIv->config->add2Total) { mTotalFound = true; switch (rec->assign[mPos].fieldId) { @@ -152,11 +159,7 @@ class PubMqttIvData { mTotal[1] += mIv->getValue(mPos, rec); break; case FLD_YD: { - float val = mIv->getValue(mPos, rec); - if(0 == val) // inverter restarted during day - mSendTotalYd = false; - else - mTotal[2] += val; + mTotal[2] += mIv->getValue(mPos, rec); break; } case FLD_PDC: @@ -236,7 +239,6 @@ class PubMqttIvData { mPos++; } else { mSendList->pop(); - mZeroValues = false; mPos = 0; mState = IDLE; } @@ -251,7 +253,7 @@ class PubMqttIvData { uint8_t mCmd; uint8_t mLastIvId; bool mSendTotals, mTotalFound, mAllTotalFound, mSendTotalYd; - float mTotal[4]; + float mTotal[4], mYldTotalStore; Inverter<> *mIv, *mIvSend; uint8_t mPos; @@ -259,7 +261,6 @@ class PubMqttIvData { char mSubTopic[32 + MAX_NAME_LENGTH + 1]; char mVal[140]; - bool mZeroValues; // makes sure that yield day is sent even if no inverter is online std::queue *mSendList; }; From 873f5a71419d018962162084927e55f97ca5e812 Mon Sep 17 00:00:00 2001 From: geronet1 Date: Sat, 13 Jan 2024 20:37:15 +0100 Subject: [PATCH 073/179] ETH support for CMT2300A - HMS/HMT --- src/config/config.h | 10 ++++++++++ src/hms/cmtHal.h | 2 +- src/hms/esp32_3wSpi.h | 10 +++++----- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/config/config.h b/src/config/config.h index 40cb5b76..058caf3f 100644 --- a/src/config/config.h +++ b/src/config/config.h @@ -93,6 +93,16 @@ #define DEF_NRF_SCLK_PIN 18 #endif +#if defined(ETHERNET) + #ifndef DEF_CMT_SPI_HOST + #define DEF_CMT_SPI_HOST SPI3_HOST + #endif +#else + #ifndef DEF_CMT_SPI_HOST + #define DEF_CMT_SPI_HOST SPI2_HOST + #endif +#endif /* defined(ETHERNET) */ + #ifndef DEF_CMT_SCLK #define DEF_CMT_SCLK 12 #endif diff --git a/src/hms/cmtHal.h b/src/hms/cmtHal.h index d5e31ea0..768b8da5 100644 --- a/src/hms/cmtHal.h +++ b/src/hms/cmtHal.h @@ -17,7 +17,7 @@ class cmtHal : public SpiPatcherHandle { public: cmtHal() { - mSpiPatcher = SpiPatcher::getInstance(SPI2_HOST); + mSpiPatcher = SpiPatcher::getInstance(DEF_CMT_SPI_HOST); } void patch() override { diff --git a/src/hms/esp32_3wSpi.h b/src/hms/esp32_3wSpi.h index 20c42632..c562671a 100644 --- a/src/hms/esp32_3wSpi.h +++ b/src/hms/esp32_3wSpi.h @@ -21,7 +21,7 @@ // for ESP32 this is the so-called HSPI // for ESP32-S2/S3/C3 this nomenclature does not really exist anymore, // it is simply the first externally usable hardware SPI master controller -#define SPI_CMT SPI2_HOST +//#define SPI_CMT SPI2_HOST class esp32_3wSpi { public: @@ -54,8 +54,8 @@ class esp32_3wSpi { .post_cb = NULL, }; - ESP_ERROR_CHECK(spi_bus_initialize(SPI_CMT, &buscfg, SPI_DMA_DISABLED)); - ESP_ERROR_CHECK(spi_bus_add_device(SPI_CMT, &devcfg, &spi_reg)); + ESP_ERROR_CHECK(spi_bus_initialize(DEF_CMT_SPI_HOST, &buscfg, SPI_DMA_DISABLED)); + ESP_ERROR_CHECK(spi_bus_add_device(DEF_CMT_SPI_HOST, &devcfg, &spi_reg)); // FiFo spi_device_interface_config_t devcfg2 = { @@ -72,9 +72,9 @@ class esp32_3wSpi { .pre_cb = NULL, .post_cb = NULL, }; - ESP_ERROR_CHECK(spi_bus_add_device(SPI_CMT, &devcfg2, &spi_fifo)); + ESP_ERROR_CHECK(spi_bus_add_device(DEF_CMT_SPI_HOST, &devcfg2, &spi_fifo)); - esp_rom_gpio_connect_out_signal(pinSdio, spi_periph_signal[SPI_CMT].spid_out, true, false); + esp_rom_gpio_connect_out_signal(pinSdio, spi_periph_signal[DEF_CMT_SPI_HOST].spid_out, true, false); delay(100); //pinMode(pinGpio3, INPUT); From 60111d0696671370889c6dea360b171217af114c Mon Sep 17 00:00:00 2001 From: lumapu Date: Sun, 14 Jan 2024 01:52:56 +0100 Subject: [PATCH 074/179] 0.8.54 * added minimal version (without: MqTT, Display, History), WebUI is not changed! * added simulator (must be activated before compile, standard: off) * changed communication attempts back to 5 --- .github/workflows/compile_development.yml | 2 + src/CHANGES.md | 5 + src/app.cpp | 29 ++++ src/app.h | 61 +++++++- src/config/config.h | 34 ++++- src/config/config_override_example.h | 8 - src/defines.h | 3 +- src/hm/CommQueue.h | 7 +- src/hm/Communication.h | 6 +- src/hm/simulator.h | 175 ++++++++++++++++++++++ src/platformio.ini | 106 +++++++++++-- src/plugins/Display/Display.h | 4 + src/plugins/history.h | 5 +- src/publisher/pubMqtt.h | 2 + src/web/RestApi.h | 6 +- src/web/html/setup.html | 4 +- src/web/lang.json | 4 +- 17 files changed, 420 insertions(+), 41 deletions(-) create mode 100644 src/hm/simulator.h diff --git a/.github/workflows/compile_development.yml b/.github/workflows/compile_development.yml index 7eaf9b34..90a6293f 100644 --- a/.github/workflows/compile_development.yml +++ b/.github/workflows/compile_development.yml @@ -25,11 +25,13 @@ jobs: - esp8266-prometheus - esp8285 - esp32-wroom32 + - esp32-wroom32-minimal - esp32-wroom32-prometheus - esp32-wroom32-ethernet - esp32-s2-mini - esp32-c3-mini - opendtufusion + - opendtufusion-minimal - opendtufusion-ethernet steps: - uses: actions/checkout@v3 diff --git a/src/CHANGES.md b/src/CHANGES.md index 3fb87fce..6b3d0f80 100644 --- a/src/CHANGES.md +++ b/src/CHANGES.md @@ -1,5 +1,10 @@ # Development Changes +## 0.8.54 - 2024-01-13 +* added minimal version (without: MqTT, Display, History), WebUI is not changed! +* added simulator (must be activated before compile, standard: off) +* changed communication attempts back to 5 + ## 0.8.53 - 2024-01-12 * fix history graph * fix MqTT yield day #1331 diff --git a/src/app.cpp b/src/app.cpp index 97afe16f..8cbe775d 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -52,7 +52,9 @@ void app::setup() { mCommunication.setup(&mTimestamp, &mConfig->serial.debug, &mConfig->serial.privacyLog, &mConfig->serial.printWholeTrace, &mConfig->inst.gapMs); mCommunication.addPayloadListener(std::bind(&app::payloadEventListener, this, std::placeholders::_1, std::placeholders::_2)); + #if defined(ENABLE_MQTT) mCommunication.addPowerLimitAckListener([this] (Inverter<> *iv) { mMqtt.setPowerLimitAck(iv); }); + #endif mSys.setup(&mTimestamp, &mConfig->inst, this); for (uint8_t i = 0; i < MAX_NUM_INVERTERS; i++) { initInverter(i); @@ -65,6 +67,7 @@ void app::setup() { // when WiFi is in client mode, then enable mqtt broker #if !defined(AP_ONLY) + #if defined(ENABLE_MQTT) mMqttEnabled = (mConfig->mqtt.broker[0] > 0); if (mMqttEnabled) { mMqtt.setup(&mConfig->mqtt, mConfig->sys.deviceName, mVersion, &mSys, &mTimestamp, &mUptime); @@ -72,6 +75,7 @@ void app::setup() { mCommunication.addAlarmListener([this](Inverter<> *iv) { mMqtt.alarmEvent(iv); }); } #endif + #endif setupLed(); mWeb.setup(this, &mSys, mConfig); @@ -92,7 +96,9 @@ void app::setup() { #endif #endif + #if defined(ENABLE_HISTORY) mHistory.setup(this, &mSys, mConfig, &mTimestamp); + #endif /*ENABLE_HISTORY*/ mPubSerial.setup(mConfig, &mSys, &mTimestamp); @@ -100,6 +106,13 @@ void app::setup() { //mImprov.setup(this, mConfig->sys.deviceName, mVersion); #endif + #if defined(ENABLE_SIMULATOR) + mSimulator.setup(&mSys, &mTimestamp, 0); + mSimulator.addPayloadListener([this](uint8_t cmd, Inverter<> *iv) { + payloadEventListener(cmd, iv); + }); + #endif /*ENABLE_SIMULATOR*/ + regularTickers(); } @@ -115,8 +128,10 @@ void app::loop(void) { ah::Scheduler::loop(); mCommunication.loop(); + #if defined(ENABLE_MQTT) if (mMqttEnabled && mNetworkConnected) mMqtt.loop(); + #endif } //----------------------------------------------------------------------------- @@ -152,7 +167,13 @@ void app::regularTickers(void) { //everySec([this]() { mImprov.tickSerial(); }, "impro"); #endif + #if defined(ENABLE_HISTORY) everySec(std::bind(&HistoryType::tickerSecond, &mHistory), "hist"); + #endif /*ENABLE_HISTORY*/ + + #if defined(ENABLE_SIMULATOR) + every(std::bind(&SimulatorType::tick, &mSimulator), 5, "sim"); + #endif /*ENABLE_SIMULATOR*/ } #if defined(ETHERNET) @@ -168,11 +189,13 @@ void app::onNtpUpdate(bool gotTime) { //----------------------------------------------------------------------------- void app::updateNtp(void) { + #if defined(ENABLE_MQTT) if (mMqttReconnect && mMqttEnabled) { mMqtt.tickerSecond(); everySec(std::bind(&PubMqttType::tickerSecond, &mMqtt), "mqttS"); everyMin(std::bind(&PubMqttType::tickerMinute, &mMqtt), "mqttM"); } + #endif /*ENABLE_MQTT*/ // only install schedulers once even if NTP wasn't successful in first loop if (mMqttReconnect) { // @TODO: mMqttReconnect is variable which scope has changed @@ -287,15 +310,19 @@ void app::tickIVCommunication(void) { //----------------------------------------------------------------------------- void app::tickSun(void) { // only used and enabled by MQTT (see setup()) + #if defined(ENABLE_MQTT) if (!mMqtt.tickerSun(mSunrise, mSunset, mConfig->sun.offsetSecMorning, mConfig->sun.offsetSecEvening)) once(std::bind(&app::tickSun, this), 1, "mqSun"); // MQTT not connected, retry + #endif } //----------------------------------------------------------------------------- void app::tickSunrise(void) { // only used and enabled by MQTT (see setup()) + #if defined(ENABLE_MQTT) if (!mMqtt.tickerSun(mSunrise, mSunset, mConfig->sun.offsetSecMorning, mConfig->sun.offsetSecEvening, true)) once(std::bind(&app::tickSun, this), 1, "mqSun"); // MQTT not connected, retry + #endif } //----------------------------------------------------------------------------- @@ -340,8 +367,10 @@ void app::tickMidnight(void) { if (mConfig->inst.rstYieldMidNight) { zeroIvValues(!CHECK_AVAIL, !SKIP_YIELD_DAY); + #if defined(ENABLE_MQTT) if (mMqttEnabled) mMqtt.tickerMidnight(); + #endif } } diff --git a/src/app.h b/src/app.h index 04a7f1cc..194f291e 100644 --- a/src/app.h +++ b/src/app.h @@ -17,14 +17,18 @@ #if defined(ESP32) #include "hms/hmsRadio.h" #endif +#if defined(ENABLE_MQTT) #include "publisher/pubMqtt.h" +#endif /*ENABLE_MQTT*/ #include "publisher/pubSerial.h" #include "utils/crc.h" #include "utils/dbg.h" #include "utils/scheduler.h" #include "utils/syslog.h" #include "web/RestApi.h" +#if defined(ENABLE_HISTORY) #include "plugins/history.h" +#endif /*ENABLE_HISTORY*/ #include "web/web.h" #include "hm/Communication.h" #if defined(ETHERNET) @@ -34,6 +38,10 @@ #include "utils/improv.h" #endif /* defined(ETHERNET) */ +#if defined(ENABLE_SIMULATOR) + #include "hm/simulator.h" +#endif /*ENABLE_SIMULATOR*/ + #include // position is relevant since version 1.4.7 of this library @@ -46,9 +54,16 @@ typedef HmSystem HmSystemType; typedef Web WebType; typedef RestApi RestApiType; +#if defined(ENABLE_MQTT) typedef PubMqtt PubMqttType; +#endif /*ENABLE_MQTT*/ typedef PubSerial PubSerialType; +#if defined(ENABLE_HISTORY) typedef HistoryData HistoryType; +#endif /*ENABLE_HISTORY*/ +#if defined (ENABLE_SIMULATOR) +typedef Simulator SimulatorType; +#endif /*ENABLE_SIMULATOR*/ // PLUGINS #if defined(PLUGIN_DISPLAY) @@ -190,19 +205,33 @@ class app : public IApp, public ah::Scheduler { } void setMqttDiscoveryFlag() { + #if defined(ENABLE_MQTT) once(std::bind(&PubMqttType::sendDiscoveryConfig, &mMqtt), 1, "disCf"); + #endif } bool getMqttIsConnected() { - return mMqtt.isConnected(); + #if defined(ENABLE_MQTT) + return mMqtt.isConnected(); + #else + return false; + #endif } uint32_t getMqttTxCnt() { - return mMqtt.getTxCnt(); + #if defined(ENABLE_MQTT) + return mMqtt.getTxCnt(); + #else + return 0; + #endif } uint32_t getMqttRxCnt() { - return mMqtt.getRxCnt(); + #if defined(ENABLE_MQTT) + return mMqtt.getRxCnt(); + #else + return 0; + #endif } bool getProtection(AsyncWebServerRequest *request) { @@ -253,11 +282,19 @@ class app : public IApp, public ah::Scheduler { } uint16_t getHistoryValue(uint8_t type, uint16_t i) { - return mHistory.valueAt((HistoryStorageType)type, i); + #if defined(ENABLE_HISTORY) + return mHistory.valueAt((HistoryStorageType)type, i); + #else + return 0; + #endif } uint16_t getHistoryMaxDay() { - return mHistory.getMaximumDay(); + #if defined(ENABLE_HISTORY) + return mHistory.getMaximumDay(); + #else + return 0; + #endif } private: @@ -269,8 +306,10 @@ class app : public IApp, public ah::Scheduler { void payloadEventListener(uint8_t cmd, Inverter<> *iv) { #if !defined(AP_ONLY) - if (mMqttEnabled) - mMqtt.payloadEventListener(cmd, iv); + #if defined(ENABLE_MQTT) + if (mMqttEnabled) + mMqtt.payloadEventListener(cmd, iv); + #endif /*ENABLE_MQTT*/ #endif #if defined(PLUGIN_DISPLAY) if(mConfig->plugin.display.type != 0) @@ -359,7 +398,9 @@ class app : public IApp, public ah::Scheduler { bool mNetworkConnected; // mqtt + #if defined(ENABLE_MQTT) PubMqttType mMqtt; + #endif /*ENABLE_MQTT*/ bool mMqttReconnect; bool mMqttEnabled; @@ -372,7 +413,13 @@ class app : public IApp, public ah::Scheduler { DisplayType mDisplay; DisplayData mDispData; #endif + #if defined(ENABLE_HISTORY) HistoryType mHistory; + #endif /*ENABLE_HISTORY*/ + + #if defined(ENABLE_SIMULATOR) + SimulatorType mSimulator; + #endif /*ENABLE_SIMULATOR*/ }; #endif /*__APP_H__*/ diff --git a/src/config/config.h b/src/config/config.h index 40cb5b76..5e6ae42c 100644 --- a/src/config/config.h +++ b/src/config/config.h @@ -32,12 +32,33 @@ // timeout for automatic logoff (20 minutes) #define LOGOUT_TIMEOUT (20 * 60) + +//------------------------------------- +// MODULE SELECTOR - done by platform.ini +//------------------------------------- + +// MqTT connection +//#define ENABLE_MQTT + +// display plugin +//#define PLUGIN_DISPLAY + +// history graph (WebUI) +//#define ENABLE_HISTORY + +// inverter simulation +//#define ENABLE_SIMULATOR + +// to enable the syslog logging (will disable web-serial) +//#define ENABLE_SYSLOG + + + //------------------------------------- // CONFIGURATION - COMPILE TIME //------------------------------------- // ethernet - #if defined(ETHERNET) #define ETH_SPI_HOST SPI2_HOST #define ETH_SPI_CLOCK_MHZ 25 @@ -184,7 +205,7 @@ #define INVERTER_OFF_THRES_SEC 15*60 // threshold of minimum power on which the inverter is marked as inactive -#define INACT_PWR_THRESH 3 +#define INACT_PWR_THRESH 1 // Timezone #define TIMEZONE 1 @@ -222,6 +243,15 @@ // reconnect delay #define MQTT_RECONNECT_DELAY 5000 + +// syslog settings +#ifdef ENABLE_SYSLOG +#define SYSLOG_HOST "" +#define SYSLOG_APP "ahoy" +#define SYSLOG_FACILITY FAC_USER +#define SYSLOG_PORT 514 +#endif + #if __has_include("config_override.h") #include "config_override.h" #endif diff --git a/src/config/config_override_example.h b/src/config/config_override_example.h index b90bbdbd..44623c1f 100644 --- a/src/config/config_override_example.h +++ b/src/config/config_override_example.h @@ -35,13 +35,5 @@ // #define ENABLE_PROMETHEUS_EP -// to enable the syslog logging (will disable web-serial) -//#define ENABLE_SYSLOG -#ifdef ENABLE_SYSLOG -#define SYSLOG_HOST "" -#define SYSLOG_APP "ahoy" -#define SYSLOG_FACILITY FAC_USER -#define SYSLOG_PORT 514 -#endif #endif /*__CONFIG_OVERRIDE_H__*/ diff --git a/src/defines.h b/src/defines.h index 0fb35f2f..4dcead16 100644 --- a/src/defines.h +++ b/src/defines.h @@ -13,7 +13,7 @@ //------------------------------------- #define VERSION_MAJOR 0 #define VERSION_MINOR 8 -#define VERSION_PATCH 53 +#define VERSION_PATCH 54 //------------------------------------- typedef struct { @@ -94,7 +94,6 @@ enum {MQTT_STATUS_OFFLINE = 0, MQTT_STATUS_PARTIAL, MQTT_STATUS_ONLINE}; #define MQTT_MAX_PACKET_SIZE 384 -#define PLUGIN_DISPLAY typedef struct { uint32_t rxFail; diff --git a/src/hm/CommQueue.h b/src/hm/CommQueue.h index eb770bb0..4e73d53a 100644 --- a/src/hm/CommQueue.h +++ b/src/hm/CommQueue.h @@ -11,9 +11,10 @@ #include "hmInverter.h" #include "../utils/dbg.h" -#define DEFAULT_ATTEMPS 10 -#define MORE_ATTEMPS_ALARMDATA 15 -#define MORE_ATTEMPS_GRIDONPROFILEPARA 15 +// needs a '+1' because the comparison does not send if attempts is equal 0 +#define DEFAULT_ATTEMPS 5 + 1 +#define MORE_ATTEMPS_ALARMDATA 15 + 1 +#define MORE_ATTEMPS_GRIDONPROFILEPARA 15 + 1 template class CommQueue { diff --git a/src/hm/Communication.h b/src/hm/Communication.h index cf0d0054..a8a46e1a 100644 --- a/src/hm/Communication.h +++ b/src/hm/Communication.h @@ -152,9 +152,9 @@ class Communication : public CommQueue<> { while(!q->iv->radio->mBufCtrl.empty()) { packet_t *p = &q->iv->radio->mBufCtrl.front(); - printRxInfo(q, p); if(validateIvSerial(&p->packet[1], q->iv)) { + printRxInfo(q, p); q->iv->radioStatistics.frmCnt++; q->iv->mDtuRxCnt++; @@ -302,11 +302,11 @@ class Communication : public CommQueue<> { CP_U32_BigEndian(tmp, iv->radioId.u64 >> 8); for(uint8_t i = 0; i < 4; i++) { if(tmp[i] != buf[i]) { - DPRINT(DBG_WARN, F("Inverter serial does not match, got: 0x")); + /*DPRINT(DBG_WARN, F("Inverter serial does not match, got: 0x")); DHEX(buf[0]);DHEX(buf[1]);DHEX(buf[2]);DHEX(buf[3]); DBGPRINT(F(", expected: 0x")); DHEX(tmp[0]);DHEX(tmp[1]);DHEX(tmp[2]);DHEX(tmp[3]); - DBGPRINTLN(""); + DBGPRINTLN("");*/ return false; } } diff --git a/src/hm/simulator.h b/src/hm/simulator.h new file mode 100644 index 00000000..4e06062e --- /dev/null +++ b/src/hm/simulator.h @@ -0,0 +1,175 @@ +//----------------------------------------------------------------------------- +// 2024 Ahoy, https://github.com/lumpapu/ahoy +// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/4.0/deed +//----------------------------------------------------------------------------- + +#ifndef __SIMULATOR_H__ +#define __SIMULATOR_H__ + +#if defined(ENABLE_SIMULATOR) + +#include "../defines.h" +#include "../utils/dbg.h" +#include "../utils/helper.h" +#include "hmSystem.h" +#include "hmInverter.h" +#include "Communication.h" + +template +class Simulator { + public: + void setup(HMSYSTEM *sys, uint32_t *ts, uint8_t ivId = 0) { + mTimestamp = ts; + mSys = sys; + mIvId = ivId; + } + + void addPayloadListener(payloadListenerType cb) { + mCbPayload = cb; + } + + void tick() { + uint8_t cmd, len; + uint8_t *payload; + getPayload(&cmd, &payload, &len); + + Inverter<> *iv = mSys->getInverterByPos(mIvId); + if (NULL == iv) + return; + + DPRINT(DBG_INFO, F("add payload with cmd: 0x")); + DBGHEXLN(cmd); + + if(GridOnProFilePara == cmd) { + iv->addGridProfile(payload, len); + return; + } + + record_t<> *rec = iv->getRecordStruct(cmd); + rec->ts = *mTimestamp; + for (uint8_t i = 0; i < rec->length; i++) { + iv->addValue(i, payload, rec); + yield(); + } + iv->doCalculations(); + + if((nullptr != mCbPayload) && (GridOnProFilePara != cmd)) + (mCbPayload)(cmd, iv); + } + + private: + inline void getPayload(uint8_t *cmd, uint8_t *payload[], uint8_t *len) { + switch(payloadCtrl) { + default: *cmd = RealTimeRunData_Debug; break; + case 1: *cmd = SystemConfigPara; break; + case 3: *cmd = InverterDevInform_All; break; + case 5: *cmd = InverterDevInform_Simple; break; + case 7: *cmd = GridOnProFilePara; break; + } + + if(payloadCtrl < 8) + payloadCtrl++; + + switch(*cmd) { + default: + case RealTimeRunData_Debug: + *payload = plRealtime; + modifyAcPwr(); + *len = 62; + break; + case InverterDevInform_All: + *payload = plFirmware; + *len = 14; + break; + case InverterDevInform_Simple: + *payload = plPart; + *len = 14; + break; + case SystemConfigPara: + *payload = plLimit; + *len = 14; + break; + case AlarmData: + *payload = plAlarm; + *len = 26; + break; + case GridOnProFilePara: + *payload = plGrid; + *len = 70; + break; + } + } + + inline void modifyAcPwr() { + uint16_t cur = (plRealtime[50] << 8) | plRealtime[51]; + uint16_t change = cur ^ 0xa332; + if(0 == change) + change = 140; + else if(change > 200) + change = (change % 200) + 1; + + if(cur > 7000) + cur -= change; + else + cur += change; + + plRealtime[50] = (cur >> 8) & 0xff; + plRealtime[51] = (cur ) & 0xff; + } + + private: + HMSYSTEM *mSys; + uint8_t mIvId; + uint32_t *mTimestamp; + payloadListenerType mCbPayload = nullptr; + uint8_t payloadCtrl = 0; + + private: + uint8_t plRealtime[62] = { + 0x00, 0x01, 0x01, 0x24, 0x00, 0x22, 0x00, 0x23, + 0x00, 0x63, 0x00, 0x65, 0x00, 0x08, 0x5c, 0xbb, + 0x00, 0x09, 0x6f, 0x08, 0x00, 0x0c, 0x00, 0x0c, + 0x01, 0x1e, 0x00, 0x22, 0x00, 0x21, 0x00, 0x60, + 0x00, 0x5f, 0x00, 0x08, 0xdd, 0x84, 0x00, 0x09, + 0x13, 0x6f, 0x00, 0x0b, 0x00, 0x0b, 0x09, 0x27, + 0x13, 0x8c, 0x01, 0x75, 0x00, 0xc2, 0x00, 0x10, + 0x03, 0x77, 0x00, 0x61, 0x00, 0x02 + }; + + uint8_t plPart[14] = { + 0x27, 0x1c, 0x10, 0x12, 0x10, 0x01, 0x01, 0x00, + 0x0a, 0x00, 0x20, 0x01, 0x00, 0x00 + }; + + uint8_t plFirmware[14] = { + 0x00, 0x01, 0x80, 0x01, 0x00, 0x01, 0x60, 0x42, + 0x60, 0x42, 0x00, 0x00, 0x00, 0x00 + }; + + uint8_t plLimit[14] = { + 0x00, 0x01, 0x03, 0xe8, 0x00, 0x00, 0x03, 0xe8, + 0xff, 0xff, 0xff, 0xff, 0x01, 0x68 + }; + + uint8_t plGrid[70] = { + 0x0D, 0x00, 0x20, 0x00, 0x00, 0x08, 0x08, 0xFC, + 0x07, 0x30, 0x00, 0x01, 0x0A, 0x55, 0x00, 0x01, + 0x09, 0xE2, 0x10, 0x00, 0x13, 0x88, 0x12, 0x8E, + 0x00, 0x01, 0x14, 0x1E, 0x00, 0x01, 0x20, 0x00, + 0x00, 0x01, 0x30, 0x07, 0x01, 0x2C, 0x0A, 0x55, + 0x07, 0x30, 0x14, 0x1E, 0x12, 0x8E, 0x00, 0x32, + 0x00, 0x1E, 0x40, 0x00, 0x07, 0xD0, 0x00, 0x10, + 0x50, 0x00, 0x00, 0x01, 0x13, 0x9C, 0x01, 0x90, + 0x00, 0x10, 0x70, 0x00, 0x00, 0x01 + }; + + uint8_t plAlarm[26] = { + 0x00, 0x01, 0x80, 0x01, 0x00, 0x01, 0x51, 0xc7, + 0x51, 0xc7, 0x00, 0x00, 0x00, 0x00, 0x80, 0x02, + 0x00, 0x02, 0xa6, 0xc9, 0xa6, 0xc9, 0x65, 0x3e, + 0x47, 0x21 + }; +}; + +#endif /*ENABLE_SIMULATOR*/ +#endif /*__SIMULATOR_H__*/ diff --git a/src/platformio.ini b/src/platformio.ini index 89040215..719ae300 100644 --- a/src/platformio.ini +++ b/src/platformio.ini @@ -46,6 +46,9 @@ board = esp12e board_build.f_cpu = 80000000L build_flags = ${env.build_flags} -DEMC_MIN_FREE_MEMORY=4096 + -DENABLE_MQTT + -DPLUGIN_DISPLAY + -DENABLE_HISTORY ;-Wl,-Map,output.map monitor_filters = esp8266_exception_decoder @@ -57,6 +60,9 @@ board_build.f_cpu = 80000000L build_flags = ${env.build_flags} -DEMC_MIN_FREE_MEMORY=4096 -DLANG_DE + -DENABLE_MQTT + -DPLUGIN_DISPLAY + -DENABLE_HISTORY ;-Wl,-Map,output.map monitor_filters = esp8266_exception_decoder @@ -66,8 +72,11 @@ platform = espressif8266 board = esp12e board_build.f_cpu = 80000000L build_flags = ${env.build_flags} - -DENABLE_PROMETHEUS_EP -DEMC_MIN_FREE_MEMORY=4096 + -DENABLE_PROMETHEUS_EP + -DENABLE_MQTT + -DPLUGIN_DISPLAY + -DENABLE_HISTORY monitor_filters = esp8266_exception_decoder @@ -76,9 +85,12 @@ platform = espressif8266 board = esp12e board_build.f_cpu = 80000000L build_flags = ${env.build_flags} - -DENABLE_PROMETHEUS_EP -DEMC_MIN_FREE_MEMORY=4096 + -DENABLE_PROMETHEUS_EP -DLANG_DE + -DENABLE_MQTT + -DPLUGIN_DISPLAY + -DENABLE_HISTORY monitor_filters = esp8266_exception_decoder @@ -89,6 +101,9 @@ board_build.ldscript = eagle.flash.1m64.ld board_build.f_cpu = 80000000L build_flags = ${env.build_flags} -DEMC_MIN_FREE_MEMORY=4096 + -DENABLE_MQTT + -DPLUGIN_DISPLAY + -DENABLE_HISTORY monitor_filters = esp8266_exception_decoder @@ -100,12 +115,26 @@ board_build.f_cpu = 80000000L build_flags = ${env.build_flags} -DEMC_MIN_FREE_MEMORY=4096 -DLANG_DE + -DENABLE_MQTT + -DPLUGIN_DISPLAY + -DENABLE_HISTORY monitor_filters = esp8266_exception_decoder [env:esp32-wroom32] platform = espressif32@6.5.0 board = lolin_d32 +build_flags = ${env.build_flags} + -DUSE_HSPI_FOR_EPD + -DENABLE_MQTT + -DPLUGIN_DISPLAY + -DENABLE_HISTORY +monitor_filters = + esp32_exception_decoder + +[env:esp32-wroom32-minimal] +platform = espressif32@6.5.0 +board = lolin_d32 build_flags = ${env.build_flags} -DUSE_HSPI_FOR_EPD monitor_filters = @@ -116,6 +145,9 @@ platform = espressif32@6.5.0 board = lolin_d32 build_flags = ${env.build_flags} -DUSE_HSPI_FOR_EPD + -DENABLE_MQTT + -DPLUGIN_DISPLAY + -DENABLE_HISTORY -DLANG_DE monitor_filters = esp32_exception_decoder @@ -126,6 +158,9 @@ board = lolin_d32 build_flags = ${env.build_flags} -DUSE_HSPI_FOR_EPD -DENABLE_PROMETHEUS_EP + -DENABLE_MQTT + -DPLUGIN_DISPLAY + -DENABLE_HISTORY monitor_filters = esp32_exception_decoder @@ -134,8 +169,11 @@ platform = espressif32@6.5.0 board = lolin_d32 build_flags = ${env.build_flags} -DUSE_HSPI_FOR_EPD - -DENABLE_PROMETHEUS_EP -DLANG_DE + -DENABLE_PROMETHEUS_EP + -DENABLE_MQTT + -DPLUGIN_DISPLAY + -DENABLE_HISTORY monitor_filters = esp32_exception_decoder @@ -156,8 +194,9 @@ build_flags = ${env.build_flags} -D ETHERNET -DRELEASE -DUSE_HSPI_FOR_EPD - -DLOG_LOCAL_LEVEL=ESP_LOG_INFO - -DDEBUG_LEVEL=DBG_INFO + -DENABLE_MQTT + -DPLUGIN_DISPLAY + -DENABLE_HISTORY monitor_filters = esp32_exception_decoder @@ -179,8 +218,9 @@ build_flags = ${env.build_flags} -DRELEASE -DUSE_HSPI_FOR_EPD -DLANG_DE - -DLOG_LOCAL_LEVEL=ESP_LOG_INFO - -DDEBUG_LEVEL=DBG_INFO + -DENABLE_MQTT + -DPLUGIN_DISPLAY + -DENABLE_HISTORY monitor_filters = esp32_exception_decoder @@ -189,6 +229,9 @@ platform = espressif32@6.5.0 board = lolin_s2_mini build_flags = ${env.build_flags} -DUSE_HSPI_FOR_EPD + -DENABLE_MQTT + -DPLUGIN_DISPLAY + -DENABLE_HISTORY -DDEF_NRF_CS_PIN=12 -DDEF_NRF_CE_PIN=3 -DDEF_NRF_IRQ_PIN=5 @@ -208,6 +251,9 @@ platform = espressif32@6.5.0 board = lolin_s2_mini build_flags = ${env.build_flags} -DUSE_HSPI_FOR_EPD + -DENABLE_MQTT + -DPLUGIN_DISPLAY + -DENABLE_HISTORY -DDEF_NRF_CS_PIN=12 -DDEF_NRF_CE_PIN=3 -DDEF_NRF_IRQ_PIN=5 @@ -228,6 +274,9 @@ platform = espressif32@6.5.0 board = lolin_c3_mini build_flags = ${env.build_flags} -DUSE_HSPI_FOR_EPD + -DENABLE_MQTT + -DPLUGIN_DISPLAY + -DENABLE_HISTORY -DDEF_NRF_CS_PIN=5 -DDEF_NRF_CE_PIN=0 -DDEF_NRF_IRQ_PIN=1 @@ -247,6 +296,9 @@ platform = espressif32@6.5.0 board = lolin_c3_mini build_flags = ${env.build_flags} -DUSE_HSPI_FOR_EPD + -DENABLE_MQTT + -DPLUGIN_DISPLAY + -DENABLE_HISTORY -DDEF_NRF_CS_PIN=5 -DDEF_NRF_CE_PIN=0 -DDEF_NRF_IRQ_PIN=1 @@ -267,6 +319,9 @@ platform = espressif32@6.5.0 board = esp32-s3-devkitc-1 upload_protocol = esp-builtin build_flags = ${env.build_flags} + -DENABLE_MQTT + -DPLUGIN_DISPLAY + -DENABLE_HISTORY -DDEF_NRF_CS_PIN=37 -DDEF_NRF_CE_PIN=38 -DDEF_NRF_IRQ_PIN=47 @@ -282,6 +337,7 @@ build_flags = ${env.build_flags} -DDEF_LED1=17 -DLED_ACTIVE_HIGH -DARDUINO_USB_MODE=1 + #-DARDUINO_USB_CDC_ON_BOOT=1 monitor_filters = esp32_exception_decoder, colorize @@ -289,6 +345,33 @@ monitor_filters = platform = espressif32@6.5.0 board = esp32-s3-devkitc-1 upload_protocol = esp-builtin +build_flags = ${env.build_flags} + -DLANG_DE + -DENABLE_MQTT + -DPLUGIN_DISPLAY + -DENABLE_HISTORY + -DDEF_NRF_CS_PIN=37 + -DDEF_NRF_CE_PIN=38 + -DDEF_NRF_IRQ_PIN=47 + -DDEF_NRF_MISO_PIN=48 + -DDEF_NRF_MOSI_PIN=35 + -DDEF_NRF_SCLK_PIN=36 + -DDEF_CMT_CSB=4 + -DDEF_CMT_FCSB=21 + -DDEF_CMT_IRQ=8 + -DDEF_CMT_SDIO=5 + -DDEF_CMT_SCLK=6 + -DDEF_LED0=18 + -DDEF_LED1=17 + -DLED_ACTIVE_HIGH + -DARDUINO_USB_MODE=1 +monitor_filters = + esp32_exception_decoder, colorize + +[env:opendtufusion-minimal] +platform = espressif32@6.5.0 +board = esp32-s3-devkitc-1 +upload_protocol = esp-builtin build_flags = ${env.build_flags} -DDEF_NRF_CS_PIN=37 -DDEF_NRF_CE_PIN=38 @@ -305,7 +388,6 @@ build_flags = ${env.build_flags} -DDEF_LED1=17 -DLED_ACTIVE_HIGH -DARDUINO_USB_MODE=1 - -DLANG_DE monitor_filters = esp32_exception_decoder, colorize @@ -326,6 +408,9 @@ upload_protocol = esp-builtin build_flags = ${env.build_flags} -DETHERNET -DSPI_HAL + -DENABLE_MQTT + -DPLUGIN_DISPLAY + -DENABLE_HISTORY -DDEF_ETH_CS_PIN=42 -DDEF_ETH_SCK_PIN=39 -DDEF_ETH_MISO_PIN=41 @@ -368,6 +453,10 @@ upload_protocol = esp-builtin build_flags = ${env.build_flags} -DETHERNET -DSPI_HAL + -DLANG_DE + -DENABLE_MQTT + -DPLUGIN_DISPLAY + -DENABLE_HISTORY -DDEF_ETH_CS_PIN=42 -DDEF_ETH_SCK_PIN=39 -DDEF_ETH_MISO_PIN=41 @@ -390,6 +479,5 @@ build_flags = ${env.build_flags} -DLED_ACTIVE_HIGH -DARDUINO_USB_MODE=1 #-DARDUINO_USB_CDC_ON_BOOT=1 - -DLANG_DE monitor_filters = esp32_exception_decoder, colorize diff --git a/src/plugins/Display/Display.h b/src/plugins/Display/Display.h index 0ce49c33..7780afef 100644 --- a/src/plugins/Display/Display.h +++ b/src/plugins/Display/Display.h @@ -1,6 +1,8 @@ #ifndef __DISPLAY__ #define __DISPLAY__ +#if defined(PLUGIN_DISPLAY) + #include #include @@ -238,4 +240,6 @@ class Display { DisplayMono *mMono; }; +#endif /*PLUGIN_DISPLAY*/ + #endif /*__DISPLAY__*/ diff --git a/src/plugins/history.h b/src/plugins/history.h index c4d3ab35..da57800f 100644 --- a/src/plugins/history.h +++ b/src/plugins/history.h @@ -6,6 +6,8 @@ #ifndef __HISTORY_DATA_H__ #define __HISTORY_DATA_H__ +#if defined(ENABLE_HISTORY) + #include #include "../appInterface.h" #include "../hm/hmSystem.h" @@ -122,4 +124,5 @@ class HistoryData { uint16_t mMaximumDay = 0; }; -#endif +#endif /*ENABLE_HISTORY*/ +#endif /*__HISTORY_DATA_H__*/ diff --git a/src/publisher/pubMqtt.h b/src/publisher/pubMqtt.h index e36338c9..48300b30 100644 --- a/src/publisher/pubMqtt.h +++ b/src/publisher/pubMqtt.h @@ -8,6 +8,7 @@ #ifndef __PUB_MQTT_H__ #define __PUB_MQTT_H__ +#if defined(ENABLE_MQTT) #ifdef ESP8266 #include #elif defined(ESP32) @@ -623,4 +624,5 @@ class PubMqtt { discovery_t mDiscovery; }; +#endif /*ENABLE_MQTT*/ #endif /*__PUB_MQTT_H__*/ diff --git a/src/web/RestApi.h b/src/web/RestApi.h index 8148eb20..afaa3455 100644 --- a/src/web/RestApi.h +++ b/src/web/RestApi.h @@ -202,8 +202,6 @@ class RestApi { ep[F("setup")] = url + F("setup"); ep[F("system")] = url + F("system"); ep[F("live")] = url + F("live"); - ep[F("powerHistory")] = url + F("powerHistory"); - ep[F("yieldDayHistory")] = url + F("yieldDayHistory"); } @@ -791,6 +789,7 @@ class RestApi { void getPowerHistory(AsyncWebServerRequest *request, JsonObject obj) { getGeneric(request, obj.createNestedObject(F("generic"))); + #if defined(ENABLE_HISTORY) obj[F("refresh")] = mConfig->inst.sendInterval; uint16_t max = 0; for (uint16_t fld = 0; fld < HISTORY_DATA_ARR_LENGTH; fld++) { @@ -801,10 +800,12 @@ class RestApi { } obj[F("max")] = max; obj[F("maxDay")] = mApp->getHistoryMaxDay(); + #endif /*ENABLE_HISTORY*/ } void getYieldDayHistory(AsyncWebServerRequest *request, JsonObject obj) { getGeneric(request, obj.createNestedObject(F("generic"))); + #if defined(ENABLE_HISTORY) obj[F("refresh")] = 86400; // 1 day uint16_t max = 0; for (uint16_t fld = 0; fld < HISTORY_DATA_ARR_LENGTH; fld++) { @@ -814,6 +815,7 @@ class RestApi { max = value; } obj[F("max")] = max; + #endif /*ENABLE_HISTORY*/ } diff --git a/src/web/html/setup.html b/src/web/html/setup.html index e75faf5b..961e8805 100644 --- a/src/web/html/setup.html +++ b/src/web/html/setup.html @@ -141,11 +141,11 @@ {#INVERTER}
    -
    {#INTERVAL}
    +
    {#INTERVAL} [s]
    -
    {#INV_GAP}
    +
    {#INV_GAP} [ms]
    diff --git a/src/web/lang.json b/src/web/lang.json index 6717306d..c495ac9f 100644 --- a/src/web/lang.json +++ b/src/web/lang.json @@ -265,8 +265,8 @@ }, { "token": "INTERVAL", - "en": "Interval [s]", - "de": "Intervall [s]" + "en": "Interval", + "de": "Intervall" }, { "token": "INV_GAP", From f5158d47254a093e9ba100f1bc22962e83febb92 Mon Sep 17 00:00:00 2001 From: Frank Date: Sun, 14 Jan 2024 10:26:16 +0100 Subject: [PATCH 075/179] Enhancement: Add info about compiled modules to version string --- src/app.cpp | 23 ++++++++++++++++++++++- src/app.h | 2 +- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/app.cpp b/src/app.cpp index 8cbe775d..80f2083e 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -465,7 +465,28 @@ void app:: zeroIvValues(bool checkAvail, bool skipYieldDay) { //----------------------------------------------------------------------------- void app::resetSystem(void) { - snprintf(mVersion, 12, "%d.%d.%d", VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH); + snprintf(mVersion, sizeof(mVersion), "%d.%d.%d%s", VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH, + "-" +#ifdef ENABLE_PROMETHEUS_EP + "P" +#endif + +#ifdef ENABLE_MQTT + "M" +#endif + +#ifdef PLUGIN_DISPLAY + "D" +#endif + +#ifdef ENABLE_HISTORY + "H" +#endif + +#ifdef AP_ONLY + "A" +#endif + ); #ifdef AP_ONLY mTimestamp = 1; diff --git a/src/app.h b/src/app.h index 194f291e..8da8f3f7 100644 --- a/src/app.h +++ b/src/app.h @@ -386,7 +386,7 @@ class app : public IApp, public ah::Scheduler { CmtRadio<> mCmtRadio; #endif - char mVersion[12]; + char mVersion[18]; settings mSettings; settings_t *mConfig; bool mSavePending; From 7c1ddf875f6dc08b60223e20d268a28c64ff804b Mon Sep 17 00:00:00 2001 From: Frank Date: Sun, 14 Jan 2024 13:47:22 +0100 Subject: [PATCH 076/179] add language, syslog and simulator identifier to version string --- src/app.cpp | 15 +++++++++++++++ src/app.h | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/app.cpp b/src/app.cpp index 80f2083e..ddbc2771 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -486,6 +486,21 @@ void app::resetSystem(void) { #ifdef AP_ONLY "A" #endif + +#ifdef ENABLE_SYSLOG + "Y" +#endif + +#ifdef ENABLE_SIMULATOR + "S" +#endif + + "-" +#ifdef LANG_DE + "de" +#else + "en" +#endif ); #ifdef AP_ONLY diff --git a/src/app.h b/src/app.h index 8da8f3f7..0015ede4 100644 --- a/src/app.h +++ b/src/app.h @@ -386,7 +386,7 @@ class app : public IApp, public ah::Scheduler { CmtRadio<> mCmtRadio; #endif - char mVersion[18]; + char mVersion[23]; settings mSettings; settings_t *mConfig; bool mSavePending; From 28418998fc3abe36558a4a5dc912aac775cbfaae Mon Sep 17 00:00:00 2001 From: you69man Date: Sun, 14 Jan 2024 15:26:27 +0100 Subject: [PATCH 077/179] fix reboot problem with deactivated power graph --- src/plugins/Display/Display_Mono.h | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/plugins/Display/Display_Mono.h b/src/plugins/Display/Display_Mono.h index a0d46d11..e480baf0 100644 --- a/src/plugins/Display/Display_Mono.h +++ b/src/plugins/Display/Display_Mono.h @@ -18,6 +18,7 @@ #endif #include "../../utils/helper.h" #include "Display_data.h" +#include "config/settings.h" #include "../../utils/dbg.h" #include "../../utils/timemonitor.h" @@ -184,7 +185,7 @@ class DisplayMono { } void addPowerGraphEntry(float val) { - if (mDisplayData->utcTs > 0) { // precondition: utc time available + if ((mPgData != nullptr) && (mDisplayData->utcTs > 0)) { // precondition: power graph initialized and utc time available calcPowerGraphValues(); //mPgData[mPgLastPos] = std::max(mPgData[mPgLastPos], (uint8_t) (val * 255.0 / mPgMaxAvailPower)); // normalizing of data to 0-255 mPgData[mPgLastPos] = std::max(mPgData[mPgLastPos], val); @@ -205,12 +206,15 @@ class DisplayMono { } void plotPowerGraph(uint8_t xoff, uint8_t yoff) { + if (mPgData == nullptr) // power graph not initialized + return; + // draw axes mDisplay->drawLine(xoff, yoff, xoff, yoff - mPgHeight); // vertical axis mDisplay->drawLine(xoff, yoff, xoff + mPgWidth, yoff); // horizontal axis // do not draw as long as time is not set correctly and no data was received - if ((mDisplayData->pGraphStartTime == 0) || (mDisplayData->pGraphEndTime == 0) || (mDisplayData->utcTs < 1) || (mPgMaxPwr < 1) || (mPgLastPos < 1)) + if ((mDisplayData->pGraphStartTime == 0) || (mDisplayData->pGraphEndTime == 0) || (mDisplayData->utcTs == 0) || (mPgMaxPwr < 1) || (mPgLastPos == 0)) return; // draw X scale From 0fb98518ead7d664e352bf6c90a4db86489de5ff Mon Sep 17 00:00:00 2001 From: lumapu Date: Sun, 14 Jan 2024 16:30:01 +0100 Subject: [PATCH 078/179] 0.8.55 * merge PR: fix reboot problem with deactivated power graph #1360 --- src/CHANGES.md | 3 +++ src/defines.h | 2 +- src/plugins/Display/Display_Mono.h | 7 +++---- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/CHANGES.md b/src/CHANGES.md index 6b3d0f80..e65fca23 100644 --- a/src/CHANGES.md +++ b/src/CHANGES.md @@ -1,5 +1,8 @@ # Development Changes +## 0.8.55 - 2024-01-14 +* merge PR: fix reboot problem with deactivated power graph #1360 + ## 0.8.54 - 2024-01-13 * added minimal version (without: MqTT, Display, History), WebUI is not changed! * added simulator (must be activated before compile, standard: off) diff --git a/src/defines.h b/src/defines.h index 4dcead16..975abb58 100644 --- a/src/defines.h +++ b/src/defines.h @@ -13,7 +13,7 @@ //------------------------------------- #define VERSION_MAJOR 0 #define VERSION_MINOR 8 -#define VERSION_PATCH 54 +#define VERSION_PATCH 55 //------------------------------------- typedef struct { diff --git a/src/plugins/Display/Display_Mono.h b/src/plugins/Display/Display_Mono.h index e480baf0..1e34be87 100644 --- a/src/plugins/Display/Display_Mono.h +++ b/src/plugins/Display/Display_Mono.h @@ -18,7 +18,6 @@ #endif #include "../../utils/helper.h" #include "Display_data.h" -#include "config/settings.h" #include "../../utils/dbg.h" #include "../../utils/timemonitor.h" @@ -185,7 +184,7 @@ class DisplayMono { } void addPowerGraphEntry(float val) { - if ((mPgData != nullptr) && (mDisplayData->utcTs > 0)) { // precondition: power graph initialized and utc time available + if ((nullptr != mPgData) && (mDisplayData->utcTs > 0)) { // precondition: power graph initialized and utc time available calcPowerGraphValues(); //mPgData[mPgLastPos] = std::max(mPgData[mPgLastPos], (uint8_t) (val * 255.0 / mPgMaxAvailPower)); // normalizing of data to 0-255 mPgData[mPgLastPos] = std::max(mPgData[mPgLastPos], val); @@ -206,7 +205,7 @@ class DisplayMono { } void plotPowerGraph(uint8_t xoff, uint8_t yoff) { - if (mPgData == nullptr) // power graph not initialized + if (nullptr == mPgData) // power graph not initialized return; // draw axes @@ -214,7 +213,7 @@ class DisplayMono { mDisplay->drawLine(xoff, yoff, xoff + mPgWidth, yoff); // horizontal axis // do not draw as long as time is not set correctly and no data was received - if ((mDisplayData->pGraphStartTime == 0) || (mDisplayData->pGraphEndTime == 0) || (mDisplayData->utcTs == 0) || (mPgMaxPwr < 1) || (mPgLastPos == 0)) + if ((0 == mDisplayData->pGraphStartTime) || (0 == mDisplayData->pGraphEndTime) || (0 == mDisplayData->utcTs) || (mPgMaxPwr < 1) || (0 == mPgLastPos)) return; // draw X scale From ba6c38ede0c546e0b33fcc6ffc527cd8734a865a Mon Sep 17 00:00:00 2001 From: lumapu Date: Sun, 14 Jan 2024 16:42:33 +0100 Subject: [PATCH 079/179] 0.8.55 * changed scope of variables and member functions inside display classes --- src/CHANGES.md | 1 + src/plugins/Display/Display_Mono.h | 184 +++++++++++++++-------------- 2 files changed, 95 insertions(+), 90 deletions(-) diff --git a/src/CHANGES.md b/src/CHANGES.md index e65fca23..3b917c9f 100644 --- a/src/CHANGES.md +++ b/src/CHANGES.md @@ -2,6 +2,7 @@ ## 0.8.55 - 2024-01-14 * merge PR: fix reboot problem with deactivated power graph #1360 +* changed scope of variables and member functions inside display classes ## 0.8.54 - 2024-01-13 * added minimal version (without: MqTT, Display, History), WebUI is not changed! diff --git a/src/plugins/Display/Display_Mono.h b/src/plugins/Display/Display_Mono.h index 1e34be87..712059ea 100644 --- a/src/plugins/Display/Display_Mono.h +++ b/src/plugins/Display/Display_Mono.h @@ -69,33 +69,6 @@ class DisplayMono { }; protected: - display_t *mCfg; - U8G2 *mDisplay; - DisplayData *mDisplayData; - - float *mPgData = nullptr; - uint8_t mPgWidth = 0; - uint8_t mPgHeight = 0; - float mPgMaxPwr = 0.0; - uint32_t mPgPeriod = 0; // seconds - uint32_t mPgTimeOfDay = 0; - uint8_t mPgLastPos = 0; - - uint16_t mDispWidth; - uint16_t mDispHeight; - uint8_t mLuminance; - - uint8_t mLineXOffsets[5] = {}; - uint8_t mLineYOffsets[5] = {}; - - uint8_t mExtra; - int8_t mPixelshift=0; - TimeMonitor mDisplayTime = TimeMonitor(1000 * DISP_DEFAULT_TIMEOUT, true); - TimeMonitor mDispSwitchTime = TimeMonitor(); - DispSwitchState mDispSwitchState = DispSwitchState::TEXT; - bool mDisplayActive = true; // always start with display on - char mFmtText[DISP_FMT_TEXT_LEN]; - // Common initialization function to be called by subclasses void monoInit(U8G2* display, DisplayData *displayData) { mDisplay = display; @@ -113,25 +86,10 @@ class DisplayMono { mDispSwitchTime.startTimeMonitor(150 * (100 - mCfg->graph_ratio)); // start display mode change only if ratio is neither 0 nor 100 } - bool monoMaintainDispSwitchState(void) { - bool change = false; - switch(mDispSwitchState) { - case DispSwitchState::TEXT: - if (mDispSwitchTime.isTimeout()) { - mDispSwitchState = DispSwitchState::GRAPH; - mDispSwitchTime.startTimeMonitor(150 * mCfg->graph_ratio); // graph_ratio: 0-100 Gesamtperiode 15000 ms - change = true; - } - break; - case DispSwitchState::GRAPH: - if (mDispSwitchTime.isTimeout()) { - mDispSwitchState = DispSwitchState::TEXT; - mDispSwitchTime.startTimeMonitor(150 * (100 - mCfg->graph_ratio)); - change = true; - } - break; - } - return change; + // pixelshift screensaver with wipe effect + void calcPixelShift(int range) { + int8_t mod = (millis() / 10000) % ((range >> 1) << 2); + mPixelshift = mCfg->screenSaver == 1 ? ((mod < range) ? mod - (range >> 1) : -(mod - range - (range >> 1) + 1)) : 0; } void initPowerGraph(uint8_t width, uint8_t height) { @@ -155,34 +113,6 @@ class DisplayMono { DBGPRINTLN("max. Power = " + String(mPgMaxAvailPower));*/ } - void resetPowerGraph() { - if (mPgData != nullptr) { - mPgMaxPwr = 0.0; - mPgLastPos = 0; - for (uint8_t i = 0; i < mPgWidth; i++) { - mPgData[i] = 0.0; - } - } - } - - uint8_t sss2pgpos(uint seconds_since_start) { - uint32_t diff = (mDisplayData->pGraphEndTime - mDisplayData->pGraphStartTime); - if(diff) - return (seconds_since_start * (mPgWidth - 1) / diff); - return 0; - } - - void calcPowerGraphValues() { - mPgPeriod = mDisplayData->pGraphEndTime - mDisplayData->pGraphStartTime; // length of power graph for scaling of x-axis - uint32_t oldTimeOfDay = mPgTimeOfDay; - mPgTimeOfDay = (mDisplayData->utcTs > mDisplayData->pGraphStartTime) ? mDisplayData->utcTs - mDisplayData->pGraphStartTime : 0; // current time of day with respect to current sunrise time - if (oldTimeOfDay > mPgTimeOfDay) // new day -> reset old data - resetPowerGraph(); - if(0 == mPgPeriod) - mPgPeriod = 1; - mPgLastPos = std::min((uint8_t) (mPgTimeOfDay * (mPgWidth - 1) / mPgPeriod), (uint8_t) (mPgWidth - 1)); // current datapoint based on currenct time of day - } - void addPowerGraphEntry(float val) { if ((nullptr != mPgData) && (mDisplayData->utcTs > 0)) { // precondition: power graph initialized and utc time available calcPowerGraphValues(); @@ -192,18 +122,6 @@ class DisplayMono { } } - uint8_t getPowerGraphXpos(uint8_t p) { - if ((p <= mPgLastPos) && (mPgLastPos > 0)) - return((p * (mPgWidth - 1)) / mPgLastPos); // scaling of x-axis - return 0; - } - - uint8_t getPowerGraphYpos(uint8_t p) { - if ((p < mPgWidth) && (mPgMaxPwr > 0)) - return((mPgData[p] * (uint32_t) mPgHeight / mPgMaxPwr)); // scaling of data to graph height - return 0; - } - void plotPowerGraph(uint8_t xoff, uint8_t yoff) { if (nullptr == mPgData) // power graph not initialized return; @@ -254,11 +172,97 @@ class DisplayMono { mDisplay->drawStr(xoff + 3, yoff - mPgHeight + 5, mFmtText); } - // pixelshift screensaver with wipe effect - void calcPixelShift(int range) { - int8_t mod = (millis() / 10000) % ((range >> 1) << 2); - mPixelshift = mCfg->screenSaver == 1 ? ((mod < range) ? mod - (range >> 1) : -(mod - range - (range >> 1) + 1)) : 0; + private: + bool monoMaintainDispSwitchState(void) { + bool change = false; + switch(mDispSwitchState) { + case DispSwitchState::TEXT: + if (mDispSwitchTime.isTimeout()) { + mDispSwitchState = DispSwitchState::GRAPH; + mDispSwitchTime.startTimeMonitor(150 * mCfg->graph_ratio); // graph_ratio: 0-100 Gesamtperiode 15000 ms + change = true; + } + break; + case DispSwitchState::GRAPH: + if (mDispSwitchTime.isTimeout()) { + mDispSwitchState = DispSwitchState::TEXT; + mDispSwitchTime.startTimeMonitor(150 * (100 - mCfg->graph_ratio)); + change = true; + } + break; + } + return change; + } + + void resetPowerGraph() { + if (mPgData != nullptr) { + mPgMaxPwr = 0.0; + mPgLastPos = 0; + for (uint8_t i = 0; i < mPgWidth; i++) { + mPgData[i] = 0.0; + } + } } + + uint8_t sss2pgpos(uint seconds_since_start) { + uint32_t diff = (mDisplayData->pGraphEndTime - mDisplayData->pGraphStartTime); + if(diff) + return (seconds_since_start * (mPgWidth - 1) / diff); + return 0; + } + + void calcPowerGraphValues() { + mPgPeriod = mDisplayData->pGraphEndTime - mDisplayData->pGraphStartTime; // length of power graph for scaling of x-axis + uint32_t oldTimeOfDay = mPgTimeOfDay; + mPgTimeOfDay = (mDisplayData->utcTs > mDisplayData->pGraphStartTime) ? mDisplayData->utcTs - mDisplayData->pGraphStartTime : 0; // current time of day with respect to current sunrise time + if (oldTimeOfDay > mPgTimeOfDay) // new day -> reset old data + resetPowerGraph(); + if(0 == mPgPeriod) + mPgPeriod = 1; + mPgLastPos = std::min((uint8_t) (mPgTimeOfDay * (mPgWidth - 1) / mPgPeriod), (uint8_t) (mPgWidth - 1)); // current datapoint based on currenct time of day + } + + uint8_t getPowerGraphXpos(uint8_t p) { + if ((p <= mPgLastPos) && (mPgLastPos > 0)) + return((p * (mPgWidth - 1)) / mPgLastPos); // scaling of x-axis + return 0; + } + + uint8_t getPowerGraphYpos(uint8_t p) { + if ((p < mPgWidth) && (mPgMaxPwr > 0)) + return((mPgData[p] * (uint32_t) mPgHeight / mPgMaxPwr)); // scaling of data to graph height + return 0; + } + + protected: + display_t *mCfg; + U8G2 *mDisplay; + DisplayData *mDisplayData; + DispSwitchState mDispSwitchState = DispSwitchState::TEXT; + + uint16_t mDispWidth; + uint8_t mExtra; + int8_t mPixelshift=0; + char mFmtText[DISP_FMT_TEXT_LEN]; + uint8_t mLineXOffsets[5] = {}; + uint8_t mLineYOffsets[5] = {}; + + uint8_t mPgWidth = 0; + + private: + float *mPgData = nullptr; + uint8_t mPgHeight = 0; + float mPgMaxPwr = 0.0; + uint32_t mPgPeriod = 0; // seconds + uint32_t mPgTimeOfDay = 0; + uint8_t mPgLastPos = 0; + + uint16_t mDispHeight; + uint8_t mLuminance; + + TimeMonitor mDisplayTime = TimeMonitor(1000 * DISP_DEFAULT_TIMEOUT, true); + TimeMonitor mDispSwitchTime = TimeMonitor(); + bool mDisplayActive = true; // always start with display on }; /* adapted 5x8 Font for low-res displays with symbols From 8500c9e37d820e97b0bc69e4d8a071f0687ea673 Mon Sep 17 00:00:00 2001 From: lumapu Date: Sun, 14 Jan 2024 16:43:14 +0100 Subject: [PATCH 080/179] 0.8.55 * removed automatically "minimal" builds --- .github/workflows/compile_development.yml | 2 -- src/CHANGES.md | 1 + 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/compile_development.yml b/.github/workflows/compile_development.yml index 90a6293f..7eaf9b34 100644 --- a/.github/workflows/compile_development.yml +++ b/.github/workflows/compile_development.yml @@ -25,13 +25,11 @@ jobs: - esp8266-prometheus - esp8285 - esp32-wroom32 - - esp32-wroom32-minimal - esp32-wroom32-prometheus - esp32-wroom32-ethernet - esp32-s2-mini - esp32-c3-mini - opendtufusion - - opendtufusion-minimal - opendtufusion-ethernet steps: - uses: actions/checkout@v3 diff --git a/src/CHANGES.md b/src/CHANGES.md index 3b917c9f..13521857 100644 --- a/src/CHANGES.md +++ b/src/CHANGES.md @@ -3,6 +3,7 @@ ## 0.8.55 - 2024-01-14 * merge PR: fix reboot problem with deactivated power graph #1360 * changed scope of variables and member functions inside display classes +* removed automatically "minimal" builds ## 0.8.54 - 2024-01-13 * added minimal version (without: MqTT, Display, History), WebUI is not changed! From de81296b28a72f51992c68ca49905e7d528ee5ea Mon Sep 17 00:00:00 2001 From: lumapu Date: Sun, 14 Jan 2024 16:45:36 +0100 Subject: [PATCH 081/179] 0.8.55 * fix include of "settings.h" (was already done in #1360) --- src/CHANGES.md | 1 + src/plugins/Display/Display_Mono.h | 1 + 2 files changed, 2 insertions(+) diff --git a/src/CHANGES.md b/src/CHANGES.md index 13521857..62616096 100644 --- a/src/CHANGES.md +++ b/src/CHANGES.md @@ -4,6 +4,7 @@ * merge PR: fix reboot problem with deactivated power graph #1360 * changed scope of variables and member functions inside display classes * removed automatically "minimal" builds +* fix include of "settings.h" (was already done in #1360) ## 0.8.54 - 2024-01-13 * added minimal version (without: MqTT, Display, History), WebUI is not changed! diff --git a/src/plugins/Display/Display_Mono.h b/src/plugins/Display/Display_Mono.h index 712059ea..c8d25f8b 100644 --- a/src/plugins/Display/Display_Mono.h +++ b/src/plugins/Display/Display_Mono.h @@ -20,6 +20,7 @@ #include "Display_data.h" #include "../../utils/dbg.h" #include "../../utils/timemonitor.h" +#include "../../config/settings.h" class DisplayMono { public: From 6c4e6f9d901bd846192dea50269cffc9f9bb251b Mon Sep 17 00:00:00 2001 From: lumapu Date: Sun, 14 Jan 2024 17:38:37 +0100 Subject: [PATCH 082/179] 0.8.55 * merge PR: fix reboot problem with deactivated power graph #1360 * changed scope of variables and member functions inside display classes * removed automatically "minimal" builds * fix include of "settings.h" (was already done in #1360) * merge PR: Enhancement: Add info about compiled modules to version string #1357 * add info about installed binary to `/update` #1353 * fix lang in `/system` #1346 --- scripts/convertHtml.py | 30 ++++++++++++++++----- src/CHANGES.md | 3 +++ src/app.cpp | 58 ++++++++++++++++++++-------------------- src/app.h | 7 ++++- src/appInterface.h | 1 + src/web/RestApi.h | 5 +++- src/web/html/system.html | 2 +- src/web/html/update.html | 2 ++ src/web/lang.h | 12 +++++++++ src/web/lang.json | 7 ++++- 10 files changed, 87 insertions(+), 40 deletions(-) diff --git a/scripts/convertHtml.py b/scripts/convertHtml.py index 4e01a875..c39e95ac 100644 --- a/scripts/convertHtml.py +++ b/scripts/convertHtml.py @@ -23,18 +23,33 @@ def readVersion(path): today = date.today() search = ["_MAJOR", "_MINOR", "_PATCH"] - version = today.strftime("%y%m%d") + "_ahoy_" ver = "" for line in lines: if(line.find("VERSION_") != -1): for s in search: p = line.find(s) if(p != -1): - version += line[p+13:].rstrip() + "." ver += line[p+13:].rstrip() + "." return ver[:-1] -def htmlParts(file, header, nav, footer, version, lang): +def readVersionFull(path): + f = open(path, "r") + lines = f.readlines() + f.close() + + today = date.today() + search = ["_MAJOR", "_MINOR", "_PATCH"] + version = today.strftime("%y%m%d") + "_ahoy_" + for line in lines: + if(line.find("VERSION_") != -1): + for s in search: + p = line.find(s) + if(p != -1): + version += line[p+13:].rstrip() + "." + version = version[:-1] + "_" + get_git_sha() + return version + +def htmlParts(file, header, nav, footer, versionPath, lang): p = ""; f = open(file, "r") lines = f.readlines() @@ -59,8 +74,10 @@ def htmlParts(file, header, nav, footer, version, lang): p += line #placeholders + version = readVersion(versionPath); link = 'GIT SHA: ' + get_git_sha() + ' :: ' + version + '' p = p.replace("{#VERSION}", version) + p = p.replace("{#VERSION_FULL}", readVersionFull(versionPath)) p = p.replace("{#VERSION_GIT}", link) # remove if - endif ESP32 @@ -120,7 +137,7 @@ def translate(file, data, lang="de"): return data -def convert2Header(inFile, version, lang): +def convert2Header(inFile, versionPath, lang): fileType = inFile.split(".")[1] define = inFile.split(".")[0].upper() define2 = inFile.split(".")[1].upper() @@ -140,7 +157,7 @@ def convert2Header(inFile, version, lang): f.close() else: if fileType == "html": - data = htmlParts(inFile, "includes/header.html", "includes/nav.html", "includes/footer.html", version, lang) + data = htmlParts(inFile, "includes/header.html", "includes/nav.html", "includes/footer.html", versionPath, lang) else: f = open(inFile, "r") data = f.read() @@ -193,7 +210,6 @@ for files in types: Path("h").mkdir(exist_ok=True) Path("tmp").mkdir(exist_ok=True) # created to check if webpages are valid with all replacements shutil.copyfile("style.css", "tmp/style.css") -version = readVersion("../../defines.h") # get language from environment lang = "en" @@ -202,4 +218,4 @@ if env['PIOENV'][-3:] == "-de": # go throw the array for val in files_grabbed: - convert2Header(val, version, lang) + convert2Header(val, "../../defines.h", lang) diff --git a/src/CHANGES.md b/src/CHANGES.md index 62616096..db4618d5 100644 --- a/src/CHANGES.md +++ b/src/CHANGES.md @@ -5,6 +5,9 @@ * changed scope of variables and member functions inside display classes * removed automatically "minimal" builds * fix include of "settings.h" (was already done in #1360) +* merge PR: Enhancement: Add info about compiled modules to version string #1357 +* add info about installed binary to `/update` #1353 +* fix lang in `/system` #1346 ## 0.8.54 - 2024-01-13 * added minimal version (without: MqTT, Display, History), WebUI is not changed! diff --git a/src/app.cpp b/src/app.cpp index ddbc2771..dd84ec61 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -465,42 +465,42 @@ void app:: zeroIvValues(bool checkAvail, bool skipYieldDay) { //----------------------------------------------------------------------------- void app::resetSystem(void) { - snprintf(mVersion, sizeof(mVersion), "%d.%d.%d%s", VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH, - "-" -#ifdef ENABLE_PROMETHEUS_EP - "P" -#endif + snprintf(mVersion, sizeof(mVersion), "%d.%d.%d", VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH); + snprintf(mVersionModules, sizeof(mVersionModules), "%s", + #ifdef ENABLE_PROMETHEUS_EP + "P" + #endif -#ifdef ENABLE_MQTT - "M" -#endif + #ifdef ENABLE_MQTT + "M" + #endif -#ifdef PLUGIN_DISPLAY - "D" -#endif + #ifdef PLUGIN_DISPLAY + "D" + #endif -#ifdef ENABLE_HISTORY - "H" -#endif + #ifdef ENABLE_HISTORY + "H" + #endif -#ifdef AP_ONLY - "A" -#endif + #ifdef AP_ONLY + "A" + #endif -#ifdef ENABLE_SYSLOG - "Y" -#endif + #ifdef ENABLE_SYSLOG + "Y" + #endif -#ifdef ENABLE_SIMULATOR - "S" -#endif + #ifdef ENABLE_SIMULATOR + "S" + #endif - "-" -#ifdef LANG_DE - "de" -#else - "en" -#endif + "-" + #ifdef LANG_DE + "de" + #else + "en" + #endif ); #ifdef AP_ONLY diff --git a/src/app.h b/src/app.h index 0015ede4..5b39405c 100644 --- a/src/app.h +++ b/src/app.h @@ -188,6 +188,10 @@ class app : public IApp, public ah::Scheduler { return mVersion; } + const char *getVersionModules() { + return mVersionModules; + } + uint32_t getSunrise() { return mSunrise; } @@ -386,7 +390,8 @@ class app : public IApp, public ah::Scheduler { CmtRadio<> mCmtRadio; #endif - char mVersion[23]; + char mVersion[12]; + char mVersionModules[12]; settings mSettings; settings_t *mConfig; bool mSavePending; diff --git a/src/appInterface.h b/src/appInterface.h index 5feccd3c..ad38f756 100644 --- a/src/appInterface.h +++ b/src/appInterface.h @@ -27,6 +27,7 @@ class IApp { virtual bool getShouldReboot() = 0; virtual void setRebootFlag() = 0; virtual const char *getVersion() = 0; + virtual const char *getVersionModules() = 0; #if !defined(ETHERNET) virtual void scanAvailNetworks() = 0; diff --git a/src/web/RestApi.h b/src/web/RestApi.h index afaa3455..edf92a97 100644 --- a/src/web/RestApi.h +++ b/src/web/RestApi.h @@ -252,6 +252,7 @@ class RestApi { obj[F("ts_uptime")] = mApp->getUptime(); obj[F("ts_now")] = mApp->getTimestamp(); obj[F("version")] = String(mApp->getVersion()); + obj[F("modules")] = String(mApp->getVersionModules()); obj[F("build")] = String(AUTO_GIT_HASH); obj[F("env")] = String(ENV_NAME); obj[F("menu_prot")] = mApp->getProtection(request); @@ -325,7 +326,9 @@ class RestApi { void getHtmlSystem(AsyncWebServerRequest *request, JsonObject obj) { getSysInfo(request, obj.createNestedObject(F("system"))); getGeneric(request, obj.createNestedObject(F("generic"))); - obj[F("html")] = F("AhoyFactory Reset

    Reboot"); + char tmp[100]; + snprintf(tmp, 100, "%s

    %s", FACTORY_RESET, BTN_REBOOT); + obj[F("html")] = String(tmp); } void getHtmlLogout(AsyncWebServerRequest *request, JsonObject obj) { diff --git a/src/web/html/system.html b/src/web/html/system.html index ab0b5289..a646e8b8 100644 --- a/src/web/html/system.html +++ b/src/web/html/system.html @@ -24,7 +24,7 @@ const data = ["sdk", "cpu_freq", "chip_revision", "chip_model", "chip_cores", "esp_type", "mac", "wifi_rssi", "ts_uptime", "flash_size", "sketch_used", "heap_total", "heap_free", "heap_frag", - "max_free_blk", "version", "core_version", "reboot_reason"]; + "max_free_blk", "version", "modules", "env", "core_version", "reboot_reason"]; lines = []; for (const [key, value] of Object.entries(obj)) { diff --git a/src/web/html/update.html b/src/web/html/update.html index f0e39fe7..3fed75a5 100644 --- a/src/web/html/update.html +++ b/src/web/html/update.html @@ -10,6 +10,7 @@
    {#SELECT_FILE} (*.bin) +

    {#INSTALLED_VERSION}:

    @@ -26,6 +27,7 @@ parseNav(obj); parseESP(obj); parseRssi(obj); + document.getElementById("version").innerHTML = "{#VERSION_FULL}_" + obj.env + ".bin" } function hide() { diff --git a/src/web/lang.h b/src/web/lang.h index a82c7888..3c3cd631 100644 --- a/src/web/lang.h +++ b/src/web/lang.h @@ -72,4 +72,16 @@ #define INV_NOT_FOUND "inverter not found!" #endif +#ifdef LANG_DE + #define FACTORY_RESET "Ahoy Factory Reset" +#else /*LANG_EN*/ + #define FACTORY_RESET "Ahoy auf Werkseinstellungen zurücksetzen" +#endif + +#ifdef LANG_DE + #define BTN_REBOOT "Reboot" +#else /*LANG_EN*/ + #define BTN_REBOOT "Ahoy neustarten" +#endif + #endif /*__LANG_H__*/ diff --git a/src/web/lang.json b/src/web/lang.json index c495ac9f..c910650e 100644 --- a/src/web/lang.json +++ b/src/web/lang.json @@ -51,7 +51,7 @@ { "token": "WELCOME", "en": "Welcome to AhoyDTU", - "de": "Willkommen zu AhoyDTU" + "de": "Willkommen bei AhoyDTU" }, { "token": "NETWORK_SETUP", @@ -1043,6 +1043,11 @@ "en": "Download latest Release and Development versions (without login)", "de": "Lade die letzte Releaseversion oder Entwicklerversion herunter (ohne Login)" }, + { + "token": "INSTALLED_VERSION", + "en": "installed version (original filename)", + "de": "aktuell installierte Version" + }, { "token": "UPDATE_STARTED", "en": "update started", From f4a82242dfa77933a369c7c363be0604703a600f Mon Sep 17 00:00:00 2001 From: lumapu Date: Mon, 15 Jan 2024 07:53:41 +0100 Subject: [PATCH 083/179] 0.8.56 * potential fix of update problems and random reboots #1359 #1354 --- src/CHANGES.md | 3 +++ src/app.cpp | 1 + src/defines.h | 2 +- 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/CHANGES.md b/src/CHANGES.md index db4618d5..d566216a 100644 --- a/src/CHANGES.md +++ b/src/CHANGES.md @@ -1,5 +1,8 @@ # Development Changes +## 0.8.56 - 2024-01-15 +* potential fix of update problems and random reboots #1359 #1354 + ## 0.8.55 - 2024-01-14 * merge PR: fix reboot problem with deactivated power graph #1360 * changed scope of variables and member functions inside display classes diff --git a/src/app.cpp b/src/app.cpp index dd84ec61..6350e990 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -132,6 +132,7 @@ void app::loop(void) { if (mMqttEnabled && mNetworkConnected) mMqtt.loop(); #endif + yield(); } //----------------------------------------------------------------------------- diff --git a/src/defines.h b/src/defines.h index 975abb58..46c9a249 100644 --- a/src/defines.h +++ b/src/defines.h @@ -13,7 +13,7 @@ //------------------------------------- #define VERSION_MAJOR 0 #define VERSION_MINOR 8 -#define VERSION_PATCH 55 +#define VERSION_PATCH 56 //------------------------------------- typedef struct { From e44e72231530a818f1c949ca3b90fa7c581ebfeb Mon Sep 17 00:00:00 2001 From: rejoe2 Date: Mon, 15 Jan 2024 13:09:23 +0100 Subject: [PATCH 084/179] MI-MQTT and last retransmit - fix MI not sending data over MQTT (discord https://discord.com/channels/984173303147155506/1144045244166443159/1196092306873397351) - change logic for last retransmit (discord https://discord.com/channels/984173303147155506/1144045244166443159/1196098984167608360) --- src/hm/CommQueue.h | 6 +++--- src/hm/Communication.h | 31 +++++++++++++++++++------------ 2 files changed, 22 insertions(+), 15 deletions(-) diff --git a/src/hm/CommQueue.h b/src/hm/CommQueue.h index 4e73d53a..d62efe07 100644 --- a/src/hm/CommQueue.h +++ b/src/hm/CommQueue.h @@ -12,9 +12,9 @@ #include "../utils/dbg.h" // needs a '+1' because the comparison does not send if attempts is equal 0 -#define DEFAULT_ATTEMPS 5 + 1 -#define MORE_ATTEMPS_ALARMDATA 15 + 1 -#define MORE_ATTEMPS_GRIDONPROFILEPARA 15 + 1 +#define DEFAULT_ATTEMPS 5 +#define MORE_ATTEMPS_ALARMDATA 15 +#define MORE_ATTEMPS_GRIDONPROFILEPARA 15 template class CommQueue { diff --git a/src/hm/Communication.h b/src/hm/Communication.h index a8a46e1a..5f14a5af 100644 --- a/src/hm/Communication.h +++ b/src/hm/Communication.h @@ -178,11 +178,11 @@ class Communication : public CommQueue<> { yield(); } - if(0 == q->attempts) { + /*if(0 == q->attempts) { DPRINT_IVID(DBG_INFO, q->iv->id); DBGPRINT(F("no attempts left")); closeRequest(q, false); - } else { + } else {*/ if(q->iv->ivGen != IV_MI) { mState = States::CHECK_PACKAGE; } else { @@ -207,7 +207,7 @@ class Communication : public CommQueue<> { closeRequest(q, true); } } - } + //} } break; @@ -235,6 +235,12 @@ class Communication : public CommQueue<> { } if(framnr) { + if(0 == q->attempts) { + DPRINT_IVID(DBG_INFO, q->iv->id); + DBGPRINT(F("no attempts left")); + closeRequest(q, false); + return; + } setAttempt(); if(*mSerialDebug) { @@ -516,15 +522,15 @@ class Communication : public CommQueue<> { } void sendRetransmit(const queue_s *q, uint8_t i) { - if(q->attempts) { + //if(q->attempts) { q->iv->radio->sendCmdPacket(q->iv, TX_REQ_INFO, (SINGLE_FRAME + i), true); q->iv->radioStatistics.retransmits++; mWaitTime.startTimeMonitor(SINGLEFR_TIMEOUT); // timeout mState = States::WAIT; - } else { + /*} else { //add(q, true); closeRequest(q, false); - } + }*/ } private: @@ -604,6 +610,8 @@ class Communication : public CommQueue<> { rec->ts = q->ts; q->iv->setValue(1, rec, (uint32_t) ((p->packet[24] << 8) + p->packet[25])/1); q->iv->miMultiParts +=4; + rec->mqttSentStatus = MqttSentStatus::NEW_DATA; + } else if ( p->packet[9] == 0x01 || p->packet[9] == 0x10 ) {//second frame for MI, 3rd gen. answers in 0x10 DPRINT_IVID(DBG_INFO, q->iv->id); if ( p->packet[9] == 0x01 ) { @@ -620,6 +628,7 @@ class Communication : public CommQueue<> { record_t<> *rec = q->iv->getRecordStruct(InverterDevInform_Simple); // choose the record structure rec->ts = q->ts; q->iv->setValue(0, rec, (uint32_t) ((((p->packet[10] << 8) | p->packet[11]) << 8 | p->packet[12]) << 8 | p->packet[13])/1); + rec->mqttSentStatus = MqttSentStatus::NEW_DATA; if(*mSerialDebug) { DPRINT(DBG_INFO,F("HW_FB_TLmValue ")); @@ -667,6 +676,7 @@ class Communication : public CommQueue<> { inline void miGPFDecode(packet_t *p, const queue_s *q) { record_t<> *rec = q->iv->getRecordStruct(InverterDevInform_Simple); // choose the record structure rec->ts = q->ts; + rec->mqttSentStatus = MqttSentStatus::NEW_DATA; q->iv->setValue(2, rec, (uint32_t) (((p->packet[10] << 8) | p->packet[11]))); //FLD_GRID_PROFILE_CODE q->iv->setValue(3, rec, (uint32_t) (((p->packet[12] << 8) | p->packet[13]))); //FLD_GRID_PROFILE_VERSION @@ -849,6 +859,8 @@ class Communication : public CommQueue<> { if (!stsok) { q->iv->setValue(q->iv->getPosByChFld(0, FLD_EVT, rec), rec, prntsts); q->iv->lastAlarm[0] = alarm_t(prntsts, q->ts, 0); + rec->ts = q->ts; + rec->mqttSentStatus = MqttSentStatus::NEW_DATA; } if (q->iv->alarmMesIndex < rec->record[q->iv->getPosByChFld(0, FLD_EVT, rec)]) { @@ -907,17 +919,12 @@ class Communication : public CommQueue<> { iv->setValue(iv->getPosByChFld(0, FLD_PAC, rec), rec, (float) ac_pow/10); iv->doCalculations(); + rec->mqttSentStatus = MqttSentStatus::NEW_DATA; // update status state-machine, if (ac_pow) iv->isProducing(); - //closeRequest(iv, iv->miMultiParts > 5); - - //mHeu.setGotAll(iv); - //cmdDone(false); if(NULL != mCbPayload) (mCbPayload)(RealTimeRunData_Debug, iv); - - //mState = States::RESET; // everything ok, next request } private: From ac2f772b74ed8c9fa4439ba01acde517eb5ee748 Mon Sep 17 00:00:00 2001 From: you69man Date: Sun, 14 Jan 2024 17:16:32 +0100 Subject: [PATCH 085/179] fix immediate clearing of display after sunset --- src/plugins/Display/Display_Mono.h | 121 ++++++++++++++-------- src/plugins/Display/Display_Mono_128X64.h | 3 +- 2 files changed, 79 insertions(+), 45 deletions(-) diff --git a/src/plugins/Display/Display_Mono.h b/src/plugins/Display/Display_Mono.h index c8d25f8b..e0a03c61 100644 --- a/src/plugins/Display/Display_Mono.h +++ b/src/plugins/Display/Display_Mono.h @@ -81,7 +81,7 @@ class DisplayMono { mDispWidth = mDisplay->getDisplayWidth(); mDispHeight = mDisplay->getDisplayHeight(); mDispSwitchTime.stopTimeMonitor(); - if (mCfg->graph_ratio == 100) // if graph ratio is 100% start in graph mode + if (100 == mCfg->graph_ratio) // if graph ratio is 100% start in graph mode mDispSwitchState = DispSwitchState::GRAPH; else if (mCfg->graph_ratio != 0) mDispSwitchTime.startTimeMonitor(150 * (100 - mCfg->graph_ratio)); // start display mode change only if ratio is neither 0 nor 100 @@ -90,39 +90,70 @@ class DisplayMono { // pixelshift screensaver with wipe effect void calcPixelShift(int range) { int8_t mod = (millis() / 10000) % ((range >> 1) << 2); - mPixelshift = mCfg->screenSaver == 1 ? ((mod < range) ? mod - (range >> 1) : -(mod - range - (range >> 1) + 1)) : 0; + mPixelshift = (1 == mCfg->screenSaver) ? ((mod < range) ? mod - (range >> 1) : -(mod - range - (range >> 1) + 1)) : 0; } + protected: + enum class PowerGraphState { + NO_TIME_SYNC, + IN_PERIOD, + WAIT_4_NEW_PERIOD, + WAIT_4_RESTART + }; + + // initialize power graph and allocate data buffer based on pixel width void initPowerGraph(uint8_t width, uint8_t height) { DBGPRINTLN(F("---- Init Power Graph ----")); mPgWidth = width; mPgHeight = height; mPgData = new float[mPgWidth]; + mPgState = PowerGraphState::NO_TIME_SYNC; resetPowerGraph(); -/* - Inverter<> *iv; - mPgMaxAvailPower = 0; - uint8_t nInv = mSys->getNumInverters(); - for (uint8_t i = 0; i < nInv; i++) { - iv = mSys->getInverterByPos(i); - if (iv == NULL) - continue; - for (uint8_t ch = 0; ch < 6; ch++) { - mPgMaxAvailPower += iv->config->chMaxPwr[ch]; - } - } - DBGPRINTLN("max. Power = " + String(mPgMaxAvailPower));*/ } + // add new value to power graph and maintain state engine for period times void addPowerGraphEntry(float val) { - if ((nullptr != mPgData) && (mDisplayData->utcTs > 0)) { // precondition: power graph initialized and utc time available - calcPowerGraphValues(); - //mPgData[mPgLastPos] = std::max(mPgData[mPgLastPos], (uint8_t) (val * 255.0 / mPgMaxAvailPower)); // normalizing of data to 0-255 - mPgData[mPgLastPos] = std::max(mPgData[mPgLastPos], val); - mPgMaxPwr = std::max(mPgMaxPwr, val); // max value of stored data for scaling of y-axis + if (nullptr == mPgData) // power graph not initialized + return; + + bool store_entry = false; + switch(mPgState) { + case PowerGraphState::NO_TIME_SYNC: + if ((mDisplayData->pGraphStartTime > 0) && (mDisplayData->pGraphEndTime > 0) && // wait until period data is available ... + (mDisplayData->utcTs >= mDisplayData->pGraphStartTime) && (mDisplayData->utcTs < mDisplayData->pGraphEndTime)) { // and current time is in period + storeStartEndTimes(); // period was received -> store + store_entry = true; + mPgState = PowerGraphState::IN_PERIOD; + } + break; + case PowerGraphState::IN_PERIOD: + if (mDisplayData->utcTs > mPgEndTime) // check if end of day is reached ... + mPgState = PowerGraphState::WAIT_4_NEW_PERIOD; // then wait for new period setting + else + store_entry = true; + break; + case PowerGraphState::WAIT_4_NEW_PERIOD: + if ((mPgStartTime != mDisplayData->pGraphStartTime) || (mPgEndTime != mDisplayData->pGraphEndTime)) { // wait until new time period was received ... + storeStartEndTimes(); // and store it for next period + mPgState = PowerGraphState::WAIT_4_RESTART; + } + break; + case PowerGraphState::WAIT_4_RESTART: + if ((mDisplayData->utcTs >= mPgStartTime) && (mDisplayData->utcTs < mPgEndTime)) { // wait until current time is in period again ... + resetPowerGraph(); // then reset power graph data + store_entry = true; + mPgState = PowerGraphState::IN_PERIOD; + } + break; + } + if (store_entry) { + mPgLastPos = std::min((uint8_t) sss2PgPos(mDisplayData->utcTs - mPgStartTime), (uint8_t) (mPgWidth - 1)); // current datapoint based on seconds since start + mPgData[mPgLastPos] = std::max(mPgData[mPgLastPos], val); // update current datapoint to maximum of all seen values + mPgMaxPwr = std::max(mPgMaxPwr, val); // update max value of stored data for scaling of y-axis } } + // plot power graph to given display offset void plotPowerGraph(uint8_t xoff, uint8_t yoff) { if (nullptr == mPgData) // power graph not initialized return; @@ -146,7 +177,7 @@ class DisplayMono { tm.Minute = 0; tm.Second = 0; for (; tm.Hour <= endHour; tm.Hour++) { - uint8_t x_pos_screen = getPowerGraphXpos(sss2pgpos((uint32_t) makeTime(tm) - mDisplayData->pGraphStartTime)); // scale horizontal axis + uint8_t x_pos_screen = getPowerGraphXpos(sss2PgPos((uint32_t) makeTime(tm) - mDisplayData->pGraphStartTime)); // scale horizontal axis mDisplay->drawPixel(xoff + x_pos_screen, yoff - 1); } @@ -163,8 +194,8 @@ class DisplayMono { // draw curve for (uint8_t i = 1; i <= mPgLastPos; i++) { - mDisplay->drawLine(xoff + getPowerGraphXpos(i - 1), yoff - getPowerGraphYpos(i - 1), - xoff + getPowerGraphXpos(i), yoff - getPowerGraphYpos(i)); + mDisplay->drawLine(xoff + getPowerGraphXpos(i - 1), yoff - getPowerGraphValueYpos(i - 1), + xoff + getPowerGraphXpos(i), yoff - getPowerGraphValueYpos(i)); } // print max power value @@ -195,6 +226,7 @@ class DisplayMono { return change; } + // reset power graph void resetPowerGraph() { if (mPgData != nullptr) { mPgMaxPwr = 0.0; @@ -205,34 +237,35 @@ class DisplayMono { } } - uint8_t sss2pgpos(uint seconds_since_start) { - uint32_t diff = (mDisplayData->pGraphEndTime - mDisplayData->pGraphStartTime); - if(diff) - return (seconds_since_start * (mPgWidth - 1) / diff); - return 0; + // store start and end times of current time period and calculate period length + void storeStartEndTimes() { + mPgStartTime = mDisplayData->pGraphStartTime; + mPgEndTime = mDisplayData->pGraphEndTime; + mPgPeriod = mDisplayData->pGraphEndTime - mDisplayData->pGraphStartTime; // time period of power graph in sec for scaling of x-axis } - void calcPowerGraphValues() { - mPgPeriod = mDisplayData->pGraphEndTime - mDisplayData->pGraphStartTime; // length of power graph for scaling of x-axis - uint32_t oldTimeOfDay = mPgTimeOfDay; - mPgTimeOfDay = (mDisplayData->utcTs > mDisplayData->pGraphStartTime) ? mDisplayData->utcTs - mDisplayData->pGraphStartTime : 0; // current time of day with respect to current sunrise time - if (oldTimeOfDay > mPgTimeOfDay) // new day -> reset old data - resetPowerGraph(); - if(0 == mPgPeriod) - mPgPeriod = 1; - mPgLastPos = std::min((uint8_t) (mPgTimeOfDay * (mPgWidth - 1) / mPgPeriod), (uint8_t) (mPgWidth - 1)); // current datapoint based on currenct time of day + // get power graph datapoint index, scaled to current time period, by seconds since start + uint8_t sss2PgPos(uint seconds_since_start) { + if(mPgPeriod) + return (seconds_since_start * (mPgWidth - 1) / mPgPeriod); + else + return 0; } - uint8_t getPowerGraphXpos(uint8_t p) { + // get X-position of power graph, scaled to lastpos, by according data point index + uint8_t getPowerGraphXpos(uint8_t p) { if ((p <= mPgLastPos) && (mPgLastPos > 0)) return((p * (mPgWidth - 1)) / mPgLastPos); // scaling of x-axis - return 0; + else + return 0; } - uint8_t getPowerGraphYpos(uint8_t p) { + // get Y-position of power graph, scaled to maximum value, by according datapoint index + uint8_t getPowerGraphValueYpos(uint8_t p) { if ((p < mPgWidth) && (mPgMaxPwr > 0)) return((mPgData[p] * (uint32_t) mPgHeight / mPgMaxPwr)); // scaling of data to graph height - return 0; + else + return 0; } protected: @@ -254,9 +287,11 @@ class DisplayMono { float *mPgData = nullptr; uint8_t mPgHeight = 0; float mPgMaxPwr = 0.0; - uint32_t mPgPeriod = 0; // seconds - uint32_t mPgTimeOfDay = 0; + uint32_t mPgStartTime = 0; + uint32_t mPgEndTime = 0; + uint32_t mPgPeriod = 0; // seconds uint8_t mPgLastPos = 0; + PowerGraphState mPgState = PowerGraphState::NO_TIME_SYNC; uint16_t mDispHeight; uint8_t mLuminance; diff --git a/src/plugins/Display/Display_Mono_128X64.h b/src/plugins/Display/Display_Mono_128X64.h index 34f35834..b4aa08cc 100644 --- a/src/plugins/Display/Display_Mono_128X64.h +++ b/src/plugins/Display/Display_Mono_128X64.h @@ -89,9 +89,8 @@ class DisplayMono128X64 : public DisplayMono { calcPixelShift(pixelShiftRange); // add new power data to power graph - if (mDisplayData->nrProducing > 0) { + if (mDisplayData->nrProducing > 0) addPowerGraphEntry(mDisplayData->totalPower); - } // print Date and time if (0 != mDisplayData->utcTs) From 9d29276c06de2296bcc4b2d2f46770697ea8e62d Mon Sep 17 00:00:00 2001 From: rejoe2 Date: Mon, 15 Jan 2024 16:12:51 +0100 Subject: [PATCH 086/179] Add "second try" - serial info deactivated - first tx is counted as retransmit --- src/hm/Communication.h | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/src/hm/Communication.h b/src/hm/Communication.h index 5f14a5af..9e383b6a 100644 --- a/src/hm/Communication.h +++ b/src/hm/Communication.h @@ -91,6 +91,7 @@ class Communication : public CommQueue<> { mIsRetransmit = false; if(NULL == q->iv->radio) cmdDone(false); // can't communicate while radio is not defined! + mFirstTry = q->iv->isAvailable(); q->iv->mCmd = q->cmd; q->iv->mIsSingleframeReq = false; mState = States::START; @@ -140,8 +141,25 @@ class Communication : public CommQueue<> { if((IV_HMS == q->iv->ivGen) || (IV_HMT == q->iv->ivGen)) { q->iv->radio->switchFrequency(q->iv, HOY_BOOT_FREQ_KHZ, (q->iv->config->frequency*FREQ_STEP_KHZ + HOY_BASE_FREQ_KHZ)); mWaitTime.startTimeMonitor(1000); - } else if(IV_MI == q->iv->ivGen) - q->iv->mIvTxCnt++; + } else { + if(IV_MI == q->iv->ivGen) + q->iv->mIvTxCnt++; + if(mFirstTry){ + mFirstTry = false; + mState = States::START; + setAttempt(); + mHeu.evalTxChQuality(q->iv, false, 0, 0); + //q->iv->radioStatistics.rxFailNoAnser++; + q->iv->radioStatistics.retransmits++; + mWaitTime.stopTimeMonitor(); + + /*if(*mSerialDebug) { + DPRINT_IVID(DBG_INFO, q->iv->id); + DBGPRINTLN(F("second try")); + }*/ + return; + } + } } closeRequest(q, false); break; @@ -553,7 +571,6 @@ class Communication : public CommQueue<> { q->iv->mGotLastMsg = false; q->iv->miMultiParts = 0; mIsRetransmit = false; - mFirstTry = false; // for correct reset mState = States::RESET; DBGPRINTLN(F("-----")); } From 1ac920d52d8b5768f0be26db5b5959543af84e38 Mon Sep 17 00:00:00 2001 From: lumapu Date: Mon, 15 Jan 2024 21:38:54 +0100 Subject: [PATCH 087/179] 0.8.57 * merge PR: fix immediate clearing of display after sunset #1364 --- src/CHANGES.md | 3 +++ src/defines.h | 2 +- src/plugins/Display/Display_Mono.h | 27 ++++++++++++++++----------- 3 files changed, 20 insertions(+), 12 deletions(-) diff --git a/src/CHANGES.md b/src/CHANGES.md index d566216a..2032f009 100644 --- a/src/CHANGES.md +++ b/src/CHANGES.md @@ -1,5 +1,8 @@ # Development Changes +## 0.8.57 - 2024-01-15 +* merge PR: fix immediate clearing of display after sunset #1364 + ## 0.8.56 - 2024-01-15 * potential fix of update problems and random reboots #1359 #1354 diff --git a/src/defines.h b/src/defines.h index 46c9a249..528aa714 100644 --- a/src/defines.h +++ b/src/defines.h @@ -13,7 +13,7 @@ //------------------------------------- #define VERSION_MAJOR 0 #define VERSION_MINOR 8 -#define VERSION_PATCH 56 +#define VERSION_PATCH 57 //------------------------------------- typedef struct { diff --git a/src/plugins/Display/Display_Mono.h b/src/plugins/Display/Display_Mono.h index e0a03c61..58b4224f 100644 --- a/src/plugins/Display/Display_Mono.h +++ b/src/plugins/Display/Display_Mono.h @@ -116,12 +116,16 @@ class DisplayMono { if (nullptr == mPgData) // power graph not initialized return; + bool storeStartEndTimes = false; bool store_entry = false; switch(mPgState) { case PowerGraphState::NO_TIME_SYNC: - if ((mDisplayData->pGraphStartTime > 0) && (mDisplayData->pGraphEndTime > 0) && // wait until period data is available ... - (mDisplayData->utcTs >= mDisplayData->pGraphStartTime) && (mDisplayData->utcTs < mDisplayData->pGraphEndTime)) { // and current time is in period - storeStartEndTimes(); // period was received -> store + if ((mDisplayData->pGraphStartTime > 0) + && (mDisplayData->pGraphEndTime > 0) // wait until period data is available ... + && (mDisplayData->utcTs >= mDisplayData->pGraphStartTime) + && (mDisplayData->utcTs < mDisplayData->pGraphEndTime)) // and current time is in period + { + storeStartEndTimes = true; // period was received -> store store_entry = true; mPgState = PowerGraphState::IN_PERIOD; } @@ -134,7 +138,7 @@ class DisplayMono { break; case PowerGraphState::WAIT_4_NEW_PERIOD: if ((mPgStartTime != mDisplayData->pGraphStartTime) || (mPgEndTime != mDisplayData->pGraphEndTime)) { // wait until new time period was received ... - storeStartEndTimes(); // and store it for next period + storeStartEndTimes = true; // and store it for next period mPgState = PowerGraphState::WAIT_4_RESTART; } break; @@ -146,6 +150,14 @@ class DisplayMono { } break; } + + // store start and end times of current time period and calculate period length + if (storeStartEndTimes) { + mPgStartTime = mDisplayData->pGraphStartTime; + mPgEndTime = mDisplayData->pGraphEndTime; + mPgPeriod = mDisplayData->pGraphEndTime - mDisplayData->pGraphStartTime; // time period of power graph in sec for scaling of x-axis + } + if (store_entry) { mPgLastPos = std::min((uint8_t) sss2PgPos(mDisplayData->utcTs - mPgStartTime), (uint8_t) (mPgWidth - 1)); // current datapoint based on seconds since start mPgData[mPgLastPos] = std::max(mPgData[mPgLastPos], val); // update current datapoint to maximum of all seen values @@ -237,13 +249,6 @@ class DisplayMono { } } - // store start and end times of current time period and calculate period length - void storeStartEndTimes() { - mPgStartTime = mDisplayData->pGraphStartTime; - mPgEndTime = mDisplayData->pGraphEndTime; - mPgPeriod = mDisplayData->pGraphEndTime - mDisplayData->pGraphStartTime; // time period of power graph in sec for scaling of x-axis - } - // get power graph datapoint index, scaled to current time period, by seconds since start uint8_t sss2PgPos(uint seconds_since_start) { if(mPgPeriod) From 358344ad6d0f23f20aa5f52a0c493b473decb182 Mon Sep 17 00:00:00 2001 From: lumapu Date: Mon, 15 Jan 2024 22:55:56 +0100 Subject: [PATCH 088/179] 0.8.57 * merge PR: MI-MQTT and last retransmit #1363 * fixed DTU-ID, now built from the unique part of the MAC * fix lang in `/system` #1346 --- src/CHANGES.md | 3 +++ src/hm/CommQueue.h | 1 - src/hm/Communication.h | 4 ++-- src/hm/radio.h | 15 +++++++++------ src/web/html/update.html | 2 +- src/web/lang.h | 8 ++++---- src/web/lang.json | 2 +- 7 files changed, 20 insertions(+), 15 deletions(-) diff --git a/src/CHANGES.md b/src/CHANGES.md index 2032f009..15dcfba2 100644 --- a/src/CHANGES.md +++ b/src/CHANGES.md @@ -2,6 +2,9 @@ ## 0.8.57 - 2024-01-15 * merge PR: fix immediate clearing of display after sunset #1364 +* merge PR: MI-MQTT and last retransmit #1363 +* fixed DTU-ID, now built from the unique part of the MAC +* fix lang in `/system` #1346 ## 0.8.56 - 2024-01-15 * potential fix of update problems and random reboots #1359 #1354 diff --git a/src/hm/CommQueue.h b/src/hm/CommQueue.h index d62efe07..13decc01 100644 --- a/src/hm/CommQueue.h +++ b/src/hm/CommQueue.h @@ -11,7 +11,6 @@ #include "hmInverter.h" #include "../utils/dbg.h" -// needs a '+1' because the comparison does not send if attempts is equal 0 #define DEFAULT_ATTEMPS 5 #define MORE_ATTEMPS_ALARMDATA 15 #define MORE_ATTEMPS_GRIDONPROFILEPARA 15 diff --git a/src/hm/Communication.h b/src/hm/Communication.h index 9e383b6a..7b5c84f9 100644 --- a/src/hm/Communication.h +++ b/src/hm/Communication.h @@ -144,14 +144,14 @@ class Communication : public CommQueue<> { } else { if(IV_MI == q->iv->ivGen) q->iv->mIvTxCnt++; - if(mFirstTry){ + if(mFirstTry) { mFirstTry = false; - mState = States::START; setAttempt(); mHeu.evalTxChQuality(q->iv, false, 0, 0); //q->iv->radioStatistics.rxFailNoAnser++; q->iv->radioStatistics.retransmits++; mWaitTime.stopTimeMonitor(); + mState = States::START; /*if(*mSerialDebug) { DPRINT_IVID(DBG_INFO, q->iv->id); diff --git a/src/hm/radio.h b/src/hm/radio.h index e5eda128..1422b285 100644 --- a/src/hm/radio.h +++ b/src/hm/radio.h @@ -103,16 +103,19 @@ class Radio { void generateDtuSn(void) { uint32_t chipID = 0; #ifdef ESP32 - uint64_t MAC = ESP.getEfuseMac(); - chipID = ((MAC >> 8) & 0xFF0000) | ((MAC >> 24) & 0xFF00) | ((MAC >> 40) & 0xFF); + chipID = (ESP.getEfuseMac() & 0xffffffff); #else chipID = ESP.getChipId(); #endif - mDtuSn = 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++) { - mDtuSn |= (chipID % 10) << (i * 4); - chipID /= 10; + + uint8_t t; + for(int i = 0; i < (7 << 2); i += 4) { + t = (chipID >> i) & 0x0f; + if(t > 0x09) + t -= 6; + mDtuSn |= (t << i); } + mDtuSn |= 0x80000000; // the first digit is an 8 for DTU production year 2022, the rest is filled with the ESP chipID in decimal } uint32_t mDtuSn; diff --git a/src/web/html/update.html b/src/web/html/update.html index 3fed75a5..800d9dfe 100644 --- a/src/web/html/update.html +++ b/src/web/html/update.html @@ -10,7 +10,7 @@
    {#SELECT_FILE} (*.bin) -

    {#INSTALLED_VERSION}:

    +

    {#INSTALLED_VERSION}:

    diff --git a/src/web/lang.h b/src/web/lang.h index 3c3cd631..546399aa 100644 --- a/src/web/lang.h +++ b/src/web/lang.h @@ -73,15 +73,15 @@ #endif #ifdef LANG_DE - #define FACTORY_RESET "Ahoy Factory Reset" -#else /*LANG_EN*/ #define FACTORY_RESET "Ahoy auf Werkseinstellungen zurücksetzen" +#else /*LANG_EN*/ + #define FACTORY_RESET "Ahoy Factory Reset" #endif #ifdef LANG_DE - #define BTN_REBOOT "Reboot" -#else /*LANG_EN*/ #define BTN_REBOOT "Ahoy neustarten" +#else /*LANG_EN*/ + #define BTN_REBOOT "Reboot" #endif #endif /*__LANG_H__*/ diff --git a/src/web/lang.json b/src/web/lang.json index c910650e..ae8c0ca2 100644 --- a/src/web/lang.json +++ b/src/web/lang.json @@ -781,7 +781,7 @@ { "token": "IRQ_WORKING", "en": "Interrupt Pin working", - "de": "Interrupt Pin funktoniert" + "de": "Interrupt Pin funktioniert" }, { "token": "NRF24_DATA_RATE", From f503516c9feba7a7cc6fb427954165ce05cd7746 Mon Sep 17 00:00:00 2001 From: lumapu Date: Tue, 16 Jan 2024 00:11:34 +0100 Subject: [PATCH 089/179] 0.8.57 * added protection to prevent update to wrong firmware (environment check) --- ahoy.code-workspace | 45 ++++++++++++++++++++++++++++++++++++++++ src/CHANGES.md | 1 + src/app.cpp | 2 +- src/web/RestApi.h | 4 ++-- src/web/html/update.html | 29 ++++++++++++++++++++------ src/web/lang.json | 20 ++++++++++++++++++ 6 files changed, 92 insertions(+), 9 deletions(-) create mode 100644 ahoy.code-workspace diff --git a/ahoy.code-workspace b/ahoy.code-workspace new file mode 100644 index 00000000..20d5909e --- /dev/null +++ b/ahoy.code-workspace @@ -0,0 +1,45 @@ +{ + "folders": [ + { + "path": "." + }, + { + "path": "src" + } + ], + "settings": { + "files.associations": { + "algorithm": "cpp", + "array": "cpp", + "chrono": "cpp", + "deque": "cpp", + "format": "cpp", + "forward_list": "cpp", + "functional": "cpp", + "initializer_list": "cpp", + "iterator": "cpp", + "list": "cpp", + "memory": "cpp", + "queue": "cpp", + "random": "cpp", + "regex": "cpp", + "vector": "cpp", + "xhash": "cpp", + "xlocmon": "cpp", + "xlocnum": "cpp", + "xmemory": "cpp", + "xstring": "cpp", + "xtree": "cpp", + "xutility": "cpp", + "*.tcc": "cpp", + "string": "cpp", + "unordered_map": "cpp", + "unordered_set": "cpp", + "string_view": "cpp", + "sstream": "cpp", + "istream": "cpp", + "ostream": "cpp" + }, + "editor.formatOnSave": false + } +} \ No newline at end of file diff --git a/src/CHANGES.md b/src/CHANGES.md index 15dcfba2..316224ab 100644 --- a/src/CHANGES.md +++ b/src/CHANGES.md @@ -5,6 +5,7 @@ * merge PR: MI-MQTT and last retransmit #1363 * fixed DTU-ID, now built from the unique part of the MAC * fix lang in `/system` #1346 +* added protection to prevent update to wrong firmware (environment check) ## 0.8.56 - 2024-01-15 * potential fix of update problems and random reboots #1359 #1354 diff --git a/src/app.cpp b/src/app.cpp index 6350e990..8dfcdb4d 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -35,7 +35,7 @@ void app::setup() { } #if defined(ESP32) if(mConfig->cmt.enabled) { - mCmtRadio.setup(&mConfig->serial.debug, &mConfig->serial.privacyLog, &mConfig->serial.printWholeTrace, mConfig->cmt.pinSclk, mConfig->cmt.pinSdio, mConfig->cmt.pinCsb, mConfig->cmt.pinFcsb, false); + mCmtRadio.setup(&mConfig->serial.debug, &mConfig->serial.privacyLog, &mConfig->serial.printWholeTrace, mConfig->cmt.pinSclk, mConfig->cmt.pinSdio, mConfig->cmt.pinCsb, mConfig->cmt.pinFcsb); } #endif #ifdef ETHERNET diff --git a/src/web/RestApi.h b/src/web/RestApi.h index edf92a97..097adac3 100644 --- a/src/web/RestApi.h +++ b/src/web/RestApi.h @@ -326,8 +326,8 @@ class RestApi { void getHtmlSystem(AsyncWebServerRequest *request, JsonObject obj) { getSysInfo(request, obj.createNestedObject(F("system"))); getGeneric(request, obj.createNestedObject(F("generic"))); - char tmp[100]; - snprintf(tmp, 100, "%s

    %s", FACTORY_RESET, BTN_REBOOT); + char tmp[200]; + snprintf(tmp, 200, "%s

    %s", FACTORY_RESET, BTN_REBOOT); obj[F("html")] = String(tmp); } diff --git a/src/web/html/update.html b/src/web/html/update.html index 800d9dfe..52ace5f1 100644 --- a/src/web/html/update.html +++ b/src/web/html/update.html @@ -23,17 +23,34 @@
    {#HTML_FOOTER} + From 7e81709eb83d5a13c0b838c94c23a0e97cbce7d3 Mon Sep 17 00:00:00 2001 From: lumapu Date: Sun, 21 Jan 2024 14:00:44 +0100 Subject: [PATCH 114/179] 0.8.61 * add favicon to header * improved NRF communication * merge PR: provide localized times to display mono classes #1376 * merge PR: Bypass OOM-Crash on minimal version & history access #1378 * merge PR: Add some REST Api Endpoints to avail_endpoints #1380 --- src/CHANGES.md | 3 +++ src/web/RestApi.h | 2 ++ 2 files changed, 5 insertions(+) diff --git a/src/CHANGES.md b/src/CHANGES.md index 2c52b991..d37c7eb6 100644 --- a/src/CHANGES.md +++ b/src/CHANGES.md @@ -3,6 +3,9 @@ ## 0.8.61 - 2024-01-21 * add favicon to header * improved NRF communication +* merge PR: provide localized times to display mono classes #1376 +* merge PR: Bypass OOM-Crash on minimal version & history access #1378 +* merge PR: Add some REST Api Endpoints to avail_endpoints #1380 ## 0.8.60 - 2024-01-20 * merge PR: non blocking nRF loop #1371 diff --git a/src/web/RestApi.h b/src/web/RestApi.h index 003a0592..98e0afd2 100644 --- a/src/web/RestApi.h +++ b/src/web/RestApi.h @@ -206,8 +206,10 @@ class RestApi { #endif /* !defined(ETHERNET) */ ep[F("system")] = url + F("system"); ep[F("live")] = url + F("live"); + #if defined(ENABLE_HISTORY) ep[F("powerHistory")] = url + F("powerHistory"); ep[F("yieldDayHistory")] = url + F("yieldDayHistory"); + #endif } From e039820dba7fc7a6f26b0778192cf2b852c32f24 Mon Sep 17 00:00:00 2001 From: lumapu Date: Sun, 21 Jan 2024 17:44:10 +0100 Subject: [PATCH 115/179] 0.8.62 * updated version in footer #1381 * repaired radio statistics #1382 --- src/CHANGES.md | 4 ++++ src/defines.h | 2 +- src/hm/Communication.h | 7 ++++--- src/hm/hmInverter.h | 3 ++- src/hm/hmRadio.h | 27 ++++----------------------- src/web/html/includes/footer.html | 2 +- 6 files changed, 16 insertions(+), 29 deletions(-) diff --git a/src/CHANGES.md b/src/CHANGES.md index d37c7eb6..23f0e685 100644 --- a/src/CHANGES.md +++ b/src/CHANGES.md @@ -1,5 +1,9 @@ # Development Changes +## 0.8.62 - 2024-01-21 +* updated version in footer #1381 +* repaired radio statistics #1382 + ## 0.8.61 - 2024-01-21 * add favicon to header * improved NRF communication diff --git a/src/defines.h b/src/defines.h index 8d6a3086..c614103e 100644 --- a/src/defines.h +++ b/src/defines.h @@ -13,7 +13,7 @@ //------------------------------------- #define VERSION_MAJOR 0 #define VERSION_MINOR 8 -#define VERSION_PATCH 61 +#define VERSION_PATCH 62 //------------------------------------- typedef struct { diff --git a/src/hm/Communication.h b/src/hm/Communication.h index 59f21d2b..80b43ab3 100644 --- a/src/hm/Communication.h +++ b/src/hm/Communication.h @@ -143,16 +143,17 @@ class Communication : public CommQueue<> { } else { if(IV_MI == q->iv->ivGen) q->iv->mIvTxCnt++; + if(mFirstTry) { - mFirstTry = false; + mFirstTry = false; setAttempt(); mHeu.evalTxChQuality(q->iv, false, 0, 0); - //q->iv->radioStatistics.rxFailNoAnser++; + q->iv->radioStatistics.rxFailNoAnser++; q->iv->radioStatistics.retransmits++; q->iv->radio->mRadioWaitTime.stopTimeMonitor(); mState = States::START; - return; + return; } } } diff --git a/src/hm/hmInverter.h b/src/hm/hmInverter.h index 2c1c6362..b28682e5 100644 --- a/src/hm/hmInverter.h +++ b/src/hm/hmInverter.h @@ -210,9 +210,10 @@ class Inverter { cb(GridOnProFilePara, false); } else if (mGetLossInterval > AHOY_GET_LOSS_INTERVAL) { // get loss rate mGetLossInterval = 1; + cb(RealTimeRunData_Debug, false); // get live data cb(GetLossRate, false); } else - cb(RealTimeRunData_Debug, false); // get live data + cb(RealTimeRunData_Debug, false); // get live data } } else { // MI if(0 == getFwVersion()) { diff --git a/src/hm/hmRadio.h b/src/hm/hmRadio.h index d298afd3..67b1abcd 100644 --- a/src/hm/hmRadio.h +++ b/src/hm/hmRadio.h @@ -78,13 +78,11 @@ class HmRadio : public Radio { #else mNrf24->begin(mSpi.get(), ce, cs); #endif - mNrf24->setRetries(3, 15); // 3*250us + 250us and 16 loops -> 15.25ms + mNrf24->setRetries(3, 15); // wait 3*250 = 750us, 16 * 250us -> 4000us = 4ms - mNrf24->setChannel(mRfChLst[mRxChIdx]); - mNrf24->startListening(); mNrf24->setDataRate(RF24_250KBPS); - mNrf24->setAutoAck(true); - mNrf24->enableDynamicAck(); + //mNrf24->setAutoAck(true); // enabled by default + //mNrf24->enableDynamicAck(); mNrf24->enableDynamicPayloads(); mNrf24->setCRCLength(RF24_CRC_16); mNrf24->setAddressWidth(5); @@ -155,21 +153,6 @@ class HmRadio : public Radio { if(tx_ok) mLastIv->mAckCount++; - // start listening - if(!mIsRetransmit) { - if(mTxSetupTime < 30) { - mRxChIdx = (mTxChIdx + 4) % RF_CHANNELS; - mNrf24->setChannel(mRfChLst[mRxChIdx]); - mNrf24->startListening(); - - do { - yield(); - } while((millis() - mMillis) < 37); - } - } - mIsRetransmit = false; - - mRxChIdx = (mTxChIdx + 2) % RF_CHANNELS; mNrf24->setChannel(mRfChLst[mRxChIdx]); mNrf24->startListening(); @@ -185,7 +168,7 @@ class HmRadio : public Radio { if (getReceived()) { // check what we got, returns true for last package mNRFisInRX = false; mRadioWaitTime.startTimeMonitor(DURATION_PAUSE_LASTFR); // let the inverter first end his transmissions - // add stop listening? + mNrf24->stopListening(); } else { innerLoopTimeout = DURATION_LISTEN_MIN; mTimeslotStart = millis(); @@ -398,7 +381,6 @@ class HmRadio : public Radio { mLastIv = iv; iv->mDtuTxCnt++; mNRFisInRX = false; - mIsRetransmit = isRetransmit; } uint64_t getIvId(Inverter<> *iv) { @@ -433,7 +415,6 @@ class HmRadio : public Radio { bool rxPendular = false; uint32_t innerLoopTimeout = DURATION_LISTEN_MIN; uint8_t mTxSetupTime = 0; - bool mIsRetransmit = false; std::unique_ptr mSpi; std::unique_ptr mNrf24; diff --git a/src/web/html/includes/footer.html b/src/web/html/includes/footer.html index bbc4c6ce..6aa89673 100644 --- a/src/web/html/includes/footer.html +++ b/src/web/html/includes/footer.html @@ -1,6 +1,6 @@ diff --git a/src/web/lang.json b/src/web/lang.json index 92f046a5..78c7d653 100644 --- a/src/web/lang.json +++ b/src/web/lang.json @@ -865,6 +865,36 @@ } ] }, + { + "name": "serial.html", + "list": [ + { + "token": "BTN_CLEAR", + "en": "clear", + "de": "l&ouuml;schen" + }, + { + "token": "BTN_AUTOSCROLL", + "en": "autoscroll", + "de": "automatisch scrollen" + }, + { + "token": "BTN_COPY", + "en": "copy", + "de": "kopieren" + }, + { + "token": "CONSOLE_ACTIVE", + "en": "console active", + "de": "Konsole aktiv" + }, + { + "token": "UPTIME", + "en": "uptime", + "de": "Laufzeit" + } + ] + }, { "name": "index.html", "list": [ diff --git a/src/web/web.h b/src/web/web.h index 77a059cb..0ffd0594 100644 --- a/src/web/web.h +++ b/src/web/web.h @@ -387,7 +387,6 @@ class Web { AsyncWebServerResponse *response = request->beginResponse_P(200, favicon_type, favicon_ico, favicon_ico_len); response->addHeader(F("Content-Encoding"), "gzip"); request->send(response); - mApp->resetLockTimeout(); } void showNotFound(AsyncWebServerRequest *request) { From 2e94f2844137dcac147adebafe2c5acf5d6721a0 Mon Sep 17 00:00:00 2001 From: lumapu Date: Sun, 4 Feb 2024 00:24:44 +0100 Subject: [PATCH 149/179] 0.8.71 * removed "yield day" history --- src/CHANGES.md | 1 + src/plugins/history.h | 13 +++++++------ src/web/RestApi.h | 20 -------------------- src/web/html/history.html | 22 ---------------------- 4 files changed, 8 insertions(+), 48 deletions(-) diff --git a/src/CHANGES.md b/src/CHANGES.md index b661b8d2..4a2e91f6 100644 --- a/src/CHANGES.md +++ b/src/CHANGES.md @@ -9,6 +9,7 @@ * save settings before they are exported #1395 * fix autologin bug if no password is set * translated `/serial` +* removed "yield day" history ## 0.8.70 - 2024-02-01 * prevent sending commands to inverter which isn't active #1387 diff --git a/src/plugins/history.h b/src/plugins/history.h index da57800f..7a938284 100644 --- a/src/plugins/history.h +++ b/src/plugins/history.h @@ -52,8 +52,8 @@ class HistoryData { mCurPwr.reset(); mCurPwr.refreshCycle = mConfig->inst.sendInterval; - mYieldDay.reset(); - mYieldDay.refreshCycle = 60; + //mYieldDay.reset(); + //mYieldDay.refreshCycle = 60; } void tickerSecond() { @@ -80,7 +80,7 @@ class HistoryData { mMaximumDay = roundf(maxPwr); } - if((++mYieldDay.loopCnt % mYieldDay.refreshCycle) == 0) { + /*if((++mYieldDay.loopCnt % mYieldDay.refreshCycle) == 0) { if (*mTs > mApp->getSunset()) { if ((!mDayStored) && (yldDay > 0)) { addValue(&mYieldDay, roundf(yldDay)); @@ -88,11 +88,12 @@ class HistoryData { } } else if (*mTs > mApp->getSunrise()) mDayStored = false; - } + }*/ } uint16_t valueAt(HistoryStorageType type, uint16_t i) { - storage_t *s = (HistoryStorageType::POWER == type) ? &mCurPwr : &mYieldDay; + //storage_t *s = (HistoryStorageType::POWER == type) ? &mCurPwr : &mYieldDay; + storage_t *s = &mCurPwr; uint16_t idx = (s->dispIdx + i) % HISTORY_DATA_ARR_LENGTH; return s->data[idx]; } @@ -119,7 +120,7 @@ class HistoryData { uint32_t *mTs; storage_t mCurPwr; - storage_t mYieldDay; + //storage_t mYieldDay; bool mDayStored = false; uint16_t mMaximumDay = 0; }; diff --git a/src/web/RestApi.h b/src/web/RestApi.h index e6760390..240a18f2 100644 --- a/src/web/RestApi.h +++ b/src/web/RestApi.h @@ -107,7 +107,6 @@ class RestApi { #endif /* !defined(ETHERNET) */ else if(path == "live") getLive(request,root); else if (path == "powerHistory") getPowerHistory(request, root); - else if (path == "yieldDayHistory") getYieldDayHistory(request, root); else { if(path.substring(0, 12) == "inverter/id/") getInverter(root, request->url().substring(17).toInt()); @@ -208,7 +207,6 @@ class RestApi { ep[F("live")] = url + F("live"); #if defined(ENABLE_HISTORY) ep[F("powerHistory")] = url + F("powerHistory"); - ep[F("yieldDayHistory")] = url + F("yieldDayHistory"); #endif } @@ -832,24 +830,6 @@ class RestApi { #endif /*ENABLE_HISTORY*/ } - void getYieldDayHistory(AsyncWebServerRequest *request, JsonObject obj) { - getGeneric(request, obj.createNestedObject(F("generic"))); - #if defined(ENABLE_HISTORY) - obj[F("refresh")] = 86400; // 1 day - uint16_t max = 0; - for (uint16_t fld = 0; fld < HISTORY_DATA_ARR_LENGTH; fld++) { - uint16_t value = mApp->getHistoryValue((uint8_t)HistoryStorageType::YIELD, fld); - obj[F("value")][fld] = value; - if (value > max) - max = value; - } - obj[F("max")] = max; - #else - obj[F("refresh")] = 86400; // 1 day - #endif /*ENABLE_HISTORY*/ - } - - bool setCtrl(JsonObject jsonIn, JsonObject jsonOut, const char *clientIP) { Inverter<> *iv = mSys->getInverterByPos(jsonIn[F("id")]); bool accepted = true; diff --git a/src/web/html/history.html b/src/web/html/history.html index 975a02ed..b553a19f 100644 --- a/src/web/html/history.html +++ b/src/web/html/history.html @@ -20,14 +20,6 @@ {#MAXIMUM}: W. {#UPDATED} {#SECONDS}

    -

    {#TOTAL_YIELD_PER_DAY}

    -
    -
    -

    - {#MAXIMUM}: Wh
    - {#UPDATED} {#SECONDS} -

    -
    {#HTML_FOOTER} @@ -94,20 +86,6 @@ if (pwrExeOnce) { pwrExeOnce = false; window.setInterval("getAjax('/api/powerHistory', parsePowerHistory)", mRefresh * 1000); - - setTimeout(() => { - getAjax("/api/yieldDayHistory", parseYieldDayHistory); - } , 20); - } - } - function parseYieldDayHistory(obj) { - if (null != obj) { - parseNav(obj.generic); - parseHistory(obj, "yd", ydExeOnce) - } - if (ydExeOnce) { - ydExeOnce = false; - window.setInterval("getAjax('/api/yieldDayHistory', parseYieldDayHistory)", mRefresh * 500); } } From 883aefbe643ce04c6fe45882479f560ecd33db16 Mon Sep 17 00:00:00 2001 From: lumapu Date: Sun, 4 Feb 2024 00:31:39 +0100 Subject: [PATCH 150/179] 0.8.71 * fix html --- src/web/html/history.html | 1 + 1 file changed, 1 insertion(+) diff --git a/src/web/html/history.html b/src/web/html/history.html index b553a19f..7e317b59 100644 --- a/src/web/html/history.html +++ b/src/web/html/history.html @@ -79,6 +79,7 @@ function parsePowerHistory(obj){ if (null != obj) { + parseNav(obj.generic); parseHistory(obj,"pwr", pwrExeOnce) document.getElementById("pwrLast").innerHTML = mLastValue document.getElementById("pwrMaxDay").innerHTML = obj.maxDay From 14c5a7ad3227c3701ecaef8b2dea8bea13dfbd9d Mon Sep 17 00:00:00 2001 From: lumapu Date: Sun, 4 Feb 2024 13:21:02 +0100 Subject: [PATCH 151/179] 0.8.72 * fixed translation #1403 * fixed sending commands to inverters which are soft turned off #1397 * reduce switchChannel command for HMS (only each 5th cycle it will be send now) --- src/CHANGES.md | 5 +++++ src/defines.h | 2 +- src/hm/hmInverter.h | 9 ++++----- src/hms/hmsRadio.h | 12 ++++++++++-- src/web/RestApi.h | 12 +++++++----- src/web/lang.json | 2 +- 6 files changed, 28 insertions(+), 14 deletions(-) diff --git a/src/CHANGES.md b/src/CHANGES.md index 4a2e91f6..82a6ed45 100644 --- a/src/CHANGES.md +++ b/src/CHANGES.md @@ -1,5 +1,10 @@ # Development Changes +## 0.8.72 - 2024-02-03 +* fixed translation #1403 +* fixed sending commands to inverters which are soft turned off #1397 +* reduce switchChannel command for HMS (only each 5th cycle it will be send now) + ## 0.8.71 - 2024-02-03 * fix heuristics reset * fix CMT missing frames problem diff --git a/src/defines.h b/src/defines.h index 6fbc5ff1..f0ec528d 100644 --- a/src/defines.h +++ b/src/defines.h @@ -13,7 +13,7 @@ //------------------------------------- #define VERSION_MAJOR 0 #define VERSION_MINOR 8 -#define VERSION_PATCH 71 +#define VERSION_PATCH 72 //------------------------------------- typedef struct { diff --git a/src/hm/hmInverter.h b/src/hm/hmInverter.h index d9db0b7f..b62e985a 100644 --- a/src/hm/hmInverter.h +++ b/src/hm/hmInverter.h @@ -152,7 +152,6 @@ class Inverter { static uint32_t *timestamp; // system timestamp static cfgInst_t *generalConfig; // general inverter configuration from setup - //static IApp *app; // pointer to app interface public: @@ -290,18 +289,18 @@ class Inverter { } bool setDevControlRequest(uint8_t cmd) { - if(InverterStatus::PRODUCING == status) { + if(InverterStatus::OFF != status) { mDevControlRequest = true; devControlCmd = cmd; //app->triggerTickSend(); // done in RestApi.h, because of "chicken-and-egg problem ;-)" } - return (InverterStatus::PRODUCING == status); + return (InverterStatus::OFF != status); } bool setDevCommand(uint8_t cmd) { - if(InverterStatus::PRODUCING == status) + if(InverterStatus::OFF != status) devControlCmd = cmd; - return (InverterStatus::PRODUCING == status); + return (InverterStatus::OFF != status); } void addValue(uint8_t pos, uint8_t buf[], record_t<> *rec) { diff --git a/src/hms/hmsRadio.h b/src/hms/hmsRadio.h index dd9af3ac..0fa1b6ab 100644 --- a/src/hms/hmsRadio.h +++ b/src/hms/hmsRadio.h @@ -9,6 +9,8 @@ #include "cmt2300a.h" #include "../hm/radio.h" +#define CMT_SWITCH_CHANNEL_CYCLE 5 + template class CmtRadio : public Radio { typedef Cmt2300a CmtType; @@ -151,6 +153,10 @@ class CmtRadio : public Radio { } inline void sendSwitchChCmd(Inverter<> *iv, uint8_t ch) { + if(CMT_SWITCH_CHANNEL_CYCLE > ++mSwitchCycle) + return; + mSwitchCycle = 0; + /** ch: * 0x00: 860.00 MHz * 0x01: 860.25 MHz @@ -172,9 +178,10 @@ class CmtRadio : public Radio { inline void getRx(void) { packet_t p; p.millis = millis() - mMillis; - CmtStatus status = mCmt.getRx(p.packet, &p.len, 28, &p.rssi); - if(CmtStatus::SUCCESS == status) + if(CmtStatus::SUCCESS == mCmt.getRx(p.packet, &p.len, 28, &p.rssi)) { + mSwitchCycle = 0; mBufCtrl.push(p); + } if(p.packet[9] > ALL_FRAMES) { // indicates last frame setExpectedFrames(p.packet[9] - ALL_FRAMES); @@ -188,6 +195,7 @@ class CmtRadio : public Radio { bool mCmtAvail = false; bool mRqstGetRx = false; uint32_t mMillis; + uint8_t mSwitchCycle = 0; }; #endif /*__HMS_RADIO_H__*/ diff --git a/src/web/RestApi.h b/src/web/RestApi.h index 240a18f2..564d6c62 100644 --- a/src/web/RestApi.h +++ b/src/web/RestApi.h @@ -68,7 +68,7 @@ class RestApi { DynamicJsonDocument json(128); JsonObject dummy = json.as(); if(obj[F("path")] == "ctrl") - setCtrl(obj, dummy, "api"); + setCtrl(obj, dummy, "*"); else if(obj[F("path")] == "setup") setSetup(obj, dummy); } @@ -839,10 +839,12 @@ class RestApi { } jsonOut[F("id")] = jsonIn[F("id")]; - if(strncmp("api", clientIP, 3) != 0) { - if(mApp->isProtected(clientIP)) { - jsonOut[F("error")] = F(INV_IS_PROTECTED); - return false; + if(mConfig->sys.adminPwd[0] != '\0') { + if(strncmp("*", clientIP, 1) != 0) { // no call from API (MqTT) + if(mApp->isProtected(clientIP)) { + jsonOut[F("error")] = F(INV_IS_PROTECTED); + return false; + } } } diff --git a/src/web/lang.json b/src/web/lang.json index 78c7d653..689fbb06 100644 --- a/src/web/lang.json +++ b/src/web/lang.json @@ -871,7 +871,7 @@ { "token": "BTN_CLEAR", "en": "clear", - "de": "l&ouuml;schen" + "de": "löschen" }, { "token": "BTN_AUTOSCROLL", From 6b5435a2467bb1ba745d40213f1b7e4d0db359e6 Mon Sep 17 00:00:00 2001 From: lumapu Date: Sun, 4 Feb 2024 21:26:48 +0100 Subject: [PATCH 152/179] 0.8.73 * fix nullpointer during communication #1401 * added `max_power` to MqTT total values #1375 --- src/CHANGES.md | 4 ++++ src/defines.h | 2 +- src/hm/Communication.h | 33 ++++++++++++++++----------------- src/publisher/pubMqttIvData.h | 13 ++++++++++--- 4 files changed, 31 insertions(+), 21 deletions(-) diff --git a/src/CHANGES.md b/src/CHANGES.md index 82a6ed45..813f1708 100644 --- a/src/CHANGES.md +++ b/src/CHANGES.md @@ -1,5 +1,9 @@ # Development Changes +## 0.8.73 - 2024-02-03 +* fix nullpointer during communication #1401 +* added `max_power` to MqTT total values #1375 + ## 0.8.72 - 2024-02-03 * fixed translation #1403 * fixed sending commands to inverters which are soft turned off #1397 diff --git a/src/defines.h b/src/defines.h index f0ec528d..b71a1d25 100644 --- a/src/defines.h +++ b/src/defines.h @@ -13,7 +13,7 @@ //------------------------------------- #define VERSION_MAJOR 0 #define VERSION_MINOR 8 -#define VERSION_PATCH 72 +#define VERSION_PATCH 73 //------------------------------------- typedef struct { diff --git a/src/hm/Communication.h b/src/hm/Communication.h index 4350eac7..d1521f81 100644 --- a/src/hm/Communication.h +++ b/src/hm/Communication.h @@ -295,12 +295,14 @@ class Communication : public CommQueue<> { return; } - compilePayload(q); + if(compilePayload(q)) { + if((NULL != mCbPayload) && (GridOnProFilePara != q->cmd) && (GetLossRate != q->cmd)) + (mCbPayload)(q->cmd, q->iv); - if((NULL != mCbPayload) && (GridOnProFilePara != q->cmd) && (GetLossRate != q->cmd)) - (mCbPayload)(q->cmd, q->iv); + closeRequest(q, true); + } else + closeRequest(q, false); - closeRequest(q, true); break; } } @@ -498,7 +500,7 @@ class Communication : public CommQueue<> { return accepted; } - inline void compilePayload(const queue_s *q) { + inline bool compilePayload(const queue_s *q) { uint16_t crc = 0xffff, crcRcv = 0x0000; for(uint8_t i = 0; i < mMaxFrameId; i++) { if(i == (mMaxFrameId - 1)) { @@ -514,13 +516,12 @@ class Communication : public CommQueue<> { DBGPRINT(F("CRC Error ")); if(q->attempts == 0) { DBGPRINTLN(F("-> Fail")); - closeRequest(q, false); } else DBGPRINTLN(F("-> complete retransmit")); mCompleteRetry = true; mState = States::RESET; - return; + return false; } memset(mPayload, 0, MAX_BUFFER); @@ -530,7 +531,7 @@ class Communication : public CommQueue<> { for(uint8_t i = 0; i < mMaxFrameId; i++) { if(mLocalBuf[i].len + len > MAX_BUFFER) { DPRINTLN(DBG_ERROR, F("payload buffer to small!")); - return; + return true; } memcpy(&mPayload[len], mLocalBuf[i].buf, mLocalBuf[i].len); len += mLocalBuf[i].len; @@ -552,19 +553,18 @@ class Communication : public CommQueue<> { if(GridOnProFilePara == q->cmd) { q->iv->addGridProfile(mPayload, len); - return; + return true; } record_t<> *rec = q->iv->getRecordStruct(q->cmd); if(NULL == rec) { if(GetLossRate == q->cmd) { q->iv->parseGetLossRate(mPayload, len); - return; - } else { + return true; + } else DPRINTLN(DBG_ERROR, F("record is NULL!")); - closeRequest(q, false); - } - return; + + return false; } if((rec->pyldLen != len) && (0 != rec->pyldLen)) { if(*mSerialDebug) { @@ -572,10 +572,8 @@ class Communication : public CommQueue<> { DBGPRINT(String(rec->pyldLen)); DBGPRINTLN(F(" bytes")); } - /*q->iv->radioStatistics.rxFail++;*/ - closeRequest(q, false); - return; + return false; } rec->ts = q->ts; @@ -597,6 +595,7 @@ class Communication : public CommQueue<> { yield(); } } + return true; } void sendRetransmit(const queue_s *q, uint8_t i) { diff --git a/src/publisher/pubMqttIvData.h b/src/publisher/pubMqttIvData.h index 64f72e1e..cfed38b9 100644 --- a/src/publisher/pubMqttIvData.h +++ b/src/publisher/pubMqttIvData.h @@ -78,7 +78,7 @@ class PubMqttIvData { if((RealTimeRunData_Debug != mCmd) || !mRTRDataHasBeenSent) { // send RealTimeRunData only once mSendTotals = (RealTimeRunData_Debug == mCmd); - memset(mTotal, 0, sizeof(float) * 4); + memset(mTotal, 0, sizeof(float) * 5); mState = FIND_NXT_IV; } else mSendList->pop(); @@ -164,6 +164,9 @@ class PubMqttIvData { case FLD_PDC: mTotal[3] += mIv->getValue(mPos, rec); break; + case FLD_MP: + mTotal[4] += mIv->getValue(mPos, rec); + break; } } else mAllTotalFound = false; @@ -204,7 +207,7 @@ class PubMqttIvData { void stateSendTotals() { uint8_t fieldId; mRTRDataHasBeenSent = true; - if(mPos < 4) { + if(mPos < 5) { bool retained = true; switch (mPos) { default: @@ -230,6 +233,10 @@ class PubMqttIvData { fieldId = FLD_PDC; retained = false; break; + case 4: + fieldId = FLD_MP; + retained = false; + break; } snprintf(mSubTopic, 32 + MAX_NAME_LENGTH, "total/%s", fields[fieldId]); snprintf(mVal, 40, "%g", ah::round3(mTotal[mPos])); @@ -251,7 +258,7 @@ class PubMqttIvData { uint8_t mCmd; uint8_t mLastIvId; bool mSendTotals, mTotalFound, mAllTotalFound, mSendTotalYd; - float mTotal[4], mYldTotalStore; + float mTotal[5], mYldTotalStore; Inverter<> *mIv, *mIvSend; uint8_t mPos; From 3740a09d2a04e46da340d98cb3479a54a55b0f61 Mon Sep 17 00:00:00 2001 From: lumapu Date: Mon, 5 Feb 2024 23:58:52 +0100 Subject: [PATCH 153/179] 0.8.74 * reduced cppcheck linter warnings significantly --- src/CHANGES.md | 3 + src/app.cpp | 15 ++- src/app.h | 100 ++++++++--------- src/config/settings.h | 7 +- src/defines.h | 2 +- src/hm/CommQueue.h | 2 +- src/hm/Communication.h | 39 ++++--- src/hm/Heuristic.h | 7 +- src/hm/hmDefines.h | 2 +- src/hm/hmInverter.h | 141 ++++++++++------------- src/hm/hmRadio.h | 27 +++-- src/hm/hmSystem.h | 14 +-- src/hm/nrfHal.h | 2 +- src/hm/radio.h | 8 +- src/hms/cmt2300a.h | 15 +-- src/hms/cmtHal.h | 2 +- src/hms/esp32_3wSpi.h | 7 +- src/hms/hmsRadio.h | 25 ++--- src/plugins/Display/Display.h | 11 +- src/plugins/Display/Display_Mono.h | 12 +- src/plugins/history.h | 33 ++---- src/publisher/pubMqtt.h | 172 +++++++++++++++-------------- src/publisher/pubMqttIvData.h | 2 +- src/publisher/pubSerial.h | 12 +- src/utils/improv.h | 16 +-- src/utils/scheduler.h | 16 ++- src/utils/spiPatcher.h | 2 +- src/utils/timemonitor.h | 6 +- src/web/RestApi.h | 24 ++-- src/web/web.h | 48 ++++---- src/wifi/ahoywifi.h | 16 +-- 31 files changed, 375 insertions(+), 413 deletions(-) diff --git a/src/CHANGES.md b/src/CHANGES.md index 813f1708..f0491335 100644 --- a/src/CHANGES.md +++ b/src/CHANGES.md @@ -1,5 +1,8 @@ # Development Changes +## 0.8.74 - 2024-02-05 +* reduced cppcheck linter warnings significantly + ## 0.8.73 - 2024-02-03 * fix nullpointer during communication #1401 * added `max_power` to MqTT total values #1375 diff --git a/src/app.cpp b/src/app.cpp index 4f49d93b..d9b0f933 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -13,7 +13,10 @@ //----------------------------------------------------------------------------- -app::app() : ah::Scheduler {} {} +app::app() : ah::Scheduler {} { + memset(mVersion, 0, sizeof(char) * 12); + memset(mVersionModules, 0, sizeof(char) * 12); +} //----------------------------------------------------------------------------- @@ -228,7 +231,6 @@ void app::updateNtp(void) { onceAt(std::bind(&app::tickMidnight, this), midTrig, "midNi"); if (mConfig->sys.schedReboot) { - uint32_t localTime = gTimezone.toLocal(mTimestamp); uint32_t rebootTrig = gTimezone.toUTC(localTime - (localTime % 86400) + 86410); // reboot 10 secs after midnght if (rebootTrig <= mTimestamp) { //necessary for times other than midnight to prevent reboot loop rebootTrig += 86400; @@ -301,9 +303,8 @@ void app::tickIVCommunication(void) { bool zeroValues = false; uint32_t nxtTrig = 0; - Inverter<> *iv; for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i ++) { - iv = mSys.getInverterByPos(i); + Inverter<> *iv = mSys.getInverterByPos(i); if(NULL == iv) continue; @@ -390,10 +391,9 @@ void app::tickMidnight(void) { // clear max values if(mConfig->inst.rstMaxValsMidNight) { - uint8_t pos; record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug); for(uint8_t i = 0; i <= iv->channels; i++) { - pos = iv->getPosByChFld(i, FLD_MP, rec); + uint8_t pos = iv->getPosByChFld(i, FLD_MP, rec); iv->setValue(pos, rec, 0.0f); } } @@ -592,9 +592,8 @@ void app::updateLed(void) { uint8_t led_on = (mConfig->led.high_active) ? (mConfig->led.luminance) : (255-mConfig->led.luminance); if (mConfig->led.led[0] != DEF_PIN_OFF) { - Inverter<> *iv; for (uint8_t id = 0; id < mSys.getNumInverters(); id++) { - iv = mSys.getInverterByPos(id); + Inverter<> *iv = mSys.getInverterByPos(id); if (NULL != iv) { if (iv->isProducing()) { // turn on when at least one inverter is producing diff --git a/src/app.h b/src/app.h index b44d3781..60be4d1d 100644 --- a/src/app.h +++ b/src/app.h @@ -90,7 +90,7 @@ class app : public IApp, public ah::Scheduler { void handleIntr(void) { mNrfRadio.handleIntr(); } - void* getRadioObj(bool nrf) { + void* getRadioObj(bool nrf) override { if(nrf) return (void*)&mNrfRadio; else { @@ -108,19 +108,19 @@ class app : public IApp, public ah::Scheduler { } #endif - uint32_t getUptime() { + uint32_t getUptime() override { return Scheduler::getUptime(); } - uint32_t getTimestamp() { + uint32_t getTimestamp() override { return Scheduler::mTimestamp; } - uint64_t getTimestampMs() { + uint64_t getTimestampMs() override { return ((uint64_t)Scheduler::mTimestamp * 1000) + ((uint64_t)millis() - (uint64_t)Scheduler::mTsMillis) % 1000; } - bool saveSettings(bool reboot) { + bool saveSettings(bool reboot) override { mShowRebootRequest = true; // only message on index, no reboot mSavePending = true; mSaveReboot = reboot; @@ -131,7 +131,7 @@ class app : public IApp, public ah::Scheduler { return true; } - void initInverter(uint8_t id) { + void initInverter(uint8_t id) override { mSys.addInverter(id, [this](Inverter<> *iv) { if((IV_MI == iv->ivGen) || (IV_HM == iv->ivGen)) iv->radio = &mNrfRadio; @@ -142,7 +142,7 @@ class app : public IApp, public ah::Scheduler { }); } - bool readSettings(const char *path) { + bool readSettings(const char *path) override { return mSettings.readSettings(path); } @@ -150,80 +150,80 @@ class app : public IApp, public ah::Scheduler { return mSettings.eraseSettings(eraseWifi); } - bool getSavePending() { + bool getSavePending() override { return mSavePending; } - bool getLastSaveSucceed() { + bool getLastSaveSucceed() override { return mSettings.getLastSaveSucceed(); } - bool getShouldReboot() { + bool getShouldReboot() override { return mSaveReboot; } #if !defined(ETHERNET) - void scanAvailNetworks() { + void scanAvailNetworks() override { mWifi.scanAvailNetworks(); } - bool getAvailNetworks(JsonObject obj) { + bool getAvailNetworks(JsonObject obj) override { return mWifi.getAvailNetworks(obj); } - void setupStation(void) { + void setupStation(void) override { mWifi.setupStation(); } - void setStopApAllowedMode(bool allowed) { + void setStopApAllowedMode(bool allowed) override { mWifi.setStopApAllowedMode(allowed); } - String getStationIp(void) { + String getStationIp(void) override { return mWifi.getStationIp(); } - bool getWasInCh12to14(void) const { + bool getWasInCh12to14(void) const override { return mWifi.getWasInCh12to14(); } #endif /* !defined(ETHERNET) */ - void setRebootFlag() { + void setRebootFlag() override { once(std::bind(&app::tickReboot, this), 3, "rboot"); } - const char *getVersion() { + const char *getVersion() override { return mVersion; } - const char *getVersionModules() { + const char *getVersionModules() override { return mVersionModules; } - uint32_t getSunrise() { + uint32_t getSunrise() override { return mSunrise; } - uint32_t getSunset() { + uint32_t getSunset() override { return mSunset; } - bool getSettingsValid() { + bool getSettingsValid() override { return mSettings.getValid(); } - bool getRebootRequestState() { + bool getRebootRequestState() override { return mShowRebootRequest; } - void setMqttDiscoveryFlag() { + void setMqttDiscoveryFlag() override { #if defined(ENABLE_MQTT) once(std::bind(&PubMqttType::sendDiscoveryConfig, &mMqtt), 1, "disCf"); #endif } - bool getMqttIsConnected() { + bool getMqttIsConnected() override { #if defined(ENABLE_MQTT) return mMqtt.isConnected(); #else @@ -231,7 +231,7 @@ class app : public IApp, public ah::Scheduler { #endif } - uint32_t getMqttTxCnt() { + uint32_t getMqttTxCnt() override { #if defined(ENABLE_MQTT) return mMqtt.getTxCnt(); #else @@ -239,7 +239,7 @@ class app : public IApp, public ah::Scheduler { #endif } - uint32_t getMqttRxCnt() { + uint32_t getMqttRxCnt() override { #if defined(ENABLE_MQTT) return mMqtt.getRxCnt(); #else @@ -267,11 +267,11 @@ class app : public IApp, public ah::Scheduler { return mProtection->isProtected(clientIp); } - bool getNrfEnabled(void) { + bool getNrfEnabled(void) override { return mConfig->nrf.enabled; } - bool getCmtEnabled(void) { + bool getCmtEnabled(void) override { return mConfig->cmt.enabled; } @@ -283,19 +283,19 @@ class app : public IApp, public ah::Scheduler { return mConfig->cmt.pinIrq; } - uint32_t getTimezoneOffset() { + uint32_t getTimezoneOffset() override { return mApi.getTimezoneOffset(); } - void getSchedulerInfo(uint8_t *max) { + void getSchedulerInfo(uint8_t *max) override { getStat(max); } - void getSchedulerNames(void) { + void getSchedulerNames(void) override { printSchedulers(); } - void setTimestamp(uint32_t newTime) { + void setTimestamp(uint32_t newTime) override { DPRINT(DBG_DEBUG, F("setTimestamp: ")); DBGPRINTLN(String(newTime)); if(0 == newTime) @@ -310,7 +310,7 @@ class app : public IApp, public ah::Scheduler { Scheduler::setTimestamp(newTime); } - uint16_t getHistoryValue(uint8_t type, uint16_t i) { + uint16_t getHistoryValue(uint8_t type, uint16_t i) override { #if defined(ENABLE_HISTORY) return mHistory.valueAt((HistoryStorageType)type, i); #else @@ -318,7 +318,7 @@ class app : public IApp, public ah::Scheduler { #endif } - uint16_t getHistoryMaxDay() { + uint16_t getHistoryMaxDay() override { #if defined(ENABLE_HISTORY) return mHistory.getMaximumDay(); #else @@ -372,11 +372,11 @@ class app : public IApp, public ah::Scheduler { void tickNtpUpdate(void); #if defined(ETHERNET) void onNtpUpdate(bool gotTime); - bool mNtpReceived; + bool mNtpReceived = false; #endif /* defined(ETHERNET) */ void updateNtp(void); - void triggerTickSend() { + void triggerTickSend() override { once(std::bind(&app::tickSend, this), 0, "tSend"); } @@ -395,7 +395,7 @@ class app : public IApp, public ah::Scheduler { HmRadio<> mNrfRadio; Communication mCommunication; - bool mShowRebootRequest; + bool mShowRebootRequest = false; #if defined(ETHERNET) ahoyeth mEth; @@ -404,7 +404,7 @@ class app : public IApp, public ah::Scheduler { #endif /* defined(ETHERNET) */ WebType mWeb; RestApiType mApi; - Protection *mProtection; + Protection *mProtection = nullptr; #ifdef ENABLE_SYSLOG DbgSyslog mDbgSyslog; #endif @@ -421,26 +421,26 @@ class app : public IApp, public ah::Scheduler { char mVersion[12]; char mVersionModules[12]; settings mSettings; - settings_t *mConfig; - bool mSavePending; - bool mSaveReboot; + settings_t *mConfig = nullptr; + bool mSavePending = false; + bool mSaveReboot = false; - uint8_t mSendLastIvId; - bool mSendFirst; - bool mAllIvNotAvail; + uint8_t mSendLastIvId = 0; + bool mSendFirst = false; + bool mAllIvNotAvail = false; - bool mNetworkConnected; + bool mNetworkConnected = false; // mqtt #if defined(ENABLE_MQTT) PubMqttType mMqtt; #endif /*ENABLE_MQTT*/ - bool mMqttReconnect; - bool mMqttEnabled; + bool mMqttReconnect = false; + bool mMqttEnabled = false; // sun - int32_t mCalculatedTimezoneOffset; - uint32_t mSunrise, mSunset; + int32_t mCalculatedTimezoneOffset = 0; + uint32_t mSunrise = 0, mSunset = 0; // plugins #if defined(PLUGIN_DISPLAY) diff --git a/src/config/settings.h b/src/config/settings.h index ec9712ea..0cb9ed4e 100644 --- a/src/config/settings.h +++ b/src/config/settings.h @@ -13,6 +13,7 @@ #include #include +#include #include #include "../defines.h" @@ -206,7 +207,7 @@ typedef struct { class settings { public: settings() { - mLastSaveSucceed = false; + std::fill(reinterpret_cast(&mCfg), reinterpret_cast(&mCfg) + sizeof(mCfg), 0); } void setup() { @@ -377,7 +378,7 @@ class settings { memcpy(&tmp, &mCfg.sys, sizeof(cfgSys_t)); } // erase all settings and reset to default - memset(&mCfg, 0, sizeof(settings_t)); + std::fill(reinterpret_cast(&mCfg), reinterpret_cast(&mCfg) + sizeof(mCfg), 0); mCfg.sys.protectionMask = DEF_PROT_INDEX | DEF_PROT_LIVE | DEF_PROT_SERIAL | DEF_PROT_SETUP | DEF_PROT_UPDATE | DEF_PROT_SYSTEM | DEF_PROT_API | DEF_PROT_MQTT | DEF_PROT_HISTORY; mCfg.sys.darkMode = false; @@ -847,7 +848,7 @@ class settings { #endif settings_t mCfg; - bool mLastSaveSucceed; + bool mLastSaveSucceed = 0; }; #endif /*__SETTINGS_H__*/ diff --git a/src/defines.h b/src/defines.h index b71a1d25..bc9d2452 100644 --- a/src/defines.h +++ b/src/defines.h @@ -13,7 +13,7 @@ //------------------------------------- #define VERSION_MAJOR 0 #define VERSION_MINOR 8 -#define VERSION_PATCH 73 +#define VERSION_PATCH 74 //------------------------------------- typedef struct { diff --git a/src/hm/CommQueue.h b/src/hm/CommQueue.h index acbe9a35..328309ac 100644 --- a/src/hm/CommQueue.h +++ b/src/hm/CommQueue.h @@ -91,7 +91,7 @@ class CommQueue { inc(&mRdPtr); } - void setTs(uint32_t *ts) { + void setTs(const uint32_t *ts) { mQueue[mRdPtr].ts = *ts; } diff --git a/src/hm/Communication.h b/src/hm/Communication.h index d1521f81..326fb57e 100644 --- a/src/hm/Communication.h +++ b/src/hm/Communication.h @@ -6,6 +6,7 @@ #ifndef __COMMUNICATION_H__ #define __COMMUNICATION_H__ +#include #include "CommQueue.h" #include #include "../utils/crc.h" @@ -194,8 +195,8 @@ class Communication : public CommQueue<> { q->iv->radio->mBufCtrl.pop(); return; // don't wait for empty buffer } else if(IV_MI == q->iv->ivGen) { - if(parseMiFrame(p, q)) - q->iv->curFrmCnt++; + parseMiFrame(p, q); + q->iv->curFrmCnt++; } } //else -> serial does not match @@ -385,7 +386,7 @@ class Communication : public CommQueue<> { } } - inline bool validateIvSerial(uint8_t buf[], Inverter<> *iv) { + inline bool validateIvSerial(const uint8_t buf[], Inverter<> *iv) { uint8_t tmp[4]; CP_U32_BigEndian(tmp, iv->radioId.u64 >> 8); for(uint8_t i = 0; i < 4; i++) { @@ -435,7 +436,7 @@ class Communication : public CommQueue<> { return true; } - inline bool parseMiFrame(packet_t *p, const queue_s *q) { + inline void parseMiFrame(packet_t *p, const queue_s *q) { if((!mIsRetransmit && p->packet[9] == 0x00) && (p->millis < LIMIT_FAST_IV_MI)) //first frame is fast? mHeu.setIvRetriesGood(q->iv,p->millis < LIMIT_VERYFAST_IV_MI); if ((p->packet[0] == MI_REQ_CH1 + ALL_FRAMES) @@ -458,11 +459,9 @@ class Communication : public CommQueue<> { rec->ts = q->ts; miStsConsolidate(q, ((p->packet[0] == 0x88) ? 1 : 2), rec, p->packet[10], p->packet[12], p->packet[9], p->packet[11]); } - - return true; } - inline bool parseDevCtrl(packet_t *p, const queue_s *q) { + inline bool parseDevCtrl(const packet_t *p, const queue_s *q) { switch(p->packet[12]) { case ActivePowerContr: if(p->packet[13] != 0x00) @@ -524,7 +523,7 @@ class Communication : public CommQueue<> { return false; } - memset(mPayload, 0, MAX_BUFFER); + mPayload.fill(0); int8_t rssi = -127; uint8_t len = 0; @@ -547,19 +546,19 @@ class Communication : public CommQueue<> { DBGPRINT(String(len)); if(*mPrintWholeTrace) { DBGPRINT(F("): ")); - ah::dumpBuf(mPayload, len); + ah::dumpBuf(mPayload.data(), len); } else DBGPRINTLN(F(")")); if(GridOnProFilePara == q->cmd) { - q->iv->addGridProfile(mPayload, len); + q->iv->addGridProfile(mPayload.data(), len); return true; } record_t<> *rec = q->iv->getRecordStruct(q->cmd); if(NULL == rec) { if(GetLossRate == q->cmd) { - q->iv->parseGetLossRate(mPayload, len); + q->iv->parseGetLossRate(mPayload.data(), len); return true; } else DPRINTLN(DBG_ERROR, F("record is NULL!")); @@ -578,7 +577,7 @@ class Communication : public CommQueue<> { rec->ts = q->ts; for (uint8_t i = 0; i < rec->length; i++) { - q->iv->addValue(i, mPayload, rec); + q->iv->addValue(i, mPayload.data(), rec); } rec->mqttSentStatus = MqttSentStatus::NEW_DATA; @@ -588,7 +587,7 @@ class Communication : public CommQueue<> { if(AlarmData == q->cmd) { uint8_t i = 0; while(1) { - if(0 == q->iv->parseAlarmLog(i++, mPayload, len)) + if(0 == q->iv->parseAlarmLog(i++, mPayload.data(), len)) break; if (NULL != mCbAlarm) (mCbAlarm)(q->iv); @@ -670,7 +669,7 @@ class Communication : public CommQueue<> { }; */ - if ( p->packet[9] == 0x00 ) {//first frame + if ( p->packet[9] == 0x00 ) { //first frame //FLD_FW_VERSION for (uint8_t i = 0; i < 5; i++) { q->iv->setValue(i, rec, (float) ((p->packet[(12+2*i)] << 8) + p->packet[(13+2*i)])/1); @@ -680,7 +679,7 @@ class Communication : public CommQueue<> { DBGPRINT(F("HW_VER is ")); DBGPRINTLN(String((p->packet[24] << 8) + p->packet[25])); } - record_t<> *rec = q->iv->getRecordStruct(InverterDevInform_Simple); // choose the record structure + rec = q->iv->getRecordStruct(InverterDevInform_Simple); // choose the record structure rec->ts = q->ts; q->iv->setValue(1, rec, (uint32_t) ((p->packet[24] << 8) + p->packet[25])/1); q->iv->miMultiParts +=4; @@ -894,7 +893,7 @@ class Communication : public CommQueue<> { statusMi = 8310; //trick? } - uint16_t prntsts = statusMi == 3 ? 1 : statusMi; + uint16_t prntsts = (statusMi == 3) ? 1 : statusMi; bool stsok = true; if ( prntsts != rec->record[q->iv->getPosByChFld(0, FLD_EVT, rec)] ) { //sth.'s changed? q->iv->alarmCnt = 1; // minimum... @@ -1017,17 +1016,17 @@ class Communication : public CommQueue<> { private: States mState = States::RESET; - uint32_t *mTimestamp; - bool *mPrivacyMode, *mSerialDebug, *mPrintWholeTrace; + uint32_t *mTimestamp = nullptr; + bool *mPrivacyMode = nullptr, *mSerialDebug = nullptr, *mPrintWholeTrace = nullptr; TimeMonitor mWaitTime = TimeMonitor(0, true); // start as expired (due to code in RESET state) std::array mLocalBuf; bool mFirstTry = false; // see, if we should do a second try bool mCompleteRetry = false; // remember if we did request a complete retransmission bool mIsRetransmit = false; // we already had waited one complete cycle - uint8_t mMaxFrameId; + uint8_t mMaxFrameId = 0; uint8_t mFramesExpected = 12; // 0x8c was highest last frame for alarm data uint16_t mTimeout = 0; // calculating that once should be ok - uint8_t mPayload[MAX_BUFFER]; + std::array mPayload; payloadListenerType mCbPayload = NULL; powerLimitAckListenerType mCbPwrAck = NULL; alarmListenerType mCbAlarm = NULL; diff --git a/src/hm/Heuristic.h b/src/hm/Heuristic.h index fa4512b5..2a36bc78 100644 --- a/src/hm/Heuristic.h +++ b/src/hm/Heuristic.h @@ -157,7 +157,7 @@ class Heuristic { DBGPRINTLN(String(iv->config->powerLevel)); } - uint8_t getIvRetries(Inverter<> *iv) { + uint8_t getIvRetries(const Inverter<> *iv) const { if(iv->heuristics.rxSpeeds[0]) return RETRIES_VERYFAST_IV; if(iv->heuristics.rxSpeeds[1]) @@ -200,7 +200,7 @@ class Heuristic { } private: - bool isNewTxCh(HeuristicInv *ih) { + bool isNewTxCh(const HeuristicInv *ih) const { return ih->txRfChId != ih->lastBestTxChId; } @@ -222,9 +222,6 @@ class Heuristic { } return 3; // standard } - - private: - uint8_t mChList[5] = {03, 23, 40, 61, 75}; }; diff --git a/src/hm/hmDefines.h b/src/hm/hmDefines.h index 0b42aeae..8fc4f71f 100644 --- a/src/hm/hmDefines.h +++ b/src/hm/hmDefines.h @@ -76,7 +76,7 @@ enum {CMD_CALC = 0xffff}; enum {CH0 = 0, CH1, CH2, CH3, CH4, CH5, CH6}; enum {INV_TYPE_1CH = 0, INV_TYPE_2CH, INV_TYPE_4CH, INV_TYPE_6CH}; -enum {INV_RADIO_TYPE_NRF = 0, INV_RADIO_TYPE_CMT}; +enum {INV_RADIO_TYPE_UNKNOWN = 0, INV_RADIO_TYPE_NRF, INV_RADIO_TYPE_CMT}; #define DURATION_ONEFRAME 50 // timeout parameter for each expected frame (ms) diff --git a/src/hm/hmInverter.h b/src/hm/hmInverter.h index b62e985a..275fce2c 100644 --- a/src/hm/hmInverter.h +++ b/src/hm/hmInverter.h @@ -81,12 +81,12 @@ enum class InverterStatus : uint8_t { template struct record_t { - byteAssign_t* assign; // assignment of bytes in payload - uint8_t length; // length of the assignment list - T *record; // data pointer - uint32_t ts; // timestamp of last received payload - uint8_t pyldLen; // expected payload length for plausibility check - MqttSentStatus mqttSentStatus; // indicates the current MqTT sent status + byteAssign_t* assign = nullptr; // assignment of bytes in payload + uint8_t length = 0; // length of the assignment list + T *record = nullptr; // data pointer + uint32_t ts = 0; // timestamp of last received payload + uint8_t pyldLen = 0; // expected payload length for plausibility check + MqttSentStatus mqttSentStatus = MqttSentStatus:: NEW_DATA; // indicates the current MqTT sent status }; struct alarm_t { @@ -113,42 +113,42 @@ const calcFunc_t calcFunctions[] = { template class Inverter { public: - uint8_t ivGen; // generation of inverter (HM / MI) - uint8_t ivRadioType; // refers to used radio (nRF24 / CMT) - cfgIv_t *config; // stored settings - uint8_t id; // unique id - uint8_t type; // integer which refers to inverter type - uint16_t alarmMesIndex; // Last recorded Alarm Message Index - uint16_t powerLimit[2]; // limit power output (multiplied by 10) - float actPowerLimit; // actual power limit - bool powerLimitAck; // acknowledged power limit (default: false) - uint8_t devControlCmd; // carries the requested cmd - 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 recordHwInfo; // structure for simple (hardware) info values - record_t recordConfig; // structure for system config values - record_t recordAlarm; // structure for alarm values - InverterStatus status; // indicates the current inverter status - std::array lastAlarm; // holds last 10 alarms - uint8_t rxOffset; // holds the default channel offset between tx and rx channel (nRF only) - int8_t rssi; // RSSI - uint16_t alarmCnt; // counts the total number of occurred alarms - uint16_t alarmLastId; // lastId which was received - uint8_t mCmd; // holds the command to send - bool mGotFragment; // shows if inverter has sent at least one fragment - uint8_t miMultiParts; // helper info for MI multiframe msgs - uint8_t outstandingFrames; // helper info to count difference between expected and received frames - uint8_t curFrmCnt; // count received frames in current loop - bool mGotLastMsg; // shows if inverter has already finished transmission cycle - bool mIsSingleframeReq; // indicates this is a missing single frame request - Radio *radio; // pointer to associated radio class - statistics_t radioStatistics; // information about transmitted, failed, ... packets - HeuristicInv heuristics; // heuristic information / logic - uint8_t curCmtFreq; // current used CMT frequency, used to check if freq. was changed during runtime - bool commEnabled; // 'pause night communication' sets this field to false - uint32_t tsMaxAcPower; // holds the timestamp when the MaxAC power was seen + uint8_t ivGen = IV_UNKNOWN; // generation of inverter (HM / MI) + uint8_t ivRadioType = INV_RADIO_TYPE_UNKNOWN; // refers to used radio (nRF24 / CMT) + cfgIv_t *config = nullptr; // stored settings + uint8_t id = 0; // unique id + uint8_t type = INV_TYPE_1CH; // integer which refers to inverter type + uint16_t alarmMesIndex = 0; // Last recorded Alarm Message Index + uint16_t powerLimit[2] = {0xffff, AbsolutNonPersistent}; // limit power output (multiplied by 10) + float actPowerLimit = -1; // actual power limit + bool powerLimitAck = false; // acknowledged power limit + uint8_t devControlCmd = InitDataState; // carries the requested cmd + serial_u radioId; // id converted to modbus + uint8_t channels = 1; // number of PV channels (1-4) + record_t recordMeas; // structure for measured values + record_t recordInfo; // structure for info values + record_t recordHwInfo; // structure for simple (hardware) info values + record_t recordConfig; // structure for system config values + record_t recordAlarm; // structure for alarm values + InverterStatus status = InverterStatus::OFF; // indicates the current inverter status + std::array lastAlarm; // holds last 10 alarms + uint8_t rxOffset = 0; // holds the default channel offset between tx and rx channel (nRF only) + int8_t rssi = 0; // RSSI + uint16_t alarmCnt = 0; // counts the total number of occurred alarms + uint16_t alarmLastId = 0; // lastId which was received + uint8_t mCmd = InitDataState; // holds the command to send + bool mGotFragment = false; // shows if inverter has sent at least one fragment + uint8_t miMultiParts = 0; // helper info for MI multiframe msgs + uint8_t outstandingFrames = 0; // helper info to count difference between expected and received frames + uint8_t curFrmCnt = 0; // count received frames in current loop + bool mGotLastMsg = false; // shows if inverter has already finished transmission cycle + bool mIsSingleframeReq = false; // indicates this is a missing single frame request + Radio *radio = nullptr; // pointer to associated radio class + statistics_t radioStatistics; // information about transmitted, failed, ... packets + HeuristicInv heuristics; // heuristic information / logic + uint8_t curCmtFreq = 0; // current used CMT frequency, used to check if freq. was changed during runtime + uint32_t tsMaxAcPower = 0; // holds the timestamp when the MaxAC power was seen + bool commEnabled = true; // 'pause night communication' sets this field to false static uint32_t *timestamp; // system timestamp static cfgInst_t *generalConfig; // general inverter configuration from setup @@ -156,29 +156,10 @@ class Inverter { public: Inverter() { - ivGen = IV_HM; - powerLimit[0] = 0xffff; // 6553.5 W Limit -> unlimited - powerLimit[1] = AbsolutNonPersistent; // default power limit setting - powerLimitAck = false; - actPowerLimit = 0xffff; // init feedback from inverter to -1 - mDevControlRequest = false; - devControlCmd = InitDataState; - alarmMesIndex = 0; - status = InverterStatus::OFF; - alarmCnt = 0; - alarmLastId = 0; - rssi = -127; - miMultiParts = 0; - mGotLastMsg = false; - mCmd = InitDataState; - mIsSingleframeReq = false; - radio = NULL; - commEnabled = true; - tsMaxAcPower = 0; - memset(&radioStatistics, 0, sizeof(statistics_t)); memset(mOffYD, 0, sizeof(float) * 6); memset(mLastYD, 0, sizeof(float) * 6); + mGridProfile.fill(0); } void tickSend(std::function cb) { @@ -255,8 +236,8 @@ class Inverter { uint8_t getPosByChFld(uint8_t channel, uint8_t fieldId, record_t<> *rec) { DPRINTLN(DBG_VERBOSE, F("hmInverter.h:getPosByChFld")); - uint8_t pos = 0; if(NULL != rec) { + uint8_t pos = 0; for(; pos < rec->length; pos++) { if((rec->assign[pos].ch == channel) && (rec->assign[pos].fieldId == fieldId)) break; @@ -303,7 +284,7 @@ class Inverter { return (InverterStatus::OFF != status); } - void addValue(uint8_t pos, uint8_t buf[], record_t<> *rec) { + void addValue(uint8_t pos, const uint8_t buf[], record_t<> *rec) { DPRINTLN(DBG_VERBOSE, F("hmInverter.h:addValue")); if(NULL != rec) { uint8_t ptr = rec->assign[pos].start; @@ -387,8 +368,8 @@ class Inverter { } REC_TYP getChannelFieldValue(uint8_t channel, uint8_t fieldId, record_t<> *rec) { - uint8_t pos = 0; if(NULL != rec) { + uint8_t pos = 0; for(; pos < rec->length; pos++) { if((rec->assign[pos].ch == channel) && (rec->assign[pos].fieldId == fieldId)) break; @@ -529,11 +510,11 @@ class Inverter { if (INV_TYPE_1CH == type) { if((IV_HM == ivGen) || (IV_MI == ivGen)) { rec->length = (uint8_t)(HM1CH_LIST_LEN); - rec->assign = (byteAssign_t *)hm1chAssignment; + rec->assign = reinterpret_cast(const_cast(hm1chAssignment)); rec->pyldLen = HM1CH_PAYLOAD_LEN; } else if(IV_HMS == ivGen) { rec->length = (uint8_t)(HMS1CH_LIST_LEN); - rec->assign = (byteAssign_t *)hms1chAssignment; + rec->assign = reinterpret_cast(const_cast(hms1chAssignment)); rec->pyldLen = HMS1CH_PAYLOAD_LEN; } channels = 1; @@ -541,11 +522,11 @@ class Inverter { else if (INV_TYPE_2CH == type) { if((IV_HM == ivGen) || (IV_MI == ivGen)) { rec->length = (uint8_t)(HM2CH_LIST_LEN); - rec->assign = (byteAssign_t *)hm2chAssignment; + rec->assign = reinterpret_cast(const_cast(hm2chAssignment)); rec->pyldLen = HM2CH_PAYLOAD_LEN; } else if(IV_HMS == ivGen) { rec->length = (uint8_t)(HMS2CH_LIST_LEN); - rec->assign = (byteAssign_t *)hms2chAssignment; + rec->assign = reinterpret_cast(const_cast(hms2chAssignment)); rec->pyldLen = HMS2CH_PAYLOAD_LEN; } channels = 2; @@ -553,18 +534,18 @@ class Inverter { else if (INV_TYPE_4CH == type) { if((IV_HM == ivGen) || (IV_MI == ivGen)) { rec->length = (uint8_t)(HM4CH_LIST_LEN); - rec->assign = (byteAssign_t *)hm4chAssignment; + rec->assign = reinterpret_cast(const_cast(hm4chAssignment)); rec->pyldLen = HM4CH_PAYLOAD_LEN; } else if(IV_HMS == ivGen) { rec->length = (uint8_t)(HMS4CH_LIST_LEN); - rec->assign = (byteAssign_t *)hms4chAssignment; + rec->assign = reinterpret_cast(const_cast(hms4chAssignment)); rec->pyldLen = HMS4CH_PAYLOAD_LEN; } channels = 4; } else if (INV_TYPE_6CH == type) { rec->length = (uint8_t)(HMT6CH_LIST_LEN); - rec->assign = (byteAssign_t *)hmt6chAssignment; + rec->assign = reinterpret_cast(const_cast(hmt6chAssignment)); rec->pyldLen = HMT6CH_PAYLOAD_LEN; channels = 6; } @@ -577,22 +558,22 @@ class Inverter { break; case InverterDevInform_All: rec->length = (uint8_t)(HMINFO_LIST_LEN); - rec->assign = (byteAssign_t *)InfoAssignment; + rec->assign = reinterpret_cast(const_cast(InfoAssignment)); rec->pyldLen = HMINFO_PAYLOAD_LEN; break; case InverterDevInform_Simple: rec->length = (uint8_t)(HMSIMPLE_INFO_LIST_LEN); - rec->assign = (byteAssign_t *)SimpleInfoAssignment; + rec->assign = reinterpret_cast(const_cast(SimpleInfoAssignment)); rec->pyldLen = HMSIMPLE_INFO_PAYLOAD_LEN; break; case SystemConfigPara: rec->length = (uint8_t)(HMSYSTEM_LIST_LEN); - rec->assign = (byteAssign_t *)SystemConfigParaAssignment; + rec->assign = reinterpret_cast(const_cast(SystemConfigParaAssignment)); rec->pyldLen = HMSYSTEM_PAYLOAD_LEN; break; case AlarmData: rec->length = (uint8_t)(HMALARMDATA_LIST_LEN); - rec->assign = (byteAssign_t *)AlarmDataAssignment; + rec->assign = reinterpret_cast(const_cast(AlarmDataAssignment)); rec->pyldLen = HMALARMDATA_PAYLOAD_LEN; break; default: @@ -616,7 +597,7 @@ class Inverter { memset(mLastYD, 0, sizeof(float) * 6); } - bool parseGetLossRate(uint8_t pyld[], uint8_t len) { + bool parseGetLossRate(const uint8_t pyld[], uint8_t len) { if (len == HMGETLOSSRATE_PAYLOAD_LEN) { uint16_t rxCnt = (pyld[0] << 8) + pyld[1]; uint16_t txCnt = (pyld[2] << 8) + pyld[3]; @@ -815,7 +796,7 @@ class Inverter { void addGridProfile(uint8_t buf[], uint8_t length) { mGridLen = (length > MAX_GRID_LENGTH) ? MAX_GRID_LENGTH : length; - std::copy(buf, &buf[mGridLen], mGridProfile); + std::copy(buf, &buf[mGridLen], mGridProfile.data()); } String getGridProfile(void) { @@ -847,9 +828,9 @@ class Inverter { private: float mOffYD[6], mLastYD[6]; - bool mDevControlRequest; // true if change needed + bool mDevControlRequest = false; // true if change needed uint8_t mGridLen = 0; - uint8_t mGridProfile[MAX_GRID_LENGTH]; + std::array mGridProfile; uint8_t mAlarmNxtWrPos = 0; // indicates the position in array (rolling buffer) public: diff --git a/src/hm/hmRadio.h b/src/hm/hmRadio.h index ca96a333..aef4f49d 100644 --- a/src/hm/hmRadio.h +++ b/src/hm/hmRadio.h @@ -52,7 +52,7 @@ class HmRadio : public Radio { mPrintWholeTrace = printWholeTrace; generateDtuSn(); - DTU_RADIO_ID = ((uint64_t)(((mDtuSn >> 24) & 0xFF) | ((mDtuSn >> 8) & 0xFF00) | ((mDtuSn << 8) & 0xFF0000) | ((mDtuSn << 24) & 0xFF000000)) << 8) | 0x01; + mDtuRadioId = ((uint64_t)(((mDtuSn >> 24) & 0xFF) | ((mDtuSn >> 8) & 0xFF00) | ((mDtuSn << 8) & 0xFF0000) | ((mDtuSn << 24) & 0xFF000000)) << 8) | 0x01; #ifdef ESP32 #if defined(CONFIG_IDF_TARGET_ESP32S3) && defined(SPI_HAL) @@ -85,7 +85,7 @@ class HmRadio : public Radio { mNrf24->enableDynamicPayloads(); mNrf24->setCRCLength(RF24_CRC_16); mNrf24->setAddressWidth(5); - mNrf24->openReadingPipe(1, reinterpret_cast(&DTU_RADIO_ID)); + mNrf24->openReadingPipe(1, reinterpret_cast(&mDtuRadioId)); mNrf24->maskIRQ(false, false, false); // enable all receiving interrupts mNrf24->setPALevel(1); // low is default @@ -99,7 +99,7 @@ class HmRadio : public Radio { } // returns true if communication is active - bool loop(void) { + bool loop(void) override { if (!mIrqRcvd && !mNRFisInRX) return false; // first quick check => nothing to do at all here @@ -198,11 +198,11 @@ class HmRadio : public Radio { return false; } - bool isChipConnected(void) const { + bool isChipConnected(void) const override { return mNrf24->isChipConnected(); } - void sendControlPacket(Inverter<> *iv, uint8_t cmd, uint16_t *data, bool isRetransmit) { + void sendControlPacket(Inverter<> *iv, uint8_t cmd, uint16_t *data, bool isRetransmit) override { DPRINT_IVID(DBG_INFO, iv->id); DBGPRINT(F("sendControlPacket cmd: ")); DBGHEXLN(cmd); @@ -423,15 +423,15 @@ class HmRadio : public Radio { mNRFisInRX = false; } - uint64_t getIvId(Inverter<> *iv) const { + uint64_t getIvId(Inverter<> *iv) const override { return iv->radioId.u64; } - uint8_t getIvGen(Inverter<> *iv) const { + uint8_t getIvGen(Inverter<> *iv) const override { return iv->ivGen; } - inline bool checkIvSerial(uint8_t buf[], Inverter<> *iv) { + inline bool checkIvSerial(const uint8_t buf[], Inverter<> *iv) { for(uint8_t i = 1; i < 5; i++) { if(buf[i] != iv->radioId.b[i]) return false; @@ -439,14 +439,14 @@ class HmRadio : public Radio { return true; } - uint64_t DTU_RADIO_ID; - uint8_t mRfChLst[RF_CHANNELS] = {03, 23, 40, 61, 75}; // channel List:2403, 2423, 2440, 2461, 2475MHz + uint64_t mDtuRadioId = 0ULL; + const uint8_t mRfChLst[RF_CHANNELS] = {03, 23, 40, 61, 75}; // channel List:2403, 2423, 2440, 2461, 2475MHz uint8_t mTxChIdx = 0; uint8_t mRxChIdx = 0; - uint8_t tempRxChIdx = mRxChIdx; + uint8_t tempRxChIdx = 0; bool mGotLastMsg = false; - uint32_t mMillis; - bool tx_ok, tx_fail, rx_ready = false; + uint32_t mMillis = 0; + bool tx_ok = false, tx_fail = false, rx_ready = false; unsigned long mTimeslotStart = 0; unsigned long mLastIrqTime = 0; bool mNRFloopChannels = false; @@ -454,7 +454,6 @@ class HmRadio : public Radio { bool isRxInit = true; bool mRxPendular = false; uint32_t innerLoopTimeout = DURATION_LISTEN_MIN; - //uint8_t mTxSetupTime = 0; uint8_t mTxRetries = 15; // memorize last setting for mNrf24->setRetries(3, 15); std::unique_ptr mSpi; diff --git a/src/hm/hmSystem.h b/src/hm/hmSystem.h index aa2910e5..0266a948 100644 --- a/src/hm/hmSystem.h +++ b/src/hm/hmSystem.h @@ -98,35 +98,33 @@ class HmSystem { #ifdef DYNAMIC_OFFSET iv->rxOffset = (iv->ivGen == IV_HM) ? 13 : 12; // effective 3 (or 2), but can easily be recognized as default setting #else - iv->rxOffset = ((iv->ivGen == IV_HM) && (iv->type == INV_TYPE_4CH)) ? 3 : 2; + //iv->rxOffset = ((iv->ivGen == IV_HM) && (iv->type == INV_TYPE_4CH)) ? 3 : 2; iv->rxOffset = (iv->ivGen == IV_HM) ? 3 : 2; #endif cb(iv); } - INVERTERTYPE *findInverter(uint8_t buf[]) { - DPRINTLN(DBG_VERBOSE, F("hmSystem.h:findInverter")); - INVERTERTYPE *p; + INVERTERTYPE *findInverter(const uint8_t buf[]) { for(uint8_t i = 0; i < MAX_INVERTER; i++) { - p = &mInverter[i]; + INVERTERTYPE *p = &mInverter[i]; if((p->config->serial.b[3] == buf[0]) && (p->config->serial.b[2] == buf[1]) && (p->config->serial.b[1] == buf[2]) && (p->config->serial.b[0] == buf[3])) return p; } - return NULL; + return nullptr; } INVERTERTYPE *getInverterByPos(uint8_t pos, bool check = true) { DPRINTLN(DBG_VERBOSE, F("hmSystem.h:getInverterByPos")); if(pos >= MAX_INVERTER) - return NULL; + return nullptr; else if((mInverter[pos].config->serial.u64 != 0ULL) || (false == check)) return &mInverter[pos]; else - return NULL; + return nullptr; } uint8_t getNumInverters(void) { diff --git a/src/hm/nrfHal.h b/src/hm/nrfHal.h index 0532a524..b9265626 100644 --- a/src/hm/nrfHal.h +++ b/src/hm/nrfHal.h @@ -142,7 +142,7 @@ class nrfHal: public RF24_hal, public SpiPatcherHandle { } uint8_t read(uint8_t cmd, uint8_t* buf, uint8_t len) override { - uint8_t data[NRF_MAX_TRANSFER_SZ]; + uint8_t data[NRF_MAX_TRANSFER_SZ + 1]; data[0] = cmd; if(len > NRF_MAX_TRANSFER_SZ) len = NRF_MAX_TRANSFER_SZ; diff --git a/src/hm/radio.h b/src/hm/radio.h index 7bbb42bc..db694b54 100644 --- a/src/hm/radio.h +++ b/src/hm/radio.h @@ -11,6 +11,7 @@ #define ALL_FRAMES 0x80 #define SINGLE_FRAME 0x81 +#include #include "../utils/dbg.h" #include "../utils/crc.h" #include "../utils/timemonitor.h" @@ -119,9 +120,8 @@ class Radio { #endif mDtuSn = 0; - uint8_t t; for(int i = 0; i < (7 << 2); i += 4) { - t = (chipID >> i) & 0x0f; + uint8_t t = (chipID >> i) & 0x0f; if(t > 0x09) t -= 6; mDtuSn |= (t << i); @@ -132,8 +132,8 @@ class Radio { uint32_t mDtuSn; - volatile bool mIrqRcvd; - bool *mSerialDebug, *mPrivacyMode, *mPrintWholeTrace; + std::atomic mIrqRcvd; + bool *mSerialDebug = nullptr, *mPrivacyMode = nullptr, *mPrintWholeTrace = nullptr; uint8_t mTxBuf[MAX_RF_PAYLOAD_SIZE]; }; diff --git a/src/hms/cmt2300a.h b/src/hms/cmt2300a.h index e6456f5b..23911b15 100644 --- a/src/hms/cmt2300a.h +++ b/src/hms/cmt2300a.h @@ -529,7 +529,8 @@ class Cmt2300a { return false; } - inline bool switchDtuFreq(const uint32_t freqKhz) { + // maybe used in future + /*inline bool switchDtuFreq(const uint32_t freqKhz) { uint8_t toCh = freq2Chan(freqKhz); if(0xff == toCh) return false; @@ -537,7 +538,7 @@ class Cmt2300a { switchChannel(toCh); return true; - } + }*/ inline uint8_t getChipStatus(void) { return mSpi.readReg(CMT2300A_CUS_MODE_STA) & CMT2300A_MASK_CHIP_MODE_STA; @@ -549,11 +550,11 @@ class Cmt2300a { #else esp32_3wSpi mSpi; #endif - uint8_t mCnt; - bool mTxPending; - bool mInRxMode; - uint8_t mCusIntFlag; - uint8_t mRqstCh, mCurCh; + uint8_t mCnt = 0; + bool mTxPending = false; + bool mInRxMode = false; + uint8_t mCusIntFlag = 0; + uint8_t mRqstCh = 0, mCurCh = 0; RegionCfg mRegionCfg = RegionCfg::EUROPE; }; diff --git a/src/hms/cmtHal.h b/src/hms/cmtHal.h index 768b8da5..a4bec587 100644 --- a/src/hms/cmtHal.h +++ b/src/hms/cmtHal.h @@ -89,7 +89,7 @@ class cmtHal : public SpiPatcherHandle { } uint8_t readReg(uint8_t addr) { - uint8_t data; + uint8_t data = 0; request_spi(); diff --git a/src/hms/esp32_3wSpi.h b/src/hms/esp32_3wSpi.h index c562671a..a0366b23 100644 --- a/src/hms/esp32_3wSpi.h +++ b/src/hms/esp32_3wSpi.h @@ -10,6 +10,7 @@ #if defined(ESP32) #include "driver/spi_master.h" #include "esp_rom_gpio.h" // for esp_rom_gpio_connect_out_signal +#include "../config/config.h" #define SPI_CLK 1 * 1000 * 1000 // 1MHz @@ -104,7 +105,7 @@ class esp32_3wSpi { if(!mInitialized) return 0; - uint8_t rx_data; + uint8_t rx_data = 0; spi_transaction_t t = { .cmd = 0, .addr = (uint64_t)(~addr), @@ -121,7 +122,7 @@ class esp32_3wSpi { return rx_data; } - void writeFifo(uint8_t buf[], uint8_t len) { + void writeFifo(const uint8_t buf[], uint8_t len) { if(!mInitialized) return; uint8_t tx_data; @@ -144,7 +145,7 @@ class esp32_3wSpi { void readFifo(uint8_t buf[], uint8_t *len, uint8_t maxlen) { if(!mInitialized) return; - uint8_t rx_data; + uint8_t rx_data = 0; spi_transaction_t t = { .length = 8, diff --git a/src/hms/hmsRadio.h b/src/hms/hmsRadio.h index 0fa1b6ab..66e312d7 100644 --- a/src/hms/hmsRadio.h +++ b/src/hms/hmsRadio.h @@ -15,10 +15,6 @@ template class CmtRadio : public Radio { typedef Cmt2300a CmtType; public: - CmtRadio() { - mDtuSn = DTU_SN; - } - void setup(bool *serialDebug, bool *privacyMode, bool *printWholeTrace, uint8_t pinSclk, uint8_t pinSdio, uint8_t pinCsb, uint8_t pinFcsb, uint8_t region = 0, bool genDtuSn = true) { mCmt.setup(pinSclk, pinSdio, pinCsb, pinFcsb); reset(genDtuSn, static_cast(region)); @@ -27,7 +23,7 @@ class CmtRadio : public Radio { mPrintWholeTrace = printWholeTrace; } - bool loop() { + bool loop() override { mCmt.loop(); if((!mIrqRcvd) && (!mRqstGetRx)) return false; @@ -39,11 +35,11 @@ class CmtRadio : public Radio { return false; } - bool isChipConnected(void) const { + bool isChipConnected(void) const override { return mCmtAvail; } - void sendControlPacket(Inverter<> *iv, uint8_t cmd, uint16_t *data, bool isRetransmit) { + void sendControlPacket(Inverter<> *iv, uint8_t cmd, uint16_t *data, bool isRetransmit) override { DPRINT(DBG_INFO, F("sendControlPacket cmd: ")); DBGHEXLN(cmd); initPacket(iv->radioId.u64, TX_REQ_DEVCONTROL, SINGLE_FRAME); @@ -61,14 +57,14 @@ class CmtRadio : public Radio { sendPacket(iv, cnt, isRetransmit); } - bool switchFrequency(Inverter<> *iv, uint32_t fromkHz, uint32_t tokHz) { + bool switchFrequency(Inverter<> *iv, uint32_t fromkHz, uint32_t tokHz) override { uint8_t fromCh = mCmt.freq2Chan(fromkHz); uint8_t toCh = mCmt.freq2Chan(tokHz); return switchFrequencyCh(iv, fromCh, toCh); } - bool switchFrequencyCh(Inverter<> *iv, uint8_t fromCh, uint8_t toCh) { + bool switchFrequencyCh(Inverter<> *iv, uint8_t fromCh, uint8_t toCh) override { if((0xff == fromCh) || (0xff == toCh)) return false; @@ -92,7 +88,7 @@ class CmtRadio : public Radio { private: - void sendPacket(Inverter<> *iv, uint8_t len, bool isRetransmit, bool appendCrc16=true) { + void sendPacket(Inverter<> *iv, uint8_t len, bool isRetransmit, bool appendCrc16=true) override { // inverters have maybe different settings regarding frequency if(mCmt.getCurrentChannel() != iv->config->frequency) mCmt.switchChannel(iv->config->frequency); @@ -129,11 +125,11 @@ class CmtRadio : public Radio { iv->mDtuTxCnt++; } - uint64_t getIvId(Inverter<> *iv) const { + uint64_t getIvId(Inverter<> *iv) const override { return iv->radioId.u64; } - uint8_t getIvGen(Inverter<> *iv) const { + uint8_t getIvGen(Inverter<> *iv) const override { return iv->ivGen; } @@ -180,6 +176,7 @@ class CmtRadio : public Radio { p.millis = millis() - mMillis; if(CmtStatus::SUCCESS == mCmt.getRx(p.packet, &p.len, 28, &p.rssi)) { mSwitchCycle = 0; + p.ch = 0; // not used for CMT inverters mBufCtrl.push(p); } @@ -187,14 +184,12 @@ class CmtRadio : public Radio { setExpectedFrames(p.packet[9] - ALL_FRAMES); mRadioWaitTime.startTimeMonitor(DURATION_PAUSE_LASTFR); // let the inverter first get back to rx mode? } - // optionally instead: - // mRadioWaitTime.stopTimeMonitor(); // we got everything we expected and can exit rx mode... } CmtType mCmt; bool mCmtAvail = false; bool mRqstGetRx = false; - uint32_t mMillis; + uint32_t mMillis = 0; uint8_t mSwitchCycle = 0; }; diff --git a/src/plugins/Display/Display.h b/src/plugins/Display/Display.h index 862e452b..c59a56b6 100644 --- a/src/plugins/Display/Display.h +++ b/src/plugins/Display/Display.h @@ -192,15 +192,14 @@ class Display { if ((mCfg->screenSaver == 2) && (mCfg->pirPin != DEF_PIN_OFF)) { #if defined(ESP8266) if (mCfg->pirPin == A0) - return((analogRead(A0) >= 512)); + return (analogRead(A0) >= 512); else - return(digitalRead(mCfg->pirPin)); + return digitalRead(mCfg->pirPin); #elif defined(ESP32) - return(digitalRead(mCfg->pirPin)); + return digitalRead(mCfg->pirPin); #endif - } - else - return(false); + } else + return false; } // approximate RSSI in dB by invQuality levels from heuristic function (very unscientific but better than nothing :-) ) diff --git a/src/plugins/Display/Display_Mono.h b/src/plugins/Display/Display_Mono.h index 2c1fc935..e855eeb9 100644 --- a/src/plugins/Display/Display_Mono.h +++ b/src/plugins/Display/Display_Mono.h @@ -24,8 +24,6 @@ class DisplayMono { public: - DisplayMono() {}; - virtual void init(DisplayData *displayData) = 0; virtual void config(display_t *cfg) = 0; virtual void disp(void) = 0; @@ -289,11 +287,11 @@ class DisplayMono { DispSwitchState mDispSwitchState = DispSwitchState::TEXT; uint16_t mDispWidth; - uint8_t mExtra; + uint8_t mExtra = 0; int8_t mPixelshift=0; char mFmtText[DISP_FMT_TEXT_LEN]; - uint8_t mLineXOffsets[5] = {}; - uint8_t mLineYOffsets[5] = {}; + uint8_t mLineXOffsets[5] = {0, 0, 0, 0, 0}; + uint8_t mLineYOffsets[5] = {0, 0, 0, 0, 0}; uint8_t mPgWidth = 0; @@ -308,8 +306,8 @@ class DisplayMono { uint32_t mPgLastTime = 0; PowerGraphState mPgState = PowerGraphState::NO_TIME_SYNC; - uint16_t mDispHeight; - uint8_t mLuminance; + uint16_t mDispHeight = 0; + uint8_t mLuminance = 0; TimeMonitor mDisplayTime = TimeMonitor(1000 * DISP_DEFAULT_TIMEOUT, true); TimeMonitor mDispSwitchTime = TimeMonitor(); diff --git a/src/plugins/history.h b/src/plugins/history.h index 7a938284..7e415675 100644 --- a/src/plugins/history.h +++ b/src/plugins/history.h @@ -24,23 +24,15 @@ template class HistoryData { private: struct storage_t { - uint16_t refreshCycle; - uint16_t loopCnt; - uint16_t listIdx; // index for next Element to write into WattArr - uint16_t dispIdx; // index for 1st Element to display from WattArr - bool wrapped; + uint16_t refreshCycle = 0; + uint16_t loopCnt = 0; + uint16_t listIdx = 0; // index for next Element to write into WattArr + uint16_t dispIdx = 0; // index for 1st Element to display from WattArr + bool wrapped = false; // ring buffer for watt history std::array data; - void reset() { - loopCnt = 0; - listIdx = 0; - dispIdx = 0; - wrapped = false; - for(uint16_t i = 0; i < (HISTORY_DATA_ARR_LENGTH + 1); i++) { - data[i] = 0; - } - } + storage_t() { data.fill(0); } }; public: @@ -50,9 +42,7 @@ class HistoryData { mConfig = config; mTs = ts; - mCurPwr.reset(); mCurPwr.refreshCycle = mConfig->inst.sendInterval; - //mYieldDay.reset(); //mYieldDay.refreshCycle = 60; } @@ -113,14 +103,13 @@ class HistoryData { } private: - IApp *mApp; - HMSYSTEM *mSys; - settings *mSettings; - settings_t *mConfig; - uint32_t *mTs; + IApp *mApp = nullptr; + HMSYSTEM *mSys = nullptr; + settings *mSettings = nullptr; + settings_t *mConfig = nullptr; + uint32_t *mTs = nullptr; storage_t mCurPwr; - //storage_t mYieldDay; bool mDayStored = false; uint16_t mMaximumDay = 0; }; diff --git a/src/publisher/pubMqtt.h b/src/publisher/pubMqtt.h index 865c76b5..7799b6d2 100644 --- a/src/publisher/pubMqtt.h +++ b/src/publisher/pubMqtt.h @@ -15,6 +15,7 @@ #include #endif +#include #include "../utils/dbg.h" #include "../config/config.h" #include @@ -38,12 +39,15 @@ template class PubMqtt { public: PubMqtt() { - mRxCnt = 0; - mTxCnt = 0; - mSubscriptionCb = NULL; - memset(mLastIvState, (uint8_t)InverterStatus::OFF, MAX_NUM_INVERTERS); - memset(mIvLastRTRpub, 0, MAX_NUM_INVERTERS * 4); - mLastAnyAvail = false; + mLastIvState.fill(InverterStatus::OFF); + mIvLastRTRpub.fill(0); + + mVal.fill(0); + mTopic.fill(0); + mSubTopic.fill(0); + mClientId.fill(0); + mLwtTopic.fill(0); + mSendAlarm.fill(false); } ~PubMqtt() { } @@ -63,16 +67,16 @@ class PubMqtt { }); mDiscovery.running = false; - snprintf(mLwtTopic, MQTT_TOPIC_LEN + 5, "%s/mqtt", mCfgMqtt->topic); + snprintf(mLwtTopic.data(), mLwtTopic.size(), "%s/mqtt", mCfgMqtt->topic); if((strlen(mCfgMqtt->user) > 0) && (strlen(mCfgMqtt->pwd) > 0)) mClient.setCredentials(mCfgMqtt->user, mCfgMqtt->pwd); if(strlen(mCfgMqtt->clientId) > 0) - snprintf(mClientId, 23, "%s", mCfgMqtt->clientId); + snprintf(mClientId.data(), mClientId.size(), "%s", mCfgMqtt->clientId); else { - snprintf(mClientId, 23, "%s-", mDevName); - uint8_t pos = strlen(mClientId); + snprintf(mClientId.data(), mClientId.size(), "%s-", mDevName); + uint8_t pos = strlen(mClientId.data()); mClientId[pos++] = WiFi.macAddress().substring( 9, 10).c_str()[0]; mClientId[pos++] = WiFi.macAddress().substring(10, 11).c_str()[0]; mClientId[pos++] = WiFi.macAddress().substring(12, 13).c_str()[0]; @@ -82,9 +86,9 @@ class PubMqtt { mClientId[pos++] = '\0'; } - mClient.setClientId(mClientId); + mClient.setClientId(mClientId.data()); mClient.setServer(mCfgMqtt->broker, mCfgMqtt->port); - mClient.setWill(mLwtTopic, QOS_0, true, mqttStr[MQTT_STR_LWT_NOT_CONN]); + mClient.setWill(mLwtTopic.data(), QOS_0, true, mqttStr[MQTT_STR_LWT_NOT_CONN]); mClient.onConnect(std::bind(&PubMqtt::onConnect, this, std::placeholders::_1)); mClient.onDisconnect(std::bind(&PubMqtt::onDisconnect, this, std::placeholders::_1)); mClient.onMessage(std::bind(&PubMqtt::onMessage, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5, std::placeholders::_6)); @@ -125,8 +129,8 @@ class PubMqtt { } void tickerMinute() { - snprintf(mVal, 40, "%d", (*mUptime)); - publish(subtopics[MQTT_UPTIME], mVal); + snprintf(mVal.data(), mVal.size(), "%u", (*mUptime)); + publish(subtopics[MQTT_UPTIME], mVal.data()); publish(subtopics[MQTT_RSSI], String(WiFi.RSSI()).c_str()); publish(subtopics[MQTT_FREE_HEAP], String(ESP.getFreeHeap()).c_str()); #ifndef ESP32 @@ -143,18 +147,17 @@ class PubMqtt { publish(subtopics[MQTT_COMM_START], String(sunrise + offsM).c_str(), true); publish(subtopics[MQTT_COMM_STOP], String(sunset + offsE).c_str(), true); - Inverter<> *iv; for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i++) { - iv = mSys->getInverterByPos(i); + Inverter<> *iv = mSys->getInverterByPos(i); if(NULL == iv) continue; - snprintf(mSubTopic, 32 + MAX_NAME_LENGTH, "%s/dis_night_comm", iv->config->name); - publish(mSubTopic, ((iv->commEnabled) ? dict[STR_TRUE] : dict[STR_FALSE]), true); + snprintf(mSubTopic.data(), mSubTopic.size(), "%s/dis_night_comm", iv->config->name); + publish(mSubTopic.data(), ((iv->commEnabled) ? dict[STR_TRUE] : dict[STR_FALSE]), true); } - snprintf(mSubTopic, 32 + MAX_NAME_LENGTH, "comm_disabled"); - publish(mSubTopic, (((*mUtcTimestamp > (sunset + offsE)) || (*mUtcTimestamp < (sunrise + offsM))) ? dict[STR_TRUE] : dict[STR_FALSE]), true); + snprintf(mSubTopic.data(), mSubTopic.size(), "comm_disabled"); + publish(mSubTopic.data(), (((*mUtcTimestamp > (sunset + offsE)) || (*mUtcTimestamp < (sunrise + offsM))) ? dict[STR_TRUE] : dict[STR_FALSE]), true); return true; } @@ -176,9 +179,9 @@ class PubMqtt { void tickerMidnight() { // set Total YieldDay to zero - snprintf(mSubTopic, 32 + MAX_NAME_LENGTH, "total/%s", fields[FLD_YD]); - snprintf(mVal, 2, "0"); - publish(mSubTopic, mVal, true); + snprintf(mSubTopic.data(), mSubTopic.size(), "total/%s", fields[FLD_YD]); + snprintf(mVal.data(), mVal.size(), "0"); + publish(mSubTopic.data(), mVal.data(), true); } void payloadEventListener(uint8_t cmd, Inverter<> *iv) { @@ -197,11 +200,11 @@ class PubMqtt { return; if(addTopic) - snprintf(mTopic, MQTT_TOPIC_LEN + 32 + MAX_NAME_LENGTH + 1, "%s/%s", mCfgMqtt->topic, subTopic); + snprintf(mTopic.data(), mTopic.size(), "%s/%s", mCfgMqtt->topic, subTopic); else - snprintf(mTopic, MQTT_TOPIC_LEN + 32 + MAX_NAME_LENGTH + 1, "%s", subTopic); + snprintf(mTopic.data(), mTopic.size(), "%s", subTopic); - mClient.publish(mTopic, qos, retained, payload); + mClient.publish(mTopic.data(), qos, retained, payload); yield(); mTxCnt++; } @@ -238,8 +241,8 @@ class PubMqtt { void setPowerLimitAck(Inverter<> *iv) { if (NULL != iv) { - snprintf(mSubTopic, 32 + MAX_NAME_LENGTH, "%s/%s", iv->config->name, subtopics[MQTT_ACK_PWR_LMT]); - publish(mSubTopic, "true", true, true, QOS_2); + snprintf(mSubTopic.data(), mSubTopic.size(), "%s/%s", iv->config->name, subtopics[MQTT_ACK_PWR_LMT]); + publish(mSubTopic.data(), "true", true, true, QOS_2); } } @@ -255,15 +258,15 @@ class PubMqtt { publish(subtopics[MQTT_IP_ADDR], WiFi.localIP().toString().c_str(), true); #endif tickerMinute(); - publish(mLwtTopic, mqttStr[MQTT_STR_LWT_CONN], true, false); + publish(mLwtTopic.data(), mqttStr[MQTT_STR_LWT_CONN], true, false); for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i++) { - snprintf(mVal, 20, "ctrl/limit/%d", i); - subscribe(mVal, QOS_2); - snprintf(mVal, 20, "ctrl/restart/%d", i); - subscribe(mVal); - snprintf(mVal, 20, "ctrl/power/%d", i); - subscribe(mVal); + snprintf(mVal.data(), mVal.size(), "ctrl/limit/%d", i); + subscribe(mVal.data(), QOS_2); + snprintf(mVal.data(), mVal.size(), "ctrl/restart/%d", i); + subscribe(mVal.data()); + snprintf(mVal.data(), mVal.size(), "ctrl/power/%d", i); + subscribe(mVal.data()); } subscribe(subscr[MQTT_SUBS_SET_TIME]); } @@ -359,11 +362,9 @@ class PubMqtt { } void discoveryConfigLoop(void) { - char topic[64], name[32], uniq_id[32], buf[350]; DynamicJsonDocument doc(256); - uint8_t fldTotal[4] = {FLD_PAC, FLD_YT, FLD_YD, FLD_PDC}; - const char* unitTotal[4] = {"W", "kWh", "Wh", "W"}; + constexpr static uint8_t fldTotal[] = {FLD_PAC, FLD_YT, FLD_YD, FLD_PDC}; String node_id = String(mDevName) + "_TOTAL"; bool total = (mDiscovery.lastIvId == MAX_NUM_INVERTERS); @@ -392,32 +393,37 @@ class PubMqtt { doc[F("mf")] = F("Hoymiles"); JsonObject deviceObj = doc.as(); // deviceObj is only pointer!? + std::array topic; + std::array name; + std::array uniq_id; + std::array buf; const char *devCls, *stateCls; if (!total) { if (rec->assign[mDiscovery.sub].ch == CH0) - snprintf(name, 32, "%s", iv->getFieldName(mDiscovery.sub, rec)); + snprintf(name.data(), name.size(), "%s", iv->getFieldName(mDiscovery.sub, rec)); else - snprintf(name, 32, "CH%d_%s", rec->assign[mDiscovery.sub].ch, iv->getFieldName(mDiscovery.sub, rec)); - snprintf(topic, 64, "/ch%d/%s", rec->assign[mDiscovery.sub].ch, iv->getFieldName(mDiscovery.sub, rec)); - snprintf(uniq_id, 32, "ch%d_%s", rec->assign[mDiscovery.sub].ch, iv->getFieldName(mDiscovery.sub, rec)); + snprintf(name.data(), name.size(), "CH%d_%s", rec->assign[mDiscovery.sub].ch, iv->getFieldName(mDiscovery.sub, rec)); + snprintf(topic.data(), name.size(), "/ch%d/%s", rec->assign[mDiscovery.sub].ch, iv->getFieldName(mDiscovery.sub, rec)); + snprintf(uniq_id.data(), uniq_id.size(), "ch%d_%s", rec->assign[mDiscovery.sub].ch, iv->getFieldName(mDiscovery.sub, rec)); devCls = getFieldDeviceClass(rec->assign[mDiscovery.sub].fieldId); stateCls = getFieldStateClass(rec->assign[mDiscovery.sub].fieldId); } else { // total values - snprintf(name, 32, "Total %s", fields[fldTotal[mDiscovery.sub]]); - snprintf(topic, 64, "/%s", fields[fldTotal[mDiscovery.sub]]); - snprintf(uniq_id, 32, "total_%s", fields[fldTotal[mDiscovery.sub]]); + snprintf(name.data(), name.size(), "Total %s", fields[fldTotal[mDiscovery.sub]]); + snprintf(topic.data(), topic.size(), "/%s", fields[fldTotal[mDiscovery.sub]]); + snprintf(uniq_id.data(), uniq_id.size(), "total_%s", fields[fldTotal[mDiscovery.sub]]); devCls = getFieldDeviceClass(fldTotal[mDiscovery.sub]); stateCls = getFieldStateClass(fldTotal[mDiscovery.sub]); } DynamicJsonDocument doc2(512); + constexpr static char* unitTotal[] = {"W", "kWh", "Wh", "W"}; doc2[F("name")] = name; - doc2[F("stat_t")] = String(mCfgMqtt->topic) + "/" + ((!total) ? String(iv->config->name) : "total" ) + String(topic); + doc2[F("stat_t")] = String(mCfgMqtt->topic) + "/" + ((!total) ? String(iv->config->name) : "total" ) + String(topic.data()); doc2[F("unit_of_meas")] = ((!total) ? (iv->getUnit(mDiscovery.sub, rec)) : (unitTotal[mDiscovery.sub])); - doc2[F("uniq_id")] = ((!total) ? (String(iv->config->serial.u64, HEX)) : (node_id)) + "_" + uniq_id; + doc2[F("uniq_id")] = ((!total) ? (String(iv->config->serial.u64, HEX)) : (node_id)) + "_" + uniq_id.data(); doc2[F("dev")] = deviceObj; if (!(String(stateCls) == String("total_increasing"))) doc2[F("exp_aft")] = MQTT_INTERVAL + 5; // add 5 sec if connection is bad or ESP too slow @TODO: stimmt das wirklich als expire!? @@ -427,13 +433,13 @@ class PubMqtt { doc2[F("stat_cla")] = String(stateCls); if (!total) - snprintf(topic, 64, "%s/sensor/%s/ch%d_%s/config", MQTT_DISCOVERY_PREFIX, iv->config->name, rec->assign[mDiscovery.sub].ch, iv->getFieldName(mDiscovery.sub, rec)); + snprintf(topic.data(), topic.size(), "%s/sensor/%s/ch%d_%s/config", MQTT_DISCOVERY_PREFIX, iv->config->name, rec->assign[mDiscovery.sub].ch, iv->getFieldName(mDiscovery.sub, rec)); else // total values - snprintf(topic, 64, "%s/sensor/%s/total_%s/config", MQTT_DISCOVERY_PREFIX, node_id.c_str(), fields[fldTotal[mDiscovery.sub]]); + snprintf(topic.data(), topic.size(), "%s/sensor/%s/total_%s/config", MQTT_DISCOVERY_PREFIX, node_id.c_str(), fields[fldTotal[mDiscovery.sub]]); size_t size = measureJson(doc2) + 1; - memset(buf, 0, size); - serializeJson(doc2, buf, size); - publish(topic, buf, true, false); + buf.fill(0); + serializeJson(doc2, buf.data(), size); + publish(topic.data(), buf.data(), true, false); if(++mDiscovery.sub == ((!total) ? (rec->length) : 4)) { mDiscovery.sub = 0; @@ -503,15 +509,15 @@ class PubMqtt { mLastIvState[id] = status; changed = true; - snprintf(mSubTopic, 32 + MAX_NAME_LENGTH, "%s/available", iv->config->name); - snprintf(mVal, 40, "%d", (uint8_t)status); - publish(mSubTopic, mVal, true); + snprintf(mSubTopic.data(), mSubTopic.size(), "%s/available", iv->config->name); + snprintf(mVal.data(), mVal.size(), "%d", (uint8_t)status); + publish(mSubTopic.data(), mVal.data(), true); } } if(changed) { - snprintf(mVal, 32, "%d", ((allAvail) ? MQTT_STATUS_ONLINE : ((anyAvail) ? MQTT_STATUS_PARTIAL : MQTT_STATUS_OFFLINE))); - publish("status", mVal, true); + snprintf(mVal.data(), mVal.size(), "%d", ((allAvail) ? MQTT_STATUS_ONLINE : ((anyAvail) ? MQTT_STATUS_PARTIAL : MQTT_STATUS_OFFLINE))); + publish("status", mVal.data(), true); } return anyAvail; @@ -533,19 +539,19 @@ class PubMqtt { mSendAlarm[i] = false; - snprintf(mSubTopic, 32 + MAX_NAME_LENGTH, "%s/alarm/cnt", iv->config->name); - snprintf(mVal, 40, "%d", iv->alarmCnt); - publish(mSubTopic, mVal, false); + snprintf(mSubTopic.data(), mSubTopic.size(), "%s/alarm/cnt", iv->config->name); + snprintf(mVal.data(), mVal.size(), "%d", iv->alarmCnt); + publish(mSubTopic.data(), mVal.data(), false); for(uint8_t j = 0; j < 10; j++) { if(0 != iv->lastAlarm[j].code) { - snprintf(mSubTopic, 32 + MAX_NAME_LENGTH, "%s/alarm/%d", iv->config->name, j); - snprintf(mVal, 100, "{\"code\":%d,\"str\":\"%s\",\"start\":%d,\"end\":%d}", + snprintf(mSubTopic.data(), mSubTopic.size(), "%s/alarm/%d", iv->config->name, j); + snprintf(mVal.data(), mVal.size(), "{\"code\":%d,\"str\":\"%s\",\"start\":%d,\"end\":%d}", iv->lastAlarm[j].code, iv->getAlarmStr(iv->lastAlarm[j].code).c_str(), iv->lastAlarm[j].start + lastMidnight, iv->lastAlarm[j].end + lastMidnight); - publish(mSubTopic, mVal, false); + publish(mSubTopic.data(), mVal.data(), false); yield(); } } @@ -575,9 +581,9 @@ class PubMqtt { } } - snprintf(mSubTopic, 32 + MAX_NAME_LENGTH, "%s/ch%d/%s", iv->config->name, rec->assign[i].ch, fields[rec->assign[i].fieldId]); - snprintf(mVal, 40, "%g", ah::round3(iv->getValue(i, rec))); - publish(mSubTopic, mVal, retained); + snprintf(mSubTopic.data(), mSubTopic.size(), "%s/ch%d/%s", iv->config->name, rec->assign[i].ch, fields[rec->assign[i].fieldId]); + snprintf(mVal.data(), mVal.size(), "%g", ah::round3(iv->getValue(i, rec))); + publish(mSubTopic.data(), mVal.data(), retained); yield(); } @@ -597,33 +603,33 @@ class PubMqtt { } espMqttClient mClient; - cfgMqtt_t *mCfgMqtt; + cfgMqtt_t *mCfgMqtt = nullptr; #if defined(ESP8266) WiFiEventHandler mHWifiCon, mHWifiDiscon; #endif - HMSYSTEM *mSys; + HMSYSTEM *mSys = nullptr; PubMqttIvData mSendIvData; - uint32_t *mUtcTimestamp, *mUptime; - uint32_t mRxCnt, mTxCnt; + uint32_t *mUtcTimestamp = nullptr, *mUptime = nullptr; + uint32_t mRxCnt = 0, mTxCnt = 0; std::queue mSendList; - std::array mSendAlarm{}; - subscriptionCb mSubscriptionCb; - bool mLastAnyAvail; - InverterStatus mLastIvState[MAX_NUM_INVERTERS]; - uint32_t mIvLastRTRpub[MAX_NUM_INVERTERS]; - uint16_t mIntervalTimeout; + std::array mSendAlarm; + subscriptionCb mSubscriptionCb = nullptr; + bool mLastAnyAvail = false; + std::array mLastIvState; + std::array mIvLastRTRpub; + uint16_t mIntervalTimeout = 0; // last will topic and payload must be available through lifetime of 'espMqttClient' - char mLwtTopic[MQTT_TOPIC_LEN+5]; - const char *mDevName, *mVersion; - char mClientId[24]; // number of chars is limited to 23 up to v3.1 of MQTT + std::array mLwtTopic; + const char *mDevName = nullptr, *mVersion = nullptr; + std::array mClientId; // number of chars is limited to 23 up to v3.1 of MQTT // global buffer for mqtt topic. Used when publishing mqtt messages. - char mTopic[MQTT_TOPIC_LEN + 32 + MAX_NAME_LENGTH + 1]; - char mSubTopic[32 + MAX_NAME_LENGTH + 1]; - char mVal[100]; - discovery_t mDiscovery; + std::array mTopic; + std::array mSubTopic; + std::array mVal; + discovery_t mDiscovery = {true, 0, 0, 0}; }; #endif /*ENABLE_MQTT*/ diff --git a/src/publisher/pubMqttIvData.h b/src/publisher/pubMqttIvData.h index cfed38b9..4405d037 100644 --- a/src/publisher/pubMqttIvData.h +++ b/src/publisher/pubMqttIvData.h @@ -205,9 +205,9 @@ class PubMqttIvData { } void stateSendTotals() { - uint8_t fieldId; mRTRDataHasBeenSent = true; if(mPos < 5) { + uint8_t fieldId; bool retained = true; switch (mPos) { default: diff --git a/src/publisher/pubSerial.h b/src/publisher/pubSerial.h index 34dc64f2..9ac24593 100644 --- a/src/publisher/pubSerial.h +++ b/src/publisher/pubSerial.h @@ -1,6 +1,6 @@ //----------------------------------------------------------------------------- -// 2022 Ahoy, https://ahoydtu.de -// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/ +// 2024 Ahoy, https://github.com/lumpapu/ahoy +// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/4.0/deed //----------------------------------------------------------------------------- #ifndef __PUB_SERIAL_H__ @@ -13,8 +13,6 @@ template class PubSerial { public: - PubSerial() {} - void setup(settings_t *cfg, HMSYSTEM *sys, uint32_t *utcTs) { mCfg = cfg; mSys = sys; @@ -46,9 +44,9 @@ class PubSerial { } private: - settings_t *mCfg; - HMSYSTEM *mSys; - uint32_t *mUtcTimestamp; + settings_t *mCfg = nullptr; + HMSYSTEM *mSys = nullptr; + uint32_t *mUtcTimestamp = nullptr; }; diff --git a/src/utils/improv.h b/src/utils/improv.h index d5a9ae74..32bc403c 100644 --- a/src/utils/improv.h +++ b/src/utils/improv.h @@ -10,6 +10,7 @@ #include #include "dbg.h" #include "AsyncJson.h" +#include "../appInterface.h" // https://www.improv-wifi.com/serial/ // https://github.com/jnthas/improv-wifi-demo/blob/main/src/esp32-wifiimprov/esp32-wifiimprov.ino @@ -78,7 +79,7 @@ class Improv { DBGPRINTLN(""); } - inline uint8_t buildChecksum(uint8_t buf[], uint8_t len) { + inline uint8_t buildChecksum(const uint8_t buf[], uint8_t len) { uint8_t calc = 0; for(uint8_t i = 0; i < len; i++) { calc += buf[i]; @@ -86,7 +87,7 @@ class Improv { return calc; } - inline bool checkChecksum(uint8_t buf[], uint8_t len) { + inline bool checkChecksum(const uint8_t buf[], uint8_t len) { /*DHEX(buf[len], false); DBGPRINT(F(" == "), false); DBGHEXLN(buildChecksum(buf, len), false);*/ @@ -97,7 +98,7 @@ class Improv { if(len < 11) return false; - if(0 != strncmp((char*)buf, "IMPROV", 6)) + if(0 != strncmp(reinterpret_cast(buf), "IMPROV", 6)) return false; // version check (only version 1 is supported!) @@ -199,7 +200,7 @@ class Improv { dumpBuf(buf, len); } - void parsePayload(uint8_t type, uint8_t buf[], uint8_t len) { + void parsePayload(uint8_t type, const uint8_t buf[], uint8_t len) { if(TYPE_RPC == type) { if(GET_CURRENT_STATE == buf[0]) { setDebugEn(false); @@ -212,9 +213,10 @@ class Improv { } } - IApp *mApp; - const char *mDevName, *mVersion; - bool mScanRunning; + IApp *mApp = nullptr; + const char *mDevName = nullptr; + const char *mVersion = nullptr; + bool mScanRunning = false; }; #endif diff --git a/src/utils/scheduler.h b/src/utils/scheduler.h index 751550df..16009778 100644 --- a/src/utils/scheduler.h +++ b/src/utils/scheduler.h @@ -7,6 +7,7 @@ #define __SCHEDULER_H__ #include +#include #include "dbg.h" namespace ah { @@ -28,8 +29,6 @@ namespace ah { class Scheduler { public: - Scheduler() {} - void setup(bool directStart) { mUptime = 0; mTimestamp = (directStart) ? 1 : 0; @@ -93,8 +92,7 @@ namespace ah { } inline void resetTicker(void) { - for (uint8_t i = 0; i < MAX_NUM_TICKER; i++) - mTickerInUse[i] = false; + mTickerInUse.fill(false); } void getStat(uint8_t *max) { @@ -159,11 +157,11 @@ namespace ah { } } - sP mTicker[MAX_NUM_TICKER]; - bool mTickerInUse[MAX_NUM_TICKER]; - uint32_t mMillis, mPrevMillis, mDiff; - uint8_t mDiffSeconds; - uint8_t mMax; + std::array mTicker; + std::array mTickerInUse; + uint32_t mMillis = 0, mPrevMillis = 0, mDiff = 0; + uint8_t mDiffSeconds = 0; + uint8_t mMax = 0; }; } diff --git a/src/utils/spiPatcher.h b/src/utils/spiPatcher.h index 2f8ce616..210b2a09 100644 --- a/src/utils/spiPatcher.h +++ b/src/utils/spiPatcher.h @@ -16,7 +16,7 @@ class SpiPatcher { protected: - SpiPatcher(spi_host_device_t dev) : + explicit SpiPatcher(spi_host_device_t dev) : mHostDevice(dev), mCurHandle(nullptr) { // Use binary semaphore instead of mutex for performance reasons mutex = xSemaphoreCreateBinaryStatic(&mutex_buffer); diff --git a/src/utils/timemonitor.h b/src/utils/timemonitor.h index 9f99cc92..18ab51d9 100644 --- a/src/utils/timemonitor.h +++ b/src/utils/timemonitor.h @@ -22,14 +22,14 @@ class TimeMonitor { /** * A constructor for creating a TimeMonitor object */ - TimeMonitor(void) {} + TimeMonitor() {} /** * A constructor for initializing a TimeMonitor object * @param timeout timeout in ms * @param start (optional) if true, start TimeMonitor immediately */ - TimeMonitor(uint32_t timeout, bool start = false) { + explicit TimeMonitor(uint32_t timeout, bool start = false) { if (start) startTimeMonitor(timeout); else @@ -80,7 +80,7 @@ class TimeMonitor { * true: TimeMonitor already timed out * false: TimeMonitor still in time or TimeMonitor was stopped */ - bool isTimeout(void) { + bool isTimeout(void) const { if ((mStarted) && ((millis() - mStartTime) >= mTimeout)) return true; else diff --git a/src/web/RestApi.h b/src/web/RestApi.h index 564d6c62..acd085f3 100644 --- a/src/web/RestApi.h +++ b/src/web/RestApi.h @@ -141,7 +141,7 @@ class RestApi { #endif } - void onApiPostBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) { + void onApiPostBody(AsyncWebServerRequest *request, const uint8_t *data, size_t len, size_t index, size_t total) { DPRINTLN(DBG_VERBOSE, "onApiPostBody"); if(0 == index) { @@ -158,7 +158,7 @@ class RestApi { DynamicJsonDocument json(1000); - DeserializationError err = deserializeJson(json, (const char *)mTmpBuf, mTmpSize); + DeserializationError err = deserializeJson(json, reinterpret_cast(mTmpBuf), mTmpSize); JsonObject obj = json.as(); AsyncJsonResponse* response = new AsyncJsonResponse(false, 200); @@ -930,20 +930,20 @@ class RestApi { return true; } - IApp *mApp; - HMSYSTEM *mSys; - HmRadio<> *mRadioNrf; + IApp *mApp = nullptr; + HMSYSTEM *mSys = nullptr; + HmRadio<> *mRadioNrf = nullptr; #if defined(ESP32) - CmtRadio<> *mRadioCmt; + CmtRadio<> *mRadioCmt = nullptr; #endif - AsyncWebServer *mSrv; - settings_t *mConfig; + AsyncWebServer *mSrv = nullptr; + settings_t *mConfig = nullptr; - uint32_t mTimezoneOffset; - uint32_t mHeapFree, mHeapFreeBlk; - uint8_t mHeapFrag; + uint32_t mTimezoneOffset = 0; + uint32_t mHeapFree = 0, mHeapFreeBlk = 0; + uint8_t mHeapFrag = 0; uint8_t *mTmpBuf = NULL; - uint32_t mTmpSize; + uint32_t mTmpSize = 0; }; #endif /*__WEB_API_H__*/ diff --git a/src/web/web.h b/src/web/web.h index 0ffd0594..a166377f 100644 --- a/src/web/web.h +++ b/src/web/web.h @@ -48,9 +48,6 @@ class Web { public: Web(void) : mWeb(80), mEvts("/events") { memset(mSerialBuf, 0, WEB_SERIAL_BUF_SIZE); - mSerialBufFill = 0; - mSerialAddTime = true; - mSerialClientConnnected = false; } void setup(IApp *app, HMSYSTEM *sys, settings_t *config) { @@ -490,9 +487,8 @@ class Web { // pinout - uint8_t pin; for (uint8_t i = 0; i < 16; i++) { - pin = request->arg(String(pinArgNames[i])).toInt(); + uint8_t pin = request->arg(String(pinArgNames[i])).toInt(); switch(i) { case 0: mConfig->nrf.pinCs = ((pin != 0xff) ? pin : DEF_NRF_CS_PIN); break; case 1: mConfig->nrf.pinCe = ((pin != 0xff) ? pin : DEF_NRF_CE_PIN); break; @@ -633,8 +629,8 @@ class Web { // NOTE: Grouping for fields with channels and totals is currently not working // TODO: Handle grouping and sorting for independant from channel number // NOTE: Check packetsize for MAX_NUM_INVERTERS. Successfully Tested with 4 Inverters (each with 4 channels) - const char * metricConstPrefix = "ahoy_solar_"; - const char * metricConstInverterFormat = " {inverter=\"%s\"} %d\n"; + const char* metricConstPrefix = "ahoy_solar_"; + const char* metricConstInverterFormat = " {inverter=\"%s\"} %d\n"; typedef enum { metricsStateInverterInfo=0, metricsStateInverterEnabled=1, metricsStateInverterAvailable=2, metricsStateInverterProducing=3, metricsStateInverterPowerLimitRead=4, metricsStateInverterPowerLimitAck=5, @@ -648,7 +644,7 @@ class Web { metricsStateStart, metricsStateEnd } MetricStep_t; - MetricStep_t metricsStep; + MetricStep_t metricsStep = metricsStateInverterInfo; typedef struct { const char *topic; const char *type; @@ -674,9 +670,6 @@ class Web { { "radio_dtu_loss_cnt", "counter" ,metricConstInverterFormat, [](Inverter<> *iv)-> uint64_t {return iv->radioStatistics.dtuLoss;} }, { "radio_dtu_sent_cnt", "counter" ,metricConstInverterFormat, [](Inverter<> *iv)-> uint64_t {return iv->radioStatistics.dtuSent;} } }; - int metricsInverterId; - uint8_t metricsFieldId; - bool metricDeclared, metricTotalDeclard; void showMetrics(AsyncWebServerRequest *request) { DPRINTLN(DBG_VERBOSE, F("web::showMetrics")); @@ -715,7 +708,7 @@ class Web { snprintf(topic,sizeof(topic),"%swifi_rssi_db{devicename=\"%s\"} %d\n",metricConstPrefix, mConfig->sys.deviceName, WiFi.RSSI()); metrics += String(type) + String(topic); - len = snprintf((char *)buffer,maxLen,"%s",metrics.c_str()); + len = snprintf(reinterpret_cast(buffer), maxLen,"%s",metrics.c_str()); // Next is Inverter information metricsStep = metricsStateInverterInfo; break; @@ -743,7 +736,7 @@ class Web { (String("ahoy_solar_inverter_") + inverterMetrics[metricsStep].topic + inverterMetrics[metricsStep].format).c_str(), inverterMetrics[metricsStep].valueFunc); - len = snprintf((char *)buffer,maxLen,"%s",metrics.c_str()); + len = snprintf(reinterpret_cast(buffer), maxLen, "%s", metrics.c_str()); // ugly hack to increment the enum metricsStep = static_cast( static_cast(metricsStep) + 1); // Prepare Realtime Field loop, which may be startet next @@ -763,7 +756,7 @@ class Web { metrics = "# Info: all realtime fields processed\n"; metricsStep = metricsStateAlarmData; } - len = snprintf((char *)buffer,maxLen,"%s",metrics.c_str()); + len = snprintf(reinterpret_cast(buffer), maxLen, "%s", metrics.c_str()); break; case metricStateRealtimeInverterId: // Iterate over all inverters for this field @@ -837,7 +830,7 @@ class Web { metricsFieldId++; // Process next field Id metricsStep = metricStateRealtimeFieldId; } - len = snprintf((char *)buffer,maxLen,"%s",metrics.c_str()); + len = snprintf(reinterpret_cast(buffer), maxLen, "%s", metrics.c_str()); break; case metricsStateAlarmData: // Alarm Info loop : fit to one packet @@ -861,7 +854,7 @@ class Web { } } } - len = snprintf((char*)buffer,maxLen,"%s",metrics.c_str()); + len = snprintf(reinterpret_cast(buffer), maxLen, "%s", metrics.c_str()); metricsStep = metricsStateEnd; break; @@ -880,10 +873,9 @@ class Web { // Traverse all inverters and collect the metric via valueFunc String inverterMetric(char *buffer, size_t len, const char *format, std::function *iv)> valueFunc) { - Inverter<> *iv; String metric = ""; for (int metricsInverterId = 0; metricsInverterId < mSys->getNumInverters();metricsInverterId++) { - iv = mSys->getInverterByPos(metricsInverterId); + Inverter<> *iv = mSys->getInverterByPos(metricsInverterId); if (NULL != iv) { snprintf(buffer,len,format,iv->config->name, valueFunc(iv)); metric += String(buffer); @@ -904,21 +896,27 @@ class Web { if(shortUnit == "Hz") return {"_hertz", "gauge"}; return {"", "gauge"}; } + + private: + int metricsInverterId = 0; + uint8_t metricsFieldId = 0; + bool metricDeclared = false, metricTotalDeclard = false; #endif + private: AsyncWebServer mWeb; AsyncEventSource mEvts; - IApp *mApp; - HMSYSTEM *mSys; + IApp *mApp = nullptr; + HMSYSTEM *mSys = nullptr; - settings_t *mConfig; + settings_t *mConfig = nullptr; - bool mSerialAddTime; + bool mSerialAddTime = true; char mSerialBuf[WEB_SERIAL_BUF_SIZE]; - uint16_t mSerialBufFill; - bool mSerialClientConnnected; + uint16_t mSerialBufFill = 0; + bool mSerialClientConnnected = false; File mUploadFp; - bool mUploadFail; + bool mUploadFail = false; }; #endif /*__WEB_H__*/ diff --git a/src/wifi/ahoywifi.h b/src/wifi/ahoywifi.h index 709c08c4..d38701aa 100644 --- a/src/wifi/ahoywifi.h +++ b/src/wifi/ahoywifi.h @@ -71,7 +71,7 @@ class ahoywifi { void welcome(String ip, String mode); - settings_t *mConfig = NULL; + settings_t *mConfig = nullptr; appWifiCb mAppWifiCb; DNSServer mDns; @@ -81,15 +81,15 @@ class ahoywifi { WiFiEventHandler wifiConnectHandler, wifiDisconnectHandler, wifiGotIPHandler; #endif - WiFiStatus_t mStaConn; - uint8_t mCnt; - uint32_t *mUtcTimestamp; + WiFiStatus_t mStaConn = DISCONNECTED; + uint8_t mCnt = 0; + uint32_t *mUtcTimestamp = nullptr; - uint8_t mScanCnt; - bool mScanActive; - bool mGotDisconnect; + uint8_t mScanCnt = 0; + bool mScanActive = false; + bool mGotDisconnect = false; std::list mBSSIDList; - bool mStopApAllowed; + bool mStopApAllowed = false; bool mWasInCh12to14 = false; }; From 1f0b15c5beece854c02df929835aa274369762e3 Mon Sep 17 00:00:00 2001 From: lumapu Date: Tue, 6 Feb 2024 00:01:45 +0100 Subject: [PATCH 154/179] 0.8.74 --- .github/workflows/compile_development.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/compile_development.yml b/.github/workflows/compile_development.yml index 4b5a0e1a..66dbf74b 100644 --- a/.github/workflows/compile_development.yml +++ b/.github/workflows/compile_development.yml @@ -33,7 +33,7 @@ jobs: - opendtufusion-ethernet steps: - uses: actions/checkout@v4 - - uses: benjlevesque/short-sha@v2.2 + - uses: benjlevesque/short-sha@v3.0 id: short-sha with: length: 7 @@ -93,7 +93,7 @@ jobs: - opendtufusion-ethernet-de steps: - uses: actions/checkout@v4 - - uses: benjlevesque/short-sha@v2.2 + - uses: benjlevesque/short-sha@v3.0 id: short-sha with: length: 7 From 90d4df4b916a1b1c873d305d6529c528ae06f7c3 Mon Sep 17 00:00:00 2001 From: lumapu Date: Tue, 6 Feb 2024 23:04:10 +0100 Subject: [PATCH 155/179] 0.8.75 * fix active power control value #1406, #1409 * update Mqtt lib to version `1.6.0` * take care of null terminator of chars #1410, #1411 --- src/CHANGES.md | 5 ++++ src/defines.h | 2 +- src/hm/hmInverter.h | 2 +- src/platformio.ini | 10 +++---- src/publisher/pubMqtt.h | 64 ++++++++++++++++++++--------------------- src/web/RestApi.h | 2 +- src/web/web.h | 20 ++++++------- 7 files changed, 55 insertions(+), 50 deletions(-) diff --git a/src/CHANGES.md b/src/CHANGES.md index f0491335..c01127ce 100644 --- a/src/CHANGES.md +++ b/src/CHANGES.md @@ -1,5 +1,10 @@ # Development Changes +## 0.8.75 - 2024-02-06 +* fix active power control value #1406, #1409 +* update Mqtt lib to version `1.6.0` +* take care of null terminator of chars #1410, #1411 + ## 0.8.74 - 2024-02-05 * reduced cppcheck linter warnings significantly diff --git a/src/defines.h b/src/defines.h index bc9d2452..cdbeee42 100644 --- a/src/defines.h +++ b/src/defines.h @@ -13,7 +13,7 @@ //------------------------------------- #define VERSION_MAJOR 0 #define VERSION_MINOR 8 -#define VERSION_PATCH 74 +#define VERSION_PATCH 75 //------------------------------------- typedef struct { diff --git a/src/hm/hmInverter.h b/src/hm/hmInverter.h index 275fce2c..51cb0d6d 100644 --- a/src/hm/hmInverter.h +++ b/src/hm/hmInverter.h @@ -120,7 +120,7 @@ class Inverter { uint8_t type = INV_TYPE_1CH; // integer which refers to inverter type uint16_t alarmMesIndex = 0; // Last recorded Alarm Message Index uint16_t powerLimit[2] = {0xffff, AbsolutNonPersistent}; // limit power output (multiplied by 10) - float actPowerLimit = -1; // actual power limit + uint16_t actPowerLimit = 0xffff; // actual power limit bool powerLimitAck = false; // acknowledged power limit uint8_t devControlCmd = InitDataState; // carries the requested cmd serial_u radioId; // id converted to modbus diff --git a/src/platformio.ini b/src/platformio.ini index 9b97e018..f949aa37 100644 --- a/src/platformio.ini +++ b/src/platformio.ini @@ -28,7 +28,7 @@ lib_deps = https://github.com/yubox-node-org/ESPAsyncWebServer https://github.com/nRF24/RF24 @ 1.4.8 paulstoffregen/Time @ ^1.6.1 - https://github.com/bertmelis/espMqttClient#v1.5.0 + https://github.com/bertmelis/espMqttClient#v1.6.0 bblanchon/ArduinoJson @ ^6.21.3 https://github.com/JChristensen/Timezone @ ^1.2.4 olikraus/U8g2 @ ^2.35.9 @@ -197,7 +197,7 @@ lib_deps = khoih-prog/AsyncUDP_ESP32_W5500 https://github.com/nRF24/RF24 @ ^1.4.8 paulstoffregen/Time @ ^1.6.1 - https://github.com/bertmelis/espMqttClient#v1.5.0 + https://github.com/bertmelis/espMqttClient#v1.6.0 bblanchon/ArduinoJson @ ^6.21.3 https://github.com/JChristensen/Timezone @ ^1.2.4 olikraus/U8g2 @ ^2.35.9 @@ -220,7 +220,7 @@ lib_deps = khoih-prog/AsyncUDP_ESP32_W5500 https://github.com/nRF24/RF24 @ ^1.4.8 paulstoffregen/Time @ ^1.6.1 - https://github.com/bertmelis/espMqttClient#v1.5.0 + https://github.com/bertmelis/espMqttClient#v1.6.0 bblanchon/ArduinoJson @ ^6.21.3 https://github.com/JChristensen/Timezone @ ^1.2.4 olikraus/U8g2 @ ^2.35.9 @@ -414,7 +414,7 @@ lib_deps = khoih-prog/AsyncUDP_ESP32_W5500 https://github.com/nrf24/RF24 @ ^1.4.8 paulstoffregen/Time @ ^1.6.1 - https://github.com/bertmelis/espMqttClient#v1.5.0 + https://github.com/bertmelis/espMqttClient#v1.6.0 bblanchon/ArduinoJson @ ^6.21.3 https://github.com/JChristensen/Timezone @ ^1.2.4 olikraus/U8g2 @ ^2.35.9 @@ -459,7 +459,7 @@ lib_deps = khoih-prog/AsyncUDP_ESP32_W5500 https://github.com/nrf24/RF24 @ ^1.4.8 paulstoffregen/Time @ ^1.6.1 - https://github.com/bertmelis/espMqttClient#v1.5.0 + https://github.com/bertmelis/espMqttClient#v1.6.0 bblanchon/ArduinoJson @ ^6.21.3 https://github.com/JChristensen/Timezone @ ^1.2.4 olikraus/U8g2 @ ^2.35.9 diff --git a/src/publisher/pubMqtt.h b/src/publisher/pubMqtt.h index 7799b6d2..0e10e688 100644 --- a/src/publisher/pubMqtt.h +++ b/src/publisher/pubMqtt.h @@ -67,15 +67,15 @@ class PubMqtt { }); mDiscovery.running = false; - snprintf(mLwtTopic.data(), mLwtTopic.size(), "%s/mqtt", mCfgMqtt->topic); + snprintf(mLwtTopic.data(), mLwtTopic.size() - 1, "%s/mqtt", mCfgMqtt->topic); if((strlen(mCfgMqtt->user) > 0) && (strlen(mCfgMqtt->pwd) > 0)) mClient.setCredentials(mCfgMqtt->user, mCfgMqtt->pwd); if(strlen(mCfgMqtt->clientId) > 0) - snprintf(mClientId.data(), mClientId.size(), "%s", mCfgMqtt->clientId); + snprintf(mClientId.data(), mClientId.size() - 1, "%s", mCfgMqtt->clientId); else { - snprintf(mClientId.data(), mClientId.size(), "%s-", mDevName); + snprintf(mClientId.data(), mClientId.size() - 1, "%s-", mDevName); uint8_t pos = strlen(mClientId.data()); mClientId[pos++] = WiFi.macAddress().substring( 9, 10).c_str()[0]; mClientId[pos++] = WiFi.macAddress().substring(10, 11).c_str()[0]; @@ -129,7 +129,7 @@ class PubMqtt { } void tickerMinute() { - snprintf(mVal.data(), mVal.size(), "%u", (*mUptime)); + snprintf(mVal.data(), mVal.size() - 1, "%u", (*mUptime)); publish(subtopics[MQTT_UPTIME], mVal.data()); publish(subtopics[MQTT_RSSI], String(WiFi.RSSI()).c_str()); publish(subtopics[MQTT_FREE_HEAP], String(ESP.getFreeHeap()).c_str()); @@ -152,11 +152,11 @@ class PubMqtt { if(NULL == iv) continue; - snprintf(mSubTopic.data(), mSubTopic.size(), "%s/dis_night_comm", iv->config->name); + snprintf(mSubTopic.data(), mSubTopic.size() - 1, "%s/dis_night_comm", iv->config->name); publish(mSubTopic.data(), ((iv->commEnabled) ? dict[STR_TRUE] : dict[STR_FALSE]), true); } - snprintf(mSubTopic.data(), mSubTopic.size(), "comm_disabled"); + snprintf(mSubTopic.data(), mSubTopic.size() - 1, "comm_disabled"); publish(mSubTopic.data(), (((*mUtcTimestamp > (sunset + offsE)) || (*mUtcTimestamp < (sunrise + offsM))) ? dict[STR_TRUE] : dict[STR_FALSE]), true); return true; @@ -179,8 +179,8 @@ class PubMqtt { void tickerMidnight() { // set Total YieldDay to zero - snprintf(mSubTopic.data(), mSubTopic.size(), "total/%s", fields[FLD_YD]); - snprintf(mVal.data(), mVal.size(), "0"); + snprintf(mSubTopic.data(), mSubTopic.size() - 1, "total/%s", fields[FLD_YD]); + snprintf(mVal.data(), mVal.size() - 1, "0"); publish(mSubTopic.data(), mVal.data(), true); } @@ -200,9 +200,9 @@ class PubMqtt { return; if(addTopic) - snprintf(mTopic.data(), mTopic.size(), "%s/%s", mCfgMqtt->topic, subTopic); + snprintf(mTopic.data(), mTopic.size() - 1, "%s/%s", mCfgMqtt->topic, subTopic); else - snprintf(mTopic.data(), mTopic.size(), "%s", subTopic); + snprintf(mTopic.data(), mTopic.size() - 1, "%s", subTopic); mClient.publish(mTopic.data(), qos, retained, payload); yield(); @@ -241,7 +241,7 @@ class PubMqtt { void setPowerLimitAck(Inverter<> *iv) { if (NULL != iv) { - snprintf(mSubTopic.data(), mSubTopic.size(), "%s/%s", iv->config->name, subtopics[MQTT_ACK_PWR_LMT]); + snprintf(mSubTopic.data(), mSubTopic.size() - 1, "%s/%s", iv->config->name, subtopics[MQTT_ACK_PWR_LMT]); publish(mSubTopic.data(), "true", true, true, QOS_2); } } @@ -261,11 +261,11 @@ class PubMqtt { publish(mLwtTopic.data(), mqttStr[MQTT_STR_LWT_CONN], true, false); for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i++) { - snprintf(mVal.data(), mVal.size(), "ctrl/limit/%d", i); + snprintf(mVal.data(), mVal.size() - 1, "ctrl/limit/%d", i); subscribe(mVal.data(), QOS_2); - snprintf(mVal.data(), mVal.size(), "ctrl/restart/%d", i); + snprintf(mVal.data(), mVal.size() - 1, "ctrl/restart/%d", i); subscribe(mVal.data()); - snprintf(mVal.data(), mVal.size(), "ctrl/power/%d", i); + snprintf(mVal.data(), mVal.size() - 1, "ctrl/power/%d", i); subscribe(mVal.data()); } subscribe(subscr[MQTT_SUBS_SET_TIME]); @@ -400,20 +400,20 @@ class PubMqtt { const char *devCls, *stateCls; if (!total) { if (rec->assign[mDiscovery.sub].ch == CH0) - snprintf(name.data(), name.size(), "%s", iv->getFieldName(mDiscovery.sub, rec)); + snprintf(name.data(), name.size() - 1, "%s", iv->getFieldName(mDiscovery.sub, rec)); else - snprintf(name.data(), name.size(), "CH%d_%s", rec->assign[mDiscovery.sub].ch, iv->getFieldName(mDiscovery.sub, rec)); - snprintf(topic.data(), name.size(), "/ch%d/%s", rec->assign[mDiscovery.sub].ch, iv->getFieldName(mDiscovery.sub, rec)); - snprintf(uniq_id.data(), uniq_id.size(), "ch%d_%s", rec->assign[mDiscovery.sub].ch, iv->getFieldName(mDiscovery.sub, rec)); + snprintf(name.data(), name.size() - 1, "CH%d_%s", rec->assign[mDiscovery.sub].ch, iv->getFieldName(mDiscovery.sub, rec)); + snprintf(topic.data(), name.size() - 1, "/ch%d/%s", rec->assign[mDiscovery.sub].ch, iv->getFieldName(mDiscovery.sub, rec)); + snprintf(uniq_id.data(), uniq_id.size() - 1, "ch%d_%s", rec->assign[mDiscovery.sub].ch, iv->getFieldName(mDiscovery.sub, rec)); devCls = getFieldDeviceClass(rec->assign[mDiscovery.sub].fieldId); stateCls = getFieldStateClass(rec->assign[mDiscovery.sub].fieldId); } else { // total values - snprintf(name.data(), name.size(), "Total %s", fields[fldTotal[mDiscovery.sub]]); - snprintf(topic.data(), topic.size(), "/%s", fields[fldTotal[mDiscovery.sub]]); - snprintf(uniq_id.data(), uniq_id.size(), "total_%s", fields[fldTotal[mDiscovery.sub]]); + snprintf(name.data(), name.size() - 1, "Total %s", fields[fldTotal[mDiscovery.sub]]); + snprintf(topic.data(), topic.size() - 1, "/%s", fields[fldTotal[mDiscovery.sub]]); + snprintf(uniq_id.data(), uniq_id.size() - 1, "total_%s", fields[fldTotal[mDiscovery.sub]]); devCls = getFieldDeviceClass(fldTotal[mDiscovery.sub]); stateCls = getFieldStateClass(fldTotal[mDiscovery.sub]); } @@ -433,9 +433,9 @@ class PubMqtt { doc2[F("stat_cla")] = String(stateCls); if (!total) - snprintf(topic.data(), topic.size(), "%s/sensor/%s/ch%d_%s/config", MQTT_DISCOVERY_PREFIX, iv->config->name, rec->assign[mDiscovery.sub].ch, iv->getFieldName(mDiscovery.sub, rec)); + snprintf(topic.data(), topic.size() - 1, "%s/sensor/%s/ch%d_%s/config", MQTT_DISCOVERY_PREFIX, iv->config->name, rec->assign[mDiscovery.sub].ch, iv->getFieldName(mDiscovery.sub, rec)); else // total values - snprintf(topic.data(), topic.size(), "%s/sensor/%s/total_%s/config", MQTT_DISCOVERY_PREFIX, node_id.c_str(), fields[fldTotal[mDiscovery.sub]]); + snprintf(topic.data(), topic.size() - 1, "%s/sensor/%s/total_%s/config", MQTT_DISCOVERY_PREFIX, node_id.c_str(), fields[fldTotal[mDiscovery.sub]]); size_t size = measureJson(doc2) + 1; buf.fill(0); serializeJson(doc2, buf.data(), size); @@ -509,14 +509,14 @@ class PubMqtt { mLastIvState[id] = status; changed = true; - snprintf(mSubTopic.data(), mSubTopic.size(), "%s/available", iv->config->name); - snprintf(mVal.data(), mVal.size(), "%d", (uint8_t)status); + snprintf(mSubTopic.data(), mSubTopic.size() - 1, "%s/available", iv->config->name); + snprintf(mVal.data(), mVal.size() - 1, "%d", (uint8_t)status); publish(mSubTopic.data(), mVal.data(), true); } } if(changed) { - snprintf(mVal.data(), mVal.size(), "%d", ((allAvail) ? MQTT_STATUS_ONLINE : ((anyAvail) ? MQTT_STATUS_PARTIAL : MQTT_STATUS_OFFLINE))); + snprintf(mVal.data(), mVal.size() - 1, "%d", ((allAvail) ? MQTT_STATUS_ONLINE : ((anyAvail) ? MQTT_STATUS_PARTIAL : MQTT_STATUS_OFFLINE))); publish("status", mVal.data(), true); } @@ -539,14 +539,14 @@ class PubMqtt { mSendAlarm[i] = false; - snprintf(mSubTopic.data(), mSubTopic.size(), "%s/alarm/cnt", iv->config->name); - snprintf(mVal.data(), mVal.size(), "%d", iv->alarmCnt); + snprintf(mSubTopic.data(), mSubTopic.size() - 1, "%s/alarm/cnt", iv->config->name); + snprintf(mVal.data(), mVal.size() - 1, "%d", iv->alarmCnt); publish(mSubTopic.data(), mVal.data(), false); for(uint8_t j = 0; j < 10; j++) { if(0 != iv->lastAlarm[j].code) { - snprintf(mSubTopic.data(), mSubTopic.size(), "%s/alarm/%d", iv->config->name, j); - snprintf(mVal.data(), mVal.size(), "{\"code\":%d,\"str\":\"%s\",\"start\":%d,\"end\":%d}", + snprintf(mSubTopic.data(), mSubTopic.size() - 1, "%s/alarm/%d", iv->config->name, j); + snprintf(mVal.data(), mVal.size() - 1, "{\"code\":%d,\"str\":\"%s\",\"start\":%d,\"end\":%d}", iv->lastAlarm[j].code, iv->getAlarmStr(iv->lastAlarm[j].code).c_str(), iv->lastAlarm[j].start + lastMidnight, @@ -581,8 +581,8 @@ class PubMqtt { } } - snprintf(mSubTopic.data(), mSubTopic.size(), "%s/ch%d/%s", iv->config->name, rec->assign[i].ch, fields[rec->assign[i].fieldId]); - snprintf(mVal.data(), mVal.size(), "%g", ah::round3(iv->getValue(i, rec))); + snprintf(mSubTopic.data(), mSubTopic.size() - 1, "%s/ch%d/%s", iv->config->name, rec->assign[i].ch, fields[rec->assign[i].fieldId]); + snprintf(mVal.data(), mVal.size() - 1, "%g", ah::round3(iv->getValue(i, rec))); publish(mSubTopic.data(), mVal.data(), retained); yield(); diff --git a/src/web/RestApi.h b/src/web/RestApi.h index acd085f3..ce580dd5 100644 --- a/src/web/RestApi.h +++ b/src/web/RestApi.h @@ -500,7 +500,7 @@ class RestApi { obj[F("name")] = String(iv->config->name); obj[F("serial")] = String(iv->config->serial.u64, HEX); obj[F("version")] = String(iv->getFwVersion()); - obj[F("power_limit_read")] = ah::round3(iv->actPowerLimit); + obj[F("power_limit_read")] = iv->actPowerLimit; obj[F("power_limit_ack")] = iv->powerLimitAck; obj[F("max_pwr")] = iv->getMaxPower(); obj[F("ts_last_success")] = rec->ts; diff --git a/src/web/web.h b/src/web/web.h index a166377f..c40c817c 100644 --- a/src/web/web.h +++ b/src/web/web.h @@ -656,7 +656,7 @@ class Web { { "is_enabled", "gauge", metricConstInverterFormat, [](Inverter<> *iv)-> uint64_t {return iv->config->enabled;} }, { "is_available", "gauge", metricConstInverterFormat, [](Inverter<> *iv)-> uint64_t {return iv->isAvailable();} }, { "is_producing", "gauge", metricConstInverterFormat, [](Inverter<> *iv)-> uint64_t {return iv->isProducing();} }, - { "power_limit_read", "gauge", metricConstInverterFormat, [](Inverter<> *iv)-> uint64_t {return (int64_t)ah::round3(iv->actPowerLimit);} }, + { "power_limit_read", "gauge", metricConstInverterFormat, [](Inverter<> *iv)-> uint64_t {return iv->actPowerLimit;} }, { "power_limit_ack", "gauge", metricConstInverterFormat, [](Inverter<> *iv)-> uint64_t {return (iv->powerLimitAck)?1:0;} }, { "max_power", "gauge", metricConstInverterFormat, [](Inverter<> *iv)-> uint64_t {return iv->getMaxPower();} }, { "radio_rx_success", "counter" ,metricConstInverterFormat, [](Inverter<> *iv)-> uint64_t {return iv->radioStatistics.rxSuccess;} }, @@ -786,14 +786,14 @@ class Web { char total[7]; if (metricDeclared) { // A declaration and value for channels have been delivered. So declare and deliver a _total metric - strncpy(total, "_total", 6); + snprintf(total, sizeof(total)-1, "_total"); } if (!metricTotalDeclard) { - snprintf(type, sizeof(type), "# TYPE %s%s%s%s %s\n",metricConstPrefix, iv->getFieldName(metricsChannelId, rec), promUnit.c_str(), total, promType.c_str()); + snprintf(type, sizeof(type)-1, "# TYPE %s%s%s%s %s\n",metricConstPrefix, iv->getFieldName(metricsChannelId, rec), promUnit.c_str(), total, promType.c_str()); metrics += type; metricTotalDeclard = true; } - snprintf(topic, sizeof(topic), "%s%s%s%s{inverter=\"%s\"}",metricConstPrefix, iv->getFieldName(metricsChannelId, rec), promUnit.c_str(), total,iv->config->name); + snprintf(topic, sizeof(topic)-1, "%s%s%s%s{inverter=\"%s\"}",metricConstPrefix, iv->getFieldName(metricsChannelId, rec), promUnit.c_str(), total,iv->config->name); } else { // Report (non zero) channel value // Use a fallback channel name (ch0, ch1, ...)if non is given by user @@ -801,11 +801,11 @@ class Web { if (iv->config->chName[channel-1][0] != 0) { strncpy(chName, iv->config->chName[channel-1], sizeof(chName)); } else { - snprintf(chName,sizeof(chName),"ch%1d",channel); + snprintf(chName,sizeof(chName)-1,"ch%1d",channel); } - snprintf(topic, sizeof(topic), "%s%s%s{inverter=\"%s\",channel=\"%s\"}",metricConstPrefix, iv->getFieldName(metricsChannelId, rec), promUnit.c_str(), iv->config->name,chName); + snprintf(topic, sizeof(topic)-1, "%s%s%s{inverter=\"%s\",channel=\"%s\"}",metricConstPrefix, iv->getFieldName(metricsChannelId, rec), promUnit.c_str(), iv->config->name,chName); } - snprintf(val, sizeof(val), " %.3f\n", iv->getValue(metricsChannelId, rec)); + snprintf(val, sizeof(val)-1, " %.3f\n", iv->getValue(metricsChannelId, rec)); metrics += topic; metrics += val; } @@ -835,7 +835,7 @@ class Web { case metricsStateAlarmData: // Alarm Info loop : fit to one packet // Perform grouping on metrics according to Prometheus exposition format specification - snprintf(type, sizeof(type),"# TYPE %s%s gauge\n",metricConstPrefix,fields[FLD_LAST_ALARM_CODE]); + snprintf(type, sizeof(type)-1,"# TYPE %s%s gauge\n",metricConstPrefix,fields[FLD_LAST_ALARM_CODE]); metrics = type; for (metricsInverterId = 0; metricsInverterId < mSys->getNumInverters();metricsInverterId++) { @@ -847,8 +847,8 @@ class Web { alarmChannelId = 0; if (alarmChannelId < rec->length) { std::tie(promUnit, promType) = convertToPromUnits(iv->getUnit(alarmChannelId, rec)); - snprintf(topic, sizeof(topic), "%s%s%s{inverter=\"%s\"}",metricConstPrefix, iv->getFieldName(alarmChannelId, rec), promUnit.c_str(), iv->config->name); - snprintf(val, sizeof(val), " %.3f\n", iv->getValue(alarmChannelId, rec)); + snprintf(topic, sizeof(topic)-1, "%s%s%s{inverter=\"%s\"}",metricConstPrefix, iv->getFieldName(alarmChannelId, rec), promUnit.c_str(), iv->config->name); + snprintf(val, sizeof(val)-1, " %.3f\n", iv->getValue(alarmChannelId, rec)); metrics += topic; metrics += val; } From 716c3da3df6483047754ad154819f70b8ec009b1 Mon Sep 17 00:00:00 2001 From: tictrick <117273857+tictrick@users.noreply.github.com> Date: Wed, 7 Feb 2024 11:36:57 +0100 Subject: [PATCH 156/179] Bugfix: HMT would not start Am Morgen wollten die HMTs nicht starten. --- src/hm/hmSystem.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/hm/hmSystem.h b/src/hm/hmSystem.h index 0266a948..22318347 100644 --- a/src/hm/hmSystem.h +++ b/src/hm/hmSystem.h @@ -69,6 +69,7 @@ class HmSystem { } else if(iv->config->serial.b[5] == 0x13) { iv->ivGen = IV_HMT; iv->type = INV_TYPE_6CH; + iv->ivRadioType = INV_RADIO_TYPE_CMT; } else if(iv->config->serial.u64 != 0ULL) { DPRINTLN(DBG_ERROR, F("inverter type can't be detected!")); return; From 7c532ca1cc63bfda9a68cfb9b7baf23d1d17f94b Mon Sep 17 00:00:00 2001 From: lumapu Date: Wed, 7 Feb 2024 22:50:13 +0100 Subject: [PATCH 157/179] 0.8.76 * revert changes from yesterday regarding snprintf and its size #1410, #1411 * reduced cppcheck linter warnings significantly * try to improve ePaper (ghosting) #1107 --- src/CHANGES.md | 5 ++ src/app.cpp | 5 +- src/defines.h | 2 +- src/hm/Communication.h | 14 ++-- src/hm/Heuristic.h | 2 +- src/hm/hmRadio.h | 6 +- src/hm/radio.h | 14 ++-- src/hm/simulator.h | 6 +- src/hms/hmsRadio.h | 7 +- src/plugins/Display/Display.h | 20 +++--- src/plugins/Display/Display_Mono_128X32.h | 6 +- src/plugins/Display/Display_Mono_128X64.h | 39 +++++------- src/plugins/Display/Display_Mono_64X48.h | 6 +- src/plugins/Display/Display_Mono_84X48.h | 27 ++++---- src/plugins/Display/Display_ePaper.cpp | 17 +++-- src/plugins/history.h | 7 +- src/publisher/pubMqtt.h | 78 +++++++++++------------ src/publisher/pubMqttIvData.h | 26 ++++---- src/utils/improv.h | 2 +- src/utils/syslog.cpp | 1 + src/utils/syslog.h | 5 +- src/web/Protection.h | 2 +- src/web/web.h | 25 ++++---- 23 files changed, 164 insertions(+), 158 deletions(-) diff --git a/src/CHANGES.md b/src/CHANGES.md index c01127ce..a9301d44 100644 --- a/src/CHANGES.md +++ b/src/CHANGES.md @@ -1,5 +1,10 @@ # Development Changes +## 0.8.76 - 2024-02-07 +* revert changes from yesterday regarding snprintf and its size #1410, #1411 +* reduced cppcheck linter warnings significantly +* try to improve ePaper (ghosting) #1107 + ## 0.8.75 - 2024-02-06 * fix active power control value #1406, #1409 * update Mqtt lib to version `1.6.0` diff --git a/src/app.cpp b/src/app.cpp index d9b0f933..c93be777 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -264,11 +264,10 @@ void app::tickNtpUpdate(void) { #endif if (isOK) { this->updateNtp(); - - nxtTrig = isOK ? (mConfig->ntp.interval * 60) : 60; // depending on NTP update success check again in 12h (depends on setting) or in 1 min + nxtTrig = mConfig->ntp.interval * 60; // check again in 12h // immediately start communicating - if (isOK && mSendFirst) { + if (mSendFirst) { mSendFirst = false; once(std::bind(&app::tickSend, this), 1, "senOn"); } diff --git a/src/defines.h b/src/defines.h index cdbeee42..9ea3834c 100644 --- a/src/defines.h +++ b/src/defines.h @@ -13,7 +13,7 @@ //------------------------------------- #define VERSION_MAJOR 0 #define VERSION_MINOR 8 -#define VERSION_PATCH 75 +#define VERSION_PATCH 76 //------------------------------------- typedef struct { diff --git a/src/hm/Communication.h b/src/hm/Communication.h index 326fb57e..21b78405 100644 --- a/src/hm/Communication.h +++ b/src/hm/Communication.h @@ -698,7 +698,7 @@ class Communication : public CommQueue<> { byte[23] to byte[26] Matching_APPFW_PN*/ DPRINT(DBG_INFO,F("HW_PartNo ")); DBGPRINTLN(String((uint32_t) (((p->packet[10] << 8) | p->packet[11]) << 8 | p->packet[12]) << 8 | p->packet[13])); - record_t<> *rec = q->iv->getRecordStruct(InverterDevInform_Simple); // choose the record structure + rec = q->iv->getRecordStruct(InverterDevInform_Simple); // choose the record structure rec->ts = q->ts; q->iv->setValue(0, rec, (uint32_t) ((((p->packet[10] << 8) | p->packet[11]) << 8 | p->packet[12]) << 8 | p->packet[13])/1); rec->mqttSentStatus = MqttSentStatus::NEW_DATA; @@ -899,14 +899,16 @@ class Communication : public CommQueue<> { q->iv->alarmCnt = 1; // minimum... stsok = false; //sth is or was wrong? - if ( (q->iv->type != INV_TYPE_1CH) && ( (statusMi != 3) - || ((q->iv->lastAlarm[stschan].code) && (statusMi == 3) && (q->iv->lastAlarm[stschan].code != 1))) + if ((q->iv->type != INV_TYPE_1CH) + && ((statusMi != 3) + || ((q->iv->lastAlarm[stschan].code) && (q->iv->lastAlarm[stschan].code != 1))) ) { q->iv->lastAlarm[stschan+q->iv->type==INV_TYPE_2CH ? 2: 4] = alarm_t(q->iv->lastAlarm[stschan].code, q->iv->lastAlarm[stschan].start,q->ts); q->iv->lastAlarm[stschan] = alarm_t(prntsts, q->ts,0); q->iv->alarmCnt = q->iv->type == INV_TYPE_2CH ? 3 : 5; - } else if ( (q->iv->type == INV_TYPE_1CH) && ( (statusMi != 3) - || ((q->iv->lastAlarm[stschan].code) && (statusMi == 3) && (q->iv->lastAlarm[stschan].code != 1))) + } else if ((q->iv->type == INV_TYPE_1CH) + && ( (statusMi != 3) + || ((q->iv->lastAlarm[stschan].code) && (q->iv->lastAlarm[stschan].code != 1))) ) { q->iv->lastAlarm[stschan] = alarm_t(q->iv->lastAlarm[0].code, q->iv->lastAlarm[0].start,q->ts); } else if (q->iv->type == INV_TYPE_1CH) @@ -962,7 +964,7 @@ class Communication : public CommQueue<> { iv->radioStatistics.ivLoss = iv->radioStatistics.ivSent - iv->mDtuRxCnt; // this is what we didn't receive iv->radioStatistics.dtuLoss = iv->mIvTxCnt; // this is somehow the requests w/o answers in that periode iv->radioStatistics.dtuSent = iv->mDtuTxCnt; - if (mSerialDebug) { + if (*mSerialDebug) { DPRINT_IVID(DBG_INFO, iv->id); DBGPRINTLN("DTU loss: " + String (iv->radioStatistics.ivLoss) + "/" + diff --git a/src/hm/Heuristic.h b/src/hm/Heuristic.h index 2a36bc78..ecf82aa7 100644 --- a/src/hm/Heuristic.h +++ b/src/hm/Heuristic.h @@ -132,7 +132,7 @@ class Heuristic { ih->lastRxFragments = rxFragments; } - void printStatus(Inverter<> *iv) { + void printStatus(const Inverter<> *iv) { DPRINT_IVID(DBG_INFO, iv->id); DBGPRINT(F("Radio infos:")); if((IV_HMS != iv->ivGen) && (IV_HMT != iv->ivGen)) { diff --git a/src/hm/hmRadio.h b/src/hm/hmRadio.h index aef4f49d..df6a18b4 100644 --- a/src/hm/hmRadio.h +++ b/src/hm/hmRadio.h @@ -395,9 +395,9 @@ class HmRadio : public Radio { #endif*/ if(*mPrintWholeTrace) { if(*mPrivacyMode) - ah::dumpBuf(mTxBuf, len, 1, 4); + ah::dumpBuf(mTxBuf.data(), len, 1, 4); else - ah::dumpBuf(mTxBuf, len); + ah::dumpBuf(mTxBuf.data(), len); } else { DHEX(mTxBuf[0]); DBGPRINT(F(" ")); @@ -415,7 +415,7 @@ class HmRadio : public Radio { } mNrf24->setChannel(mRfChLst[mTxChIdx]); mNrf24->openWritingPipe(reinterpret_cast(&iv->radioId.u64)); - mNrf24->startWrite(mTxBuf, len, false); // false = request ACK response + mNrf24->startWrite(mTxBuf.data(), len, false); // false = request ACK response mMillis = millis(); mLastIv = iv; diff --git a/src/hm/radio.h b/src/hm/radio.h index db694b54..31643980 100644 --- a/src/hm/radio.h +++ b/src/hm/radio.h @@ -11,6 +11,7 @@ #define ALL_FRAMES 0x80 #define SINGLE_FRAME 0x81 +#include #include #include "../utils/dbg.h" #include "../utils/crc.h" @@ -34,6 +35,8 @@ class Radio { virtual std::pair getFreqRangeMhz(void) { return std::make_pair(0, 0); } virtual bool loop(void) = 0; + Radio() : mTxBuf{} {} + void handleIntr(void) { mIrqRcvd = true; mIrqOk = IRQ_OK; @@ -107,7 +110,7 @@ class Radio { mTxBuf[(*len)++] = (crc ) & 0xff; } // crc over all - mTxBuf[*len] = ah::crc8(mTxBuf, *len); + mTxBuf[*len] = ah::crc8(mTxBuf.data(), *len); (*len)++; } @@ -129,12 +132,11 @@ class Radio { mDtuSn |= 0x80000000; // the first digit is an 8 for DTU production year 2022, the rest is filled with the ESP chipID in decimal } - - - uint32_t mDtuSn; - std::atomic mIrqRcvd; + protected: + uint32_t mDtuSn = 0; + std::atomic mIrqRcvd = false; bool *mSerialDebug = nullptr, *mPrivacyMode = nullptr, *mPrintWholeTrace = nullptr; - uint8_t mTxBuf[MAX_RF_PAYLOAD_SIZE]; + std::array mTxBuf; }; #endif /*__RADIO_H__*/ diff --git a/src/hm/simulator.h b/src/hm/simulator.h index 4e06062e..3d0b43d7 100644 --- a/src/hm/simulator.h +++ b/src/hm/simulator.h @@ -118,9 +118,9 @@ class Simulator { } private: - HMSYSTEM *mSys; - uint8_t mIvId; - uint32_t *mTimestamp; + HMSYSTEM *mSys = nullptr; + uint8_t mIvId = 0; + uint32_t *mTimestamp = nullptr; payloadListenerType mCbPayload = nullptr; uint8_t payloadCtrl = 0; diff --git a/src/hms/hmsRadio.h b/src/hms/hmsRadio.h index 66e312d7..cb181b0b 100644 --- a/src/hms/hmsRadio.h +++ b/src/hms/hmsRadio.h @@ -21,6 +21,7 @@ class CmtRadio : public Radio { mPrivacyMode = privacyMode; mSerialDebug = serialDebug; mPrintWholeTrace = printWholeTrace; + mTxBuf.fill(0); } bool loop() override { @@ -102,9 +103,9 @@ class CmtRadio : public Radio { DBGPRINT(F("Mhz | ")); if(*mPrintWholeTrace) { if(*mPrivacyMode) - ah::dumpBuf(mTxBuf, len, 1, 4); + ah::dumpBuf(mTxBuf.data(), len, 1, 4); else - ah::dumpBuf(mTxBuf, len); + ah::dumpBuf(mTxBuf.data(), len); } else { DHEX(mTxBuf[0]); DBGPRINT(F(" ")); @@ -114,7 +115,7 @@ class CmtRadio : public Radio { } } - CmtStatus status = mCmt.tx(mTxBuf, len); + CmtStatus status = mCmt.tx(mTxBuf.data(), len); mMillis = millis(); if(CmtStatus::SUCCESS != status) { DPRINT(DBG_WARN, F("CMT TX failed, code: ")); diff --git a/src/plugins/Display/Display.h b/src/plugins/Display/Display.h index c59a56b6..b2c88b05 100644 --- a/src/plugins/Display/Display.h +++ b/src/plugins/Display/Display.h @@ -223,21 +223,21 @@ class Display { } // private member variables - IApp *mApp; + IApp *mApp = nullptr; DisplayData mDisplayData; - bool mNewPayload; - uint8_t mLoopCnt; - uint32_t *mUtcTs; - display_t *mCfg; - HMSYSTEM *mSys; - RADIO *mHmRadio; - RADIO *mHmsRadio; - uint16_t mRefreshCycle; + bool mNewPayload = false; + uint8_t mLoopCnt = 0; + uint32_t *mUtcTs = nullptr; + display_t *mCfg = nullptr; + HMSYSTEM *mSys = nullptr; + RADIO *mHmRadio = nullptr; + RADIO *mHmsRadio = nullptr; + uint16_t mRefreshCycle = 0; #if defined(ESP32) && !defined(ETHERNET) DisplayEPaper mEpaper; #endif - DisplayMono *mMono; + DisplayMono *mMono = nullptr; }; #endif /*PLUGIN_DISPLAY*/ diff --git a/src/plugins/Display/Display_Mono_128X32.h b/src/plugins/Display/Display_Mono_128X32.h index 3e5a1762..6262e3f6 100644 --- a/src/plugins/Display/Display_Mono_128X32.h +++ b/src/plugins/Display/Display_Mono_128X32.h @@ -12,11 +12,11 @@ class DisplayMono128X32 : public DisplayMono { mExtra = 0; } - void config(display_t *cfg) { + void config(display_t *cfg) override { mCfg = cfg; } - void init(DisplayData *displayData) { + void init(DisplayData *displayData) override { u8g2_cb_t *rot = (u8g2_cb_t *)((mCfg->rot != 0x00) ? U8G2_R2 : U8G2_R0); monoInit(new U8G2_SSD1306_128X32_UNIVISION_F_HW_I2C(rot, 0xff, mCfg->disp_clk, mCfg->disp_data), displayData); calcLinePositions(); @@ -26,7 +26,7 @@ class DisplayMono128X32 : public DisplayMono { mDisplay->sendBuffer(); } - void disp(void) { + void disp(void) override { mDisplay->clearBuffer(); // calculate current pixelshift for pixelshift screensaver diff --git a/src/plugins/Display/Display_Mono_128X64.h b/src/plugins/Display/Display_Mono_128X64.h index 69afae04..c63f0b22 100644 --- a/src/plugins/Display/Display_Mono_128X64.h +++ b/src/plugins/Display/Display_Mono_128X64.h @@ -13,11 +13,11 @@ class DisplayMono128X64 : public DisplayMono { mExtra = 0; } - void config(display_t *cfg) { + void config(display_t *cfg) override { mCfg = cfg; } - void init(DisplayData *displayData) { + void init(DisplayData *displayData) override { u8g2_cb_t *rot = (u8g2_cb_t *)(( mCfg->rot != 0x00) ? U8G2_R2 : U8G2_R0); switch (mCfg->type) { case DISP_TYPE_T1_SSD1306_128X64: @@ -68,9 +68,7 @@ class DisplayMono128X64 : public DisplayMono { mDisplay->sendBuffer(); } - void disp(void) { - uint8_t pos, sun_pos, moon_pos; - + void disp(void) override { mDisplay->clearBuffer(); // Layout-Test @@ -106,8 +104,8 @@ class DisplayMono128X64 : public DisplayMono { } // print status of inverters else { - sun_pos = -1; - moon_pos = -1; + int8_t sun_pos = -1; + int8_t moon_pos = -1; setLineFont(l_Status); if (0 == mDisplayData->nrSleeping + mDisplayData->nrProducing) snprintf(mFmtText, DISP_FMT_TEXT_LEN, "no inverter"); @@ -128,11 +126,11 @@ class DisplayMono128X64 : public DisplayMono { } printText(mFmtText, l_Status, 0xff); - pos = (mDispWidth - mDisplay->getStrWidth(mFmtText)) / 2; + uint8_t pos = (mDispWidth - mDisplay->getStrWidth(mFmtText)) / 2; mDisplay->setFont(u8g2_font_ncenB08_symbols8_ahoy); - if (sun_pos!=-1) + if (sun_pos != -1) mDisplay->drawStr(pos + sun_pos + mPixelshift, mLineYOffsets[l_Status], "G"); // sun symbol - if (moon_pos!=-1) + if (moon_pos != -1) mDisplay->drawStr(pos + moon_pos + mPixelshift, mLineYOffsets[l_Status], "H"); // moon symbol } } @@ -181,12 +179,11 @@ class DisplayMono128X64 : public DisplayMono { // draw dynamic RSSI bars int rssi_bar_height = 9; for (int i = 0; i < 4; i++) { - int radio_rssi_threshold = -60 - i * 10; - int wifi_rssi_threshold = -60 - i * 10; + int rssi_threshold = -60 - i * 10; uint8_t barwidth = std::min(4 - i, 3); - if (mDisplayData->RadioRSSI > radio_rssi_threshold) + if (mDisplayData->RadioRSSI > rssi_threshold) mDisplay->drawBox(widthShrink / 2 + mPixelshift, 8 + (rssi_bar_height + 1) * i, barwidth, rssi_bar_height); - if (mDisplayData->WifiRSSI > wifi_rssi_threshold) + if (mDisplayData->WifiRSSI > rssi_threshold) mDisplay->drawBox(mDispWidth - barwidth - widthShrink / 2 + mPixelshift, 8 + (rssi_bar_height + 1) * i, barwidth, rssi_bar_height); } // draw dynamic antenna and WiFi symbols @@ -223,23 +220,22 @@ class DisplayMono128X64 : public DisplayMono { l_MAX_LINES = 5, }; - uint8_t graph_first_line; - uint8_t graph_last_line; + uint8_t graph_first_line = 0; + uint8_t graph_last_line = 0; const uint8_t pixelShiftRange = 11; // number of pixels to shift from left to right (centered -> must be odd!) - uint8_t widthShrink; + uint8_t widthShrink = 0; void calcLinePositions() { uint8_t yOff = 0; uint8_t i = 0; - uint8_t asc, dsc; do { setLineFont(i); - asc = mDisplay->getAscent(); + uint8_t asc = mDisplay->getAscent(); yOff += asc; mLineYOffsets[i] = yOff; - dsc = mDisplay->getDescent(); + uint8_t dsc = mDisplay->getDescent(); yOff -= dsc; if (l_Time == i) // prevent time and status line to touch yOff++; // -> one pixels space @@ -248,8 +244,7 @@ class DisplayMono128X64 : public DisplayMono { } inline void setLineFont(uint8_t line) { - if ((line == l_TotalPower) || - (line == l_Ahoy)) + if (line == l_TotalPower) // || (line == l_Ahoy) -> l_TotalPower == l_Ahoy == 2 mDisplay->setFont(u8g2_font_ncenB14_tr); else if ((line == l_YieldDay) || (line == l_YieldTotal)) diff --git a/src/plugins/Display/Display_Mono_64X48.h b/src/plugins/Display/Display_Mono_64X48.h index 84e40126..7f98cae5 100644 --- a/src/plugins/Display/Display_Mono_64X48.h +++ b/src/plugins/Display/Display_Mono_64X48.h @@ -12,11 +12,11 @@ class DisplayMono64X48 : public DisplayMono { mExtra = 0; } - void config(display_t *cfg) { + void config(display_t *cfg) override { mCfg = cfg; } - void init(DisplayData *displayData) { + void init(DisplayData *displayData) override { u8g2_cb_t *rot = (u8g2_cb_t *)((mCfg->rot != 0x00) ? U8G2_R2 : U8G2_R0); // Wemos OLed Shield is not defined in u8 lib -> use nearest compatible monoInit(new U8G2_SSD1306_64X48_ER_F_HW_I2C(rot, 0xff, mCfg->disp_clk, mCfg->disp_data), displayData); @@ -28,7 +28,7 @@ class DisplayMono64X48 : public DisplayMono { mDisplay->sendBuffer(); } - void disp(void) { + void disp(void) override { mDisplay->clearBuffer(); // calculate current pixelshift for pixelshift screensaver diff --git a/src/plugins/Display/Display_Mono_84X48.h b/src/plugins/Display/Display_Mono_84X48.h index d3be1f31..b5daacd5 100644 --- a/src/plugins/Display/Display_Mono_84X48.h +++ b/src/plugins/Display/Display_Mono_84X48.h @@ -12,11 +12,11 @@ class DisplayMono84X48 : public DisplayMono { mExtra = 0; } - void config(display_t *cfg) { + void config(display_t *cfg) override { mCfg = cfg; } - void init(DisplayData *displayData) { + void init(DisplayData *displayData) override { u8g2_cb_t *rot = (u8g2_cb_t *)((mCfg->rot != 0x00) ? U8G2_R2 : U8G2_R0); monoInit(new U8G2_PCD8544_84X48_F_4W_SW_SPI(rot, mCfg->disp_clk, mCfg->disp_data, mCfg->disp_cs, mCfg->disp_dc, 0xff), displayData); @@ -55,7 +55,7 @@ class DisplayMono84X48 : public DisplayMono { mDisplay->sendBuffer(); } - void disp(void) { + void disp(void) override { mDisplay->clearBuffer(); // Layout-Test @@ -143,12 +143,11 @@ class DisplayMono84X48 : public DisplayMono { // draw dynamic RSSI bars int rssi_bar_height = 7; for (int i = 0; i < 4; i++) { - int radio_rssi_threshold = -60 - i * 10; - int wifi_rssi_threshold = -60 - i * 10; + int rssi_threshold = -60 - i * 10; uint8_t barwidth = std::min(4 - i, 3); - if (mDisplayData->RadioRSSI > radio_rssi_threshold) + if (mDisplayData->RadioRSSI > rssi_threshold) mDisplay->drawBox(0, 8 + (rssi_bar_height + 1) * i, barwidth, rssi_bar_height); - if (mDisplayData->WifiRSSI > wifi_rssi_threshold) + if (mDisplayData->WifiRSSI > rssi_threshold) mDisplay->drawBox(mDispWidth - barwidth, 8 + (rssi_bar_height + 1) * i, barwidth, rssi_bar_height); } @@ -184,30 +183,28 @@ class DisplayMono84X48 : public DisplayMono { l_MAX_LINES = 5, }; - uint8_t graph_first_line; - uint8_t graph_last_line; + uint8_t graph_first_line = 0; + uint8_t graph_last_line = 0; void calcLinePositions() { uint8_t yOff = 0; uint8_t i = 0; - uint8_t asc, dsc; do { setLineFont(i); - asc = mDisplay->getAscent(); + uint8_t asc = mDisplay->getAscent(); yOff += asc; mLineYOffsets[i] = yOff; - dsc = mDisplay->getDescent(); + uint8_t dsc = mDisplay->getDescent(); if (l_TotalPower != i) // power line needs no descent spacing yOff -= dsc; yOff++; // instead lets spend one pixel space between all lines i++; - } while(l_MAX_LINES>i); + } while(l_MAX_LINES > i); } inline void setLineFont(uint8_t line) { - if ((line == l_TotalPower) || - (line == l_Ahoy)) + if (line == l_TotalPower) // || (line == l_Ahoy) -> l_TotalPower == l_Ahoy == 2 mDisplay->setFont(u8g2_font_logisoso16_tr); else mDisplay->setFont(u8g2_font_5x8_symbols_ahoy); diff --git a/src/plugins/Display/Display_ePaper.cpp b/src/plugins/Display/Display_ePaper.cpp index fdcec767..087d784b 100644 --- a/src/plugins/Display/Display_ePaper.cpp +++ b/src/plugins/Display/Display_ePaper.cpp @@ -67,7 +67,7 @@ void DisplayEPaper::refreshLoop() { case RefreshStatus::LOGO: _display->fillScreen(GxEPD_BLACK); _display->drawBitmap(0, 0, logo, 200, 200, GxEPD_WHITE); - _display->display(false); // full update + mSecondCnt = 2; mNextRefreshState = RefreshStatus::PARTITIALS; mRefreshState = RefreshStatus::WAIT; break; @@ -79,11 +79,11 @@ void DisplayEPaper::refreshLoop() { break; case RefreshStatus::WHITE: - if(mSecondCnt == 0) { - _display->fillScreen(GxEPD_WHITE); - mNextRefreshState = RefreshStatus::PARTITIALS; - mRefreshState = RefreshStatus::WAIT; - } + if(0 != mSecondCnt) + break; + _display->fillScreen(GxEPD_WHITE); + mNextRefreshState = RefreshStatus::PARTITIALS; + mRefreshState = RefreshStatus::WAIT; break; case RefreshStatus::WAIT: @@ -92,10 +92,13 @@ void DisplayEPaper::refreshLoop() { break; case RefreshStatus::PARTITIALS: + if(0 != mSecondCnt) + break; headlineIP(); versionFooter(); mSecondCnt = 4; // display Logo time during boot up - mRefreshState = RefreshStatus::DONE; + mNextRefreshState = RefreshStatus::DONE; + mRefreshState = RefreshStatus::WAIT; break; default: // RefreshStatus::DONE diff --git a/src/plugins/history.h b/src/plugins/history.h index 7e415675..5076e295 100644 --- a/src/plugins/history.h +++ b/src/plugins/history.h @@ -47,14 +47,13 @@ class HistoryData { } void tickerSecond() { - Inverter<> *iv; - record_t<> *rec; + ; float curPwr = 0; float maxPwr = 0; float yldDay = -0.1; for (uint8_t i = 0; i < mSys->getNumInverters(); i++) { - iv = mSys->getInverterByPos(i); - rec = iv->getRecordStruct(RealTimeRunData_Debug); + Inverter<> *iv = mSys->getInverterByPos(i); + record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug); if (iv == NULL) continue; curPwr += iv->getChannelFieldValue(CH0, FLD_PAC, rec); diff --git a/src/publisher/pubMqtt.h b/src/publisher/pubMqtt.h index 0e10e688..1b9e35ef 100644 --- a/src/publisher/pubMqtt.h +++ b/src/publisher/pubMqtt.h @@ -38,7 +38,7 @@ typedef struct { template class PubMqtt { public: - PubMqtt() { + PubMqtt() : SendIvData() { mLastIvState.fill(InverterStatus::OFF); mIvLastRTRpub.fill(0); @@ -61,21 +61,21 @@ class PubMqtt { mUptime = uptime; mIntervalTimeout = 1; - mSendIvData.setup(sys, utcTs, &mSendList); - mSendIvData.setPublishFunc([this](const char *subTopic, const char *payload, bool retained, uint8_t qos) { + SendIvData.setup(sys, utcTs, &mSendList); + SendIvData.setPublishFunc([this](const char *subTopic, const char *payload, bool retained, uint8_t qos) { publish(subTopic, payload, retained, true, qos); }); mDiscovery.running = false; - snprintf(mLwtTopic.data(), mLwtTopic.size() - 1, "%s/mqtt", mCfgMqtt->topic); + snprintf(mLwtTopic.data(), mLwtTopic.size(), "%s/mqtt", mCfgMqtt->topic); if((strlen(mCfgMqtt->user) > 0) && (strlen(mCfgMqtt->pwd) > 0)) mClient.setCredentials(mCfgMqtt->user, mCfgMqtt->pwd); if(strlen(mCfgMqtt->clientId) > 0) - snprintf(mClientId.data(), mClientId.size() - 1, "%s", mCfgMqtt->clientId); + snprintf(mClientId.data(), mClientId.size(), "%s", mCfgMqtt->clientId); else { - snprintf(mClientId.data(), mClientId.size() - 1, "%s-", mDevName); + snprintf(mClientId.data(), mClientId.size(), "%s-", mDevName); uint8_t pos = strlen(mClientId.data()); mClientId[pos++] = WiFi.macAddress().substring( 9, 10).c_str()[0]; mClientId[pos++] = WiFi.macAddress().substring(10, 11).c_str()[0]; @@ -95,7 +95,7 @@ class PubMqtt { } void loop() { - mSendIvData.loop(); + SendIvData.loop(); #if defined(ESP8266) mClient.loop(); @@ -129,7 +129,7 @@ class PubMqtt { } void tickerMinute() { - snprintf(mVal.data(), mVal.size() - 1, "%u", (*mUptime)); + snprintf(mVal.data(), mVal.size(), "%u", (*mUptime)); publish(subtopics[MQTT_UPTIME], mVal.data()); publish(subtopics[MQTT_RSSI], String(WiFi.RSSI()).c_str()); publish(subtopics[MQTT_FREE_HEAP], String(ESP.getFreeHeap()).c_str()); @@ -152,11 +152,11 @@ class PubMqtt { if(NULL == iv) continue; - snprintf(mSubTopic.data(), mSubTopic.size() - 1, "%s/dis_night_comm", iv->config->name); + snprintf(mSubTopic.data(), mSubTopic.size(), "%s/dis_night_comm", iv->config->name); publish(mSubTopic.data(), ((iv->commEnabled) ? dict[STR_TRUE] : dict[STR_FALSE]), true); } - snprintf(mSubTopic.data(), mSubTopic.size() - 1, "comm_disabled"); + snprintf(mSubTopic.data(), mSubTopic.size(), "comm_disabled"); publish(mSubTopic.data(), (((*mUtcTimestamp > (sunset + offsE)) || (*mUtcTimestamp < (sunrise + offsM))) ? dict[STR_TRUE] : dict[STR_FALSE]), true); return true; @@ -164,7 +164,7 @@ class PubMqtt { void notAvailChanged(bool allNotAvail) { if(!allNotAvail) - mSendIvData.resetYieldDay(); + SendIvData.resetYieldDay(); } bool tickerComm(bool disabled) { @@ -179,8 +179,8 @@ class PubMqtt { void tickerMidnight() { // set Total YieldDay to zero - snprintf(mSubTopic.data(), mSubTopic.size() - 1, "total/%s", fields[FLD_YD]); - snprintf(mVal.data(), mVal.size() - 1, "0"); + snprintf(mSubTopic.data(), mSubTopic.size(), "total/%s", fields[FLD_YD]); + snprintf(mVal.data(), mVal.size(), "0"); publish(mSubTopic.data(), mVal.data(), true); } @@ -200,9 +200,9 @@ class PubMqtt { return; if(addTopic) - snprintf(mTopic.data(), mTopic.size() - 1, "%s/%s", mCfgMqtt->topic, subTopic); + snprintf(mTopic.data(), mTopic.size(), "%s/%s", mCfgMqtt->topic, subTopic); else - snprintf(mTopic.data(), mTopic.size() - 1, "%s", subTopic); + snprintf(mTopic.data(), mTopic.size(), "%s", subTopic); mClient.publish(mTopic.data(), qos, retained, payload); yield(); @@ -241,7 +241,7 @@ class PubMqtt { void setPowerLimitAck(Inverter<> *iv) { if (NULL != iv) { - snprintf(mSubTopic.data(), mSubTopic.size() - 1, "%s/%s", iv->config->name, subtopics[MQTT_ACK_PWR_LMT]); + snprintf(mSubTopic.data(), mSubTopic.size(), "%s/%s", iv->config->name, subtopics[MQTT_ACK_PWR_LMT]); publish(mSubTopic.data(), "true", true, true, QOS_2); } } @@ -261,11 +261,11 @@ class PubMqtt { publish(mLwtTopic.data(), mqttStr[MQTT_STR_LWT_CONN], true, false); for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i++) { - snprintf(mVal.data(), mVal.size() - 1, "ctrl/limit/%d", i); + snprintf(mVal.data(), mVal.size(), "ctrl/limit/%d", i); subscribe(mVal.data(), QOS_2); - snprintf(mVal.data(), mVal.size() - 1, "ctrl/restart/%d", i); + snprintf(mVal.data(), mVal.size(), "ctrl/restart/%d", i); subscribe(mVal.data()); - snprintf(mVal.data(), mVal.size() - 1, "ctrl/power/%d", i); + snprintf(mVal.data(), mVal.size(), "ctrl/power/%d", i); subscribe(mVal.data()); } subscribe(subscr[MQTT_SUBS_SET_TIME]); @@ -400,20 +400,20 @@ class PubMqtt { const char *devCls, *stateCls; if (!total) { if (rec->assign[mDiscovery.sub].ch == CH0) - snprintf(name.data(), name.size() - 1, "%s", iv->getFieldName(mDiscovery.sub, rec)); + snprintf(name.data(), name.size(), "%s", iv->getFieldName(mDiscovery.sub, rec)); else - snprintf(name.data(), name.size() - 1, "CH%d_%s", rec->assign[mDiscovery.sub].ch, iv->getFieldName(mDiscovery.sub, rec)); - snprintf(topic.data(), name.size() - 1, "/ch%d/%s", rec->assign[mDiscovery.sub].ch, iv->getFieldName(mDiscovery.sub, rec)); - snprintf(uniq_id.data(), uniq_id.size() - 1, "ch%d_%s", rec->assign[mDiscovery.sub].ch, iv->getFieldName(mDiscovery.sub, rec)); + snprintf(name.data(), name.size(), "CH%d_%s", rec->assign[mDiscovery.sub].ch, iv->getFieldName(mDiscovery.sub, rec)); + snprintf(topic.data(), name.size(), "/ch%d/%s", rec->assign[mDiscovery.sub].ch, iv->getFieldName(mDiscovery.sub, rec)); + snprintf(uniq_id.data(), uniq_id.size(), "ch%d_%s", rec->assign[mDiscovery.sub].ch, iv->getFieldName(mDiscovery.sub, rec)); devCls = getFieldDeviceClass(rec->assign[mDiscovery.sub].fieldId); stateCls = getFieldStateClass(rec->assign[mDiscovery.sub].fieldId); } else { // total values - snprintf(name.data(), name.size() - 1, "Total %s", fields[fldTotal[mDiscovery.sub]]); - snprintf(topic.data(), topic.size() - 1, "/%s", fields[fldTotal[mDiscovery.sub]]); - snprintf(uniq_id.data(), uniq_id.size() - 1, "total_%s", fields[fldTotal[mDiscovery.sub]]); + snprintf(name.data(), name.size(), "Total %s", fields[fldTotal[mDiscovery.sub]]); + snprintf(topic.data(), topic.size(), "/%s", fields[fldTotal[mDiscovery.sub]]); + snprintf(uniq_id.data(), uniq_id.size(), "total_%s", fields[fldTotal[mDiscovery.sub]]); devCls = getFieldDeviceClass(fldTotal[mDiscovery.sub]); stateCls = getFieldStateClass(fldTotal[mDiscovery.sub]); } @@ -433,9 +433,9 @@ class PubMqtt { doc2[F("stat_cla")] = String(stateCls); if (!total) - snprintf(topic.data(), topic.size() - 1, "%s/sensor/%s/ch%d_%s/config", MQTT_DISCOVERY_PREFIX, iv->config->name, rec->assign[mDiscovery.sub].ch, iv->getFieldName(mDiscovery.sub, rec)); + snprintf(topic.data(), topic.size(), "%s/sensor/%s/ch%d_%s/config", MQTT_DISCOVERY_PREFIX, iv->config->name, rec->assign[mDiscovery.sub].ch, iv->getFieldName(mDiscovery.sub, rec)); else // total values - snprintf(topic.data(), topic.size() - 1, "%s/sensor/%s/total_%s/config", MQTT_DISCOVERY_PREFIX, node_id.c_str(), fields[fldTotal[mDiscovery.sub]]); + snprintf(topic.data(), topic.size(), "%s/sensor/%s/total_%s/config", MQTT_DISCOVERY_PREFIX, node_id.c_str(), fields[fldTotal[mDiscovery.sub]]); size_t size = measureJson(doc2) + 1; buf.fill(0); serializeJson(doc2, buf.data(), size); @@ -509,14 +509,14 @@ class PubMqtt { mLastIvState[id] = status; changed = true; - snprintf(mSubTopic.data(), mSubTopic.size() - 1, "%s/available", iv->config->name); - snprintf(mVal.data(), mVal.size() - 1, "%d", (uint8_t)status); + snprintf(mSubTopic.data(), mSubTopic.size(), "%s/available", iv->config->name); + snprintf(mVal.data(), mVal.size(), "%d", (uint8_t)status); publish(mSubTopic.data(), mVal.data(), true); } } if(changed) { - snprintf(mVal.data(), mVal.size() - 1, "%d", ((allAvail) ? MQTT_STATUS_ONLINE : ((anyAvail) ? MQTT_STATUS_PARTIAL : MQTT_STATUS_OFFLINE))); + snprintf(mVal.data(), mVal.size(), "%d", ((allAvail) ? MQTT_STATUS_ONLINE : ((anyAvail) ? MQTT_STATUS_PARTIAL : MQTT_STATUS_OFFLINE))); publish("status", mVal.data(), true); } @@ -539,14 +539,14 @@ class PubMqtt { mSendAlarm[i] = false; - snprintf(mSubTopic.data(), mSubTopic.size() - 1, "%s/alarm/cnt", iv->config->name); - snprintf(mVal.data(), mVal.size() - 1, "%d", iv->alarmCnt); + snprintf(mSubTopic.data(), mSubTopic.size(), "%s/alarm/cnt", iv->config->name); + snprintf(mVal.data(), mVal.size(), "%d", iv->alarmCnt); publish(mSubTopic.data(), mVal.data(), false); for(uint8_t j = 0; j < 10; j++) { if(0 != iv->lastAlarm[j].code) { - snprintf(mSubTopic.data(), mSubTopic.size() - 1, "%s/alarm/%d", iv->config->name, j); - snprintf(mVal.data(), mVal.size() - 1, "{\"code\":%d,\"str\":\"%s\",\"start\":%d,\"end\":%d}", + snprintf(mSubTopic.data(), mSubTopic.size(), "%s/alarm/%d", iv->config->name, j); + snprintf(mVal.data(), mVal.size(), "{\"code\":%d,\"str\":\"%s\",\"start\":%d,\"end\":%d}", iv->lastAlarm[j].code, iv->getAlarmStr(iv->lastAlarm[j].code).c_str(), iv->lastAlarm[j].start + lastMidnight, @@ -581,8 +581,8 @@ class PubMqtt { } } - snprintf(mSubTopic.data(), mSubTopic.size() - 1, "%s/ch%d/%s", iv->config->name, rec->assign[i].ch, fields[rec->assign[i].fieldId]); - snprintf(mVal.data(), mVal.size() - 1, "%g", ah::round3(iv->getValue(i, rec))); + snprintf(mSubTopic.data(), mSubTopic.size(), "%s/ch%d/%s", iv->config->name, rec->assign[i].ch, fields[rec->assign[i].fieldId]); + snprintf(mVal.data(), mVal.size(), "%g", ah::round3(iv->getValue(i, rec))); publish(mSubTopic.data(), mVal.data(), retained); yield(); @@ -598,7 +598,7 @@ class PubMqtt { if(mSendList.empty()) return; - mSendIvData.start(); + SendIvData.start(); mLastAnyAvail = anyAvail; } @@ -609,7 +609,7 @@ class PubMqtt { #endif HMSYSTEM *mSys = nullptr; - PubMqttIvData mSendIvData; + PubMqttIvData SendIvData; uint32_t *mUtcTimestamp = nullptr, *mUptime = nullptr; uint32_t mRxCnt = 0, mTxCnt = 0; diff --git a/src/publisher/pubMqttIvData.h b/src/publisher/pubMqttIvData.h index 4405d037..5f38768d 100644 --- a/src/publisher/pubMqttIvData.h +++ b/src/publisher/pubMqttIvData.h @@ -1,4 +1,4 @@ -//----------------------------------------------------------------------------- + //----------------------------------------------------------------------------- // 2024 Ahoy, https://ahoydtu.de // Creative Commons - https://creativecommons.org/licenses/by-nc-sa/4.0/deed //----------------------------------------------------------------------------- @@ -21,6 +21,8 @@ struct sendListCmdIv { template class PubMqttIvData { public: + PubMqttIvData() : mTotal{}, mSubTopic{}, mVal{} {} + void setup(HMSYSTEM *sys, uint32_t *utcTs, std::queue *sendList) { mSys = sys; mUtcTimestamp = utcTs; @@ -249,25 +251,25 @@ class PubMqttIvData { } } - HMSYSTEM *mSys; - uint32_t *mUtcTimestamp; + HMSYSTEM *mSys = nullptr; + uint32_t *mUtcTimestamp = nullptr; pubMqttPublisherType mPublish; - State mState; + State mState = IDLE; StateFunction mTable[NUM_STATES]; - uint8_t mCmd; - uint8_t mLastIvId; - bool mSendTotals, mTotalFound, mAllTotalFound, mSendTotalYd; - float mTotal[5], mYldTotalStore; + uint8_t mCmd = 0; + uint8_t mLastIvId = 0; + bool mSendTotals = false, mTotalFound = false, mAllTotalFound = false, mSendTotalYd = false; + float mTotal[5], mYldTotalStore = 0; - Inverter<> *mIv, *mIvSend; - uint8_t mPos; - bool mRTRDataHasBeenSent; + Inverter<> *mIv = nullptr, *mIvSend = nullptr; + uint8_t mPos = 0; + bool mRTRDataHasBeenSent = false; char mSubTopic[32 + MAX_NAME_LENGTH + 1]; char mVal[140]; - std::queue *mSendList; + std::queue *mSendList = nullptr; }; #endif /*__PUB_MQTT_IV_DATA_H__*/ diff --git a/src/utils/improv.h b/src/utils/improv.h index 32bc403c..20b2bcad 100644 --- a/src/utils/improv.h +++ b/src/utils/improv.h @@ -71,7 +71,7 @@ class Improv { TYPE_RPC_RESPONSE = 0x04 }; - void dumpBuf(uint8_t buf[], uint8_t len) { + void dumpBuf(const uint8_t buf[], uint8_t len) { for(uint8_t i = 0; i < len; i++) { DHEX(buf[i]); DBGPRINT(F(" ")); diff --git a/src/utils/syslog.cpp b/src/utils/syslog.cpp index 9c4bf93b..c251e899 100644 --- a/src/utils/syslog.cpp +++ b/src/utils/syslog.cpp @@ -10,6 +10,7 @@ #define SYSLOG_MAX_PACKET_SIZE 256 +DbgSyslog::DbgSyslog() : mSyslogBuffer{} {} //----------------------------------------------------------------------------- void DbgSyslog::setup(settings_t *config) { diff --git a/src/utils/syslog.h b/src/utils/syslog.h index 37bdc524..ec7f4f6e 100644 --- a/src/utils/syslog.h +++ b/src/utils/syslog.h @@ -36,6 +36,7 @@ class DbgSyslog { public: + DbgSyslog(); void setup (settings_t *config); void syslogCb(String msg); void log(const char *hostname, uint8_t facility, uint8_t severity, char* msg); @@ -43,7 +44,7 @@ class DbgSyslog { private: WiFiUDP mSyslogUdp; IPAddress mSyslogIP; - settings_t *mConfig; + settings_t *mConfig = nullptr; char mSyslogBuffer[SYSLOG_BUF_SIZE+1]; uint16_t mSyslogBufFill = 0; int mSyslogSeverity = PRI_NOTICE; @@ -51,4 +52,4 @@ class DbgSyslog { #endif /*ENABLE_SYSLOG*/ -#endif /*__SYSLOG_H__*/ \ No newline at end of file +#endif /*__SYSLOG_H__*/ diff --git a/src/web/Protection.h b/src/web/Protection.h index 0c7b8379..6b079e82 100644 --- a/src/web/Protection.h +++ b/src/web/Protection.h @@ -15,7 +15,7 @@ class Protection { protected: - Protection(const char *pwd) { + explicit Protection(const char *pwd) { mPwd = pwd; mLogoutTimeout = 0; mLoginIp.fill(0); diff --git a/src/web/web.h b/src/web/web.h index c40c817c..a85e4b0a 100644 --- a/src/web/web.h +++ b/src/web/web.h @@ -684,7 +684,6 @@ class Web { char type[60], topic[100], val[25]; size_t len = 0; int alarmChannelId; - int metricsChannelId; // Perform grouping on metrics according to format specification // Each step must return at least one character. Otherwise the processing of AsyncWebServerResponse stops. @@ -766,7 +765,7 @@ class Web { iv = mSys->getInverterByPos(metricsInverterId); if (NULL != iv) { rec = iv->getRecordStruct(RealTimeRunData_Debug); - for (metricsChannelId=0; metricsChannelId < rec->length;metricsChannelId++) { + for (int metricsChannelId=0; metricsChannelId < rec->length;metricsChannelId++) { uint8_t channel = rec->assign[metricsChannelId].ch; // Try inverter channel (channel 0) or any channel with maxPwr > 0 @@ -786,14 +785,14 @@ class Web { char total[7]; if (metricDeclared) { // A declaration and value for channels have been delivered. So declare and deliver a _total metric - snprintf(total, sizeof(total)-1, "_total"); + snprintf(total, sizeof(total), "_total"); } if (!metricTotalDeclard) { - snprintf(type, sizeof(type)-1, "# TYPE %s%s%s%s %s\n",metricConstPrefix, iv->getFieldName(metricsChannelId, rec), promUnit.c_str(), total, promType.c_str()); + snprintf(type, sizeof(type), "# TYPE %s%s%s%s %s\n",metricConstPrefix, iv->getFieldName(metricsChannelId, rec), promUnit.c_str(), total, promType.c_str()); metrics += type; metricTotalDeclard = true; } - snprintf(topic, sizeof(topic)-1, "%s%s%s%s{inverter=\"%s\"}",metricConstPrefix, iv->getFieldName(metricsChannelId, rec), promUnit.c_str(), total,iv->config->name); + snprintf(topic, sizeof(topic), "%s%s%s%s{inverter=\"%s\"}",metricConstPrefix, iv->getFieldName(metricsChannelId, rec), promUnit.c_str(), total,iv->config->name); } else { // Report (non zero) channel value // Use a fallback channel name (ch0, ch1, ...)if non is given by user @@ -801,11 +800,11 @@ class Web { if (iv->config->chName[channel-1][0] != 0) { strncpy(chName, iv->config->chName[channel-1], sizeof(chName)); } else { - snprintf(chName,sizeof(chName)-1,"ch%1d",channel); + snprintf(chName,sizeof(chName),"ch%1d",channel); } - snprintf(topic, sizeof(topic)-1, "%s%s%s{inverter=\"%s\",channel=\"%s\"}",metricConstPrefix, iv->getFieldName(metricsChannelId, rec), promUnit.c_str(), iv->config->name,chName); + snprintf(topic, sizeof(topic), "%s%s%s{inverter=\"%s\",channel=\"%s\"}",metricConstPrefix, iv->getFieldName(metricsChannelId, rec), promUnit.c_str(), iv->config->name,chName); } - snprintf(val, sizeof(val)-1, " %.3f\n", iv->getValue(metricsChannelId, rec)); + snprintf(val, sizeof(val), " %.3f\n", iv->getValue(metricsChannelId, rec)); metrics += topic; metrics += val; } @@ -835,7 +834,7 @@ class Web { case metricsStateAlarmData: // Alarm Info loop : fit to one packet // Perform grouping on metrics according to Prometheus exposition format specification - snprintf(type, sizeof(type)-1,"# TYPE %s%s gauge\n",metricConstPrefix,fields[FLD_LAST_ALARM_CODE]); + snprintf(type, sizeof(type),"# TYPE %s%s gauge\n",metricConstPrefix,fields[FLD_LAST_ALARM_CODE]); metrics = type; for (metricsInverterId = 0; metricsInverterId < mSys->getNumInverters();metricsInverterId++) { @@ -847,8 +846,8 @@ class Web { alarmChannelId = 0; if (alarmChannelId < rec->length) { std::tie(promUnit, promType) = convertToPromUnits(iv->getUnit(alarmChannelId, rec)); - snprintf(topic, sizeof(topic)-1, "%s%s%s{inverter=\"%s\"}",metricConstPrefix, iv->getFieldName(alarmChannelId, rec), promUnit.c_str(), iv->config->name); - snprintf(val, sizeof(val)-1, " %.3f\n", iv->getValue(alarmChannelId, rec)); + snprintf(topic, sizeof(topic), "%s%s%s{inverter=\"%s\"}",metricConstPrefix, iv->getFieldName(alarmChannelId, rec), promUnit.c_str(), iv->config->name); + snprintf(val, sizeof(val), " %.3f\n", iv->getValue(alarmChannelId, rec)); metrics += topic; metrics += val; } @@ -874,8 +873,8 @@ class Web { // Traverse all inverters and collect the metric via valueFunc String inverterMetric(char *buffer, size_t len, const char *format, std::function *iv)> valueFunc) { String metric = ""; - for (int metricsInverterId = 0; metricsInverterId < mSys->getNumInverters();metricsInverterId++) { - Inverter<> *iv = mSys->getInverterByPos(metricsInverterId); + for (int id = 0; id < mSys->getNumInverters();id++) { + Inverter<> *iv = mSys->getInverterByPos(id); if (NULL != iv) { snprintf(buffer,len,format,iv->config->name, valueFunc(iv)); metric += String(buffer); From d4a4f9cfb6616135d7cdb8a1c6954aa44d102f44 Mon Sep 17 00:00:00 2001 From: tictrick <117273857+tictrick@users.noreply.github.com> Date: Thu, 8 Feb 2024 08:38:11 +0100 Subject: [PATCH 158/179] BugFix: ACK MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Die Funktion startWrite() der RF24 Bibliothek ist ungeeignet für den Empfang von ACKs, da der Verstärker während des Sendens statt in den RX-Modus Ausgeschaltet wird. --- src/hm/hmRadio.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hm/hmRadio.h b/src/hm/hmRadio.h index df6a18b4..09500826 100644 --- a/src/hm/hmRadio.h +++ b/src/hm/hmRadio.h @@ -415,7 +415,7 @@ class HmRadio : public Radio { } mNrf24->setChannel(mRfChLst[mTxChIdx]); mNrf24->openWritingPipe(reinterpret_cast(&iv->radioId.u64)); - mNrf24->startWrite(mTxBuf.data(), len, false); // false = request ACK response + mNrf24->startFastWrite(mTxBuf.data(), len, false); // false = request ACK response mMillis = millis(); mLastIv = iv; From 15349520d26538cee0dba501ea69b54e7fe8a66f Mon Sep 17 00:00:00 2001 From: lumapu Date: Fri, 9 Feb 2024 00:05:54 +0100 Subject: [PATCH 159/179] 0.8.77 * merge PR: BugFix: ACK #1414 * fix suspicious if condition #1416 * prepared API token for access, not functional #1415 --- src/CHANGES.md | 5 ++++ src/app.cpp | 2 +- src/app.h | 8 +++++-- src/appInterface.h | 3 ++- src/defines.h | 2 +- src/web/Protection.h | 34 ++++++++++++++++++++++++--- src/web/RestApi.h | 55 ++++++++++++++++++++++++++++++++++---------- src/web/lang.h | 10 ++++++-- src/web/web.h | 2 +- 9 files changed, 98 insertions(+), 23 deletions(-) diff --git a/src/CHANGES.md b/src/CHANGES.md index a9301d44..b75ba2b3 100644 --- a/src/CHANGES.md +++ b/src/CHANGES.md @@ -1,5 +1,10 @@ # Development Changes +## 0.8.77 - 2024-02-08 +* merge PR: BugFix: ACK #1414 +* fix suspicious if condition #1416 +* prepared API token for access, not functional #1415 + ## 0.8.76 - 2024-02-07 * revert changes from yesterday regarding snprintf and its size #1410, #1411 * reduced cppcheck linter warnings significantly diff --git a/src/app.cpp b/src/app.cpp index c93be777..bee12387 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -239,7 +239,7 @@ void app::updateNtp(void) { } } - if ((mSunrise == 0) && (mConfig->sun.lat) && (mConfig->sun.lon)) { + if ((0 == mSunrise) && (0.0 != mConfig->sun.lat) && (0.0 != mConfig->sun.lon)) { mCalculatedTimezoneOffset = (int8_t)((mConfig->sun.lon >= 0 ? mConfig->sun.lon + 7.5 : mConfig->sun.lon - 7.5) / 15) * 3600; tickCalcSunrise(); } diff --git a/src/app.h b/src/app.h index 60be4d1d..be20f26f 100644 --- a/src/app.h +++ b/src/app.h @@ -251,8 +251,8 @@ class app : public IApp, public ah::Scheduler { mProtection->lock(); } - void unlock(const char *clientIp) override { - mProtection->unlock(clientIp); + char *unlock(const char *clientIp) override { + return mProtection->unlock(clientIp); } void resetLockTimeout(void) override { @@ -267,6 +267,10 @@ class app : public IApp, public ah::Scheduler { return mProtection->isProtected(clientIp); } + bool isProtected(const char *clientIp, const char *token) const override { + return mProtection->isProtected(clientIp, token); + } + bool getNrfEnabled(void) override { return mConfig->nrf.enabled; } diff --git a/src/appInterface.h b/src/appInterface.h index 6703b5bf..937908e2 100644 --- a/src/appInterface.h +++ b/src/appInterface.h @@ -62,10 +62,11 @@ class IApp { virtual uint32_t getMqttTxCnt() = 0; virtual void lock(void) = 0; - virtual void unlock(const char *clientIp) = 0; + virtual char *unlock(const char *clientIp) = 0; virtual void resetLockTimeout(void) = 0; virtual bool isProtected(void) const = 0; virtual bool isProtected(const char *clientIp) const = 0; + virtual bool isProtected(const char *clientIp, const char *token) const = 0; virtual uint16_t getHistoryValue(uint8_t type, uint16_t i) = 0; virtual uint16_t getHistoryMaxDay() = 0; diff --git a/src/defines.h b/src/defines.h index 9ea3834c..46be8dac 100644 --- a/src/defines.h +++ b/src/defines.h @@ -13,7 +13,7 @@ //------------------------------------- #define VERSION_MAJOR 0 #define VERSION_MINOR 8 -#define VERSION_PATCH 76 +#define VERSION_PATCH 77 //------------------------------------- typedef struct { diff --git a/src/web/Protection.h b/src/web/Protection.h index 6b079e82..f62f1380 100644 --- a/src/web/Protection.h +++ b/src/web/Protection.h @@ -19,6 +19,7 @@ class Protection { mPwd = pwd; mLogoutTimeout = 0; mLoginIp.fill(0); + mToken.fill(0); // no password set - unlock if(pwd[0] == '\0') @@ -50,20 +51,34 @@ class Protection { mLoginIp.fill(0); } - void unlock(const char *clientIp) { + char *unlock(const char *clientIp) { mLogoutTimeout = LOGOUT_TIMEOUT; mProtected = false; ah::ip2Arr(static_cast(mLoginIp.data()), clientIp); + genToken(); + + return reinterpret_cast(mToken.data()); } void resetLockTimeout(void) { - mLogoutTimeout = LOGOUT_TIMEOUT; + if(0 != mLogoutTimeout) + mLogoutTimeout = LOGOUT_TIMEOUT; } bool isProtected(void) const { return mProtected; } + bool isProtected(const char *clientIp, const char *token) const { + if(isProtected(clientIp)) + return true; + + if(0 == mToken[0]) // token is zero + return true; + + return (0 != strncmp(token, mToken.data(), 16)); + } + bool isProtected(const char *clientIp) const { if(mProtected) return true; @@ -81,14 +96,27 @@ class Protection { return false; } + private: + void genToken() { + mToken.fill(0); + for(uint8_t i = 0; i < 16; i++) { + mToken[i] = random(1, 35); + if(mToken[i] < 10) + mToken[i] += 0x30; // convert to ascii number 1-9 (zero isn't allowed) + else + mToken[i] += 0x37; // convert to ascii upper case character + } + } + protected: static Protection *mInstance; private: const char *mPwd; bool mProtected = true; - uint16_t mLogoutTimeout = LOGOUT_TIMEOUT; + uint16_t mLogoutTimeout = 0; std::array mLoginIp; + std::array mToken; }; #endif /*__PROTECTION_H__*/ diff --git a/src/web/RestApi.h b/src/web/RestApi.h index ce580dd5..59b786d7 100644 --- a/src/web/RestApi.h +++ b/src/web/RestApi.h @@ -70,7 +70,7 @@ class RestApi { if(obj[F("path")] == "ctrl") setCtrl(obj, dummy, "*"); else if(obj[F("path")] == "setup") - setSetup(obj, dummy); + setSetup(obj, dummy, "*"); } private: @@ -169,7 +169,7 @@ class RestApi { if(path == "ctrl") root[F("success")] = setCtrl(obj, root, request->client()->remoteIP().toString().c_str()); else if(path == "setup") - root[F("success")] = setSetup(obj, root); + root[F("success")] = setSetup(obj, root, request->client()->remoteIP().toString().c_str()); else { root[F("success")] = false; root[F("error")] = F(PATH_NOT_FOUND) + path; @@ -831,23 +831,44 @@ class RestApi { } bool setCtrl(JsonObject jsonIn, JsonObject jsonOut, const char *clientIP) { - Inverter<> *iv = mSys->getInverterByPos(jsonIn[F("id")]); - bool accepted = true; - if(NULL == iv) { - jsonOut[F("error")] = F(INV_INDEX_INVALID) + jsonIn[F("id")].as(); - return false; + if(F("auth") == jsonIn[F("cmd")]) { + if(String(jsonIn["val"]) == String(mConfig->sys.adminPwd)) + jsonOut["token"] = mApp->unlock(clientIP); + else { + jsonOut[F("error")] = F(AUTH_ERROR); + return false; + } + return true; } - jsonOut[F("id")] = jsonIn[F("id")]; - if(mConfig->sys.adminPwd[0] != '\0') { - if(strncmp("*", clientIP, 1) != 0) { // no call from API (MqTT) + /*if(mConfig->sys.adminPwd[0] != '\0') { // check if admin password is set + if(strncmp("*", clientIP, 1) != 0) { // no call from MqTT + const char* token = jsonIn["token"]; + if(mApp->isProtected(clientIP, token)) { + jsonOut[F("error")] = F(IS_PROTECTED); + jsonOut[F("bla")] = String(token); + return false; + } + } + }*/ + + if(mConfig->sys.adminPwd[0] != '\0') { // check if admin password is set + if(strncmp("*", clientIP, 1) != 0) { // no call from MqTT if(mApp->isProtected(clientIP)) { - jsonOut[F("error")] = F(INV_IS_PROTECTED); + jsonOut[F("error")] = F(IS_PROTECTED); return false; } } } + Inverter<> *iv = mSys->getInverterByPos(jsonIn[F("id")]); + bool accepted = true; + if(NULL == iv) { + jsonOut[F("error")] = F(INV_INDEX_INVALID) + jsonIn[F("id")].as(); + return false; + } + jsonOut[F("id")] = jsonIn[F("id")]; + if(F("power") == jsonIn[F("cmd")]) accepted = iv->setDevControlRequest((jsonIn[F("val")] == 1) ? TurnOn : TurnOff); else if(F("restart") == jsonIn[F("cmd")]) @@ -882,7 +903,17 @@ class RestApi { return true; } - bool setSetup(JsonObject jsonIn, JsonObject jsonOut) { + bool setSetup(JsonObject jsonIn, JsonObject jsonOut, const char *clientIP) { + /*if(mConfig->sys.adminPwd[0] != '\0') { // check if admin password is set + if(strncmp("*", clientIP, 1) != 0) { // no call from MqTT + const char* token = jsonIn["token"]; + if(mApp->isProtected(clientIP, token)) { + jsonOut[F("error")] = F(IS_PROTECTED); + return false; + } + } + }*/ + #if !defined(ETHERNET) if(F("scan_wifi") == jsonIn[F("cmd")]) mApp->scanAvailNetworks(); diff --git a/src/web/lang.h b/src/web/lang.h index 6cddbc12..1e066928 100644 --- a/src/web/lang.h +++ b/src/web/lang.h @@ -30,6 +30,12 @@ #define INV_INDEX_INVALID "inverter index invalid: " #endif +#ifdef LANG_DE + #define AUTH_ERROR "Authentifizierungsfehler" +#else /*LANG_EN*/ + #define AUTH_ERROR "authentication error" +#endif + #ifdef LANG_DE #define UNKNOWN_CMD "unbekanntes Kommando: '" #else /*LANG_EN*/ @@ -37,9 +43,9 @@ #endif #ifdef LANG_DE - #define INV_IS_PROTECTED "nicht angemeldet, Kommando nicht möglich!" + #define IS_PROTECTED "nicht angemeldet, Kommando nicht möglich!" #else /*LANG_EN*/ - #define INV_IS_PROTECTED "not logged in, command not possible!" + #define IS_PROTECTED "not logged in, command not possible!" #endif #ifdef LANG_DE diff --git a/src/web/web.h b/src/web/web.h index a85e4b0a..76503f0b 100644 --- a/src/web/web.h +++ b/src/web/web.h @@ -782,7 +782,7 @@ class Web { // report value if (0 == channel) { // Report a _total value if also channel values were reported. Otherwise report without _total - char total[7]; + char total[7] = {0}; if (metricDeclared) { // A declaration and value for channels have been delivered. So declare and deliver a _total metric snprintf(total, sizeof(total), "_total"); From 8b2db4abfa24a9a6d596661860a4bd9dd3e52502 Mon Sep 17 00:00:00 2001 From: lumapu Date: Fri, 9 Feb 2024 00:25:25 +0100 Subject: [PATCH 160/179] 0.8.78 * try to finalize API token protection --- src/CHANGES.md | 3 +++ src/app.h | 12 ++++-------- src/appInterface.h | 5 ++--- src/web/Protection.h | 40 +++++++++++++++++++++------------------- src/web/RestApi.h | 24 +++++++----------------- src/web/web.h | 4 ++-- 6 files changed, 39 insertions(+), 49 deletions(-) diff --git a/src/CHANGES.md b/src/CHANGES.md index b75ba2b3..ea067bd2 100644 --- a/src/CHANGES.md +++ b/src/CHANGES.md @@ -1,5 +1,8 @@ # Development Changes +## 0.8.78 - 2024-02-09 +* finalized API token access #1415 + ## 0.8.77 - 2024-02-08 * merge PR: BugFix: ACK #1414 * fix suspicious if condition #1416 diff --git a/src/app.h b/src/app.h index be20f26f..1a4e780d 100644 --- a/src/app.h +++ b/src/app.h @@ -251,8 +251,8 @@ class app : public IApp, public ah::Scheduler { mProtection->lock(); } - char *unlock(const char *clientIp) override { - return mProtection->unlock(clientIp); + char *unlock(const char *clientIp, bool loginFromWeb) override { + return mProtection->unlock(clientIp, loginFromWeb); } void resetLockTimeout(void) override { @@ -263,12 +263,8 @@ class app : public IApp, public ah::Scheduler { return mProtection->isProtected(); } - bool isProtected(const char *clientIp) const override { - return mProtection->isProtected(clientIp); - } - - bool isProtected(const char *clientIp, const char *token) const override { - return mProtection->isProtected(clientIp, token); + bool isProtected(const char *token, bool askedFromWeb) const override { + return mProtection->isProtected(token, askedFromWeb); } bool getNrfEnabled(void) override { diff --git a/src/appInterface.h b/src/appInterface.h index 937908e2..f0d2b2a0 100644 --- a/src/appInterface.h +++ b/src/appInterface.h @@ -62,11 +62,10 @@ class IApp { virtual uint32_t getMqttTxCnt() = 0; virtual void lock(void) = 0; - virtual char *unlock(const char *clientIp) = 0; + virtual char *unlock(const char *clientIp, bool loginFromWeb) = 0; virtual void resetLockTimeout(void) = 0; virtual bool isProtected(void) const = 0; - virtual bool isProtected(const char *clientIp) const = 0; - virtual bool isProtected(const char *clientIp, const char *token) const = 0; + virtual bool isProtected(const char *token, bool askedFromWeb) const = 0; virtual uint16_t getHistoryValue(uint8_t type, uint16_t i) = 0; virtual uint16_t getHistoryMaxDay() = 0; diff --git a/src/web/Protection.h b/src/web/Protection.h index f62f1380..b9eeef95 100644 --- a/src/web/Protection.h +++ b/src/web/Protection.h @@ -40,8 +40,9 @@ class Protection { // auto logout if(0 != mLogoutTimeout) { if (0 == --mLogoutTimeout) { - if(mPwd[0] != '\0') + if(mPwd[0] != '\0') { mProtected = true; + } } } } @@ -49,13 +50,17 @@ class Protection { void lock(void) { mProtected = true; mLoginIp.fill(0); + mToken.fill(0); } - char *unlock(const char *clientIp) { + char *unlock(const char *clientIp, bool loginFromWeb) { mLogoutTimeout = LOGOUT_TIMEOUT; mProtected = false; - ah::ip2Arr(static_cast(mLoginIp.data()), clientIp); - genToken(); + + if(loginFromWeb) + ah::ip2Arr(static_cast(mLoginIp.data()), clientIp); + else + genToken(); return reinterpret_cast(mToken.data()); } @@ -69,28 +74,25 @@ class Protection { return mProtected; } - bool isProtected(const char *clientIp, const char *token) const { - if(isProtected(clientIp)) - return true; - - if(0 == mToken[0]) // token is zero - return true; - - return (0 != strncmp(token, mToken.data(), 16)); - } - - bool isProtected(const char *clientIp) const { + bool isProtected(const char *token, bool askedFromWeb) const { // token == clientIp if(mProtected) return true; if(mPwd[0] == '\0') return false; - std::array ip; - ah::ip2Arr(static_cast(ip.data()), clientIp); - for(uint8_t i = 0; i < 4; i++) { - if(mLoginIp[i] != ip[i]) + if(askedFromWeb) { // check IP address + std::array ip; + ah::ip2Arr(static_cast(ip.data()), token); + for(uint8_t i = 0; i < 4; i++) { + if(mLoginIp[i] != ip[i]) + return true; + } + } else { // API call - check token + if(0 == mToken[0]) // token is zero return true; + + return (0 != strncmp(token, mToken.data(), 16)); } return false; diff --git a/src/web/RestApi.h b/src/web/RestApi.h index 59b786d7..5103f3ba 100644 --- a/src/web/RestApi.h +++ b/src/web/RestApi.h @@ -266,7 +266,7 @@ class RestApi { obj[F("modules")] = String(mApp->getVersionModules()); obj[F("build")] = String(AUTO_GIT_HASH); obj[F("env")] = String(ENV_NAME); - obj[F("menu_prot")] = mApp->isProtected(request->client()->remoteIP().toString().c_str()); + obj[F("menu_prot")] = mApp->isProtected(request->client()->remoteIP().toString().c_str(), true); obj[F("menu_mask")] = (uint16_t)(mConfig->sys.protectionMask ); obj[F("menu_protEn")] = (bool) (mConfig->sys.adminPwd[0] != '\0'); obj[F("cst_lnk")] = String(mConfig->plugin.customLink); @@ -833,7 +833,7 @@ class RestApi { bool setCtrl(JsonObject jsonIn, JsonObject jsonOut, const char *clientIP) { if(F("auth") == jsonIn[F("cmd")]) { if(String(jsonIn["val"]) == String(mConfig->sys.adminPwd)) - jsonOut["token"] = mApp->unlock(clientIP); + jsonOut["token"] = mApp->unlock(clientIP, false); else { jsonOut[F("error")] = F(AUTH_ERROR); return false; @@ -841,20 +841,10 @@ class RestApi { return true; } - /*if(mConfig->sys.adminPwd[0] != '\0') { // check if admin password is set - if(strncmp("*", clientIP, 1) != 0) { // no call from MqTT - const char* token = jsonIn["token"]; - if(mApp->isProtected(clientIP, token)) { - jsonOut[F("error")] = F(IS_PROTECTED); - jsonOut[F("bla")] = String(token); - return false; - } - } - }*/ - if(mConfig->sys.adminPwd[0] != '\0') { // check if admin password is set if(strncmp("*", clientIP, 1) != 0) { // no call from MqTT - if(mApp->isProtected(clientIP)) { + const char* token = jsonIn["token"]; + if(mApp->isProtected(token, false)) { jsonOut[F("error")] = F(IS_PROTECTED); return false; } @@ -904,15 +894,15 @@ class RestApi { } bool setSetup(JsonObject jsonIn, JsonObject jsonOut, const char *clientIP) { - /*if(mConfig->sys.adminPwd[0] != '\0') { // check if admin password is set + if(mConfig->sys.adminPwd[0] != '\0') { // check if admin password is set if(strncmp("*", clientIP, 1) != 0) { // no call from MqTT const char* token = jsonIn["token"]; - if(mApp->isProtected(clientIP, token)) { + if(mApp->isProtected(token, false)) { jsonOut[F("error")] = F(IS_PROTECTED); return false; } } - }*/ + } #if !defined(ETHERNET) if(F("scan_wifi") == jsonIn[F("cmd")]) diff --git a/src/web/web.h b/src/web/web.h index 76503f0b..7ed41f92 100644 --- a/src/web/web.h +++ b/src/web/web.h @@ -227,7 +227,7 @@ class Web { } void checkProtection(AsyncWebServerRequest *request) { - if(mApp->isProtected(request->client()->remoteIP().toString().c_str())) { + if(mApp->isProtected(request->client()->remoteIP().toString().c_str(), true)) { checkRedirect(request); return; } @@ -314,7 +314,7 @@ class Web { if (request->args() > 0) { if (String(request->arg("pwd")) == String(mConfig->sys.adminPwd)) { - mApp->unlock(request->client()->remoteIP().toString().c_str()); + mApp->unlock(request->client()->remoteIP().toString().c_str(), true); request->redirect("/"); } } From d15e75dbabe222f206bf16b6d29b5f3d1e27c1aa Mon Sep 17 00:00:00 2001 From: rejoe2 Date: Fri, 9 Feb 2024 11:53:20 +0100 Subject: [PATCH 161/179] simplify rxOffset logic - Code cleanup - fix "nRF CE keep high" code (missing argument) --- src/hm/Communication.h | 91 +++++++++++++++++++++--------------------- src/hm/hmDefines.h | 2 +- src/hm/hmInverter.h | 1 - src/hm/hmRadio.h | 42 ++++--------------- src/hm/hmSystem.h | 10 +---- 5 files changed, 55 insertions(+), 91 deletions(-) diff --git a/src/hm/Communication.h b/src/hm/Communication.h index 21b78405..a37bcdb2 100644 --- a/src/hm/Communication.h +++ b/src/hm/Communication.h @@ -13,7 +13,7 @@ #include "../utils/timemonitor.h" #include "Heuristic.h" -#define MAX_BUFFER 250 +#define MAX_BUFFER 200 typedef std::function *)> payloadListenerType; typedef std::function *)> powerLimitAckListenerType; @@ -92,6 +92,8 @@ class Communication : public CommQueue<> { q->iv->mIsSingleframeReq = false; mFramesExpected = getFramesExpected(q); // function to get expected frame count. mTimeout = DURATION_TXFRAME + mFramesExpected*DURATION_ONEFRAME + duration_reserve[q->iv->ivRadioType]; + if((q->iv->ivGen == IV_MI) && ((q->cmd == MI_REQ_CH1) || (q->cmd == MI_REQ_4CH))) + incrAttempt(q->iv->channels); // 2 more attempts for 2ch, 4 more for 4ch mState = States::START; break; @@ -209,7 +211,7 @@ class Communication : public CommQueue<> { } else { if(q->iv->miMultiParts < 6) { mState = States::WAIT; - if((q->iv->radio->mRadioWaitTime.isTimeout() && mIsRetransmit) || !mIsRetransmit) { + if(q->iv->radio->mRadioWaitTime.isTimeout() && q->attempts) { miRepeatRequest(q); return; } @@ -220,6 +222,10 @@ class Communication : public CommQueue<> { || ((q->cmd == MI_REQ_CH1) && (q->iv->type == INV_TYPE_1CH))) { miComplete(q->iv); } + if(*mSerialDebug) { + DPRINT_IVID(DBG_INFO, q->iv->id); + DBGPRINTLN(F("Payload (MI got all)")); + } closeRequest(q, true); } } @@ -443,9 +449,8 @@ class Communication : public CommQueue<> { || (p->packet[0] == MI_REQ_CH2 + ALL_FRAMES) || ((p->packet[0] >= (MI_REQ_4CH + ALL_FRAMES)) && (p->packet[0] < (0x39 + SINGLE_FRAME)) - )) { //&& (p->packet[0] != (0x0f + ALL_FRAMES)))) { + )) { // small MI or MI 1500 data responses to 0x09, 0x11, 0x36, 0x37, 0x38 and 0x39 - //mPayload[iv->id].txId = p->packet[0]; miDataDecode(p, q); } else if (p->packet[0] == (0x0f + ALL_FRAMES)) { miHwDecode(p, q); @@ -541,14 +546,16 @@ class Communication : public CommQueue<> { len -= 2; - DPRINT_IVID(DBG_INFO, q->iv->id); - DBGPRINT(F("Payload (")); - DBGPRINT(String(len)); - if(*mPrintWholeTrace) { - DBGPRINT(F("): ")); - ah::dumpBuf(mPayload.data(), len); - } else - DBGPRINTLN(F(")")); + if(*mSerialDebug) { + DPRINT_IVID(DBG_INFO, q->iv->id); + DBGPRINT(F("Payload (")); + DBGPRINT(String(len)); + if(*mPrintWholeTrace) { + DBGPRINT(F("): ")); + ah::dumpBuf(mPayload.data(), len); + } else + DBGPRINTLN(F(")")); + } if(GridOnProFilePara == q->cmd) { q->iv->addGridProfile(mPayload.data(), len); @@ -813,35 +820,21 @@ class Communication : public CommQueue<> { miStsConsolidate(q, datachan, rec, p->packet[23], p->packet[24]); if (p->packet[0] < (0x39 + ALL_FRAMES) ) { - mHeu.evalTxChQuality(q->iv, true, (q->attemptsMax - 1 - q->attempts), 1); miNextRequest((p->packet[0] - ALL_FRAMES + 1), q); } else { q->iv->miMultiParts = 7; // indicate we are ready } } else if((p->packet[0] == (MI_REQ_CH1 + ALL_FRAMES)) && (q->iv->type == INV_TYPE_2CH)) { - //addImportant(q->iv, MI_REQ_CH2); miNextRequest(MI_REQ_CH2, q); - mHeu.evalTxChQuality(q->iv, true, (q->attemptsMax - 1 - q->attempts), q->iv->curFrmCnt); - q->iv->mIvRxCnt++; // statistics workaround... + q->iv->mIvRxCnt++; // statistics workaround... - } else { // first data msg for 1ch, 2nd for 2ch + } else // first data msg for 1ch, 2nd for 2ch q->iv->miMultiParts += 6; // indicate we are ready - - } } void miNextRequest(uint8_t cmd, const queue_s *q) { - incrAttempt(); // if function is called, we got something, and we necessarily need more transmissions for MI types... - if(*mSerialDebug) { - DPRINT_IVID(DBG_WARN, q->iv->id); - DBGPRINT(F("next request (")); - DBGPRINT(String(q->attempts)); - DBGPRINT(F(" attempts left): 0x")); - DBGHEXLN(cmd); - } - - /*if(q->iv->miMultiParts > 5) //if(q->iv->miMultiParts == 7) - q->iv->radioStatistics.rxSuccess++;*/ + mHeu.evalTxChQuality(q->iv, true, (q->attemptsMax - 1 - q->attempts), q->iv->curFrmCnt); + mHeu.getTxCh(q->iv); q->iv->radioStatistics.ivSent++; mFramesExpected = getFramesExpected(q); @@ -851,6 +844,13 @@ class Communication : public CommQueue<> { q->iv->radio->mRadioWaitTime.startTimeMonitor(DURATION_TXFRAME + DURATION_ONEFRAME + duration_reserve[q->iv->ivRadioType]); q->iv->miMultiParts = 0; q->iv->mGotFragment = 0; + if(*mSerialDebug) { + DPRINT_IVID(DBG_INFO, q->iv->id); + DBGPRINT(F("next: (")); + DBGPRINT(String(q->attempts)); + DBGPRINT(F(" attempts left): 0x")); + DBGHEXLN(cmd); + } mIsRetransmit = true; chgCmd(cmd); //mState = States::WAIT; @@ -858,19 +858,17 @@ class Communication : public CommQueue<> { void miRepeatRequest(const queue_s *q) { setAttempt(); // if function is called, we got something, and we necessarily need more transmissions for MI types... + q->iv->radio->sendCmdPacket(q->iv, q->cmd, 0x00, true); + q->iv->radioStatistics.retransmits++; + q->iv->radio->mRadioWaitTime.startTimeMonitor(DURATION_TXFRAME + DURATION_ONEFRAME + duration_reserve[q->iv->ivRadioType]); if(*mSerialDebug) { - DPRINT_IVID(DBG_WARN, q->iv->id); + DPRINT_IVID(DBG_INFO, q->iv->id); DBGPRINT(F("resend request (")); DBGPRINT(String(q->attempts)); DBGPRINT(F(" attempts left): 0x")); DBGHEXLN(q->cmd); } - - q->iv->radio->sendCmdPacket(q->iv, q->cmd, 0x00, true); - q->iv->radioStatistics.retransmits++; - - q->iv->radio->mRadioWaitTime.startTimeMonitor(DURATION_TXFRAME + DURATION_ONEFRAME + duration_reserve[q->iv->ivRadioType]); - mIsRetransmit = false; + //mIsRetransmit = false; } void miStsConsolidate(const queue_s *q, uint8_t stschan, record_t<> *rec, uint8_t uState, uint8_t uEnum, uint8_t lState = 0, uint8_t lEnum = 0) { @@ -953,11 +951,6 @@ class Communication : public CommQueue<> { void miComplete(Inverter<> *iv) { - if (*mSerialDebug) { - DPRINT_IVID(DBG_INFO, iv->id); - DBGPRINTLN(F("got all data msgs")); - } - if (iv->mGetLossInterval >= AHOY_GET_LOSS_INTERVAL) { // initially mIvRxCnt = mIvTxCnt = 0 iv->mGetLossInterval = 1; iv->radioStatistics.ivSent = iv->mIvRxCnt + iv->mDtuTxCnt; // iv->mIvRxCnt is the nr. of additional answer frames, default we expect one frame per request @@ -966,10 +959,16 @@ class Communication : public CommQueue<> { iv->radioStatistics.dtuSent = iv->mDtuTxCnt; if (*mSerialDebug) { DPRINT_IVID(DBG_INFO, iv->id); - DBGPRINTLN("DTU loss: " + - String (iv->radioStatistics.ivLoss) + "/" + - String (iv->radioStatistics.ivSent) + " frames for " + - String (iv->radioStatistics.dtuSent) + " requests"); + DBGPRINT(F("DTU loss: ") + + String (iv->radioStatistics.ivLoss) + F("/") + + String (iv->radioStatistics.ivSent) + F(" frames for ") + + String (iv->radioStatistics.dtuSent) + F(" requests")); + if(iv->mAckCount) { + DBGPRINT(F(". ACKs: ")); + DBGPRINTLN(String(iv->mAckCount)); + iv->mAckCount = 0; + } else + DBGPRINTLN(F("")); } iv->mIvRxCnt = 0; // start new interval, iVRxCnt is abused to collect additional possible frames iv->mIvTxCnt = 0; // start new interval, iVTxCnt is abused to collect nr. of unanswered requests diff --git a/src/hm/hmDefines.h b/src/hm/hmDefines.h index 8fc4f71f..6ba92774 100644 --- a/src/hm/hmDefines.h +++ b/src/hm/hmDefines.h @@ -89,7 +89,7 @@ const uint8_t duration_reserve[2] = {65, 115}; #define LIMIT_FAST_IV 85 // time limit to qualify an inverter as very fast answering inverter #define LIMIT_VERYFAST_IV 70 // time limit to qualify an inverter as very fast answering inverter #define LIMIT_FAST_IV_MI 35 // time limit to qualify a MI type inverter as fast answering inverter -#define LIMIT_VERYFAST_IV_MI 22 // time limit to qualify a MI type inverter as very fast answering inverter +#define LIMIT_VERYFAST_IV_MI 25 // time limit to qualify a MI type inverter as very fast answering inverter #define RETRIES_FAST_IV 12 // how often shall a message be automatically retransmitted by the nRF (fast answering inverter) #define RETRIES_VERYFAST_IV 9 // how often shall a message be automatically retransmitted by the nRF (very fast answering inverter) diff --git a/src/hm/hmInverter.h b/src/hm/hmInverter.h index 51cb0d6d..b6dc93fa 100644 --- a/src/hm/hmInverter.h +++ b/src/hm/hmInverter.h @@ -132,7 +132,6 @@ class Inverter { record_t recordAlarm; // structure for alarm values InverterStatus status = InverterStatus::OFF; // indicates the current inverter status std::array lastAlarm; // holds last 10 alarms - uint8_t rxOffset = 0; // holds the default channel offset between tx and rx channel (nRF only) int8_t rssi = 0; // RSSI uint16_t alarmCnt = 0; // counts the total number of occurred alarms uint16_t alarmLastId = 0; // lastId which was received diff --git a/src/hm/hmRadio.h b/src/hm/hmRadio.h index 09500826..b01df277 100644 --- a/src/hm/hmRadio.h +++ b/src/hm/hmRadio.h @@ -152,31 +152,22 @@ class HmRadio : public Radio { if(tx_ok) mLastIv->mAckCount++; - //#ifdef DYNAMIC_OFFSET - mRxChIdx = (mTxChIdx + mLastIv->rxOffset) % RF_CHANNELS; - /*#else - mRxChIdx = (mTxChIdx + 2) % RF_CHANNELS; - #endif*/ + rxOffset = mLastIv->ivGen == IV_HM ? 3 : 2; // holds the default channel offset between tx and rx channel (nRF only) + mRxChIdx = (mTxChIdx + rxOffset) % RF_CHANNELS; mNrf24->setChannel(mRfChLst[mRxChIdx]); mNrf24->startListening(); mTimeslotStart = millis(); tempRxChIdx = mRxChIdx; // might be better to start off with one channel less? mRxPendular = false; - mNRFloopChannels = (mLastIv->ivGen == IV_MI); - - //innerLoopTimeout = mLastIv->ivGen != IV_MI ? DURATION_TXFRAME : DURATION_ONEFRAME; - //innerLoopTimeout = mLastIv->ivGen != IV_MI ? DURATION_LISTEN_MIN : 4; - //innerLoopTimeout = (mLastIv->mIsSingleframeReq || mLastIv->ivGen == IV_MI) ? DURATION_LISTEN_MIN : DURATION_TXFRAME; + mNRFloopChannels = (mLastIv->mCmd == MI_REQ_CH1 || mLastIv->mCmd == MI_REQ_CH1); innerLoopTimeout = DURATION_LISTEN_MIN; } if(rx_ready) { if (getReceived()) { // check what we got, returns true for last package or success for single frame request mNRFisInRX = false; - rx_ready = false; mRadioWaitTime.startTimeMonitor(DURATION_PAUSE_LASTFR); // let the inverter first end his transmissions mNrf24->stopListening(); - return false; } else { innerLoopTimeout = DURATION_LISTEN_MIN; mTimeslotStart = millis(); @@ -188,7 +179,6 @@ class HmRadio : public Radio { } else mRxChIdx = tempRxChIdx; } - return true; } rx_ready = false; // reset return mNRFisInRX; @@ -317,9 +307,11 @@ class HmRadio : public Radio { if (p.packet[0] != 0x00) { if(!checkIvSerial(p.packet, mLastIv)) { - DPRINT(DBG_WARN, "RX other inverter "); + DPRINT(DBG_WARN, F("RX other inverter ")); if(!*mPrivacyMode) ah::dumpBuf(p.packet, p.len); + else + DBGPRINTLN(F("")); } else { mLastIv->mGotFragment = true; mBufCtrl.push(p); @@ -331,14 +323,6 @@ class HmRadio : public Radio { if(isLastPackage) setExpectedFrames(p.packet[9] - ALL_FRAMES); - #ifdef DYNAMIC_OFFSET - if((p.packet[9] == 1) && (p.millis < DURATION_ONEFRAME)) - mLastIv->rxOffset = (RF_CHANNELS + mTxChIdx - tempRxChIdx + 1) % RF_CHANNELS; - else if(mNRFloopChannels && (mLastIv->rxOffset > RF_CHANNELS)) { // unsure setting? - mLastIv->rxOffset = (RF_CHANNELS + mTxChIdx - tempRxChIdx + (isLastPackage ? mFramesExpected : p.packet[9])); // make clear it's not sure, start with one more offset - mNRFloopChannels = false; - } - #endif } if(IV_MI == mLastIv->ivGen) { @@ -346,10 +330,6 @@ class HmRadio : public Radio { isLastPackage = (p.packet[9] > 0x10); // > 0x10 indicates last packet received else if ((p.packet[0] != 0x88) && (p.packet[0] != 0x92)) // ignore MI status messages //#0 was p.packet[0] != 0x00 && isLastPackage = true; // response from dev control command - #ifdef DYNAMIC_OFFSET - if((p.packet[9] == 0x00) && (p.millis < DURATION_ONEFRAME)) - mLastIv->rxOffset = (RF_CHANNELS + mTxChIdx - tempRxChIdx - 1) % RF_CHANNELS; - #endif } rx_ready = true; //reset in case we first read messages from other inverter or ACK zero payloads } @@ -385,14 +365,7 @@ class HmRadio : public Radio { DBGPRINT(String(mRfChLst[mTxChIdx])); DBGPRINT(F(", ")); DBGPRINT(String(mTxRetriesNext)); - //DBGPRINT(F(" retries | ")); - //#ifdef DYNAMIC_OFFSET - DBGPRINT(F(" ret., rx offset: ")); - DBGPRINT(String(iv->rxOffset)); - DBGPRINT(F(" | ")); - /*#else DBGPRINT(F(" ret. | ")); - #endif*/ if(*mPrintWholeTrace) { if(*mPrivacyMode) ah::dumpBuf(mTxBuf.data(), len, 1, 4); @@ -415,7 +388,7 @@ class HmRadio : public Radio { } mNrf24->setChannel(mRfChLst[mTxChIdx]); mNrf24->openWritingPipe(reinterpret_cast(&iv->radioId.u64)); - mNrf24->startFastWrite(mTxBuf.data(), len, false); // false = request ACK response + mNrf24->startFastWrite(mTxBuf.data(), len, false, true); // false (3) = request ACK response; true (4) reset CE to high after transmission mMillis = millis(); mLastIv = iv; @@ -455,6 +428,7 @@ class HmRadio : public Radio { bool mRxPendular = false; uint32_t innerLoopTimeout = DURATION_LISTEN_MIN; uint8_t mTxRetries = 15; // memorize last setting for mNrf24->setRetries(3, 15); + uint8_t rxOffset = 3; // holds the channel offset between tx and rx channel used for actual inverter std::unique_ptr mSpi; std::unique_ptr mNrf24; diff --git a/src/hm/hmSystem.h b/src/hm/hmSystem.h index 22318347..7e79f30a 100644 --- a/src/hm/hmSystem.h +++ b/src/hm/hmSystem.h @@ -93,16 +93,8 @@ class HmSystem { DBGPRINTLN(String(iv->config->serial.u64, HEX)); - if((iv->config->serial.b[5] == 0x10) && ((iv->config->serial.b[4] & 0x03) == 0x01)) + if(IV_MI == iv->ivGen) DPRINTLN(DBG_WARN, F("MI Inverter, has some restrictions!")); - - #ifdef DYNAMIC_OFFSET - iv->rxOffset = (iv->ivGen == IV_HM) ? 13 : 12; // effective 3 (or 2), but can easily be recognized as default setting - #else - //iv->rxOffset = ((iv->ivGen == IV_HM) && (iv->type == INV_TYPE_4CH)) ? 3 : 2; - iv->rxOffset = (iv->ivGen == IV_HM) ? 3 : 2; - #endif - cb(iv); } From b8cc3bcfe9cb4c7f614b26c7b4389fae9ae9d148 Mon Sep 17 00:00:00 2001 From: Frank Date: Fri, 9 Feb 2024 16:37:08 +0100 Subject: [PATCH 162/179] Add hint to 'INV_RESET_MIDNIGHT' --- src/web/lang.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/web/lang.json b/src/web/lang.json index 689fbb06..1e79f8bf 100644 --- a/src/web/lang.json +++ b/src/web/lang.json @@ -290,8 +290,8 @@ }, { "token": "INV_RESET_MIDNIGHT", - "en": "Reset values and YieldDay at midnight", - "de": "Werte und Gesamtertrag um Mitternacht zurücksetzen" + "en": "Reset values and YieldDay at midnight. ('Pause communication during night' need to be set)", + "de": "Werte und Gesamtertrag um Mitternacht zurücksetzen ('Kommunikation während der Nacht pausieren' muss gesetzt sein)" }, { "token": "INV_PAUSE_SUNSET", From d5cecbb5b017349ece28431ed9b44ad724887039 Mon Sep 17 00:00:00 2001 From: lumapu Date: Sat, 10 Feb 2024 13:03:29 +0100 Subject: [PATCH 163/179] 0.8.78 * fixed protection --- src/CHANGES.md | 2 +- src/app.h | 12 +++----- src/appInterface.h | 5 ++- src/defines.h | 2 +- src/web/Protection.h | 72 +++++++++++++++++++++----------------------- src/web/RestApi.h | 6 ++-- src/web/web.h | 6 ++-- 7 files changed, 49 insertions(+), 56 deletions(-) diff --git a/src/CHANGES.md b/src/CHANGES.md index ea067bd2..ce0adc6b 100644 --- a/src/CHANGES.md +++ b/src/CHANGES.md @@ -1,6 +1,6 @@ # Development Changes -## 0.8.78 - 2024-02-09 +## 0.8.78 - 2024-02-10 * finalized API token access #1415 ## 0.8.77 - 2024-02-08 diff --git a/src/app.h b/src/app.h index 1a4e780d..b16d7aeb 100644 --- a/src/app.h +++ b/src/app.h @@ -247,8 +247,8 @@ class app : public IApp, public ah::Scheduler { #endif } - void lock(void) override { - mProtection->lock(); + void lock(bool fromWeb) override { + mProtection->lock(fromWeb); } char *unlock(const char *clientIp, bool loginFromWeb) override { @@ -259,12 +259,8 @@ class app : public IApp, public ah::Scheduler { mProtection->resetLockTimeout(); } - bool isProtected(void) const override { - return mProtection->isProtected(); - } - - bool isProtected(const char *token, bool askedFromWeb) const override { - return mProtection->isProtected(token, askedFromWeb); + bool isProtected(const char *clientIp, const char *token, bool askedFromWeb) const override { + return mProtection->isProtected(clientIp, token, askedFromWeb); } bool getNrfEnabled(void) override { diff --git a/src/appInterface.h b/src/appInterface.h index f0d2b2a0..536455e0 100644 --- a/src/appInterface.h +++ b/src/appInterface.h @@ -61,11 +61,10 @@ class IApp { virtual uint32_t getMqttRxCnt() = 0; virtual uint32_t getMqttTxCnt() = 0; - virtual void lock(void) = 0; + virtual void lock(bool fromWeb) = 0; virtual char *unlock(const char *clientIp, bool loginFromWeb) = 0; virtual void resetLockTimeout(void) = 0; - virtual bool isProtected(void) const = 0; - virtual bool isProtected(const char *token, bool askedFromWeb) const = 0; + virtual bool isProtected(const char *clientIp, const char *token, bool askedFromWeb) const = 0; virtual uint16_t getHistoryValue(uint8_t type, uint16_t i) = 0; virtual uint16_t getHistoryMaxDay() = 0; diff --git a/src/defines.h b/src/defines.h index 46be8dac..ab685698 100644 --- a/src/defines.h +++ b/src/defines.h @@ -13,7 +13,7 @@ //------------------------------------- #define VERSION_MAJOR 0 #define VERSION_MINOR 8 -#define VERSION_PATCH 77 +#define VERSION_PATCH 78 //------------------------------------- typedef struct { diff --git a/src/web/Protection.h b/src/web/Protection.h index b9eeef95..7c1ff71e 100644 --- a/src/web/Protection.h +++ b/src/web/Protection.h @@ -18,12 +18,9 @@ class Protection { explicit Protection(const char *pwd) { mPwd = pwd; mLogoutTimeout = 0; - mLoginIp.fill(0); + mWebIp.fill(0); + mApiIp.fill(0); mToken.fill(0); - - // no password set - unlock - if(pwd[0] == '\0') - mProtected = false; } public: @@ -40,27 +37,30 @@ class Protection { // auto logout if(0 != mLogoutTimeout) { if (0 == --mLogoutTimeout) { - if(mPwd[0] != '\0') { - mProtected = true; - } + if(mPwd[0] != '\0') + lock(false); } } } - void lock(void) { - mProtected = true; - mLoginIp.fill(0); + void lock(bool fromWeb) { + mWebIp.fill(0); + if(fromWeb) + return; + + mApiIp.fill(0); mToken.fill(0); } char *unlock(const char *clientIp, bool loginFromWeb) { mLogoutTimeout = LOGOUT_TIMEOUT; - mProtected = false; if(loginFromWeb) - ah::ip2Arr(static_cast(mLoginIp.data()), clientIp); - else + ah::ip2Arr(static_cast(mWebIp.data()), clientIp); + else { + ah::ip2Arr(static_cast(mApiIp.data()), clientIp); genToken(); + } return reinterpret_cast(mToken.data()); } @@ -70,30 +70,19 @@ class Protection { mLogoutTimeout = LOGOUT_TIMEOUT; } - bool isProtected(void) const { - return mProtected; - } - - bool isProtected(const char *token, bool askedFromWeb) const { // token == clientIp - if(mProtected) - return true; - - if(mPwd[0] == '\0') + bool isProtected(const char *clientIp, const char *token, bool askedFromWeb) const { + if(mPwd[0] == '\0') // no password set return false; - if(askedFromWeb) { // check IP address - std::array ip; - ah::ip2Arr(static_cast(ip.data()), token); - for(uint8_t i = 0; i < 4; i++) { - if(mLoginIp[i] != ip[i]) - return true; - } - } else { // API call - check token - if(0 == mToken[0]) // token is zero - return true; + if(askedFromWeb) + return !isIdentical(clientIp, mWebIp); + + // API call + if(0 == mToken[0]) // token is zero, from WebUi (logged in) + return !isIdentical(clientIp, mWebIp); + if(isIdentical(clientIp, mApiIp)) return (0 != strncmp(token, mToken.data(), 16)); - } return false; } @@ -106,8 +95,18 @@ class Protection { if(mToken[i] < 10) mToken[i] += 0x30; // convert to ascii number 1-9 (zero isn't allowed) else - mToken[i] += 0x37; // convert to ascii upper case character + mToken[i] += 0x37; // convert to ascii upper case character A-Z + } + } + + bool isIdentical(const char *clientIp, const std::array cmp) const { + std::array ip; + ah::ip2Arr(static_cast(ip.data()), clientIp); + for(uint8_t i = 0; i < 4; i++) { + if(cmp[i] != ip[i]) + return false; } + return true; } protected: @@ -115,9 +114,8 @@ class Protection { private: const char *mPwd; - bool mProtected = true; uint16_t mLogoutTimeout = 0; - std::array mLoginIp; + std::array mWebIp, mApiIp; std::array mToken; }; diff --git a/src/web/RestApi.h b/src/web/RestApi.h index 5103f3ba..53b604b5 100644 --- a/src/web/RestApi.h +++ b/src/web/RestApi.h @@ -266,7 +266,7 @@ class RestApi { obj[F("modules")] = String(mApp->getVersionModules()); obj[F("build")] = String(AUTO_GIT_HASH); obj[F("env")] = String(ENV_NAME); - obj[F("menu_prot")] = mApp->isProtected(request->client()->remoteIP().toString().c_str(), true); + obj[F("menu_prot")] = mApp->isProtected(request->client()->remoteIP().toString().c_str(), "", true); obj[F("menu_mask")] = (uint16_t)(mConfig->sys.protectionMask ); obj[F("menu_protEn")] = (bool) (mConfig->sys.adminPwd[0] != '\0'); obj[F("cst_lnk")] = String(mConfig->plugin.customLink); @@ -844,7 +844,7 @@ class RestApi { if(mConfig->sys.adminPwd[0] != '\0') { // check if admin password is set if(strncmp("*", clientIP, 1) != 0) { // no call from MqTT const char* token = jsonIn["token"]; - if(mApp->isProtected(token, false)) { + if(mApp->isProtected(clientIP, token, false)) { jsonOut[F("error")] = F(IS_PROTECTED); return false; } @@ -897,7 +897,7 @@ class RestApi { if(mConfig->sys.adminPwd[0] != '\0') { // check if admin password is set if(strncmp("*", clientIP, 1) != 0) { // no call from MqTT const char* token = jsonIn["token"]; - if(mApp->isProtected(token, false)) { + if(mApp->isProtected(clientIP, token, false)) { jsonOut[F("error")] = F(IS_PROTECTED); return false; } diff --git a/src/web/web.h b/src/web/web.h index 7ed41f92..692dd779 100644 --- a/src/web/web.h +++ b/src/web/web.h @@ -227,7 +227,7 @@ class Web { } void checkProtection(AsyncWebServerRequest *request) { - if(mApp->isProtected(request->client()->remoteIP().toString().c_str(), true)) { + if(mApp->isProtected(request->client()->remoteIP().toString().c_str(), "", true)) { checkRedirect(request); return; } @@ -328,7 +328,7 @@ class Web { DPRINTLN(DBG_VERBOSE, F("onLogout")); checkProtection(request); - mApp->lock(); + mApp->lock(true); AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html; charset=UTF-8"), system_html, system_html_len); response->addHeader(F("Content-Encoding"), "gzip"); @@ -455,7 +455,7 @@ class Web { // protection if (request->arg("adminpwd") != "{PWD}") { request->arg("adminpwd").toCharArray(mConfig->sys.adminPwd, PWD_LEN); - mApp->lock(); + mApp->lock(false); } mConfig->sys.protectionMask = 0x0000; for (uint8_t i = 0; i < 7; i++) { From a51a7612155cb7d13f15c4cb4f9b6e33ac894229 Mon Sep 17 00:00:00 2001 From: lumapu Date: Sun, 11 Feb 2024 00:07:20 +0100 Subject: [PATCH 164/179] 0.8.78 * finalized API token access #1415 --- src/platformio.ini | 2 +- src/web/Protection.h | 16 ++++++------- src/web/RestApi.h | 41 ++++++++++++++++++--------------- src/web/html/index.html | 23 ++++++++---------- src/web/html/setup.html | 28 ++++++++++------------ src/web/html/visualization.html | 18 ++++++++------- 6 files changed, 64 insertions(+), 64 deletions(-) diff --git a/src/platformio.ini b/src/platformio.ini index f949aa37..7130bf4c 100644 --- a/src/platformio.ini +++ b/src/platformio.ini @@ -350,7 +350,7 @@ build_flags = ${env.build_flags} -DDEF_LED1=17 -DLED_ACTIVE_HIGH -DARDUINO_USB_MODE=1 - #-DARDUINO_USB_CDC_ON_BOOT=1 + -DARDUINO_USB_CDC_ON_BOOT=1 monitor_filters = esp32_exception_decoder, colorize diff --git a/src/web/Protection.h b/src/web/Protection.h index 7c1ff71e..82e4be49 100644 --- a/src/web/Protection.h +++ b/src/web/Protection.h @@ -33,8 +33,7 @@ class Protection { return mInstance; } - void tickSecond() { - // auto logout + void tickSecond() { // auto logout if(0 != mLogoutTimeout) { if (0 == --mLogoutTimeout) { if(mPwd[0] != '\0') @@ -77,8 +76,10 @@ class Protection { if(askedFromWeb) return !isIdentical(clientIp, mWebIp); - // API call - if(0 == mToken[0]) // token is zero, from WebUi (logged in) + if(nullptr == token) + return true; + + if('*' == token[0]) // call from WebUI return !isIdentical(clientIp, mWebIp); if(isIdentical(clientIp, mApiIp)) @@ -92,10 +93,9 @@ class Protection { mToken.fill(0); for(uint8_t i = 0; i < 16; i++) { mToken[i] = random(1, 35); - if(mToken[i] < 10) - mToken[i] += 0x30; // convert to ascii number 1-9 (zero isn't allowed) - else - mToken[i] += 0x37; // convert to ascii upper case character A-Z + // convert to ascii number 1-9 (zero isn't allowed) or upper + // case character A-Z + mToken[i] += (mToken[i] < 10) ? 0x30 : 0x37; } } diff --git a/src/web/RestApi.h b/src/web/RestApi.h index 53b604b5..fadd7277 100644 --- a/src/web/RestApi.h +++ b/src/web/RestApi.h @@ -841,15 +841,8 @@ class RestApi { return true; } - if(mConfig->sys.adminPwd[0] != '\0') { // check if admin password is set - if(strncmp("*", clientIP, 1) != 0) { // no call from MqTT - const char* token = jsonIn["token"]; - if(mApp->isProtected(clientIP, token, false)) { - jsonOut[F("error")] = F(IS_PROTECTED); - return false; - } - } - } + if(isProtected(jsonIn, jsonOut, clientIP)) + return false; Inverter<> *iv = mSys->getInverterByPos(jsonIn[F("id")]); bool accepted = true; @@ -894,15 +887,8 @@ class RestApi { } bool setSetup(JsonObject jsonIn, JsonObject jsonOut, const char *clientIP) { - if(mConfig->sys.adminPwd[0] != '\0') { // check if admin password is set - if(strncmp("*", clientIP, 1) != 0) { // no call from MqTT - const char* token = jsonIn["token"]; - if(mApp->isProtected(clientIP, token, false)) { - jsonOut[F("error")] = F(IS_PROTECTED); - return false; - } - } - } + if(isProtected(jsonIn, jsonOut, clientIP)) + return false; #if !defined(ETHERNET) if(F("scan_wifi") == jsonIn[F("cmd")]) @@ -951,6 +937,25 @@ class RestApi { return true; } + bool isProtected(JsonObject jsonIn, JsonObject jsonOut, const char *clientIP) { + if(mConfig->sys.adminPwd[0] != '\0') { // check if admin password is set + if(strncmp("*", clientIP, 1) != 0) { // no call from MqTT + const char* token = nullptr; + if(jsonIn.containsKey(F("token"))) + token = jsonIn["token"]; + + if(!mApp->isProtected(clientIP, token, false)) + return false; + + jsonOut[F("error")] = F(IS_PROTECTED); + return true; + } + } + + return false; + } + + private: IApp *mApp = nullptr; HMSYSTEM *mSys = nullptr; HmRadio<> *mRadioNrf = nullptr; diff --git a/src/web/html/index.html b/src/web/html/index.html index e7b7afc4..2611db5b 100644 --- a/src/web/html/index.html +++ b/src/web/html/index.html @@ -41,27 +41,24 @@ var release = null; function apiCb(obj) { - var e = document.getElementById("apiResult"); + var e = document.getElementById("apiResult") if(obj.success) { - e.innerHTML = " {#COMMAND_EXE}"; - getAjax("/api/index", parse); - } - else - e.innerHTML = " {#ERROR}: " + obj.error; + e.innerHTML = " {#COMMAND_EXE}" + getAjax("/api/index", parse) + } else + e.innerHTML = " {#ERROR}: " + obj.error } function setTime() { - var date = new Date(); - var obj = new Object(); - obj.cmd = "set_time"; - obj.val = parseInt(date.getTime() / 1000); - getAjax("/api/setup", apiCb, "POST", JSON.stringify(obj)); + var date = new Date() + var obj = {cmd: "set_time", token: "*", val: parseInt(date.getTime() / 1000)} + getAjax("/api/setup", apiCb, "POST", JSON.stringify(obj)) } function parseGeneric(obj) { if(exeOnce) - parseESP(obj); - parseRssi(obj); + parseESP(obj) + parseRssi(obj) } function parseSys(obj) { diff --git a/src/web/html/setup.html b/src/web/html/setup.html index dec62830..4859fe34 100644 --- a/src/web/html/setup.html +++ b/src/web/html/setup.html @@ -559,31 +559,26 @@ } function setTime() { - var date = new Date(); - var obj = new Object(); - obj.cmd = "set_time"; - obj.val = parseInt(date.getTime() / 1000); - getAjax("/api/setup", apiCbNtp, "POST", JSON.stringify(obj)); - setTimeout(function() {getAjax('/api/index', apiCbNtp2)}, 2000); + var date = new Date() + var obj = {cmd: "set_time", token: "*", val: parseInt(date.getTime() / 1000)} + getAjax("/api/setup", apiCbNtp, "POST", JSON.stringify(obj)) + setTimeout(function() {getAjax('/api/index', apiCbNtp2)}, 2000) } function scan() { - var obj = new Object(); - obj.cmd = "scan_wifi"; + var obj = {cmd: "scan_wifi", token: "*"} getAjax("/api/setup", apiCbWifi, "POST", JSON.stringify(obj)); setTimeout(function() {getAjax('/api/setup/networks', listNetworks)}, 5000); } function syncTime() { - var obj = new Object(); - obj.cmd = "sync_ntp"; - getAjax("/api/setup", apiCbNtp, "POST", JSON.stringify(obj)); - setTimeout(function() {getAjax('/api/index', apiCbNtp2)}, 2000); + var obj = {cmd: "sync_ntp", token: "*"} + getAjax("/api/setup", apiCbNtp, "POST", JSON.stringify(obj)) + setTimeout(function() {getAjax('/api/index', apiCbNtp2)}, 2000) } function sendDiscoveryConfig() { - var obj = new Object(); - obj.cmd = "discovery_cfg"; + var obj = {cmd: "discovery_cfg", token: "*"} getAjax("/api/setup", apiCbMqtt, "POST", JSON.stringify(obj)); } @@ -837,8 +832,9 @@ function ivSave() { var o = new Object(); - o.cmd = "save_iv"; - o.id = obj.id; + o.cmd = "save_iv" + o.token = "*" + o.id = obj.id o.ser = parseInt(document.getElementsByName("ser")[0].value, 16); o.name = document.getElementsByName("name")[0].value; o.en = document.getElementsByName("enable")[0].checked; diff --git a/src/web/html/visualization.html b/src/web/html/visualization.html index 3b90a028..1ce4e264 100644 --- a/src/web/html/visualization.html +++ b/src/web/html/visualization.html @@ -454,18 +454,20 @@ val = 100; var obj = new Object(); - obj.id = id; - obj.cmd = cmd; - obj.val = Math.round(val*10); - getAjax("/api/ctrl", ctrlCb, "POST", JSON.stringify(obj)); + obj.id = id + obj.token = "*" + obj.cmd = cmd + obj.val = Math.round(val*10) + getAjax("/api/ctrl", ctrlCb, "POST", JSON.stringify(obj)) } function applyCtrl(id, cmd, val=0) { var obj = new Object(); - obj.id = id; - obj.cmd = cmd; - obj.val = val; - getAjax("/api/ctrl", ctrlCb2, "POST", JSON.stringify(obj)); + obj.id = id + obj.token = "*" + obj.cmd = cmd + obj.val = val + getAjax("/api/ctrl", ctrlCb2, "POST", JSON.stringify(obj)) } function ctrlCb(obj) { From e73e31b1ccf39f05b85c6129f3f285c9ffcb91f1 Mon Sep 17 00:00:00 2001 From: lumapu Date: Sun, 11 Feb 2024 00:17:33 +0100 Subject: [PATCH 165/179] 0.8.78 * possible fix of MqTT fix "total values are sent to often" #1421 --- src/CHANGES.md | 1 + src/publisher/pubMqttIvData.h | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/CHANGES.md b/src/CHANGES.md index ce0adc6b..dab588ed 100644 --- a/src/CHANGES.md +++ b/src/CHANGES.md @@ -2,6 +2,7 @@ ## 0.8.78 - 2024-02-10 * finalized API token access #1415 +* possible fix of MqTT fix "total values are sent to often" #1421 ## 0.8.77 - 2024-02-08 * merge PR: BugFix: ACK #1414 diff --git a/src/publisher/pubMqttIvData.h b/src/publisher/pubMqttIvData.h index 5f38768d..06f8e629 100644 --- a/src/publisher/pubMqttIvData.h +++ b/src/publisher/pubMqttIvData.h @@ -246,7 +246,7 @@ class PubMqttIvData { mPos++; } else { mSendList->pop(); - mPos = 0; + mSendTotals = false; mState = IDLE; } } From 493c583b79fea184aeb899b6db052ee58e24d134 Mon Sep 17 00:00:00 2001 From: lumapu Date: Sun, 11 Feb 2024 00:21:47 +0100 Subject: [PATCH 166/179] 0.8.78 * removed `switchCycle` from `hmsRadio.h` #1412 --- src/CHANGES.md | 1 + src/hms/hmsRadio.h | 12 ++++++------ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/CHANGES.md b/src/CHANGES.md index dab588ed..be5e011c 100644 --- a/src/CHANGES.md +++ b/src/CHANGES.md @@ -3,6 +3,7 @@ ## 0.8.78 - 2024-02-10 * finalized API token access #1415 * possible fix of MqTT fix "total values are sent to often" #1421 +* removed `switchCycle` from `hmsRadio.h` #1412 ## 0.8.77 - 2024-02-08 * merge PR: BugFix: ACK #1414 diff --git a/src/hms/hmsRadio.h b/src/hms/hmsRadio.h index cb181b0b..d074442b 100644 --- a/src/hms/hmsRadio.h +++ b/src/hms/hmsRadio.h @@ -9,7 +9,7 @@ #include "cmt2300a.h" #include "../hm/radio.h" -#define CMT_SWITCH_CHANNEL_CYCLE 5 +//#define CMT_SWITCH_CHANNEL_CYCLE 5 template class CmtRadio : public Radio { @@ -150,9 +150,9 @@ class CmtRadio : public Radio { } inline void sendSwitchChCmd(Inverter<> *iv, uint8_t ch) { - if(CMT_SWITCH_CHANNEL_CYCLE > ++mSwitchCycle) - return; - mSwitchCycle = 0; + //if(CMT_SWITCH_CHANNEL_CYCLE > ++mSwitchCycle) + // return; + //mSwitchCycle = 0; /** ch: * 0x00: 860.00 MHz @@ -176,7 +176,7 @@ class CmtRadio : public Radio { packet_t p; p.millis = millis() - mMillis; if(CmtStatus::SUCCESS == mCmt.getRx(p.packet, &p.len, 28, &p.rssi)) { - mSwitchCycle = 0; + //mSwitchCycle = 0; p.ch = 0; // not used for CMT inverters mBufCtrl.push(p); } @@ -191,7 +191,7 @@ class CmtRadio : public Radio { bool mCmtAvail = false; bool mRqstGetRx = false; uint32_t mMillis = 0; - uint8_t mSwitchCycle = 0; + //uint8_t mSwitchCycle = 0; }; #endif /*__HMS_RADIO_H__*/ From c2902737901d59895306fa6a3239193b036d3c1f Mon Sep 17 00:00:00 2001 From: lumapu Date: Sun, 11 Feb 2024 01:07:07 +0100 Subject: [PATCH 167/179] 0.8.78 * merge PR: Add hint to INV_RESET_MIDNIGHT resp. INV_PAUSE_DURING_NIGHT #1418 * merge PR: simplify rxOffset logic #1417 * code quality improvments --- src/CHANGES.md | 3 +++ src/eth/ahoyeth.h | 2 +- src/utils/helper.cpp | 6 ++---- src/utils/syslog.cpp | 3 +-- src/web/html/colorBright.css | 6 +++--- src/web/html/colorDark.css | 16 ++++++++-------- src/web/html/style.css | 21 +++++++++++---------- 7 files changed, 29 insertions(+), 28 deletions(-) diff --git a/src/CHANGES.md b/src/CHANGES.md index be5e011c..aa63153f 100644 --- a/src/CHANGES.md +++ b/src/CHANGES.md @@ -4,6 +4,9 @@ * finalized API token access #1415 * possible fix of MqTT fix "total values are sent to often" #1421 * removed `switchCycle` from `hmsRadio.h` #1412 +* merge PR: Add hint to INV_RESET_MIDNIGHT resp. INV_PAUSE_DURING_NIGHT #1418 +* merge PR: simplify rxOffset logic #1417 +* code quality improvments ## 0.8.77 - 2024-02-08 * merge PR: BugFix: ACK #1414 diff --git a/src/eth/ahoyeth.h b/src/eth/ahoyeth.h index ef8d0751..ebd91c67 100644 --- a/src/eth/ahoyeth.h +++ b/src/eth/ahoyeth.h @@ -49,7 +49,7 @@ class ahoyeth { #if defined(CONFIG_IDF_TARGET_ESP32S3) EthSpi mEthSpi; #endif - settings_t *mConfig = NULL; + settings_t *mConfig = nullptr; uint32_t *mUtcTimestamp; AsyncUDP mUdp; // for time server diff --git a/src/utils/helper.cpp b/src/utils/helper.cpp index b3f53a23..24a4d9ee 100644 --- a/src/utils/helper.cpp +++ b/src/utils/helper.cpp @@ -72,11 +72,10 @@ namespace ah { String getTimeStrMs(uint64_t t) { char str[13]; - uint16_t m; if(0 == t) sprintf(str, "n/a"); else { - m = t % 1000; + uint16_t m = t % 1000; t = t / 1000; sprintf(str, "%02d:%02d:%02d.%03d", hour(t), minute(t), second(t), m); } @@ -86,14 +85,13 @@ namespace ah { uint64_t Serial2u64(const char *val) { char tmp[3]; uint64_t ret = 0ULL; - uint64_t u64; memset(tmp, 0, 3); for(uint8_t i = 0; i < 6; i++) { tmp[0] = val[i*2]; tmp[1] = val[i*2 + 1]; if((tmp[0] == '\0') || (tmp[1] == '\0')) break; - u64 = strtol(tmp, NULL, 16); + uint64_t u64 = strtol(tmp, NULL, 16); ret |= (u64 << ((5-i) << 3)); } return ret; diff --git a/src/utils/syslog.cpp b/src/utils/syslog.cpp index c251e899..d02e25ed 100644 --- a/src/utils/syslog.cpp +++ b/src/utils/syslog.cpp @@ -68,12 +68,11 @@ void DbgSyslog::syslogCb (String msg) // Send mSyslogBuffer in chunks because mSyslogBuffer is larger than syslog packet size int packetStart = 0; int packetSize = 122; // syslog payload depends also on hostname and app - char saveChar; if (isEolFound) { mSyslogBuffer[mSyslogBufFill-2]=0; // skip \r\n } while(packetStart < mSyslogBufFill) { - saveChar = mSyslogBuffer[packetStart+packetSize]; + char saveChar = mSyslogBuffer[packetStart+packetSize]; mSyslogBuffer[packetStart+packetSize] = 0; log(mConfig->sys.deviceName,SYSLOG_FACILITY, mSyslogSeverity, &mSyslogBuffer[packetStart]); mSyslogBuffer[packetStart+packetSize] = saveChar; diff --git a/src/web/html/colorBright.css b/src/web/html/colorBright.css index 47382daa..2e676029 100644 --- a/src/web/html/colorBright.css +++ b/src/web/html/colorBright.css @@ -3,9 +3,9 @@ --fg: #000; --fg2: #fff; - --info: #0000dd; - --warn: #ff7700; - --success: #009900; + --info: #00d; + --warn: #f70; + --success: #090; --input-bg: #eee; --table-border: #ccc; diff --git a/src/web/html/colorDark.css b/src/web/html/colorDark.css index 65100721..40bd4cf3 100644 --- a/src/web/html/colorDark.css +++ b/src/web/html/colorDark.css @@ -4,8 +4,8 @@ --fg2: #fff; --info: #0072c8; - --warn: #ffaa00; - --success: #00bb00; + --warn: #fa0; + --success: #0b0; --input-bg: #333; --table-border: #333; @@ -20,14 +20,14 @@ --invalid-bg: #400; - --total-head-title: #555511; - --total-bg: #666622; - --iv-head-title: #115511; - --iv-head-bg: #226622; + --total-head-title: #551; + --total-bg: #662; + --iv-head-title: #151; + --iv-head-bg: #262; --iv-dis-title: #333; --iv-dis: #444; - --ch-head-title: #112255; - --ch-head-bg: #223366; + --ch-head-title: #125; + --ch-head-bg: #236; --ts-head: #333; --ts-bg: #555; } diff --git a/src/web/html/style.css b/src/web/html/style.css index 395fcb99..2d6a03c7 100644 --- a/src/web/html/style.css +++ b/src/web/html/style.css @@ -16,11 +16,11 @@ span, li, h3, label, fieldset { color: var(--fg); } -fieldset, input[type=submit], .btn { +fieldset, input[type="submit"], .btn { border-radius: 4px; } -input[type=file] { +input[type="file"] { width: 100%; } @@ -33,7 +33,7 @@ textarea { color: var(--fg2); } -svg rect {fill: #0000AA;} +svg rect {fill: #00A;} svg.chart { background: #f2f2f2; border: 2px solid gray; @@ -44,6 +44,7 @@ div.chartDivContainer { padding: 1px; margin: 1px; } + div.chartdivContainer span { color: var(--fg2); } @@ -95,7 +96,7 @@ svg.icon { vertical-align: middle; display: inline-block; margin-top:-4x; - padding: 5px 7px 5px 0px; + padding: 5px 7px 5px 0; } .icon-info { @@ -141,7 +142,7 @@ svg.icon { span.seperator { width: 100%; height: 1px; - margin: 5px 0px 5px; + margin: 5px 0 5px; background-color: #494949; display: block; } @@ -391,7 +392,7 @@ th { #footer .left { color: #bbb; - margin: 23px 0px 0px 25px; + margin: 23px 0 0 25px; } #footer ul { @@ -525,7 +526,7 @@ input, select { font-size: 13pt; } -input[type=text], input[type=password], select, input[type=number] { +input[type="text"], input[type="password"], select, input[type="number"] { width: 100%; box-sizing: border-box; border: 1px solid #ccc; @@ -551,7 +552,7 @@ input.btnDel { input.btn { background-color: var(--primary); color: #fff; - border: 0px; + border: 0; padding: 7px 20px 7px 20px; margin-bottom: 10px; text-transform: uppercase; @@ -572,7 +573,7 @@ label { display: inline-block; font-size: 12pt; padding-right: 10px; - margin: 10px 0px 0px 15px; + margin: 10px 0 0 15px; vertical-align: top; } @@ -601,7 +602,7 @@ div.ModPwr, div.ModName, div.YieldCor { div.hr { height: 1px; border-top: 1px solid #ccc; - margin: 10px 0px 10px; + margin: 10px 0 10px; } #note { From 3e533e82af88471f358af3a4651a39ff8b5c0c26 Mon Sep 17 00:00:00 2001 From: lumapu Date: Sun, 11 Feb 2024 01:37:55 +0100 Subject: [PATCH 168/179] updated GxEPD2 to 1.5.5 --- patches/GxEPD2_SW_SPI.patch | 56 +++++++++++++------------------------ src/platformio.ini | 10 +++---- 2 files changed, 25 insertions(+), 41 deletions(-) diff --git a/patches/GxEPD2_SW_SPI.patch b/patches/GxEPD2_SW_SPI.patch index dc3fa9ca..87458cce 100644 --- a/patches/GxEPD2_SW_SPI.patch +++ b/patches/GxEPD2_SW_SPI.patch @@ -1,5 +1,5 @@ diff --git a/src/GxEPD2_EPD.cpp b/src/GxEPD2_EPD.cpp -index 8df8bef..91d7f49 100644 +index 40b1b13..d0dbdba 100644 --- a/src/GxEPD2_EPD.cpp +++ b/src/GxEPD2_EPD.cpp @@ -19,9 +19,9 @@ @@ -14,29 +14,25 @@ index 8df8bef..91d7f49 100644 { _initial_write = true; _initial_refresh = true; -@@ -71,27 +71,30 @@ void GxEPD2_EPD::init(uint32_t serial_diag_bitrate, bool initial, uint16_t reset +@@ -61,7 +61,6 @@ void GxEPD2_EPD::init(uint32_t serial_diag_bitrate, bool initial, uint16_t reset + digitalWrite(_cs, HIGH); // set (needed e.g. for RP2040) + } + _reset(); +- _pSPIx->begin(); // may steal _rst pin (Waveshare Pico-ePaper-2.9) + if (_rst >= 0) + { + digitalWrite(_rst, HIGH); // preset (less glitch for any analyzer) +@@ -84,14 +83,30 @@ void GxEPD2_EPD::init(uint32_t serial_diag_bitrate, bool initial, uint16_t reset { pinMode(_busy, INPUT); } -- _pSPIx->begin(); -- if (_busy == MISO) // may be overridden -- { -- pinMode(_busy, INPUT); -- } -- if (_dc == MISO) // may be overridden, TTGO T5 V2.66 -- { -- pinMode(_dc, OUTPUT); -- } -- if (_cs == MISO) // may be overridden + if (_sck < 0) SPI.begin(); +} + +void GxEPD2_EPD::init(int16_t sck, int16_t mosi, uint32_t serial_diag_bitrate, bool initial, uint16_t reset_duration, bool pulldown_rst_mode) +{ + if ((sck >= 0) && (mosi >= 0)) - { -- pinMode(_cs, INPUT); -- } ++ { + _sck = sck; + _mosi = mosi; + digitalWrite(_sck, LOW); @@ -58,7 +54,7 @@ index 8df8bef..91d7f49 100644 } void GxEPD2_EPD::setBusyCallback(void (*busyCallback)(const void*), const void* busy_callback_parameter) -@@ -100,12 +103,6 @@ void GxEPD2_EPD::setBusyCallback(void (*busyCallback)(const void*), const void* +@@ -100,12 +115,6 @@ void GxEPD2_EPD::setBusyCallback(void (*busyCallback)(const void*), const void* _busy_callback_parameter = busy_callback_parameter; } @@ -71,7 +67,7 @@ index 8df8bef..91d7f49 100644 void GxEPD2_EPD::_reset() { if (_rst >= 0) -@@ -174,115 +171,201 @@ void GxEPD2_EPD::_waitWhileBusy(const char* comment, uint16_t busy_time) +@@ -174,115 +183,201 @@ void GxEPD2_EPD::_waitWhileBusy(const char* comment, uint16_t busy_time) void GxEPD2_EPD::_writeCommand(uint8_t c) { @@ -304,21 +300,10 @@ index 8df8bef..91d7f49 100644 + _endTransaction(); } diff --git a/src/GxEPD2_EPD.h b/src/GxEPD2_EPD.h -index 34c1145..c480b7d 100644 +index 3daf37e..96198c2 100644 --- a/src/GxEPD2_EPD.h +++ b/src/GxEPD2_EPD.h -@@ -8,6 +8,10 @@ - // Version: see library.properties - // - // Library: https://github.com/ZinggJM/GxEPD2 -+// To use SW SPI with GxEPD2: -+// add the special call to the added init method BEFORE the normal init method: -+// display.epd2.init(SW_SCK, SW_MOSI, 115200, true, 20, false); // define or replace SW_SCK, SW_MOSI -+// display.init(115200); // needed to init upper level - - #ifndef _GxEPD2_EPD_H_ - #define _GxEPD2_EPD_H_ -@@ -35,6 +39,7 @@ class GxEPD2_EPD +@@ -35,6 +35,7 @@ class GxEPD2_EPD uint16_t w, uint16_t h, GxEPD2::Panel p, bool c, bool pu, bool fpu); virtual void init(uint32_t serial_diag_bitrate = 0); // serial_diag_bitrate = 0 : disabled virtual void init(uint32_t serial_diag_bitrate, bool initial, uint16_t reset_duration = 10, bool pulldown_rst_mode = false); @@ -326,7 +311,7 @@ index 34c1145..c480b7d 100644 virtual void end(); // release SPI and control pins // Support for Bitmaps (Sprites) to Controller Buffer and to Screen virtual void clearScreen(uint8_t value) = 0; // init controller memory and screen (default white) -@@ -97,7 +102,6 @@ class GxEPD2_EPD +@@ -97,7 +98,6 @@ class GxEPD2_EPD { return (a > b ? a : b); }; @@ -334,7 +319,7 @@ index 34c1145..c480b7d 100644 protected: void _reset(); void _waitWhileBusy(const char* comment = 0, uint16_t busy_time = 5000); -@@ -111,17 +115,22 @@ class GxEPD2_EPD +@@ -111,8 +111,14 @@ class GxEPD2_EPD void _startTransfer(); void _transfer(uint8_t value); void _endTransfer(); @@ -346,12 +331,11 @@ index 34c1145..c480b7d 100644 + void _readData(uint8_t* data, uint16_t n); protected: - int16_t _cs, _dc, _rst, _busy, _busy_level; -+ int16_t _cs, _dc, _rst, _busy, _busy_level, _sck, _mosi;; ++ int16_t _cs, _dc, _rst, _busy, _busy_level, _sck, _mosi; uint32_t _busy_timeout; bool _diag_enabled, _pulldown_rst_mode; -- SPIClass* _pSPIx; - SPISettings _spi_settings; - bool _initial_write, _initial_refresh; + SPIClass* _pSPIx; +@@ -121,7 +127,7 @@ class GxEPD2_EPD bool _power_is_on, _using_partial_mode, _hibernating; bool _init_display_done; uint16_t _reset_duration; diff --git a/src/platformio.ini b/src/platformio.ini index 7130bf4c..0a76a59d 100644 --- a/src/platformio.ini +++ b/src/platformio.ini @@ -32,7 +32,7 @@ lib_deps = bblanchon/ArduinoJson @ ^6.21.3 https://github.com/JChristensen/Timezone @ ^1.2.4 olikraus/U8g2 @ ^2.35.9 - https://github.com/zinggjm/GxEPD2#1.5.3 + https://github.com/zinggjm/GxEPD2#1.5.5 build_flags = -std=c++17 -std=gnu++17 @@ -201,7 +201,7 @@ lib_deps = bblanchon/ArduinoJson @ ^6.21.3 https://github.com/JChristensen/Timezone @ ^1.2.4 olikraus/U8g2 @ ^2.35.9 - https://github.com/zinggjm/GxEPD2#1.5.3 + https://github.com/zinggjm/GxEPD2#1.5.5 build_flags = ${env.build_flags} -D ETHERNET -DRELEASE @@ -224,7 +224,7 @@ lib_deps = bblanchon/ArduinoJson @ ^6.21.3 https://github.com/JChristensen/Timezone @ ^1.2.4 olikraus/U8g2 @ ^2.35.9 - https://github.com/zinggjm/GxEPD2#1.5.3 + https://github.com/zinggjm/GxEPD2#1.5.5 build_flags = ${env.build_flags} -D ETHERNET -DRELEASE @@ -418,7 +418,7 @@ lib_deps = bblanchon/ArduinoJson @ ^6.21.3 https://github.com/JChristensen/Timezone @ ^1.2.4 olikraus/U8g2 @ ^2.35.9 - https://github.com/zinggjm/GxEPD2#1.5.3 + https://github.com/zinggjm/GxEPD2#1.5.5 upload_protocol = esp-builtin build_flags = ${env.build_flags} -DETHERNET @@ -463,7 +463,7 @@ lib_deps = bblanchon/ArduinoJson @ ^6.21.3 https://github.com/JChristensen/Timezone @ ^1.2.4 olikraus/U8g2 @ ^2.35.9 - https://github.com/zinggjm/GxEPD2#1.5.3 + https://github.com/zinggjm/GxEPD2#1.5.5 upload_protocol = esp-builtin build_flags = ${env.build_flags} -DETHERNET From 315541ea51d5573a48b78c09e0e73a07b607868c Mon Sep 17 00:00:00 2001 From: lumapu Date: Sun, 11 Feb 2024 22:44:32 +0100 Subject: [PATCH 169/179] 0.8.79 * fix `opendtufusion` build (started only once USB-console was connected) * code quality improvments --- patches/GxEPD2_SW_SPI.patch | 56 ++++++++++++++++++++++++------------- src/CHANGES.md | 4 +++ src/defines.h | 2 +- src/hm/hmRadio.h | 2 +- src/platformio.ini | 12 ++++---- 5 files changed, 48 insertions(+), 28 deletions(-) diff --git a/patches/GxEPD2_SW_SPI.patch b/patches/GxEPD2_SW_SPI.patch index 87458cce..dc3fa9ca 100644 --- a/patches/GxEPD2_SW_SPI.patch +++ b/patches/GxEPD2_SW_SPI.patch @@ -1,5 +1,5 @@ diff --git a/src/GxEPD2_EPD.cpp b/src/GxEPD2_EPD.cpp -index 40b1b13..d0dbdba 100644 +index 8df8bef..91d7f49 100644 --- a/src/GxEPD2_EPD.cpp +++ b/src/GxEPD2_EPD.cpp @@ -19,9 +19,9 @@ @@ -14,25 +14,29 @@ index 40b1b13..d0dbdba 100644 { _initial_write = true; _initial_refresh = true; -@@ -61,7 +61,6 @@ void GxEPD2_EPD::init(uint32_t serial_diag_bitrate, bool initial, uint16_t reset - digitalWrite(_cs, HIGH); // set (needed e.g. for RP2040) - } - _reset(); -- _pSPIx->begin(); // may steal _rst pin (Waveshare Pico-ePaper-2.9) - if (_rst >= 0) - { - digitalWrite(_rst, HIGH); // preset (less glitch for any analyzer) -@@ -84,14 +83,30 @@ void GxEPD2_EPD::init(uint32_t serial_diag_bitrate, bool initial, uint16_t reset +@@ -71,27 +71,30 @@ void GxEPD2_EPD::init(uint32_t serial_diag_bitrate, bool initial, uint16_t reset { pinMode(_busy, INPUT); } +- _pSPIx->begin(); +- if (_busy == MISO) // may be overridden +- { +- pinMode(_busy, INPUT); +- } +- if (_dc == MISO) // may be overridden, TTGO T5 V2.66 +- { +- pinMode(_dc, OUTPUT); +- } +- if (_cs == MISO) // may be overridden + if (_sck < 0) SPI.begin(); +} + +void GxEPD2_EPD::init(int16_t sck, int16_t mosi, uint32_t serial_diag_bitrate, bool initial, uint16_t reset_duration, bool pulldown_rst_mode) +{ + if ((sck >= 0) && (mosi >= 0)) -+ { + { +- pinMode(_cs, INPUT); +- } + _sck = sck; + _mosi = mosi; + digitalWrite(_sck, LOW); @@ -54,7 +58,7 @@ index 40b1b13..d0dbdba 100644 } void GxEPD2_EPD::setBusyCallback(void (*busyCallback)(const void*), const void* busy_callback_parameter) -@@ -100,12 +115,6 @@ void GxEPD2_EPD::setBusyCallback(void (*busyCallback)(const void*), const void* +@@ -100,12 +103,6 @@ void GxEPD2_EPD::setBusyCallback(void (*busyCallback)(const void*), const void* _busy_callback_parameter = busy_callback_parameter; } @@ -67,7 +71,7 @@ index 40b1b13..d0dbdba 100644 void GxEPD2_EPD::_reset() { if (_rst >= 0) -@@ -174,115 +183,201 @@ void GxEPD2_EPD::_waitWhileBusy(const char* comment, uint16_t busy_time) +@@ -174,115 +171,201 @@ void GxEPD2_EPD::_waitWhileBusy(const char* comment, uint16_t busy_time) void GxEPD2_EPD::_writeCommand(uint8_t c) { @@ -300,10 +304,21 @@ index 40b1b13..d0dbdba 100644 + _endTransaction(); } diff --git a/src/GxEPD2_EPD.h b/src/GxEPD2_EPD.h -index 3daf37e..96198c2 100644 +index 34c1145..c480b7d 100644 --- a/src/GxEPD2_EPD.h +++ b/src/GxEPD2_EPD.h -@@ -35,6 +35,7 @@ class GxEPD2_EPD +@@ -8,6 +8,10 @@ + // Version: see library.properties + // + // Library: https://github.com/ZinggJM/GxEPD2 ++// To use SW SPI with GxEPD2: ++// add the special call to the added init method BEFORE the normal init method: ++// display.epd2.init(SW_SCK, SW_MOSI, 115200, true, 20, false); // define or replace SW_SCK, SW_MOSI ++// display.init(115200); // needed to init upper level + + #ifndef _GxEPD2_EPD_H_ + #define _GxEPD2_EPD_H_ +@@ -35,6 +39,7 @@ class GxEPD2_EPD uint16_t w, uint16_t h, GxEPD2::Panel p, bool c, bool pu, bool fpu); virtual void init(uint32_t serial_diag_bitrate = 0); // serial_diag_bitrate = 0 : disabled virtual void init(uint32_t serial_diag_bitrate, bool initial, uint16_t reset_duration = 10, bool pulldown_rst_mode = false); @@ -311,7 +326,7 @@ index 3daf37e..96198c2 100644 virtual void end(); // release SPI and control pins // Support for Bitmaps (Sprites) to Controller Buffer and to Screen virtual void clearScreen(uint8_t value) = 0; // init controller memory and screen (default white) -@@ -97,7 +98,6 @@ class GxEPD2_EPD +@@ -97,7 +102,6 @@ class GxEPD2_EPD { return (a > b ? a : b); }; @@ -319,7 +334,7 @@ index 3daf37e..96198c2 100644 protected: void _reset(); void _waitWhileBusy(const char* comment = 0, uint16_t busy_time = 5000); -@@ -111,8 +111,14 @@ class GxEPD2_EPD +@@ -111,17 +115,22 @@ class GxEPD2_EPD void _startTransfer(); void _transfer(uint8_t value); void _endTransfer(); @@ -331,11 +346,12 @@ index 3daf37e..96198c2 100644 + void _readData(uint8_t* data, uint16_t n); protected: - int16_t _cs, _dc, _rst, _busy, _busy_level; -+ int16_t _cs, _dc, _rst, _busy, _busy_level, _sck, _mosi; ++ int16_t _cs, _dc, _rst, _busy, _busy_level, _sck, _mosi;; uint32_t _busy_timeout; bool _diag_enabled, _pulldown_rst_mode; - SPIClass* _pSPIx; -@@ -121,7 +127,7 @@ class GxEPD2_EPD +- SPIClass* _pSPIx; + SPISettings _spi_settings; + bool _initial_write, _initial_refresh; bool _power_is_on, _using_partial_mode, _hibernating; bool _init_display_done; uint16_t _reset_duration; diff --git a/src/CHANGES.md b/src/CHANGES.md index aa63153f..dff1bb30 100644 --- a/src/CHANGES.md +++ b/src/CHANGES.md @@ -1,5 +1,9 @@ # Development Changes +## 0.8.79 - 2024-02-11 +* fix `opendtufusion` build (started only once USB-console was connected) +* code quality improvments + ## 0.8.78 - 2024-02-10 * finalized API token access #1415 * possible fix of MqTT fix "total values are sent to often" #1421 diff --git a/src/defines.h b/src/defines.h index ab685698..40084c47 100644 --- a/src/defines.h +++ b/src/defines.h @@ -13,7 +13,7 @@ //------------------------------------- #define VERSION_MAJOR 0 #define VERSION_MINOR 8 -#define VERSION_PATCH 78 +#define VERSION_PATCH 79 //------------------------------------- typedef struct { diff --git a/src/hm/hmRadio.h b/src/hm/hmRadio.h index b01df277..d1d24364 100644 --- a/src/hm/hmRadio.h +++ b/src/hm/hmRadio.h @@ -159,7 +159,7 @@ class HmRadio : public Radio { mTimeslotStart = millis(); tempRxChIdx = mRxChIdx; // might be better to start off with one channel less? mRxPendular = false; - mNRFloopChannels = (mLastIv->mCmd == MI_REQ_CH1 || mLastIv->mCmd == MI_REQ_CH1); + mNRFloopChannels = (mLastIv->mCmd == MI_REQ_CH1); innerLoopTimeout = DURATION_LISTEN_MIN; } diff --git a/src/platformio.ini b/src/platformio.ini index 0a76a59d..f949aa37 100644 --- a/src/platformio.ini +++ b/src/platformio.ini @@ -32,7 +32,7 @@ lib_deps = bblanchon/ArduinoJson @ ^6.21.3 https://github.com/JChristensen/Timezone @ ^1.2.4 olikraus/U8g2 @ ^2.35.9 - https://github.com/zinggjm/GxEPD2#1.5.5 + https://github.com/zinggjm/GxEPD2#1.5.3 build_flags = -std=c++17 -std=gnu++17 @@ -201,7 +201,7 @@ lib_deps = bblanchon/ArduinoJson @ ^6.21.3 https://github.com/JChristensen/Timezone @ ^1.2.4 olikraus/U8g2 @ ^2.35.9 - https://github.com/zinggjm/GxEPD2#1.5.5 + https://github.com/zinggjm/GxEPD2#1.5.3 build_flags = ${env.build_flags} -D ETHERNET -DRELEASE @@ -224,7 +224,7 @@ lib_deps = bblanchon/ArduinoJson @ ^6.21.3 https://github.com/JChristensen/Timezone @ ^1.2.4 olikraus/U8g2 @ ^2.35.9 - https://github.com/zinggjm/GxEPD2#1.5.5 + https://github.com/zinggjm/GxEPD2#1.5.3 build_flags = ${env.build_flags} -D ETHERNET -DRELEASE @@ -350,7 +350,7 @@ build_flags = ${env.build_flags} -DDEF_LED1=17 -DLED_ACTIVE_HIGH -DARDUINO_USB_MODE=1 - -DARDUINO_USB_CDC_ON_BOOT=1 + #-DARDUINO_USB_CDC_ON_BOOT=1 monitor_filters = esp32_exception_decoder, colorize @@ -418,7 +418,7 @@ lib_deps = bblanchon/ArduinoJson @ ^6.21.3 https://github.com/JChristensen/Timezone @ ^1.2.4 olikraus/U8g2 @ ^2.35.9 - https://github.com/zinggjm/GxEPD2#1.5.5 + https://github.com/zinggjm/GxEPD2#1.5.3 upload_protocol = esp-builtin build_flags = ${env.build_flags} -DETHERNET @@ -463,7 +463,7 @@ lib_deps = bblanchon/ArduinoJson @ ^6.21.3 https://github.com/JChristensen/Timezone @ ^1.2.4 olikraus/U8g2 @ ^2.35.9 - https://github.com/zinggjm/GxEPD2#1.5.5 + https://github.com/zinggjm/GxEPD2#1.5.3 upload_protocol = esp-builtin build_flags = ${env.build_flags} -DETHERNET From 31232bfd80b4f4405a2f8faed0f0cc468ad5dcf5 Mon Sep 17 00:00:00 2001 From: lumapu Date: Mon, 12 Feb 2024 22:32:13 +0100 Subject: [PATCH 170/179] 0.8.80 * optimize API authentication, Error-Codes #1415 * breaking change: authentication API command changed #1415 * breaking change: limit has to be send als `float`, `0.0 .. 100.0` #1415 * updated documentation #1415 * fix don't send control command twice #1426 --- README.md | 8 ++--- manual/User_Manual.md | 55 +++++++++++++++++++++++++-------- src/CHANGES.md | 7 +++++ src/defines.h | 2 +- src/hm/hmInverter.h | 5 +-- src/publisher/pubMqtt.h | 2 +- src/web/RestApi.h | 37 ++++++++++++---------- src/web/html/visualization.html | 15 +++++++-- src/web/lang.h | 30 ------------------ src/web/lang.json | 25 +++++++++++++++ 10 files changed, 116 insertions(+), 70 deletions(-) diff --git a/README.md b/README.md index 7a51a98a..16b2a256 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ Table of approaches: | Board | MI | HM | HMS/HMT | comment | HowTo start | | ------ | -- | -- | ------- | ------- | ---------- | -| [ESP8266/ESP32, C++](Getting_Started.md) | ✔️ | ✔️ | ✔️ | 👈 the most effort is spent here | [create your own DTU](https://ahoydtu.de/getting_started/) | +| [ESP8266/ESP32, C++](manual/Getting_Started.md) | ✔️ | ✔️ | ✔️ | 👈 the most effort is spent here | [create your own DTU](https://ahoydtu.de/getting_started/) | | [Arduino Nano, C++](tools/nano/NRF24_SendRcv/) | ❌ | ✔️ | ❌ | | | [Raspberry Pi, Python](tools/rpi/) | ❌ | ✔️ | ❌ | | | [Others, C/C++](tools/nano/NRF24_SendRcv/) | ❌ | ✔️ | ❌ | | @@ -39,11 +39,11 @@ Table of approaches: ⚠️ **Warning: HMS-XXXXW-2T WiFi inverters are not supported. They have a 'W' in their name and a DTU serial number on its sticker** ## Getting Started -1. [Guide how to start with a ESP module](Getting_Started.md) +1. [Guide how to start with a ESP module](manual/Getting_Started.md) 2. [ESP Webinstaller (Edge / Chrome Browser only)](https://ahoydtu.de/web_install) -3. [Ahoy Configuration ](ahoy_config.md) +3. [Ahoy Configuration ](manual/ahoy_config.md) ## Our Website [https://ahoydtu.de](https://ahoydtu.de) @@ -64,4 +64,4 @@ If you encounter any problems, use the issue tracker on Github. Provide a detail - [OpenDTU](https://github.com/tbnobody/OpenDTU) <- Our sister project ✨ for Hoymiles HM- and HMS-/HMT-series (for ESP32 only!) - [hms-mqtt-publisher](https://github.com/DennisOSRM/hms-mqtt-publisher) - <- a project which can handle WiFi inverters like HMS-XXXXW-2T \ No newline at end of file + <- a project which can handle WiFi inverters like HMS-XXXXW-2T diff --git a/manual/User_Manual.md b/manual/User_Manual.md index 53da34f4..b5cf5a7e 100644 --- a/manual/User_Manual.md +++ b/manual/User_Manual.md @@ -166,6 +166,8 @@ inverter/ctrl/limit/0 600W ### Power Limit persistent This feature was removed. The persisten limit should not be modified cyclic by a script because of potential wearout of the flash inside the inverter. + + ## Control via REST API ### Generic Information @@ -174,6 +176,46 @@ The rest API works with *JSON* POST requests. All the following instructions mus 👆 `` is the number of the specific inverter in the setup page. +### Authentication (new for versions > `0.8.79`) + +The authentication is only needed if a password was set. +To authenticate from API you have to add the following `JSON` to your request: + +```json +{ + "auth": +} +``` +`` is your DTU password in plain text. + +As Response you get the following `JSON` if successful: + +```json +{ + "success": true, + "token": "" +} +``` +Where `` is a random token with a length of 16 characters. + +For all following commands you have only to include the token into your `JSON`: +```json +{ + "token": "" +} +``` + +ℹ️ Do not pass the plain text password with each command. Authenticate once and then use the token for all following commands. The token expires once the token wasn't sent for 20 minutes. + +If the authentication fails or the token is expired you will receive the following `JSON`: + +```json +{ + "success": false, + "error": "ERR_PROTECTED" +} +``` + ### Inverter Power (On / Off) ```json @@ -245,19 +287,6 @@ The `VALUE` represents a percent number in a range of `[2.0 .. 100.0]` The `VALUE` represents watts in a range of `[1.0 .. 6553.5]` - -### Developer Information REST API (obsolete) -In the same approach as for MQTT any other SubCmd and also MainCmd can be applied and the response payload can be observed in the serial logs. Eg. request the Alarm-Data from the Alarm-Index 5 from inverter 0 will look like this: -```json -{ - "inverter":0, - "tx_request": 21, - "cmd": 17, - "payload": 5, - "payload2": 0 -} -``` - ## Zero Export Control (needs rework) * You can use the mqtt topic `/devcontrol//11` with a number as payload (eg. 300 -> 300 Watt) to set the power limit to the published number in Watt. (In regular cases the inverter will use the new set point within one intervall period; to verify this see next bullet) * You can check the inverter set point for the power limit control on the topic `//ch0/PowerLimit` 👆 This value is ALWAYS in percent of the maximum power limit of the inverter. In regular cases this value will be updated within approx. 15 seconds. (depends on request intervall) diff --git a/src/CHANGES.md b/src/CHANGES.md index dff1bb30..714dc8cc 100644 --- a/src/CHANGES.md +++ b/src/CHANGES.md @@ -1,5 +1,12 @@ # Development Changes +## 0.8.80 - 2024-02-12 +* optimize API authentication, Error-Codes #1415 +* breaking change: authentication API command changed #1415 +* breaking change: limit has to be send als `float`, `0.0 .. 100.0` #1415 +* updated documentation #1415 +* fix don't send control command twice #1426 + ## 0.8.79 - 2024-02-11 * fix `opendtufusion` build (started only once USB-console was connected) * code quality improvments diff --git a/src/defines.h b/src/defines.h index 40084c47..8913db5e 100644 --- a/src/defines.h +++ b/src/defines.h @@ -13,7 +13,7 @@ //------------------------------------- #define VERSION_MAJOR 0 #define VERSION_MINOR 8 -#define VERSION_PATCH 79 +#define VERSION_PATCH 80 //------------------------------------- typedef struct { diff --git a/src/hm/hmInverter.h b/src/hm/hmInverter.h index b6dc93fa..f608908e 100644 --- a/src/hm/hmInverter.h +++ b/src/hm/hmInverter.h @@ -163,9 +163,10 @@ class Inverter { void tickSend(std::function cb) { if(mDevControlRequest) { - if(InverterStatus::OFF != status) + if(InverterStatus::OFF != status) { cb(devControlCmd, true); - else + devControlCmd = InitDataState; + } else DPRINTLN(DBG_WARN, F("Inverter is not avail")); mDevControlRequest = false; } else if (IV_MI != ivGen) { // HM / HMS / HMT diff --git a/src/publisher/pubMqtt.h b/src/publisher/pubMqtt.h index 1b9e35ef..369d9ead 100644 --- a/src/publisher/pubMqtt.h +++ b/src/publisher/pubMqtt.h @@ -316,7 +316,7 @@ class PubMqtt { if(NULL == strstr(topic, "limit")) root[F("val")] = atoi(pyld); else - root[F("val")] = (int)(atof(pyld) * 10.0f); + root[F("val")] = atof(pyld); if(pyld[len-1] == 'W') limitAbs = true; diff --git a/src/web/RestApi.h b/src/web/RestApi.h index fadd7277..37e988c0 100644 --- a/src/web/RestApi.h +++ b/src/web/RestApi.h @@ -30,10 +30,6 @@ #define F(sl) (sl) #endif -const uint8_t acList[] = {FLD_UAC, FLD_IAC, FLD_PAC, FLD_F, FLD_PF, FLD_T, FLD_YT, FLD_YD, FLD_PDC, FLD_EFF, FLD_Q, FLD_MP}; -const uint8_t acListHmt[] = {FLD_UAC_1N, FLD_IAC_1, FLD_PAC, FLD_F, FLD_PF, FLD_T, FLD_YT, FLD_YD, FLD_PDC, FLD_EFF, FLD_Q, FLD_MP}; -const uint8_t dcList[] = {FLD_UDC, FLD_IDC, FLD_PDC, FLD_YD, FLD_YT, FLD_IRR, FLD_MP}; - template class RestApi { public: @@ -831,14 +827,16 @@ class RestApi { } bool setCtrl(JsonObject jsonIn, JsonObject jsonOut, const char *clientIP) { - if(F("auth") == jsonIn[F("cmd")]) { - if(String(jsonIn["val"]) == String(mConfig->sys.adminPwd)) - jsonOut["token"] = mApp->unlock(clientIP, false); - else { - jsonOut[F("error")] = F(AUTH_ERROR); + if(jsonIn.containsKey(F("auth"))) { + if(String(jsonIn[F("auth")]) == String(mConfig->sys.adminPwd)) { + jsonOut[F("token")] = mApp->unlock(clientIP, false); + jsonIn[F("token")] = jsonOut[F("token")]; + } else { + jsonOut[F("error")] = F("ERR_AUTH"); return false; } - return true; + if(!jsonIn.containsKey(F("cmd"))) + return true; } if(isProtected(jsonIn, jsonOut, clientIP)) @@ -847,7 +845,7 @@ class RestApi { Inverter<> *iv = mSys->getInverterByPos(jsonIn[F("id")]); bool accepted = true; if(NULL == iv) { - jsonOut[F("error")] = F(INV_INDEX_INVALID) + jsonIn[F("id")].as(); + jsonOut[F("error")] = F("ERR_INDEX"); return false; } jsonOut[F("id")] = jsonIn[F("id")]; @@ -857,7 +855,7 @@ class RestApi { else if(F("restart") == jsonIn[F("cmd")]) accepted = iv->setDevControlRequest(Restart); else if(0 == strncmp("limit_", jsonIn[F("cmd")].as(), 6)) { - iv->powerLimit[0] = jsonIn["val"]; + iv->powerLimit[0] = static_cast(jsonIn["val"].as() * 10.0); if(F("limit_persistent_relative") == jsonIn[F("cmd")]) iv->powerLimit[1] = RelativPersistent; else if(F("limit_persistent_absolute") == jsonIn[F("cmd")]) @@ -874,12 +872,12 @@ class RestApi { DPRINTLN(DBG_INFO, F("dev cmd")); iv->setDevCommand(jsonIn[F("val")].as()); } else { - jsonOut[F("error")] = F(UNKNOWN_CMD) + jsonIn["cmd"].as() + "'"; + jsonOut[F("error")] = F("ERR_UNKNOWN_CMD"); return false; } if(!accepted) { - jsonOut[F("error")] = F(INV_DOES_NOT_ACCEPT_LIMIT_AT_MOMENT); + jsonOut[F("error")] = F("ERR_LIMIT_NOT_ACCEPT"); return false; } @@ -930,7 +928,7 @@ class RestApi { iv->config->disNightCom = jsonIn[F("disnightcom")]; mApp->saveSettings(false); // without reboot } else { - jsonOut[F("error")] = F(UNKNOWN_CMD); + jsonOut[F("error")] = F("ERR_UNKNOWN_CMD"); return false; } @@ -947,7 +945,7 @@ class RestApi { if(!mApp->isProtected(clientIP, token, false)) return false; - jsonOut[F("error")] = F(IS_PROTECTED); + jsonOut[F("error")] = F("ERR_PROTECTED"); return true; } } @@ -955,6 +953,13 @@ class RestApi { return false; } + private: + constexpr static uint8_t acList[] = {FLD_UAC, FLD_IAC, FLD_PAC, FLD_F, FLD_PF, FLD_T, FLD_YT, + FLD_YD, FLD_PDC, FLD_EFF, FLD_Q, FLD_MP}; + constexpr static uint8_t acListHmt[] = {FLD_UAC_1N, FLD_IAC_1, FLD_PAC, FLD_F, FLD_PF, FLD_T, + FLD_YT, FLD_YD, FLD_PDC, FLD_EFF, FLD_Q, FLD_MP}; + constexpr static uint8_t dcList[] = {FLD_UDC, FLD_IDC, FLD_PDC, FLD_YD, FLD_YT, FLD_IRR, FLD_MP}; + private: IApp *mApp = nullptr; HMSYSTEM *mSys = nullptr; diff --git a/src/web/html/visualization.html b/src/web/html/visualization.html index 1ce4e264..81962add 100644 --- a/src/web/html/visualization.html +++ b/src/web/html/visualization.html @@ -22,6 +22,15 @@ var total = Array(6).fill(0); var tPwrAck; + function getErrStr(code) { + if("ERR_AUTH") return "{#ERR_AUTH}" + if("ERR_INDEX") return "{#ERR_INDEX}" + if("ERR_UNKNOWN_CMD") return "{#ERR_UNKNOWN_CMD}" + if("ERR_LIMIT_NOT_ACCEPT") return "{#ERR_LIMIT_NOT_ACCEPT}" + if("ERR_UNKNOWN_CMD") return "{#ERR_AUTH}" + return "n/a" + } + function parseGeneric(obj) { if(true == exeOnce){ parseNav(obj); @@ -457,7 +466,7 @@ obj.id = id obj.token = "*" obj.cmd = cmd - obj.val = Math.round(val*10) + obj.val = val getAjax("/api/ctrl", ctrlCb, "POST", JSON.stringify(obj)) } @@ -477,7 +486,7 @@ tPwrAck = window.setInterval("getAjax('/api/inverter/pwrack/" + obj.id + "', updatePwrAck)", 1000); } else - e.innerHTML = "{#ERROR}: " + obj["error"]; + e.innerHTML = "{#ERROR}: " + getErrStr(obj.error); } function ctrlCb2(obj) { @@ -485,7 +494,7 @@ if(obj.success) e.innerHTML = "{#COMMAND_RECEIVED}"; else - e.innerHTML = "{#ERROR}: " + obj["error"]; + e.innerHTML = "{#ERROR}: " + getErrStr(obj.error); } function updatePwrAck(obj) { diff --git a/src/web/lang.h b/src/web/lang.h index 1e066928..fb5506ee 100644 --- a/src/web/lang.h +++ b/src/web/lang.h @@ -24,36 +24,6 @@ #define WAS_IN_CH_12_TO_14 "Your ESP was in wifi channel 12 to 14. It may cause reboots of your AhoyDTU" #endif -#ifdef LANG_DE - #define INV_INDEX_INVALID "Wechselrichterindex ungültig; " -#else /*LANG_EN*/ - #define INV_INDEX_INVALID "inverter index invalid: " -#endif - -#ifdef LANG_DE - #define AUTH_ERROR "Authentifizierungsfehler" -#else /*LANG_EN*/ - #define AUTH_ERROR "authentication error" -#endif - -#ifdef LANG_DE - #define UNKNOWN_CMD "unbekanntes Kommando: '" -#else /*LANG_EN*/ - #define UNKNOWN_CMD "unknown cmd: '" -#endif - -#ifdef LANG_DE - #define IS_PROTECTED "nicht angemeldet, Kommando nicht möglich!" -#else /*LANG_EN*/ - #define IS_PROTECTED "not logged in, command not possible!" -#endif - -#ifdef LANG_DE - #define INV_DOES_NOT_ACCEPT_LIMIT_AT_MOMENT "Leistungsbegrenzung / Ansteuerung aktuell nicht möglich" -#else /*LANG_EN*/ - #define INV_DOES_NOT_ACCEPT_LIMIT_AT_MOMENT "inverter does not accept dev control request at this moment" -#endif - #ifdef LANG_DE #define PATH_NOT_FOUND "Pfad nicht gefunden: " #else /*LANG_EN*/ diff --git a/src/web/lang.json b/src/web/lang.json index 1e79f8bf..335f0d2a 100644 --- a/src/web/lang.json +++ b/src/web/lang.json @@ -1432,6 +1432,31 @@ "token": "INV_ACK", "en": "inverter acknowledged active power control command", "de": "Wechselrichter hat die Leistungsbegrenzung akzeptiert" + }, + { + "token": "ERR_AUTH", + "en": "authentication error", + "de": "Authentifizierungsfehler" + }, + { + "token": "ERR_INDEX", + "en": "inverter index invalid", + "de": "Wechselrichterindex ungültig" + }, + { + "token": "ERR_UNKNOWN_CMD", + "en": "unknown cmd", + "de": "unbekanntes Kommando" + }, + { + "token": "ERR_LIMIT_NOT_ACCEPT", + "en": "inverter does not accept dev control request at this moment", + "de": "Leistungsbegrenzung / Ansteuerung aktuell nicht möglich" + }, + { + "token": "ERR_PROTECTED", + "en": "not logged in, command not possible!", + "de": "nicht angemeldet, Kommando nicht möglich!" } ] }, From bd532805a66f64bae723b5a62f38668e84b7fb34 Mon Sep 17 00:00:00 2001 From: lumapu Date: Tue, 13 Feb 2024 23:46:15 +0100 Subject: [PATCH 171/179] 0.8.81 * fixed authentication with empty token #1415 * added new setting for future function to send log via MqTT * combined firmware and hardware version to JSON topics (MqTT) #1212 --- src/CHANGES.md | 5 +++ src/config/settings.h | 9 ++++- src/defines.h | 2 +- src/publisher/pubMqttIvData.h | 59 +++++++++++++++++++--------- src/web/Protection.h | 2 +- src/web/RestApi.h | 1 + src/web/html/setup.html | 73 +++++++++++++---------------------- src/web/lang.json | 5 +++ src/web/web.h | 1 + 9 files changed, 90 insertions(+), 67 deletions(-) diff --git a/src/CHANGES.md b/src/CHANGES.md index 714dc8cc..f39a5640 100644 --- a/src/CHANGES.md +++ b/src/CHANGES.md @@ -1,5 +1,10 @@ # Development Changes +## 0.8.81 - 2024-02-13 +* fixed authentication with empty token #1415 +* added new setting for future function to send log via MqTT +* combined firmware and hardware version to JSON topics (MqTT) #1212 + ## 0.8.80 - 2024-02-12 * optimize API authentication, Error-Codes #1415 * breaking change: authentication API command changed #1415 diff --git a/src/config/settings.h b/src/config/settings.h index 0cb9ed4e..18725b48 100644 --- a/src/config/settings.h +++ b/src/config/settings.h @@ -31,7 +31,7 @@ * https://arduino-esp8266.readthedocs.io/en/latest/filesystem.html#flash-layout * */ -#define CONFIG_VERSION 10 +#define CONFIG_VERSION 11 #define PROT_MASK_INDEX 0x0001 @@ -120,6 +120,7 @@ typedef struct { bool debug; bool privacyLog; bool printWholeTrace; + bool log2mqtt; } cfgSerial_t; typedef struct { @@ -436,6 +437,7 @@ class settings { mCfg.serial.debug = false; mCfg.serial.privacyLog = true; mCfg.serial.printWholeTrace = false; + mCfg.serial.log2mqtt = false; mCfg.mqtt.port = DEF_MQTT_PORT; snprintf(mCfg.mqtt.broker, MQTT_ADDR_LEN, "%s", DEF_MQTT_BROKER); @@ -509,6 +511,9 @@ class settings { mCfg.sys.region = 0; // Europe mCfg.sys.timezone = 1; } + if(mCfg.configVersion < 11) { + mCfg.serial.log2mqtt = false; + } } } @@ -658,11 +663,13 @@ class settings { obj[F("debug")] = mCfg.serial.debug; obj[F("prv")] = (bool) mCfg.serial.privacyLog; obj[F("trc")] = (bool) mCfg.serial.printWholeTrace; + obj[F("mqtt")] = (bool) mCfg.serial.log2mqtt; } else { getVal(obj, F("show"), &mCfg.serial.showIv); getVal(obj, F("debug"), &mCfg.serial.debug); getVal(obj, F("prv"), &mCfg.serial.privacyLog); getVal(obj, F("trc"), &mCfg.serial.printWholeTrace); + getVal(obj, F("mqtt"), &mCfg.serial.log2mqtt); } } diff --git a/src/defines.h b/src/defines.h index 8913db5e..200c2561 100644 --- a/src/defines.h +++ b/src/defines.h @@ -13,7 +13,7 @@ //------------------------------------- #define VERSION_MAJOR 0 #define VERSION_MINOR 8 -#define VERSION_PATCH 80 +#define VERSION_PATCH 81 //------------------------------------- typedef struct { diff --git a/src/publisher/pubMqttIvData.h b/src/publisher/pubMqttIvData.h index 06f8e629..c430da85 100644 --- a/src/publisher/pubMqttIvData.h +++ b/src/publisher/pubMqttIvData.h @@ -6,6 +6,7 @@ #ifndef __PUB_MQTT_IV_DATA_H__ #define __PUB_MQTT_IV_DATA_H__ +#include #include "../utils/dbg.h" #include "../hm/hmSystem.h" #include "pubMqttDefs.h" @@ -107,14 +108,14 @@ class PubMqttIvData { if(found) { record_t<> *rec = mIv->getRecordStruct(mCmd); if(MqttSentStatus::NEW_DATA == rec->mqttSentStatus) { - snprintf(mSubTopic, 32 + MAX_NAME_LENGTH, "%s/last_success", mIv->config->name); - snprintf(mVal, 40, "%d", mIv->getLastTs(rec)); - mPublish(mSubTopic, mVal, true, QOS_0); + snprintf(mSubTopic.data(), mSubTopic.size(), "%s/last_success", mIv->config->name); + snprintf(mVal.data(), mVal.size(), "%d", mIv->getLastTs(rec)); + mPublish(mSubTopic.data(), mVal.data(), true, QOS_0); if((mIv->ivGen == IV_HMS) || (mIv->ivGen == IV_HMT)) { - snprintf(mSubTopic, 32 + MAX_NAME_LENGTH, "%s/ch0/rssi", mIv->config->name); - snprintf(mVal, 40, "%d", mIv->rssi); - mPublish(mSubTopic, mVal, false, QOS_0); + snprintf(mSubTopic.data(), mSubTopic.size(), "%s/ch0/rssi", mIv->config->name); + snprintf(mVal.data(), mVal.size(), "%d", mIv->rssi); + mPublish(mSubTopic.data(), mVal.data(), false, QOS_0); } rec->mqttSentStatus = MqttSentStatus::LAST_SUCCESS_SENT; } @@ -144,7 +145,7 @@ class PubMqttIvData { if(mPos < rec->length) { bool retained = false; - if (mCmd == RealTimeRunData_Debug) { + if (RealTimeRunData_Debug == mCmd) { if((FLD_YT == rec->assign[mPos].fieldId) || (FLD_YD == rec->assign[mPos].fieldId)) retained = true; @@ -176,10 +177,32 @@ class PubMqttIvData { } if (MqttSentStatus::LAST_SUCCESS_SENT == rec->mqttSentStatus) { + if(InverterDevInform_All == mCmd) { + snprintf(mSubTopic.data(), mSubTopic.size(), "%s/firmware", mIv->config->name); + snprintf(mVal.data(), mVal.size(), "{\"version\":%d,\"build_year\":\"%s\",\"build_month_day\":%d,\"build_hour_min\":%d,\"bootloader\":%d}", + mIv->getChannelFieldValue(CH0, FLD_FW_VERSION, rec), + mIv->getChannelFieldValue(CH0, FLD_FW_BUILD_YEAR, rec), + mIv->getChannelFieldValue(CH0, FLD_FW_BUILD_MONTH_DAY, rec), + mIv->getChannelFieldValue(CH0, FLD_FW_BUILD_HOUR_MINUTE, rec), + mIv->getChannelFieldValue(CH0, FLD_BOOTLOADER_VER, rec)); + retained = true; + } else if(InverterDevInform_Simple == mCmd) { + snprintf(mSubTopic.data(), mSubTopic.size(), "%s/hardware", mIv->config->name); + snprintf(mVal.data(), mVal.size(), "{\"part\":%d,\"version\":\"%s\",\"grid_profile_code\":%d,\"grid_profile_version\":%d}", + mIv->getChannelFieldValue(CH0, FLD_PART_NUM, rec), + mIv->getChannelFieldValue(CH0, FLD_HW_VERSION, rec), + mIv->getChannelFieldValue(CH0, FLD_GRID_PROFILE_CODE, rec), + mIv->getChannelFieldValue(CH0, FLD_GRID_PROFILE_VERSION, rec)); + retained = true; + } else { + snprintf(mSubTopic.data(), mSubTopic.size(), "%s/ch%d/%s", mIv->config->name, rec->assign[mPos].ch, fields[rec->assign[mPos].fieldId]); + snprintf(mVal.data(), mVal.size(), "%g", ah::round3(mIv->getValue(mPos, rec))); + } + uint8_t qos = (FLD_ACT_ACTIVE_PWR_LIMIT == rec->assign[mPos].fieldId) ? QOS_2 : QOS_0; - snprintf(mSubTopic, 32 + MAX_NAME_LENGTH, "%s/ch%d/%s", mIv->config->name, rec->assign[mPos].ch, fields[rec->assign[mPos].fieldId]); - snprintf(mVal, 40, "%g", ah::round3(mIv->getValue(mPos, rec))); - mPublish(mSubTopic, mVal, retained, qos); + if((FLD_EVT != rec->assign[mPos].fieldId) + && (FLD_LAST_ALARM_CODE != rec->assign[mPos].fieldId)) + mPublish(mSubTopic.data(), mVal.data(), retained, qos); } mPos++; } else { @@ -192,8 +215,8 @@ class PubMqttIvData { } inline void sendRadioStat(uint8_t start) { - snprintf(mSubTopic, 32 + MAX_NAME_LENGTH, "%s/radio_stat", mIv->config->name); - snprintf(mVal, 140, "{\"tx\":%d,\"success\":%d,\"fail\":%d,\"no_answer\":%d,\"retransmits\":%d,\"lossIvRx\":%d,\"lossIvTx\":%d,\"lossDtuRx\":%d,\"lossDtuTx\":%d}", + snprintf(mSubTopic.data(), mSubTopic.size(), "%s/radio_stat", mIv->config->name); + snprintf(mVal.data(), mVal.size(), "{\"tx\":%d,\"success\":%d,\"fail\":%d,\"no_answer\":%d,\"retransmits\":%d,\"lossIvRx\":%d,\"lossIvTx\":%d,\"lossDtuRx\":%d,\"lossDtuTx\":%d}", mIv->radioStatistics.txCnt, mIv->radioStatistics.rxSuccess, mIv->radioStatistics.rxFail, @@ -203,7 +226,7 @@ class PubMqttIvData { mIv->radioStatistics.ivSent, mIv->radioStatistics.dtuLoss, mIv->radioStatistics.dtuSent); - mPublish(mSubTopic, mVal, false, QOS_0); + mPublish(mSubTopic.data(), mVal.data(), false, QOS_0); } void stateSendTotals() { @@ -240,9 +263,9 @@ class PubMqttIvData { retained = false; break; } - snprintf(mSubTopic, 32 + MAX_NAME_LENGTH, "total/%s", fields[fieldId]); - snprintf(mVal, 40, "%g", ah::round3(mTotal[mPos])); - mPublish(mSubTopic, mVal, retained, QOS_0); + snprintf(mSubTopic.data(), mSubTopic.size(), "total/%s", fields[fieldId]); + snprintf(mVal.data(), mVal.size(), "%g", ah::round3(mTotal[mPos])); + mPublish(mSubTopic.data(), mVal.data(), retained, QOS_0); mPos++; } else { mSendList->pop(); @@ -266,8 +289,8 @@ class PubMqttIvData { uint8_t mPos = 0; bool mRTRDataHasBeenSent = false; - char mSubTopic[32 + MAX_NAME_LENGTH + 1]; - char mVal[140]; + std::array mSubTopic; + std::array mVal; std::queue *mSendList = nullptr; }; diff --git a/src/web/Protection.h b/src/web/Protection.h index 82e4be49..74f04b52 100644 --- a/src/web/Protection.h +++ b/src/web/Protection.h @@ -85,7 +85,7 @@ class Protection { if(isIdentical(clientIp, mApiIp)) return (0 != strncmp(token, mToken.data(), 16)); - return false; + return true; } private: diff --git a/src/web/RestApi.h b/src/web/RestApi.h index 37e988c0..09bba06e 100644 --- a/src/web/RestApi.h +++ b/src/web/RestApi.h @@ -680,6 +680,7 @@ class RestApi { obj[F("debug")] = mConfig->serial.debug; obj[F("priv")] = mConfig->serial.privacyLog; obj[F("wholeTrace")] = mConfig->serial.printWholeTrace; + obj[F("log2mqtt")] = mConfig->serial.log2mqtt; } void getStaticIp(JsonObject obj) { diff --git a/src/web/html/setup.html b/src/web/html/setup.html index 4859fe34..47d935b9 100644 --- a/src/web/html/setup.html +++ b/src/web/html/setup.html @@ -43,24 +43,8 @@
    -
    +
    {#SERIAL_CONSOLE} -
    -
    {#LOG_PRINT_INVERTER_DATA}
    -
    -
    -
    -
    {#LOG_SERIAL_DEBUG}
    -
    -
    -
    -
    {#LOG_PRIVACY_MODE}
    -
    -
    -
    -
    {#LOG_PRINT_TRACES}
    -
    -
    @@ -716,6 +700,13 @@ ivGlob(obj); } + function divRow(item0, item1) { + return ml("div", {class: "row mb-3"}, [ + ml("div", {class: "col-3 mt-2"}, item0), + ml("div", {class: "col-9"}, item1) + ]) + } + function ivModal(obj) { var lines = []; lines.push(ml("tr", {}, [ @@ -743,43 +734,23 @@ var html = ml("div", {}, [ tabs(["{#TAB_GENERAL}", "{#TAB_INPUTS}", "{#TAB_RADIO}", "{#TAB_ADVANCED}"]), ml("div", {id: "div{#TAB_GENERAL}", class: "tab-content"}, [ - ml("div", {class: "row mb-3"}, [ - ml("div", {class: "col-2"}, "{#INV_ENABLE}"), - ml("div", {class: "col-10"}, cbEn) - ]), - ml("div", {class: "row mb-3"}, [ - ml("div", {class: "col-2 mt-2"}, "{#INV_SERIAL}"), - ml("div", {class: "col-10"}, ser) - ]), - ml("div", {class: "row mb-3"}, [ - ml("div", {class: "col-2 mt-2"}, "Name"), - ml("div", {class: "col-10"}, ml("input", {name: "name", class: "text", type: "text", value: obj.name}, null)) - ]) + divRow("{#INV_ENABLE}", cbEn), + divRow("{#INV_SERIAL}", ser), + divRow("Name", ml("input", {name: "name", class: "text", type: "text", value: obj.name}, null)) ]), ml("div", {id: "div{#TAB_INPUTS}", class: "tab-content hide"}, [ ml("div", {class: "row mb-3"}, - ml("table", {class: "table"}, - ml("tbody", {}, lines) - ) + ml("table", {class: "table"}, ml("tbody", {}, lines)) ) ]), ml("div", {id: "div{#TAB_RADIO}", class: "tab-content hide"}, [ ml("input", {type: "hidden", name: "isnrf"}, null), ml("div", {id: "setcmt"}, [ - ml("div", {class: "row mb-3"}, [ - ml("div", {class: "col-3 mt-2"}, "{#INV_FREQUENCY}"), - ml("div", {class: "col-9"}, sel("freq", esp32cmtFreq, obj.freq)) - ]), - ml("div", {class: "row mb-3"}, [ - ml("div", {class: "col-3 mt-2"}, "{#INV_POWER_LEVEL}"), - ml("div", {class: "col-9"}, sel("cmtpa", esp32cmtPa, obj.pa)) - ]), + divRow("{#INV_FREQUENCY}", sel("freq", esp32cmtFreq, obj.freq)), + divRow("{#INV_POWER_LEVEL}", sel("cmtpa", esp32cmtPa, obj.pa)) ]), ml("div", {id: "setnrf"}, - ml("div", {class: "row mb-3"}, [ - ml("div", {class: "col-3 mt-2"}, "{#INV_POWER_LEVEL}"), - ml("div", {class: "col-9"}, sel("nrfpa", nrfPa, obj.pa)) - ]), + divRow("{#INV_POWER_LEVEL}", sel("nrfpa", nrfPa, obj.pa)) ), ]), ml("div", {id: "div{#TAB_ADVANCED}", class: "tab-content hide"}, [ @@ -1026,8 +997,18 @@ /*ENDIF_ESP32*/ function parseSerial(obj) { - for(var i of [["serEn", "show_live_data"], ["serDbg", "debug"], ["priv", "priv"], ["wholeTrace", "wholeTrace"]]) - document.getElementsByName(i[0])[0].checked = obj[i[1]]; + var e = document.getElementById("serialCb") + var l = [["serEn", "show_live_data", "{#LOG_PRINT_INVERTER_DATA}"], ["serDbg", "debug", "{#LOG_SERIAL_DEBUG}"], ["priv", "priv", "{#LOG_PRIVACY_MODE}"], ["wholeTrace", "wholeTrace", "{#LOG_PRINT_TRACES}"], ["log2mqtt", "log2mqtt", "{#LOG_TO_MQTT}"]] + for(var i of l) { + var cb = ml("input", {name: i[0], type: "checkbox"}, null) + cb.checked = obj[i[1]] + e.appendChild( + ml("div", {class: "row mb-3"}, [ + ml("div", {class: "col-8 col-sm-3"}, i[2]), + ml("div", {class: "col-4 col-sm-9"}, cb) + ]) + ) + } } function parseDisplay(obj, type, system) { diff --git a/src/web/lang.json b/src/web/lang.json index 335f0d2a..066370c5 100644 --- a/src/web/lang.json +++ b/src/web/lang.json @@ -203,6 +203,11 @@ "en": "Print whole traces in Log", "de": "alle Informationen in Log schreiben" }, + { + "token": "LOG_TO_MQTT", + "en": "Send Serial debug over MqTT", + "de": "sende serielles Log über MqTT" + }, { "token": "NETWORK", "en": "Network", diff --git a/src/web/web.h b/src/web/web.h index 692dd779..8495ba23 100644 --- a/src/web/web.h +++ b/src/web/web.h @@ -552,6 +552,7 @@ class Web { mConfig->serial.privacyLog = (request->arg("priv") == "on"); mConfig->serial.printWholeTrace = (request->arg("wholeTrace") == "on"); mConfig->serial.showIv = (request->arg("serEn") == "on"); + mConfig->serial.log2mqtt = (request->arg("log2mqtt") == "on"); // display mConfig->plugin.display.pwrSaveAtIvOffline = (request->arg("disp_pwr") == "on"); From 8c132048e678201100bba964805e2ef86fe5364c Mon Sep 17 00:00:00 2001 From: lumapu Date: Thu, 15 Feb 2024 23:22:24 +0100 Subject: [PATCH 172/179] 0.8.82 * fixed crash once firmware version was read and sent via MqTT #1428 * possible fix: reset yield offset on midnight #1429 --- src/CHANGES.md | 4 ++++ src/app.cpp | 3 +-- src/defines.h | 2 +- src/hm/hmInverter.h | 20 ++++++++++---------- src/publisher/pubMqttIvData.h | 22 +++++++++++----------- 5 files changed, 27 insertions(+), 24 deletions(-) diff --git a/src/CHANGES.md b/src/CHANGES.md index f39a5640..75fede55 100644 --- a/src/CHANGES.md +++ b/src/CHANGES.md @@ -1,5 +1,9 @@ # Development Changes +## 0.8.82 - 2024-02-15 +* fixed crash once firmware version was read and sent via MqTT #1428 +* possible fix: reset yield offset on midnight #1429 + ## 0.8.81 - 2024-02-13 * fixed authentication with empty token #1415 * added new setting for future function to send log via MqTT diff --git a/src/app.cpp b/src/app.cpp index bee12387..0bead49e 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -466,8 +466,6 @@ void app:: zeroIvValues(bool checkAvail, bool skipYieldDay) { continue; // skip to next inverter if (!iv->config->enabled) continue; // skip to next inverter - if (iv->commEnabled) - continue; // skip to next inverter if (checkAvail) { if (!iv->isAvailable()) @@ -495,6 +493,7 @@ void app:: zeroIvValues(bool checkAvail, bool skipYieldDay) { pos = iv->getPosByChFld(ch, FLD_MP, rec); iv->setValue(pos, rec, 0.0f); } + iv->resetAlarms(); iv->doCalculations(); } diff --git a/src/defines.h b/src/defines.h index 200c2561..68094ba3 100644 --- a/src/defines.h +++ b/src/defines.h @@ -13,7 +13,7 @@ //------------------------------------- #define VERSION_MAJOR 0 #define VERSION_MINOR 8 -#define VERSION_PATCH 81 +#define VERSION_PATCH 82 //------------------------------------- typedef struct { diff --git a/src/hm/hmInverter.h b/src/hm/hmInverter.h index f608908e..50b4267c 100644 --- a/src/hm/hmInverter.h +++ b/src/hm/hmInverter.h @@ -149,9 +149,6 @@ class Inverter { uint32_t tsMaxAcPower = 0; // holds the timestamp when the MaxAC power was seen bool commEnabled = true; // 'pause night communication' sets this field to false - static uint32_t *timestamp; // system timestamp - static cfgInst_t *generalConfig; // general inverter configuration from setup - public: Inverter() { @@ -826,20 +823,23 @@ class Inverter { radioId.b[0] = 0x01; } - private: - float mOffYD[6], mLastYD[6]; - bool mDevControlRequest = false; // true if change needed - uint8_t mGridLen = 0; - std::array mGridProfile; - uint8_t mAlarmNxtWrPos = 0; // indicates the position in array (rolling buffer) - public: + static uint32_t *timestamp; // system timestamp + static cfgInst_t *generalConfig; // general inverter configuration from setup + uint16_t mDtuRxCnt = 0; uint16_t mDtuTxCnt = 0; uint8_t mGetLossInterval = 0; // request iv every AHOY_GET_LOSS_INTERVAL RealTimeRunData_Debug uint16_t mIvRxCnt = 0; uint16_t mIvTxCnt = 0; uint16_t mAckCount = 0; + + private: + float mOffYD[6], mLastYD[6]; + bool mDevControlRequest = false; // true if change needed + uint8_t mGridLen = 0; + std::array mGridProfile; + uint8_t mAlarmNxtWrPos = 0; // indicates the position in array (rolling buffer) }; template diff --git a/src/publisher/pubMqttIvData.h b/src/publisher/pubMqttIvData.h index c430da85..c3ae3814 100644 --- a/src/publisher/pubMqttIvData.h +++ b/src/publisher/pubMqttIvData.h @@ -179,20 +179,20 @@ class PubMqttIvData { if (MqttSentStatus::LAST_SUCCESS_SENT == rec->mqttSentStatus) { if(InverterDevInform_All == mCmd) { snprintf(mSubTopic.data(), mSubTopic.size(), "%s/firmware", mIv->config->name); - snprintf(mVal.data(), mVal.size(), "{\"version\":%d,\"build_year\":\"%s\",\"build_month_day\":%d,\"build_hour_min\":%d,\"bootloader\":%d}", - mIv->getChannelFieldValue(CH0, FLD_FW_VERSION, rec), - mIv->getChannelFieldValue(CH0, FLD_FW_BUILD_YEAR, rec), - mIv->getChannelFieldValue(CH0, FLD_FW_BUILD_MONTH_DAY, rec), - mIv->getChannelFieldValue(CH0, FLD_FW_BUILD_HOUR_MINUTE, rec), - mIv->getChannelFieldValue(CH0, FLD_BOOTLOADER_VER, rec)); + snprintf(mVal.data(), mVal.size(), "{\"version\":%d,\"build_year\":\"%d\",\"build_month_day\":%d,\"build_hour_min\":%d,\"bootloader\":%d}", + static_cast(mIv->getChannelFieldValue(CH0, FLD_FW_VERSION, rec)), + static_cast(mIv->getChannelFieldValue(CH0, FLD_FW_BUILD_YEAR, rec)), + static_cast(mIv->getChannelFieldValue(CH0, FLD_FW_BUILD_MONTH_DAY, rec)), + static_cast(mIv->getChannelFieldValue(CH0, FLD_FW_BUILD_HOUR_MINUTE, rec)), + static_cast(mIv->getChannelFieldValue(CH0, FLD_BOOTLOADER_VER, rec))); retained = true; } else if(InverterDevInform_Simple == mCmd) { snprintf(mSubTopic.data(), mSubTopic.size(), "%s/hardware", mIv->config->name); - snprintf(mVal.data(), mVal.size(), "{\"part\":%d,\"version\":\"%s\",\"grid_profile_code\":%d,\"grid_profile_version\":%d}", - mIv->getChannelFieldValue(CH0, FLD_PART_NUM, rec), - mIv->getChannelFieldValue(CH0, FLD_HW_VERSION, rec), - mIv->getChannelFieldValue(CH0, FLD_GRID_PROFILE_CODE, rec), - mIv->getChannelFieldValue(CH0, FLD_GRID_PROFILE_VERSION, rec)); + snprintf(mVal.data(), mVal.size(), "{\"part\":%d,\"version\":\"%d\",\"grid_profile_code\":%d,\"grid_profile_version\":%d}", + static_cast(mIv->getChannelFieldValue(CH0, FLD_PART_NUM, rec)), + static_cast(mIv->getChannelFieldValue(CH0, FLD_HW_VERSION, rec)), + static_cast(mIv->getChannelFieldValue(CH0, FLD_GRID_PROFILE_CODE, rec)), + static_cast(mIv->getChannelFieldValue(CH0, FLD_GRID_PROFILE_VERSION, rec))); retained = true; } else { snprintf(mSubTopic.data(), mSubTopic.size(), "%s/ch%d/%s", mIv->config->name, rec->assign[mPos].ch, fields[rec->assign[mPos].fieldId]); From 024445b472005fbe7e71d847abb675053f1fd554 Mon Sep 17 00:00:00 2001 From: lumapu Date: Sat, 17 Feb 2024 00:26:24 +0100 Subject: [PATCH 173/179] 0.8.83 release * updated workflows --- .github/workflows/compile_development.yml | 9 +- .github/workflows/compile_release.yml | 150 ++- src/CHANGES.md | 1295 +-------------------- src/defines.h | 2 +- 4 files changed, 148 insertions(+), 1308 deletions(-) diff --git a/.github/workflows/compile_development.yml b/.github/workflows/compile_development.yml index 66dbf74b..c15bd883 100644 --- a/.github/workflows/compile_development.yml +++ b/.github/workflows/compile_development.yml @@ -1,4 +1,4 @@ -name: Ahoy Dev-Build for ESP8266/ESP32 +name: Ahoy Development on: push: @@ -8,6 +8,7 @@ on: jobs: check: + name: Check Repository runs-on: ubuntu-latest if: github.repository == 'lumapu/ahoy' && github.ref_name == 'development03' continue-on-error: true @@ -15,6 +16,7 @@ jobs: - uses: actions/checkout@v4 build-en: + name: Build Environments (English) needs: check runs-on: ubuntu-latest continue-on-error: true @@ -75,6 +77,7 @@ jobs: path: firmware/* build-de: + name: Build Environments (German) needs: check runs-on: ubuntu-latest continue-on-error: true @@ -135,8 +138,10 @@ jobs: path: firmware/* deploy: + name: Deploy Environments needs: [build-en, build-de] runs-on: ubuntu-latest + continue-on-error: false steps: - uses: actions/checkout@v4 #- name: Copy boot_app0.bin @@ -155,7 +160,7 @@ jobs: - name: Set Version uses: cschleiden/replace-tokens@v1 with: - files: tools/esp8266/User_Manual.md + files: manual/User_Manual.md env: VERSION: ${{ steps.version_name.outputs.name }} diff --git a/.github/workflows/compile_release.yml b/.github/workflows/compile_release.yml index a7e3511d..bf9709a0 100644 --- a/.github/workflows/compile_release.yml +++ b/.github/workflows/compile_release.yml @@ -1,29 +1,50 @@ -name: Ahoy Release for ESP8266/ESP32 +name: Ahoy Release on: push: branches: main - paths: - - 'src/**' # build only when changes occur here - - '.github/workflows/compile_release.yml' - - '!README.md' - - '!CHANGES.md' - - '!User_Manual.md' + paths-ignore: + - '**.md' # Do no build on *.md changes + jobs: build: + name: Build Environments + needs: check runs-on: ubuntu-latest - + if: github.repository == 'lumapu/ahoy' && github.ref_name == 'main' + continue-on-error: false + strategy: + matrix: + variant: + - esp8266 + - esp8266-prometheus + - esp8285 + - esp32-wroom32 + - esp32-wroom32-prometheus + - esp32-wroom32-ethernet + - esp32-s2-mini + - esp32-c3-mini + - opendtufusion + - opendtufusion-ethernet + - esp8266-de + - esp8266-prometheus-de + - esp8285-de + - esp32-wroom32-de + - esp32-wroom32-prometheus-de + - esp32-wroom32-ethernet-de + - esp32-s2-mini-de + - esp32-c3-mini-de + - opendtufusion-de + - opendtufusion-ethernet-de steps: - - uses: actions/checkout@v3 - with: - ref: main - - uses: benjlevesque/short-sha@v2.1 + - uses: actions/checkout@v4 + - uses: benjlevesque/short-sha@v3.0 id: short-sha with: length: 7 - name: Cache Pip - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ~/.cache/pip key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} @@ -31,13 +52,13 @@ jobs: ${{ runner.os }}-pip- - name: Cache PlatformIO - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ~/.platformio key: ${{ runner.os }}-${{ hashFiles('**/lockfiles') }} - name: Setup Python - uses: actions/setup-python@v4.3.0 + uses: actions/setup-python@v5 with: python-version: "3.x" @@ -47,58 +68,91 @@ jobs: pip install --upgrade platformio - name: Run PlatformIO - run: pio run -d src --environment esp8266 --environment esp8266-prometheus --environment esp8285 --environment esp32-wroom32 --environment esp32-wroom32-prometheus --environment esp32-wroom32-ethernet --environment esp32-s2-mini --environment esp32-c3-mini --environment opendtufusion --environment opendtufusion-ethernet + run: pio run -d src -e ${{ matrix.variant }} - - name: Copy boot_app0.bin - run: cp ~/.platformio/packages/framework-arduinoespressif32/tools/partitions/boot_app0.bin src/.pio/build/opendtufusion/ota.bin + - name: Rename Firmware + run: python scripts/getVersion.py ${{ matrix.variant }} >> $GITHUB_OUTPUT + + - name: Create Artifact + uses: actions/upload-artifact@v4 + with: + name: ${{ matrix.variant }} + path: firmware/* - - name: Rename Binary files - id: rename-binary-files - working-directory: src - run: python ../scripts/getVersion.py >> $GITHUB_OUTPUT - - name: Create Release - id: create-release - uses: actions/create-release@v1 + release: + name: Create Release + runs-on: ubuntu-latest + needs: [build] + continue-on-error: false + permissions: + contents: write + + steps: + - name: Get Artifacts + uses: actions/download-artifact@v4 with: - draft: false - prerelease: false - release_name: ${{ steps.rename-binary-files.outputs.name }} - tag_name: ${{ steps.rename-binary-files.outputs.name }} - body_path: src/CHANGES.md - env: - GITHUB_TOKEN: ${{ github.token }} + merge-multiple: true + path: firmware + + - name: Get Version from code + id: version_name + run: python scripts/getVersion.py ${{ matrix.variant }} >> $GITHUB_OUTPUT - name: Set Version uses: cschleiden/replace-tokens@v1 with: - files: User_Manual.md + files: manual/User_Manual.md env: - VERSION: ${{ steps.rename-binary-files.outputs.name }} + VERSION: ${{ steps.version_name.outputs.name }} - - name: Create Artifact - run: zip --junk-paths ${{ steps.rename-binary-files.outputs.name }}.zip src/firmware/* User_Manual.md + - name: Rename firmware directory + run: mv firmware ${{ steps.version_name.outputs.name }} - - name: Upload Release - id: upload-release - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Publish Release + uses: actions/checkout@v3 + uses: ncipollo/release-action@v1 + with: + artifacts: ${{ steps.version_name.outputs.name }} + bodyFile: src/CHANGES.md + commit: "main" + tag: ${{ steps.rename-binary-files.outputs.name }} + name: ${{ steps.rename-binary-files.outputs.name }} + + + deploy: + name: Deploy Environments to fw.ahoydtu.de + needs: [build] + runs-on: ubuntu-latest + continue-on-error: false + steps: + - uses: actions/checkout@v4 + - name: Get Artifacts + uses: actions/download-artifact@v4 with: - upload_url: ${{ steps.create-release.outputs.upload_url }} - asset_path: ./${{ steps.rename-binary-files.outputs.name }}.zip - asset_name: ${{ steps.rename-binary-files.outputs.name }}.zip - asset_content_type: application/zip + merge-multiple: true + path: firmware + + - name: Get Version from code + id: version_name + run: python scripts/getVersion.py ${{ matrix.variant }} >> $GITHUB_OUTPUT + + - name: Set Version + uses: cschleiden/replace-tokens@v1 + with: + files: manual/User_Manual.md + env: + VERSION: ${{ steps.version_name.outputs.name }} - name: Rename firmware directory - run: mv src/firmware src/${{ steps.rename-binary-files.outputs.name }} + run: mv firmware ${{ steps.version_name.outputs.name }} - name: Deploy uses: nogsantos/scp-deploy@master with: - src: src/${{ steps.rename-binary-files.outputs.name }}/ + src: ${{ steps.version_name.outputs.name }}/ host: ${{ secrets.FW_SSH_HOST }} - remote: ${{ secrets.FW_SSH_DIR }}/release + remote: ${{ secrets.FW_SSH_DIR }}/dev port: ${{ secrets.FW_SSH_PORT }} user: ${{ secrets.FW_SSH_USER }} key: ${{ secrets.FW_SSH_KEY }} diff --git a/src/CHANGES.md b/src/CHANGES.md index 75fede55..5fd4fc31 100644 --- a/src/CHANGES.md +++ b/src/CHANGES.md @@ -1,1260 +1,41 @@ -# Development Changes +Changelog v0.8.83 -## 0.8.82 - 2024-02-15 -* fixed crash once firmware version was read and sent via MqTT #1428 -* possible fix: reset yield offset on midnight #1429 - -## 0.8.81 - 2024-02-13 -* fixed authentication with empty token #1415 -* added new setting for future function to send log via MqTT -* combined firmware and hardware version to JSON topics (MqTT) #1212 - -## 0.8.80 - 2024-02-12 -* optimize API authentication, Error-Codes #1415 -* breaking change: authentication API command changed #1415 -* breaking change: limit has to be send als `float`, `0.0 .. 100.0` #1415 -* updated documentation #1415 -* fix don't send control command twice #1426 - -## 0.8.79 - 2024-02-11 -* fix `opendtufusion` build (started only once USB-console was connected) -* code quality improvments - -## 0.8.78 - 2024-02-10 -* finalized API token access #1415 -* possible fix of MqTT fix "total values are sent to often" #1421 -* removed `switchCycle` from `hmsRadio.h` #1412 -* merge PR: Add hint to INV_RESET_MIDNIGHT resp. INV_PAUSE_DURING_NIGHT #1418 -* merge PR: simplify rxOffset logic #1417 -* code quality improvments - -## 0.8.77 - 2024-02-08 -* merge PR: BugFix: ACK #1414 -* fix suspicious if condition #1416 -* prepared API token for access, not functional #1415 - -## 0.8.76 - 2024-02-07 -* revert changes from yesterday regarding snprintf and its size #1410, #1411 -* reduced cppcheck linter warnings significantly -* try to improve ePaper (ghosting) #1107 - -## 0.8.75 - 2024-02-06 -* fix active power control value #1406, #1409 -* update Mqtt lib to version `1.6.0` -* take care of null terminator of chars #1410, #1411 - -## 0.8.74 - 2024-02-05 -* reduced cppcheck linter warnings significantly - -## 0.8.73 - 2024-02-03 -* fix nullpointer during communication #1401 -* added `max_power` to MqTT total values #1375 - -## 0.8.72 - 2024-02-03 -* fixed translation #1403 -* fixed sending commands to inverters which are soft turned off #1397 -* reduce switchChannel command for HMS (only each 5th cycle it will be send now) - -## 0.8.71 - 2024-02-03 -* fix heuristics reset -* fix CMT missing frames problem -* removed inverter gap setting -* removed add to total (MqTT) inverter setting -* fixed sending commands to inverters which are soft turned off -* save settings before they are exported #1395 -* fix autologin bug if no password is set -* translated `/serial` -* removed "yield day" history - -## 0.8.70 - 2024-02-01 -* prevent sending commands to inverter which isn't active #1387 -* protect commands from popup in `/live` if password is set #1199 - -## 0.8.69 - 2024-01-31 -* merge PR: Dynamic retries, pendular first rx chan #1394 - -## 0.8.68 - 2024-01-29 -* fix HMS / HMT startup -* added `flush_rx` to NRF on TX -* start with heuristics set to `0` -* added warning for WiFi channel 12-14 (ESP8266 only) #1381 - -## 0.8.67 - 2024-01-29 -* fix HMS frequency -* fix display of inverter id in serial log (was displayed twice) - -## 0.8.66 - 2024-01-28 -* added support for other regions - untested #1271 -* fix generation of DTU-ID; was computed twice without reset if two radios are enabled - -## 0.8.65 - 2024-01-24 -* removed patch for NRF `PLOS` -* fix lang issues #1388 -* fix build on Windows of `opendtufusion` environments (git: trailing whitespaces) - -## 0.8.64 - 2024-01-22 -* add `ARC` to log (NRF24 Debug) -* merge PR: ETH NTP update bugfix #1385 - -## 0.8.63 - 2024-01-22 -* made code review -* fixed endless loop #1387 - -## 0.8.62 - 2024-01-21 -* updated version in footer #1381 -* repaired radio statistics #1382 - -## 0.8.61 - 2024-01-21 -* add favicon to header -* improved NRF communication -* merge PR: provide localized times to display mono classes #1376 -* merge PR: Bypass OOM-Crash on minimal version & history access #1378 -* merge PR: Add some REST Api Endpoints to avail_endpoints #1380 - -## 0.8.60 - 2024-01-20 -* merge PR: non blocking nRF loop #1371 -* merge PR: fixed millis in serial log #1373 -* merge PR: fix powergraph scale #1374 -* changed inverter gap to `1` as default (old settings will be overridden) - -## 0.8.59 - 2024-01-18 -* merge PR: solve display settings dependencies #1369 -* fix language typos #1346 -* full update of ePaper after booting #1107 -* fix MqTT yield day reset even if `pause inverter during nighttime` isn't active #1368 - -## 0.8.58 - 2024-01-17 -* fix missing refresh URL #1366 -* fix view of grid profile #1365 -* fix webUI translation #1346 -* fix protection mask #1352 -* merge PR: Add Watchdog for ESP32 #1367 -* merge PR: ETH support for CMT2300A - HMS/HMT #1356 -* full refresh of ePaper after booting #1107 -* add optional custom link #1199 -* pinout has an own subgroup in `/settings` -* grid profile will be displayed as hex in every case #1199 - -## 0.8.57 - 2024-01-15 -* merge PR: fix immediate clearing of display after sunset #1364 -* merge PR: MI-MQTT and last retransmit #1363 -* fixed DTU-ID, now built from the unique part of the MAC -* fix lang in `/system` #1346 -* added protection to prevent update to wrong firmware (environment check) - -## 0.8.56 - 2024-01-15 -* potential fix of update problems and random reboots #1359 #1354 - -## 0.8.55 - 2024-01-14 -* merge PR: fix reboot problem with deactivated power graph #1360 -* changed scope of variables and member functions inside display classes -* removed automatically "minimal" builds -* fix include of "settings.h" (was already done in #1360) -* merge PR: Enhancement: Add info about compiled modules to version string #1357 -* add info about installed binary to `/update` #1353 -* fix lang in `/system` #1346 - -## 0.8.54 - 2024-01-13 -* added minimal version (without: MqTT, Display, History), WebUI is not changed! -* added simulator (must be activated before compile, standard: off) -* changed communication attempts back to 5 - -## 0.8.53 - 2024-01-12 -* fix history graph -* fix MqTT yield day #1331 - -## 0.8.52 - 2024-01-11 -* possible fix of 'division by zero' #1345 -* fix lang #1348 #1346 -* fix timestamp `max AC power` #1324 -* fix stylesheet overlay `max AC power` #1324 -* fix download link #1340 -* fix history graph -* try to fix #1331 - -## 0.8.51 - 2024-01-10 -* fix translation #1346 -* further improve sending active power control command faster #1332 -* added history protection mask -* merge PR: display graph improvements #1347 - -## 0.8.50 - 2024-01-09 -* merge PR: added history charts to web #1336 -* merge PR: small display changes #1339 -* merge PR: MI - add "get loss logic" #1341 -* translated `/history` -* fix translations in title of documents -* added translations for error messages #1343 - -## 0.8.49 - 2024-01-08 -* fix send total values if inverter state is different from `OFF` #1331 -* fix german language issues #1335 - -## 0.8.48 - 2024-01-07 -* merge PR: pin selection for ESP-32 S2 #1334 -* merge PR: enhancement: power graph display option #1330 - -## 0.8.47 - 2024-01-06 -* reduce GxEPD2 lib to compile faster -* upgraded GxEPD2 lib to `1.5.3` -* updated espressif32 platform to `6.5.0` -* updated U8g2 to `2.35.9` -* started to convert deprecated functions of new ArduinoJson `7.0.0` -* started to have german translations of all variants (environments) #925 #1199 -* merge PR: add defines for retry attempts #1329 - -## 0.8.46 - 2024-01-06 -* improved communication - -## 0.8.45 - 2024-01-05 -* fix MqTT total values #1326 -* start implementing a wizard for initial (WiFi) configuration #1199 - -## 0.8.44 - 2024-01-05 -* fix MqTT transmission of data #1326 -* live data is read much earlier / faster and more often #1272 - -## 0.8.43 - 2024-01-04 -* fix display of sunrise in `/system` #1308 -* fix overflow of `getLossRate` calculation #1318 -* improved MqTT by marking sent data and improved `last_success` resends #1319 -* added timestamp for `max ac power` as tooltip #1324 #1123 #1199 -* repaired Power-limit acknowledge #1322 -* fix `max_power` in `/visualization` was set to `0` after sunset - -## 0.8.42 - 2024-01-02 -* add LED to display whether it's night time or not. Can be reused as output to control battery system #1308 -* merge PR: beautifiying typography, added spaces between value and unit for `/visualization` #1314 -* merge PR: Prometheus add `getLossRate` and bugfixing #1315 -* add loss rate to `/visualization` in the statistics window -* corrected `getLossRate` infos for MqTT and prometheus +* added German translations for all variants +* added reading grid profile +* added decimal place for active power control (APC aka power limit) * added information about working IRQ for NRF24 and CMT2300A to `/system` +* added loss rate to `/visualization` in the statistics window and MqTT +* added optional output to display whether it's night time or not. Can be reused as output to control battery system or mapped to a LED +* added timestamp for `max ac power` as tooltip +* added wizard for initial WiFi connection +* added history graph (still under development) +* added simulator (must be activated before compile, standard: off) +* added minimal version (without: MqTT, Display, History), WebUI is not changed! (not compiled automatically) +* added info about installed binary to `/update` +* added protection to prevent update to wrong firmware (environment check) +* added optional custom link to the menu +* added support for other regions (USA, Indonesia) +* added warning for WiFi channel 12-14 (ESP8266 only) +* added `max_power` to MqTT total values +* added API-Token authentification for external scripts +* improved MqTT by marking sent data and improved `last_success` resends +* improved communication for HM and MI inverters +* improved reading live data from inverter +* improved sending active power control command faster +* improved `/settings`: pinout has an own subgroup +* improved export by saving settings before they are exported (to have everything in JSON) +* improved code quality (cppcheck) +* seperated sunrise and sunset offset to two fields +* fix MqTT night communication +* fix missing favicon to html header +* fix build on Windows of `opendtufusion` environments (git: trailing whitespaces) +* fix generation of DTU-ID +* fix: protect commands from popup in `/live` if password is set +* fix: prevent sending commands to inverter which isn't active +* combined firmware and hardware version to JSON topics (MqTT) +* updated Prometheus with latest changes +* upgraded most libraries to newer versions +* beautified typography, added spaces between value and unit for `/visualization` +* removed add to total (MqTT) inverter setting -## 0.8.41 - 2024-01-02 -* fix display timeout (OLED) to 60s -* change offs to signed value - -## 0.8.40 - 2024-01-02 -* fix display of sunrise and sunset in `/system` #1308 -* fix MqTT set power limit #1313 - -## 0.8.39 - 2024-01-01 -* fix MqTT dis_night_comm in the morning #1309 #1286 -* seperated offset for sunrise and sunset #1308 -* powerlimit (active power control) now has one decimal place (MqTT / API) #1199 -* merge Prometheus metrics fix #1310 -* merge MI grid profile request #1306 -* merge update documentation / readme #1305 -* add `getLossRate` to radio statistics and to MqTT #1199 - -## 0.8.38 - 2023-12-31 -* fix Grid-Profile JSON #1304 - -## 0.8.37 - 2023-12-30 -* added grid profiles -* format version of grid profile - -# RELEASE 0.8.36 - 2023-12-30 - -## 0.8.35 - 2023-12-30 -* added dim option for LEDS -* changed reload time for opendtufusion after update to 5s -* fix default interval and gap for communication -* fix serial number in exported json (was decimal, now correct as hexdecimal number) -* beautified factory reset -* added second stage for erase settings -* increased maximal number of inverters to 32 for opendtufusion board (ESP32-S3) -* fixed crash if CMT inverter is enabled, but CMT isn't configured - -# RELEASE 0.8.34 - 2023-12-29 - -## 0.8.33 - 2023-12-29 -* improved communication thx @rejoe2 - -## 0.8.32 - 2023-12-29 -* fix `start` / `stop` / `restart` commands #1287 -* added message, if profile was not read until now #1300 -* added developer option to use 'syslog-server' instead of 'web-serail' #1299 - -## 0.8.31 - 2023-12-29 -* added class to handle timeouts PR #1298 - -## 0.8.30 - 2023-12-28 -* added info if grid profile was not found -* merge PR #1293 -* merge PR #1295 fix ESP8266 pin settings -* merge PR #1297 fix layout for OLED displays - -## 0.8.29 - 2023-12-27 -* fix MqTT generic topic `comm_disabled` #1265 #1286 -* potential fix of #1285 (reset yield day) -* fix fraction of yield correction #1280 -* fix crash if `getLossRate` was read from inverter #1288 #1290 -* reduce reload time for opendtufusion ethernet variant to 5 seconds -* added basic grid parser -* added ESP32-C3 mini environment #1289 - -## 0.8.28 - 2023-12-23 -* fix bug heuristic -* add version information to clipboard once 'copy' was clicked -* add get loss rate @rejoe2 -* improve communication @rejoe2 - -## 0.8.27 - 2023-12-18 -* fix set power limit #1276 - -## 0.8.26 - 2023-12-17 -* read grid profile as HEX (`live` -> click inverter name -> `show grid profile`) - -## 0.8.25 - 2023-12-17 -* RX channel ID starts with fixed value #1277 -* fix static IP for Ethernet - -## 0.8.24 - 2023-12-16 -* fix NRF communication for opendtufusion ethernet variant - -## 0.8.23 - 2023-12-14 -* heuristics fix #1269 #1270 -* moved `sendInterval` in settings, **important:** *will be reseted to 15s after update to this version* -* try to prevent access to radio classes if they are not activated -* fixed millis in serial log -* changed 'print whole trace' = `false` as default -* added communication loop duration in [ms] to serial console -* don't print Hex-Payload if 'print whole trace' == `false` - -## 0.8.22 - 2023-12-13 -* fix communication state-machine regarding zero export #1267 - -## 0.8.21 - 2023-12-12 -* fix ethernet save inverter parameters #886 -* fix ethernet OTA update #886 -* improved radio statistics, fixed heuristic output for HMS and HMT inverters - -## 0.8.20 - 2023-12-12 -* improved HM communication #1259 #1249 -* fix `loadDefaults` for ethernet builds #1263 -* don't loop through radios which aren't in use #1264 - -## 0.8.19 - 2023-12-11 -* added ms to serial log -* added (debug) option to configure gap between inverter requests - -## 0.8.18 - 2023-12-10 -* copied even more from the original heuristic code #1259 -* added mDNS support #1262 - -## 0.8.17 - 2023-12-10 -* possible fix of NRF with opendtufusion (without ETH) -* small fix in heuristics (if conditions made assignment not comparisson) - -## 0.8.16 - 2023-12-09 -* fix crash if NRF is not enabled -* updated heuristic #1080 #1259 -* fix compile opendtufusion fusion ethernet - -## 0.8.15 - 2023-12-09 -* added support for opendtufusion fusion ethernet shield #886 -* fixed range of HMS / HMT frequencies to 863 to 870 MHz #1238 -* changed `yield effiency` per default to `1.0` #1243 -* small heuristics improvements #1258 -* added class to combine inverter heuristics fields #1258 - -## 0.8.14 - 2023-12-07 -* fixed decimal points for temperature (WebUI) PR #1254 #1251 -* fixed inverter statemachine available state PR #1252 #1253 -* fixed NTP update and sunrise calculation #1240 #886 -* display improvments #1248 #1247 -* fixed overflow in `hmRadio.h` #1244 - -## 0.8.13 - 2023-11-28 -* merge PR #1239 symbolic layout for OLED 128x64 + motion senser functionality -* fix MqTT IP addr for ETH connections PR #1240 -* added ethernet build for fusion board, not tested so far - -## 0.8.12 - 2023-11-20 -* added button `copy to clipboard` to `/serial` - -## 0.8.11 - 2023-11-20 -* improved communication, thx @rejoe2 -* improved heuristics, thx @rejoe2, @Oberfritze -* added option to strip payload of frames to significant area - -## 0.8.10 - 2023-11-19 -* fix Mi and HM inverter communication #1235 -* added privacy mode option #1211 -* changed serial debug option to work without reboot - -## 0.8.9 - 2023-11-19 -* merged PR #1234 -* added new alarm codes -* removed serial interval, was not in use anymore - -## 0.8.8 - 2023-11-16 -* fix ESP8266 save inverter #1232 - -## 0.8.7 - 2023-11-13 -* fix ESP8266 inverter settings #1226 -* send radio statistics via MqTT #1227 -* made night communication inverter depended -* added option to prevent adding values of inverter to total values (MqTT only) #1199 - -## 0.8.6 - 2023-11-12 -* merged PR #1225 -* improved heuristics (prevent update of statitistic during testing) - -## 0.8.5 - 2023-11-12 -* fixed endless loop while switching CMT frequency -* removed obsolete "retries" field from settings #1224 -* fixed crash while defining new invertes #1224 -* fixed default frequency settings -* added default input power to `400` while adding new inverters -* fixed color of wifi RSSI icon #1224 - -## 0.8.4 - 2023-11-10 -* changed MqTT alarm topic, removed retained flag #1212 -* reduce last_success MQTT messages (#1124) -* introduced tabs in WebGUI (inverter settings) -* added inverter-wise power level and frequency - -## 0.8.3 - 2023-11-09 -* fix yield day reset during day #848 -* add total AC Max Power to WebUI -* fix opendtufusion build (GxEPD patch) -* fix null ptr PR #1222 - -## 0.8.2 - 2023-11-08 -* beautified inverter settings in `setup` (preperation for future, settings become more inverter dependent) - -## 0.8.1 - 2023-11-05 -* added tx channel heuristics (per inverter) -* fix statistics counter - -## 0.8.0 - 2023-10-?? -* switched to new communication scheme - -## 0.7.66 - 2023-10-04 -* prepared PA-Level for CMT -* removed settings for number of retransmits, its fixed to `5` now -* added parentheses to have a excactly defined behaviour - -## 0.7.65 - 2023-10-02 -* MI control command review #1197 - -## 0.7.64 - 2023-10-02 -* moved active power control to modal in `live` view (per inverter) by click on current APC state - -## 0.7.63 - 2023-10-01 -* fix NRF24 communication #1200 - -## 0.7.62 - 2023-10-01 -* fix communication to inverters #1198 -* add timeout before payload is tried to process (necessary for HMS/HMT) - -## 0.7.61 - 2023-10-01 -* merged `hmPayload` and `hmsPayload` into single class -* merged generic radio functions into new parent class `radio.h` -* moved radio statistics into the inverter - each inverter has now seperate statistics which can be accessed by click on the footer in `/live` -* fix compiler warnings #1191 -* fix ePaper logo during night time #1151 - -## 0.7.60 - 2023-09-27 -* fixed typos in changelog #1172 -* fixed MqTT manual clientId storage #1174 -* fixed inverter name length in setup #1181 -* added inverter name to the header of alarm list #1181 -* improved code to avoid warning during compilation #1182 -* fix scheduler #1188, #1179 - -## 0.7.59 - 2023-09-20 -* re-add another HM-400 hardware serial number accidentally removed with `0.7.45` (#1169) -* merge PR #1170 -* reduce last_success MQTT messages (#1124) -* add re-request if inverter is known to be online and first try fails -* add alarm reporting to MI (might need review!) -* rebuild MI limiting code closer to DTUSimMI example -* round APC in `W` to an integer #1171 - -## 0.7.58 -* fix ESP8266 save settings issue #1166 - -## 0.7.57 - 2023-09-18 -* fix Alarms are always in queue (since 0.7.56) -* fix display active power control to long for small devices #1165 - -## 0.7.56 - 2023-09-17 -* only request alarms which were not received before #1113 -* added flag if alarm was requested but not received and re-request it #1105 -* merge PR #1163 - -## 0.7.55 - 2023-09-17 -* fix prometheus builds -* fix ESP32 default pinout #1159 -* added `opendtufusion-dev` because of annoying `-DARDUINO_USB_CDC_ON_BOOT=1` flag -* fix display of current power on `index` -* fix OTA, was damaged by version `0.7.51`, need to use webinstaller (from `0.7.51` to `0.7.54`) - -## 0.7.54 - 2023-09-16 -* added active power control in `W` to live view #201, #673 -* updated docu, active power control related #706 -* added current AC-Power to `index` page and removed version #763 -* improved statistic data, moved to entire struct -* removed `/api/statistics` endpoint from REST-API - -## 0.7.53 - 2023-09-16 -* fix ePaper / display night behaviour #1151 -* fix ESP8266 compile error - -## 0.7.52 - 2023-09-16 -* fix CMT configurable pins #1150, #1159 -* update MqTT lib to version `1.4.5` - -## 0.7.51 - 2023-09-16 -* fix CMT configurable pins #1150 -* fix default CMT pins for opendtufusion -* beautified `system` -* changed main loops, fix resets #1125, #1135 - -## 0.7.50 - 2023-09-12 -* moved MqTT info to `system` -* added CMT info for ESP32 devices -* improved CMT settings, now `SCLK` and `SDIO` are configurable #1046, #1150 -* changed `Power-Limit` in live-view to `Active Power Control` -* increase length of update file selector #1132 - -## 0.7.49 - 2023-09-11 -* merge PR: symbolic icons for mono displays, PR #1136 -* merge MI code restructuring PR #1145 -* merge Prometheus PR #1148 -* add option to strip webUI for ESP8266 (reduce code size, add ESP32 special features; `IF_ESP32` directives) -* started to get CMT info into `system` - not finished - -## 0.7.48 - 2023-09-10 -* fix SSD1309 2.42" display pinout -* improved setup page: save and delete of inverters - -## 0.7.47 - 2023-09-07 -* fix boot loop #1140 -* fix regex in `setup` page -* fix MI serial number display `max-module-power` in `setup` #1142 -* renamed `opendtufusionv1` to `opendtufusion` - -## 0.7.46 - 2023-09-04 -* removed `delay` from ePaper -* started improvements of `/system` -* fix LEDs to check all configured inverters -* send loop skip disabled inverters fix -* print generated DTU SN to console -* HW Versions for MI series PR #1133 -* 2.42" display (SSD1309) integration PR #1139 -* update user manual PR #1121 -* add / rename alarm codes PR #1118 -* revert default pin ESP32 for NRF23-CE #1132 -* luminance of display can be changed during runtime #1106 - -## 0.7.45 - 2023-08-29 -* change ePaper text to symbols PR #1131 -* added some invertes to dev info list #1111 - -## 0.7.44 - 2023-08-28 -* fix `last_success` transmitted to often #1124 - -## 0.7.43 - 2023-08-28 -* improved RSSI for NRF24, now it's read per package (and inverter) #1129 -* arranged `heap` related info together in `/system` -* fix display navi during save -* clean up binary output, separated to folders - -## 0.7.42 - 2023-08-27 -* fix ePaper for opendtufusion_v2.x boards (Software SPI) -* add signal strength for NRF24 - PR #1119 -* refactor wifi class to support ESP32 S2 PR #1127 -* update platform for ESP32 to 6.3.2 -* fix opendtufusion LED (were mixed) -* fix `last_success` transmitted to often #1124 -* added ESP32-S3-mini to github actions -* added old Changelog Entries, to have full log of changes - -## 0.7.41 - 2023-08-26 -* merge PR #1117 code spelling fixes #1112 -* alarms were not read after the first day - -## 0.7.40 - 2023-08-21 -* added default pins for opendtu-fusion-v1 board -* fixed hw version display in `live` -* removed development builds, renamed environments in `platform.ini` - -## 0.7.39 - 2023-08-21 -* fix background color of invalid inputs -* add hardware info (click in `live` on inverter name) - -## 0.7.38 - 2023-08-21 -* reset alarms at midnight (if inverter is not available) #1105, #1096 -* add option to reset 'max' values on midnight #1102 -* added default pins for CMT2300A (matching OpenDTU) - -## 0.7.37 - 2023-08-18 -* fix alarm time on WebGui #1099 -* added RSSI info for HMS and HMT inverters (MqTT + REST API) - -# RELEASE 0.7.36 - 2023-08-18 - -## 0.7.35 - 2023-08-17 -* fixed timestamp for alarms send over MqTT -* auto-patch of `AsyncWebServer` #834, #1036 -* Update documentation in Git regarding `ESP8266` default NRF24 pin assignments - -## 0.7.34 - 2023-08-16 -* fixed timezone offset of alarms -* added `AC` and `DC` to `/live` #1098 -* changed `ESP8266` default NRF24 pin assignments (`D3` = `CE` and `D4` = `IRQ`) -* fixed background of modal window for bright color -* fix MI crashes -* fix some lost debug messages -* merged PR #1095, MI fixes for 0.7.x versions -* fix scheduled reboot #1097 -* added vector graphic logo `/doc/logo.svg` -* merge PR #1093, improved Nokia5110 display layout - -## 0.7.33 - 2023-08-15 -* add alarms overview to WebGui #608 -* fix webGui total values #1084 - -## 0.7.32 - 2023-08-14 -* fix colors of live view #1091 - -## 0.7.31 - 2023-08-13 -* fixed docu #1085 -* changed active power limit MqTT messages to QOS2 #1072 -* improved alarm messages, added alarm-id to log #1089 -* trigger power limit read on next day (if inverter was offline meanwhile) -* disabled improv implementation to check if it is related to 'Schwuppdizitaet' -* changed live view to gray once inverter isn't available -* added inverter status to API -* changed sum of totals on WebGui depending on inverter status #1084 -* merge maximum power (AC and DC) from PR #1080 - -## 0.7.30 - 2023-08-10 -* attempt to improve speed / response times (Schwuppdizitaet) #1075 - -## 0.7.29 - 2023-08-09 -* MqTT alarm data was never sent, fixed -* REST API: added alarm data -* REST API: made get record obsolete -* REST API: added power limit acknowledge `/api/inverter/id/[0-x]` #1072 - -## 0.7.28 - 2023-08-08 -* fix MI inverter support #1078 - -## 0.7.27 - 2023-08-08 -* added compile option for ethernet #886 -* fix ePaper configuration, missing `Busy`-Pin #1075 - -# RELEASE 0.7.26 - 2023-08-06 - -* fix MqTT `last_success` - -# RELEASE 0.7.25 - 2023-08-06 - -## 0.7.24 - 2023-08-05 -* merge PR #1069 make MqTT client ID configurable -* fix #1016, general MqTT status depending on inverter state machine -* changed icon for fully available inverter to a filled check mark #1070 -* fixed `last_success` update with MqTT #1068 -* removed `improv` esp-web-installer script, because it is not fully functional at this time - -## 0.7.23 - 2023-08-04 -* merge PR #1056, visualization html -* update MqTT library to 1.4.4 -* update RF24 library to 1.4.7 -* update ArduinoJson library to 6.21.3 -* set minimum invervall for `/live` to 5 seconds - -## 0.7.22 - 2023-08-04 -* attempt to fix homeassistant auto discovery #1066 - -## 0.7.21 - 2023-07-30 -* fix MqTT YieldDay Total goes to 0 several times #1016 - -## 0.7.20 - 2023-07-28 -* merge PR #1048 version and hash in API, fixes #1045 -* fix: no yield day update if yield day reads `0` after inverter reboot (mostly on evening) #848 -* try to fix Wifi override #1047 -* added information after NTP sync to WebUI #1040 - -## 0.7.19 - 2023-07-27 -* next attempt to fix yield day for multiple inverters #1016 -* reduced threshold for inverter state machine from 60min to 15min to go from state `WAS_ON` to `OFF` - -## 0.7.18 - 2023-07-26 -* next attempt to fix yield day for multiple inverters #1016 - -## 0.7.17 - 2023-07-25 -* next attempt to fix yield day for multiple inverters #1016 -* added two more states for the inverter status (also docu) - -## 0.7.16 - 2023-07-24 -* next attempt to fix yield day for multiple inverters #1016 -* fix export settings date #1040 -* fix time on WebUI (timezone was not observed) #913 #1016 - -## 0.7.15 - 2023-07-23 -* add NTP sync interval #1019 -* adjusted range of contrast / luminance setting #1041 -* use only ISO time format in Web-UI #913 - -## 0.7.14 - 2023-07-23 -* fix Contrast for Nokia Display #1041 -* attempt to fix #1016 by improving inverter status -* added option to adjust efficiency for yield (day/total) #1028 - -## 0.7.13 - 2023-07-19 -* merged display PR #1027 -* add date, time and version to export json #1024 - -## 0.7.12 - 2023-07-09 -* added inverter status - state-machine #1016 - -## 0.7.11 - 2023-07-09 -* fix MqTT endless loop #1013 - -## 0.7.10 - 2023-07-08 -* fix MqTT endless loop #1013 - -## 0.7.9 - 2023-07-08 -* added 'improve' functions to set wifi password directly with ESP web tools #1014 -* fixed MqTT publish while applying power limit #1013 -* slightly improved HMT live view (Voltage & Current) - -## 0.7.8 - 2023-07-05 -* fix `YieldDay`, `YieldTotal` and `P_AC` in `TotalValues` #929 -* fix some serial debug prints -* merge PR #1005 which fixes issue #889 -* merge homeassistant PR #963 -* merge PR #890 which gives option for scheduled reboot at midnight (default off) - -## 0.7.7 - 2023-07-03 -* attempt to fix MqTT `YieldDay` in `TotalValues` #927 -* attempt to fix MqTT `YieldDay` and `YieldTotal` even if inverters are not completely available #929 -* fix wrong message 'NRF not connected' if it is disabled #1007 - -## 0.7.6 - 2023-06-17 -* fix display of hidden SSID checkbox -* changed yield correction data type to `double`, now decimal places are supported -* corrected name of 0.91" display in settings -* attempt to fix MqTT zero values only if setting is there #980, #957 -* made AP password configurable #951 -* added option to start without time-sync, eg. for AP-only-mode #951 - -## 0.7.5 - 2023-06-16 -* fix yield day reset on midnight #957 -* improved tickers in `app.cpp` - -## 0.7.4 - 2023-06-15 -* fix MqTT `P_AC` send if inverters are available #987 -* fix assignments for HMS 1CH and 2CH devices -* fixed uptime overflow #990 - -## 0.7.3 - 2023-06-09 -* fix hidden SSID scan #983 -* improved NRF24 missing message on home screen #981 -* fix MqTT publishing only updated values #982 - -## 0.7.2 - 2023-06-08 -* fix HMS-800 and HMS-1000 assignments #981 -* make nrf enabled all the time for ESP8266 -* fix menu item `active` highlight for 'API' and 'Doku' -* fix MqTT totals issue #927, #980 -* reduce maximum number of inverters to 4 for ESP8266, increase to 16 for ESP32 - -## 0.7.1 - 2023-06-05 -* enabled power limit control for HMS / HMT devices -* changed NRF24 lib version back to 1.4.5 because of compile problems for EPS8266 - -## 0.7.0 - 2023-06-04 -* HMS / HMT support for ESP32 devices - -## 0.6.15 - 2023-05-25 -* improved Prometheus Endpoint PR #958 -* fix turn off ePaper only if setting was set #956 -* improved reset values and update MqTT #957 - -## 0.6.14 - 2023-05-21 -* merge PR #902 Mono-Display - -## 0.6.13 - 2023-05-16 -* merge PR #934 (fix JSON API) and #944 (update manual) - -## 0.6.12 - 2023-04-28 -* improved MqTT -* fix menu active item - -## 0.6.11 - 2023-04-27 -* added MqTT class for publishing all values in Arduino `loop` - -## 0.6.10 - HMS -* Version available in `HMS` branch - -# RELEASE 0.6.9 - 2023-04-19 - -## 0.6.8 - 2023-04-19 -* fix #892 `zeroYieldDay` loop was not applied to all channels - -## 0.6.7 - 2023-04-13 -* merge PR #883, improved store of settings and javascript, thx @tastendruecker123 -* support `.` and `,` as floating point separator in setup #881 - -## 0.6.6 - 2023-04-12 -* increased distance for `import` button in mobile view #879 -* changed `led_high_active` to `bool` #879 - -## 0.6.5 - 2023-04-11 -* fix #845 MqTT subscription for `ctrl/power/[IV-ID]` was missing -* merge PR #876, check JSON settings during read for existence -* **NOTE:** incompatible change: renamed `led_high_active` to `act_high`, maybe setting must be changed after update -* merge PR #861 do not send channel metric if channel is disabled - -## 0.6.4 - 2023-04-06 -* merge PR #846, improved NRF24 communication and MI, thx @beegee3 & @rejoe2 -* merge PR #859, fix burger menu height, thx @ThomasPohl - -## 0.6.3 - 2023-04-04 -* fix login, password length was not checked #852 -* merge PR #854 optimize browser caching, thx @tastendruecker123 #828 -* fix WiFi reconnect not working #851 -* updated issue templates #822 - -## 0.6.2 - 2023-04-04 -* fix login from multiple clients #819 -* fix login screen on small displays - -## 0.6.1 - 2023-04-01 -* merge LED fix - LED1 shows MqTT state, LED configurable active high/low #839 -* only publish new inverter data #826 -* potential fix of WiFi hostname during boot up #752 - -# RELEASE 0.6.0 - 2023-03-27 - -## 0.5.110 -* MQTT fix reconnection by new lib version #780 -* add `about` page -* improved documentation regarding SPI pins #814 -* improved documentation (getting started) #815 #816 -* improved MI 4-ch inverter #820 - -## 0.5.109 -* reduced heap fragmentation by optimizing MqTT #768 -* ePaper: centered text thx @knickohr - -## 0.5.108 -* merge: PR SPI pins configurable (ESP32) #807, #806 (requires manual set of MISO=19, MOSI=23, SCLK=18 in GUI for existing installs) -* merge: PR MI serial outputs #809 -* fix: no MQTT `total` sensor for autodiscover if only one inverter was found #805 -* fix: MQTT `total` renamed to `device_name` + `_TOTOL` for better visibility #805 - -## 0.5.107 -* fix: show save message -* fix: removed serial newline for `enqueueCmd` -* Merged improved Prometheus #808 - -## 0.5.106 -* merged MI and debug message changes #804 -* fixed MQTT autodiscover #794, #632 - -## 0.5.105 -* merged MI, thx @rejoe2 #788 -* fixed reboot message #793 - -## 0.5.104 -* further improved save settings -* removed `#` character from ePaper -* fixed saving pinout for `Nokia-Display` -* removed `Reset` Pin for monochrome displays -* improved wifi connection #652 - -## 0.5.103 -* merged MI improvements, thx @rejoe2 #778 -* changed display inverter online message -* merged heap improvements #772 - -## 0.5.102 -* Warning: old exports are not compatible any more! -* fix JSON import #775 -* fix save settings, at least already stored settings are not lost #771 -* further save settings improvements (only store inverters which are existing) -* improved display of settings save return value -* made save settings asynchronous (more heap memory is free) - -## 0.5.101 -* fix SSD1306 -* update documentation -* Update miPayload.h -* Update README.md -* MI - remarks to user manual -* MI - fix AC calc -* MI - fix status msg. analysis - -## 0.5.100 -* fix add inverter `setup.html` #766 -* fix MQTT retained flag for total values #726 -* renamed buttons for import and export `setup.html` -* added serial message `settings saved` - -## 0.5.99 -* fix limit in [User_Manual.md](../User_Manual.md) -* changed `contrast` to `luminance` in `setup.html` -* try to fix SSD1306 display #759 -* only show necessary display pins depending on setting - -## 0.5.98 -* fix SH1106 rotation and turn off during night #756 -* removed MQTT subscription `sync_ntp`, `set_time` with a value of `0` does the same #696 -* simplified MQTT subscription for `limit`. Check [User_Manual.md](../User_Manual.md) for new syntax #696, #713 -* repaired inverter wise limit control -* fix upload settings #686 - -## 0.5.97 -* Attention: re-ordered display types, check your settings! #746 -* improved saving settings of display #747, #746 -* disabled contrast for Nokia display #746 -* added Prometheus as compile option #719, #615 -* update MQTT lib to v1.4.1 -* limit decimal places to 2 in `live` -* added `-DPIO_FRAMEWORK_ARDUINO_MMU_CACHE16_IRAM48` to esp8266 debug build #657 -* a `max-module-power` of `0` disables channel in live view `setup` -* merge MI improvements, get firmware information #753 - -## 0.5.96 -* added Nokia display again for ESP8266 #764 -* changed `var` / `VAr` to SI unit `var` #732 -* fix MQTT retained flags for totals (P_AC, P_DC) #726, #721 - -## 0.5.95 -* merged #742 MI Improvements -* merged #736 remove obsolete JSON Endpoint - -## 0.5.94 -* added ePaper (for ESP32 only!), thx @dAjaY85 #735 -* improved `/live` margins #732 -* renamed `var` to `VAr` #732 - -## 0.5.93 -* improved web API for `live` -* added dark mode option -* converted all forms to reponsive design -* repaired menu with password protection #720, #716, #709 -* merged MI series fixes #729 - -## 0.5.92 -* fix mobile menu -* fix inverters in select `serial.html` #709 - -## 0.5.91 -* improved html and navi, navi is visible even when API dies #660 -* reduced maximum allowed JSON size for API to 6000Bytes #660 -* small fix: output command at `prepareDevInformCmd` #692 -* improved inverter handling #671 - -## 0.5.90 -* merged PR #684, #698, #705 -* webserial minor overflow fix #660 -* web `index.html` improve version information #701 -* fix MQTT sets power limit to zero (0) #692 -* changed `reset at midnight` with timezone #697 - -## 0.5.89 -* reduced heap fragmentation (removed `strtok` completely) #644, #645, #682 -* added part of mac address to MQTT client ID to separate multiple ESPs in same network -* added dictionary for MQTT to reduce heap-fragmentation -* removed `last Alarm` from Live view, because it showed always the same alarm - will change in future - -## 0.5.88 -* MQTT Yield Day zero, next try to fix #671, thx @beegee3 -* added Solenso inverter to supported devices -* improved reconnection of MQTT #650 - -## 0.5.87 -* fix yield total correction as module (inverter input) value #570 -* reneabled instant start communication (once NTP is synced) #674 - -## 0.5.86 -* prevent send devcontrol request during disabled night communication -* changed yield total correction as module (inverter input) value #570 -* MQTT Yield Day zero, next try to fix #671 - -## 0.5.85 -* fix power-limit was not checked for max retransmits #667 -* fix blue LED lights up all the time #672 -* fix installing schedulers if NTP server isn't available -* improved zero values on triggers #671 -* hardcoded MQTT subtopics, because wildcard `#` leads to errors -* rephrased some messages on webif, thx to @Argafal #638 -* fixed 'polling stop message' on `index.html` #639 - -## 0.5.84 -* fix blue LED lights up all the time #672 -* added an instant start communication (once NTP is synced) -* add MI 3rd generation inverters (10x2 serial numbers) -* first decodings of messages from MI 2nd generation inverters - -## 0.5.83 -* fix MQTT publishing, `callback` was set but reset by following `setup()` - -## 0.5.82 -* fixed communication error #652 -* reset values is no bound to MQTT any more, setting moved to `inverter` #649 -* fixed wording on `index.hmtl` #661 - -## 0.5.81 -* started implementation of MI inverters (setup.html, own processing `MiPayload.h`) - -## 0.5.80 -* fixed communication #656 - -## 0.5.79 -* fixed mixed reset flags #648 -* fixed `mCbAlarm` if MQTT is not used #653 -* fixed MQTT `autodiscover` #630 thanks to @antibill51 -* next changes from @beegee many thanks for your contribution! -* replaced `CircularBuffer` by `std::queue` -* reworked `hmRadio.h` completely (interrupts, packaging) -* fix exception while `reboot` -* cleanup MQTT coding - -## 0.5.78 -* further improvements regarding wifi #611, fix connection if only one AP with same SSID is there -* fix endless loop in `zerovalues` #564 -* fix auto discover again #565 -* added total values to autodiscover #630 -* improved zero at midnight #625 - -## 0.5.77 -* fix wrong filename for automatically created manifest (online installer) #620 -* added rotate display feature #619 -* improved Prometheus endpoint #615, thx to @fsck-block -* improved wifi to connect always to strongest RSSI, thx to @beegee3 #611 - -## 0.5.76 -* reduce MQTT retry interval from maximum speed to one second -* fixed homeassistant autodiscovery #565 -* implemented `getNTPTime` improvements #609 partially #611 -* added alarm messages to MQTT #177, #600, #608 - -## 0.5.75 -* fix wakeup issue, once wifi was lost during night the communication didn't start in the morning -* re-enabled FlashStringHelper because of lacking RAM -* complete rewrite of monochrome display class, thx to @dAjaY85 -> displays are now configurable in setup -* fix power limit not possible #607 - -## 0.5.74 -* improved payload handling (retransmit all fragments on CRC error) -* improved `isAvailable`, checks all record structs, inverter becomes available more early because version is check first -* fix tickers were not set if NTP is not available -* disabled annoying `FlashStringHelper` it gives randomly Exceptions during development, feels more stable since then -* moved erase button to the bottom in settings, not nice but more functional -* split `tx_count` to `tx_cnt` and `retransmits` in `system.html` -* fix mqtt retransmit IP address #602 -* added debug infos for `scheduler` (web -> `/debug` as trigger prints list of tickers to serial console) - -## 0.5.73 -* improved payload handling (request / retransmit) #464 -* included alarm ID parse to serial console (in development) - -## 0.5.72 -* repaired system, scheduler was not called any more #596 - -## 0.5.71 -* improved wifi handling and tickers, many thanks to @beegee3 #571 -* fixed YieldTotal correction calculation #589 -* fixed serial output of power limit acknowledge #569 -* reviewed `sendDiscoveryConfig` #565 -* merged PR `Monodisplay`, many thanks to @dAjaY85 #566, Note: (settings are introduced but not able to be modified, will be included in next version) - -## 0.5.70 -* corrected MQTT `comm_disabled` #529 -* fix Prometheus and JSON endpoints (`config_override.h`) #561 -* publish MQTT with fixed interval even if inverter is not available #542 -* added JSON settings upload. NOTE: settings JSON download changed, so only settings should be uploaded starting from version `0.5.70` #551 -* MQTT topic and inverter name have more allowed characters: `[A-Za-z0-9./#$%&=+_-]+`, thx: @Mo Demman -* improved potential issue with `checkTicker`, thx @cbscpe -* MQTT option for reset values on midnight / not avail / communication stop #539 -* small fix in `tickIVCommunication` #534 -* add `YieldTotal` correction, eg. to have the option to zero at year start #512 - -## 0.5.69 -* merged SH1106 1.3" Display, thx @dAjaY85 -* added SH1106 to automatic build -* added IP address to MQTT (version, device and IP are retained and only transmitted once after boot) #556 -* added `set_power_limit` acknowledge MQTT publish #553 -* changed: version, device name are only published via MQTT once after boot -* added `Login` to menu if admin password is set #554 -* added `development` to second changelog link in `index.html` #543 -* added interval for MQTT (as option). With this settings MQTT live data is published in a fixed timing (only if inverter is available) #542, #523 -* added MQTT `comm_disabled` #529 -* changed name of binaries, moved GIT-Sha to the front #538 - -## 0.5.68 -* repaired receive payload -* Powerlimit is transferred immediately to inverter - -## 0.5.67 -* changed calculation of start / stop communication to 1 min after last comm. stop #515 -* moved payload send to `payload.h`, function `ivSend` #515 -* payload: if last frame is missing, request all frames again - -# RELEASE 0.5.66 - 2022-12-30 - -## 0.5.65 -* wifi, code optimization #509 - -## 0.5.64 -* channel name can use any character, not limited any more -* added `/` to MQTT topic and Inverter name -* trigger for `calcSunrise` is now using local time #515 -* fix reconnect timeout for WiFi #509 -* start AP only after boot, not on WiFi connection loss -* improved /system `free_heap` value (measured before JSON-tree is built) - -## 0.5.63 -* fix Update button protection (prevent double click #527) -* optimized scheduler #515 (thx @beegee3) -* potential fix of #526 duplicates in API `/api/record/live` -* added update information to `index.html` - -## 0.5.62 -* fix MQTT `status` update -* removed MQTT `available_text` (can be deducted from `available`) -* enhanced MQTT documentation in `User_Manual.md` -* removed `tickSunset` and `tickSunrise` from MQTT. It's not needed any more because of minute wise check of status (`processIvStatus`) -* changed MQTT topic `status` to nummeric value, check documentation in `User_Manual.md` -* fix regular expression of `setup.html` for inverter name and channel name - -## 0.5.61 -* fix #521 no reconnect at beginning of day -* added immediate (each minute) report of inverter status MQTT #522 -* added protection mask to select which pages should be protected -* update of monochrome display, show values also if nothing changed - -## 0.5.60 -* added regex to inverter name and MQTT topic (setup.html) -* beautified serial.html -* added ticker for wifi loop #515 - -## 0.5.59 -* fix night communication enable -* improved different WiFi connection scenarios (STA WiFi not found, reconnect #509, redirect for AP to configuration) -* increased MQTT user, pwd and topic length to 64 characters + `\0`. (The string end `\0` reduces the available size by one) #516 - -## 0.5.58 -* improved stability -* improved WiFi initial connection - especially if station WiFi is not available -* removed new operators from web.h (reduce dynamic allocation) -* improved sun calculation #515, #505 -* fixed WiFi auto reconnect #509 -* added disable night communication flag to MQTT #505 -* changed MQTT publish of `available` and `available_text` to sunset #468 - -## 0.5.57 -* improved stability -* added icons to index.html, added WiFi-strength symbol on each page -* moved packet stats and sun to system.html -* refactored communication offset (adjustable in minutes now) - -## 0.5.56 -* factory reset formats entire little fs -* renamed sunrise / sunset on index.html to start / stop communication -* show system information only if called directly from menu -* beautified system.html - -## 0.5.55 -* fixed static IP save - -## 0.5.54 -* changed sunrise / sunset calculation, angle is now `-3.5` instead of original `-0.83` -* improved scheduler (removed -1 from `reload`) #483 -* improved reboot flag in `app.h` -* fixed #493 no MQTT payload once display is defined - -## 0.5.53 -* Mono-Display: show values in offline mode #498 -* improved WiFi class #483 -* added communication enable / disable (to test multiple DTUs with the same inverter) -* fix factory reset #495 - -## 0.5.52 -* improved ahoyWifi class -* added interface class for app -* refactored web and webApi -> RestApi -* fix calcSunrise was not called every day -* added MQTT RX counter to index.html -* all values are displayed on /live even if they are 0 -* added MQTT /status to show status over all inverters - -## 0.5.51 -* improved scheduler, @beegee3 #483 -* refactored get NTP time, @beegee3 #483 -* generate `bin.gz` only for 1M device ESP8285 -* fix calcSunrise was not called every day -* increased number of allowed characters for MQTT user, broker and password, @DanielR92 -* added NRF24 info to Systeminfo, @DanielR92 -* added timezone for monochrome displays, @gh-fx2 -* added support for second inverter for monochrome displays, @gh-fx2 - -## 0.5.50 -* fixed scheduler, uptime and timestamp counted too fast -* added / renamed automatically build outputs -* fixed MQTT ESP uptime on reconnect (not zero any more) -* changed uptime on index.html to count each second, synced with ESP each 10 seconds - -## 0.5.49 -* fixed AP mode on brand new ESP modules -* fixed `last_success` MQTT message -* fixed MQTT inverter available status at sunset -* reordered enqueue commands after boot up to prevent same payload length for successive commands -* added automatic build for Nokia5110 and SSD1306 displays (ESP8266) - -## 0.5.48 -* added MQTT message send at sunset -* added monochrome display support -* added `once` and `onceAt` to scheduler to make code cleaner -* improved sunrise / sunset calculation - -## 0.5.47 -* refactored ahoyWifi class: AP is opened on every boot, once station connection is successful the AP will be closed -* improved NTP sync after boot, faster sync -* fix NRF24 details only on valid SPI connection - -## 0.5.46 -* fix sunrise / sunset calculation -* improved setup.html: `reboot on save` is checked as default - -## 0.5.45 -* changed MQTT last will topic from `status` to `mqtt` -* fix sunrise / sunset calculation -* fix time of serial web console - -## 0.5.44 -* marked some MQTT messages as retained -* moved global functions to global location (no duplicates) -* changed index.html interval to static 10 seconds -* fix static IP -* fix NTP with static IP -* print MQTT info only if MQTT was configured - -## 0.5.43 -* updated REST API and MQTT (both of them use the same functionality) -* added ESP-heap information as MQTT message -* changed output name of automatic development build to fixed name (to have a static link from https://ahoydtu.de) -* updated user manual to latest MQTT and API changes - -## 0.5.42 -* fix web logout (auto logout) -* switched MQTT library - -# RELEASE 0.5.41 - 2022-11-22 - -# RELEASE 0.5.28 - 2022-10-30 - -# RELEASE 0.5.17 - 2022-09-06 - -# RELEASE 0.5.16 - 2022-09-04 - +full version log: [Development Log](https://github.com/lumapu/ahoy/blob/development03/src/CHANGES.md) diff --git a/src/defines.h b/src/defines.h index 68094ba3..bf111d99 100644 --- a/src/defines.h +++ b/src/defines.h @@ -13,7 +13,7 @@ //------------------------------------- #define VERSION_MAJOR 0 #define VERSION_MINOR 8 -#define VERSION_PATCH 82 +#define VERSION_PATCH 83 //------------------------------------- typedef struct { From 9e58d58fa26714d0792e26b3265a48cb0a432e81 Mon Sep 17 00:00:00 2001 From: lumapu Date: Sat, 17 Feb 2024 00:29:02 +0100 Subject: [PATCH 174/179] 0.8.83 release --- .github/workflows/compile_release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/compile_release.yml b/.github/workflows/compile_release.yml index bf9709a0..c5f52308 100644 --- a/.github/workflows/compile_release.yml +++ b/.github/workflows/compile_release.yml @@ -89,6 +89,7 @@ jobs: contents: write steps: + - uses: actions/checkout@v4 - name: Get Artifacts uses: actions/download-artifact@v4 with: @@ -110,7 +111,6 @@ jobs: run: mv firmware ${{ steps.version_name.outputs.name }} - name: Publish Release - uses: actions/checkout@v3 uses: ncipollo/release-action@v1 with: artifacts: ${{ steps.version_name.outputs.name }} From ca98e75eead35507045ba12713dac988eb826e9b Mon Sep 17 00:00:00 2001 From: lumapu Date: Sat, 17 Feb 2024 00:29:51 +0100 Subject: [PATCH 175/179] 0.8.83 release --- .github/workflows/compile_release.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/compile_release.yml b/.github/workflows/compile_release.yml index c5f52308..65b6ca76 100644 --- a/.github/workflows/compile_release.yml +++ b/.github/workflows/compile_release.yml @@ -9,7 +9,6 @@ on: jobs: build: name: Build Environments - needs: check runs-on: ubuntu-latest if: github.repository == 'lumapu/ahoy' && github.ref_name == 'main' continue-on-error: false From 630e3fd219bf756a202972e7f1e483e2c0fe0e97 Mon Sep 17 00:00:00 2001 From: lumapu Date: Sat, 17 Feb 2024 00:45:09 +0100 Subject: [PATCH 176/179] 0.8.83 release --- .github/workflows/compile_release.yml | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/.github/workflows/compile_release.yml b/.github/workflows/compile_release.yml index 65b6ca76..569b1843 100644 --- a/.github/workflows/compile_release.yml +++ b/.github/workflows/compile_release.yml @@ -99,6 +99,17 @@ jobs: id: version_name run: python scripts/getVersion.py ${{ matrix.variant }} >> $GITHUB_OUTPUT + - name: Create tag + uses: actions/github-script@v7 + with: + script: | + github.rest.git.createRef({ + owner: context.repo.owner, + repo: context.repo.repo, + ref: 'refs/tags/${{ steps.version_name.outputs.name }}', + sha: context.sha + }) + - name: Set Version uses: cschleiden/replace-tokens@v1 with: @@ -113,15 +124,17 @@ jobs: uses: ncipollo/release-action@v1 with: artifacts: ${{ steps.version_name.outputs.name }} + artifactErrorsFailBuild: true + skipIfReleaseExists: true bodyFile: src/CHANGES.md - commit: "main" tag: ${{ steps.rename-binary-files.outputs.name }} name: ${{ steps.rename-binary-files.outputs.name }} + token: ${{ secrets.GITHUB_TOKEN }} deploy: name: Deploy Environments to fw.ahoydtu.de - needs: [build] + needs: [build, release] runs-on: ubuntu-latest continue-on-error: false steps: @@ -151,7 +164,7 @@ jobs: with: src: ${{ steps.version_name.outputs.name }}/ host: ${{ secrets.FW_SSH_HOST }} - remote: ${{ secrets.FW_SSH_DIR }}/dev + remote: ${{ secrets.FW_SSH_DIR }}/release port: ${{ secrets.FW_SSH_PORT }} user: ${{ secrets.FW_SSH_USER }} key: ${{ secrets.FW_SSH_KEY }} From 894700458697938daf191010a5a41685c1c2289a Mon Sep 17 00:00:00 2001 From: lumapu Date: Sat, 17 Feb 2024 00:51:44 +0100 Subject: [PATCH 177/179] 0.8.83 release --- .github/workflows/compile_release.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/compile_release.yml b/.github/workflows/compile_release.yml index 569b1843..6eb1366a 100644 --- a/.github/workflows/compile_release.yml +++ b/.github/workflows/compile_release.yml @@ -123,12 +123,12 @@ jobs: - name: Publish Release uses: ncipollo/release-action@v1 with: - artifacts: ${{ steps.version_name.outputs.name }} artifactErrorsFailBuild: true skipIfReleaseExists: true bodyFile: src/CHANGES.md - tag: ${{ steps.rename-binary-files.outputs.name }} - name: ${{ steps.rename-binary-files.outputs.name }} + artifacts: ${{ steps.version_name.outputs.name }} + tag: ${{ steps.version_name.outputs.name }} + name: ${{ steps.version_name.outputs.name }} token: ${{ secrets.GITHUB_TOKEN }} From 360832254f18792aa05b0d41f0d70c0c123819ef Mon Sep 17 00:00:00 2001 From: lumapu Date: Sat, 17 Feb 2024 01:05:52 +0100 Subject: [PATCH 178/179] 0.8.83 release --- .github/workflows/compile_release.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/compile_release.yml b/.github/workflows/compile_release.yml index 6eb1366a..8cafc57e 100644 --- a/.github/workflows/compile_release.yml +++ b/.github/workflows/compile_release.yml @@ -120,13 +120,19 @@ jobs: - name: Rename firmware directory run: mv firmware ${{ steps.version_name.outputs.name }} + - name: Rename firmware directory + uses: vimtor/action-zip@v1.2 + with: + files: ${{ steps.version_name.outputs.name }} manual/User_Manual.md manual/Getting_Started.md + dest: '${{ steps.version_name.outputs.name }}.zip' + - name: Publish Release uses: ncipollo/release-action@v1 with: artifactErrorsFailBuild: true skipIfReleaseExists: true bodyFile: src/CHANGES.md - artifacts: ${{ steps.version_name.outputs.name }} + artifacts: '${{ steps.version_name.outputs.name }}.zip' tag: ${{ steps.version_name.outputs.name }} name: ${{ steps.version_name.outputs.name }} token: ${{ secrets.GITHUB_TOKEN }} From 5ebfe5a8fb5bcc833b64a0dd083686031808cec8 Mon Sep 17 00:00:00 2001 From: lumapu Date: Sat, 17 Feb 2024 01:08:48 +0100 Subject: [PATCH 179/179] 0.8.83 release --- .github/workflows/compile_release.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/compile_release.yml b/.github/workflows/compile_release.yml index 8cafc57e..59adf28c 100644 --- a/.github/workflows/compile_release.yml +++ b/.github/workflows/compile_release.yml @@ -122,9 +122,9 @@ jobs: - name: Rename firmware directory uses: vimtor/action-zip@v1.2 - with: - files: ${{ steps.version_name.outputs.name }} manual/User_Manual.md manual/Getting_Started.md - dest: '${{ steps.version_name.outputs.name }}.zip' + with: + files: ${{ steps.version_name.outputs.name }} manual/User_Manual.md manual/Getting_Started.md + dest: '${{ steps.version_name.outputs.name }}.zip' - name: Publish Release uses: ncipollo/release-action@v1