mirror of
https://github.com/lumapu/ahoy.git
synced 2025-05-15 18:06:39 +02:00
Merge branch 'development03' into main
This commit is contained in:
commit
5fae7fa9e8
34 changed files with 2594 additions and 1774 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -21,3 +21,4 @@ tools/esp8266/.vscode/extensions.json
|
||||||
.DS_Store
|
.DS_Store
|
||||||
.vscode
|
.vscode
|
||||||
tools/esp8266/platformio-device-monitor-*.log
|
tools/esp8266/platformio-device-monitor-*.log
|
||||||
|
tools/esp8266/html/h/*
|
|
@ -21,10 +21,7 @@
|
||||||
#ifndef CircularBuffer_h
|
#ifndef CircularBuffer_h
|
||||||
#define CircularBuffer_h
|
#define CircularBuffer_h
|
||||||
|
|
||||||
#ifdef ESP8266
|
#if defined(ESP8266) || defined(ESP32)
|
||||||
#define DISABLE_IRQ noInterrupts()
|
|
||||||
#define RESTORE_IRQ interrupts()
|
|
||||||
#elif defined(ESP32)
|
|
||||||
#define DISABLE_IRQ noInterrupts()
|
#define DISABLE_IRQ noInterrupts()
|
||||||
#define RESTORE_IRQ interrupts()
|
#define RESTORE_IRQ interrupts()
|
||||||
#else
|
#else
|
||||||
|
|
|
@ -1,23 +1,30 @@
|
||||||
## Table of Contents
|
## Table of Contents
|
||||||
|
|
||||||
|
- [Table of Contents](#table-of-contents)
|
||||||
- [Overview](#overview)
|
- [Overview](#overview)
|
||||||
- [Compatiblity](#compatiblity)
|
- [Compatiblity](#compatiblity)
|
||||||
- [Things needed](#things-needed)
|
- [Things needed](#things-needed)
|
||||||
+ [Faked Modules Warning](#there-are-fake-nrf24l01-modules-out-there)
|
- [There are fake NRF24L01+ Modules out there](#there-are-fake-nrf24l01-modules-out-there)
|
||||||
- [Wiring things up](#wiring-things-up)
|
- [Wiring things up](#wiring-things-up)
|
||||||
+ [ESP8266 wiring example](#esp8266-wiring-example)
|
- [ESP8266 wiring example](#esp8266-wiring-example)
|
||||||
|
- [Schematic](#schematic)
|
||||||
|
- [Symbolic view](#symbolic-view)
|
||||||
|
- [ESP32 wiring example](#esp32-wiring-example)
|
||||||
|
- [Schematic](#schematic-1)
|
||||||
|
- [Symbolic view](#symbolic-view-1)
|
||||||
|
- [ESP32 GPIO settings](#esp32-gpio-settings)
|
||||||
- [Flash the Firmware on your Ahoy DTU Hardware](#flash-the-firmware-on-your-ahoy-dtu-hardware)
|
- [Flash the Firmware on your Ahoy DTU Hardware](#flash-the-firmware-on-your-ahoy-dtu-hardware)
|
||||||
+ [Compiling your own Version (the geek way)](#compiling-your-own-version)
|
- [Compiling your own Version](#compiling-your-own-version)
|
||||||
- [Optional Configuration before compilation](#optional-configuration-before-compilation)
|
- [Optional Configuration before compilation](#optional-configuration-before-compilation)
|
||||||
+ [Using a ready-to-flash binary using nodemcu-pyflasher (the easy way)](#using-a-ready-to-flash-binary-using-nodemcu-pyflasher)
|
- [Using a ready-to-flash binary using nodemcu-pyflasher](#using-a-ready-to-flash-binary-using-nodemcu-pyflasher)
|
||||||
- [Connect to your Ahoy DTU](#connect-to-your-ahoy-dtu)
|
- [Connect to your Ahoy DTU](#connect-to-your-ahoy-dtu)
|
||||||
+ [Your Ahoy DTO is very verbose using the Serial Console](#your-ahoy-dto-is-very-verbose-using-the-serial-console)
|
- [Your Ahoy DTU is very verbose using the Serial Console](#your-ahoy-dtu-is-very-verbose-using-the-serial-console)
|
||||||
+ [Connect to the Ahoy DTU Webinterface using your Browser](#connect-to-the-ahoy-dtu-webinterface-using-your-browser)
|
- [Connect to the Ahoy DTU Webinterface using your Browser](#connect-to-the-ahoy-dtu-webinterface-using-your-browser)
|
||||||
- [HTTP based Pages](#http-based-pages)
|
- [HTTP based Pages](#http-based-pages)
|
||||||
- [MQTT command to set the DTU without webinterface](#mqtt-command-to-set-the-dtu-without-webinterface)
|
- [MQTT command to set the DTU without webinterface](#mqtt-command-to-set-the-dtu-without-webinterface)
|
||||||
- [Used Libraries](#used-libraries)
|
- [Used Libraries](#used-libraries)
|
||||||
- [Contact](#contact)
|
- [Contact](#contact)
|
||||||
- [ToDo's - remove when done](#todo)
|
- [ToDo](#todo)
|
||||||
|
|
||||||
***
|
***
|
||||||
|
|
||||||
|
@ -29,9 +36,11 @@ Further information will help you to communicate to the compatible inverters.
|
||||||
You find the full [User_Manual here](User_Manual.md)
|
You find the full [User_Manual here](User_Manual.md)
|
||||||
|
|
||||||
## Compatiblity
|
## Compatiblity
|
||||||
|
|
||||||
For now the following Inverters should work out of the box:
|
For now the following Inverters should work out of the box:
|
||||||
|
|
||||||
Hoymiles Inverters
|
Hoymiles Inverters
|
||||||
|
|
||||||
- HM300
|
- HM300
|
||||||
- HM350
|
- HM350
|
||||||
- HM400
|
- HM400
|
||||||
|
@ -43,26 +52,46 @@ Hoymiles Inverters
|
||||||
- HM1500
|
- HM1500
|
||||||
|
|
||||||
TSun Inverters:
|
TSun Inverters:
|
||||||
|
|
||||||
- TSOL-350
|
- TSOL-350
|
||||||
- TSOL-400
|
- TSOL-400
|
||||||
- othery may work as well (need to be veryfied).
|
- others may work as well (need to be verified).
|
||||||
|
|
||||||
|
|
||||||
## Things needed
|
## Things needed
|
||||||
|
|
||||||
In order to build your own Ahoy DTU, you will need some things.<br/>
|
In order to build your own Ahoy DTU, you will need some things.<br/>
|
||||||
This list is not closing as the Maker Community offers more Boards than we could cover in this Readme.<br/><br/>
|
This list is not closing as the Maker Community offers more Boards than we could cover in this Readme.<br/><br/>
|
||||||
|
|
||||||
We suggest to use a WEMOS D1 mini Board as well as a NRF24L01+ Breakout Board.<br/>
|
We suggest to use a WEMOS D1 mini Board as well as a NRF24L01+ Breakout Board as a bare minimum.<br/>
|
||||||
Make sure it has the "+" in its name as we depend on some features provided with the plus-variant.<br/>
|
Any other ESP8266 Board with at least 4MBytes of ROM could work as well, depending on your skills and goals.<br/>
|
||||||
Any other ESP8266 Board with at least 4MBytes of ROM could work as well, depending on your skills.
|
Make sure the NRF24L01+ module has the "+" in its name as we depend on the 250kbps features provided only with the plus-variant.
|
||||||
|
|
||||||
|
| **Parts** | **Price** |
|
||||||
|
| --- | --- |
|
||||||
|
| D1 ESP8266 Mini WLAN Board Mikrokontroller | 4,40 Euro |
|
||||||
|
| NRF24L01+ SMD Modul 2,4 GHz Wi-Fi Funkmodul | 3,45 Euro |
|
||||||
|
| Jumper Wire Steckbrücken Steckbrett weiblich-weiblich | 2,49 Euro |
|
||||||
|
| **Total costs** | **10,34 Euro** |
|
||||||
|
|
||||||
|
To also run our sister project OpenDTU and be upwards compatible for the future we would recommend to spend some more money on an ESP32 board which has two CPU cores and a NRF24L01+ module with external antenna.
|
||||||
|
|
||||||
|
| **Parts** | **Price** |
|
||||||
|
| --- | --- |
|
||||||
|
| ESP32 Dev Board NodeMCU WROOM32 WiFi | 7,90 Euro |
|
||||||
|
| NRF24L01+ PA LNA SMA mit Antenne Long | 4,50 Euro |
|
||||||
|
| Jumper Wire Steckbrücken Steckbrett weiblich-weiblich | 2,49 Euro |
|
||||||
|
| **Total costs** | **14,89 Euro** |
|
||||||
|
|
||||||
#### There are fake NRF24L01+ Modules out there
|
#### There are fake NRF24L01+ Modules out there
|
||||||
Whatch out, there are some fake NRF24L01+ Modules out there that seem to use rebranded NRF24L01 Chips (without the +).<br/>
|
|
||||||
An example can be found in [Issue #230](https://github.com/lumapu/ahoy/issues/230).<br/>
|
Watch out, there are some fake NRF24L01+ Modules out there that seem to use rebranded NRF24L01 Chips (without the +).<br/>
|
||||||
You are welcome to add more examples of faked chips. We will that information here.<br/>
|
An example can be found in [Issue #230](https://github.com/grindylow/ahoy/issues/230).<br/>
|
||||||
|
You are welcome to add more examples of faked chips. We will add that information here.<br/>
|
||||||
|
|
||||||
## Wiring things up
|
## Wiring things up
|
||||||
|
|
||||||
The NRF24L01+ radio module is connected to the standard SPI pins:
|
The NRF24L01+ radio module is connected to the standard SPI pins:
|
||||||
|
|
||||||
- SCLK (Signal Clock),
|
- SCLK (Signal Clock),
|
||||||
- MISO (Master In Slave Out) and
|
- MISO (Master In Slave Out) and
|
||||||
- MOSI (Master Out Slave In)
|
- MOSI (Master Out Slave In)
|
||||||
|
@ -70,6 +99,7 @@ The NRF24L01+ radio module is connected to the standard SPI pins:
|
||||||
*These pins need to be configured in the config.h.*
|
*These pins need to be configured in the config.h.*
|
||||||
|
|
||||||
Additional, there are 3 pins, which can be set individual:
|
Additional, there are 3 pins, which can be set individual:
|
||||||
|
|
||||||
- CS (Chip Select),
|
- CS (Chip Select),
|
||||||
- CE (Chip Enable) and
|
- CE (Chip Enable) and
|
||||||
- IRQ (Interrupt)
|
- IRQ (Interrupt)
|
||||||
|
@ -77,23 +107,31 @@ Additional, there are 3 pins, which can be set individual:
|
||||||
*These pins can be changed from the /setup URL.*
|
*These pins can be changed from the /setup URL.*
|
||||||
|
|
||||||
#### ESP8266 wiring example
|
#### ESP8266 wiring example
|
||||||
|
|
||||||
This is an example wiring using a Wemos D1 mini.<br>
|
This is an example wiring using a Wemos D1 mini.<br>
|
||||||
|
|
||||||
##### Schematic
|
##### Schematic
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
##### Symbolic view
|
##### Symbolic view
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
#### ESP32 wiring example
|
#### ESP32 wiring example
|
||||||
|
|
||||||
Example wiring for a 38pin ESP32 module
|
Example wiring for a 38pin ESP32 module
|
||||||
|
|
||||||
##### Schematic
|
##### Schematic
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
##### Symbolic view
|
##### Symbolic view
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
##### ESP32 GPIO settings
|
##### ESP32 GPIO settings
|
||||||
|
|
||||||
For this wiring, set the 3 individual GPIOs under the /setup URL:
|
For this wiring, set the 3 individual GPIOs under the /setup URL:
|
||||||
|
|
||||||
```
|
```
|
||||||
|
@ -103,11 +141,12 @@ IRQ D0 (GPIO16 - no IRQ!)
|
||||||
```
|
```
|
||||||
|
|
||||||
## Flash the Firmware on your Ahoy DTU Hardware
|
## Flash the Firmware on your Ahoy DTU Hardware
|
||||||
Once your Hardware is ready to run, you need to flash the Ahoy DTU Firmware to your Board.
|
|
||||||
You can either build your own using your own configuration or use one or our pre-compiled generic builds.
|
|
||||||
|
|
||||||
|
Once your Hardware is ready to run, you need to flash the Ahoy DTU Firmware to your Board.
|
||||||
|
You can either build your own using your own configuration or use one of our pre-compiled generic builds.
|
||||||
|
|
||||||
#### Compiling your own Version
|
#### Compiling your own Version
|
||||||
|
|
||||||
This information suits you if you want to configure and build your own firmware.
|
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.<br/>
|
This code comes to you as a **PlatformIO** project and can be compiled using the **PlatformIO** Addon.<br/>
|
||||||
|
@ -118,13 +157,13 @@ If you do not want to compile your own build, you can use one of our ready-to-fl
|
||||||
|
|
||||||
- number of supported inverters (set to 3 by default) `config.h`
|
- number of supported inverters (set to 3 by default) `config.h`
|
||||||
- DTU radio id `config.h` (default = 1234567801)
|
- DTU radio id `config.h` (default = 1234567801)
|
||||||
- unformated list in webbrowser `/livedata` `config.h`, `LIVEDATA_VISUALIZED`
|
- unformatted list in webbrowser `/livedata` `config.h`, `LIVEDATA_VISUALIZED`
|
||||||
|
|
||||||
Alternativly, instead of modifying `config.h`, `config_override_example.h` can be copied to `config_override.h` and customized.
|
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.
|
config_override.h is excluded from version control and stays local.
|
||||||
|
|
||||||
|
|
||||||
#### Using a ready-to-flash binary using nodemcu-pyflasher
|
#### Using a ready-to-flash binary using nodemcu-pyflasher
|
||||||
|
|
||||||
This information suits you if you just want to use an easy way.
|
This information suits you if you just want to use an easy way.
|
||||||
|
|
||||||
1. download the flash-tool [nodemcu-pyflasher](https://github.com/marcelstoer/nodemcu-pyflasher)
|
1. download the flash-tool [nodemcu-pyflasher](https://github.com/marcelstoer/nodemcu-pyflasher)
|
||||||
|
@ -135,58 +174,53 @@ This information suits you if you just want to use an easy way.
|
||||||
6. flash the ESP with the compiled firmware using the UART pins or
|
6. flash the ESP with the compiled firmware using the UART pins or
|
||||||
7. repower the ESP
|
7. repower the ESP
|
||||||
8. the ESP will start as access point (AP) if there is no network config stored in its eeprom
|
8. the ESP will start as access point (AP) if there is no network config stored in its eeprom
|
||||||
9. connect to the AP, you will be forwarded to the setup page
|
9. connect to the AP (password: `esp_8266`), you will be forwarded to the setup page
|
||||||
10. configure your WiFi settings, save, repower
|
10. configure your WiFi settings, save, repower
|
||||||
11. check your router or serial console for the IP address of the module. You can try ping the configured device name as well.
|
11. check your router or serial console for the IP address of the module. You can try ping the configured device name as well.
|
||||||
|
|
||||||
|
|
||||||
Once your Ahoy DTU is running, you can use the Over The Air (OTA) capabilities to update your firmware.
|
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!
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Connect to your Ahoy DTU
|
## Connect to your Ahoy DTU
|
||||||
|
|
||||||
When everything is wired up and the firmware is flashed, it is time to connect to your Ahoy DTU.
|
When everything is wired up and the firmware is flashed, it is time to connect to your Ahoy DTU.
|
||||||
|
|
||||||
|
|
||||||
#### Your Ahoy DTU is very verbose using the Serial Console
|
#### 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.<br/>
|
When connected to your computer, you can open a Serial Console to obtain additional information.<br/>
|
||||||
This might be useful in case of any troubles that might occur as well as to simply<br/>
|
This might be useful in case of any troubles that might occur as well as to simply<br/>
|
||||||
obtain information about the converted values which were read out of the inverter(s).
|
obtain information about the converted values which were read out of the inverter(s).
|
||||||
|
|
||||||
|
|
||||||
#### Connect to the Ahoy DTU Webinterface using your Browser
|
#### 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.<br/>
|
After you have sucessfully flashed and powered your Ahoy DTU, you can access it via your Browser.<br/>
|
||||||
If your Ahoy DTU was able to log into the configured WiFi Network, it will try to obtain an IP-Address<br/>
|
If your Ahoy DTU was able to log into the configured WiFi Network, it will try to obtain an IP-Address<br/>
|
||||||
from your local DHCP Server (in most cases thats your Router).<br/><br/>
|
from your local DHCP Server (in most cases thats your Router).<br/><br/>
|
||||||
In case it could not connect to your configured Network, it will provide its own WiFi Network that you can<br/>
|
In case it could not connect to your configured Network, it will provide its own WiFi Network that you can<br/>
|
||||||
connect to for furter configuration.<br/>
|
connect to for furter configuration.<br/>
|
||||||
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".<br/>
|
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`".<br/>
|
||||||
The Ahoy DTU will keep that Network open for a certain amount of time (also configurable in the config.h and defaults to 60secs).<br/>
|
The Ahoy DTU will keep that Network open for a certain amount of time (also configurable in the config.h and defaults to 60secs).<br/>
|
||||||
If nothing connects to it and that time runs up, it will retry to connect to the configured network an so on.<br/>
|
If nothing connects to it and that time runs up, it will retry to connect to the configured network an so on.<br/>
|
||||||
<br/>
|
<br/>
|
||||||
If connected to your local Network, you just have to find out the used IP Address. In most cases your Router will give you a hint.<br/>
|
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.<br/>
|
||||||
If you connect to the WiFi the Ahoy DTU opens in case it could not connect to any other Network, the IP-Address of your Ahoy DTU is 192.168.1.1.<br/>
|
If you connect to the WiFi the Ahoy DTU opens in case it could not connect to any other Network, the IP-Address of your Ahoy DTU is [http://192.168.1.1/](http://192.168.1.1/).<br/>
|
||||||
Just open the IP-Address in your browser.<br/>
|
Just open the IP-Address in your browser.<br/>
|
||||||
<br/>
|
<br/>
|
||||||
The webinterface has the following abilities:
|
The webinterface has the following abilities:
|
||||||
|
|
||||||
- OTA Update (Over The Air Update)
|
- OTA Update (Over The Air Update)
|
||||||
- Configuration (Wifi, inverter(s), NTP Server, Pinout, MQTT, Amplifier Power Level, Debug)
|
- Configuration (Wifi, inverter(s), NTP Server, Pinout, MQTT, Amplifier Power Level, Debug)
|
||||||
- visual display of the connected inverters / modules
|
- visual display of the connected inverters / modules
|
||||||
- some statistics about communication (debug)
|
- some statistics about communication (debug)
|
||||||
|
|
||||||
|
|
||||||
##### HTTP based Pages
|
##### HTTP based Pages
|
||||||
To take control of your Ahoy DTU, you can directly call one of the following sub-pages (e.g. http://192.168.1.1/setup ).<br/>
|
|
||||||
|
To take control of your Ahoy DTU, you can directly call one of the following sub-pages (e.g. [http://ahoy-dtu/setup](http://ahoy-dtu/setup) or [http://192.168.1.1/setup](http://192.168.1.1/setup) ).<br/>
|
||||||
|
|
||||||
| page | use | output |
|
| page | use | output |
|
||||||
| ---- | ------ | ------ |
|
| ---- | ------ | ------ |
|
||||||
| /uptime | displays the uptime uf your Ahoy DTU | 0 Days, 01:37:34; now: 2022-08-21 11:13:53 |
|
| /uptime | displays the uptime of your Ahoy DTU | 0 Days, 01:37:34; now: 2022-08-21 11:13:53 |
|
||||||
| /reboot | reboots the Ahoy DTU | |
|
| /reboot | reboots the Ahoy DTU | |
|
||||||
| /erase | erases the EEPROM | |
|
| /erase | erases the EEPROM | |
|
||||||
| /factory | resets to the factory defaults configured in config.h | |
|
| /factory | resets to the factory defaults configured in config.h | |
|
||||||
|
@ -198,31 +232,31 @@ When everything is wired up and the firmware is flashed, it is time to connect t
|
||||||
| /json | gets live-data in JSON format | json output from the livedata |
|
| /json | gets live-data in JSON format | json output from the livedata |
|
||||||
| /api | | |
|
| /api | | |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## MQTT command to set the DTU without webinterface
|
## MQTT command to set the DTU without webinterface
|
||||||
[Read here](User_Manual.md)
|
|
||||||
|
|
||||||
|
|
||||||
|
[Read here](tools/esp8266/User_Manual.md)
|
||||||
|
|
||||||
## Used Libraries
|
## Used Libraries
|
||||||
|
|
||||||
- `ESP8266WiFi` 1.0
|
| Name | version | License |
|
||||||
- `DNSServer` 1.1.0
|
| --------------------- | ------- | -------- |
|
||||||
- `Ticker` 1.0
|
| `ESP8266WiFi` | 1.0 | LGPL-2.1 |
|
||||||
- `ESP8266HTTPUpdateServer` 1.0
|
| `DNSServer` | 1.1.1 | LGPL-2.1 |
|
||||||
- `Time` 1.6.1
|
| `SPI` | 1.0 | LGPL-2.1 |
|
||||||
- `RF24` 1.4.5
|
| `Hash` | 1.0 | LGPL-2.1 |
|
||||||
- `PubSubClient` 2.8
|
| `EEPROM` | 1.0 | LGPL-2.1 |
|
||||||
- `ArduinoJson` 6.19.4
|
| `ESP Async WebServer` | 1.2.3 | LGPL-3.0 |
|
||||||
|
| `ESPAsyncTCP` | 1.2.2 | LGPL-3.0 |
|
||||||
|
| `Time` | 1.6.1 | LGPL-2.1 |
|
||||||
|
| `RF24` | 1.4.5 | GPL-2.0 |
|
||||||
|
| `PubSubClient` | 2.8 | MIT |
|
||||||
|
| `ArduinoJson` | 6.19.4 | MIT |
|
||||||
|
|
||||||
## Contact
|
## Contact
|
||||||
|
|
||||||
We run a Discord Server that can be used to get in touch with the Developers and Users.
|
We run a Discord Server that can be used to get in touch with the Developers and Users.
|
||||||
|
|
||||||
https://discord.gg/WzhxEY62mB
|
<https://discord.gg/WzhxEY62mB>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## ToDo
|
## ToDo
|
||||||
|
|
||||||
|
|
|
@ -18,11 +18,11 @@ The ahoy dtu will publish on the following topics
|
||||||
|U_AC | 233.300|actual AC Voltage in Volt|
|
|U_AC | 233.300|actual AC Voltage in Volt|
|
||||||
|I_AC | 0.300 | actual AC Current in Ampere|
|
|I_AC | 0.300 | actual AC Current in Ampere|
|
||||||
|P_AC | 71.000| actual AC Power in Watt|
|
|P_AC | 71.000| actual AC Power in Watt|
|
||||||
|P_ACr | 21.200| actual AC reactive power in VAr|
|
|Q_AC | 21.200| actual AC reactive power in var|
|
||||||
|Freq | 49.990|actual AC Frequency in 1/s|
|
|F_AC | 49.990| actual AC Frequency in Hz|
|
||||||
|Pct | 95.800|actual AC Power factor in %|
|
|PF_AC | 95.800| actual AC Power factor|
|
||||||
|Temp | 19.800|Temperature of inverter in Celsius|
|
|Temp | 19.800|Temperature of inverter in Celsius|
|
||||||
|LARM_MES_ID | 9.000|Last Alarm Message Id|
|
|EVT | 9.000|Last Event/Alarm Message Index|
|
||||||
|YieldDay | 51.000|Energy converted to AC per day in Watt hours (measured on DC)|
|
|YieldDay | 51.000|Energy converted to AC per day in Watt hours (measured on DC)|
|
||||||
|YieldTotal | 465.294|Energy converted to AC since reset Watt hours (measured on DC)|
|
|YieldTotal | 465.294|Energy converted to AC since reset Watt hours (measured on DC)|
|
||||||
|P_DC | 74.600|actual DC Power in Watt|
|
|P_DC | 74.600|actual DC Power in Watt|
|
||||||
|
|
|
@ -54,7 +54,7 @@ void ahoywifi::setup(uint32_t timeout, bool settingValid) {
|
||||||
if(mApActive)
|
if(mApActive)
|
||||||
DBGPRINTLN(F("192.168.1.1"));
|
DBGPRINTLN(F("192.168.1.1"));
|
||||||
else
|
else
|
||||||
DBGPRINTLN(WiFi.localIP());
|
DBGPRINTLN(WiFi.localIP().toString());
|
||||||
DPRINTLN(DBG_INFO, F("to configure your device"));
|
DPRINTLN(DBG_INFO, F("to configure your device"));
|
||||||
DPRINTLN(DBG_INFO, F("----------------------------------------\n"));
|
DPRINTLN(DBG_INFO, F("----------------------------------------\n"));
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,13 +7,6 @@
|
||||||
#define __AHOYWIFI_H__
|
#define __AHOYWIFI_H__
|
||||||
|
|
||||||
#include "dbg.h"
|
#include "dbg.h"
|
||||||
#ifdef ESP8266
|
|
||||||
#include <ESP8266WebServer.h>
|
|
||||||
#include <ESP8266WiFi.h>
|
|
||||||
#elif defined(ESP32)
|
|
||||||
#include <WebServer.h>
|
|
||||||
#include <WiFi.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// NTP
|
// NTP
|
||||||
#include <WiFiUdp.h>
|
#include <WiFiUdp.h>
|
||||||
|
|
|
@ -23,6 +23,7 @@ app::app() {
|
||||||
loadDefaultConfig();
|
loadDefaultConfig();
|
||||||
|
|
||||||
mSys = new HmSystemType();
|
mSys = new HmSystemType();
|
||||||
|
mShouldReboot = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -41,7 +42,7 @@ void app::setup(uint32_t timeout) {
|
||||||
#endif
|
#endif
|
||||||
mSys->setup(&mConfig);
|
mSys->setup(&mConfig);
|
||||||
|
|
||||||
mWebInst = new web(this, &mSysConfig, &mConfig, mVersion);
|
mWebInst = new web(this, &mSysConfig, &mConfig, &mStat, mVersion);
|
||||||
mWebInst->setup();
|
mWebInst->setup();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,10 +61,19 @@ void app::loop(void) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if(checkTicker(&mNtpRefreshTicker, mNtpRefreshInterval)) {
|
if(checkTicker(&mNtpRefreshTicker, mNtpRefreshInterval)) {
|
||||||
if(!apActive) {
|
if(!apActive)
|
||||||
|
mUpdateNtp = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(mUpdateNtp) {
|
||||||
|
mUpdateNtp = false;
|
||||||
mTimestamp = mWifi->getNtpTime();
|
mTimestamp = mWifi->getNtpTime();
|
||||||
DPRINTLN(DBG_INFO, "[NTP]: " + getDateTimeStr(mTimestamp));
|
DPRINTLN(DBG_INFO, "[NTP]: " + getDateTimeStr(mTimestamp));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(mShouldReboot) {
|
||||||
|
DPRINTLN(DBG_INFO, F("Rebooting..."));
|
||||||
|
ESP.restart();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -84,30 +94,26 @@ void app::loop(void) {
|
||||||
DPRINT(DBG_INFO, "RX " + String(len) + "B Ch" + String(p->rxCh) + " | ");
|
DPRINT(DBG_INFO, "RX " + String(len) + "B Ch" + String(p->rxCh) + " | ");
|
||||||
mSys->Radio.dumpBuf(NULL, p->packet, len);
|
mSys->Radio.dumpBuf(NULL, p->packet, len);
|
||||||
}
|
}
|
||||||
mFrameCnt++;
|
mStat.frmCnt++;
|
||||||
|
|
||||||
if(0 != len) {
|
if(0 != len) {
|
||||||
Inverter<> *iv = mSys->findInverter(&p->packet[1]);
|
Inverter<> *iv = mSys->findInverter(&p->packet[1]);
|
||||||
if(NULL != iv && p->packet[0] == (TX_REQ_INFO + 0x80)) { // response from get information command
|
if((NULL != iv) && (p->packet[0] == (TX_REQ_INFO + 0x80))) { // response from get information command
|
||||||
mPayload[iv->id].txId = p->packet[0];
|
mPayload[iv->id].txId = p->packet[0];
|
||||||
DPRINTLN(DBG_DEBUG, F("Response from info request received"));
|
DPRINTLN(DBG_DEBUG, F("Response from info request received"));
|
||||||
uint8_t *pid = &p->packet[9];
|
uint8_t *pid = &p->packet[9];
|
||||||
if (*pid == 0x00)
|
if (*pid == 0x00)
|
||||||
{
|
|
||||||
DPRINT(DBG_DEBUG, "fragment number zero received and ignored");
|
DPRINT(DBG_DEBUG, "fragment number zero received and ignored");
|
||||||
}
|
else {
|
||||||
else
|
DPRINTLN(DBG_DEBUG, "PID: 0x" + String(*pid, HEX));
|
||||||
{
|
if ((*pid & 0x7F) < 5) {
|
||||||
if ((*pid & 0x7F) < 5)
|
|
||||||
{
|
|
||||||
memcpy(mPayload[iv->id].data[(*pid & 0x7F) - 1], &p->packet[10], len - 11);
|
memcpy(mPayload[iv->id].data[(*pid & 0x7F) - 1], &p->packet[10], len - 11);
|
||||||
mPayload[iv->id].len[(*pid & 0x7F) - 1] = len - 11;
|
mPayload[iv->id].len[(*pid & 0x7F) - 1] = len - 11;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((*pid & 0x80) == 0x80)
|
if ((*pid & 0x80) == 0x80) {
|
||||||
{ // Last packet
|
// Last packet
|
||||||
if ((*pid & 0x7f) > mPayload[iv->id].maxPackId)
|
if ((*pid & 0x7f) > mPayload[iv->id].maxPackId) {
|
||||||
{
|
|
||||||
mPayload[iv->id].maxPackId = (*pid & 0x7f);
|
mPayload[iv->id].maxPackId = (*pid & 0x7f);
|
||||||
if (*pid > 0x81)
|
if (*pid > 0x81)
|
||||||
mLastPacketId = *pid;
|
mLastPacketId = *pid;
|
||||||
|
@ -115,35 +121,17 @@ void app::loop(void) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(NULL != iv && p->packet[0] == (TX_REQ_DEVCONTROL + 0x80)) { // response from dev control command
|
if((NULL != iv) && (p->packet[0] == (TX_REQ_DEVCONTROL + 0x80))) { // response from dev control command
|
||||||
mPayload[iv->id].txId = p->packet[0];
|
mPayload[iv->id].txId = p->packet[0];
|
||||||
DPRINTLN(DBG_DEBUG, F("Response from devcontrol request received"));
|
DPRINTLN(DBG_DEBUG, F("Response from devcontrol request received"));
|
||||||
iv->devControlRequest = false;
|
iv->devControlRequest = false;
|
||||||
switch (p->packet[12]) {
|
if ((p->packet[12] == ActivePowerContr) && (p->packet[13] == 0x00)) {
|
||||||
case ActivePowerContr:
|
if (p->packet[10] == 0x00 && p->packet[11] == 0x00)
|
||||||
if (iv->devControlCmd >= ActivePowerContr && iv->devControlCmd <= PFSet) { // ok inverter accepted the set point copy it to dtu eeprom
|
|
||||||
if ((iv->powerLimit[1] & 0xff00) > 0) { // User want to have it persistent
|
|
||||||
mEep->write(ADDR_INV_PWR_LIM + iv->id * 2, iv->powerLimit[0]);
|
|
||||||
mEep->write(ADDR_INV_PWR_LIM_CON + iv->id * 2, iv->powerLimit[1]);
|
|
||||||
updateCrc();
|
|
||||||
mEep->commit();
|
|
||||||
DPRINTLN(DBG_INFO, F("Inverter ") + String(iv->id) + F(" has accepted power limit set point ") + String(iv->powerLimit[0]) + F(" with PowerLimitControl ") + String(iv->powerLimit[1]) + F(", written to dtu eeprom"));
|
|
||||||
} else
|
|
||||||
DPRINTLN(DBG_INFO, F("Inverter ") + String(iv->id) + F(" has accepted power limit set point ") + String(iv->powerLimit[0]) + F(" with PowerLimitControl ") + String(iv->powerLimit[1]));
|
DPRINTLN(DBG_INFO, F("Inverter ") + String(iv->id) + F(" has accepted power limit set point ") + String(iv->powerLimit[0]) + F(" with PowerLimitControl ") + String(iv->powerLimit[1]));
|
||||||
iv->devControlCmd = Init;
|
else
|
||||||
}
|
DPRINTLN(DBG_INFO, F("Inverter ") + String(iv->id) + F(" has NOT accepted power limit set point") + String(iv->powerLimit[0]) + F(" with PowerLimitControl ") + String(iv->powerLimit[1]));
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
if (iv->devControlCmd == ActivePowerContr) {
|
|
||||||
//case inverter did not accept the sent limit; set back to last stored limit
|
|
||||||
mEep->read(ADDR_INV_PWR_LIM + iv->id * 2, (uint16_t *)&(iv->powerLimit[0]));
|
|
||||||
mEep->read(ADDR_INV_PWR_LIM_CON + iv->id * 2, (uint16_t *)&(iv->powerLimit[1]));
|
|
||||||
DPRINTLN(DBG_INFO, F("Inverter has not accepted power limit set point"));
|
|
||||||
}
|
}
|
||||||
iv->devControlCmd = Init;
|
iv->devControlCmd = Init;
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -186,12 +174,13 @@ void app::loop(void) {
|
||||||
for(uint8_t id = 0; id < mSys->getNumInverters(); id++) {
|
for(uint8_t id = 0; id < mSys->getNumInverters(); id++) {
|
||||||
Inverter<> *iv = mSys->getInverterByPos(id);
|
Inverter<> *iv = mSys->getInverterByPos(id);
|
||||||
if(NULL != iv) {
|
if(NULL != iv) {
|
||||||
if(iv->isAvailable(mTimestamp)) {
|
record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug);
|
||||||
|
if(iv->isAvailable(mTimestamp, rec)) {
|
||||||
DPRINTLN(DBG_INFO, "Inverter: " + String(id));
|
DPRINTLN(DBG_INFO, "Inverter: " + String(id));
|
||||||
for(uint8_t i = 0; i < iv->listLen; i++) {
|
for(uint8_t i = 0; i < rec->length; i++) {
|
||||||
if(0.0f != iv->getValue(i)) {
|
if(0.0f != iv->getValue(i, rec)) {
|
||||||
snprintf(topic, 30, "%s/ch%d/%s", iv->name, iv->assign[i].ch, iv->getFieldName(i));
|
snprintf(topic, 30, "%s/ch%d/%s", iv->name, rec->assign[i].ch, iv->getFieldName(i, rec));
|
||||||
snprintf(val, 10, "%.3f %s", iv->getValue(i), iv->getUnit(i));
|
snprintf(val, 10, "%.3f %s", iv->getValue(i, rec), iv->getUnit(i, rec));
|
||||||
DPRINTLN(DBG_INFO, String(topic) + ": " + String(val));
|
DPRINTLN(DBG_INFO, String(topic) + ": " + String(val));
|
||||||
}
|
}
|
||||||
yield();
|
yield();
|
||||||
|
@ -218,8 +207,8 @@ void app::loop(void) {
|
||||||
int8_t maxLoop = MAX_NUM_INVERTERS;
|
int8_t maxLoop = MAX_NUM_INVERTERS;
|
||||||
Inverter<> *iv = mSys->getInverterByPos(mSendLastIvId);
|
Inverter<> *iv = mSys->getInverterByPos(mSendLastIvId);
|
||||||
do {
|
do {
|
||||||
if(NULL != iv)
|
//if(NULL != iv)
|
||||||
mPayload[iv->id].requested = false;
|
// mPayload[iv->id].requested = false;
|
||||||
mSendLastIvId = ((MAX_NUM_INVERTERS-1) == mSendLastIvId) ? 0 : mSendLastIvId + 1;
|
mSendLastIvId = ((MAX_NUM_INVERTERS-1) == mSendLastIvId) ? 0 : mSendLastIvId + 1;
|
||||||
iv = mSys->getInverterByPos(mSendLastIvId);
|
iv = mSys->getInverterByPos(mSendLastIvId);
|
||||||
} while((NULL == iv) && ((maxLoop--) > 0));
|
} while((NULL == iv) && ((maxLoop--) > 0));
|
||||||
|
@ -229,11 +218,14 @@ void app::loop(void) {
|
||||||
processPayload(false);
|
processPayload(false);
|
||||||
|
|
||||||
if(!mPayload[iv->id].complete) {
|
if(!mPayload[iv->id].complete) {
|
||||||
mRxFailed++;
|
if(0 == mPayload[iv->id].maxPackId)
|
||||||
|
mStat.rxFailNoAnser++;
|
||||||
|
else
|
||||||
|
mStat.rxFail++;
|
||||||
|
|
||||||
iv->setQueuedCmdFinished(); // command failed
|
iv->setQueuedCmdFinished(); // command failed
|
||||||
if(mConfig.serialDebug) {
|
if(mConfig.serialDebug)
|
||||||
DPRINTLN(DBG_INFO, F("enqueued cmd failed/timeout"));
|
DPRINTLN(DBG_INFO, F("enqueued cmd failed/timeout"));
|
||||||
}
|
|
||||||
if(mConfig.serialDebug) {
|
if(mConfig.serialDebug) {
|
||||||
DPRINT(DBG_INFO, F("Inverter #") + String(iv->id) + " ");
|
DPRINT(DBG_INFO, F("Inverter #") + String(iv->id) + " ");
|
||||||
DPRINTLN(DBG_INFO, F("no Payload received! (retransmits: ") + String(mPayload[iv->id].retransmits) + ")");
|
DPRINTLN(DBG_INFO, F("no Payload received! (retransmits: ") + String(mPayload[iv->id].retransmits) + ")");
|
||||||
|
@ -241,19 +233,25 @@ void app::loop(void) {
|
||||||
}
|
}
|
||||||
|
|
||||||
resetPayload(iv);
|
resetPayload(iv);
|
||||||
|
mPayload[iv->id].requested = true;
|
||||||
|
|
||||||
yield();
|
yield();
|
||||||
if(mConfig.serialDebug)
|
if(mConfig.serialDebug) {
|
||||||
DPRINTLN(DBG_DEBUG, F("app:loop WiFi WiFi.status ") + String(WiFi.status()));
|
DPRINTLN(DBG_DEBUG, F("app:loop WiFi WiFi.status ") + String(WiFi.status()));
|
||||||
DPRINTLN(DBG_INFO, F("Requesting Inverter SN ") + String(iv->serial.u64, HEX));
|
DPRINTLN(DBG_INFO, F("Requesting Inverter SN ") + String(iv->serial.u64, HEX));
|
||||||
if(iv->devControlRequest && (iv->powerLimit[0] > 0) && (NoPowerLimit != iv->powerLimit[1])) { // prevent to "switch off"
|
}
|
||||||
|
if(iv->devControlRequest) {
|
||||||
if(mConfig.serialDebug)
|
if(mConfig.serialDebug)
|
||||||
DPRINTLN(DBG_INFO, F("Devcontrol request ") + String(iv->devControlCmd) + F(" power limit ") + String(iv->powerLimit[0]));
|
DPRINTLN(DBG_INFO, F("Devcontrol request ") + String(iv->devControlCmd) + F(" power limit ") + String(iv->powerLimit[0]));
|
||||||
mSys->Radio.sendControlPacket(iv->radioId.u64, iv->devControlCmd, iv->powerLimit);
|
mSys->Radio.sendControlPacket(iv->radioId.u64, iv->devControlCmd, iv->powerLimit);
|
||||||
|
mPayload[iv->id].txCmd = iv->devControlCmd;
|
||||||
iv->clearCmdQueue();
|
iv->clearCmdQueue();
|
||||||
iv->enqueCommand<InfoCommand>(SystemConfigPara);
|
iv->enqueCommand<InfoCommand>(SystemConfigPara);
|
||||||
} else {
|
}
|
||||||
mSys->Radio.sendTimePacket(iv->radioId.u64,iv->getQueuedCmd(), mPayload[iv->id].ts,iv->alarmMesIndex);
|
else {
|
||||||
|
uint8_t cmd = iv->getQueuedCmd();
|
||||||
|
mSys->Radio.sendTimePacket(iv->radioId.u64, cmd, mPayload[iv->id].ts, iv->alarmMesIndex);
|
||||||
|
mPayload[iv->id].txCmd = cmd;
|
||||||
mRxTicker = 0;
|
mRxTicker = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -283,12 +281,12 @@ bool app::buildPayload(uint8_t id) {
|
||||||
for(uint8_t i = 0; i < mPayload[id].maxPackId; i ++) {
|
for(uint8_t i = 0; i < mPayload[id].maxPackId; i ++) {
|
||||||
if(mPayload[id].len[i] > 0) {
|
if(mPayload[id].len[i] > 0) {
|
||||||
if(i == (mPayload[id].maxPackId-1)) {
|
if(i == (mPayload[id].maxPackId-1)) {
|
||||||
crc = Hoymiles::crc16(mPayload[id].data[i], mPayload[id].len[i] - 2, crc);
|
crc = Ahoy::crc16(mPayload[id].data[i], mPayload[id].len[i] - 2, crc);
|
||||||
crcRcv = (mPayload[id].data[i][mPayload[id].len[i] - 2] << 8)
|
crcRcv = (mPayload[id].data[i][mPayload[id].len[i] - 2] << 8)
|
||||||
| (mPayload[id].data[i][mPayload[id].len[i] - 1]);
|
| (mPayload[id].data[i][mPayload[id].len[i] - 1]);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
crc = Hoymiles::crc16(mPayload[id].data[i], mPayload[id].len[i], crc);
|
crc = Ahoy::crc16(mPayload[id].data[i], mPayload[id].len[i], crc);
|
||||||
}
|
}
|
||||||
yield();
|
yield();
|
||||||
}
|
}
|
||||||
|
@ -305,25 +303,32 @@ void app::processPayload(bool retransmit) {
|
||||||
boolean doMQTT = false;
|
boolean doMQTT = false;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
DPRINTLN(DBG_VERBOSE, F("app::processPayload"));
|
//DPRINTLN(DBG_INFO, F("processPayload"));
|
||||||
for(uint8_t id = 0; id < mSys->getNumInverters(); id++) {
|
for(uint8_t id = 0; id < mSys->getNumInverters(); id++) {
|
||||||
Inverter<> *iv = mSys->getInverterByPos(id);
|
Inverter<> *iv = mSys->getInverterByPos(id);
|
||||||
if(NULL != iv) {
|
if(NULL != iv) {
|
||||||
if(mPayload[iv->id].txId != (TX_REQ_INFO + 0x80)) {
|
if((mPayload[iv->id].txId != (TX_REQ_INFO + 0x80)) && (0 != mPayload[iv->id].txId)) {
|
||||||
// no processing needed if txId is not 0x95
|
// no processing needed if txId is not 0x95
|
||||||
|
//DPRINTLN(DBG_INFO, F("processPayload - set complete, txId: ") + String(mPayload[iv->id].txId, HEX));
|
||||||
mPayload[iv->id].complete = true;
|
mPayload[iv->id].complete = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!mPayload[iv->id].complete ) {
|
if(!mPayload[iv->id].complete ) {
|
||||||
if(!buildPayload(iv->id)) {
|
if(!buildPayload(iv->id)) { // payload not complete
|
||||||
if(mPayload[iv->id].requested) {
|
if(mPayload[iv->id].requested) {
|
||||||
if(retransmit) {
|
if(retransmit) {
|
||||||
|
if(iv->devControlCmd == Restart || iv->devControlCmd == CleanState_LockAndAlarm ) {
|
||||||
|
// This is required to prevent retransmissions without answer.
|
||||||
|
DPRINTLN(DBG_INFO, F("Prevent retransmit on Restart / CleanState_LockAndAlarm..."));
|
||||||
|
mPayload[iv->id].retransmits = mConfig.maxRetransPerPyld;
|
||||||
|
} else {
|
||||||
if(mPayload[iv->id].retransmits < mConfig.maxRetransPerPyld) {
|
if(mPayload[iv->id].retransmits < mConfig.maxRetransPerPyld) {
|
||||||
mPayload[iv->id].retransmits++;
|
mPayload[iv->id].retransmits++;
|
||||||
if(mPayload[iv->id].maxPackId != 0) {
|
if(mPayload[iv->id].maxPackId != 0) {
|
||||||
for(uint8_t i = 0; i < (mPayload[iv->id].maxPackId-1); i++) {
|
for(uint8_t i = 0; i < (mPayload[iv->id].maxPackId-1); i++) {
|
||||||
if(mPayload[iv->id].len[i] == 0) {
|
if(mPayload[iv->id].len[i] == 0) {
|
||||||
if(mConfig.serialDebug)
|
if(mConfig.serialDebug)
|
||||||
DPRINTLN(DBG_ERROR, F("while retrieving data: Frame ") + String(i+1) + F(" missing: Request Retransmit"));
|
DPRINTLN(DBG_WARN, F("while retrieving data: Frame ") + String(i+1) + F(" missing: Request Retransmit"));
|
||||||
mSys->Radio.sendCmdPacket(iv->radioId.u64, TX_REQ_INFO, (SINGLE_FRAME+i), true);
|
mSys->Radio.sendCmdPacket(iv->radioId.u64, TX_REQ_INFO, (SINGLE_FRAME+i), true);
|
||||||
break; // only retransmit one frame per loop
|
break; // only retransmit one frame per loop
|
||||||
}
|
}
|
||||||
|
@ -332,20 +337,29 @@ void app::processPayload(bool retransmit) {
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if(mConfig.serialDebug)
|
if(mConfig.serialDebug)
|
||||||
DPRINTLN(DBG_ERROR, F("while retrieving data: last frame missing: Request Retransmit"));
|
DPRINTLN(DBG_WARN, F("while retrieving data: last frame missing: Request Retransmit"));
|
||||||
if(0x00 != mLastPacketId)
|
if(0x00 != mLastPacketId)
|
||||||
mSys->Radio.sendCmdPacket(iv->radioId.u64, TX_REQ_INFO, mLastPacketId, true);
|
mSys->Radio.sendCmdPacket(iv->radioId.u64, TX_REQ_INFO, mLastPacketId, true);
|
||||||
else
|
else {
|
||||||
mSys->Radio.sendTimePacket(iv->radioId.u64, iv->getQueuedCmd(), mPayload[iv->id].ts,iv->alarmMesIndex);
|
mPayload[iv->id].txCmd = iv->getQueuedCmd();
|
||||||
|
mSys->Radio.sendTimePacket(iv->radioId.u64, mPayload[iv->id].txCmd, mPayload[iv->id].ts, iv->alarmMesIndex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
mSys->Radio.switchRxCh(100);
|
mSys->Radio.switchRxCh(100);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
}
|
||||||
|
else { // payload complete
|
||||||
|
DPRINTLN(DBG_INFO, F("procPyld: cmd: ") + String(mPayload[iv->id].txCmd));
|
||||||
|
DPRINTLN(DBG_INFO, F("procPyld: txid: 0x") + String(mPayload[iv->id].txId, HEX));
|
||||||
|
DPRINTLN(DBG_DEBUG, F("procPyld: max: ") + String(mPayload[iv->id].maxPackId));
|
||||||
|
record_t<> *rec = iv->getRecordStruct(mPayload[iv->id].txCmd); // choose the parser
|
||||||
mPayload[iv->id].complete = true;
|
mPayload[iv->id].complete = true;
|
||||||
iv->ts = mPayload[iv->id].ts;
|
if(mPayload[iv->id].txId == (TX_REQ_INFO + 0x80))
|
||||||
|
mStat.rxSuccess++;
|
||||||
|
|
||||||
uint8_t payload[128];
|
uint8_t payload[128];
|
||||||
uint8_t offs = 0;
|
uint8_t offs = 0;
|
||||||
|
|
||||||
|
@ -361,44 +375,109 @@ void app::processPayload(bool retransmit) {
|
||||||
DPRINT(DBG_INFO, F("Payload (") + String(offs) + "): ");
|
DPRINT(DBG_INFO, F("Payload (") + String(offs) + "): ");
|
||||||
mSys->Radio.dumpBuf(NULL, payload, offs);
|
mSys->Radio.dumpBuf(NULL, payload, offs);
|
||||||
}
|
}
|
||||||
mRxSuccess++;
|
|
||||||
|
|
||||||
iv->getAssignment(); // choose the parser
|
if(NULL == rec)
|
||||||
for(uint8_t i = 0; i < iv->listLen; i++) {
|
DPRINTLN(DBG_ERROR, F("record is NULL!"));
|
||||||
iv->addValue(i, payload); // cmd value decides which parser is used to decode payload
|
else {
|
||||||
|
rec->ts = mPayload[iv->id].ts;
|
||||||
|
for(uint8_t i = 0; i < rec->length; i++) {
|
||||||
|
iv->addValue(i, payload, rec);
|
||||||
yield();
|
yield();
|
||||||
}
|
}
|
||||||
iv->doCalculations(); // cmd value decides which parser is used to decode payload
|
iv->doCalculations();
|
||||||
|
|
||||||
iv->setQueuedCmdFinished();
|
|
||||||
|
|
||||||
// MQTT send out
|
// MQTT send out
|
||||||
if(mMqttActive) {
|
if(mMqttActive) {
|
||||||
char topic[30], val[10];
|
record_t<> *recRealtime = iv->getRecordStruct(RealTimeRunData_Debug);
|
||||||
for (uint8_t id = 0; id < mSys->getNumInverters(); id++)
|
char topic[32 + MAX_NAME_LENGTH], val[32];
|
||||||
{
|
float total[4];
|
||||||
|
memset(total, 0, sizeof(float) * 4);
|
||||||
|
for (uint8_t id = 0; id < mSys->getNumInverters(); id++) {
|
||||||
Inverter<> *iv = mSys->getInverterByPos(id);
|
Inverter<> *iv = mSys->getInverterByPos(id);
|
||||||
if (NULL != iv)
|
if (NULL != iv) {
|
||||||
{
|
if (iv->isAvailable(mTimestamp, rec)) {
|
||||||
if (iv->isAvailable(mTimestamp))
|
for (uint8_t i = 0; i < rec->length; i++) {
|
||||||
{
|
snprintf(topic, 32 + MAX_NAME_LENGTH, "%s/ch%d/%s", iv->name, rec->assign[i].ch, fields[rec->assign[i].fieldId]);
|
||||||
for (uint8_t i = 0; i < iv->listLen; i++)
|
snprintf(val, 10, "%.3f", iv->getValue(i, rec));
|
||||||
{
|
|
||||||
snprintf(topic, 30, "%s/ch%d/%s", iv->name, iv->assign[i].ch, fields[iv->assign[i].fieldId]);
|
|
||||||
snprintf(val, 10, "%.3f", iv->getValue(i));
|
|
||||||
mMqtt.sendMsg(topic, val);
|
mMqtt.sendMsg(topic, val);
|
||||||
|
if(recRealtime == rec) {
|
||||||
|
if(CH0 == rec->assign[i].ch) {
|
||||||
|
switch(rec->assign[i].fieldId) {
|
||||||
|
case FLD_PAC: total[0] += iv->getValue(i, rec); break;
|
||||||
|
case FLD_YT: total[1] += iv->getValue(i, rec); break;
|
||||||
|
case FLD_YD: total[2] += iv->getValue(i, rec); break;
|
||||||
|
case FLD_PDC: total[3] += iv->getValue(i, rec); break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(iv->isProducing(mTimestamp, rec)){
|
||||||
|
snprintf(topic, 32 + MAX_NAME_LENGTH, "%s/available_text", iv->name);
|
||||||
|
snprintf(val, 32, DEF_MQTT_IV_MESSAGE_INVERTER_AVAIL_AND_PRODUCED);
|
||||||
|
mMqtt.sendMsg(topic, val);
|
||||||
|
snprintf(topic, 32 + MAX_NAME_LENGTH, "%s/available", iv->name);
|
||||||
|
snprintf(val, 32, "2");
|
||||||
|
mMqtt.sendMsg(topic, val);
|
||||||
|
} else {
|
||||||
|
snprintf(topic, 32 + MAX_NAME_LENGTH, "%s/available_text", iv->name);
|
||||||
|
snprintf(val, 32, DEF_MQTT_IV_MESSAGE_INVERTER_AVAIL_AND_NOT_PRODUCED);
|
||||||
|
mMqtt.sendMsg(topic, val);
|
||||||
|
snprintf(topic, 32 + MAX_NAME_LENGTH, "%s/available", iv->name);
|
||||||
|
snprintf(val, 32, "1");
|
||||||
|
mMqtt.sendMsg(topic, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
snprintf(topic, 32 + MAX_NAME_LENGTH, "%s/last_success", iv->name);
|
||||||
|
snprintf(val, 48, "%i", iv->getLastTs(rec) * 1000);
|
||||||
|
mMqtt.sendMsg(topic, val);
|
||||||
|
|
||||||
yield();
|
yield();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// total values (sum of all inverters)
|
||||||
|
if(recRealtime == rec) {
|
||||||
|
if(mSys->getNumInverters() > 1) {
|
||||||
|
uint8_t fieldId = 0;
|
||||||
|
for (uint8_t i = 0; i < 4; i++) {
|
||||||
|
switch(i) {
|
||||||
|
case 0: fieldId = FLD_PAC; break;
|
||||||
|
case 1: fieldId = FLD_YT; break;
|
||||||
|
case 2: fieldId = FLD_YD; break;
|
||||||
|
case 3: fieldId = FLD_PDC; break;
|
||||||
}
|
}
|
||||||
|
snprintf(topic, 32 + MAX_NAME_LENGTH, "total/%s", fields[fieldId]);
|
||||||
|
snprintf(val, 10, "%.3f", total[i]);
|
||||||
|
mMqtt.sendMsg(topic, val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
iv->setQueuedCmdFinished();
|
||||||
|
|
||||||
#ifdef __MQTT_AFTER_RX__
|
#ifdef __MQTT_AFTER_RX__
|
||||||
doMQTT = true;
|
doMQTT = true;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(mMqttActive) {
|
||||||
|
record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug);
|
||||||
|
char topic[32 + MAX_NAME_LENGTH], val[32];
|
||||||
|
if (!iv->isAvailable(mTimestamp, rec) && !iv->isProducing(mTimestamp, rec)){
|
||||||
|
snprintf(topic, 32 + MAX_NAME_LENGTH, "%s/available_text", iv->name);
|
||||||
|
snprintf(val, 32, DEF_MQTT_IV_MESSAGE_NOT_AVAIL_AND_NOT_PRODUCED);
|
||||||
|
mMqtt.sendMsg(topic, val);
|
||||||
|
snprintf(topic, 32 + MAX_NAME_LENGTH, "%s/available", iv->name);
|
||||||
|
snprintf(val, 32, "0");
|
||||||
|
mMqtt.sendMsg(topic, val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
yield();
|
yield();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -484,6 +563,11 @@ void app::cbMqtt(char* topic, byte* payload, unsigned int length) {
|
||||||
// uint16_t power_factor = std::stoi(strtok(NULL, "/"));
|
// uint16_t power_factor = std::stoi(strtok(NULL, "/"));
|
||||||
DPRINTLN(DBG_INFO, F("Set Power Factor not implemented for inverter ") + String(iv->id) );
|
DPRINTLN(DBG_INFO, F("Set Power Factor not implemented for inverter ") + String(iv->id) );
|
||||||
break;
|
break;
|
||||||
|
case CleanState_LockAndAlarm: // CleanState lock & alarm
|
||||||
|
iv->devControlCmd = CleanState_LockAndAlarm;
|
||||||
|
DPRINTLN(DBG_INFO, F("CleanState lock & alarm for inverter ") + String(iv->id) );
|
||||||
|
iv->devControlRequest = true;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
DPRINTLN(DBG_INFO, "Not implemented");
|
DPRINTLN(DBG_INFO, "Not implemented");
|
||||||
break;
|
break;
|
||||||
|
@ -499,82 +583,6 @@ void app::cbMqtt(char* topic, byte* payload, unsigned int length) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
|
||||||
String app::getStatistics(void) {
|
|
||||||
String content = F("Receive success: ") + String(mRxSuccess) + "\n";
|
|
||||||
content += F("Receive fail: ") + String(mRxFailed) + "\n";
|
|
||||||
content += F("Frames received: ") + String(mFrameCnt) + "\n";
|
|
||||||
content += F("Send Cnt: ") + String(mSys->Radio.mSendCnt) + String("\n\n");
|
|
||||||
|
|
||||||
Inverter<> *iv;
|
|
||||||
for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i++) {
|
|
||||||
iv = mSys->getInverterByPos(i);
|
|
||||||
content += F("Inverter #") + String(i) + F(": ");
|
|
||||||
if(NULL != iv) {
|
|
||||||
bool avail = true;
|
|
||||||
content += String(iv->name) + F(" (v") + String(iv->fwVersion) +F(")") + F(" is ");
|
|
||||||
if(!iv->isAvailable(mTimestamp)) {
|
|
||||||
content += F("not ");
|
|
||||||
avail = false;
|
|
||||||
}
|
|
||||||
content += F("available and is ");
|
|
||||||
if(!iv->isProducing(mTimestamp))
|
|
||||||
content += F("not ");
|
|
||||||
content += F("producing\n");
|
|
||||||
|
|
||||||
if(!avail) {
|
|
||||||
if(iv->getLastTs() > 0)
|
|
||||||
content += F("-> last successful transmission: ") + getDateTimeStr(iv->getLastTs()) + "\n";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
content += F("n/a\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!mSys->Radio.isChipConnected())
|
|
||||||
content += F("WARNING! your NRF24 module can't be reached, check the wiring and pinout (<a href=\"/setup\">setup</a>)\n");
|
|
||||||
|
|
||||||
if(mShowRebootRequest)
|
|
||||||
content += F("INFO: reboot your ESP to apply all your configuration changes!\n");
|
|
||||||
|
|
||||||
if(!mSettingsValid)
|
|
||||||
content += F("INFO: your settings are invalid, please switch to <a href=\"/setup\">Setup</a> to correct this.\n");
|
|
||||||
|
|
||||||
content += F("MQTT: ");
|
|
||||||
if(!mMqtt.isConnected())
|
|
||||||
content += F("not ");
|
|
||||||
content += F("connected\n");
|
|
||||||
|
|
||||||
return content;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
|
||||||
String app::getJson(void) {
|
|
||||||
DPRINTLN(DBG_VERBOSE, F("app::showJson"));
|
|
||||||
String modJson;
|
|
||||||
|
|
||||||
modJson = F("{\n");
|
|
||||||
for(uint8_t id = 0; id < mSys->getNumInverters(); id++) {
|
|
||||||
Inverter<> *iv = mSys->getInverterByPos(id);
|
|
||||||
if(NULL != iv) {
|
|
||||||
char topic[40], val[25];
|
|
||||||
snprintf(topic, 30, "\"%s\": {\n", iv->name);
|
|
||||||
modJson += String(topic);
|
|
||||||
for(uint8_t i = 0; i < iv->listLen; i++) {
|
|
||||||
snprintf(topic, 40, "\t\"ch%d/%s\"", iv->assign[i].ch, iv->getFieldName(i));
|
|
||||||
snprintf(val, 25, "[%.3f, \"%s\"]", iv->getValue(i), iv->getUnit(i));
|
|
||||||
modJson += String(topic) + ": " + String(val) + F(",\n");
|
|
||||||
}
|
|
||||||
modJson += F("\t\"last_msg\": \"") + getDateTimeStr(iv->ts) + F("\"\n\t},\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
modJson += F("\"json_ts\": \"") + String(getDateTimeStr(mTimestamp)) + F("\"\n}\n");
|
|
||||||
|
|
||||||
return modJson;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
bool app::getWifiApActive(void) {
|
bool app::getWifiApActive(void) {
|
||||||
return mWifi->getApActive();
|
return mWifi->getApActive();
|
||||||
|
@ -589,7 +597,8 @@ void app::sendMqttDiscoveryConfig(void) {
|
||||||
for(uint8_t id = 0; id < mSys->getNumInverters(); id++) {
|
for(uint8_t id = 0; id < mSys->getNumInverters(); id++) {
|
||||||
Inverter<> *iv = mSys->getInverterByPos(id);
|
Inverter<> *iv = mSys->getInverterByPos(id);
|
||||||
if(NULL != iv) {
|
if(NULL != iv) {
|
||||||
if(iv->isAvailable(mTimestamp) && mMqttConfigSendState[id] != true) {
|
record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug);
|
||||||
|
if(iv->isAvailable(mTimestamp, rec) && mMqttConfigSendState[id] != true) {
|
||||||
DynamicJsonDocument deviceDoc(128);
|
DynamicJsonDocument deviceDoc(128);
|
||||||
deviceDoc["name"] = iv->name;
|
deviceDoc["name"] = iv->name;
|
||||||
deviceDoc["ids"] = String(iv->serial.u64, HEX);
|
deviceDoc["ids"] = String(iv->serial.u64, HEX);
|
||||||
|
@ -599,21 +608,21 @@ void app::sendMqttDiscoveryConfig(void) {
|
||||||
JsonObject deviceObj = deviceDoc.as<JsonObject>();
|
JsonObject deviceObj = deviceDoc.as<JsonObject>();
|
||||||
DynamicJsonDocument doc(384);
|
DynamicJsonDocument doc(384);
|
||||||
|
|
||||||
for(uint8_t i = 0; i < iv->listLen; i++) {
|
for(uint8_t i = 0; i < rec->length; i++) {
|
||||||
if (iv->assign[i].ch == CH0) {
|
if (rec->assign[i].ch == CH0) {
|
||||||
snprintf(name, 32, "%s %s", iv->name, iv->getFieldName(i));
|
snprintf(name, 32, "%s %s", iv->name, iv->getFieldName(i, rec));
|
||||||
} else {
|
} else {
|
||||||
snprintf(name, 32, "%s CH%d %s", iv->name, iv->assign[i].ch, iv->getFieldName(i));
|
snprintf(name, 32, "%s CH%d %s", iv->name, rec->assign[i].ch, iv->getFieldName(i, rec));
|
||||||
}
|
}
|
||||||
snprintf(stateTopic, 64, "%s/%s/ch%d/%s", mConfig.mqtt.topic, iv->name, iv->assign[i].ch, iv->getFieldName(i));
|
snprintf(stateTopic, 64, "%s/%s/ch%d/%s", mConfig.mqtt.topic, iv->name, rec->assign[i].ch, iv->getFieldName(i, rec));
|
||||||
snprintf(discoveryTopic, 64, "%s/sensor/%s/ch%d_%s/config", MQTT_DISCOVERY_PREFIX, iv->name, iv->assign[i].ch, iv->getFieldName(i));
|
snprintf(discoveryTopic, 64, "%s/sensor/%s/ch%d_%s/config", MQTT_DISCOVERY_PREFIX, iv->name, rec->assign[i].ch, iv->getFieldName(i, rec));
|
||||||
snprintf(uniq_id, 32, "ch%d_%s", iv->assign[i].ch, iv->getFieldName(i));
|
snprintf(uniq_id, 32, "ch%d_%s", rec->assign[i].ch, iv->getFieldName(i, rec));
|
||||||
const char* devCls = getFieldDeviceClass(iv->assign[i].fieldId);
|
const char* devCls = getFieldDeviceClass(rec->assign[i].fieldId);
|
||||||
const char* stateCls = getFieldStateClass(iv->assign[i].fieldId);
|
const char* stateCls = getFieldStateClass(rec->assign[i].fieldId);
|
||||||
|
|
||||||
doc["name"] = name;
|
doc["name"] = name;
|
||||||
doc["stat_t"] = stateTopic;
|
doc["stat_t"] = stateTopic;
|
||||||
doc["unit_of_meas"] = iv->getUnit(i);
|
doc["unit_of_meas"] = iv->getUnit(i, rec);
|
||||||
doc["uniq_id"] = String(iv->serial.u64, HEX) + "_" + uniq_id;
|
doc["uniq_id"] = String(iv->serial.u64, HEX) + "_" + uniq_id;
|
||||||
doc["dev"] = deviceObj;
|
doc["dev"] = deviceObj;
|
||||||
doc["exp_aft"] = mMqttInterval + 5; // add 5 sec if connection is bad or ESP too slow
|
doc["exp_aft"] = mMqttInterval + 5; // add 5 sec if connection is bad or ESP too slow
|
||||||
|
@ -664,6 +673,7 @@ const char* app::getFieldStateClass(uint8_t fieldId) {
|
||||||
void app::resetSystem(void) {
|
void app::resetSystem(void) {
|
||||||
mUptimeSecs = 0;
|
mUptimeSecs = 0;
|
||||||
mPrevMillis = 0;
|
mPrevMillis = 0;
|
||||||
|
mUpdateNtp = false;
|
||||||
|
|
||||||
mNtpRefreshTicker = 0;
|
mNtpRefreshTicker = 0;
|
||||||
mNtpRefreshInterval = NTP_REFRESH_INTERVAL; // [ms]
|
mNtpRefreshInterval = NTP_REFRESH_INTERVAL; // [ms]
|
||||||
|
@ -691,9 +701,7 @@ void app::resetSystem(void) {
|
||||||
|
|
||||||
|
|
||||||
memset(mPayload, 0, (MAX_NUM_INVERTERS * sizeof(invPayload_t)));
|
memset(mPayload, 0, (MAX_NUM_INVERTERS * sizeof(invPayload_t)));
|
||||||
mRxFailed = 0;
|
memset(&mStat, 0, sizeof(statistics_t));
|
||||||
mRxSuccess = 0;
|
|
||||||
mFrameCnt = 0;
|
|
||||||
mLastPacketId = 0x00;
|
mLastPacketId = 0x00;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -761,23 +769,6 @@ void app::loadEEpconfig(void) {
|
||||||
if(0ULL != invSerial) {
|
if(0ULL != invSerial) {
|
||||||
iv = mSys->addInverter(name, invSerial, modPwr);
|
iv = mSys->addInverter(name, invSerial, modPwr);
|
||||||
if(NULL != iv) { // will run once on every dtu boot
|
if(NULL != iv) { // will run once on every dtu boot
|
||||||
mEep->read(ADDR_INV_PWR_LIM + (i * 2),(uint16_t *)&(iv->powerLimit[0]));
|
|
||||||
mEep->read(ADDR_INV_PWR_LIM_CON + (i * 2),(uint16_t *)&(iv->powerLimit[1]));
|
|
||||||
// only set it, if it is changed by user. Default value in the html setup page is -1 = 0xffff
|
|
||||||
// it is "doppelt-gemoppelt" because the inverter shall remember the setting if the dtu makes a power cycle / reboot
|
|
||||||
if (iv->powerLimit[0] != 0xffff) {
|
|
||||||
iv->devControlCmd = ActivePowerContr; // set active power limit
|
|
||||||
DPRINT(DBG_INFO, F("add inverter: ") + String(name) + ", SN: " + String(invSerial, HEX));
|
|
||||||
if(iv->powerLimit[1] != NoPowerLimit) {
|
|
||||||
DBGPRINT(F(", Power Limit: ") + String(iv->powerLimit[0]));
|
|
||||||
if ((iv->powerLimit[1] & 0x0001) == 0x0001)
|
|
||||||
DBGPRINTLN(F(" in %"));
|
|
||||||
else
|
|
||||||
DBGPRINTLN(F(" in Watt"));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
DBGPRINTLN(F(" "));
|
|
||||||
}
|
|
||||||
for(uint8_t j = 0; j < 4; j++) {
|
for(uint8_t j = 0; j < 4; j++) {
|
||||||
mEep->read(ADDR_INV_CH_NAME + (i * 4 * MAX_NAME_LENGTH) + j * MAX_NAME_LENGTH, iv->chName[j], MAX_NAME_LENGTH);
|
mEep->read(ADDR_INV_CH_NAME + (i * 4 * MAX_NAME_LENGTH) + j * MAX_NAME_LENGTH, iv->chName[j], MAX_NAME_LENGTH);
|
||||||
}
|
}
|
||||||
|
@ -787,6 +778,12 @@ void app::loadEEpconfig(void) {
|
||||||
mMqttInterval += mConfig.sendInterval;
|
mMqttInterval += mConfig.sendInterval;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i++) {
|
||||||
|
iv = mSys->getInverterByPos(i, false);
|
||||||
|
if(NULL != iv)
|
||||||
|
resetPayload(iv);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -801,8 +798,6 @@ void app::saveValues(void) {
|
||||||
for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i ++) {
|
for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i ++) {
|
||||||
iv = mSys->getInverterByPos(i, false);
|
iv = mSys->getInverterByPos(i, false);
|
||||||
mEep->write(ADDR_INV_ADDR + (i * 8), iv->serial.u64);
|
mEep->write(ADDR_INV_ADDR + (i * 8), iv->serial.u64);
|
||||||
mEep->write(ADDR_INV_PWR_LIM + i * 2, iv->powerLimit[0]);
|
|
||||||
mEep->write(ADDR_INV_PWR_LIM_CON + i * 2, iv->powerLimit[1]);
|
|
||||||
mEep->write(ADDR_INV_NAME + (i * MAX_NAME_LENGTH), iv->name, MAX_NAME_LENGTH);
|
mEep->write(ADDR_INV_NAME + (i * MAX_NAME_LENGTH), iv->name, MAX_NAME_LENGTH);
|
||||||
// max channel power / name
|
// max channel power / name
|
||||||
for(uint8_t j = 0; j < 4; j++) {
|
for(uint8_t j = 0; j < 4; j++) {
|
||||||
|
@ -812,7 +807,6 @@ void app::saveValues(void) {
|
||||||
}
|
}
|
||||||
|
|
||||||
updateCrc();
|
updateCrc();
|
||||||
mEep->commit();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -857,13 +851,13 @@ void app::setupMqtt(void) {
|
||||||
}
|
}
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
void app::resetPayload(Inverter<>* iv)
|
void app::resetPayload(Inverter<>* iv) {
|
||||||
{
|
DPRINTLN(DBG_INFO, "resetPayload: id: " + String(iv->id));
|
||||||
// reset payload data
|
|
||||||
memset(mPayload[iv->id].len, 0, MAX_PAYLOAD_ENTRIES);
|
memset(mPayload[iv->id].len, 0, MAX_PAYLOAD_ENTRIES);
|
||||||
|
mPayload[iv->id].txCmd = 0;
|
||||||
mPayload[iv->id].retransmits = 0;
|
mPayload[iv->id].retransmits = 0;
|
||||||
mPayload[iv->id].maxPackId = 0;
|
mPayload[iv->id].maxPackId = 0;
|
||||||
mPayload[iv->id].complete = false;
|
mPayload[iv->id].complete = false;
|
||||||
mPayload[iv->id].requested = true;
|
mPayload[iv->id].requested = false;
|
||||||
mPayload[iv->id].ts = mTimestamp;
|
mPayload[iv->id].ts = mTimestamp;
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,15 +36,9 @@ typedef HmRadio<DEF_RF24_CE_PIN, DEF_RF24_CS_PIN, BufferType> RadioType;
|
||||||
typedef Inverter<float> InverterType;
|
typedef Inverter<float> InverterType;
|
||||||
typedef HmSystem<RadioType, BufferType, MAX_NUM_INVERTERS, InverterType> HmSystemType;
|
typedef HmSystem<RadioType, BufferType, MAX_NUM_INVERTERS, InverterType> HmSystemType;
|
||||||
|
|
||||||
const char* const wemosPins[] = {"D3 (GPIO0)", "TX (GPIO1)", "D4 (GPIO2)", "RX (GPIO3)",
|
|
||||||
"D2 (GPIO4)", "D1 (GPIO5)", "GPIO6", "GPIO7", "GPIO8",
|
|
||||||
"GPIO9", "GPIO10", "GPIO11", "D6 (GPIO12)", "D7 (GPIO13)",
|
|
||||||
"D5 (GPIO14)", "D8 (GPIO15)", "D0 (GPIO16 - no IRQ!)"};
|
|
||||||
const char* const pinNames[] = {"CS", "CE", "IRQ"};
|
|
||||||
const char* const pinArgNames[] = {"pinCs", "pinCe", "pinIrq"};
|
|
||||||
|
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
uint8_t txCmd;
|
||||||
uint8_t txId;
|
uint8_t txId;
|
||||||
uint8_t invId;
|
uint8_t invId;
|
||||||
uint32_t ts;
|
uint32_t ts;
|
||||||
|
@ -56,7 +50,6 @@ typedef struct {
|
||||||
bool requested;
|
bool requested;
|
||||||
} invPayload_t;
|
} invPayload_t;
|
||||||
|
|
||||||
|
|
||||||
class ahoywifi;
|
class ahoywifi;
|
||||||
class web;
|
class web;
|
||||||
|
|
||||||
|
@ -71,8 +64,6 @@ class app {
|
||||||
void cbMqtt(char* topic, byte* payload, unsigned int length);
|
void cbMqtt(char* topic, byte* payload, unsigned int length);
|
||||||
void saveValues(void);
|
void saveValues(void);
|
||||||
void resetPayload(Inverter<>* iv);
|
void resetPayload(Inverter<>* iv);
|
||||||
String getStatistics(void);
|
|
||||||
String getJson(void);
|
|
||||||
bool getWifiApActive(void);
|
bool getWifiApActive(void);
|
||||||
|
|
||||||
uint8_t getIrqPin(void) {
|
uint8_t getIrqPin(void) {
|
||||||
|
@ -104,6 +95,15 @@ class app {
|
||||||
return String(str);
|
return String(str);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String getTimeStr(void) {
|
||||||
|
char str[20];
|
||||||
|
if(0 == mTimestamp)
|
||||||
|
sprintf(str, "n/a");
|
||||||
|
else
|
||||||
|
sprintf(str, "%02d:%02d:%02d ", hour(mTimestamp), minute(mTimestamp), second(mTimestamp));
|
||||||
|
return String(str);
|
||||||
|
}
|
||||||
|
|
||||||
inline uint32_t getUptime(void) {
|
inline uint32_t getUptime(void) {
|
||||||
return mUptimeSecs;
|
return mUptimeSecs;
|
||||||
}
|
}
|
||||||
|
@ -112,6 +112,14 @@ class app {
|
||||||
return mTimestamp;
|
return mTimestamp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline void setTimestamp(uint32_t newTime) {
|
||||||
|
DPRINTLN(DBG_DEBUG, F("setTimestamp: ") + String(newTime));
|
||||||
|
if(0 == newTime)
|
||||||
|
mUpdateNtp = true;
|
||||||
|
else
|
||||||
|
mTimestamp = newTime;
|
||||||
|
}
|
||||||
|
|
||||||
void eraseSettings(bool all = false) {
|
void eraseSettings(bool all = false) {
|
||||||
//DPRINTLN(DBG_VERBOSE, F("main.h:eraseSettings"));
|
//DPRINTLN(DBG_VERBOSE, F("main.h:eraseSettings"));
|
||||||
uint8_t buf[64];
|
uint8_t buf[64];
|
||||||
|
@ -144,7 +152,12 @@ class app {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline bool mqttIsConnected(void) { return mMqtt.isConnected(); }
|
||||||
|
inline bool getSettingsValid(void) { return mSettingsValid; }
|
||||||
|
inline bool getRebootRequestState(void) { return mShowRebootRequest; }
|
||||||
|
|
||||||
HmSystemType *mSys;
|
HmSystemType *mSys;
|
||||||
|
bool mShouldReboot;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void resetSystem(void);
|
void resetSystem(void);
|
||||||
|
@ -169,7 +182,7 @@ class app {
|
||||||
while(length > 0) {
|
while(length > 0) {
|
||||||
len = (length < 32) ? length : 32;
|
len = (length < 32) ? length : 32;
|
||||||
mEep->read(start, buf, len);
|
mEep->read(start, buf, len);
|
||||||
crc = Hoymiles::crc16(buf, len, crc);
|
crc = Ahoy::crc16(buf, len, crc);
|
||||||
start += len;
|
start += len;
|
||||||
length -= len;
|
length -= len;
|
||||||
}
|
}
|
||||||
|
@ -234,6 +247,7 @@ class app {
|
||||||
|
|
||||||
eep *mEep;
|
eep *mEep;
|
||||||
uint32_t mTimestamp;
|
uint32_t mTimestamp;
|
||||||
|
bool mUpdateNtp;
|
||||||
|
|
||||||
bool mShowRebootRequest;
|
bool mShowRebootRequest;
|
||||||
|
|
||||||
|
@ -247,14 +261,11 @@ class app {
|
||||||
uint8_t mSendLastIvId;
|
uint8_t mSendLastIvId;
|
||||||
|
|
||||||
invPayload_t mPayload[MAX_NUM_INVERTERS];
|
invPayload_t mPayload[MAX_NUM_INVERTERS];
|
||||||
uint32_t mRxFailed;
|
statistics_t mStat;
|
||||||
uint32_t mRxSuccess;
|
|
||||||
uint32_t mFrameCnt;
|
|
||||||
uint8_t mLastPacketId;
|
uint8_t mLastPacketId;
|
||||||
|
|
||||||
// timer
|
// timer
|
||||||
uint32_t mTicker;
|
uint32_t mTicker;
|
||||||
|
|
||||||
uint32_t mRxTicker;
|
uint32_t mRxTicker;
|
||||||
|
|
||||||
// mqtt
|
// mqtt
|
||||||
|
|
|
@ -45,17 +45,14 @@
|
||||||
#define DEF_RF24_CE_PIN 2
|
#define DEF_RF24_CE_PIN 2
|
||||||
#define DEF_RF24_IRQ_PIN 0
|
#define DEF_RF24_IRQ_PIN 0
|
||||||
|
|
||||||
// default radio ID
|
|
||||||
#define DTU_RADIO_ID ((uint64_t)0x1234567801ULL)
|
|
||||||
|
|
||||||
// default NRF24 power, possible values (0 - 3)
|
// default NRF24 power, possible values (0 - 3)
|
||||||
#define DEF_AMPLIFIERPOWER 2
|
#define DEF_AMPLIFIERPOWER 1
|
||||||
|
|
||||||
// number of packets hold in buffer
|
// number of packets hold in buffer
|
||||||
#define PACKET_BUFFER_SIZE 30
|
#define PACKET_BUFFER_SIZE 30
|
||||||
|
|
||||||
// number of configurable inverters
|
// number of configurable inverters
|
||||||
#define MAX_NUM_INVERTERS 3
|
#define MAX_NUM_INVERTERS 4
|
||||||
|
|
||||||
// default serial interval
|
// default serial interval
|
||||||
#define SERIAL_INTERVAL 5
|
#define SERIAL_INTERVAL 5
|
||||||
|
@ -108,8 +105,10 @@
|
||||||
// default MQTT topic
|
// default MQTT topic
|
||||||
#define DEF_MQTT_TOPIC "inverter"
|
#define DEF_MQTT_TOPIC "inverter"
|
||||||
|
|
||||||
// changes the style of "/setup" page, visualized = nicer
|
//default MQTT Message Inverter Status
|
||||||
#define LIVEDATA_VISUALIZED
|
#define DEF_MQTT_IV_MESSAGE_NOT_AVAIL_AND_NOT_PRODUCED "not available and not producing" // STATUS 0
|
||||||
|
#define DEF_MQTT_IV_MESSAGE_INVERTER_AVAIL_AND_NOT_PRODUCED "available and not producing" // STATUS 1
|
||||||
|
#define DEF_MQTT_IV_MESSAGE_INVERTER_AVAIL_AND_PRODUCED "available and producing" // STATUS 2
|
||||||
|
|
||||||
#if __has_include("config_override.h")
|
#if __has_include("config_override.h")
|
||||||
#include "config_override.h"
|
#include "config_override.h"
|
||||||
|
|
|
@ -24,7 +24,4 @@
|
||||||
#undef DEF_RF24_IRQ_PIN
|
#undef DEF_RF24_IRQ_PIN
|
||||||
#define DEF_RF24_IRQ_PIN 16
|
#define DEF_RF24_IRQ_PIN 16
|
||||||
|
|
||||||
#undef DTU_RADIO_ID
|
|
||||||
#define DTU_RADIO_ID ((uint64_t)0x1234567802ULL)
|
|
||||||
|
|
||||||
#endif /*__CONFIG_OVERRIDE_H__*/
|
#endif /*__CONFIG_OVERRIDE_H__*/
|
||||||
|
|
|
@ -5,8 +5,7 @@
|
||||||
|
|
||||||
#include "crc.h"
|
#include "crc.h"
|
||||||
|
|
||||||
namespace Hoymiles {
|
namespace Ahoy {
|
||||||
|
|
||||||
uint8_t crc8(uint8_t buf[], uint8_t len) {
|
uint8_t crc8(uint8_t buf[], uint8_t len) {
|
||||||
uint8_t crc = CRC8_INIT;
|
uint8_t crc = CRC8_INIT;
|
||||||
for(uint8_t i = 0; i < len; i++) {
|
for(uint8_t i = 0; i < len; i++) {
|
||||||
|
@ -14,7 +13,6 @@ uint8_t crc8(uint8_t buf[], uint8_t len) {
|
||||||
for(uint8_t b = 0; b < 8; b ++) {
|
for(uint8_t b = 0; b < 8; b ++) {
|
||||||
crc = (crc << 1) ^ ((crc & 0x80) ? CRC8_POLY : 0x00);
|
crc = (crc << 1) ^ ((crc & 0x80) ? CRC8_POLY : 0x00);
|
||||||
}
|
}
|
||||||
yield();
|
|
||||||
}
|
}
|
||||||
return crc;
|
return crc;
|
||||||
}
|
}
|
||||||
|
@ -31,8 +29,7 @@ uint16_t crc16(uint8_t buf[], uint8_t len, uint16_t start) {
|
||||||
if(shift != 0)
|
if(shift != 0)
|
||||||
crc = crc ^ CRC16_MODBUS_POLYNOM;
|
crc = crc ^ CRC16_MODBUS_POLYNOM;
|
||||||
}
|
}
|
||||||
yield();
|
|
||||||
}
|
}
|
||||||
return crc;
|
return crc;
|
||||||
}
|
}
|
||||||
} // namespace Hoymiles
|
}
|
||||||
|
|
|
@ -14,10 +14,8 @@
|
||||||
|
|
||||||
#define CRC16_MODBUS_POLYNOM 0xA001
|
#define CRC16_MODBUS_POLYNOM 0xA001
|
||||||
|
|
||||||
namespace Hoymiles {
|
namespace Ahoy {
|
||||||
|
|
||||||
uint8_t crc8(uint8_t buf[], uint8_t len);
|
uint8_t crc8(uint8_t buf[], uint8_t len);
|
||||||
uint16_t crc16(uint8_t buf[], uint8_t len, uint16_t start = 0xffff);
|
uint16_t crc16(uint8_t buf[], uint8_t len, uint16_t start = 0xffff);
|
||||||
|
|
||||||
}
|
}
|
||||||
#endif /*__CRC_H__*/
|
#endif /*__CRC_H__*/
|
||||||
|
|
3
tools/esp8266/dbg.cpp
Normal file
3
tools/esp8266/dbg.cpp
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
#include "dbg.h"
|
||||||
|
|
||||||
|
DBG_CB mCb = NULL;
|
|
@ -13,7 +13,7 @@
|
||||||
//-------------------------------------
|
//-------------------------------------
|
||||||
#define VERSION_MAJOR 0
|
#define VERSION_MAJOR 0
|
||||||
#define VERSION_MINOR 5
|
#define VERSION_MINOR 5
|
||||||
#define VERSION_PATCH 17
|
#define VERSION_PATCH 19
|
||||||
|
|
||||||
|
|
||||||
//-------------------------------------
|
//-------------------------------------
|
||||||
|
@ -57,21 +57,15 @@ typedef enum {
|
||||||
Init = 0xff
|
Init = 0xff
|
||||||
} DevControlCmdType;
|
} DevControlCmdType;
|
||||||
|
|
||||||
typedef enum { // ToDo: to be verified by field tests
|
typedef enum {
|
||||||
NoPowerLimit = 0xffff, // ahoy internal value, no hoymiles value!
|
|
||||||
AbsolutNonPersistent = 0UL, // 0x0000
|
AbsolutNonPersistent = 0UL, // 0x0000
|
||||||
RelativNonPersistent = 1UL, // 0x0001
|
RelativNonPersistent = 1UL, // 0x0001
|
||||||
AbsolutPersistent = 256UL, // 0x0100
|
AbsolutPersistent = 256UL, // 0x0100
|
||||||
RelativPersistent = 257UL // 0x0101
|
RelativPersistent = 257UL // 0x0101
|
||||||
} PowerLimitControlType;
|
} PowerLimitControlType;
|
||||||
|
|
||||||
// minimum serial interval
|
|
||||||
#define MIN_SERIAL_INTERVAL 5
|
#define MIN_SERIAL_INTERVAL 5
|
||||||
|
|
||||||
// minimum send interval
|
|
||||||
#define MIN_SEND_INTERVAL 15
|
#define MIN_SEND_INTERVAL 15
|
||||||
|
|
||||||
// minimum mqtt interval
|
|
||||||
#define MIN_MQTT_INTERVAL 60
|
#define MIN_MQTT_INTERVAL 60
|
||||||
|
|
||||||
//-------------------------------------
|
//-------------------------------------
|
||||||
|
@ -90,27 +84,16 @@ typedef enum { // ToDo: to be verified by field tests
|
||||||
#define INV_MAX_RTRY_LEN 1 // uint8_t
|
#define INV_MAX_RTRY_LEN 1 // uint8_t
|
||||||
#define INV_PWR_LIM_LEN MAX_NUM_INVERTERS * 2 // uint16_t
|
#define INV_PWR_LIM_LEN MAX_NUM_INVERTERS * 2 // uint16_t
|
||||||
|
|
||||||
#define PINOUT_LEN 3 // 3 pins: CS, CE, IRQ
|
|
||||||
|
|
||||||
#define RF24_AMP_PWR_LEN 1
|
|
||||||
|
|
||||||
#define NTP_ADDR_LEN 32 // DNS Name
|
#define NTP_ADDR_LEN 32 // DNS Name
|
||||||
#define NTP_PORT_LEN 2 // uint16_t
|
|
||||||
|
|
||||||
#define MQTT_ADDR_LEN 32 // DNS Name
|
#define MQTT_ADDR_LEN 32 // DNS Name
|
||||||
#define MQTT_USER_LEN 16
|
#define MQTT_USER_LEN 16
|
||||||
#define MQTT_PWD_LEN 32
|
#define MQTT_PWD_LEN 32
|
||||||
#define MQTT_TOPIC_LEN 32
|
#define MQTT_TOPIC_LEN 32
|
||||||
#define MQTT_INTERVAL_LEN 2 // uint16_t
|
|
||||||
#define MQTT_PORT_LEN 2 // uint16_t
|
|
||||||
#define MQTT_DISCOVERY_PREFIX "homeassistant"
|
#define MQTT_DISCOVERY_PREFIX "homeassistant"
|
||||||
#define MQTT_MAX_PACKET_SIZE 384
|
#define MQTT_MAX_PACKET_SIZE 384
|
||||||
#define MQTT_RECONNECT_DELAY 5000
|
#define MQTT_RECONNECT_DELAY 5000
|
||||||
|
|
||||||
#define SER_ENABLE_LEN 1 // uint8_t
|
|
||||||
#define SER_DEBUG_LEN 1 // uint8_t
|
|
||||||
#define SER_INTERVAL_LEN 2 // uint16_t
|
|
||||||
|
|
||||||
#pragma pack(push) // push current alignment to stack
|
#pragma pack(push) // push current alignment to stack
|
||||||
#pragma pack(1) // set alignment to 1 byte boundary
|
#pragma pack(1) // set alignment to 1 byte boundary
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
@ -119,8 +102,10 @@ typedef struct {
|
||||||
char user[MQTT_USER_LEN];
|
char user[MQTT_USER_LEN];
|
||||||
char pwd[MQTT_PWD_LEN];
|
char pwd[MQTT_PWD_LEN];
|
||||||
char topic[MQTT_TOPIC_LEN];
|
char topic[MQTT_TOPIC_LEN];
|
||||||
} /*__attribute__((__packed__))*/ mqttConfig_t;
|
} mqttConfig_t;
|
||||||
#pragma pack(pop) // restore original alignment from stack
|
#pragma pack(pop) // restore original alignment from stack
|
||||||
|
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
char deviceName[DEVNAME_LEN];
|
char deviceName[DEVNAME_LEN];
|
||||||
|
|
||||||
|
@ -151,9 +136,16 @@ typedef struct {
|
||||||
uint16_t serialInterval;
|
uint16_t serialInterval;
|
||||||
bool serialShowIv;
|
bool serialShowIv;
|
||||||
bool serialDebug;
|
bool serialDebug;
|
||||||
} /*__attribute__((__packed__))*/ config_t;
|
} config_t;
|
||||||
#pragma pack(pop) // restore original alignment from stack
|
#pragma pack(pop) // restore original alignment from stack
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint32_t rxFail;
|
||||||
|
uint32_t rxFailNoAnser;
|
||||||
|
uint32_t rxSuccess;
|
||||||
|
uint32_t frmCnt;
|
||||||
|
} statistics_t;
|
||||||
|
|
||||||
|
|
||||||
#define CFG_MQTT_LEN MQTT_ADDR_LEN + 2 + MQTT_USER_LEN + MQTT_PWD_LEN +MQTT_TOPIC_LEN
|
#define CFG_MQTT_LEN MQTT_ADDR_LEN + 2 + MQTT_USER_LEN + MQTT_PWD_LEN +MQTT_TOPIC_LEN
|
||||||
#define CFG_SYS_LEN DEVNAME_LEN + SSID_LEN + PWD_LEN + 1
|
#define CFG_SYS_LEN DEVNAME_LEN + SSID_LEN + PWD_LEN + 1
|
||||||
|
@ -173,10 +165,8 @@ typedef struct {
|
||||||
#define ADDR_INV_CH_NAME ADDR_INV_CH_PWR + INV_CH_CH_PWR_LEN
|
#define ADDR_INV_CH_NAME ADDR_INV_CH_PWR + INV_CH_CH_PWR_LEN
|
||||||
#define ADDR_INV_INTERVAL ADDR_INV_CH_NAME + INV_CH_CH_NAME_LEN
|
#define ADDR_INV_INTERVAL ADDR_INV_CH_NAME + INV_CH_CH_NAME_LEN
|
||||||
#define ADDR_INV_MAX_RTRY ADDR_INV_INTERVAL + INV_INTERVAL_LEN
|
#define ADDR_INV_MAX_RTRY ADDR_INV_INTERVAL + INV_INTERVAL_LEN
|
||||||
#define ADDR_INV_PWR_LIM ADDR_INV_MAX_RTRY + INV_MAX_RTRY_LEN
|
|
||||||
#define ADDR_INV_PWR_LIM_CON ADDR_INV_PWR_LIM + INV_PWR_LIM_LEN
|
|
||||||
|
|
||||||
#define ADDR_NEXT ADDR_INV_PWR_LIM_CON + INV_PWR_LIM_LEN
|
#define ADDR_NEXT ADDR_INV_MAX_RTRY + INV_INTERVAL_LEN
|
||||||
|
|
||||||
|
|
||||||
#define ADDR_SETTINGS_CRC ADDR_NEXT + 2
|
#define ADDR_SETTINGS_CRC ADDR_NEXT + 2
|
||||||
|
|
|
@ -1,194 +0,0 @@
|
||||||
//-----------------------------------------------------------------------------
|
|
||||||
// 2022 Ahoy, https://www.mikrocontroller.net/topic/525778
|
|
||||||
// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/
|
|
||||||
//-----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
// a) https://www.favicon-generator.org/
|
|
||||||
// b) exiftool -all:all= -r
|
|
||||||
// c) hexlify.py:
|
|
||||||
// import sys
|
|
||||||
// f = open (sys.argv[1], 'rb').read()
|
|
||||||
// for n, c in enumerate(f):
|
|
||||||
// if n % 16 == 0: print (' "', end = '')
|
|
||||||
// print (f"\\x{c:02x}", end = '')
|
|
||||||
// if n % 16 == 15: print ('" \\')
|
|
||||||
// if n % 16 != 15: print ('"')
|
|
||||||
|
|
||||||
#define FAVICON_PANEL_16 \
|
|
||||||
"\x89\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52" \
|
|
||||||
"\x00\x00\x00\x10\x00\x00\x00\x10\x08\x03\x00\x00\x00\x28\x2d\x0f" \
|
|
||||||
"\x53\x00\x00\x00\x04\x67\x41\x4d\x41\x00\x00\xb1\x8f\x0b\xfc\x61" \
|
|
||||||
"\x05\x00\x00\x00\x20\x63\x48\x52\x4d\x00\x00\x7a\x26\x00\x00\x80" \
|
|
||||||
"\x84\x00\x00\xfa\x00\x00\x00\x80\xe8\x00\x00\x75\x30\x00\x00\xea" \
|
|
||||||
"\x60\x00\x00\x3a\x98\x00\x00\x17\x70\x9c\xba\x51\x3c\x00\x00\x01" \
|
|
||||||
"\x9b\x50\x4c\x54\x45\xfc\xfe\xff\xff\xff\xff\xcb\xcd\xcf\x22\x25" \
|
|
||||||
"\x30\x12\x16\x21\x11\x15\x21\x11\x15\x23\x12\x16\x25\x10\x13\x1f" \
|
|
||||||
"\x8f\x91\x93\x9b\x9d\xa1\x14\x17\x22\x14\x18\x25\x14\x17\x23\x13" \
|
|
||||||
"\x17\x22\x14\x17\x24\x17\x19\x27\x22\x24\x2e\xc1\xc2\xc3\xf8\xfa" \
|
|
||||||
"\xfb\x61\x63\x6b\x11\x13\x1f\x15\x19\x24\x17\x19\x24\x18\x1a\x24" \
|
|
||||||
"\x18\x1b\x26\x16\x19\x26\x42\x44\x4b\xe7\xe8\xe9\xfd\xff\xff\xfe" \
|
|
||||||
"\xff\xff\xe0\xe2\xe4\x33\x36\x3f\x16\x19\x24\x16\x17\x22\x16\x18" \
|
|
||||||
"\x22\x17\x18\x23\x11\x12\x1e\x71\x73\x76\xfb\xfc\xfd\xb5\xb7\xba" \
|
|
||||||
"\x1a\x1c\x27\x15\x17\x22\x15\x17\x21\x14\x15\x20\x14\x16\x23\x17" \
|
|
||||||
"\x1a\x26\x1b\x1c\x25\xaa\xac\xad\x7c\x7f\x85\x11\x13\x1d\x16\x18" \
|
|
||||||
"\x24\x17\x19\x25\x18\x1a\x25\x30\x32\x38\xd7\xd9\xd9\xef\xf1\xf2" \
|
|
||||||
"\x47\x49\x51\x12\x13\x1e\x17\x19\x22\x16\x17\x23\x10\x11\x1c\x59" \
|
|
||||||
"\x5a\x5f\xf4\xf6\xf7\xcc\xce\xd0\x24\x25\x2e\x16\x18\x23\x19\x1a" \
|
|
||||||
"\x24\x16\x17\x21\x15\x16\x21\x17\x17\x21\x15\x17\x20\x92\x93\x95" \
|
|
||||||
"\x99\x9a\x9e\x16\x16\x20\x17\x19\x23\x1a\x1b\x25\x23\x25\x2c\xc4" \
|
|
||||||
"\xc6\xc7\xf8\xfa\xfa\x61\x62\x68\x15\x16\x20\x11\x12\x1d\x44\x45" \
|
|
||||||
"\x49\xea\xec\xec\xdf\xe1\xe2\x33\x35\x3b\x15\x16\x1f\x13\x14\x1d" \
|
|
||||||
"\x78\x7a\x7c\xfc\xfd\xfe\xb2\xb4\xb7\x1a\x1c\x26\x18\x1b\x25\x1a" \
|
|
||||||
"\x1b\x22\x19\x1b\x22\xae\xaf\xb0\x86\x88\x8b\x14\x16\x1e\x19\x1b" \
|
|
||||||
"\x25\x17\x19\x21\x17\x19\x20\x12\x13\x1a\x2c\x2d\x32\xcc\xce\xcf" \
|
|
||||||
"\xfb\xfe\xff\xd8\xdb\xdb\x72\x74\x76\x25\x26\x2c\x19\x1c\x24\x13" \
|
|
||||||
"\x16\x1d\x11\x13\x18\x4c\x4d\x4f\xc1\xc3\xc3\xe2\xe4\xe6\xf6\xf8" \
|
|
||||||
"\xf8\xb6\xb9\xb9\x4f\x51\x55\x1b\x1c\x23\x19\x1b\x23\x15\x17\x1f" \
|
|
||||||
"\x70\x71\x72\xbb\xbb\xbc\xc3\xc4\xc5\xde\xe0\xe1\xf6\xf8\xf9\xfd" \
|
|
||||||
"\xfe\xff\xe3\xe5\xe6\x7e\x81\x82\x21\x22\x27\x1d\x1f\x26\x94\x95" \
|
|
||||||
"\x95\xbd\xbd\xbe\xbd\xbe\xbe\xc8\xca\xcb\xe5\xe7\xe9\xfa\xfc\xfd" \
|
|
||||||
"\x52\x5f\xd3\xea\x00\x00\x00\x01\x62\x4b\x47\x44\x01\xff\x02\x2d" \
|
|
||||||
"\xde\x00\x00\x00\xd2\x49\x44\x41\x54\x18\xd3\x63\x60\x00\x02\x46" \
|
|
||||||
"\x26\x66\x16\x16\x56\x36\x76\x0e\x4e\x46\x06\x30\x60\xe4\xe2\xe6" \
|
|
||||||
"\xe1\xe5\xe3\x17\x10\x14\x82\x0a\x08\x8b\x88\x8a\x89\x4b\x48\x4a" \
|
|
||||||
"\x49\xcb\xc8\x82\xf9\x72\xf2\x0a\xdc\x8a\x4a\xca\x2a\xaa\x6a\xea" \
|
|
||||||
"\x50\x1d\x1a\x9a\x5a\xda\x3a\xba\x7a\xfa\x06\x50\x1d\xb2\x86\x46" \
|
|
||||||
"\x5a\xbc\xc6\x26\xa6\x66\xe6\x72\x10\xbe\x85\xa5\x95\xb5\x89\xa9" \
|
|
||||||
"\x8d\xad\x9d\x3d\xc4\x08\x46\x07\x47\x27\x67\x17\x57\x37\x77\x0f" \
|
|
||||||
"\x39\xa8\x11\x9e\x4a\xa6\x36\x5e\xde\x3e\xbe\x7e\x50\x23\xfc\x03" \
|
|
||||||
"\x02\x55\x8c\x9d\x55\x82\x82\x43\xa0\x96\x86\x86\x85\x4b\x48\xb8" \
|
|
||||||
"\x84\x47\x44\x46\x41\x75\x44\xc7\x78\xc7\xba\x87\xc7\xc5\x27\x40" \
|
|
||||||
"\x75\xc8\x25\x26\x25\x7b\xa7\xa4\xa6\xa5\x67\x64\x42\x5d\x91\x95" \
|
|
||||||
"\x9d\x93\x92\x9b\x97\x5f\x50\x58\x24\x0c\x55\x52\x5c\x52\x5a\x56" \
|
|
||||||
"\x5e\x51\x59\x55\x5d\x53\x0b\x11\xa9\x93\xab\x6f\x68\x6c\x6a\x6e" \
|
|
||||||
"\x69\x6d\x6b\xef\x60\x00\x00\x01\x53\x2a\x2a\x63\x34\xcd\xf7\x00" \
|
|
||||||
"\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82"
|
|
||||||
|
|
||||||
#define FAVICON_PANEL_32 \
|
|
||||||
"\x89\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52" \
|
|
||||||
"\x00\x00\x00\x20\x00\x00\x00\x20\x08\x06\x00\x00\x00\x73\x7a\x7a" \
|
|
||||||
"\xf4\x00\x00\x00\x04\x67\x41\x4d\x41\x00\x00\xb1\x8f\x0b\xfc\x61" \
|
|
||||||
"\x05\x00\x00\x00\x20\x63\x48\x52\x4d\x00\x00\x7a\x26\x00\x00\x80" \
|
|
||||||
"\x84\x00\x00\xfa\x00\x00\x00\x80\xe8\x00\x00\x75\x30\x00\x00\xea" \
|
|
||||||
"\x60\x00\x00\x3a\x98\x00\x00\x17\x70\x9c\xba\x51\x3c\x00\x00\x00" \
|
|
||||||
"\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00" \
|
|
||||||
"\x00\x07\x4a\x49\x44\x41\x54\x58\xc3\x9d\x97\xd9\x72\x5d\xc5\x15" \
|
|
||||||
"\x86\xbf\xee\xde\xd3\xd9\x67\xd0\x99\x74\x34\x3b\x48\x32\x48\xe0" \
|
|
||||||
"\x21\x60\xe1\x32\xd8\x15\x5e\x80\xdc\xf2\x00\xe4\x41\x12\x9e\x21" \
|
|
||||||
"\x37\xc9\x43\xa4\x20\x95\xe2\x9a\x82\x0c\x10\x70\x62\x06\x0f\xc2" \
|
|
||||||
"\x24\x15\xe2\x59\x96\xce\xbc\xe7\xb1\x73\x21\x93\x32\x20\x4b\xa7" \
|
|
||||||
"\xe8\xaa\x7d\xb3\x2f\x7a\x7d\xf5\xff\xfd\xaf\x5e\x2d\xf2\x52\x6b" \
|
|
||||||
"\x66\x58\x4a\xc0\x67\xff\xf8\x27\x6f\xbd\xf5\x36\xa3\xf1\x04\xad" \
|
|
||||||
"\x0b\x84\x94\x68\xad\xc9\xf3\x1c\xc7\x76\x08\xa3\x04\xdb\xb6\xc8" \
|
|
||||||
"\xf3\x12\x0d\xb8\x15\x9b\x28\x8c\x90\x52\xb0\xfe\xdc\x02\xbf\xff" \
|
|
||||||
"\xdd\x6f\xb9\x72\xf9\x32\xc5\x53\x15\xe5\x2c\xc5\xbf\x5b\xdf\x7e" \
|
|
||||||
"\x7b\x87\x34\x4b\x68\x36\xeb\x38\x8e\x4d\xb3\x59\xc7\xb6\x0d\x3a" \
|
|
||||||
"\x9d\x16\xb6\x63\x52\x71\x6c\xea\xb5\x2a\x52\x0a\x6a\xd5\x0a\x96" \
|
|
||||||
"\x65\x02\x1a\xc7\x36\xd9\xde\xda\x64\x6b\x6b\x9b\xf2\x07\x7b\x1a" \
|
|
||||||
"\xb3\x16\xcf\x8a\x82\x4f\x3f\xbd\x4a\x18\x86\x34\x1a\x73\x68\x24" \
|
|
||||||
"\x52\x1a\x08\xa1\xa8\x54\x1c\xa2\x30\xa4\xd9\xac\xd3\x9d\xef\xa0" \
|
|
||||||
"\xb5\x66\x65\x65\x91\x30\x8c\x50\x52\xd3\x6a\xd5\xd9\xda\x7a\x81" \
|
|
||||||
"\x56\xbb\xcd\x0f\xf5\x9e\x09\x40\x0a\x78\xb4\xf7\x98\x8f\x3f\xfe" \
|
|
||||||
"\x8c\xa2\x28\x99\x4c\xa6\x94\x25\xe4\x59\x49\x14\x47\xc4\x51\x4c" \
|
|
||||||
"\x92\x24\x54\x9c\x0a\xfd\xfe\x88\x3c\xcb\x89\xc2\x88\x2c\xcb\xd1" \
|
|
||||||
"\x5a\x23\x74\xce\xf6\xf6\x36\x4a\xf0\x3d\xf9\x67\x06\x10\xc0\xd7" \
|
|
||||||
"\xb7\xbf\xa1\x3f\x18\xb1\xb0\x30\x8f\xef\xfb\x98\xa6\x49\x96\x6b" \
|
|
||||||
"\xda\x9d\x39\x6c\x4b\x31\x1c\x8e\xa9\xd5\xaa\x8c\xc7\x1e\xae\xeb" \
|
|
||||||
"\x50\x71\xab\x0c\xfa\x63\x4c\x43\xb2\x79\xfa\x39\x2e\xbd\xf6\x1a" \
|
|
||||||
"\x47\x1d\xb6\x99\x2d\xb8\x7a\xf5\x73\xee\xde\x79\x80\x6d\x9b\x64" \
|
|
||||||
"\x79\x86\x6d\xdb\x64\x59\x86\x28\x73\x22\xa1\xa1\x2c\xb1\x2d\x03" \
|
|
||||||
"\xd3\x54\x2c\x2e\xf6\x70\x6c\x87\xb2\x28\xa8\xd5\x1c\xce\x9f\x3b" \
|
|
||||||
"\xc3\xda\xda\x29\x4a\xfd\x13\x00\x84\x00\xcf\x0f\xb8\x7e\xfd\x3a" \
|
|
||||||
"\xbd\x85\x0e\x8e\x6d\x31\x99\x7a\x58\x96\xc5\x70\x38\x22\x8a\x63" \
|
|
||||||
"\xd2\x24\xc5\xb2\x4c\x1e\xde\x7f\x44\x9c\xa4\xa4\x71\x42\x91\xe7" \
|
|
||||||
"\x14\x65\x49\x96\xda\xac\xad\xad\xe2\x58\xe6\x8f\xe4\x9f\x09\x40" \
|
|
||||||
"\x02\xb7\xbf\xf9\x86\x4f\x3e\xb9\x4a\x92\xc4\x68\x5d\xd2\x68\xd4" \
|
|
||||||
"\x71\x9c\x0a\x4a\x49\x1c\xdb\xe2\xe0\x60\xc0\xe2\x52\x0f\x7f\xea" \
|
|
||||||
"\xe3\xe4\x19\xae\x5b\xe5\xe0\x60\x88\x10\x8a\x4e\xbb\xc5\xeb\x97" \
|
|
||||||
"\x2f\x3f\x73\xff\x99\x2c\xd8\xdd\xbd\xcd\x70\x34\x46\x6b\xcd\x74" \
|
|
||||||
"\xea\x63\x99\x16\x79\x9e\x63\x5b\x06\x9e\x80\x2c\xcb\x99\x4e\x7d" \
|
|
||||||
"\x92\xa4\xa0\xd9\x6a\xb1\xd0\xeb\x90\x65\x39\x96\x65\xb0\xb3\xf3" \
|
|
||||||
"\x73\xce\xbc\x74\xe6\x47\xf1\x9b\x19\x20\x2b\x0a\xbe\xf8\xe2\x3a" \
|
|
||||||
"\x95\x4a\x85\xb9\x46\x95\xfe\x60\x48\xb7\xdb\x65\x32\x1a\xa1\x94" \
|
|
||||||
"\x62\x3a\x0d\x90\x52\x12\x85\x11\x7e\x90\x50\x14\x05\xfd\xbd\x3d" \
|
|
||||||
"\xb2\x3c\xa3\xde\x70\x59\x5b\x5b\x65\xae\x39\xc7\xb3\xda\xdd\xb1" \
|
|
||||||
"\x00\x52\xc0\x83\x47\x7b\x7c\xf8\xd1\xdf\x18\x8f\x27\x44\xbe\x07" \
|
|
||||||
"\x42\xe1\xfb\x11\x85\x96\x74\xda\x6d\xb4\x50\xb8\xae\x4d\x51\x94" \
|
|
||||||
"\x98\x56\x82\xeb\x3a\x0c\xfb\x03\x84\x90\x28\x25\x39\x7f\xfe\x3c" \
|
|
||||||
"\x12\x28\x7e\x8a\x02\x02\xd8\xdd\xfd\x9a\xbd\xbd\x7d\x96\x16\x7b" \
|
|
||||||
"\x84\xbe\x87\x46\x3e\xe9\x03\x9a\xfb\xf7\xf6\x88\x93\x84\x38\x76" \
|
|
||||||
"\x28\x72\xa8\x56\x5d\xe6\xe7\x3b\x14\x79\x86\xd6\x70\xee\xdc\xf3" \
|
|
||||||
"\x5c\xba\x74\x89\xe3\x7a\xfd\x89\x16\x5c\xbb\xf6\x25\xfd\x7e\x9f" \
|
|
||||||
"\xb9\x46\x0d\x29\x25\xed\x76\x0b\x06\x23\x9a\xed\x16\x9e\x17\x81" \
|
|
||||||
"\x00\x43\x29\x02\xcf\x47\x08\xf8\xd7\xed\x7f\x13\xc7\x09\x8d\xb9" \
|
|
||||||
"\x3a\xa7\x37\x37\x58\x5e\x59\x39\x32\x7e\x27\x02\x08\x01\x5e\x10" \
|
|
||||||
"\x70\xfd\xc6\x4d\x2a\x8e\x4d\x96\x66\x24\x49\x42\x9e\x17\x44\x51" \
|
|
||||||
"\x8c\x32\x14\x68\x58\x5d\x5d\x24\x4f\x13\xa4\x94\xd4\xea\xd5\x43" \
|
|
||||||
"\xf9\xa5\xa4\x2c\x33\x36\xd6\x9f\xc3\x32\x8c\x23\xe3\x77\x22\x80" \
|
|
||||||
"\x7c\x22\xff\xd5\xcf\xae\xd2\xeb\x75\x48\xd3\x0c\xcb\xb2\x48\x93" \
|
|
||||||
"\x84\x2c\xcb\x49\xa2\x98\x30\x88\x88\xa3\x94\xb2\x48\x31\x0c\x03" \
|
|
||||||
"\x77\xbe\x45\xde\xa8\xe3\xe4\x05\xa7\xd6\x7a\x5c\xbe\x72\xe5\x24" \
|
|
||||||
"\x81\x8f\xb7\xe0\xe6\xcd\x5b\x3c\x7c\xf0\x08\x29\x0c\x0a\xad\xe9" \
|
|
||||||
"\x74\x5a\x20\x24\xcb\x2b\x8b\xa4\x69\x8a\x65\xd9\x28\x43\x31\x1a" \
|
|
||||||
"\x44\x80\xe4\xe1\x83\x3d\x82\x20\xa2\x5a\xaf\xb2\xbd\xf5\x02\xdb" \
|
|
||||||
"\x2f\xbe\xf8\xcc\xf8\x9d\x08\x90\xe5\x05\x37\x6f\xdc\xa2\xd1\x68" \
|
|
||||||
"\x50\x6f\xcc\xb1\xbf\xdf\x27\x08\x62\xa2\x30\xc4\x77\x2c\xb4\x2e" \
|
|
||||||
"\x69\xb7\x9b\x74\xe6\xbb\xa4\x69\x4a\xaf\xd7\x65\xd0\x1f\x20\xa4" \
|
|
||||||
"\x40\x88\x92\x53\xa7\x56\xa9\xd7\x6a\xc7\xfa\xff\x4c\x00\x29\xe0" \
|
|
||||||
"\xde\xc3\x87\x7c\xf0\xc1\x5f\x08\xc2\x14\x43\x05\x74\x3b\x73\x18" \
|
|
||||||
"\x96\xc3\x74\x3c\x01\x34\xa3\xe1\x98\xd1\x70\x42\x7f\x30\xa1\x2c" \
|
|
||||||
"\x72\xb2\x34\xc3\xa9\xb8\x28\x43\xd1\xee\x34\xb8\x70\xe1\x02\xe2" \
|
|
||||||
"\x44\x03\x9e\x01\x70\x18\xbf\x5d\x06\xc3\x21\x8e\x63\x33\x19\x4f" \
|
|
||||||
"\xb0\x2c\x93\x42\x2b\x1a\xcd\x26\xad\xa6\x4b\x51\xe4\xd4\x1b\x75" \
|
|
||||||
"\x1e\xef\x1d\x00\x30\x1a\x4d\xf0\xbd\x90\x8a\x6b\xb1\xbe\x73\x96" \
|
|
||||||
"\x57\x2f\x5e\x64\x96\x51\xeb\x48\x00\x0d\x7c\x7e\xed\x4b\x26\x93" \
|
|
||||||
"\x29\xbd\x5e\x97\x40\x81\x53\xa9\xf0\xf8\xf1\x90\xf1\x68\xcc\xb0" \
|
|
||||||
"\xbf\x8f\x69\x2a\x5c\xd7\xa5\x5a\xab\xd2\xed\xb6\xf1\xfd\x10\xdf" \
|
|
||||||
"\x0f\x50\x0a\xd6\x7f\x76\x8a\x85\x85\xc5\x13\xe5\x87\x23\x46\xb2" \
|
|
||||||
"\xc3\xdb\xcf\xe7\xda\xb5\x6b\x64\x69\x42\x7f\xbf\x4f\x92\xa4\xb8" \
|
|
||||||
"\xd5\x0a\xcd\x66\x8d\x85\xc5\x0e\x08\x41\xa9\x05\x77\xff\x7b\x9f" \
|
|
||||||
"\xc9\xd8\xc3\x0f\x42\x84\x28\x69\xb6\x1b\x74\xba\x2d\x5e\xd8\xda" \
|
|
||||||
"\xc2\x54\xb3\x4d\x7b\xf2\xa8\x1f\xbb\xbb\xbb\xdc\xb8\x79\x8b\xc5" \
|
|
||||||
"\xa5\x1e\xae\xeb\xa0\x81\x7b\x77\x1f\xe2\xf9\x01\xe8\x82\x7a\xdd" \
|
|
||||||
"\xa5\x3b\xdf\xc2\xb4\x14\x52\x68\xa6\xe3\x31\x7b\x8f\x1e\x13\xf8" \
|
|
||||||
"\x01\xed\x76\x8b\x2b\x33\xc4\xef\x58\x0b\xbe\xfa\xea\x16\x77\xee" \
|
|
||||||
"\x3c\xc2\x75\x1d\x2c\xd3\xa0\xd9\x6e\xe2\xfb\x11\x59\x96\xb1\xbf" \
|
|
||||||
"\xb7\x4f\x9e\x17\x28\x29\x31\x2d\x83\xd5\xce\x12\x42\x1c\xaa\xa1" \
|
|
||||||
"\x94\xe2\xf4\xe6\x3a\xa7\x9f\x7f\xfe\xc4\xf8\x3d\x13\x20\xcd\x0b" \
|
|
||||||
"\xae\xdf\xb8\x49\xad\xd1\xc0\x50\x8a\xe9\x64\x42\x56\x68\xa4\x54" \
|
|
||||||
"\x74\x3b\x4d\x3c\xcf\x27\x89\x13\xc2\x20\x24\x0c\x02\x04\x1a\xcb" \
|
|
||||||
"\xb6\x69\x34\x1b\x54\xaa\x15\x4e\x6f\x6e\x50\x75\xdd\x99\xfc\xff" \
|
|
||||||
"\x11\x80\x14\x70\xe7\xfe\x3d\x3e\xfa\xf0\xcf\x58\xa6\xa2\x56\xab" \
|
|
||||||
"\x61\x59\x26\x69\x9a\x32\x9d\x78\xa0\x0b\x40\xb3\xb4\xbc\x80\xe7" \
|
|
||||||
"\x79\x24\x69\x4c\xe0\x07\x8c\xfa\x63\x84\x61\xd0\x68\xb8\x9c\x39" \
|
|
||||||
"\x7b\x96\xa2\xd4\x08\x31\x4b\x08\x7f\x00\x20\x80\x7b\xf7\xef\x93" \
|
|
||||||
"\xc4\x01\xa1\x37\xc1\x9f\x4e\x59\x5a\x5e\xc2\xb2\x4c\x2c\xcb\x24" \
|
|
||||||
"\x8e\x22\xbc\xa9\x47\x59\x14\x08\x43\x31\xbf\xd0\xc5\x75\x1d\xee" \
|
|
||||||
"\xfc\xe7\x2e\x52\xc1\xea\xca\x12\x4b\xcb\x2b\x0c\x87\x23\xea\xf5" \
|
|
||||||
"\xfa\x93\x77\xc1\xf1\x4b\xfd\xfa\x37\xef\xbc\xf3\x34\x41\xbd\x56" \
|
|
||||||
"\x67\x71\xb9\xc7\x78\x32\xe2\xe0\xf1\x3e\x81\x17\xe0\x79\x3e\xdd" \
|
|
||||||
"\x6e\x07\xc3\x30\x10\x42\x20\xa4\xc4\x9b\x7a\x68\x01\xba\x28\x91" \
|
|
||||||
"\x4a\xd2\x6c\xd6\x79\xe9\xa5\x2d\x76\x76\x76\xc8\x8b\x02\x21\x04" \
|
|
||||||
"\xa6\x69\xa0\x94\x3a\x56\x8d\xef\x03\x00\xae\x5b\xe1\xdc\xd9\xb3" \
|
|
||||||
"\xbc\xfc\xca\xcb\x38\xae\xcd\x64\x32\x66\xd0\x1f\x10\xf8\x3e\x41" \
|
|
||||||
"\x10\xd2\x5b\xe8\x61\x3b\x0e\x49\x92\x52\x16\x05\xde\xd4\x23\x4d" \
|
|
||||||
"\x12\x2c\x5b\xf1\xfa\x6b\x97\xd8\xd8\xd8\x20\x4b\x53\xf2\x3c\x07" \
|
|
||||||
"\x0e\x21\xbe\x03\x9f\x09\xe0\x50\x09\xc1\x42\xaf\xc7\xc5\x8b\x17" \
|
|
||||||
"\x59\xdf\x5c\x47\x8b\x92\xc9\x74\xc2\x68\x30\x22\x89\x53\xa2\x30" \
|
|
||||||
"\xa6\x56\xab\xd2\x9d\xef\x12\xf8\x11\x5a\xc3\xf2\x52\x8f\x5f\xbe" \
|
|
||||||
"\xf9\x26\x8d\x46\x9d\xa2\x28\xc8\xb2\x9c\x2c\xcf\x0f\x7d\x36\x0c" \
|
|
||||||
"\x0c\xc3\x3c\x12\xe2\x68\x00\x0e\xbb\xa1\x65\x59\x9c\xde\xdc\xe4" \
|
|
||||||
"\xc2\x2b\xaf\xd0\x6c\xcd\x31\x1a\x8f\x89\xe3\x98\xe9\x68\x8c\x2e" \
|
|
||||||
"\x0b\xb4\x06\xa9\x14\xcd\x56\x93\xed\xad\x4d\xde\x78\xe3\x17\x48" \
|
|
||||||
"\xa9\x00\x8d\x2e\x4b\x8a\x22\x27\xcf\x0f\x5f\x47\x87\x10\x06\x52" \
|
|
||||||
"\x7e\x1f\xe2\xd8\xeb\xf8\xbb\x41\x72\x75\x75\x95\x5f\xbd\xfd\x36" \
|
|
||||||
"\xaf\xee\xec\xf0\xa7\xf7\xdf\xe7\xdd\x3f\xfc\x91\x83\xfd\x7d\xa6" \
|
|
||||||
"\xe3\x11\x85\x96\xd8\x8e\xc9\xd2\xe2\x02\x8e\xe3\xa0\xff\x3f\x7d" \
|
|
||||||
"\x6a\x8a\x3c\x27\x0c\x43\x84\x10\x68\x0d\x5a\x6b\xaa\xd5\x2a\xea" \
|
|
||||||
"\xa9\x2e\x39\xd3\x58\xae\x35\x98\x86\xc9\xab\x3b\x3b\xac\x9d\x3a" \
|
|
||||||
"\xc5\xc6\xfa\x3a\xef\xbe\xf7\x1e\x7f\xff\xeb\xa7\xe4\x71\x46\x99" \
|
|
||||||
"\xe7\xac\xaf\xaf\xa3\x94\xa2\x2c\xf5\x93\x2b\x59\x22\xe5\xe1\xa7" \
|
|
||||||
"\x35\x94\x65\x49\x59\x16\x68\x5d\xf2\x74\x03\xfe\x1f\xc2\x60\x72" \
|
|
||||||
"\xe2\x6a\x9b\x4e\x8f\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60" \
|
|
||||||
"\x82"
|
|
|
@ -17,19 +17,21 @@ union serial_u {
|
||||||
|
|
||||||
|
|
||||||
// units
|
// units
|
||||||
enum {UNIT_V = 0, UNIT_A, UNIT_W, UNIT_WH, UNIT_KWH, UNIT_HZ, UNIT_C, UNIT_PCT, UNIT_VA, UNIT_NONE};
|
enum {UNIT_V = 0, UNIT_A, UNIT_W, UNIT_WH, UNIT_KWH, UNIT_HZ, UNIT_C, UNIT_PCT, UNIT_VAR, UNIT_NONE};
|
||||||
const char* const units[] = {"V", "A", "W", "Wh", "kWh", "Hz", "°C", "%","VAr",""};
|
const char* const units[] = {"V", "A", "W", "Wh", "kWh", "Hz", "°C", "%", "var", ""};
|
||||||
|
|
||||||
|
|
||||||
// field types
|
// field types
|
||||||
enum {FLD_UDC = 0, FLD_IDC, FLD_PDC, FLD_YD, FLD_YW, FLD_YT,
|
enum {FLD_UDC = 0, FLD_IDC, FLD_PDC, FLD_YD, FLD_YW, FLD_YT,
|
||||||
FLD_UAC, FLD_IAC, FLD_PAC, FLD_F, FLD_T, FLD_PCT, FLD_EFF,
|
FLD_UAC, FLD_IAC, FLD_PAC, FLD_F, FLD_T, FLD_PF, FLD_EFF,
|
||||||
FLD_IRR, FLD_PRA,FLD_ALARM_MES_ID,FLD_FW_VERSION,FLD_FW_BUILD_YEAR,
|
FLD_IRR, FLD_Q, FLD_EVT, FLD_FW_VERSION, FLD_FW_BUILD_YEAR,
|
||||||
FLD_FW_BUILD_MONTH_DAY,FLD_HW_ID,FLD_ACT_PWR_LIMIT,FLD_LAST_ALARM_CODE};
|
FLD_FW_BUILD_MONTH_DAY, FLD_FW_BUILD_HOUR_MINUTE, FLD_HW_ID,
|
||||||
|
FLD_ACT_ACTIVE_PWR_LIMIT, /*FLD_ACT_REACTIVE_PWR_LIMIT, FLD_ACT_PF,*/ FLD_LAST_ALARM_CODE};
|
||||||
|
|
||||||
const char* const fields[] = {"U_DC", "I_DC", "P_DC", "YieldDay", "YieldWeek", "YieldTotal",
|
const char* const fields[] = {"U_DC", "I_DC", "P_DC", "YieldDay", "YieldWeek", "YieldTotal",
|
||||||
"U_AC", "I_AC", "P_AC", "Freq", "Temp", "Pct", "Efficiency", "Irradiation","P_ACr",
|
"U_AC", "I_AC", "P_AC", "F_AC", "Temp", "PF_AC", "Efficiency", "Irradiation","Q_AC",
|
||||||
"ALARM_MES_ID","FWVersion","FWBuildYear","FWBuildMonthDay","HWPartId","PowerLimit","LastAlarmCode"};
|
"ALARM_MES_ID","FWVersion","FWBuildYear","FWBuildMonthDay","FWBuildHourMinute","HWPartId",
|
||||||
|
"active PowerLimit", /*"reactive PowerLimit","Powerfactor",*/ "LastAlarmCode"};
|
||||||
|
const char* const notAvail = "n/a";
|
||||||
|
|
||||||
// mqtt discovery device classes
|
// mqtt discovery device classes
|
||||||
enum {DEVICE_CLS_NONE = 0, DEVICE_CLS_CURRENT, DEVICE_CLS_ENERGY, DEVICE_CLS_PWR, DEVICE_CLS_VOLTAGE, DEVICE_CLS_FREQ, DEVICE_CLS_TEMP};
|
enum {DEVICE_CLS_NONE = 0, DEVICE_CLS_CURRENT, DEVICE_CLS_ENERGY, DEVICE_CLS_PWR, DEVICE_CLS_VOLTAGE, DEVICE_CLS_FREQ, DEVICE_CLS_TEMP};
|
||||||
|
@ -53,7 +55,7 @@ const byteAssign_fieldDeviceClass deviceFieldAssignment[] = {
|
||||||
{FLD_PAC, DEVICE_CLS_PWR, STATE_CLS_MEASUREMENT},
|
{FLD_PAC, DEVICE_CLS_PWR, STATE_CLS_MEASUREMENT},
|
||||||
{FLD_F, DEVICE_CLS_FREQ, STATE_CLS_NONE},
|
{FLD_F, DEVICE_CLS_FREQ, STATE_CLS_NONE},
|
||||||
{FLD_T, DEVICE_CLS_TEMP, STATE_CLS_MEASUREMENT},
|
{FLD_T, DEVICE_CLS_TEMP, STATE_CLS_MEASUREMENT},
|
||||||
{FLD_PCT, DEVICE_CLS_NONE, STATE_CLS_NONE},
|
{FLD_PF, DEVICE_CLS_NONE, STATE_CLS_NONE},
|
||||||
{FLD_EFF, DEVICE_CLS_NONE, STATE_CLS_NONE},
|
{FLD_EFF, DEVICE_CLS_NONE, STATE_CLS_NONE},
|
||||||
{FLD_IRR, DEVICE_CLS_NONE, STATE_CLS_NONE}
|
{FLD_IRR, DEVICE_CLS_NONE, STATE_CLS_NONE}
|
||||||
};
|
};
|
||||||
|
@ -92,12 +94,15 @@ const byteAssign_t InfoAssignment[] = {
|
||||||
{ FLD_FW_VERSION, UNIT_NONE, CH0, 0, 2, 1 },
|
{ FLD_FW_VERSION, UNIT_NONE, CH0, 0, 2, 1 },
|
||||||
{ FLD_FW_BUILD_YEAR, UNIT_NONE, CH0, 2, 2, 1 },
|
{ FLD_FW_BUILD_YEAR, UNIT_NONE, CH0, 2, 2, 1 },
|
||||||
{ FLD_FW_BUILD_MONTH_DAY, UNIT_NONE, CH0, 4, 2, 1 },
|
{ FLD_FW_BUILD_MONTH_DAY, UNIT_NONE, CH0, 4, 2, 1 },
|
||||||
|
{ FLD_FW_BUILD_HOUR_MINUTE, UNIT_NONE, CH0, 6, 2, 1 },
|
||||||
{ FLD_HW_ID, UNIT_NONE, CH0, 8, 2, 1 }
|
{ FLD_HW_ID, UNIT_NONE, CH0, 8, 2, 1 }
|
||||||
};
|
};
|
||||||
#define HMINFO_LIST_LEN (sizeof(InfoAssignment) / sizeof(byteAssign_t))
|
#define HMINFO_LIST_LEN (sizeof(InfoAssignment) / sizeof(byteAssign_t))
|
||||||
|
|
||||||
const byteAssign_t SystemConfigParaAssignment[] = {
|
const byteAssign_t SystemConfigParaAssignment[] = {
|
||||||
{ FLD_ACT_PWR_LIMIT, UNIT_PCT, CH0, 2, 2, 10 }
|
{ FLD_ACT_ACTIVE_PWR_LIMIT, UNIT_PCT, CH0, 2, 2, 10 }/*,
|
||||||
|
{ FLD_ACT_REACTIVE_PWR_LIMIT, UNIT_PCT, CH0, 4, 2, 10 },
|
||||||
|
{ FLD_ACT_PF, UNIT_NONE, CH0, 6, 2, 1000 }*/
|
||||||
};
|
};
|
||||||
#define HMSYSTEM_LIST_LEN (sizeof(SystemConfigParaAssignment) / sizeof(byteAssign_t))
|
#define HMSYSTEM_LIST_LEN (sizeof(SystemConfigParaAssignment) / sizeof(byteAssign_t))
|
||||||
|
|
||||||
|
@ -122,10 +127,11 @@ const byteAssign_t hm1chAssignment[] = {
|
||||||
{ FLD_UAC, UNIT_V, CH0, 14, 2, 10 },
|
{ FLD_UAC, UNIT_V, CH0, 14, 2, 10 },
|
||||||
{ FLD_IAC, UNIT_A, CH0, 22, 2, 100 },
|
{ FLD_IAC, UNIT_A, CH0, 22, 2, 100 },
|
||||||
{ FLD_PAC, UNIT_W, CH0, 18, 2, 10 },
|
{ FLD_PAC, UNIT_W, CH0, 18, 2, 10 },
|
||||||
{ FLD_PRA, UNIT_VA, CH0, 20, 2, 10 },
|
{ FLD_Q, UNIT_VAR, CH0, 20, 2, 10 },
|
||||||
{ FLD_F, UNIT_HZ, CH0, 16, 2, 100 },
|
{ FLD_F, UNIT_HZ, CH0, 16, 2, 100 },
|
||||||
|
{ FLD_PF, UNIT_NONE, CH0, 24, 2, 1000 },
|
||||||
{ FLD_T, UNIT_C, CH0, 26, 2, 10 },
|
{ FLD_T, UNIT_C, CH0, 26, 2, 10 },
|
||||||
{ FLD_ALARM_MES_ID, UNIT_NONE, CH0, 24, 2, 1 },
|
{ FLD_EVT, UNIT_NONE, CH0, 28, 2, 1 },
|
||||||
{ FLD_YD, UNIT_WH, CH0, CALC_YD_CH0, 0, CMD_CALC },
|
{ FLD_YD, UNIT_WH, CH0, CALC_YD_CH0, 0, CMD_CALC },
|
||||||
{ FLD_YT, UNIT_KWH, CH0, CALC_YT_CH0, 0, CMD_CALC },
|
{ FLD_YT, UNIT_KWH, CH0, CALC_YT_CH0, 0, CMD_CALC },
|
||||||
{ FLD_PDC, UNIT_W, CH0, CALC_PDC_CH0, 0, CMD_CALC },
|
{ FLD_PDC, UNIT_W, CH0, CALC_PDC_CH0, 0, CMD_CALC },
|
||||||
|
@ -155,10 +161,11 @@ const byteAssign_t hm2chAssignment[] = {
|
||||||
{ FLD_UAC, UNIT_V, CH0, 26, 2, 10 },
|
{ FLD_UAC, UNIT_V, CH0, 26, 2, 10 },
|
||||||
{ FLD_IAC, UNIT_A, CH0, 34, 2, 100 },
|
{ FLD_IAC, UNIT_A, CH0, 34, 2, 100 },
|
||||||
{ FLD_PAC, UNIT_W, CH0, 30, 2, 10 },
|
{ FLD_PAC, UNIT_W, CH0, 30, 2, 10 },
|
||||||
{ FLD_PRA, UNIT_VA, CH0, 32, 2, 10 },
|
{ FLD_Q, UNIT_VAR, CH0, 32, 2, 10 },
|
||||||
{ FLD_F, UNIT_HZ, CH0, 28, 2, 100 },
|
{ FLD_F, UNIT_HZ, CH0, 28, 2, 100 },
|
||||||
|
{ FLD_PF, UNIT_NONE, CH0, 36, 2, 1000 },
|
||||||
{ FLD_T, UNIT_C, CH0, 38, 2, 10 },
|
{ FLD_T, UNIT_C, CH0, 38, 2, 10 },
|
||||||
{ FLD_ALARM_MES_ID, UNIT_NONE, CH0, 40, 2, 1 },
|
{ FLD_EVT, UNIT_NONE, CH0, 40, 2, 1 },
|
||||||
{ FLD_YD, UNIT_WH, CH0, CALC_YD_CH0, 0, CMD_CALC },
|
{ FLD_YD, UNIT_WH, CH0, CALC_YD_CH0, 0, CMD_CALC },
|
||||||
{ FLD_YT, UNIT_KWH, CH0, CALC_YT_CH0, 0, CMD_CALC },
|
{ FLD_YT, UNIT_KWH, CH0, CALC_YT_CH0, 0, CMD_CALC },
|
||||||
{ FLD_PDC, UNIT_W, CH0, CALC_PDC_CH0, 0, CMD_CALC },
|
{ FLD_PDC, UNIT_W, CH0, CALC_PDC_CH0, 0, CMD_CALC },
|
||||||
|
@ -203,11 +210,11 @@ const byteAssign_t hm4chAssignment[] = {
|
||||||
{ FLD_UAC, UNIT_V, CH0, 46, 2, 10 },
|
{ FLD_UAC, UNIT_V, CH0, 46, 2, 10 },
|
||||||
{ FLD_IAC, UNIT_A, CH0, 54, 2, 100 },
|
{ FLD_IAC, UNIT_A, CH0, 54, 2, 100 },
|
||||||
{ FLD_PAC, UNIT_W, CH0, 50, 2, 10 },
|
{ FLD_PAC, UNIT_W, CH0, 50, 2, 10 },
|
||||||
{ FLD_PRA, UNIT_VA, CH0, 52, 2, 10 },
|
{ FLD_Q, UNIT_VAR, CH0, 52, 2, 10 },
|
||||||
{ FLD_F, UNIT_HZ, CH0, 48, 2, 100 },
|
{ FLD_F, UNIT_HZ, CH0, 48, 2, 100 },
|
||||||
{ FLD_PCT, UNIT_PCT, CH0, 56, 2, 10 },
|
{ FLD_PF, UNIT_NONE, CH0, 56, 2, 1000 },
|
||||||
{ FLD_T, UNIT_C, CH0, 58, 2, 10 },
|
{ FLD_T, UNIT_C, CH0, 58, 2, 10 },
|
||||||
{ FLD_ALARM_MES_ID, UNIT_NONE, CH0, 60, 2, 1 },
|
{ FLD_EVT, UNIT_NONE, CH0, 60, 2, 1 },
|
||||||
{ FLD_YD, UNIT_WH, CH0, CALC_YD_CH0, 0, CMD_CALC },
|
{ FLD_YD, UNIT_WH, CH0, CALC_YD_CH0, 0, CMD_CALC },
|
||||||
{ FLD_YT, UNIT_KWH, CH0, CALC_YT_CH0, 0, CMD_CALC },
|
{ FLD_YT, UNIT_KWH, CH0, CALC_YT_CH0, 0, CMD_CALC },
|
||||||
{ FLD_PDC, UNIT_W, CH0, CALC_PDC_CH0, 0, CMD_CALC },
|
{ FLD_PDC, UNIT_W, CH0, CALC_PDC_CH0, 0, CMD_CALC },
|
||||||
|
|
|
@ -23,7 +23,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// forward declaration of class
|
// forward declaration of class
|
||||||
template <class RECORDTYPE=float>
|
template <class REC_TYP=float>
|
||||||
class Inverter;
|
class Inverter;
|
||||||
|
|
||||||
|
|
||||||
|
@ -55,6 +55,13 @@ struct calcFunc_t {
|
||||||
func_t<T>* func; // function pointer
|
func_t<T>* func; // function pointer
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template<class T=float>
|
||||||
|
struct record_t {
|
||||||
|
byteAssign_t* assign; // assigment of bytes in payload
|
||||||
|
uint8_t length; // length of the assignment list
|
||||||
|
T *record; // data pointer
|
||||||
|
uint32_t ts; // timestamp of last received payload
|
||||||
|
};
|
||||||
|
|
||||||
class CommandAbstract {
|
class CommandAbstract {
|
||||||
public:
|
public:
|
||||||
|
@ -64,8 +71,7 @@ class CommandAbstract {
|
||||||
};
|
};
|
||||||
virtual ~CommandAbstract() {};
|
virtual ~CommandAbstract() {};
|
||||||
|
|
||||||
const uint8_t getCmd()
|
const uint8_t getCmd() {
|
||||||
{
|
|
||||||
return _Cmd;
|
return _Cmd;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,34 +100,33 @@ const calcFunc_t<T> calcFunctions[] = {
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
template <class RECORDTYPE>
|
template <class REC_TYP>
|
||||||
class Inverter {
|
class Inverter {
|
||||||
public:
|
public:
|
||||||
uint8_t id; // unique id
|
uint8_t id; // unique id
|
||||||
char name[MAX_NAME_LENGTH]; // human readable name, eg. "HM-600.1"
|
char name[MAX_NAME_LENGTH]; // human readable name, eg. "HM-600.1"
|
||||||
uint8_t type; // integer which refers to inverter type
|
uint8_t type; // integer which refers to inverter type
|
||||||
byteAssign_t* assign; // type of inverter
|
|
||||||
uint8_t listLen; // length of assignments
|
|
||||||
uint16_t alarmMesIndex; // Last recorded Alarm Message Index
|
uint16_t alarmMesIndex; // Last recorded Alarm Message Index
|
||||||
uint16_t fwVersion; // Firmware Version from Info Command Request
|
uint16_t fwVersion; // Firmware Version from Info Command Request
|
||||||
uint16_t powerLimit[2]; // limit power output
|
uint16_t powerLimit[2]; // limit power output
|
||||||
uint16_t actPowerLimit; //
|
float actPowerLimit; // actual power limit
|
||||||
uint8_t devControlCmd; // carries the requested cmd
|
uint8_t devControlCmd; // carries the requested cmd
|
||||||
bool devControlRequest; // true if change needed
|
bool devControlRequest; // true if change needed
|
||||||
serial_u serial; // serial number as on barcode
|
serial_u serial; // serial number as on barcode
|
||||||
serial_u radioId; // id converted to modbus
|
serial_u radioId; // id converted to modbus
|
||||||
uint8_t channels; // number of PV channels (1-4)
|
uint8_t channels; // number of PV channels (1-4)
|
||||||
uint32_t ts; // timestamp of last received payload
|
record_t<REC_TYP> recordMeas; // structure for measured values
|
||||||
RECORDTYPE *record; // pointer for values
|
record_t<REC_TYP> recordInfo; // structure for info values
|
||||||
|
record_t<REC_TYP> recordConfig; // structure for system config values
|
||||||
|
record_t<REC_TYP> recordAlarm; // structure for alarm values
|
||||||
uint16_t chMaxPwr[4]; // maximum power of the modules (Wp)
|
uint16_t chMaxPwr[4]; // maximum power of the modules (Wp)
|
||||||
char chName[4][MAX_NAME_LENGTH]; // human readable name for channel
|
char chName[4][MAX_NAME_LENGTH]; // human readable name for channels
|
||||||
String lastAlarmMsg;
|
String lastAlarmMsg;
|
||||||
bool initialized; // needed to check if the inverter was correctly added (ESP32 specific - union types are never null)
|
bool initialized; // needed to check if the inverter was correctly added (ESP32 specific - union types are never null)
|
||||||
|
|
||||||
Inverter() {
|
Inverter() {
|
||||||
ts = 0;
|
|
||||||
powerLimit[0] = 0xffff; // 65535 W Limit -> unlimited
|
powerLimit[0] = 0xffff; // 65535 W Limit -> unlimited
|
||||||
powerLimit[1] = NoPowerLimit; //
|
powerLimit[1] = AbsolutNonPersistent; // default power limit setting
|
||||||
actPowerLimit = 0xffff; // init feedback from inverter to -1
|
actPowerLimit = 0xffff; // init feedback from inverter to -1
|
||||||
devControlRequest = false;
|
devControlRequest = false;
|
||||||
devControlCmd = InitDataState;
|
devControlCmd = InitDataState;
|
||||||
|
@ -136,8 +141,7 @@ class Inverter {
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
void enqueCommand(uint8_t cmd)
|
void enqueCommand(uint8_t cmd) {
|
||||||
{
|
|
||||||
_commandQueue.push(std::make_shared<T>(cmd));
|
_commandQueue.push(std::make_shared<T>(cmd));
|
||||||
DPRINTLN(DBG_INFO, "enqueuedCmd: " + String(cmd));
|
DPRINTLN(DBG_INFO, "enqueuedCmd: " + String(cmd));
|
||||||
}
|
}
|
||||||
|
@ -155,8 +159,8 @@ class Inverter {
|
||||||
_commandQueue.pop();
|
_commandQueue.pop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
uint8_t getQueuedCmd()
|
|
||||||
{
|
uint8_t getQueuedCmd() {
|
||||||
if (_commandQueue.empty()){
|
if (_commandQueue.empty()){
|
||||||
// Fill with default commands
|
// Fill with default commands
|
||||||
enqueCommand<InfoCommand>(RealTimeRunData_Debug);
|
enqueCommand<InfoCommand>(RealTimeRunData_Debug);
|
||||||
|
@ -175,394 +179,289 @@ class Inverter {
|
||||||
|
|
||||||
void init(void) {
|
void init(void) {
|
||||||
DPRINTLN(DBG_VERBOSE, F("hmInverter.h:init"));
|
DPRINTLN(DBG_VERBOSE, F("hmInverter.h:init"));
|
||||||
getAssignment();
|
initAssignment(&recordMeas, RealTimeRunData_Debug);
|
||||||
|
initAssignment(&recordInfo, InverterDevInform_All);
|
||||||
|
initAssignment(&recordConfig, SystemConfigPara);
|
||||||
|
initAssignment(&recordAlarm, AlarmData);
|
||||||
toRadioId();
|
toRadioId();
|
||||||
record = new RECORDTYPE[listLen];
|
|
||||||
memset(name, 0, MAX_NAME_LENGTH);
|
memset(name, 0, MAX_NAME_LENGTH);
|
||||||
memset(chName, 0, MAX_NAME_LENGTH * 4);
|
memset(chName, 0, MAX_NAME_LENGTH * 4);
|
||||||
memset(record, 0, sizeof(RECORDTYPE) * listLen);
|
|
||||||
initialized = true;
|
initialized = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t getPosByChFld(uint8_t channel, uint8_t fieldId) {
|
uint8_t getPosByChFld(uint8_t channel, uint8_t fieldId, record_t<> *rec) {
|
||||||
DPRINTLN(DBG_VERBOSE, F("hmInverter.h:getPosByChFld"));
|
DPRINTLN(DBG_VERBOSE, F("hmInverter.h:getPosByChFld"));
|
||||||
uint8_t pos = 0;
|
uint8_t pos = 0;
|
||||||
for(; pos < listLen; pos++) {
|
if(NULL != rec) {
|
||||||
if((assign[pos].ch == channel) && (assign[pos].fieldId == fieldId))
|
for(; pos < rec->length; pos++) {
|
||||||
|
if((rec->assign[pos].ch == channel) && (rec->assign[pos].fieldId == fieldId))
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return (pos >= listLen) ? 0xff : pos;
|
return (pos >= rec->length) ? 0xff : pos;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return 0xff;
|
||||||
}
|
}
|
||||||
|
|
||||||
const char *getFieldName(uint8_t pos) {
|
byteAssign_t *getByteAssign(uint8_t pos, record_t<> *rec) {
|
||||||
|
return &rec->assign[pos];
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *getFieldName(uint8_t pos, record_t<> *rec) {
|
||||||
DPRINTLN(DBG_VERBOSE, F("hmInverter.h:getFieldName"));
|
DPRINTLN(DBG_VERBOSE, F("hmInverter.h:getFieldName"));
|
||||||
return fields[assign[pos].fieldId];
|
if(NULL != rec)
|
||||||
|
return fields[rec->assign[pos].fieldId];
|
||||||
|
return notAvail;
|
||||||
}
|
}
|
||||||
|
|
||||||
const char *getUnit(uint8_t pos) {
|
const char *getUnit(uint8_t pos, record_t<> *rec) {
|
||||||
DPRINTLN(DBG_VERBOSE, F("hmInverter.h:getUnit"));
|
DPRINTLN(DBG_VERBOSE, F("hmInverter.h:getUnit"));
|
||||||
return units[assign[pos].unitId];
|
if(NULL != rec)
|
||||||
|
return units[rec->assign[pos].unitId];
|
||||||
|
return notAvail;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t getChannel(uint8_t pos) {
|
uint8_t getChannel(uint8_t pos, record_t<> *rec) {
|
||||||
DPRINTLN(DBG_VERBOSE, F("hmInverter.h:getChannel"));
|
DPRINTLN(DBG_VERBOSE, F("hmInverter.h:getChannel"));
|
||||||
return assign[pos].ch;
|
if(NULL != rec)
|
||||||
|
return rec->assign[pos].ch;
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void addValue(uint8_t pos, uint8_t buf[]) {
|
void addValue(uint8_t pos, uint8_t buf[], record_t<> *rec) {
|
||||||
DPRINTLN(DBG_VERBOSE, F("hmInverter.h:addValue"));
|
DPRINTLN(DBG_VERBOSE, F("hmInverter.h:addValue"));
|
||||||
uint8_t cmd = getQueuedCmd();
|
if(NULL != rec) {
|
||||||
uint8_t ptr = assign[pos].start;
|
uint8_t ptr = rec->assign[pos].start;
|
||||||
uint8_t end = ptr + assign[pos].num;
|
uint8_t end = ptr + rec->assign[pos].num;
|
||||||
uint16_t div = assign[pos].div;
|
uint16_t div = rec->assign[pos].div;
|
||||||
|
|
||||||
|
if(NULL != rec) {
|
||||||
if(CMD_CALC != div) {
|
if(CMD_CALC != div) {
|
||||||
uint32_t val = 0;
|
uint32_t val = 0;
|
||||||
do {
|
do {
|
||||||
val <<= 8;
|
val <<= 8;
|
||||||
val |= buf[ptr];
|
val |= buf[ptr];
|
||||||
} while(++ptr != end);
|
} while(++ptr != end);
|
||||||
if ((RECORDTYPE)(div) > 1){
|
if ((REC_TYP)(div) > 1)
|
||||||
record[pos] = (RECORDTYPE)(val) / (RECORDTYPE)(div);
|
rec->record[pos] = (REC_TYP)(val) / (REC_TYP)(div);
|
||||||
|
else
|
||||||
|
rec->record[pos] = (REC_TYP)(val);
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
record[pos] = (RECORDTYPE)(val);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
if(rec == &recordMeas) {
|
||||||
if (cmd == RealTimeRunData_Debug) {
|
DPRINTLN(DBG_VERBOSE, "add real time");
|
||||||
|
|
||||||
// get last alarm message index and save it in the inverter object
|
// get last alarm message index and save it in the inverter object
|
||||||
if (getPosByChFld(0, FLD_ALARM_MES_ID) == pos){
|
if (getPosByChFld(0, FLD_EVT, rec) == pos){
|
||||||
if (alarmMesIndex < record[pos]){
|
if (alarmMesIndex < rec->record[pos]){
|
||||||
alarmMesIndex = record[pos];
|
alarmMesIndex = rec->record[pos];
|
||||||
//enqueCommand<InfoCommand>(AlarmUpdate); // What is the function of AlarmUpdate?
|
//enqueCommand<InfoCommand>(AlarmUpdate); // What is the function of AlarmUpdate?
|
||||||
enqueCommand<InfoCommand>(AlarmData);
|
enqueCommand<InfoCommand>(AlarmData);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
alarmMesIndex = record[pos]; // no change
|
alarmMesIndex = rec->record[pos]; // no change
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (cmd == InverterDevInform_All) {
|
else if (rec->assign == InfoAssignment) {
|
||||||
|
DPRINTLN(DBG_DEBUG, "add info");
|
||||||
// get at least the firmware version and save it to the inverter object
|
// get at least the firmware version and save it to the inverter object
|
||||||
if (getPosByChFld(0, FLD_FW_VERSION) == pos){
|
if (getPosByChFld(0, FLD_FW_VERSION, rec) == pos){
|
||||||
fwVersion = record[pos];
|
fwVersion = rec->record[pos];
|
||||||
DPRINT(DBG_DEBUG, F("Inverter FW-Version: ") + String(fwVersion));
|
DPRINT(DBG_DEBUG, F("Inverter FW-Version: ") + String(fwVersion));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (cmd == SystemConfigPara) {
|
else if (rec->assign == SystemConfigParaAssignment) {
|
||||||
|
DPRINTLN(DBG_DEBUG, "add config");
|
||||||
// get at least the firmware version and save it to the inverter object
|
// get at least the firmware version and save it to the inverter object
|
||||||
if (getPosByChFld(0, FLD_ACT_PWR_LIMIT) == pos){
|
if (getPosByChFld(0, FLD_ACT_ACTIVE_PWR_LIMIT, rec) == pos){
|
||||||
actPowerLimit = record[pos];
|
actPowerLimit = rec->record[pos];
|
||||||
DPRINT(DBG_DEBUG, F("Inverter actual power limit: ") + String(actPowerLimit));
|
DPRINT(DBG_DEBUG, F("Inverter actual power limit: ") + String(actPowerLimit, 1));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (cmd == AlarmData){
|
else if (rec->assign == AlarmDataAssignment) {
|
||||||
if (getPosByChFld(0, FLD_LAST_ALARM_CODE) == pos){
|
DPRINTLN(DBG_DEBUG, "add alarm");
|
||||||
lastAlarmMsg = getAlarmStr(record[pos]);
|
if (getPosByChFld(0, FLD_LAST_ALARM_CODE, rec) == pos){
|
||||||
|
lastAlarmMsg = getAlarmStr(rec->record[pos]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
DPRINTLN(DBG_WARN, F("add with unknown assginment"));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
DPRINTLN(DBG_ERROR, F("addValue: assignment not found with cmd 0x"));
|
||||||
}
|
}
|
||||||
|
|
||||||
RECORDTYPE getValue(uint8_t pos) {
|
REC_TYP getValue(uint8_t pos, record_t<> *rec) {
|
||||||
DPRINTLN(DBG_VERBOSE, F("hmInverter.h:getValue"));
|
DPRINTLN(DBG_VERBOSE, F("hmInverter.h:getValue"));
|
||||||
return record[pos];
|
if(NULL == rec)
|
||||||
|
return 0;
|
||||||
|
return rec->record[pos];
|
||||||
}
|
}
|
||||||
|
|
||||||
void doCalculations() {
|
void doCalculations() {
|
||||||
DPRINTLN(DBG_VERBOSE, F("hmInverter.h:doCalculations"));
|
DPRINTLN(DBG_VERBOSE, F("hmInverter.h:doCalculations"));
|
||||||
uint8_t cmd = getQueuedCmd();
|
record_t<> *rec = getRecordStruct(RealTimeRunData_Debug);
|
||||||
getAssignment();
|
for(uint8_t i = 0; i < rec->length; i++) {
|
||||||
if (cmd == RealTimeRunData_Debug){
|
if(CMD_CALC == rec->assign[i].div) {
|
||||||
for(uint8_t i = 0; i < listLen; i++) {
|
rec->record[i] = calcFunctions<REC_TYP>[rec->assign[i].start].func(this, rec->assign[i].num);
|
||||||
if(CMD_CALC == assign[i].div) {
|
|
||||||
record[i] = calcFunctions<RECORDTYPE>[assign[i].start].func(this, assign[i].num);
|
|
||||||
}
|
}
|
||||||
yield();
|
yield();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
bool isAvailable(uint32_t timestamp) {
|
bool isAvailable(uint32_t timestamp, record_t<> *rec) {
|
||||||
DPRINTLN(DBG_VERBOSE, F("hmInverter.h:isAvailable"));
|
DPRINTLN(DBG_VERBOSE, F("hmInverter.h:isAvailable"));
|
||||||
return ((timestamp - ts) < INACT_THRES_SEC);
|
return ((timestamp - rec->ts) < INACT_THRES_SEC);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isProducing(uint32_t timestamp) {
|
bool isProducing(uint32_t timestamp, record_t<> *rec) {
|
||||||
DPRINTLN(DBG_VERBOSE, F("hmInverter.h:isProducing"));
|
DPRINTLN(DBG_VERBOSE, F("hmInverter.h:isProducing"));
|
||||||
if(isAvailable(timestamp)) {
|
if(isAvailable(timestamp, rec)) {
|
||||||
uint8_t pos = getPosByChFld(CH0, FLD_PAC);
|
uint8_t pos = getPosByChFld(CH0, FLD_PAC, rec);
|
||||||
return (getValue(pos) > INACT_PWR_THRESH);
|
return (getValue(pos, rec) > INACT_PWR_THRESH);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t getLastTs(void)
|
uint32_t getLastTs(record_t<> *rec) {
|
||||||
{
|
|
||||||
DPRINTLN(DBG_VERBOSE, F("hmInverter.h:getLastTs"));
|
DPRINTLN(DBG_VERBOSE, F("hmInverter.h:getLastTs"));
|
||||||
return ts;
|
return rec->ts;
|
||||||
}
|
}
|
||||||
|
|
||||||
void getAssignment()
|
record_t<> *getRecordStruct(uint8_t cmd) {
|
||||||
{
|
switch (cmd) {
|
||||||
DPRINTLN(DBG_DEBUG, F("hmInverter.h:getAssignment"));
|
case RealTimeRunData_Debug: return &recordMeas;
|
||||||
// Default assignment;
|
case InverterDevInform_All: return &recordInfo;
|
||||||
if (INV_TYPE_1CH == type)
|
case SystemConfigPara: return &recordConfig;
|
||||||
{
|
case AlarmData: return &recordAlarm;
|
||||||
listLen = (uint8_t)(HM1CH_LIST_LEN);
|
default: break;
|
||||||
assign = (byteAssign_t *)hm1chAssignment;
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void initAssignment(record_t<> *rec, uint8_t cmd) {
|
||||||
|
DPRINTLN(DBG_VERBOSE, F("hmInverter.h:initAssignment"));
|
||||||
|
rec->ts = 0;
|
||||||
|
rec->length = 0;
|
||||||
|
switch (cmd) {
|
||||||
|
case RealTimeRunData_Debug:
|
||||||
|
if (INV_TYPE_1CH == type) {
|
||||||
|
rec->length = (uint8_t)(HM1CH_LIST_LEN);
|
||||||
|
rec->assign = (byteAssign_t *)hm1chAssignment;
|
||||||
channels = 1;
|
channels = 1;
|
||||||
}
|
}
|
||||||
else if (INV_TYPE_2CH == type)
|
else if (INV_TYPE_2CH == type) {
|
||||||
{
|
rec->length = (uint8_t)(HM2CH_LIST_LEN);
|
||||||
listLen = (uint8_t)(HM2CH_LIST_LEN);
|
rec->assign = (byteAssign_t *)hm2chAssignment;
|
||||||
assign = (byteAssign_t *)hm2chAssignment;
|
|
||||||
channels = 2;
|
channels = 2;
|
||||||
}
|
}
|
||||||
else if (INV_TYPE_4CH == type)
|
else if (INV_TYPE_4CH == type) {
|
||||||
{
|
rec->length = (uint8_t)(HM4CH_LIST_LEN);
|
||||||
listLen = (uint8_t)(HM4CH_LIST_LEN);
|
rec->assign = (byteAssign_t *)hm4chAssignment;
|
||||||
assign = (byteAssign_t *)hm4chAssignment;
|
|
||||||
channels = 4;
|
channels = 4;
|
||||||
}
|
}
|
||||||
else
|
else {
|
||||||
{
|
rec->length = 0;
|
||||||
listLen = 0;
|
rec->assign = NULL;
|
||||||
channels = 0;
|
channels = 0;
|
||||||
assign = NULL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (getQueuedCmd()) {
|
|
||||||
case RealTimeRunData_Debug:
|
|
||||||
// Do nothing will use default
|
|
||||||
break;
|
break;
|
||||||
case InverterDevInform_All:
|
case InverterDevInform_All:
|
||||||
listLen = (uint8_t)(HMINFO_LIST_LEN);
|
rec->length = (uint8_t)(HMINFO_LIST_LEN);
|
||||||
assign = (byteAssign_t *)InfoAssignment;
|
rec->assign = (byteAssign_t *)InfoAssignment;
|
||||||
break;
|
break;
|
||||||
case SystemConfigPara:
|
case SystemConfigPara:
|
||||||
listLen = (uint8_t)(HMSYSTEM_LIST_LEN);
|
rec->length = (uint8_t)(HMSYSTEM_LIST_LEN);
|
||||||
assign = (byteAssign_t *)SystemConfigParaAssignment;
|
rec->assign = (byteAssign_t *)SystemConfigParaAssignment;
|
||||||
break;
|
break;
|
||||||
case AlarmData:
|
case AlarmData:
|
||||||
listLen = (uint8_t)(HMALARMDATA_LIST_LEN);
|
rec->length = (uint8_t)(HMALARMDATA_LIST_LEN);
|
||||||
assign = (byteAssign_t *)AlarmDataAssignment;
|
rec->assign = (byteAssign_t *)AlarmDataAssignment;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
DPRINTLN(DBG_INFO, "Parser not implemented");
|
DPRINTLN(DBG_INFO, F("initAssignment: Parser not implemented"));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(0 != rec->length) {
|
||||||
|
rec->record = new REC_TYP[rec->length];
|
||||||
|
memset(rec->record, 0, sizeof(REC_TYP) * rec->length);
|
||||||
}
|
}
|
||||||
String getAlarmStr(u_int16_t alarmCode)
|
}
|
||||||
{
|
|
||||||
switch (alarmCode)
|
String getAlarmStr(u_int16_t alarmCode) {
|
||||||
{
|
switch (alarmCode) { // breaks are intentionally missing!
|
||||||
case 1:
|
case 1: return String(F("Inverter start"));
|
||||||
return String(F("Inverter start"));
|
case 2: return String(F("DTU command failed"));
|
||||||
break;
|
case 121: return String(F("Over temperature protection"));
|
||||||
case 2:
|
case 125: return String(F("Grid configuration parameter error"));
|
||||||
return String(F("DTU command failed"));
|
case 126: return String(F("Software error code 126"));
|
||||||
break;
|
case 127: return String(F("Firmware error"));
|
||||||
case 121:
|
case 128: return String(F("Software error code 128"));
|
||||||
return String(F("Over temperature protection"));
|
case 129: return String(F("Software error code 129"));
|
||||||
break;
|
case 130: return String(F("Offline"));
|
||||||
case 125:
|
case 141: return String(F("Grid overvoltage"));
|
||||||
return String(F("Grid configuration parameter error"));
|
case 142: return String(F("Average grid overvoltage"));
|
||||||
break;
|
case 143: return String(F("Grid undervoltage"));
|
||||||
case 126:
|
case 144: return String(F("Grid overfrequency"));
|
||||||
return String(F("Software error code 126"));
|
case 145: return String(F("Grid underfrequency"));
|
||||||
break;
|
case 146: return String(F("Rapid grid frequency change"));
|
||||||
case 127:
|
case 147: return String(F("Power grid outage"));
|
||||||
return String(F("Firmware error"));
|
case 148: return String(F("Grid disconnection"));
|
||||||
break;
|
case 149: return String(F("Island detected"));
|
||||||
case 128:
|
case 205: return String(F("Input port 1 & 2 overvoltage"));
|
||||||
return String(F("Software error code 128"));
|
case 206: return String(F("Input port 3 & 4 overvoltage"));
|
||||||
break;
|
case 207: return String(F("Input port 1 & 2 undervoltage"));
|
||||||
case 129:
|
case 208: return String(F("Input port 3 & 4 undervoltage"));
|
||||||
return String(F("Software error code 129"));
|
case 209: return String(F("Port 1 no input"));
|
||||||
break;
|
case 210: return String(F("Port 2 no input"));
|
||||||
case 130:
|
case 211: return String(F("Port 3 no input"));
|
||||||
return String(F("Offline"));
|
case 212: return String(F("Port 4 no input"));
|
||||||
break;
|
case 213: return String(F("PV-1 & PV-2 abnormal wiring"));
|
||||||
case 141:
|
case 214: return String(F("PV-3 & PV-4 abnormal wiring"));
|
||||||
return String(F("Grid overvoltage"));
|
case 215: return String(F("PV-1 Input overvoltage"));
|
||||||
break;
|
case 216: return String(F("PV-1 Input undervoltage"));
|
||||||
case 142:
|
case 217: return String(F("PV-2 Input overvoltage"));
|
||||||
return String(F("Average grid overvoltage"));
|
case 218: return String(F("PV-2 Input undervoltage"));
|
||||||
break;
|
case 219: return String(F("PV-3 Input overvoltage"));
|
||||||
case 143:
|
case 220: return String(F("PV-3 Input undervoltage"));
|
||||||
return String(F("Grid undervoltage"));
|
case 221: return String(F("PV-4 Input overvoltage"));
|
||||||
break;
|
case 222: return String(F("PV-4 Input undervoltage"));
|
||||||
case 144:
|
case 301: return String(F("Hardware error code 301"));
|
||||||
return String(F("Grid overfrequency"));
|
case 302: return String(F("Hardware error code 302"));
|
||||||
break;
|
case 303: return String(F("Hardware error code 303"));
|
||||||
case 145:
|
case 304: return String(F("Hardware error code 304"));
|
||||||
return String(F("Grid underfrequency"));
|
case 305: return String(F("Hardware error code 305"));
|
||||||
break;
|
case 306: return String(F("Hardware error code 306"));
|
||||||
case 146:
|
case 307: return String(F("Hardware error code 307"));
|
||||||
return String(F("Rapid grid frequency change"));
|
case 308: return String(F("Hardware error code 308"));
|
||||||
break;
|
case 309: return String(F("Hardware error code 309"));
|
||||||
case 147:
|
case 310: return String(F("Hardware error code 310"));
|
||||||
return String(F("Power grid outage"));
|
case 311: return String(F("Hardware error code 311"));
|
||||||
break;
|
case 312: return String(F("Hardware error code 312"));
|
||||||
case 148:
|
case 313: return String(F("Hardware error code 313"));
|
||||||
return String(F("Grid disconnection"));
|
case 314: return String(F("Hardware error code 314"));
|
||||||
break;
|
case 5041: return String(F("Error code-04 Port 1"));
|
||||||
case 149:
|
case 5042: return String(F("Error code-04 Port 2"));
|
||||||
return String(F("Island detected"));
|
case 5043: return String(F("Error code-04 Port 3"));
|
||||||
break;
|
case 5044: return String(F("Error code-04 Port 4"));
|
||||||
case 205:
|
case 5051: return String(F("PV Input 1 Overvoltage/Undervoltage"));
|
||||||
return String(F("Input port 1 & 2 overvoltage"));
|
case 5052: return String(F("PV Input 2 Overvoltage/Undervoltage"));
|
||||||
break;
|
case 5053: return String(F("PV Input 3 Overvoltage/Undervoltage"));
|
||||||
case 206:
|
case 5054: return String(F("PV Input 4 Overvoltage/Undervoltage"));
|
||||||
return String(F("Input port 3 & 4 overvoltage"));
|
case 5060: return String(F("Abnormal bias"));
|
||||||
break;
|
case 5070: return String(F("Over temperature protection"));
|
||||||
case 207:
|
case 5080: return String(F("Grid Overvoltage/Undervoltage"));
|
||||||
return String(F("Input port 1 & 2 undervoltage"));
|
case 5090: return String(F("Grid Overfrequency/Underfrequency"));
|
||||||
break;
|
case 5100: return String(F("Island detected"));
|
||||||
case 208:
|
case 5120: return String(F("EEPROM reading and writing error"));
|
||||||
return String(F("Input port 3 & 4 undervoltage"));
|
case 5150: return String(F("10 min value grid overvoltage"));
|
||||||
break;
|
case 5200: return String(F("Firmware error"));
|
||||||
case 209:
|
case 8310: return String(F("Shut down"));
|
||||||
return String(F("Port 1 no input"));
|
case 9000: return String(F("Microinverter is suspected of being stolen"));
|
||||||
break;
|
default: return String(F("Unknown"));
|
||||||
case 210:
|
|
||||||
return String(F("Port 2 no input"));
|
|
||||||
break;
|
|
||||||
case 211:
|
|
||||||
return String(F("Port 3 no input"));
|
|
||||||
break;
|
|
||||||
case 212:
|
|
||||||
return String(F("Port 4 no input"));
|
|
||||||
break;
|
|
||||||
case 213:
|
|
||||||
return String(F("PV-1 & PV-2 abnormal wiring"));
|
|
||||||
break;
|
|
||||||
case 214:
|
|
||||||
return String(F("PV-3 & PV-4 abnormal wiring"));
|
|
||||||
break;
|
|
||||||
case 215:
|
|
||||||
return String(F("PV-1 Input overvoltage"));
|
|
||||||
break;
|
|
||||||
case 216:
|
|
||||||
return String(F("PV-1 Input undervoltage"));
|
|
||||||
break;
|
|
||||||
case 217:
|
|
||||||
return String(F("PV-2 Input overvoltage"));
|
|
||||||
break;
|
|
||||||
case 218:
|
|
||||||
return String(F("PV-2 Input undervoltage"));
|
|
||||||
break;
|
|
||||||
case 219:
|
|
||||||
return String(F("PV-3 Input overvoltage"));
|
|
||||||
break;
|
|
||||||
case 220:
|
|
||||||
return String(F("PV-3 Input undervoltage"));
|
|
||||||
break;
|
|
||||||
case 221:
|
|
||||||
return String(F("PV-4 Input overvoltage"));
|
|
||||||
break;
|
|
||||||
case 222:
|
|
||||||
return String(F("PV-4 Input undervoltage"));
|
|
||||||
break;
|
|
||||||
case 301:
|
|
||||||
return String(F("Hardware error code 301"));
|
|
||||||
break;
|
|
||||||
case 302:
|
|
||||||
return String(F("Hardware error code 302"));
|
|
||||||
break;
|
|
||||||
case 303:
|
|
||||||
return String(F("Hardware error code 303"));
|
|
||||||
break;
|
|
||||||
case 304:
|
|
||||||
return String(F("Hardware error code 304"));
|
|
||||||
break;
|
|
||||||
case 305:
|
|
||||||
return String(F("Hardware error code 305"));
|
|
||||||
break;
|
|
||||||
case 306:
|
|
||||||
return String(F("Hardware error code 306"));
|
|
||||||
break;
|
|
||||||
case 307:
|
|
||||||
return String(F("Hardware error code 307"));
|
|
||||||
break;
|
|
||||||
case 308:
|
|
||||||
return String(F("Hardware error code 308"));
|
|
||||||
break;
|
|
||||||
case 309:
|
|
||||||
return String(F("Hardware error code 309"));
|
|
||||||
break;
|
|
||||||
case 310:
|
|
||||||
return String(F("Hardware error code 310"));
|
|
||||||
break;
|
|
||||||
case 311:
|
|
||||||
return String(F("Hardware error code 311"));
|
|
||||||
break;
|
|
||||||
case 312:
|
|
||||||
return String(F("Hardware error code 312"));
|
|
||||||
break;
|
|
||||||
case 313:
|
|
||||||
return String(F("Hardware error code 313"));
|
|
||||||
break;
|
|
||||||
case 314:
|
|
||||||
return String(F("Hardware error code 314"));
|
|
||||||
break;
|
|
||||||
case 5041:
|
|
||||||
return String(F("Error code-04 Port 1"));
|
|
||||||
break;
|
|
||||||
case 5042:
|
|
||||||
return String(F("Error code-04 Port 2"));
|
|
||||||
break;
|
|
||||||
case 5043:
|
|
||||||
return String(F("Error code-04 Port 3"));
|
|
||||||
break;
|
|
||||||
case 5044:
|
|
||||||
return String(F("Error code-04 Port 4"));
|
|
||||||
break;
|
|
||||||
case 5051:
|
|
||||||
return String(F("PV Input 1 Overvoltage/Undervoltage"));
|
|
||||||
break;
|
|
||||||
case 5052:
|
|
||||||
return String(F("PV Input 2 Overvoltage/Undervoltage"));
|
|
||||||
break;
|
|
||||||
case 5053:
|
|
||||||
return String(F("PV Input 3 Overvoltage/Undervoltage"));
|
|
||||||
break;
|
|
||||||
case 5054:
|
|
||||||
return String(F("PV Input 4 Overvoltage/Undervoltage"));
|
|
||||||
break;
|
|
||||||
case 5060:
|
|
||||||
return String(F("Abnormal bias"));
|
|
||||||
break;
|
|
||||||
case 5070:
|
|
||||||
return String(F("Over temperature protection"));
|
|
||||||
break;
|
|
||||||
case 5080:
|
|
||||||
return String(F("Grid Overvoltage/Undervoltage"));
|
|
||||||
break;
|
|
||||||
case 5090:
|
|
||||||
return String(F("Grid Overfrequency/Underfrequency"));
|
|
||||||
break;
|
|
||||||
case 5100:
|
|
||||||
return String(F("Island detected"));
|
|
||||||
break;
|
|
||||||
case 5120:
|
|
||||||
return String(F("EEPROM reading and writing error"));
|
|
||||||
break;
|
|
||||||
case 5150:
|
|
||||||
return String(F("10 min value grid overvoltage"));
|
|
||||||
break;
|
|
||||||
case 5200:
|
|
||||||
return String(F("Firmware error"));
|
|
||||||
break;
|
|
||||||
case 8310:
|
|
||||||
return String(F("Shut down"));
|
|
||||||
break;
|
|
||||||
case 9000:
|
|
||||||
return String(F("Microinverter is suspected of being stolen"));
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
return String(F("Unknown"));
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -590,10 +489,11 @@ template<class T=float>
|
||||||
static T calcYieldTotalCh0(Inverter<> *iv, uint8_t arg0) {
|
static T calcYieldTotalCh0(Inverter<> *iv, uint8_t arg0) {
|
||||||
DPRINTLN(DBG_VERBOSE, F("hmInverter.h:calcYieldTotalCh0"));
|
DPRINTLN(DBG_VERBOSE, F("hmInverter.h:calcYieldTotalCh0"));
|
||||||
if(NULL != iv) {
|
if(NULL != iv) {
|
||||||
|
record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug);
|
||||||
T yield = 0;
|
T yield = 0;
|
||||||
for(uint8_t i = 1; i <= iv->channels; i++) {
|
for(uint8_t i = 1; i <= iv->channels; i++) {
|
||||||
uint8_t pos = iv->getPosByChFld(i, FLD_YT);
|
uint8_t pos = iv->getPosByChFld(i, FLD_YT, rec);
|
||||||
yield += iv->getValue(pos);
|
yield += iv->getValue(pos, rec);
|
||||||
}
|
}
|
||||||
return yield;
|
return yield;
|
||||||
}
|
}
|
||||||
|
@ -604,10 +504,11 @@ template<class T=float>
|
||||||
static T calcYieldDayCh0(Inverter<> *iv, uint8_t arg0) {
|
static T calcYieldDayCh0(Inverter<> *iv, uint8_t arg0) {
|
||||||
DPRINTLN(DBG_VERBOSE, F("hmInverter.h:calcYieldDayCh0"));
|
DPRINTLN(DBG_VERBOSE, F("hmInverter.h:calcYieldDayCh0"));
|
||||||
if(NULL != iv) {
|
if(NULL != iv) {
|
||||||
|
record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug);
|
||||||
T yield = 0;
|
T yield = 0;
|
||||||
for(uint8_t i = 1; i <= iv->channels; i++) {
|
for(uint8_t i = 1; i <= iv->channels; i++) {
|
||||||
uint8_t pos = iv->getPosByChFld(i, FLD_YD);
|
uint8_t pos = iv->getPosByChFld(i, FLD_YD, rec);
|
||||||
yield += iv->getValue(pos);
|
yield += iv->getValue(pos, rec);
|
||||||
}
|
}
|
||||||
return yield;
|
return yield;
|
||||||
}
|
}
|
||||||
|
@ -618,9 +519,10 @@ template<class T=float>
|
||||||
static T calcUdcCh(Inverter<> *iv, uint8_t arg0) {
|
static T calcUdcCh(Inverter<> *iv, uint8_t arg0) {
|
||||||
DPRINTLN(DBG_VERBOSE, F("hmInverter.h:calcUdcCh"));
|
DPRINTLN(DBG_VERBOSE, F("hmInverter.h:calcUdcCh"));
|
||||||
// arg0 = channel of source
|
// arg0 = channel of source
|
||||||
for(uint8_t i = 0; i < iv->listLen; i++) {
|
record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug);
|
||||||
if((FLD_UDC == iv->assign[i].fieldId) && (arg0 == iv->assign[i].ch)) {
|
for(uint8_t i = 0; i < rec->length; i++) {
|
||||||
return iv->getValue(i);
|
if((FLD_UDC == rec->assign[i].fieldId) && (arg0 == rec->assign[i].ch)) {
|
||||||
|
return iv->getValue(i, rec);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -631,10 +533,11 @@ template<class T=float>
|
||||||
static T calcPowerDcCh0(Inverter<> *iv, uint8_t arg0) {
|
static T calcPowerDcCh0(Inverter<> *iv, uint8_t arg0) {
|
||||||
DPRINTLN(DBG_VERBOSE, F("hmInverter.h:calcPowerDcCh0"));
|
DPRINTLN(DBG_VERBOSE, F("hmInverter.h:calcPowerDcCh0"));
|
||||||
if(NULL != iv) {
|
if(NULL != iv) {
|
||||||
|
record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug);
|
||||||
T dcPower = 0;
|
T dcPower = 0;
|
||||||
for(uint8_t i = 1; i <= iv->channels; i++) {
|
for(uint8_t i = 1; i <= iv->channels; i++) {
|
||||||
uint8_t pos = iv->getPosByChFld(i, FLD_PDC);
|
uint8_t pos = iv->getPosByChFld(i, FLD_PDC, rec);
|
||||||
dcPower += iv->getValue(pos);
|
dcPower += iv->getValue(pos, rec);
|
||||||
}
|
}
|
||||||
return dcPower;
|
return dcPower;
|
||||||
}
|
}
|
||||||
|
@ -645,12 +548,13 @@ template<class T=float>
|
||||||
static T calcEffiencyCh0(Inverter<> *iv, uint8_t arg0) {
|
static T calcEffiencyCh0(Inverter<> *iv, uint8_t arg0) {
|
||||||
DPRINTLN(DBG_VERBOSE, F("hmInverter.h:calcEfficiencyCh0"));
|
DPRINTLN(DBG_VERBOSE, F("hmInverter.h:calcEfficiencyCh0"));
|
||||||
if(NULL != iv) {
|
if(NULL != iv) {
|
||||||
uint8_t pos = iv->getPosByChFld(CH0, FLD_PAC);
|
record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug);
|
||||||
T acPower = iv->getValue(pos);
|
uint8_t pos = iv->getPosByChFld(CH0, FLD_PAC, rec);
|
||||||
|
T acPower = iv->getValue(pos, rec);
|
||||||
T dcPower = 0;
|
T dcPower = 0;
|
||||||
for(uint8_t i = 1; i <= iv->channels; i++) {
|
for(uint8_t i = 1; i <= iv->channels; i++) {
|
||||||
pos = iv->getPosByChFld(i, FLD_PDC);
|
pos = iv->getPosByChFld(i, FLD_PDC, rec);
|
||||||
dcPower += iv->getValue(pos);
|
dcPower += iv->getValue(pos, rec);
|
||||||
}
|
}
|
||||||
if(dcPower > 0)
|
if(dcPower > 0)
|
||||||
return acPower / dcPower * 100.0f;
|
return acPower / dcPower * 100.0f;
|
||||||
|
@ -663,9 +567,10 @@ static T calcIrradiation(Inverter<> *iv, uint8_t arg0) {
|
||||||
DPRINTLN(DBG_VERBOSE, F("hmInverter.h:calcIrradiation"));
|
DPRINTLN(DBG_VERBOSE, F("hmInverter.h:calcIrradiation"));
|
||||||
// arg0 = channel
|
// arg0 = channel
|
||||||
if(NULL != iv) {
|
if(NULL != iv) {
|
||||||
uint8_t pos = iv->getPosByChFld(arg0, FLD_PDC);
|
record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug);
|
||||||
|
uint8_t pos = iv->getPosByChFld(arg0, FLD_PDC, rec);
|
||||||
if(iv->chMaxPwr[arg0-1] > 0)
|
if(iv->chMaxPwr[arg0-1] > 0)
|
||||||
return iv->getValue(pos) / iv->chMaxPwr[arg0-1] * 100.0f;
|
return iv->getValue(pos, rec) / iv->chMaxPwr[arg0-1] * 100.0f;
|
||||||
}
|
}
|
||||||
return 0.0;
|
return 0.0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,7 @@
|
||||||
#define RF_CHANNELS 5
|
#define RF_CHANNELS 5
|
||||||
#define RF_LOOP_CNT 300
|
#define RF_LOOP_CNT 300
|
||||||
|
|
||||||
#define TX_REQ_INFO 0X15
|
#define TX_REQ_INFO 0x15
|
||||||
#define TX_REQ_DEVCONTROL 0x51
|
#define TX_REQ_DEVCONTROL 0x51
|
||||||
#define ALL_FRAMES 0x80
|
#define ALL_FRAMES 0x80
|
||||||
#define SINGLE_FRAME 0x81
|
#define SINGLE_FRAME 0x81
|
||||||
|
@ -54,7 +54,7 @@ const char* const rf24AmpPowerNames[] = {"MIN", "LOW", "HIGH", "MAX"};
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
// HM Radio class
|
// HM Radio class
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
template <uint8_t CE_PIN, uint8_t CS_PIN, class BUFFER, uint64_t DTU_ID=DTU_RADIO_ID>
|
template <uint8_t CE_PIN, uint8_t CS_PIN, class BUFFER>
|
||||||
class HmRadio {
|
class HmRadio {
|
||||||
public:
|
public:
|
||||||
HmRadio() : mNrf24(CE_PIN, CS_PIN, SPI_SPEED) {
|
HmRadio() : mNrf24(CE_PIN, CS_PIN, SPI_SPEED) {
|
||||||
|
@ -89,6 +89,25 @@ class HmRadio {
|
||||||
pinMode(config->pinIrq, INPUT_PULLUP);
|
pinMode(config->pinIrq, INPUT_PULLUP);
|
||||||
|
|
||||||
mBufCtrl = ctrl;
|
mBufCtrl = ctrl;
|
||||||
|
mSerialDebug = config->serialDebug;
|
||||||
|
|
||||||
|
uint32_t DTU_SN = 0x87654321;
|
||||||
|
uint32_t chipID = 0; // will be filled with last 3 bytes of MAC
|
||||||
|
#ifdef ESP32
|
||||||
|
uint64_t MAC = ESP.getEfuseMac();
|
||||||
|
chipID = ((MAC >> 8) & 0xFF0000) | ((MAC >> 24) & 0xFF00) | ((MAC >> 40) & 0xFF);
|
||||||
|
#else
|
||||||
|
chipID = ESP.getChipId();
|
||||||
|
#endif
|
||||||
|
if(chipID) {
|
||||||
|
DTU_SN = 0x80000000; // the first digit is an 8 for DTU production year 2022, the rest is filled with the ESP chipID in decimal
|
||||||
|
for(int i = 0; i < 7; i++) {
|
||||||
|
DTU_SN |= (chipID % 10) << (i * 4);
|
||||||
|
chipID /= 10;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// change the byte order of the DTU serial number and append the required 0x01 at the end
|
||||||
|
DTU_RADIO_ID = ((uint64_t)(((DTU_SN >> 24) & 0xFF) | ((DTU_SN >> 8) & 0xFF00) | ((DTU_SN << 8) & 0xFF0000) | ((DTU_SN << 24) & 0xFF000000)) << 8) | 0x01;
|
||||||
|
|
||||||
mNrf24.begin(config->pinCe, config->pinCs);
|
mNrf24.begin(config->pinCe, config->pinCs);
|
||||||
mNrf24.setRetries(0, 0);
|
mNrf24.setRetries(0, 0);
|
||||||
|
@ -163,30 +182,31 @@ class HmRadio {
|
||||||
}
|
}
|
||||||
|
|
||||||
void sendControlPacket(uint64_t invId, uint8_t cmd, uint16_t *data) {
|
void sendControlPacket(uint64_t invId, uint8_t cmd, uint16_t *data) {
|
||||||
DPRINTLN(DBG_VERBOSE, F("hmRadio.h:sendControlPacket"));
|
DPRINTLN(DBG_INFO, F("sendControlPacket cmd: ") + String(cmd));
|
||||||
sendCmdPacket(invId, TX_REQ_DEVCONTROL, ALL_FRAMES, false); // 0x80 implementation as original DTU code
|
sendCmdPacket(invId, TX_REQ_DEVCONTROL, SINGLE_FRAME, false);
|
||||||
int cnt = 0;
|
uint8_t cnt = 0;
|
||||||
mTxBuf[10] = cmd; // cmd --> 0x0b => Type_ActivePowerContr, 0 on, 1 off, 2 restart, 12 reactive power, 13 power factor
|
mTxBuf[10 + cnt++] = cmd; // cmd -> 0 on, 1 off, 2 restart, 11 active power, 12 reactive power, 13 power factor
|
||||||
mTxBuf[10 + (++cnt)] = 0x00;
|
mTxBuf[10 + cnt++] = 0x00;
|
||||||
if (cmd >= ActivePowerContr && cmd <= PFSet){
|
if(cmd >= ActivePowerContr && cmd <= PFSet) { // ActivePowerContr, ReactivePowerContr, PFSet
|
||||||
mTxBuf[10 + (++cnt)] = ((data[0] * 10) >> 8) & 0xff; // power limit
|
mTxBuf[10 + cnt++] = ((data[0] * 10) >> 8) & 0xff; // power limit
|
||||||
mTxBuf[10 + (++cnt)] = ((data[0] * 10) ) & 0xff; // power limit
|
mTxBuf[10 + cnt++] = ((data[0] * 10) ) & 0xff; // power limit
|
||||||
mTxBuf[10 + (++cnt)] = ((data[1] ) >> 8) & 0xff; // setting for persistens handlings
|
mTxBuf[10 + cnt++] = ((data[1] ) >> 8) & 0xff; // setting for persistens handlings
|
||||||
mTxBuf[10 + (++cnt)] = ((data[1] ) ) & 0xff; // setting for persistens handling
|
mTxBuf[10 + cnt++] = ((data[1] ) ) & 0xff; // setting for persistens handling
|
||||||
}
|
}
|
||||||
// crc control data
|
|
||||||
uint16_t crc = Hoymiles::crc16(&mTxBuf[10], cnt+1);
|
|
||||||
mTxBuf[10 + (++cnt)] = (crc >> 8) & 0xff;
|
|
||||||
mTxBuf[10 + (++cnt)] = (crc ) & 0xff;
|
|
||||||
// crc over all
|
|
||||||
cnt +=1;
|
|
||||||
mTxBuf[10 + cnt] = Hoymiles::crc8(mTxBuf, 10 + cnt);
|
|
||||||
|
|
||||||
sendPacket(invId, mTxBuf, 10 + (++cnt), true);
|
// crc control data
|
||||||
|
uint16_t crc = Ahoy::crc16(&mTxBuf[10], cnt);
|
||||||
|
mTxBuf[10 + cnt++] = (crc >> 8) & 0xff;
|
||||||
|
mTxBuf[10 + cnt++] = (crc ) & 0xff;
|
||||||
|
|
||||||
|
// crc over all
|
||||||
|
mTxBuf[10 + cnt] = Ahoy::crc8(mTxBuf, 10 + cnt);
|
||||||
|
|
||||||
|
sendPacket(invId, mTxBuf, 10 + cnt + 1, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void sendTimePacket(uint64_t invId, uint8_t cmd, uint32_t ts, uint16_t alarmMesId) {
|
void sendTimePacket(uint64_t invId, uint8_t cmd, uint32_t ts, uint16_t alarmMesId) {
|
||||||
//DPRINTLN(DBG_VERBOSE, F("hmRadio.h:sendTimePacket"));
|
DPRINTLN(DBG_INFO, F("sendTimePacket"));
|
||||||
sendCmdPacket(invId, TX_REQ_INFO, ALL_FRAMES, false);
|
sendCmdPacket(invId, TX_REQ_INFO, ALL_FRAMES, false);
|
||||||
mTxBuf[10] = cmd; // cid
|
mTxBuf[10] = cmd; // cid
|
||||||
mTxBuf[11] = 0x00;
|
mTxBuf[11] = 0x00;
|
||||||
|
@ -194,33 +214,30 @@ class HmRadio {
|
||||||
if (cmd == RealTimeRunData_Debug || cmd == AlarmData ) {
|
if (cmd == RealTimeRunData_Debug || cmd == AlarmData ) {
|
||||||
mTxBuf[18] = (alarmMesId >> 8) & 0xff;
|
mTxBuf[18] = (alarmMesId >> 8) & 0xff;
|
||||||
mTxBuf[19] = (alarmMesId ) & 0xff;
|
mTxBuf[19] = (alarmMesId ) & 0xff;
|
||||||
} else {
|
|
||||||
mTxBuf[18] = 0x00;
|
|
||||||
mTxBuf[19] = 0x00;
|
|
||||||
}
|
}
|
||||||
uint16_t crc = Hoymiles::crc16(&mTxBuf[10], 14);
|
uint16_t crc = Ahoy::crc16(&mTxBuf[10], 14);
|
||||||
mTxBuf[24] = (crc >> 8) & 0xff;
|
mTxBuf[24] = (crc >> 8) & 0xff;
|
||||||
mTxBuf[25] = (crc ) & 0xff;
|
mTxBuf[25] = (crc ) & 0xff;
|
||||||
mTxBuf[26] = Hoymiles::crc8(mTxBuf, 26);
|
mTxBuf[26] = Ahoy::crc8(mTxBuf, 26);
|
||||||
|
|
||||||
sendPacket(invId, mTxBuf, 27, true);
|
sendPacket(invId, mTxBuf, 27, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void sendCmdPacket(uint64_t invId, uint8_t mid, uint8_t pid, bool calcCrc = true) {
|
void sendCmdPacket(uint64_t invId, uint8_t mid, uint8_t pid, bool calcCrc = true) {
|
||||||
//DPRINTLN(DBG_VERBOSE, F("hmRadio.h:sendCmdPacket"));
|
DPRINTLN(DBG_VERBOSE, F("sendCmdPacket, mid: ") + String(mid, HEX) + F(" pid: ") + String(pid, HEX));
|
||||||
memset(mTxBuf, 0, MAX_RF_PAYLOAD_SIZE);
|
memset(mTxBuf, 0, MAX_RF_PAYLOAD_SIZE);
|
||||||
mTxBuf[0] = mid; // message id
|
mTxBuf[0] = mid; // message id
|
||||||
CP_U32_BigEndian(&mTxBuf[1], (invId >> 8));
|
CP_U32_BigEndian(&mTxBuf[1], (invId >> 8));
|
||||||
CP_U32_BigEndian(&mTxBuf[5], (DTU_ID >> 8));
|
CP_U32_BigEndian(&mTxBuf[5], (DTU_RADIO_ID >> 8));
|
||||||
mTxBuf[9] = pid;
|
mTxBuf[9] = pid;
|
||||||
if(calcCrc) {
|
if(calcCrc) {
|
||||||
mTxBuf[10] = Hoymiles::crc8(mTxBuf, 10);
|
mTxBuf[10] = Ahoy::crc8(mTxBuf, 10);
|
||||||
sendPacket(invId, mTxBuf, 11, false);
|
sendPacket(invId, mTxBuf, 11, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool checkPaketCrc(uint8_t buf[], uint8_t *len, uint8_t rxCh) {
|
bool checkPaketCrc(uint8_t buf[], uint8_t *len, uint8_t rxCh) {
|
||||||
//DPRINTLN(DBG_VERBOSE, F("hmRadio.h:checkPaketCrc"));
|
//DPRINTLN(DBG_INFO, F("hmRadio.h:checkPaketCrc"));
|
||||||
*len = (buf[0] >> 2);
|
*len = (buf[0] >> 2);
|
||||||
if(*len > (MAX_RF_PAYLOAD_SIZE - 2))
|
if(*len > (MAX_RF_PAYLOAD_SIZE - 2))
|
||||||
*len = MAX_RF_PAYLOAD_SIZE - 2;
|
*len = MAX_RF_PAYLOAD_SIZE - 2;
|
||||||
|
@ -228,7 +245,7 @@ class HmRadio {
|
||||||
buf[i-1] = (buf[i] << 1) | (buf[i+1] >> 7);
|
buf[i-1] = (buf[i] << 1) | (buf[i+1] >> 7);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t crc = Hoymiles::crc8(buf, *len-1);
|
uint8_t crc = Ahoy::crc8(buf, *len-1);
|
||||||
bool valid = (crc == buf[*len-1]);
|
bool valid = (crc == buf[*len-1]);
|
||||||
|
|
||||||
return valid;
|
return valid;
|
||||||
|
@ -236,8 +253,6 @@ class HmRadio {
|
||||||
|
|
||||||
bool switchRxCh(uint16_t addLoop = 0) {
|
bool switchRxCh(uint16_t addLoop = 0) {
|
||||||
//DPRINTLN(DBG_VERBOSE, F("hmRadio.h:switchRxCh"));
|
//DPRINTLN(DBG_VERBOSE, F("hmRadio.h:switchRxCh"));
|
||||||
//DPRINTLN(DBG_VERBOSE, F("R"));
|
|
||||||
|
|
||||||
mRxLoopCnt += addLoop;
|
mRxLoopCnt += addLoop;
|
||||||
if(mRxLoopCnt != 0) {
|
if(mRxLoopCnt != 0) {
|
||||||
mRxLoopCnt--;
|
mRxLoopCnt--;
|
||||||
|
@ -325,6 +340,8 @@ class HmRadio {
|
||||||
return mRfChLst[mRxChIdx];
|
return mRfChLst[mRxChIdx];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint64_t DTU_RADIO_ID;
|
||||||
|
|
||||||
uint8_t mTxCh;
|
uint8_t mTxCh;
|
||||||
uint8_t mTxChIdx;
|
uint8_t mTxChIdx;
|
||||||
|
|
||||||
|
|
|
@ -63,15 +63,9 @@ class HmSystem {
|
||||||
uint8_t len = (uint8_t)strlen(name);
|
uint8_t len = (uint8_t)strlen(name);
|
||||||
strncpy(p->name, name, (len > MAX_NAME_LENGTH) ? MAX_NAME_LENGTH : len);
|
strncpy(p->name, name, (len > MAX_NAME_LENGTH) ? MAX_NAME_LENGTH : len);
|
||||||
|
|
||||||
if(NULL == p->assign) {
|
|
||||||
DPRINT(DBG_ERROR, F("no assignment for type found!"));
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
mNumInv ++;
|
mNumInv ++;
|
||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
INVERTERTYPE *findInverter(uint8_t buf[]) {
|
INVERTERTYPE *findInverter(uint8_t buf[]) {
|
||||||
DPRINTLN(DBG_VERBOSE, F("hmSystem.h:findInverter"));
|
DPRINTLN(DBG_VERBOSE, F("hmSystem.h:findInverter"));
|
||||||
|
@ -89,7 +83,9 @@ class HmSystem {
|
||||||
|
|
||||||
INVERTERTYPE *getInverterByPos(uint8_t pos, bool check = true) {
|
INVERTERTYPE *getInverterByPos(uint8_t pos, bool check = true) {
|
||||||
DPRINTLN(DBG_VERBOSE, F("hmSystem.h:getInverterByPos"));
|
DPRINTLN(DBG_VERBOSE, F("hmSystem.h:getInverterByPos"));
|
||||||
if((mInverter[pos].initialized && mInverter[pos].serial.u64 != 0ULL) || false == check)
|
if(pos >= MAX_INVERTER)
|
||||||
|
return NULL;
|
||||||
|
else if((mInverter[pos].initialized && mInverter[pos].serial.u64 != 0ULL) || false == check)
|
||||||
return &mInverter[pos];
|
return &mInverter[pos];
|
||||||
else
|
else
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
79
tools/esp8266/html/api.js
Normal file
79
tools/esp8266/html/api.js
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
function toggle(id, hide) {
|
||||||
|
var elm = document.getElementById(id);
|
||||||
|
if(hide) {
|
||||||
|
if(!elm.classList.contains("hide"))
|
||||||
|
elm.classList.add("hide");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
elm.classList.remove('hide');
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAjax(url, ptr, method="GET", json=null) {
|
||||||
|
var xhr = new XMLHttpRequest();
|
||||||
|
if(xhr != null) {
|
||||||
|
xhr.open(method, url, true);
|
||||||
|
xhr.onreadystatechange = p;
|
||||||
|
if("POST" == method)
|
||||||
|
xhr.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
|
||||||
|
xhr.send(json);
|
||||||
|
}
|
||||||
|
function p() {
|
||||||
|
if(xhr.readyState == 4) {
|
||||||
|
if(null != xhr.responseText)
|
||||||
|
ptr(JSON.parse(xhr.responseText));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function des(val) {
|
||||||
|
e = document.createElement('p');
|
||||||
|
e.classList.add("subdes");
|
||||||
|
e.innerHTML = val;
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
|
||||||
|
function lbl(htmlfor, val, cl=null, id=null) {
|
||||||
|
e = document.createElement('label');
|
||||||
|
e.htmlFor = htmlfor;
|
||||||
|
e.innerHTML = val;
|
||||||
|
if(null != cl) e.classList.add(...cl);
|
||||||
|
if(null != id) e.id = id;
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
|
||||||
|
function inp(name, val, max=32, cl=["text"], id=null) {
|
||||||
|
e = document.createElement('input');
|
||||||
|
e.classList.add(...cl);
|
||||||
|
e.name = name;
|
||||||
|
e.value = val;
|
||||||
|
e.maxLength = max;
|
||||||
|
if(null != id) e.id = id;
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
|
||||||
|
function sel(name, opt, selId) {
|
||||||
|
e = document.createElement('select');
|
||||||
|
e.name = name;
|
||||||
|
for(it of opt) {
|
||||||
|
o = document.createElement('option');
|
||||||
|
o.value = it[0];
|
||||||
|
o.innerHTML = it[1];
|
||||||
|
if(it[0] == selId)
|
||||||
|
o.selected = true;
|
||||||
|
e.appendChild(o);
|
||||||
|
}
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
|
||||||
|
function div(cl) {
|
||||||
|
e = document.createElement('div');
|
||||||
|
e.classList.add(...cl);
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
|
||||||
|
function span(val, cl) {
|
||||||
|
e = document.createElement('span');
|
||||||
|
e.innerHTML = val;
|
||||||
|
e.classList.add(...cl);
|
||||||
|
return e;
|
||||||
|
}
|
|
@ -1,17 +1,17 @@
|
||||||
import re
|
import re
|
||||||
import sys
|
|
||||||
import os
|
import os
|
||||||
|
import gzip
|
||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
def convert2Header(inFile):
|
def convert2Header(inFile, compress):
|
||||||
fileType = inFile.split(".")[1]
|
fileType = inFile.split(".")[1]
|
||||||
define = inFile.split(".")[0].upper()
|
define = inFile.split(".")[0].upper()
|
||||||
define2 = inFile.split(".")[1].upper()
|
define2 = inFile.split(".")[1].upper()
|
||||||
inFileVarName = inFile.replace(".", "_")
|
inFileVarName = inFile.replace(".", "_")
|
||||||
|
print(inFile + ", compress: " + str(compress))
|
||||||
|
|
||||||
if os.getcwd()[-4:] != "html":
|
if os.getcwd()[-4:] != "html":
|
||||||
print("ok")
|
|
||||||
outName = "html/" + "h/" + inFileVarName + ".h"
|
outName = "html/" + "h/" + inFileVarName + ".h"
|
||||||
inFile = "html/" + inFile
|
inFile = "html/" + inFile
|
||||||
Path("html/h").mkdir(exist_ok=True)
|
Path("html/h").mkdir(exist_ok=True)
|
||||||
|
@ -20,23 +20,52 @@ def convert2Header(inFile):
|
||||||
Path("h").mkdir(exist_ok=True)
|
Path("h").mkdir(exist_ok=True)
|
||||||
|
|
||||||
f = open(inFile, "r")
|
f = open(inFile, "r")
|
||||||
data = f.read().replace('\n', '')
|
data = f.read()
|
||||||
f.close()
|
f.close()
|
||||||
|
|
||||||
if fileType == "html":
|
if fileType == "html":
|
||||||
|
if False == compress:
|
||||||
|
data = data.replace('\n', '')
|
||||||
data = re.sub(r"\>\s+\<", '><', data) # whitespaces between xml tags
|
data = re.sub(r"\>\s+\<", '><', data) # whitespaces between xml tags
|
||||||
data = re.sub(r"(\;|\}|\>|\{)\s+", r'\1', data) # whitespaces inner javascript
|
data = re.sub(r"(\r\n|\r|\n)(\s+|\s?)", '', data) # whitespaces inner javascript
|
||||||
|
length = len(data) # get unescaped length
|
||||||
|
if False == compress:
|
||||||
|
data = re.sub(r"\"", '\\\"', data) # escape quotation marks
|
||||||
|
elif fileType == "js":
|
||||||
|
#data = re.sub(r"(\r\n|\r|\n)(\s+|\s?)", '', data) # whitespaces inner javascript
|
||||||
|
#data = re.sub(r"\s?(\=|\!\=|\{|,)+\s?", r'\1', data) # whitespaces inner javascript
|
||||||
|
length = len(data) # get unescaped length
|
||||||
|
if False == compress:
|
||||||
data = re.sub(r"\"", '\\\"', data) # escape quotation marks
|
data = re.sub(r"\"", '\\\"', data) # escape quotation marks
|
||||||
else:
|
else:
|
||||||
|
data = data.replace('\n', '')
|
||||||
data = re.sub(r"(\;|\}|\:|\{)\s+", r'\1', data) # whitespaces inner css
|
data = re.sub(r"(\;|\}|\:|\{)\s+", r'\1', data) # whitespaces inner css
|
||||||
|
length = len(data) # get unescaped length # get unescaped length
|
||||||
|
|
||||||
f = open(outName, "w")
|
f = open(outName, "w")
|
||||||
f.write("#ifndef __{}_{}_H__\n".format(define, define2))
|
f.write("#ifndef __{}_{}_H__\n".format(define, define2))
|
||||||
f.write("#define __{}_{}_H__\n".format(define, define2))
|
f.write("#define __{}_{}_H__\n".format(define, define2))
|
||||||
|
if compress:
|
||||||
|
zipped = gzip.compress(bytes(data, 'utf-8'))
|
||||||
|
zippedStr = ""
|
||||||
|
for i in range(len(zipped)):
|
||||||
|
zippedStr += "0x{:02x}".format(zipped[i]) #hex(zipped[i])
|
||||||
|
if (i + 1) != len(zipped):
|
||||||
|
zippedStr += ", "
|
||||||
|
if (i + 1) % 16 == 0 and i != 0:
|
||||||
|
zippedStr += "\n"
|
||||||
|
f.write("#define {}_len {}\n".format(inFileVarName, len(zipped)))
|
||||||
|
f.write("const uint8_t {}[] PROGMEM = {{\n{}}};\n".format(inFileVarName, zippedStr))
|
||||||
|
else:
|
||||||
f.write("const char {}[] PROGMEM = \"{}\";\n".format(inFileVarName, data))
|
f.write("const char {}[] PROGMEM = \"{}\";\n".format(inFileVarName, data))
|
||||||
|
f.write("const uint32_t {}_len = {};\n".format(inFileVarName, length))
|
||||||
f.write("#endif /*__{}_{}_H__*/\n".format(define, define2))
|
f.write("#endif /*__{}_{}_H__*/\n".format(define, define2))
|
||||||
f.close()
|
f.close()
|
||||||
|
|
||||||
convert2Header("index.html")
|
convert2Header("index.html", True)
|
||||||
convert2Header("setup.html")
|
convert2Header("setup.html", True)
|
||||||
convert2Header("visualization.html")
|
convert2Header("visualization.html", True)
|
||||||
convert2Header("style.css")
|
convert2Header("update.html", True)
|
||||||
|
convert2Header("serial.html", True)
|
||||||
|
convert2Header("style.css", True)
|
||||||
|
convert2Header("api.js", True)
|
||||||
|
|
100
tools/esp8266/html/h/favicon_ico_gz.h
Normal file
100
tools/esp8266/html/h/favicon_ico_gz.h
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
#ifndef __FAVICON_ICO_GZ_H__
|
||||||
|
#define __FAVICON_ICO_GZ_H__
|
||||||
|
#define favicon_ico_gz_len 1533
|
||||||
|
const uint8_t favicon_ico_gz[] PROGMEM = {0x1f, 0x8b, 0x08, 0x08, 0xf2, 0xc5, 0xd5, 0x62, 0x04, 0x00, 0x66, 0x61, 0x76, 0x69, 0x63, 0x6f,
|
||||||
|
0x6e, 0x2e, 0x69, 0x63, 0x6f, 0x00, 0xed, 0x5c, 0x49, 0x68, 0x13, 0x51, 0x18, 0xfe, 0x62, 0xa3,
|
||||||
|
0x51, 0x28, 0xd6, 0x83, 0x82, 0xa0, 0x98, 0xb8, 0x1c, 0xbc, 0x59, 0x11, 0x5c, 0x50, 0xac, 0x88,
|
||||||
|
0x8a, 0xb8, 0xdd, 0x3c, 0x89, 0xd0, 0x93, 0x7a, 0x53, 0x51, 0x9b, 0x80, 0x4b, 0x46, 0xad, 0xfb,
|
||||||
|
0xd2, 0xb4, 0x2e, 0xb8, 0xa3, 0xc6, 0xba, 0xe1, 0x02, 0xae, 0xad, 0x0a, 0x26, 0x3d, 0xe8, 0xc5,
|
||||||
|
0x83, 0x57, 0x31, 0x2d, 0xc1, 0x8b, 0xb7, 0x92, 0x63, 0x0e, 0xa1, 0xcf, 0xff, 0xcf, 0xbc, 0xc9,
|
||||||
|
0x32, 0xa4, 0x66, 0xcf, 0x4b, 0xf3, 0xfa, 0xc3, 0xc7, 0x97, 0xcc, 0xcc, 0xcb, 0xf7, 0xbe, 0x6f,
|
||||||
|
0x26, 0x6f, 0x26, 0xf3, 0x92, 0x00, 0x0e, 0x34, 0x61, 0xda, 0x34, 0x66, 0x0f, 0xf6, 0x38, 0x81,
|
||||||
|
0xa5, 0x00, 0x3c, 0x1e, 0xf3, 0xf9, 0x53, 0x5a, 0x7e, 0x8f, 0x96, 0xad, 0x59, 0x63, 0x3e, 0x5f,
|
||||||
|
0xb8, 0x16, 0xd8, 0x30, 0x03, 0x58, 0x48, 0xdb, 0xd0, 0x2a, 0x5a, 0x62, 0x2e, 0x1f, 0xad, 0x06,
|
||||||
|
0xc3, 0x5d, 0xc6, 0x60, 0x38, 0x20, 0x08, 0x5e, 0xf1, 0x08, 0x77, 0x09, 0x42, 0xf4, 0xe2, 0xd7,
|
||||||
|
0x67, 0x2f, 0x56, 0xf5, 0x7b, 0x21, 0x18, 0xfc, 0xb8, 0x63, 0x0b, 0x7e, 0x11, 0x84, 0x77, 0x0b,
|
||||||
|
0xee, 0x2e, 0x9a, 0x03, 0x6f, 0xab, 0x1b, 0x82, 0x31, 0x38, 0x10, 0x38, 0xce, 0xaf, 0x51, 0x72,
|
||||||
|
0x7b, 0xd2, 0xe6, 0xf6, 0xbf, 0xbf, 0xf6, 0xcc, 0x2e, 0xbd, 0x7d, 0xf2, 0x35, 0x76, 0x88, 0x20,
|
||||||
|
0xf6, 0x96, 0xde, 0x3e, 0xe0, 0xa5, 0x76, 0xad, 0xd4, 0xfe, 0x64, 0xc9, 0xed, 0xa9, 0xff, 0xd4,
|
||||||
|
0xd6, 0x4f, 0x68, 0x2f, 0xb9, 0x3d, 0xf5, 0x9f, 0x70, 0xb8, 0xaf, 0x03, 0x4b, 0x4b, 0x6e, 0x4f,
|
||||||
|
0xfd, 0xe7, 0x7d, 0x4b, 0x6d, 0x4f, 0x96, 0xd3, 0xde, 0x7a, 0x8d, 0x72, 0xda, 0x73, 0x7d, 0xdb,
|
||||||
|
0x8f, 0x29, 0x45, 0xb5, 0x7f, 0x84, 0x4e, 0xfb, 0x71, 0xda, 0xb1, 0x19, 0x9d, 0x05, 0xb5, 0xef,
|
||||||
|
0xc5, 0xa5, 0xd1, 0x8e, 0x75, 0xef, 0x56, 0x9c, 0xfa, 0x6f, 0xfb, 0x87, 0x58, 0x86, 0x3c, 0x75,
|
||||||
|
0x70, 0x13, 0x96, 0xe5, 0x6a, 0x1f, 0x19, 0xe8, 0x6a, 0x47, 0x81, 0xd5, 0xea, 0x41, 0x7b, 0x56,
|
||||||
|
0xfb, 0x50, 0xe0, 0x72, 0xae, 0xed, 0xbe, 0x1a, 0x98, 0xcc, 0xc8, 0xf9, 0x1a, 0x73, 0x70, 0x9a,
|
||||||
|
0xda, 0x8f, 0xda, 0xf6, 0xb9, 0x81, 0x49, 0xfd, 0x3e, 0xbc, 0xa5, 0xfd, 0xd7, 0xcf, 0xf9, 0x8f,
|
||||||
|
0xf6, 0x1a, 0xff, 0xeb, 0xe7, 0x04, 0x42, 0x13, 0xc3, 0x20, 0x84, 0x00, 0x57, 0x14, 0x68, 0x89,
|
||||||
|
0x01, 0xee, 0x38, 0xb0, 0xfa, 0x08, 0x70, 0x64, 0xb5, 0x39, 0xce, 0x78, 0x08, 0x6b, 0x0a, 0x1f,
|
||||||
|
0x67, 0x2c, 0x0c, 0xf3, 0x72, 0xf1, 0x18, 0xab, 0xe4, 0x3e, 0x4f, 0x23, 0x88, 0x37, 0xbc, 0xae,
|
||||||
|
0xcf, 0x07, 0xaf, 0xf9, 0x1e, 0x48, 0x83, 0x97, 0xf1, 0xba, 0x43, 0x5b, 0xf0, 0x86, 0x8f, 0x87,
|
||||||
|
0x4c, 0xd0, 0xfe, 0x5d, 0xc5, 0xeb, 0x28, 0x97, 0x61, 0x33, 0xdf, 0x34, 0x78, 0x39, 0x8f, 0x55,
|
||||||
|
0xaa, 0xf4, 0x23, 0x03, 0x81, 0x53, 0x56, 0x1f, 0x54, 0xe8, 0xb3, 0xe6, 0x50, 0x28, 0x70, 0xde,
|
||||||
|
0xea, 0x83, 0x0a, 0xfd, 0x64, 0x1f, 0xc2, 0x81, 0x93, 0xbc, 0x0d, 0xf1, 0x01, 0x15, 0xfa, 0x49,
|
||||||
|
0x0c, 0x74, 0x9d, 0xe3, 0xed, 0x94, 0xe9, 0x13, 0x78, 0x1f, 0x24, 0xf5, 0x7b, 0xb1, 0x54, 0x89,
|
||||||
|
0x3e, 0xef, 0x7f, 0x59, 0x7c, 0xce, 0x53, 0xa5, 0x2f, 0x7a, 0xe0, 0x92, 0x7d, 0x38, 0xad, 0x44,
|
||||||
|
0x9f, 0xf6, 0x3f, 0x9f, 0xb3, 0x65, 0x1f, 0x3a, 0x95, 0xe8, 0x9b, 0xbe, 0x8f, 0x59, 0x7d, 0x50,
|
||||||
|
0xa4, 0x9f, 0xea, 0x03, 0xe9, 0xaf, 0x57, 0xa4, 0x9f, 0xea, 0x83, 0x42, 0x7d, 0xbe, 0x7e, 0xd9,
|
||||||
|
0xcf, 0xeb, 0x3e, 0xf9, 0xd0, 0xae, 0x44, 0x9f, 0xf7, 0xbf, 0x2c, 0x3a, 0x87, 0x9f, 0x56, 0xa9,
|
||||||
|
0x6f, 0xf5, 0x41, 0xa5, 0xbe, 0xbc, 0x0e, 0x3d, 0xa3, 0x52, 0xdf, 0xea, 0x83, 0x4a, 0x7d, 0xae,
|
||||||
|
0x0f, 0x87, 0x30, 0x5f, 0xa5, 0x3e, 0x57, 0x0d, 0xf4, 0x8d, 0x7c, 0xfa, 0xa4, 0x67, 0x54, 0x49,
|
||||||
|
0xff, 0x86, 0x10, 0x74, 0x29, 0x9b, 0xbf, 0x1c, 0xa4, 0xd9, 0x5d, 0x51, 0xfd, 0x20, 0x6e, 0x16,
|
||||||
|
0xa8, 0x9d, 0xd5, 0x87, 0x8a, 0xe8, 0x17, 0xaf, 0x9d, 0xea, 0x03, 0xe9, 0xf6, 0x94, 0xa9, 0x7f,
|
||||||
|
0xab, 0x44, 0xed, 0xac, 0x3e, 0x94, 0xa4, 0xff, 0x1c, 0xcd, 0x65, 0x6a, 0xa7, 0xfa, 0x60, 0x6c,
|
||||||
|
0x47, 0x73, 0xb1, 0xfa, 0xd5, 0xa8, 0x42, 0xf4, 0x23, 0xe1, 0xc0, 0x15, 0x54, 0xa9, 0x16, 0xbb,
|
||||||
|
0x71, 0xe5, 0x7f, 0xfa, 0x91, 0x50, 0xf7, 0x6d, 0x21, 0x8c, 0x09, 0xa8, 0x5e, 0x39, 0x48, 0xf3,
|
||||||
|
0x6a, 0x86, 0x7e, 0x59, 0xda, 0xfd, 0x07, 0x31, 0x97, 0x81, 0xe2, 0x2a, 0xdd, 0x87, 0xb4, 0xfe,
|
||||||
|
0x9d, 0x62, 0xb5, 0xe9, 0xde, 0xcb, 0x1c, 0x3a, 0xef, 0x0c, 0xf5, 0x77, 0xe0, 0xcf, 0x17, 0x1f,
|
||||||
|
0xe6, 0xa1, 0xb8, 0xe2, 0x3e, 0x5c, 0x23, 0x94, 0xa4, 0xfd, 0xd1, 0x07, 0x0f, 0x5d, 0x03, 0x45,
|
||||||
|
0xad, 0x73, 0x3f, 0x3f, 0xe6, 0x65, 0x28, 0xae, 0x92, 0x7d, 0x70, 0x4e, 0x9e, 0xe6, 0xe9, 0x24,
|
||||||
|
0xac, 0x23, 0x2c, 0x20, 0x4c, 0x27, 0x34, 0x13, 0x78, 0xf9, 0x24, 0xc2, 0x44, 0x7e, 0x7c, 0x96,
|
||||||
|
0xb0, 0x91, 0xb0, 0x90, 0x30, 0xd3, 0x5c, 0xe7, 0x6c, 0x26, 0x4c, 0x25, 0xb4, 0x30, 0x02, 0x84,
|
||||||
|
0x9f, 0x84, 0x58, 0x1a, 0xee, 0xb8, 0xc7, 0xd9, 0x96, 0xf0, 0x38, 0xfd, 0x23, 0x1e, 0xa7, 0x10,
|
||||||
|
0x1e, 0x97, 0x10, 0x51, 0x0b, 0x2d, 0x23, 0xfe, 0x98, 0x3b, 0xd1, 0x16, 0x6f, 0x8b, 0xbb, 0x13,
|
||||||
|
0x0b, 0xe5, 0x3d, 0x0a, 0xa3, 0xf8, 0xfb, 0x14, 0xb9, 0x3e, 0xbb, 0x45, 0xac, 0xed, 0xe5, 0x67,
|
||||||
|
0x38, 0x2f, 0x8f, 0xa1, 0x79, 0xd1, 0x8b, 0x5d, 0xb6, 0xeb, 0x89, 0x61, 0xce, 0x35, 0x0f, 0x86,
|
||||||
|
0x6d, 0xf7, 0xc1, 0x76, 0xf1, 0x58, 0x9b, 0x17, 0x9b, 0xe1, 0xb5, 0x8d, 0x09, 0x91, 0xf4, 0xfb,
|
||||||
|
0x21, 0x8d, 0x02, 0xee, 0x93, 0x68, 0xe7, 0x3f, 0x6f, 0x06, 0x1a, 0xf8, 0xe7, 0x1a, 0x0a, 0x75,
|
||||||
|
0x9d, 0xd0, 0xcd, 0xff, 0xe0, 0x40, 0xf7, 0xa5, 0xbc, 0x19, 0x34, 0xb2, 0x7f, 0xf3, 0xda, 0xe0,
|
||||||
|
0xa2, 0x3d, 0x03, 0x9d, 0xfc, 0xe7, 0xca, 0x80, 0xef, 0x5b, 0xea, 0xe4, 0xdf, 0xcc, 0xa0, 0xeb,
|
||||||
|
0x82, 0x3d, 0x03, 0x9d, 0xfc, 0xe7, 0xca, 0x20, 0x12, 0xee, 0xee, 0xd4, 0xc9, 0x3f, 0x21, 0x35,
|
||||||
|
0x77, 0x91, 0xce, 0x20, 0xb0, 0x5b, 0x27, 0xff, 0xe9, 0xb9, 0x93, 0x74, 0xe9, 0xe6, 0xdf, 0xca,
|
||||||
|
0x40, 0x67, 0xff, 0xd6, 0xdc, 0x91, 0xd6, 0xfe, 0x93, 0xe8, 0xf6, 0xdb, 0xfc, 0xb7, 0xe7, 0xf5,
|
||||||
|
0xde, 0x48, 0xfe, 0x6d, 0xe3, 0xbf, 0x35, 0x7f, 0xa5, 0xb3, 0xff, 0x82, 0x32, 0x68, 0x70, 0xff,
|
||||||
|
0x32, 0x83, 0x33, 0xba, 0xf9, 0x17, 0x0f, 0x30, 0x2b, 0xeb, 0x79, 0x10, 0x97, 0xb5, 0xf2, 0x4f,
|
||||||
|
0xe3, 0x3f, 0xc1, 0x6f, 0xcb, 0xe0, 0xac, 0x56, 0xfe, 0xe5, 0xdc, 0xad, 0x3d, 0x03, 0xcd, 0xfc,
|
||||||
|
0x67, 0x65, 0xc0, 0xf3, 0x0a, 0xf4, 0xbc, 0x5b, 0x33, 0xff, 0x3c, 0x97, 0x74, 0xd4, 0x9e, 0x81,
|
||||||
|
0x56, 0xfe, 0x73, 0x67, 0xd0, 0xa3, 0x95, 0x7f, 0x13, 0x87, 0x6d, 0x19, 0xcc, 0xd6, 0xcc, 0x3f,
|
||||||
|
0x1f, 0xf3, 0x47, 0x32, 0xb7, 0xd5, 0xce, 0xbf, 0x3c, 0x0e, 0x34, 0xf7, 0x9f, 0xcc, 0x40, 0x73,
|
||||||
|
0xff, 0x3c, 0x26, 0x6e, 0xb3, 0xda, 0xc8, 0xef, 0x57, 0x5d, 0xd1, 0xca, 0xbf, 0x6d, 0xfc, 0x17,
|
||||||
|
0x34, 0x15, 0xd9, 0xe7, 0xc5, 0x55, 0x5d, 0xfd, 0x17, 0x98, 0x41, 0x43, 0xfb, 0xb7, 0x32, 0xa0,
|
||||||
|
0xef, 0xd8, 0x5d, 0xd3, 0xd5, 0x7f, 0x66, 0x06, 0xba, 0xfa, 0xcf, 0xcc, 0x40, 0x57, 0xff, 0x56,
|
||||||
|
0x06, 0xe4, 0xf9, 0xba, 0xae, 0xfe, 0x33, 0x33, 0xd0, 0xd5, 0x7f, 0x66, 0x06, 0xba, 0xfa, 0xcf,
|
||||||
|
0x18, 0x0f, 0x4e, 0xeb, 0xea, 0xdf, 0xaa, 0x71, 0xff, 0xe3, 0xfe, 0x1b, 0xc4, 0xff, 0x4e, 0x94,
|
||||||
|
0x59, 0x1d, 0x5b, 0xb1, 0x33, 0xaf, 0xf7, 0x7a, 0xf4, 0x1f, 0xc4, 0x13, 0xf1, 0x9c, 0x7e, 0x4e,
|
||||||
|
0x5a, 0x66, 0x19, 0x06, 0x26, 0x50, 0x06, 0xf7, 0xc7, 0x94, 0xff, 0x0a, 0x79, 0x2f, 0x2a, 0x83,
|
||||||
|
0xfa, 0xf1, 0xff, 0x54, 0x7c, 0x85, 0x13, 0x15, 0x2e, 0xce, 0x80, 0x7e, 0x83, 0xf2, 0xa0, 0xce,
|
||||||
|
0xfd, 0x57, 0xc5, 0xbb, 0x55, 0xdb, 0xb7, 0xa3, 0x89, 0x33, 0xa8, 0x53, 0xff, 0xcf, 0xaa, 0xe9,
|
||||||
|
0x3d, 0x33, 0x03, 0xf2, 0xfb, 0xb0, 0xce, 0xfc, 0xd7, 0xc4, 0xbb, 0x3d, 0x83, 0x3a, 0xf1, 0x5f,
|
||||||
|
0x53, 0xef, 0x59, 0xef, 0x85, 0xad, 0x08, 0x2a, 0xf6, 0xff, 0x5c, 0x85, 0x77, 0x7b, 0x06, 0x8a,
|
||||||
|
0xfc, 0x2b, 0xf5, 0x6e, 0xcf, 0xa0, 0xa6, 0xfe, 0x83, 0x58, 0x54, 0xc9, 0xf3, 0x7b, 0xb9, 0xc5,
|
||||||
|
0x19, 0xd0, 0xff, 0x9c, 0x2c, 0xaa, 0x95, 0xff, 0xb1, 0x50, 0xe3, 0xfe, 0xc7, 0xfd, 0xb7, 0x56,
|
||||||
|
0xc8, 0xff, 0x50, 0xb8, 0xeb, 0x3e, 0xc6, 0x58, 0xd1, 0x7f, 0xfd, 0xdc, 0x6f, 0xad, 0x8c, 0xff,
|
||||||
|
0x17, 0x3f, 0x7e, 0xdc, 0x9c, 0x88, 0x31, 0x56, 0xdb, 0xe9, 0x2f, 0x8c, 0xc8, 0x5f, 0x6f, 0x99,
|
||||||
|
0xfe, 0xc7, 0xa4, 0xf7, 0x62, 0x32, 0x68, 0x54, 0xef, 0xb6, 0x0c, 0x1e, 0x17, 0xe9, 0xff, 0x65,
|
||||||
|
0x23, 0x78, 0x2f, 0x24, 0x03, 0x95, 0xde, 0x69, 0x1e, 0xf8, 0x14, 0x03, 0x35, 0x28, 0xce, 0x60,
|
||||||
|
0x91, 0x1b, 0x4f, 0x6c, 0xfe, 0xd5, 0x79, 0xf7, 0xe1, 0x44, 0x6a, 0xce, 0xab, 0x03, 0xe7, 0x51,
|
||||||
|
0x83, 0xca, 0x91, 0x41, 0xe6, 0x77, 0xfc, 0xdf, 0xfd, 0xfe, 0xd0, 0xe3, 0x42, 0x0d, 0xea, 0x93,
|
||||||
|
0x17, 0xc7, 0xed, 0xf3, 0xbe, 0x74, 0x1c, 0x9c, 0x43, 0x0d, 0x6a, 0xc9, 0x12, 0x4c, 0x24, 0xdf,
|
||||||
|
0xaf, 0x6c, 0xfe, 0xdf, 0xab, 0xf4, 0xae, 0x28, 0x83, 0xd7, 0x84, 0xba, 0xf1, 0x6e, 0x81, 0xb7,
|
||||||
|
0x41, 0x0d, 0xca, 0xca, 0x00, 0xcb, 0x11, 0x75, 0xd1, 0xd7, 0x14, 0x0d, 0xc9, 0x21, 0xc9, 0x51,
|
||||||
|
0xc9, 0xfb, 0x24, 0x2f, 0x97, 0x3c, 0x5b, 0xf2, 0x54, 0xc9, 0x2e, 0xc9, 0x4d, 0x7d, 0x26, 0x3b,
|
||||||
|
0xe2, 0x26, 0xc3, 0x62, 0x9f, 0xe4, 0xe5, 0x92, 0x57, 0x49, 0x5e, 0x2d, 0xb9, 0x4d, 0xf2, 0x4a,
|
||||||
|
0xc3, 0xe4, 0x15, 0x21, 0xb9, 0x7d, 0x54, 0x72, 0xbb, 0xe4, 0x56, 0xc9, 0x33, 0x25, 0x37, 0x4b,
|
||||||
|
0x76, 0x49, 0x6e, 0x92, 0xec, 0xb0, 0xf4, 0xec, 0x1c, 0x93, 0x1c, 0x97, 0x9c, 0x90, 0x3c, 0x22,
|
||||||
|
0x59, 0x58, 0x7c, 0x46, 0xf2, 0x77, 0xc9, 0x7f, 0x25, 0x8b, 0x82, 0xd8, 0x41, 0x7f, 0x3b, 0x91,
|
||||||
|
0xec, 0x8f, 0x10, 0x21, 0x66, 0xfe, 0x67, 0x06, 0xe6, 0x16, 0x21, 0x62, 0xcc, 0x6e, 0x21, 0xe2,
|
||||||
|
0xcc, 0x6d, 0x42, 0x24, 0x98, 0xfd, 0x42, 0x8c, 0x30, 0x0b, 0x2a, 0xf6, 0xcf, 0x7c, 0x87, 0x72,
|
||||||
|
0x61, 0x4e, 0xe4, 0x67, 0x3f, 0xf3, 0x08, 0x7d, 0x28, 0x4f, 0x16, 0x1c, 0x26, 0x1b, 0x4d, 0xb4,
|
||||||
|
0x90, 0x56, 0x85, 0x5c, 0xb4, 0x11, 0x6d, 0x1a, 0x6d, 0x21, 0x55, 0x52, 0x8e, 0x31, 0xf8, 0x31,
|
||||||
|
0x2f, 0xe3, 0x75, 0xff, 0x00, 0xd3, 0x39, 0x74, 0x2c, 0x6e, 0x57, 0x00, 0x00};
|
||||||
|
#endif /*__FAVICON_ICO_GZ_H__*/
|
|
@ -1,72 +1,127 @@
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>Index - {DEVICE}</title>
|
<title>Index</title>
|
||||||
<link rel="stylesheet" type="text/css" href="style.css"/>
|
<link rel="stylesheet" type="text/css" href="style.css"/>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<script type="text/javascript">
|
<script type="text/javascript" src="api.js"></script>
|
||||||
getAjax('/uptime', 'uptime');
|
|
||||||
getAjax('/cmdstat', 'cmds');
|
|
||||||
window.setInterval("getAjax('/uptime', 'uptime')", {JS_TS});
|
|
||||||
window.setInterval("getAjax('/cmdstat', 'cmds')", {JS_TS});
|
|
||||||
|
|
||||||
function getAjax(url, resid) {
|
|
||||||
var http = null;
|
|
||||||
http = new XMLHttpRequest();
|
|
||||||
if(http != null) {
|
|
||||||
http.open("GET", url, true);
|
|
||||||
http.onreadystatechange = print;
|
|
||||||
http.send(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
function print() {
|
|
||||||
if(http.readyState == 4) {
|
|
||||||
document.getElementById(resid).innerHTML = http.responseText;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function getInverterInfo(data){
|
|
||||||
var http = null;
|
|
||||||
http = new XMLHttpRequest();
|
|
||||||
if(http != null) {
|
|
||||||
http.open("POST", "/api");
|
|
||||||
http.setRequestHeader("Accept", "application/json");
|
|
||||||
http.setRequestHeader("Content-Type", "application/json");
|
|
||||||
http.send(data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h1>AHOY - {DEVICE}</h1>
|
<h1>AHOY</h1>
|
||||||
<div id="content" class="content">
|
<div id="content" class="content">
|
||||||
<p>
|
<p>
|
||||||
<a href="/visualization">Visualization</a><br/>
|
<a href="/live">Visualization</a><br/>
|
||||||
<br/>
|
<br/>
|
||||||
<a href="/setup">Setup</a><br/>
|
<a href="/setup">Setup</a><br/>
|
||||||
|
<a href="/serial">Webserial & Commands</a><br/>
|
||||||
</p>
|
</p>
|
||||||
<p><span class="des">Uptime: </span><span id="uptime"></span></p>
|
<p><span class="des">Uptime: </span><span id="uptime"></span></p>
|
||||||
<p><span class="des">Statistics: </span><pre id="cmds"></pre></p>
|
<p><span class="des">ESP-Time: </span><span id="date"></span></p>
|
||||||
<p>Every {TS}seconds the values are updated</p>
|
<p><span class="des">RSSI: </span><span id="wifi_rssi"></span>dBm</p>
|
||||||
|
<p>
|
||||||
|
<span class="des">Statistics: </span>
|
||||||
|
<pre id="stat"></pre>
|
||||||
|
<pre id="iv"></pre>
|
||||||
|
<pre id="warn_info"></pre>
|
||||||
|
</p>
|
||||||
|
<p>Every <span id="refresh"></span> seconds the values are updated</p>
|
||||||
|
|
||||||
<div id="note">
|
<div id="note">
|
||||||
This project was started from <a href="https://www.mikrocontroller.net/topic/525778" target="_blank">this discussion. (Mikrocontroller.net)</a><br/>
|
This project was started from <a href="https://www.mikrocontroller.net/topic/525778" target="_blank">this discussion. (Mikrocontroller.net)</a><br/>
|
||||||
New updates can be found on Github: <a href="https://github.com/grindylow/ahoy" target="_blank">https://github.com/grindylow/ahoy</a><br/>
|
New updates can be found on Github: <a href="https://github.com/lumapu/ahoy" target="_blank">https://github.com/lumapu/ahoy</a><br/>
|
||||||
<br/>
|
<br/>
|
||||||
Please report issues using the feature provided by <a href="https://github.com/grindylow/ahoy/issues">Github</a><br/>
|
Please report issues using the feature provided by <a href="https://github.com/lumapu/ahoy/issues">Github</a><br/>
|
||||||
<br/>
|
<br/>
|
||||||
Discuss with us on <a href="https://discord.gg/WzhxEY62mB">Discord</a>
|
Discuss with us on <a href="https://discord.gg/WzhxEY62mB">Discord</a>
|
||||||
<br/>
|
<br/>
|
||||||
<p class="lic"><a href="https://creativecommons.org/licenses/by-nc-sa/3.0/de">Creative Commons - https://creativecommons.org/licenses/by-nc-sa/3.0/de/</a><br/>
|
<p class="lic"><a href="https://creativecommons.org/licenses/by-nc-sa/3.0/de">Creative Commons - https://creativecommons.org/licenses/by-nc-sa/3.0/de/</a><br/>
|
||||||
Check the licenses which are published on <a href="https://github.com/grindylow/ahoy">https://github.com/grindylow/ahoy</a> as well</p>
|
Check the licenses which are published on <a href="https://github.com/lumapu/ahoy">https://github.com/lumapu/ahoy</a> as well</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="footer">
|
<div id="footer">
|
||||||
<p class="left">© 2022</p>
|
<p class="left">© 2022</p>
|
||||||
<p class="left"><a href="/update">Update Firmware</a></p>
|
<p class="left"><a href="/update">Update Firmware</a></p>
|
||||||
<p class="right">AHOY :: {VERSION}</p>
|
<p class="right" id="version"></p>
|
||||||
<p class="right"><a href="/reboot">Reboot</a></p>
|
<p class="right"><a href="/reboot">Reboot</a></p>
|
||||||
<p class="right">Git SHA: {BUILD}</p>
|
<p class="right"><a href="/api">REST API</a></p>
|
||||||
</div>
|
</div>
|
||||||
|
<script type="text/javascript">
|
||||||
|
var mIntervalSet = false;
|
||||||
|
function parseSys(obj) {
|
||||||
|
document.getElementById("version").innerHTML = "Git SHA: " + obj["build"] + " :: " + obj["version"];
|
||||||
|
document.getElementById("wifi_rssi").innerHTML = obj["wifi_rssi"];;
|
||||||
|
|
||||||
|
var date = new Date(obj["ts_now"] * 1000);
|
||||||
|
var up = obj["ts_uptime"];
|
||||||
|
var days = parseInt(up / 86400) % 365;
|
||||||
|
var hrs = parseInt(up / 3600) % 24;
|
||||||
|
var min = parseInt(up / 60) % 60;
|
||||||
|
var sec = up % 60;
|
||||||
|
document.getElementById("uptime").innerHTML = days + " Days, "
|
||||||
|
+ ("0"+hrs).substr(-2) + ":"
|
||||||
|
+ ("0"+min).substr(-2) + ":"
|
||||||
|
+ ("0"+sec).substr(-2);
|
||||||
|
document.getElementById("date").innerHTML = date.toLocaleString('de-DE', {timeZone: 'UTC'});
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseStat(obj) {
|
||||||
|
document.getElementById("stat").innerHTML = "RX success: " + obj["rx_success"]
|
||||||
|
+ "\nRX fail: " + obj["rx_fail"]
|
||||||
|
+ "\nRX no anwser: " + obj["rx_fail_answer"]
|
||||||
|
+ "\nFrames received: " + obj["frame_cnt"]
|
||||||
|
+ "\nTX Cnt: " + obj["tx_cnt"];
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseIv(obj) {
|
||||||
|
var html = "";
|
||||||
|
for(var i of obj) {
|
||||||
|
html += "Inverter #" + i["id"] + ": " + i["name"] + " (v" + i["version"] + ") is ";
|
||||||
|
if(false == i["is_avail"])
|
||||||
|
html += "not ";
|
||||||
|
html += "available and is ";
|
||||||
|
if(false == i["is_producing"])
|
||||||
|
html += "not ";
|
||||||
|
html += "producing\n";
|
||||||
|
|
||||||
|
if(false == i["is_avail"]) {
|
||||||
|
if(i["ts_last_success"] > 0) {
|
||||||
|
var date = new Date(i["ts_last_success"] * 1000);
|
||||||
|
html += "-> last successful transmission: " + date.toLocaleString('de-DE', {timeZone: 'UTC'});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
document.getElementById("iv").innerHTML = html;
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseWarnInfo(warn, info) {
|
||||||
|
var html = "";
|
||||||
|
for(var w of warn) {
|
||||||
|
html += "WARN: " + w + "\n";
|
||||||
|
}
|
||||||
|
for(var i of info) {
|
||||||
|
html += "INFO: " + i + "\n";
|
||||||
|
}
|
||||||
|
document.getElementById("warn_info").innerHTML = html;
|
||||||
|
}
|
||||||
|
|
||||||
|
function parse(obj) {
|
||||||
|
if(null != obj) {
|
||||||
|
parseSys(obj["system"]);
|
||||||
|
parseStat(obj["statistics"]);
|
||||||
|
parseIv(obj["inverter"]);
|
||||||
|
parseWarnInfo(obj["warnings"], obj["infos"]);
|
||||||
|
document.getElementById("refresh").innerHTML = obj["refresh_interval"];
|
||||||
|
if(false == mIntervalSet) {
|
||||||
|
window.setInterval("getAjax('/api/index', parse)", obj["refresh_interval"] * 1000);
|
||||||
|
mIntervalSet = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
document.getElementById("refresh").innerHTML = "n/a";
|
||||||
|
}
|
||||||
|
|
||||||
|
getAjax("/api/index", parse);
|
||||||
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
188
tools/esp8266/html/serial.html
Normal file
188
tools/esp8266/html/serial.html
Normal file
|
@ -0,0 +1,188 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Serial Console</title>
|
||||||
|
<link rel="stylesheet" type="text/css" href="style.css"/>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<script type="text/javascript" src="api.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Serial Console</h1>
|
||||||
|
<div id="content" class="content">
|
||||||
|
<div class="serial">
|
||||||
|
<textarea id="serial" cols="80" rows="20" readonly></textarea><br/>
|
||||||
|
connected: <span class="dot" id="connected"></span>
|
||||||
|
Uptime: <span id="uptime"></span>
|
||||||
|
<input type="button" value="clear" class="btn" id="clear"/>
|
||||||
|
<input type="button" value="autoscroll" class="btn" id="scroll"/>
|
||||||
|
<br/>
|
||||||
|
<br/>
|
||||||
|
<br/>
|
||||||
|
<br/>
|
||||||
|
<hr>
|
||||||
|
<h3>Commands</h3>
|
||||||
|
<br/>
|
||||||
|
<label for="iv">Select Inverter:</label>
|
||||||
|
<select name="iv" id="InvID">
|
||||||
|
</select>
|
||||||
|
<br/>
|
||||||
|
<div id="power">
|
||||||
|
<input type="button" value="Restart" class="btn" id="restart"/>
|
||||||
|
<input type="button" value="Turn Off" class="btn" id="power_off"/>
|
||||||
|
<input type="button" value="Turn On" class="btn" id="power_on"/>
|
||||||
|
</div>
|
||||||
|
<br/>
|
||||||
|
<br/>
|
||||||
|
<br/>
|
||||||
|
<br/>
|
||||||
|
<br/>
|
||||||
|
<label>Send Power Limit: </label>
|
||||||
|
<input type="number" class="text" name="pwrlimval" maxlength="4"/>
|
||||||
|
<label> </label>
|
||||||
|
<select name="pwrlimcntrl" id="pwrlimcntrl">
|
||||||
|
<option value="" selected disabled hidden>select the unit and persistence</option>
|
||||||
|
<option value="0">absolute in Watt non persistent</option>
|
||||||
|
<option value="1">relative in percent non persistent</option>
|
||||||
|
<option value="256">absolute in Watt persistent</option>
|
||||||
|
<option value="257">relative in percent persistent</option>
|
||||||
|
</select>
|
||||||
|
<br/>
|
||||||
|
<input type="button" value="Send Power Limit" class="btn" id="sendpwrlim"/>
|
||||||
|
<br/>
|
||||||
|
<p>Ctrl result: <span id="result">n/a</span></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="footer">
|
||||||
|
<p class="left">© 2022</p>
|
||||||
|
<p class="left"><a href="/">Home</a></p>
|
||||||
|
<p class="right" id="version"></p>
|
||||||
|
</div>
|
||||||
|
<script type="text/javascript">
|
||||||
|
var mAutoScroll = true;
|
||||||
|
var con = document.getElementById("serial");
|
||||||
|
var mIntervalSet = false;
|
||||||
|
|
||||||
|
function parseSys(obj) {
|
||||||
|
var up = obj["ts_uptime"];
|
||||||
|
var days = parseInt(up / 86400) % 365;
|
||||||
|
var hrs = parseInt(up / 3600) % 24;
|
||||||
|
var min = parseInt(up / 60) % 60;
|
||||||
|
var sec = up % 60;
|
||||||
|
document.getElementById("uptime").innerHTML = days + " Days, "
|
||||||
|
+ ("0"+hrs).substr(-2) + ":"
|
||||||
|
+ ("0"+min).substr(-2) + ":"
|
||||||
|
+ ("0"+sec).substr(-2);
|
||||||
|
|
||||||
|
if(false == mIntervalSet) {
|
||||||
|
document.getElementById("version").innerHTML = "Git SHA: " + obj["build"] + " :: " + obj["version"];
|
||||||
|
window.setInterval("getAjax('/api/system', parseSys)", 10000);
|
||||||
|
mIntervalSet = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function parse(root) {
|
||||||
|
select = document.getElementById('InvID');
|
||||||
|
|
||||||
|
if(null == root) return;
|
||||||
|
root = root.inverter;
|
||||||
|
for(var i = 0; i < root.inverter.length; i++)
|
||||||
|
{
|
||||||
|
inv = root.inverter[i];
|
||||||
|
var opt = document.createElement('option');
|
||||||
|
opt.value = inv.id;
|
||||||
|
opt.innerHTML = inv.name;
|
||||||
|
select.appendChild(opt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById("clear").addEventListener("click", function() {
|
||||||
|
con.value = "";
|
||||||
|
});
|
||||||
|
document.getElementById("scroll").addEventListener("click", function() {
|
||||||
|
mAutoScroll = !mAutoScroll;
|
||||||
|
this.value = (mAutoScroll) ? "autoscroll" : "manual scoll";
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!!window.EventSource) {
|
||||||
|
var source = new EventSource('/events');
|
||||||
|
source.addEventListener('open', function(e) {
|
||||||
|
document.getElementById("connected").style.backgroundColor = "#0c0";
|
||||||
|
}, false);
|
||||||
|
|
||||||
|
source.addEventListener('error', function(e) {
|
||||||
|
if (e.target.readyState != EventSource.OPEN) {
|
||||||
|
document.getElementById("connected").style.backgroundColor = "#f00";
|
||||||
|
}
|
||||||
|
}, false);
|
||||||
|
|
||||||
|
source.addEventListener('serial', function(e) {
|
||||||
|
con.value += e.data.replace(/\<rn\>/g, '\r\n');
|
||||||
|
if(mAutoScroll)
|
||||||
|
con.scrollTop = con.scrollHeight;
|
||||||
|
}, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
getAjax("/api/system", parseSys);
|
||||||
|
|
||||||
|
// only for test
|
||||||
|
function ctrlCb(obj) {
|
||||||
|
var e = document.getElementById("result");
|
||||||
|
if(obj["success"])
|
||||||
|
e.innerHTML = "ok";
|
||||||
|
else
|
||||||
|
e.innerHTML = "Error: " + obj["error"];
|
||||||
|
}
|
||||||
|
|
||||||
|
function get_selected_iv()
|
||||||
|
{
|
||||||
|
var e = document.getElementById("InvID");
|
||||||
|
return parseInt(e.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
const wrapper = document.getElementById('power');
|
||||||
|
|
||||||
|
wrapper.addEventListener('click', (event) => {
|
||||||
|
var power = event.target.value;
|
||||||
|
var obj = new Object();
|
||||||
|
|
||||||
|
switch (power)
|
||||||
|
{
|
||||||
|
case "Turn On":
|
||||||
|
obj.cmd = 0;
|
||||||
|
break;
|
||||||
|
case "Turn Off":
|
||||||
|
obj.cmd = 1;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
obj.cmd = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
obj.inverter = get_selected_iv();
|
||||||
|
obj.tx_request = 81;
|
||||||
|
getAjax("/api/ctrl", ctrlCb, "POST", JSON.stringify(obj));
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById("sendpwrlim").addEventListener("click", function() {
|
||||||
|
var val = parseInt(document.getElementsByName('pwrlimval')[0].value);
|
||||||
|
var ctrl = parseInt(document.getElementsByName('pwrlimcntrl')[0].value);
|
||||||
|
|
||||||
|
if((ctrl == 1 || ctrl == 257) && unit < 2) unit = 2;
|
||||||
|
if(isNaN(val) || isNaN(ctrl))
|
||||||
|
{
|
||||||
|
var tmp = (isNaN(val)) ? "Value" : "Unit";
|
||||||
|
document.getElementById("result").textContent = tmp + " is missing";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var obj = new Object();
|
||||||
|
obj.inverter = get_selected_iv();
|
||||||
|
obj.cmd = 11;
|
||||||
|
obj.tx_request = 81;
|
||||||
|
obj.payload = [val, ctrl];
|
||||||
|
getAjax("/api/ctrl", ctrlCb, "POST", JSON.stringify(obj));
|
||||||
|
});
|
||||||
|
|
||||||
|
getAjax("/api/setup", parse);
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -1,51 +1,20 @@
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>Setup - {DEVICE}</title>
|
<title>Setup</title>
|
||||||
<link rel="stylesheet" type="text/css" href="style.css"/>
|
<link rel="stylesheet" type="text/css" href="style.css"/>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<script type="text/javascript" src="api.js"></script>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
function toggle(name, hide) {
|
|
||||||
var elm = document.getElementsByName(name)[0];
|
|
||||||
if(hide) {
|
|
||||||
if(!elm.classList.contains("hide"))
|
|
||||||
elm.classList.add("hide");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
elm.classList.remove('hide');
|
|
||||||
}
|
|
||||||
|
|
||||||
function load() {
|
function load() {
|
||||||
document.querySelectorAll('input[name^="inv"][name$="Addr"]').forEach(elm => {
|
for(it of document.getElementsByClassName("s_collapsible")) {
|
||||||
elm.addEventListener("keyup", (e) => {
|
it.addEventListener("click", function() {
|
||||||
serial = elm.value.substring(0,4);
|
this.classList.toggle("active");
|
||||||
iv = elm.name.substring(3,4);
|
var content = this.nextElementSibling;
|
||||||
max = 0;
|
content.style.display = (content.style.display === "block") ? "none" : "block";
|
||||||
for(i=0;i<4;i++) {
|
|
||||||
toggle("inv"+iv+"ModPwr"+i, true);
|
|
||||||
toggle("inv"+iv+"ModName"+i, true);
|
|
||||||
}
|
|
||||||
toggle("lbl"+iv+"ModPwr", true);
|
|
||||||
toggle("lbl"+iv+"ModName", true);
|
|
||||||
|
|
||||||
if(serial == "1161") max = 4;
|
|
||||||
else if(serial == "1141") max = 2;
|
|
||||||
else if(serial == "1121") max = 1;
|
|
||||||
|
|
||||||
for(i=0;i<max;i++) {
|
|
||||||
toggle("inv"+iv+"ModPwr"+i, false);
|
|
||||||
toggle("inv"+iv+"ModName"+i, false);
|
|
||||||
}
|
|
||||||
if(max != 0) {
|
|
||||||
toggle("lbl"+iv+"ModPwr", false);
|
|
||||||
toggle("lbl"+iv+"ModName", false);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
evt = document.createEvent("HTMLEvents");
|
|
||||||
evt.initEvent("keyup", false, true);
|
|
||||||
elm.dispatchEvent(evt);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
</head>
|
</head>
|
||||||
<body onload="load()">
|
<body onload="load()">
|
||||||
|
@ -54,11 +23,11 @@
|
||||||
<div id="content">
|
<div id="content">
|
||||||
<a class="erase" href="/erase">ERASE SETTINGS (not WiFi)</a>
|
<a class="erase" href="/erase">ERASE SETTINGS (not WiFi)</a>
|
||||||
|
|
||||||
<form method="post" action="{IP}/save">
|
<form method="post" action="/save">
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend class="des">Device Host Name</legend>
|
<legend class="des">Device Host Name</legend>
|
||||||
<label for="device">Device Name</label>
|
<label for="device">Device Name</label>
|
||||||
<input type="text" class="text" name="device" value="{DEVICE}"/>
|
<input type="text" name="device" class="text"/>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
<button type="button" class="s_collapsible">WiFi</button>
|
<button type="button" class="s_collapsible">WiFi</button>
|
||||||
|
@ -67,7 +36,7 @@
|
||||||
<legend class="des">WiFi</legend>
|
<legend class="des">WiFi</legend>
|
||||||
<p>Enter the credentials to your prefered WiFi station. After rebooting the device tries to connect with this information.</p>
|
<p>Enter the credentials to your prefered WiFi station. After rebooting the device tries to connect with this information.</p>
|
||||||
<label for="ssid">SSID</label>
|
<label for="ssid">SSID</label>
|
||||||
<input type="text" class="text" name="ssid" value="{SSID}"/>
|
<input type="text" name="ssid" class="text"/>
|
||||||
<label for="pwd">Password</label>
|
<label for="pwd">Password</label>
|
||||||
<input type="password" class="text" name="pwd" value="{PWD}"/>
|
<input type="password" class="text" name="pwd" value="{PWD}"/>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
@ -77,12 +46,13 @@
|
||||||
<div class="s_content">
|
<div class="s_content">
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend class="des">Inverter</legend>
|
<legend class="des">Inverter</legend>
|
||||||
{INVERTERS}<br/>
|
<div id="inverter"></div><br/>
|
||||||
|
<input type="button" name="btnAdd" value="Add Inverter"/>
|
||||||
<p class="subdes">General</p>
|
<p class="subdes">General</p>
|
||||||
<label for="invInterval">Interval [s]</label>
|
<label for="invInterval">Interval [s]</label>
|
||||||
<input type="text" class="text" name="invInterval" value="{INV_INTVL}"/>
|
<input type="text" class="text" name="invInterval"/>
|
||||||
<label for="invRetry">Max retries per Payload</label>
|
<label for="invRetry">Max retries per Payload</label>
|
||||||
<input type="text" class="text" name="invRetry" value="{INV_RETRIES}"/>
|
<input type="text" class="text" name="invRetry"/>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -91,9 +61,13 @@
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend class="des">NTP Server</legend>
|
<legend class="des">NTP Server</legend>
|
||||||
<label for="ntpAddr">NTP Server / IP</label>
|
<label for="ntpAddr">NTP Server / IP</label>
|
||||||
<input type="text" class="text" name="ntpAddr" value="{NTP_ADDR}"/>
|
<input type="text" class="text" name="ntpAddr"/>
|
||||||
<label for="ntpPort">NTP Port</label>
|
<label for="ntpPort">NTP Port</label>
|
||||||
<input type="text" class="text" name="ntpPort" value="{NTP_PORT}"/>
|
<input type="text" class="text" name="ntpPort"/>
|
||||||
|
<label for="ntpBtn">set system time</label>
|
||||||
|
<input type="button" name="ntpBtn" id="ntpBtn" class="btn" value="from browser" onclick="setTime()"/>
|
||||||
|
<input type="button" name="ntpSync" id="ntpSync" class="btn" value="sync NTP" onclick="syncTime()"/>
|
||||||
|
<span id="apiResult">n/a</span>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -102,15 +76,15 @@
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend class="des">MQTT</legend>
|
<legend class="des">MQTT</legend>
|
||||||
<label for="mqttAddr">Broker / Server IP</label>
|
<label for="mqttAddr">Broker / Server IP</label>
|
||||||
<input type="text" class="text" name="mqttAddr" value="{MQTT_ADDR}" maxlength="32" />
|
<input type="text" class="text" name="mqttAddr" maxlength="32" />
|
||||||
<label for="mqttPort">Port</label>
|
<label for="mqttPort">Port</label>
|
||||||
<input type="text" class="text" name="mqttPort" value="{MQTT_PORT}"/>
|
<input type="text" class="text" name="mqttPort"/>
|
||||||
<label for="mqttUser">Username (optional)</label>
|
<label for="mqttUser">Username (optional)</label>
|
||||||
<input type="text" class="text" name="mqttUser" value="{MQTT_USER}"/>
|
<input type="text" class="text" name="mqttUser"/>
|
||||||
<label for="mqttPwd">Password (optional)</label>
|
<label for="mqttPwd">Password (optional)</label>
|
||||||
<input type="text" class="text" name="mqttPwd" value="{MQTT_PWD}"/>
|
<input type="password" class="text" name="mqttPwd"/>
|
||||||
<label for="mqttTopic">Topic</label>
|
<label for="mqttTopic">Topic</label>
|
||||||
<input type="text" class="text" name="mqttTopic" value="{MQTT_TOPIC}"/>
|
<input type="text" class="text" name="mqttTopic"/>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -119,47 +93,207 @@
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend class="des">System Config</legend>
|
<legend class="des">System Config</legend>
|
||||||
<p class="des">Pinout (Wemos)</p>
|
<p class="des">Pinout (Wemos)</p>
|
||||||
{PINOUT}
|
<div id="pinout"></div>
|
||||||
|
|
||||||
<p class="des">Radio (NRF24L01+)</p>
|
<p class="des">Radio (NRF24L01+)</p>
|
||||||
<label for="rf24Power">Amplifier Power Level</label>
|
<div id="rf24"></div>
|
||||||
<select name="rf24Power">{RF24}</select>
|
|
||||||
|
|
||||||
<p class="des">Serial Console</p>
|
<p class="des">Serial Console</p>
|
||||||
<label for="serEn">print inverter data</label>
|
<label for="serEn">print inverter data</label>
|
||||||
<input type="checkbox" class="cb" name="serEn" {SER_VAL_CB}/><br/>
|
<input type="checkbox" class="cb" name="serEn"/><br/>
|
||||||
<label for="serDbg">Serial Debug</label>
|
<label for="serDbg">Serial Debug</label>
|
||||||
<input type="checkbox" class="cb" name="serDbg" {SER_DBG_CB}/><br/>
|
<input type="checkbox" class="cb" name="serDbg"/><br/>
|
||||||
<label for="serIntvl">Interval [s]</label>
|
<label for="serIntvl">Interval [s]</label>
|
||||||
<input type="text" class="text" name="serIntvl" value="{SER_INTVL}"/>
|
<input type="text" class="text" name="serIntvl"/>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<label for="reboot">Reboot device after successful save</label>
|
<label for="reboot">Reboot device after successful save</label>
|
||||||
<input type="checkbox" class="cb" name="reboot"/>
|
<input type="checkbox" class="cb" name="reboot"/>
|
||||||
<input type="submit" value="save" class="btn" />
|
<input type="submit" value="save" class="btn right"/>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="footer">
|
<div id="footer">
|
||||||
<p class="left"><a href="{IP}/">Home</a></p>
|
<p class="left"><a href="/">Home</a></p>
|
||||||
<p class="left"><a href="{IP}/update">Update Firmware</a></p>
|
<p class="left"><a href="/update">Update Firmware</a></p>
|
||||||
<p class="right">AHOY - {VERSION}</p>
|
<p class="right" id="version"></p>
|
||||||
<p class="right"><a href="{IP}/factory">Factory Reset</a></p>
|
<p class="right"><a href="/factory">Factory Reset</a></p>
|
||||||
<p class="right"><a href="{IP}/reboot">Reboot</a></p>
|
<p class="right"><a href="/reboot">Reboot</a></p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
var coll = document.getElementsByClassName("s_collapsible");
|
var highestId = 0;
|
||||||
var i;
|
var maxInv = 0;
|
||||||
for (i = 0; i < coll.length; i++) {
|
|
||||||
coll[i].addEventListener("click", function() {
|
const re = /11[2,4,6]1.*/;
|
||||||
this.classList.toggle("active");
|
|
||||||
var content = this.nextElementSibling;
|
document.getElementsByName("btnAdd")[0].addEventListener("click", function() {
|
||||||
content.style.display = (content.style.display === "block") ? "none" : "block";
|
if(highestId < (maxInv-1))
|
||||||
|
ivHtml(JSON.parse('{"name":"","serial":"","channels":4,"ch_max_power":[0,0,0,0],"ch_name":["","","",""]}'), highestId + 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function apiCb(obj) {
|
||||||
|
var e = document.getElementById("apiResult");
|
||||||
|
if(obj["success"])
|
||||||
|
e.innerHTML = "command excuted";
|
||||||
|
else
|
||||||
|
e.innerHTML = "Error: " + obj["error"];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setTime() {
|
||||||
|
var date = new Date();
|
||||||
|
var offset = date.getTimezoneOffset() * -60;
|
||||||
|
var obj = new Object();
|
||||||
|
obj.cmd = "set_time";
|
||||||
|
obj.ts = parseInt(offset + (date.getTime() / 1000));
|
||||||
|
getAjax("/api/setup", apiCb, "POST", JSON.stringify(obj));
|
||||||
|
}
|
||||||
|
|
||||||
|
function syncTime() {
|
||||||
|
var obj = new Object();
|
||||||
|
obj.cmd = "sync_ntp";
|
||||||
|
getAjax("/api/setup", apiCb, "POST", JSON.stringify(obj));
|
||||||
|
}
|
||||||
|
|
||||||
|
function ivHtml(obj, id) {
|
||||||
|
highestId = id;
|
||||||
|
if(highestId == (maxInv - 1))
|
||||||
|
toggle("btnAdd", true);
|
||||||
|
iv = document.getElementById("inverter");
|
||||||
|
iv.appendChild(des("Inverter " + id));
|
||||||
|
id = "inv" + id;
|
||||||
|
|
||||||
|
iv.appendChild(lbl(id + "Addr", "Address*"));
|
||||||
|
var addr = inp(id + "Addr", obj["serial"], 12)
|
||||||
|
iv.appendChild(addr);
|
||||||
|
addr.addEventListener("keyup", (e) => {
|
||||||
|
var serial = addr.value.substring(0,4);
|
||||||
|
var max = 0;
|
||||||
|
for(var i=0;i<4;i++) {
|
||||||
|
toggle(id+"ModPwr"+i, true);
|
||||||
|
toggle(id+"ModName"+i, true);
|
||||||
|
}
|
||||||
|
toggle("lbl"+id+"ModPwr", true);
|
||||||
|
toggle("lbl"+id+"ModName", true);
|
||||||
|
|
||||||
|
if(serial == "1161") max = 4;
|
||||||
|
else if(serial == "1141") max = 2;
|
||||||
|
else if(serial == "1121") max = 1;
|
||||||
|
|
||||||
|
for(var i=0;i<max;i++) {
|
||||||
|
toggle(id+"ModPwr"+i, false);
|
||||||
|
toggle(id+"ModName"+i, false);
|
||||||
|
}
|
||||||
|
if(max != 0) {
|
||||||
|
toggle("lbl"+id+"ModPwr", false);
|
||||||
|
toggle("lbl"+id+"ModName", false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
for(var i of [["Name", "name", "Name*", 32]]) { // so richtig?
|
||||||
|
iv.appendChild(lbl(id + i[0], i[2]));
|
||||||
|
iv.appendChild(inp(id + i[0], obj[i[1]], i[3]));
|
||||||
|
}
|
||||||
|
|
||||||
|
for(var j of [["ModPwr", "ch_max_power", "Max Module Power (Wp)", 4], ["ModName", "ch_name", "Module Name", 16]]) {
|
||||||
|
var cl = (re.test(obj["serial"])) ? null : ["hide"];
|
||||||
|
iv.appendChild(lbl(null, j[2], cl, "lbl" + id + j[0]));
|
||||||
|
d = div([j[0]]);
|
||||||
|
i = 0;
|
||||||
|
cl = (re.test(obj["serial"])) ? ["text", "sh"] : ["text", "sh", "hide"];
|
||||||
|
for(it of obj[j[1]]) {
|
||||||
|
d.appendChild(inp(id + j[0] + i, it, j[3], cl, id + j[0] + i));
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
iv.appendChild(d);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function ivGlob(obj) {
|
||||||
|
for(var i of [["invInterval", "interval"], ["invRetry", "retries"]])
|
||||||
|
document.getElementsByName(i[0])[0].value = obj[i[1]];
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseSys(obj) {
|
||||||
|
for(var i of [["device", "device_name"], ["ssid", "ssid"]])
|
||||||
|
document.getElementsByName(i[0])[0].value = obj[i[1]];
|
||||||
|
document.getElementById("version").innerHTML = "Git SHA: " + obj["build"] + " :: " + obj["version"];
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseIv(obj) {
|
||||||
|
for(var i = 0; i < obj.inverter.length; i++)
|
||||||
|
ivHtml(obj.inverter[i], i);
|
||||||
|
ivGlob(obj);
|
||||||
|
maxInv = obj["max_num_inverters"];
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseMqtt(obj) {
|
||||||
|
for(var i of [["Addr", "broker"], ["Port", "port"], ["User", "user"], ["Pwd", "pwd"], ["Topic", "topic"]])
|
||||||
|
document.getElementsByName("mqtt"+i[0])[0].value = obj[i[1]];
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseNtp(obj) {
|
||||||
|
for(var i of [["ntpAddr", "addr"], ["ntpPort", "port"]])
|
||||||
|
document.getElementsByName(i[0])[0].value = obj[i[1]];
|
||||||
|
}
|
||||||
|
|
||||||
|
function parsePinout(obj) {
|
||||||
|
var e = document.getElementById("pinout");
|
||||||
|
pins = [['cs', 'pinCs'], ['ce', 'pinCe'], ['irq', 'pinIrq']];
|
||||||
|
for(p of pins) {
|
||||||
|
e.appendChild(lbl(p[1], p[0].toUpperCase()));
|
||||||
|
e.appendChild(sel(p[1], [
|
||||||
|
[0, "D3 (GPIO0)"],
|
||||||
|
[1, "TX (GPIO1)"],
|
||||||
|
[2, "D4 (GPIO2)"],
|
||||||
|
[3, "RX (GPIO3)"],
|
||||||
|
[4, "D2 (GPIO4)"],
|
||||||
|
[5, "D1 (GPIO5)"],
|
||||||
|
[6, "GPIO6"],
|
||||||
|
[7, "GPIO7"],
|
||||||
|
[8, "GPIO8"],
|
||||||
|
[9, "GPIO9"],
|
||||||
|
[10, "GPIO10"],
|
||||||
|
[11, "GPIO11"],
|
||||||
|
[12, "D6 (GPIO12)"],
|
||||||
|
[13, "D7 (GPIO13)"],
|
||||||
|
[14, "D5 (GPIO14)"],
|
||||||
|
[15, "D8 (GPIO15)"],
|
||||||
|
[16, "D0 (GPIO16 - no IRQ!)"]
|
||||||
|
], obj[p[0]]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseRadio(obj) {
|
||||||
|
var e = document.getElementById("rf24");
|
||||||
|
e.appendChild(lbl("rf24Power", "Amplifier Power Level"));
|
||||||
|
e.appendChild(sel("rf24Power", [
|
||||||
|
[0, "MIN"],
|
||||||
|
[1, "LOW"],
|
||||||
|
[2, "HIGH"],
|
||||||
|
[3, "MAX"]
|
||||||
|
], obj["power_level"]));
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseSerial(obj) {
|
||||||
|
for(var i of [["serEn", "show_live_data"], ["serDbg", "debug"]])
|
||||||
|
document.getElementsByName(i[0])[0].checked = obj[i[1]];
|
||||||
|
document.getElementsByName("serIntvl")[0].value = obj["interval"];
|
||||||
|
}
|
||||||
|
|
||||||
|
function parse(root) {
|
||||||
|
if(null != root) {
|
||||||
|
parseSys(root["system"]);
|
||||||
|
parseIv(root["inverter"]);
|
||||||
|
parseMqtt(root["mqtt"]);
|
||||||
|
parseNtp(root["ntp"]);
|
||||||
|
parsePinout(root["pinout"]);
|
||||||
|
parseRadio(root["radio"]);
|
||||||
|
parseSerial(root["serial"]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getAjax("/api/setup", parse);
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -133,9 +133,14 @@ input.btn {
|
||||||
background-color: #006ec0;
|
background-color: #006ec0;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
border: 0px;
|
border: 0px;
|
||||||
float: right;
|
padding: 7px 20px 7px 20px;
|
||||||
margin: 10px 0 30px;
|
margin-bottom: 10px;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
input.btn:hover {
|
||||||
|
background-color: #044e86;
|
||||||
}
|
}
|
||||||
|
|
||||||
input.cb {
|
input.cb {
|
||||||
|
@ -245,7 +250,7 @@ div.ts {
|
||||||
padding: 7px;
|
padding: 7px;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.modpwr, div.modname {
|
div.ModPwr, div.ModName {
|
||||||
width:70%;
|
width:70%;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
@ -271,3 +276,20 @@ div.modpwr, div.modname {
|
||||||
width: 180px;
|
width: 180px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#serial {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#content .serial {
|
||||||
|
max-width: 1000px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dot {
|
||||||
|
height: 15px;
|
||||||
|
width: 15px;
|
||||||
|
background-color: #f00;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: inline-block;
|
||||||
|
margin-top: 15px;
|
||||||
|
}
|
||||||
|
|
33
tools/esp8266/html/update.html
Normal file
33
tools/esp8266/html/update.html
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Update</title>
|
||||||
|
<link rel="stylesheet" type="text/css" href="style.css"/>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<script type="text/javascript" src="api.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Update</h1>
|
||||||
|
<div id="content" class="content">
|
||||||
|
<div>
|
||||||
|
Make sure that you have noted all your settings before starting an update. New versions may have changed their memory layout which can break your existing settings.
|
||||||
|
</div>
|
||||||
|
<br/><br/>
|
||||||
|
<form method="POST" action="/update" enctype="multipart/form-data" accept-charset="utf-8">
|
||||||
|
<input type="file" name="update"><input type="submit" value="Update">
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div id="footer">
|
||||||
|
<p class="left">© 2022</p>
|
||||||
|
<p class="left"><a href="/">Home</a></p>
|
||||||
|
<p class="right" id="version"></p>
|
||||||
|
</div>
|
||||||
|
<script type="text/javascript">
|
||||||
|
function parseSys(obj) {
|
||||||
|
document.getElementById("version").innerHTML = "Git SHA: " + obj["build"] + " :: " + obj["version"];
|
||||||
|
}
|
||||||
|
|
||||||
|
getAjax("/api/system", parseSys);
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -1,43 +1,118 @@
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>Index - {DEVICE}</title>
|
<title>Live</title>
|
||||||
<link rel="stylesheet" type="text/css" href="style.css"/>
|
<link rel="stylesheet" type="text/css" href="style.css"/>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||||
<script type="text/javascript">
|
<script type="text/javascript" src="api.js"></script>
|
||||||
getAjax('/livedata', 'livedata');
|
|
||||||
window.setInterval("getAjax('/livedata', 'livedata')", {JS_TS});
|
|
||||||
|
|
||||||
function getAjax(url, resid) {
|
|
||||||
var http = null;
|
|
||||||
http = new XMLHttpRequest();
|
|
||||||
if(http != null) {
|
|
||||||
http.open("GET", url, true);
|
|
||||||
http.onreadystatechange = print;
|
|
||||||
http.send(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
function print() {
|
|
||||||
if(http.readyState == 4) {
|
|
||||||
document.getElementById(resid).innerHTML = http.responseText;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<style type="text/css">
|
|
||||||
</style>
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h1>AHOY - {DEVICE}</h1>
|
<h1>AHOY</h1>
|
||||||
<div id="content" class="content">
|
<div id="content" class="content">
|
||||||
<div id="livedata"></div>
|
<div id="live"></div>
|
||||||
<p>Every {TS}seconds the values are updated</p>
|
<p>Every <span id="refresh"></span> seconds the values are updated</p>
|
||||||
</div>
|
</div>
|
||||||
<div id="footer">
|
<div id="footer">
|
||||||
<p class="left">© 2022</p>
|
<p class="left">© 2022</p>
|
||||||
<p class="left"><a href="/">Home</a></p>
|
<p class="left"><a href="/">Home</a></p>
|
||||||
<p class="right">AHOY :: {VERSION}</p>
|
<p class="right" id="version"></p>
|
||||||
</div>
|
</div>
|
||||||
|
<script type="text/javascript">
|
||||||
|
var intervalSet = false;
|
||||||
|
function parseSys(obj) {
|
||||||
|
document.getElementById("version").innerHTML = "Git SHA: " + obj["build"] + " :: " + obj["version"];
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseIv(obj, root) {
|
||||||
|
var ivHtml = [];
|
||||||
|
|
||||||
|
var tDiv = div(["ch-all", "iv"]);
|
||||||
|
tDiv.appendChild(span("Total", ["head"]));
|
||||||
|
var total = new Array(root.ch0_fld_names.length).fill(0);
|
||||||
|
if(obj.length > 1)
|
||||||
|
ivHtml.push(tDiv);
|
||||||
|
|
||||||
|
for(var iv of obj) {
|
||||||
|
main = div(["iv"]);
|
||||||
|
var ch0 = div(["ch-iv"]);
|
||||||
|
var limit = iv["power_limit_read"] + "%";
|
||||||
|
if(limit == "65535%")
|
||||||
|
limit = "n/a";
|
||||||
|
ch0.appendChild(span(iv["name"] + " Limit " + limit + " | last Alarm: " + iv["last_alarm"], ["head"]));
|
||||||
|
|
||||||
|
for(var j = 0; j < root.ch0_fld_names.length; j++) {
|
||||||
|
var val = Math.round(iv["ch"][0][j] * 100) / 100;
|
||||||
|
if(val > 0) {
|
||||||
|
var sub = div(["subgrp"]);
|
||||||
|
sub.appendChild(span(val + " " + span(root["ch0_fld_units"][j], ["unit"]).innerHTML, ["value"]));
|
||||||
|
sub.appendChild(span(root["ch0_fld_names"][j], ["info"]));
|
||||||
|
ch0.appendChild(sub);
|
||||||
|
|
||||||
|
switch(j) {
|
||||||
|
case 2: total[j] += val; break; // P_AC
|
||||||
|
case 6: total[j] += val; break; // YieldTotal
|
||||||
|
case 7: total[j] += val; break; // YieldDay
|
||||||
|
case 8: total[j] += val; break; // P_DC
|
||||||
|
case 10: total[j] += val; break; // Q_AC
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
main.appendChild(ch0);
|
||||||
|
|
||||||
|
|
||||||
|
for(var i = 1; i < (iv["channels"] + 1); i++) {
|
||||||
|
var ch = div(["ch"]);
|
||||||
|
ch.appendChild(span(("" == iv["ch_names"][i]) ? ("CHANNEL " + i) : iv["ch_names"][i], ["head"]));
|
||||||
|
|
||||||
|
for(var j = 0; j < root.fld_names.length; j++) {
|
||||||
|
var val = Math.round(iv["ch"][i][j] * 100) / 100;
|
||||||
|
if(val > 0) {
|
||||||
|
ch.appendChild(span(val + " " + span(root["fld_units"][j], ["unit"]).innerHTML, ["value"]));
|
||||||
|
ch.appendChild(span(root["fld_names"][j], ["info"]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
main.appendChild(ch);
|
||||||
|
}
|
||||||
|
|
||||||
|
var ts = div(["ts"]);
|
||||||
|
var date = new Date(iv["ts_last_success"] * 1000);
|
||||||
|
ts.innerHTML = "Last received data requested at: " + date.toLocaleString('de-DE', {timeZone: 'UTC'});
|
||||||
|
main.appendChild(ts);
|
||||||
|
ivHtml.push(main);
|
||||||
|
}
|
||||||
|
|
||||||
|
// total
|
||||||
|
if(obj.length > 1) {
|
||||||
|
for(var j = 0; j < root.ch0_fld_names.length; j++) {
|
||||||
|
var val = total[j];
|
||||||
|
if(val > 0) {
|
||||||
|
var sub = div(["subgrp"]);
|
||||||
|
sub.appendChild(span(val + " " + span(root["ch0_fld_units"][j], ["unit"]).innerHTML, ["value"]));
|
||||||
|
sub.appendChild(span(root["ch0_fld_names"][j], ["info"]));
|
||||||
|
tDiv.appendChild(sub);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById("live").replaceChildren(...ivHtml);
|
||||||
|
}
|
||||||
|
|
||||||
|
function parse(obj) {
|
||||||
|
if(null != obj) {
|
||||||
|
parseSys(obj["system"]);
|
||||||
|
parseIv(obj["inverter"], obj);
|
||||||
|
document.getElementById("refresh").innerHTML = obj["refresh_interval"];
|
||||||
|
if(false == intervalSet) {
|
||||||
|
window.setInterval("getAjax('/api/live', parse)", obj["refresh_interval"] * 1000);
|
||||||
|
intervalSet = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
document.getElementById("refresh").innerHTML = "n/a";
|
||||||
|
}
|
||||||
|
|
||||||
|
getAjax("/api/live", parse);
|
||||||
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
#define F(sl) (sl)
|
#define F(sl) (sl)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
// available levels
|
// available levels
|
||||||
#define DBG_ERROR 1
|
#define DBG_ERROR 1
|
||||||
|
@ -21,7 +22,9 @@
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
// globally used level
|
// globally used level
|
||||||
|
#ifndef DEBUG_LEVEL
|
||||||
#define DEBUG_LEVEL DBG_INFO
|
#define DEBUG_LEVEL DBG_INFO
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef ARDUINO
|
#ifdef ARDUINO
|
||||||
#include "Arduino.h"
|
#include "Arduino.h"
|
||||||
|
@ -32,23 +35,40 @@
|
||||||
#define DBGPRINTLN(str)
|
#define DBGPRINTLN(str)
|
||||||
#else
|
#else
|
||||||
#ifdef ARDUINO
|
#ifdef ARDUINO
|
||||||
|
#define DBG_CB std::function<void(String)>
|
||||||
|
extern DBG_CB mCb;
|
||||||
|
|
||||||
|
inline void registerDebugCb(DBG_CB cb) {
|
||||||
|
mCb = cb;
|
||||||
|
}
|
||||||
|
|
||||||
#ifndef DSERIAL
|
#ifndef DSERIAL
|
||||||
#define DSERIAL Serial
|
#define DSERIAL Serial
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
template <class T>
|
//template <class T>
|
||||||
inline void DBGPRINT(T str) { DSERIAL.print(str); }
|
inline void DBGPRINT(String str) { DSERIAL.print(str); if(NULL != mCb) mCb(str); }
|
||||||
template <class T>
|
//template <class T>
|
||||||
inline void DBGPRINTLN(T str) { DBGPRINT(str); DBGPRINT(F("\r\n")); }
|
inline void DBGPRINTLN(String str) { DBGPRINT(str); DBGPRINT(F("\r\n")); }
|
||||||
inline void DHEX(uint8_t b) {
|
inline void DHEX(uint8_t b) {
|
||||||
if( b<0x10 ) DSERIAL.print('0');
|
if( b<0x10 ) DSERIAL.print(F("0"));
|
||||||
DSERIAL.print(b,HEX);
|
DSERIAL.print(b,HEX);
|
||||||
|
if(NULL != mCb) {
|
||||||
|
if( b<0x10 ) mCb(F("0"));
|
||||||
|
mCb(String(b, HEX));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
inline void DHEX(uint16_t b) {
|
inline void DHEX(uint16_t b) {
|
||||||
if( b<0x10 ) DSERIAL.print(F("000"));
|
if( b<0x10 ) DSERIAL.print(F("000"));
|
||||||
else if( b<0x100 ) DSERIAL.print(F("00"));
|
else if( b<0x100 ) DSERIAL.print(F("00"));
|
||||||
else if( b<0x1000 ) DSERIAL.print(F("0"));
|
else if( b<0x1000 ) DSERIAL.print(F("0"));
|
||||||
DSERIAL.print(b, HEX);
|
DSERIAL.print(b, HEX);
|
||||||
|
if(NULL != mCb) {
|
||||||
|
if( b<0x10 ) mCb(F("000"));
|
||||||
|
else if( b<0x100 ) mCb(F("00"));
|
||||||
|
else if( b<0x1000 ) mCb(F("0"));
|
||||||
|
mCb(String(b, HEX));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
inline void DHEX(uint32_t b) {
|
inline void DHEX(uint32_t b) {
|
||||||
if( b<0x10 ) DSERIAL.print(F("0000000"));
|
if( b<0x10 ) DSERIAL.print(F("0000000"));
|
||||||
|
@ -59,6 +79,16 @@
|
||||||
else if( b<0x1000000 ) DSERIAL.print(F("00"));
|
else if( b<0x1000000 ) DSERIAL.print(F("00"));
|
||||||
else if( b<0x10000000 ) DSERIAL.print(F("0"));
|
else if( b<0x10000000 ) DSERIAL.print(F("0"));
|
||||||
DSERIAL.print(b, HEX);
|
DSERIAL.print(b, HEX);
|
||||||
|
if(NULL != mCb) {
|
||||||
|
if( b<0x10 ) mCb(F("0000000"));
|
||||||
|
else if( b<0x100 ) mCb(F("000000"));
|
||||||
|
else if( b<0x1000 ) mCb(F("00000"));
|
||||||
|
else if( b<0x10000 ) mCb(F("0000"));
|
||||||
|
else if( b<0x100000 ) mCb(F("000"));
|
||||||
|
else if( b<0x1000000 ) mCb(F("00"));
|
||||||
|
else if( b<0x10000000 ) mCb(F("0"));
|
||||||
|
mCb(String(b, HEX));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -32,17 +32,16 @@ extra_scripts =
|
||||||
pre:html/convert.py
|
pre:html/convert.py
|
||||||
|
|
||||||
lib_deps =
|
lib_deps =
|
||||||
nrf24/RF24@1.4.5
|
https://github.com/yubox-node-org/ESPAsyncWebServer
|
||||||
paulstoffregen/Time@^1.6.1
|
nrf24/RF24
|
||||||
knolleary/PubSubClient@^2.8
|
paulstoffregen/Time
|
||||||
bblanchon/ArduinoJson@^6.19.4
|
knolleary/PubSubClient
|
||||||
;esp8266/DNSServer@1.1.0
|
bblanchon/ArduinoJson
|
||||||
;esp8266/EEPROM@^1.0
|
;esp8266/DNSServer
|
||||||
;esp8266/ESP8266HTTPUpdateServer@^1.0
|
;esp8266/EEPROM
|
||||||
;esp8266/ESP8266WebServer@^1.0
|
;esp8266/ESP8266WiFi
|
||||||
;esp8266/ESP8266WiFi@^1.0
|
;esp8266/SPI
|
||||||
;esp8266/SPI@1.0
|
;esp8266/Ticker
|
||||||
;esp8266/Ticker@^1.0
|
|
||||||
|
|
||||||
[env:esp8266-release]
|
[env:esp8266-release]
|
||||||
platform = espressif8266
|
platform = espressif8266
|
||||||
|
@ -58,7 +57,7 @@ monitor_filters =
|
||||||
platform = espressif8266
|
platform = espressif8266
|
||||||
board = esp12e
|
board = esp12e
|
||||||
board_build.f_cpu = 80000000L
|
board_build.f_cpu = 80000000L
|
||||||
build_flags = -DDEBUG_ESP_CORE -DDEBUG_ESP_WIFI -DDEBUG_ESP_HTTP_CLIENT -DDEBUG_ESP_HTTP_SERVER -DDEBUG_ESP_OOM -DDEBUG_ESP_PORT=Serial
|
build_flags = -DDEBUG_LEVEL=DBG_DEBUG -DDEBUG_ESP_CORE -DDEBUG_ESP_WIFI -DDEBUG_ESP_HTTP_CLIENT -DDEBUG_ESP_HTTP_SERVER -DDEBUG_ESP_OOM -DDEBUG_ESP_PORT=Serial
|
||||||
build_type = debug
|
build_type = debug
|
||||||
monitor_filters =
|
monitor_filters =
|
||||||
;default ; Remove typical terminal control codes from input
|
;default ; Remove typical terminal control codes from input
|
||||||
|
@ -68,7 +67,9 @@ monitor_filters =
|
||||||
[env:esp32-wroom32-release]
|
[env:esp32-wroom32-release]
|
||||||
platform = espressif32
|
platform = espressif32
|
||||||
board = lolin_d32
|
board = lolin_d32
|
||||||
build_flags = -D RELEASE
|
build_flags = -D RELEASE -std=gnu++14
|
||||||
|
build_unflags = -std=gnu++11
|
||||||
|
upload_port = /dev/cu.SLAB_USBtoUART
|
||||||
monitor_filters =
|
monitor_filters =
|
||||||
;default ; Remove typical terminal control codes from input
|
;default ; Remove typical terminal control codes from input
|
||||||
time ; Add timestamp with milliseconds for each new line
|
time ; Add timestamp with milliseconds for each new line
|
||||||
|
@ -77,8 +78,10 @@ monitor_filters =
|
||||||
[env:esp32-wroom32-debug]
|
[env:esp32-wroom32-debug]
|
||||||
platform = espressif32
|
platform = espressif32
|
||||||
board = lolin_d32
|
board = lolin_d32
|
||||||
build_flags = -DDEBUG_ESP_CORE -DDEBUG_ESP_WIFI -DDEBUG_ESP_HTTP_CLIENT -DDEBUG_ESP_HTTP_SERVER -DDEBUG_ESP_OOM -DDEBUG_ESP_PORT=Serial
|
build_flags = -DDEBUG_LEVEL=DBG_DEBUG -DDEBUG_ESP_CORE -DDEBUG_ESP_WIFI -DDEBUG_ESP_HTTP_CLIENT -DDEBUG_ESP_HTTP_SERVER -DDEBUG_ESP_OOM -DDEBUG_ESP_PORT=Serial -std=gnu++14
|
||||||
|
build_unflags = -std=gnu++11
|
||||||
build_type = debug
|
build_type = debug
|
||||||
|
upload_port = /dev/cu.SLAB_USBtoUART
|
||||||
monitor_filters =
|
monitor_filters =
|
||||||
;default ; Remove typical terminal control codes from input
|
;default ; Remove typical terminal control codes from input
|
||||||
time ; Add timestamp with milliseconds for each new line
|
time ; Add timestamp with milliseconds for each new line
|
||||||
|
|
|
@ -12,41 +12,31 @@
|
||||||
|
|
||||||
#include "html/h/index_html.h"
|
#include "html/h/index_html.h"
|
||||||
#include "html/h/style_css.h"
|
#include "html/h/style_css.h"
|
||||||
#include "favicon.h"
|
#include "html/h/api_js.h"
|
||||||
|
#include "html/h/favicon_ico_gz.h"
|
||||||
#include "html/h/setup_html.h"
|
#include "html/h/setup_html.h"
|
||||||
#include "html/h/visualization_html.h"
|
#include "html/h/visualization_html.h"
|
||||||
|
#include "html/h/update_html.h"
|
||||||
|
#include "html/h/serial_html.h"
|
||||||
|
|
||||||
|
const char* const pinArgNames[] = {"pinCs", "pinCe", "pinIrq"};
|
||||||
const uint16_t pwrLimitOptionValues[] {
|
|
||||||
NoPowerLimit,
|
|
||||||
AbsolutNonPersistent,
|
|
||||||
AbsolutPersistent,
|
|
||||||
RelativNonPersistent,
|
|
||||||
RelativPersistent
|
|
||||||
};
|
|
||||||
|
|
||||||
const char* const pwrLimitOptions[] {
|
|
||||||
"no power limit",
|
|
||||||
"absolute in Watt non persistent",
|
|
||||||
"absolute in Watt persistent",
|
|
||||||
"relativ in percent non persistent",
|
|
||||||
"relativ in percent persistent"
|
|
||||||
};
|
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
web::web(app *main, sysConfig_t *sysCfg, config_t *config, char version[]) {
|
web::web(app *main, sysConfig_t *sysCfg, config_t *config, statistics_t *stat, char version[]) {
|
||||||
mMain = main;
|
mMain = main;
|
||||||
mSysCfg = sysCfg;
|
mSysCfg = sysCfg;
|
||||||
mConfig = config;
|
mConfig = config;
|
||||||
|
mStat = stat;
|
||||||
mVersion = version;
|
mVersion = version;
|
||||||
#ifdef ESP8266
|
mWeb = new AsyncWebServer(80);
|
||||||
mWeb = new ESP8266WebServer(80);
|
mEvts = new AsyncEventSource("/events");
|
||||||
mUpdater = new ESP8266HTTPUpdateServer();
|
mApi = new webApi(mWeb, main, sysCfg, config, stat, version);
|
||||||
#elif defined(ESP32)
|
|
||||||
mWeb = new WebServer(80);
|
memset(mSerialBuf, 0, WEB_SERIAL_BUF_SIZE);
|
||||||
mUpdater = new HTTPUpdateServer();
|
mSerialBufFill = 0;
|
||||||
#endif
|
mWebSerialTicker = 0;
|
||||||
mUpdater->setup(mWeb);
|
mWebSerialInterval = 1000; // [ms]
|
||||||
|
mSerialAddTime = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -55,107 +45,141 @@ void web::setup(void) {
|
||||||
DPRINTLN(DBG_VERBOSE, F("app::setup-begin"));
|
DPRINTLN(DBG_VERBOSE, F("app::setup-begin"));
|
||||||
mWeb->begin();
|
mWeb->begin();
|
||||||
DPRINTLN(DBG_VERBOSE, F("app::setup-on"));
|
DPRINTLN(DBG_VERBOSE, F("app::setup-on"));
|
||||||
mWeb->on("/", std::bind(&web::showIndex, this));
|
mWeb->on("/", HTTP_GET, std::bind(&web::onIndex, this, std::placeholders::_1));
|
||||||
mWeb->on("/style.css", std::bind(&web::showCss, this));
|
mWeb->on("/style.css", HTTP_GET, std::bind(&web::onCss, this, std::placeholders::_1));
|
||||||
mWeb->on("/favicon.ico", std::bind(&web::showFavicon, this));
|
mWeb->on("/api.js", HTTP_GET, std::bind(&web::onApiJs, this, std::placeholders::_1));
|
||||||
mWeb->onNotFound ( std::bind(&web::showNotFound, this));
|
mWeb->on("/favicon.ico", HTTP_GET, std::bind(&web::onFavicon, this, std::placeholders::_1));
|
||||||
mWeb->on("/uptime", std::bind(&web::showUptime, this));
|
mWeb->onNotFound ( std::bind(&web::showNotFound, this, std::placeholders::_1));
|
||||||
mWeb->on("/reboot", std::bind(&web::showReboot, this));
|
mWeb->on("/reboot", HTTP_ANY, std::bind(&web::onReboot, this, std::placeholders::_1));
|
||||||
mWeb->on("/erase", std::bind(&web::showErase, this));
|
mWeb->on("/erase", HTTP_ANY, std::bind(&web::showErase, this, std::placeholders::_1));
|
||||||
mWeb->on("/factory", std::bind(&web::showFactoryRst, this));
|
mWeb->on("/factory", HTTP_ANY, std::bind(&web::showFactoryRst, this, std::placeholders::_1));
|
||||||
|
|
||||||
mWeb->on("/setup", std::bind(&web::showSetup, this));
|
mWeb->on("/setup", HTTP_GET, std::bind(&web::onSetup, this, std::placeholders::_1));
|
||||||
mWeb->on("/save", std::bind(&web::showSave, this));
|
mWeb->on("/save", HTTP_ANY, std::bind(&web::showSave, this, std::placeholders::_1));
|
||||||
|
|
||||||
mWeb->on("/cmdstat", std::bind(&web::showStatistics, this));
|
mWeb->on("/live", HTTP_ANY, std::bind(&web::onLive, this, std::placeholders::_1));
|
||||||
mWeb->on("/visualization", std::bind(&web::showVisualization, this));
|
mWeb->on("/api1", HTTP_POST, std::bind(&web::showWebApi, this, std::placeholders::_1));
|
||||||
mWeb->on("/livedata", std::bind(&web::showLiveData, this));
|
|
||||||
mWeb->on("/json", std::bind(&web::showJson, this));
|
|
||||||
mWeb->on("/api", HTTP_POST, std::bind(&web::showWebApi, this));
|
mWeb->on("/update", HTTP_GET, std::bind(&web::onUpdate, this, std::placeholders::_1));
|
||||||
|
mWeb->on("/update", HTTP_POST, std::bind(&web::showUpdate, this, std::placeholders::_1),
|
||||||
|
std::bind(&web::showUpdate2, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5, std::placeholders::_6));
|
||||||
|
mWeb->on("/serial", HTTP_GET, std::bind(&web::onSerial, this, std::placeholders::_1));
|
||||||
|
|
||||||
|
|
||||||
|
mEvts->onConnect(std::bind(&web::onConnect, this, std::placeholders::_1));
|
||||||
|
mWeb->addHandler(mEvts);
|
||||||
|
|
||||||
|
mApi->setup();
|
||||||
|
|
||||||
|
registerDebugCb(std::bind(&web::serialCb, this, std::placeholders::_1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
void web::loop(void) {
|
void web::loop(void) {
|
||||||
mWeb->handleClient();
|
mApi->loop();
|
||||||
|
|
||||||
|
if(mMain->checkTicker(&mWebSerialTicker, mWebSerialInterval)) {
|
||||||
|
if(mSerialBufFill > 0) {
|
||||||
|
mEvts->send(mSerialBuf, "serial", millis());
|
||||||
|
memset(mSerialBuf, 0, WEB_SERIAL_BUF_SIZE);
|
||||||
|
mSerialBufFill = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
void web::showIndex(void) {
|
void web::onConnect(AsyncEventSourceClient *client) {
|
||||||
DPRINTLN(DBG_VERBOSE, F("showIndex"));
|
DPRINTLN(DBG_VERBOSE, "onConnect");
|
||||||
String html = FPSTR(index_html);
|
|
||||||
html.replace(F("{DEVICE}"), mSysCfg->deviceName);
|
if(client->lastId())
|
||||||
html.replace(F("{VERSION}"), mVersion);
|
DPRINTLN(DBG_VERBOSE, "Client reconnected! Last message ID that it got is: " + String(client->lastId()));
|
||||||
html.replace(F("{TS}"), String(mConfig->sendInterval) + " ");
|
|
||||||
html.replace(F("{JS_TS}"), String(mConfig->sendInterval * 1000));
|
client->send("hello!", NULL, millis(), 1000);
|
||||||
html.replace(F("{BUILD}"), String(AUTO_GIT_HASH));
|
|
||||||
mWeb->send(200, "text/html", html);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
void web::showCss(void) {
|
void web::onIndex(AsyncWebServerRequest *request) {
|
||||||
mWeb->send(200, "text/css", FPSTR(style_css));
|
DPRINTLN(DBG_VERBOSE, F("onIndex"));
|
||||||
|
|
||||||
|
AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html"), index_html, index_html_len);
|
||||||
|
response->addHeader(F("Content-Encoding"), "gzip");
|
||||||
|
request->send(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
void web::showFavicon(void) {
|
void web::onCss(AsyncWebServerRequest *request) {
|
||||||
|
AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/css"), style_css, style_css_len);
|
||||||
|
response->addHeader(F("Content-Encoding"), "gzip");
|
||||||
|
request->send(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
void web::onApiJs(AsyncWebServerRequest *request) {
|
||||||
|
DPRINTLN(DBG_VERBOSE, F("onApiJs"));
|
||||||
|
|
||||||
|
AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/javascript"), api_js, api_js_len);
|
||||||
|
response->addHeader(F("Content-Encoding"), "gzip");
|
||||||
|
request->send(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
void web::onFavicon(AsyncWebServerRequest *request) {
|
||||||
static const char favicon_type[] PROGMEM = "image/x-icon";
|
static const char favicon_type[] PROGMEM = "image/x-icon";
|
||||||
static const char favicon_content[] PROGMEM = FAVICON_PANEL_16;
|
AsyncWebServerResponse *response = request->beginResponse_P(200, favicon_type, favicon_ico_gz, favicon_ico_gz_len);
|
||||||
mWeb->send_P(200, favicon_type, favicon_content, sizeof(favicon_content));
|
response->addHeader(F("Content-Encoding"), "gzip");
|
||||||
|
request->send(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
void web::showNotFound(void) {
|
void web::showNotFound(AsyncWebServerRequest *request) {
|
||||||
DPRINTLN(DBG_VERBOSE, F("showNotFound - ") + mWeb->uri());
|
DPRINTLN(DBG_VERBOSE, F("showNotFound - ") + request->url());
|
||||||
String msg = F("File Not Found\n\nURI: ");
|
String msg = F("File Not Found\n\nURL: ");
|
||||||
msg += mWeb->uri();
|
msg += request->url();
|
||||||
mWeb->send(404, F("text/plain"), msg);
|
msg += F("\nMethod: ");
|
||||||
|
msg += ( request->method() == HTTP_GET ) ? "GET" : "POST";
|
||||||
|
msg += F("\nArguments: ");
|
||||||
|
msg += request->args();
|
||||||
|
msg += "\n";
|
||||||
|
|
||||||
|
for(uint8_t i = 0; i < request->args(); i++ ) {
|
||||||
|
msg += " " + request->argName(i) + ": " + request->arg(i) + "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
request->send(404, F("text/plain"), msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
void web::showUptime(void) {
|
void web::onReboot(AsyncWebServerRequest *request) {
|
||||||
char time[21] = {0};
|
request->send(200, F("text/html"), F("<!doctype html><html><head><title>Rebooting ...</title><meta http-equiv=\"refresh\" content=\"10; URL=/\"></head><body>rebooting ... auto reload after 10s</body></html>"));
|
||||||
uint32_t uptime = mMain->getUptime();
|
mMain->mShouldReboot = true;
|
||||||
|
|
||||||
uint32_t upTimeSc = uint32_t((uptime) % 60);
|
|
||||||
uint32_t upTimeMn = uint32_t((uptime / (60)) % 60);
|
|
||||||
uint32_t upTimeHr = uint32_t((uptime / (60 * 60)) % 24);
|
|
||||||
uint32_t upTimeDy = uint32_t((uptime / (60 * 60 * 24)) % 365);
|
|
||||||
|
|
||||||
snprintf(time, 20, "%d Days, %02d:%02d:%02d", upTimeDy, upTimeHr, upTimeMn, upTimeSc);
|
|
||||||
|
|
||||||
mWeb->send(200, "text/plain", String(time) + "; now: " + mMain->getDateTimeStr(mMain->getTimestamp()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
void web::showReboot(void) {
|
void web::showErase(AsyncWebServerRequest *request) {
|
||||||
mWeb->send(200, F("text/html"), F("<!doctype html><html><head><title>Rebooting ...</title><meta http-equiv=\"refresh\" content=\"10; URL=/\"></head><body>rebooting ... auto reload after 10s</body></html>"));
|
|
||||||
delay(1000);
|
|
||||||
ESP.restart();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
|
||||||
void web::showErase() {
|
|
||||||
DPRINTLN(DBG_VERBOSE, F("showErase"));
|
DPRINTLN(DBG_VERBOSE, F("showErase"));
|
||||||
mMain->eraseSettings();
|
mMain->eraseSettings();
|
||||||
showReboot();
|
onReboot(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
void web::showFactoryRst(void) {
|
void web::showFactoryRst(AsyncWebServerRequest *request) {
|
||||||
DPRINTLN(DBG_VERBOSE, F("showFactoryRst"));
|
DPRINTLN(DBG_VERBOSE, F("showFactoryRst"));
|
||||||
String content = "";
|
String content = "";
|
||||||
int refresh = 3;
|
int refresh = 3;
|
||||||
if(mWeb->args() > 0) {
|
if(request->args() > 0) {
|
||||||
if(mWeb->arg("reset").toInt() == 1) {
|
if(request->arg("reset").toInt() == 1) {
|
||||||
mMain->eraseSettings(true);
|
mMain->eraseSettings(true);
|
||||||
content = F("factory reset: success\n\nrebooting ... ");
|
content = F("factory reset: success\n\nrebooting ... ");
|
||||||
refresh = 10;
|
refresh = 10;
|
||||||
|
@ -170,7 +194,7 @@ void web::showFactoryRst(void) {
|
||||||
"<p><a href=\"/factory?reset=1\">RESET</a><br/><br/><a href=\"/factory?reset=0\">CANCEL</a><br/></p>");
|
"<p><a href=\"/factory?reset=1\">RESET</a><br/><br/><a href=\"/factory?reset=0\">CANCEL</a><br/></p>");
|
||||||
refresh = 120;
|
refresh = 120;
|
||||||
}
|
}
|
||||||
mWeb->send(200, F("text/html"), F("<!doctype html><html><head><title>Factory Reset</title><meta http-equiv=\"refresh\" content=\"") + String(refresh) + F("; URL=/\"></head><body>") + content + F("</body></html>"));
|
request->send(200, F("text/html"), F("<!doctype html><html><head><title>Factory Reset</title><meta http-equiv=\"refresh\" content=\"") + String(refresh) + F("; URL=/\"></head><body>") + content + F("</body></html>"));
|
||||||
if(refresh == 10) {
|
if(refresh == 10) {
|
||||||
delay(1000);
|
delay(1000);
|
||||||
ESP.restart();
|
ESP.restart();
|
||||||
|
@ -179,193 +203,59 @@ void web::showFactoryRst(void) {
|
||||||
|
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
void web::showSetup(void) {
|
void web::onSetup(AsyncWebServerRequest *request) {
|
||||||
DPRINTLN(DBG_VERBOSE, F("showSetup"));
|
DPRINTLN(DBG_VERBOSE, F("onSetup"));
|
||||||
String html = FPSTR(setup_html);
|
|
||||||
html.replace(F("{SSID}"), mSysCfg->stationSsid);
|
|
||||||
// PWD will be left at the default value (for protection)
|
|
||||||
// -> the PWD will only be changed if it does not match the default "{PWD}"
|
|
||||||
html.replace(F("{DEVICE}"), String(mSysCfg->deviceName));
|
|
||||||
html.replace(F("{VERSION}"), String(mVersion));
|
|
||||||
if(mMain->getWifiApActive())
|
|
||||||
html.replace("{IP}", String(F("http://192.168.1.1")));
|
|
||||||
else
|
|
||||||
html.replace("{IP}", (F("http://") + String(WiFi.localIP().toString())));
|
|
||||||
|
|
||||||
String inv = "";
|
AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html"), setup_html, setup_html_len);
|
||||||
Inverter<> *iv;
|
response->addHeader(F("Content-Encoding"), "gzip");
|
||||||
for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i ++) {
|
request->send(response);
|
||||||
iv = mMain->mSys->getInverterByPos(i);
|
|
||||||
|
|
||||||
inv += F("<p class=\"subdes\">Inverter ") + String(i) + "</p>";
|
|
||||||
inv += F("<label for=\"inv") + String(i) + F("Addr\">Address*</label>");
|
|
||||||
inv += F("<input type=\"text\" class=\"text\" name=\"inv") + String(i) + F("Addr\" value=\"");
|
|
||||||
if(NULL != iv)
|
|
||||||
inv += String(iv->serial.u64, HEX);
|
|
||||||
inv += F("\"/ maxlength=\"12\">");
|
|
||||||
|
|
||||||
inv += F("<label for=\"inv") + String(i) + F("Name\">Name*</label>");
|
|
||||||
inv += F("<input type=\"text\" class=\"text\" name=\"inv") + String(i) + F("Name\" value=\"");
|
|
||||||
if(NULL != iv)
|
|
||||||
inv += String(iv->name);
|
|
||||||
inv += F("\"/ maxlength=\"") + String(MAX_NAME_LENGTH) + "\">";
|
|
||||||
|
|
||||||
inv += F("<label for=\"inv") + String(i) + F("ActivePowerLimit\">Active Power Limit</label>");
|
|
||||||
inv += F("<input type=\"text\" class=\"text\" name=\"inv") + String(i) + F("ActivePowerLimit\" value=\"");
|
|
||||||
if(NULL != iv)
|
|
||||||
inv += String(iv->powerLimit[0]);
|
|
||||||
inv += F("\"/ maxlength=\"") + String(6) + "\">";
|
|
||||||
|
|
||||||
inv += F("<label for=\"inv") + String(i) + F("ActivePowerLimitConType\">Active Power Limit Control Type</label>");
|
|
||||||
inv += F("<select name=\"inv") + String(i) + F("PowerLimitControl\">");
|
|
||||||
for(uint8_t j = 0; j < 5; j++) {
|
|
||||||
inv += F("<option value=\"") + String(pwrLimitOptionValues[j]) + F("\"");
|
|
||||||
if(NULL != iv) {
|
|
||||||
if(iv->powerLimit[1] == pwrLimitOptionValues[j])
|
|
||||||
inv += F(" selected");
|
|
||||||
}
|
|
||||||
inv += F(">") + String(pwrLimitOptions[j]) + F("</option>");
|
|
||||||
}
|
|
||||||
inv += F("</select>");
|
|
||||||
|
|
||||||
inv += F("<label for=\"inv") + String(i) + F("ModPwr0\" name=\"lbl") + String(i);
|
|
||||||
inv += F("ModPwr\">Max Module Power (Wp)</label><div class=\"modpwr\">");
|
|
||||||
for(uint8_t j = 0; j < 4; j++) {
|
|
||||||
inv += F("<input type=\"text\" class=\"text sh\" name=\"inv") + String(i) + F("ModPwr") + String(j) + F("\" value=\"");
|
|
||||||
if(NULL != iv)
|
|
||||||
inv += String(iv->chMaxPwr[j]);
|
|
||||||
inv += F("\"/ maxlength=\"4\">");
|
|
||||||
}
|
|
||||||
inv += F("</div><br/><label for=\"inv") + String(i) + F("ModName0\" name=\"lbl") + String(i);
|
|
||||||
inv += F("ModName\">Module Name</label><div class=\"modname\">");
|
|
||||||
for(uint8_t j = 0; j < 4; j++) {
|
|
||||||
inv += F("<input type=\"text\" class=\"text sh\" name=\"inv") + String(i) + F("ModName") + String(j) + F("\" value=\"");
|
|
||||||
if(NULL != iv)
|
|
||||||
inv += String(iv->chName[j]);
|
|
||||||
inv += F("\"/ maxlength=\"") + String(MAX_NAME_LENGTH) + "\">";
|
|
||||||
}
|
|
||||||
inv += F("</div>");
|
|
||||||
}
|
|
||||||
html.replace(F("{INVERTERS}"), String(inv));
|
|
||||||
|
|
||||||
|
|
||||||
// pinout
|
|
||||||
String pinout;
|
|
||||||
for(uint8_t i = 0; i < 3; i++) {
|
|
||||||
pinout += F("<label for=\"") + String(pinArgNames[i]) + "\">" + String(pinNames[i]) + F("</label>");
|
|
||||||
pinout += F("<select name=\"") + String(pinArgNames[i]) + "\">";
|
|
||||||
for(uint8_t j = 0; j <= 16; j++) {
|
|
||||||
pinout += F("<option value=\"") + String(j) + "\"";
|
|
||||||
switch(i) {
|
|
||||||
default: if(j == mConfig->pinCs) pinout += F(" selected"); break;
|
|
||||||
case 1: if(j == mConfig->pinCe) pinout += F(" selected"); break;
|
|
||||||
case 2: if(j == mConfig->pinIrq) pinout += F(" selected"); break;
|
|
||||||
}
|
|
||||||
pinout += ">" + String(wemosPins[j]) + F("</option>");
|
|
||||||
}
|
|
||||||
pinout += F("</select>");
|
|
||||||
}
|
|
||||||
html.replace(F("{PINOUT}"), String(pinout));
|
|
||||||
|
|
||||||
|
|
||||||
// nrf24l01+
|
|
||||||
String rf24;
|
|
||||||
for(uint8_t i = 0; i <= 3; i++) {
|
|
||||||
rf24 += F("<option value=\"") + String(i) + "\"";
|
|
||||||
if(i == mConfig->amplifierPower)
|
|
||||||
rf24 += F(" selected");
|
|
||||||
rf24 += ">" + String(rf24AmpPowerNames[i]) + F("</option>");
|
|
||||||
}
|
|
||||||
html.replace(F("{RF24}"), String(rf24));
|
|
||||||
|
|
||||||
|
|
||||||
html.replace(F("{INV_INTVL}"), String(mConfig->sendInterval));
|
|
||||||
html.replace(F("{INV_RETRIES}"), String(mConfig->maxRetransPerPyld));
|
|
||||||
|
|
||||||
html.replace(F("{SER_INTVL}"), String(mConfig->serialInterval));
|
|
||||||
html.replace(F("{SER_VAL_CB}"), (mConfig->serialShowIv) ? "checked" : "");
|
|
||||||
html.replace(F("{SER_DBG_CB}"), (mConfig->serialDebug) ? "checked" : "");
|
|
||||||
|
|
||||||
html.replace(F("{NTP_ADDR}"), String(mConfig->ntpAddr));
|
|
||||||
html.replace(F("{NTP_PORT}"), String(mConfig->ntpPort));
|
|
||||||
|
|
||||||
html.replace(F("{MQTT_ADDR}"), String(mConfig->mqtt.broker));
|
|
||||||
html.replace(F("{MQTT_PORT}"), String(mConfig->mqtt.port));
|
|
||||||
html.replace(F("{MQTT_USER}"), String(mConfig->mqtt.user));
|
|
||||||
html.replace(F("{MQTT_PWD}"), String(mConfig->mqtt.pwd));
|
|
||||||
html.replace(F("{MQTT_TOPIC}"), String(mConfig->mqtt.topic));
|
|
||||||
|
|
||||||
mWeb->send(200, F("text/html"), html);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
void web::showSave(void) {
|
void web::showSave(AsyncWebServerRequest *request) {
|
||||||
DPRINTLN(DBG_VERBOSE, F("showSave"));
|
DPRINTLN(DBG_VERBOSE, F("showSave"));
|
||||||
|
|
||||||
if(mWeb->args() > 0) {
|
if(request->args() > 0) {
|
||||||
char buf[20] = {0};
|
char buf[20] = {0};
|
||||||
|
|
||||||
// general
|
// general
|
||||||
if(mWeb->arg("ssid") != "")
|
if(request->arg("ssid") != "")
|
||||||
mWeb->arg("ssid").toCharArray(mSysCfg->stationSsid, SSID_LEN);
|
request->arg("ssid").toCharArray(mSysCfg->stationSsid, SSID_LEN);
|
||||||
if(mWeb->arg("pwd") != "{PWD}")
|
if(request->arg("pwd") != "{PWD}")
|
||||||
mWeb->arg("pwd").toCharArray(mSysCfg->stationPwd, PWD_LEN);
|
request->arg("pwd").toCharArray(mSysCfg->stationPwd, PWD_LEN);
|
||||||
if(mWeb->arg("device") != "")
|
if(request->arg("device") != "")
|
||||||
mWeb->arg("device").toCharArray(mSysCfg->deviceName, DEVNAME_LEN);
|
request->arg("device").toCharArray(mSysCfg->deviceName, DEVNAME_LEN);
|
||||||
|
|
||||||
// inverter
|
// inverter
|
||||||
Inverter<> *iv;
|
Inverter<> *iv;
|
||||||
for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i ++) {
|
for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i ++) {
|
||||||
iv = mMain->mSys->getInverterByPos(i, false);
|
iv = mMain->mSys->getInverterByPos(i, false);
|
||||||
// address
|
// address
|
||||||
mWeb->arg("inv" + String(i) + "Addr").toCharArray(buf, 20);
|
request->arg("inv" + String(i) + "Addr").toCharArray(buf, 20);
|
||||||
if(strlen(buf) == 0)
|
if(strlen(buf) == 0)
|
||||||
memset(buf, 0, 20);
|
memset(buf, 0, 20);
|
||||||
iv->serial.u64 = mMain->Serial2u64(buf);
|
iv->serial.u64 = mMain->Serial2u64(buf);
|
||||||
|
|
||||||
// active power limit
|
|
||||||
uint16_t actPwrLimit = mWeb->arg("inv" + String(i) + "ActivePowerLimit").toInt();
|
|
||||||
uint16_t actPwrLimitControl = mWeb->arg("inv" + String(i) + "PowerLimitControl").toInt();
|
|
||||||
if (actPwrLimit != 0xffff && actPwrLimit > 0){
|
|
||||||
iv->powerLimit[0] = actPwrLimit;
|
|
||||||
iv->powerLimit[1] = actPwrLimitControl;
|
|
||||||
iv->devControlCmd = ActivePowerContr;
|
|
||||||
iv->devControlRequest = true;
|
|
||||||
if ((iv->powerLimit[1] & 0x0001) == 0x0001)
|
|
||||||
DPRINTLN(DBG_INFO, F("Power limit for inverter ") + String(iv->id) + F(" set to ") + String(iv->powerLimit[0]) + F("%") );
|
|
||||||
else {
|
|
||||||
DPRINTLN(DBG_INFO, F("Power limit for inverter ") + String(iv->id) + F(" set to ") + String(iv->powerLimit[0]) + F("W") );
|
|
||||||
DPRINTLN(DBG_INFO, F("Power Limit Control Setting ") + String(iv->powerLimit[1]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (actPwrLimit == 0xffff) { // set to 100%
|
|
||||||
iv->powerLimit[0] = 100;
|
|
||||||
iv->powerLimit[1] = RelativPersistent;
|
|
||||||
iv->devControlCmd = ActivePowerContr;
|
|
||||||
iv->devControlRequest = true;
|
|
||||||
DPRINTLN(DBG_INFO, F("Power limit for inverter ") + String(iv->id) + F(" set to unlimted"));
|
|
||||||
}
|
|
||||||
|
|
||||||
// name
|
// name
|
||||||
mWeb->arg("inv" + String(i) + "Name").toCharArray(iv->name, MAX_NAME_LENGTH);
|
request->arg("inv" + String(i) + "Name").toCharArray(iv->name, MAX_NAME_LENGTH);
|
||||||
|
|
||||||
// max channel power / name
|
// max channel power / name
|
||||||
for(uint8_t j = 0; j < 4; j++) {
|
for(uint8_t j = 0; j < 4; j++) {
|
||||||
iv->chMaxPwr[j] = mWeb->arg("inv" + String(i) + "ModPwr" + String(j)).toInt() & 0xffff;
|
iv->chMaxPwr[j] = request->arg("inv" + String(i) + "ModPwr" + String(j)).toInt() & 0xffff;
|
||||||
mWeb->arg("inv" + String(i) + "ModName" + String(j)).toCharArray(iv->chName[j], MAX_NAME_LENGTH);
|
request->arg("inv" + String(i) + "ModName" + String(j)).toCharArray(iv->chName[j], MAX_NAME_LENGTH);
|
||||||
}
|
}
|
||||||
iv->initialized = true;
|
iv->initialized = true;
|
||||||
}
|
}
|
||||||
if(mWeb->arg("invInterval") != "")
|
if(request->arg("invInterval") != "")
|
||||||
mConfig->sendInterval = mWeb->arg("invInterval").toInt();
|
mConfig->sendInterval = request->arg("invInterval").toInt();
|
||||||
if(mWeb->arg("invRetry") != "")
|
if(request->arg("invRetry") != "")
|
||||||
mConfig->maxRetransPerPyld = mWeb->arg("invRetry").toInt();
|
mConfig->maxRetransPerPyld = request->arg("invRetry").toInt();
|
||||||
|
|
||||||
// pinout
|
// pinout
|
||||||
uint8_t pin;
|
uint8_t pin;
|
||||||
for(uint8_t i = 0; i < 3; i ++) {
|
for(uint8_t i = 0; i < 3; i ++) {
|
||||||
pin = mWeb->arg(String(pinArgNames[i])).toInt();
|
pin = request->arg(String(pinArgNames[i])).toInt();
|
||||||
switch(i) {
|
switch(i) {
|
||||||
default: mConfig->pinCs = pin; break;
|
default: mConfig->pinCs = pin; break;
|
||||||
case 1: mConfig->pinCe = pin; break;
|
case 1: mConfig->pinCe = pin; break;
|
||||||
|
@ -374,218 +264,72 @@ void web::showSave(void) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// nrf24 amplifier power
|
// nrf24 amplifier power
|
||||||
mConfig->amplifierPower = mWeb->arg("rf24Power").toInt() & 0x03;
|
mConfig->amplifierPower = request->arg("rf24Power").toInt() & 0x03;
|
||||||
|
|
||||||
// ntp
|
// ntp
|
||||||
if(mWeb->arg("ntpAddr") != "") {
|
if(request->arg("ntpAddr") != "") {
|
||||||
mWeb->arg("ntpAddr").toCharArray(mConfig->ntpAddr, NTP_ADDR_LEN);
|
request->arg("ntpAddr").toCharArray(mConfig->ntpAddr, NTP_ADDR_LEN);
|
||||||
mConfig->ntpPort = mWeb->arg("ntpPort").toInt() & 0xffff;
|
mConfig->ntpPort = request->arg("ntpPort").toInt() & 0xffff;
|
||||||
}
|
}
|
||||||
|
|
||||||
// mqtt
|
// mqtt
|
||||||
if(mWeb->arg("mqttAddr") != "") {
|
if(request->arg("mqttAddr") != "") {
|
||||||
String addr = mWeb->arg("mqttAddr");
|
String addr = request->arg("mqttAddr");
|
||||||
addr.trim();
|
addr.trim();
|
||||||
addr.toCharArray(mConfig->mqtt.broker, MQTT_ADDR_LEN);
|
addr.toCharArray(mConfig->mqtt.broker, MQTT_ADDR_LEN);
|
||||||
mWeb->arg("mqttUser").toCharArray(mConfig->mqtt.user, MQTT_USER_LEN);
|
request->arg("mqttUser").toCharArray(mConfig->mqtt.user, MQTT_USER_LEN);
|
||||||
mWeb->arg("mqttPwd").toCharArray(mConfig->mqtt.pwd, MQTT_PWD_LEN);
|
if(request->arg("mqttPwd") != "{PWD}")
|
||||||
mWeb->arg("mqttTopic").toCharArray(mConfig->mqtt.topic, MQTT_TOPIC_LEN);
|
request->arg("mqttPwd").toCharArray(mConfig->mqtt.pwd, MQTT_PWD_LEN);
|
||||||
mConfig->mqtt.port = mWeb->arg("mqttPort").toInt();
|
request->arg("mqttTopic").toCharArray(mConfig->mqtt.topic, MQTT_TOPIC_LEN);
|
||||||
|
mConfig->mqtt.port = request->arg("mqttPort").toInt();
|
||||||
}
|
}
|
||||||
|
|
||||||
// serial console
|
// serial console
|
||||||
if(mWeb->arg("serIntvl") != "") {
|
if(request->arg("serIntvl") != "") {
|
||||||
mConfig->serialInterval = mWeb->arg("serIntvl").toInt() & 0xffff;
|
mConfig->serialInterval = request->arg("serIntvl").toInt() & 0xffff;
|
||||||
|
|
||||||
mConfig->serialDebug = (mWeb->arg("serDbg") == "on");
|
mConfig->serialDebug = (request->arg("serDbg") == "on");
|
||||||
mConfig->serialShowIv = (mWeb->arg("serEn") == "on");
|
mConfig->serialShowIv = (request->arg("serEn") == "on");
|
||||||
// Needed to log TX buffers to serial console
|
// Needed to log TX buffers to serial console
|
||||||
mMain->mSys->Radio.mSerialDebug = mConfig->serialDebug;
|
mMain->mSys->Radio.mSerialDebug = mConfig->serialDebug;
|
||||||
}
|
}
|
||||||
|
|
||||||
mMain->saveValues();
|
mMain->saveValues();
|
||||||
|
|
||||||
if(mWeb->arg("reboot") == "on")
|
if(request->arg("reboot") == "on")
|
||||||
showReboot();
|
onReboot(request);
|
||||||
else
|
else
|
||||||
mWeb->send(200, F("text/html"), F("<!doctype html><html><head><title>Setup saved</title><meta http-equiv=\"refresh\" content=\"0; URL=/setup\"></head><body>"
|
request->send(200, F("text/html"), F("<!doctype html><html><head><title>Setup saved</title><meta http-equiv=\"refresh\" content=\"0; URL=/setup\"></head><body>"
|
||||||
"<p>saved</p></body></html>"));
|
"<p>saved</p></body></html>"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
void web::showStatistics(void) {
|
void web::onLive(AsyncWebServerRequest *request) {
|
||||||
DPRINTLN(DBG_VERBOSE, F("web::showStatistics"));
|
DPRINTLN(DBG_VERBOSE, F("onLive"));
|
||||||
mWeb->send(200, F("text/plain"), mMain->getStatistics());
|
|
||||||
|
AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html"), visualization_html, visualization_html_len);
|
||||||
|
response->addHeader(F("Content-Encoding"), "gzip");
|
||||||
|
request->send(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
void web::showVisualization(void) {
|
void web::showWebApi(AsyncWebServerRequest *request) {
|
||||||
DPRINTLN(DBG_VERBOSE, F("web::showVisualization"));
|
|
||||||
String html = FPSTR(visualization_html);
|
|
||||||
html.replace(F("{DEVICE}"), mSysCfg->deviceName);
|
|
||||||
html.replace(F("{VERSION}"), mVersion);
|
|
||||||
html.replace(F("{TS}"), String(mConfig->sendInterval) + " ");
|
|
||||||
html.replace(F("{JS_TS}"), String(mConfig->sendInterval * 1000));
|
|
||||||
mWeb->send(200, F("text/html"), html);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
|
||||||
void web::showLiveData(void) {
|
|
||||||
DPRINTLN(DBG_VERBOSE, F("web::showLiveData"));
|
|
||||||
|
|
||||||
String modHtml, totalModHtml;
|
|
||||||
float totalYield = 0, totalYieldToday = 0, totalActual = 0;
|
|
||||||
uint8_t count = 0;
|
|
||||||
|
|
||||||
for (uint8_t id = 0; id < mMain->mSys->getNumInverters(); id++) {
|
|
||||||
count++;
|
|
||||||
|
|
||||||
Inverter<> *iv = mMain->mSys->getInverterByPos(id);
|
|
||||||
if (NULL != iv) {
|
|
||||||
#ifdef LIVEDATA_VISUALIZED
|
|
||||||
uint8_t modNum, pos;
|
|
||||||
switch (iv->type) {
|
|
||||||
default:
|
|
||||||
case INV_TYPE_1CH: modNum = 1; break;
|
|
||||||
case INV_TYPE_2CH: modNum = 2; break;
|
|
||||||
case INV_TYPE_4CH: modNum = 4; break;
|
|
||||||
}
|
|
||||||
|
|
||||||
modHtml += F("<div class=\"iv\">"
|
|
||||||
"<div class=\"ch-iv\"><span class=\"head\">")
|
|
||||||
+ String(iv->name) + F(" Limit ")
|
|
||||||
+ String(iv->actPowerLimit) + F("%");
|
|
||||||
if(NoPowerLimit == iv->powerLimit[1])
|
|
||||||
modHtml += F(" (not controlled)");
|
|
||||||
modHtml += F(" | last Alarm: ") + iv->lastAlarmMsg + F("</span>");
|
|
||||||
|
|
||||||
uint8_t list[] = {FLD_UAC, FLD_IAC, FLD_PAC, FLD_F, FLD_PCT, FLD_T, FLD_YT, FLD_YD, FLD_PDC, FLD_EFF, FLD_PRA, FLD_ALARM_MES_ID};
|
|
||||||
|
|
||||||
for (uint8_t fld = 0; fld < 11; fld++) {
|
|
||||||
pos = (iv->getPosByChFld(CH0, list[fld]));
|
|
||||||
|
|
||||||
if(fld == 6){
|
|
||||||
totalYield += iv->getValue(pos);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(fld == 7){
|
|
||||||
totalYieldToday += iv->getValue(pos);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(fld == 2){
|
|
||||||
totalActual += iv->getValue(pos);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (0xff != pos) {
|
|
||||||
modHtml += F("<div class=\"subgrp\">");
|
|
||||||
modHtml += F("<span class=\"value\">") + String(iv->getValue(pos));
|
|
||||||
modHtml += F("<span class=\"unit\">") + String(iv->getUnit(pos)) + F("</span></span>");
|
|
||||||
modHtml += F("<span class=\"info\">") + String(iv->getFieldName(pos)) + F("</span>");
|
|
||||||
modHtml += F("</div>");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
modHtml += "</div>";
|
|
||||||
|
|
||||||
for (uint8_t ch = 1; ch <= modNum; ch++) {
|
|
||||||
modHtml += F("<div class=\"ch\"><span class=\"head\">");
|
|
||||||
if (iv->chName[ch - 1][0] == 0)
|
|
||||||
modHtml += F("CHANNEL ") + String(ch);
|
|
||||||
else
|
|
||||||
modHtml += String(iv->chName[ch - 1]);
|
|
||||||
modHtml += F("</span>");
|
|
||||||
for (uint8_t j = 0; j < 6; j++) {
|
|
||||||
switch (j) {
|
|
||||||
default: pos = (iv->getPosByChFld(ch, FLD_UDC)); break;
|
|
||||||
case 1: pos = (iv->getPosByChFld(ch, FLD_IDC)); break;
|
|
||||||
case 2: pos = (iv->getPosByChFld(ch, FLD_PDC)); break;
|
|
||||||
case 3: pos = (iv->getPosByChFld(ch, FLD_YD)); break;
|
|
||||||
case 4: pos = (iv->getPosByChFld(ch, FLD_YT)); break;
|
|
||||||
case 5: pos = (iv->getPosByChFld(ch, FLD_IRR)); break;
|
|
||||||
}
|
|
||||||
if (0xff != pos) {
|
|
||||||
modHtml += F("<span class=\"value\">") + String(iv->getValue(pos));
|
|
||||||
modHtml += F("<span class=\"unit\">") + String(iv->getUnit(pos)) + F("</span></span>");
|
|
||||||
modHtml += F("<span class=\"info\">") + String(iv->getFieldName(pos)) + F("</span>");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
modHtml += "</div>";
|
|
||||||
yield();
|
|
||||||
}
|
|
||||||
modHtml += F("<div class=\"ts\">Last received data requested at: ") + mMain->getDateTimeStr(iv->ts) + F("</div>");
|
|
||||||
modHtml += F("</div>");
|
|
||||||
#else
|
|
||||||
// dump all data to web frontend
|
|
||||||
modHtml = F("<pre>");
|
|
||||||
char topic[30], val[10];
|
|
||||||
for (uint8_t i = 0; i < iv->listLen; i++) {
|
|
||||||
snprintf(topic, 30, "%s/ch%d/%s", iv->name, iv->assign[i].ch, iv->getFieldName(i));
|
|
||||||
snprintf(val, 10, "%.3f %s", iv->getValue(i), iv->getUnit(i));
|
|
||||||
modHtml += String(topic) + ": " + String(val) + "\n";
|
|
||||||
}
|
|
||||||
modHtml += F("</pre>");
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(count > 1){
|
|
||||||
totalModHtml += F("<div class=\"iv\">"
|
|
||||||
"<div class=\"ch-all\"><span class=\"head\">Gesamt</span>");
|
|
||||||
|
|
||||||
totalModHtml += F("<div class=\"subgrp\">");
|
|
||||||
totalModHtml += F("<span class=\"value\">") + String(totalActual);
|
|
||||||
totalModHtml += F("<span class=\"unit\">W</span></span>");
|
|
||||||
totalModHtml += F("<span class=\"info\">P_AC All</span>");
|
|
||||||
totalModHtml += F("</div>");
|
|
||||||
|
|
||||||
totalModHtml += F("<div class=\"subgrp\">");
|
|
||||||
totalModHtml += F("<span class=\"value\">") + String(totalYieldToday);
|
|
||||||
totalModHtml += F("<span class=\"unit\">Wh</span></span>");
|
|
||||||
totalModHtml += F("<span class=\"info\">YieldDayAll</span>");
|
|
||||||
totalModHtml += F("</div>");
|
|
||||||
|
|
||||||
totalModHtml += F("<div class=\"subgrp\">");
|
|
||||||
totalModHtml += F("<span class=\"value\">") + String(totalYield);
|
|
||||||
totalModHtml += F("<span class=\"unit\">kWh</span></span>");
|
|
||||||
totalModHtml += F("<span class=\"info\">YieldTotalAll</span>");
|
|
||||||
totalModHtml += F("</div>");
|
|
||||||
|
|
||||||
totalModHtml += F("</div>");
|
|
||||||
totalModHtml += F("</div>");
|
|
||||||
mWeb->send(200, F("text/html"), totalModHtml + modHtml);
|
|
||||||
} else {
|
|
||||||
mWeb->send(200, F("text/html"), modHtml);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
|
||||||
void web::showJson(void) {
|
|
||||||
DPRINTLN(DBG_VERBOSE, F("web::showJson"));
|
|
||||||
mWeb->send(200, F("application/json"), mMain->getJson());
|
|
||||||
}
|
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
|
||||||
void web::showWebApi(void)
|
|
||||||
{
|
|
||||||
DPRINTLN(DBG_VERBOSE, F("web::showWebApi"));
|
DPRINTLN(DBG_VERBOSE, F("web::showWebApi"));
|
||||||
DPRINTLN(DBG_DEBUG, mWeb->arg("plain"));
|
DPRINTLN(DBG_DEBUG, request->arg("plain"));
|
||||||
const size_t capacity = 200; // Use arduinojson.org/assistant to compute the capacity.
|
const size_t capacity = 200; // Use arduinojson.org/assistant to compute the capacity.
|
||||||
DynamicJsonDocument response(capacity);
|
DynamicJsonDocument response(capacity);
|
||||||
|
|
||||||
// Parse JSON object
|
// Parse JSON object
|
||||||
deserializeJson(response, mWeb->arg("plain"));
|
deserializeJson(response, request->arg("plain"));
|
||||||
// ToDo: error handling for payload
|
// ToDo: error handling for payload
|
||||||
uint8_t iv_id = response["inverter"];
|
uint8_t iv_id = response["inverter"];
|
||||||
uint8_t cmd = response["cmd"];
|
uint8_t cmd = response["cmd"];
|
||||||
Inverter<> *iv = mMain->mSys->getInverterByPos(iv_id);
|
Inverter<> *iv = mMain->mSys->getInverterByPos(iv_id);
|
||||||
if (NULL != iv)
|
if (NULL != iv) {
|
||||||
{
|
if (response["tx_request"] == (uint8_t)TX_REQ_INFO) {
|
||||||
if (response["tx_request"] == (uint8_t)TX_REQ_INFO)
|
|
||||||
{
|
|
||||||
// if the AlarmData is requested set the Alarm Index to the requested one
|
// if the AlarmData is requested set the Alarm Index to the requested one
|
||||||
if (cmd == AlarmData || cmd == AlarmUpdate) {
|
if (cmd == AlarmData || cmd == AlarmUpdate) {
|
||||||
// set the AlarmMesIndex for the request from user input
|
// set the AlarmMesIndex for the request from user input
|
||||||
|
@ -597,32 +341,21 @@ void web::showWebApi(void)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (response["tx_request"] == (uint8_t)TX_REQ_DEVCONTROL)
|
if (response["tx_request"] == (uint8_t)TX_REQ_DEVCONTROL) {
|
||||||
{
|
if (response["cmd"] == (uint8_t)ActivePowerContr) {
|
||||||
if (response["cmd"] == (uint8_t)ActivePowerContr)
|
|
||||||
{
|
|
||||||
uint16_t webapiPayload = response["payload"];
|
uint16_t webapiPayload = response["payload"];
|
||||||
uint16_t webapiPayload2 = response["payload2"];
|
uint16_t webapiPayload2 = response["payload2"];
|
||||||
if (webapiPayload > 0 && webapiPayload < 10000)
|
if (webapiPayload > 0 && webapiPayload < 10000) {
|
||||||
{
|
|
||||||
iv->devControlCmd = ActivePowerContr;
|
iv->devControlCmd = ActivePowerContr;
|
||||||
iv->powerLimit[0] = webapiPayload;
|
iv->powerLimit[0] = webapiPayload;
|
||||||
if (webapiPayload2 > 0)
|
if (webapiPayload2 > 0)
|
||||||
{
|
|
||||||
iv->powerLimit[1] = webapiPayload2; // dev option, no sanity check
|
iv->powerLimit[1] = webapiPayload2; // dev option, no sanity check
|
||||||
}
|
else // if not set, set it to 0x0000 default
|
||||||
else
|
iv->powerLimit[1] = AbsolutNonPersistent; // payload will be seted temporary in Watt absolut
|
||||||
{ // if not set, set it to 0x0000 default
|
|
||||||
iv->powerLimit[1] = AbsolutNonPersistent; // payload will be seted temporay in Watt absolut
|
|
||||||
}
|
|
||||||
if (iv->powerLimit[1] & 0x0001)
|
if (iv->powerLimit[1] & 0x0001)
|
||||||
{
|
|
||||||
DPRINTLN(DBG_INFO, F("Power limit for inverter ") + String(iv->id) + F(" set to ") + String(iv->powerLimit[0]) + F("% via REST API"));
|
DPRINTLN(DBG_INFO, F("Power limit for inverter ") + String(iv->id) + F(" set to ") + String(iv->powerLimit[0]) + F("% via REST API"));
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
|
||||||
DPRINTLN(DBG_INFO, F("Power limit for inverter ") + String(iv->id) + F(" set to ") + String(iv->powerLimit[0]) + F("W via REST API"));
|
DPRINTLN(DBG_INFO, F("Power limit for inverter ") + String(iv->id) + F(" set to ") + String(iv->powerLimit[0]) + F("W via REST API"));
|
||||||
}
|
|
||||||
iv->devControlRequest = true; // queue it in the request loop
|
iv->devControlRequest = true; // queue it in the request loop
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -634,7 +367,111 @@ void web::showWebApi(void)
|
||||||
iv->devControlCmd = TurnOn;
|
iv->devControlCmd = TurnOn;
|
||||||
iv->devControlRequest = true; // queue it in the request loop
|
iv->devControlRequest = true; // queue it in the request loop
|
||||||
}
|
}
|
||||||
|
if (response["cmd"] == (uint8_t)CleanState_LockAndAlarm) {
|
||||||
|
iv->devControlCmd = CleanState_LockAndAlarm;
|
||||||
|
iv->devControlRequest = true; // queue it in the request loop
|
||||||
|
}
|
||||||
|
if (response["cmd"] == (uint8_t)Restart) {
|
||||||
|
iv->devControlCmd = Restart;
|
||||||
|
iv->devControlRequest = true; // queue it in the request loop
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
mWeb->send(200, "text/json", "{success:true}");
|
}
|
||||||
|
request->send(200, "text/json", "{success:true}");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
void web::onUpdate(AsyncWebServerRequest *request) {
|
||||||
|
DPRINTLN(DBG_VERBOSE, F("onUpdate"));
|
||||||
|
|
||||||
|
|
||||||
|
AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html"), update_html, update_html_len);
|
||||||
|
response->addHeader(F("Content-Encoding"), "gzip");
|
||||||
|
request->send(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
void web::showUpdate(AsyncWebServerRequest *request) {
|
||||||
|
bool reboot = !Update.hasError();
|
||||||
|
|
||||||
|
String html = F("<!doctype html><html><head><title>Update</title><meta http-equiv=\"refresh\" content=\"20; URL=/\"></head><body>Update: ");
|
||||||
|
if(reboot)
|
||||||
|
html += "success";
|
||||||
|
else
|
||||||
|
html += "failed";
|
||||||
|
html += F("<br/><br/>rebooting ... auto reload after 20s</body></html>");
|
||||||
|
|
||||||
|
AsyncWebServerResponse *response = request->beginResponse(200, F("text/html"), html);
|
||||||
|
response->addHeader("Connection", "close");
|
||||||
|
request->send(response);
|
||||||
|
mMain->mShouldReboot = reboot;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
void web::showUpdate2(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) {
|
||||||
|
if(!index) {
|
||||||
|
Serial.printf("Update Start: %s\n", filename.c_str());
|
||||||
|
#ifndef ESP32
|
||||||
|
Update.runAsync(true);
|
||||||
|
#endif
|
||||||
|
if(!Update.begin((ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000)) {
|
||||||
|
Update.printError(Serial);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(!Update.hasError()) {
|
||||||
|
if(Update.write(data, len) != len){
|
||||||
|
Update.printError(Serial);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(final) {
|
||||||
|
if(Update.end(true)) {
|
||||||
|
Serial.printf("Update Success: %uB\n", index+len);
|
||||||
|
} else {
|
||||||
|
Update.printError(Serial);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
void web::onSerial(AsyncWebServerRequest *request) {
|
||||||
|
DPRINTLN(DBG_VERBOSE, F("onSerial"));
|
||||||
|
|
||||||
|
AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html"), serial_html, serial_html_len);
|
||||||
|
response->addHeader(F("Content-Encoding"), "gzip");
|
||||||
|
request->send(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
void web::serialCb(String msg) {
|
||||||
|
msg.replace("\r\n", "<rn>");
|
||||||
|
if(mSerialAddTime) {
|
||||||
|
if((9 + mSerialBufFill) <= WEB_SERIAL_BUF_SIZE) {
|
||||||
|
strncpy(&mSerialBuf[mSerialBufFill], mMain->getTimeStr().c_str(), 9);
|
||||||
|
mSerialBufFill += 9;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
mSerialBufFill = 0;
|
||||||
|
mEvts->send("webSerial, buffer overflow!", "serial", millis());
|
||||||
|
}
|
||||||
|
mSerialAddTime = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(msg.endsWith("<rn>"))
|
||||||
|
mSerialAddTime = true;
|
||||||
|
|
||||||
|
uint16_t length = msg.length();
|
||||||
|
if((length + mSerialBufFill) <= WEB_SERIAL_BUF_SIZE) {
|
||||||
|
strncpy(&mSerialBuf[mSerialBufFill], msg.c_str(), length);
|
||||||
|
mSerialBufFill += length;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
mSerialBufFill = 0;
|
||||||
|
mEvts->send("webSerial, buffer overflow!", "serial", millis());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,56 +7,69 @@
|
||||||
#define __WEB_H__
|
#define __WEB_H__
|
||||||
|
|
||||||
#include "dbg.h"
|
#include "dbg.h"
|
||||||
#ifdef ESP8266
|
#ifdef ESP32
|
||||||
#include <ESP8266WebServer.h>
|
#include "AsyncTCP.h"
|
||||||
#include <ESP8266HTTPUpdateServer.h>
|
#include "Update.h"
|
||||||
#elif defined(ESP32)
|
#else
|
||||||
#include <WebServer.h>
|
#include "ESPAsyncTCP.h"
|
||||||
#include <HTTPUpdateServer.h>
|
|
||||||
#endif
|
#endif
|
||||||
|
#include "ESPAsyncWebServer.h"
|
||||||
#include "app.h"
|
#include "app.h"
|
||||||
|
#include "webApi.h"
|
||||||
|
|
||||||
|
#define WEB_SERIAL_BUF_SIZE 2048
|
||||||
|
|
||||||
class app;
|
class app;
|
||||||
|
class webApi;
|
||||||
|
|
||||||
class web {
|
class web {
|
||||||
public:
|
public:
|
||||||
web(app *main, sysConfig_t *sysCfg, config_t *config, char version[]);
|
web(app *main, sysConfig_t *sysCfg, config_t *config, statistics_t *stat, char version[]);
|
||||||
~web() {}
|
~web() {}
|
||||||
|
|
||||||
void setup(void);
|
void setup(void);
|
||||||
void loop(void);
|
void loop(void);
|
||||||
|
|
||||||
void showIndex(void);
|
void onConnect(AsyncEventSourceClient *client);
|
||||||
void showCss(void);
|
|
||||||
void showFavicon(void);
|
|
||||||
void showNotFound(void);
|
|
||||||
void showUptime(void);
|
|
||||||
void showReboot(void);
|
|
||||||
void showErase();
|
|
||||||
void showFactoryRst(void);
|
|
||||||
void showSetup(void);
|
|
||||||
void showSave(void);
|
|
||||||
|
|
||||||
void showStatistics(void);
|
void onIndex(AsyncWebServerRequest *request);
|
||||||
void showVisualization(void);
|
void onCss(AsyncWebServerRequest *request);
|
||||||
void showLiveData(void);
|
void onApiJs(AsyncWebServerRequest *request);
|
||||||
void showJson(void);
|
void onFavicon(AsyncWebServerRequest *request);
|
||||||
void showWebApi(void);
|
void showNotFound(AsyncWebServerRequest *request);
|
||||||
|
void onReboot(AsyncWebServerRequest *request);
|
||||||
|
void showErase(AsyncWebServerRequest *request);
|
||||||
|
void showFactoryRst(AsyncWebServerRequest *request);
|
||||||
|
void onSetup(AsyncWebServerRequest *request);
|
||||||
|
void showSave(AsyncWebServerRequest *request);
|
||||||
|
|
||||||
|
void onLive(AsyncWebServerRequest *request);
|
||||||
|
void showWebApi(AsyncWebServerRequest *request);
|
||||||
|
|
||||||
|
void onUpdate(AsyncWebServerRequest *request);
|
||||||
|
void showUpdate(AsyncWebServerRequest *request);
|
||||||
|
void showUpdate2(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final);
|
||||||
|
|
||||||
|
void serialCb(String msg);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
#ifdef ESP8266
|
void onSerial(AsyncWebServerRequest *request);
|
||||||
ESP8266WebServer *mWeb;
|
|
||||||
ESP8266HTTPUpdateServer *mUpdater;
|
AsyncWebServer *mWeb;
|
||||||
#elif defined(ESP32)
|
AsyncEventSource *mEvts;
|
||||||
WebServer *mWeb;
|
|
||||||
HTTPUpdateServer *mUpdater;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
config_t *mConfig;
|
config_t *mConfig;
|
||||||
sysConfig_t *mSysCfg;
|
sysConfig_t *mSysCfg;
|
||||||
|
statistics_t *mStat;
|
||||||
char *mVersion;
|
char *mVersion;
|
||||||
app *mMain;
|
app *mMain;
|
||||||
|
webApi *mApi;
|
||||||
|
|
||||||
|
bool mSerialAddTime;
|
||||||
|
char mSerialBuf[WEB_SERIAL_BUF_SIZE];
|
||||||
|
uint16_t mSerialBufFill;
|
||||||
|
uint32_t mWebSerialTicker;
|
||||||
|
uint32_t mWebSerialInterval;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif /*__WEB_H__*/
|
#endif /*__WEB_H__*/
|
||||||
|
|
415
tools/esp8266/webApi.cpp
Normal file
415
tools/esp8266/webApi.cpp
Normal file
|
@ -0,0 +1,415 @@
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
// 2022 Ahoy, https://www.mikrocontroller.net/topic/525778
|
||||||
|
// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
#if defined(ESP32) && defined(F)
|
||||||
|
#undef F
|
||||||
|
#define F(sl) (sl)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "webApi.h"
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
webApi::webApi(AsyncWebServer *srv, app *app, sysConfig_t *sysCfg, config_t *config, statistics_t *stat, char version[]) {
|
||||||
|
mSrv = srv;
|
||||||
|
mApp = app;
|
||||||
|
mSysCfg = sysCfg;
|
||||||
|
mConfig = config;
|
||||||
|
mStat = stat;
|
||||||
|
mVersion = version;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
void webApi::setup(void) {
|
||||||
|
mSrv->on("/api", HTTP_GET, std::bind(&webApi::onApi, this, std::placeholders::_1));
|
||||||
|
mSrv->on("/api", HTTP_POST, std::bind(&webApi::onApiPost, this, std::placeholders::_1)).onBody(
|
||||||
|
std::bind(&webApi::onApiPostBody, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
void webApi::loop(void) {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
void webApi::onApi(AsyncWebServerRequest *request) {
|
||||||
|
AsyncJsonResponse* response = new AsyncJsonResponse(false, 8192);
|
||||||
|
JsonObject root = response->getRoot();
|
||||||
|
|
||||||
|
Inverter<> *iv = mApp->mSys->getInverterByPos(0, false);
|
||||||
|
String path = request->url().substring(5);
|
||||||
|
if(path == "system") getSystem(root);
|
||||||
|
else if(path == "statistics") getStatistics(root);
|
||||||
|
else if(path == "inverter/list") getInverterList(root);
|
||||||
|
else if(path == "index") getIndex(root);
|
||||||
|
else if(path == "setup") getSetup(root);
|
||||||
|
else if(path == "live") getLive(root);
|
||||||
|
else if(path == "record/info") getRecord(root, iv->getRecordStruct(InverterDevInform_All));
|
||||||
|
else if(path == "record/alarm") getRecord(root, iv->getRecordStruct(AlarmData));
|
||||||
|
else if(path == "record/config") getRecord(root, iv->getRecordStruct(SystemConfigPara));
|
||||||
|
else if(path == "record/live") getRecord(root, iv->getRecordStruct(RealTimeRunData_Debug));
|
||||||
|
else
|
||||||
|
getNotFound(root, F("http://") + request->host() + F("/api/"));
|
||||||
|
|
||||||
|
response->addHeader("Access-Control-Allow-Origin", "*");
|
||||||
|
response->addHeader("Access-Control-Allow-Headers", "content-type");
|
||||||
|
response->setLength();
|
||||||
|
request->send(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
void webApi::onApiPost(AsyncWebServerRequest *request) {
|
||||||
|
DPRINTLN(DBG_VERBOSE, "onApiPost");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
void webApi::onApiPostBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) {
|
||||||
|
DPRINTLN(DBG_VERBOSE, "onApiPostBody");
|
||||||
|
DynamicJsonDocument json(200);
|
||||||
|
AsyncJsonResponse* response = new AsyncJsonResponse(false, 200);
|
||||||
|
JsonObject root = response->getRoot();
|
||||||
|
|
||||||
|
DeserializationError err = deserializeJson(json, (const char *)data);
|
||||||
|
root[F("success")] = (err) ? false : true;
|
||||||
|
if(!err) {
|
||||||
|
String path = request->url().substring(5);
|
||||||
|
if(path == "ctrl")
|
||||||
|
root[F("success")] = setCtrl(json, root);
|
||||||
|
else if(path == "setup")
|
||||||
|
root[F("success")] = setSetup(json, root);
|
||||||
|
else {
|
||||||
|
root[F("success")] = false;
|
||||||
|
root[F("error")] = "Path not found: " + path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
switch (err.code()) {
|
||||||
|
case DeserializationError::Ok: break;
|
||||||
|
case DeserializationError::InvalidInput: root[F("error")] = F("Invalid input"); break;
|
||||||
|
case DeserializationError::NoMemory: root[F("error")] = F("Not enough memory"); break;
|
||||||
|
default: root[F("error")] = F("Deserialization failed"); break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
response->setLength();
|
||||||
|
request->send(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
void webApi::getNotFound(JsonObject obj, String url) {
|
||||||
|
JsonObject ep = obj.createNestedObject("avail_endpoints");
|
||||||
|
ep[F("system")] = url + F("system");
|
||||||
|
ep[F("statistics")] = url + F("statistics");
|
||||||
|
ep[F("inverter/list")] = url + F("inverter/list");
|
||||||
|
ep[F("index")] = url + F("index");
|
||||||
|
ep[F("setup")] = url + F("setup");
|
||||||
|
ep[F("live")] = url + F("live");
|
||||||
|
ep[F("record/info")] = url + F("record/info");
|
||||||
|
ep[F("record/alarm")] = url + F("record/alarm");
|
||||||
|
ep[F("record/config")] = url + F("record/config");
|
||||||
|
ep[F("record/live")] = url + F("record/live");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
void webApi::getSystem(JsonObject obj) {
|
||||||
|
obj[F("ssid")] = mSysCfg->stationSsid;
|
||||||
|
obj[F("device_name")] = mSysCfg->deviceName;
|
||||||
|
obj[F("version")] = String(mVersion);
|
||||||
|
obj[F("build")] = String(AUTO_GIT_HASH);
|
||||||
|
obj[F("ts_uptime")] = mApp->getUptime();
|
||||||
|
obj[F("ts_now")] = mApp->getTimestamp();
|
||||||
|
obj[F("wifi_rssi")] = WiFi.RSSI();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
void webApi::getStatistics(JsonObject obj) {
|
||||||
|
obj[F("rx_success")] = mStat->rxSuccess;
|
||||||
|
obj[F("rx_fail")] = mStat->rxFail;
|
||||||
|
obj[F("rx_fail_answer")] = mStat->rxFailNoAnser;
|
||||||
|
obj[F("frame_cnt")] = mStat->frmCnt;
|
||||||
|
obj[F("tx_cnt")] = mApp->mSys->Radio.mSendCnt;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
void webApi::getInverterList(JsonObject obj) {
|
||||||
|
JsonArray invArr = obj.createNestedArray(F("inverter"));
|
||||||
|
|
||||||
|
Inverter<> *iv;
|
||||||
|
for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i ++) {
|
||||||
|
iv = mApp->mSys->getInverterByPos(i);
|
||||||
|
if(NULL != iv) {
|
||||||
|
JsonObject obj2 = invArr.createNestedObject();
|
||||||
|
obj2[F("id")] = i;
|
||||||
|
obj2[F("name")] = String(iv->name);
|
||||||
|
obj2[F("serial")] = String(iv->serial.u64, HEX);
|
||||||
|
obj2[F("channels")] = iv->channels;
|
||||||
|
obj2[F("version")] = String(iv->fwVersion);
|
||||||
|
|
||||||
|
for(uint8_t j = 0; j < iv->channels; j ++) {
|
||||||
|
obj2[F("ch_max_power")][j] = iv->chMaxPwr[j];
|
||||||
|
obj2[F("ch_name")][j] = iv->chName[j];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
obj[F("interval")] = String(mConfig->sendInterval);
|
||||||
|
obj[F("retries")] = String(mConfig->maxRetransPerPyld);
|
||||||
|
obj[F("max_num_inverters")] = MAX_NUM_INVERTERS;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
void webApi::getMqtt(JsonObject obj) {
|
||||||
|
obj[F("broker")] = String(mConfig->mqtt.broker);
|
||||||
|
obj[F("port")] = String(mConfig->mqtt.port);
|
||||||
|
obj[F("user")] = String(mConfig->mqtt.user);
|
||||||
|
obj[F("pwd")] = (strlen(mConfig->mqtt.pwd) > 0) ? F("{PWD}") : String("");
|
||||||
|
obj[F("topic")] = String(mConfig->mqtt.topic);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
void webApi::getNtp(JsonObject obj) {
|
||||||
|
obj[F("addr")] = String(mConfig->ntpAddr);
|
||||||
|
obj[F("port")] = String(mConfig->ntpPort);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
void webApi::getPinout(JsonObject obj) {
|
||||||
|
obj[F("cs")] = mConfig->pinCs;
|
||||||
|
obj[F("ce")] = mConfig->pinCe;
|
||||||
|
obj[F("irq")] = mConfig->pinIrq;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
void webApi::getRadio(JsonObject obj) {
|
||||||
|
obj[F("power_level")] = mConfig->amplifierPower;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
void webApi::getSerial(JsonObject obj) {
|
||||||
|
obj[F("interval")] = (uint16_t)mConfig->serialInterval;
|
||||||
|
obj[F("show_live_data")] = mConfig->serialShowIv;
|
||||||
|
obj[F("debug")] = mConfig->serialDebug;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
void webApi::getIndex(JsonObject obj) {
|
||||||
|
getSystem(obj.createNestedObject(F("system")));
|
||||||
|
getStatistics(obj.createNestedObject(F("statistics")));
|
||||||
|
obj["refresh_interval"] = SEND_INTERVAL;
|
||||||
|
|
||||||
|
JsonArray inv = obj.createNestedArray(F("inverter"));
|
||||||
|
Inverter<> *iv;
|
||||||
|
for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i ++) {
|
||||||
|
iv = mApp->mSys->getInverterByPos(i);
|
||||||
|
if(NULL != iv) {
|
||||||
|
record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug);
|
||||||
|
JsonObject invObj = inv.createNestedObject();
|
||||||
|
invObj[F("id")] = i;
|
||||||
|
invObj[F("name")] = String(iv->name);
|
||||||
|
invObj[F("version")] = String(iv->fwVersion);
|
||||||
|
invObj[F("is_avail")] = iv->isAvailable(mApp->getTimestamp(), rec);
|
||||||
|
invObj[F("is_producing")] = iv->isProducing(mApp->getTimestamp(), rec);
|
||||||
|
invObj[F("ts_last_success")] = iv->getLastTs(rec);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
JsonArray warn = obj.createNestedArray(F("warnings"));
|
||||||
|
if(!mApp->mSys->Radio.isChipConnected())
|
||||||
|
warn.add(F("your NRF24 module can't be reached, check the wiring and pinout"));
|
||||||
|
if(!mApp->mqttIsConnected())
|
||||||
|
warn.add(F("MQTT is not connected"));
|
||||||
|
|
||||||
|
JsonArray info = obj.createNestedArray(F("infos"));
|
||||||
|
if(mApp->getRebootRequestState())
|
||||||
|
info.add(F("reboot your ESP to apply all your configuration changes!"));
|
||||||
|
if(!mApp->getSettingsValid())
|
||||||
|
info.add(F("your settings are invalid"));
|
||||||
|
if(mApp->mqttIsConnected())
|
||||||
|
info.add(F("MQTT is connected"));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
void webApi::getSetup(JsonObject obj) {
|
||||||
|
getSystem(obj.createNestedObject(F("system")));
|
||||||
|
getInverterList(obj.createNestedObject(F("inverter")));
|
||||||
|
getMqtt(obj.createNestedObject(F("mqtt")));
|
||||||
|
getNtp(obj.createNestedObject(F("ntp")));
|
||||||
|
getPinout(obj.createNestedObject(F("pinout")));
|
||||||
|
getRadio(obj.createNestedObject(F("radio")));
|
||||||
|
getSerial(obj.createNestedObject(F("serial")));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
void webApi::getLive(JsonObject obj) {
|
||||||
|
getSystem(obj.createNestedObject(F("system")));
|
||||||
|
JsonArray invArr = obj.createNestedArray(F("inverter"));
|
||||||
|
obj["refresh_interval"] = SEND_INTERVAL;
|
||||||
|
|
||||||
|
uint8_t list[] = {FLD_UAC, FLD_IAC, FLD_PAC, FLD_F, FLD_PF, FLD_T, FLD_YT, FLD_YD, FLD_PDC, FLD_EFF, FLD_Q};
|
||||||
|
|
||||||
|
Inverter<> *iv;
|
||||||
|
uint8_t pos;
|
||||||
|
for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i ++) {
|
||||||
|
iv = mApp->mSys->getInverterByPos(i);
|
||||||
|
if(NULL != iv) {
|
||||||
|
record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug);
|
||||||
|
JsonObject obj2 = invArr.createNestedObject();
|
||||||
|
obj2[F("name")] = String(iv->name);
|
||||||
|
obj2[F("channels")] = iv->channels;
|
||||||
|
obj2[F("power_limit_read")] = round3(iv->actPowerLimit);
|
||||||
|
obj2[F("last_alarm")] = String(iv->lastAlarmMsg);
|
||||||
|
obj2[F("ts_last_success")] = rec->ts;
|
||||||
|
|
||||||
|
JsonArray ch = obj2.createNestedArray("ch");
|
||||||
|
JsonArray ch0 = ch.createNestedArray();
|
||||||
|
obj2[F("ch_names")][0] = "AC";
|
||||||
|
for (uint8_t fld = 0; fld < sizeof(list); fld++) {
|
||||||
|
pos = (iv->getPosByChFld(CH0, list[fld], rec));
|
||||||
|
ch0[fld] = (0xff != pos) ? round3(iv->getValue(pos, rec)) : 0.0;
|
||||||
|
obj[F("ch0_fld_units")][fld] = (0xff != pos) ? String(iv->getUnit(pos, rec)) : notAvail;
|
||||||
|
obj[F("ch0_fld_names")][fld] = (0xff != pos) ? String(iv->getFieldName(pos, rec)) : notAvail;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(uint8_t j = 1; j <= iv->channels; j ++) {
|
||||||
|
obj2[F("ch_names")][j] = String(iv->chName[j-1]);
|
||||||
|
JsonArray cur = ch.createNestedArray();
|
||||||
|
for (uint8_t k = 0; k < 6; k++) {
|
||||||
|
switch(k) {
|
||||||
|
default: pos = (iv->getPosByChFld(j, FLD_UDC, rec)); break;
|
||||||
|
case 1: pos = (iv->getPosByChFld(j, FLD_IDC, rec)); break;
|
||||||
|
case 2: pos = (iv->getPosByChFld(j, FLD_PDC, rec)); break;
|
||||||
|
case 3: pos = (iv->getPosByChFld(j, FLD_YD, rec)); break;
|
||||||
|
case 4: pos = (iv->getPosByChFld(j, FLD_YT, rec)); break;
|
||||||
|
case 5: pos = (iv->getPosByChFld(j, FLD_IRR, rec)); break;
|
||||||
|
}
|
||||||
|
cur[k] = (0xff != pos) ? round3(iv->getValue(pos, rec)) : 0.0;
|
||||||
|
if(1 == j) {
|
||||||
|
obj[F("fld_units")][k] = (0xff != pos) ? String(iv->getUnit(pos, rec)) : notAvail;
|
||||||
|
obj[F("fld_names")][k] = (0xff != pos) ? String(iv->getFieldName(pos, rec)) : notAvail;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
void webApi::getRecord(JsonObject obj, record_t<> *rec) {
|
||||||
|
JsonArray invArr = obj.createNestedArray(F("inverter"));
|
||||||
|
|
||||||
|
Inverter<> *iv;
|
||||||
|
uint8_t pos;
|
||||||
|
for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i ++) {
|
||||||
|
iv = mApp->mSys->getInverterByPos(i);
|
||||||
|
if(NULL != iv) {
|
||||||
|
JsonArray obj2 = invArr.createNestedArray();
|
||||||
|
for(uint8_t j = 0; j < rec->length; j++) {
|
||||||
|
byteAssign_t *assign = iv->getByteAssign(j, rec);
|
||||||
|
pos = (iv->getPosByChFld(assign->ch, assign->fieldId, rec));
|
||||||
|
obj2[j]["fld"] = (0xff != pos) ? String(iv->getFieldName(pos, rec)) : notAvail;
|
||||||
|
obj2[j]["unit"] = (0xff != pos) ? String(iv->getUnit(pos, rec)) : notAvail;
|
||||||
|
obj2[j]["val"] = (0xff != pos) ? String(iv->getValue(pos, rec)) : notAvail;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
bool webApi::setCtrl(DynamicJsonDocument jsonIn, JsonObject jsonOut) {
|
||||||
|
uint8_t cmd = jsonIn[F("cmd")];
|
||||||
|
|
||||||
|
// Todo: num is the inverter number 0-3. For better display in DPRINTLN
|
||||||
|
uint8_t num = jsonIn[F("inverter")];
|
||||||
|
uint8_t tx_request = jsonIn[F("tx_request")];
|
||||||
|
|
||||||
|
if(TX_REQ_DEVCONTROL == tx_request)
|
||||||
|
{
|
||||||
|
DPRINTLN(DBG_INFO, F("devcontrol [") + String(num) + F("], cmd: 0x") + String(cmd, HEX));
|
||||||
|
|
||||||
|
Inverter<> *iv = getInverter(jsonIn, jsonOut);
|
||||||
|
JsonArray payload = jsonIn[F("payload")].as<JsonArray>();
|
||||||
|
|
||||||
|
if(NULL != iv)
|
||||||
|
{
|
||||||
|
switch (cmd)
|
||||||
|
{
|
||||||
|
case TurnOn:
|
||||||
|
iv->devControlCmd = TurnOn;
|
||||||
|
iv->devControlRequest = true;
|
||||||
|
break;
|
||||||
|
case TurnOff:
|
||||||
|
iv->devControlCmd = TurnOff;
|
||||||
|
iv->devControlRequest = true;
|
||||||
|
break;
|
||||||
|
case CleanState_LockAndAlarm:
|
||||||
|
iv->devControlCmd = CleanState_LockAndAlarm;
|
||||||
|
iv->devControlRequest = true;
|
||||||
|
break;
|
||||||
|
case Restart:
|
||||||
|
iv->devControlCmd = Restart;
|
||||||
|
iv->devControlRequest = true;
|
||||||
|
break;
|
||||||
|
case ActivePowerContr:
|
||||||
|
iv->devControlCmd = ActivePowerContr;
|
||||||
|
iv->devControlRequest = true;
|
||||||
|
iv->powerLimit[0] = payload[0];
|
||||||
|
iv->powerLimit[1] = payload[1];
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
jsonOut["error"] = "unknown 'cmd' = " + String(cmd);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
jsonOut[F("error")] = F("unknown 'tx_request'");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
bool webApi::setSetup(DynamicJsonDocument jsonIn, JsonObject jsonOut) {
|
||||||
|
if(F("set_time") == jsonIn[F("cmd")])
|
||||||
|
mApp->setTimestamp(jsonIn[F("ts")]);
|
||||||
|
else if(F("sync_ntp") == jsonIn[F("cmd")])
|
||||||
|
mApp->setTimestamp(0); // 0: update ntp flag
|
||||||
|
else {
|
||||||
|
jsonOut[F("error")] = F("unknown cmd");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
Inverter<> *webApi::getInverter(DynamicJsonDocument jsonIn, JsonObject jsonOut) {
|
||||||
|
uint8_t id = jsonIn[F("inverter")];
|
||||||
|
Inverter<> *iv = mApp->mSys->getInverterByPos(id);
|
||||||
|
if(NULL == iv)
|
||||||
|
jsonOut[F("error")] = F("inverter index to high: ") + String(id);
|
||||||
|
return iv;
|
||||||
|
}
|
62
tools/esp8266/webApi.h
Normal file
62
tools/esp8266/webApi.h
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
#ifndef __WEB_API_H__
|
||||||
|
#define __WEB_API_H__
|
||||||
|
|
||||||
|
#include "dbg.h"
|
||||||
|
#ifdef ESP32
|
||||||
|
#include "AsyncTCP.h"
|
||||||
|
#else
|
||||||
|
#include "ESPAsyncTCP.h"
|
||||||
|
#endif
|
||||||
|
#include "ESPAsyncWebServer.h"
|
||||||
|
#include "AsyncJson.h"
|
||||||
|
#include "app.h"
|
||||||
|
|
||||||
|
|
||||||
|
class app;
|
||||||
|
|
||||||
|
class webApi {
|
||||||
|
public:
|
||||||
|
webApi(AsyncWebServer *srv, app *app, sysConfig_t *sysCfg, config_t *config, statistics_t *stat, char version[]);
|
||||||
|
|
||||||
|
void setup(void);
|
||||||
|
void loop(void);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void onApi(AsyncWebServerRequest *request);
|
||||||
|
void onApiPost(AsyncWebServerRequest *request);
|
||||||
|
void onApiPostBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total);
|
||||||
|
void getNotFound(JsonObject obj, String url);
|
||||||
|
|
||||||
|
void getSystem(JsonObject obj);
|
||||||
|
void getStatistics(JsonObject obj);
|
||||||
|
void getInverterList(JsonObject obj);
|
||||||
|
void getMqtt(JsonObject obj);
|
||||||
|
void getNtp(JsonObject obj);
|
||||||
|
void getPinout(JsonObject obj);
|
||||||
|
void getRadio(JsonObject obj);
|
||||||
|
void getSerial(JsonObject obj);
|
||||||
|
|
||||||
|
void getIndex(JsonObject obj);
|
||||||
|
void getSetup(JsonObject obj);
|
||||||
|
void getLive(JsonObject obj);
|
||||||
|
void getRecord(JsonObject obj, record_t<> *rec);
|
||||||
|
|
||||||
|
bool setCtrl(DynamicJsonDocument jsonIn, JsonObject jsonOut);
|
||||||
|
bool setSetup(DynamicJsonDocument jsonIn, JsonObject jsonOut);
|
||||||
|
|
||||||
|
Inverter<> *getInverter(DynamicJsonDocument jsonIn, JsonObject jsonOut);
|
||||||
|
|
||||||
|
double round3(double value) {
|
||||||
|
return (int)(value * 1000 + 0.5) / 1000.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
AsyncWebServer *mSrv;
|
||||||
|
app *mApp;
|
||||||
|
|
||||||
|
config_t *mConfig;
|
||||||
|
sysConfig_t *mSysCfg;
|
||||||
|
statistics_t *mStat;
|
||||||
|
char *mVersion;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif /*__WEB_API_H__*/
|
Loading…
Add table
Add a link
Reference in a new issue