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

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

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

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

|
||||
|
||||
##### ESP32 GPIO settings
|
||||
|
||||
For this wiring, set the 3 individual GPIOs under the /setup URL:
|
||||
|
||||
```
|
||||
|
@ -103,11 +141,12 @@ IRQ D0 (GPIO16 - no IRQ!)
|
|||
```
|
||||
|
||||
## Flash the Firmware on your Ahoy DTU Hardware
|
||||
Once your Hardware is ready to run, you need to flash the Ahoy DTU Firmware to your Board.
|
||||
You can either build your own using your own configuration or use one or our pre-compiled generic builds.
|
||||
|
||||
Once your Hardware is ready to run, you need to flash the Ahoy DTU Firmware to your Board.
|
||||
You can either build your own using your own configuration or use one of our pre-compiled generic builds.
|
||||
|
||||
#### Compiling your own Version
|
||||
|
||||
This information suits you if you want to configure and build your own firmware.
|
||||
|
||||
This code comes to you as a **PlatformIO** project and can be compiled using the **PlatformIO** Addon.<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`
|
||||
- DTU radio id `config.h` (default = 1234567801)
|
||||
- unformated list in webbrowser `/livedata` `config.h`, `LIVEDATA_VISUALIZED`
|
||||
- unformatted list in webbrowser `/livedata` `config.h`, `LIVEDATA_VISUALIZED`
|
||||
|
||||
Alternativly, instead of modifying `config.h`, `config_override_example.h` can be copied to `config_override.h` and customized.
|
||||
config_override.h is excluded from version control and stays local.
|
||||
|
||||
|
||||
#### Using a ready-to-flash binary using nodemcu-pyflasher
|
||||
|
||||
This information suits you if you just want to use an easy way.
|
||||
|
||||
1. download the flash-tool [nodemcu-pyflasher](https://github.com/marcelstoer/nodemcu-pyflasher)
|
||||
|
@ -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
|
||||
7. repower the ESP
|
||||
8. the ESP will start as access point (AP) if there is no network config stored in its eeprom
|
||||
9. connect to the AP, you will be forwarded to the setup page
|
||||
9. connect to the AP (password: `esp_8266`), you will be forwarded to the setup page
|
||||
10. configure your WiFi settings, save, repower
|
||||
11. check your router or serial console for the IP address of the module. You can try ping the configured device name as well.
|
||||
|
||||
|
||||
Once your Ahoy DTU is running, you can use the Over The Air (OTA) capabilities to update your firmware.
|
||||
|
||||
|
||||
! ATTENTION: If you update from a very low version to the newest, please make sure to wipe all flash data!
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Connect to your Ahoy DTU
|
||||
|
||||
When everything is wired up and the firmware is flashed, it is time to connect to your Ahoy DTU.
|
||||
|
||||
|
||||
#### 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/>
|
||||
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).
|
||||
|
||||
|
||||
#### 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/>
|
||||
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/>
|
||||
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/>
|
||||
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/>
|
||||
If nothing connects to it and that time runs up, it will retry to connect to the configured network an so on.<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 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 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 [http://192.168.1.1/](http://192.168.1.1/).<br/>
|
||||
Just open the IP-Address in your browser.<br/>
|
||||
<br/>
|
||||
The webinterface has the following abilities:
|
||||
|
||||
- OTA Update (Over The Air Update)
|
||||
- Configuration (Wifi, inverter(s), NTP Server, Pinout, MQTT, Amplifier Power Level, Debug)
|
||||
- visual display of the connected inverters / modules
|
||||
- some statistics about communication (debug)
|
||||
|
||||
|
||||
##### HTTP based Pages
|
||||
To take control of your Ahoy DTU, you can directly call one of the following sub-pages (e.g. http://192.168.1.1/setup ).<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 |
|
||||
| ---- | ------ | ------ |
|
||||
| /uptime | displays the uptime uf your Ahoy DTU | 0 Days, 01:37:34; now: 2022-08-21 11:13:53 |
|
||||
| /uptime | displays the uptime of your Ahoy DTU | 0 Days, 01:37:34; now: 2022-08-21 11:13:53 |
|
||||
| /reboot | reboots the Ahoy DTU | |
|
||||
| /erase | erases the EEPROM | |
|
||||
| /factory | resets to the factory defaults configured in config.h | |
|
||||
|
@ -198,31 +232,31 @@ When everything is wired up and the firmware is flashed, it is time to connect t
|
|||
| /json | gets live-data in JSON format | json output from the livedata |
|
||||
| /api | | |
|
||||
|
||||
|
||||
|
||||
## MQTT command to set the DTU without webinterface
|
||||
[Read here](User_Manual.md)
|
||||
|
||||
|
||||
[Read here](tools/esp8266/User_Manual.md)
|
||||
|
||||
## Used Libraries
|
||||
|
||||
- `ESP8266WiFi` 1.0
|
||||
- `DNSServer` 1.1.0
|
||||
- `Ticker` 1.0
|
||||
- `ESP8266HTTPUpdateServer` 1.0
|
||||
- `Time` 1.6.1
|
||||
- `RF24` 1.4.5
|
||||
- `PubSubClient` 2.8
|
||||
- `ArduinoJson` 6.19.4
|
||||
|
||||
| Name | version | License |
|
||||
| --------------------- | ------- | -------- |
|
||||
| `ESP8266WiFi` | 1.0 | LGPL-2.1 |
|
||||
| `DNSServer` | 1.1.1 | LGPL-2.1 |
|
||||
| `SPI` | 1.0 | LGPL-2.1 |
|
||||
| `Hash` | 1.0 | LGPL-2.1 |
|
||||
| `EEPROM` | 1.0 | LGPL-2.1 |
|
||||
| `ESP Async WebServer` | 1.2.3 | LGPL-3.0 |
|
||||
| `ESPAsyncTCP` | 1.2.2 | LGPL-3.0 |
|
||||
| `Time` | 1.6.1 | LGPL-2.1 |
|
||||
| `RF24` | 1.4.5 | GPL-2.0 |
|
||||
| `PubSubClient` | 2.8 | MIT |
|
||||
| `ArduinoJson` | 6.19.4 | MIT |
|
||||
|
||||
## Contact
|
||||
|
||||
We run a Discord Server that can be used to get in touch with the Developers and Users.
|
||||
|
||||
https://discord.gg/WzhxEY62mB
|
||||
|
||||
|
||||
<https://discord.gg/WzhxEY62mB>
|
||||
|
||||
## ToDo
|
||||
|
||||
|
|
|
@ -18,11 +18,11 @@ The ahoy dtu will publish on the following topics
|
|||
|U_AC | 233.300|actual AC Voltage in Volt|
|
||||
|I_AC | 0.300 | actual AC Current in Ampere|
|
||||
|P_AC | 71.000| actual AC Power in Watt|
|
||||
|P_ACr | 21.200| actual AC reactive power in VAr|
|
||||
|Freq | 49.990|actual AC Frequency in 1/s|
|
||||
|Pct | 95.800|actual AC Power factor in %|
|
||||
|Q_AC | 21.200| actual AC reactive power in var|
|
||||
|F_AC | 49.990| actual AC Frequency in Hz|
|
||||
|PF_AC | 95.800| actual AC Power factor|
|
||||
|Temp | 19.800|Temperature of inverter in Celsius|
|
||||
|LARM_MES_ID | 9.000|Last Alarm Message Id|
|
||||
|EVT | 9.000|Last Event/Alarm Message Index|
|
||||
|YieldDay | 51.000|Energy converted to AC per day in Watt hours (measured on DC)|
|
||||
|YieldTotal | 465.294|Energy converted to AC since reset Watt hours (measured on DC)|
|
||||
|P_DC | 74.600|actual DC Power in Watt|
|
||||
|
|
|
@ -54,7 +54,7 @@ void ahoywifi::setup(uint32_t timeout, bool settingValid) {
|
|||
if(mApActive)
|
||||
DBGPRINTLN(F("192.168.1.1"));
|
||||
else
|
||||
DBGPRINTLN(WiFi.localIP());
|
||||
DBGPRINTLN(WiFi.localIP().toString());
|
||||
DPRINTLN(DBG_INFO, F("to configure your device"));
|
||||
DPRINTLN(DBG_INFO, F("----------------------------------------\n"));
|
||||
}
|
||||
|
|
|
@ -7,13 +7,6 @@
|
|||
#define __AHOYWIFI_H__
|
||||
|
||||
#include "dbg.h"
|
||||
#ifdef ESP8266
|
||||
#include <ESP8266WebServer.h>
|
||||
#include <ESP8266WiFi.h>
|
||||
#elif defined(ESP32)
|
||||
#include <WebServer.h>
|
||||
#include <WiFi.h>
|
||||
#endif
|
||||
|
||||
// NTP
|
||||
#include <WiFiUdp.h>
|
||||
|
|
|
@ -23,6 +23,7 @@ app::app() {
|
|||
loadDefaultConfig();
|
||||
|
||||
mSys = new HmSystemType();
|
||||
mShouldReboot = false;
|
||||
}
|
||||
|
||||
|
||||
|
@ -41,7 +42,7 @@ void app::setup(uint32_t timeout) {
|
|||
#endif
|
||||
mSys->setup(&mConfig);
|
||||
|
||||
mWebInst = new web(this, &mSysConfig, &mConfig, mVersion);
|
||||
mWebInst = new web(this, &mSysConfig, &mConfig, &mStat, mVersion);
|
||||
mWebInst->setup();
|
||||
}
|
||||
|
||||
|
@ -60,10 +61,19 @@ void app::loop(void) {
|
|||
}
|
||||
|
||||
if(checkTicker(&mNtpRefreshTicker, mNtpRefreshInterval)) {
|
||||
if(!apActive) {
|
||||
if(!apActive)
|
||||
mUpdateNtp = true;
|
||||
}
|
||||
|
||||
if(mUpdateNtp) {
|
||||
mUpdateNtp = false;
|
||||
mTimestamp = mWifi->getNtpTime();
|
||||
DPRINTLN(DBG_INFO, "[NTP]: " + getDateTimeStr(mTimestamp));
|
||||
}
|
||||
|
||||
if(mShouldReboot) {
|
||||
DPRINTLN(DBG_INFO, F("Rebooting..."));
|
||||
ESP.restart();
|
||||
}
|
||||
|
||||
|
||||
|
@ -84,30 +94,26 @@ void app::loop(void) {
|
|||
DPRINT(DBG_INFO, "RX " + String(len) + "B Ch" + String(p->rxCh) + " | ");
|
||||
mSys->Radio.dumpBuf(NULL, p->packet, len);
|
||||
}
|
||||
mFrameCnt++;
|
||||
mStat.frmCnt++;
|
||||
|
||||
if(0 != len) {
|
||||
Inverter<> *iv = mSys->findInverter(&p->packet[1]);
|
||||
if(NULL != iv && p->packet[0] == (TX_REQ_INFO + 0x80)) { // response from get information command
|
||||
if((NULL != iv) && (p->packet[0] == (TX_REQ_INFO + 0x80))) { // response from get information command
|
||||
mPayload[iv->id].txId = p->packet[0];
|
||||
DPRINTLN(DBG_DEBUG, F("Response from info request received"));
|
||||
uint8_t *pid = &p->packet[9];
|
||||
if (*pid == 0x00)
|
||||
{
|
||||
DPRINT(DBG_DEBUG, "fragment number zero received and ignored");
|
||||
}
|
||||
else
|
||||
{
|
||||
if ((*pid & 0x7F) < 5)
|
||||
{
|
||||
else {
|
||||
DPRINTLN(DBG_DEBUG, "PID: 0x" + String(*pid, HEX));
|
||||
if ((*pid & 0x7F) < 5) {
|
||||
memcpy(mPayload[iv->id].data[(*pid & 0x7F) - 1], &p->packet[10], len - 11);
|
||||
mPayload[iv->id].len[(*pid & 0x7F) - 1] = len - 11;
|
||||
}
|
||||
|
||||
if ((*pid & 0x80) == 0x80)
|
||||
{ // Last packet
|
||||
if ((*pid & 0x7f) > mPayload[iv->id].maxPackId)
|
||||
{
|
||||
if ((*pid & 0x80) == 0x80) {
|
||||
// Last packet
|
||||
if ((*pid & 0x7f) > mPayload[iv->id].maxPackId) {
|
||||
mPayload[iv->id].maxPackId = (*pid & 0x7f);
|
||||
if (*pid > 0x81)
|
||||
mLastPacketId = *pid;
|
||||
|
@ -115,35 +121,17 @@ void app::loop(void) {
|
|||
}
|
||||
}
|
||||
}
|
||||
if(NULL != iv && p->packet[0] == (TX_REQ_DEVCONTROL + 0x80)) { // response from dev control command
|
||||
if((NULL != iv) && (p->packet[0] == (TX_REQ_DEVCONTROL + 0x80))) { // response from dev control command
|
||||
mPayload[iv->id].txId = p->packet[0];
|
||||
DPRINTLN(DBG_DEBUG, F("Response from devcontrol request received"));
|
||||
iv->devControlRequest = false;
|
||||
switch (p->packet[12]) {
|
||||
case ActivePowerContr:
|
||||
if (iv->devControlCmd >= ActivePowerContr && iv->devControlCmd <= PFSet) { // ok inverter accepted the set point copy it to dtu eeprom
|
||||
if ((iv->powerLimit[1] & 0xff00) > 0) { // User want to have it persistent
|
||||
mEep->write(ADDR_INV_PWR_LIM + iv->id * 2, iv->powerLimit[0]);
|
||||
mEep->write(ADDR_INV_PWR_LIM_CON + iv->id * 2, iv->powerLimit[1]);
|
||||
updateCrc();
|
||||
mEep->commit();
|
||||
DPRINTLN(DBG_INFO, F("Inverter ") + String(iv->id) + F(" has accepted power limit set point ") + String(iv->powerLimit[0]) + F(" with PowerLimitControl ") + String(iv->powerLimit[1]) + F(", written to dtu eeprom"));
|
||||
} else
|
||||
if ((p->packet[12] == ActivePowerContr) && (p->packet[13] == 0x00)) {
|
||||
if (p->packet[10] == 0x00 && p->packet[11] == 0x00)
|
||||
DPRINTLN(DBG_INFO, F("Inverter ") + String(iv->id) + F(" has accepted power limit set point ") + String(iv->powerLimit[0]) + F(" with PowerLimitControl ") + String(iv->powerLimit[1]));
|
||||
iv->devControlCmd = Init;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
if (iv->devControlCmd == ActivePowerContr) {
|
||||
//case inverter did not accept the sent limit; set back to last stored limit
|
||||
mEep->read(ADDR_INV_PWR_LIM + iv->id * 2, (uint16_t *)&(iv->powerLimit[0]));
|
||||
mEep->read(ADDR_INV_PWR_LIM_CON + iv->id * 2, (uint16_t *)&(iv->powerLimit[1]));
|
||||
DPRINTLN(DBG_INFO, F("Inverter has not accepted power limit set point"));
|
||||
else
|
||||
DPRINTLN(DBG_INFO, F("Inverter ") + String(iv->id) + F(" has NOT accepted power limit set point") + String(iv->powerLimit[0]) + F(" with PowerLimitControl ") + String(iv->powerLimit[1]));
|
||||
}
|
||||
iv->devControlCmd = Init;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -186,12 +174,13 @@ void app::loop(void) {
|
|||
for(uint8_t id = 0; id < mSys->getNumInverters(); id++) {
|
||||
Inverter<> *iv = mSys->getInverterByPos(id);
|
||||
if(NULL != iv) {
|
||||
if(iv->isAvailable(mTimestamp)) {
|
||||
record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug);
|
||||
if(iv->isAvailable(mTimestamp, rec)) {
|
||||
DPRINTLN(DBG_INFO, "Inverter: " + String(id));
|
||||
for(uint8_t i = 0; i < iv->listLen; i++) {
|
||||
if(0.0f != iv->getValue(i)) {
|
||||
snprintf(topic, 30, "%s/ch%d/%s", iv->name, iv->assign[i].ch, iv->getFieldName(i));
|
||||
snprintf(val, 10, "%.3f %s", iv->getValue(i), iv->getUnit(i));
|
||||
for(uint8_t i = 0; i < rec->length; i++) {
|
||||
if(0.0f != iv->getValue(i, rec)) {
|
||||
snprintf(topic, 30, "%s/ch%d/%s", iv->name, rec->assign[i].ch, iv->getFieldName(i, rec));
|
||||
snprintf(val, 10, "%.3f %s", iv->getValue(i, rec), iv->getUnit(i, rec));
|
||||
DPRINTLN(DBG_INFO, String(topic) + ": " + String(val));
|
||||
}
|
||||
yield();
|
||||
|
@ -218,8 +207,8 @@ void app::loop(void) {
|
|||
int8_t maxLoop = MAX_NUM_INVERTERS;
|
||||
Inverter<> *iv = mSys->getInverterByPos(mSendLastIvId);
|
||||
do {
|
||||
if(NULL != iv)
|
||||
mPayload[iv->id].requested = false;
|
||||
//if(NULL != iv)
|
||||
// mPayload[iv->id].requested = false;
|
||||
mSendLastIvId = ((MAX_NUM_INVERTERS-1) == mSendLastIvId) ? 0 : mSendLastIvId + 1;
|
||||
iv = mSys->getInverterByPos(mSendLastIvId);
|
||||
} while((NULL == iv) && ((maxLoop--) > 0));
|
||||
|
@ -229,11 +218,14 @@ void app::loop(void) {
|
|||
processPayload(false);
|
||||
|
||||
if(!mPayload[iv->id].complete) {
|
||||
mRxFailed++;
|
||||
if(0 == mPayload[iv->id].maxPackId)
|
||||
mStat.rxFailNoAnser++;
|
||||
else
|
||||
mStat.rxFail++;
|
||||
|
||||
iv->setQueuedCmdFinished(); // command failed
|
||||
if(mConfig.serialDebug) {
|
||||
if(mConfig.serialDebug)
|
||||
DPRINTLN(DBG_INFO, F("enqueued cmd failed/timeout"));
|
||||
}
|
||||
if(mConfig.serialDebug) {
|
||||
DPRINT(DBG_INFO, F("Inverter #") + String(iv->id) + " ");
|
||||
DPRINTLN(DBG_INFO, F("no Payload received! (retransmits: ") + String(mPayload[iv->id].retransmits) + ")");
|
||||
|
@ -241,19 +233,25 @@ void app::loop(void) {
|
|||
}
|
||||
|
||||
resetPayload(iv);
|
||||
mPayload[iv->id].requested = true;
|
||||
|
||||
yield();
|
||||
if(mConfig.serialDebug)
|
||||
if(mConfig.serialDebug) {
|
||||
DPRINTLN(DBG_DEBUG, F("app:loop WiFi WiFi.status ") + String(WiFi.status()));
|
||||
DPRINTLN(DBG_INFO, F("Requesting Inverter SN ") + String(iv->serial.u64, HEX));
|
||||
if(iv->devControlRequest && (iv->powerLimit[0] > 0) && (NoPowerLimit != iv->powerLimit[1])) { // prevent to "switch off"
|
||||
}
|
||||
if(iv->devControlRequest) {
|
||||
if(mConfig.serialDebug)
|
||||
DPRINTLN(DBG_INFO, F("Devcontrol request ") + String(iv->devControlCmd) + F(" power limit ") + String(iv->powerLimit[0]));
|
||||
mSys->Radio.sendControlPacket(iv->radioId.u64, iv->devControlCmd ,iv->powerLimit);
|
||||
mSys->Radio.sendControlPacket(iv->radioId.u64, iv->devControlCmd, iv->powerLimit);
|
||||
mPayload[iv->id].txCmd = iv->devControlCmd;
|
||||
iv->clearCmdQueue();
|
||||
iv->enqueCommand<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;
|
||||
}
|
||||
}
|
||||
|
@ -283,12 +281,12 @@ bool app::buildPayload(uint8_t id) {
|
|||
for(uint8_t i = 0; i < mPayload[id].maxPackId; i ++) {
|
||||
if(mPayload[id].len[i] > 0) {
|
||||
if(i == (mPayload[id].maxPackId-1)) {
|
||||
crc = Hoymiles::crc16(mPayload[id].data[i], mPayload[id].len[i] - 2, crc);
|
||||
crc = Ahoy::crc16(mPayload[id].data[i], mPayload[id].len[i] - 2, crc);
|
||||
crcRcv = (mPayload[id].data[i][mPayload[id].len[i] - 2] << 8)
|
||||
| (mPayload[id].data[i][mPayload[id].len[i] - 1]);
|
||||
}
|
||||
else
|
||||
crc = Hoymiles::crc16(mPayload[id].data[i], mPayload[id].len[i], crc);
|
||||
crc = Ahoy::crc16(mPayload[id].data[i], mPayload[id].len[i], crc);
|
||||
}
|
||||
yield();
|
||||
}
|
||||
|
@ -305,25 +303,32 @@ void app::processPayload(bool retransmit) {
|
|||
boolean doMQTT = false;
|
||||
#endif
|
||||
|
||||
DPRINTLN(DBG_VERBOSE, F("app::processPayload"));
|
||||
//DPRINTLN(DBG_INFO, F("processPayload"));
|
||||
for(uint8_t id = 0; id < mSys->getNumInverters(); id++) {
|
||||
Inverter<> *iv = mSys->getInverterByPos(id);
|
||||
if(NULL != iv) {
|
||||
if(mPayload[iv->id].txId != (TX_REQ_INFO + 0x80)) {
|
||||
if((mPayload[iv->id].txId != (TX_REQ_INFO + 0x80)) && (0 != mPayload[iv->id].txId)) {
|
||||
// no processing needed if txId is not 0x95
|
||||
//DPRINTLN(DBG_INFO, F("processPayload - set complete, txId: ") + String(mPayload[iv->id].txId, HEX));
|
||||
mPayload[iv->id].complete = true;
|
||||
}
|
||||
|
||||
if(!mPayload[iv->id].complete ) {
|
||||
if(!buildPayload(iv->id)) {
|
||||
if(!buildPayload(iv->id)) { // payload not complete
|
||||
if(mPayload[iv->id].requested) {
|
||||
if(retransmit) {
|
||||
if(iv->devControlCmd == Restart || iv->devControlCmd == CleanState_LockAndAlarm ) {
|
||||
// This is required to prevent retransmissions without answer.
|
||||
DPRINTLN(DBG_INFO, F("Prevent retransmit on Restart / CleanState_LockAndAlarm..."));
|
||||
mPayload[iv->id].retransmits = mConfig.maxRetransPerPyld;
|
||||
} else {
|
||||
if(mPayload[iv->id].retransmits < mConfig.maxRetransPerPyld) {
|
||||
mPayload[iv->id].retransmits++;
|
||||
if(mPayload[iv->id].maxPackId != 0) {
|
||||
for(uint8_t i = 0; i < (mPayload[iv->id].maxPackId-1); i ++) {
|
||||
for(uint8_t i = 0; i < (mPayload[iv->id].maxPackId-1); i++) {
|
||||
if(mPayload[iv->id].len[i] == 0) {
|
||||
if(mConfig.serialDebug)
|
||||
DPRINTLN(DBG_ERROR, F("while retrieving data: Frame ") + String(i+1) + F(" missing: Request Retransmit"));
|
||||
DPRINTLN(DBG_WARN, F("while retrieving data: Frame ") + String(i+1) + F(" missing: Request Retransmit"));
|
||||
mSys->Radio.sendCmdPacket(iv->radioId.u64, TX_REQ_INFO, (SINGLE_FRAME+i), true);
|
||||
break; // only retransmit one frame per loop
|
||||
}
|
||||
|
@ -332,20 +337,29 @@ void app::processPayload(bool retransmit) {
|
|||
}
|
||||
else {
|
||||
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)
|
||||
mSys->Radio.sendCmdPacket(iv->radioId.u64, TX_REQ_INFO, mLastPacketId, true);
|
||||
else
|
||||
mSys->Radio.sendTimePacket(iv->radioId.u64, iv->getQueuedCmd(), mPayload[iv->id].ts,iv->alarmMesIndex);
|
||||
else {
|
||||
mPayload[iv->id].txCmd = iv->getQueuedCmd();
|
||||
mSys->Radio.sendTimePacket(iv->radioId.u64, mPayload[iv->id].txCmd, mPayload[iv->id].ts, iv->alarmMesIndex);
|
||||
}
|
||||
}
|
||||
mSys->Radio.switchRxCh(100);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
}
|
||||
else { // payload complete
|
||||
DPRINTLN(DBG_INFO, F("procPyld: cmd: ") + String(mPayload[iv->id].txCmd));
|
||||
DPRINTLN(DBG_INFO, F("procPyld: txid: 0x") + String(mPayload[iv->id].txId, HEX));
|
||||
DPRINTLN(DBG_DEBUG, F("procPyld: max: ") + String(mPayload[iv->id].maxPackId));
|
||||
record_t<> *rec = iv->getRecordStruct(mPayload[iv->id].txCmd); // choose the parser
|
||||
mPayload[iv->id].complete = true;
|
||||
iv->ts = mPayload[iv->id].ts;
|
||||
if(mPayload[iv->id].txId == (TX_REQ_INFO + 0x80))
|
||||
mStat.rxSuccess++;
|
||||
|
||||
uint8_t payload[128];
|
||||
uint8_t offs = 0;
|
||||
|
||||
|
@ -361,44 +375,109 @@ void app::processPayload(bool retransmit) {
|
|||
DPRINT(DBG_INFO, F("Payload (") + String(offs) + "): ");
|
||||
mSys->Radio.dumpBuf(NULL, payload, offs);
|
||||
}
|
||||
mRxSuccess++;
|
||||
|
||||
iv->getAssignment(); // choose the parser
|
||||
for(uint8_t i = 0; i < iv->listLen; i++) {
|
||||
iv->addValue(i, payload); // cmd value decides which parser is used to decode payload
|
||||
if(NULL == rec)
|
||||
DPRINTLN(DBG_ERROR, F("record is NULL!"));
|
||||
else {
|
||||
rec->ts = mPayload[iv->id].ts;
|
||||
for(uint8_t i = 0; i < rec->length; i++) {
|
||||
iv->addValue(i, payload, rec);
|
||||
yield();
|
||||
}
|
||||
iv->doCalculations(); // cmd value decides which parser is used to decode payload
|
||||
|
||||
iv->setQueuedCmdFinished();
|
||||
iv->doCalculations();
|
||||
|
||||
// MQTT send out
|
||||
if(mMqttActive) {
|
||||
char topic[30], val[10];
|
||||
for (uint8_t id = 0; id < mSys->getNumInverters(); id++)
|
||||
{
|
||||
record_t<> *recRealtime = iv->getRecordStruct(RealTimeRunData_Debug);
|
||||
char topic[32 + MAX_NAME_LENGTH], val[32];
|
||||
float total[4];
|
||||
memset(total, 0, sizeof(float) * 4);
|
||||
for (uint8_t id = 0; id < mSys->getNumInverters(); id++) {
|
||||
Inverter<> *iv = mSys->getInverterByPos(id);
|
||||
if (NULL != iv)
|
||||
{
|
||||
if (iv->isAvailable(mTimestamp))
|
||||
{
|
||||
for (uint8_t i = 0; i < iv->listLen; i++)
|
||||
{
|
||||
snprintf(topic, 30, "%s/ch%d/%s", iv->name, iv->assign[i].ch, fields[iv->assign[i].fieldId]);
|
||||
snprintf(val, 10, "%.3f", iv->getValue(i));
|
||||
if (NULL != iv) {
|
||||
if (iv->isAvailable(mTimestamp, rec)) {
|
||||
for (uint8_t i = 0; i < rec->length; i++) {
|
||||
snprintf(topic, 32 + MAX_NAME_LENGTH, "%s/ch%d/%s", iv->name, rec->assign[i].ch, fields[rec->assign[i].fieldId]);
|
||||
snprintf(val, 10, "%.3f", iv->getValue(i, rec));
|
||||
mMqtt.sendMsg(topic, val);
|
||||
if(recRealtime == rec) {
|
||||
if(CH0 == rec->assign[i].ch) {
|
||||
switch(rec->assign[i].fieldId) {
|
||||
case FLD_PAC: total[0] += iv->getValue(i, rec); break;
|
||||
case FLD_YT: total[1] += iv->getValue(i, rec); break;
|
||||
case FLD_YD: total[2] += iv->getValue(i, rec); break;
|
||||
case FLD_PDC: total[3] += iv->getValue(i, rec); break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(iv->isProducing(mTimestamp, rec)){
|
||||
snprintf(topic, 32 + MAX_NAME_LENGTH, "%s/available_text", iv->name);
|
||||
snprintf(val, 32, DEF_MQTT_IV_MESSAGE_INVERTER_AVAIL_AND_PRODUCED);
|
||||
mMqtt.sendMsg(topic, val);
|
||||
snprintf(topic, 32 + MAX_NAME_LENGTH, "%s/available", iv->name);
|
||||
snprintf(val, 32, "2");
|
||||
mMqtt.sendMsg(topic, val);
|
||||
} else {
|
||||
snprintf(topic, 32 + MAX_NAME_LENGTH, "%s/available_text", iv->name);
|
||||
snprintf(val, 32, DEF_MQTT_IV_MESSAGE_INVERTER_AVAIL_AND_NOT_PRODUCED);
|
||||
mMqtt.sendMsg(topic, val);
|
||||
snprintf(topic, 32 + MAX_NAME_LENGTH, "%s/available", iv->name);
|
||||
snprintf(val, 32, "1");
|
||||
mMqtt.sendMsg(topic, val);
|
||||
}
|
||||
|
||||
snprintf(topic, 32 + MAX_NAME_LENGTH, "%s/last_success", iv->name);
|
||||
snprintf(val, 48, "%i", iv->getLastTs(rec) * 1000);
|
||||
mMqtt.sendMsg(topic, val);
|
||||
|
||||
yield();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// total values (sum of all inverters)
|
||||
if(recRealtime == rec) {
|
||||
if(mSys->getNumInverters() > 1) {
|
||||
uint8_t fieldId = 0;
|
||||
for (uint8_t i = 0; i < 4; i++) {
|
||||
switch(i) {
|
||||
case 0: fieldId = FLD_PAC; break;
|
||||
case 1: fieldId = FLD_YT; break;
|
||||
case 2: fieldId = FLD_YD; break;
|
||||
case 3: fieldId = FLD_PDC; break;
|
||||
}
|
||||
snprintf(topic, 32 + MAX_NAME_LENGTH, "total/%s", fields[fieldId]);
|
||||
snprintf(val, 10, "%.3f", total[i]);
|
||||
mMqtt.sendMsg(topic, val);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
iv->setQueuedCmdFinished();
|
||||
|
||||
#ifdef __MQTT_AFTER_RX__
|
||||
doMQTT = true;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
if(mMqttActive) {
|
||||
record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug);
|
||||
char topic[32 + MAX_NAME_LENGTH], val[32];
|
||||
if (!iv->isAvailable(mTimestamp, rec) && !iv->isProducing(mTimestamp, rec)){
|
||||
snprintf(topic, 32 + MAX_NAME_LENGTH, "%s/available_text", iv->name);
|
||||
snprintf(val, 32, DEF_MQTT_IV_MESSAGE_NOT_AVAIL_AND_NOT_PRODUCED);
|
||||
mMqtt.sendMsg(topic, val);
|
||||
snprintf(topic, 32 + MAX_NAME_LENGTH, "%s/available", iv->name);
|
||||
snprintf(val, 32, "0");
|
||||
mMqtt.sendMsg(topic, val);
|
||||
}
|
||||
}
|
||||
|
||||
yield();
|
||||
}
|
||||
}
|
||||
|
@ -484,6 +563,11 @@ void app::cbMqtt(char* topic, byte* payload, unsigned int length) {
|
|||
// uint16_t power_factor = std::stoi(strtok(NULL, "/"));
|
||||
DPRINTLN(DBG_INFO, F("Set Power Factor not implemented for inverter ") + String(iv->id) );
|
||||
break;
|
||||
case CleanState_LockAndAlarm: // CleanState lock & alarm
|
||||
iv->devControlCmd = CleanState_LockAndAlarm;
|
||||
DPRINTLN(DBG_INFO, F("CleanState lock & alarm for inverter ") + String(iv->id) );
|
||||
iv->devControlRequest = true;
|
||||
break;
|
||||
default:
|
||||
DPRINTLN(DBG_INFO, "Not implemented");
|
||||
break;
|
||||
|
@ -499,82 +583,6 @@ void app::cbMqtt(char* topic, byte* payload, unsigned int length) {
|
|||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
String app::getStatistics(void) {
|
||||
String content = F("Receive success: ") + String(mRxSuccess) + "\n";
|
||||
content += F("Receive fail: ") + String(mRxFailed) + "\n";
|
||||
content += F("Frames received: ") + String(mFrameCnt) + "\n";
|
||||
content += F("Send Cnt: ") + String(mSys->Radio.mSendCnt) + String("\n\n");
|
||||
|
||||
Inverter<> *iv;
|
||||
for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i++) {
|
||||
iv = mSys->getInverterByPos(i);
|
||||
content += F("Inverter #") + String(i) + F(": ");
|
||||
if(NULL != iv) {
|
||||
bool avail = true;
|
||||
content += String(iv->name) + F(" (v") + String(iv->fwVersion) +F(")") + F(" is ");
|
||||
if(!iv->isAvailable(mTimestamp)) {
|
||||
content += F("not ");
|
||||
avail = false;
|
||||
}
|
||||
content += F("available and is ");
|
||||
if(!iv->isProducing(mTimestamp))
|
||||
content += F("not ");
|
||||
content += F("producing\n");
|
||||
|
||||
if(!avail) {
|
||||
if(iv->getLastTs() > 0)
|
||||
content += F("-> last successful transmission: ") + getDateTimeStr(iv->getLastTs()) + "\n";
|
||||
}
|
||||
}
|
||||
else
|
||||
content += F("n/a\n");
|
||||
}
|
||||
|
||||
if(!mSys->Radio.isChipConnected())
|
||||
content += F("WARNING! your NRF24 module can't be reached, check the wiring and pinout (<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) {
|
||||
return mWifi->getApActive();
|
||||
|
@ -589,7 +597,8 @@ void app::sendMqttDiscoveryConfig(void) {
|
|||
for(uint8_t id = 0; id < mSys->getNumInverters(); id++) {
|
||||
Inverter<> *iv = mSys->getInverterByPos(id);
|
||||
if(NULL != iv) {
|
||||
if(iv->isAvailable(mTimestamp) && mMqttConfigSendState[id] != true) {
|
||||
record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug);
|
||||
if(iv->isAvailable(mTimestamp, rec) && mMqttConfigSendState[id] != true) {
|
||||
DynamicJsonDocument deviceDoc(128);
|
||||
deviceDoc["name"] = iv->name;
|
||||
deviceDoc["ids"] = String(iv->serial.u64, HEX);
|
||||
|
@ -599,21 +608,21 @@ void app::sendMqttDiscoveryConfig(void) {
|
|||
JsonObject deviceObj = deviceDoc.as<JsonObject>();
|
||||
DynamicJsonDocument doc(384);
|
||||
|
||||
for(uint8_t i = 0; i < iv->listLen; i++) {
|
||||
if (iv->assign[i].ch == CH0) {
|
||||
snprintf(name, 32, "%s %s", iv->name, iv->getFieldName(i));
|
||||
for(uint8_t i = 0; i < rec->length; i++) {
|
||||
if (rec->assign[i].ch == CH0) {
|
||||
snprintf(name, 32, "%s %s", iv->name, iv->getFieldName(i, rec));
|
||||
} else {
|
||||
snprintf(name, 32, "%s CH%d %s", iv->name, iv->assign[i].ch, iv->getFieldName(i));
|
||||
snprintf(name, 32, "%s CH%d %s", iv->name, rec->assign[i].ch, iv->getFieldName(i, rec));
|
||||
}
|
||||
snprintf(stateTopic, 64, "%s/%s/ch%d/%s", mConfig.mqtt.topic, iv->name, iv->assign[i].ch, iv->getFieldName(i));
|
||||
snprintf(discoveryTopic, 64, "%s/sensor/%s/ch%d_%s/config", MQTT_DISCOVERY_PREFIX, iv->name, iv->assign[i].ch, iv->getFieldName(i));
|
||||
snprintf(uniq_id, 32, "ch%d_%s", iv->assign[i].ch, iv->getFieldName(i));
|
||||
const char* devCls = getFieldDeviceClass(iv->assign[i].fieldId);
|
||||
const char* stateCls = getFieldStateClass(iv->assign[i].fieldId);
|
||||
snprintf(stateTopic, 64, "%s/%s/ch%d/%s", mConfig.mqtt.topic, iv->name, rec->assign[i].ch, iv->getFieldName(i, rec));
|
||||
snprintf(discoveryTopic, 64, "%s/sensor/%s/ch%d_%s/config", MQTT_DISCOVERY_PREFIX, iv->name, rec->assign[i].ch, iv->getFieldName(i, rec));
|
||||
snprintf(uniq_id, 32, "ch%d_%s", rec->assign[i].ch, iv->getFieldName(i, rec));
|
||||
const char* devCls = getFieldDeviceClass(rec->assign[i].fieldId);
|
||||
const char* stateCls = getFieldStateClass(rec->assign[i].fieldId);
|
||||
|
||||
doc["name"] = name;
|
||||
doc["stat_t"] = stateTopic;
|
||||
doc["unit_of_meas"] = iv->getUnit(i);
|
||||
doc["unit_of_meas"] = iv->getUnit(i, rec);
|
||||
doc["uniq_id"] = String(iv->serial.u64, HEX) + "_" + uniq_id;
|
||||
doc["dev"] = deviceObj;
|
||||
doc["exp_aft"] = mMqttInterval + 5; // add 5 sec if connection is bad or ESP too slow
|
||||
|
@ -664,6 +673,7 @@ const char* app::getFieldStateClass(uint8_t fieldId) {
|
|||
void app::resetSystem(void) {
|
||||
mUptimeSecs = 0;
|
||||
mPrevMillis = 0;
|
||||
mUpdateNtp = false;
|
||||
|
||||
mNtpRefreshTicker = 0;
|
||||
mNtpRefreshInterval = NTP_REFRESH_INTERVAL; // [ms]
|
||||
|
@ -691,9 +701,7 @@ void app::resetSystem(void) {
|
|||
|
||||
|
||||
memset(mPayload, 0, (MAX_NUM_INVERTERS * sizeof(invPayload_t)));
|
||||
mRxFailed = 0;
|
||||
mRxSuccess = 0;
|
||||
mFrameCnt = 0;
|
||||
memset(&mStat, 0, sizeof(statistics_t));
|
||||
mLastPacketId = 0x00;
|
||||
}
|
||||
|
||||
|
@ -761,23 +769,6 @@ void app::loadEEpconfig(void) {
|
|||
if(0ULL != invSerial) {
|
||||
iv = mSys->addInverter(name, invSerial, modPwr);
|
||||
if(NULL != iv) { // will run once on every dtu boot
|
||||
mEep->read(ADDR_INV_PWR_LIM + (i * 2),(uint16_t *)&(iv->powerLimit[0]));
|
||||
mEep->read(ADDR_INV_PWR_LIM_CON + (i * 2),(uint16_t *)&(iv->powerLimit[1]));
|
||||
// only set it, if it is changed by user. Default value in the html setup page is -1 = 0xffff
|
||||
// it is "doppelt-gemoppelt" because the inverter shall remember the setting if the dtu makes a power cycle / reboot
|
||||
if (iv->powerLimit[0] != 0xffff) {
|
||||
iv->devControlCmd = ActivePowerContr; // set active power limit
|
||||
DPRINT(DBG_INFO, F("add inverter: ") + String(name) + ", SN: " + String(invSerial, HEX));
|
||||
if(iv->powerLimit[1] != NoPowerLimit) {
|
||||
DBGPRINT(F(", Power Limit: ") + String(iv->powerLimit[0]));
|
||||
if ((iv->powerLimit[1] & 0x0001) == 0x0001)
|
||||
DBGPRINTLN(F(" in %"));
|
||||
else
|
||||
DBGPRINTLN(F(" in Watt"));
|
||||
}
|
||||
else
|
||||
DBGPRINTLN(F(" "));
|
||||
}
|
||||
for(uint8_t j = 0; j < 4; j++) {
|
||||
mEep->read(ADDR_INV_CH_NAME + (i * 4 * MAX_NAME_LENGTH) + j * MAX_NAME_LENGTH, iv->chName[j], MAX_NAME_LENGTH);
|
||||
}
|
||||
|
@ -787,6 +778,12 @@ void app::loadEEpconfig(void) {
|
|||
mMqttInterval += mConfig.sendInterval;
|
||||
}
|
||||
}
|
||||
|
||||
for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i++) {
|
||||
iv = mSys->getInverterByPos(i, false);
|
||||
if(NULL != iv)
|
||||
resetPayload(iv);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -801,8 +798,6 @@ void app::saveValues(void) {
|
|||
for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i ++) {
|
||||
iv = mSys->getInverterByPos(i, false);
|
||||
mEep->write(ADDR_INV_ADDR + (i * 8), iv->serial.u64);
|
||||
mEep->write(ADDR_INV_PWR_LIM + i * 2, iv->powerLimit[0]);
|
||||
mEep->write(ADDR_INV_PWR_LIM_CON + i * 2, iv->powerLimit[1]);
|
||||
mEep->write(ADDR_INV_NAME + (i * MAX_NAME_LENGTH), iv->name, MAX_NAME_LENGTH);
|
||||
// max channel power / name
|
||||
for(uint8_t j = 0; j < 4; j++) {
|
||||
|
@ -812,7 +807,6 @@ void app::saveValues(void) {
|
|||
}
|
||||
|
||||
updateCrc();
|
||||
mEep->commit();
|
||||
}
|
||||
|
||||
|
||||
|
@ -857,13 +851,13 @@ void app::setupMqtt(void) {
|
|||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void app::resetPayload(Inverter<>* iv)
|
||||
{
|
||||
// reset payload data
|
||||
void app::resetPayload(Inverter<>* iv) {
|
||||
DPRINTLN(DBG_INFO, "resetPayload: id: " + String(iv->id));
|
||||
memset(mPayload[iv->id].len, 0, MAX_PAYLOAD_ENTRIES);
|
||||
mPayload[iv->id].txCmd = 0;
|
||||
mPayload[iv->id].retransmits = 0;
|
||||
mPayload[iv->id].maxPackId = 0;
|
||||
mPayload[iv->id].complete = false;
|
||||
mPayload[iv->id].requested = true;
|
||||
mPayload[iv->id].requested = false;
|
||||
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 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 {
|
||||
uint8_t txCmd;
|
||||
uint8_t txId;
|
||||
uint8_t invId;
|
||||
uint32_t ts;
|
||||
|
@ -56,7 +50,6 @@ typedef struct {
|
|||
bool requested;
|
||||
} invPayload_t;
|
||||
|
||||
|
||||
class ahoywifi;
|
||||
class web;
|
||||
|
||||
|
@ -71,8 +64,6 @@ class app {
|
|||
void cbMqtt(char* topic, byte* payload, unsigned int length);
|
||||
void saveValues(void);
|
||||
void resetPayload(Inverter<>* iv);
|
||||
String getStatistics(void);
|
||||
String getJson(void);
|
||||
bool getWifiApActive(void);
|
||||
|
||||
uint8_t getIrqPin(void) {
|
||||
|
@ -104,6 +95,15 @@ class app {
|
|||
return String(str);
|
||||
}
|
||||
|
||||
String getTimeStr(void) {
|
||||
char str[20];
|
||||
if(0 == mTimestamp)
|
||||
sprintf(str, "n/a");
|
||||
else
|
||||
sprintf(str, "%02d:%02d:%02d ", hour(mTimestamp), minute(mTimestamp), second(mTimestamp));
|
||||
return String(str);
|
||||
}
|
||||
|
||||
inline uint32_t getUptime(void) {
|
||||
return mUptimeSecs;
|
||||
}
|
||||
|
@ -112,6 +112,14 @@ class app {
|
|||
return mTimestamp;
|
||||
}
|
||||
|
||||
inline void setTimestamp(uint32_t newTime) {
|
||||
DPRINTLN(DBG_DEBUG, F("setTimestamp: ") + String(newTime));
|
||||
if(0 == newTime)
|
||||
mUpdateNtp = true;
|
||||
else
|
||||
mTimestamp = newTime;
|
||||
}
|
||||
|
||||
void eraseSettings(bool all = false) {
|
||||
//DPRINTLN(DBG_VERBOSE, F("main.h:eraseSettings"));
|
||||
uint8_t buf[64];
|
||||
|
@ -144,7 +152,12 @@ class app {
|
|||
return false;
|
||||
}
|
||||
|
||||
inline bool mqttIsConnected(void) { return mMqtt.isConnected(); }
|
||||
inline bool getSettingsValid(void) { return mSettingsValid; }
|
||||
inline bool getRebootRequestState(void) { return mShowRebootRequest; }
|
||||
|
||||
HmSystemType *mSys;
|
||||
bool mShouldReboot;
|
||||
|
||||
private:
|
||||
void resetSystem(void);
|
||||
|
@ -169,7 +182,7 @@ class app {
|
|||
while(length > 0) {
|
||||
len = (length < 32) ? length : 32;
|
||||
mEep->read(start, buf, len);
|
||||
crc = Hoymiles::crc16(buf, len, crc);
|
||||
crc = Ahoy::crc16(buf, len, crc);
|
||||
start += len;
|
||||
length -= len;
|
||||
}
|
||||
|
@ -234,6 +247,7 @@ class app {
|
|||
|
||||
eep *mEep;
|
||||
uint32_t mTimestamp;
|
||||
bool mUpdateNtp;
|
||||
|
||||
bool mShowRebootRequest;
|
||||
|
||||
|
@ -247,14 +261,11 @@ class app {
|
|||
uint8_t mSendLastIvId;
|
||||
|
||||
invPayload_t mPayload[MAX_NUM_INVERTERS];
|
||||
uint32_t mRxFailed;
|
||||
uint32_t mRxSuccess;
|
||||
uint32_t mFrameCnt;
|
||||
statistics_t mStat;
|
||||
uint8_t mLastPacketId;
|
||||
|
||||
// timer
|
||||
uint32_t mTicker;
|
||||
|
||||
uint32_t mRxTicker;
|
||||
|
||||
// mqtt
|
||||
|
|
|
@ -45,17 +45,14 @@
|
|||
#define DEF_RF24_CE_PIN 2
|
||||
#define DEF_RF24_IRQ_PIN 0
|
||||
|
||||
// default radio ID
|
||||
#define DTU_RADIO_ID ((uint64_t)0x1234567801ULL)
|
||||
|
||||
// default NRF24 power, possible values (0 - 3)
|
||||
#define DEF_AMPLIFIERPOWER 2
|
||||
#define DEF_AMPLIFIERPOWER 1
|
||||
|
||||
// number of packets hold in buffer
|
||||
#define PACKET_BUFFER_SIZE 30
|
||||
|
||||
// number of configurable inverters
|
||||
#define MAX_NUM_INVERTERS 3
|
||||
#define MAX_NUM_INVERTERS 4
|
||||
|
||||
// default serial interval
|
||||
#define SERIAL_INTERVAL 5
|
||||
|
@ -108,8 +105,10 @@
|
|||
// default MQTT topic
|
||||
#define DEF_MQTT_TOPIC "inverter"
|
||||
|
||||
// changes the style of "/setup" page, visualized = nicer
|
||||
#define LIVEDATA_VISUALIZED
|
||||
//default MQTT Message Inverter Status
|
||||
#define DEF_MQTT_IV_MESSAGE_NOT_AVAIL_AND_NOT_PRODUCED "not available and not producing" // STATUS 0
|
||||
#define DEF_MQTT_IV_MESSAGE_INVERTER_AVAIL_AND_NOT_PRODUCED "available and not producing" // STATUS 1
|
||||
#define DEF_MQTT_IV_MESSAGE_INVERTER_AVAIL_AND_PRODUCED "available and producing" // STATUS 2
|
||||
|
||||
#if __has_include("config_override.h")
|
||||
#include "config_override.h"
|
||||
|
|
|
@ -24,7 +24,4 @@
|
|||
#undef DEF_RF24_IRQ_PIN
|
||||
#define DEF_RF24_IRQ_PIN 16
|
||||
|
||||
#undef DTU_RADIO_ID
|
||||
#define DTU_RADIO_ID ((uint64_t)0x1234567802ULL)
|
||||
|
||||
#endif /*__CONFIG_OVERRIDE_H__*/
|
||||
|
|
|
@ -5,21 +5,19 @@
|
|||
|
||||
#include "crc.h"
|
||||
|
||||
namespace Hoymiles {
|
||||
|
||||
uint8_t crc8(uint8_t buf[], uint8_t len) {
|
||||
namespace Ahoy {
|
||||
uint8_t crc8(uint8_t buf[], uint8_t len) {
|
||||
uint8_t crc = CRC8_INIT;
|
||||
for(uint8_t i = 0; i < len; i++) {
|
||||
crc ^= buf[i];
|
||||
for(uint8_t b = 0; b < 8; b ++) {
|
||||
crc = (crc << 1) ^ ((crc & 0x80) ? CRC8_POLY : 0x00);
|
||||
}
|
||||
yield();
|
||||
}
|
||||
return crc;
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t crc16(uint8_t buf[], uint8_t len, uint16_t start) {
|
||||
uint16_t crc16(uint8_t buf[], uint8_t len, uint16_t start) {
|
||||
uint16_t crc = start;
|
||||
uint8_t shift = 0;
|
||||
|
||||
|
@ -31,8 +29,7 @@ uint16_t crc16(uint8_t buf[], uint8_t len, uint16_t start) {
|
|||
if(shift != 0)
|
||||
crc = crc ^ CRC16_MODBUS_POLYNOM;
|
||||
}
|
||||
yield();
|
||||
}
|
||||
return crc;
|
||||
}
|
||||
}
|
||||
} // namespace Hoymiles
|
|
@ -14,10 +14,8 @@
|
|||
|
||||
#define CRC16_MODBUS_POLYNOM 0xA001
|
||||
|
||||
namespace Hoymiles {
|
||||
|
||||
namespace Ahoy {
|
||||
uint8_t crc8(uint8_t buf[], uint8_t len);
|
||||
uint16_t crc16(uint8_t buf[], uint8_t len, uint16_t start = 0xffff);
|
||||
|
||||
}
|
||||
#endif /*__CRC_H__*/
|
||||
|
|
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_MINOR 5
|
||||
#define VERSION_PATCH 17
|
||||
#define VERSION_PATCH 19
|
||||
|
||||
|
||||
//-------------------------------------
|
||||
|
@ -57,21 +57,15 @@ typedef enum {
|
|||
Init = 0xff
|
||||
} DevControlCmdType;
|
||||
|
||||
typedef enum { // ToDo: to be verified by field tests
|
||||
NoPowerLimit = 0xffff, // ahoy internal value, no hoymiles value!
|
||||
typedef enum {
|
||||
AbsolutNonPersistent = 0UL, // 0x0000
|
||||
RelativNonPersistent = 1UL, // 0x0001
|
||||
AbsolutPersistent = 256UL, // 0x0100
|
||||
RelativPersistent = 257UL // 0x0101
|
||||
} PowerLimitControlType;
|
||||
|
||||
// minimum serial interval
|
||||
#define MIN_SERIAL_INTERVAL 5
|
||||
|
||||
// minimum send interval
|
||||
#define MIN_SEND_INTERVAL 15
|
||||
|
||||
// minimum mqtt interval
|
||||
#define MIN_MQTT_INTERVAL 60
|
||||
|
||||
//-------------------------------------
|
||||
|
@ -90,27 +84,16 @@ typedef enum { // ToDo: to be verified by field tests
|
|||
#define INV_MAX_RTRY_LEN 1 // uint8_t
|
||||
#define INV_PWR_LIM_LEN MAX_NUM_INVERTERS * 2 // uint16_t
|
||||
|
||||
#define PINOUT_LEN 3 // 3 pins: CS, CE, IRQ
|
||||
|
||||
#define RF24_AMP_PWR_LEN 1
|
||||
|
||||
#define NTP_ADDR_LEN 32 // DNS Name
|
||||
#define NTP_PORT_LEN 2 // uint16_t
|
||||
|
||||
#define MQTT_ADDR_LEN 32 // DNS Name
|
||||
#define MQTT_USER_LEN 16
|
||||
#define MQTT_PWD_LEN 32
|
||||
#define MQTT_TOPIC_LEN 32
|
||||
#define MQTT_INTERVAL_LEN 2 // uint16_t
|
||||
#define MQTT_PORT_LEN 2 // uint16_t
|
||||
#define MQTT_DISCOVERY_PREFIX "homeassistant"
|
||||
#define MQTT_MAX_PACKET_SIZE 384
|
||||
#define MQTT_RECONNECT_DELAY 5000
|
||||
|
||||
#define SER_ENABLE_LEN 1 // uint8_t
|
||||
#define SER_DEBUG_LEN 1 // uint8_t
|
||||
#define SER_INTERVAL_LEN 2 // uint16_t
|
||||
|
||||
#pragma pack(push) // push current alignment to stack
|
||||
#pragma pack(1) // set alignment to 1 byte boundary
|
||||
typedef struct {
|
||||
|
@ -119,8 +102,10 @@ typedef struct {
|
|||
char user[MQTT_USER_LEN];
|
||||
char pwd[MQTT_PWD_LEN];
|
||||
char topic[MQTT_TOPIC_LEN];
|
||||
} /*__attribute__((__packed__))*/ mqttConfig_t;
|
||||
} mqttConfig_t;
|
||||
#pragma pack(pop) // restore original alignment from stack
|
||||
|
||||
|
||||
typedef struct {
|
||||
char deviceName[DEVNAME_LEN];
|
||||
|
||||
|
@ -151,9 +136,16 @@ typedef struct {
|
|||
uint16_t serialInterval;
|
||||
bool serialShowIv;
|
||||
bool serialDebug;
|
||||
} /*__attribute__((__packed__))*/ config_t;
|
||||
} config_t;
|
||||
#pragma pack(pop) // restore original alignment from stack
|
||||
|
||||
typedef struct {
|
||||
uint32_t rxFail;
|
||||
uint32_t rxFailNoAnser;
|
||||
uint32_t rxSuccess;
|
||||
uint32_t frmCnt;
|
||||
} statistics_t;
|
||||
|
||||
|
||||
#define CFG_MQTT_LEN MQTT_ADDR_LEN + 2 + MQTT_USER_LEN + MQTT_PWD_LEN +MQTT_TOPIC_LEN
|
||||
#define CFG_SYS_LEN DEVNAME_LEN + SSID_LEN + PWD_LEN + 1
|
||||
|
@ -173,10 +165,8 @@ typedef struct {
|
|||
#define ADDR_INV_CH_NAME ADDR_INV_CH_PWR + INV_CH_CH_PWR_LEN
|
||||
#define ADDR_INV_INTERVAL ADDR_INV_CH_NAME + INV_CH_CH_NAME_LEN
|
||||
#define ADDR_INV_MAX_RTRY ADDR_INV_INTERVAL + INV_INTERVAL_LEN
|
||||
#define ADDR_INV_PWR_LIM ADDR_INV_MAX_RTRY + INV_MAX_RTRY_LEN
|
||||
#define ADDR_INV_PWR_LIM_CON ADDR_INV_PWR_LIM + INV_PWR_LIM_LEN
|
||||
|
||||
#define ADDR_NEXT ADDR_INV_PWR_LIM_CON + INV_PWR_LIM_LEN
|
||||
#define ADDR_NEXT ADDR_INV_MAX_RTRY + INV_INTERVAL_LEN
|
||||
|
||||
|
||||
#define ADDR_SETTINGS_CRC ADDR_NEXT + 2
|
||||
|
|
|
@ -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
|
||||
enum {UNIT_V = 0, UNIT_A, UNIT_W, UNIT_WH, UNIT_KWH, UNIT_HZ, UNIT_C, UNIT_PCT, UNIT_VA, UNIT_NONE};
|
||||
const char* const units[] = {"V", "A", "W", "Wh", "kWh", "Hz", "°C", "%","VAr",""};
|
||||
|
||||
enum {UNIT_V = 0, UNIT_A, UNIT_W, UNIT_WH, UNIT_KWH, UNIT_HZ, UNIT_C, UNIT_PCT, UNIT_VAR, UNIT_NONE};
|
||||
const char* const units[] = {"V", "A", "W", "Wh", "kWh", "Hz", "°C", "%", "var", ""};
|
||||
|
||||
// field types
|
||||
enum {FLD_UDC = 0, FLD_IDC, FLD_PDC, FLD_YD, FLD_YW, FLD_YT,
|
||||
FLD_UAC, FLD_IAC, FLD_PAC, FLD_F, FLD_T, FLD_PCT, FLD_EFF,
|
||||
FLD_IRR, FLD_PRA,FLD_ALARM_MES_ID,FLD_FW_VERSION,FLD_FW_BUILD_YEAR,
|
||||
FLD_FW_BUILD_MONTH_DAY,FLD_HW_ID,FLD_ACT_PWR_LIMIT,FLD_LAST_ALARM_CODE};
|
||||
FLD_UAC, FLD_IAC, FLD_PAC, FLD_F, FLD_T, FLD_PF, FLD_EFF,
|
||||
FLD_IRR, FLD_Q, FLD_EVT, FLD_FW_VERSION, FLD_FW_BUILD_YEAR,
|
||||
FLD_FW_BUILD_MONTH_DAY, FLD_FW_BUILD_HOUR_MINUTE, FLD_HW_ID,
|
||||
FLD_ACT_ACTIVE_PWR_LIMIT, /*FLD_ACT_REACTIVE_PWR_LIMIT, FLD_ACT_PF,*/ FLD_LAST_ALARM_CODE};
|
||||
|
||||
const char* const fields[] = {"U_DC", "I_DC", "P_DC", "YieldDay", "YieldWeek", "YieldTotal",
|
||||
"U_AC", "I_AC", "P_AC", "Freq", "Temp", "Pct", "Efficiency", "Irradiation","P_ACr",
|
||||
"ALARM_MES_ID","FWVersion","FWBuildYear","FWBuildMonthDay","HWPartId","PowerLimit","LastAlarmCode"};
|
||||
"U_AC", "I_AC", "P_AC", "F_AC", "Temp", "PF_AC", "Efficiency", "Irradiation","Q_AC",
|
||||
"ALARM_MES_ID","FWVersion","FWBuildYear","FWBuildMonthDay","FWBuildHourMinute","HWPartId",
|
||||
"active PowerLimit", /*"reactive PowerLimit","Powerfactor",*/ "LastAlarmCode"};
|
||||
const char* const notAvail = "n/a";
|
||||
|
||||
// mqtt discovery device classes
|
||||
enum {DEVICE_CLS_NONE = 0, DEVICE_CLS_CURRENT, DEVICE_CLS_ENERGY, DEVICE_CLS_PWR, DEVICE_CLS_VOLTAGE, DEVICE_CLS_FREQ, DEVICE_CLS_TEMP};
|
||||
|
@ -53,7 +55,7 @@ const byteAssign_fieldDeviceClass deviceFieldAssignment[] = {
|
|||
{FLD_PAC, DEVICE_CLS_PWR, STATE_CLS_MEASUREMENT},
|
||||
{FLD_F, DEVICE_CLS_FREQ, STATE_CLS_NONE},
|
||||
{FLD_T, DEVICE_CLS_TEMP, STATE_CLS_MEASUREMENT},
|
||||
{FLD_PCT, DEVICE_CLS_NONE, STATE_CLS_NONE},
|
||||
{FLD_PF, DEVICE_CLS_NONE, STATE_CLS_NONE},
|
||||
{FLD_EFF, DEVICE_CLS_NONE, STATE_CLS_NONE},
|
||||
{FLD_IRR, DEVICE_CLS_NONE, STATE_CLS_NONE}
|
||||
};
|
||||
|
@ -92,12 +94,15 @@ const byteAssign_t InfoAssignment[] = {
|
|||
{ FLD_FW_VERSION, UNIT_NONE, CH0, 0, 2, 1 },
|
||||
{ FLD_FW_BUILD_YEAR, UNIT_NONE, CH0, 2, 2, 1 },
|
||||
{ FLD_FW_BUILD_MONTH_DAY, UNIT_NONE, CH0, 4, 2, 1 },
|
||||
{ FLD_FW_BUILD_HOUR_MINUTE, UNIT_NONE, CH0, 6, 2, 1 },
|
||||
{ FLD_HW_ID, UNIT_NONE, CH0, 8, 2, 1 }
|
||||
};
|
||||
#define HMINFO_LIST_LEN (sizeof(InfoAssignment) / sizeof(byteAssign_t))
|
||||
|
||||
const byteAssign_t SystemConfigParaAssignment[] = {
|
||||
{ FLD_ACT_PWR_LIMIT, UNIT_PCT, CH0, 2, 2, 10 }
|
||||
{ FLD_ACT_ACTIVE_PWR_LIMIT, UNIT_PCT, CH0, 2, 2, 10 }/*,
|
||||
{ FLD_ACT_REACTIVE_PWR_LIMIT, UNIT_PCT, CH0, 4, 2, 10 },
|
||||
{ FLD_ACT_PF, UNIT_NONE, CH0, 6, 2, 1000 }*/
|
||||
};
|
||||
#define HMSYSTEM_LIST_LEN (sizeof(SystemConfigParaAssignment) / sizeof(byteAssign_t))
|
||||
|
||||
|
@ -122,10 +127,11 @@ const byteAssign_t hm1chAssignment[] = {
|
|||
{ FLD_UAC, UNIT_V, CH0, 14, 2, 10 },
|
||||
{ FLD_IAC, UNIT_A, CH0, 22, 2, 100 },
|
||||
{ FLD_PAC, UNIT_W, CH0, 18, 2, 10 },
|
||||
{ FLD_PRA, UNIT_VA, CH0, 20, 2, 10 },
|
||||
{ FLD_Q, UNIT_VAR, CH0, 20, 2, 10 },
|
||||
{ FLD_F, UNIT_HZ, CH0, 16, 2, 100 },
|
||||
{ FLD_PF, UNIT_NONE, CH0, 24, 2, 1000 },
|
||||
{ FLD_T, UNIT_C, CH0, 26, 2, 10 },
|
||||
{ FLD_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_YT, UNIT_KWH, CH0, CALC_YT_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_IAC, UNIT_A, CH0, 34, 2, 100 },
|
||||
{ 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_PF, UNIT_NONE, CH0, 36, 2, 1000 },
|
||||
{ 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_YT, UNIT_KWH, CH0, CALC_YT_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_IAC, UNIT_A, CH0, 54, 2, 100 },
|
||||
{ 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_PCT, UNIT_PCT, CH0, 56, 2, 10 },
|
||||
{ FLD_PF, UNIT_NONE, CH0, 56, 2, 1000 },
|
||||
{ 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_YT, UNIT_KWH, CH0, CALC_YT_CH0, 0, CMD_CALC },
|
||||
{ FLD_PDC, UNIT_W, CH0, CALC_PDC_CH0, 0, CMD_CALC },
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
*/
|
||||
|
||||
// forward declaration of class
|
||||
template <class RECORDTYPE=float>
|
||||
template <class REC_TYP=float>
|
||||
class Inverter;
|
||||
|
||||
|
||||
|
@ -55,17 +55,23 @@ struct calcFunc_t {
|
|||
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 {
|
||||
public:
|
||||
CommandAbstract(uint8_t txType = 0, uint8_t cmd = 0){
|
||||
CommandAbstract(uint8_t txType = 0, uint8_t cmd = 0) {
|
||||
_TxType = txType;
|
||||
_Cmd = cmd;
|
||||
};
|
||||
virtual ~CommandAbstract() {};
|
||||
|
||||
const uint8_t getCmd()
|
||||
{
|
||||
const uint8_t getCmd() {
|
||||
return _Cmd;
|
||||
}
|
||||
|
||||
|
@ -75,7 +81,7 @@ class CommandAbstract {
|
|||
};
|
||||
|
||||
class InfoCommand : public CommandAbstract {
|
||||
public:
|
||||
public:
|
||||
InfoCommand(uint8_t cmd){
|
||||
_TxType = 0x15;
|
||||
_Cmd = cmd;
|
||||
|
@ -94,34 +100,33 @@ const calcFunc_t<T> calcFunctions[] = {
|
|||
};
|
||||
|
||||
|
||||
template <class RECORDTYPE>
|
||||
template <class REC_TYP>
|
||||
class Inverter {
|
||||
public:
|
||||
uint8_t id; // unique id
|
||||
char name[MAX_NAME_LENGTH]; // human readable name, eg. "HM-600.1"
|
||||
uint8_t type; // integer which refers to inverter type
|
||||
byteAssign_t* assign; // type of inverter
|
||||
uint8_t listLen; // length of assignments
|
||||
uint16_t alarmMesIndex; // Last recorded Alarm Message Index
|
||||
uint16_t fwVersion; // Firmware Version from Info Command Request
|
||||
uint16_t powerLimit[2]; // limit power output
|
||||
uint16_t actPowerLimit; //
|
||||
float actPowerLimit; // actual power limit
|
||||
uint8_t devControlCmd; // carries the requested cmd
|
||||
bool devControlRequest; // true if change needed
|
||||
serial_u serial; // serial number as on barcode
|
||||
serial_u radioId; // id converted to modbus
|
||||
uint8_t channels; // number of PV channels (1-4)
|
||||
uint32_t ts; // timestamp of last received payload
|
||||
RECORDTYPE *record; // pointer for values
|
||||
record_t<REC_TYP> recordMeas; // structure for measured 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)
|
||||
char chName[4][MAX_NAME_LENGTH]; // human readable name for channel
|
||||
char chName[4][MAX_NAME_LENGTH]; // human readable name for channels
|
||||
String lastAlarmMsg;
|
||||
bool initialized; // needed to check if the inverter was correctly added (ESP32 specific - union types are never null)
|
||||
|
||||
Inverter() {
|
||||
ts = 0;
|
||||
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
|
||||
devControlRequest = false;
|
||||
devControlCmd = InitDataState;
|
||||
|
@ -136,27 +141,26 @@ class Inverter {
|
|||
}
|
||||
|
||||
template <typename T>
|
||||
void enqueCommand(uint8_t cmd)
|
||||
{
|
||||
void enqueCommand(uint8_t cmd) {
|
||||
_commandQueue.push(std::make_shared<T>(cmd));
|
||||
DPRINTLN(DBG_INFO, "enqueuedCmd: " + String(cmd));
|
||||
}
|
||||
|
||||
void setQueuedCmdFinished(){
|
||||
if (!_commandQueue.empty()){
|
||||
void setQueuedCmdFinished() {
|
||||
if (!_commandQueue.empty()) {
|
||||
// Will destroy CommandAbstract Class Object (?)
|
||||
_commandQueue.pop();
|
||||
}
|
||||
}
|
||||
|
||||
void clearCmdQueue(){
|
||||
while (!_commandQueue.empty()){
|
||||
void clearCmdQueue() {
|
||||
while (!_commandQueue.empty()) {
|
||||
// Will destroy CommandAbstract Class Object (?)
|
||||
_commandQueue.pop();
|
||||
}
|
||||
}
|
||||
uint8_t getQueuedCmd()
|
||||
{
|
||||
|
||||
uint8_t getQueuedCmd() {
|
||||
if (_commandQueue.empty()){
|
||||
// Fill with default commands
|
||||
enqueCommand<InfoCommand>(RealTimeRunData_Debug);
|
||||
|
@ -175,394 +179,289 @@ class Inverter {
|
|||
|
||||
void init(void) {
|
||||
DPRINTLN(DBG_VERBOSE, F("hmInverter.h:init"));
|
||||
getAssignment();
|
||||
initAssignment(&recordMeas, RealTimeRunData_Debug);
|
||||
initAssignment(&recordInfo, InverterDevInform_All);
|
||||
initAssignment(&recordConfig, SystemConfigPara);
|
||||
initAssignment(&recordAlarm, AlarmData);
|
||||
toRadioId();
|
||||
record = new RECORDTYPE[listLen];
|
||||
memset(name, 0, MAX_NAME_LENGTH);
|
||||
memset(chName, 0, MAX_NAME_LENGTH * 4);
|
||||
memset(record, 0, sizeof(RECORDTYPE) * listLen);
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
uint8_t getPosByChFld(uint8_t channel, uint8_t fieldId) {
|
||||
uint8_t getPosByChFld(uint8_t channel, uint8_t fieldId, record_t<> *rec) {
|
||||
DPRINTLN(DBG_VERBOSE, F("hmInverter.h:getPosByChFld"));
|
||||
uint8_t pos = 0;
|
||||
for(; pos < listLen; pos++) {
|
||||
if((assign[pos].ch == channel) && (assign[pos].fieldId == fieldId))
|
||||
if(NULL != rec) {
|
||||
for(; pos < rec->length; pos++) {
|
||||
if((rec->assign[pos].ch == channel) && (rec->assign[pos].fieldId == fieldId))
|
||||
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"));
|
||||
return fields[assign[pos].fieldId];
|
||||
if(NULL != rec)
|
||||
return fields[rec->assign[pos].fieldId];
|
||||
return notAvail;
|
||||
}
|
||||
|
||||
const char *getUnit(uint8_t pos) {
|
||||
const char *getUnit(uint8_t pos, record_t<> *rec) {
|
||||
DPRINTLN(DBG_VERBOSE, F("hmInverter.h:getUnit"));
|
||||
return units[assign[pos].unitId];
|
||||
if(NULL != rec)
|
||||
return units[rec->assign[pos].unitId];
|
||||
return notAvail;
|
||||
}
|
||||
|
||||
uint8_t getChannel(uint8_t pos) {
|
||||
uint8_t getChannel(uint8_t pos, record_t<> *rec) {
|
||||
DPRINTLN(DBG_VERBOSE, F("hmInverter.h:getChannel"));
|
||||
return assign[pos].ch;
|
||||
if(NULL != rec)
|
||||
return rec->assign[pos].ch;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void addValue(uint8_t pos, uint8_t buf[]) {
|
||||
void addValue(uint8_t pos, uint8_t buf[], record_t<> *rec) {
|
||||
DPRINTLN(DBG_VERBOSE, F("hmInverter.h:addValue"));
|
||||
uint8_t cmd = getQueuedCmd();
|
||||
uint8_t ptr = assign[pos].start;
|
||||
uint8_t end = ptr + assign[pos].num;
|
||||
uint16_t div = assign[pos].div;
|
||||
if(NULL != rec) {
|
||||
uint8_t ptr = rec->assign[pos].start;
|
||||
uint8_t end = ptr + rec->assign[pos].num;
|
||||
uint16_t div = rec->assign[pos].div;
|
||||
|
||||
if(NULL != rec) {
|
||||
if(CMD_CALC != div) {
|
||||
uint32_t val = 0;
|
||||
do {
|
||||
val <<= 8;
|
||||
val |= buf[ptr];
|
||||
} while(++ptr != end);
|
||||
if ((RECORDTYPE)(div) > 1){
|
||||
record[pos] = (RECORDTYPE)(val) / (RECORDTYPE)(div);
|
||||
if ((REC_TYP)(div) > 1)
|
||||
rec->record[pos] = (REC_TYP)(val) / (REC_TYP)(div);
|
||||
else
|
||||
rec->record[pos] = (REC_TYP)(val);
|
||||
}
|
||||
else {
|
||||
record[pos] = (RECORDTYPE)(val);
|
||||
}
|
||||
|
||||
}
|
||||
if (cmd == RealTimeRunData_Debug) {
|
||||
if(rec == &recordMeas) {
|
||||
DPRINTLN(DBG_VERBOSE, "add real time");
|
||||
|
||||
// get last alarm message index and save it in the inverter object
|
||||
if (getPosByChFld(0, FLD_ALARM_MES_ID) == pos){
|
||||
if (alarmMesIndex < record[pos]){
|
||||
alarmMesIndex = record[pos];
|
||||
if (getPosByChFld(0, FLD_EVT, rec) == pos){
|
||||
if (alarmMesIndex < rec->record[pos]){
|
||||
alarmMesIndex = rec->record[pos];
|
||||
//enqueCommand<InfoCommand>(AlarmUpdate); // What is the function of AlarmUpdate?
|
||||
enqueCommand<InfoCommand>(AlarmData);
|
||||
}
|
||||
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
|
||||
if (getPosByChFld(0, FLD_FW_VERSION) == pos){
|
||||
fwVersion = record[pos];
|
||||
if (getPosByChFld(0, FLD_FW_VERSION, rec) == pos){
|
||||
fwVersion = rec->record[pos];
|
||||
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
|
||||
if (getPosByChFld(0, FLD_ACT_PWR_LIMIT) == pos){
|
||||
actPowerLimit = record[pos];
|
||||
DPRINT(DBG_DEBUG, F("Inverter actual power limit: ") + String(actPowerLimit));
|
||||
if (getPosByChFld(0, FLD_ACT_ACTIVE_PWR_LIMIT, rec) == pos){
|
||||
actPowerLimit = rec->record[pos];
|
||||
DPRINT(DBG_DEBUG, F("Inverter actual power limit: ") + String(actPowerLimit, 1));
|
||||
}
|
||||
}
|
||||
if (cmd == AlarmData){
|
||||
if (getPosByChFld(0, FLD_LAST_ALARM_CODE) == pos){
|
||||
lastAlarmMsg = getAlarmStr(record[pos]);
|
||||
else if (rec->assign == AlarmDataAssignment) {
|
||||
DPRINTLN(DBG_DEBUG, "add alarm");
|
||||
if (getPosByChFld(0, FLD_LAST_ALARM_CODE, rec) == pos){
|
||||
lastAlarmMsg = getAlarmStr(rec->record[pos]);
|
||||
}
|
||||
}
|
||||
else
|
||||
DPRINTLN(DBG_WARN, F("add with unknown assginment"));
|
||||
}
|
||||
else
|
||||
DPRINTLN(DBG_ERROR, F("addValue: assignment not found with cmd 0x"));
|
||||
}
|
||||
|
||||
RECORDTYPE getValue(uint8_t pos) {
|
||||
REC_TYP getValue(uint8_t pos, record_t<> *rec) {
|
||||
DPRINTLN(DBG_VERBOSE, F("hmInverter.h:getValue"));
|
||||
return record[pos];
|
||||
if(NULL == rec)
|
||||
return 0;
|
||||
return rec->record[pos];
|
||||
}
|
||||
|
||||
void doCalculations() {
|
||||
DPRINTLN(DBG_VERBOSE, F("hmInverter.h:doCalculations"));
|
||||
uint8_t cmd = getQueuedCmd();
|
||||
getAssignment();
|
||||
if (cmd == RealTimeRunData_Debug){
|
||||
for(uint8_t i = 0; i < listLen; i++) {
|
||||
if(CMD_CALC == assign[i].div) {
|
||||
record[i] = calcFunctions<RECORDTYPE>[assign[i].start].func(this, assign[i].num);
|
||||
record_t<> *rec = getRecordStruct(RealTimeRunData_Debug);
|
||||
for(uint8_t i = 0; i < rec->length; i++) {
|
||||
if(CMD_CALC == rec->assign[i].div) {
|
||||
rec->record[i] = calcFunctions<REC_TYP>[rec->assign[i].start].func(this, rec->assign[i].num);
|
||||
}
|
||||
yield();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool isAvailable(uint32_t timestamp) {
|
||||
bool isAvailable(uint32_t timestamp, record_t<> *rec) {
|
||||
DPRINTLN(DBG_VERBOSE, F("hmInverter.h:isAvailable"));
|
||||
return ((timestamp - ts) < INACT_THRES_SEC);
|
||||
return ((timestamp - rec->ts) < INACT_THRES_SEC);
|
||||
}
|
||||
|
||||
bool isProducing(uint32_t timestamp) {
|
||||
bool isProducing(uint32_t timestamp, record_t<> *rec) {
|
||||
DPRINTLN(DBG_VERBOSE, F("hmInverter.h:isProducing"));
|
||||
if(isAvailable(timestamp)) {
|
||||
uint8_t pos = getPosByChFld(CH0, FLD_PAC);
|
||||
return (getValue(pos) > INACT_PWR_THRESH);
|
||||
if(isAvailable(timestamp, rec)) {
|
||||
uint8_t pos = getPosByChFld(CH0, FLD_PAC, rec);
|
||||
return (getValue(pos, rec) > INACT_PWR_THRESH);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t getLastTs(void)
|
||||
{
|
||||
uint32_t getLastTs(record_t<> *rec) {
|
||||
DPRINTLN(DBG_VERBOSE, F("hmInverter.h:getLastTs"));
|
||||
return ts;
|
||||
return rec->ts;
|
||||
}
|
||||
|
||||
void getAssignment()
|
||||
{
|
||||
DPRINTLN(DBG_DEBUG, F("hmInverter.h:getAssignment"));
|
||||
// Default assignment;
|
||||
if (INV_TYPE_1CH == type)
|
||||
{
|
||||
listLen = (uint8_t)(HM1CH_LIST_LEN);
|
||||
assign = (byteAssign_t *)hm1chAssignment;
|
||||
record_t<> *getRecordStruct(uint8_t cmd) {
|
||||
switch (cmd) {
|
||||
case RealTimeRunData_Debug: return &recordMeas;
|
||||
case InverterDevInform_All: return &recordInfo;
|
||||
case SystemConfigPara: return &recordConfig;
|
||||
case AlarmData: return &recordAlarm;
|
||||
default: break;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
else if (INV_TYPE_2CH == type)
|
||||
{
|
||||
listLen = (uint8_t)(HM2CH_LIST_LEN);
|
||||
assign = (byteAssign_t *)hm2chAssignment;
|
||||
else if (INV_TYPE_2CH == type) {
|
||||
rec->length = (uint8_t)(HM2CH_LIST_LEN);
|
||||
rec->assign = (byteAssign_t *)hm2chAssignment;
|
||||
channels = 2;
|
||||
}
|
||||
else if (INV_TYPE_4CH == type)
|
||||
{
|
||||
listLen = (uint8_t)(HM4CH_LIST_LEN);
|
||||
assign = (byteAssign_t *)hm4chAssignment;
|
||||
else if (INV_TYPE_4CH == type) {
|
||||
rec->length = (uint8_t)(HM4CH_LIST_LEN);
|
||||
rec->assign = (byteAssign_t *)hm4chAssignment;
|
||||
channels = 4;
|
||||
}
|
||||
else
|
||||
{
|
||||
listLen = 0;
|
||||
else {
|
||||
rec->length = 0;
|
||||
rec->assign = NULL;
|
||||
channels = 0;
|
||||
assign = NULL;
|
||||
}
|
||||
|
||||
switch (getQueuedCmd()) {
|
||||
case RealTimeRunData_Debug:
|
||||
// Do nothing will use default
|
||||
break;
|
||||
case InverterDevInform_All:
|
||||
listLen = (uint8_t)(HMINFO_LIST_LEN);
|
||||
assign = (byteAssign_t *)InfoAssignment;
|
||||
rec->length = (uint8_t)(HMINFO_LIST_LEN);
|
||||
rec->assign = (byteAssign_t *)InfoAssignment;
|
||||
break;
|
||||
case SystemConfigPara:
|
||||
listLen = (uint8_t)(HMSYSTEM_LIST_LEN);
|
||||
assign = (byteAssign_t *)SystemConfigParaAssignment;
|
||||
rec->length = (uint8_t)(HMSYSTEM_LIST_LEN);
|
||||
rec->assign = (byteAssign_t *)SystemConfigParaAssignment;
|
||||
break;
|
||||
case AlarmData:
|
||||
listLen = (uint8_t)(HMALARMDATA_LIST_LEN);
|
||||
assign = (byteAssign_t *)AlarmDataAssignment;
|
||||
rec->length = (uint8_t)(HMALARMDATA_LIST_LEN);
|
||||
rec->assign = (byteAssign_t *)AlarmDataAssignment;
|
||||
break;
|
||||
default:
|
||||
DPRINTLN(DBG_INFO, "Parser not implemented");
|
||||
DPRINTLN(DBG_INFO, F("initAssignment: Parser not implemented"));
|
||||
break;
|
||||
}
|
||||
|
||||
if(0 != rec->length) {
|
||||
rec->record = new REC_TYP[rec->length];
|
||||
memset(rec->record, 0, sizeof(REC_TYP) * rec->length);
|
||||
}
|
||||
String getAlarmStr(u_int16_t alarmCode)
|
||||
{
|
||||
switch (alarmCode)
|
||||
{
|
||||
case 1:
|
||||
return String(F("Inverter start"));
|
||||
break;
|
||||
case 2:
|
||||
return String(F("DTU command failed"));
|
||||
break;
|
||||
case 121:
|
||||
return String(F("Over temperature protection"));
|
||||
break;
|
||||
case 125:
|
||||
return String(F("Grid configuration parameter error"));
|
||||
break;
|
||||
case 126:
|
||||
return String(F("Software error code 126"));
|
||||
break;
|
||||
case 127:
|
||||
return String(F("Firmware error"));
|
||||
break;
|
||||
case 128:
|
||||
return String(F("Software error code 128"));
|
||||
break;
|
||||
case 129:
|
||||
return String(F("Software error code 129"));
|
||||
break;
|
||||
case 130:
|
||||
return String(F("Offline"));
|
||||
break;
|
||||
case 141:
|
||||
return String(F("Grid overvoltage"));
|
||||
break;
|
||||
case 142:
|
||||
return String(F("Average grid overvoltage"));
|
||||
break;
|
||||
case 143:
|
||||
return String(F("Grid undervoltage"));
|
||||
break;
|
||||
case 144:
|
||||
return String(F("Grid overfrequency"));
|
||||
break;
|
||||
case 145:
|
||||
return String(F("Grid underfrequency"));
|
||||
break;
|
||||
case 146:
|
||||
return String(F("Rapid grid frequency change"));
|
||||
break;
|
||||
case 147:
|
||||
return String(F("Power grid outage"));
|
||||
break;
|
||||
case 148:
|
||||
return String(F("Grid disconnection"));
|
||||
break;
|
||||
case 149:
|
||||
return String(F("Island detected"));
|
||||
break;
|
||||
case 205:
|
||||
return String(F("Input port 1 & 2 overvoltage"));
|
||||
break;
|
||||
case 206:
|
||||
return String(F("Input port 3 & 4 overvoltage"));
|
||||
break;
|
||||
case 207:
|
||||
return String(F("Input port 1 & 2 undervoltage"));
|
||||
break;
|
||||
case 208:
|
||||
return String(F("Input port 3 & 4 undervoltage"));
|
||||
break;
|
||||
case 209:
|
||||
return String(F("Port 1 no input"));
|
||||
break;
|
||||
case 210:
|
||||
return String(F("Port 2 no input"));
|
||||
break;
|
||||
case 211:
|
||||
return String(F("Port 3 no input"));
|
||||
break;
|
||||
case 212:
|
||||
return String(F("Port 4 no input"));
|
||||
break;
|
||||
case 213:
|
||||
return String(F("PV-1 & PV-2 abnormal wiring"));
|
||||
break;
|
||||
case 214:
|
||||
return String(F("PV-3 & PV-4 abnormal wiring"));
|
||||
break;
|
||||
case 215:
|
||||
return String(F("PV-1 Input overvoltage"));
|
||||
break;
|
||||
case 216:
|
||||
return String(F("PV-1 Input undervoltage"));
|
||||
break;
|
||||
case 217:
|
||||
return String(F("PV-2 Input overvoltage"));
|
||||
break;
|
||||
case 218:
|
||||
return String(F("PV-2 Input undervoltage"));
|
||||
break;
|
||||
case 219:
|
||||
return String(F("PV-3 Input overvoltage"));
|
||||
break;
|
||||
case 220:
|
||||
return String(F("PV-3 Input undervoltage"));
|
||||
break;
|
||||
case 221:
|
||||
return String(F("PV-4 Input overvoltage"));
|
||||
break;
|
||||
case 222:
|
||||
return String(F("PV-4 Input undervoltage"));
|
||||
break;
|
||||
case 301:
|
||||
return String(F("Hardware error code 301"));
|
||||
break;
|
||||
case 302:
|
||||
return String(F("Hardware error code 302"));
|
||||
break;
|
||||
case 303:
|
||||
return String(F("Hardware error code 303"));
|
||||
break;
|
||||
case 304:
|
||||
return String(F("Hardware error code 304"));
|
||||
break;
|
||||
case 305:
|
||||
return String(F("Hardware error code 305"));
|
||||
break;
|
||||
case 306:
|
||||
return String(F("Hardware error code 306"));
|
||||
break;
|
||||
case 307:
|
||||
return String(F("Hardware error code 307"));
|
||||
break;
|
||||
case 308:
|
||||
return String(F("Hardware error code 308"));
|
||||
break;
|
||||
case 309:
|
||||
return String(F("Hardware error code 309"));
|
||||
break;
|
||||
case 310:
|
||||
return String(F("Hardware error code 310"));
|
||||
break;
|
||||
case 311:
|
||||
return String(F("Hardware error code 311"));
|
||||
break;
|
||||
case 312:
|
||||
return String(F("Hardware error code 312"));
|
||||
break;
|
||||
case 313:
|
||||
return String(F("Hardware error code 313"));
|
||||
break;
|
||||
case 314:
|
||||
return String(F("Hardware error code 314"));
|
||||
break;
|
||||
case 5041:
|
||||
return String(F("Error code-04 Port 1"));
|
||||
break;
|
||||
case 5042:
|
||||
return String(F("Error code-04 Port 2"));
|
||||
break;
|
||||
case 5043:
|
||||
return String(F("Error code-04 Port 3"));
|
||||
break;
|
||||
case 5044:
|
||||
return String(F("Error code-04 Port 4"));
|
||||
break;
|
||||
case 5051:
|
||||
return String(F("PV Input 1 Overvoltage/Undervoltage"));
|
||||
break;
|
||||
case 5052:
|
||||
return String(F("PV Input 2 Overvoltage/Undervoltage"));
|
||||
break;
|
||||
case 5053:
|
||||
return String(F("PV Input 3 Overvoltage/Undervoltage"));
|
||||
break;
|
||||
case 5054:
|
||||
return String(F("PV Input 4 Overvoltage/Undervoltage"));
|
||||
break;
|
||||
case 5060:
|
||||
return String(F("Abnormal bias"));
|
||||
break;
|
||||
case 5070:
|
||||
return String(F("Over temperature protection"));
|
||||
break;
|
||||
case 5080:
|
||||
return String(F("Grid Overvoltage/Undervoltage"));
|
||||
break;
|
||||
case 5090:
|
||||
return String(F("Grid Overfrequency/Underfrequency"));
|
||||
break;
|
||||
case 5100:
|
||||
return String(F("Island detected"));
|
||||
break;
|
||||
case 5120:
|
||||
return String(F("EEPROM reading and writing error"));
|
||||
break;
|
||||
case 5150:
|
||||
return String(F("10 min value grid overvoltage"));
|
||||
break;
|
||||
case 5200:
|
||||
return String(F("Firmware error"));
|
||||
break;
|
||||
case 8310:
|
||||
return String(F("Shut down"));
|
||||
break;
|
||||
case 9000:
|
||||
return String(F("Microinverter is suspected of being stolen"));
|
||||
break;
|
||||
default:
|
||||
return String(F("Unknown"));
|
||||
break;
|
||||
}
|
||||
|
||||
String getAlarmStr(u_int16_t alarmCode) {
|
||||
switch (alarmCode) { // breaks are intentionally missing!
|
||||
case 1: return String(F("Inverter start"));
|
||||
case 2: return String(F("DTU command failed"));
|
||||
case 121: return String(F("Over temperature protection"));
|
||||
case 125: return String(F("Grid configuration parameter error"));
|
||||
case 126: return String(F("Software error code 126"));
|
||||
case 127: return String(F("Firmware error"));
|
||||
case 128: return String(F("Software error code 128"));
|
||||
case 129: return String(F("Software error code 129"));
|
||||
case 130: return String(F("Offline"));
|
||||
case 141: return String(F("Grid overvoltage"));
|
||||
case 142: return String(F("Average grid overvoltage"));
|
||||
case 143: return String(F("Grid undervoltage"));
|
||||
case 144: return String(F("Grid overfrequency"));
|
||||
case 145: return String(F("Grid underfrequency"));
|
||||
case 146: return String(F("Rapid grid frequency change"));
|
||||
case 147: return String(F("Power grid outage"));
|
||||
case 148: return String(F("Grid disconnection"));
|
||||
case 149: return String(F("Island detected"));
|
||||
case 205: return String(F("Input port 1 & 2 overvoltage"));
|
||||
case 206: return String(F("Input port 3 & 4 overvoltage"));
|
||||
case 207: return String(F("Input port 1 & 2 undervoltage"));
|
||||
case 208: return String(F("Input port 3 & 4 undervoltage"));
|
||||
case 209: return String(F("Port 1 no input"));
|
||||
case 210: return String(F("Port 2 no input"));
|
||||
case 211: return String(F("Port 3 no input"));
|
||||
case 212: return String(F("Port 4 no input"));
|
||||
case 213: return String(F("PV-1 & PV-2 abnormal wiring"));
|
||||
case 214: return String(F("PV-3 & PV-4 abnormal wiring"));
|
||||
case 215: return String(F("PV-1 Input overvoltage"));
|
||||
case 216: return String(F("PV-1 Input undervoltage"));
|
||||
case 217: return String(F("PV-2 Input overvoltage"));
|
||||
case 218: return String(F("PV-2 Input undervoltage"));
|
||||
case 219: return String(F("PV-3 Input overvoltage"));
|
||||
case 220: return String(F("PV-3 Input undervoltage"));
|
||||
case 221: return String(F("PV-4 Input overvoltage"));
|
||||
case 222: return String(F("PV-4 Input undervoltage"));
|
||||
case 301: return String(F("Hardware error code 301"));
|
||||
case 302: return String(F("Hardware error code 302"));
|
||||
case 303: return String(F("Hardware error code 303"));
|
||||
case 304: return String(F("Hardware error code 304"));
|
||||
case 305: return String(F("Hardware error code 305"));
|
||||
case 306: return String(F("Hardware error code 306"));
|
||||
case 307: return String(F("Hardware error code 307"));
|
||||
case 308: return String(F("Hardware error code 308"));
|
||||
case 309: return String(F("Hardware error code 309"));
|
||||
case 310: return String(F("Hardware error code 310"));
|
||||
case 311: return String(F("Hardware error code 311"));
|
||||
case 312: return String(F("Hardware error code 312"));
|
||||
case 313: return String(F("Hardware error code 313"));
|
||||
case 314: return String(F("Hardware error code 314"));
|
||||
case 5041: return String(F("Error code-04 Port 1"));
|
||||
case 5042: return String(F("Error code-04 Port 2"));
|
||||
case 5043: return String(F("Error code-04 Port 3"));
|
||||
case 5044: return String(F("Error code-04 Port 4"));
|
||||
case 5051: return String(F("PV Input 1 Overvoltage/Undervoltage"));
|
||||
case 5052: return String(F("PV Input 2 Overvoltage/Undervoltage"));
|
||||
case 5053: return String(F("PV Input 3 Overvoltage/Undervoltage"));
|
||||
case 5054: return String(F("PV Input 4 Overvoltage/Undervoltage"));
|
||||
case 5060: return String(F("Abnormal bias"));
|
||||
case 5070: return String(F("Over temperature protection"));
|
||||
case 5080: return String(F("Grid Overvoltage/Undervoltage"));
|
||||
case 5090: return String(F("Grid Overfrequency/Underfrequency"));
|
||||
case 5100: return String(F("Island detected"));
|
||||
case 5120: return String(F("EEPROM reading and writing error"));
|
||||
case 5150: return String(F("10 min value grid overvoltage"));
|
||||
case 5200: return String(F("Firmware error"));
|
||||
case 8310: return String(F("Shut down"));
|
||||
case 9000: return String(F("Microinverter is suspected of being stolen"));
|
||||
default: return String(F("Unknown"));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -590,10 +489,11 @@ template<class T=float>
|
|||
static T calcYieldTotalCh0(Inverter<> *iv, uint8_t arg0) {
|
||||
DPRINTLN(DBG_VERBOSE, F("hmInverter.h:calcYieldTotalCh0"));
|
||||
if(NULL != iv) {
|
||||
record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug);
|
||||
T yield = 0;
|
||||
for(uint8_t i = 1; i <= iv->channels; i++) {
|
||||
uint8_t pos = iv->getPosByChFld(i, FLD_YT);
|
||||
yield += iv->getValue(pos);
|
||||
uint8_t pos = iv->getPosByChFld(i, FLD_YT, rec);
|
||||
yield += iv->getValue(pos, rec);
|
||||
}
|
||||
return yield;
|
||||
}
|
||||
|
@ -604,10 +504,11 @@ template<class T=float>
|
|||
static T calcYieldDayCh0(Inverter<> *iv, uint8_t arg0) {
|
||||
DPRINTLN(DBG_VERBOSE, F("hmInverter.h:calcYieldDayCh0"));
|
||||
if(NULL != iv) {
|
||||
record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug);
|
||||
T yield = 0;
|
||||
for(uint8_t i = 1; i <= iv->channels; i++) {
|
||||
uint8_t pos = iv->getPosByChFld(i, FLD_YD);
|
||||
yield += iv->getValue(pos);
|
||||
uint8_t pos = iv->getPosByChFld(i, FLD_YD, rec);
|
||||
yield += iv->getValue(pos, rec);
|
||||
}
|
||||
return yield;
|
||||
}
|
||||
|
@ -618,9 +519,10 @@ template<class T=float>
|
|||
static T calcUdcCh(Inverter<> *iv, uint8_t arg0) {
|
||||
DPRINTLN(DBG_VERBOSE, F("hmInverter.h:calcUdcCh"));
|
||||
// arg0 = channel of source
|
||||
for(uint8_t i = 0; i < iv->listLen; i++) {
|
||||
if((FLD_UDC == iv->assign[i].fieldId) && (arg0 == iv->assign[i].ch)) {
|
||||
return iv->getValue(i);
|
||||
record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug);
|
||||
for(uint8_t i = 0; i < rec->length; i++) {
|
||||
if((FLD_UDC == rec->assign[i].fieldId) && (arg0 == rec->assign[i].ch)) {
|
||||
return iv->getValue(i, rec);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -631,10 +533,11 @@ template<class T=float>
|
|||
static T calcPowerDcCh0(Inverter<> *iv, uint8_t arg0) {
|
||||
DPRINTLN(DBG_VERBOSE, F("hmInverter.h:calcPowerDcCh0"));
|
||||
if(NULL != iv) {
|
||||
record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug);
|
||||
T dcPower = 0;
|
||||
for(uint8_t i = 1; i <= iv->channels; i++) {
|
||||
uint8_t pos = iv->getPosByChFld(i, FLD_PDC);
|
||||
dcPower += iv->getValue(pos);
|
||||
uint8_t pos = iv->getPosByChFld(i, FLD_PDC, rec);
|
||||
dcPower += iv->getValue(pos, rec);
|
||||
}
|
||||
return dcPower;
|
||||
}
|
||||
|
@ -645,12 +548,13 @@ template<class T=float>
|
|||
static T calcEffiencyCh0(Inverter<> *iv, uint8_t arg0) {
|
||||
DPRINTLN(DBG_VERBOSE, F("hmInverter.h:calcEfficiencyCh0"));
|
||||
if(NULL != iv) {
|
||||
uint8_t pos = iv->getPosByChFld(CH0, FLD_PAC);
|
||||
T acPower = iv->getValue(pos);
|
||||
record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug);
|
||||
uint8_t pos = iv->getPosByChFld(CH0, FLD_PAC, rec);
|
||||
T acPower = iv->getValue(pos, rec);
|
||||
T dcPower = 0;
|
||||
for(uint8_t i = 1; i <= iv->channels; i++) {
|
||||
pos = iv->getPosByChFld(i, FLD_PDC);
|
||||
dcPower += iv->getValue(pos);
|
||||
pos = iv->getPosByChFld(i, FLD_PDC, rec);
|
||||
dcPower += iv->getValue(pos, rec);
|
||||
}
|
||||
if(dcPower > 0)
|
||||
return acPower / dcPower * 100.0f;
|
||||
|
@ -663,9 +567,10 @@ static T calcIrradiation(Inverter<> *iv, uint8_t arg0) {
|
|||
DPRINTLN(DBG_VERBOSE, F("hmInverter.h:calcIrradiation"));
|
||||
// arg0 = channel
|
||||
if(NULL != iv) {
|
||||
uint8_t pos = iv->getPosByChFld(arg0, FLD_PDC);
|
||||
record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug);
|
||||
uint8_t pos = iv->getPosByChFld(arg0, FLD_PDC, rec);
|
||||
if(iv->chMaxPwr[arg0-1] > 0)
|
||||
return iv->getValue(pos) / iv->chMaxPwr[arg0-1] * 100.0f;
|
||||
return iv->getValue(pos, rec) / iv->chMaxPwr[arg0-1] * 100.0f;
|
||||
}
|
||||
return 0.0;
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
#define RF_CHANNELS 5
|
||||
#define RF_LOOP_CNT 300
|
||||
|
||||
#define TX_REQ_INFO 0X15
|
||||
#define TX_REQ_INFO 0x15
|
||||
#define TX_REQ_DEVCONTROL 0x51
|
||||
#define ALL_FRAMES 0x80
|
||||
#define SINGLE_FRAME 0x81
|
||||
|
@ -54,7 +54,7 @@ const char* const rf24AmpPowerNames[] = {"MIN", "LOW", "HIGH", "MAX"};
|
|||
//-----------------------------------------------------------------------------
|
||||
// HM Radio class
|
||||
//-----------------------------------------------------------------------------
|
||||
template <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 {
|
||||
public:
|
||||
HmRadio() : mNrf24(CE_PIN, CS_PIN, SPI_SPEED) {
|
||||
|
@ -89,6 +89,25 @@ class HmRadio {
|
|||
pinMode(config->pinIrq, INPUT_PULLUP);
|
||||
|
||||
mBufCtrl = ctrl;
|
||||
mSerialDebug = config->serialDebug;
|
||||
|
||||
uint32_t DTU_SN = 0x87654321;
|
||||
uint32_t chipID = 0; // will be filled with last 3 bytes of MAC
|
||||
#ifdef ESP32
|
||||
uint64_t MAC = ESP.getEfuseMac();
|
||||
chipID = ((MAC >> 8) & 0xFF0000) | ((MAC >> 24) & 0xFF00) | ((MAC >> 40) & 0xFF);
|
||||
#else
|
||||
chipID = ESP.getChipId();
|
||||
#endif
|
||||
if(chipID) {
|
||||
DTU_SN = 0x80000000; // the first digit is an 8 for DTU production year 2022, the rest is filled with the ESP chipID in decimal
|
||||
for(int i = 0; i < 7; i++) {
|
||||
DTU_SN |= (chipID % 10) << (i * 4);
|
||||
chipID /= 10;
|
||||
}
|
||||
}
|
||||
// change the byte order of the DTU serial number and append the required 0x01 at the end
|
||||
DTU_RADIO_ID = ((uint64_t)(((DTU_SN >> 24) & 0xFF) | ((DTU_SN >> 8) & 0xFF00) | ((DTU_SN << 8) & 0xFF0000) | ((DTU_SN << 24) & 0xFF000000)) << 8) | 0x01;
|
||||
|
||||
mNrf24.begin(config->pinCe, config->pinCs);
|
||||
mNrf24.setRetries(0, 0);
|
||||
|
@ -163,64 +182,62 @@ class HmRadio {
|
|||
}
|
||||
|
||||
void sendControlPacket(uint64_t invId, uint8_t cmd, uint16_t *data) {
|
||||
DPRINTLN(DBG_VERBOSE, F("hmRadio.h:sendControlPacket"));
|
||||
sendCmdPacket(invId, TX_REQ_DEVCONTROL, ALL_FRAMES, false); // 0x80 implementation as original DTU code
|
||||
int cnt = 0;
|
||||
mTxBuf[10] = cmd; // cmd --> 0x0b => Type_ActivePowerContr, 0 on, 1 off, 2 restart, 12 reactive power, 13 power factor
|
||||
mTxBuf[10 + (++cnt)] = 0x00;
|
||||
if (cmd >= ActivePowerContr && cmd <= PFSet){
|
||||
mTxBuf[10 + (++cnt)] = ((data[0] * 10) >> 8) & 0xff; // power limit
|
||||
mTxBuf[10 + (++cnt)] = ((data[0] * 10) ) & 0xff; // power limit
|
||||
mTxBuf[10 + (++cnt)] = ((data[1] ) >> 8) & 0xff; // setting for persistens handlings
|
||||
mTxBuf[10 + (++cnt)] = ((data[1] ) ) & 0xff; // setting for persistens handling
|
||||
DPRINTLN(DBG_INFO, F("sendControlPacket cmd: ") + String(cmd));
|
||||
sendCmdPacket(invId, TX_REQ_DEVCONTROL, SINGLE_FRAME, false);
|
||||
uint8_t cnt = 0;
|
||||
mTxBuf[10 + cnt++] = cmd; // cmd -> 0 on, 1 off, 2 restart, 11 active power, 12 reactive power, 13 power factor
|
||||
mTxBuf[10 + cnt++] = 0x00;
|
||||
if(cmd >= ActivePowerContr && cmd <= PFSet) { // ActivePowerContr, ReactivePowerContr, PFSet
|
||||
mTxBuf[10 + cnt++] = ((data[0] * 10) >> 8) & 0xff; // power limit
|
||||
mTxBuf[10 + cnt++] = ((data[0] * 10) ) & 0xff; // power limit
|
||||
mTxBuf[10 + cnt++] = ((data[1] ) >> 8) & 0xff; // setting for persistens handlings
|
||||
mTxBuf[10 + cnt++] = ((data[1] ) ) & 0xff; // setting for persistens handling
|
||||
}
|
||||
// crc control data
|
||||
uint16_t crc = Hoymiles::crc16(&mTxBuf[10], cnt+1);
|
||||
mTxBuf[10 + (++cnt)] = (crc >> 8) & 0xff;
|
||||
mTxBuf[10 + (++cnt)] = (crc ) & 0xff;
|
||||
// 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) {
|
||||
//DPRINTLN(DBG_VERBOSE, F("hmRadio.h:sendTimePacket"));
|
||||
DPRINTLN(DBG_INFO, F("sendTimePacket"));
|
||||
sendCmdPacket(invId, TX_REQ_INFO, ALL_FRAMES, false);
|
||||
mTxBuf[10] = cmd; // cid
|
||||
mTxBuf[11] = 0x00;
|
||||
CP_U32_LittleEndian(&mTxBuf[12], ts);
|
||||
if (cmd == RealTimeRunData_Debug || cmd == AlarmData ){
|
||||
if (cmd == RealTimeRunData_Debug || cmd == AlarmData ) {
|
||||
mTxBuf[18] = (alarmMesId >> 8) & 0xff;
|
||||
mTxBuf[19] = (alarmMesId ) & 0xff;
|
||||
} else {
|
||||
mTxBuf[18] = 0x00;
|
||||
mTxBuf[19] = 0x00;
|
||||
}
|
||||
uint16_t crc = Hoymiles::crc16(&mTxBuf[10], 14);
|
||||
uint16_t crc = Ahoy::crc16(&mTxBuf[10], 14);
|
||||
mTxBuf[24] = (crc >> 8) & 0xff;
|
||||
mTxBuf[25] = (crc ) & 0xff;
|
||||
mTxBuf[26] = Hoymiles::crc8(mTxBuf, 26);
|
||||
mTxBuf[26] = Ahoy::crc8(mTxBuf, 26);
|
||||
|
||||
sendPacket(invId, mTxBuf, 27, true);
|
||||
}
|
||||
|
||||
void sendCmdPacket(uint64_t invId, uint8_t mid, uint8_t pid, bool calcCrc = true) {
|
||||
//DPRINTLN(DBG_VERBOSE, F("hmRadio.h:sendCmdPacket"));
|
||||
DPRINTLN(DBG_VERBOSE, F("sendCmdPacket, mid: ") + String(mid, HEX) + F(" pid: ") + String(pid, HEX));
|
||||
memset(mTxBuf, 0, MAX_RF_PAYLOAD_SIZE);
|
||||
mTxBuf[0] = mid; // message id
|
||||
CP_U32_BigEndian(&mTxBuf[1], (invId >> 8));
|
||||
CP_U32_BigEndian(&mTxBuf[5], (DTU_ID >> 8));
|
||||
CP_U32_BigEndian(&mTxBuf[5], (DTU_RADIO_ID >> 8));
|
||||
mTxBuf[9] = pid;
|
||||
if(calcCrc) {
|
||||
mTxBuf[10] = Hoymiles::crc8(mTxBuf, 10);
|
||||
mTxBuf[10] = Ahoy::crc8(mTxBuf, 10);
|
||||
sendPacket(invId, mTxBuf, 11, false);
|
||||
}
|
||||
}
|
||||
|
||||
bool checkPaketCrc(uint8_t buf[], uint8_t *len, uint8_t rxCh) {
|
||||
//DPRINTLN(DBG_VERBOSE, F("hmRadio.h:checkPaketCrc"));
|
||||
//DPRINTLN(DBG_INFO, F("hmRadio.h:checkPaketCrc"));
|
||||
*len = (buf[0] >> 2);
|
||||
if(*len > (MAX_RF_PAYLOAD_SIZE - 2))
|
||||
*len = MAX_RF_PAYLOAD_SIZE - 2;
|
||||
|
@ -228,7 +245,7 @@ class HmRadio {
|
|||
buf[i-1] = (buf[i] << 1) | (buf[i+1] >> 7);
|
||||
}
|
||||
|
||||
uint8_t crc = Hoymiles::crc8(buf, *len-1);
|
||||
uint8_t crc = Ahoy::crc8(buf, *len-1);
|
||||
bool valid = (crc == buf[*len-1]);
|
||||
|
||||
return valid;
|
||||
|
@ -236,8 +253,6 @@ class HmRadio {
|
|||
|
||||
bool switchRxCh(uint16_t addLoop = 0) {
|
||||
//DPRINTLN(DBG_VERBOSE, F("hmRadio.h:switchRxCh"));
|
||||
//DPRINTLN(DBG_VERBOSE, F("R"));
|
||||
|
||||
mRxLoopCnt += addLoop;
|
||||
if(mRxLoopCnt != 0) {
|
||||
mRxLoopCnt--;
|
||||
|
@ -325,6 +340,8 @@ class HmRadio {
|
|||
return mRfChLst[mRxChIdx];
|
||||
}
|
||||
|
||||
uint64_t DTU_RADIO_ID;
|
||||
|
||||
uint8_t mTxCh;
|
||||
uint8_t mTxChIdx;
|
||||
|
||||
|
|
|
@ -63,15 +63,9 @@ class HmSystem {
|
|||
uint8_t len = (uint8_t)strlen(name);
|
||||
strncpy(p->name, name, (len > MAX_NAME_LENGTH) ? MAX_NAME_LENGTH : len);
|
||||
|
||||
if(NULL == p->assign) {
|
||||
DPRINT(DBG_ERROR, F("no assignment for type found!"));
|
||||
return NULL;
|
||||
}
|
||||
else {
|
||||
mNumInv ++;
|
||||
return p;
|
||||
}
|
||||
}
|
||||
|
||||
INVERTERTYPE *findInverter(uint8_t buf[]) {
|
||||
DPRINTLN(DBG_VERBOSE, F("hmSystem.h:findInverter"));
|
||||
|
@ -89,7 +83,9 @@ class HmSystem {
|
|||
|
||||
INVERTERTYPE *getInverterByPos(uint8_t pos, bool check = true) {
|
||||
DPRINTLN(DBG_VERBOSE, F("hmSystem.h:getInverterByPos"));
|
||||
if((mInverter[pos].initialized && mInverter[pos].serial.u64 != 0ULL) || false == check)
|
||||
if(pos >= MAX_INVERTER)
|
||||
return NULL;
|
||||
else if((mInverter[pos].initialized && mInverter[pos].serial.u64 != 0ULL) || false == check)
|
||||
return &mInverter[pos];
|
||||
else
|
||||
return NULL;
|
||||
|
|
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 sys
|
||||
import os
|
||||
import gzip
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
def convert2Header(inFile):
|
||||
def convert2Header(inFile, compress):
|
||||
fileType = inFile.split(".")[1]
|
||||
define = inFile.split(".")[0].upper()
|
||||
define2 = inFile.split(".")[1].upper()
|
||||
inFileVarName = inFile.replace(".", "_")
|
||||
print(inFile + ", compress: " + str(compress))
|
||||
|
||||
if os.getcwd()[-4:] != "html":
|
||||
print("ok")
|
||||
outName = "html/" + "h/" + inFileVarName + ".h"
|
||||
inFile = "html/" + inFile
|
||||
Path("html/h").mkdir(exist_ok=True)
|
||||
|
@ -20,23 +20,52 @@ def convert2Header(inFile):
|
|||
Path("h").mkdir(exist_ok=True)
|
||||
|
||||
f = open(inFile, "r")
|
||||
data = f.read().replace('\n', '')
|
||||
data = f.read()
|
||||
f.close()
|
||||
|
||||
if fileType == "html":
|
||||
if False == compress:
|
||||
data = data.replace('\n', '')
|
||||
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
|
||||
else:
|
||||
data = data.replace('\n', '')
|
||||
data = re.sub(r"(\;|\}|\:|\{)\s+", r'\1', data) # whitespaces inner css
|
||||
length = len(data) # get unescaped length # get unescaped length
|
||||
|
||||
f = open(outName, "w")
|
||||
f.write("#ifndef __{}_{}_H__\n".format(define, define2))
|
||||
f.write("#define __{}_{}_H__\n".format(define, define2))
|
||||
if compress:
|
||||
zipped = gzip.compress(bytes(data, 'utf-8'))
|
||||
zippedStr = ""
|
||||
for i in range(len(zipped)):
|
||||
zippedStr += "0x{:02x}".format(zipped[i]) #hex(zipped[i])
|
||||
if (i + 1) != len(zipped):
|
||||
zippedStr += ", "
|
||||
if (i + 1) % 16 == 0 and i != 0:
|
||||
zippedStr += "\n"
|
||||
f.write("#define {}_len {}\n".format(inFileVarName, len(zipped)))
|
||||
f.write("const uint8_t {}[] PROGMEM = {{\n{}}};\n".format(inFileVarName, zippedStr))
|
||||
else:
|
||||
f.write("const char {}[] PROGMEM = \"{}\";\n".format(inFileVarName, data))
|
||||
f.write("const uint32_t {}_len = {};\n".format(inFileVarName, length))
|
||||
f.write("#endif /*__{}_{}_H__*/\n".format(define, define2))
|
||||
f.close()
|
||||
|
||||
convert2Header("index.html")
|
||||
convert2Header("setup.html")
|
||||
convert2Header("visualization.html")
|
||||
convert2Header("style.css")
|
||||
convert2Header("index.html", True)
|
||||
convert2Header("setup.html", True)
|
||||
convert2Header("visualization.html", True)
|
||||
convert2Header("update.html", True)
|
||||
convert2Header("serial.html", True)
|
||||
convert2Header("style.css", True)
|
||||
convert2Header("api.js", True)
|
||||
|
|
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>
|
||||
<html>
|
||||
<head>
|
||||
<title>Index - {DEVICE}</title>
|
||||
<title>Index</title>
|
||||
<link rel="stylesheet" type="text/css" href="style.css"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<script type="text/javascript">
|
||||
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>
|
||||
<script type="text/javascript" src="api.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<h1>AHOY - {DEVICE}</h1>
|
||||
<h1>AHOY</h1>
|
||||
<div id="content" class="content">
|
||||
<p>
|
||||
<a href="/visualization">Visualization</a><br/>
|
||||
<a href="/live">Visualization</a><br/>
|
||||
<br/>
|
||||
<a href="/setup">Setup</a><br/>
|
||||
<a href="/serial">Webserial & Commands</a><br/>
|
||||
</p>
|
||||
<p><span class="des">Uptime: </span><span id="uptime"></span></p>
|
||||
<p><span class="des">Statistics: </span><pre id="cmds"></pre></p>
|
||||
<p>Every {TS}seconds the values are updated</p>
|
||||
<p><span class="des">ESP-Time: </span><span id="date"></span></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">
|
||||
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/>
|
||||
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/>
|
||||
Discuss with us on <a href="https://discord.gg/WzhxEY62mB">Discord</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 id="footer">
|
||||
<p class="left">© 2022</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">Git SHA: {BUILD}</p>
|
||||
<p class="right"><a href="/api">REST API</a></p>
|
||||
</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>
|
||||
</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>
|
||||
<html>
|
||||
<head>
|
||||
<title>Setup - {DEVICE}</title>
|
||||
<title>Setup</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>
|
||||
<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() {
|
||||
document.querySelectorAll('input[name^="inv"][name$="Addr"]').forEach(elm => {
|
||||
elm.addEventListener("keyup", (e) => {
|
||||
serial = elm.value.substring(0,4);
|
||||
iv = elm.name.substring(3,4);
|
||||
max = 0;
|
||||
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);
|
||||
for(it of document.getElementsByClassName("s_collapsible")) {
|
||||
it.addEventListener("click", function() {
|
||||
this.classList.toggle("active");
|
||||
var content = this.nextElementSibling;
|
||||
content.style.display = (content.style.display === "block") ? "none" : "block";
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body onload="load()">
|
||||
|
@ -54,11 +23,11 @@
|
|||
<div id="content">
|
||||
<a class="erase" href="/erase">ERASE SETTINGS (not WiFi)</a>
|
||||
|
||||
<form method="post" action="{IP}/save">
|
||||
<form method="post" action="/save">
|
||||
<fieldset>
|
||||
<legend class="des">Device Host Name</legend>
|
||||
<label for="device">Device Name</label>
|
||||
<input type="text" class="text" name="device" value="{DEVICE}"/>
|
||||
<input type="text" name="device" class="text"/>
|
||||
</fieldset>
|
||||
|
||||
<button type="button" class="s_collapsible">WiFi</button>
|
||||
|
@ -67,7 +36,7 @@
|
|||
<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>
|
||||
<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>
|
||||
<input type="password" class="text" name="pwd" value="{PWD}"/>
|
||||
</fieldset>
|
||||
|
@ -77,12 +46,13 @@
|
|||
<div class="s_content">
|
||||
<fieldset>
|
||||
<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>
|
||||
<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>
|
||||
<input type="text" class="text" name="invRetry" value="{INV_RETRIES}"/>
|
||||
<input type="text" class="text" name="invRetry"/>
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
|
@ -91,9 +61,13 @@
|
|||
<fieldset>
|
||||
<legend class="des">NTP Server</legend>
|
||||
<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>
|
||||
<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>
|
||||
</div>
|
||||
|
||||
|
@ -102,15 +76,15 @@
|
|||
<fieldset>
|
||||
<legend class="des">MQTT</legend>
|
||||
<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>
|
||||
<input type="text" class="text" name="mqttPort" value="{MQTT_PORT}"/>
|
||||
<input type="text" class="text" name="mqttPort"/>
|
||||
<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>
|
||||
<input type="text" class="text" name="mqttPwd" value="{MQTT_PWD}"/>
|
||||
<input type="password" class="text" name="mqttPwd"/>
|
||||
<label for="mqttTopic">Topic</label>
|
||||
<input type="text" class="text" name="mqttTopic" value="{MQTT_TOPIC}"/>
|
||||
<input type="text" class="text" name="mqttTopic"/>
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
|
@ -119,47 +93,207 @@
|
|||
<fieldset>
|
||||
<legend class="des">System Config</legend>
|
||||
<p class="des">Pinout (Wemos)</p>
|
||||
{PINOUT}
|
||||
<div id="pinout"></div>
|
||||
|
||||
<p class="des">Radio (NRF24L01+)</p>
|
||||
<label for="rf24Power">Amplifier Power Level</label>
|
||||
<select name="rf24Power">{RF24}</select>
|
||||
<div id="rf24"></div>
|
||||
|
||||
<p class="des">Serial Console</p>
|
||||
<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>
|
||||
<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>
|
||||
<input type="text" class="text" name="serIntvl" value="{SER_INTVL}"/>
|
||||
<input type="text" class="text" name="serIntvl"/>
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
<label for="reboot">Reboot device after successful save</label>
|
||||
<input type="checkbox" class="cb" name="reboot"/>
|
||||
<input type="submit" value="save" class="btn" />
|
||||
<input type="submit" value="save" class="btn right"/>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="footer">
|
||||
<p class="left"><a href="{IP}/">Home</a></p>
|
||||
<p class="left"><a href="{IP}/update">Update Firmware</a></p>
|
||||
<p class="right">AHOY - {VERSION}</p>
|
||||
<p class="right"><a href="{IP}/factory">Factory Reset</a></p>
|
||||
<p class="right"><a href="{IP}/reboot">Reboot</a></p>
|
||||
<p class="left"><a href="/">Home</a></p>
|
||||
<p class="left"><a href="/update">Update Firmware</a></p>
|
||||
<p class="right" id="version"></p>
|
||||
<p class="right"><a href="/factory">Factory Reset</a></p>
|
||||
<p class="right"><a href="/reboot">Reboot</a></p>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
var coll = document.getElementsByClassName("s_collapsible");
|
||||
var i;
|
||||
for (i = 0; i < coll.length; i++) {
|
||||
coll[i].addEventListener("click", function() {
|
||||
this.classList.toggle("active");
|
||||
var content = this.nextElementSibling;
|
||||
content.style.display = (content.style.display === "block") ? "none" : "block";
|
||||
var highestId = 0;
|
||||
var maxInv = 0;
|
||||
|
||||
const re = /11[2,4,6]1.*/;
|
||||
|
||||
document.getElementsByName("btnAdd")[0].addEventListener("click", function() {
|
||||
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>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -133,9 +133,14 @@ input.btn {
|
|||
background-color: #006ec0;
|
||||
color: #fff;
|
||||
border: 0px;
|
||||
float: right;
|
||||
margin: 10px 0 30px;
|
||||
padding: 7px 20px 7px 20px;
|
||||
margin-bottom: 10px;
|
||||
text-transform: uppercase;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
input.btn:hover {
|
||||
background-color: #044e86;
|
||||
}
|
||||
|
||||
input.cb {
|
||||
|
@ -245,7 +250,7 @@ div.ts {
|
|||
padding: 7px;
|
||||
}
|
||||
|
||||
div.modpwr, div.modname {
|
||||
div.ModPwr, div.ModName {
|
||||
width:70%;
|
||||
display: inline-block;
|
||||
}
|
||||
|
@ -271,3 +276,20 @@ div.modpwr, div.modname {
|
|||
width: 180px;
|
||||
}
|
||||
}
|
||||
|
||||
#serial {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#content .serial {
|
||||
max-width: 1000px;
|
||||
}
|
||||
|
||||
.dot {
|
||||
height: 15px;
|
||||
width: 15px;
|
||||
background-color: #f00;
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
|
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>
|
||||
<html>
|
||||
<head>
|
||||
<title>Index - {DEVICE}</title>
|
||||
<title>Live</title>
|
||||
<link rel="stylesheet" type="text/css" href="style.css"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<script type="text/javascript">
|
||||
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>
|
||||
<script type="text/javascript" src="api.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<h1>AHOY - {DEVICE}</h1>
|
||||
<h1>AHOY</h1>
|
||||
<div id="content" class="content">
|
||||
<div id="livedata"></div>
|
||||
<p>Every {TS}seconds the values are updated</p>
|
||||
<div id="live"></div>
|
||||
<p>Every <span id="refresh"></span> seconds the values are updated</p>
|
||||
</div>
|
||||
<div id="footer">
|
||||
<p class="left">© 2022</p>
|
||||
<p class="left"><a href="/">Home</a></p>
|
||||
<p class="right">AHOY :: {VERSION}</p>
|
||||
<p class="right" id="version"></p>
|
||||
</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>
|
||||
</html>
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#define F(sl) (sl)
|
||||
#endif
|
||||
|
||||
#include <functional>
|
||||
//-----------------------------------------------------------------------------
|
||||
// available levels
|
||||
#define DBG_ERROR 1
|
||||
|
@ -21,7 +22,9 @@
|
|||
|
||||
//-----------------------------------------------------------------------------
|
||||
// globally used level
|
||||
#ifndef DEBUG_LEVEL
|
||||
#define DEBUG_LEVEL DBG_INFO
|
||||
#endif
|
||||
|
||||
#ifdef ARDUINO
|
||||
#include "Arduino.h"
|
||||
|
@ -32,23 +35,40 @@
|
|||
#define DBGPRINTLN(str)
|
||||
#else
|
||||
#ifdef ARDUINO
|
||||
#define DBG_CB std::function<void(String)>
|
||||
extern DBG_CB mCb;
|
||||
|
||||
inline void registerDebugCb(DBG_CB cb) {
|
||||
mCb = cb;
|
||||
}
|
||||
|
||||
#ifndef DSERIAL
|
||||
#define DSERIAL Serial
|
||||
#endif
|
||||
|
||||
template <class T>
|
||||
inline void DBGPRINT(T str) { DSERIAL.print(str); }
|
||||
template <class T>
|
||||
inline void DBGPRINTLN(T str) { DBGPRINT(str); DBGPRINT(F("\r\n")); }
|
||||
//template <class T>
|
||||
inline void DBGPRINT(String str) { DSERIAL.print(str); if(NULL != mCb) mCb(str); }
|
||||
//template <class T>
|
||||
inline void DBGPRINTLN(String str) { DBGPRINT(str); DBGPRINT(F("\r\n")); }
|
||||
inline void DHEX(uint8_t b) {
|
||||
if( b<0x10 ) DSERIAL.print('0');
|
||||
if( b<0x10 ) DSERIAL.print(F("0"));
|
||||
DSERIAL.print(b,HEX);
|
||||
if(NULL != mCb) {
|
||||
if( b<0x10 ) mCb(F("0"));
|
||||
mCb(String(b, HEX));
|
||||
}
|
||||
}
|
||||
inline void DHEX(uint16_t b) {
|
||||
if( b<0x10 ) DSERIAL.print(F("000"));
|
||||
else if( b<0x100 ) DSERIAL.print(F("00"));
|
||||
else if( b<0x1000 ) DSERIAL.print(F("0"));
|
||||
DSERIAL.print(b,HEX);
|
||||
DSERIAL.print(b, HEX);
|
||||
if(NULL != mCb) {
|
||||
if( b<0x10 ) mCb(F("000"));
|
||||
else if( b<0x100 ) mCb(F("00"));
|
||||
else if( b<0x1000 ) mCb(F("0"));
|
||||
mCb(String(b, HEX));
|
||||
}
|
||||
}
|
||||
inline void DHEX(uint32_t b) {
|
||||
if( b<0x10 ) DSERIAL.print(F("0000000"));
|
||||
|
@ -58,7 +78,17 @@
|
|||
else if( b<0x100000 ) DSERIAL.print(F("000"));
|
||||
else if( b<0x1000000 ) DSERIAL.print(F("00"));
|
||||
else if( b<0x10000000 ) DSERIAL.print(F("0"));
|
||||
DSERIAL.print(b,HEX);
|
||||
DSERIAL.print(b, HEX);
|
||||
if(NULL != mCb) {
|
||||
if( b<0x10 ) mCb(F("0000000"));
|
||||
else if( b<0x100 ) mCb(F("000000"));
|
||||
else if( b<0x1000 ) mCb(F("00000"));
|
||||
else if( b<0x10000 ) mCb(F("0000"));
|
||||
else if( b<0x100000 ) mCb(F("000"));
|
||||
else if( b<0x1000000 ) mCb(F("00"));
|
||||
else if( b<0x10000000 ) mCb(F("0"));
|
||||
mCb(String(b, HEX));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
|
|
@ -32,17 +32,16 @@ extra_scripts =
|
|||
pre:html/convert.py
|
||||
|
||||
lib_deps =
|
||||
nrf24/RF24@1.4.5
|
||||
paulstoffregen/Time@^1.6.1
|
||||
knolleary/PubSubClient@^2.8
|
||||
bblanchon/ArduinoJson@^6.19.4
|
||||
;esp8266/DNSServer@1.1.0
|
||||
;esp8266/EEPROM@^1.0
|
||||
;esp8266/ESP8266HTTPUpdateServer@^1.0
|
||||
;esp8266/ESP8266WebServer@^1.0
|
||||
;esp8266/ESP8266WiFi@^1.0
|
||||
;esp8266/SPI@1.0
|
||||
;esp8266/Ticker@^1.0
|
||||
https://github.com/yubox-node-org/ESPAsyncWebServer
|
||||
nrf24/RF24
|
||||
paulstoffregen/Time
|
||||
knolleary/PubSubClient
|
||||
bblanchon/ArduinoJson
|
||||
;esp8266/DNSServer
|
||||
;esp8266/EEPROM
|
||||
;esp8266/ESP8266WiFi
|
||||
;esp8266/SPI
|
||||
;esp8266/Ticker
|
||||
|
||||
[env:esp8266-release]
|
||||
platform = espressif8266
|
||||
|
@ -58,7 +57,7 @@ monitor_filters =
|
|||
platform = espressif8266
|
||||
board = esp12e
|
||||
board_build.f_cpu = 80000000L
|
||||
build_flags = -DDEBUG_ESP_CORE -DDEBUG_ESP_WIFI -DDEBUG_ESP_HTTP_CLIENT -DDEBUG_ESP_HTTP_SERVER -DDEBUG_ESP_OOM -DDEBUG_ESP_PORT=Serial
|
||||
build_flags = -DDEBUG_LEVEL=DBG_DEBUG -DDEBUG_ESP_CORE -DDEBUG_ESP_WIFI -DDEBUG_ESP_HTTP_CLIENT -DDEBUG_ESP_HTTP_SERVER -DDEBUG_ESP_OOM -DDEBUG_ESP_PORT=Serial
|
||||
build_type = debug
|
||||
monitor_filters =
|
||||
;default ; Remove typical terminal control codes from input
|
||||
|
@ -68,7 +67,9 @@ monitor_filters =
|
|||
[env:esp32-wroom32-release]
|
||||
platform = espressif32
|
||||
board = lolin_d32
|
||||
build_flags = -D RELEASE
|
||||
build_flags = -D RELEASE -std=gnu++14
|
||||
build_unflags = -std=gnu++11
|
||||
upload_port = /dev/cu.SLAB_USBtoUART
|
||||
monitor_filters =
|
||||
;default ; Remove typical terminal control codes from input
|
||||
time ; Add timestamp with milliseconds for each new line
|
||||
|
@ -77,8 +78,10 @@ monitor_filters =
|
|||
[env:esp32-wroom32-debug]
|
||||
platform = espressif32
|
||||
board = lolin_d32
|
||||
build_flags = -DDEBUG_ESP_CORE -DDEBUG_ESP_WIFI -DDEBUG_ESP_HTTP_CLIENT -DDEBUG_ESP_HTTP_SERVER -DDEBUG_ESP_OOM -DDEBUG_ESP_PORT=Serial
|
||||
build_flags = -DDEBUG_LEVEL=DBG_DEBUG -DDEBUG_ESP_CORE -DDEBUG_ESP_WIFI -DDEBUG_ESP_HTTP_CLIENT -DDEBUG_ESP_HTTP_SERVER -DDEBUG_ESP_OOM -DDEBUG_ESP_PORT=Serial -std=gnu++14
|
||||
build_unflags = -std=gnu++11
|
||||
build_type = debug
|
||||
upload_port = /dev/cu.SLAB_USBtoUART
|
||||
monitor_filters =
|
||||
;default ; Remove typical terminal control codes from input
|
||||
time ; Add timestamp with milliseconds for each new line
|
||||
|
|
|
@ -12,41 +12,31 @@
|
|||
|
||||
#include "html/h/index_html.h"
|
||||
#include "html/h/style_css.h"
|
||||
#include "favicon.h"
|
||||
#include "html/h/api_js.h"
|
||||
#include "html/h/favicon_ico_gz.h"
|
||||
#include "html/h/setup_html.h"
|
||||
#include "html/h/visualization_html.h"
|
||||
#include "html/h/update_html.h"
|
||||
#include "html/h/serial_html.h"
|
||||
|
||||
|
||||
const uint16_t pwrLimitOptionValues[] {
|
||||
NoPowerLimit,
|
||||
AbsolutNonPersistent,
|
||||
AbsolutPersistent,
|
||||
RelativNonPersistent,
|
||||
RelativPersistent
|
||||
};
|
||||
|
||||
const char* const pwrLimitOptions[] {
|
||||
"no power limit",
|
||||
"absolute in Watt non persistent",
|
||||
"absolute in Watt persistent",
|
||||
"relativ in percent non persistent",
|
||||
"relativ in percent persistent"
|
||||
};
|
||||
const char* const pinArgNames[] = {"pinCs", "pinCe", "pinIrq"};
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
web::web(app *main, sysConfig_t *sysCfg, config_t *config, char version[]) {
|
||||
web::web(app *main, sysConfig_t *sysCfg, config_t *config, statistics_t *stat, char version[]) {
|
||||
mMain = main;
|
||||
mSysCfg = sysCfg;
|
||||
mConfig = config;
|
||||
mStat = stat;
|
||||
mVersion = version;
|
||||
#ifdef ESP8266
|
||||
mWeb = new ESP8266WebServer(80);
|
||||
mUpdater = new ESP8266HTTPUpdateServer();
|
||||
#elif defined(ESP32)
|
||||
mWeb = new WebServer(80);
|
||||
mUpdater = new HTTPUpdateServer();
|
||||
#endif
|
||||
mUpdater->setup(mWeb);
|
||||
mWeb = new AsyncWebServer(80);
|
||||
mEvts = new AsyncEventSource("/events");
|
||||
mApi = new webApi(mWeb, main, sysCfg, config, stat, version);
|
||||
|
||||
memset(mSerialBuf, 0, WEB_SERIAL_BUF_SIZE);
|
||||
mSerialBufFill = 0;
|
||||
mWebSerialTicker = 0;
|
||||
mWebSerialInterval = 1000; // [ms]
|
||||
mSerialAddTime = true;
|
||||
}
|
||||
|
||||
|
||||
|
@ -55,107 +45,141 @@ void web::setup(void) {
|
|||
DPRINTLN(DBG_VERBOSE, F("app::setup-begin"));
|
||||
mWeb->begin();
|
||||
DPRINTLN(DBG_VERBOSE, F("app::setup-on"));
|
||||
mWeb->on("/", std::bind(&web::showIndex, this));
|
||||
mWeb->on("/style.css", std::bind(&web::showCss, this));
|
||||
mWeb->on("/favicon.ico", std::bind(&web::showFavicon, this));
|
||||
mWeb->onNotFound ( std::bind(&web::showNotFound, this));
|
||||
mWeb->on("/uptime", std::bind(&web::showUptime, this));
|
||||
mWeb->on("/reboot", std::bind(&web::showReboot, this));
|
||||
mWeb->on("/erase", std::bind(&web::showErase, this));
|
||||
mWeb->on("/factory", std::bind(&web::showFactoryRst, this));
|
||||
mWeb->on("/", HTTP_GET, std::bind(&web::onIndex, this, std::placeholders::_1));
|
||||
mWeb->on("/style.css", HTTP_GET, std::bind(&web::onCss, this, std::placeholders::_1));
|
||||
mWeb->on("/api.js", HTTP_GET, std::bind(&web::onApiJs, this, std::placeholders::_1));
|
||||
mWeb->on("/favicon.ico", HTTP_GET, std::bind(&web::onFavicon, this, std::placeholders::_1));
|
||||
mWeb->onNotFound ( std::bind(&web::showNotFound, this, std::placeholders::_1));
|
||||
mWeb->on("/reboot", HTTP_ANY, std::bind(&web::onReboot, this, std::placeholders::_1));
|
||||
mWeb->on("/erase", HTTP_ANY, std::bind(&web::showErase, this, std::placeholders::_1));
|
||||
mWeb->on("/factory", HTTP_ANY, std::bind(&web::showFactoryRst, this, std::placeholders::_1));
|
||||
|
||||
mWeb->on("/setup", std::bind(&web::showSetup, this));
|
||||
mWeb->on("/save", std::bind(&web::showSave, this));
|
||||
mWeb->on("/setup", HTTP_GET, std::bind(&web::onSetup, this, std::placeholders::_1));
|
||||
mWeb->on("/save", HTTP_ANY, std::bind(&web::showSave, this, std::placeholders::_1));
|
||||
|
||||
mWeb->on("/cmdstat", std::bind(&web::showStatistics, this));
|
||||
mWeb->on("/visualization", std::bind(&web::showVisualization, this));
|
||||
mWeb->on("/livedata", std::bind(&web::showLiveData, this));
|
||||
mWeb->on("/json", std::bind(&web::showJson, this));
|
||||
mWeb->on("/api", HTTP_POST, std::bind(&web::showWebApi, this));
|
||||
mWeb->on("/live", HTTP_ANY, std::bind(&web::onLive, this, std::placeholders::_1));
|
||||
mWeb->on("/api1", HTTP_POST, std::bind(&web::showWebApi, this, std::placeholders::_1));
|
||||
|
||||
|
||||
mWeb->on("/update", HTTP_GET, std::bind(&web::onUpdate, this, std::placeholders::_1));
|
||||
mWeb->on("/update", HTTP_POST, std::bind(&web::showUpdate, this, std::placeholders::_1),
|
||||
std::bind(&web::showUpdate2, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5, std::placeholders::_6));
|
||||
mWeb->on("/serial", HTTP_GET, std::bind(&web::onSerial, this, std::placeholders::_1));
|
||||
|
||||
|
||||
mEvts->onConnect(std::bind(&web::onConnect, this, std::placeholders::_1));
|
||||
mWeb->addHandler(mEvts);
|
||||
|
||||
mApi->setup();
|
||||
|
||||
registerDebugCb(std::bind(&web::serialCb, this, std::placeholders::_1));
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void web::loop(void) {
|
||||
mWeb->handleClient();
|
||||
mApi->loop();
|
||||
|
||||
if(mMain->checkTicker(&mWebSerialTicker, mWebSerialInterval)) {
|
||||
if(mSerialBufFill > 0) {
|
||||
mEvts->send(mSerialBuf, "serial", millis());
|
||||
memset(mSerialBuf, 0, WEB_SERIAL_BUF_SIZE);
|
||||
mSerialBufFill = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void web::showIndex(void) {
|
||||
DPRINTLN(DBG_VERBOSE, F("showIndex"));
|
||||
String html = FPSTR(index_html);
|
||||
html.replace(F("{DEVICE}"), mSysCfg->deviceName);
|
||||
html.replace(F("{VERSION}"), mVersion);
|
||||
html.replace(F("{TS}"), String(mConfig->sendInterval) + " ");
|
||||
html.replace(F("{JS_TS}"), String(mConfig->sendInterval * 1000));
|
||||
html.replace(F("{BUILD}"), String(AUTO_GIT_HASH));
|
||||
mWeb->send(200, "text/html", html);
|
||||
void web::onConnect(AsyncEventSourceClient *client) {
|
||||
DPRINTLN(DBG_VERBOSE, "onConnect");
|
||||
|
||||
if(client->lastId())
|
||||
DPRINTLN(DBG_VERBOSE, "Client reconnected! Last message ID that it got is: " + String(client->lastId()));
|
||||
|
||||
client->send("hello!", NULL, millis(), 1000);
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void web::showCss(void) {
|
||||
mWeb->send(200, "text/css", FPSTR(style_css));
|
||||
void web::onIndex(AsyncWebServerRequest *request) {
|
||||
DPRINTLN(DBG_VERBOSE, F("onIndex"));
|
||||
|
||||
AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html"), index_html, index_html_len);
|
||||
response->addHeader(F("Content-Encoding"), "gzip");
|
||||
request->send(response);
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void web::showFavicon(void) {
|
||||
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_content[] PROGMEM = FAVICON_PANEL_16;
|
||||
mWeb->send_P(200, favicon_type, favicon_content, sizeof(favicon_content));
|
||||
AsyncWebServerResponse *response = request->beginResponse_P(200, favicon_type, favicon_ico_gz, favicon_ico_gz_len);
|
||||
response->addHeader(F("Content-Encoding"), "gzip");
|
||||
request->send(response);
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void web::showNotFound(void) {
|
||||
DPRINTLN(DBG_VERBOSE, F("showNotFound - ") + mWeb->uri());
|
||||
String msg = F("File Not Found\n\nURI: ");
|
||||
msg += mWeb->uri();
|
||||
mWeb->send(404, F("text/plain"), msg);
|
||||
void web::showNotFound(AsyncWebServerRequest *request) {
|
||||
DPRINTLN(DBG_VERBOSE, F("showNotFound - ") + request->url());
|
||||
String msg = F("File Not Found\n\nURL: ");
|
||||
msg += request->url();
|
||||
msg += F("\nMethod: ");
|
||||
msg += ( request->method() == HTTP_GET ) ? "GET" : "POST";
|
||||
msg += F("\nArguments: ");
|
||||
msg += request->args();
|
||||
msg += "\n";
|
||||
|
||||
for(uint8_t i = 0; i < request->args(); i++ ) {
|
||||
msg += " " + request->argName(i) + ": " + request->arg(i) + "\n";
|
||||
}
|
||||
|
||||
request->send(404, F("text/plain"), msg);
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void web::showUptime(void) {
|
||||
char time[21] = {0};
|
||||
uint32_t uptime = mMain->getUptime();
|
||||
|
||||
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::onReboot(AsyncWebServerRequest *request) {
|
||||
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>"));
|
||||
mMain->mShouldReboot = true;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void web::showReboot(void) {
|
||||
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() {
|
||||
void web::showErase(AsyncWebServerRequest *request) {
|
||||
DPRINTLN(DBG_VERBOSE, F("showErase"));
|
||||
mMain->eraseSettings();
|
||||
showReboot();
|
||||
onReboot(request);
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void web::showFactoryRst(void) {
|
||||
void web::showFactoryRst(AsyncWebServerRequest *request) {
|
||||
DPRINTLN(DBG_VERBOSE, F("showFactoryRst"));
|
||||
String content = "";
|
||||
int refresh = 3;
|
||||
if(mWeb->args() > 0) {
|
||||
if(mWeb->arg("reset").toInt() == 1) {
|
||||
if(request->args() > 0) {
|
||||
if(request->arg("reset").toInt() == 1) {
|
||||
mMain->eraseSettings(true);
|
||||
content = F("factory reset: success\n\nrebooting ... ");
|
||||
refresh = 10;
|
||||
|
@ -170,7 +194,7 @@ void web::showFactoryRst(void) {
|
|||
"<p><a href=\"/factory?reset=1\">RESET</a><br/><br/><a href=\"/factory?reset=0\">CANCEL</a><br/></p>");
|
||||
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) {
|
||||
delay(1000);
|
||||
ESP.restart();
|
||||
|
@ -179,193 +203,59 @@ void web::showFactoryRst(void) {
|
|||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void web::showSetup(void) {
|
||||
DPRINTLN(DBG_VERBOSE, F("showSetup"));
|
||||
String html = FPSTR(setup_html);
|
||||
html.replace(F("{SSID}"), mSysCfg->stationSsid);
|
||||
// PWD will be left at the default value (for protection)
|
||||
// -> the PWD will only be changed if it does not match the default "{PWD}"
|
||||
html.replace(F("{DEVICE}"), String(mSysCfg->deviceName));
|
||||
html.replace(F("{VERSION}"), String(mVersion));
|
||||
if(mMain->getWifiApActive())
|
||||
html.replace("{IP}", String(F("http://192.168.1.1")));
|
||||
else
|
||||
html.replace("{IP}", (F("http://") + String(WiFi.localIP().toString())));
|
||||
void web::onSetup(AsyncWebServerRequest *request) {
|
||||
DPRINTLN(DBG_VERBOSE, F("onSetup"));
|
||||
|
||||
String inv = "";
|
||||
Inverter<> *iv;
|
||||
for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i ++) {
|
||||
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);
|
||||
AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html"), setup_html, setup_html_len);
|
||||
response->addHeader(F("Content-Encoding"), "gzip");
|
||||
request->send(response);
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void web::showSave(void) {
|
||||
void web::showSave(AsyncWebServerRequest *request) {
|
||||
DPRINTLN(DBG_VERBOSE, F("showSave"));
|
||||
|
||||
if(mWeb->args() > 0) {
|
||||
if(request->args() > 0) {
|
||||
char buf[20] = {0};
|
||||
|
||||
// general
|
||||
if(mWeb->arg("ssid") != "")
|
||||
mWeb->arg("ssid").toCharArray(mSysCfg->stationSsid, SSID_LEN);
|
||||
if(mWeb->arg("pwd") != "{PWD}")
|
||||
mWeb->arg("pwd").toCharArray(mSysCfg->stationPwd, PWD_LEN);
|
||||
if(mWeb->arg("device") != "")
|
||||
mWeb->arg("device").toCharArray(mSysCfg->deviceName, DEVNAME_LEN);
|
||||
if(request->arg("ssid") != "")
|
||||
request->arg("ssid").toCharArray(mSysCfg->stationSsid, SSID_LEN);
|
||||
if(request->arg("pwd") != "{PWD}")
|
||||
request->arg("pwd").toCharArray(mSysCfg->stationPwd, PWD_LEN);
|
||||
if(request->arg("device") != "")
|
||||
request->arg("device").toCharArray(mSysCfg->deviceName, DEVNAME_LEN);
|
||||
|
||||
// inverter
|
||||
Inverter<> *iv;
|
||||
for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i ++) {
|
||||
iv = mMain->mSys->getInverterByPos(i, false);
|
||||
// address
|
||||
mWeb->arg("inv" + String(i) + "Addr").toCharArray(buf, 20);
|
||||
request->arg("inv" + String(i) + "Addr").toCharArray(buf, 20);
|
||||
if(strlen(buf) == 0)
|
||||
memset(buf, 0, 20);
|
||||
iv->serial.u64 = mMain->Serial2u64(buf);
|
||||
|
||||
// active power limit
|
||||
uint16_t actPwrLimit = mWeb->arg("inv" + String(i) + "ActivePowerLimit").toInt();
|
||||
uint16_t actPwrLimitControl = mWeb->arg("inv" + String(i) + "PowerLimitControl").toInt();
|
||||
if (actPwrLimit != 0xffff && actPwrLimit > 0){
|
||||
iv->powerLimit[0] = actPwrLimit;
|
||||
iv->powerLimit[1] = actPwrLimitControl;
|
||||
iv->devControlCmd = ActivePowerContr;
|
||||
iv->devControlRequest = true;
|
||||
if ((iv->powerLimit[1] & 0x0001) == 0x0001)
|
||||
DPRINTLN(DBG_INFO, F("Power limit for inverter ") + String(iv->id) + F(" set to ") + String(iv->powerLimit[0]) + F("%") );
|
||||
else {
|
||||
DPRINTLN(DBG_INFO, F("Power limit for inverter ") + String(iv->id) + F(" set to ") + String(iv->powerLimit[0]) + F("W") );
|
||||
DPRINTLN(DBG_INFO, F("Power Limit Control Setting ") + String(iv->powerLimit[1]));
|
||||
}
|
||||
}
|
||||
if (actPwrLimit == 0xffff) { // set to 100%
|
||||
iv->powerLimit[0] = 100;
|
||||
iv->powerLimit[1] = RelativPersistent;
|
||||
iv->devControlCmd = ActivePowerContr;
|
||||
iv->devControlRequest = true;
|
||||
DPRINTLN(DBG_INFO, F("Power limit for inverter ") + String(iv->id) + F(" set to unlimted"));
|
||||
}
|
||||
|
||||
// name
|
||||
mWeb->arg("inv" + String(i) + "Name").toCharArray(iv->name, MAX_NAME_LENGTH);
|
||||
request->arg("inv" + String(i) + "Name").toCharArray(iv->name, MAX_NAME_LENGTH);
|
||||
|
||||
// max channel power / name
|
||||
for(uint8_t j = 0; j < 4; j++) {
|
||||
iv->chMaxPwr[j] = mWeb->arg("inv" + String(i) + "ModPwr" + String(j)).toInt() & 0xffff;
|
||||
mWeb->arg("inv" + String(i) + "ModName" + String(j)).toCharArray(iv->chName[j], MAX_NAME_LENGTH);
|
||||
iv->chMaxPwr[j] = request->arg("inv" + String(i) + "ModPwr" + String(j)).toInt() & 0xffff;
|
||||
request->arg("inv" + String(i) + "ModName" + String(j)).toCharArray(iv->chName[j], MAX_NAME_LENGTH);
|
||||
}
|
||||
iv->initialized = true;
|
||||
}
|
||||
if(mWeb->arg("invInterval") != "")
|
||||
mConfig->sendInterval = mWeb->arg("invInterval").toInt();
|
||||
if(mWeb->arg("invRetry") != "")
|
||||
mConfig->maxRetransPerPyld = mWeb->arg("invRetry").toInt();
|
||||
if(request->arg("invInterval") != "")
|
||||
mConfig->sendInterval = request->arg("invInterval").toInt();
|
||||
if(request->arg("invRetry") != "")
|
||||
mConfig->maxRetransPerPyld = request->arg("invRetry").toInt();
|
||||
|
||||
// pinout
|
||||
uint8_t pin;
|
||||
for(uint8_t i = 0; i < 3; i ++) {
|
||||
pin = mWeb->arg(String(pinArgNames[i])).toInt();
|
||||
pin = request->arg(String(pinArgNames[i])).toInt();
|
||||
switch(i) {
|
||||
default: mConfig->pinCs = pin; break;
|
||||
case 1: mConfig->pinCe = pin; break;
|
||||
|
@ -374,220 +264,74 @@ void web::showSave(void) {
|
|||
}
|
||||
|
||||
// nrf24 amplifier power
|
||||
mConfig->amplifierPower = mWeb->arg("rf24Power").toInt() & 0x03;
|
||||
mConfig->amplifierPower = request->arg("rf24Power").toInt() & 0x03;
|
||||
|
||||
// ntp
|
||||
if(mWeb->arg("ntpAddr") != "") {
|
||||
mWeb->arg("ntpAddr").toCharArray(mConfig->ntpAddr, NTP_ADDR_LEN);
|
||||
mConfig->ntpPort = mWeb->arg("ntpPort").toInt() & 0xffff;
|
||||
if(request->arg("ntpAddr") != "") {
|
||||
request->arg("ntpAddr").toCharArray(mConfig->ntpAddr, NTP_ADDR_LEN);
|
||||
mConfig->ntpPort = request->arg("ntpPort").toInt() & 0xffff;
|
||||
}
|
||||
|
||||
// mqtt
|
||||
if(mWeb->arg("mqttAddr") != "") {
|
||||
String addr = mWeb->arg("mqttAddr");
|
||||
if(request->arg("mqttAddr") != "") {
|
||||
String addr = request->arg("mqttAddr");
|
||||
addr.trim();
|
||||
addr.toCharArray(mConfig->mqtt.broker, MQTT_ADDR_LEN);
|
||||
mWeb->arg("mqttUser").toCharArray(mConfig->mqtt.user, MQTT_USER_LEN);
|
||||
mWeb->arg("mqttPwd").toCharArray(mConfig->mqtt.pwd, MQTT_PWD_LEN);
|
||||
mWeb->arg("mqttTopic").toCharArray(mConfig->mqtt.topic, MQTT_TOPIC_LEN);
|
||||
mConfig->mqtt.port = mWeb->arg("mqttPort").toInt();
|
||||
request->arg("mqttUser").toCharArray(mConfig->mqtt.user, MQTT_USER_LEN);
|
||||
if(request->arg("mqttPwd") != "{PWD}")
|
||||
request->arg("mqttPwd").toCharArray(mConfig->mqtt.pwd, MQTT_PWD_LEN);
|
||||
request->arg("mqttTopic").toCharArray(mConfig->mqtt.topic, MQTT_TOPIC_LEN);
|
||||
mConfig->mqtt.port = request->arg("mqttPort").toInt();
|
||||
}
|
||||
|
||||
// serial console
|
||||
if(mWeb->arg("serIntvl") != "") {
|
||||
mConfig->serialInterval = mWeb->arg("serIntvl").toInt() & 0xffff;
|
||||
if(request->arg("serIntvl") != "") {
|
||||
mConfig->serialInterval = request->arg("serIntvl").toInt() & 0xffff;
|
||||
|
||||
mConfig->serialDebug = (mWeb->arg("serDbg") == "on");
|
||||
mConfig->serialShowIv = (mWeb->arg("serEn") == "on");
|
||||
mConfig->serialDebug = (request->arg("serDbg") == "on");
|
||||
mConfig->serialShowIv = (request->arg("serEn") == "on");
|
||||
// Needed to log TX buffers to serial console
|
||||
mMain->mSys->Radio.mSerialDebug = mConfig->serialDebug;
|
||||
}
|
||||
|
||||
mMain->saveValues();
|
||||
|
||||
if(mWeb->arg("reboot") == "on")
|
||||
showReboot();
|
||||
if(request->arg("reboot") == "on")
|
||||
onReboot(request);
|
||||
else
|
||||
mWeb->send(200, F("text/html"), F("<!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>"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void web::showStatistics(void) {
|
||||
DPRINTLN(DBG_VERBOSE, F("web::showStatistics"));
|
||||
mWeb->send(200, F("text/plain"), mMain->getStatistics());
|
||||
void web::onLive(AsyncWebServerRequest *request) {
|
||||
DPRINTLN(DBG_VERBOSE, F("onLive"));
|
||||
|
||||
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) {
|
||||
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)
|
||||
{
|
||||
void web::showWebApi(AsyncWebServerRequest *request) {
|
||||
DPRINTLN(DBG_VERBOSE, F("web::showWebApi"));
|
||||
DPRINTLN(DBG_DEBUG, mWeb->arg("plain"));
|
||||
DPRINTLN(DBG_DEBUG, request->arg("plain"));
|
||||
const size_t capacity = 200; // Use arduinojson.org/assistant to compute the capacity.
|
||||
DynamicJsonDocument response(capacity);
|
||||
|
||||
// Parse JSON object
|
||||
deserializeJson(response, mWeb->arg("plain"));
|
||||
deserializeJson(response, request->arg("plain"));
|
||||
// ToDo: error handling for payload
|
||||
uint8_t iv_id = response["inverter"];
|
||||
uint8_t cmd = response["cmd"];
|
||||
Inverter<> *iv = mMain->mSys->getInverterByPos(iv_id);
|
||||
if (NULL != iv)
|
||||
{
|
||||
if (response["tx_request"] == (uint8_t)TX_REQ_INFO)
|
||||
{
|
||||
if (NULL != iv) {
|
||||
if (response["tx_request"] == (uint8_t)TX_REQ_INFO) {
|
||||
// if the AlarmData is requested set the Alarm Index to the requested one
|
||||
if (cmd == AlarmData || cmd == AlarmUpdate){
|
||||
if (cmd == AlarmData || cmd == AlarmUpdate) {
|
||||
// set the AlarmMesIndex for the request from user input
|
||||
iv->alarmMesIndex = response["payload"];
|
||||
}
|
||||
|
@ -597,44 +341,137 @@ void web::showWebApi(void)
|
|||
}
|
||||
|
||||
|
||||
if (response["tx_request"] == (uint8_t)TX_REQ_DEVCONTROL)
|
||||
{
|
||||
if (response["cmd"] == (uint8_t)ActivePowerContr)
|
||||
{
|
||||
if (response["tx_request"] == (uint8_t)TX_REQ_DEVCONTROL) {
|
||||
if (response["cmd"] == (uint8_t)ActivePowerContr) {
|
||||
uint16_t webapiPayload = response["payload"];
|
||||
uint16_t webapiPayload2 = response["payload2"];
|
||||
if (webapiPayload > 0 && webapiPayload < 10000)
|
||||
{
|
||||
if (webapiPayload > 0 && webapiPayload < 10000) {
|
||||
iv->devControlCmd = ActivePowerContr;
|
||||
iv->powerLimit[0] = webapiPayload;
|
||||
if (webapiPayload2 > 0)
|
||||
{
|
||||
iv->powerLimit[1] = webapiPayload2; // dev option, no sanity check
|
||||
}
|
||||
else
|
||||
{ // if not set, set it to 0x0000 default
|
||||
iv->powerLimit[1] = AbsolutNonPersistent; // payload will be seted temporay in Watt absolut
|
||||
}
|
||||
else // if not set, set it to 0x0000 default
|
||||
iv->powerLimit[1] = AbsolutNonPersistent; // payload will be seted temporary in Watt absolut
|
||||
if (iv->powerLimit[1] & 0x0001)
|
||||
{
|
||||
DPRINTLN(DBG_INFO, F("Power limit for inverter ") + String(iv->id) + F(" set to ") + String(iv->powerLimit[0]) + F("% via REST API"));
|
||||
}
|
||||
else
|
||||
{
|
||||
DPRINTLN(DBG_INFO, F("Power limit for inverter ") + String(iv->id) + F(" set to ") + String(iv->powerLimit[0]) + F("W via REST API"));
|
||||
}
|
||||
iv->devControlRequest = true; // queue it in the request loop
|
||||
}
|
||||
}
|
||||
if (response["cmd"] == (uint8_t)TurnOff){
|
||||
if (response["cmd"] == (uint8_t)TurnOff) {
|
||||
iv->devControlCmd = TurnOff;
|
||||
iv->devControlRequest = true; // queue it in the request loop
|
||||
}
|
||||
if (response["cmd"] == (uint8_t)TurnOn){
|
||||
if (response["cmd"] == (uint8_t)TurnOn) {
|
||||
iv->devControlCmd = TurnOn;
|
||||
iv->devControlRequest = true; // queue it in the request loop
|
||||
}
|
||||
if (response["cmd"] == (uint8_t)CleanState_LockAndAlarm) {
|
||||
iv->devControlCmd = CleanState_LockAndAlarm;
|
||||
iv->devControlRequest = true; // queue it in the request loop
|
||||
}
|
||||
if (response["cmd"] == (uint8_t)Restart) {
|
||||
iv->devControlCmd = Restart;
|
||||
iv->devControlRequest = true; // queue it in the request loop
|
||||
}
|
||||
}
|
||||
}
|
||||
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__
|
||||
|
||||
#include "dbg.h"
|
||||
#ifdef ESP8266
|
||||
#include <ESP8266WebServer.h>
|
||||
#include <ESP8266HTTPUpdateServer.h>
|
||||
#elif defined(ESP32)
|
||||
#include <WebServer.h>
|
||||
#include <HTTPUpdateServer.h>
|
||||
#ifdef ESP32
|
||||
#include "AsyncTCP.h"
|
||||
#include "Update.h"
|
||||
#else
|
||||
#include "ESPAsyncTCP.h"
|
||||
#endif
|
||||
|
||||
#include "ESPAsyncWebServer.h"
|
||||
#include "app.h"
|
||||
#include "webApi.h"
|
||||
|
||||
#define WEB_SERIAL_BUF_SIZE 2048
|
||||
|
||||
class app;
|
||||
class webApi;
|
||||
|
||||
class web {
|
||||
public:
|
||||
web(app *main, sysConfig_t *sysCfg, config_t *config, char version[]);
|
||||
web(app *main, sysConfig_t *sysCfg, config_t *config, statistics_t *stat, char version[]);
|
||||
~web() {}
|
||||
|
||||
void setup(void);
|
||||
void loop(void);
|
||||
|
||||
void showIndex(void);
|
||||
void showCss(void);
|
||||
void showFavicon(void);
|
||||
void showNotFound(void);
|
||||
void showUptime(void);
|
||||
void showReboot(void);
|
||||
void showErase();
|
||||
void showFactoryRst(void);
|
||||
void showSetup(void);
|
||||
void showSave(void);
|
||||
void onConnect(AsyncEventSourceClient *client);
|
||||
|
||||
void showStatistics(void);
|
||||
void showVisualization(void);
|
||||
void showLiveData(void);
|
||||
void showJson(void);
|
||||
void showWebApi(void);
|
||||
void onIndex(AsyncWebServerRequest *request);
|
||||
void onCss(AsyncWebServerRequest *request);
|
||||
void onApiJs(AsyncWebServerRequest *request);
|
||||
void onFavicon(AsyncWebServerRequest *request);
|
||||
void showNotFound(AsyncWebServerRequest *request);
|
||||
void onReboot(AsyncWebServerRequest *request);
|
||||
void showErase(AsyncWebServerRequest *request);
|
||||
void showFactoryRst(AsyncWebServerRequest *request);
|
||||
void onSetup(AsyncWebServerRequest *request);
|
||||
void showSave(AsyncWebServerRequest *request);
|
||||
|
||||
void onLive(AsyncWebServerRequest *request);
|
||||
void showWebApi(AsyncWebServerRequest *request);
|
||||
|
||||
void onUpdate(AsyncWebServerRequest *request);
|
||||
void showUpdate(AsyncWebServerRequest *request);
|
||||
void showUpdate2(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final);
|
||||
|
||||
void serialCb(String msg);
|
||||
|
||||
private:
|
||||
#ifdef ESP8266
|
||||
ESP8266WebServer *mWeb;
|
||||
ESP8266HTTPUpdateServer *mUpdater;
|
||||
#elif defined(ESP32)
|
||||
WebServer *mWeb;
|
||||
HTTPUpdateServer *mUpdater;
|
||||
#endif
|
||||
void onSerial(AsyncWebServerRequest *request);
|
||||
|
||||
AsyncWebServer *mWeb;
|
||||
AsyncEventSource *mEvts;
|
||||
|
||||
config_t *mConfig;
|
||||
sysConfig_t *mSysCfg;
|
||||
statistics_t *mStat;
|
||||
char *mVersion;
|
||||
app *mMain;
|
||||
webApi *mApi;
|
||||
|
||||
bool mSerialAddTime;
|
||||
char mSerialBuf[WEB_SERIAL_BUF_SIZE];
|
||||
uint16_t mSerialBufFill;
|
||||
uint32_t mWebSerialTicker;
|
||||
uint32_t mWebSerialInterval;
|
||||
};
|
||||
|
||||
#endif /*__WEB_H__*/
|
||||
|
|
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