mirror of
https://github.com/lumapu/ahoy.git
synced 2025-06-28 15:26:58 +02:00
Merge branch 'development02' into patch-2
This commit is contained in:
commit
ebe532d773
23 changed files with 783 additions and 354 deletions
58
.github/workflows/compile_development.yml
vendored
Normal file
58
.github/workflows/compile_development.yml
vendored
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
name: Ahoy Dev-Build for ESP8266/ESP32
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: development*
|
||||||
|
paths-ignore:
|
||||||
|
- '**.md' # Do no build on *.md changes
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
ref: development02
|
||||||
|
- uses: benjlevesque/short-sha@v1.2
|
||||||
|
id: short-sha
|
||||||
|
with:
|
||||||
|
length: 7
|
||||||
|
- name: cache-pip
|
||||||
|
uses: actions/cache@v3
|
||||||
|
with:
|
||||||
|
path: ~/.cache/pip
|
||||||
|
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-pip-
|
||||||
|
- name: cache-platformio
|
||||||
|
uses: actions/cache@v3
|
||||||
|
with:
|
||||||
|
path: ~/.platformio
|
||||||
|
key: ${{ runner.os }}-${{ hashFiles('**/lockfiles') }}
|
||||||
|
- name: setup-python
|
||||||
|
uses: actions/setup-python@v3
|
||||||
|
- name: install-platformio
|
||||||
|
run: |
|
||||||
|
python -m pip install --upgrade pip
|
||||||
|
pip install --upgrade platformio
|
||||||
|
- name: update-html
|
||||||
|
working-directory: tools/esp8266/html
|
||||||
|
run: python convert.py
|
||||||
|
- name: Run PlatformIO
|
||||||
|
run: pio run -d tools/esp8266 --environment esp8266-release --environment esp32-wroom32-release
|
||||||
|
- name: rename-binary-files
|
||||||
|
id: rename-binary-files
|
||||||
|
working-directory: tools/esp8266/scripts
|
||||||
|
run: python getVersion.py
|
||||||
|
- name: set-version
|
||||||
|
uses: cschleiden/replace-tokens@v1
|
||||||
|
with:
|
||||||
|
files: tools/esp8266/User_Manual.md
|
||||||
|
env:
|
||||||
|
VERSION: ${{ steps.rename-binary-files.outputs.name }}
|
||||||
|
- name: create-artifact
|
||||||
|
run: zip --junk-paths ${{ steps.rename-binary-files.outputs.name }}.zip tools/esp8266/.pio/build/out/* tools/esp8266/User_Manual.md
|
||||||
|
- uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: ${{ steps.rename-binary-files.outputs.name }}_dev_build
|
||||||
|
path: ./${{ steps.rename-binary-files.outputs.name }}.zip
|
2
.github/workflows/compile_esp8266.yml
vendored
2
.github/workflows/compile_esp8266.yml
vendored
|
@ -39,7 +39,7 @@ jobs:
|
||||||
working-directory: tools/esp8266/html
|
working-directory: tools/esp8266/html
|
||||||
run: python convert.py
|
run: python convert.py
|
||||||
- name: Run PlatformIO
|
- name: Run PlatformIO
|
||||||
run: pio run -d tools/esp8266 --environment esp8266-release
|
run: pio run -d tools/esp8266 --environment esp8266-release --environment esp32-wroom32-release
|
||||||
- name: rename-binary-files
|
- name: rename-binary-files
|
||||||
id: rename-binary-files
|
id: rename-binary-files
|
||||||
working-directory: tools/esp8266/scripts
|
working-directory: tools/esp8266/scripts
|
||||||
|
|
28
README.md
28
README.md
|
@ -1,22 +1,28 @@
|
||||||
|
 
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
# ahoy
|
# ahoy
|
||||||
Various tools, examples, and documentation for communicating with Hoymiles microinverters.
|
Ahoi is a project to bypass the original Hoymiles cloud solution.
|
||||||
|
In order to use this project, it is important what the area of application looks like.
|
||||||
|
With each version it is necessary to have an NRF24L01+.
|
||||||
|
|
||||||
In particular:
|
Click on the link below you are interested in.
|
||||||
|
There you will find further explanations on how to proceed. (*Note: It is still under construction!*)
|
||||||
|
|
||||||
* `doc/hoymiles-format-description.txt` is a [detailed description of the communications format](doc/hoymiles-format-description.md) and the history of this project
|
##### Most updated section
|
||||||
* `doc/getting-started-ESP8266.md` shows the [hardware setup for an ESP8266-based system](doc/getting-started-ESP8266.md)
|
- [ESP8266](tools/esp8266/) that includes an web interface
|
||||||
* The `tools` folder contains various software tools for RaspberryPi, Arduino and ESP8266/ESP32:
|
|
||||||
* A [version for ESP8266](tools/esp8266/) that includes an web interface 
|
|
||||||
* A [version for Arduino Nano](tools/nano/NRF24_SendRcv/)
|
|
||||||
* An [alternative Version of the above](tools/NRF24_SendRcv/)
|
|
||||||
* A [different implementation](tools/HoyDtuSim/)
|
|
||||||
* An [implementation for Raspberry Pi](tools/rpi/) that polls an inverter and archives results as log files/stdout as well as posting them to an MQTT broker.
|
|
||||||
|
|
||||||
Contributors are always welcome!
|
##### will be updated as needed
|
||||||
|
- [Arduino Nano](tools/nano/NRF24_SendRcv/)
|
||||||
|
- [Raspberry Pi](tools/rpi/)
|
||||||
|
- [others](tools/nano/NRF24_SendRcv/)
|
||||||
|
|
||||||
|
If errors occur or you have suggestions for ideas, please feel free to contact us [here](https://github.com/grindylow/ahoy/issues).
|
||||||
|
|
||||||
## Contact
|
## Contact
|
||||||
We run a Discord Server that can be used to get in touch with the Developers and Users.
|
We run a Discord Server that can be used to get in touch with the Developers and Users.
|
||||||
|
|
||||||
https://discord.gg/WzhxEY62mB
|
https://discord.gg/WzhxEY62mB
|
||||||
|
|
||||||
|
**Contributors are always welcome!**
|
||||||
|
|
1
tools/esp8266/.gitignore
vendored
1
tools/esp8266/.gitignore
vendored
|
@ -3,3 +3,4 @@
|
||||||
.vscode/c_cpp_properties.json
|
.vscode/c_cpp_properties.json
|
||||||
.vscode/launch.json
|
.vscode/launch.json
|
||||||
.vscode/ipch
|
.vscode/ipch
|
||||||
|
config_override.h
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
This code is intended to run on a Wemos D1mini or similar. The code is based on 'Hubi's code, which can be found here: <https://www.mikrocontroller.net/topic/525778?page=3#7033371>
|
This page describes how the module of a Wemos D1 mini and ESP8266 is wired to the radio module, flashed and how the further steps are to communicate with the WR HM series.
|
||||||
|
|
||||||
The NRF24L01+ radio module is connected to the
|
The NRF24L01+ radio module is connected to the
|
||||||
standard SPI pins:
|
standard SPI pins:
|
||||||
|
@ -28,52 +28,8 @@ Additional there are 3 pins, which can be set individual:
|
||||||
|
|
||||||
These pins can be changed from the /setup URL
|
These pins can be changed from the /setup URL
|
||||||
|
|
||||||
|
#### Compatiblity
|
||||||
## Compile
|
|
||||||
|
|
||||||
This code can be compiled using Visual Studio Code and **PlatformIO** Addon. The settings were:
|
|
||||||
|
|
||||||
- Board: Generic ESP8266 Module
|
|
||||||
- Flash-Size: 4MB
|
|
||||||
- Install libraries (not included in the Arduino IDE 1.8.19):
|
|
||||||
- Time Arduino Time library (TimeLib.h)
|
|
||||||
- RF24 Optimized high speed nRF24L01+ driver class documentation
|
|
||||||
- PubSubClient A client library for MQTT messaging. By Nick O'Leary
|
|
||||||
- ArduinoJson Arduino Json library
|
|
||||||
|
|
||||||
### Optional Configuration before compilation
|
|
||||||
|
|
||||||
- number of supported inverters (set to 3 by default) `defines.h`
|
|
||||||
- DTU radio id `hmRadio.h`
|
|
||||||
- unformated list in webbrowser `/livedata` `config.h`, `LIVEDATA_VISUALIZED`
|
|
||||||
|
|
||||||
|
|
||||||
## Flash ESP with Firmware
|
|
||||||
|
|
||||||
1. flash the ESP with the compiled firmware using the UART pins or any preinstalled firmware with OTA capabilities
|
|
||||||
2. repower the ESP
|
|
||||||
3. the ESP will start as access point (AP) if there is no network config stored in its eeprom
|
|
||||||
4. connect to the AP, you will be forwarded to the setup page
|
|
||||||
5. configure your WiFi settings, save, repower
|
|
||||||
6. check your router or serial console for the IP address of the module. You can try ping the configured device name as well.
|
|
||||||
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
Connect the ESP to power and to your serial console (optional). The webinterface has the following abilities:
|
|
||||||
|
|
||||||
- OTA Update (over the air update)
|
|
||||||
- Configuration (Wifi, inverter(s), Pinout, MQTT)
|
|
||||||
- visual display of the connected inverters / modules
|
|
||||||
- some statistics about communication (debug)
|
|
||||||
|
|
||||||
The serial console will print the converted values which were read out of the inverter(s)
|
|
||||||
|
|
||||||
|
|
||||||
## Compatiblity
|
|
||||||
|
|
||||||
For now the following inverters should work out of the box:
|
For now the following inverters should work out of the box:
|
||||||
|
|
||||||
- HM300
|
- HM300
|
||||||
- HM350
|
- HM350
|
||||||
- HM400
|
- HM400
|
||||||
|
@ -83,6 +39,25 @@ For now the following inverters should work out of the box:
|
||||||
- HM1200
|
- HM1200
|
||||||
- HM1500
|
- HM1500
|
||||||
|
|
||||||
|
The NRF24L01+ radio module is connected to the standard SPI pins.
|
||||||
|
Additional there are 3 pins, which can be set individual: CS, CE and IRQ
|
||||||
|
These pins can be changed in the http://<ip-adress>/setup URL or with a click on the Setup link.
|
||||||
|
|
||||||
|
## ESP8266 electr. associate
|
||||||
|
<img src="https://github.com/grindylow/ahoy/blob/main/doc/ESP8266_nRF24L01%2B_bb.png" width="300">
|
||||||
|
|
||||||
|
## Compile
|
||||||
|
|
||||||
|
This code can be compiled using Visual Studio Code and **PlatformIO** Addon. The settings were:
|
||||||
|
|
||||||
|
- Board: Generic ESP8266 Module
|
||||||
|
- Flash-Size: 4MB
|
||||||
|
- Install libraries (not included in the Arduino IDE 1.8.19):
|
||||||
|
- `Time` 1.6.1
|
||||||
|
- `RF24` 1.4.5
|
||||||
|
- `PubSubClient` 2.8
|
||||||
|
- `ArduinoJson` 6.19.4
|
||||||
|
|
||||||
## Used Libraries
|
## Used Libraries
|
||||||
|
|
||||||
- `ESP8266WiFi` 1.0
|
- `ESP8266WiFi` 1.0
|
||||||
|
@ -94,6 +69,80 @@ For now the following inverters should work out of the box:
|
||||||
- `PubSubClient` 2.8
|
- `PubSubClient` 2.8
|
||||||
- `ArduinoJson` 6.19.4
|
- `ArduinoJson` 6.19.4
|
||||||
|
|
||||||
|
### Optional Configuration before compilation
|
||||||
|
|
||||||
|
- 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`
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
## Flash ESP with Firmware
|
||||||
|
|
||||||
|
#### nodemcu-pyflasher (easy way)
|
||||||
|
1. download the flash-tool [nodemcu-pyflasher](https://github.com/marcelstoer/nodemcu-pyflasher)
|
||||||
|
2. download latest release bin-file from [ahoy_](https://github.com/grindylow/ahoy/releases)
|
||||||
|
3. connect the target device with your pc.
|
||||||
|
4. Set the correct serial port and select the correct *.bin file
|
||||||
|
5. click now on "Flash NodeMCU"
|
||||||
|
|
||||||
|
1. flash the ESP with the compiled firmware using the UART pins or any preinstalled firmware with OTA capabilities
|
||||||
|
2. repower the ESP
|
||||||
|
3. the ESP will start as access point (AP) if there is no network config stored in its eeprom
|
||||||
|
4. connect to the AP, you will be forwarded to the setup page
|
||||||
|
|
||||||
|
X. configure your WiFi settings, save, repower
|
||||||
|
Y. check your router or serial console for the IP address of the module. You can try ping the configured device name as well.
|
||||||
|
|
||||||
|
! ATTENTION: If you update from a very low version to the newest, please make sure to wipe all flash data!
|
||||||
|
|
||||||
|
## pages
|
||||||
|
| page | output |
|
||||||
|
| ---- | ------ |
|
||||||
|
| /uptime | 0 Days, 01:37:34; now: 2022-08-21 11:13:53 |
|
||||||
|
| /reboot | reboot dtu device |
|
||||||
|
| /erase | |
|
||||||
|
| /factory | |
|
||||||
|
| /setup | |
|
||||||
|
| /save | open the setup site |
|
||||||
|
| /cmdstat | show stat from the home site |
|
||||||
|
| /visualization | |
|
||||||
|
| /livedata | |
|
||||||
|
| /json | json output from the livedata |
|
||||||
|
| /api | |
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
The webinterface has the following abilities:
|
||||||
|
- OTA Update (over the air update)
|
||||||
|
- Configuration (Wifi, inverter(s), NTP Server, Pinout, MQTT, Amplifier Power Level, Debug)
|
||||||
|
- visual display of the connected inverters / modules
|
||||||
|
- some statistics about communication (debug)
|
||||||
|
|
||||||
|
The serial console will print the converted values which were read out of the inverter(s)
|
||||||
|
|
||||||
|
### MQTT command to set the DTU without webinterface
|
||||||
|
[Read here](https://github.com/grindylow/ahoy/blob/development02/tools/esp8266/User_Manual.md)
|
||||||
|
|
||||||
|
## Todo's [See this post](https://github.com/grindylow/ahoy/issues/142)
|
||||||
|
|
||||||
|
- [ ] Wechsel zu AsyncWebServer und ElegantOTA für Stabilität
|
||||||
|
- [x] klarer Scheduler / Task manager, der ggf. den Receive Task priorisieren kann
|
||||||
|
- [x] Device Info Kommandos (Firmware Version, etc.) über das Dashboard anzeigen [Device Information ( `0x15` `REQ_ARW_DAT_ALL` ) SubCmd Kommandos #145](https://github.com/grindylow/ahoy/issues/145)
|
||||||
|
- [ ] AlarmData & AlarmUpdate Parsen und auf eigener Seite darstellen
|
||||||
|
------------------ SWIM LANE ---------------------------
|
||||||
|
- [ ] Device Control Kommandos aus dem Setup ermöglichen (TurnOn, TurnOff, Restart, ActivePower Limit, ReactivePower Limit, SetPowerFactor, etc.)
|
||||||
|
- [ ] Settings exportieren / importieren (API/UI)
|
||||||
|
- [ ] Settings in settings.ini speichern (LittleFS statt EEPROM) [Settings in settings.ini speichern (LittleFS statt EEPROM) #164](https://github.com/grindylow/ahoy/issues/164)
|
||||||
|
- [ ] Homepage aufräumen nur ein Status (aktuell drei AJAX Calls /uptime, /time, /cmdstat)
|
||||||
|
- [ ] app.cpp aufräumen und in hmRadio / hmProtokollGen3 auslagern
|
||||||
|
- [ ] MI Wechselrichter unterstützen (miSystem, miInverter, miDefines, miProtokollGen2 etc.)
|
||||||
|
- [ ] nRF24 Interrupt Handling sinnvoll oder warum macht die nRF24 Bibliothek ständig `0x07` Statusabfragen [NRF24 polling trotz aktiviertem IRQ #83](https://github.com/grindylow/ahoy/issues/83)
|
||||||
|
- [ ] Debug Level im Setup änderbar -auch Livedata Visualisierung abschalten ?
|
||||||
|
- [ ] MQTT Discovery (HomeAssistant) im Setup optional machen
|
||||||
|
- [x] MQTT Subscribe nur beim Reconnect [Das subscribe in der Reconnect Procedure sollte doch nur nach einem conect ausgeführt werden und nicht bei jedem Duchlauf #139](https://github.com/grindylow/ahoy/issues/139)
|
||||||
|
|
||||||
## Contact
|
## Contact
|
||||||
We run a Discord Server that can be used to get in touch with the Developers and Users.
|
We run a Discord Server that can be used to get in touch with the Developers and Users.
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,7 @@ app::app() {
|
||||||
mWifi = new ahoywifi(this, &mSysConfig, &mConfig);
|
mWifi = new ahoywifi(this, &mSysConfig, &mConfig);
|
||||||
|
|
||||||
resetSystem();
|
resetSystem();
|
||||||
loadDefaultConfig();
|
loadDefaultConfig();
|
||||||
|
|
||||||
mSys = new HmSystemType();
|
mSys = new HmSystemType();
|
||||||
}
|
}
|
||||||
|
@ -53,14 +53,18 @@ void app::loop(void) {
|
||||||
mWebInst->loop();
|
mWebInst->loop();
|
||||||
|
|
||||||
if(checkTicker(&mUptimeTicker, mUptimeInterval)) {
|
if(checkTicker(&mUptimeTicker, mUptimeInterval)) {
|
||||||
mUptimeSecs++;
|
if(millis() - mPrevMillis >= 1000) {
|
||||||
if(0 != mTimestamp)
|
mPrevMillis += 1000;
|
||||||
mTimestamp++;
|
mUptimeSecs++;
|
||||||
else {
|
if(0 != mTimestamp)
|
||||||
if(!apActive) {
|
mTimestamp++;
|
||||||
mTimestamp = mWifi->getNtpTime();
|
}
|
||||||
DPRINTLN(DBG_INFO, "[NTP]: " + getDateTimeStr(mTimestamp));
|
}
|
||||||
}
|
|
||||||
|
if(checkTicker(&mNtpRefreshTicker, mNtpRefreshInterval)) {
|
||||||
|
if(!apActive) {
|
||||||
|
mTimestamp = mWifi->getNtpTime();
|
||||||
|
DPRINTLN(DBG_INFO, "[NTP]: " + getDateTimeStr(mTimestamp));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -115,30 +119,30 @@ 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
|
||||||
DPRINTLN(DBG_DEBUG, F("Response from devcontrol request received"));
|
DPRINTLN(DBG_DEBUG, F("Response from devcontrol request received"));
|
||||||
iv->devControlRequest = false;
|
iv->devControlRequest = false;
|
||||||
switch (p->packet[12]){
|
switch (p->packet[12]) {
|
||||||
case ActivePowerContr:
|
case ActivePowerContr:
|
||||||
if (iv->devControlCmd >= ActivePowerContr && iv->devControlCmd <= PFSet){ // ok inverter accepted the set point copy it to dtu eeprom
|
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
|
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 + iv->id * 2, iv->powerLimit[0]);
|
||||||
mEep->write(ADDR_INV_PWR_LIM_CON + iv->id * 2,iv->powerLimit[1]);
|
mEep->write(ADDR_INV_PWR_LIM_CON + iv->id * 2, iv->powerLimit[1]);
|
||||||
updateCrc();
|
updateCrc();
|
||||||
mEep->commit();
|
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"));
|
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 {
|
} else
|
||||||
DPRINTLN(DBG_INFO, F("Inverter ") + String(iv->id) + F(" has accepted power limit set point ") + String(iv->powerLimit[0]) + F(" with PowerLimitControl ") + String(iv->powerLimit[1]));
|
DPRINTLN(DBG_INFO, F("Inverter ") + String(iv->id) + F(" has accepted power limit set point ") + String(iv->powerLimit[0]) + F(" with PowerLimitControl ") + String(iv->powerLimit[1]));
|
||||||
|
iv->devControlCmd = Init;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
if (iv->devControlCmd == ActivePowerContr) {
|
||||||
|
//case inverter did not accept the sent limit; set back to last stored limit
|
||||||
|
mEep->read(ADDR_INV_PWR_LIM + iv->id * 2, (uint16_t *)&(iv->powerLimit[0]));
|
||||||
|
mEep->read(ADDR_INV_PWR_LIM_CON + iv->id * 2, (uint16_t *)&(iv->powerLimit[1]));
|
||||||
|
DPRINTLN(DBG_INFO, F("Inverter has not accepted power limit set point"));
|
||||||
}
|
}
|
||||||
iv->devControlCmd = Init;
|
iv->devControlCmd = Init;
|
||||||
}
|
break;
|
||||||
break;
|
|
||||||
default:
|
|
||||||
if (iv->devControlCmd == ActivePowerContr){
|
|
||||||
//case inverter did not accept the sent limit; set back to last stored limit
|
|
||||||
mEep->read(ADDR_INV_PWR_LIM + iv->id * 2, (uint16_t *)&(iv->powerLimit[0]));
|
|
||||||
mEep->read(ADDR_INV_PWR_LIM_CON + iv->id * 2, (uint16_t *)&(iv->powerLimit[1]));
|
|
||||||
DPRINTLN(DBG_INFO, F("Inverter has not accepted power limit set point"));
|
|
||||||
}
|
|
||||||
iv->devControlCmd = Init;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -160,20 +164,7 @@ void app::loop(void) {
|
||||||
if((++mMqttTicker >= mMqttInterval) && (mMqttInterval != 0xffff) && mMqttActive) {
|
if((++mMqttTicker >= mMqttInterval) && (mMqttInterval != 0xffff) && mMqttActive) {
|
||||||
mMqttTicker = 0;
|
mMqttTicker = 0;
|
||||||
mMqtt.isConnected(true); // really needed? See comment from HorstG-57 #176
|
mMqtt.isConnected(true); // really needed? See comment from HorstG-57 #176
|
||||||
char topic[30], val[10];
|
char val[10];
|
||||||
for(uint8_t id = 0; id < mSys->getNumInverters(); id++) {
|
|
||||||
Inverter<> *iv = mSys->getInverterByPos(id);
|
|
||||||
if(NULL != iv) {
|
|
||||||
if(iv->isAvailable(mTimestamp)) {
|
|
||||||
for(uint8_t i = 0; i < iv->listLen; i++) {
|
|
||||||
snprintf(topic, 30, "%s/ch%d/%s", iv->name, iv->assign[i].ch, fields[iv->assign[i].fieldId]);
|
|
||||||
snprintf(val, 10, "%.3f", iv->getValue(i));
|
|
||||||
mMqtt.sendMsg(topic, val);
|
|
||||||
yield();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
snprintf(val, 10, "%ld", millis()/1000);
|
snprintf(val, 10, "%ld", millis()/1000);
|
||||||
|
|
||||||
#ifndef __MQTT_NO_DISCOVERCONFIG__
|
#ifndef __MQTT_NO_DISCOVERCONFIG__
|
||||||
|
@ -239,6 +230,10 @@ void app::loop(void) {
|
||||||
|
|
||||||
if(!mPayload[iv->id].complete) {
|
if(!mPayload[iv->id].complete) {
|
||||||
mRxFailed++;
|
mRxFailed++;
|
||||||
|
iv->setQueuedCmdFinished(); // command failed
|
||||||
|
if(mConfig.serialDebug) {
|
||||||
|
DPRINTLN(DBG_INFO, F("enqueued cmd failed/timeout"));
|
||||||
|
}
|
||||||
if(mConfig.serialDebug) {
|
if(mConfig.serialDebug) {
|
||||||
DPRINT(DBG_INFO, F("Inverter #") + String(iv->id) + " ");
|
DPRINT(DBG_INFO, F("Inverter #") + String(iv->id) + " ");
|
||||||
DPRINTLN(DBG_INFO, F("no Payload received! (retransmits: ") + String(mPayload[iv->id].retransmits) + ")");
|
DPRINTLN(DBG_INFO, F("no Payload received! (retransmits: ") + String(mPayload[iv->id].retransmits) + ")");
|
||||||
|
@ -249,12 +244,12 @@ void app::loop(void) {
|
||||||
|
|
||||||
yield();
|
yield();
|
||||||
if(mConfig.serialDebug)
|
if(mConfig.serialDebug)
|
||||||
DPRINTLN(DBG_DEBUG, F("app:loop WiFi WiFi.status ") + String(WiFi.status()) );
|
DPRINTLN(DBG_DEBUG, F("app:loop WiFi WiFi.status ") + String(WiFi.status()));
|
||||||
DPRINTLN(DBG_INFO, F("Requesting Inverter SN ") + String(iv->serial.u64, HEX));
|
DPRINTLN(DBG_INFO, F("Requesting Inverter SN ") + String(iv->serial.u64, HEX));
|
||||||
if(iv->devControlRequest && iv->powerLimit[0] > 0){ // prevent to "switch off"
|
if(iv->devControlRequest && (iv->powerLimit[0] > 0) && (NoPowerLimit != iv->powerLimit[1])) { // prevent to "switch off"
|
||||||
if(mConfig.serialDebug)
|
if(mConfig.serialDebug)
|
||||||
DPRINTLN(DBG_INFO, F("Devcontrol request ") + String(iv->devControlCmd) + F(" power limit ") + String(iv->powerLimit[0]));
|
DPRINTLN(DBG_INFO, F("Devcontrol request ") + String(iv->devControlCmd) + F(" power limit ") + String(iv->powerLimit[0]));
|
||||||
mSys->Radio.sendControlPacket(iv->radioId.u64,iv->devControlCmd ,iv->powerLimit);
|
mSys->Radio.sendControlPacket(iv->radioId.u64, iv->devControlCmd ,iv->powerLimit);
|
||||||
iv->enqueCommand<InfoCommand>(SystemConfigPara);
|
iv->enqueCommand<InfoCommand>(SystemConfigPara);
|
||||||
} else {
|
} else {
|
||||||
mSys->Radio.sendTimePacket(iv->radioId.u64,iv->getQueuedCmd(), mPayload[iv->id].ts,iv->alarmMesIndex);
|
mSys->Radio.sendTimePacket(iv->radioId.u64,iv->getQueuedCmd(), mPayload[iv->id].ts,iv->alarmMesIndex);
|
||||||
|
@ -346,8 +341,11 @@ void app::processPayload(bool retransmit) {
|
||||||
else {
|
else {
|
||||||
mPayload[iv->id].complete = true;
|
mPayload[iv->id].complete = true;
|
||||||
iv->ts = mPayload[iv->id].ts;
|
iv->ts = mPayload[iv->id].ts;
|
||||||
uint8_t payload[128] = {0};
|
uint8_t payload[128];
|
||||||
uint8_t offs = 0;
|
uint8_t offs = 0;
|
||||||
|
|
||||||
|
memset(payload, 0, 128);
|
||||||
|
|
||||||
for(uint8_t i = 0; i < (mPayload[iv->id].maxPackId); i ++) {
|
for(uint8_t i = 0; i < (mPayload[iv->id].maxPackId); i ++) {
|
||||||
memcpy(&payload[offs], mPayload[iv->id].data[i], (mPayload[iv->id].len[i]));
|
memcpy(&payload[offs], mPayload[iv->id].data[i], (mPayload[iv->id].len[i]));
|
||||||
offs += (mPayload[iv->id].len[i]);
|
offs += (mPayload[iv->id].len[i]);
|
||||||
|
@ -367,10 +365,33 @@ void app::processPayload(bool retransmit) {
|
||||||
}
|
}
|
||||||
iv->doCalculations(); // cmd value decides which parser is used to decode payload
|
iv->doCalculations(); // cmd value decides which parser is used to decode payload
|
||||||
|
|
||||||
|
iv->setQueuedCmdFinished();
|
||||||
|
|
||||||
|
// MQTT send out
|
||||||
|
if(mMqttActive) {
|
||||||
|
char topic[30], val[10];
|
||||||
|
for (uint8_t id = 0; id < mSys->getNumInverters(); id++)
|
||||||
|
{
|
||||||
|
Inverter<> *iv = mSys->getInverterByPos(id);
|
||||||
|
if (NULL != iv)
|
||||||
|
{
|
||||||
|
if (iv->isAvailable(mTimestamp))
|
||||||
|
{
|
||||||
|
for (uint8_t i = 0; i < iv->listLen; i++)
|
||||||
|
{
|
||||||
|
snprintf(topic, 30, "%s/ch%d/%s", iv->name, iv->assign[i].ch, fields[iv->assign[i].fieldId]);
|
||||||
|
snprintf(val, 10, "%.3f", iv->getValue(i));
|
||||||
|
mMqtt.sendMsg(topic, val);
|
||||||
|
yield();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef __MQTT_AFTER_RX__
|
#ifdef __MQTT_AFTER_RX__
|
||||||
doMQTT = true;
|
doMQTT = true;
|
||||||
#endif
|
#endif
|
||||||
iv->setQueuedCmdFinished();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
yield();
|
yield();
|
||||||
|
@ -483,9 +504,10 @@ String app::getStatistics(void) {
|
||||||
Inverter<> *iv;
|
Inverter<> *iv;
|
||||||
for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i++) {
|
for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i++) {
|
||||||
iv = mSys->getInverterByPos(i);
|
iv = mSys->getInverterByPos(i);
|
||||||
|
content += F("Inverter #") + String(i) + F(": ");
|
||||||
if(NULL != iv) {
|
if(NULL != iv) {
|
||||||
bool avail = true;
|
bool avail = true;
|
||||||
content += F("Inverter '") + String(iv->name) + F(" (FW-Version: ") + String(iv->fwVersion) +F(")") + F("' is ");
|
content += String(iv->name) + F(" (v") + String(iv->fwVersion) +F(")") + F(" is ");
|
||||||
if(!iv->isAvailable(mTimestamp)) {
|
if(!iv->isAvailable(mTimestamp)) {
|
||||||
content += F("not ");
|
content += F("not ");
|
||||||
avail = false;
|
avail = false;
|
||||||
|
@ -500,9 +522,8 @@ String app::getStatistics(void) {
|
||||||
content += F("-> last successful transmission: ") + getDateTimeStr(iv->getLastTs()) + "\n";
|
content += F("-> last successful transmission: ") + getDateTimeStr(iv->getLastTs()) + "\n";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else
|
||||||
content += F("Inverter ") + String(i) + F(" not (correctly) configured\n");
|
content += F("n/a\n");
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!mSys->Radio.isChipConnected())
|
if(!mSys->Radio.isChipConnected())
|
||||||
|
@ -523,119 +544,6 @@ String app::getStatistics(void) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
|
||||||
String app::getLiveData(void)
|
|
||||||
{
|
|
||||||
String modHtml;
|
|
||||||
for (uint8_t id = 0; id < mSys->getNumInverters(); id++)
|
|
||||||
{
|
|
||||||
Inverter<> *iv = mSys->getInverterByPos(id);
|
|
||||||
if (NULL != iv)
|
|
||||||
{
|
|
||||||
#ifdef LIVEDATA_VISUALIZED
|
|
||||||
uint8_t modNum, pos;
|
|
||||||
switch (iv->type)
|
|
||||||
{
|
|
||||||
default:
|
|
||||||
case INV_TYPE_1CH:
|
|
||||||
modNum = 1;
|
|
||||||
break;
|
|
||||||
case INV_TYPE_2CH:
|
|
||||||
modNum = 2;
|
|
||||||
break;
|
|
||||||
case INV_TYPE_4CH:
|
|
||||||
modNum = 4;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
modHtml += F("<div class=\"iv\">"
|
|
||||||
"<div class=\"ch-iv\"><span class=\"head\">") +
|
|
||||||
String(iv->name) + F(" Limit ") + String(iv->actPowerLimit);
|
|
||||||
if (true)
|
|
||||||
{ // live Power Limit from inverter is always in %
|
|
||||||
modHtml += F(" %</span>");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
modHtml += F(" W</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 < 12; fld++)
|
|
||||||
{
|
|
||||||
pos = (iv->getPosByChFld(CH0, list[fld]));
|
|
||||||
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: ") + 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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return modHtml;
|
|
||||||
}
|
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
String app::getJson(void) {
|
String app::getJson(void) {
|
||||||
DPRINTLN(DBG_VERBOSE, F("app::showJson"));
|
DPRINTLN(DBG_VERBOSE, F("app::showJson"));
|
||||||
|
@ -751,7 +659,11 @@ const char* app::getFieldStateClass(uint8_t fieldId) {
|
||||||
void app::resetSystem(void) {
|
void app::resetSystem(void) {
|
||||||
mUptimeSecs = 0;
|
mUptimeSecs = 0;
|
||||||
mUptimeTicker = 0xffffffff;
|
mUptimeTicker = 0xffffffff;
|
||||||
mUptimeInterval = 1000;
|
mUptimeInterval = 500; // [ms]
|
||||||
|
mPrevMillis = 0;
|
||||||
|
|
||||||
|
mNtpRefreshTicker = 0;
|
||||||
|
mNtpRefreshInterval = NTP_REFRESH_INTERVAL; // [ms]
|
||||||
|
|
||||||
#ifdef AP_ONLY
|
#ifdef AP_ONLY
|
||||||
mTimestamp = 1;
|
mTimestamp = 1;
|
||||||
|
@ -852,11 +764,16 @@ void app::loadEEpconfig(void) {
|
||||||
// it is "doppelt-gemoppelt" because the inverter shall remember the setting if the dtu makes a power cycle / reboot
|
// it is "doppelt-gemoppelt" because the inverter shall remember the setting if the dtu makes a power cycle / reboot
|
||||||
if (iv->powerLimit[0] != 0xffff) {
|
if (iv->powerLimit[0] != 0xffff) {
|
||||||
iv->devControlCmd = ActivePowerContr; // set active power limit
|
iv->devControlCmd = ActivePowerContr; // set active power limit
|
||||||
if (iv->powerLimit[1] & 0x0001){
|
DPRINT(DBG_INFO, F("add inverter: ") + String(name) + ", SN: " + String(invSerial, HEX));
|
||||||
DPRINTLN(DBG_INFO, F("add inverter: ") + String(name) + ", SN: " + String(invSerial, HEX) + ", Power Limit: " + String(iv->powerLimit[0]) + " in %");
|
if(iv->powerLimit[1] != NoPowerLimit) {
|
||||||
} else {
|
DBGPRINT(F(", Power Limit: ") + String(iv->powerLimit[0]));
|
||||||
DPRINTLN(DBG_INFO, F("add inverter: ") + String(name) + ", SN: " + String(invSerial, HEX) + ", Power Limit: " + String(iv->powerLimit[0]) + " in Watt");
|
if ((iv->powerLimit[1] & 0x0001) == 0x0001)
|
||||||
|
DBGPRINTLN(F(" in %"));
|
||||||
|
else
|
||||||
|
DBGPRINTLN(F(" in Watt"));
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
DBGPRINTLN(F(" "));
|
||||||
}
|
}
|
||||||
for(uint8_t j = 0; j < 4; j++) {
|
for(uint8_t j = 0; j < 4; j++) {
|
||||||
mEep->read(ADDR_INV_CH_NAME + (i * 4 * MAX_NAME_LENGTH) + j * MAX_NAME_LENGTH, iv->chName[j], MAX_NAME_LENGTH);
|
mEep->read(ADDR_INV_CH_NAME + (i * 4 * MAX_NAME_LENGTH) + j * MAX_NAME_LENGTH, iv->chName[j], MAX_NAME_LENGTH);
|
||||||
|
@ -879,17 +796,15 @@ void app::saveValues(void) {
|
||||||
mEep->write(ADDR_CFG, (uint8_t*)&mConfig, CFG_LEN);
|
mEep->write(ADDR_CFG, (uint8_t*)&mConfig, CFG_LEN);
|
||||||
Inverter<> *iv;
|
Inverter<> *iv;
|
||||||
for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i ++) {
|
for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i ++) {
|
||||||
iv = mSys->getInverterByPos(i);
|
iv = mSys->getInverterByPos(i, false);
|
||||||
if(NULL != iv) {
|
mEep->write(ADDR_INV_ADDR + (i * 8), iv->serial.u64);
|
||||||
mEep->write(ADDR_INV_ADDR + (i * 8), iv->serial.u64);
|
mEep->write(ADDR_INV_PWR_LIM + i * 2, iv->powerLimit[0]);
|
||||||
mEep->write(ADDR_INV_PWR_LIM + i * 2, iv->powerLimit[0]);
|
mEep->write(ADDR_INV_PWR_LIM_CON + i * 2, iv->powerLimit[1]);
|
||||||
mEep->write(ADDR_INV_PWR_LIM_CON + i * 2, iv->powerLimit[1]);
|
mEep->write(ADDR_INV_NAME + (i * MAX_NAME_LENGTH), iv->name, MAX_NAME_LENGTH);
|
||||||
mEep->write(ADDR_INV_NAME + (i * MAX_NAME_LENGTH), iv->name, MAX_NAME_LENGTH);
|
// max channel power / name
|
||||||
// max channel power / name
|
for(uint8_t j = 0; j < 4; j++) {
|
||||||
for(uint8_t j = 0; j < 4; j++) {
|
mEep->write(ADDR_INV_CH_PWR + (i * 2 * 4) + (j*2), iv->chMaxPwr[j]);
|
||||||
mEep->write(ADDR_INV_CH_PWR + (i * 2 * 4) + (j*2), iv->chMaxPwr[j]);
|
mEep->write(ADDR_INV_CH_NAME + (i * 4 * MAX_NAME_LENGTH) + j * MAX_NAME_LENGTH, iv->chName[j], MAX_NAME_LENGTH);
|
||||||
mEep->write(ADDR_INV_CH_NAME + (i * 4 * MAX_NAME_LENGTH) + j * MAX_NAME_LENGTH, iv->chName[j], MAX_NAME_LENGTH);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -71,7 +71,6 @@ class app {
|
||||||
void saveValues(void);
|
void saveValues(void);
|
||||||
void resetPayload(Inverter<>* iv);
|
void resetPayload(Inverter<>* iv);
|
||||||
String getStatistics(void);
|
String getStatistics(void);
|
||||||
String getLiveData(void);
|
|
||||||
String getJson(void);
|
String getJson(void);
|
||||||
bool getWifiApActive(void);
|
bool getWifiApActive(void);
|
||||||
|
|
||||||
|
@ -80,9 +79,10 @@ class app {
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64_t Serial2u64(const char *val) {
|
uint64_t Serial2u64(const char *val) {
|
||||||
char tmp[3] = {0};
|
char tmp[3];
|
||||||
uint64_t ret = 0ULL;
|
uint64_t ret = 0ULL;
|
||||||
uint64_t u64;
|
uint64_t u64;
|
||||||
|
memset(tmp, 0, 3);
|
||||||
for(uint8_t i = 0; i < 6; i++) {
|
for(uint8_t i = 0; i < 6; i++) {
|
||||||
tmp[0] = val[i*2];
|
tmp[0] = val[i*2];
|
||||||
tmp[1] = val[i*2 + 1];
|
tmp[1] = val[i*2 + 1];
|
||||||
|
@ -95,7 +95,7 @@ class app {
|
||||||
}
|
}
|
||||||
|
|
||||||
String getDateTimeStr(time_t t) {
|
String getDateTimeStr(time_t t) {
|
||||||
char str[20] = {0};
|
char str[20];
|
||||||
if(0 == t)
|
if(0 == t)
|
||||||
sprintf(str, "n/a");
|
sprintf(str, "n/a");
|
||||||
else
|
else
|
||||||
|
@ -113,9 +113,11 @@ class app {
|
||||||
|
|
||||||
void eraseSettings(bool all = false) {
|
void eraseSettings(bool all = false) {
|
||||||
//DPRINTLN(DBG_VERBOSE, F("main.h:eraseSettings"));
|
//DPRINTLN(DBG_VERBOSE, F("main.h:eraseSettings"));
|
||||||
uint8_t buf[64] = {0};
|
uint8_t buf[64];
|
||||||
uint16_t addr = (all) ? ADDR_START : ADDR_START_SETTINGS;
|
uint16_t addr = (all) ? ADDR_START : ADDR_START_SETTINGS;
|
||||||
uint16_t end;
|
uint16_t end;
|
||||||
|
|
||||||
|
memset(buf, 0xff, 64);
|
||||||
do {
|
do {
|
||||||
end = addr + 64;
|
end = addr + 64;
|
||||||
if(end > (ADDR_SETTINGS_CRC + 2))
|
if(end > (ADDR_SETTINGS_CRC + 2))
|
||||||
|
@ -222,7 +224,10 @@ class app {
|
||||||
uint32_t mUptimeTicker;
|
uint32_t mUptimeTicker;
|
||||||
uint16_t mUptimeInterval;
|
uint16_t mUptimeInterval;
|
||||||
uint32_t mUptimeSecs;
|
uint32_t mUptimeSecs;
|
||||||
|
uint32_t mPrevMillis;
|
||||||
uint8_t mHeapStatCnt;
|
uint8_t mHeapStatCnt;
|
||||||
|
uint32_t mNtpRefreshTicker;
|
||||||
|
uint32_t mNtpRefreshInterval;
|
||||||
|
|
||||||
|
|
||||||
bool mWifiSettingsValid;
|
bool mWifiSettingsValid;
|
||||||
|
|
|
@ -36,6 +36,9 @@
|
||||||
#define DEF_RF24_CE_PIN 2
|
#define DEF_RF24_CE_PIN 2
|
||||||
#define DEF_RF24_IRQ_PIN 0
|
#define DEF_RF24_IRQ_PIN 0
|
||||||
|
|
||||||
|
// default radio ID
|
||||||
|
#define DTU_RADIO_ID ((uint64_t)0x1234567801ULL)
|
||||||
|
|
||||||
// default NRF24 power, possible values (0 - 3)
|
// default NRF24 power, possible values (0 - 3)
|
||||||
#define DEF_AMPLIFIERPOWER 2
|
#define DEF_AMPLIFIERPOWER 2
|
||||||
|
|
||||||
|
@ -69,12 +72,15 @@
|
||||||
// threshold of minimum power on which the inverter is marked as inactive
|
// threshold of minimum power on which the inverter is marked as inactive
|
||||||
#define INACT_PWR_THRESH 3
|
#define INACT_PWR_THRESH 3
|
||||||
|
|
||||||
// default ntp server uri
|
// default NTP server uri
|
||||||
#define DEF_NTP_SERVER_NAME "pool.ntp.org"
|
#define DEF_NTP_SERVER_NAME "pool.ntp.org"
|
||||||
|
|
||||||
// default ntp server port
|
// default NTP server port
|
||||||
#define DEF_NTP_PORT 8888
|
#define DEF_NTP_PORT 8888
|
||||||
|
|
||||||
|
// NTP refresh interval in ms (default 12h)
|
||||||
|
#define NTP_REFRESH_INTERVAL 12 * 3600 * 1000
|
||||||
|
|
||||||
// default mqtt interval
|
// default mqtt interval
|
||||||
#define MQTT_INTERVAL 60
|
#define MQTT_INTERVAL 60
|
||||||
|
|
||||||
|
@ -96,4 +102,8 @@
|
||||||
// changes the style of "/setup" page, visualized = nicer
|
// changes the style of "/setup" page, visualized = nicer
|
||||||
#define LIVEDATA_VISUALIZED
|
#define LIVEDATA_VISUALIZED
|
||||||
|
|
||||||
|
#if __has_include("config_override.h")
|
||||||
|
#include "config_override.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
#endif /*__CONFIG_H__*/
|
#endif /*__CONFIG_H__*/
|
||||||
|
|
30
tools/esp8266/config_override_example.h
Normal file
30
tools/esp8266/config_override_example.h
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
// 2022 Ahoy, https://www.mikrocontroller.net/topic/525778
|
||||||
|
// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
#ifndef __CONFIG_OVERRIDE_H__
|
||||||
|
#define __CONFIG_OVERRIDE_H__
|
||||||
|
|
||||||
|
// override fallback WiFi info
|
||||||
|
|
||||||
|
// each ovveride must be preceeded with an #undef statement
|
||||||
|
#undef FB_WIFI_SSID
|
||||||
|
#define FB_WIFI_SSID "MY_SSID"
|
||||||
|
|
||||||
|
// each ovveride must be preceeded with an #undef statement
|
||||||
|
#undef FB_WIFI_PWD
|
||||||
|
#define FB_WIFI_PWD "MY_WIFI_KEY"
|
||||||
|
|
||||||
|
// ESP32 default pinout
|
||||||
|
#undef DEF_RF24_CS_PIN
|
||||||
|
#define DEF_RF24_CS_PIN 5
|
||||||
|
#undef DEF_RF24_CE_PIN
|
||||||
|
#define DEF_RF24_CE_PIN 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__*/
|
|
@ -13,7 +13,7 @@
|
||||||
//-------------------------------------
|
//-------------------------------------
|
||||||
#define VERSION_MAJOR 0
|
#define VERSION_MAJOR 0
|
||||||
#define VERSION_MINOR 5
|
#define VERSION_MINOR 5
|
||||||
#define VERSION_PATCH 15
|
#define VERSION_PATCH 16
|
||||||
|
|
||||||
|
|
||||||
//-------------------------------------
|
//-------------------------------------
|
||||||
|
@ -58,6 +58,7 @@ typedef enum {
|
||||||
} DevControlCmdType;
|
} DevControlCmdType;
|
||||||
|
|
||||||
typedef enum { // ToDo: to be verified by field tests
|
typedef enum { // ToDo: to be verified by field tests
|
||||||
|
NoPowerLimit = 0xffff, // ahoy internal value, no hoymiles value!
|
||||||
AbsolutNonPersistent = 0UL, // 0x0000
|
AbsolutNonPersistent = 0UL, // 0x0000
|
||||||
RelativNonPersistent = 1UL, // 0x0001
|
RelativNonPersistent = 1UL, // 0x0001
|
||||||
AbsolutPersistent = 256UL, // 0x0100
|
AbsolutPersistent = 256UL, // 0x0100
|
||||||
|
@ -104,6 +105,7 @@ typedef enum { // ToDo: to be verified by field tests
|
||||||
#define MQTT_PORT_LEN 2 // uint16_t
|
#define MQTT_PORT_LEN 2 // uint16_t
|
||||||
#define MQTT_DISCOVERY_PREFIX "homeassistant"
|
#define MQTT_DISCOVERY_PREFIX "homeassistant"
|
||||||
#define MQTT_MAX_PACKET_SIZE 384
|
#define MQTT_MAX_PACKET_SIZE 384
|
||||||
|
#define MQTT_RECONNECT_DELAY 5000
|
||||||
|
|
||||||
#define SER_ENABLE_LEN 1 // uint8_t
|
#define SER_ENABLE_LEN 1 // uint8_t
|
||||||
#define SER_DEBUG_LEN 1 // uint8_t
|
#define SER_DEBUG_LEN 1 // uint8_t
|
||||||
|
|
|
@ -23,9 +23,13 @@ const char* const units[] = {"V", "A", "W", "Wh", "kWh", "Hz", "°C", "%","VAr",
|
||||||
|
|
||||||
// field types
|
// field types
|
||||||
enum {FLD_UDC = 0, FLD_IDC, FLD_PDC, FLD_YD, FLD_YW, FLD_YT,
|
enum {FLD_UDC = 0, FLD_IDC, FLD_PDC, FLD_YD, FLD_YW, FLD_YT,
|
||||||
FLD_UAC, FLD_IAC, FLD_PAC, FLD_F, FLD_T, FLD_PCT, FLD_EFF, FLD_IRR, FLD_PRA,FLD_ALARM_MES_ID,FLD_FW_VERSION,FLD_FW_BUILD_YEAR,FLD_FW_BUILD_MONTH_DAY,FLD_HW_ID,FLD_ACT_PWR_LIMIT};
|
FLD_UAC, FLD_IAC, FLD_PAC, FLD_F, FLD_T, FLD_PCT, FLD_EFF,
|
||||||
|
FLD_IRR, FLD_PRA,FLD_ALARM_MES_ID,FLD_FW_VERSION,FLD_FW_BUILD_YEAR,
|
||||||
|
FLD_FW_BUILD_MONTH_DAY,FLD_HW_ID,FLD_ACT_PWR_LIMIT,FLD_LAST_ALARM_CODE};
|
||||||
|
|
||||||
const char* const fields[] = {"U_DC", "I_DC", "P_DC", "YieldDay", "YieldWeek", "YieldTotal",
|
const char* const fields[] = {"U_DC", "I_DC", "P_DC", "YieldDay", "YieldWeek", "YieldTotal",
|
||||||
"U_AC", "I_AC", "P_AC", "Freq", "Temp", "Pct", "Efficiency", "Irradiation","P_ACr","ALARM_MES_ID","FWVersion","FWBuildYear","FWBuildMonthDay","HWPartId","PowerLimit"};
|
"U_AC", "I_AC", "P_AC", "Freq", "Temp", "Pct", "Efficiency", "Irradiation","P_ACr",
|
||||||
|
"ALARM_MES_ID","FWVersion","FWBuildYear","FWBuildMonthDay","HWPartId","PowerLimit","LastAlarmCode"};
|
||||||
|
|
||||||
// mqtt discovery device classes
|
// mqtt discovery device classes
|
||||||
enum {DEVICE_CLS_NONE = 0, DEVICE_CLS_CURRENT, DEVICE_CLS_ENERGY, DEVICE_CLS_PWR, DEVICE_CLS_VOLTAGE, DEVICE_CLS_FREQ, DEVICE_CLS_TEMP};
|
enum {DEVICE_CLS_NONE = 0, DEVICE_CLS_CURRENT, DEVICE_CLS_ENERGY, DEVICE_CLS_PWR, DEVICE_CLS_VOLTAGE, DEVICE_CLS_FREQ, DEVICE_CLS_TEMP};
|
||||||
|
@ -97,6 +101,10 @@ const byteAssign_t SystemConfigParaAssignment[] = {
|
||||||
};
|
};
|
||||||
#define HMSYSTEM_LIST_LEN (sizeof(SystemConfigParaAssignment) / sizeof(byteAssign_t))
|
#define HMSYSTEM_LIST_LEN (sizeof(SystemConfigParaAssignment) / sizeof(byteAssign_t))
|
||||||
|
|
||||||
|
const byteAssign_t AlarmDataAssignment[] = {
|
||||||
|
{ FLD_LAST_ALARM_CODE, UNIT_NONE, CH0, 0, 2, 1 }
|
||||||
|
};
|
||||||
|
#define HMALARMDATA_LIST_LEN (sizeof(AlarmDataAssignment) / sizeof(byteAssign_t))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -115,17 +115,20 @@ class Inverter {
|
||||||
RECORDTYPE *record; // pointer for values
|
RECORDTYPE *record; // pointer for values
|
||||||
uint16_t chMaxPwr[4]; // maximum power of the modules (Wp)
|
uint16_t chMaxPwr[4]; // maximum power of the modules (Wp)
|
||||||
char chName[4][MAX_NAME_LENGTH]; // human readable name for channel
|
char chName[4][MAX_NAME_LENGTH]; // human readable name for channel
|
||||||
|
String lastAlarmMsg;
|
||||||
bool initialized; // needed to check if the inverter was correctly added (ESP32 specific - union types are never null)
|
bool initialized; // needed to check if the inverter was correctly added (ESP32 specific - union types are never null)
|
||||||
|
|
||||||
Inverter() {
|
Inverter() {
|
||||||
ts = 0;
|
ts = 0;
|
||||||
powerLimit[0] = 0xffff; // 65535 W Limit -> unlimited
|
powerLimit[0] = 0xffff; // 65535 W Limit -> unlimited
|
||||||
powerLimit[1] = 0x0000; //
|
powerLimit[1] = NoPowerLimit; //
|
||||||
actPowerLimit = 0xffff; // init feedback from inverter to -1
|
actPowerLimit = 0xffff; // init feedback from inverter to -1
|
||||||
devControlRequest = false;
|
devControlRequest = false;
|
||||||
devControlCmd = 0xff;
|
devControlCmd = InitDataState;
|
||||||
initialized = false;
|
initialized = false;
|
||||||
fwVersion = 0;
|
fwVersion = 0;
|
||||||
|
lastAlarmMsg = "nothing";
|
||||||
|
alarmMesIndex = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
~Inverter() {
|
~Inverter() {
|
||||||
|
@ -141,7 +144,8 @@ class Inverter {
|
||||||
|
|
||||||
void setQueuedCmdFinished(){
|
void setQueuedCmdFinished(){
|
||||||
if (!_commandQueue.empty()){
|
if (!_commandQueue.empty()){
|
||||||
_commandQueue.pop(); // Will destroy CommandAbstract Class Object (?)
|
// Will destroy CommandAbstract Class Object (?)
|
||||||
|
_commandQueue.pop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -150,7 +154,14 @@ class Inverter {
|
||||||
if (_commandQueue.empty()){
|
if (_commandQueue.empty()){
|
||||||
// Fill with default commands
|
// Fill with default commands
|
||||||
enqueCommand<InfoCommand>(RealTimeRunData_Debug);
|
enqueCommand<InfoCommand>(RealTimeRunData_Debug);
|
||||||
//enqueCommand<InfoCommand>(SystemConfigPara);
|
if (fwVersion == 0)
|
||||||
|
{ // info needed maybe after "one night" (=> DC>0 to DC=0 and to DC>0) or reboot
|
||||||
|
enqueCommand<InfoCommand>(InverterDevInform_All);
|
||||||
|
}
|
||||||
|
if (actPowerLimit == 0xffff)
|
||||||
|
{ // info needed maybe after "one nigth" (=> DC>0 to DC=0 and to DC>0) or reboot
|
||||||
|
enqueCommand<InfoCommand>(SystemConfigPara);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return _commandQueue.front().get()->getCmd();
|
return _commandQueue.front().get()->getCmd();
|
||||||
}
|
}
|
||||||
|
@ -164,8 +175,6 @@ class Inverter {
|
||||||
memset(name, 0, MAX_NAME_LENGTH);
|
memset(name, 0, MAX_NAME_LENGTH);
|
||||||
memset(chName, 0, MAX_NAME_LENGTH * 4);
|
memset(chName, 0, MAX_NAME_LENGTH * 4);
|
||||||
memset(record, 0, sizeof(RECORDTYPE) * listLen);
|
memset(record, 0, sizeof(RECORDTYPE) * listLen);
|
||||||
enqueCommand<InfoCommand>(InverterDevInform_All);
|
|
||||||
enqueCommand<InfoCommand>(SystemConfigPara);
|
|
||||||
initialized = true;
|
initialized = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -206,12 +215,25 @@ class Inverter {
|
||||||
val <<= 8;
|
val <<= 8;
|
||||||
val |= buf[ptr];
|
val |= buf[ptr];
|
||||||
} while(++ptr != end);
|
} while(++ptr != end);
|
||||||
record[pos] = (RECORDTYPE)(val) / (RECORDTYPE)(div);
|
if ((RECORDTYPE)(div) > 1){
|
||||||
|
record[pos] = (RECORDTYPE)(val) / (RECORDTYPE)(div);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
record[pos] = (RECORDTYPE)(val);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
if (cmd == RealTimeRunData_Debug) {
|
if (cmd == RealTimeRunData_Debug) {
|
||||||
// get last alarm message index and save it in the inverter object
|
// get last alarm message index and save it in the inverter object
|
||||||
if (getPosByChFld(0, FLD_ALARM_MES_ID) == pos){
|
if (getPosByChFld(0, FLD_ALARM_MES_ID) == pos){
|
||||||
alarmMesIndex = record[pos];
|
if (alarmMesIndex < record[pos]){
|
||||||
|
alarmMesIndex = record[pos];
|
||||||
|
//enqueCommand<InfoCommand>(AlarmUpdate); // What is the function of AlarmUpdate?
|
||||||
|
enqueCommand<InfoCommand>(AlarmData);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
alarmMesIndex = record[pos]; // no change
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (cmd == InverterDevInform_All) {
|
if (cmd == InverterDevInform_All) {
|
||||||
|
@ -228,6 +250,11 @@ class Inverter {
|
||||||
DPRINT(DBG_DEBUG, F("Inverter actual power limit: ") + String(actPowerLimit));
|
DPRINT(DBG_DEBUG, F("Inverter actual power limit: ") + String(actPowerLimit));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (cmd == AlarmData){
|
||||||
|
if (getPosByChFld(0, FLD_LAST_ALARM_CODE) == pos){
|
||||||
|
lastAlarmMsg = getAlarmStr(record[pos]);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
RECORDTYPE getValue(uint8_t pos) {
|
RECORDTYPE getValue(uint8_t pos) {
|
||||||
|
@ -263,52 +290,273 @@ class Inverter {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t getLastTs(void) {
|
uint32_t getLastTs(void)
|
||||||
|
{
|
||||||
DPRINTLN(DBG_VERBOSE, F("hmInverter.h:getLastTs"));
|
DPRINTLN(DBG_VERBOSE, F("hmInverter.h:getLastTs"));
|
||||||
return ts;
|
return ts;
|
||||||
}
|
}
|
||||||
|
|
||||||
void getAssignment() {
|
void getAssignment()
|
||||||
|
{
|
||||||
DPRINTLN(DBG_DEBUG, F("hmInverter.h:getAssignment"));
|
DPRINTLN(DBG_DEBUG, F("hmInverter.h:getAssignment"));
|
||||||
uint8_t cmd = getQueuedCmd();
|
// Default assignment;
|
||||||
switch (cmd)
|
if (INV_TYPE_1CH == type)
|
||||||
{
|
{
|
||||||
case RealTimeRunData_Debug:
|
listLen = (uint8_t)(HM1CH_LIST_LEN);
|
||||||
if (INV_TYPE_1CH == type)
|
assign = (byteAssign_t *)hm1chAssignment;
|
||||||
{
|
channels = 1;
|
||||||
listLen = (uint8_t)(HM1CH_LIST_LEN);
|
}
|
||||||
assign = (byteAssign_t *)hm1chAssignment;
|
else if (INV_TYPE_2CH == type)
|
||||||
channels = 1;
|
{
|
||||||
}
|
listLen = (uint8_t)(HM2CH_LIST_LEN);
|
||||||
else if (INV_TYPE_2CH == type)
|
assign = (byteAssign_t *)hm2chAssignment;
|
||||||
{
|
channels = 2;
|
||||||
listLen = (uint8_t)(HM2CH_LIST_LEN);
|
}
|
||||||
assign = (byteAssign_t *)hm2chAssignment;
|
else if (INV_TYPE_4CH == type)
|
||||||
channels = 2;
|
{
|
||||||
}
|
listLen = (uint8_t)(HM4CH_LIST_LEN);
|
||||||
else if (INV_TYPE_4CH == type)
|
assign = (byteAssign_t *)hm4chAssignment;
|
||||||
{
|
channels = 4;
|
||||||
listLen = (uint8_t)(HM4CH_LIST_LEN);
|
}
|
||||||
assign = (byteAssign_t *)hm4chAssignment;
|
else
|
||||||
channels = 4;
|
{
|
||||||
}
|
listLen = 0;
|
||||||
else
|
channels = 0;
|
||||||
{
|
assign = NULL;
|
||||||
listLen = 0;
|
}
|
||||||
channels = 0;
|
|
||||||
assign = NULL;
|
switch (getQueuedCmd()) {
|
||||||
}
|
case RealTimeRunData_Debug:
|
||||||
|
// Do nothing will use default
|
||||||
|
break;
|
||||||
|
case InverterDevInform_All:
|
||||||
|
listLen = (uint8_t)(HMINFO_LIST_LEN);
|
||||||
|
assign = (byteAssign_t *)InfoAssignment;
|
||||||
|
break;
|
||||||
|
case SystemConfigPara:
|
||||||
|
listLen = (uint8_t)(HMSYSTEM_LIST_LEN);
|
||||||
|
assign = (byteAssign_t *)SystemConfigParaAssignment;
|
||||||
|
break;
|
||||||
|
case AlarmData:
|
||||||
|
listLen = (uint8_t)(HMALARMDATA_LIST_LEN);
|
||||||
|
assign = (byteAssign_t *)AlarmDataAssignment;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
DPRINTLN(DBG_INFO, "Parser not implemented");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
String getAlarmStr(u_int16_t alarmCode)
|
||||||
|
{
|
||||||
|
switch (alarmCode)
|
||||||
|
{
|
||||||
|
case 1:
|
||||||
|
return String(F("Inverter start"));
|
||||||
break;
|
break;
|
||||||
case InverterDevInform_All:
|
case 2:
|
||||||
listLen = (uint8_t)(HMINFO_LIST_LEN);
|
return String(F("DTU command failed"));
|
||||||
assign = (byteAssign_t *)InfoAssignment;
|
|
||||||
break;
|
break;
|
||||||
case SystemConfigPara:
|
case 121:
|
||||||
listLen = (uint8_t)(HMSYSTEM_LIST_LEN);
|
return String(F("Over temperature protection"));
|
||||||
assign = (byteAssign_t *)SystemConfigParaAssignment;
|
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;
|
break;
|
||||||
default:
|
default:
|
||||||
DPRINTLN(DBG_INFO, "Parser not implemented");
|
return String(F("Unknown"));
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,6 @@
|
||||||
#define DEFAULT_RECV_CHANNEL 3
|
#define DEFAULT_RECV_CHANNEL 3
|
||||||
#define SPI_SPEED 1000000
|
#define SPI_SPEED 1000000
|
||||||
|
|
||||||
#define DTU_RADIO_ID ((uint64_t)0x1234567801ULL)
|
|
||||||
#define DUMMY_RADIO_ID ((uint64_t)0xDEADBEEF01ULL)
|
#define DUMMY_RADIO_ID ((uint64_t)0xDEADBEEF01ULL)
|
||||||
|
|
||||||
#define RF_CHANNELS 5
|
#define RF_CHANNELS 5
|
||||||
|
@ -192,10 +191,9 @@ class HmRadio {
|
||||||
mTxBuf[10] = cmd; // cid
|
mTxBuf[10] = cmd; // cid
|
||||||
mTxBuf[11] = 0x00;
|
mTxBuf[11] = 0x00;
|
||||||
CP_U32_LittleEndian(&mTxBuf[12], ts);
|
CP_U32_LittleEndian(&mTxBuf[12], ts);
|
||||||
if (cmd == RealTimeRunData_Debug || cmd == AlarmData || cmd == AlarmUpdate ){
|
if (cmd == RealTimeRunData_Debug || cmd == AlarmData ){
|
||||||
mTxBuf[18] = (alarmMesId >> 8) & 0xff;
|
mTxBuf[18] = (alarmMesId >> 8) & 0xff;
|
||||||
mTxBuf[19] = (alarmMesId ) & 0xff;
|
mTxBuf[19] = (alarmMesId ) & 0xff;
|
||||||
//mTxBuf[19] = 0x05; // ToDo: Shall be the last received Alarm Index Number
|
|
||||||
} else {
|
} else {
|
||||||
mTxBuf[18] = 0x00;
|
mTxBuf[18] = 0x00;
|
||||||
mTxBuf[19] = 0x00;
|
mTxBuf[19] = 0x00;
|
||||||
|
|
|
@ -2,6 +2,8 @@ import re
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
def convert2Header(inFile):
|
def convert2Header(inFile):
|
||||||
fileType = inFile.split(".")[1]
|
fileType = inFile.split(".")[1]
|
||||||
define = inFile.split(".")[0].upper()
|
define = inFile.split(".")[0].upper()
|
||||||
|
@ -12,8 +14,10 @@ def convert2Header(inFile):
|
||||||
print("ok")
|
print("ok")
|
||||||
outName = "html/" + "h/" + inFileVarName + ".h"
|
outName = "html/" + "h/" + inFileVarName + ".h"
|
||||||
inFile = "html/" + inFile
|
inFile = "html/" + inFile
|
||||||
|
Path("html/h").mkdir(exist_ok=True)
|
||||||
else:
|
else:
|
||||||
outName = "h/" + inFileVarName + ".h"
|
outName = "h/" + inFileVarName + ".h"
|
||||||
|
Path("h").mkdir(exist_ok=True)
|
||||||
|
|
||||||
f = open(inFile, "r")
|
f = open(inFile, "r")
|
||||||
data = f.read().replace('\n', '')
|
data = f.read().replace('\n', '')
|
||||||
|
|
|
@ -1,4 +0,0 @@
|
||||||
#ifndef __INDEX_HTML_H__
|
|
||||||
#define __INDEX_HTML_H__
|
|
||||||
const char index_html[] PROGMEM = "<!doctype html><html><head><title>Index - {DEVICE}</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></head><body><h1>AHOY - {DEVICE}</h1><div id=\"content\" class=\"content\"><p><a href=\"/visualization\">Visualization</a><br/><br/><a href=\"/setup\">Setup</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><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/><br/>Please report issues using the feature provided by <a href=\"https://github.com/grindylow/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></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\"><a href=\"/reboot\">Reboot</a></p><p class=\"right\">Git SHA: {BUILD}</p></div></body></html>";
|
|
||||||
#endif /*__INDEX_HTML_H__*/
|
|
File diff suppressed because one or more lines are too long
|
@ -1,4 +0,0 @@
|
||||||
#ifndef __STYLE_CSS_H__
|
|
||||||
#define __STYLE_CSS_H__
|
|
||||||
const char style_css[] PROGMEM = "h1 {margin:0;padding:20pt;font-size:22pt;color:#fff;background-color:#006ec0;display:block;text-transform:uppercase;}html, body {font-family:Arial;margin:0;padding:0;}p {text-align:justify;font-size:13pt;}p.lic, p.lic a {font-size:8pt;color:#999;}.des {margin-top:20px;font-size:13pt;color:#006ec0;}.s_active, .s_collapsible:hover {background-color:#006ec0;}.s_content {display:none;overflow:hidden;}.s_collapsible {background-color:#044e86;color:white;cursor:pointer;padding:18px;width:100%;border:none;text-align:left;outline:none;font-size:15px;margin-bottom:4px;}.subdes {font-size:12pt;color:#006ec0;margin-left:7px;}.subsubdes {font-size:12pt;color:#006ec0;margin:0 0 7px 12px;}.hide {display:none;}a:link, a:visited {text-decoration:none;font-size:13pt;color:#006ec0;}a:hover, a:focus {color:#f00;}a.erase {background-color:#006ec0;color:#fff;padding:7px;display:inline-block;margin-top:30px;}#content {padding:15px 15px 60px 15px;}#footer {position:fixed;bottom:0px;height:45px;background-color:#006ec0;width:100%;border-top:5px solid #fff;}#footer p, #footer a {color:#fff;padding:0 7px 0 7px;font-size:10pt !important;}div.content {background-color:#fff;padding-bottom:65px;overflow:auto;}input, select {padding:7px;font-size:13pt;}input.text, select {width:70%;box-sizing:border-box;margin-bottom:10px;border:1px solid #ccc;}input.sh {max-width:150px !important;margin-right:10px;}input.btn {background-color:#006ec0;color:#fff;border:0px;float:right;margin:10px 0 30px;text-transform:uppercase;}input.cb {margin-bottom:20px;}label {width:20%;display:inline-block;font-size:12pt;padding-right:10px;margin:10px 0px 0px 15px;vertical-align:top;}fieldset {margin-bottom:15px;}.left {float:left;}.right {float:right;}div.ch-iv {width:100%;background-color:#32b004;display:inline-block;margin-bottom:15px;padding-bottom:20px;overflow:auto;}div.ch {width:220px;min-height:350px;background-color:#006ec0;display:inline-block;margin:0 10px 15px 10px;overflow:auto;padding-bottom:20px;}div.ch .value, div.ch .info, div.ch .head, div.ch-iv .value, div.ch-iv .info, div.ch-iv .head {color:#fff;display:block;width:100%;text-align:center;}.subgrp {float:left;width:220px;}div.ch .unit, div.ch-iv .unit {font-size:19px;margin-left:10px;}div.ch .value, div.ch-iv .value {margin-top:20px;font-size:24px;}div.ch .info, div.ch-iv .info {margin-top:3px;font-size:10px;}div.ch .head {background-color:#003c80;padding:10px 0 10px 0;}div.ch-iv .head {background-color:#1c6800;padding:10px 0 10px 0;}div.iv {max-width:960px;margin-bottom:40px;}div.ts {font-size:13px;background-color:#ddd;border-top:7px solid #999;padding:7px;}div.modpwr, div.modname {width:70%;display:inline-block;}#note {margin:50px 10px 10px 10px;padding-top:10px;width:100%;border-top:1px solid #bbb;}@media(max-width:500px) {div.ch .unit, div.ch-iv .unit {font-size:18px;}div.ch {width:170px;min-height:100px }.subgrp {width:180px;}}";
|
|
||||||
#endif /*__STYLE_CSS_H__*/
|
|
|
@ -1,4 +0,0 @@
|
||||||
#ifndef __VISUALIZATION_HTML_H__
|
|
||||||
#define __VISUALIZATION_HTML_H__
|
|
||||||
const char visualization_html[] PROGMEM = "<!doctype html><html><head><title>Index - {DEVICE}</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('/livedata', 'livedata');window.setInterval(\"getAjax('/livedata', 'livedata')\", {JS_TS});function getAjax(url, resid) {var http = null;http = new XMLHttpRequest();if(http != null) {http.open(\"GET\", url, true);http.onreadystatechange = print;http.send(null);}function print() {if(http.readyState == 4) {document.getElementById(resid).innerHTML = http.responseText;}}}</script><style type=\"text/css\"></style></head><body><h1>AHOY - {DEVICE}</h1><div id=\"content\" class=\"content\"><div id=\"livedata\"></div><p>Every {TS}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></div></body></html>";
|
|
||||||
#endif /*__VISUALIZATION_HTML_H__*/
|
|
|
@ -102,7 +102,7 @@
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend class="des">MQTT</legend>
|
<legend class="des">MQTT</legend>
|
||||||
<label for="mqttAddr">Broker / Server IP</label>
|
<label for="mqttAddr">Broker / Server IP</label>
|
||||||
<input type="text" class="text" name="mqttAddr" value="{MQTT_ADDR}"/>
|
<input type="text" class="text" name="mqttAddr" value="{MQTT_ADDR}" maxlength="32" />
|
||||||
<label for="mqttPort">Port</label>
|
<label for="mqttPort">Port</label>
|
||||||
<input type="text" class="text" name="mqttPort" value="{MQTT_PORT}"/>
|
<input type="text" class="text" name="mqttPort" value="{MQTT_PORT}"/>
|
||||||
<label for="mqttUser">Username (optional)</label>
|
<label for="mqttUser">Username (optional)</label>
|
||||||
|
@ -128,7 +128,7 @@
|
||||||
<p class="des">Serial Console</p>
|
<p class="des">Serial Console</p>
|
||||||
<label for="serEn">print inverter data</label>
|
<label for="serEn">print inverter data</label>
|
||||||
<input type="checkbox" class="cb" name="serEn" {SER_VAL_CB}/><br/>
|
<input type="checkbox" class="cb" name="serEn" {SER_VAL_CB}/><br/>
|
||||||
<label for="serDbg">print RF24 debug</label>
|
<label for="serDbg">Serial Debug</label>
|
||||||
<input type="checkbox" class="cb" name="serDbg" {SER_DBG_CB}/><br/>
|
<input type="checkbox" class="cb" name="serDbg" {SER_DBG_CB}/><br/>
|
||||||
<label for="serIntvl">Interval [s]</label>
|
<label for="serIntvl">Interval [s]</label>
|
||||||
<input type="text" class="text" name="serIntvl" value="{SER_INTVL}"/>
|
<input type="text" class="text" name="serIntvl" value="{SER_INTVL}"/>
|
||||||
|
|
|
@ -85,7 +85,8 @@ class mqtt {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
boolean resub = false;
|
boolean resub = false;
|
||||||
if(!mClient->connected()) {
|
if(!mClient->connected() && (millis() - lastReconnect) > MQTT_RECONNECT_DELAY ) {
|
||||||
|
lastReconnect = millis();
|
||||||
if(strlen(mDevName) > 0) {
|
if(strlen(mDevName) > 0) {
|
||||||
// der Server und der Port müssen neu gesetzt werden,
|
// der Server und der Port müssen neu gesetzt werden,
|
||||||
// da ein MQTT_CONNECTION_LOST -3 die Werte zerstört hat.
|
// da ein MQTT_CONNECTION_LOST -3 die Werte zerstört hat.
|
||||||
|
@ -95,14 +96,14 @@ class mqtt {
|
||||||
resub = mClient->connect(mDevName, mCfg->user, mCfg->pwd);
|
resub = mClient->connect(mDevName, mCfg->user, mCfg->pwd);
|
||||||
else
|
else
|
||||||
resub = mClient->connect(mDevName);
|
resub = mClient->connect(mDevName);
|
||||||
}
|
// ein Subscribe ist nur nach einem connect notwendig
|
||||||
// ein Subscribe ist nur nach einem connect notwendig
|
if(resub) {
|
||||||
if(resub) {
|
char topic[MQTT_TOPIC_LEN + 13 ]; // "/devcontrol/#" --> + 6 byte
|
||||||
char topic[MQTT_TOPIC_LEN + 13 ]; // "/devcontrol/#" --> + 6 byte
|
// ToDo: "/devcontrol/#" is hardcoded
|
||||||
// ToDo: "/devcontrol/#" is hardcoded
|
snprintf(topic, MQTT_TOPIC_LEN + 13, "%s/devcontrol/#", mCfg->topic);
|
||||||
snprintf(topic, MQTT_TOPIC_LEN + 13, "%s/devcontrol/#", mCfg->topic);
|
DPRINTLN(DBG_INFO, F("subscribe to ") + String(topic));
|
||||||
DPRINTLN(DBG_INFO, F("subscribe to ") + String(topic));
|
mClient->subscribe(topic); // subscribe to mTopic + "/devcontrol/#"
|
||||||
mClient->subscribe(topic); // subscribe to mTopic + "/devcontrol/#"
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -113,6 +114,7 @@ class mqtt {
|
||||||
bool mAddressSet;
|
bool mAddressSet;
|
||||||
mqttConfig_t *mCfg;
|
mqttConfig_t *mCfg;
|
||||||
char mDevName[DEVNAME_LEN];
|
char mDevName[DEVNAME_LEN];
|
||||||
|
unsigned long lastReconnect = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif /*__MQTT_H_*/
|
#endif /*__MQTT_H_*/
|
||||||
|
|
|
@ -14,9 +14,11 @@ src_dir = .
|
||||||
[env]
|
[env]
|
||||||
framework = arduino
|
framework = arduino
|
||||||
|
|
||||||
|
build_flags =
|
||||||
|
-include "config.h"
|
||||||
; ;;;;; Possible Debug options ;;;;;;
|
; ;;;;; Possible Debug options ;;;;;;
|
||||||
; https://docs.platformio.org/en/latest/platforms/espressif8266.html#debug-level
|
; https://docs.platformio.org/en/latest/platforms/espressif8266.html#debug-level
|
||||||
;build_flags = -DDEBUG_ESP_PORT=Serial
|
;-DDEBUG_ESP_PORT=Serial
|
||||||
;-DDEBUG_ESP_CORE
|
;-DDEBUG_ESP_CORE
|
||||||
;-DDEBUG_ESP_WIFI
|
;-DDEBUG_ESP_WIFI
|
||||||
;-DDEBUG_ESP_HTTP_CLIENT
|
;-DDEBUG_ESP_HTTP_CLIENT
|
||||||
|
|
|
@ -24,6 +24,12 @@ def readVersion(path, infile):
|
||||||
src = path + ".pio/build/esp8266-release/firmware.bin"
|
src = path + ".pio/build/esp8266-release/firmware.bin"
|
||||||
dst = path + ".pio/build/out/" + versionout
|
dst = path + ".pio/build/out/" + versionout
|
||||||
os.rename(src, dst)
|
os.rename(src, dst)
|
||||||
|
|
||||||
|
versionout = version[:-1] + "_esp32_" + sha + ".bin"
|
||||||
|
src = path + ".pio/build/esp32-wroom32-release/firmware.bin"
|
||||||
|
dst = path + ".pio/build/out/" + versionout
|
||||||
|
os.rename(src, dst)
|
||||||
|
|
||||||
print("::set-output name=name::" + versionnumber[:-1] )
|
print("::set-output name=name::" + versionnumber[:-1] )
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,23 @@
|
||||||
#include "html/h/setup_html.h"
|
#include "html/h/setup_html.h"
|
||||||
#include "html/h/visualization_html.h"
|
#include "html/h/visualization_html.h"
|
||||||
|
|
||||||
|
|
||||||
|
const uint16_t pwrLimitOptionValues[] {
|
||||||
|
NoPowerLimit,
|
||||||
|
AbsolutNonPersistent,
|
||||||
|
AbsolutPersistent,
|
||||||
|
RelativNonPersistent,
|
||||||
|
RelativPersistent
|
||||||
|
};
|
||||||
|
|
||||||
|
const char* const pwrLimitOptions[] {
|
||||||
|
"no power limit",
|
||||||
|
"absolute in Watt non persistent",
|
||||||
|
"absolute in Watt persistent",
|
||||||
|
"relativ in percent non persistent",
|
||||||
|
"relativ in percent persistent"
|
||||||
|
};
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
web::web(app *main, sysConfig_t *sysCfg, config_t *config, char version[]) {
|
web::web(app *main, sysConfig_t *sysCfg, config_t *config, char version[]) {
|
||||||
mMain = main;
|
mMain = main;
|
||||||
|
@ -185,7 +202,7 @@ void web::showSetup(void) {
|
||||||
inv += F("<input type=\"text\" class=\"text\" name=\"inv") + String(i) + F("Addr\" value=\"");
|
inv += F("<input type=\"text\" class=\"text\" name=\"inv") + String(i) + F("Addr\" value=\"");
|
||||||
if(NULL != iv)
|
if(NULL != iv)
|
||||||
inv += String(iv->serial.u64, HEX);
|
inv += String(iv->serial.u64, HEX);
|
||||||
inv += F("\"/ maxlength=\"12\" onkeyup=\"checkSerial()\">");
|
inv += F("\"/ maxlength=\"12\">");
|
||||||
|
|
||||||
inv += F("<label for=\"inv") + String(i) + F("Name\">Name*</label>");
|
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=\"");
|
inv += F("<input type=\"text\" class=\"text\" name=\"inv") + String(i) + F("Name\" value=\"");
|
||||||
|
@ -200,21 +217,16 @@ void web::showSetup(void) {
|
||||||
inv += F("\"/ maxlength=\"") + String(6) + "\">";
|
inv += F("\"/ maxlength=\"") + String(6) + "\">";
|
||||||
|
|
||||||
inv += F("<label for=\"inv") + String(i) + F("ActivePowerLimitConType\">Active Power Limit Control Type</label>");
|
inv += F("<label for=\"inv") + String(i) + F("ActivePowerLimitConType\">Active Power Limit Control Type</label>");
|
||||||
inv += F("<select name=\"inv") + String(i);
|
inv += F("<select name=\"inv") + String(i) + F("PowerLimitControl\">");
|
||||||
// UGLY! But I do not know it a better way
|
for(uint8_t j = 0; j < 5; j++) {
|
||||||
// ToDo: Need Cookies, IndexDB or PWA for that or in general client browser storage
|
inv += F("<option value=\"") + String(pwrLimitOptionValues[j]) + F("\"");
|
||||||
if(NULL != iv){
|
if(NULL != iv) {
|
||||||
if(iv->powerLimit[1] == AbsolutNonPersistent)
|
if(iv->powerLimit[1] == pwrLimitOptionValues[j])
|
||||||
inv += F("PowerLimitControl\"><option value=\"0\">absolute in Watt non persistent</option><option value=\"1\">relativ in percent non persistent</option><option value=\"256\">absolute in Watt persistent</option><option value=\"257\">relativ in percent persistent</option></select>");
|
inv += F(" selected");
|
||||||
if(iv->powerLimit[1] == RelativNonPersistent)
|
}
|
||||||
inv += F("PowerLimitControl\"><option value=\"1\">relativ in percent non persistent</option><option value=\"0\">absolute in Watt non persistent</option><option value=\"256\">absolute in Watt persistent</option><option value=\"257\">relativ in percent persistent</option></select>");
|
inv += F(">") + String(pwrLimitOptions[j]) + F("</option>");
|
||||||
if(iv->powerLimit[1] == AbsolutPersistent)
|
}
|
||||||
inv += F("PowerLimitControl\"><option value=\"256\">absolute in Watt persistent</option><option value=\"1\">relativ in percent non persistent</option><option value=\"0\">absolute in Watt non persistent</option><option value=\"257\">relativ in percent persistent</option></select>");
|
inv += F("</select>");
|
||||||
if(iv->powerLimit[1] == RelativPersistent)
|
|
||||||
inv += F("PowerLimitControl\"><option value=\"257\">relativ in percent persistent</option><option value=\"256\">absolute in Watt persistent</option><option value=\"1\">relativ in percent non persistent</option><option value=\"0\">absolute in Watt non persistent</option></select>");
|
|
||||||
} else
|
|
||||||
inv += F("PowerLimitControl\"><option value=\"0\">absolute in Watt non persistent</option><option value=\"1\">relativ in percent non persistent</option><option value=\"256\">absolute in Watt persistent</option><option value=\"257\">relativ in percent persistent</option></select>");
|
|
||||||
// UGLY! But I do not know it a better way --//
|
|
||||||
|
|
||||||
inv += F("<label for=\"inv") + String(i) + F("ModPwr0\" name=\"lbl") + String(i);
|
inv += F("<label for=\"inv") + String(i) + F("ModPwr0\" name=\"lbl") + String(i);
|
||||||
inv += F("ModPwr\">Max Module Power (Wp)</label><div class=\"modpwr\">");
|
inv += F("ModPwr\">Max Module Power (Wp)</label><div class=\"modpwr\">");
|
||||||
|
@ -320,13 +332,14 @@ void web::showSave(void) {
|
||||||
iv->powerLimit[1] = actPwrLimitControl;
|
iv->powerLimit[1] = actPwrLimitControl;
|
||||||
iv->devControlCmd = ActivePowerContr;
|
iv->devControlCmd = ActivePowerContr;
|
||||||
iv->devControlRequest = true;
|
iv->devControlRequest = true;
|
||||||
if (iv->powerLimit[1] & 0x0001)
|
if ((iv->powerLimit[1] & 0x0001) == 0x0001)
|
||||||
DPRINTLN(DBG_INFO, F("Power limit for inverter ") + String(iv->id) + F(" set to ") + String(iv->powerLimit[0]) + F("%") );
|
DPRINTLN(DBG_INFO, F("Power limit for inverter ") + String(iv->id) + F(" set to ") + String(iv->powerLimit[0]) + F("%") );
|
||||||
else
|
else {
|
||||||
DPRINTLN(DBG_INFO, F("Power limit for inverter ") + String(iv->id) + F(" set to ") + String(iv->powerLimit[0]) + F("W") );
|
DPRINTLN(DBG_INFO, F("Power limit 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]));
|
DPRINTLN(DBG_INFO, F("Power Limit Control Setting ") + String(iv->powerLimit[1]));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (actPwrLimit == 0xffff){ // set to 100%
|
if (actPwrLimit == 0xffff) { // set to 100%
|
||||||
iv->powerLimit[0] = 100;
|
iv->powerLimit[0] = 100;
|
||||||
iv->powerLimit[1] = RelativPersistent;
|
iv->powerLimit[1] = RelativPersistent;
|
||||||
iv->devControlCmd = ActivePowerContr;
|
iv->devControlCmd = ActivePowerContr;
|
||||||
|
@ -371,7 +384,9 @@ void web::showSave(void) {
|
||||||
|
|
||||||
// mqtt
|
// mqtt
|
||||||
if(mWeb->arg("mqttAddr") != "") {
|
if(mWeb->arg("mqttAddr") != "") {
|
||||||
mWeb->arg("mqttAddr").toCharArray(mConfig->mqtt.broker, MQTT_ADDR_LEN);
|
String addr = mWeb->arg("mqttAddr");
|
||||||
|
addr.trim();
|
||||||
|
addr.toCharArray(mConfig->mqtt.broker, MQTT_ADDR_LEN);
|
||||||
mWeb->arg("mqttUser").toCharArray(mConfig->mqtt.user, MQTT_USER_LEN);
|
mWeb->arg("mqttUser").toCharArray(mConfig->mqtt.user, MQTT_USER_LEN);
|
||||||
mWeb->arg("mqttPwd").toCharArray(mConfig->mqtt.pwd, MQTT_PWD_LEN);
|
mWeb->arg("mqttPwd").toCharArray(mConfig->mqtt.pwd, MQTT_PWD_LEN);
|
||||||
mWeb->arg("mqttTopic").toCharArray(mConfig->mqtt.topic, MQTT_TOPIC_LEN);
|
mWeb->arg("mqttTopic").toCharArray(mConfig->mqtt.topic, MQTT_TOPIC_LEN);
|
||||||
|
@ -421,7 +436,84 @@ void web::showVisualization(void) {
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
void web::showLiveData(void) {
|
void web::showLiveData(void) {
|
||||||
DPRINTLN(DBG_VERBOSE, F("web::showLiveData"));
|
DPRINTLN(DBG_VERBOSE, F("web::showLiveData"));
|
||||||
mWeb->send(200, F("text/html"), mMain->getLiveData());
|
|
||||||
|
String modHtml;
|
||||||
|
for (uint8_t id = 0; id < mMain->mSys->getNumInverters(); id++) {
|
||||||
|
Inverter<> *iv = mMain->mSys->getInverterByPos(id);
|
||||||
|
if (NULL != iv) {
|
||||||
|
#ifdef LIVEDATA_VISUALIZED
|
||||||
|
uint8_t modNum, pos;
|
||||||
|
switch (iv->type) {
|
||||||
|
default:
|
||||||
|
case INV_TYPE_1CH: modNum = 1; break;
|
||||||
|
case INV_TYPE_2CH: modNum = 2; break;
|
||||||
|
case INV_TYPE_4CH: modNum = 4; break;
|
||||||
|
}
|
||||||
|
|
||||||
|
modHtml += F("<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 (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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mWeb->send(200, F("text/html"), modHtml);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -450,10 +542,11 @@ void web::showWebApi(void)
|
||||||
if (response["tx_request"] == (uint8_t)TX_REQ_INFO)
|
if (response["tx_request"] == (uint8_t)TX_REQ_INFO)
|
||||||
{
|
{
|
||||||
// if the AlarmData is requested set the Alarm Index to the requested one
|
// if the AlarmData is requested set the Alarm Index to the requested one
|
||||||
if (cmd == AlarmData){
|
if (cmd == AlarmData || cmd == AlarmUpdate){
|
||||||
|
// set the AlarmMesIndex for the request from user input
|
||||||
iv->alarmMesIndex = response["payload"];
|
iv->alarmMesIndex = response["payload"];
|
||||||
}
|
}
|
||||||
DPRINTLN(DBG_INFO, F("Will make tx-request 0x15 with subcmd ") + String(cmd) + F(" and payload ") + String(response["payload"]));
|
DPRINTLN(DBG_INFO, F("Will make tx-request 0x15 with subcmd ") + String(cmd) + F(" and payload ") + String((uint16_t) response["payload"]));
|
||||||
// process payload from web request corresponding to the cmd
|
// process payload from web request corresponding to the cmd
|
||||||
iv->enqueCommand<InfoCommand>(cmd);
|
iv->enqueCommand<InfoCommand>(cmd);
|
||||||
}
|
}
|
||||||
|
@ -488,6 +581,14 @@ void web::showWebApi(void)
|
||||||
iv->devControlRequest = true; // queue it in the request loop
|
iv->devControlRequest = true; // queue it in the request loop
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (response["cmd"] == (uint8_t)TurnOff){
|
||||||
|
iv->devControlCmd = TurnOff;
|
||||||
|
iv->devControlRequest = true; // queue it in the request loop
|
||||||
|
}
|
||||||
|
if (response["cmd"] == (uint8_t)TurnOn){
|
||||||
|
iv->devControlCmd = TurnOn;
|
||||||
|
iv->devControlRequest = true; // queue it in the request loop
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
mWeb->send(200, "text/json", "{success:true}");
|
mWeb->send(200, "text/json", "{success:true}");
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue