DEYE Wechselrichter: Unterschied zwischen den Versionen
Zeile 166: | Zeile 166: | ||
Folgendes Skript starte ich jeden Morgen um 04:00 Uhr per Cron (und in rc.local fürs booten): | Folgendes Skript starte ich jeden Morgen um 04:00 Uhr per Cron (und in rc.local fürs booten): | ||
''' | '''deye_mqtt_loop.sh''' | ||
<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: | ||
Zeile 184: | Zeile 186: | ||
exit 1 | exit 1 | ||
fi | fi | ||
# read my config | |||
source ${deye_inverter_mqtt_cfg} | |||
# ----- functions () ------------------------------------ | |||
log_info () | |||
{ | |||
if [ ${LOG_LEVEL} = "INFO" ]; then | |||
logger -t $(basename $0) $@ | |||
fi | |||
} | |||
log_error () | |||
{ | |||
logger -t $(basename $0) $@ | |||
echo 2>&1 $@ | |||
} | |||
# start deye_inverter_mqtt | # start deye_inverter_mqtt | ||
Zeile 192: | Zeile 211: | ||
# detach deye_inverter_mqtt with screen | # detach deye_inverter_mqtt with screen | ||
screen -d -m ${deye_inverter_mqtt_cmd} | # screen -d -m ${deye_inverter_mqtt_cmd} | ||
log_info "MQTT starts now" | |||
${deye_inverter_mqtt_cmd} | |||
} | } | ||
Zeile 206: | Zeile 228: | ||
# get PIDs and stop them | # get PIDs and stop them | ||
deye_mqtt_pid=`deye_inverter_mqtt_pid` | deye_mqtt_pid=`deye_inverter_mqtt_pid` | ||
[ -z ${deye_mqtt_pid} ] | if [ -z ${deye_mqtt_pid} ]; then | ||
log_info "Info: KILL running deye_inverter_mqtt ($deye_mqtt_pid)" | |||
else | |||
kill ${deye_mqtt_pid} | |||
fi | |||
# check if any still runs | # check if any still runs | ||
Zeile 213: | Zeile 239: | ||
# be patient for kill cmd, try again | # be patient for kill cmd, try again | ||
log_info "Info: found running deye_inverter_mqtt" | |||
sleep 1 | sleep 1 | ||
[ -z ${deye_mqtt_pid} ] || kill ${deye_mqtt_pid} 2>/dev/null | [ -z ${deye_mqtt_pid} ] || kill ${deye_mqtt_pid} 2>/dev/null | ||
Zeile 220: | Zeile 246: | ||
deye_mqtt_pid=`deye_inverter_mqtt_pid` | deye_mqtt_pid=`deye_inverter_mqtt_pid` | ||
if [ ! -z ${deye_mqtt_pid} ]; then | if [ ! -z ${deye_mqtt_pid} ]; then | ||
log_error "Error: couldn't stop running deye_inverter_mqtt" | |||
exit 1 | exit 1 | ||
fi | fi | ||
else | |||
log_info "Info: NO running deye_inverter_mqtt" | |||
fi | fi | ||
} | } | ||
Zeile 237: | Zeile 263: | ||
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 Daily power register | |||
read_daily_Power () | |||
{ | |||
./deye_inverter.sh -c 10 -r 60 | sed 's/int: //g' | cut -d, -f1 | |||
} | } | ||
Zeile 246: | Zeile 278: | ||
deye_inverter_mqtt_stop | deye_inverter_mqtt_stop | ||
# wait | # wait until deye inverter starts by reading (old) year/month (register 22) | ||
val22_old=$(./deye_inverter.sh -c 60 -r 22 | sed 's/int: //g' | cut -d, -f1) | val22_old=$(./deye_inverter.sh -c 60 -r 22 | sed 's/int: //g' | cut -d, -f1) | ||
log_info "date/time update initialized" | |||
# read Daily power register (yesterday?) | |||
dailyPower=`read_daily_Power` | |||
# daily reset phase | |||
daily_reset=init | |||
# ensure with loop, that all values are written | # ensure with loop, that all register values are written | ||
while [ | while [ ${daily_reset} != "done" ]; do | ||
# read (old) day | # read (old) day | ||
val23_day=$(./deye_inverter.sh -c 10 -r 23 | sed 's/.*h: //g' | cut -d, -f1) | val23_day=$(./deye_inverter.sh -c 10 -r 23 | sed 's/.*h: //g' | cut -d, -f1) | ||
Zeile 264: | Zeile 302: | ||
val24=$(( ${minute} * 256 + ${second} )) | val24=$(( ${minute} * 256 + ${second} )) | ||
# reset date only once a day | # reset power/date/time only once a day or exit loop | ||
[ ${val22_old} = ${val22} -a ${val23_day} = ${day} ] && break | [ "$dailyPower" -eq 0 -a ${val22_old} = ${val22} -a ${val23_day} = ${day} ] && break | ||
# reset daily_power by | # reset daily_power by | ||
Zeile 273: | Zeile 311: | ||
./deye_inverter.sh -w 24 $val24 || continue | ./deye_inverter.sh -w 24 $val24 || continue | ||
# exit loop if | # test success | ||
daily_reset=done | # read Daily power register, should be resetted to 0 now | ||
dailyPower=`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 60 | |||
else | |||
log_info "daily power reset failed" | |||
break | |||
fi | |||
done | done | ||
log_info "date/time updated" | |||
# stop old runnning deye_inverter_mqtt processes | # stop old runnning deye_inverter_mqtt processes | ||
# shouldn't find any running instance | # (we shouldn't find any running instance here now) | ||
deye_inverter_mqtt_stop | deye_inverter_mqtt_stop | ||
# start deye_inverter_mqtt | # (re-)start deye_inverter_mqtt until 22:00 | ||
deye_inverter_mqtt_start | while true; do | ||
# restart only before 22:00 | |||
get_date_time | |||
[ $hour -le 22 ] || break | |||
# restart | |||
[ -z $(deye_inverter_mqtt_pid) ] && deye_inverter_mqtt_start | |||
sleep 600 | |||
done | |||
</pre> | </pre> | ||
Mein Crontab Eintrag: | Mein Crontab Eintrag: | ||
<pre>0 4 * * * /root/deye-inverter-mqtt/ | <pre>0 4 * * * /root/deye-inverter-mqtt/deye_mqtt_loop.sh</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/deye-inverter-mqtt/ | $(sleep 20; cd /root/deye-inverter-mqtt; screen -d -m ./deye_mqtt_loop.sh)& | ||
</pre> | </pre> | ||
Version vom 5. Januar 2024, 13:46 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. Ausserdem installiere ich Screen, um das Skript von der Console zu lösen.
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 "$@"
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
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=5 # 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 2>&1 $@ } # 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
#!/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_mqtt_cfg='config.env' deye_inverter_mqtt_cmd='python3 deye_docker_entrypoint.py' # installation: # locate script in deye_inverter_mqtt directory cd $(dirname $0) if [ ! -f deye_cli.sh -o ! -f ${deye_inverter_mqtt_cfg} ]; then echo "Error: ./deye_cli.sh not found." echo " Please move $(basename $0) in deye-inverter-mqtt directory." exit 1 fi # read my config source ${deye_inverter_mqtt_cfg} # ----- functions () ------------------------------------ log_info () { if [ ${LOG_LEVEL} = "INFO" ]; then logger -t $(basename $0) $@ fi } log_error () { logger -t $(basename $0) $@ echo 2>&1 $@ } # start deye_inverter_mqtt deye_inverter_mqtt_start () { # read config file set -a; source ${deye_inverter_mqtt_cfg}; set +a # detach deye_inverter_mqtt with screen # screen -d -m ${deye_inverter_mqtt_cmd} log_info "MQTT starts now" ${deye_inverter_mqtt_cmd} } # get deye_inverter_mqtt PIDs deye_inverter_mqtt_pid () { ps ax | grep "${deye_inverter_mqtt_cmd}" | grep -v grep | grep -i -v SCREEN | awk '{print $1}' } # stop all runnning deye_inverter_mqtt instances deye_inverter_mqtt_stop () { # get PIDs and stop them deye_mqtt_pid=`deye_inverter_mqtt_pid` if [ -z ${deye_mqtt_pid} ]; then log_info "Info: KILL running deye_inverter_mqtt ($deye_mqtt_pid)" else kill ${deye_mqtt_pid} fi # check if any still runs deye_mqtt_pid=`deye_inverter_mqtt_pid` if [ ! -z ${deye_mqtt_pid} ]; then # be patient for kill cmd, try again log_info "Info: found running deye_inverter_mqtt" sleep 1 [ -z ${deye_mqtt_pid} ] || kill ${deye_mqtt_pid} 2>/dev/null # check again deye_mqtt_pid=`deye_inverter_mqtt_pid` if [ ! -z ${deye_mqtt_pid} ]; then log_error "Error: couldn't stop running deye_inverter_mqtt" exit 1 fi else log_info "Info: NO running deye_inverter_mqtt" fi } # store actual date + time into vars get_date_time () { 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 Daily power register read_daily_Power () { ./deye_inverter.sh -c 10 -r 60 | sed 's/int: //g' | cut -d, -f1 } # ------------------------ # MAIN: Script starts here # ------------------------ # stop any runnning deye_inverter_mqtt processes deye_inverter_mqtt_stop # wait until deye inverter starts by reading (old) year/month (register 22) val22_old=$(./deye_inverter.sh -c 60 -r 22 | sed 's/int: //g' | cut -d, -f1) log_info "date/time update initialized" # read Daily power register (yesterday?) dailyPower=`read_daily_Power` # daily reset phase daily_reset=init # ensure with loop, that all register values are written while [ ${daily_reset} != "done" ]; do # read (old) day val23_day=$(./deye_inverter.sh -c 10 -r 23 | sed 's/.*h: //g' | cut -d, -f1) # set actual year, month, day, hour, minute, second get_date_time # calculate register 22-24 by date and time 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 -a ${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 dailyPower=`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 60 else log_info "daily power reset failed" break fi done log_info "date/time updated" # stop old runnning deye_inverter_mqtt processes # (we shouldn't find any running instance here now) deye_inverter_mqtt_stop # (re-)start deye_inverter_mqtt until 22:00 while true; do # restart only before 22:00 get_date_time [ $hour -le 22 ] || break # restart [ -z $(deye_inverter_mqtt_pid) ] && deye_inverter_mqtt_start sleep 600 done
Mein Crontab Eintrag:
0 4 * * * /root/deye-inverter-mqtt/deye_mqtt_loop.sh
und /etc/rc.local:
# start monitoring of PV-System: DEYE inverter $(sleep 20; cd /root/deye-inverter-mqtt; screen -d -m ./deye_mqtt_loop.sh)&
So sieht das aus, wenn es den ganzen Tag im April regnet und bedeckt ist:
Die Eigenverbrauchsquote habe ich mittels Grafana ermittelt: