DEYE Wechselrichter: Unterschied zwischen den Versionen
(52 dazwischenliegende Versionen desselben Benutzers werden nicht angezeigt) | |||
Zeile 3: | Zeile 3: | ||
Anfang März habe ich endlich ein '''Balkonkraftwerk''' über Amazon bestellt und installiert. Es ist ja inzwischen keine Raketenwissenschaft mehr, und die Akzeptanz und Möglichkeiten zur einfachen Installation per Do-It-Yourself haben mich nun endlich Fakten schaffen lassen. | Anfang März habe ich endlich ein '''Balkonkraftwerk''' über Amazon bestellt und installiert. Es ist ja inzwischen keine Raketenwissenschaft mehr, und die Akzeptanz und Möglichkeiten zur einfachen Installation per Do-It-Yourself haben mich nun endlich Fakten schaffen lassen. | ||
Ich habe '''zwei Standard PV-Module''' je 410W samt einem passendem '''DEYE Wechselrichter''' (WR) mit Anschlusskabel als Komplettpaket bestellt. Da ich am Balkon schon eine Klimaanlage stehen habe, ist der Stromanschluss auch kein Problem gewesen. Und endlich gilt im Sommer: Wenn es heiß ist und die Klimaanlage läuft, produzieren die PV-Module ordentlich Strom dafür, außerdem ich | Ich habe '''zwei Standard PV-Module''' je 410W samt einem passendem '''DEYE Wechselrichter''' (WR) mit Anschlusskabel als Komplettpaket bestellt. Da ich am Balkon schon eine Klimaanlage stehen habe, ist der Stromanschluss auch kein Problem gewesen. Und endlich gilt im Sommer: Wenn es heiß ist und die Klimaanlage läuft, produzieren die PV-Module ordentlich Strom dafür, außerdem muss ich dann keinen Überschuss verschenken. | ||
Zum Start hatten die DEYE Wechselrichter, die meines Wissens identisch zu z.B. ''Bosswerk'', ''Blaupunkt'' und ''Turbo Energy'' sind, allerdings noch ein Security Problem mit der Firmware, darüber hatte u.a. ''Heise'' berichtet. Ein ''Firmware Upgrade'' stand kurz darauf zur Verfügung und wurde automatisch eingespielt. | Zum Start hatten die DEYE Wechselrichter, die meines Wissens identisch zu z.B. ''Bosswerk'', ''Blaupunkt'' und ''Turbo Energy'' sind, allerdings noch ein Security Problem mit der Firmware, darüber hatte u.a. ''Heise'' berichtet. Ein ''Firmware Upgrade'' stand kurz darauf zur Verfügung und wurde automatisch eingespielt. | ||
Die DEYE Wechselrichter haben bereits WLAN eingebaut, d.h. steckt die wirklich nur in die Steckdose und die PV-Module und | Die DEYE Wechselrichter haben bereits WLAN eingebaut, d.h. man steckt die wirklich nur in die Steckdose und verbindet anschliessend die PV-Module und es geht los. Die Einbindung ins eigene WLAN erfolgt wie üblich mittels eingebautem WLAN Accesspoint und internem Webserver, worüber man die Konfiguration vornimmt und dann am besten den WLAN-AP deaktiviert. Die Internetverbindung des Wechselrichters habe ich dann über meine Fritzbox gesperrt. | ||
Der WR ist '''über WLAN nur erreichbar, wenn die PV-Module eine Spannung (DC) erzeugen'''. Der Anschluss an das Stromnetz (AC) ist hierfür irrelevant. Das heisst wenn es dunkel ist, ist er nicht erreichbar bzw. aus. | Der WR ist '''über WLAN nur erreichbar, wenn die PV-Module eine Spannung (DC) erzeugen'''. Der Anschluss an das Stromnetz (AC) ist hierfür irrelevant. Das heisst wenn es dunkel ist, ist er nicht erreichbar bzw. aus. | ||
Der WR | Der WR bietet verschiedene Möglichkeiten zur Konfiguration und Abfrage der aktuellen Werte: | ||
* einen internen Webserver (http://10.10.10.254) | * einen internen Webserver (http://10.10.10.254) | ||
* die Cloud Anbindung zu Solarman (Solarman Smart App) | * die Cloud Anbindung zu Solarman (Solarman Smart App) | ||
* Modbus Protokoll über Port 8899 | * (angepasstes) Modbus Protokoll über Port 8899 | ||
* AT+ Befehle über Port 48899 | * AT+ Befehle über Port 48899 | ||
Zeile 23: | Zeile 23: | ||
Für DEYE kompatible WR stehen inzwischen einige Lösungsansätze zur Verfügung. Ich verwende die '''[https://github.com/kbialek/deye-inverter-mqtt DEYE Inverter MQTT Bridge]''' von ''Krzysztof Białek'', da ich hier bereits einen '''MQTT Server''' innerhalb der Heimautomatisierungslösung '''FHEM''' zur Einbindung meiner '''Tasmota''' Devices (Shelly; Hichi) eingerichtet habe. | Für DEYE kompatible WR stehen inzwischen einige Lösungsansätze zur Verfügung. Ich verwende die '''[https://github.com/kbialek/deye-inverter-mqtt DEYE Inverter MQTT Bridge]''' von ''Krzysztof Białek'', da ich hier bereits einen '''MQTT Server''' innerhalb der Heimautomatisierungslösung '''FHEM''' zur Einbindung meiner '''Tasmota''' Devices (Shelly; Hichi) eingerichtet habe. | ||
Diese Lösung ist als Docker Container | Diese Lösung ist als Docker Container konzipiert, was mir aber unnötiger Overhead ist. Da es in Python realisiert ist, starte ich das Skript ganz einfach direkt, ohne Docker. | ||
== Wechselrichter Eigenheiten == | == Wechselrichter Eigenheiten == | ||
Der Wechselrichter läuft nur, wenn die PV-Module eine Gleichspannung einspeisen - d.h. wenn es draussen hell ist. Ein Auslesen der Werte des WR funktioniert also nicht, wenn es dunkel ist (Timeout). Ausserdem wird der Tageszähler erst durch setzen von Uhrzeit/Datum wieder auf 0 gesetzt, was über eine Internetverbindung erfolgt. Ist die gesperrt, läuft der Tageszähler weiter. | Der Wechselrichter läuft nur, wenn die PV-Module eine Gleichspannung einspeisen - d.h. wenn es draussen hell ist. Ein Auslesen der Werte des WR funktioniert also nicht, wenn es dunkel ist (Timeout). Ausserdem wird der Tageszähler erst durch setzen von Uhrzeit/Datum wieder auf 0 gesetzt, was über eine Internetverbindung erfolgt. Ist die gesperrt, läuft der Tageszähler weiter. | ||
Also, jeden Abend geht der WR aus, und sobald er beim ersten Tageslicht wieder startet möchte ich Datum und Uhrzeit setzen und anschliessend die Werte kontinuierlich auslesen und über MQTT ausgeben. Bis dahin | Also, jeden Abend geht der WR aus, und sobald er beim ersten Tageslicht wieder startet möchte ich Datum und Uhrzeit setzen und anschliessend die Werte kontinuierlich auslesen und über MQTT ausgeben. Bis dahin ist der WR aber halt nicht erreichbar. | ||
== Installation == | == Installation == | ||
Das Projekt von Github laden und in einem Verzeichnis speichern, '''Python''' ist normalerweise ja schon installiert. | Das Projekt von Github laden und in einem Verzeichnis speichern, '''Python''' ist normalerweise ja schon installiert. | ||
<pre>git clone https://github.com/kbialek/deye-inverter-mqtt | |||
pip install paho-mqtt | |||
cd deye-inverter-mqtt</pre> | |||
Unter Debian habe ich im Skript '''deye_cli.sh''' python durch python3 ersetzen müssen: | Unter Debian habe ich im Skript '''deye_cli.sh''' python durch python3 ersetzen müssen: | ||
<pre>#!/bin/bash | <pre>#!/bin/bash | ||
Zeile 38: | Zeile 43: | ||
</pre> | </pre> | ||
=== config.env === | |||
Die Konfiguration meines DEYE Wechselrichters vom Typ Micro-Inverter: | Die Konfiguration meines DEYE Wechselrichters vom Typ Micro-Inverter: | ||
'''config.env''' | '''config.env''' | ||
<pre>DEYE_LOGGER_IP_ADDRESS=<IP Wechselrichter im WLAN> | <pre>DEYE_LOGGER_IP_ADDRESS=<IP Wechselrichter im WLAN> | ||
Zeile 56: | Zeile 61: | ||
</pre> | </pre> | ||
Ausserdem habe ich ein kleines ''Wrapper Skript'' geschrieben, um '''Daten des WR''' einfacher '''lesen und schreiben''' zu können: | === deye_inverter.sh === | ||
Ausserdem habe ich ein kleines ''Wrapper Skript'' geschrieben, um '''Daten des WR''' einfacher '''lesen und schreiben''' zu können:<br> | |||
'''deye_inverter.sh''' [--check <pause>] --read <register> | --write <register> <value> | |||
<pre>#!/bin/bash | <pre>#!/bin/bash | ||
# Read / Write DEYE Inverters | # Read / Write DEYE Inverters | ||
Zeile 65: | Zeile 72: | ||
retries=2 # retry deye_inverter command multiple times | retries=2 # retry deye_inverter command multiple times | ||
sleep= | sleep=60 # sleep time between retries | ||
# locate script in deye_inverter_mqtt directory | # locate script in deye_inverter_mqtt directory | ||
Zeile 74: | Zeile 81: | ||
exit 1 | exit 1 | ||
fi | fi | ||
. ./config.env | |||
log_info () | log_info () | ||
{ | { | ||
if [ ${LOG_LEVEL} = "INFO" ]; then | if [ ${LOG_LEVEL} = "INFO" ]; then | ||
logger -t $(basename $0) $@ | logger -t $(basename $0) $@ | ||
Zeile 86: | Zeile 92: | ||
log_error () | log_error () | ||
{ | { | ||
echo | logger -t $(basename $0) $@ | ||
echo 1>&2 $@ | |||
} | } | ||
Zeile 160: | Zeile 167: | ||
Also, jede Nacht geht der WR aus, und sobald er beim ersten Tageslicht wieder startet möchte ich Datum und Uhrzeit setzen und anschliessend die Werte kontinuierlich auslesen und über MQTT ausgeben. | Also, jede Nacht geht der WR aus, und sobald er beim ersten Tageslicht wieder startet möchte ich Datum und Uhrzeit setzen und anschliessend die Werte kontinuierlich auslesen und über MQTT ausgeben. | ||
Folgendes Skript starte ich jeden Morgen um 04:00 Uhr per Cron (und in rc.local | Folgendes Skript starte ich jeden Morgen um 04:00 Uhr per Cron (und in rc.local fürs booten): | ||
=== deye_mqtt_loop.sh === | |||
Dieses Script liegt bei mir unter ''/root/sbin'' und startet eine Endlosschleife. Dazu löscht es alte (vorher gestartete) Instanzen von sich selbst - dadurch kann es jederzeit (zB. in der crontab) erneut gestartet werden. Es wartet auf den Sonnenaufgang, der mittels der sunrise() von '''FHEM''' ermittelt wird. Es wartet auf den Sonnenuntergang per sunset() von FHEM, um danach die Abfrage zu pausieren bis zum nächsten Sonnenaufgang. | |||
Ausserdem setzt es beim Start den Tageszähler des Wechselrichters bedarfsweise zurück, bevor die Werte des DEYE Wechselrichters per Endlosschleife abgefragt werden. | |||
<pre>#!/bin/bash | <pre>#!/bin/bash | ||
# reset Daily_Power of DEYE Inverters | # reset Daily_Power of DEYE Inverters | ||
# via deye_inverter_mqtt python package | # via deye_inverter_mqtt python package | ||
# https://github.com/kbialek/deye-inverter-mqtt | # https://github.com/kbialek/deye-inverter-mqtt | ||
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin | |||
# config: | # config: | ||
deye_inverter_mqtt_cfg= | deye_inverter_dir=/root/deye-inverter-mqtt | ||
deye_inverter_mqtt_cmd= | fhem_dir=/opt/fhem | ||
horizon="-2" # sunset CIVIL=-6, REAL=0 | |||
debug=info | |||
myPID=$$ | |||
myname=$(basename $0) | |||
arg="$1" | |||
deye_inverter_sh=$deye_inverter_dir/deye_inverter.sh | |||
deye_inverter_mqtt_cfg=$deye_inverter_dir/config.env | |||
deye_inverter_mqtt_cmd="python3 deye_docker_entrypoint.py" | |||
sunrise_cmd="perl $fhem_dir/fhem.pl localhost:7072 {sunrise_abs('HORIZON=$horizon')}" | |||
sunset_cmd="perl $fhem_dir/fhem.pl localhost:7072 {sunset_abs('HORIZON=$horizon')}" | |||
# installation: | # installation: | ||
# locate FHEM, used for sunrise(), sunset() | |||
if [ ! -x ${fhem_dir/fhem.pl} ]; then | |||
echo "Error: ${fhem_dir} not found." | |||
echo " Is FHEM installed in $fhem_dir ?" | |||
exit 1 | |||
fi | |||
# locate script in deye_inverter_mqtt directory | # locate script in deye_inverter_mqtt directory | ||
if [ ! -f ${deye_inverter_mqtt_cfg} ]; then | |||
if [ | echo "Error: ${deye_inverter_mqtt_cfg} not found." | ||
echo "Error: | echo " Please update \$deye_inverter_sh path in $0" | ||
echo " Please | |||
exit 1 | exit 1 | ||
fi | fi | ||
# | # read my config | ||
set -a; source ${deye_inverter_mqtt_cfg}; set +a | |||
[ "$debug" = "full" ] && set -x | |||
# ----- functions () ------------------------------------ | |||
log_info () | |||
{ | { | ||
[ "$debug" = "yes" -o "$debug" = "true" -o "$debug" = "info" ] && | |||
echo $myname: $@ | |||
[ ${LOG_LEVEL} = "INFO" ] && logger -t $myname $@ | |||
} | |||
log_error () | |||
{ | |||
logger -t $myname $@ | |||
echo 1>&2 $@ | |||
} | } | ||
# get deye_inverter_mqtt PIDs | # get deye_inverter_mqtt PIDs | ||
get_deye_mqtt_pid () | |||
{ | |||
deye_mqtt_pid=$(ps ax | grep -v grep | grep "${deye_inverter_mqtt_cmd}" | awk '{print $1}') | |||
} | |||
# kill deye_inverter_loop () | |||
kill_old_instance () | |||
{ | { | ||
ps | # kill old instances of me | ||
oldPID=$(ps -ef | grep -v grep | grep $myname | cut -b10-26,53- | grep -v $myPID | awk '{print $1}') | |||
[ ! -z "$oldPID" ] && kill $oldPID | |||
} | } | ||
# | # kill deye_inverter_instance () | ||
kill_deye_inverter_instance () | |||
{ | { | ||
get_deye_mqtt_pid | |||
if [ ! -z "${deye_mqtt_pid}" ]; then | |||
[ -z ${deye_mqtt_pid} ] | kill ${deye_mqtt_pid} | ||
log_info "Info: KILL running deye_mqtt ${deye_mqtt_pid}" | |||
fi | |||
# | # kill any running deye requests | ||
deye_pid=$(ps ax | grep -v grep | grep "$deye_inverter_sh " | awk '{print $1}') | |||
[ ! -z "$deye_pid" ] && kill $deye_pid | |||
} | |||
# stop all runnning instances | |||
deye_inverter_mqtt_stop () | |||
{ | |||
kill_old_instance | |||
kill_deye_inverter_instance | |||
} | |||
# start deye_inverter_mqtt | |||
deye_inverter_mqtt_start () | |||
{ | |||
log_info "Deye MQTT start" | |||
cd ${deye_inverter_dir} | |||
${deye_inverter_mqtt_cmd} | |||
} | } | ||
# store actual date + time into vars | # store actual date + time into vars | ||
set_date_time_vars () | |||
{ | { | ||
year=$(date +%y) | year=$(date +%y) | ||
Zeile 233: | Zeile 285: | ||
minute=$(date +%m | sed 's/^0//g') | minute=$(date +%m | sed 's/^0//g') | ||
second=$(date +%S | sed 's/^0//g') | second=$(date +%S | sed 's/^0//g') | ||
} | |||
# read year_month register 0x16 = 22 | |||
deye_read_year_month () | |||
{ | |||
${deye_inverter_sh} -r 22 | awk '/^int: .*/ {split($2,i,","); print i[1]}' | |||
} | |||
# read day register 0x17 = 23 | |||
deye_read_day () | |||
{ | |||
${deye_inverter_sh} -r 23 | awk '/^int: .*/ {split($6,i,","); print i[1]}' | |||
} | |||
# read Daily power register 0x3c = 60 | |||
deye_read_daily_Power () | |||
{ | |||
${deye_inverter_sh} -r 60 | awk '/^int: .*/ {split($2,i,","); print i[1]}' | |||
} | |||
# read actual power register 0x56 = 86 | |||
deye_read_actual_Power () | |||
{ | |||
# doubleRegisterSensor("AC Active Power", 0x56, 0.1, mqtt_topic_suffix='ac/active_power', groups=['string', 'micro']) | |||
# (int.from_bytes(high_word, 'big') * 65536 + int.from_bytes(low_word, 'big')) * self.factor | |||
# val86=${deye_inverter_sh} -r 86 | sed 's/int: //g' | cut -d, -f1 | |||
# val87=${deye_inverter_sh} -r 87 | sed 's/int: //g' | cut -d, -f1 | |||
# echo $(( (${val87} * 65536 + ${val86}) * 0.1 )) | |||
# simple value is enough for our purpose | |||
${deye_inverter_sh} -r 86 | awk '/^int: .*/ {split($2,i,","); print i[1]}' | |||
} | |||
# write actual date/time into deye inverter | |||
init_deye_inverter () | |||
{ | |||
# daily reset phase | |||
daily_reset=init | |||
# ensure by loop, that all register values are written | |||
while [ ${daily_reset} != "done" ]; do | |||
# wait until we have enough power | |||
while [ "0$(deye_read_actual_Power)" -lt 20 ]; do | |||
sleep 300 | |||
done | |||
# read Daily power register | |||
dailyPower=$(deye_read_daily_Power) | |||
# read (old) year_month and day | |||
val22_old=$(deye_read_year_month) | |||
val23_day=$(deye_read_day) | |||
log_info "date/time update initialized" | |||
# set variables year, month, day, hour, minute, second | |||
set_date_time_vars | |||
# calculate register 22-24 with date and time vars | |||
val22=$(( ${year} * 256 + ${month} )) | |||
val23=$(( ${day} * 256 + ${hour} )) | |||
val24=$(( ${minute} * 256 + ${second} )) | |||
# reset power/date/time only once a day or exit loop | |||
[ "$dailyPower" -eq 0 ] && break | |||
[ "${val22_old}" = "${val22}" -a "${val23_day}" = "${day}" ] && break | |||
# reset daily_power by | |||
# setting register 22-24 with actual date + time | |||
${deye_inverter_sh} -w 22 $val22 || continue | |||
${deye_inverter_sh} -w 23 $val23 || continue | |||
${deye_inverter_sh} -w 24 $val24 || continue | |||
# test success | |||
# read Daily power register, should be resetted to 0 now | |||
#sleep 300 # it takes some time :-( | |||
dailyPower=$(deye_read_daily_Power) | |||
# exit loop if power is updated, else log that reset failed | |||
if [ "$dailyPower" -eq 0 ]; then | |||
daily_reset=done | |||
### skip loop here, reset doesn't work always :-( | |||
# elif [ $daily_reset = "init" ]; then | |||
# daily_reset=failed | |||
# sleep 300 | |||
else | |||
log_info "daily power reset failed" | |||
# sleep 300 | |||
break | |||
fi | |||
done | |||
log_info "date/time reset done" | |||
} | |||
# wait for (next) daylight | |||
wait_for_sunrise () | |||
{ | |||
sunrise=$($sunrise_cmd) | |||
sunset=$($sunset_cmd) | |||
current_time=$(date +%s) | |||
sunrise=$(date -d"$sunrise" +%s) | |||
sunset=$(date -d"$sunset" +%s) | |||
# calulate time in secs until (next) sunrise | |||
sleep_sunrise=$(( $sunrise - $current_time )) # this morning | |||
[ $sleep_sunrise -lt 0 ] && | |||
sleep_sunrise=$(( $sleep_sunrise + 86400 )) # next morning | |||
# wait for daylight only | |||
if [ $current_time -lt $sunrise -o $current_time -gt $sunset ]; then | |||
log_info sleep $sleep_sunrise | |||
sleep $sleep_sunrise | |||
fi | |||
} | |||
# wait only before sunset | |||
wait_before_sunset () | |||
{ | |||
sunset=$($sunset_cmd) | |||
current_time=$(date +%s) | |||
sunset=$(date -d"$sunset" +%s) | |||
# calulate time in secs until next sunset | |||
sleep_sunset=$(( $sunset - $current_time +60 )) | |||
# wait before sunset only | |||
if [ $sleep_sunset -gt 0 ]; then | |||
log_info sleep $sleep_sunset | |||
sleep $sleep_sunset | |||
fi | |||
} | } | ||
Zeile 238: | Zeile 424: | ||
# MAIN: Script starts here | # MAIN: Script starts here | ||
# ------------------------ | # ------------------------ | ||
# sunset_stop: wait until sunset before we stop running instances | |||
[ "$arg" = "sunset_stop" ] && | |||
wait_before_sunset | |||
# stop any runnning deye_inverter_mqtt processes | # stop any runnning deye_inverter_mqtt processes | ||
deye_inverter_mqtt_stop | deye_inverter_mqtt_stop | ||
# | # stop: don't start again | ||
[ "$arg" = "stop" ] && exit 0 | |||
# | # reset and start deye_mqtt_loop | ||
while | # check every 15 min for running deye_inverter_mqtt | ||
while :; do | |||
# | # wait for (next) daylight | ||
wait_for_sunrise | |||
# | # setting date/time also resets daily power register | ||
init_deye_inverter | |||
# | # start deye_inverter_mqtt | ||
get_deye_mqtt_pid | |||
if [ -z "$deye_mqtt_pid" ]; then | |||
deye_inverter_mqtt_start | |||
fi | |||
# | # wait 15 min | ||
sleep 900 | |||
done | done | ||
</pre> | </pre> | ||
=== Contrab und /etc/rc.local === | |||
Mein Crontab Eintrag: | Mein Crontab Eintrag: | ||
<pre> | <pre># stop DEYE monitoring after sunset (FHEM) | ||
10 16 * * * /root/sbin/deye_mqtt_loop.sh sunset_stop 2>/dev/null | |||
</pre> | |||
und /etc/rc.local: | und /etc/rc.local: | ||
<pre># start monitoring of PV-System: DEYE inverter | <pre># start monitoring of PV-System: DEYE inverter | ||
/root/ | $(sleep 10; /root/sbin/deye_mqtt_loop.sh 2>dev/null)& | ||
</pre> | </pre> | ||
== Dashboard == | |||
So sieht das aus, wenn es den ganzen Tag im April regnet und bedeckt ist: | |||
[[Datei:Solar Bedeckt Regen.jpg|midi]] | |||
Die Eigenverbrauchsquote habe ich mittels Grafana ermittelt: | |||
[[Datei:Solar Eigenverbrauch.jpg|midi|Eigenverbrauchsquote]] |
Aktuelle Version vom 13. Februar 2024, 17:12 Uhr
Balkonkraftwerk: DEYE Wechselrichter ohne Cloud auslesen und per MQTT monitoren
Anfang März habe ich endlich ein Balkonkraftwerk über Amazon bestellt und installiert. Es ist ja inzwischen keine Raketenwissenschaft mehr, und die Akzeptanz und Möglichkeiten zur einfachen Installation per Do-It-Yourself haben mich nun endlich Fakten schaffen lassen.
Ich habe zwei Standard PV-Module je 410W samt einem passendem DEYE Wechselrichter (WR) mit Anschlusskabel als Komplettpaket bestellt. Da ich am Balkon schon eine Klimaanlage stehen habe, ist der Stromanschluss auch kein Problem gewesen. Und endlich gilt im Sommer: Wenn es heiß ist und die Klimaanlage läuft, produzieren die PV-Module ordentlich Strom dafür, außerdem muss ich dann keinen Überschuss verschenken.
Zum Start hatten die DEYE Wechselrichter, die meines Wissens identisch zu z.B. Bosswerk, Blaupunkt und Turbo Energy sind, allerdings noch ein Security Problem mit der Firmware, darüber hatte u.a. Heise berichtet. Ein Firmware Upgrade stand kurz darauf zur Verfügung und wurde automatisch eingespielt.
Die DEYE Wechselrichter haben bereits WLAN eingebaut, d.h. man steckt die wirklich nur in die Steckdose und verbindet anschliessend die PV-Module und es geht los. Die Einbindung ins eigene WLAN erfolgt wie üblich mittels eingebautem WLAN Accesspoint und internem Webserver, worüber man die Konfiguration vornimmt und dann am besten den WLAN-AP deaktiviert. Die Internetverbindung des Wechselrichters habe ich dann über meine Fritzbox gesperrt.
Der WR ist über WLAN nur erreichbar, wenn die PV-Module eine Spannung (DC) erzeugen. Der Anschluss an das Stromnetz (AC) ist hierfür irrelevant. Das heisst wenn es dunkel ist, ist er nicht erreichbar bzw. aus.
Der WR bietet verschiedene Möglichkeiten zur Konfiguration und Abfrage der aktuellen Werte:
- einen internen Webserver (http://10.10.10.254)
- die Cloud Anbindung zu Solarman (Solarman Smart App)
- (angepasstes) Modbus Protokoll über Port 8899
- AT+ Befehle über Port 48899
Ich verwende das Modbus Protokoll zum auslesen des WR, das
- effizient/schnell ist (Performance)
- ohne Internetverbindung auskommt
Für DEYE kompatible WR stehen inzwischen einige Lösungsansätze zur Verfügung. Ich verwende die DEYE Inverter MQTT Bridge von Krzysztof Białek, da ich hier bereits einen MQTT Server innerhalb der Heimautomatisierungslösung FHEM zur Einbindung meiner Tasmota Devices (Shelly; Hichi) eingerichtet habe.
Diese Lösung ist als Docker Container konzipiert, was mir aber unnötiger Overhead ist. Da es in Python realisiert ist, starte ich das Skript ganz einfach direkt, ohne Docker.
Wechselrichter Eigenheiten
Der Wechselrichter läuft nur, wenn die PV-Module eine Gleichspannung einspeisen - d.h. wenn es draussen hell ist. Ein Auslesen der Werte des WR funktioniert also nicht, wenn es dunkel ist (Timeout). Ausserdem wird der Tageszähler erst durch setzen von Uhrzeit/Datum wieder auf 0 gesetzt, was über eine Internetverbindung erfolgt. Ist die gesperrt, läuft der Tageszähler weiter.
Also, jeden Abend geht der WR aus, und sobald er beim ersten Tageslicht wieder startet möchte ich Datum und Uhrzeit setzen und anschliessend die Werte kontinuierlich auslesen und über MQTT ausgeben. Bis dahin ist der WR aber halt nicht erreichbar.
Installation
Das Projekt von Github laden und in einem Verzeichnis speichern, Python ist normalerweise ja schon installiert.
git clone https://github.com/kbialek/deye-inverter-mqtt pip install paho-mqtt cd deye-inverter-mqtt
Unter Debian habe ich im Skript deye_cli.sh python durch python3 ersetzen müssen:
#!/bin/bash set -a; source config.env; set +a python3 deye_cli.py "$@"
config.env
Die Konfiguration meines DEYE Wechselrichters vom Typ Micro-Inverter: config.env
DEYE_LOGGER_IP_ADDRESS=<IP Wechselrichter im WLAN> DEYE_LOGGER_PORT=8899 DEYE_LOGGER_SERIAL_NUMBER=<Seriennummer des WR> MQTT_HOST=<IP von MQTT Server> MQTT_PORT=1883 MQTT_USERNAME= MQTT_PASSWORD= MQTT_TOPIC_PREFIX=deye LOG_LEVEL=ERROR DEYE_DATA_READ_INTERVAL=60 DEYE_METRIC_GROUPS=micro
deye_inverter.sh
Ausserdem habe ich ein kleines Wrapper Skript geschrieben, um Daten des WR einfacher lesen und schreiben zu können:
deye_inverter.sh [--check <pause>] --read <register> | --write <register> <value>
#!/bin/bash # Read / Write DEYE Inverters # via deye_inverter_mqtt python package # https://github.com/kbialek/deye-inverter-mqtt retries=2 # retry deye_inverter command multiple times sleep=60 # sleep time between retries # locate script in deye_inverter_mqtt directory cd $(dirname $0) if [ ! -f ./deye_cli.sh ]; then echo "Error: ./deye_cli.sh not found." echo " Please move $(basename $0) in deye-inverter-mqtt directory." exit 1 fi . ./config.env log_info () { if [ ${LOG_LEVEL} = "INFO" ]; then logger -t $(basename $0) $@ fi } log_error () { logger -t $(basename $0) $@ echo 1>&2 $@ } # read parameters: mode [rw], register, value and optional -c <check pause> pause=0 while [ $# -gt 0 ]; do case $1 in -c|--check) shift [ $# -ge 3 ] || exit 1 pause=$1 ;; -w|--write) mode=w shift [ $# -eq 2 ] || exit 1 reg=$1 val=$2 shift ;; -r|--read) mode=r shift [ $# -eq 1 ] || exit 1 reg=$1 ;; esac shift done # handle offline deye_inverter by # ${pause} > 0 -> endless loop # ${pause} = 0 -> error after ${retries} while true; do # try deye_cli.sh $retries times every $sleep secs check=${retries} while [ ${check} -gt 0 ]; do case ${mode} in r) result=$(./deye_cli.sh $mode $reg | grep 'int: ') if [ -n "$result" ]; then echo "$result" log_info -t $0 deye_cli.sh $mode $reg exit 0 fi ;; w) result=$(./deye_cli.sh $mode $reg $val) if [ "$result" = "Ok" ]; then log_info -t $0 deye_cli.sh $mode $reg $val exit 0 fi ;; esac # wait ${sleep} or ${pause} secs sleep $(( ${pause} ? ${sleep} : ${pause} )) # try ${retries} times (( check-- )) done # error, or retry endless until wakeup of deye_inverter if [ ${pause} -eq 0 ]; then log_error "Error: deye_cli.sh $mode $reg $val failed." exit 1 fi done
Obiges Skript deye_inverter.sh -r 22 liest z.B. das Register 22 aus dem Wechselrichter aus, deye_inverter.sh -w 22 5892 schreibt das Jahr 2023 und den Monat April (23 *256 + 4) in Register 22.
Das benötigt man, um dem WR jeden Morgen zum Start Datum und Uhrzeit beizubringen. Damit wird auch der Tageszähler täglich auf 0 gesetzt, ansonsten bliebe der alte Wert stehen und der Tageszähler würde einfach stetig addieren, wie der Gesamtertragszähler.
Also, jede Nacht geht der WR aus, und sobald er beim ersten Tageslicht wieder startet möchte ich Datum und Uhrzeit setzen und anschliessend die Werte kontinuierlich auslesen und über MQTT ausgeben.
Folgendes Skript starte ich jeden Morgen um 04:00 Uhr per Cron (und in rc.local fürs booten):
deye_mqtt_loop.sh
Dieses Script liegt bei mir unter /root/sbin und startet eine Endlosschleife. Dazu löscht es alte (vorher gestartete) Instanzen von sich selbst - dadurch kann es jederzeit (zB. in der crontab) erneut gestartet werden. Es wartet auf den Sonnenaufgang, der mittels der sunrise() von FHEM ermittelt wird. Es wartet auf den Sonnenuntergang per sunset() von FHEM, um danach die Abfrage zu pausieren bis zum nächsten Sonnenaufgang.
Ausserdem setzt es beim Start den Tageszähler des Wechselrichters bedarfsweise zurück, bevor die Werte des DEYE Wechselrichters per Endlosschleife abgefragt werden.
#!/bin/bash # reset Daily_Power of DEYE Inverters # via deye_inverter_mqtt python package # https://github.com/kbialek/deye-inverter-mqtt PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin # config: deye_inverter_dir=/root/deye-inverter-mqtt fhem_dir=/opt/fhem horizon="-2" # sunset CIVIL=-6, REAL=0 debug=info myPID=$$ myname=$(basename $0) arg="$1" deye_inverter_sh=$deye_inverter_dir/deye_inverter.sh deye_inverter_mqtt_cfg=$deye_inverter_dir/config.env deye_inverter_mqtt_cmd="python3 deye_docker_entrypoint.py" sunrise_cmd="perl $fhem_dir/fhem.pl localhost:7072 {sunrise_abs('HORIZON=$horizon')}" sunset_cmd="perl $fhem_dir/fhem.pl localhost:7072 {sunset_abs('HORIZON=$horizon')}" # installation: # locate FHEM, used for sunrise(), sunset() if [ ! -x ${fhem_dir/fhem.pl} ]; then echo "Error: ${fhem_dir} not found." echo " Is FHEM installed in $fhem_dir ?" exit 1 fi # locate script in deye_inverter_mqtt directory if [ ! -f ${deye_inverter_mqtt_cfg} ]; then echo "Error: ${deye_inverter_mqtt_cfg} not found." echo " Please update \$deye_inverter_sh path in $0" exit 1 fi # read my config set -a; source ${deye_inverter_mqtt_cfg}; set +a [ "$debug" = "full" ] && set -x # ----- functions () ------------------------------------ log_info () { [ "$debug" = "yes" -o "$debug" = "true" -o "$debug" = "info" ] && echo $myname: $@ [ ${LOG_LEVEL} = "INFO" ] && logger -t $myname $@ } log_error () { logger -t $myname $@ echo 1>&2 $@ } # get deye_inverter_mqtt PIDs get_deye_mqtt_pid () { deye_mqtt_pid=$(ps ax | grep -v grep | grep "${deye_inverter_mqtt_cmd}" | awk '{print $1}') } # kill deye_inverter_loop () kill_old_instance () { # kill old instances of me oldPID=$(ps -ef | grep -v grep | grep $myname | cut -b10-26,53- | grep -v $myPID | awk '{print $1}') [ ! -z "$oldPID" ] && kill $oldPID } # kill deye_inverter_instance () kill_deye_inverter_instance () { get_deye_mqtt_pid if [ ! -z "${deye_mqtt_pid}" ]; then kill ${deye_mqtt_pid} log_info "Info: KILL running deye_mqtt ${deye_mqtt_pid}" fi # kill any running deye requests deye_pid=$(ps ax | grep -v grep | grep "$deye_inverter_sh " | awk '{print $1}') [ ! -z "$deye_pid" ] && kill $deye_pid } # stop all runnning instances deye_inverter_mqtt_stop () { kill_old_instance kill_deye_inverter_instance } # start deye_inverter_mqtt deye_inverter_mqtt_start () { log_info "Deye MQTT start" cd ${deye_inverter_dir} ${deye_inverter_mqtt_cmd} } # store actual date + time into vars set_date_time_vars () { year=$(date +%y) month=$(date +%m | sed 's/^0//g') day=$(date +%d | sed 's/^0//g') hour=$(date +%H | sed 's/^0//g') minute=$(date +%m | sed 's/^0//g') second=$(date +%S | sed 's/^0//g') } # read year_month register 0x16 = 22 deye_read_year_month () { ${deye_inverter_sh} -r 22 | awk '/^int: .*/ {split($2,i,","); print i[1]}' } # read day register 0x17 = 23 deye_read_day () { ${deye_inverter_sh} -r 23 | awk '/^int: .*/ {split($6,i,","); print i[1]}' } # read Daily power register 0x3c = 60 deye_read_daily_Power () { ${deye_inverter_sh} -r 60 | awk '/^int: .*/ {split($2,i,","); print i[1]}' } # read actual power register 0x56 = 86 deye_read_actual_Power () { # doubleRegisterSensor("AC Active Power", 0x56, 0.1, mqtt_topic_suffix='ac/active_power', groups=['string', 'micro']) # (int.from_bytes(high_word, 'big') * 65536 + int.from_bytes(low_word, 'big')) * self.factor # val86=${deye_inverter_sh} -r 86 | sed 's/int: //g' | cut -d, -f1 # val87=${deye_inverter_sh} -r 87 | sed 's/int: //g' | cut -d, -f1 # echo $(( (${val87} * 65536 + ${val86}) * 0.1 )) # simple value is enough for our purpose ${deye_inverter_sh} -r 86 | awk '/^int: .*/ {split($2,i,","); print i[1]}' } # write actual date/time into deye inverter init_deye_inverter () { # daily reset phase daily_reset=init # ensure by loop, that all register values are written while [ ${daily_reset} != "done" ]; do # wait until we have enough power while [ "0$(deye_read_actual_Power)" -lt 20 ]; do sleep 300 done # read Daily power register dailyPower=$(deye_read_daily_Power) # read (old) year_month and day val22_old=$(deye_read_year_month) val23_day=$(deye_read_day) log_info "date/time update initialized" # set variables year, month, day, hour, minute, second set_date_time_vars # calculate register 22-24 with date and time vars val22=$(( ${year} * 256 + ${month} )) val23=$(( ${day} * 256 + ${hour} )) val24=$(( ${minute} * 256 + ${second} )) # reset power/date/time only once a day or exit loop [ "$dailyPower" -eq 0 ] && break [ "${val22_old}" = "${val22}" -a "${val23_day}" = "${day}" ] && break # reset daily_power by # setting register 22-24 with actual date + time ${deye_inverter_sh} -w 22 $val22 || continue ${deye_inverter_sh} -w 23 $val23 || continue ${deye_inverter_sh} -w 24 $val24 || continue # test success # read Daily power register, should be resetted to 0 now #sleep 300 # it takes some time :-( dailyPower=$(deye_read_daily_Power) # exit loop if power is updated, else log that reset failed if [ "$dailyPower" -eq 0 ]; then daily_reset=done ### skip loop here, reset doesn't work always :-( # elif [ $daily_reset = "init" ]; then # daily_reset=failed # sleep 300 else log_info "daily power reset failed" # sleep 300 break fi done log_info "date/time reset done" } # wait for (next) daylight wait_for_sunrise () { sunrise=$($sunrise_cmd) sunset=$($sunset_cmd) current_time=$(date +%s) sunrise=$(date -d"$sunrise" +%s) sunset=$(date -d"$sunset" +%s) # calulate time in secs until (next) sunrise sleep_sunrise=$(( $sunrise - $current_time )) # this morning [ $sleep_sunrise -lt 0 ] && sleep_sunrise=$(( $sleep_sunrise + 86400 )) # next morning # wait for daylight only if [ $current_time -lt $sunrise -o $current_time -gt $sunset ]; then log_info sleep $sleep_sunrise sleep $sleep_sunrise fi } # wait only before sunset wait_before_sunset () { sunset=$($sunset_cmd) current_time=$(date +%s) sunset=$(date -d"$sunset" +%s) # calulate time in secs until next sunset sleep_sunset=$(( $sunset - $current_time +60 )) # wait before sunset only if [ $sleep_sunset -gt 0 ]; then log_info sleep $sleep_sunset sleep $sleep_sunset fi } # ------------------------ # MAIN: Script starts here # ------------------------ # sunset_stop: wait until sunset before we stop running instances [ "$arg" = "sunset_stop" ] && wait_before_sunset # stop any runnning deye_inverter_mqtt processes deye_inverter_mqtt_stop # stop: don't start again [ "$arg" = "stop" ] && exit 0 # reset and start deye_mqtt_loop # check every 15 min for running deye_inverter_mqtt while :; do # wait for (next) daylight wait_for_sunrise # setting date/time also resets daily power register init_deye_inverter # start deye_inverter_mqtt get_deye_mqtt_pid if [ -z "$deye_mqtt_pid" ]; then deye_inverter_mqtt_start fi # wait 15 min sleep 900 done
Contrab und /etc/rc.local
Mein Crontab Eintrag:
# stop DEYE monitoring after sunset (FHEM) 10 16 * * * /root/sbin/deye_mqtt_loop.sh sunset_stop 2>/dev/null
und /etc/rc.local:
# start monitoring of PV-System: DEYE inverter $(sleep 10; /root/sbin/deye_mqtt_loop.sh 2>dev/null)&
Dashboard
So sieht das aus, wenn es den ganzen Tag im April regnet und bedeckt ist:
Die Eigenverbrauchsquote habe ich mittels Grafana ermittelt: