Browse Source

* Merge branch 'development03' into asyncWeb03

* improved webApi
* updated Library Information (version + license)
pull/283/head
lumapu 2 years ago
parent
commit
200ce0c0cc
  1. 2
      .github/workflows/compile_esp8266.yml
  2. 2
      README.md
  3. BIN
      doc/AhoyWemosD1.fzz
  4. BIN
      doc/AhoyWemos_Schaltplan.jpg
  5. BIN
      doc/AhoyWemos_Steckplatine.jpg
  6. 91
      tools/esp8266/CHANGES.md
  7. 66
      tools/esp8266/README.md
  8. 67
      tools/esp8266/User_Manual.md
  9. 9
      tools/esp8266/app.cpp
  10. 1
      tools/esp8266/app.h
  11. 2
      tools/esp8266/defines.h
  12. 6
      tools/esp8266/hmInverter.h
  13. 22
      tools/esp8266/html/style.css
  14. 50
      tools/esp8266/web.cpp
  15. 9
      tools/esp8266/webApi.cpp
  16. 3
      tools/rpi/hoymiles/__main__.py
  17. 3
      tools/rpi/hoymiles/outputs.py

2
.github/workflows/compile_esp8266.yml

@ -51,7 +51,7 @@ jobs:
draft: false draft: false
prerelease: false prerelease: false
release_name: ${{ steps.rename-binary-files.outputs.name }} release_name: ${{ steps.rename-binary-files.outputs.name }}
tag_name: ${{ github.ref }} tag_name: ${{ steps.rename-binary-files.outputs.name }}
body_path: tools/esp8266/CHANGES.md body_path: tools/esp8266/CHANGES.md
env: env:
GITHUB_TOKEN: ${{ github.token }} GITHUB_TOKEN: ${{ github.token }}

2
README.md

@ -3,7 +3,7 @@
![Logo](https://github.com/grindylow/ahoy/blob/main/doc/logo1_small.png?raw=true) ![Logo](https://github.com/grindylow/ahoy/blob/main/doc/logo1_small.png?raw=true)
# ahoy # ahoy
Ahoi is a project to bypass the original Hoymiles cloud solution. Ahoy 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. 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+. With each version it is necessary to have an NRF24L01+.

BIN
doc/AhoyWemosD1.fzz

Binary file not shown.

BIN
doc/AhoyWemos_Schaltplan.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

BIN
doc/AhoyWemos_Steckplatine.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 183 KiB

91
tools/esp8266/CHANGES.md

@ -1,88 +1,7 @@
# Changelog # Changelog
- v0.5.16 - v0.5.17
* Add alarm messages dictonary in the hminverter class * Bug fix for 1 channel inverters (HM300, HM400) see #246
* show last alarm message in the overview after receiving the message id from the AlarmData command * Bug fix for read back the active power limit from inverter #243 (before version 0.5.16 the reported limit was just a copy of the user set point, now it is the actual value which the inverter uses)
* Added No-PowerLimit function/setting (thx @lumapu) * Update the [user manual](https://github.com/grindylow/ahoy/blob/main/tools/esp8266/User_Manual.md); added section aobut the published data on mqtt; section about zero export control; added section about code implementation command queue
* Bug fix #195 trailing and leading spaces in setup parameters (thx @lumapu) * Added tx-Id number to packet payload struct. (eg. can be 0x95 or 0xD1) --> less messages fails and faster handling of changing power limit
* Added parametric CAD model for a case (thx @cubinet-code)
* Code styling improvements (eg. dynamic creation of html code) (thx @stefan123t, @lumapu)
* Mqtt publish action is now after successful parse a payload, no own ticker
* Fixes/improvements #183, #184, #216, #213, #196, #176, #171
- v0.5.15
* Bug fix: mqtt payload handling (thx @klahus1, silverserver)
* Bug fix: eeprom alignment fixed (thx @klahus1)
* mqtt reconnect improvements (thx @tastendruecker123 , @HorstG-57 )
* simple command scheduler (one place fifo)
* InverterDevInform_All Command parser and output to mqtt
* New workflow to build github release
* Introduction of a command queue (like OpenDTU)
* Feedback from inverter for actual power limit via InfoCmd -> SystemConfigPara (0x05) placed in visualization
* REST API will enqueue a new info command (all commands supported)
* Change in power limit will (Setup, MQTT or REST API) enqueues a new infocmd request to get actual power limit
* Actual power limit is available under MQTT topic <TOPIC>/<INVERTER-NAME>/ch0/PowerLimit ALWAYS in percent
* Firmware information will be requested automatically up on start of dtu
* Added User_Manual.md
- v0.5.14
- v0.5.13
- v0.5.12
- v0.5.11
- v0.5.10
- v0.5.9 *fix PowerLimit PowerPFDev.Desc=0x0001 for permanent
- v0.5.8 *fix #146 device name in setup
- v0.5.7 *add collapsible setup
- v0.5.6 *fix only MQTT sub after the first loop in a conenction
- v0.5.5 *fixed MQTT sub only after connection is established (HorstG-57)
+ added in app.cpp some compiler if statements
*fix: compile possible for non repository versions (if project was download as zip - lumapu)
*fix README.md - Update line 69 (`RF24` 1.4.2 -> 1.4.5) (DanielR92)
*Update hmRadio.h (lumapu)
- v0.5.4 + added Github report text with a URL (aschiffler)
+ added auto_firmware_version.py for GIT_HASH
+ added switch case AlarmData/AlarmUpdate
- v0.5.3 #Bugfix #125 PowerLimit
+ prototype webapi to get info, improved pwr limit (aschiffler)
+ Merge remote-tracking branch 'upstream/main' into pwrlimit
- v0.5.2 add #114 ntp_server_name and port to eeprom
+ stefan123t added some functions (devcontrol/cbMqtt/...)
- v0.5.1 *Merge branch 'upstream/HEAD' into control
*update revision (0.4.26 -> 0.5.1)
- v0.4.26 first poc for power set via mqtt
- v0.4.25 added default SERIAL/MQTT/SEND_INTERVAL #100, fixed env:node_mcu_v2 build #101
- v0.4.24 added fixes for #63, #88, #93. revert #36 (*) EEPROM changes
- v0.4.23 added workflow, fix index.html to load inverter info immediately, changed timestamp to 1 for stand alone ESP #90, Implement MQTT discovery for Home Assistant
- v0.4.22 compiles with PlatformIO
- v0.4.21 reduced warnings
- v0.4.20 improved setup (if no data is in EEprom), improved NRF24 Pinout regarding to #36, Standard Pinout should be now: #36 (comment), add JSON output, fix favicon, improve eeprom default settings (*) EEPROM changes
- v0.4.19 updated debug messages: now 5 different levels are available, fixed CRC loop issue, add fritzing/schematics for Arduino, Raspberry Pi and NodeMCU
- v0.4.18 Creative Commons NC-SA-BY v3.0 license included, tried to increase stability, fix NRF24 CRClength, add debug & documentation links, added variable error messages using #pragma error
- v0.4.17 add printed circuit board layout, more debug output (#retransmits), improved loop counters (*) EEPROM changes
- v0.4.16 request only one inverter per loop (#53 (comment)), mqtt loop interval calculated by # of inverters and inverter request interval, limit maximum number of retries, added feature request #62 (readable names for channels), improved setup page, added javascript to hide / show channel fields (*) EEPROM changes
- v0.4.15 reduced debug messages, fixes after merge
- v0.4.14 added RX channel 40, improved RF24 ISR, reduced AP active time to 60s (will be increase once a client is connected), added `yield` without success -> random reboot (cause 4) (*) EEPROM changes
- v0.4.13 rename to AHOY-DTU, add RX channel 40, update stats on index based on mSendInterval, MQTT Interval, EEPROM CRC settings, fix #56 v0.4.10 ESP8266 stuck in boot loop
- v0.4.12 version skipped ?
- v0.4.11 inverter dependent mqtt (is avail), implemented heap stats #58, inserted 'break' in ISR while loop
- v0.4.10 reduced heap size (>50%) by using 'F()' for (nearly) all static strings, added Wemos D1 case STL files
- v0.4.9 try to fix mqtt and wifi loss issue #52, document libraries (*) EEPROM changes
- v0.4.8 moved mqtt loop out of checkTicker as mentioned in #49, added irritation and efficiency calculations, improved style (*) EEPROM changes
- v0.4.7 version skipped ?
- v0.4.6 version skipped ?
- v0.4.5 fix #38 4-channel inverter current assignment, added last received timestamp in /hoymiles livedata web page #47, improved style.css, improved NTP as described in #46
- v0.4.4 added free heap, mentioned in #24 (added in serial print, status on index and mqtt), fixed #45, AC current by factor 10 too high, fixed failed payload counter
- v0.4.3 fixed #41 HM800 Yield total and Yield day were mixed around. Found issue while comparing to Python version, fixed #43 HM350 channel 2 is displayed in Live-View, added #42 YieldTotal and YieldTotal Day for HM600 - HM800 inverters
- v0.4.2 fix #39 Assignment 2-Channel inverters (HM-600, HM-700, HM-800)
- v0.4.1 multi inverter support, full re transmit included
- v0.4.0 complete payload processed (and crc checked), inverter type is defined by serial number, serial debug can be switched live (using setup), Note: only one inverter is supported for now!
- v0.3.9 fix #26 ticker / interval in app.cpp
- v0.3.8 improved stability (in comparison to 0.3.7), reset wifi AP timout once a client is detected, fix #26 wrong variable reset
- v0.3.7 added rx channel switching, switched to crc8 check for valid packet-payload
- v0.3.6 improved tickers, only one ticker is active, added feature to use the ESP as access point for all the time, added serial features to setup
- v0.3.5 fixed erase settings, fixed behavior if no MQTT IP is set (the system was nearly unusable because of delayed responses), fixed Station / AP WiFi on startup -> more information will be printed to the serial console, added new ticker for serial value dump
- v0.3.4 added config.h for general configuration, added option to compile WiFi SSID + PWD to firmware, added option to configure WiFi access point name and password, added feature to retry connect to station WiFi (configurable timeouts), updated index.html, added option for factory reset, added info about project on index.html, moved "update" and "home" to footer, fixed #23 HM1200 yield day unit, fixed DNS name of ESP after setup (some commits before)
- v0.3.3 converted to "poor-man-ticker" using millis() for uptime, send and mqtt, added inverter overview, added send count to statistics
- v0.3.2 compile of merge, binary published on https://www.mikrocontroller.net/topic/525778?goto=7051413#7051413
- v0.3.1 fix: doCalculations was not called
- v0.3.0 version 0.3.0, added unit test
(*) EEPROM changes require settings to be changed, your settings will be overwritten and need to be set again!

66
tools/esp8266/README.md

@ -3,6 +3,7 @@
- [Overview](#overview) - [Overview](#overview)
- [Compatiblity](#compatiblity) - [Compatiblity](#compatiblity)
- [Things needed](#things-needed) - [Things needed](#things-needed)
+ [Faked Modules Warning](#there-are-fake-nrf24l01-modules-out-there)
- [Wiring things up](#wiring-things-up) - [Wiring things up](#wiring-things-up)
+ [ESP8266 wiring example](#esp8266-wiring-example) + [ESP8266 wiring example](#esp8266-wiring-example)
- [Flash the Firmware on your Ahoy DTU Hardware](#flash-the-firmware-on-your-ahoy-dtu-hardware) - [Flash the Firmware on your Ahoy DTU Hardware](#flash-the-firmware-on-your-ahoy-dtu-hardware)
@ -53,11 +54,13 @@ We suggest to use a WEMOS D1 mini Board as well as a NRF24L01+ Breakout Board.<b
Make sure it has the "+" in its name as we depend on some features provided with the plus-variant.<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. Any other ESP8266 Board with at least 4MBytes of ROM could work as well, depending on your skills.
#### 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/grindylow/ahoy/issues/230).<br/>
You are welcome to add more examples of faked chips. We will that information here.<br/>
## Wiring things up ## Wiring things up
The NRF24L01+ radio module is connected to the standard SPI pins:
The NRF24L01+ radio module is connected to the
standard SPI pins:
- SCLK (Signal Clock), - SCLK (Signal Clock),
- MISO (Master In Slave Out) and - MISO (Master In Slave Out) and
- MOSI (Master Out Slave In) - MOSI (Master Out Slave In)
@ -72,8 +75,9 @@ Additional, there are 3 pins, which can be set individual:
*These pins can be changed from the /setup URL.* *These pins can be changed from the /setup URL.*
#### ESP8266 wiring example #### ESP8266 wiring example
ToDo: (this one needs to be reworked - also a generified one would be helpful) This is an example wiring using a Wemos D1 mini.<br>
<img src="https://github.com/grindylow/ahoy/blob/main/doc/ESP8266_nRF24L01%2B_bb.png" width="300"> <img src="https://github.com/grindylow/ahoy/blob/main/doc/AhoyWemos_Steckplatine.jpg" width="300">
<img src="https://github.com/grindylow/ahoy/blob/main/doc/AhoyWemos_Schaltplan.jpg" width="300">
@ -131,20 +135,25 @@ When everything is wired up and the firmware is flashed, it is time to connect t
#### Your Ahoy DTU is very verbose using the Serial Console #### Your Ahoy DTU is very verbose using the Serial Console
When connected to your computer, you can open a Serial Console to obtain additional information. 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 obtain information about the converted values which were read out of the inverter(s). 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 #### 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. 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 from your local DHCP Server (in most cases thats your Router). If your Ahoy DTU was able to log into the configured WiFi Network, it will try to obtain an IP-Address<br/>
In case it could not connect to your configured Network, it will provide its own WiFi Network that you can connect to for furter configuration. The WiFi SSID *(the WiFi Name)* and Passwort is configured in the config.h and defaults to the SSID "AHOY-DTU" with the Passwort "esp_8266". from your local DHCP Server (in most cases thats your Router).<br/><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). If nothing connects to it and that time runs up, it will retry to connect to the configured network an so on. 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/>
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. 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/>
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. 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/>
Just open the IP-Address in your browser. 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/>
Just open the IP-Address in your browser.<br/>
<br/>
The webinterface has the following abilities: The webinterface has the following abilities:
- OTA Update (Over The Air Update) - OTA Update (Over The Air Update)
- Configuration (Wifi, inverter(s), NTP Server, Pinout, MQTT, Amplifier Power Level, Debug) - Configuration (Wifi, inverter(s), NTP Server, Pinout, MQTT, Amplifier Power Level, Debug)
@ -153,7 +162,7 @@ When everything is wired up and the firmware is flashed, it is time to connect t
##### HTTP based Pages ##### HTTP based Pages
To take control of your Ahoy DTU, you can directly call one of the following sub-pages (e.g. http://192.168.1.1/setup ). 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/>
| page | use | output | | page | use | output |
| ---- | ------ | ------ | | ---- | ------ | ------ |
@ -172,20 +181,25 @@ When everything is wired up and the firmware is flashed, it is time to connect t
## MQTT command to set the DTU without webinterface ## MQTT command to set the DTU without webinterface
[Read here](https://github.com/grindylow/ahoy/blob/development02/tools/esp8266/User_Manual.md) [Read here](https://github.com/grindylow/ahoy/blob/main/tools/esp8266/User_Manual.md)
## Used Libraries ## Used Libraries
- `ESP8266WiFi` 1.0 | Name | version | License |
- `DNSServer` 1.1.0 | --------------------- | ------- | -------- |
- `Ticker` 1.0 | `ESP8266WiFi` | 1.0 | LGPL-2.1 |
- `ESP8266HTTPUpdateServer` 1.0 | `DNSServer` | 1.1.1 | LGPL-2.1 |
- `Time` 1.6.1 | `SPI` | 1.0 | LGPL-2.1 |
- `RF24` 1.4.5 | `Hash` | 1.0 | LGPL-2.1 |
- `PubSubClient` 2.8 | `EEPROM` | 1.0 | LGPL-2.1 |
- `ArduinoJson` 6.19.4 | `ESP Async WebServer` | 1.2.3 | LGPL-3.0 |
| `ESPAsyncTCP` | 1.2.2 | LGPL-3.0 |
| `Time` | 1.6.1 | LGPL-2.1 |
| `RF24` | 1.4.5 | GPL-2.0 |
| `PubSubClient` | 2.8 | MIT |
| `ArduinoJson` | 6.19.4 | MIT |
## Contact ## Contact

67
tools/esp8266/User_Manual.md

@ -9,6 +9,42 @@ In the initial case or after click "erase settings" the fields for the inverter
Set at least the serial number and a name for each inverter, check the "reboot after save" and click the "Save" button. Set at least the serial number and a name for each inverter, check the "reboot after save" and click the "Save" button.
## MQTT Output
The ahoy dtu will publish on the following topics
``<CHOOSEN_TOPIC_FROM_SETUP>/<INVERTER_NAME_FROM_SETUP>/ch0/#``
| Topic | Example Value | Remarks |
|---|---|---|
|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 %|
|Temp | 19.800|Temperature of inverter in Celsius|
|LARM_MES_ID | 9.000|Last Alarm Message Id|
|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|
|Efficiency | 95.174|actual ration AC Power over DC Power in percent|
|FWVersion | 10012.000| Firmware version eg. 1.00.12|
|FWBuildYear | 2020.000| Firmware build date|
|FWBuildMonthDay | 624.000| Firmware build month and day eg. 24th of june|
|HWPartId | 100.000| Hardware Id|
|PowerLimit | 80.000|actual set point for power limit control AC active power in percent|
|LastAlarmCode | 1.000| Last Alarm Code eg. "inverter start"|
``<CHOOSEN_TOPIC_FROM_SETUP>/<INVERTER_NAME_FROM_SETUP>/ch<CHANNEL_NUMBER>/#``
``<CHANNEL_NUMBER>`` is in the range 1 to 4 depending on the inverter type
| Topic | Example Value | Remarks |
|---|---|---|
|U_DC | 38.900| actual DC Voltage in Volt|
|I_DC | 0.640 | actual DC current in Ampere|
|P_DC | 25.000 | actual DC power in Watt|
|YieldDay | 17.000 | Energy converted to AC per day Watt hours per module/channel (measured on DC) |
|YieldTotal | 110.819 | Energy converted to AC since reset Watt hours per module/channel (measured on DC) |
|Irradiation |5.65 | ratio DC Power over set maximum power per module/channel in percent |
## Active Power Limit via Setup Page ## Active Power Limit via Setup Page
If you leave the field "Active Power Limit" empty during the setup and reboot the ahoy-dtu will set a value of 65535 in the setup. If you leave the field "Active Power Limit" empty during the setup and reboot the ahoy-dtu will set a value of 65535 in the setup.
That is the value you have to fill in case you want to operate the inverter without a active power limit. That is the value you have to fill in case you want to operate the inverter without a active power limit.
@ -39,6 +75,12 @@ To set the active power limit (controled value is the AC Power of the inverter)
| <CHOOSEN_TOPIC_FROM_SETUP>/devcontrol/<INVERTER_ID>/11/1 | [2...100] | % | not persistent | <CHOOSEN_TOPIC_FROM_SETUP>/devcontrol/<INVERTER_ID>/11/1 | [2...100] | % | not persistent
| <CHOOSEN_TOPIC_FROM_SETUP>/devcontrol/<INVERTER_ID>/11/257 | [2...100] | % | persistent | <CHOOSEN_TOPIC_FROM_SETUP>/devcontrol/<INVERTER_ID>/11/257 | [2...100] | % | persistent
👆 ``<INVERTER_ID>`` is the number of the specific inverter in the setup page.
* First inverter --> ``<INVERTER_ID>`` = 0
* Second inverter --> ``<INVERTER_ID>`` = 1
* ...
### Developer Information MQTT Interface ### Developer Information MQTT Interface
``<CHOOSEN_TOPIC_FROM_SETUP>/devcontrol/<INVERTER_ID>/<DevControlCmdType>/<DATA2>`` ``<CHOOSEN_TOPIC_FROM_SETUP>/devcontrol/<INVERTER_ID>/<DevControlCmdType>/<DATA2>``
@ -143,6 +185,10 @@ In the same approach as for MQTT any other SubCmd and also MainCmd can be applie
} }
``` ```
## Zero Export Control
* You can use the mqtt topic ``<CHOOSEN_TOPIC_FROM_SETUP>/devcontrol/<INVERTER_ID>/11`` with a number as payload (eg. 300 -> 300 Watt) to set the power limit to the published number in Watt. (In regular cases the inverter will use the new set point within one intervall period; to verify this see next bullet)
* You can check the inverter set point for the power limit control on the topic ``<CHOOSEN_TOPIC_FROM_SETUP>/<INVERTER_NAME_FROM_SETUP>/ch0/PowerLimit`` 👆 This value is ALWAYS in percent of the maximum power limit of the inverter. In regular cases this value will be updated within approx. 15 seconds. (depends on request intervall)
* You can monitor the actual AC power by subscribing to the topic ``<CHOOSEN_TOPIC_FROM_SETUP>/<INVERTER_NAME_FROM_SETUP>/ch0/P_AC`` 👆 This value is ALWAYS in Watt
## Issues and Debuging for active power limit settings ## Issues and Debuging for active power limit settings
Turn on the serial debugging in the setup. Try to have find out if the behavior is deterministic. That means can you reproduce the behavior. Be patient and wait on inverter reactions at least some minutes and beware that the DC-Power is sufficient. Turn on the serial debugging in the setup. Try to have find out if the behavior is deterministic. That means can you reproduce the behavior. Be patient and wait on inverter reactions at least some minutes and beware that the DC-Power is sufficient.
@ -154,7 +200,9 @@ In case of issues please report:
**Developer Information General for Active Power Limit** **Developer Information General for Active Power Limit**
⚡Was verified by field tests and feedback from three users
⚡The following was verified by field tests and feedback from users
Internally this values will be set for the second two bytes for MainCmd: 0x51 SubCmd: 0x0b --> DevControl set ActivePowerLimit Internally this values will be set for the second two bytes for MainCmd: 0x51 SubCmd: 0x0b --> DevControl set ActivePowerLimit
```C ```C
typedef enum { typedef enum {
@ -178,6 +226,23 @@ Gather user inverter information here to understand what differs between some in
| eeprom23 | HM-1200 2t | 0.1.0 | 1.0.16 | 2021 | 10-12 | 269619207 | 17:06:00 | HWRev 256 | | eeprom23 | HM-1200 2t | 0.1.0 | 1.0.16 | 2021 | 10-12 | 269619207 | 17:06:00 | HWRev 256 |
| fila612 | HM-700 | | 1.0.10 | 2021 | 11-01 | 104 | | | | fila612 | HM-700 | | 1.0.10 | 2021 | 11-01 | 104 | | |
| tfhcm | TSUN-350 | | 1.0.14 | 2021 | 12-09 | 102 | | | | tfhcm | TSUN-350 | | 1.0.14 | 2021 | 12-09 | 102 | | |
| Groobi | TSOL-M400 | | 1.0.14 | 2021 | 12-09 | 102 | | |
| setje | HM-600 | | 1.0.08 | 2020 | 07-10 | 104 | | |
| | | | | | | | | | | | | | | | | | | |
| | | | | | | | | | | | | | | | | | | |
| | | | | | | | | | | | | | | | | | | |
## Developer Information about Command Queue
After reboot or startup the ahoy firmware it will enque three commands in the following sequence:
1. Get active power limit in percent ( ```SystemConfigPara = 5 // 0x05```)
2. Get firmware version (```InverterDevInform_All = 1 // 0x01```)
3. Get data (```RealTimeRunData_Debug = 11 // 0x0b```)
With the command get data (```RealTimeRunData_Debug = 11 // 0x0b```) the alarm message counter will be updated. In the initial case then aonther command is queued to get the alarm code (``` AlarmData = 17 // 0x11```).
This command (``` AlarmData = 17 // 0x11```) will enqued in any operation phase if alarm message counter is raised by one or greater compared to the last request with command get data (```RealTimeRunData_Debug = 11 // 0x0b```)
In case all commands are processed (```_commandQueue.empty() == true```) then as a default command the get data (```RealTimeRunData_Debug = 11 // 0x0b```) will be enqueued.
In case a Device Control command (Power Limit, Off, On) is requested via MQTT or REST API this request will be send before any other enqueued command.
In case of a accepted change in power limit the command get active power limit in percent ( ```SystemConfigPara = 5 // 0x05```) will be enqueued. The acceptance is checked by the reponse packets on the devive control commands (tx id 0x51 --> rx id 0xD1) if in byte 12 the requested sub-command (eg. power limit) is present.

9
tools/esp8266/app.cpp

@ -95,6 +95,7 @@ void app::loop(void) {
if(0 != len) { if(0 != len) {
Inverter<> *iv = mSys->findInverter(&p->packet[1]); Inverter<> *iv = mSys->findInverter(&p->packet[1]);
if(NULL != iv && p->packet[0] == (TX_REQ_INFO + 0x80)) { // response from get information command if(NULL != iv && p->packet[0] == (TX_REQ_INFO + 0x80)) { // response from get information command
mPayload[iv->id].txId = p->packet[0];
DPRINTLN(DBG_DEBUG, F("Response from info request received")); DPRINTLN(DBG_DEBUG, F("Response from info request received"));
uint8_t *pid = &p->packet[9]; uint8_t *pid = &p->packet[9];
if (*pid == 0x00) if (*pid == 0x00)
@ -121,6 +122,7 @@ 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")); DPRINTLN(DBG_DEBUG, F("Response from devcontrol request received"));
iv->devControlRequest = false; iv->devControlRequest = false;
switch (p->packet[12]) { switch (p->packet[12]) {
@ -254,6 +256,7 @@ void app::loop(void) {
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->clearCmdQueue();
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);
@ -312,7 +315,11 @@ void app::processPayload(bool retransmit) {
for(uint8_t id = 0; id < mSys->getNumInverters(); id++) { for(uint8_t id = 0; id < mSys->getNumInverters(); id++) {
Inverter<> *iv = mSys->getInverterByPos(id); Inverter<> *iv = mSys->getInverterByPos(id);
if(NULL != iv) { if(NULL != iv) {
if(!mPayload[iv->id].complete) { if(mPayload[iv->id].txId != (TX_REQ_INFO + 0x80)) {
// no processing needed if txId is not 0x95
mPayload[iv->id].complete = true;
}
if(!mPayload[iv->id].complete ) {
if(!buildPayload(iv->id)) { if(!buildPayload(iv->id)) {
if(mPayload[iv->id].requested) { if(mPayload[iv->id].requested) {
if(retransmit) { if(retransmit) {

1
tools/esp8266/app.h

@ -38,6 +38,7 @@ typedef HmSystem<RadioType, BufferType, MAX_NUM_INVERTERS, InverterType> HmSyste
typedef struct { typedef struct {
uint8_t txId;
uint8_t invId; uint8_t invId;
uint32_t ts; uint32_t ts;
uint8_t data[MAX_PAYLOAD_ENTRIES][MAX_RF_PAYLOAD_SIZE]; uint8_t data[MAX_PAYLOAD_ENTRIES][MAX_RF_PAYLOAD_SIZE];

2
tools/esp8266/defines.h

@ -13,7 +13,7 @@
//------------------------------------- //-------------------------------------
#define VERSION_MAJOR 0 #define VERSION_MAJOR 0
#define VERSION_MINOR 5 #define VERSION_MINOR 5
#define VERSION_PATCH 17 #define VERSION_PATCH 18
//------------------------------------- //-------------------------------------

6
tools/esp8266/hmInverter.h

@ -149,6 +149,12 @@ class Inverter {
} }
} }
void clearCmdQueue(){
while (!_commandQueue.empty()){
// Will destroy CommandAbstract Class Object (?)
_commandQueue.pop();
}
}
uint8_t getQueuedCmd() uint8_t getQueuedCmd()
{ {
if (_commandQueue.empty()){ if (_commandQueue.empty()){

22
tools/esp8266/html/style.css

@ -182,7 +182,16 @@ div.ch {
padding-bottom: 20px; padding-bottom: 20px;
} }
div.ch .value, div.ch .info, div.ch .head, div.ch-iv .value, div.ch-iv .info, div.ch-iv .head { div.ch-all {
width: 100%;
background-color: #b06e04;
display: inline-block;
margin-bottom: 15px;
padding-bottom: 20px;
overflow: auto;
}
div.ch .value, div.ch .info, div.ch .head, div.ch-iv .value, div.ch-iv .info, div.ch-iv .head, div.ch-all .value, div.ch-all .info, div.ch-all .head {
color: #fff; color: #fff;
display: block; display: block;
width: 100%; width: 100%;
@ -194,17 +203,17 @@ div.ch .value, div.ch .info, div.ch .head, div.ch-iv .value, div.ch-iv .info, di
width: 220px; width: 220px;
} }
div.ch .unit, div.ch-iv .unit { div.ch .unit, div.ch-iv .unit, div.ch-all .unit {
font-size: 19px; font-size: 19px;
margin-left: 10px; margin-left: 10px;
} }
div.ch .value, div.ch-iv .value { div.ch .value, div.ch-iv .value, div.ch-all .value {
margin-top: 20px; margin-top: 20px;
font-size: 24px; font-size: 24px;
} }
div.ch .info, div.ch-iv .info { div.ch .info, div.ch-iv .info, div.ch-all .info {
margin-top: 3px; margin-top: 3px;
font-size: 10px; font-size: 10px;
} }
@ -214,6 +223,11 @@ div.ch .head {
padding: 10px 0 10px 0; padding: 10px 0 10px 0;
} }
div.ch-all .head {
background-color: #8e5903;
padding: 10px 0 10px 0;
}
div.ch-iv .head { div.ch-iv .head {
background-color: #1c6800; background-color: #1c6800;
padding: 10px 0 10px 0; padding: 10px 0 10px 0;

50
tools/esp8266/web.cpp

@ -333,8 +333,13 @@ void web::showVisualization(AsyncWebServerRequest *request) {
void web::showLiveData(AsyncWebServerRequest *request) { void web::showLiveData(AsyncWebServerRequest *request) {
DPRINTLN(DBG_VERBOSE, F("web::showLiveData")); DPRINTLN(DBG_VERBOSE, F("web::showLiveData"));
String modHtml; String modHtml, totalModHtml;
float totalYield = 0, totalYieldToday = 0, totalActual = 0;
uint8_t count = 0;
for (uint8_t id = 0; id < mMain->mSys->getNumInverters(); id++) { for (uint8_t id = 0; id < mMain->mSys->getNumInverters(); id++) {
count++;
Inverter<> *iv = mMain->mSys->getInverterByPos(id); Inverter<> *iv = mMain->mSys->getInverterByPos(id);
if (NULL != iv) { if (NULL != iv) {
#ifdef LIVEDATA_VISUALIZED #ifdef LIVEDATA_VISUALIZED
@ -358,6 +363,19 @@ void web::showLiveData(AsyncWebServerRequest *request) {
for (uint8_t fld = 0; fld < 11; fld++) { for (uint8_t fld = 0; fld < 11; fld++) {
pos = (iv->getPosByChFld(CH0, list[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) { if (0xff != pos) {
modHtml += F("<div class=\"subgrp\">"); modHtml += F("<div class=\"subgrp\">");
modHtml += F("<span class=\"value\">") + String(iv->getValue(pos)); modHtml += F("<span class=\"value\">") + String(iv->getValue(pos));
@ -408,7 +426,35 @@ void web::showLiveData(AsyncWebServerRequest *request) {
} }
} }
request->send(200, F("text/html"), modHtml); 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>");
request->send(200, F("text/html"), modHtml);
} else {
request->send(200, F("text/html"), modHtml);
}
} }

9
tools/esp8266/webApi.cpp

@ -226,7 +226,6 @@ void webApi::getLive(JsonObject obj) {
iv = mApp->mSys->getInverterByPos(i); iv = mApp->mSys->getInverterByPos(i);
if(NULL != iv) { if(NULL != iv) {
JsonObject obj2 = invArr.createNestedObject(); JsonObject obj2 = invArr.createNestedObject();
obj2[F("id")] = i;
obj2[F("name")] = String(iv->name); obj2[F("name")] = String(iv->name);
obj2[F("channels")] = iv->channels; obj2[F("channels")] = iv->channels;
obj2[F("power_limit_read")] = iv->actPowerLimit; obj2[F("power_limit_read")] = iv->actPowerLimit;
@ -246,8 +245,8 @@ void webApi::getLive(JsonObject obj) {
} }
} }
for(uint8_t j = 0; j < iv->channels; j ++) { for(uint8_t j = 1; j <= iv->channels; j ++) {
obj2[F("ch_names")][j] = iv->chName[j]; obj2[F("ch_names")][j-1] = iv->chName[j];
JsonArray cur = ch.createNestedArray(); JsonArray cur = ch.createNestedArray();
for (uint8_t k = 0; k < 6; k++) { for (uint8_t k = 0; k < 6; k++) {
switch(k) { switch(k) {
@ -258,8 +257,8 @@ void webApi::getLive(JsonObject obj) {
case 4: pos = (iv->getPosByChFld(j, FLD_YT)); break; case 4: pos = (iv->getPosByChFld(j, FLD_YT)); break;
case 5: pos = (iv->getPosByChFld(j, FLD_IRR)); break; case 5: pos = (iv->getPosByChFld(j, FLD_IRR)); break;
} }
cur[k] = (0xff != pos) ? iv->getValue(pos) : 0; cur[k] = (0xff != pos) ? iv->getValue(pos) : 0.0;
if(0xff != pos) { if((0 == j) && (0xff != pos)) {
obj2[F("fld_units")][k] = String(iv->getUnit(pos)); obj2[F("fld_units")][k] = String(iv->getUnit(pos));
obj2[F("fld_names")][k] = String(iv->getFieldName(pos)); obj2[F("fld_names")][k] = String(iv->getFieldName(pos));
} }

3
tools/rpi/hoymiles/__main__.py

@ -72,7 +72,8 @@ def poll_inverter(inverter, retries=4):
# Handle the response data if any # Handle the response data if any
if response: if response:
c_datetime = datetime.now() c_datetime = datetime.now()
print(f'{c_datetime} Payload: ' + hoymiles.hexify_payload(response)) if hoymiles.HOYMILES_DEBUG_LOGGING:
print(f'{c_datetime} Payload: ' + hoymiles.hexify_payload(response))
decoder = hoymiles.ResponseDecoder(response, decoder = hoymiles.ResponseDecoder(response,
request=com.request, request=com.request,
inverter_ser=inverter_ser inverter_ser=inverter_ser

3
tools/rpi/hoymiles/outputs.py

@ -215,6 +215,7 @@ class VolkszaehlerOutputPlugin(OutputPluginFactory):
""" """
super().__init__(**params) super().__init__(**params)
self.session = requests.Session()
self.baseurl = config.get('url', 'http://localhost/middleware/') self.baseurl = config.get('url', 'http://localhost/middleware/')
self.channels = dict() self.channels = dict()
for channel in config.get('channels', []): for channel in config.get('channels', []):
@ -271,7 +272,7 @@ class VolkszaehlerOutputPlugin(OutputPluginFactory):
uid = self.channels[ctype] uid = self.channels[ctype]
url = f'{self.baseurl}/data/{uid}.json?operation=add&ts={ts}&value={value}' url = f'{self.baseurl}/data/{uid}.json?operation=add&ts={ts}&value={value}'
try: try:
r = requests.get(url) r = self.session.get(url)
if r.status_code != 200: if r.status_code != 200:
raise ValueError('Could not send request (%s)' % url) raise ValueError('Could not send request (%s)' % url)
except ConnectionError as e: except ConnectionError as e:

Loading…
Cancel
Save