http://neobiker.de/wiki/api.php?action=feedcontributions&user=Neobiker&feedformat=atom
Neobikers Wiki - Benutzerbeiträge [de]
2024-03-28T11:51:46Z
Benutzerbeiträge
MediaWiki 1.40.2
http://neobiker.de/wiki/index.php?title=DEYE_Wechselrichter&diff=2152
DEYE Wechselrichter
2024-02-13T16:12:41Z
<p>Neobiker: /* deye_mqtt_loop.sh */</p>
<hr />
<div>= Balkonkraftwerk: DEYE Wechselrichter ohne Cloud auslesen und per MQTT monitoren =<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
Der WR bietet verschiedene Möglichkeiten zur Konfiguration und Abfrage der aktuellen Werte:<br />
* einen internen Webserver (http://10.10.10.254)<br />
* die Cloud Anbindung zu Solarman (Solarman Smart App)<br />
* (angepasstes) Modbus Protokoll über Port 8899<br />
* AT+ Befehle über Port 48899<br />
<br />
Ich verwende das Modbus Protokoll zum auslesen des WR, das <br />
# effizient/schnell ist (Performance)<br />
# ohne Internetverbindung auskommt<br />
<br />
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.<br />
<br />
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.<br />
<br />
== Wechselrichter Eigenheiten ==<br />
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. <br />
<br />
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.<br />
<br />
== Installation ==<br />
Das Projekt von Github laden und in einem Verzeichnis speichern, '''Python''' ist normalerweise ja schon installiert. <br />
<br />
<pre>git clone https://github.com/kbialek/deye-inverter-mqtt<br />
pip install paho-mqtt<br />
cd deye-inverter-mqtt</pre><br />
<br />
Unter Debian habe ich im Skript '''deye_cli.sh''' python durch python3 ersetzen müssen:<br />
<pre>#!/bin/bash<br />
set -a; source config.env; set +a<br />
python3 deye_cli.py "$@"<br />
</pre><br />
<br />
=== config.env ===<br />
Die Konfiguration meines DEYE Wechselrichters vom Typ Micro-Inverter:<br />
'''config.env'''<br />
<pre>DEYE_LOGGER_IP_ADDRESS=<IP Wechselrichter im WLAN><br />
DEYE_LOGGER_PORT=8899<br />
DEYE_LOGGER_SERIAL_NUMBER=<Seriennummer des WR><br />
<br />
MQTT_HOST=<IP von MQTT Server><br />
MQTT_PORT=1883<br />
MQTT_USERNAME=<br />
MQTT_PASSWORD=<br />
MQTT_TOPIC_PREFIX=deye<br />
<br />
LOG_LEVEL=ERROR<br />
DEYE_DATA_READ_INTERVAL=60<br />
DEYE_METRIC_GROUPS=micro<br />
</pre><br />
<br />
=== deye_inverter.sh === <br />
Ausserdem habe ich ein kleines ''Wrapper Skript'' geschrieben, um '''Daten des WR''' einfacher '''lesen und schreiben''' zu können:<br><br />
<br />
'''deye_inverter.sh''' [--check <pause>] --read <register> | --write <register> <value><br />
<br />
<pre>#!/bin/bash<br />
# Read / Write DEYE Inverters<br />
# via deye_inverter_mqtt python package<br />
# https://github.com/kbialek/deye-inverter-mqtt<br />
<br />
retries=2 # retry deye_inverter command multiple times<br />
sleep=60 # sleep time between retries<br />
<br />
# locate script in deye_inverter_mqtt directory<br />
cd $(dirname $0)<br />
if [ ! -f ./deye_cli.sh ]; then<br />
echo "Error: ./deye_cli.sh not found."<br />
echo " Please move $(basename $0) in deye-inverter-mqtt directory."<br />
exit 1<br />
fi<br />
. ./config.env<br />
<br />
log_info ()<br />
{<br />
if [ ${LOG_LEVEL} = "INFO" ]; then<br />
logger -t $(basename $0) $@<br />
fi<br />
}<br />
<br />
log_error ()<br />
{<br />
logger -t $(basename $0) $@<br />
echo 1>&2 $@<br />
}<br />
<br />
# read parameters: mode [rw], register, value and optional -c <check pause><br />
pause=0<br />
while [ $# -gt 0 ]; do<br />
case $1 in<br />
-c|--check) shift<br />
[ $# -ge 3 ] || exit 1<br />
pause=$1<br />
;;<br />
-w|--write) mode=w<br />
shift<br />
[ $# -eq 2 ] || exit 1<br />
reg=$1<br />
val=$2<br />
shift<br />
;;<br />
-r|--read) mode=r<br />
shift<br />
[ $# -eq 1 ] || exit 1<br />
reg=$1<br />
;;<br />
esac<br />
shift<br />
done<br />
<br />
# handle offline deye_inverter by<br />
# ${pause} > 0 -> endless loop<br />
# ${pause} = 0 -> error after ${retries}<br />
while true; do<br />
<br />
# try deye_cli.sh $retries times every $sleep secs<br />
check=${retries}<br />
while [ ${check} -gt 0 ]; do<br />
<br />
case ${mode} in<br />
r) result=$(./deye_cli.sh $mode $reg | grep 'int: ')<br />
if [ -n "$result" ]; then<br />
echo "$result"<br />
log_info -t $0 deye_cli.sh $mode $reg<br />
exit 0<br />
fi<br />
;;<br />
w) result=$(./deye_cli.sh $mode $reg $val)<br />
if [ "$result" = "Ok" ]; then<br />
log_info -t $0 deye_cli.sh $mode $reg $val<br />
exit 0<br />
fi<br />
;;<br />
esac<br />
<br />
# wait ${sleep} or ${pause} secs<br />
sleep $(( ${pause} ? ${sleep} : ${pause} ))<br />
<br />
# try ${retries} times<br />
(( check-- ))<br />
done<br />
<br />
# error, or retry endless until wakeup of deye_inverter<br />
if [ ${pause} -eq 0 ]; then<br />
log_error "Error: deye_cli.sh $mode $reg $val failed."<br />
exit 1<br />
fi<br />
<br />
done<br />
</pre><br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
Folgendes Skript starte ich jeden Morgen um 04:00 Uhr per Cron (und in rc.local fürs booten):<br />
<br />
=== deye_mqtt_loop.sh ===<br />
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.<br />
<br />
Ausserdem setzt es beim Start den Tageszähler des Wechselrichters bedarfsweise zurück, bevor die Werte des DEYE Wechselrichters per Endlosschleife abgefragt werden.<br />
<br />
<pre>#!/bin/bash<br />
# reset Daily_Power of DEYE Inverters<br />
# via deye_inverter_mqtt python package<br />
# https://github.com/kbialek/deye-inverter-mqtt<br />
<br />
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin<br />
<br />
# config:<br />
deye_inverter_dir=/root/deye-inverter-mqtt<br />
fhem_dir=/opt/fhem<br />
horizon="-2" # sunset CIVIL=-6, REAL=0<br />
<br />
debug=info<br />
<br />
myPID=$$<br />
myname=$(basename $0)<br />
arg="$1"<br />
<br />
deye_inverter_sh=$deye_inverter_dir/deye_inverter.sh<br />
deye_inverter_mqtt_cfg=$deye_inverter_dir/config.env<br />
deye_inverter_mqtt_cmd="python3 deye_docker_entrypoint.py"<br />
<br />
sunrise_cmd="perl $fhem_dir/fhem.pl localhost:7072 {sunrise_abs('HORIZON=$horizon')}"<br />
sunset_cmd="perl $fhem_dir/fhem.pl localhost:7072 {sunset_abs('HORIZON=$horizon')}"<br />
<br />
# installation:<br />
# locate FHEM, used for sunrise(), sunset()<br />
if [ ! -x ${fhem_dir/fhem.pl} ]; then<br />
echo "Error: ${fhem_dir} not found."<br />
echo " Is FHEM installed in $fhem_dir ?"<br />
exit 1<br />
fi<br />
<br />
# locate script in deye_inverter_mqtt directory<br />
if [ ! -f ${deye_inverter_mqtt_cfg} ]; then<br />
echo "Error: ${deye_inverter_mqtt_cfg} not found."<br />
echo " Please update \$deye_inverter_sh path in $0"<br />
exit 1<br />
fi<br />
<br />
# read my config<br />
set -a; source ${deye_inverter_mqtt_cfg}; set +a<br />
<br />
[ "$debug" = "full" ] && set -x<br />
<br />
# ----- functions () ------------------------------------<br />
log_info ()<br />
{<br />
[ "$debug" = "yes" -o "$debug" = "true" -o "$debug" = "info" ] &&<br />
echo $myname: $@<br />
[ ${LOG_LEVEL} = "INFO" ] && logger -t $myname $@<br />
}<br />
<br />
log_error ()<br />
{<br />
logger -t $myname $@<br />
echo 1>&2 $@<br />
}<br />
<br />
# get deye_inverter_mqtt PIDs<br />
get_deye_mqtt_pid ()<br />
{<br />
deye_mqtt_pid=$(ps ax | grep -v grep | grep "${deye_inverter_mqtt_cmd}" | awk '{print $1}')<br />
}<br />
<br />
# kill deye_inverter_loop ()<br />
kill_old_instance ()<br />
{<br />
# kill old instances of me<br />
oldPID=$(ps -ef | grep -v grep | grep $myname | cut -b10-26,53- | grep -v $myPID | awk '{print $1}')<br />
[ ! -z "$oldPID" ] && kill $oldPID<br />
}<br />
<br />
# kill deye_inverter_instance ()<br />
kill_deye_inverter_instance ()<br />
{<br />
get_deye_mqtt_pid<br />
if [ ! -z "${deye_mqtt_pid}" ]; then<br />
kill ${deye_mqtt_pid}<br />
log_info "Info: KILL running deye_mqtt ${deye_mqtt_pid}"<br />
fi<br />
<br />
# kill any running deye requests<br />
deye_pid=$(ps ax | grep -v grep | grep "$deye_inverter_sh " | awk '{print $1}')<br />
[ ! -z "$deye_pid" ] && kill $deye_pid<br />
}<br />
<br />
# stop all runnning instances<br />
deye_inverter_mqtt_stop ()<br />
{<br />
kill_old_instance<br />
kill_deye_inverter_instance<br />
}<br />
<br />
# start deye_inverter_mqtt<br />
deye_inverter_mqtt_start ()<br />
{<br />
log_info "Deye MQTT start"<br />
cd ${deye_inverter_dir}<br />
${deye_inverter_mqtt_cmd}<br />
}<br />
<br />
# store actual date + time into vars<br />
set_date_time_vars ()<br />
{<br />
year=$(date +%y)<br />
month=$(date +%m | sed 's/^0//g')<br />
day=$(date +%d | sed 's/^0//g')<br />
hour=$(date +%H | sed 's/^0//g')<br />
minute=$(date +%m | sed 's/^0//g')<br />
second=$(date +%S | sed 's/^0//g')<br />
}<br />
<br />
# read year_month register 0x16 = 22<br />
deye_read_year_month ()<br />
{<br />
${deye_inverter_sh} -r 22 | awk '/^int: .*/ {split($2,i,","); print i[1]}'<br />
}<br />
<br />
# read day register 0x17 = 23<br />
deye_read_day ()<br />
{<br />
${deye_inverter_sh} -r 23 | awk '/^int: .*/ {split($6,i,","); print i[1]}'<br />
}<br />
<br />
# read Daily power register 0x3c = 60<br />
deye_read_daily_Power ()<br />
{<br />
${deye_inverter_sh} -r 60 | awk '/^int: .*/ {split($2,i,","); print i[1]}'<br />
}<br />
<br />
# read actual power register 0x56 = 86<br />
deye_read_actual_Power ()<br />
{<br />
# doubleRegisterSensor("AC Active Power", 0x56, 0.1, mqtt_topic_suffix='ac/active_power', groups=['string', 'micro'])<br />
# (int.from_bytes(high_word, 'big') * 65536 + int.from_bytes(low_word, 'big')) * self.factor<br />
# val86=${deye_inverter_sh} -r 86 | sed 's/int: //g' | cut -d, -f1<br />
# val87=${deye_inverter_sh} -r 87 | sed 's/int: //g' | cut -d, -f1<br />
# echo $(( (${val87} * 65536 + ${val86}) * 0.1 ))<br />
<br />
# simple value is enough for our purpose<br />
${deye_inverter_sh} -r 86 | awk '/^int: .*/ {split($2,i,","); print i[1]}'<br />
}<br />
<br />
# write actual date/time into deye inverter<br />
init_deye_inverter ()<br />
{<br />
# daily reset phase<br />
daily_reset=init<br />
<br />
# ensure by loop, that all register values are written<br />
while [ ${daily_reset} != "done" ]; do<br />
<br />
# wait until we have enough power<br />
while [ "0$(deye_read_actual_Power)" -lt 20 ]; do<br />
sleep 300<br />
done<br />
<br />
# read Daily power register<br />
dailyPower=$(deye_read_daily_Power)<br />
<br />
# read (old) year_month and day<br />
val22_old=$(deye_read_year_month)<br />
val23_day=$(deye_read_day)<br />
log_info "date/time update initialized"<br />
<br />
# set variables year, month, day, hour, minute, second<br />
set_date_time_vars<br />
<br />
# calculate register 22-24 with date and time vars<br />
val22=$(( ${year} * 256 + ${month} ))<br />
val23=$(( ${day} * 256 + ${hour} ))<br />
val24=$(( ${minute} * 256 + ${second} ))<br />
<br />
# reset power/date/time only once a day or exit loop<br />
[ "$dailyPower" -eq 0 ] && break<br />
[ "${val22_old}" = "${val22}" -a "${val23_day}" = "${day}" ] && break<br />
<br />
# reset daily_power by<br />
# setting register 22-24 with actual date + time<br />
${deye_inverter_sh} -w 22 $val22 || continue<br />
${deye_inverter_sh} -w 23 $val23 || continue<br />
${deye_inverter_sh} -w 24 $val24 || continue<br />
<br />
# test success<br />
# read Daily power register, should be resetted to 0 now<br />
#sleep 300 # it takes some time :-(<br />
dailyPower=$(deye_read_daily_Power)<br />
<br />
# exit loop if power is updated, else log that reset failed<br />
if [ "$dailyPower" -eq 0 ]; then<br />
daily_reset=done<br />
<br />
### skip loop here, reset doesn't work always :-(<br />
# elif [ $daily_reset = "init" ]; then<br />
# daily_reset=failed<br />
# sleep 300<br />
<br />
else<br />
log_info "daily power reset failed"<br />
# sleep 300<br />
break<br />
fi<br />
done<br />
log_info "date/time reset done"<br />
}<br />
<br />
# wait for (next) daylight<br />
wait_for_sunrise ()<br />
{<br />
sunrise=$($sunrise_cmd)<br />
sunset=$($sunset_cmd)<br />
<br />
current_time=$(date +%s)<br />
sunrise=$(date -d"$sunrise" +%s)<br />
sunset=$(date -d"$sunset" +%s)<br />
<br />
# calulate time in secs until (next) sunrise<br />
sleep_sunrise=$(( $sunrise - $current_time )) # this morning<br />
[ $sleep_sunrise -lt 0 ] &&<br />
sleep_sunrise=$(( $sleep_sunrise + 86400 )) # next morning<br />
<br />
# wait for daylight only<br />
if [ $current_time -lt $sunrise -o $current_time -gt $sunset ]; then<br />
log_info sleep $sleep_sunrise<br />
sleep $sleep_sunrise<br />
fi<br />
}<br />
<br />
# wait only before sunset<br />
wait_before_sunset ()<br />
{<br />
sunset=$($sunset_cmd)<br />
<br />
current_time=$(date +%s)<br />
sunset=$(date -d"$sunset" +%s)<br />
<br />
# calulate time in secs until next sunset<br />
sleep_sunset=$(( $sunset - $current_time +60 ))<br />
<br />
# wait before sunset only<br />
if [ $sleep_sunset -gt 0 ]; then<br />
log_info sleep $sleep_sunset<br />
sleep $sleep_sunset<br />
fi<br />
}<br />
<br />
# ------------------------<br />
# MAIN: Script starts here<br />
# ------------------------<br />
<br />
# sunset_stop: wait until sunset before we stop running instances<br />
[ "$arg" = "sunset_stop" ] &&<br />
wait_before_sunset<br />
<br />
# stop any runnning deye_inverter_mqtt processes<br />
deye_inverter_mqtt_stop<br />
<br />
# stop: don't start again<br />
[ "$arg" = "stop" ] && exit 0<br />
<br />
# reset and start deye_mqtt_loop<br />
# check every 15 min for running deye_inverter_mqtt<br />
while :; do<br />
<br />
# wait for (next) daylight<br />
wait_for_sunrise<br />
<br />
# setting date/time also resets daily power register<br />
init_deye_inverter<br />
<br />
# start deye_inverter_mqtt<br />
get_deye_mqtt_pid<br />
if [ -z "$deye_mqtt_pid" ]; then<br />
deye_inverter_mqtt_start<br />
fi<br />
<br />
# wait 15 min<br />
sleep 900<br />
done<br />
</pre><br />
<br />
=== Contrab und /etc/rc.local ===<br />
Mein Crontab Eintrag:<br />
<pre># stop DEYE monitoring after sunset (FHEM)<br />
10 16 * * * /root/sbin/deye_mqtt_loop.sh sunset_stop 2>/dev/null<br />
</pre><br />
und /etc/rc.local:<br />
<pre># start monitoring of PV-System: DEYE inverter<br />
$(sleep 10; /root/sbin/deye_mqtt_loop.sh 2>dev/null)&<br />
</pre><br />
<br />
== Dashboard ==<br />
So sieht das aus, wenn es den ganzen Tag im April regnet und bedeckt ist:<br />
[[Datei:Solar Bedeckt Regen.jpg|midi]]<br />
<br />
Die Eigenverbrauchsquote habe ich mittels Grafana ermittelt:<br />
[[Datei:Solar Eigenverbrauch.jpg|midi|Eigenverbrauchsquote]]</div>
Neobiker
http://neobiker.de/wiki/index.php?title=DEYE_Wechselrichter&diff=2151
DEYE Wechselrichter
2024-02-07T17:20:10Z
<p>Neobiker: /* Contrab und /etc/rc.local */</p>
<hr />
<div>= Balkonkraftwerk: DEYE Wechselrichter ohne Cloud auslesen und per MQTT monitoren =<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
Der WR bietet verschiedene Möglichkeiten zur Konfiguration und Abfrage der aktuellen Werte:<br />
* einen internen Webserver (http://10.10.10.254)<br />
* die Cloud Anbindung zu Solarman (Solarman Smart App)<br />
* (angepasstes) Modbus Protokoll über Port 8899<br />
* AT+ Befehle über Port 48899<br />
<br />
Ich verwende das Modbus Protokoll zum auslesen des WR, das <br />
# effizient/schnell ist (Performance)<br />
# ohne Internetverbindung auskommt<br />
<br />
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.<br />
<br />
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.<br />
<br />
== Wechselrichter Eigenheiten ==<br />
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. <br />
<br />
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.<br />
<br />
== Installation ==<br />
Das Projekt von Github laden und in einem Verzeichnis speichern, '''Python''' ist normalerweise ja schon installiert. <br />
<br />
<pre>git clone https://github.com/kbialek/deye-inverter-mqtt<br />
pip install paho-mqtt<br />
cd deye-inverter-mqtt</pre><br />
<br />
Unter Debian habe ich im Skript '''deye_cli.sh''' python durch python3 ersetzen müssen:<br />
<pre>#!/bin/bash<br />
set -a; source config.env; set +a<br />
python3 deye_cli.py "$@"<br />
</pre><br />
<br />
=== config.env ===<br />
Die Konfiguration meines DEYE Wechselrichters vom Typ Micro-Inverter:<br />
'''config.env'''<br />
<pre>DEYE_LOGGER_IP_ADDRESS=<IP Wechselrichter im WLAN><br />
DEYE_LOGGER_PORT=8899<br />
DEYE_LOGGER_SERIAL_NUMBER=<Seriennummer des WR><br />
<br />
MQTT_HOST=<IP von MQTT Server><br />
MQTT_PORT=1883<br />
MQTT_USERNAME=<br />
MQTT_PASSWORD=<br />
MQTT_TOPIC_PREFIX=deye<br />
<br />
LOG_LEVEL=ERROR<br />
DEYE_DATA_READ_INTERVAL=60<br />
DEYE_METRIC_GROUPS=micro<br />
</pre><br />
<br />
=== deye_inverter.sh === <br />
Ausserdem habe ich ein kleines ''Wrapper Skript'' geschrieben, um '''Daten des WR''' einfacher '''lesen und schreiben''' zu können:<br><br />
<br />
'''deye_inverter.sh''' [--check <pause>] --read <register> | --write <register> <value><br />
<br />
<pre>#!/bin/bash<br />
# Read / Write DEYE Inverters<br />
# via deye_inverter_mqtt python package<br />
# https://github.com/kbialek/deye-inverter-mqtt<br />
<br />
retries=2 # retry deye_inverter command multiple times<br />
sleep=60 # sleep time between retries<br />
<br />
# locate script in deye_inverter_mqtt directory<br />
cd $(dirname $0)<br />
if [ ! -f ./deye_cli.sh ]; then<br />
echo "Error: ./deye_cli.sh not found."<br />
echo " Please move $(basename $0) in deye-inverter-mqtt directory."<br />
exit 1<br />
fi<br />
. ./config.env<br />
<br />
log_info ()<br />
{<br />
if [ ${LOG_LEVEL} = "INFO" ]; then<br />
logger -t $(basename $0) $@<br />
fi<br />
}<br />
<br />
log_error ()<br />
{<br />
logger -t $(basename $0) $@<br />
echo 1>&2 $@<br />
}<br />
<br />
# read parameters: mode [rw], register, value and optional -c <check pause><br />
pause=0<br />
while [ $# -gt 0 ]; do<br />
case $1 in<br />
-c|--check) shift<br />
[ $# -ge 3 ] || exit 1<br />
pause=$1<br />
;;<br />
-w|--write) mode=w<br />
shift<br />
[ $# -eq 2 ] || exit 1<br />
reg=$1<br />
val=$2<br />
shift<br />
;;<br />
-r|--read) mode=r<br />
shift<br />
[ $# -eq 1 ] || exit 1<br />
reg=$1<br />
;;<br />
esac<br />
shift<br />
done<br />
<br />
# handle offline deye_inverter by<br />
# ${pause} > 0 -> endless loop<br />
# ${pause} = 0 -> error after ${retries}<br />
while true; do<br />
<br />
# try deye_cli.sh $retries times every $sleep secs<br />
check=${retries}<br />
while [ ${check} -gt 0 ]; do<br />
<br />
case ${mode} in<br />
r) result=$(./deye_cli.sh $mode $reg | grep 'int: ')<br />
if [ -n "$result" ]; then<br />
echo "$result"<br />
log_info -t $0 deye_cli.sh $mode $reg<br />
exit 0<br />
fi<br />
;;<br />
w) result=$(./deye_cli.sh $mode $reg $val)<br />
if [ "$result" = "Ok" ]; then<br />
log_info -t $0 deye_cli.sh $mode $reg $val<br />
exit 0<br />
fi<br />
;;<br />
esac<br />
<br />
# wait ${sleep} or ${pause} secs<br />
sleep $(( ${pause} ? ${sleep} : ${pause} ))<br />
<br />
# try ${retries} times<br />
(( check-- ))<br />
done<br />
<br />
# error, or retry endless until wakeup of deye_inverter<br />
if [ ${pause} -eq 0 ]; then<br />
log_error "Error: deye_cli.sh $mode $reg $val failed."<br />
exit 1<br />
fi<br />
<br />
done<br />
</pre><br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
Folgendes Skript starte ich jeden Morgen um 04:00 Uhr per Cron (und in rc.local fürs booten):<br />
<br />
=== deye_mqtt_loop.sh ===<br />
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.<br />
<br />
Ausserdem setzt es beim Start den Tageszähler des Wechselrichters bedarfsweise zurück, bevor die Werte des DEYE Wechselrichters per Endlosschleife abgefragt werden.<br />
<br />
<pre>#!/bin/bash<br />
# reset Daily_Power of DEYE Inverters<br />
# via deye_inverter_mqtt python package<br />
# https://github.com/kbialek/deye-inverter-mqtt<br />
<br />
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin<br />
<br />
# config:<br />
deye_inverter_dir=/root/deye-inverter-mqtt<br />
fhem_dir=/opt/fhem<br />
horizon="-2" # sunset CIVIL=-6, REAL=0<br />
<br />
debug=info<br />
<br />
myPID=$$<br />
myname=$(basename $0)<br />
arg="$1"<br />
<br />
deye_inverter_sh=$deye_inverter_dir/deye_inverter.sh<br />
deye_inverter_mqtt_cfg=$deye_inverter_dir/config.env<br />
deye_inverter_mqtt_cmd="python3 deye_docker_entrypoint.py"<br />
<br />
sunrise_cmd="perl $fhem_dir/fhem.pl localhost:7072 {sunrise_abs('HORIZON=$horizon')}"<br />
sunset_cmd="perl $fhem_dir/fhem.pl localhost:7072 {sunset_abs('HORIZON=$horizon')}"<br />
<br />
# installation:<br />
# locate FHEM, used for sunrise(), sunset()<br />
if [ ! -x ${fhem_dir/fhem.pl} ]; then<br />
echo "Error: ${fhem_dir} not found."<br />
echo " Is FHEM installed in $fhem_dir ?"<br />
exit 1<br />
fi<br />
<br />
# locate script in deye_inverter_mqtt directory<br />
if [ ! -f ${deye_inverter_mqtt_cfg} ]; then<br />
echo "Error: ${deye_inverter_mqtt_cfg} not found."<br />
echo " Please update \$deye_inverter_sh path in $0"<br />
exit 1<br />
fi<br />
<br />
# read my config<br />
set -a; source ${deye_inverter_mqtt_cfg}; set +a<br />
<br />
[ "$debug" = "full" ] && set -x<br />
<br />
# ----- functions () ------------------------------------<br />
log_info ()<br />
{<br />
[ "$debug" = "yes" -o "$debug" = "true" -o "$debug" = "info" ] &&<br />
echo $myname: $@<br />
[ ${LOG_LEVEL} = "INFO" ] && logger -t $myname $@<br />
}<br />
<br />
log_error ()<br />
{<br />
logger -t $myname $@<br />
echo 1>&2 $@<br />
}<br />
<br />
# kill deye_inverter_loop ()<br />
kill_old_instance ()<br />
{<br />
# kill old instances of me<br />
my_PS=$(ps -ef | grep -v grep | grep $myname | cut -b10-26,53- | grep -v $myPID | awk '{print $1}')<br />
for ps in $my_PS; do<br />
[ $ps -lt $myPID ] && kill $ps<br />
done<br />
}<br />
<br />
# get deye_inverter_mqtt PIDs<br />
get_deye_mqtt_pid ()<br />
{<br />
deye_mqtt_pid=$(ps ax | grep -v grep | grep "${deye_inverter_mqtt_cmd}" | awk '{print $1}')<br />
}<br />
<br />
# kill deye_inverter_instance ()<br />
kill_deye_inverter_instance ()<br />
{<br />
get_deye_mqtt_pid<br />
if [ ! -z "${deye_mqtt_pid}" ]; then<br />
kill ${deye_mqtt_pid}<br />
log_info "Info: KILL running deye_mqtt ${deye_mqtt_pid}"<br />
fi<br />
<br />
# kill any running deye requests<br />
deye_ps=$(ps ax | grep -v grep | grep "$deye_inverter_sh " | awk '{print $1}')<br />
[ ! -z "$deye_ps" ] && kill $deye_ps<br />
}<br />
<br />
# stop all runnning instances<br />
deye_inverter_mqtt_stop ()<br />
{<br />
kill_old_instance<br />
kill_deye_inverter_instance<br />
}<br />
<br />
# start deye_inverter_mqtt<br />
deye_inverter_mqtt_start ()<br />
{<br />
log_info "Deye MQTT start"<br />
cd ${deye_inverter_dir}<br />
${deye_inverter_mqtt_cmd}<br />
}<br />
<br />
# store actual date + time into vars<br />
set_date_time_vars ()<br />
{<br />
year=$(date +%y)<br />
month=$(date +%m | sed 's/^0//g')<br />
day=$(date +%d | sed 's/^0//g')<br />
hour=$(date +%H | sed 's/^0//g')<br />
minute=$(date +%m | sed 's/^0//g')<br />
second=$(date +%S | sed 's/^0//g')<br />
}<br />
<br />
# read year_month register 0x16 = 22<br />
read_year_month ()<br />
{<br />
${deye_inverter_sh} -r 22 | awk '/^int: .*/ {split($2,i,","); print i[1]}'<br />
}<br />
<br />
# read day register 0x17 = 23<br />
read_day ()<br />
{<br />
${deye_inverter_sh} -r 23 | awk '/^int: .*/ {split($2,i,","); print i[1]}'<br />
}<br />
<br />
# read Daily power register 0x3c = 60<br />
read_daily_Power ()<br />
{<br />
${deye_inverter_sh} -r 60 | awk '/^int: .*/ {split($2,i,","); print i[1]}'<br />
}<br />
<br />
# read actual power register 0x56 = 86<br />
read_actual_Power ()<br />
{<br />
# doubleRegisterSensor("AC Active Power", 0x56, 0.1, mqtt_topic_suffix='ac/active_power', groups=['string', 'micro'])<br />
# (int.from_bytes(high_word, 'big') * 65536 + int.from_bytes(low_word, 'big')) * self.factor<br />
# val86=${deye_inverter_sh} -r 86 | sed 's/int: //g' | cut -d, -f1<br />
# val87=${deye_inverter_sh} -r 87 | sed 's/int: //g' | cut -d, -f1<br />
# echo $(( (${val87} * 65536 + ${val86}) * 0.1 ))<br />
<br />
# simple value is enough for our purpose<br />
${deye_inverter_sh} -c 300 -r 86 | awk '/^int: .*/ {split($2,i,","); print i[1]}'<br />
}<br />
<br />
# write actual date/time into deye inverter<br />
init_deye_inverter ()<br />
{<br />
# daily reset phase<br />
daily_reset=init<br />
<br />
# ensure by loop, that all register values are written<br />
while [ ${daily_reset} != "done" ]; do<br />
<br />
# wait until we have enough power<br />
while [ "0$(read_actual_Power)" -le 20 ]; do<br />
sleep 600<br />
done<br />
<br />
# read Daily power register<br />
dailyPower=$(read_daily_Power)<br />
<br />
# read (old) year_month and day<br />
val22_old=$(read_year_month)<br />
val23_day=$(read_day)<br />
log_info "date/time update initialized"<br />
<br />
# set variables year, month, day, hour, minute, second<br />
set_date_time_vars<br />
<br />
# calculate register 22-24 with date and time vars<br />
val22=$(( ${year} * 256 + ${month} ))<br />
val23=$(( ${day} * 256 + ${hour} ))<br />
val24=$(( ${minute} * 256 + ${second} ))<br />
<br />
# reset power/date/time only once a day or exit loop<br />
[ "$dailyPower" -eq 0 -a "${val22_old}" = "${val22}" -a "${val23_day}" = "${day}" ] && break<br />
<br />
# reset daily_power by<br />
# setting register 22-24 with actual date + time<br />
${deye_inverter_sh} -w 22 $val22 || continue<br />
${deye_inverter_sh} -w 23 $val23 || continue<br />
${deye_inverter_sh} -w 24 $val24 || continue<br />
<br />
# test success<br />
# read Daily power register, should be resetted to 0 now<br />
dailyPower=$(read_daily_Power)<br />
<br />
# exit loop if power is updated, else log that reset failed<br />
if [ "$dailyPower" -eq 0 ]; then<br />
daily_reset=done<br />
<br />
### skip loop here, reset doesn't work always :-(<br />
# elif [ $daily_reset = "init" ]; then<br />
# daily_reset=failed<br />
# sleep 300<br />
<br />
else<br />
log_info "daily power reset failed"<br />
# wait 5 mins, often reset is done after 5 mins<br />
sleep 300<br />
break<br />
fi<br />
done<br />
log_info "date/time reset done"<br />
}<br />
<br />
# wait for (next) daylight<br />
wait_for_sunrise ()<br />
{<br />
sunrise=$($sunrise_cmd)<br />
sunset=$($sunset_cmd)<br />
<br />
current_time=$(date +%s)<br />
sunrise=$(date -d"$sunrise" +%s)<br />
sunset=$(date -d"$sunset" +%s)<br />
<br />
# calulate time in secs until (next) sunrise<br />
sleep_sunrise=$(( $sunrise - $current_time )) # this morning<br />
[ $sleep_sunrise -lt 0 ] &&<br />
sleep_sunrise=$(( $sleep_sunrise + 86400 )) # next morning<br />
<br />
# wait for daylight only<br />
if [ $current_time -lt $sunrise -o $current_time -gt $sunset ]; then<br />
log_info sleep $sleep_sunrise<br />
sleep $sleep_sunrise<br />
fi<br />
}<br />
<br />
# wait only before sunset<br />
wait_before_sunset ()<br />
{<br />
sunset=$($sunset_cmd)<br />
<br />
current_time=$(date +%s)<br />
sunset=$(date -d"$sunset" +%s)<br />
<br />
# calulate time in secs until next sunset<br />
sleep_sunset=$(( $sunset - $current_time + 60 ))<br />
<br />
# wait before sunset only<br />
if [ $sleep_sunset -gt 0 ]; then<br />
log_info sleep $sleep_sunset<br />
sleep $sleep_sunset<br />
fi<br />
}<br />
<br />
# ------------------------<br />
# MAIN: Script starts here<br />
# ------------------------<br />
<br />
# sunset_stop: wait until sunset before we stop running instances<br />
[ "$arg" = "sunset_stop" ] &&<br />
wait_before_sunset<br />
<br />
# stop any runnning deye_inverter_mqtt processes<br />
deye_inverter_mqtt_stop<br />
<br />
# stop: don't start again<br />
[ "$arg" = "stop" ] && exit 0<br />
<br />
# reset and start deye_mqtt_loop<br />
# check every 15 min for running deye_inverter_mqtt<br />
while :; do<br />
<br />
# wait for (next) daylight<br />
wait_for_sunrise<br />
<br />
# setting date/time also resets daily power register<br />
init_deye_inverter<br />
sleep 300<br />
<br />
# start deye_inverter_mqtt<br />
get_deye_mqtt_pid<br />
if [ -z "$deye_mqtt_pid" ]; then<br />
deye_inverter_mqtt_start<br />
fi<br />
<br />
# wait 15 min<br />
sleep 900<br />
done<br />
</pre><br />
<br />
=== Contrab und /etc/rc.local ===<br />
Mein Crontab Eintrag:<br />
<pre># stop DEYE monitoring after sunset (FHEM)<br />
10 16 * * * /root/sbin/deye_mqtt_loop.sh sunset_stop 2>/dev/null<br />
</pre><br />
und /etc/rc.local:<br />
<pre># start monitoring of PV-System: DEYE inverter<br />
$(sleep 10; /root/sbin/deye_mqtt_loop.sh 2>dev/null)&<br />
</pre><br />
<br />
== Dashboard ==<br />
So sieht das aus, wenn es den ganzen Tag im April regnet und bedeckt ist:<br />
[[Datei:Solar Bedeckt Regen.jpg|midi]]<br />
<br />
Die Eigenverbrauchsquote habe ich mittels Grafana ermittelt:<br />
[[Datei:Solar Eigenverbrauch.jpg|midi|Eigenverbrauchsquote]]</div>
Neobiker
http://neobiker.de/wiki/index.php?title=DEYE_Wechselrichter&diff=2150
DEYE Wechselrichter
2024-02-07T17:18:49Z
<p>Neobiker: /* deye_mqtt_loop.sh */</p>
<hr />
<div>= Balkonkraftwerk: DEYE Wechselrichter ohne Cloud auslesen und per MQTT monitoren =<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
Der WR bietet verschiedene Möglichkeiten zur Konfiguration und Abfrage der aktuellen Werte:<br />
* einen internen Webserver (http://10.10.10.254)<br />
* die Cloud Anbindung zu Solarman (Solarman Smart App)<br />
* (angepasstes) Modbus Protokoll über Port 8899<br />
* AT+ Befehle über Port 48899<br />
<br />
Ich verwende das Modbus Protokoll zum auslesen des WR, das <br />
# effizient/schnell ist (Performance)<br />
# ohne Internetverbindung auskommt<br />
<br />
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.<br />
<br />
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.<br />
<br />
== Wechselrichter Eigenheiten ==<br />
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. <br />
<br />
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.<br />
<br />
== Installation ==<br />
Das Projekt von Github laden und in einem Verzeichnis speichern, '''Python''' ist normalerweise ja schon installiert. <br />
<br />
<pre>git clone https://github.com/kbialek/deye-inverter-mqtt<br />
pip install paho-mqtt<br />
cd deye-inverter-mqtt</pre><br />
<br />
Unter Debian habe ich im Skript '''deye_cli.sh''' python durch python3 ersetzen müssen:<br />
<pre>#!/bin/bash<br />
set -a; source config.env; set +a<br />
python3 deye_cli.py "$@"<br />
</pre><br />
<br />
=== config.env ===<br />
Die Konfiguration meines DEYE Wechselrichters vom Typ Micro-Inverter:<br />
'''config.env'''<br />
<pre>DEYE_LOGGER_IP_ADDRESS=<IP Wechselrichter im WLAN><br />
DEYE_LOGGER_PORT=8899<br />
DEYE_LOGGER_SERIAL_NUMBER=<Seriennummer des WR><br />
<br />
MQTT_HOST=<IP von MQTT Server><br />
MQTT_PORT=1883<br />
MQTT_USERNAME=<br />
MQTT_PASSWORD=<br />
MQTT_TOPIC_PREFIX=deye<br />
<br />
LOG_LEVEL=ERROR<br />
DEYE_DATA_READ_INTERVAL=60<br />
DEYE_METRIC_GROUPS=micro<br />
</pre><br />
<br />
=== deye_inverter.sh === <br />
Ausserdem habe ich ein kleines ''Wrapper Skript'' geschrieben, um '''Daten des WR''' einfacher '''lesen und schreiben''' zu können:<br><br />
<br />
'''deye_inverter.sh''' [--check <pause>] --read <register> | --write <register> <value><br />
<br />
<pre>#!/bin/bash<br />
# Read / Write DEYE Inverters<br />
# via deye_inverter_mqtt python package<br />
# https://github.com/kbialek/deye-inverter-mqtt<br />
<br />
retries=2 # retry deye_inverter command multiple times<br />
sleep=60 # sleep time between retries<br />
<br />
# locate script in deye_inverter_mqtt directory<br />
cd $(dirname $0)<br />
if [ ! -f ./deye_cli.sh ]; then<br />
echo "Error: ./deye_cli.sh not found."<br />
echo " Please move $(basename $0) in deye-inverter-mqtt directory."<br />
exit 1<br />
fi<br />
. ./config.env<br />
<br />
log_info ()<br />
{<br />
if [ ${LOG_LEVEL} = "INFO" ]; then<br />
logger -t $(basename $0) $@<br />
fi<br />
}<br />
<br />
log_error ()<br />
{<br />
logger -t $(basename $0) $@<br />
echo 1>&2 $@<br />
}<br />
<br />
# read parameters: mode [rw], register, value and optional -c <check pause><br />
pause=0<br />
while [ $# -gt 0 ]; do<br />
case $1 in<br />
-c|--check) shift<br />
[ $# -ge 3 ] || exit 1<br />
pause=$1<br />
;;<br />
-w|--write) mode=w<br />
shift<br />
[ $# -eq 2 ] || exit 1<br />
reg=$1<br />
val=$2<br />
shift<br />
;;<br />
-r|--read) mode=r<br />
shift<br />
[ $# -eq 1 ] || exit 1<br />
reg=$1<br />
;;<br />
esac<br />
shift<br />
done<br />
<br />
# handle offline deye_inverter by<br />
# ${pause} > 0 -> endless loop<br />
# ${pause} = 0 -> error after ${retries}<br />
while true; do<br />
<br />
# try deye_cli.sh $retries times every $sleep secs<br />
check=${retries}<br />
while [ ${check} -gt 0 ]; do<br />
<br />
case ${mode} in<br />
r) result=$(./deye_cli.sh $mode $reg | grep 'int: ')<br />
if [ -n "$result" ]; then<br />
echo "$result"<br />
log_info -t $0 deye_cli.sh $mode $reg<br />
exit 0<br />
fi<br />
;;<br />
w) result=$(./deye_cli.sh $mode $reg $val)<br />
if [ "$result" = "Ok" ]; then<br />
log_info -t $0 deye_cli.sh $mode $reg $val<br />
exit 0<br />
fi<br />
;;<br />
esac<br />
<br />
# wait ${sleep} or ${pause} secs<br />
sleep $(( ${pause} ? ${sleep} : ${pause} ))<br />
<br />
# try ${retries} times<br />
(( check-- ))<br />
done<br />
<br />
# error, or retry endless until wakeup of deye_inverter<br />
if [ ${pause} -eq 0 ]; then<br />
log_error "Error: deye_cli.sh $mode $reg $val failed."<br />
exit 1<br />
fi<br />
<br />
done<br />
</pre><br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
Folgendes Skript starte ich jeden Morgen um 04:00 Uhr per Cron (und in rc.local fürs booten):<br />
<br />
=== deye_mqtt_loop.sh ===<br />
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.<br />
<br />
Ausserdem setzt es beim Start den Tageszähler des Wechselrichters bedarfsweise zurück, bevor die Werte des DEYE Wechselrichters per Endlosschleife abgefragt werden.<br />
<br />
<pre>#!/bin/bash<br />
# reset Daily_Power of DEYE Inverters<br />
# via deye_inverter_mqtt python package<br />
# https://github.com/kbialek/deye-inverter-mqtt<br />
<br />
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin<br />
<br />
# config:<br />
deye_inverter_dir=/root/deye-inverter-mqtt<br />
fhem_dir=/opt/fhem<br />
horizon="-2" # sunset CIVIL=-6, REAL=0<br />
<br />
debug=info<br />
<br />
myPID=$$<br />
myname=$(basename $0)<br />
arg="$1"<br />
<br />
deye_inverter_sh=$deye_inverter_dir/deye_inverter.sh<br />
deye_inverter_mqtt_cfg=$deye_inverter_dir/config.env<br />
deye_inverter_mqtt_cmd="python3 deye_docker_entrypoint.py"<br />
<br />
sunrise_cmd="perl $fhem_dir/fhem.pl localhost:7072 {sunrise_abs('HORIZON=$horizon')}"<br />
sunset_cmd="perl $fhem_dir/fhem.pl localhost:7072 {sunset_abs('HORIZON=$horizon')}"<br />
<br />
# installation:<br />
# locate FHEM, used for sunrise(), sunset()<br />
if [ ! -x ${fhem_dir/fhem.pl} ]; then<br />
echo "Error: ${fhem_dir} not found."<br />
echo " Is FHEM installed in $fhem_dir ?"<br />
exit 1<br />
fi<br />
<br />
# locate script in deye_inverter_mqtt directory<br />
if [ ! -f ${deye_inverter_mqtt_cfg} ]; then<br />
echo "Error: ${deye_inverter_mqtt_cfg} not found."<br />
echo " Please update \$deye_inverter_sh path in $0"<br />
exit 1<br />
fi<br />
<br />
# read my config<br />
set -a; source ${deye_inverter_mqtt_cfg}; set +a<br />
<br />
[ "$debug" = "full" ] && set -x<br />
<br />
# ----- functions () ------------------------------------<br />
log_info ()<br />
{<br />
[ "$debug" = "yes" -o "$debug" = "true" -o "$debug" = "info" ] &&<br />
echo $myname: $@<br />
[ ${LOG_LEVEL} = "INFO" ] && logger -t $myname $@<br />
}<br />
<br />
log_error ()<br />
{<br />
logger -t $myname $@<br />
echo 1>&2 $@<br />
}<br />
<br />
# kill deye_inverter_loop ()<br />
kill_old_instance ()<br />
{<br />
# kill old instances of me<br />
my_PS=$(ps -ef | grep -v grep | grep $myname | cut -b10-26,53- | grep -v $myPID | awk '{print $1}')<br />
for ps in $my_PS; do<br />
[ $ps -lt $myPID ] && kill $ps<br />
done<br />
}<br />
<br />
# get deye_inverter_mqtt PIDs<br />
get_deye_mqtt_pid ()<br />
{<br />
deye_mqtt_pid=$(ps ax | grep -v grep | grep "${deye_inverter_mqtt_cmd}" | awk '{print $1}')<br />
}<br />
<br />
# kill deye_inverter_instance ()<br />
kill_deye_inverter_instance ()<br />
{<br />
get_deye_mqtt_pid<br />
if [ ! -z "${deye_mqtt_pid}" ]; then<br />
kill ${deye_mqtt_pid}<br />
log_info "Info: KILL running deye_mqtt ${deye_mqtt_pid}"<br />
fi<br />
<br />
# kill any running deye requests<br />
deye_ps=$(ps ax | grep -v grep | grep "$deye_inverter_sh " | awk '{print $1}')<br />
[ ! -z "$deye_ps" ] && kill $deye_ps<br />
}<br />
<br />
# stop all runnning instances<br />
deye_inverter_mqtt_stop ()<br />
{<br />
kill_old_instance<br />
kill_deye_inverter_instance<br />
}<br />
<br />
# start deye_inverter_mqtt<br />
deye_inverter_mqtt_start ()<br />
{<br />
log_info "Deye MQTT start"<br />
cd ${deye_inverter_dir}<br />
${deye_inverter_mqtt_cmd}<br />
}<br />
<br />
# store actual date + time into vars<br />
set_date_time_vars ()<br />
{<br />
year=$(date +%y)<br />
month=$(date +%m | sed 's/^0//g')<br />
day=$(date +%d | sed 's/^0//g')<br />
hour=$(date +%H | sed 's/^0//g')<br />
minute=$(date +%m | sed 's/^0//g')<br />
second=$(date +%S | sed 's/^0//g')<br />
}<br />
<br />
# read year_month register 0x16 = 22<br />
read_year_month ()<br />
{<br />
${deye_inverter_sh} -r 22 | awk '/^int: .*/ {split($2,i,","); print i[1]}'<br />
}<br />
<br />
# read day register 0x17 = 23<br />
read_day ()<br />
{<br />
${deye_inverter_sh} -r 23 | awk '/^int: .*/ {split($2,i,","); print i[1]}'<br />
}<br />
<br />
# read Daily power register 0x3c = 60<br />
read_daily_Power ()<br />
{<br />
${deye_inverter_sh} -r 60 | awk '/^int: .*/ {split($2,i,","); print i[1]}'<br />
}<br />
<br />
# read actual power register 0x56 = 86<br />
read_actual_Power ()<br />
{<br />
# doubleRegisterSensor("AC Active Power", 0x56, 0.1, mqtt_topic_suffix='ac/active_power', groups=['string', 'micro'])<br />
# (int.from_bytes(high_word, 'big') * 65536 + int.from_bytes(low_word, 'big')) * self.factor<br />
# val86=${deye_inverter_sh} -r 86 | sed 's/int: //g' | cut -d, -f1<br />
# val87=${deye_inverter_sh} -r 87 | sed 's/int: //g' | cut -d, -f1<br />
# echo $(( (${val87} * 65536 + ${val86}) * 0.1 ))<br />
<br />
# simple value is enough for our purpose<br />
${deye_inverter_sh} -c 300 -r 86 | awk '/^int: .*/ {split($2,i,","); print i[1]}'<br />
}<br />
<br />
# write actual date/time into deye inverter<br />
init_deye_inverter ()<br />
{<br />
# daily reset phase<br />
daily_reset=init<br />
<br />
# ensure by loop, that all register values are written<br />
while [ ${daily_reset} != "done" ]; do<br />
<br />
# wait until we have enough power<br />
while [ "0$(read_actual_Power)" -le 20 ]; do<br />
sleep 600<br />
done<br />
<br />
# read Daily power register<br />
dailyPower=$(read_daily_Power)<br />
<br />
# read (old) year_month and day<br />
val22_old=$(read_year_month)<br />
val23_day=$(read_day)<br />
log_info "date/time update initialized"<br />
<br />
# set variables year, month, day, hour, minute, second<br />
set_date_time_vars<br />
<br />
# calculate register 22-24 with date and time vars<br />
val22=$(( ${year} * 256 + ${month} ))<br />
val23=$(( ${day} * 256 + ${hour} ))<br />
val24=$(( ${minute} * 256 + ${second} ))<br />
<br />
# reset power/date/time only once a day or exit loop<br />
[ "$dailyPower" -eq 0 -a "${val22_old}" = "${val22}" -a "${val23_day}" = "${day}" ] && break<br />
<br />
# reset daily_power by<br />
# setting register 22-24 with actual date + time<br />
${deye_inverter_sh} -w 22 $val22 || continue<br />
${deye_inverter_sh} -w 23 $val23 || continue<br />
${deye_inverter_sh} -w 24 $val24 || continue<br />
<br />
# test success<br />
# read Daily power register, should be resetted to 0 now<br />
dailyPower=$(read_daily_Power)<br />
<br />
# exit loop if power is updated, else log that reset failed<br />
if [ "$dailyPower" -eq 0 ]; then<br />
daily_reset=done<br />
<br />
### skip loop here, reset doesn't work always :-(<br />
# elif [ $daily_reset = "init" ]; then<br />
# daily_reset=failed<br />
# sleep 300<br />
<br />
else<br />
log_info "daily power reset failed"<br />
# wait 5 mins, often reset is done after 5 mins<br />
sleep 300<br />
break<br />
fi<br />
done<br />
log_info "date/time reset done"<br />
}<br />
<br />
# wait for (next) daylight<br />
wait_for_sunrise ()<br />
{<br />
sunrise=$($sunrise_cmd)<br />
sunset=$($sunset_cmd)<br />
<br />
current_time=$(date +%s)<br />
sunrise=$(date -d"$sunrise" +%s)<br />
sunset=$(date -d"$sunset" +%s)<br />
<br />
# calulate time in secs until (next) sunrise<br />
sleep_sunrise=$(( $sunrise - $current_time )) # this morning<br />
[ $sleep_sunrise -lt 0 ] &&<br />
sleep_sunrise=$(( $sleep_sunrise + 86400 )) # next morning<br />
<br />
# wait for daylight only<br />
if [ $current_time -lt $sunrise -o $current_time -gt $sunset ]; then<br />
log_info sleep $sleep_sunrise<br />
sleep $sleep_sunrise<br />
fi<br />
}<br />
<br />
# wait only before sunset<br />
wait_before_sunset ()<br />
{<br />
sunset=$($sunset_cmd)<br />
<br />
current_time=$(date +%s)<br />
sunset=$(date -d"$sunset" +%s)<br />
<br />
# calulate time in secs until next sunset<br />
sleep_sunset=$(( $sunset - $current_time + 60 ))<br />
<br />
# wait before sunset only<br />
if [ $sleep_sunset -gt 0 ]; then<br />
log_info sleep $sleep_sunset<br />
sleep $sleep_sunset<br />
fi<br />
}<br />
<br />
# ------------------------<br />
# MAIN: Script starts here<br />
# ------------------------<br />
<br />
# sunset_stop: wait until sunset before we stop running instances<br />
[ "$arg" = "sunset_stop" ] &&<br />
wait_before_sunset<br />
<br />
# stop any runnning deye_inverter_mqtt processes<br />
deye_inverter_mqtt_stop<br />
<br />
# stop: don't start again<br />
[ "$arg" = "stop" ] && exit 0<br />
<br />
# reset and start deye_mqtt_loop<br />
# check every 15 min for running deye_inverter_mqtt<br />
while :; do<br />
<br />
# wait for (next) daylight<br />
wait_for_sunrise<br />
<br />
# setting date/time also resets daily power register<br />
init_deye_inverter<br />
sleep 300<br />
<br />
# start deye_inverter_mqtt<br />
get_deye_mqtt_pid<br />
if [ -z "$deye_mqtt_pid" ]; then<br />
deye_inverter_mqtt_start<br />
fi<br />
<br />
# wait 15 min<br />
sleep 900<br />
done<br />
</pre><br />
<br />
=== Contrab und /etc/rc.local ===<br />
Mein Crontab Eintrag:<br />
<pre># stop DEYE monitoring after sunset (FHEM)<br />
0 18 * * * /root/sbin/deye_mqtt_loop.sh sunset_stop 2>/dev/null<br />
</pre><br />
und /etc/rc.local:<br />
<pre># start monitoring of PV-System: DEYE inverter<br />
$(sleep 10; /root/sbin/deye_mqtt_loop.sh 2>dev/null)&<br />
</pre><br />
<br />
== Dashboard ==<br />
So sieht das aus, wenn es den ganzen Tag im April regnet und bedeckt ist:<br />
[[Datei:Solar Bedeckt Regen.jpg|midi]]<br />
<br />
Die Eigenverbrauchsquote habe ich mittels Grafana ermittelt:<br />
[[Datei:Solar Eigenverbrauch.jpg|midi|Eigenverbrauchsquote]]</div>
Neobiker
http://neobiker.de/wiki/index.php?title=DEYE_Wechselrichter&diff=2149
DEYE Wechselrichter
2024-02-07T17:12:32Z
<p>Neobiker: /* deye_mqtt_loop.sh */</p>
<hr />
<div>= Balkonkraftwerk: DEYE Wechselrichter ohne Cloud auslesen und per MQTT monitoren =<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
Der WR bietet verschiedene Möglichkeiten zur Konfiguration und Abfrage der aktuellen Werte:<br />
* einen internen Webserver (http://10.10.10.254)<br />
* die Cloud Anbindung zu Solarman (Solarman Smart App)<br />
* (angepasstes) Modbus Protokoll über Port 8899<br />
* AT+ Befehle über Port 48899<br />
<br />
Ich verwende das Modbus Protokoll zum auslesen des WR, das <br />
# effizient/schnell ist (Performance)<br />
# ohne Internetverbindung auskommt<br />
<br />
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.<br />
<br />
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.<br />
<br />
== Wechselrichter Eigenheiten ==<br />
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. <br />
<br />
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.<br />
<br />
== Installation ==<br />
Das Projekt von Github laden und in einem Verzeichnis speichern, '''Python''' ist normalerweise ja schon installiert. <br />
<br />
<pre>git clone https://github.com/kbialek/deye-inverter-mqtt<br />
pip install paho-mqtt<br />
cd deye-inverter-mqtt</pre><br />
<br />
Unter Debian habe ich im Skript '''deye_cli.sh''' python durch python3 ersetzen müssen:<br />
<pre>#!/bin/bash<br />
set -a; source config.env; set +a<br />
python3 deye_cli.py "$@"<br />
</pre><br />
<br />
=== config.env ===<br />
Die Konfiguration meines DEYE Wechselrichters vom Typ Micro-Inverter:<br />
'''config.env'''<br />
<pre>DEYE_LOGGER_IP_ADDRESS=<IP Wechselrichter im WLAN><br />
DEYE_LOGGER_PORT=8899<br />
DEYE_LOGGER_SERIAL_NUMBER=<Seriennummer des WR><br />
<br />
MQTT_HOST=<IP von MQTT Server><br />
MQTT_PORT=1883<br />
MQTT_USERNAME=<br />
MQTT_PASSWORD=<br />
MQTT_TOPIC_PREFIX=deye<br />
<br />
LOG_LEVEL=ERROR<br />
DEYE_DATA_READ_INTERVAL=60<br />
DEYE_METRIC_GROUPS=micro<br />
</pre><br />
<br />
=== deye_inverter.sh === <br />
Ausserdem habe ich ein kleines ''Wrapper Skript'' geschrieben, um '''Daten des WR''' einfacher '''lesen und schreiben''' zu können:<br><br />
<br />
'''deye_inverter.sh''' [--check <pause>] --read <register> | --write <register> <value><br />
<br />
<pre>#!/bin/bash<br />
# Read / Write DEYE Inverters<br />
# via deye_inverter_mqtt python package<br />
# https://github.com/kbialek/deye-inverter-mqtt<br />
<br />
retries=2 # retry deye_inverter command multiple times<br />
sleep=60 # sleep time between retries<br />
<br />
# locate script in deye_inverter_mqtt directory<br />
cd $(dirname $0)<br />
if [ ! -f ./deye_cli.sh ]; then<br />
echo "Error: ./deye_cli.sh not found."<br />
echo " Please move $(basename $0) in deye-inverter-mqtt directory."<br />
exit 1<br />
fi<br />
. ./config.env<br />
<br />
log_info ()<br />
{<br />
if [ ${LOG_LEVEL} = "INFO" ]; then<br />
logger -t $(basename $0) $@<br />
fi<br />
}<br />
<br />
log_error ()<br />
{<br />
logger -t $(basename $0) $@<br />
echo 1>&2 $@<br />
}<br />
<br />
# read parameters: mode [rw], register, value and optional -c <check pause><br />
pause=0<br />
while [ $# -gt 0 ]; do<br />
case $1 in<br />
-c|--check) shift<br />
[ $# -ge 3 ] || exit 1<br />
pause=$1<br />
;;<br />
-w|--write) mode=w<br />
shift<br />
[ $# -eq 2 ] || exit 1<br />
reg=$1<br />
val=$2<br />
shift<br />
;;<br />
-r|--read) mode=r<br />
shift<br />
[ $# -eq 1 ] || exit 1<br />
reg=$1<br />
;;<br />
esac<br />
shift<br />
done<br />
<br />
# handle offline deye_inverter by<br />
# ${pause} > 0 -> endless loop<br />
# ${pause} = 0 -> error after ${retries}<br />
while true; do<br />
<br />
# try deye_cli.sh $retries times every $sleep secs<br />
check=${retries}<br />
while [ ${check} -gt 0 ]; do<br />
<br />
case ${mode} in<br />
r) result=$(./deye_cli.sh $mode $reg | grep 'int: ')<br />
if [ -n "$result" ]; then<br />
echo "$result"<br />
log_info -t $0 deye_cli.sh $mode $reg<br />
exit 0<br />
fi<br />
;;<br />
w) result=$(./deye_cli.sh $mode $reg $val)<br />
if [ "$result" = "Ok" ]; then<br />
log_info -t $0 deye_cli.sh $mode $reg $val<br />
exit 0<br />
fi<br />
;;<br />
esac<br />
<br />
# wait ${sleep} or ${pause} secs<br />
sleep $(( ${pause} ? ${sleep} : ${pause} ))<br />
<br />
# try ${retries} times<br />
(( check-- ))<br />
done<br />
<br />
# error, or retry endless until wakeup of deye_inverter<br />
if [ ${pause} -eq 0 ]; then<br />
log_error "Error: deye_cli.sh $mode $reg $val failed."<br />
exit 1<br />
fi<br />
<br />
done<br />
</pre><br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
Folgendes Skript starte ich jeden Morgen um 04:00 Uhr per Cron (und in rc.local fürs booten):<br />
<br />
=== deye_mqtt_loop.sh ===<br />
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.<br />
<br />
Ausserdem setzt es beim Start den Tageszähler des Wechselrichters bedarfsweise zurück, bevor die Werte des DEYE Wechselrichters per Endlosschleife abgefragt werden.<br />
<br />
<pre>#!/bin/bash<br />
# reset Daily_Power of DEYE Inverters<br />
# via deye_inverter_mqtt python package<br />
# https://github.com/kbialek/deye-inverter-mqtt<br />
<br />
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin<br />
<br />
# config:<br />
deye_inverter_dir=/root/deye-inverter-mqtt<br />
fhem_dir=/opt/fhem<br />
horizon="-2" # sunset CIVIL=-6, REAL=0<br />
<br />
debug=info<br />
<br />
myPID=$$<br />
myname=$(basename $0)<br />
arg="$1"<br />
<br />
deye_inverter_sh=$deye_inverter_dir/deye_inverter.sh<br />
deye_inverter_mqtt_cfg=$deye_inverter_dir/config.env<br />
deye_inverter_mqtt_cmd="python3 deye_docker_entrypoint.py"<br />
<br />
sunrise_cmd="perl $fhem_dir/fhem.pl localhost:7072 {sunrise_abs('HORIZON=$horizon')}"<br />
sunset_cmd="perl $fhem_dir/fhem.pl localhost:7072 {sunset_abs('HORIZON=$horizon')}"<br />
<br />
# installation:<br />
# locate FHEM, used for sunrise(), sunset()<br />
if [ ! -x ${fhem_dir/fhem.pl} ]; then<br />
echo "Error: ${fhem_dir} not found."<br />
echo " Is FHEM installed in $fhem_dir ?"<br />
exit 1<br />
fi<br />
<br />
# locate script in deye_inverter_mqtt directory<br />
if [ ! -f ${deye_inverter_mqtt_cfg} ]; then<br />
echo "Error: ${deye_inverter_mqtt_cfg} not found."<br />
echo " Please update \$deye_inverter_sh path in $0"<br />
exit 1<br />
fi<br />
<br />
# read my config<br />
set -a; source ${deye_inverter_mqtt_cfg}; set +a<br />
<br />
[ "$debug" = "full" ] && set -x<br />
<br />
# ----- functions () ------------------------------------<br />
log_info ()<br />
{<br />
[ "$debug" = "yes" -o "$debug" = "true" -o "$debug" = "info" ] &&<br />
echo $myname: $@<br />
[ ${LOG_LEVEL} = "INFO" ] && logger -t $myname $@<br />
}<br />
<br />
log_error ()<br />
{<br />
logger -t $myname $@<br />
echo 1>&2 $@<br />
}<br />
<br />
# kill deye_inverter_loop ()<br />
kill_old_instance ()<br />
{<br />
# kill old instances of me<br />
my_PS=$(ps -ef | grep -v grep | grep $myname | cut -b10-26,53- | grep -v $myPID | awk '{print $1}')<br />
for ps in $my_PS; do<br />
[ $ps -lt $myPID ] && kill $ps<br />
done<br />
}<br />
<br />
# get deye_inverter_mqtt PIDs<br />
get_deye_mqtt_pid ()<br />
{<br />
deye_mqtt_pid=$(ps ax | grep -v grep | grep "${deye_inverter_mqtt_cmd}" | awk '{print $1}')<br />
}<br />
<br />
# kill deye_inverter_instance ()<br />
kill_deye_inverter_instance ()<br />
{<br />
get_deye_mqtt_pid<br />
if [ ! -z "${deye_mqtt_pid}" ]; then<br />
kill ${deye_mqtt_pid}<br />
log_info "Info: KILL running deye_mqtt ${deye_mqtt_pid}"<br />
fi<br />
<br />
# kill any running deye requests<br />
deye_ps=$(ps ax | grep -v grep | grep "$deye_inverter_sh " | awk '{print $1}')<br />
[ ! -z "$deye_ps" ] && kill $deye_ps<br />
}<br />
<br />
# stop all runnning instances<br />
deye_inverter_mqtt_stop ()<br />
{<br />
kill_old_instance<br />
kill_deye_inverter_instance<br />
}<br />
<br />
# start deye_inverter_mqtt<br />
deye_inverter_mqtt_start ()<br />
{<br />
log_info "Deye MQTT start"<br />
cd ${deye_inverter_dir}<br />
${deye_inverter_mqtt_cmd}<br />
}<br />
<br />
# store actual date + time into vars<br />
set_date_time_vars ()<br />
{<br />
year=$(date +%y)<br />
month=$(date +%m | sed 's/^0//g')<br />
day=$(date +%d | sed 's/^0//g')<br />
hour=$(date +%H | sed 's/^0//g')<br />
minute=$(date +%m | sed 's/^0//g')<br />
second=$(date +%S | sed 's/^0//g')<br />
}<br />
<br />
# read year_month register 0x16 = 22<br />
read_year_month ()<br />
{<br />
${deye_inverter_sh} -r 22 | awk '/^int: .*/ {split($2,i,","); print i[1]}'<br />
}<br />
<br />
# read day register 0x17 = 23<br />
read_day ()<br />
{<br />
${deye_inverter_sh} -r 23 | awk '/^int: .*/ {split($2,i,","); print i[1]}'<br />
}<br />
<br />
# read Daily power register 0x3c = 60<br />
read_daily_Power ()<br />
{<br />
${deye_inverter_sh} -r 60 | awk '/^int: .*/ {split($2,i,","); print i[1]}'<br />
}<br />
<br />
# read actual power register 0x56 = 86<br />
read_actual_Power ()<br />
{<br />
# doubleRegisterSensor("AC Active Power", 0x56, 0.1, mqtt_topic_suffix='ac/active_power', groups=['string', 'micro'])<br />
# (int.from_bytes(high_word, 'big') * 65536 + int.from_bytes(low_word, 'big')) * self.factor<br />
# val86=${deye_inverter_sh} -r 86 | sed 's/int: //g' | cut -d, -f1<br />
# val87=${deye_inverter_sh} -r 87 | sed 's/int: //g' | cut -d, -f1<br />
# echo $(( (${val87} * 65536 + ${val86}) * 0.1 ))<br />
<br />
# simple value is enough for our purpose<br />
${deye_inverter_sh} -c 600 -r 86 | awk '/^int: .*/ {split($2,i,","); print i[1]}'<br />
}<br />
<br />
# write actual date/time into deye inverter<br />
init_deye_inverter ()<br />
{<br />
# daily reset phase<br />
daily_reset=init<br />
<br />
# ensure by loop, that all register values are written<br />
while [ ${daily_reset} != "done" ]; do<br />
<br />
# wait until we have enough power<br />
while [ "0$(read_actual_Power)" -le 20 ]; do<br />
sleep 600<br />
done<br />
<br />
# read Daily power register<br />
dailyPower=$(read_daily_Power)<br />
<br />
# read (old) year_month and day<br />
val22_old=$(read_year_month)<br />
val23_day=$(read_day)<br />
log_info "date/time update initialized"<br />
<br />
# set variables year, month, day, hour, minute, second<br />
set_date_time_vars<br />
<br />
# calculate register 22-24 with date and time vars<br />
val22=$(( ${year} * 256 + ${month} ))<br />
val23=$(( ${day} * 256 + ${hour} ))<br />
val24=$(( ${minute} * 256 + ${second} ))<br />
<br />
# reset power/date/time only once a day or exit loop<br />
[ "$dailyPower" -eq 0 -a "${val22_old}" = "${val22}" -a "${val23_day}" = "${day}" ] && break<br />
<br />
# reset daily_power by<br />
# setting register 22-24 with actual date + time<br />
${deye_inverter_sh} -w 22 $val22 || continue<br />
${deye_inverter_sh} -w 23 $val23 || continue<br />
${deye_inverter_sh} -w 24 $val24 || continue<br />
<br />
# test success<br />
# read Daily power register, should be resetted to 0 now<br />
dailyPower=$(read_daily_Power)<br />
<br />
# exit loop if power is updated, else log that reset failed<br />
if [ "$dailyPower" -eq 0 ]; then<br />
daily_reset=done<br />
<br />
### skip loop here, reset doesn't work always :-(<br />
# elif [ $daily_reset = "init" ]; then<br />
# daily_reset=failed<br />
# sleep 300<br />
<br />
else<br />
log_info "daily power reset failed"<br />
break<br />
fi<br />
done<br />
log_info "date/time reset done"<br />
}<br />
<br />
# wait for (next) daylight<br />
wait_for_sunrise ()<br />
{<br />
sunrise=$($sunrise_cmd)<br />
sunset=$($sunset_cmd)<br />
<br />
current_time=$(date +%s)<br />
sunrise=$(date -d"$sunrise" +%s)<br />
sunset=$(date -d"$sunset" +%s)<br />
<br />
# calulate time in secs until (next) sunrise<br />
sleep_sunrise=$(( $sunrise - $current_time )) # this morning<br />
[ $sleep_sunrise -lt 0 ] &&<br />
sleep_sunrise=$(( $sleep_sunrise + 86400 )) # next morning<br />
<br />
# wait for daylight only<br />
if [ $current_time -lt $sunrise -o $current_time -gt $sunset ]; then<br />
log_info sleep $sleep_sunrise<br />
sleep $sleep_sunrise<br />
fi<br />
}<br />
<br />
# wait only before sunset<br />
wait_before_sunset ()<br />
{<br />
sunset=$($sunset_cmd)<br />
<br />
current_time=$(date +%s)<br />
sunset=$(date -d"$sunset" +%s)<br />
<br />
# calulate time in secs until next sunset<br />
sleep_sunset=$(( $sunset - $current_time ))<br />
<br />
# wait before sunset only<br />
if [ $sleep_sunset -gt 0 ]; then<br />
log_info sleep $sleep_sunset<br />
sleep $sleep_sunset<br />
fi<br />
}<br />
<br />
# ------------------------<br />
# MAIN: Script starts here<br />
# ------------------------<br />
<br />
# sunset_stop: wait until sunset before we stop running instances<br />
[ "$arg" = "sunset_stop" ] &&<br />
wait_before_sunset<br />
<br />
# stop any runnning deye_inverter_mqtt processes<br />
deye_inverter_mqtt_stop<br />
<br />
# stop: don't start again<br />
[ "$arg" = "stop" ] && exit 0<br />
<br />
# reset and start deye_mqtt_loop<br />
# check every 15 min for running deye_inverter_mqtt<br />
while :; do<br />
<br />
# wait for (next) daylight<br />
wait_for_sunrise<br />
<br />
# setting date/time also resets daily power register<br />
init_deye_inverter<br />
<br />
# start deye_inverter_mqtt<br />
get_deye_mqtt_pid<br />
if [ -z "$deye_mqtt_pid" ]; then<br />
deye_inverter_mqtt_start<br />
fi<br />
<br />
# wait 15 min<br />
sleep 900<br />
done<br />
</pre><br />
<br />
=== Contrab und /etc/rc.local ===<br />
Mein Crontab Eintrag:<br />
<pre># stop DEYE monitoring after sunset (FHEM)<br />
0 18 * * * /root/sbin/deye_mqtt_loop.sh sunset_stop 2>/dev/null<br />
</pre><br />
und /etc/rc.local:<br />
<pre># start monitoring of PV-System: DEYE inverter<br />
$(sleep 10; /root/sbin/deye_mqtt_loop.sh 2>dev/null)&<br />
</pre><br />
<br />
== Dashboard ==<br />
So sieht das aus, wenn es den ganzen Tag im April regnet und bedeckt ist:<br />
[[Datei:Solar Bedeckt Regen.jpg|midi]]<br />
<br />
Die Eigenverbrauchsquote habe ich mittels Grafana ermittelt:<br />
[[Datei:Solar Eigenverbrauch.jpg|midi|Eigenverbrauchsquote]]</div>
Neobiker
http://neobiker.de/wiki/index.php?title=DEYE_Wechselrichter&diff=2148
DEYE Wechselrichter
2024-02-07T17:05:52Z
<p>Neobiker: </p>
<hr />
<div>= Balkonkraftwerk: DEYE Wechselrichter ohne Cloud auslesen und per MQTT monitoren =<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
Der WR bietet verschiedene Möglichkeiten zur Konfiguration und Abfrage der aktuellen Werte:<br />
* einen internen Webserver (http://10.10.10.254)<br />
* die Cloud Anbindung zu Solarman (Solarman Smart App)<br />
* (angepasstes) Modbus Protokoll über Port 8899<br />
* AT+ Befehle über Port 48899<br />
<br />
Ich verwende das Modbus Protokoll zum auslesen des WR, das <br />
# effizient/schnell ist (Performance)<br />
# ohne Internetverbindung auskommt<br />
<br />
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.<br />
<br />
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.<br />
<br />
== Wechselrichter Eigenheiten ==<br />
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. <br />
<br />
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.<br />
<br />
== Installation ==<br />
Das Projekt von Github laden und in einem Verzeichnis speichern, '''Python''' ist normalerweise ja schon installiert. <br />
<br />
<pre>git clone https://github.com/kbialek/deye-inverter-mqtt<br />
pip install paho-mqtt<br />
cd deye-inverter-mqtt</pre><br />
<br />
Unter Debian habe ich im Skript '''deye_cli.sh''' python durch python3 ersetzen müssen:<br />
<pre>#!/bin/bash<br />
set -a; source config.env; set +a<br />
python3 deye_cli.py "$@"<br />
</pre><br />
<br />
=== config.env ===<br />
Die Konfiguration meines DEYE Wechselrichters vom Typ Micro-Inverter:<br />
'''config.env'''<br />
<pre>DEYE_LOGGER_IP_ADDRESS=<IP Wechselrichter im WLAN><br />
DEYE_LOGGER_PORT=8899<br />
DEYE_LOGGER_SERIAL_NUMBER=<Seriennummer des WR><br />
<br />
MQTT_HOST=<IP von MQTT Server><br />
MQTT_PORT=1883<br />
MQTT_USERNAME=<br />
MQTT_PASSWORD=<br />
MQTT_TOPIC_PREFIX=deye<br />
<br />
LOG_LEVEL=ERROR<br />
DEYE_DATA_READ_INTERVAL=60<br />
DEYE_METRIC_GROUPS=micro<br />
</pre><br />
<br />
=== deye_inverter.sh === <br />
Ausserdem habe ich ein kleines ''Wrapper Skript'' geschrieben, um '''Daten des WR''' einfacher '''lesen und schreiben''' zu können:<br><br />
<br />
'''deye_inverter.sh''' [--check <pause>] --read <register> | --write <register> <value><br />
<br />
<pre>#!/bin/bash<br />
# Read / Write DEYE Inverters<br />
# via deye_inverter_mqtt python package<br />
# https://github.com/kbialek/deye-inverter-mqtt<br />
<br />
retries=2 # retry deye_inverter command multiple times<br />
sleep=60 # sleep time between retries<br />
<br />
# locate script in deye_inverter_mqtt directory<br />
cd $(dirname $0)<br />
if [ ! -f ./deye_cli.sh ]; then<br />
echo "Error: ./deye_cli.sh not found."<br />
echo " Please move $(basename $0) in deye-inverter-mqtt directory."<br />
exit 1<br />
fi<br />
. ./config.env<br />
<br />
log_info ()<br />
{<br />
if [ ${LOG_LEVEL} = "INFO" ]; then<br />
logger -t $(basename $0) $@<br />
fi<br />
}<br />
<br />
log_error ()<br />
{<br />
logger -t $(basename $0) $@<br />
echo 1>&2 $@<br />
}<br />
<br />
# read parameters: mode [rw], register, value and optional -c <check pause><br />
pause=0<br />
while [ $# -gt 0 ]; do<br />
case $1 in<br />
-c|--check) shift<br />
[ $# -ge 3 ] || exit 1<br />
pause=$1<br />
;;<br />
-w|--write) mode=w<br />
shift<br />
[ $# -eq 2 ] || exit 1<br />
reg=$1<br />
val=$2<br />
shift<br />
;;<br />
-r|--read) mode=r<br />
shift<br />
[ $# -eq 1 ] || exit 1<br />
reg=$1<br />
;;<br />
esac<br />
shift<br />
done<br />
<br />
# handle offline deye_inverter by<br />
# ${pause} > 0 -> endless loop<br />
# ${pause} = 0 -> error after ${retries}<br />
while true; do<br />
<br />
# try deye_cli.sh $retries times every $sleep secs<br />
check=${retries}<br />
while [ ${check} -gt 0 ]; do<br />
<br />
case ${mode} in<br />
r) result=$(./deye_cli.sh $mode $reg | grep 'int: ')<br />
if [ -n "$result" ]; then<br />
echo "$result"<br />
log_info -t $0 deye_cli.sh $mode $reg<br />
exit 0<br />
fi<br />
;;<br />
w) result=$(./deye_cli.sh $mode $reg $val)<br />
if [ "$result" = "Ok" ]; then<br />
log_info -t $0 deye_cli.sh $mode $reg $val<br />
exit 0<br />
fi<br />
;;<br />
esac<br />
<br />
# wait ${sleep} or ${pause} secs<br />
sleep $(( ${pause} ? ${sleep} : ${pause} ))<br />
<br />
# try ${retries} times<br />
(( check-- ))<br />
done<br />
<br />
# error, or retry endless until wakeup of deye_inverter<br />
if [ ${pause} -eq 0 ]; then<br />
log_error "Error: deye_cli.sh $mode $reg $val failed."<br />
exit 1<br />
fi<br />
<br />
done<br />
</pre><br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
Folgendes Skript starte ich jeden Morgen um 04:00 Uhr per Cron (und in rc.local fürs booten):<br />
<br />
=== deye_mqtt_loop.sh ===<br />
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.<br />
<br />
Ausserdem setzt es beim Start den Tageszähler des Wechselrichters bedarfsweise zurück, bevor die Werte des DEYE Wechselrichters per Endlosschleife abgefragt werden.<br />
<br />
<pre>#!/bin/bash<br />
# reset Daily_Power of DEYE Inverters<br />
# via deye_inverter_mqtt python package<br />
# https://github.com/kbialek/deye-inverter-mqtt<br />
<br />
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin<br />
<br />
# config:<br />
deye_inverter_dir=/root/deye-inverter-mqtt<br />
fhem_dir=/opt/fhem<br />
horizon="-2" # sunset CIVIL=-6, REAL=0<br />
<br />
debug=info<br />
<br />
myPID=$$<br />
myname=$(basename $0)<br />
arg="$1"<br />
<br />
deye_inverter_sh=$deye_inverter_dir/deye_inverter.sh<br />
deye_inverter_mqtt_cfg=$deye_inverter_dir/config.env<br />
deye_inverter_mqtt_cmd="python3 deye_docker_entrypoint.py"<br />
<br />
sunrise_cmd="perl $fhem_dir/fhem.pl localhost:7072 {sunrise_abs('HORIZON=$horizon')}"<br />
sunset_cmd="perl $fhem_dir/fhem.pl localhost:7072 {sunset_abs('HORIZON=$horizon')}"<br />
<br />
# installation:<br />
# locate FHEM, used for sunrise(), sunset()<br />
if [ ! -x ${fhem_dir/fhem.pl} ]; then<br />
echo "Error: ${fhem_dir} not found."<br />
echo " Is FHEM installed in $fhem_dir ?"<br />
exit 1<br />
fi<br />
<br />
# locate script in deye_inverter_mqtt directory<br />
if [ ! -f ${deye_inverter_mqtt_cfg} ]; then<br />
echo "Error: ${deye_inverter_mqtt_cfg} not found."<br />
echo " Please update \$deye_inverter_sh path in $0"<br />
exit 1<br />
fi<br />
<br />
# read my config<br />
set -a; source ${deye_inverter_mqtt_cfg}; set +a<br />
<br />
[ "$debug" = "full" ] && set -x<br />
<br />
# ----- functions () ------------------------------------<br />
log_info ()<br />
{<br />
[ "$debug" = "yes" -o "$debug" = "true" -o "$debug" = "info" ] &&<br />
echo $myname: $@<br />
[ ${LOG_LEVEL} = "INFO" ] && logger -t $myname $@<br />
}<br />
<br />
log_error ()<br />
{<br />
logger -t $myname $@<br />
echo 1>&2 $@<br />
}<br />
<br />
# kill deye_inverter_loop ()<br />
kill_old_instance ()<br />
{<br />
# kill old instances of me<br />
my_PS=$(ps -ef | grep -v grep | grep $myname | cut -b10-26,53- | grep -v $myPID | awk '{print $1}')<br />
for ps in $my_PS; do<br />
[ $ps -lt $myPID ] && kill $ps<br />
done<br />
}<br />
<br />
# get deye_inverter_mqtt PIDs<br />
get_deye_mqtt_pid ()<br />
{<br />
deye_mqtt_pid=$(ps ax | grep -v grep | grep "${deye_inverter_mqtt_cmd}" | awk '{print $1}')<br />
}<br />
<br />
# kill deye_inverter_instance ()<br />
kill_deye_inverter_instance ()<br />
{<br />
get_deye_mqtt_pid<br />
if [ ! -z "${deye_mqtt_pid}" ]; then<br />
kill ${deye_mqtt_pid}<br />
log_info "Info: KILL running deye_mqtt ${deye_mqtt_pid}"<br />
fi<br />
<br />
# kill any running deye requests<br />
deye_ps=$(ps ax | grep -v grep | grep "$deye_inverter_sh " | awk '{print $1}')<br />
[ ! -z "$deye_ps" ] && kill $deye_ps<br />
}<br />
<br />
# stop all runnning instances<br />
deye_inverter_mqtt_stop ()<br />
{<br />
kill_old_instance<br />
kill_deye_inverter_instance<br />
}<br />
<br />
# start deye_inverter_mqtt<br />
deye_inverter_mqtt_start ()<br />
{<br />
log_info "Deye MQTT start"<br />
cd ${deye_inverter_dir}<br />
${deye_inverter_mqtt_cmd}<br />
}<br />
<br />
# store actual date + time into vars<br />
set_date_time_vars ()<br />
{<br />
year=$(date +%y)<br />
month=$(date +%m | sed 's/^0//g')<br />
day=$(date +%d | sed 's/^0//g')<br />
hour=$(date +%H | sed 's/^0//g')<br />
minute=$(date +%m | sed 's/^0//g')<br />
second=$(date +%S | sed 's/^0//g')<br />
}<br />
<br />
# read year_month register 0x16 = 22<br />
read_year_month ()<br />
{<br />
${deye_inverter_sh} -r 22 | awk '/^int: .*/ {split($2,i,","); print i[1]}'<br />
}<br />
<br />
# read day register 0x17 = 23<br />
read_day ()<br />
{<br />
${deye_inverter_sh} -r 23 | awk '/^int: .*/ {split($2,i,","); print i[1]}'<br />
}<br />
<br />
# read Daily power register 0x3c = 60<br />
read_daily_Power ()<br />
{<br />
${deye_inverter_sh} -r 60 | awk '/^int: .*/ {split($2,i,","); print i[1]}'<br />
}<br />
<br />
# read actual power register 0x56 = 86<br />
read_actual_Power ()<br />
{<br />
# doubleRegisterSensor("AC Active Power", 0x56, 0.1, mqtt_topic_suffix='ac/active_power', groups=['string', 'micro'])<br />
# (int.from_bytes(high_word, 'big') * 65536 + int.from_bytes(low_word, 'big')) * self.factor<br />
# val86=${deye_inverter_sh} -r 86 | sed 's/int: //g' | cut -d, -f1<br />
# val87=${deye_inverter_sh} -r 87 | sed 's/int: //g' | cut -d, -f1<br />
# echo $(( (${val87} * 65536 + ${val86}) * 0.1 ))<br />
<br />
# simple value is enough for our purpose<br />
${deye_inverter_sh} -c 600 -r 86 | awk '/^int: .*/ {split($2,i,","); print i[1]}'<br />
}<br />
<br />
# write actual date/time into deye inverter<br />
init_deye_inverter ()<br />
{<br />
# daily reset phase<br />
daily_reset=init<br />
<br />
# ensure by loop, that all register values are written<br />
while [ ${daily_reset} != "done" ]; do<br />
<br />
# wait until we have enough power<br />
while [ "0$(read_actual_Power)" -le 10 ]; do<br />
sleep 600<br />
done<br />
<br />
# read Daily power register<br />
dailyPower=$(read_daily_Power)<br />
<br />
# read (old) year_month and day<br />
val22_old=$(read_year_month)<br />
val23_day=$(read_day)<br />
log_info "date/time update initialized"<br />
<br />
# set variables year, month, day, hour, minute, second<br />
set_date_time_vars<br />
<br />
# calculate register 22-24 with date and time vars<br />
val22=$(( ${year} * 256 + ${month} ))<br />
val23=$(( ${day} * 256 + ${hour} ))<br />
val24=$(( ${minute} * 256 + ${second} ))<br />
<br />
# reset power/date/time only once a day or exit loop<br />
[ "$dailyPower" -eq 0 -a "${val22_old}" = "${val22}" -a "${val23_day}" = "${day}" ] && break<br />
<br />
# reset daily_power by<br />
# setting register 22-24 with actual date + time<br />
${deye_inverter_sh} -w 22 $val22 || continue<br />
${deye_inverter_sh} -w 23 $val23 || continue<br />
${deye_inverter_sh} -w 24 $val24 || continue<br />
<br />
# test success<br />
# read Daily power register, should be resetted to 0 now<br />
dailyPower=$(read_daily_Power)<br />
<br />
# exit loop if power is updated, else log that reset failed<br />
if [ "$dailyPower" -eq 0 ]; then<br />
daily_reset=done<br />
<br />
### skip loop here, reset doesn't work always :-(<br />
# elif [ $daily_reset = "init" ]; then<br />
# daily_reset=failed<br />
# sleep 300<br />
<br />
else<br />
log_info "daily power reset failed"<br />
break<br />
fi<br />
done<br />
log_info "date/time reset done"<br />
}<br />
<br />
# wait for (next) daylight<br />
wait_for_sunrise ()<br />
{<br />
sunrise=$($sunrise_cmd)<br />
sunset=$($sunset_cmd)<br />
<br />
current_time=$(date +%s)<br />
sunrise=$(date -d"$sunrise" +%s)<br />
sunset=$(date -d"$sunset" +%s)<br />
<br />
# calulate time in secs until (next) sunrise<br />
sleep_sunrise=$(( $sunrise - $current_time )) # this morning<br />
[ $sleep_sunrise -lt 0 ] &&<br />
sleep_sunrise=$(( $sleep_sunrise + 86400 )) # next morning<br />
<br />
# wait for daylight only<br />
if [ $current_time -lt $sunrise -o $current_time -gt $sunset ]; then<br />
log_info sleep $sleep_sunrise<br />
sleep $sleep_sunrise<br />
fi<br />
}<br />
<br />
# wait only before sunset<br />
wait_before_sunset ()<br />
{<br />
sunset=$($sunset_cmd)<br />
<br />
current_time=$(date +%s)<br />
sunset=$(date -d"$sunset" +%s)<br />
<br />
# calulate time in secs until next sunset<br />
sleep_sunset=$(( $sunset - $current_time ))<br />
<br />
# wait before sunset only<br />
if [ $sleep_sunset -gt 0 ]; then<br />
log_info sleep $sleep_sunset<br />
sleep $sleep_sunset<br />
fi<br />
}<br />
<br />
# ------------------------<br />
# MAIN: Script starts here<br />
# ------------------------<br />
<br />
# sunset_stop: wait until sunset before we stop running instances<br />
[ "$arg" = "sunset_stop" ] &&<br />
wait_before_sunset<br />
<br />
# stop any runnning deye_inverter_mqtt processes<br />
deye_inverter_mqtt_stop<br />
<br />
# stop: don't start again<br />
[ "$arg" = "stop" ] && exit 0<br />
<br />
# reset and start deye_mqtt_loop<br />
# check every 15 min for running deye_inverter_mqtt<br />
while :; do<br />
<br />
# wait for (next) daylight<br />
wait_for_sunrise<br />
<br />
# setting date/time also resets daily power register<br />
init_deye_inverter<br />
<br />
# start deye_inverter_mqtt<br />
get_deye_mqtt_pid<br />
if [ -z "$deye_mqtt_pid" ]; then<br />
deye_inverter_mqtt_start<br />
fi<br />
<br />
# wait 15 min<br />
sleep 900<br />
done<br />
</pre><br />
<br />
=== Contrab und /etc/rc.local ===<br />
Mein Crontab Eintrag:<br />
<pre># stop DEYE monitoring after sunset (FHEM)<br />
0 18 * * * /root/sbin/deye_mqtt_loop.sh sunset_stop 2>/dev/null<br />
</pre><br />
und /etc/rc.local:<br />
<pre># start monitoring of PV-System: DEYE inverter<br />
$(sleep 10; /root/sbin/deye_mqtt_loop.sh 2>dev/null)&<br />
</pre><br />
<br />
== Dashboard ==<br />
So sieht das aus, wenn es den ganzen Tag im April regnet und bedeckt ist:<br />
[[Datei:Solar Bedeckt Regen.jpg|midi]]<br />
<br />
Die Eigenverbrauchsquote habe ich mittels Grafana ermittelt:<br />
[[Datei:Solar Eigenverbrauch.jpg|midi|Eigenverbrauchsquote]]</div>
Neobiker
http://neobiker.de/wiki/index.php?title=DEYE_Wechselrichter&diff=2147
DEYE Wechselrichter
2024-02-04T17:59:05Z
<p>Neobiker: /* deye_mqtt_loop.sh */</p>
<hr />
<div>= Balkonkraftwerk: DEYE Wechselrichter ohne Cloud auslesen und per MQTT monitoren =<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
Der WR bietet verschiedene Möglichkeiten zur Konfiguration und Abfrage der aktuellen Werte:<br />
* einen internen Webserver (http://10.10.10.254)<br />
* die Cloud Anbindung zu Solarman (Solarman Smart App)<br />
* (angepasstes) Modbus Protokoll über Port 8899<br />
* AT+ Befehle über Port 48899<br />
<br />
Ich verwende das Modbus Protokoll zum auslesen des WR, das <br />
# effizient/schnell ist (Performance)<br />
# ohne Internetverbindung auskommt<br />
<br />
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.<br />
<br />
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.<br />
<br />
== Wechselrichter Eigenheiten ==<br />
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. <br />
<br />
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.<br />
<br />
== Installation ==<br />
Das Projekt von Github laden und in einem Verzeichnis speichern, '''Python''' ist normalerweise ja schon installiert. <br />
<br />
<pre>git clone https://github.com/kbialek/deye-inverter-mqtt<br />
pip install paho-mqtt<br />
cd deye-inverter-mqtt</pre><br />
<br />
Unter Debian habe ich im Skript '''deye_cli.sh''' python durch python3 ersetzen müssen:<br />
<pre>#!/bin/bash<br />
set -a; source config.env; set +a<br />
python3 deye_cli.py "$@"<br />
</pre><br />
<br />
=== config.env ===<br />
Die Konfiguration meines DEYE Wechselrichters vom Typ Micro-Inverter:<br />
'''config.env'''<br />
<pre>DEYE_LOGGER_IP_ADDRESS=<IP Wechselrichter im WLAN><br />
DEYE_LOGGER_PORT=8899<br />
DEYE_LOGGER_SERIAL_NUMBER=<Seriennummer des WR><br />
<br />
MQTT_HOST=<IP von MQTT Server><br />
MQTT_PORT=1883<br />
MQTT_USERNAME=<br />
MQTT_PASSWORD=<br />
MQTT_TOPIC_PREFIX=deye<br />
<br />
LOG_LEVEL=ERROR<br />
DEYE_DATA_READ_INTERVAL=60<br />
DEYE_METRIC_GROUPS=micro<br />
</pre><br />
<br />
=== deye_inverter.sh === <br />
Ausserdem habe ich ein kleines ''Wrapper Skript'' geschrieben, um '''Daten des WR''' einfacher '''lesen und schreiben''' zu können:<br><br />
<br />
'''deye_inverter.sh''' [--check <pause>] --read <register> | --write <register> <value><br />
<br />
<pre>#!/bin/bash<br />
# Read / Write DEYE Inverters<br />
# via deye_inverter_mqtt python package<br />
# https://github.com/kbialek/deye-inverter-mqtt<br />
<br />
retries=2 # retry deye_inverter command multiple times<br />
sleep=60 # sleep time between retries<br />
<br />
# locate script in deye_inverter_mqtt directory<br />
cd $(dirname $0)<br />
if [ ! -f ./deye_cli.sh ]; then<br />
echo "Error: ./deye_cli.sh not found."<br />
echo " Please move $(basename $0) in deye-inverter-mqtt directory."<br />
exit 1<br />
fi<br />
. ./config.env<br />
<br />
log_info ()<br />
{<br />
if [ ${LOG_LEVEL} = "INFO" ]; then<br />
logger -t $(basename $0) $@<br />
fi<br />
}<br />
<br />
log_error ()<br />
{<br />
logger -t $(basename $0) $@<br />
echo 1>&2 $@<br />
}<br />
<br />
# read parameters: mode [rw], register, value and optional -c <check pause><br />
pause=0<br />
while [ $# -gt 0 ]; do<br />
case $1 in<br />
-c|--check) shift<br />
[ $# -ge 3 ] || exit 1<br />
pause=$1<br />
;;<br />
-w|--write) mode=w<br />
shift<br />
[ $# -eq 2 ] || exit 1<br />
reg=$1<br />
val=$2<br />
shift<br />
;;<br />
-r|--read) mode=r<br />
shift<br />
[ $# -eq 1 ] || exit 1<br />
reg=$1<br />
;;<br />
esac<br />
shift<br />
done<br />
<br />
# handle offline deye_inverter by<br />
# ${pause} > 0 -> endless loop<br />
# ${pause} = 0 -> error after ${retries}<br />
while true; do<br />
<br />
# try deye_cli.sh $retries times every $sleep secs<br />
check=${retries}<br />
while [ ${check} -gt 0 ]; do<br />
<br />
case ${mode} in<br />
r) result=$(./deye_cli.sh $mode $reg | grep 'int: ')<br />
if [ -n "$result" ]; then<br />
echo "$result"<br />
log_info -t $0 deye_cli.sh $mode $reg<br />
exit 0<br />
fi<br />
;;<br />
w) result=$(./deye_cli.sh $mode $reg $val)<br />
if [ "$result" = "Ok" ]; then<br />
log_info -t $0 deye_cli.sh $mode $reg $val<br />
exit 0<br />
fi<br />
;;<br />
esac<br />
<br />
# wait ${sleep} or ${pause} secs<br />
sleep $(( ${pause} ? ${sleep} : ${pause} ))<br />
<br />
# try ${retries} times<br />
(( check-- ))<br />
done<br />
<br />
# error, or retry endless until wakeup of deye_inverter<br />
if [ ${pause} -eq 0 ]; then<br />
log_error "Error: deye_cli.sh $mode $reg $val failed."<br />
exit 1<br />
fi<br />
<br />
done<br />
</pre><br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
Folgendes Skript starte ich jeden Morgen um 04:00 Uhr per Cron (und in rc.local fürs booten):<br />
<br />
=== deye_mqtt_loop.sh ===<br />
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.<br />
<br />
Ausserdem setzt es beim Start den Tageszähler des Wechselrichters bedarfsweise zurück, bevor die Werte des DEYE Wechselrichters per Endlosschleife abgefragt werden.<br />
<br />
<pre>#!/bin/bash<br />
# reset Daily_Power of DEYE Inverters<br />
# via deye_inverter_mqtt python package<br />
# https://github.com/kbialek/deye-inverter-mqtt<br />
<br />
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin<br />
<br />
# config:<br />
deye_inverter_dir=/root/deye-inverter-mqtt<br />
fhem_dir=/opt/fhem<br />
horizon="-2" # sunset CIVIL=-6, REAL=0<br />
<br />
debug=info<br />
<br />
myPID=$$<br />
myname=$(basename $0)<br />
arg="$1"<br />
<br />
deye_inverter_sh=$deye_inverter_dir/deye_inverter.sh<br />
deye_inverter_mqtt_cfg=$deye_inverter_dir/config.env<br />
deye_inverter_mqtt_cmd="python3 deye_docker_entrypoint.py"<br />
<br />
sunrise_cmd="perl $fhem_dir/fhem.pl localhost:7072 {sunrise_abs('HORIZON=$horizon')}"<br />
sunset_cmd="perl $fhem_dir/fhem.pl localhost:7072 {sunset_abs('HORIZON=$horizon')}"<br />
<br />
# installation:<br />
# locate FHEM, used for sunrise(), sunset()<br />
if [ ! -x ${fhem_dir/fhem.pl} ]; then<br />
echo "Error: ${fhem_dir} not found."<br />
echo " Is FHEM installed in $fhem_dir ?"<br />
exit 1<br />
fi<br />
<br />
# locate script in deye_inverter_mqtt directory<br />
if [ ! -f ${deye_inverter_mqtt_cfg} ]; then<br />
echo "Error: ${deye_inverter_mqtt_cfg} not found."<br />
echo " Please update \$deye_inverter_sh path in $0"<br />
exit 1<br />
fi<br />
<br />
# read my config<br />
set -a; source ${deye_inverter_mqtt_cfg}; set +a<br />
<br />
[ "$debug" = "full" ] && set -x<br />
<br />
# ----- functions () ------------------------------------<br />
log_info ()<br />
{<br />
[ "$debug" = "yes" -o "$debug" = "true" -o "$debug" = "info" ] &&<br />
echo $myname: $@<br />
[ ${LOG_LEVEL} = "INFO" ] && logger -t $myname $@<br />
}<br />
<br />
log_error ()<br />
{<br />
logger -t $myname $@<br />
echo 1>&2 $@<br />
}<br />
<br />
# store actual date + time into vars<br />
set_date_time_vars ()<br />
{<br />
year=$(date +%y)<br />
month=$(date +%m | sed 's/^0//g')<br />
day=$(date +%d | sed 's/^0//g')<br />
hour=$(date +%H | sed 's/^0//g')<br />
minute=$(date +%m | sed 's/^0//g')<br />
second=$(date +%S | sed 's/^0//g')<br />
}<br />
<br />
# kill deye_inverter_loop ()<br />
kill_old_instance ()<br />
{<br />
# kill old instances of me<br />
my_PS=$(ps ax | grep -v grep | grep "${myname}" | awk '{print $1}')<br />
for ps in $my_PS; do<br />
[ $ps -lt $myPID ] && kill $ps<br />
done<br />
}<br />
<br />
# get deye_inverter_mqtt PIDs<br />
get_deye_mqtt_pid ()<br />
{<br />
deye_mqtt_pid=$(ps ax | grep -v grep | grep "${deye_inverter_mqtt_cmd}" | awk '{print $1}')<br />
}<br />
<br />
# kill deye_inverter_instance ()<br />
kill_deye_inverter_instance ()<br />
{<br />
get_deye_mqtt_pid<br />
if [ ! -z "${deye_mqtt_pid}" ]; then<br />
kill ${deye_mqtt_pid}<br />
log_info "Info: KILL running deye_mqtt ${deye_mqtt_pid}"<br />
fi<br />
<br />
# kill any running deye requests<br />
deye_ps=$(ps ax | grep -v grep | grep "$deye_inverter_sh " | awk '{print $1}')<br />
[ ! -z "$deye_ps" ] && kill $deye_ps<br />
}<br />
<br />
# stop all runnning instances<br />
deye_inverter_mqtt_stop ()<br />
{<br />
kill_old_instance<br />
kill_deye_inverter_instance<br />
}<br />
<br />
# start deye_inverter_mqtt<br />
deye_inverter_mqtt_start ()<br />
{<br />
log_info "Deye MQTT start"<br />
cd ${deye_inverter_dir}<br />
${deye_inverter_mqtt_cmd}<br />
}<br />
<br />
# read year_month register 0x16 = 22<br />
read_year_month ()<br />
{<br />
${deye_inverter_sh} -r 22 | awk '/^int: .*/ {split($2,i,","); print i[1]}'<br />
}<br />
<br />
# read day register 0x17 = 23<br />
read_day ()<br />
{<br />
${deye_inverter_sh} -r 23 | awk '/^int: .*/ {split($2,i,","); print i[1]}'<br />
}<br />
<br />
# read Daily power register 0x3c = 60<br />
read_daily_Power ()<br />
{<br />
${deye_inverter_sh} -r 60 | awk '/^int: .*/ {split($2,i,","); print i[1]}'<br />
}<br />
<br />
# read actual power register 0x56 = 86<br />
read_actual_Power ()<br />
{<br />
# doubleRegisterSensor("AC Active Power", 0x56, 0.1, mqtt_topic_suffix='ac/active_power', groups=['string', 'micro'])<br />
# (int.from_bytes(high_word, 'big') * 65536 + int.from_bytes(low_word, 'big')) * self.factor<br />
# val86=${deye_inverter_sh} -r 86 | sed 's/int: //g' | cut -d, -f1<br />
# val87=${deye_inverter_sh} -r 87 | sed 's/int: //g' | cut -d, -f1<br />
# echo $(( (${val87} * 65536 + ${val86}) * 0.1 ))<br />
<br />
# simple value is enough for our purpose<br />
${deye_inverter_sh} -c 600 -r 86 | awk '/^int: .*/ {split($2,i,","); print i[1]}'<br />
}<br />
<br />
# write actual date/time into deye inverter<br />
init_deye_inverter ()<br />
{<br />
# daily reset phase<br />
daily_reset=init<br />
<br />
# ensure by loop, that all register values are written<br />
while [ ${daily_reset} != "done" ]; do<br />
<br />
# wait until we have enough power<br />
while [ "0$(read_actual_Power)" -le 10 ]; do<br />
sleep 600<br />
done<br />
<br />
# read Daily power register<br />
dailyPower=$(read_daily_Power)<br />
<br />
# read (old) year_month and day<br />
val22_old=$(read_year_month)<br />
val23_day=$(read_day)<br />
log_info "date/time update initialized"<br />
<br />
# set variables year, month, day, hour, minute, second<br />
set_date_time_vars<br />
<br />
# calculate register 22-24 with date and time vars<br />
val22=$(( ${year} * 256 + ${month} ))<br />
val23=$(( ${day} * 256 + ${hour} ))<br />
val24=$(( ${minute} * 256 + ${second} ))<br />
<br />
# reset power/date/time only once a day or exit loop<br />
[ "$dailyPower" -eq 0 -a "${val22_old}" = "${val22}" -a "${val23_day}" = "${day}" ] && break<br />
<br />
# reset daily_power by<br />
# setting register 22-24 with actual date + time<br />
${deye_inverter_sh} -w 22 $val22 || continue<br />
${deye_inverter_sh} -w 23 $val23 || continue<br />
${deye_inverter_sh} -w 24 $val24 || continue<br />
<br />
# test success<br />
# read Daily power register, should be resetted to 0 now<br />
dailyPower=$(read_daily_Power)<br />
<br />
# exit loop if power is updated, else log that reset failed<br />
if [ "$dailyPower" -eq 0 ]; then<br />
daily_reset=done<br />
<br />
### skip loop here, reset doesn't work always :-(<br />
# elif [ $daily_reset = "init" ]; then<br />
# daily_reset=failed<br />
# sleep 300<br />
<br />
else<br />
log_info "daily power reset failed"<br />
break<br />
fi<br />
done<br />
log_info "date/time reset done"<br />
}<br />
<br />
# wait for (next) daylight<br />
wait_for_sunrise ()<br />
{<br />
sunrise=$($sunrise_cmd)<br />
sunset=$($sunset_cmd)<br />
<br />
current_time=$(date +%s)<br />
sunrise=$(date -d"$sunrise" +%s)<br />
sunset=$(date -d"$sunset" +%s)<br />
<br />
# calulate time in secs until (next) sunrise<br />
sleep_sunrise=$(( $sunrise - $current_time )) # this morning<br />
[ $sleep_sunrise -lt 0 ] &&<br />
sleep_sunrise=$(( $sleep_sunrise + 86400 )) # next morning<br />
<br />
# wait for daylight only<br />
if [ $current_time -lt $sunrise -o $current_time -gt $sunset ]; then<br />
log_info sleep $sleep_sunrise<br />
sleep $sleep_sunrise<br />
fi<br />
}<br />
<br />
# wait only before sunset<br />
wait_before_sunset ()<br />
{<br />
sunset=$($sunset_cmd)<br />
<br />
current_time=$(date +%s)<br />
sunset=$(date -d"$sunset" +%s)<br />
<br />
# calulate time in secs until next sunset<br />
sleep_sunset=$(( $sunset - $current_time ))<br />
<br />
# wait before sunset only<br />
if [ $sleep_sunset -gt 0 ]; then<br />
log_info sleep $sleep_sunset<br />
sleep $sleep_sunset<br />
fi<br />
}<br />
<br />
# ------------------------<br />
# MAIN: Script starts here<br />
# ------------------------<br />
<br />
# sunset_stop: wait until sunset before we stop running instances<br />
[ "$arg" = "sunset_stop" ] &&<br />
wait_before_sunset<br />
<br />
# stop any runnning deye_inverter_mqtt processes<br />
deye_inverter_mqtt_stop<br />
<br />
# stop: don't start again<br />
[ "$arg" = "stop" ] && exit 0<br />
<br />
# reset and start deye_mqtt_loop<br />
# check every 15 min for running deye_inverter_mqtt<br />
while :; do<br />
<br />
# wait for (next) daylight<br />
wait_for_sunrise<br />
<br />
# setting date/time also resets daily power register<br />
init_deye_inverter<br />
<br />
# start deye_inverter_mqtt<br />
get_deye_mqtt_pid<br />
if [ -z "$deye_mqtt_pid" ]; then<br />
deye_inverter_mqtt_start<br />
fi<br />
<br />
# wait 15 min<br />
sleep 900<br />
done<br />
</pre><br />
<br />
=== Contrab und /etc/rc.local ===<br />
Mein Crontab Eintrag:<br />
<pre># stop DEYE monitoring after sunset (FHEM)<br />
0 18 * * * /root/sbin/deye_mqtt_loop.sh sunset_stop 2>/dev/null<br />
</pre><br />
und /etc/rc.local:<br />
<pre># start monitoring of PV-System: DEYE inverter<br />
$(sleep 10; /root/sbin/deye_mqtt_loop.sh 2>dev/null)&<br />
</pre><br />
<br />
== Dashboard ==<br />
So sieht das aus, wenn es den ganzen Tag im April regnet und bedeckt ist:<br />
[[Datei:Solar Bedeckt Regen.jpg|midi]]<br />
<br />
Die Eigenverbrauchsquote habe ich mittels Grafana ermittelt:<br />
[[Datei:Solar Eigenverbrauch.jpg|midi|Eigenverbrauchsquote]]</div>
Neobiker
http://neobiker.de/wiki/index.php?title=DEYE_Wechselrichter&diff=2146
DEYE Wechselrichter
2024-02-04T17:11:44Z
<p>Neobiker: /* deye_inverter.sh */</p>
<hr />
<div>= Balkonkraftwerk: DEYE Wechselrichter ohne Cloud auslesen und per MQTT monitoren =<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
Der WR bietet verschiedene Möglichkeiten zur Konfiguration und Abfrage der aktuellen Werte:<br />
* einen internen Webserver (http://10.10.10.254)<br />
* die Cloud Anbindung zu Solarman (Solarman Smart App)<br />
* (angepasstes) Modbus Protokoll über Port 8899<br />
* AT+ Befehle über Port 48899<br />
<br />
Ich verwende das Modbus Protokoll zum auslesen des WR, das <br />
# effizient/schnell ist (Performance)<br />
# ohne Internetverbindung auskommt<br />
<br />
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.<br />
<br />
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.<br />
<br />
== Wechselrichter Eigenheiten ==<br />
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. <br />
<br />
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.<br />
<br />
== Installation ==<br />
Das Projekt von Github laden und in einem Verzeichnis speichern, '''Python''' ist normalerweise ja schon installiert. <br />
<br />
<pre>git clone https://github.com/kbialek/deye-inverter-mqtt<br />
pip install paho-mqtt<br />
cd deye-inverter-mqtt</pre><br />
<br />
Unter Debian habe ich im Skript '''deye_cli.sh''' python durch python3 ersetzen müssen:<br />
<pre>#!/bin/bash<br />
set -a; source config.env; set +a<br />
python3 deye_cli.py "$@"<br />
</pre><br />
<br />
=== config.env ===<br />
Die Konfiguration meines DEYE Wechselrichters vom Typ Micro-Inverter:<br />
'''config.env'''<br />
<pre>DEYE_LOGGER_IP_ADDRESS=<IP Wechselrichter im WLAN><br />
DEYE_LOGGER_PORT=8899<br />
DEYE_LOGGER_SERIAL_NUMBER=<Seriennummer des WR><br />
<br />
MQTT_HOST=<IP von MQTT Server><br />
MQTT_PORT=1883<br />
MQTT_USERNAME=<br />
MQTT_PASSWORD=<br />
MQTT_TOPIC_PREFIX=deye<br />
<br />
LOG_LEVEL=ERROR<br />
DEYE_DATA_READ_INTERVAL=60<br />
DEYE_METRIC_GROUPS=micro<br />
</pre><br />
<br />
=== deye_inverter.sh === <br />
Ausserdem habe ich ein kleines ''Wrapper Skript'' geschrieben, um '''Daten des WR''' einfacher '''lesen und schreiben''' zu können:<br><br />
<br />
'''deye_inverter.sh''' [--check <pause>] --read <register> | --write <register> <value><br />
<br />
<pre>#!/bin/bash<br />
# Read / Write DEYE Inverters<br />
# via deye_inverter_mqtt python package<br />
# https://github.com/kbialek/deye-inverter-mqtt<br />
<br />
retries=2 # retry deye_inverter command multiple times<br />
sleep=60 # sleep time between retries<br />
<br />
# locate script in deye_inverter_mqtt directory<br />
cd $(dirname $0)<br />
if [ ! -f ./deye_cli.sh ]; then<br />
echo "Error: ./deye_cli.sh not found."<br />
echo " Please move $(basename $0) in deye-inverter-mqtt directory."<br />
exit 1<br />
fi<br />
. ./config.env<br />
<br />
log_info ()<br />
{<br />
if [ ${LOG_LEVEL} = "INFO" ]; then<br />
logger -t $(basename $0) $@<br />
fi<br />
}<br />
<br />
log_error ()<br />
{<br />
logger -t $(basename $0) $@<br />
echo 1>&2 $@<br />
}<br />
<br />
# read parameters: mode [rw], register, value and optional -c <check pause><br />
pause=0<br />
while [ $# -gt 0 ]; do<br />
case $1 in<br />
-c|--check) shift<br />
[ $# -ge 3 ] || exit 1<br />
pause=$1<br />
;;<br />
-w|--write) mode=w<br />
shift<br />
[ $# -eq 2 ] || exit 1<br />
reg=$1<br />
val=$2<br />
shift<br />
;;<br />
-r|--read) mode=r<br />
shift<br />
[ $# -eq 1 ] || exit 1<br />
reg=$1<br />
;;<br />
esac<br />
shift<br />
done<br />
<br />
# handle offline deye_inverter by<br />
# ${pause} > 0 -> endless loop<br />
# ${pause} = 0 -> error after ${retries}<br />
while true; do<br />
<br />
# try deye_cli.sh $retries times every $sleep secs<br />
check=${retries}<br />
while [ ${check} -gt 0 ]; do<br />
<br />
case ${mode} in<br />
r) result=$(./deye_cli.sh $mode $reg | grep 'int: ')<br />
if [ -n "$result" ]; then<br />
echo "$result"<br />
log_info -t $0 deye_cli.sh $mode $reg<br />
exit 0<br />
fi<br />
;;<br />
w) result=$(./deye_cli.sh $mode $reg $val)<br />
if [ "$result" = "Ok" ]; then<br />
log_info -t $0 deye_cli.sh $mode $reg $val<br />
exit 0<br />
fi<br />
;;<br />
esac<br />
<br />
# wait ${sleep} or ${pause} secs<br />
sleep $(( ${pause} ? ${sleep} : ${pause} ))<br />
<br />
# try ${retries} times<br />
(( check-- ))<br />
done<br />
<br />
# error, or retry endless until wakeup of deye_inverter<br />
if [ ${pause} -eq 0 ]; then<br />
log_error "Error: deye_cli.sh $mode $reg $val failed."<br />
exit 1<br />
fi<br />
<br />
done<br />
</pre><br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
Folgendes Skript starte ich jeden Morgen um 04:00 Uhr per Cron (und in rc.local fürs booten):<br />
<br />
=== deye_mqtt_loop.sh ===<br />
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.<br />
<br />
Ausserdem setzt es beim Start den Tageszähler des Wechselrichters bedarfsweise zurück, bevor die Werte des DEYE Wechselrichters per Endlosschleife abgefragt werden.<br />
<br />
<pre>#!/bin/bash<br />
# reset Daily_Power of DEYE Inverters<br />
# via deye_inverter_mqtt python package<br />
# https://github.com/kbialek/deye-inverter-mqtt<br />
<br />
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin<br />
<br />
# config:<br />
deye_inverter_dir=/root/deye-inverter-mqtt<br />
fhem_dir=/opt/fhem<br />
horizon="-2" # sunset CIVIL=-6, REAL=0<br />
<br />
debug=info<br />
<br />
myPID=$$<br />
myname=$(basename $0)<br />
arg="$1"<br />
<br />
deye_inverter_sh=$deye_inverter_dir/deye_inverter.sh<br />
deye_inverter_mqtt_cfg=$deye_inverter_dir/config.env<br />
deye_inverter_mqtt_cmd="python3 deye_docker_entrypoint.py"<br />
<br />
sunrise_cmd="perl $fhem_dir/fhem.pl localhost:7072 {sunrise_abs('HORIZON=$horizon')}"<br />
sunset_cmd="perl $fhem_dir/fhem.pl localhost:7072 {sunset_abs('HORIZON=$horizon')}"<br />
<br />
# installation:<br />
# locate FHEM, used for sunrise(), sunset()<br />
if [ ! -x ${fhem_dir/fhem.pl} ]; then<br />
echo "Error: ${fhem_dir} not found."<br />
echo " Is FHEM installed in $fhem_dir ?"<br />
exit 1<br />
fi<br />
<br />
# locate script in deye_inverter_mqtt directory<br />
if [ ! -f ${deye_inverter_mqtt_cfg} ]; then<br />
echo "Error: ${deye_inverter_mqtt_cfg} not found."<br />
echo " Please update \$deye_inverter_sh path in $0"<br />
exit 1<br />
fi<br />
<br />
# read my config<br />
set -a; source ${deye_inverter_mqtt_cfg}; set +a<br />
<br />
[ "$debug" = "full" ] && set -x<br />
<br />
# ----- functions () ------------------------------------<br />
log_info ()<br />
{<br />
[ "$debug" = "yes" -o "$debug" = "true" -o "$debug" = "info" ] &&<br />
echo $myname: $@<br />
[ ${LOG_LEVEL} = "INFO" ] && logger -t $myname $@<br />
}<br />
<br />
log_error ()<br />
{<br />
logger -t $myname $@<br />
echo 1>&2 $@<br />
}<br />
<br />
# store actual date + time into vars<br />
set_date_time_vars ()<br />
{<br />
year=$(date +%y)<br />
month=$(date +%m | sed 's/^0//g')<br />
day=$(date +%d | sed 's/^0//g')<br />
hour=$(date +%H | sed 's/^0//g')<br />
minute=$(date +%m | sed 's/^0//g')<br />
second=$(date +%S | sed 's/^0//g')<br />
}<br />
<br />
# kill deye_inverter_loop ()<br />
kill_old_instance ()<br />
{<br />
# kill old instances of me<br />
my_PS=$(ps ax | grep -v grep | grep "${myname}" | awk '{print $1}')<br />
for ps in $my_PS; do<br />
[ $ps -lt $myPID ] && kill $ps<br />
done<br />
}<br />
<br />
# get deye_inverter_mqtt PIDs<br />
get_deye_mqtt_pid ()<br />
{<br />
deye_mqtt_pid=$(ps ax | grep -v grep | grep "${deye_inverter_mqtt_cmd}" | awk '{print $1}')<br />
}<br />
<br />
# kill deye_inverter_instance ()<br />
kill_deye_inverter_instance ()<br />
{<br />
get_deye_mqtt_pid<br />
if [ ! -z "${deye_mqtt_pid}" ]; then<br />
kill ${deye_mqtt_pid}<br />
log_info "Info: KILL running deye_mqtt ${deye_mqtt_pid}"<br />
fi<br />
<br />
# kill any running deye requests<br />
deye_ps=$(ps ax | grep -v grep | grep "$deye_inverter_sh " | awk '{print $1}')<br />
[ ! -z "$deye_ps" ] && kill $deye_ps<br />
}<br />
<br />
# stop all runnning instances<br />
deye_inverter_mqtt_stop ()<br />
{<br />
kill_old_instance<br />
kill_deye_inverter_instance<br />
}<br />
<br />
# start deye_inverter_mqtt<br />
deye_inverter_mqtt_start ()<br />
{<br />
log_info "Deye MQTT start"<br />
cd ${deye_inverter_dir}<br />
${deye_inverter_mqtt_cmd}<br />
}<br />
<br />
# read year_month register 0x16 = 22<br />
read_year_month ()<br />
{<br />
${deye_inverter_sh} -r 22 | sed 's/int: //g' | cut -d, -f1<br />
}<br />
<br />
# read day register 0x17 = 23<br />
read_day ()<br />
{<br />
${deye_inverter_sh} -r 23 | sed 's/.*h: //g' | cut -d, -f1<br />
}<br />
<br />
# read Daily power register 0x3c = 60<br />
read_daily_Power ()<br />
{<br />
${deye_inverter_sh} -r 60 | sed 's/int: //g' | cut -d, -f1<br />
}<br />
<br />
# read actual power register 0x56 = 86<br />
read_actual_Power ()<br />
{<br />
# doubleRegisterSensor("AC Active Power", 0x56, 0.1, mqtt_topic_suffix='ac/active_power', groups=['string', 'micro'])<br />
# (int.from_bytes(high_word, 'big') * 65536 + int.from_bytes(low_word, 'big')) * self.factor<br />
# val86=${deye_inverter_sh} -r 86 | sed 's/int: //g' | cut -d, -f1<br />
# val87=${deye_inverter_sh} -r 87 | sed 's/int: //g' | cut -d, -f1<br />
# echo $(( (${val87} * 65536 + ${val86}) * 0.1 ))<br />
<br />
# simple value is enough for our purpose<br />
${deye_inverter_sh} -r 86 | sed 's/int: //g' | cut -d, -f1<br />
}<br />
<br />
# write actual date/time into deye inverter<br />
init_deye_inverter ()<br />
{<br />
# daily reset phase<br />
daily_reset=init<br />
<br />
# ensure by loop, that all register values are written<br />
while [ ${daily_reset} != "done" ]; do<br />
<br />
# wait until we have enough power<br />
while [ "0$(read_actual_Power)" -le 10 ]; do<br />
sleep 600<br />
done<br />
<br />
# read Daily power register<br />
dailyPower=$(read_daily_Power)<br />
<br />
# read (old) year_month and day<br />
val22_old=$(read_year_month)<br />
val23_day=$(read_day)<br />
log_info "date/time update initialized"<br />
<br />
# set variables year, month, day, hour, minute, second<br />
set_date_time_vars<br />
<br />
# calculate register 22-24 with date and time vars<br />
val22=$(( ${year} * 256 + ${month} ))<br />
val23=$(( ${day} * 256 + ${hour} ))<br />
val24=$(( ${minute} * 256 + ${second} ))<br />
<br />
# reset power/date/time only once a day or exit loop<br />
[ "$dailyPower" -eq 0 -a "${val22_old}" = "${val22}" -a "${val23_day}" = "${day}" ] && break<br />
<br />
# reset daily_power by<br />
# setting register 22-24 with actual date + time<br />
${deye_inverter_sh} -w 22 $val22 || continue<br />
${deye_inverter_sh} -w 23 $val23 || continue<br />
${deye_inverter_sh} -w 24 $val24 || continue<br />
<br />
# test success<br />
# read Daily power register, should be resetted to 0 now<br />
dailyPower=$(read_daily_Power)<br />
<br />
# exit loop if power is updated, else log that reset failed<br />
if [ "$dailyPower" -eq 0 ]; then<br />
daily_reset=done<br />
<br />
### skip loop here, reset doesn't work always :-(<br />
# elif [ $daily_reset = "init" ]; then<br />
# daily_reset=failed<br />
# sleep 300<br />
<br />
else<br />
log_info "daily power reset failed"<br />
break<br />
fi<br />
done<br />
log_info "date/time reset done"<br />
}<br />
<br />
# wait for (next) daylight<br />
wait_for_sunrise ()<br />
{<br />
sunrise=$($sunrise_cmd)<br />
sunset=$($sunset_cmd)<br />
<br />
current_time=$(date +%s)<br />
sunrise=$(date -d"$sunrise" +%s)<br />
sunset=$(date -d"$sunset" +%s)<br />
<br />
# calulate time in secs until (next) sunrise<br />
sleep_sunrise=$(( $sunrise - $current_time )) # this morning<br />
[ $sleep_sunrise -lt 0 ] &&<br />
sleep_sunrise=$(( $sleep_sunrise + 86400 )) # next morning<br />
<br />
# wait for daylight only<br />
if [ $current_time -lt $sunrise -o $current_time -gt $sunset ]; then<br />
log_info sleep $sleep_sunrise<br />
sleep $sleep_sunrise<br />
fi<br />
}<br />
<br />
# wait only before sunset<br />
wait_before_sunset ()<br />
{<br />
sunset=$($sunset_cmd)<br />
<br />
current_time=$(date +%s)<br />
sunset=$(date -d"$sunset" +%s)<br />
<br />
# calulate time in secs until next sunset<br />
sleep_sunset=$(( $sunset - $current_time ))<br />
<br />
# wait before sunset only<br />
if [ $sleep_sunset -gt 0 ]; then<br />
log_info sleep $sleep_sunset<br />
sleep $sleep_sunset<br />
fi<br />
}<br />
<br />
# ------------------------<br />
# MAIN: Script starts here<br />
# ------------------------<br />
<br />
# sunset_stop: wait until sunset before we stop running instances<br />
[ "$arg" = "sunset_stop" ] &&<br />
wait_before_sunset<br />
<br />
# stop any runnning deye_inverter_mqtt processes<br />
deye_inverter_mqtt_stop<br />
<br />
# stop: don't start again<br />
[ "$arg" = "stop" ] && exit 0<br />
<br />
# reset and start deye_mqtt_loop<br />
# check every 15 min for running deye_inverter_mqtt<br />
while :; do<br />
<br />
# wait for (next) daylight<br />
wait_for_sunrise<br />
<br />
# setting date/time also resets daily power register<br />
init_deye_inverter<br />
<br />
# start deye_inverter_mqtt<br />
get_deye_mqtt_pid<br />
if [ -z "$deye_mqtt_pid" ]; then<br />
deye_inverter_mqtt_start<br />
fi<br />
<br />
# wait 15 min<br />
sleep 900<br />
done<br />
</pre><br />
<br />
=== Contrab und /etc/rc.local ===<br />
Mein Crontab Eintrag:<br />
<pre># stop DEYE monitoring after sunset (FHEM)<br />
0 18 * * * /root/sbin/deye_mqtt_loop.sh sunset_stop 2>/dev/null<br />
</pre><br />
und /etc/rc.local:<br />
<pre># start monitoring of PV-System: DEYE inverter<br />
$(sleep 10; /root/sbin/deye_mqtt_loop.sh 2>dev/null)&<br />
</pre><br />
<br />
== Dashboard ==<br />
So sieht das aus, wenn es den ganzen Tag im April regnet und bedeckt ist:<br />
[[Datei:Solar Bedeckt Regen.jpg|midi]]<br />
<br />
Die Eigenverbrauchsquote habe ich mittels Grafana ermittelt:<br />
[[Datei:Solar Eigenverbrauch.jpg|midi|Eigenverbrauchsquote]]</div>
Neobiker
http://neobiker.de/wiki/index.php?title=DEYE_Wechselrichter&diff=2145
DEYE Wechselrichter
2024-02-03T17:37:16Z
<p>Neobiker: /* deye_mqtt_loop.sh */</p>
<hr />
<div>= Balkonkraftwerk: DEYE Wechselrichter ohne Cloud auslesen und per MQTT monitoren =<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
Der WR bietet verschiedene Möglichkeiten zur Konfiguration und Abfrage der aktuellen Werte:<br />
* einen internen Webserver (http://10.10.10.254)<br />
* die Cloud Anbindung zu Solarman (Solarman Smart App)<br />
* (angepasstes) Modbus Protokoll über Port 8899<br />
* AT+ Befehle über Port 48899<br />
<br />
Ich verwende das Modbus Protokoll zum auslesen des WR, das <br />
# effizient/schnell ist (Performance)<br />
# ohne Internetverbindung auskommt<br />
<br />
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.<br />
<br />
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.<br />
<br />
== Wechselrichter Eigenheiten ==<br />
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. <br />
<br />
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.<br />
<br />
== Installation ==<br />
Das Projekt von Github laden und in einem Verzeichnis speichern, '''Python''' ist normalerweise ja schon installiert. <br />
<br />
<pre>git clone https://github.com/kbialek/deye-inverter-mqtt<br />
pip install paho-mqtt<br />
cd deye-inverter-mqtt</pre><br />
<br />
Unter Debian habe ich im Skript '''deye_cli.sh''' python durch python3 ersetzen müssen:<br />
<pre>#!/bin/bash<br />
set -a; source config.env; set +a<br />
python3 deye_cli.py "$@"<br />
</pre><br />
<br />
=== config.env ===<br />
Die Konfiguration meines DEYE Wechselrichters vom Typ Micro-Inverter:<br />
'''config.env'''<br />
<pre>DEYE_LOGGER_IP_ADDRESS=<IP Wechselrichter im WLAN><br />
DEYE_LOGGER_PORT=8899<br />
DEYE_LOGGER_SERIAL_NUMBER=<Seriennummer des WR><br />
<br />
MQTT_HOST=<IP von MQTT Server><br />
MQTT_PORT=1883<br />
MQTT_USERNAME=<br />
MQTT_PASSWORD=<br />
MQTT_TOPIC_PREFIX=deye<br />
<br />
LOG_LEVEL=ERROR<br />
DEYE_DATA_READ_INTERVAL=60<br />
DEYE_METRIC_GROUPS=micro<br />
</pre><br />
<br />
=== deye_inverter.sh === <br />
Ausserdem habe ich ein kleines ''Wrapper Skript'' geschrieben, um '''Daten des WR''' einfacher '''lesen und schreiben''' zu können:<br><br />
<br />
'''deye_inverter.sh''' [--check <pause>] --read <register> | --write <register> <value><br />
<br />
<pre>#!/bin/bash<br />
# Read / Write DEYE Inverters<br />
# via deye_inverter_mqtt python package<br />
# https://github.com/kbialek/deye-inverter-mqtt<br />
<br />
retries=2 # retry deye_inverter command multiple times<br />
sleep=60 # sleep time between retries<br />
<br />
# locate script in deye_inverter_mqtt directory<br />
cd $(dirname $0)<br />
if [ ! -f ./deye_cli.sh ]; then<br />
echo "Error: ./deye_cli.sh not found."<br />
echo " Please move $(basename $0) in deye-inverter-mqtt directory."<br />
exit 1<br />
fi<br />
. ./config.env<br />
<br />
log_info ()<br />
{<br />
if [ ${LOG_LEVEL} = "INFO" ]; then<br />
logger -t $(basename $0) $@<br />
fi<br />
}<br />
<br />
log_error ()<br />
{<br />
logger -t $(basename $0) $@<br />
echo 2>&1 $@<br />
}<br />
<br />
# read parameters: mode [rw], register, value and optional -c <check pause><br />
pause=0<br />
while [ $# -gt 0 ]; do<br />
case $1 in<br />
-c|--check) shift<br />
[ $# -ge 3 ] || exit 1<br />
pause=$1<br />
;;<br />
-w|--write) mode=w<br />
shift<br />
[ $# -eq 2 ] || exit 1<br />
reg=$1<br />
val=$2<br />
shift<br />
;;<br />
-r|--read) mode=r<br />
shift<br />
[ $# -eq 1 ] || exit 1<br />
reg=$1<br />
;;<br />
esac<br />
shift<br />
done<br />
<br />
# handle offline deye_inverter by<br />
# ${pause} > 0 -> endless loop<br />
# ${pause} = 0 -> error after ${retries}<br />
while true; do<br />
<br />
# try deye_cli.sh $retries times every $sleep secs<br />
check=${retries}<br />
while [ ${check} -gt 0 ]; do<br />
<br />
case ${mode} in<br />
r) result=$(./deye_cli.sh $mode $reg | grep 'int: ')<br />
if [ -n "$result" ]; then<br />
echo "$result"<br />
log_info -t $0 deye_cli.sh $mode $reg<br />
exit 0<br />
fi<br />
;;<br />
w) result=$(./deye_cli.sh $mode $reg $val)<br />
if [ "$result" = "Ok" ]; then<br />
log_info -t $0 deye_cli.sh $mode $reg $val<br />
exit 0<br />
fi<br />
;;<br />
esac<br />
<br />
# wait ${sleep} or ${pause} secs<br />
sleep $(( ${pause} ? ${sleep} : ${pause} ))<br />
<br />
# try ${retries} times<br />
(( check-- ))<br />
done<br />
<br />
# error, or retry endless until wakeup of deye_inverter<br />
if [ ${pause} -eq 0 ]; then<br />
log_error "Error: deye_cli.sh $mode $reg $val failed."<br />
exit 1<br />
fi<br />
<br />
done<br />
</pre><br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
Folgendes Skript starte ich jeden Morgen um 04:00 Uhr per Cron (und in rc.local fürs booten):<br />
<br />
=== deye_mqtt_loop.sh ===<br />
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.<br />
<br />
Ausserdem setzt es beim Start den Tageszähler des Wechselrichters bedarfsweise zurück, bevor die Werte des DEYE Wechselrichters per Endlosschleife abgefragt werden.<br />
<br />
<pre>#!/bin/bash<br />
# reset Daily_Power of DEYE Inverters<br />
# via deye_inverter_mqtt python package<br />
# https://github.com/kbialek/deye-inverter-mqtt<br />
<br />
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin<br />
<br />
# config:<br />
deye_inverter_dir=/root/deye-inverter-mqtt<br />
fhem_dir=/opt/fhem<br />
horizon="-2" # sunset CIVIL=-6, REAL=0<br />
<br />
debug=info<br />
<br />
myPID=$$<br />
myname=$(basename $0)<br />
arg="$1"<br />
<br />
deye_inverter_sh=$deye_inverter_dir/deye_inverter.sh<br />
deye_inverter_mqtt_cfg=$deye_inverter_dir/config.env<br />
deye_inverter_mqtt_cmd="python3 deye_docker_entrypoint.py"<br />
<br />
sunrise_cmd="perl $fhem_dir/fhem.pl localhost:7072 {sunrise_abs('HORIZON=$horizon')}"<br />
sunset_cmd="perl $fhem_dir/fhem.pl localhost:7072 {sunset_abs('HORIZON=$horizon')}"<br />
<br />
# installation:<br />
# locate FHEM, used for sunrise(), sunset()<br />
if [ ! -x ${fhem_dir/fhem.pl} ]; then<br />
echo "Error: ${fhem_dir} not found."<br />
echo " Is FHEM installed in $fhem_dir ?"<br />
exit 1<br />
fi<br />
<br />
# locate script in deye_inverter_mqtt directory<br />
if [ ! -f ${deye_inverter_mqtt_cfg} ]; then<br />
echo "Error: ${deye_inverter_mqtt_cfg} not found."<br />
echo " Please update \$deye_inverter_sh path in $0"<br />
exit 1<br />
fi<br />
<br />
# read my config<br />
set -a; source ${deye_inverter_mqtt_cfg}; set +a<br />
<br />
[ "$debug" = "full" ] && set -x<br />
<br />
# ----- functions () ------------------------------------<br />
log_info ()<br />
{<br />
[ "$debug" = "yes" -o "$debug" = "true" -o "$debug" = "info" ] &&<br />
echo $myname: $@<br />
[ ${LOG_LEVEL} = "INFO" ] && logger -t $myname $@<br />
}<br />
<br />
log_error ()<br />
{<br />
logger -t $myname $@<br />
echo 1>&2 $@<br />
}<br />
<br />
# store actual date + time into vars<br />
set_date_time_vars ()<br />
{<br />
year=$(date +%y)<br />
month=$(date +%m | sed 's/^0//g')<br />
day=$(date +%d | sed 's/^0//g')<br />
hour=$(date +%H | sed 's/^0//g')<br />
minute=$(date +%m | sed 's/^0//g')<br />
second=$(date +%S | sed 's/^0//g')<br />
}<br />
<br />
# kill deye_inverter_loop ()<br />
kill_old_instance ()<br />
{<br />
# kill old instances of me<br />
my_PS=$(ps ax | grep -v grep | grep "${myname}" | awk '{print $1}')<br />
for ps in $my_PS; do<br />
[ $ps -lt $myPID ] && kill $ps<br />
done<br />
}<br />
<br />
# get deye_inverter_mqtt PIDs<br />
get_deye_mqtt_pid ()<br />
{<br />
deye_mqtt_pid=$(ps ax | grep -v grep | grep "${deye_inverter_mqtt_cmd}" | awk '{print $1}')<br />
}<br />
<br />
# kill deye_inverter_instance ()<br />
kill_deye_inverter_instance ()<br />
{<br />
get_deye_mqtt_pid<br />
if [ ! -z "${deye_mqtt_pid}" ]; then<br />
kill ${deye_mqtt_pid}<br />
log_info "Info: KILL running deye_mqtt ${deye_mqtt_pid}"<br />
fi<br />
<br />
# kill any running deye requests<br />
deye_ps=$(ps ax | grep -v grep | grep "$deye_inverter_sh " | awk '{print $1}')<br />
[ ! -z "$deye_ps" ] && kill $deye_ps<br />
}<br />
<br />
# stop all runnning instances<br />
deye_inverter_mqtt_stop ()<br />
{<br />
kill_old_instance<br />
kill_deye_inverter_instance<br />
}<br />
<br />
# start deye_inverter_mqtt<br />
deye_inverter_mqtt_start ()<br />
{<br />
log_info "Deye MQTT start"<br />
cd ${deye_inverter_dir}<br />
${deye_inverter_mqtt_cmd}<br />
}<br />
<br />
# read year_month register 0x16 = 22<br />
read_year_month ()<br />
{<br />
${deye_inverter_sh} -r 22 | sed 's/int: //g' | cut -d, -f1<br />
}<br />
<br />
# read day register 0x17 = 23<br />
read_day ()<br />
{<br />
${deye_inverter_sh} -r 23 | sed 's/.*h: //g' | cut -d, -f1<br />
}<br />
<br />
# read Daily power register 0x3c = 60<br />
read_daily_Power ()<br />
{<br />
${deye_inverter_sh} -r 60 | sed 's/int: //g' | cut -d, -f1<br />
}<br />
<br />
# read actual power register 0x56 = 86<br />
read_actual_Power ()<br />
{<br />
# doubleRegisterSensor("AC Active Power", 0x56, 0.1, mqtt_topic_suffix='ac/active_power', groups=['string', 'micro'])<br />
# (int.from_bytes(high_word, 'big') * 65536 + int.from_bytes(low_word, 'big')) * self.factor<br />
# val86=${deye_inverter_sh} -r 86 | sed 's/int: //g' | cut -d, -f1<br />
# val87=${deye_inverter_sh} -r 87 | sed 's/int: //g' | cut -d, -f1<br />
# echo $(( (${val87} * 65536 + ${val86}) * 0.1 ))<br />
<br />
# simple value is enough for our purpose<br />
${deye_inverter_sh} -r 86 | sed 's/int: //g' | cut -d, -f1<br />
}<br />
<br />
# write actual date/time into deye inverter<br />
init_deye_inverter ()<br />
{<br />
# daily reset phase<br />
daily_reset=init<br />
<br />
# ensure by loop, that all register values are written<br />
while [ ${daily_reset} != "done" ]; do<br />
<br />
# wait until we have enough power<br />
while [ "0$(read_actual_Power)" -le 10 ]; do<br />
sleep 600<br />
done<br />
<br />
# read Daily power register<br />
dailyPower=$(read_daily_Power)<br />
<br />
# read (old) year_month and day<br />
val22_old=$(read_year_month)<br />
val23_day=$(read_day)<br />
log_info "date/time update initialized"<br />
<br />
# set variables year, month, day, hour, minute, second<br />
set_date_time_vars<br />
<br />
# calculate register 22-24 with date and time vars<br />
val22=$(( ${year} * 256 + ${month} ))<br />
val23=$(( ${day} * 256 + ${hour} ))<br />
val24=$(( ${minute} * 256 + ${second} ))<br />
<br />
# reset power/date/time only once a day or exit loop<br />
[ "$dailyPower" -eq 0 -a "${val22_old}" = "${val22}" -a "${val23_day}" = "${day}" ] && break<br />
<br />
# reset daily_power by<br />
# setting register 22-24 with actual date + time<br />
${deye_inverter_sh} -w 22 $val22 || continue<br />
${deye_inverter_sh} -w 23 $val23 || continue<br />
${deye_inverter_sh} -w 24 $val24 || continue<br />
<br />
# test success<br />
# read Daily power register, should be resetted to 0 now<br />
dailyPower=$(read_daily_Power)<br />
<br />
# exit loop if power is updated, else log that reset failed<br />
if [ "$dailyPower" -eq 0 ]; then<br />
daily_reset=done<br />
<br />
### skip loop here, reset doesn't work always :-(<br />
# elif [ $daily_reset = "init" ]; then<br />
# daily_reset=failed<br />
# sleep 300<br />
<br />
else<br />
log_info "daily power reset failed"<br />
break<br />
fi<br />
done<br />
log_info "date/time reset done"<br />
}<br />
<br />
# wait for (next) daylight<br />
wait_for_sunrise ()<br />
{<br />
sunrise=$($sunrise_cmd)<br />
sunset=$($sunset_cmd)<br />
<br />
current_time=$(date +%s)<br />
sunrise=$(date -d"$sunrise" +%s)<br />
sunset=$(date -d"$sunset" +%s)<br />
<br />
# calulate time in secs until (next) sunrise<br />
sleep_sunrise=$(( $sunrise - $current_time )) # this morning<br />
[ $sleep_sunrise -lt 0 ] &&<br />
sleep_sunrise=$(( $sleep_sunrise + 86400 )) # next morning<br />
<br />
# wait for daylight only<br />
if [ $current_time -lt $sunrise -o $current_time -gt $sunset ]; then<br />
log_info sleep $sleep_sunrise<br />
sleep $sleep_sunrise<br />
fi<br />
}<br />
<br />
# wait only before sunset<br />
wait_before_sunset ()<br />
{<br />
sunset=$($sunset_cmd)<br />
<br />
current_time=$(date +%s)<br />
sunset=$(date -d"$sunset" +%s)<br />
<br />
# calulate time in secs until next sunset<br />
sleep_sunset=$(( $sunset - $current_time ))<br />
<br />
# wait before sunset only<br />
if [ $sleep_sunset -gt 0 ]; then<br />
log_info sleep $sleep_sunset<br />
sleep $sleep_sunset<br />
fi<br />
}<br />
<br />
# ------------------------<br />
# MAIN: Script starts here<br />
# ------------------------<br />
<br />
# sunset_stop: wait until sunset before we stop running instances<br />
[ "$arg" = "sunset_stop" ] &&<br />
wait_before_sunset<br />
<br />
# stop any runnning deye_inverter_mqtt processes<br />
deye_inverter_mqtt_stop<br />
<br />
# stop: don't start again<br />
[ "$arg" = "stop" ] && exit 0<br />
<br />
# reset and start deye_mqtt_loop<br />
# check every 15 min for running deye_inverter_mqtt<br />
while :; do<br />
<br />
# wait for (next) daylight<br />
wait_for_sunrise<br />
<br />
# setting date/time also resets daily power register<br />
init_deye_inverter<br />
<br />
# start deye_inverter_mqtt<br />
get_deye_mqtt_pid<br />
if [ -z "$deye_mqtt_pid" ]; then<br />
deye_inverter_mqtt_start<br />
fi<br />
<br />
# wait 15 min<br />
sleep 900<br />
done<br />
</pre><br />
<br />
=== Contrab und /etc/rc.local ===<br />
Mein Crontab Eintrag:<br />
<pre># stop DEYE monitoring after sunset (FHEM)<br />
0 18 * * * /root/sbin/deye_mqtt_loop.sh sunset_stop 2>/dev/null<br />
</pre><br />
und /etc/rc.local:<br />
<pre># start monitoring of PV-System: DEYE inverter<br />
$(sleep 10; /root/sbin/deye_mqtt_loop.sh 2>dev/null)&<br />
</pre><br />
<br />
== Dashboard ==<br />
So sieht das aus, wenn es den ganzen Tag im April regnet und bedeckt ist:<br />
[[Datei:Solar Bedeckt Regen.jpg|midi]]<br />
<br />
Die Eigenverbrauchsquote habe ich mittels Grafana ermittelt:<br />
[[Datei:Solar Eigenverbrauch.jpg|midi|Eigenverbrauchsquote]]</div>
Neobiker
http://neobiker.de/wiki/index.php?title=DEYE_Wechselrichter&diff=2144
DEYE Wechselrichter
2024-02-03T15:29:43Z
<p>Neobiker: /* deye_mqtt_loop.sh */</p>
<hr />
<div>= Balkonkraftwerk: DEYE Wechselrichter ohne Cloud auslesen und per MQTT monitoren =<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
Der WR bietet verschiedene Möglichkeiten zur Konfiguration und Abfrage der aktuellen Werte:<br />
* einen internen Webserver (http://10.10.10.254)<br />
* die Cloud Anbindung zu Solarman (Solarman Smart App)<br />
* (angepasstes) Modbus Protokoll über Port 8899<br />
* AT+ Befehle über Port 48899<br />
<br />
Ich verwende das Modbus Protokoll zum auslesen des WR, das <br />
# effizient/schnell ist (Performance)<br />
# ohne Internetverbindung auskommt<br />
<br />
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.<br />
<br />
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.<br />
<br />
== Wechselrichter Eigenheiten ==<br />
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. <br />
<br />
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.<br />
<br />
== Installation ==<br />
Das Projekt von Github laden und in einem Verzeichnis speichern, '''Python''' ist normalerweise ja schon installiert. <br />
<br />
<pre>git clone https://github.com/kbialek/deye-inverter-mqtt<br />
pip install paho-mqtt<br />
cd deye-inverter-mqtt</pre><br />
<br />
Unter Debian habe ich im Skript '''deye_cli.sh''' python durch python3 ersetzen müssen:<br />
<pre>#!/bin/bash<br />
set -a; source config.env; set +a<br />
python3 deye_cli.py "$@"<br />
</pre><br />
<br />
=== config.env ===<br />
Die Konfiguration meines DEYE Wechselrichters vom Typ Micro-Inverter:<br />
'''config.env'''<br />
<pre>DEYE_LOGGER_IP_ADDRESS=<IP Wechselrichter im WLAN><br />
DEYE_LOGGER_PORT=8899<br />
DEYE_LOGGER_SERIAL_NUMBER=<Seriennummer des WR><br />
<br />
MQTT_HOST=<IP von MQTT Server><br />
MQTT_PORT=1883<br />
MQTT_USERNAME=<br />
MQTT_PASSWORD=<br />
MQTT_TOPIC_PREFIX=deye<br />
<br />
LOG_LEVEL=ERROR<br />
DEYE_DATA_READ_INTERVAL=60<br />
DEYE_METRIC_GROUPS=micro<br />
</pre><br />
<br />
=== deye_inverter.sh === <br />
Ausserdem habe ich ein kleines ''Wrapper Skript'' geschrieben, um '''Daten des WR''' einfacher '''lesen und schreiben''' zu können:<br><br />
<br />
'''deye_inverter.sh''' [--check <pause>] --read <register> | --write <register> <value><br />
<br />
<pre>#!/bin/bash<br />
# Read / Write DEYE Inverters<br />
# via deye_inverter_mqtt python package<br />
# https://github.com/kbialek/deye-inverter-mqtt<br />
<br />
retries=2 # retry deye_inverter command multiple times<br />
sleep=60 # sleep time between retries<br />
<br />
# locate script in deye_inverter_mqtt directory<br />
cd $(dirname $0)<br />
if [ ! -f ./deye_cli.sh ]; then<br />
echo "Error: ./deye_cli.sh not found."<br />
echo " Please move $(basename $0) in deye-inverter-mqtt directory."<br />
exit 1<br />
fi<br />
. ./config.env<br />
<br />
log_info ()<br />
{<br />
if [ ${LOG_LEVEL} = "INFO" ]; then<br />
logger -t $(basename $0) $@<br />
fi<br />
}<br />
<br />
log_error ()<br />
{<br />
logger -t $(basename $0) $@<br />
echo 2>&1 $@<br />
}<br />
<br />
# read parameters: mode [rw], register, value and optional -c <check pause><br />
pause=0<br />
while [ $# -gt 0 ]; do<br />
case $1 in<br />
-c|--check) shift<br />
[ $# -ge 3 ] || exit 1<br />
pause=$1<br />
;;<br />
-w|--write) mode=w<br />
shift<br />
[ $# -eq 2 ] || exit 1<br />
reg=$1<br />
val=$2<br />
shift<br />
;;<br />
-r|--read) mode=r<br />
shift<br />
[ $# -eq 1 ] || exit 1<br />
reg=$1<br />
;;<br />
esac<br />
shift<br />
done<br />
<br />
# handle offline deye_inverter by<br />
# ${pause} > 0 -> endless loop<br />
# ${pause} = 0 -> error after ${retries}<br />
while true; do<br />
<br />
# try deye_cli.sh $retries times every $sleep secs<br />
check=${retries}<br />
while [ ${check} -gt 0 ]; do<br />
<br />
case ${mode} in<br />
r) result=$(./deye_cli.sh $mode $reg | grep 'int: ')<br />
if [ -n "$result" ]; then<br />
echo "$result"<br />
log_info -t $0 deye_cli.sh $mode $reg<br />
exit 0<br />
fi<br />
;;<br />
w) result=$(./deye_cli.sh $mode $reg $val)<br />
if [ "$result" = "Ok" ]; then<br />
log_info -t $0 deye_cli.sh $mode $reg $val<br />
exit 0<br />
fi<br />
;;<br />
esac<br />
<br />
# wait ${sleep} or ${pause} secs<br />
sleep $(( ${pause} ? ${sleep} : ${pause} ))<br />
<br />
# try ${retries} times<br />
(( check-- ))<br />
done<br />
<br />
# error, or retry endless until wakeup of deye_inverter<br />
if [ ${pause} -eq 0 ]; then<br />
log_error "Error: deye_cli.sh $mode $reg $val failed."<br />
exit 1<br />
fi<br />
<br />
done<br />
</pre><br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
Folgendes Skript starte ich jeden Morgen um 04:00 Uhr per Cron (und in rc.local fürs booten):<br />
<br />
=== deye_mqtt_loop.sh ===<br />
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.<br />
<br />
Ausserdem setzt es beim Start den Tageszähler des Wechselrichters bedarfsweise zurück, bevor die Werte des DEYE Wechselrichters per Endlosschleife abgefragt werden.<br />
<br />
<pre>#!/bin/bash<br />
# reset Daily_Power of DEYE Inverters<br />
# via deye_inverter_mqtt python package<br />
# https://github.com/kbialek/deye-inverter-mqtt<br />
<br />
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin<br />
<br />
# config:<br />
deye_inverter_dir=/root/deye-inverter-mqtt<br />
fhem_dir=/opt/fhem<br />
horizon="-2" # sunset CIVIL=-6, REAL=0<br />
<br />
debug=info<br />
<br />
myPID=$$<br />
myname=$(basename $0)<br />
arg="$1"<br />
<br />
deye_inverter_sh=$deye_inverter_dir/deye_inverter.sh<br />
deye_inverter_mqtt_cfg=$deye_inverter_dir/config.env<br />
deye_inverter_mqtt_cmd="python3 deye_docker_entrypoint.py"<br />
<br />
sunrise_cmd="perl $fhem_dir/fhem.pl localhost:7072 {sunrise_abs('HORIZON=$horizon')}"<br />
sunset_cmd="perl $fhem_dir/fhem.pl localhost:7072 {sunset_abs('HORIZON=$horizon')}"<br />
<br />
# installation:<br />
# locate FHEM, used for sunrise(), sunset()<br />
if [ ! -x ${fhem_dir/fhem.pl} ]; then<br />
echo "Error: ${fhem_dir} not found."<br />
echo " Is FHEM installed in $fhem_dir ?"<br />
exit 1<br />
fi<br />
<br />
# locate script in deye_inverter_mqtt directory<br />
if [ ! -f ${deye_inverter_mqtt_cfg} ]; then<br />
echo "Error: ${deye_inverter_mqtt_cfg} not found."<br />
echo " Please update \$deye_inverter_sh path in $0"<br />
exit 1<br />
fi<br />
<br />
# read my config<br />
set -a; source ${deye_inverter_mqtt_cfg}; set +a<br />
<br />
[ "$debug" = "full" ] && set -x<br />
<br />
# ----- functions () ------------------------------------<br />
log_info ()<br />
{<br />
[ "$debug" = "yes" -o "$debug" = "true" -o "$debug" = "info" ] &&<br />
echo $myname: $@<br />
[ ${LOG_LEVEL} = "INFO" ] && logger -t $myname $@<br />
}<br />
<br />
log_error ()<br />
{<br />
logger -t $myname $@<br />
echo 1>&2 $@<br />
}<br />
<br />
# store actual date + time into vars<br />
set_date_time_vars ()<br />
{<br />
year=$(date +%y)<br />
month=$(date +%m | sed 's/^0//g')<br />
day=$(date +%d | sed 's/^0//g')<br />
hour=$(date +%H | sed 's/^0//g')<br />
minute=$(date +%m | sed 's/^0//g')<br />
second=$(date +%S | sed 's/^0//g')<br />
}<br />
<br />
# kill deye_inverter_loop ()<br />
kill_old_instance ()<br />
{<br />
# kill old instances of me<br />
my_PS=$(ps ax | grep -v grep | grep "${myname}" | awk '{print $1}')<br />
for ps in $my_PS; do<br />
[ $ps -lt $myPID ] && kill $ps<br />
done<br />
}<br />
<br />
# get deye_inverter_mqtt PIDs<br />
get_deye_mqtt_pid ()<br />
{<br />
deye_mqtt_pid=$(ps ax | grep -v grep | grep "${deye_inverter_mqtt_cmd}" | awk '{print $1}')<br />
}<br />
<br />
# kill deye_inverter_instance ()<br />
kill_deye_inverter_instance ()<br />
{<br />
get_deye_mqtt_pid<br />
if [ ! -z "${deye_mqtt_pid}" ]; then<br />
kill ${deye_mqtt_pid}<br />
log_info "Info: KILL running deye_mqtt ${deye_mqtt_pid}"<br />
fi<br />
<br />
# kill any running deye requests<br />
deye_ps=$(ps ax | grep -v grep | grep "$deye_inverter_sh " | awk '{print $1}')<br />
[ ! -z "$deye_ps" ] && kill $deye_ps<br />
}<br />
<br />
# stop all runnning instances<br />
deye_inverter_mqtt_stop ()<br />
{<br />
kill_old_instance<br />
kill_deye_inverter_instance<br />
}<br />
<br />
# start deye_inverter_mqtt<br />
deye_inverter_mqtt_start ()<br />
{<br />
log_info "Deye MQTT start"<br />
cd ${deye_inverter_dir}<br />
${deye_inverter_mqtt_cmd}<br />
}<br />
<br />
# read year_month register 0x16 = 22<br />
read_year_month ()<br />
{<br />
${deye_inverter_sh} -r 22 | sed 's/int: //g' | cut -d, -f1<br />
}<br />
<br />
# read day register 0x17 = 23<br />
read_day ()<br />
{<br />
${deye_inverter_sh} -r 23 | sed 's/.*h: //g' | cut -d, -f1<br />
}<br />
<br />
# read Daily power register 0x3c = 60<br />
read_daily_Power ()<br />
{<br />
${deye_inverter_sh} -r 60 | sed 's/int: //g' | cut -d, -f1<br />
}<br />
<br />
# read actual power register 0x56 = 86<br />
read_actual_Power ()<br />
{<br />
# doubleRegisterSensor("AC Active Power", 0x56, 0.1, mqtt_topic_suffix='ac/active_power', groups=['string', 'micro'])<br />
# (int.from_bytes(high_word, 'big') * 65536 + int.from_bytes(low_word, 'big')) * self.factor<br />
# val86=${deye_inverter_sh} -r 86 | sed 's/int: //g' | cut -d, -f1<br />
# val87=${deye_inverter_sh} -r 87 | sed 's/int: //g' | cut -d, -f1<br />
# echo $(( (${val87} * 65536 + ${val86}) * 0.1 ))<br />
<br />
# simple value is enough for our purpose<br />
${deye_inverter_sh} -r 86 | sed 's/int: //g' | cut -d, -f1<br />
}<br />
<br />
# write actual date/time into deye inverter<br />
init_deye_inverter ()<br />
{<br />
# daily reset phase<br />
daily_reset=init<br />
<br />
# ensure by loop, that all register values are written<br />
while [ ${daily_reset} != "done" ]; do<br />
<br />
# wait until we have enough power<br />
while [ "0$(read_actual_Power)" -le 10 ]; do<br />
sleep 600<br />
done<br />
<br />
# read Daily power register<br />
dailyPower=$(read_daily_Power)<br />
<br />
# read (old) year_month and day<br />
val22_old=$(read_year_month)<br />
val23_day=$(read_day)<br />
log_info "date/time update initialized"<br />
<br />
# set variables year, month, day, hour, minute, second<br />
set_date_time_vars<br />
<br />
# calculate register 22-24 with date and time vars<br />
val22=$(( ${year} * 256 + ${month} ))<br />
val23=$(( ${day} * 256 + ${hour} ))<br />
val24=$(( ${minute} * 256 + ${second} ))<br />
<br />
# reset power/date/time only once a day or exit loop<br />
[ "$dailyPower" -eq 0 -a<br />
"${val22_old}" = "${val22}" -a<br />
"${val23_day}" = "${day}" ] && break<br />
<br />
# reset daily_power by<br />
# setting register 22-24 with actual date + time<br />
${deye_inverter_sh} -w 22 $val22 || continue<br />
${deye_inverter_sh} -w 23 $val23 || continue<br />
${deye_inverter_sh} -w 24 $val24 || continue<br />
<br />
# test success<br />
# read Daily power register, should be resetted to 0 now<br />
dailyPower=$(read_daily_Power)<br />
<br />
# exit loop if power is updated, else log that reset failed<br />
if [ "$dailyPower" -eq 0 ]; then<br />
daily_reset=done<br />
<br />
### skip loop here, reset doesn't work always :-(<br />
# elif [ $daily_reset = "init" ]; then<br />
# daily_reset=failed<br />
# sleep 300<br />
<br />
else<br />
log_info "daily power reset failed"<br />
break<br />
fi<br />
done<br />
log_info "date/time reset done"<br />
}<br />
<br />
# wait for (next) daylight<br />
wait_for_sunrise ()<br />
{<br />
sunrise=$($sunrise_cmd)<br />
sunset=$($sunset_cmd)<br />
<br />
current_time=$(date +%s)<br />
sunrise=$(date -d"$sunrise" +%s)<br />
sunset=$(date -d"$sunset" +%s)<br />
<br />
# calulate time in secs until (next) sunrise<br />
((sleep_sunrise=$sunrise - $current_time)) # this morning<br />
[ $sleep_sunrise -lt 0 ] &&<br />
((sleep_sunrise=$sunrise + 86400)) # next morning<br />
<br />
# wait for daylight only<br />
if [ $current_time -lt $sunrise -o<br />
$current_time -gt $sunset ]; then<br />
log_info sleep $sleep_sunrise<br />
sleep $sleep_sunrise<br />
fi<br />
}<br />
<br />
# wait before sunset only<br />
wait_before_sunset ()<br />
{<br />
sunset=$($sunset_cmd)<br />
<br />
current_time=$(date +%s)<br />
sunset=$(date -d"$sunset" +%s)<br />
<br />
# calulate time in secs until next sunset<br />
((sleep_sunset=$sunset - $current_time))<br />
<br />
# wait before sunset only<br />
if [ $sleep_sunset -gt 0 ]; then<br />
log_info sleep $sleep_sunset<br />
sleep $sleep_sunset<br />
fi<br />
}<br />
<br />
# ------------------------<br />
# MAIN: Script starts here<br />
# ------------------------<br />
<br />
# sunset_stop: wait until sunset before we stop running instance<br />
[ "$arg" = "sunset_stop" ] &&<br />
wait_before_sunset<br />
<br />
# stop any runnning deye_inverter_mqtt processes<br />
deye_inverter_mqtt_stop<br />
<br />
# stop: don't start again<br />
[ "$arg" = "stop" ] && exit 0<br />
<br />
# start deye_mqtt_loop<br />
# check every 15 min for running deye_inverter_mqtt<br />
while :; do<br />
<br />
# wait for (next) daylight<br />
wait_for_sunrise<br />
<br />
# setting date/time also resets daily power register<br />
init_deye_inverter<br />
<br />
# start deye_inverter_mqtt<br />
get_deye_mqtt_pid<br />
if [ -z "$deye_mqtt_pid" ]; then<br />
deye_inverter_mqtt_start<br />
fi<br />
<br />
# wait 15 min<br />
sleep 900<br />
done<br />
</pre><br />
<br />
=== Contrab und /etc/rc.local ===<br />
Mein Crontab Eintrag:<br />
<pre># stop DEYE monitoring after sunset (FHEM)<br />
0 18 * * * /root/sbin/deye_mqtt_loop.sh sunset_stop 2>/dev/null<br />
</pre><br />
und /etc/rc.local:<br />
<pre># start monitoring of PV-System: DEYE inverter<br />
$(sleep 10; /root/sbin/deye_mqtt_loop.sh 2>dev/null)&<br />
</pre><br />
<br />
== Dashboard ==<br />
So sieht das aus, wenn es den ganzen Tag im April regnet und bedeckt ist:<br />
[[Datei:Solar Bedeckt Regen.jpg|midi]]<br />
<br />
Die Eigenverbrauchsquote habe ich mittels Grafana ermittelt:<br />
[[Datei:Solar Eigenverbrauch.jpg|midi|Eigenverbrauchsquote]]</div>
Neobiker
http://neobiker.de/wiki/index.php?title=DEYE_Wechselrichter&diff=2143
DEYE Wechselrichter
2024-02-03T15:26:03Z
<p>Neobiker: /* deye_mqtt_loop.sh */</p>
<hr />
<div>= Balkonkraftwerk: DEYE Wechselrichter ohne Cloud auslesen und per MQTT monitoren =<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
Der WR bietet verschiedene Möglichkeiten zur Konfiguration und Abfrage der aktuellen Werte:<br />
* einen internen Webserver (http://10.10.10.254)<br />
* die Cloud Anbindung zu Solarman (Solarman Smart App)<br />
* (angepasstes) Modbus Protokoll über Port 8899<br />
* AT+ Befehle über Port 48899<br />
<br />
Ich verwende das Modbus Protokoll zum auslesen des WR, das <br />
# effizient/schnell ist (Performance)<br />
# ohne Internetverbindung auskommt<br />
<br />
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.<br />
<br />
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.<br />
<br />
== Wechselrichter Eigenheiten ==<br />
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. <br />
<br />
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.<br />
<br />
== Installation ==<br />
Das Projekt von Github laden und in einem Verzeichnis speichern, '''Python''' ist normalerweise ja schon installiert. <br />
<br />
<pre>git clone https://github.com/kbialek/deye-inverter-mqtt<br />
pip install paho-mqtt<br />
cd deye-inverter-mqtt</pre><br />
<br />
Unter Debian habe ich im Skript '''deye_cli.sh''' python durch python3 ersetzen müssen:<br />
<pre>#!/bin/bash<br />
set -a; source config.env; set +a<br />
python3 deye_cli.py "$@"<br />
</pre><br />
<br />
=== config.env ===<br />
Die Konfiguration meines DEYE Wechselrichters vom Typ Micro-Inverter:<br />
'''config.env'''<br />
<pre>DEYE_LOGGER_IP_ADDRESS=<IP Wechselrichter im WLAN><br />
DEYE_LOGGER_PORT=8899<br />
DEYE_LOGGER_SERIAL_NUMBER=<Seriennummer des WR><br />
<br />
MQTT_HOST=<IP von MQTT Server><br />
MQTT_PORT=1883<br />
MQTT_USERNAME=<br />
MQTT_PASSWORD=<br />
MQTT_TOPIC_PREFIX=deye<br />
<br />
LOG_LEVEL=ERROR<br />
DEYE_DATA_READ_INTERVAL=60<br />
DEYE_METRIC_GROUPS=micro<br />
</pre><br />
<br />
=== deye_inverter.sh === <br />
Ausserdem habe ich ein kleines ''Wrapper Skript'' geschrieben, um '''Daten des WR''' einfacher '''lesen und schreiben''' zu können:<br><br />
<br />
'''deye_inverter.sh''' [--check <pause>] --read <register> | --write <register> <value><br />
<br />
<pre>#!/bin/bash<br />
# Read / Write DEYE Inverters<br />
# via deye_inverter_mqtt python package<br />
# https://github.com/kbialek/deye-inverter-mqtt<br />
<br />
retries=2 # retry deye_inverter command multiple times<br />
sleep=60 # sleep time between retries<br />
<br />
# locate script in deye_inverter_mqtt directory<br />
cd $(dirname $0)<br />
if [ ! -f ./deye_cli.sh ]; then<br />
echo "Error: ./deye_cli.sh not found."<br />
echo " Please move $(basename $0) in deye-inverter-mqtt directory."<br />
exit 1<br />
fi<br />
. ./config.env<br />
<br />
log_info ()<br />
{<br />
if [ ${LOG_LEVEL} = "INFO" ]; then<br />
logger -t $(basename $0) $@<br />
fi<br />
}<br />
<br />
log_error ()<br />
{<br />
logger -t $(basename $0) $@<br />
echo 2>&1 $@<br />
}<br />
<br />
# read parameters: mode [rw], register, value and optional -c <check pause><br />
pause=0<br />
while [ $# -gt 0 ]; do<br />
case $1 in<br />
-c|--check) shift<br />
[ $# -ge 3 ] || exit 1<br />
pause=$1<br />
;;<br />
-w|--write) mode=w<br />
shift<br />
[ $# -eq 2 ] || exit 1<br />
reg=$1<br />
val=$2<br />
shift<br />
;;<br />
-r|--read) mode=r<br />
shift<br />
[ $# -eq 1 ] || exit 1<br />
reg=$1<br />
;;<br />
esac<br />
shift<br />
done<br />
<br />
# handle offline deye_inverter by<br />
# ${pause} > 0 -> endless loop<br />
# ${pause} = 0 -> error after ${retries}<br />
while true; do<br />
<br />
# try deye_cli.sh $retries times every $sleep secs<br />
check=${retries}<br />
while [ ${check} -gt 0 ]; do<br />
<br />
case ${mode} in<br />
r) result=$(./deye_cli.sh $mode $reg | grep 'int: ')<br />
if [ -n "$result" ]; then<br />
echo "$result"<br />
log_info -t $0 deye_cli.sh $mode $reg<br />
exit 0<br />
fi<br />
;;<br />
w) result=$(./deye_cli.sh $mode $reg $val)<br />
if [ "$result" = "Ok" ]; then<br />
log_info -t $0 deye_cli.sh $mode $reg $val<br />
exit 0<br />
fi<br />
;;<br />
esac<br />
<br />
# wait ${sleep} or ${pause} secs<br />
sleep $(( ${pause} ? ${sleep} : ${pause} ))<br />
<br />
# try ${retries} times<br />
(( check-- ))<br />
done<br />
<br />
# error, or retry endless until wakeup of deye_inverter<br />
if [ ${pause} -eq 0 ]; then<br />
log_error "Error: deye_cli.sh $mode $reg $val failed."<br />
exit 1<br />
fi<br />
<br />
done<br />
</pre><br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
Folgendes Skript starte ich jeden Morgen um 04:00 Uhr per Cron (und in rc.local fürs booten):<br />
<br />
=== deye_mqtt_loop.sh ===<br />
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.<br />
<br />
Ausserdem setzt es beim Start den Tageszähler des Wechselrichters bedarfsweise zurück, bevor die Werte des DEYE Wechselrichters per Endlosschleife abgefragt werden.<br />
<br />
<pre>#!/bin/bash<br />
# reset Daily_Power of DEYE Inverters<br />
# via deye_inverter_mqtt python package<br />
# https://github.com/kbialek/deye-inverter-mqtt<br />
<br />
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin<br />
<br />
# config:<br />
deye_inverter_dir=/root/deye-inverter-mqtt<br />
fhem_dir=/opt/fhem<br />
horizon="-2" # sunset CIVIL=-6, REAL=0<br />
<br />
debug=info<br />
<br />
myPID=$$<br />
myname=$(basename $0)<br />
arg="$1"<br />
<br />
deye_inverter_sh=$deye_inverter_dir/deye_inverter.sh<br />
deye_inverter_mqtt_cfg=$deye_inverter_dir/config.env<br />
deye_inverter_mqtt_cmd="python3 deye_docker_entrypoint.py"<br />
<br />
sunrise_cmd="perl $fhem_dir/fhem.pl localhost:7072 {sunrise_abs('HORIZON=$horizon')}"<br />
sunset_cmd="perl $fhem_dir/fhem.pl localhost:7072 {sunset_abs('HORIZON=$horizon')}"<br />
<br />
# installation:<br />
# locate FHEM, used for sunrise(), sunset()<br />
if [ ! -x ${fhem_dir/fhem.pl} ]; then<br />
echo "Error: ${fhem_dir} not found."<br />
echo " Is FHEM installed in $fhem_dir ?"<br />
exit 1<br />
fi<br />
<br />
# locate script in deye_inverter_mqtt directory<br />
if [ ! -f ${deye_inverter_mqtt_cfg} ]; then<br />
echo "Error: ${deye_inverter_mqtt_cfg} not found."<br />
echo " Please update \$deye_inverter_sh path in $0"<br />
exit 1<br />
fi<br />
<br />
# read my config<br />
set -a; source ${deye_inverter_mqtt_cfg}; set +a<br />
<br />
[ "$debug" = "full" ] && set -x<br />
<br />
# ----- functions () ------------------------------------<br />
log_info ()<br />
{<br />
[ "$debug" = "yes" -o "$debug" = "true" -o "$debug" = "info" ] &&<br />
echo $myname: $@<br />
[ ${LOG_LEVEL} = "INFO" ] && logger -t $myname $@<br />
}<br />
<br />
log_error ()<br />
{<br />
logger -t $myname $@<br />
echo 1>&2 $@<br />
}<br />
<br />
# store actual date + time into vars<br />
set_date_time_vars ()<br />
{<br />
year=$(date +%y)<br />
month=$(date +%m | sed 's/^0//g')<br />
day=$(date +%d | sed 's/^0//g')<br />
hour=$(date +%H | sed 's/^0//g')<br />
minute=$(date +%m | sed 's/^0//g')<br />
second=$(date +%S | sed 's/^0//g')<br />
}<br />
<br />
# kill deye_inverter_loop ()<br />
kill_old_instance ()<br />
{<br />
# kill old instances of me<br />
my_PS=$(ps ax | grep -v grep | grep "${myname}" | awk '{print $1}')<br />
for ps in $my_PS; do<br />
[ $ps -lt $myPID ] && kill $ps<br />
done<br />
}<br />
<br />
# get deye_inverter_mqtt PIDs<br />
get_deye_mqtt_pid ()<br />
{<br />
deye_mqtt_pid=$(ps ax | grep -v grep | grep "${deye_inverter_mqtt_cmd}" | awk '{print $1}')<br />
}<br />
<br />
# kill deye_inverter_instance ()<br />
kill_deye_inverter_instance ()<br />
{<br />
get_deye_mqtt_pid<br />
if [ ! -z "${deye_mqtt_pid}" ]; then<br />
kill ${deye_mqtt_pid}<br />
log_info "Info: KILL running deye_mqtt ${deye_mqtt_pid}"<br />
fi<br />
<br />
# kill any running deye requests<br />
deye_ps=$(ps ax | grep -v grep | grep "$deye_inverter_sh " | awk '{print $1}')<br />
[ ! -z "$deye_ps" ] && kill $deye_ps<br />
}<br />
<br />
# stop all runnning instances<br />
deye_inverter_mqtt_stop ()<br />
{<br />
kill_old_instance<br />
kill_deye_inverter_instance<br />
}<br />
<br />
# start deye_inverter_mqtt<br />
deye_inverter_mqtt_start ()<br />
{<br />
log_info "Deye MQTT start"<br />
cd ${deye_inverter_dir}<br />
${deye_inverter_mqtt_cmd}<br />
}<br />
<br />
# read year_month register 0x16 = 22<br />
read_year_month ()<br />
{<br />
${deye_inverter_sh} -r 22 | sed 's/int: //g' | cut -d, -f1<br />
}<br />
<br />
# read day register 0x17 = 23<br />
read_day ()<br />
{<br />
${deye_inverter_sh} -r 23 | sed 's/.*h: //g' | cut -d, -f1<br />
}<br />
<br />
# read Daily power register 0x3c = 60<br />
read_daily_Power ()<br />
{<br />
${deye_inverter_sh} -r 60 | sed 's/int: //g' | cut -d, -f1<br />
}<br />
<br />
# read actual power register 0x56 = 86<br />
read_actual_Power ()<br />
{<br />
# doubleRegisterSensor("AC Active Power", 0x56, 0.1, mqtt_topic_suffix='ac/active_power', groups=['string', 'micro'])<br />
# (int.from_bytes(high_word, 'big') * 65536 + int.from_bytes(low_word, 'big')) * self.factor<br />
# val86=${deye_inverter_sh} -r 86 | sed 's/int: //g' | cut -d, -f1<br />
# val87=${deye_inverter_sh} -r 87 | sed 's/int: //g' | cut -d, -f1<br />
# echo $(( (${val87} * 65536 + ${val86}) * 0.1 ))<br />
<br />
# simple value is enough for our purpose<br />
${deye_inverter_sh} -r 86 | sed 's/int: //g' | cut -d, -f1<br />
}<br />
<br />
# write actual date/time into deye inverter<br />
reset_deye_inverter ()<br />
{<br />
# daily reset phase<br />
daily_reset=init<br />
<br />
# ensure by loop, that all register values are written<br />
while [ ${daily_reset} != "done" ]; do<br />
<br />
# wait until we have enough power<br />
while [ "0$(read_actual_Power)" -le 10 ]; do<br />
sleep 600<br />
done<br />
<br />
# read Daily power register<br />
dailyPower=$(read_daily_Power)<br />
<br />
# read (old) year_month and day<br />
val22_old=$(read_year_month)<br />
val23_day=$(read_day)<br />
log_info "date/time update initialized"<br />
<br />
# set variables year, month, day, hour, minute, second<br />
set_date_time_vars<br />
<br />
# calculate register 22-24 with date and time vars<br />
val22=$(( ${year} * 256 + ${month} ))<br />
val23=$(( ${day} * 256 + ${hour} ))<br />
val24=$(( ${minute} * 256 + ${second} ))<br />
<br />
# reset power/date/time only once a day or exit loop<br />
[ "$dailyPower" -eq 0 -a<br />
"${val22_old}" = "${val22}" -a<br />
"${val23_day}" = "${day}" ] && break<br />
<br />
# reset daily_power by<br />
# setting register 22-24 with actual date + time<br />
${deye_inverter_sh} -w 22 $val22 || continue<br />
${deye_inverter_sh} -w 23 $val23 || continue<br />
${deye_inverter_sh} -w 24 $val24 || continue<br />
<br />
# test success<br />
# read Daily power register, should be resetted to 0 now<br />
dailyPower=$(read_daily_Power)<br />
<br />
# exit loop if power is updated, else log that reset failed<br />
if [ "$dailyPower" -eq 0 ]; then<br />
daily_reset=done<br />
<br />
### skip loop here, reset doesn't work always :-(<br />
# elif [ $daily_reset = "init" ]; then<br />
# daily_reset=failed<br />
# sleep 300<br />
<br />
else<br />
log_info "daily power reset failed"<br />
break<br />
fi<br />
done<br />
log_info "date/time reset done"<br />
}<br />
<br />
# wait for (next) daylight<br />
wait_for_sunrise ()<br />
{<br />
sunrise=$($sunrise_cmd)<br />
sunset=$($sunset_cmd)<br />
<br />
current_time=$(date +%s)<br />
sunrise=$(date -d"$sunrise" +%s)<br />
sunset=$(date -d"$sunset" +%s)<br />
<br />
# calulate time in secs until (next) sunrise<br />
((sleep_sunrise=$sunrise - $current_time)) # this morning<br />
[ $sleep_sunrise -lt 0 ] &&<br />
((sleep_sunrise=$sunrise + 86400)) # next morning<br />
<br />
# wait for daylight only<br />
if [ $current_time -lt $sunrise -o<br />
$current_time -gt $sunset ]; then<br />
log_info sleep $sleep_sunrise<br />
sleep $sleep_sunrise<br />
fi<br />
}<br />
<br />
# wait before sunset only<br />
wait_before_sunset ()<br />
{<br />
sunset=$($sunset_cmd)<br />
<br />
current_time=$(date +%s)<br />
sunset=$(date -d"$sunset" +%s)<br />
<br />
# calulate time in secs until next sunset<br />
((sleep_sunset=$sunset - $current_time))<br />
<br />
# wait before sunset only<br />
if [ $sleep_sunset -gt 0 ]; then<br />
log_info sleep $sleep_sunset<br />
sleep $sleep_sunset<br />
fi<br />
}<br />
<br />
# ------------------------<br />
# MAIN: Script starts here<br />
# ------------------------<br />
<br />
# sunset_stop: wait until sunset before we stop running instance<br />
[ "$arg" = "sunset_stop" ] &&<br />
wait_before_sunset<br />
<br />
# stop any runnning deye_inverter_mqtt processes<br />
deye_inverter_mqtt_stop<br />
<br />
# stop: don't start again<br />
[ "$arg" = "stop" ] && exit 0<br />
<br />
# start deye_mqtt_loop<br />
# check every 15 min for running deye_inverter_mqtt<br />
while :; do<br />
<br />
# wait for (next) daylight<br />
wait_for_sunrise<br />
<br />
# setting date/time also resets daily power register<br />
reset_deye_inverter<br />
<br />
# start deye_inverter_mqtt<br />
get_deye_mqtt_pid<br />
if [ -z "$deye_mqtt_pid" ]; then<br />
deye_inverter_mqtt_start<br />
fi<br />
<br />
# wait 15 min<br />
sleep 900<br />
done<br />
</pre><br />
<br />
=== Contrab und /etc/rc.local ===<br />
Mein Crontab Eintrag:<br />
<pre># stop DEYE monitoring after sunset (FHEM)<br />
0 18 * * * /root/sbin/deye_mqtt_loop.sh sunset_stop 2>/dev/null<br />
</pre><br />
und /etc/rc.local:<br />
<pre># start monitoring of PV-System: DEYE inverter<br />
$(sleep 10; /root/sbin/deye_mqtt_loop.sh 2>dev/null)&<br />
</pre><br />
<br />
== Dashboard ==<br />
So sieht das aus, wenn es den ganzen Tag im April regnet und bedeckt ist:<br />
[[Datei:Solar Bedeckt Regen.jpg|midi]]<br />
<br />
Die Eigenverbrauchsquote habe ich mittels Grafana ermittelt:<br />
[[Datei:Solar Eigenverbrauch.jpg|midi|Eigenverbrauchsquote]]</div>
Neobiker
http://neobiker.de/wiki/index.php?title=DEYE_Wechselrichter&diff=2142
DEYE Wechselrichter
2024-02-03T15:14:13Z
<p>Neobiker: /* deye_mqtt_loop.sh */</p>
<hr />
<div>= Balkonkraftwerk: DEYE Wechselrichter ohne Cloud auslesen und per MQTT monitoren =<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
Der WR bietet verschiedene Möglichkeiten zur Konfiguration und Abfrage der aktuellen Werte:<br />
* einen internen Webserver (http://10.10.10.254)<br />
* die Cloud Anbindung zu Solarman (Solarman Smart App)<br />
* (angepasstes) Modbus Protokoll über Port 8899<br />
* AT+ Befehle über Port 48899<br />
<br />
Ich verwende das Modbus Protokoll zum auslesen des WR, das <br />
# effizient/schnell ist (Performance)<br />
# ohne Internetverbindung auskommt<br />
<br />
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.<br />
<br />
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.<br />
<br />
== Wechselrichter Eigenheiten ==<br />
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. <br />
<br />
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.<br />
<br />
== Installation ==<br />
Das Projekt von Github laden und in einem Verzeichnis speichern, '''Python''' ist normalerweise ja schon installiert. <br />
<br />
<pre>git clone https://github.com/kbialek/deye-inverter-mqtt<br />
pip install paho-mqtt<br />
cd deye-inverter-mqtt</pre><br />
<br />
Unter Debian habe ich im Skript '''deye_cli.sh''' python durch python3 ersetzen müssen:<br />
<pre>#!/bin/bash<br />
set -a; source config.env; set +a<br />
python3 deye_cli.py "$@"<br />
</pre><br />
<br />
=== config.env ===<br />
Die Konfiguration meines DEYE Wechselrichters vom Typ Micro-Inverter:<br />
'''config.env'''<br />
<pre>DEYE_LOGGER_IP_ADDRESS=<IP Wechselrichter im WLAN><br />
DEYE_LOGGER_PORT=8899<br />
DEYE_LOGGER_SERIAL_NUMBER=<Seriennummer des WR><br />
<br />
MQTT_HOST=<IP von MQTT Server><br />
MQTT_PORT=1883<br />
MQTT_USERNAME=<br />
MQTT_PASSWORD=<br />
MQTT_TOPIC_PREFIX=deye<br />
<br />
LOG_LEVEL=ERROR<br />
DEYE_DATA_READ_INTERVAL=60<br />
DEYE_METRIC_GROUPS=micro<br />
</pre><br />
<br />
=== deye_inverter.sh === <br />
Ausserdem habe ich ein kleines ''Wrapper Skript'' geschrieben, um '''Daten des WR''' einfacher '''lesen und schreiben''' zu können:<br><br />
<br />
'''deye_inverter.sh''' [--check <pause>] --read <register> | --write <register> <value><br />
<br />
<pre>#!/bin/bash<br />
# Read / Write DEYE Inverters<br />
# via deye_inverter_mqtt python package<br />
# https://github.com/kbialek/deye-inverter-mqtt<br />
<br />
retries=2 # retry deye_inverter command multiple times<br />
sleep=60 # sleep time between retries<br />
<br />
# locate script in deye_inverter_mqtt directory<br />
cd $(dirname $0)<br />
if [ ! -f ./deye_cli.sh ]; then<br />
echo "Error: ./deye_cli.sh not found."<br />
echo " Please move $(basename $0) in deye-inverter-mqtt directory."<br />
exit 1<br />
fi<br />
. ./config.env<br />
<br />
log_info ()<br />
{<br />
if [ ${LOG_LEVEL} = "INFO" ]; then<br />
logger -t $(basename $0) $@<br />
fi<br />
}<br />
<br />
log_error ()<br />
{<br />
logger -t $(basename $0) $@<br />
echo 2>&1 $@<br />
}<br />
<br />
# read parameters: mode [rw], register, value and optional -c <check pause><br />
pause=0<br />
while [ $# -gt 0 ]; do<br />
case $1 in<br />
-c|--check) shift<br />
[ $# -ge 3 ] || exit 1<br />
pause=$1<br />
;;<br />
-w|--write) mode=w<br />
shift<br />
[ $# -eq 2 ] || exit 1<br />
reg=$1<br />
val=$2<br />
shift<br />
;;<br />
-r|--read) mode=r<br />
shift<br />
[ $# -eq 1 ] || exit 1<br />
reg=$1<br />
;;<br />
esac<br />
shift<br />
done<br />
<br />
# handle offline deye_inverter by<br />
# ${pause} > 0 -> endless loop<br />
# ${pause} = 0 -> error after ${retries}<br />
while true; do<br />
<br />
# try deye_cli.sh $retries times every $sleep secs<br />
check=${retries}<br />
while [ ${check} -gt 0 ]; do<br />
<br />
case ${mode} in<br />
r) result=$(./deye_cli.sh $mode $reg | grep 'int: ')<br />
if [ -n "$result" ]; then<br />
echo "$result"<br />
log_info -t $0 deye_cli.sh $mode $reg<br />
exit 0<br />
fi<br />
;;<br />
w) result=$(./deye_cli.sh $mode $reg $val)<br />
if [ "$result" = "Ok" ]; then<br />
log_info -t $0 deye_cli.sh $mode $reg $val<br />
exit 0<br />
fi<br />
;;<br />
esac<br />
<br />
# wait ${sleep} or ${pause} secs<br />
sleep $(( ${pause} ? ${sleep} : ${pause} ))<br />
<br />
# try ${retries} times<br />
(( check-- ))<br />
done<br />
<br />
# error, or retry endless until wakeup of deye_inverter<br />
if [ ${pause} -eq 0 ]; then<br />
log_error "Error: deye_cli.sh $mode $reg $val failed."<br />
exit 1<br />
fi<br />
<br />
done<br />
</pre><br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
Folgendes Skript starte ich jeden Morgen um 04:00 Uhr per Cron (und in rc.local fürs booten):<br />
<br />
=== deye_mqtt_loop.sh ===<br />
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.<br />
<br />
Ausserdem setzt es beim Start den Tageszähler des Wechselrichters bedarfsweise zurück, bevor die Werte des DEYE Wechselrichters per Endlosschleife abgefragt werden.<br />
<br />
<pre>#!/bin/bash<br />
# reset Daily_Power of DEYE Inverters<br />
# via deye_inverter_mqtt python package<br />
# https://github.com/kbialek/deye-inverter-mqtt<br />
<br />
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin<br />
<br />
# config:<br />
deye_inverter_dir=/root/deye-inverter-mqtt<br />
fhem_dir=/opt/fhem<br />
horizon="-2" # sunset CIVIL=-6, REAL=0<br />
<br />
debug=info<br />
<br />
myPID=$$<br />
myname=$(basename $0)<br />
arg="$1"<br />
<br />
deye_inverter_sh=$deye_inverter_dir/deye_inverter.sh<br />
deye_inverter_mqtt_cfg=$deye_inverter_dir/config.env<br />
deye_inverter_mqtt_cmd="python3 deye_docker_entrypoint.py"<br />
<br />
sunrise_cmd="perl $fhem_dir/fhem.pl localhost:7072 {sunrise_abs('HORIZON=$horizon')}"<br />
sunset_cmd="perl $fhem_dir/fhem.pl localhost:7072 {sunset_abs('HORIZON=$horizon')}"<br />
<br />
# installation:<br />
# locate FHEM, used for sunrise(), sunset()<br />
if [ ! -x ${fhem_dir/fhem.pl} ]; then<br />
echo "Error: ${fhem_dir} not found."<br />
echo " Is FHEM installed in $fhem_dir ?"<br />
exit 1<br />
fi<br />
<br />
# locate script in deye_inverter_mqtt directory<br />
if [ ! -f ${deye_inverter_mqtt_cfg} ]; then<br />
echo "Error: ${deye_inverter_mqtt_cfg} not found."<br />
echo " Please update \$deye_inverter_sh path in $0"<br />
exit 1<br />
fi<br />
<br />
# read my config<br />
set -a; source ${deye_inverter_mqtt_cfg}; set +a<br />
<br />
[ "$debug" = "full" ] && set -x<br />
<br />
# ----- functions () ------------------------------------<br />
log_info ()<br />
{<br />
[ "$debug" = "yes" -o "$debug" = "true" -o "$debug" = "info" ] &&<br />
echo $myname: $@<br />
[ ${LOG_LEVEL} = "INFO" ] && logger -t $myname $@<br />
}<br />
<br />
log_error ()<br />
{<br />
logger -t $myname $@<br />
echo 1>&2 $@<br />
}<br />
<br />
# store actual date + time into vars<br />
set_date_time_vars ()<br />
{<br />
year=$(date +%y)<br />
month=$(date +%m | sed 's/^0//g')<br />
day=$(date +%d | sed 's/^0//g')<br />
hour=$(date +%H | sed 's/^0//g')<br />
minute=$(date +%m | sed 's/^0//g')<br />
second=$(date +%S | sed 's/^0//g')<br />
}<br />
<br />
# kill deye_inverter_loop ()<br />
kill_old_instance ()<br />
{<br />
# kill old instances of me<br />
my_PS=$(ps ax | grep -v grep | grep "${myname}" | awk '{print $1}')<br />
for ps in $my_PS; do<br />
[ $ps -lt $myPID ] && kill $ps<br />
done<br />
}<br />
<br />
# get deye_inverter_mqtt PIDs<br />
get_deye_mqtt_pid ()<br />
{<br />
deye_mqtt_pid=$(ps ax | grep -v grep | grep "${deye_inverter_mqtt_cmd}" | awk '{print $1}')<br />
}<br />
<br />
# kill deye_inverter_instance ()<br />
kill_deye_inverter_instance ()<br />
{<br />
get_deye_mqtt_pid<br />
if [ ! -z "${deye_mqtt_pid}" ]; then<br />
kill ${deye_mqtt_pid}<br />
log_info "Info: KILL running deye_mqtt ${deye_mqtt_pid}"<br />
fi<br />
<br />
# kill any running deye requests<br />
deye_ps=$(ps ax | grep -v grep | grep "$deye_inverter_sh " | awk '{print $1}')<br />
[ ! -z "$deye_ps" ] && kill $deye_ps<br />
}<br />
<br />
# stop all runnning instances<br />
deye_inverter_mqtt_stop ()<br />
{<br />
kill_old_instance<br />
kill_deye_inverter_instance<br />
}<br />
<br />
# start deye_inverter_mqtt<br />
deye_inverter_mqtt_start ()<br />
{<br />
log_info "Deye MQTT start"<br />
cd ${deye_inverter_dir}<br />
${deye_inverter_mqtt_cmd}<br />
}<br />
<br />
# read year_month register 0x16 = 22<br />
read_year_month ()<br />
{<br />
${deye_inverter_sh} -r 22 | sed 's/int: //g' | cut -d, -f1<br />
}<br />
<br />
# read day register 0x17 = 23<br />
read_day ()<br />
{<br />
${deye_inverter_sh} -r 23 | sed 's/.*h: //g' | cut -d, -f1<br />
}<br />
<br />
# read Daily power register 0x3c = 60<br />
read_daily_Power ()<br />
{<br />
${deye_inverter_sh} -r 60 | sed 's/int: //g' | cut -d, -f1<br />
}<br />
<br />
# read actual power register 0x56 = 86<br />
read_actual_Power ()<br />
{<br />
# doubleRegisterSensor("AC Active Power", 0x56, 0.1, mqtt_topic_suffix='ac/active_power', groups=['string', 'micro'])<br />
# (int.from_bytes(high_word, 'big') * 65536 + int.from_bytes(low_word, 'big')) * self.factor<br />
# val86=${deye_inverter_sh} -r 86 | sed 's/int: //g' | cut -d, -f1<br />
# val87=${deye_inverter_sh} -r 87 | sed 's/int: //g' | cut -d, -f1<br />
# echo $(( (${val87} * 65536 + ${val86}) * 0.1 ))<br />
<br />
# simple value is enough for our purpose<br />
${deye_inverter_sh} -r 86 | sed 's/int: //g' | cut -d, -f1<br />
}<br />
<br />
# write actual date/time into deye inverter<br />
reset_deye_inverter ()<br />
{<br />
# daily reset phase<br />
daily_reset=init<br />
<br />
# ensure by loop, that all register values are written<br />
while [ ${daily_reset} != "done" ]; do<br />
<br />
# wait until we have enough power<br />
while [ "$(read_actual_Power)" -le 10 ]; do<br />
sleep 600<br />
done<br />
<br />
# read Daily power register<br />
dailyPower=$(read_daily_Power)<br />
<br />
# read (old) year_month and day<br />
val22_old=$(read_year_month)<br />
val23_day=$(read_day)<br />
log_info "date/time update initialized"<br />
<br />
# set variables year, month, day, hour, minute, second<br />
set_date_time_vars<br />
<br />
# calculate register 22-24 with date and time vars<br />
val22=$(( ${year} * 256 + ${month} ))<br />
val23=$(( ${day} * 256 + ${hour} ))<br />
val24=$(( ${minute} * 256 + ${second} ))<br />
<br />
# reset power/date/time only once a day or exit loop<br />
[ "$dailyPower" -eq 0 -a<br />
"${val22_old}" = "${val22}" -a<br />
"${val23_day}" = "${day}" ] && break<br />
<br />
# reset daily_power by<br />
# setting register 22-24 with actual date + time<br />
${deye_inverter_sh} -w 22 $val22 || continue<br />
${deye_inverter_sh} -w 23 $val23 || continue<br />
${deye_inverter_sh} -w 24 $val24 || continue<br />
<br />
# test success<br />
# read Daily power register, should be resetted to 0 now<br />
dailyPower=$(read_daily_Power)<br />
<br />
# exit loop if power is updated, else log that reset failed<br />
if [ "$dailyPower" -eq 0 ]; then<br />
daily_reset=done<br />
<br />
### skip loop here, reset doesn't work always :-(<br />
# elif [ $daily_reset = "init" ]; then<br />
# daily_reset=failed<br />
# sleep 300<br />
<br />
else<br />
log_info "daily power reset failed"<br />
break<br />
fi<br />
done<br />
log_info "date/time reset done"<br />
}<br />
<br />
# wait for (next) daylight<br />
wait_for_sunrise ()<br />
{<br />
sunrise=$($sunrise_cmd)<br />
sunset=$($sunset_cmd)<br />
<br />
current_time=$(date +%s)<br />
sunrise=$(date -d"$sunrise" +%s)<br />
sunset=$(date -d"$sunset" +%s)<br />
<br />
# calulate time in secs until (next) sunrise<br />
((sleep_sunrise=$sunrise - $current_time)) # this morning<br />
[ $sleep_sunrise -lt 0 ] &&<br />
((sleep_sunrise=$sunrise + 86400)) # next morning<br />
<br />
# wait for daylight only<br />
if [ $current_time -lt $sunrise -o<br />
$current_time -gt $sunset ]; then<br />
log_info sleep $sleep_sunrise<br />
sleep $sleep_sunrise<br />
fi<br />
}<br />
<br />
# wait before sunset only<br />
wait_before_sunset ()<br />
{<br />
sunset=$($sunset_cmd)<br />
<br />
current_time=$(date +%s)<br />
sunset=$(date -d"$sunset" +%s)<br />
<br />
# calulate time in secs until next sunset<br />
((sleep_sunset=$sunset - $current_time))<br />
<br />
# wait before sunset only<br />
if [ $sleep_sunset -gt 0 ]; then<br />
log_info sleep $sleep_sunset<br />
sleep $sleep_sunset<br />
fi<br />
}<br />
<br />
# ------------------------<br />
# MAIN: Script starts here<br />
# ------------------------<br />
<br />
# sunset_stop: wait until sunset before we stop running instance<br />
[ "$arg" = "sunset_stop" ] &&<br />
wait_before_sunset<br />
<br />
# stop any runnning deye_inverter_mqtt processes<br />
deye_inverter_mqtt_stop<br />
<br />
# stop: don't start again<br />
[ "$arg" = "stop" ] && exit 0<br />
<br />
# start deye_mqtt_loop<br />
# check every 15 min for running deye_inverter_mqtt<br />
while :; do<br />
<br />
# wait for (next) daylight<br />
wait_for_sunrise<br />
<br />
# setting date/time also resets daily power register<br />
reset_deye_inverter<br />
<br />
# start deye_inverter_mqtt<br />
get_deye_mqtt_pid<br />
if [ -z "$deye_mqtt_pid" ]; then<br />
deye_inverter_mqtt_start<br />
fi<br />
<br />
# wait 15 min<br />
sleep 900<br />
done<br />
</pre><br />
<br />
=== Contrab und /etc/rc.local ===<br />
Mein Crontab Eintrag:<br />
<pre># stop DEYE monitoring after sunset (FHEM)<br />
0 18 * * * /root/sbin/deye_mqtt_loop.sh sunset_stop 2>/dev/null<br />
</pre><br />
und /etc/rc.local:<br />
<pre># start monitoring of PV-System: DEYE inverter<br />
$(sleep 10; /root/sbin/deye_mqtt_loop.sh 2>dev/null)&<br />
</pre><br />
<br />
== Dashboard ==<br />
So sieht das aus, wenn es den ganzen Tag im April regnet und bedeckt ist:<br />
[[Datei:Solar Bedeckt Regen.jpg|midi]]<br />
<br />
Die Eigenverbrauchsquote habe ich mittels Grafana ermittelt:<br />
[[Datei:Solar Eigenverbrauch.jpg|midi|Eigenverbrauchsquote]]</div>
Neobiker
http://neobiker.de/wiki/index.php?title=DEYE_Wechselrichter&diff=2141
DEYE Wechselrichter
2024-02-03T15:03:11Z
<p>Neobiker: /* deye_mqtt_loop.sh */</p>
<hr />
<div>= Balkonkraftwerk: DEYE Wechselrichter ohne Cloud auslesen und per MQTT monitoren =<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
Der WR bietet verschiedene Möglichkeiten zur Konfiguration und Abfrage der aktuellen Werte:<br />
* einen internen Webserver (http://10.10.10.254)<br />
* die Cloud Anbindung zu Solarman (Solarman Smart App)<br />
* (angepasstes) Modbus Protokoll über Port 8899<br />
* AT+ Befehle über Port 48899<br />
<br />
Ich verwende das Modbus Protokoll zum auslesen des WR, das <br />
# effizient/schnell ist (Performance)<br />
# ohne Internetverbindung auskommt<br />
<br />
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.<br />
<br />
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.<br />
<br />
== Wechselrichter Eigenheiten ==<br />
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. <br />
<br />
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.<br />
<br />
== Installation ==<br />
Das Projekt von Github laden und in einem Verzeichnis speichern, '''Python''' ist normalerweise ja schon installiert. <br />
<br />
<pre>git clone https://github.com/kbialek/deye-inverter-mqtt<br />
pip install paho-mqtt<br />
cd deye-inverter-mqtt</pre><br />
<br />
Unter Debian habe ich im Skript '''deye_cli.sh''' python durch python3 ersetzen müssen:<br />
<pre>#!/bin/bash<br />
set -a; source config.env; set +a<br />
python3 deye_cli.py "$@"<br />
</pre><br />
<br />
=== config.env ===<br />
Die Konfiguration meines DEYE Wechselrichters vom Typ Micro-Inverter:<br />
'''config.env'''<br />
<pre>DEYE_LOGGER_IP_ADDRESS=<IP Wechselrichter im WLAN><br />
DEYE_LOGGER_PORT=8899<br />
DEYE_LOGGER_SERIAL_NUMBER=<Seriennummer des WR><br />
<br />
MQTT_HOST=<IP von MQTT Server><br />
MQTT_PORT=1883<br />
MQTT_USERNAME=<br />
MQTT_PASSWORD=<br />
MQTT_TOPIC_PREFIX=deye<br />
<br />
LOG_LEVEL=ERROR<br />
DEYE_DATA_READ_INTERVAL=60<br />
DEYE_METRIC_GROUPS=micro<br />
</pre><br />
<br />
=== deye_inverter.sh === <br />
Ausserdem habe ich ein kleines ''Wrapper Skript'' geschrieben, um '''Daten des WR''' einfacher '''lesen und schreiben''' zu können:<br><br />
<br />
'''deye_inverter.sh''' [--check <pause>] --read <register> | --write <register> <value><br />
<br />
<pre>#!/bin/bash<br />
# Read / Write DEYE Inverters<br />
# via deye_inverter_mqtt python package<br />
# https://github.com/kbialek/deye-inverter-mqtt<br />
<br />
retries=2 # retry deye_inverter command multiple times<br />
sleep=60 # sleep time between retries<br />
<br />
# locate script in deye_inverter_mqtt directory<br />
cd $(dirname $0)<br />
if [ ! -f ./deye_cli.sh ]; then<br />
echo "Error: ./deye_cli.sh not found."<br />
echo " Please move $(basename $0) in deye-inverter-mqtt directory."<br />
exit 1<br />
fi<br />
. ./config.env<br />
<br />
log_info ()<br />
{<br />
if [ ${LOG_LEVEL} = "INFO" ]; then<br />
logger -t $(basename $0) $@<br />
fi<br />
}<br />
<br />
log_error ()<br />
{<br />
logger -t $(basename $0) $@<br />
echo 2>&1 $@<br />
}<br />
<br />
# read parameters: mode [rw], register, value and optional -c <check pause><br />
pause=0<br />
while [ $# -gt 0 ]; do<br />
case $1 in<br />
-c|--check) shift<br />
[ $# -ge 3 ] || exit 1<br />
pause=$1<br />
;;<br />
-w|--write) mode=w<br />
shift<br />
[ $# -eq 2 ] || exit 1<br />
reg=$1<br />
val=$2<br />
shift<br />
;;<br />
-r|--read) mode=r<br />
shift<br />
[ $# -eq 1 ] || exit 1<br />
reg=$1<br />
;;<br />
esac<br />
shift<br />
done<br />
<br />
# handle offline deye_inverter by<br />
# ${pause} > 0 -> endless loop<br />
# ${pause} = 0 -> error after ${retries}<br />
while true; do<br />
<br />
# try deye_cli.sh $retries times every $sleep secs<br />
check=${retries}<br />
while [ ${check} -gt 0 ]; do<br />
<br />
case ${mode} in<br />
r) result=$(./deye_cli.sh $mode $reg | grep 'int: ')<br />
if [ -n "$result" ]; then<br />
echo "$result"<br />
log_info -t $0 deye_cli.sh $mode $reg<br />
exit 0<br />
fi<br />
;;<br />
w) result=$(./deye_cli.sh $mode $reg $val)<br />
if [ "$result" = "Ok" ]; then<br />
log_info -t $0 deye_cli.sh $mode $reg $val<br />
exit 0<br />
fi<br />
;;<br />
esac<br />
<br />
# wait ${sleep} or ${pause} secs<br />
sleep $(( ${pause} ? ${sleep} : ${pause} ))<br />
<br />
# try ${retries} times<br />
(( check-- ))<br />
done<br />
<br />
# error, or retry endless until wakeup of deye_inverter<br />
if [ ${pause} -eq 0 ]; then<br />
log_error "Error: deye_cli.sh $mode $reg $val failed."<br />
exit 1<br />
fi<br />
<br />
done<br />
</pre><br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
Folgendes Skript starte ich jeden Morgen um 04:00 Uhr per Cron (und in rc.local fürs booten):<br />
<br />
=== deye_mqtt_loop.sh ===<br />
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.<br />
<br />
Ausserdem setzt es beim Start den Tageszähler des Wechselrichters bedarfsweise zurück, bevor die Werte des DEYE Wechselrichters per Endlosschleife abgefragt werden.<br />
<br />
<pre>#!/bin/bash<br />
# reset Daily_Power of DEYE Inverters<br />
# via deye_inverter_mqtt python package<br />
# https://github.com/kbialek/deye-inverter-mqtt<br />
<br />
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin<br />
<br />
# config:<br />
deye_inverter_dir=/root/deye-inverter-mqtt<br />
fhem_dir=/opt/fhem<br />
horizon="-2" # sunset CIVIL=-6, REAL=0<br />
<br />
debug=info<br />
<br />
myPID=$$<br />
myname=$(basename $0)<br />
arg="$1"<br />
<br />
deye_inverter_sh=$deye_inverter_dir/deye_inverter.sh<br />
deye_inverter_mqtt_cfg=$deye_inverter_dir/config.env<br />
deye_inverter_mqtt_cmd="python3 deye_docker_entrypoint.py"<br />
<br />
sunrise_cmd="perl $fhem_dir/fhem.pl localhost:7072 {sunrise_abs('HORIZON=$horizon')}"<br />
sunset_cmd="perl $fhem_dir/fhem.pl localhost:7072 {sunset_abs('HORIZON=$horizon')}"<br />
<br />
# installation:<br />
# locate FHEM, used for sunrise(), sunset()<br />
if [ ! -x ${fhem_dir/fhem.pl} ]; then<br />
echo "Error: ${fhem_dir} not found."<br />
echo " Is FHEM installed in $fhem_dir ?"<br />
exit 1<br />
fi<br />
<br />
# locate script in deye_inverter_mqtt directory<br />
if [ ! -f ${deye_inverter_mqtt_cfg} ]; then<br />
echo "Error: ${deye_inverter_mqtt_cfg} not found."<br />
echo " Please update \$deye_inverter_sh path in $0"<br />
exit 1<br />
fi<br />
<br />
# read my config<br />
set -a; source ${deye_inverter_mqtt_cfg}; set +a<br />
<br />
[ "$debug" = "full" ] && set -x<br />
<br />
# ----- functions () ------------------------------------<br />
log_info ()<br />
{<br />
[ "$debug" = "yes" -o "$debug" = "true" -o "$debug" = "info" ] &&<br />
echo $myname: $@<br />
[ ${LOG_LEVEL} = "INFO" ] && logger -t $myname $@<br />
}<br />
<br />
log_error ()<br />
{<br />
logger -t $myname $@<br />
echo 1>&2 $@<br />
}<br />
<br />
# store actual date + time into vars<br />
set_date_time_vars ()<br />
{<br />
year=$(date +%y)<br />
month=$(date +%m | sed 's/^0//g')<br />
day=$(date +%d | sed 's/^0//g')<br />
hour=$(date +%H | sed 's/^0//g')<br />
minute=$(date +%m | sed 's/^0//g')<br />
second=$(date +%S | sed 's/^0//g')<br />
}<br />
<br />
# kill deye_inverter_loop ()<br />
kill_old_instance ()<br />
{<br />
# kill old instances of me<br />
my_PS=$(ps ax | grep -v grep | grep "${myname}" | awk '{print $1}')<br />
for ps in $my_PS; do<br />
[ $ps -lt $myPID ] && kill $ps<br />
done<br />
}<br />
<br />
# get deye_inverter_mqtt PIDs<br />
get_deye_mqtt_pid ()<br />
{<br />
deye_mqtt_pid=$(ps ax | grep -v grep | grep "${deye_inverter_mqtt_cmd}" | awk '{print $1}')<br />
}<br />
<br />
# kill deye_inverter_instance ()<br />
kill_deye_inverter_instance ()<br />
{<br />
get_deye_mqtt_pid<br />
if [ ! -z "${deye_mqtt_pid}" ]; then<br />
kill ${deye_mqtt_pid}<br />
log_info "Info: KILL running deye_mqtt ${deye_mqtt_pid}"<br />
fi<br />
<br />
# kill any running deye requests<br />
deye_ps=$(ps ax | grep -v grep | grep "$deye_inverter_sh " | awk '{print $1}')<br />
[ ! -z "$deye_ps" ] && kill $deye_ps<br />
}<br />
<br />
# stop all runnning instances<br />
deye_inverter_mqtt_stop ()<br />
{<br />
kill_old_instance<br />
kill_deye_inverter_instance<br />
}<br />
<br />
# start deye_inverter_mqtt<br />
deye_inverter_mqtt_start ()<br />
{<br />
log_info "Deye MQTT start"<br />
cd ${deye_inverter_dir}<br />
${deye_inverter_mqtt_cmd}<br />
}<br />
<br />
# read year_month register 0x16 = 22<br />
read_year_month ()<br />
{<br />
${deye_inverter_sh} -r 22 | sed 's/int: //g' | cut -d, -f1<br />
}<br />
<br />
# read day register 0x17 = 23<br />
read_day ()<br />
{<br />
${deye_inverter_sh} -r 23 | sed 's/.*h: //g' | cut -d, -f1<br />
}<br />
<br />
# read Daily power register 0x3c = 60<br />
read_daily_Power ()<br />
{<br />
${deye_inverter_sh} -r 60 | sed 's/int: //g' | cut -d, -f1<br />
}<br />
<br />
# read actual power register 0x56 = 86<br />
read_actual_Power ()<br />
{<br />
# doubleRegisterSensor("AC Active Power", 0x56, 0.1, mqtt_topic_suffix='ac/active_power', groups=['string', 'micro'])<br />
# (int.from_bytes(high_word, 'big') * 65536 + int.from_bytes(low_word, 'big')) * self.factor<br />
# val86=${deye_inverter_sh} -r 86 | sed 's/int: //g' | cut -d, -f1<br />
# val87=${deye_inverter_sh} -r 87 | sed 's/int: //g' | cut -d, -f1<br />
# echo $(( (${val87} * 65536 + ${val86}) * 0.1 ))<br />
<br />
# simple value is enough for our purpose<br />
${deye_inverter_sh} -r 86 | sed 's/int: //g' | cut -d, -f1<br />
}<br />
<br />
# write actual date/time into deye inverter<br />
reset_deye_inverter ()<br />
{<br />
# daily reset phase<br />
daily_reset=init<br />
<br />
# ensure by loop, that all register values are written<br />
while [ ${daily_reset} != "done" ]; do<br />
<br />
# wait until we have enough power<br />
while [ "$(read_actual_Power)" -le 10 ]; do<br />
sleep 600<br />
done<br />
<br />
# read Daily power register<br />
dailyPower=$(read_daily_Power)<br />
<br />
# read (old) year_month and day<br />
val22_old=$(read_year_month)<br />
val23_day=$(read_day)<br />
log_info "date/time update initialized"<br />
<br />
# set variables year, month, day, hour, minute, second<br />
set_date_time_vars<br />
<br />
# calculate register 22-24 with date and time vars<br />
val22=$(( ${year} * 256 + ${month} ))<br />
val23=$(( ${day} * 256 + ${hour} ))<br />
val24=$(( ${minute} * 256 + ${second} ))<br />
<br />
# reset power/date/time only once a day or exit loop<br />
[ "$dailyPower" -eq 0 -a<br />
"${val22_old}" = "${val22}" -a<br />
"${val23_day}" = "${day}" ] && break<br />
<br />
# reset daily_power by<br />
# setting register 22-24 with actual date + time<br />
${deye_inverter_sh} -w 22 $val22 || continue<br />
${deye_inverter_sh} -w 23 $val23 || continue<br />
${deye_inverter_sh} -w 24 $val24 || continue<br />
<br />
# test success<br />
# read Daily power register, should be resetted to 0 now<br />
dailyPower=$(read_daily_Power)<br />
<br />
# exit loop if power is updated, else log that reset failed<br />
if [ "$dailyPower" -eq 0 ]; then<br />
daily_reset=done<br />
<br />
### skip loop here, reset doesn't work always :-(<br />
# elif [ $daily_reset = "init" ]; then<br />
# daily_reset=failed<br />
# sleep 300<br />
<br />
else<br />
log_info "daily power reset failed"<br />
break<br />
fi<br />
done<br />
log_info "date/time reset done"<br />
}<br />
<br />
# wait for (next) daylight<br />
wait_for_sunrise ()<br />
{<br />
sunrise=$($sunrise_cmd)<br />
sunset=$($sunset_cmd)<br />
<br />
current_time=$(date +%s)<br />
sunrise=$(date -d"$sunrise" +%s)<br />
sunset=$(date -d"$sunset" +%s)<br />
<br />
# calulate time in secs until (next) sunrise<br />
((sleep_sunrise=$sunrise - $current_time)) # this morning<br />
[ $sleep_sunrise -lt 0 ] &&<br />
((sleep_sunrise=$sunrise + 86400)) # next morning<br />
<br />
# wait for daylight only<br />
if [ $current_time -lt $sunrise -o<br />
$current_time -gt $sunset ]; then<br />
log_info sleep $sleep_sunrise<br />
sleep $sleep_sunrise<br />
fi<br />
}<br />
<br />
# wait for sunset<br />
wait_for_sunset ()<br />
{<br />
sunset=$($sunset_cmd)<br />
<br />
current_time=$(date +%s)<br />
sunset=$(date -d"$sunset" +%s)<br />
<br />
# calulate time in secs until next sunset<br />
((sleep_sunset=$sunset - $current_time))<br />
<br />
# wait before sunset only<br />
if [ $sleep_sunset -gt 0 ]; then<br />
log_info sleep $sleep_sunset<br />
sleep $sleep_sunset<br />
fi<br />
}<br />
<br />
# ------------------------<br />
# MAIN: Script starts here<br />
# ------------------------<br />
<br />
# sunset_stop: wait before sunset only<br />
[ "$arg" = "sunset_stop" ] &&<br />
wait_for_sunset<br />
<br />
# stop any runnning deye_inverter_mqtt processes<br />
deye_inverter_mqtt_stop<br />
<br />
# arg stop: don't start again<br />
[ "$arg" = "stop" ] && exit 0<br />
<br />
# wait for (next) daylight<br />
wait_for_sunrise<br />
<br />
# setting date/time also resets daily power register<br />
reset_deye_inverter<br />
<br />
# start deye_mqtt_loop<br />
# check every 15 min for running deye_inverter_mqtt<br />
while :; do<br />
<br />
# wait for sunrise<br />
wait_for_sunrise<br />
<br />
# start deye_inverter_mqtt<br />
get_deye_mqtt_pid<br />
if [ -z "$deye_mqtt_pid" ]; then<br />
deye_inverter_mqtt_start<br />
fi<br />
<br />
# wait 15 min<br />
sleep 900<br />
done<br />
</pre><br />
<br />
=== Contrab und /etc/rc.local ===<br />
Mein Crontab Eintrag:<br />
<pre># stop DEYE monitoring after sunset (FHEM)<br />
0 18 * * * /root/sbin/deye_mqtt_loop.sh sunset_stop 2>/dev/null<br />
</pre><br />
und /etc/rc.local:<br />
<pre># start monitoring of PV-System: DEYE inverter<br />
$(sleep 10; /root/sbin/deye_mqtt_loop.sh 2>dev/null)&<br />
</pre><br />
<br />
== Dashboard ==<br />
So sieht das aus, wenn es den ganzen Tag im April regnet und bedeckt ist:<br />
[[Datei:Solar Bedeckt Regen.jpg|midi]]<br />
<br />
Die Eigenverbrauchsquote habe ich mittels Grafana ermittelt:<br />
[[Datei:Solar Eigenverbrauch.jpg|midi|Eigenverbrauchsquote]]</div>
Neobiker
http://neobiker.de/wiki/index.php?title=DEYE_Wechselrichter&diff=2140
DEYE Wechselrichter
2024-02-03T15:02:17Z
<p>Neobiker: /* deye_mqtt_loop.sh */</p>
<hr />
<div>= Balkonkraftwerk: DEYE Wechselrichter ohne Cloud auslesen und per MQTT monitoren =<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
Der WR bietet verschiedene Möglichkeiten zur Konfiguration und Abfrage der aktuellen Werte:<br />
* einen internen Webserver (http://10.10.10.254)<br />
* die Cloud Anbindung zu Solarman (Solarman Smart App)<br />
* (angepasstes) Modbus Protokoll über Port 8899<br />
* AT+ Befehle über Port 48899<br />
<br />
Ich verwende das Modbus Protokoll zum auslesen des WR, das <br />
# effizient/schnell ist (Performance)<br />
# ohne Internetverbindung auskommt<br />
<br />
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.<br />
<br />
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.<br />
<br />
== Wechselrichter Eigenheiten ==<br />
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. <br />
<br />
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.<br />
<br />
== Installation ==<br />
Das Projekt von Github laden und in einem Verzeichnis speichern, '''Python''' ist normalerweise ja schon installiert. <br />
<br />
<pre>git clone https://github.com/kbialek/deye-inverter-mqtt<br />
pip install paho-mqtt<br />
cd deye-inverter-mqtt</pre><br />
<br />
Unter Debian habe ich im Skript '''deye_cli.sh''' python durch python3 ersetzen müssen:<br />
<pre>#!/bin/bash<br />
set -a; source config.env; set +a<br />
python3 deye_cli.py "$@"<br />
</pre><br />
<br />
=== config.env ===<br />
Die Konfiguration meines DEYE Wechselrichters vom Typ Micro-Inverter:<br />
'''config.env'''<br />
<pre>DEYE_LOGGER_IP_ADDRESS=<IP Wechselrichter im WLAN><br />
DEYE_LOGGER_PORT=8899<br />
DEYE_LOGGER_SERIAL_NUMBER=<Seriennummer des WR><br />
<br />
MQTT_HOST=<IP von MQTT Server><br />
MQTT_PORT=1883<br />
MQTT_USERNAME=<br />
MQTT_PASSWORD=<br />
MQTT_TOPIC_PREFIX=deye<br />
<br />
LOG_LEVEL=ERROR<br />
DEYE_DATA_READ_INTERVAL=60<br />
DEYE_METRIC_GROUPS=micro<br />
</pre><br />
<br />
=== deye_inverter.sh === <br />
Ausserdem habe ich ein kleines ''Wrapper Skript'' geschrieben, um '''Daten des WR''' einfacher '''lesen und schreiben''' zu können:<br><br />
<br />
'''deye_inverter.sh''' [--check <pause>] --read <register> | --write <register> <value><br />
<br />
<pre>#!/bin/bash<br />
# Read / Write DEYE Inverters<br />
# via deye_inverter_mqtt python package<br />
# https://github.com/kbialek/deye-inverter-mqtt<br />
<br />
retries=2 # retry deye_inverter command multiple times<br />
sleep=60 # sleep time between retries<br />
<br />
# locate script in deye_inverter_mqtt directory<br />
cd $(dirname $0)<br />
if [ ! -f ./deye_cli.sh ]; then<br />
echo "Error: ./deye_cli.sh not found."<br />
echo " Please move $(basename $0) in deye-inverter-mqtt directory."<br />
exit 1<br />
fi<br />
. ./config.env<br />
<br />
log_info ()<br />
{<br />
if [ ${LOG_LEVEL} = "INFO" ]; then<br />
logger -t $(basename $0) $@<br />
fi<br />
}<br />
<br />
log_error ()<br />
{<br />
logger -t $(basename $0) $@<br />
echo 2>&1 $@<br />
}<br />
<br />
# read parameters: mode [rw], register, value and optional -c <check pause><br />
pause=0<br />
while [ $# -gt 0 ]; do<br />
case $1 in<br />
-c|--check) shift<br />
[ $# -ge 3 ] || exit 1<br />
pause=$1<br />
;;<br />
-w|--write) mode=w<br />
shift<br />
[ $# -eq 2 ] || exit 1<br />
reg=$1<br />
val=$2<br />
shift<br />
;;<br />
-r|--read) mode=r<br />
shift<br />
[ $# -eq 1 ] || exit 1<br />
reg=$1<br />
;;<br />
esac<br />
shift<br />
done<br />
<br />
# handle offline deye_inverter by<br />
# ${pause} > 0 -> endless loop<br />
# ${pause} = 0 -> error after ${retries}<br />
while true; do<br />
<br />
# try deye_cli.sh $retries times every $sleep secs<br />
check=${retries}<br />
while [ ${check} -gt 0 ]; do<br />
<br />
case ${mode} in<br />
r) result=$(./deye_cli.sh $mode $reg | grep 'int: ')<br />
if [ -n "$result" ]; then<br />
echo "$result"<br />
log_info -t $0 deye_cli.sh $mode $reg<br />
exit 0<br />
fi<br />
;;<br />
w) result=$(./deye_cli.sh $mode $reg $val)<br />
if [ "$result" = "Ok" ]; then<br />
log_info -t $0 deye_cli.sh $mode $reg $val<br />
exit 0<br />
fi<br />
;;<br />
esac<br />
<br />
# wait ${sleep} or ${pause} secs<br />
sleep $(( ${pause} ? ${sleep} : ${pause} ))<br />
<br />
# try ${retries} times<br />
(( check-- ))<br />
done<br />
<br />
# error, or retry endless until wakeup of deye_inverter<br />
if [ ${pause} -eq 0 ]; then<br />
log_error "Error: deye_cli.sh $mode $reg $val failed."<br />
exit 1<br />
fi<br />
<br />
done<br />
</pre><br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
Folgendes Skript starte ich jeden Morgen um 04:00 Uhr per Cron (und in rc.local fürs booten):<br />
<br />
=== deye_mqtt_loop.sh ===<br />
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.<br />
<br />
Ausserdem setzt es beim Start den Tageszähler des Wechselrichters bedarfsweise zurück, bevor die Werte des DEYE Wechselrichters per Endlosschleife abgefragt werden.<br />
<br />
<pre>#!/bin/bash<br />
# reset Daily_Power of DEYE Inverters<br />
# via deye_inverter_mqtt python package<br />
# https://github.com/kbialek/deye-inverter-mqtt<br />
<br />
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin<br />
<br />
# config:<br />
deye_inverter_dir=/root/deye-inverter-mqtt<br />
fhem_dir=/opt/fhem<br />
horizon="-2" # sunset CIVIL=-6, REAL=0<br />
<br />
debug=info<br />
<br />
myPID=$$<br />
myname=$(basename $0)<br />
arg="$1"<br />
<br />
deye_inverter_sh=$deye_inverter_dir/deye_inverter.sh<br />
deye_inverter_mqtt_cfg=$deye_inverter_dir/config.env<br />
deye_inverter_mqtt_cmd="python3 deye_docker_entrypoint.py"<br />
<br />
sunrise_cmd="perl $fhem_dir/fhem.pl localhost:7072 {sunrise_abs('HORIZON=$horizon')}"<br />
sunset_cmd="perl $fhem_dir/fhem.pl localhost:7072 {sunset_abs('HORIZON=$horizon')}"<br />
<br />
# installation:<br />
# locate FHEM, used for sunrise(), sunset()<br />
if [ ! -x ${fhem_dir/fhem.pl} ]; then<br />
echo "Error: ${fhem_dir} not found."<br />
echo " Is FHEM installed in $fhem_dir ?"<br />
exit 1<br />
fi<br />
<br />
# locate script in deye_inverter_mqtt directory<br />
if [ ! -f ${deye_inverter_mqtt_cfg} ]; then<br />
echo "Error: ${deye_inverter_mqtt_cfg} not found."<br />
echo " Please update \$deye_inverter_sh path in $0"<br />
exit 1<br />
fi<br />
<br />
# read my config<br />
set -a; source ${deye_inverter_mqtt_cfg}; set +a<br />
<br />
[ "$debug" = "full" ] && set -x<br />
<br />
# ----- functions () ------------------------------------<br />
log_info ()<br />
{<br />
[ "$debug" = "yes" -o "$debug" = "true" -o "$debug" = "info" ] &&<br />
echo $myname: $@<br />
[ ${LOG_LEVEL} = "INFO" ] && logger -t $myname $@<br />
}<br />
<br />
log_error ()<br />
{<br />
logger -t $myname $@<br />
echo 1>&2 $@<br />
}<br />
<br />
# store actual date + time into vars<br />
set_date_time_vars ()<br />
{<br />
year=$(date +%y)<br />
month=$(date +%m | sed 's/^0//g')<br />
day=$(date +%d | sed 's/^0//g')<br />
hour=$(date +%H | sed 's/^0//g')<br />
minute=$(date +%m | sed 's/^0//g')<br />
second=$(date +%S | sed 's/^0//g')<br />
}<br />
<br />
# kill deye_inverter_loop ()<br />
kill_old_instance ()<br />
{<br />
# kill old instances of me<br />
my_PS=$(ps ax | grep -v grep | grep "${myname}" | awk '{print $1}')<br />
for ps in $my_PS; do<br />
[ $ps -lt $myPID ] && kill $ps<br />
done<br />
}<br />
<br />
# get deye_inverter_mqtt PIDs<br />
get_deye_mqtt_pid ()<br />
{<br />
deye_mqtt_pid=$(ps ax | grep -v grep | grep "${deye_inverter_mqtt_cmd}" | awk '{print $1}')<br />
}<br />
<br />
# kill deye_inverter_instance ()<br />
kill_deye_inverter_instance ()<br />
{<br />
get_deye_mqtt_pid<br />
if [ ! -z "${deye_mqtt_pid}" ]; then<br />
kill ${deye_mqtt_pid}<br />
log_info "Info: KILL running deye_mqtt ${deye_mqtt_pid}"<br />
fi<br />
<br />
# kill any running deye requests<br />
deye_ps=$(ps ax | grep -v grep | grep "$deye_inverter_sh " | awk '{print $1}')<br />
[ ! -z "$deye_ps" ] && kill $deye_ps<br />
}<br />
<br />
# stop all runnning instances<br />
deye_inverter_mqtt_stop ()<br />
{<br />
kill_old_instance<br />
kill_deye_inverter_instance<br />
}<br />
<br />
# start deye_inverter_mqtt<br />
deye_inverter_mqtt_start ()<br />
{<br />
log_info "Deye MQTT start"<br />
cd ${deye_inverter_dir}<br />
${deye_inverter_mqtt_cmd}<br />
}<br />
<br />
# read year_month register 0x16 = 22<br />
read_year_month ()<br />
{<br />
${deye_inverter_sh} -r 22 | sed 's/int: //g' | cut -d, -f1<br />
}<br />
<br />
# read day register 0x17 = 23<br />
read_day ()<br />
{<br />
${deye_inverter_sh} -r 23 | sed 's/.*h: //g' | cut -d, -f1<br />
}<br />
<br />
# read Daily power register 0x3c = 60<br />
read_daily_Power ()<br />
{<br />
${deye_inverter_sh} -r 60 | sed 's/int: //g' | cut -d, -f1<br />
}<br />
<br />
# read actual power register 0x56 = 86<br />
read_actual_Power ()<br />
{<br />
# doubleRegisterSensor("AC Active Power", 0x56, 0.1, mqtt_topic_suffix='ac/active_power', groups=['string', 'micro'])<br />
# (int.from_bytes(high_word, 'big') * 65536 + int.from_bytes(low_word, 'big')) * self.factor<br />
# val86=${deye_inverter_sh} -r 86 | sed 's/int: //g' | cut -d, -f1<br />
# val87=${deye_inverter_sh} -r 87 | sed 's/int: //g' | cut -d, -f1<br />
# echo $(( (${val87} * 65536 + ${val86}) * 0.1 ))<br />
<br />
# simple value is enough for our purpose<br />
${deye_inverter_sh} -r 86 | sed 's/int: //g' | cut -d, -f1<br />
}<br />
<br />
# write actual date/time into deye inverter<br />
reset_deye_inverter ()<br />
{<br />
# daily reset phase<br />
daily_reset=init<br />
<br />
# ensure by loop, that all register values are written<br />
while [ ${daily_reset} != "done" ]; do<br />
<br />
# wait until we have enough power<br />
while [ "$(read_actual_Power)" -le 10 ]; do<br />
sleep 600<br />
done<br />
<br />
# read Daily power register<br />
dailyPower=$(read_daily_Power)<br />
<br />
# read (old) year_month and day<br />
val22_old=$(read_year_month)<br />
val23_day=$(read_day)<br />
log_info "date/time update initialized"<br />
<br />
# set variables year, month, day, hour, minute, second<br />
set_date_time_vars<br />
<br />
# calculate register 22-24 with date and time vars<br />
val22=$(( ${year} * 256 + ${month} ))<br />
val23=$(( ${day} * 256 + ${hour} ))<br />
val24=$(( ${minute} * 256 + ${second} ))<br />
<br />
# reset power/date/time only once a day or exit loop<br />
[ "$dailyPower" -eq 0 -a<br />
"${val22_old}" = "${val22}" -a<br />
"${val23_day}" = "${day}" ] && break<br />
<br />
# reset daily_power by<br />
# setting register 22-24 with actual date + time<br />
${deye_inverter_sh} -w 22 $val22 || continue<br />
${deye_inverter_sh} -w 23 $val23 || continue<br />
${deye_inverter_sh} -w 24 $val24 || continue<br />
<br />
# test success<br />
# read Daily power register, should be resetted to 0 now<br />
dailyPower=$(read_daily_Power)<br />
<br />
# exit loop if power is updated, else log that reset failed<br />
if [ "$dailyPower" -eq 0 ]; then<br />
daily_reset=done<br />
<br />
### skip loop here, reset doesn't work always :-(<br />
# elif [ $daily_reset = "init" ]; then<br />
# daily_reset=failed<br />
# sleep 300<br />
<br />
else<br />
log_info "daily power reset failed"<br />
break<br />
fi<br />
done<br />
log_info "date/time reset done"<br />
}<br />
<br />
# wait for (next) daylight<br />
wait_for_sunrise ()<br />
{<br />
sunrise=$($sunrise_cmd)<br />
sunset=$($sunset_cmd)<br />
<br />
current_time=$(date +%s)<br />
sunrise=$(date -d"$sunrise" +%s)<br />
sunset=$(date -d"$sunset" +%s)<br />
<br />
# calulate time in secs until (next) sunrise<br />
((sleep_sunrise=$sunrise - $current_time)) # this morning<br />
[ $sleep_sunrise -lt 0 ] &&<br />
((sleep_sunrise=$sunrise + 86400)) # next morning<br />
<br />
# wait for daylight only<br />
if [ $current_time -lt $sunrise -o<br />
$current_time -gt $sunset ]; then<br />
log_info sleep $sleep_sunrise<br />
sleep $sleep_sunrise<br />
fi<br />
}<br />
<br />
# wait for sunset<br />
wait_for_sunset ()<br />
{<br />
sunset=$($sunset_cmd)<br />
<br />
current_time=$(date +%s)<br />
sunset=$(date -d"$sunset" +%s)<br />
<br />
# calulate time in secs until next sunset<br />
((sleep_sunset=$sunset - $current_time))<br />
<br />
# wait until sunset only<br />
if [ $sleep_sunset -gt 0 ]; then<br />
log_info sleep $sleep_sunset<br />
sleep $sleep_sunset<br />
fi<br />
}<br />
<br />
# ------------------------<br />
# MAIN: Script starts here<br />
# ------------------------<br />
<br />
# sunset_stop: wait before sunset only<br />
[ "$arg" = "sunset_stop" ] &&<br />
wait_for_sunset<br />
<br />
# stop any runnning deye_inverter_mqtt processes<br />
deye_inverter_mqtt_stop<br />
<br />
# arg stop: don't start again<br />
[ "$arg" = "stop" ] && exit 0<br />
<br />
# wait for (next) daylight<br />
wait_for_sunrise<br />
<br />
# setting date/time also resets daily power register<br />
reset_deye_inverter<br />
<br />
# start deye_mqtt_loop<br />
# check every 15 min for running deye_inverter_mqtt<br />
while :; do<br />
<br />
# wait for sunrise<br />
wait_for_sunrise<br />
<br />
# start deye_inverter_mqtt<br />
get_deye_mqtt_pid<br />
if [ -z "$deye_mqtt_pid" ]; then<br />
deye_inverter_mqtt_start<br />
fi<br />
<br />
# wait 15 min<br />
sleep 900<br />
done<br />
</pre><br />
<br />
=== Contrab und /etc/rc.local ===<br />
Mein Crontab Eintrag:<br />
<pre># stop DEYE monitoring after sunset (FHEM)<br />
0 18 * * * /root/sbin/deye_mqtt_loop.sh sunset_stop 2>/dev/null<br />
</pre><br />
und /etc/rc.local:<br />
<pre># start monitoring of PV-System: DEYE inverter<br />
$(sleep 10; /root/sbin/deye_mqtt_loop.sh 2>dev/null)&<br />
</pre><br />
<br />
== Dashboard ==<br />
So sieht das aus, wenn es den ganzen Tag im April regnet und bedeckt ist:<br />
[[Datei:Solar Bedeckt Regen.jpg|midi]]<br />
<br />
Die Eigenverbrauchsquote habe ich mittels Grafana ermittelt:<br />
[[Datei:Solar Eigenverbrauch.jpg|midi|Eigenverbrauchsquote]]</div>
Neobiker
http://neobiker.de/wiki/index.php?title=DEYE_Wechselrichter&diff=2139
DEYE Wechselrichter
2024-02-03T14:58:47Z
<p>Neobiker: /* deye_mqtt_loop.sh */</p>
<hr />
<div>= Balkonkraftwerk: DEYE Wechselrichter ohne Cloud auslesen und per MQTT monitoren =<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
Der WR bietet verschiedene Möglichkeiten zur Konfiguration und Abfrage der aktuellen Werte:<br />
* einen internen Webserver (http://10.10.10.254)<br />
* die Cloud Anbindung zu Solarman (Solarman Smart App)<br />
* (angepasstes) Modbus Protokoll über Port 8899<br />
* AT+ Befehle über Port 48899<br />
<br />
Ich verwende das Modbus Protokoll zum auslesen des WR, das <br />
# effizient/schnell ist (Performance)<br />
# ohne Internetverbindung auskommt<br />
<br />
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.<br />
<br />
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.<br />
<br />
== Wechselrichter Eigenheiten ==<br />
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. <br />
<br />
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.<br />
<br />
== Installation ==<br />
Das Projekt von Github laden und in einem Verzeichnis speichern, '''Python''' ist normalerweise ja schon installiert. <br />
<br />
<pre>git clone https://github.com/kbialek/deye-inverter-mqtt<br />
pip install paho-mqtt<br />
cd deye-inverter-mqtt</pre><br />
<br />
Unter Debian habe ich im Skript '''deye_cli.sh''' python durch python3 ersetzen müssen:<br />
<pre>#!/bin/bash<br />
set -a; source config.env; set +a<br />
python3 deye_cli.py "$@"<br />
</pre><br />
<br />
=== config.env ===<br />
Die Konfiguration meines DEYE Wechselrichters vom Typ Micro-Inverter:<br />
'''config.env'''<br />
<pre>DEYE_LOGGER_IP_ADDRESS=<IP Wechselrichter im WLAN><br />
DEYE_LOGGER_PORT=8899<br />
DEYE_LOGGER_SERIAL_NUMBER=<Seriennummer des WR><br />
<br />
MQTT_HOST=<IP von MQTT Server><br />
MQTT_PORT=1883<br />
MQTT_USERNAME=<br />
MQTT_PASSWORD=<br />
MQTT_TOPIC_PREFIX=deye<br />
<br />
LOG_LEVEL=ERROR<br />
DEYE_DATA_READ_INTERVAL=60<br />
DEYE_METRIC_GROUPS=micro<br />
</pre><br />
<br />
=== deye_inverter.sh === <br />
Ausserdem habe ich ein kleines ''Wrapper Skript'' geschrieben, um '''Daten des WR''' einfacher '''lesen und schreiben''' zu können:<br><br />
<br />
'''deye_inverter.sh''' [--check <pause>] --read <register> | --write <register> <value><br />
<br />
<pre>#!/bin/bash<br />
# Read / Write DEYE Inverters<br />
# via deye_inverter_mqtt python package<br />
# https://github.com/kbialek/deye-inverter-mqtt<br />
<br />
retries=2 # retry deye_inverter command multiple times<br />
sleep=60 # sleep time between retries<br />
<br />
# locate script in deye_inverter_mqtt directory<br />
cd $(dirname $0)<br />
if [ ! -f ./deye_cli.sh ]; then<br />
echo "Error: ./deye_cli.sh not found."<br />
echo " Please move $(basename $0) in deye-inverter-mqtt directory."<br />
exit 1<br />
fi<br />
. ./config.env<br />
<br />
log_info ()<br />
{<br />
if [ ${LOG_LEVEL} = "INFO" ]; then<br />
logger -t $(basename $0) $@<br />
fi<br />
}<br />
<br />
log_error ()<br />
{<br />
logger -t $(basename $0) $@<br />
echo 2>&1 $@<br />
}<br />
<br />
# read parameters: mode [rw], register, value and optional -c <check pause><br />
pause=0<br />
while [ $# -gt 0 ]; do<br />
case $1 in<br />
-c|--check) shift<br />
[ $# -ge 3 ] || exit 1<br />
pause=$1<br />
;;<br />
-w|--write) mode=w<br />
shift<br />
[ $# -eq 2 ] || exit 1<br />
reg=$1<br />
val=$2<br />
shift<br />
;;<br />
-r|--read) mode=r<br />
shift<br />
[ $# -eq 1 ] || exit 1<br />
reg=$1<br />
;;<br />
esac<br />
shift<br />
done<br />
<br />
# handle offline deye_inverter by<br />
# ${pause} > 0 -> endless loop<br />
# ${pause} = 0 -> error after ${retries}<br />
while true; do<br />
<br />
# try deye_cli.sh $retries times every $sleep secs<br />
check=${retries}<br />
while [ ${check} -gt 0 ]; do<br />
<br />
case ${mode} in<br />
r) result=$(./deye_cli.sh $mode $reg | grep 'int: ')<br />
if [ -n "$result" ]; then<br />
echo "$result"<br />
log_info -t $0 deye_cli.sh $mode $reg<br />
exit 0<br />
fi<br />
;;<br />
w) result=$(./deye_cli.sh $mode $reg $val)<br />
if [ "$result" = "Ok" ]; then<br />
log_info -t $0 deye_cli.sh $mode $reg $val<br />
exit 0<br />
fi<br />
;;<br />
esac<br />
<br />
# wait ${sleep} or ${pause} secs<br />
sleep $(( ${pause} ? ${sleep} : ${pause} ))<br />
<br />
# try ${retries} times<br />
(( check-- ))<br />
done<br />
<br />
# error, or retry endless until wakeup of deye_inverter<br />
if [ ${pause} -eq 0 ]; then<br />
log_error "Error: deye_cli.sh $mode $reg $val failed."<br />
exit 1<br />
fi<br />
<br />
done<br />
</pre><br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
Folgendes Skript starte ich jeden Morgen um 04:00 Uhr per Cron (und in rc.local fürs booten):<br />
<br />
=== deye_mqtt_loop.sh ===<br />
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.<br />
<br />
Ausserdem setzt es beim Start den Tageszähler des Wechselrichters bedarfsweise zurück, bevor die Werte des DEYE Wechselrichters per Endlosschleife abgefragt werden.<br />
<br />
<pre>#!/bin/bash<br />
# reset Daily_Power of DEYE Inverters<br />
# via deye_inverter_mqtt python package<br />
# https://github.com/kbialek/deye-inverter-mqtt<br />
<br />
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin<br />
<br />
# config:<br />
deye_inverter_dir=/root/deye-inverter-mqtt<br />
fhem_dir=/opt/fhem<br />
horizon="-2" # sunset CIVIL=-6, REAL=0<br />
<br />
debug=info<br />
<br />
myPID=$$<br />
myname=$(basename $0)<br />
arg="$1"<br />
<br />
deye_inverter_sh=$deye_inverter_dir/deye_inverter.sh<br />
deye_inverter_mqtt_cfg=$deye_inverter_dir/config.env<br />
deye_inverter_mqtt_cmd="python3 deye_docker_entrypoint.py"<br />
<br />
sunrise_cmd="perl $fhem_dir/fhem.pl localhost:7072 {sunrise_abs('HORIZON=$horizon')}"<br />
sunset_cmd="perl $fhem_dir/fhem.pl localhost:7072 {sunset_abs('HORIZON=$horizon')}"<br />
<br />
# installation:<br />
# locate FHEM, used for sunrise(), sunset()<br />
if [ ! -x ${fhem_dir/fhem.pl} ]; then<br />
echo "Error: ${fhem_dir} not found."<br />
echo " Is FHEM installed in $fhem_dir ?"<br />
exit 1<br />
fi<br />
<br />
# locate script in deye_inverter_mqtt directory<br />
if [ ! -f ${deye_inverter_mqtt_cfg} ]; then<br />
echo "Error: ${deye_inverter_mqtt_cfg} not found."<br />
echo " Please update \$deye_inverter_sh path in $0"<br />
exit 1<br />
fi<br />
<br />
# read my config<br />
set -a; source ${deye_inverter_mqtt_cfg}; set +a<br />
<br />
[ "$debug" = "full" ] && set -x<br />
<br />
# ----- functions () ------------------------------------<br />
log_info ()<br />
{<br />
[ "$debug" = "yes" -o "$debug" = "true" -o "$debug" = "info" ] &&<br />
echo $myname: $@<br />
[ ${LOG_LEVEL} = "INFO" ] && logger -t $myname $@<br />
}<br />
<br />
log_error ()<br />
{<br />
logger -t $myname $@<br />
echo 1>&2 $@<br />
}<br />
<br />
# store actual date + time into vars<br />
set_date_time_vars ()<br />
{<br />
year=$(date +%y)<br />
month=$(date +%m | sed 's/^0//g')<br />
day=$(date +%d | sed 's/^0//g')<br />
hour=$(date +%H | sed 's/^0//g')<br />
minute=$(date +%m | sed 's/^0//g')<br />
second=$(date +%S | sed 's/^0//g')<br />
}<br />
<br />
# kill deye_inverter_loop ()<br />
kill_old_instance ()<br />
{<br />
# kill old instances of me<br />
my_PS=$(ps ax | grep -v grep | grep "${myname}" | awk '{print $1}')<br />
for ps in $my_PS; do<br />
[ $ps -lt $myPID ] && kill $ps<br />
done<br />
}<br />
<br />
# get deye_inverter_mqtt PIDs<br />
get_deye_mqtt_pid ()<br />
{<br />
deye_mqtt_pid=$(ps ax | grep -v grep | grep "${deye_inverter_mqtt_cmd}" | awk '{print $1}')<br />
}<br />
<br />
# kill deye_inverter_instance ()<br />
kill_deye_inverter_instance ()<br />
{<br />
get_deye_mqtt_pid<br />
if [ ! -z "${deye_mqtt_pid}" ]; then<br />
kill ${deye_mqtt_pid}<br />
log_info "Info: KILL running deye_mqtt ${deye_mqtt_pid}"<br />
fi<br />
<br />
# kill any running deye requests<br />
deye_ps=$(ps ax | grep -v grep | grep "$deye_inverter_sh " | awk '{print $1}')<br />
[ ! -z "$deye_ps" ] && kill $deye_ps<br />
}<br />
<br />
# stop all runnning instances<br />
deye_inverter_mqtt_stop ()<br />
{<br />
kill_old_instance<br />
kill_deye_inverter_instance<br />
}<br />
<br />
# start deye_inverter_mqtt<br />
deye_inverter_mqtt_start ()<br />
{<br />
log_info "Deye MQTT start"<br />
cd ${deye_inverter_dir}<br />
${deye_inverter_mqtt_cmd}<br />
}<br />
<br />
# read year_month register 0x16 = 22<br />
read_year_month ()<br />
{<br />
${deye_inverter_sh} -r 22 | sed 's/int: //g' | cut -d, -f1<br />
}<br />
<br />
# read day register 0x17 = 23<br />
read_day ()<br />
{<br />
${deye_inverter_sh} -r 23 | sed 's/.*h: //g' | cut -d, -f1<br />
}<br />
<br />
# read Daily power register 0x3c = 60<br />
read_daily_Power ()<br />
{<br />
${deye_inverter_sh} -r 60 | sed 's/int: //g' | cut -d, -f1<br />
}<br />
<br />
# read actual power register 0x56 = 86<br />
read_actual_Power ()<br />
{<br />
# doubleRegisterSensor("AC Active Power", 0x56, 0.1, mqtt_topic_suffix='ac/active_power', groups=['string', 'micro'])<br />
# (int.from_bytes(high_word, 'big') * 65536 + int.from_bytes(low_word, 'big')) * self.factor<br />
# val86=${deye_inverter_sh} -r 86 | sed 's/int: //g' | cut -d, -f1<br />
# val87=${deye_inverter_sh} -r 87 | sed 's/int: //g' | cut -d, -f1<br />
# echo $(( (${val87} * 65536 + ${val86}) * 0.1 ))<br />
<br />
# simple value is enough for our purpose<br />
${deye_inverter_sh} -r 86 | sed 's/int: //g' | cut -d, -f1<br />
}<br />
<br />
# write actual date/time into deye inverter<br />
reset_deye_inverter ()<br />
{<br />
# daily reset phase<br />
daily_reset=init<br />
<br />
# ensure by loop, that all register values are written<br />
while [ ${daily_reset} != "done" ]; do<br />
<br />
# wait until we have enough power<br />
while [ "$(read_actual_Power)" -le 10 ]; do<br />
sleep 600<br />
done<br />
<br />
# read Daily power register<br />
dailyPower=$(read_daily_Power)<br />
<br />
# read (old) year_month and day<br />
val22_old=$(read_year_month)<br />
val23_day=$(read_day)<br />
log_info "date/time update initialized"<br />
<br />
# set variables year, month, day, hour, minute, second<br />
set_date_time_vars<br />
<br />
# calculate register 22-24 with date and time vars<br />
val22=$(( ${year} * 256 + ${month} ))<br />
val23=$(( ${day} * 256 + ${hour} ))<br />
val24=$(( ${minute} * 256 + ${second} ))<br />
<br />
# reset power/date/time only once a day or exit loop<br />
[ "$dailyPower" -eq 0 -a<br />
"${val22_old}" = "${val22}" -a<br />
"${val23_day}" = "${day}" ] && break<br />
<br />
# reset daily_power by<br />
# setting register 22-24 with actual date + time<br />
${deye_inverter_sh} -w 22 $val22 || continue<br />
${deye_inverter_sh} -w 23 $val23 || continue<br />
${deye_inverter_sh} -w 24 $val24 || continue<br />
<br />
# test success<br />
# read Daily power register, should be resetted to 0 now<br />
dailyPower=$(read_daily_Power)<br />
<br />
# exit loop if power is updated, else log that reset failed<br />
if [ "$dailyPower" -eq 0 ]; then<br />
daily_reset=done<br />
<br />
### skip loop here, reset doesn't work always :-(<br />
# elif [ $daily_reset = "init" ]; then<br />
# daily_reset=failed<br />
# sleep 300<br />
<br />
else<br />
log_info "daily power reset failed"<br />
break<br />
fi<br />
done<br />
log_info "date/time reset done"<br />
}<br />
<br />
# wait for (next) daylight<br />
wait_for_sunrise ()<br />
{<br />
sunrise=$($sunrise_cmd)<br />
sunset=$($sunset_cmd)<br />
<br />
current_time=$(date +%s)<br />
sunrise=$(date -d"$sunrise" +%s)<br />
sunset=$(date -d"$sunset" +%s)<br />
<br />
# calulate time in secs until (next) sunrise<br />
((sleep_sunrise=$sunrise - $current_time)) # this morning<br />
[ $sleep_sunrise -lt 0 ] &&<br />
((sleep_sunrise=$sunrise + 86400)) # next morning<br />
<br />
# wait for daylight only<br />
if [ $current_time -lt $sunrise -o<br />
$current_time -gt $sunset ]; then<br />
log_info sleep $sleep_sunrise<br />
sleep $sleep_sunrise<br />
fi<br />
}<br />
<br />
# wait for sunset<br />
wait_for_sunset ()<br />
{<br />
sunset=$($sunset_cmd)<br />
<br />
current_time=$(date +%s)<br />
sunset=$(date -d"$sunset" +%s)<br />
<br />
# calulate time in secs until next sunset<br />
((sleep_sunset=$sunset - $current_time))<br />
<br />
# wait until sunset only<br />
if [ $sleep_sunset -gt 0 ]; then<br />
log_info sleep $sleep_sunset<br />
sleep $sleep_sunset<br />
fi<br />
}<br />
<br />
# ------------------------<br />
# MAIN: Script starts here<br />
# ------------------------<br />
<br />
# sunset_stop: wait until (next) sunset<br />
[ "$arg" = "sunset_stop" ] &&<br />
wait_for_sunset<br />
<br />
# stop any runnning deye_inverter_mqtt processes<br />
deye_inverter_mqtt_stop<br />
<br />
# arg stop: don't start again<br />
[ "$arg" = "stop" ] && exit 0<br />
<br />
# wait for (next) daylight<br />
wait_for_sunrise<br />
<br />
# setting date/time also resets daily power register<br />
reset_deye_inverter<br />
<br />
# start deye_mqtt_loop<br />
# check every 15 min for running deye_inverter_mqtt<br />
while :; do<br />
<br />
# wait for sunrise<br />
wait_for_sunrise<br />
<br />
# start deye_inverter_mqtt<br />
get_deye_mqtt_pid<br />
if [ -z "$deye_mqtt_pid" ]; then<br />
deye_inverter_mqtt_start<br />
fi<br />
<br />
# wait 15 min<br />
sleep 900<br />
done<br />
</pre><br />
<br />
=== Contrab und /etc/rc.local ===<br />
Mein Crontab Eintrag:<br />
<pre># stop DEYE monitoring after sunset (FHEM)<br />
0 18 * * * /root/sbin/deye_mqtt_loop.sh sunset_stop 2>/dev/null<br />
</pre><br />
und /etc/rc.local:<br />
<pre># start monitoring of PV-System: DEYE inverter<br />
$(sleep 10; /root/sbin/deye_mqtt_loop.sh 2>dev/null)&<br />
</pre><br />
<br />
== Dashboard ==<br />
So sieht das aus, wenn es den ganzen Tag im April regnet und bedeckt ist:<br />
[[Datei:Solar Bedeckt Regen.jpg|midi]]<br />
<br />
Die Eigenverbrauchsquote habe ich mittels Grafana ermittelt:<br />
[[Datei:Solar Eigenverbrauch.jpg|midi|Eigenverbrauchsquote]]</div>
Neobiker
http://neobiker.de/wiki/index.php?title=DEYE_Wechselrichter&diff=2138
DEYE Wechselrichter
2024-02-03T14:58:11Z
<p>Neobiker: /* deye_mqtt_loop.sh */</p>
<hr />
<div>= Balkonkraftwerk: DEYE Wechselrichter ohne Cloud auslesen und per MQTT monitoren =<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
Der WR bietet verschiedene Möglichkeiten zur Konfiguration und Abfrage der aktuellen Werte:<br />
* einen internen Webserver (http://10.10.10.254)<br />
* die Cloud Anbindung zu Solarman (Solarman Smart App)<br />
* (angepasstes) Modbus Protokoll über Port 8899<br />
* AT+ Befehle über Port 48899<br />
<br />
Ich verwende das Modbus Protokoll zum auslesen des WR, das <br />
# effizient/schnell ist (Performance)<br />
# ohne Internetverbindung auskommt<br />
<br />
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.<br />
<br />
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.<br />
<br />
== Wechselrichter Eigenheiten ==<br />
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. <br />
<br />
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.<br />
<br />
== Installation ==<br />
Das Projekt von Github laden und in einem Verzeichnis speichern, '''Python''' ist normalerweise ja schon installiert. <br />
<br />
<pre>git clone https://github.com/kbialek/deye-inverter-mqtt<br />
pip install paho-mqtt<br />
cd deye-inverter-mqtt</pre><br />
<br />
Unter Debian habe ich im Skript '''deye_cli.sh''' python durch python3 ersetzen müssen:<br />
<pre>#!/bin/bash<br />
set -a; source config.env; set +a<br />
python3 deye_cli.py "$@"<br />
</pre><br />
<br />
=== config.env ===<br />
Die Konfiguration meines DEYE Wechselrichters vom Typ Micro-Inverter:<br />
'''config.env'''<br />
<pre>DEYE_LOGGER_IP_ADDRESS=<IP Wechselrichter im WLAN><br />
DEYE_LOGGER_PORT=8899<br />
DEYE_LOGGER_SERIAL_NUMBER=<Seriennummer des WR><br />
<br />
MQTT_HOST=<IP von MQTT Server><br />
MQTT_PORT=1883<br />
MQTT_USERNAME=<br />
MQTT_PASSWORD=<br />
MQTT_TOPIC_PREFIX=deye<br />
<br />
LOG_LEVEL=ERROR<br />
DEYE_DATA_READ_INTERVAL=60<br />
DEYE_METRIC_GROUPS=micro<br />
</pre><br />
<br />
=== deye_inverter.sh === <br />
Ausserdem habe ich ein kleines ''Wrapper Skript'' geschrieben, um '''Daten des WR''' einfacher '''lesen und schreiben''' zu können:<br><br />
<br />
'''deye_inverter.sh''' [--check <pause>] --read <register> | --write <register> <value><br />
<br />
<pre>#!/bin/bash<br />
# Read / Write DEYE Inverters<br />
# via deye_inverter_mqtt python package<br />
# https://github.com/kbialek/deye-inverter-mqtt<br />
<br />
retries=2 # retry deye_inverter command multiple times<br />
sleep=60 # sleep time between retries<br />
<br />
# locate script in deye_inverter_mqtt directory<br />
cd $(dirname $0)<br />
if [ ! -f ./deye_cli.sh ]; then<br />
echo "Error: ./deye_cli.sh not found."<br />
echo " Please move $(basename $0) in deye-inverter-mqtt directory."<br />
exit 1<br />
fi<br />
. ./config.env<br />
<br />
log_info ()<br />
{<br />
if [ ${LOG_LEVEL} = "INFO" ]; then<br />
logger -t $(basename $0) $@<br />
fi<br />
}<br />
<br />
log_error ()<br />
{<br />
logger -t $(basename $0) $@<br />
echo 2>&1 $@<br />
}<br />
<br />
# read parameters: mode [rw], register, value and optional -c <check pause><br />
pause=0<br />
while [ $# -gt 0 ]; do<br />
case $1 in<br />
-c|--check) shift<br />
[ $# -ge 3 ] || exit 1<br />
pause=$1<br />
;;<br />
-w|--write) mode=w<br />
shift<br />
[ $# -eq 2 ] || exit 1<br />
reg=$1<br />
val=$2<br />
shift<br />
;;<br />
-r|--read) mode=r<br />
shift<br />
[ $# -eq 1 ] || exit 1<br />
reg=$1<br />
;;<br />
esac<br />
shift<br />
done<br />
<br />
# handle offline deye_inverter by<br />
# ${pause} > 0 -> endless loop<br />
# ${pause} = 0 -> error after ${retries}<br />
while true; do<br />
<br />
# try deye_cli.sh $retries times every $sleep secs<br />
check=${retries}<br />
while [ ${check} -gt 0 ]; do<br />
<br />
case ${mode} in<br />
r) result=$(./deye_cli.sh $mode $reg | grep 'int: ')<br />
if [ -n "$result" ]; then<br />
echo "$result"<br />
log_info -t $0 deye_cli.sh $mode $reg<br />
exit 0<br />
fi<br />
;;<br />
w) result=$(./deye_cli.sh $mode $reg $val)<br />
if [ "$result" = "Ok" ]; then<br />
log_info -t $0 deye_cli.sh $mode $reg $val<br />
exit 0<br />
fi<br />
;;<br />
esac<br />
<br />
# wait ${sleep} or ${pause} secs<br />
sleep $(( ${pause} ? ${sleep} : ${pause} ))<br />
<br />
# try ${retries} times<br />
(( check-- ))<br />
done<br />
<br />
# error, or retry endless until wakeup of deye_inverter<br />
if [ ${pause} -eq 0 ]; then<br />
log_error "Error: deye_cli.sh $mode $reg $val failed."<br />
exit 1<br />
fi<br />
<br />
done<br />
</pre><br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
Folgendes Skript starte ich jeden Morgen um 04:00 Uhr per Cron (und in rc.local fürs booten):<br />
<br />
=== deye_mqtt_loop.sh ===<br />
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.<br />
<br />
Ausserdem setzt es beim Start den Tageszähler des Wechselrichters bedarfsweise zurück, bevor die Werte des DEYE Wechselrichters per Endlosschleife abgefragt werden.<br />
<br />
<pre>#!/bin/bash<br />
# reset Daily_Power of DEYE Inverters<br />
# via deye_inverter_mqtt python package<br />
# https://github.com/kbialek/deye-inverter-mqtt<br />
<br />
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin<br />
<br />
# config:<br />
deye_inverter_dir=/root/deye-inverter-mqtt<br />
fhem_dir=/opt/fhem<br />
horizon="-2" # sunset CIVIL=-6, REAL=0<br />
<br />
debug=info<br />
<br />
myPID=$$<br />
myname=$(basename $0)<br />
arg="$1"<br />
<br />
deye_inverter_sh=$deye_inverter_dir/deye_inverter.sh<br />
deye_inverter_mqtt_cfg=$deye_inverter_dir/config.env<br />
deye_inverter_mqtt_cmd="python3 deye_docker_entrypoint.py"<br />
<br />
sunrise_cmd="perl $fhem_dir/fhem.pl localhost:7072 {sunrise_abs('HORIZON=$horizon')}"<br />
sunset_cmd="perl $fhem_dir/fhem.pl localhost:7072 {sunset_abs('HORIZON=$horizon')}"<br />
<br />
# installation:<br />
# locate FHEM, used for sunrise(), sunset()<br />
if [ ! -x ${fhem_dir/fhem.pl} ]; then<br />
echo "Error: ${fhem_dir} not found."<br />
echo " Is FHEM installed in $fhem_dir ?"<br />
exit 1<br />
fi<br />
<br />
# locate script in deye_inverter_mqtt directory<br />
if [ ! -f ${deye_inverter_mqtt_cfg} ]; then<br />
echo "Error: ${deye_inverter_mqtt_cfg} not found."<br />
echo " Please update \$deye_inverter_sh path in $0"<br />
exit 1<br />
fi<br />
<br />
# read my config<br />
set -a; source ${deye_inverter_mqtt_cfg}; set +a<br />
<br />
[ "$debug" = "full" ] && set -x<br />
<br />
# ----- functions () ------------------------------------<br />
log_info ()<br />
{<br />
[ "$debug" = "yes" -o "$debug" = "true" -o "$debug" = "info" ] &&<br />
echo $myname: $@<br />
[ ${LOG_LEVEL} = "INFO" ] && logger -t $myname $@<br />
}<br />
<br />
log_error ()<br />
{<br />
logger -t $myname $@<br />
echo 1>&2 $@<br />
}<br />
<br />
# store actual date + time into vars<br />
set_date_time_vars ()<br />
{<br />
year=$(date +%y)<br />
month=$(date +%m | sed 's/^0//g')<br />
day=$(date +%d | sed 's/^0//g')<br />
hour=$(date +%H | sed 's/^0//g')<br />
minute=$(date +%m | sed 's/^0//g')<br />
second=$(date +%S | sed 's/^0//g')<br />
}<br />
<br />
# kill deye_inverter_loop ()<br />
kill_old_instance ()<br />
{<br />
# kill old instances of me<br />
my_PS=$(ps ax | grep -v grep | grep "${myname}" | awk '{print $1}')<br />
for ps in $my_PS; do<br />
[ $ps -lt $myPID ] && kill $ps<br />
done<br />
}<br />
<br />
# get deye_inverter_mqtt PIDs<br />
get_deye_mqtt_pid ()<br />
{<br />
deye_mqtt_pid=$(ps ax | grep -v grep | grep "${deye_inverter_mqtt_cmd}" | awk '{print $1}')<br />
}<br />
<br />
# kill deye_inverter_instance ()<br />
kill_deye_inverter_instance ()<br />
{<br />
get_deye_mqtt_pid<br />
if [ ! -z "${deye_mqtt_pid}" ]; then<br />
kill ${deye_mqtt_pid}<br />
log_info "Info: KILL running deye_mqtt ${deye_mqtt_pid}"<br />
fi<br />
<br />
# kill any running deye requests<br />
deye_ps=$(ps ax | grep -v grep | grep "$deye_inverter_sh " | awk '{print $1}')<br />
[ ! -z "$deye_ps" ] && kill $deye_ps<br />
}<br />
<br />
# stop all runnning instances<br />
deye_inverter_mqtt_stop ()<br />
{<br />
kill_old_instance<br />
kill_deye_inverter_instance<br />
}<br />
<br />
# start deye_inverter_mqtt<br />
deye_inverter_mqtt_start ()<br />
{<br />
log_info "Deye MQTT start"<br />
cd ${deye_inverter_dir}<br />
${deye_inverter_mqtt_cmd}<br />
}<br />
<br />
# read year_month register 0x16 = 22<br />
read_year_month ()<br />
{<br />
${deye_inverter_sh} -r 22 | sed 's/int: //g' | cut -d, -f1<br />
}<br />
<br />
# read day register 0x17 = 23<br />
read_day ()<br />
{<br />
${deye_inverter_sh} -r 23 | sed 's/.*h: //g' | cut -d, -f1<br />
}<br />
<br />
# read Daily power register 0x3c = 60<br />
read_daily_Power ()<br />
{<br />
${deye_inverter_sh} -r 60 | sed 's/int: //g' | cut -d, -f1<br />
}<br />
<br />
# read actual power register 0x56 = 86<br />
read_actual_Power ()<br />
{<br />
# doubleRegisterSensor("AC Active Power", 0x56, 0.1, mqtt_topic_suffix='ac/active_power', groups=['string', 'micro'])<br />
# (int.from_bytes(high_word, 'big') * 65536 + int.from_bytes(low_word, 'big')) * self.factor<br />
# val86=${deye_inverter_sh} -c 10 -r 86 | sed 's/int: //g' | cut -d, -f1<br />
# val87=${deye_inverter_sh} -c 10 -r 87 | sed 's/int: //g' | cut -d, -f1<br />
# echo $(( (${val87} * 65536 + ${val86}) * 0.1 ))<br />
<br />
# simple value is enough for our purpose<br />
${deye_inverter_sh} -r 86 | sed 's/int: //g' | cut -d, -f1<br />
}<br />
<br />
# write actual date/time into deye inverter<br />
reset_deye_inverter ()<br />
{<br />
# daily reset phase<br />
daily_reset=init<br />
<br />
# ensure by loop, that all register values are written<br />
while [ ${daily_reset} != "done" ]; do<br />
<br />
# wait until we have enough power<br />
while [ "$(read_actual_Power)" -le 10 ]; do<br />
sleep 600<br />
done<br />
<br />
# read Daily power register<br />
dailyPower=$(read_daily_Power)<br />
<br />
# read (old) year_month and day<br />
val22_old=$(read_year_month)<br />
val23_day=$(read_day)<br />
log_info "date/time update initialized"<br />
<br />
# set variables year, month, day, hour, minute, second<br />
set_date_time_vars<br />
<br />
# calculate register 22-24 with date and time vars<br />
val22=$(( ${year} * 256 + ${month} ))<br />
val23=$(( ${day} * 256 + ${hour} ))<br />
val24=$(( ${minute} * 256 + ${second} ))<br />
<br />
# reset power/date/time only once a day or exit loop<br />
[ "$dailyPower" -eq 0 -a<br />
"${val22_old}" = "${val22}" -a<br />
"${val23_day}" = "${day}" ] && break<br />
<br />
# reset daily_power by<br />
# setting register 22-24 with actual date + time<br />
${deye_inverter_sh} -w 22 $val22 || continue<br />
${deye_inverter_sh} -w 23 $val23 || continue<br />
${deye_inverter_sh} -w 24 $val24 || continue<br />
<br />
# test success<br />
# read Daily power register, should be resetted to 0 now<br />
dailyPower=$(read_daily_Power)<br />
<br />
# exit loop if power is updated, else log that reset failed<br />
if [ "$dailyPower" -eq 0 ]; then<br />
daily_reset=done<br />
<br />
### skip loop here, reset doesn't work always :-(<br />
# elif [ $daily_reset = "init" ]; then<br />
# daily_reset=failed<br />
# sleep 300<br />
<br />
else<br />
log_info "daily power reset failed"<br />
break<br />
fi<br />
done<br />
log_info "date/time reset done"<br />
}<br />
<br />
# wait for (next) daylight<br />
wait_for_sunrise ()<br />
{<br />
sunrise=$($sunrise_cmd)<br />
sunset=$($sunset_cmd)<br />
<br />
current_time=$(date +%s)<br />
sunrise=$(date -d"$sunrise" +%s)<br />
sunset=$(date -d"$sunset" +%s)<br />
<br />
# calulate time in secs until (next) sunrise<br />
((sleep_sunrise=$sunrise - $current_time)) # this morning<br />
[ $sleep_sunrise -lt 0 ] &&<br />
((sleep_sunrise=$sunrise + 86400)) # next morning<br />
<br />
# wait for daylight only<br />
if [ $current_time -lt $sunrise -o<br />
$current_time -gt $sunset ]; then<br />
log_info sleep $sleep_sunrise<br />
sleep $sleep_sunrise<br />
fi<br />
}<br />
<br />
# wait for sunset<br />
wait_for_sunset ()<br />
{<br />
sunset=$($sunset_cmd)<br />
<br />
current_time=$(date +%s)<br />
sunset=$(date -d"$sunset" +%s)<br />
<br />
# calulate time in secs until next sunset<br />
((sleep_sunset=$sunset - $current_time))<br />
<br />
# wait until sunset only<br />
if [ $sleep_sunset -gt 0 ]; then<br />
log_info sleep $sleep_sunset<br />
sleep $sleep_sunset<br />
fi<br />
}<br />
<br />
# ------------------------<br />
# MAIN: Script starts here<br />
# ------------------------<br />
<br />
# sunset_stop: wait until (next) sunset<br />
[ "$arg" = "sunset_stop" ] &&<br />
wait_for_sunset<br />
<br />
# stop any runnning deye_inverter_mqtt processes<br />
deye_inverter_mqtt_stop<br />
<br />
# arg stop: don't start again<br />
[ "$arg" = "stop" ] && exit 0<br />
<br />
# wait for (next) daylight<br />
wait_for_sunrise<br />
<br />
# setting date/time also resets daily power register<br />
reset_deye_inverter<br />
<br />
# start deye_mqtt_loop<br />
# check every 15 min for running deye_inverter_mqtt<br />
while :; do<br />
<br />
# wait for sunrise<br />
wait_for_sunrise<br />
<br />
# start deye_inverter_mqtt<br />
get_deye_mqtt_pid<br />
if [ -z "$deye_mqtt_pid" ]; then<br />
deye_inverter_mqtt_start<br />
fi<br />
<br />
# wait 15 min<br />
sleep 900<br />
done<br />
</pre><br />
<br />
=== Contrab und /etc/rc.local ===<br />
Mein Crontab Eintrag:<br />
<pre># stop DEYE monitoring after sunset (FHEM)<br />
0 18 * * * /root/sbin/deye_mqtt_loop.sh sunset_stop 2>/dev/null<br />
</pre><br />
und /etc/rc.local:<br />
<pre># start monitoring of PV-System: DEYE inverter<br />
$(sleep 10; /root/sbin/deye_mqtt_loop.sh 2>dev/null)&<br />
</pre><br />
<br />
== Dashboard ==<br />
So sieht das aus, wenn es den ganzen Tag im April regnet und bedeckt ist:<br />
[[Datei:Solar Bedeckt Regen.jpg|midi]]<br />
<br />
Die Eigenverbrauchsquote habe ich mittels Grafana ermittelt:<br />
[[Datei:Solar Eigenverbrauch.jpg|midi|Eigenverbrauchsquote]]</div>
Neobiker
http://neobiker.de/wiki/index.php?title=DEYE_Wechselrichter&diff=2137
DEYE Wechselrichter
2024-02-03T14:55:17Z
<p>Neobiker: /* deye_mqtt_loop.sh */</p>
<hr />
<div>= Balkonkraftwerk: DEYE Wechselrichter ohne Cloud auslesen und per MQTT monitoren =<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
Der WR bietet verschiedene Möglichkeiten zur Konfiguration und Abfrage der aktuellen Werte:<br />
* einen internen Webserver (http://10.10.10.254)<br />
* die Cloud Anbindung zu Solarman (Solarman Smart App)<br />
* (angepasstes) Modbus Protokoll über Port 8899<br />
* AT+ Befehle über Port 48899<br />
<br />
Ich verwende das Modbus Protokoll zum auslesen des WR, das <br />
# effizient/schnell ist (Performance)<br />
# ohne Internetverbindung auskommt<br />
<br />
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.<br />
<br />
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.<br />
<br />
== Wechselrichter Eigenheiten ==<br />
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. <br />
<br />
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.<br />
<br />
== Installation ==<br />
Das Projekt von Github laden und in einem Verzeichnis speichern, '''Python''' ist normalerweise ja schon installiert. <br />
<br />
<pre>git clone https://github.com/kbialek/deye-inverter-mqtt<br />
pip install paho-mqtt<br />
cd deye-inverter-mqtt</pre><br />
<br />
Unter Debian habe ich im Skript '''deye_cli.sh''' python durch python3 ersetzen müssen:<br />
<pre>#!/bin/bash<br />
set -a; source config.env; set +a<br />
python3 deye_cli.py "$@"<br />
</pre><br />
<br />
=== config.env ===<br />
Die Konfiguration meines DEYE Wechselrichters vom Typ Micro-Inverter:<br />
'''config.env'''<br />
<pre>DEYE_LOGGER_IP_ADDRESS=<IP Wechselrichter im WLAN><br />
DEYE_LOGGER_PORT=8899<br />
DEYE_LOGGER_SERIAL_NUMBER=<Seriennummer des WR><br />
<br />
MQTT_HOST=<IP von MQTT Server><br />
MQTT_PORT=1883<br />
MQTT_USERNAME=<br />
MQTT_PASSWORD=<br />
MQTT_TOPIC_PREFIX=deye<br />
<br />
LOG_LEVEL=ERROR<br />
DEYE_DATA_READ_INTERVAL=60<br />
DEYE_METRIC_GROUPS=micro<br />
</pre><br />
<br />
=== deye_inverter.sh === <br />
Ausserdem habe ich ein kleines ''Wrapper Skript'' geschrieben, um '''Daten des WR''' einfacher '''lesen und schreiben''' zu können:<br><br />
<br />
'''deye_inverter.sh''' [--check <pause>] --read <register> | --write <register> <value><br />
<br />
<pre>#!/bin/bash<br />
# Read / Write DEYE Inverters<br />
# via deye_inverter_mqtt python package<br />
# https://github.com/kbialek/deye-inverter-mqtt<br />
<br />
retries=2 # retry deye_inverter command multiple times<br />
sleep=60 # sleep time between retries<br />
<br />
# locate script in deye_inverter_mqtt directory<br />
cd $(dirname $0)<br />
if [ ! -f ./deye_cli.sh ]; then<br />
echo "Error: ./deye_cli.sh not found."<br />
echo " Please move $(basename $0) in deye-inverter-mqtt directory."<br />
exit 1<br />
fi<br />
. ./config.env<br />
<br />
log_info ()<br />
{<br />
if [ ${LOG_LEVEL} = "INFO" ]; then<br />
logger -t $(basename $0) $@<br />
fi<br />
}<br />
<br />
log_error ()<br />
{<br />
logger -t $(basename $0) $@<br />
echo 2>&1 $@<br />
}<br />
<br />
# read parameters: mode [rw], register, value and optional -c <check pause><br />
pause=0<br />
while [ $# -gt 0 ]; do<br />
case $1 in<br />
-c|--check) shift<br />
[ $# -ge 3 ] || exit 1<br />
pause=$1<br />
;;<br />
-w|--write) mode=w<br />
shift<br />
[ $# -eq 2 ] || exit 1<br />
reg=$1<br />
val=$2<br />
shift<br />
;;<br />
-r|--read) mode=r<br />
shift<br />
[ $# -eq 1 ] || exit 1<br />
reg=$1<br />
;;<br />
esac<br />
shift<br />
done<br />
<br />
# handle offline deye_inverter by<br />
# ${pause} > 0 -> endless loop<br />
# ${pause} = 0 -> error after ${retries}<br />
while true; do<br />
<br />
# try deye_cli.sh $retries times every $sleep secs<br />
check=${retries}<br />
while [ ${check} -gt 0 ]; do<br />
<br />
case ${mode} in<br />
r) result=$(./deye_cli.sh $mode $reg | grep 'int: ')<br />
if [ -n "$result" ]; then<br />
echo "$result"<br />
log_info -t $0 deye_cli.sh $mode $reg<br />
exit 0<br />
fi<br />
;;<br />
w) result=$(./deye_cli.sh $mode $reg $val)<br />
if [ "$result" = "Ok" ]; then<br />
log_info -t $0 deye_cli.sh $mode $reg $val<br />
exit 0<br />
fi<br />
;;<br />
esac<br />
<br />
# wait ${sleep} or ${pause} secs<br />
sleep $(( ${pause} ? ${sleep} : ${pause} ))<br />
<br />
# try ${retries} times<br />
(( check-- ))<br />
done<br />
<br />
# error, or retry endless until wakeup of deye_inverter<br />
if [ ${pause} -eq 0 ]; then<br />
log_error "Error: deye_cli.sh $mode $reg $val failed."<br />
exit 1<br />
fi<br />
<br />
done<br />
</pre><br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
Folgendes Skript starte ich jeden Morgen um 04:00 Uhr per Cron (und in rc.local fürs booten):<br />
<br />
=== deye_mqtt_loop.sh ===<br />
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.<br />
<br />
Ausserdem setzt es beim Start den Tageszähler des Wechselrichters bedarfsweise zurück, bevor die Werte des DEYE Wechselrichters per Endlosschleife abgefragt werden.<br />
<br />
<pre>#!/bin/bash<br />
# reset Daily_Power of DEYE Inverters<br />
# via deye_inverter_mqtt python package<br />
# https://github.com/kbialek/deye-inverter-mqtt<br />
<br />
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin<br />
<br />
# config:<br />
deye_inverter_dir=/root/deye-inverter-mqtt<br />
fhem_dir=/opt/fhem<br />
horizon="-2" # sunset CIVIL=-6, REAL=0<br />
<br />
debug=info<br />
<br />
myPID=$$<br />
myname=$(basename $0)<br />
arg="$1"<br />
<br />
deye_inverter_sh=$deye_inverter_dir/deye_inverter.sh<br />
deye_inverter_mqtt_cfg=$deye_inverter_dir/config.env<br />
deye_inverter_mqtt_cmd="python3 deye_docker_entrypoint.py"<br />
<br />
sunrise_cmd="perl $fhem_dir/fhem.pl localhost:7072 {sunrise_abs('HORIZON=$horizon')}"<br />
sunset_cmd="perl $fhem_dir/fhem.pl localhost:7072 {sunset_abs('HORIZON=$horizon')}"<br />
<br />
# installation:<br />
# locate FHEM, used for sunrise(), sunset()<br />
if [ ! -x ${fhem_dir/fhem.pl} ]; then<br />
echo "Error: ${fhem_dir} not found."<br />
echo " Is FHEM installed in $fhem_dir ?"<br />
exit 1<br />
fi<br />
<br />
# locate script in deye_inverter_mqtt directory<br />
if [ ! -f ${deye_inverter_mqtt_cfg} ]; then<br />
echo "Error: ${deye_inverter_mqtt_cfg} not found."<br />
echo " Please update \$deye_inverter_sh path in $0"<br />
exit 1<br />
fi<br />
<br />
# read my config<br />
set -a; source ${deye_inverter_mqtt_cfg}; set +a<br />
<br />
[ "$debug" = "full" ] && set -x<br />
<br />
# ----- functions () ------------------------------------<br />
log_info ()<br />
{<br />
[ "$debug" = "yes" -o "$debug" = "true" -o "$debug" = "info" ] &&<br />
echo $myname: $@<br />
[ ${LOG_LEVEL} = "INFO" ] && logger -t $myname $@<br />
}<br />
<br />
log_error ()<br />
{<br />
logger -t $myname $@<br />
echo 1>&2 $@<br />
}<br />
<br />
# store actual date + time into vars<br />
set_date_time_vars ()<br />
{<br />
year=$(date +%y)<br />
month=$(date +%m | sed 's/^0//g')<br />
day=$(date +%d | sed 's/^0//g')<br />
hour=$(date +%H | sed 's/^0//g')<br />
minute=$(date +%m | sed 's/^0//g')<br />
second=$(date +%S | sed 's/^0//g')<br />
}<br />
<br />
# kill deye_inverter_loop ()<br />
kill_old_instance ()<br />
{<br />
# kill old instances of me<br />
my_PS=$(ps ax | grep -v grep | grep "${myname}" | awk '{print $1}')<br />
for ps in $my_PS; do<br />
[ $ps -lt $myPID ] && kill $ps<br />
done<br />
}<br />
<br />
# get deye_inverter_mqtt PIDs<br />
get_deye_mqtt_pid ()<br />
{<br />
deye_mqtt_pid=$(ps ax | grep -v grep | grep "${deye_inverter_mqtt_cmd}" | awk '{print $1}')<br />
}<br />
<br />
# kill deye_inverter_instance ()<br />
kill_deye_inverter_instance ()<br />
{<br />
get_deye_mqtt_pid<br />
if [ ! -z "${deye_mqtt_pid}" ]; then<br />
kill ${deye_mqtt_pid}<br />
log_info "Info: KILL running deye_mqtt ${deye_mqtt_pid}"<br />
fi<br />
<br />
# kill any running deye requests<br />
deye_ps=$(ps ax | grep -v grep | grep "$deye_inverter_sh " | awk '{print $1}')<br />
[ ! -z "$deye_ps" ] && kill $deye_ps<br />
}<br />
<br />
# stop all runnning instances<br />
deye_inverter_mqtt_stop ()<br />
{<br />
kill_old_instance<br />
kill_deye_inverter_instance<br />
}<br />
<br />
# start deye_inverter_mqtt<br />
deye_inverter_mqtt_start ()<br />
{<br />
log_info "Deye MQTT start"<br />
cd ${deye_inverter_dir}<br />
${deye_inverter_mqtt_cmd}<br />
}<br />
<br />
# read year_month register 0x16 = 22<br />
read_year_month ()<br />
{<br />
${deye_inverter_sh} -c 60 -r 22 | sed 's/int: //g' | cut -d, -f1<br />
}<br />
<br />
# read day register 0x17 = 23<br />
read_day ()<br />
{<br />
${deye_inverter_sh} -c 10 -r 23 | sed 's/.*h: //g' | cut -d, -f1<br />
}<br />
<br />
# read Daily power register 0x3c = 60<br />
read_daily_Power ()<br />
{<br />
${deye_inverter_sh} -c 10 -r 60 | sed 's/int: //g' | cut -d, -f1<br />
}<br />
<br />
# read actual power register 0x56 = 86<br />
read_actual_Power ()<br />
{<br />
# doubleRegisterSensor("AC Active Power", 0x56, 0.1, mqtt_topic_suffix='ac/active_power', groups=['string', 'micro'])<br />
# (int.from_bytes(high_word, 'big') * 65536 + int.from_bytes(low_word, 'big')) * self.factor<br />
# val86=${deye_inverter_sh} -c 10 -r 86 | sed 's/int: //g' | cut -d, -f1<br />
# val87=${deye_inverter_sh} -c 10 -r 87 | sed 's/int: //g' | cut -d, -f1<br />
# echo $(( (${val87} * 65536 + ${val86}) * 0.1 ))<br />
<br />
# simple value is enough for our purpose<br />
${deye_inverter_sh} -c 10 -r 86 | sed 's/int: //g' | cut -d, -f1<br />
}<br />
<br />
# write actual date/time into deye inverter<br />
reset_deye_inverter ()<br />
{<br />
# daily reset phase<br />
daily_reset=init<br />
<br />
# ensure by loop, that all register values are written<br />
while [ ${daily_reset} != "done" ]; do<br />
<br />
# wait until we have enough power<br />
while [ "$(read_actual_Power)" -le 10 ]; do<br />
sleep 600<br />
done<br />
<br />
# read Daily power register<br />
dailyPower=$(read_daily_Power)<br />
<br />
# read (old) year_month and day<br />
val22_old=$(read_year_month)<br />
val23_day=$(read_day)<br />
log_info "date/time update initialized"<br />
<br />
# set variables year, month, day, hour, minute, second<br />
set_date_time_vars<br />
<br />
# calculate register 22-24 with date and time vars<br />
val22=$(( ${year} * 256 + ${month} ))<br />
val23=$(( ${day} * 256 + ${hour} ))<br />
val24=$(( ${minute} * 256 + ${second} ))<br />
<br />
# reset power/date/time only once a day or exit loop<br />
[ "$dailyPower" -eq 0 -a<br />
"${val22_old}" = "${val22}" -a<br />
"${val23_day}" = "${day}" ] && break<br />
<br />
# reset daily_power by<br />
# setting register 22-24 with actual date + time<br />
${deye_inverter_sh} -w 22 $val22 || continue<br />
${deye_inverter_sh} -w 23 $val23 || continue<br />
${deye_inverter_sh} -w 24 $val24 || continue<br />
<br />
# test success<br />
# read Daily power register, should be resetted to 0 now<br />
dailyPower=$(read_daily_Power)<br />
<br />
# exit loop if power is updated, else log that reset failed<br />
if [ "$dailyPower" -eq 0 ]; then<br />
daily_reset=done<br />
<br />
### skip loop here, reset doesn't work always :-(<br />
# elif [ $daily_reset = "init" ]; then<br />
# daily_reset=failed<br />
# sleep 300<br />
<br />
else<br />
log_info "daily power reset failed"<br />
break<br />
fi<br />
done<br />
log_info "date/time reset done"<br />
}<br />
<br />
# wait for (next) daylight<br />
wait_for_sunrise ()<br />
{<br />
sunrise=$($sunrise_cmd)<br />
sunset=$($sunset_cmd)<br />
<br />
current_time=$(date +%s)<br />
sunrise=$(date -d"$sunrise" +%s)<br />
sunset=$(date -d"$sunset" +%s)<br />
<br />
# calulate time in secs until (next) sunrise<br />
((sleep_sunrise=$sunrise - $current_time)) # this morning<br />
[ $sleep_sunrise -lt 0 ] &&<br />
((sleep_sunrise=$sunrise + 86400)) # next morning<br />
<br />
# wait for daylight only<br />
if [ $current_time -lt $sunrise -o<br />
$current_time -gt $sunset ]; then<br />
log_info sleep $sleep_sunrise<br />
sleep $sleep_sunrise<br />
fi<br />
}<br />
<br />
# wait for sunset<br />
wait_for_sunset ()<br />
{<br />
sunset=$($sunset_cmd)<br />
<br />
current_time=$(date +%s)<br />
sunset=$(date -d"$sunset" +%s)<br />
<br />
# calulate time in secs until next sunset<br />
((sleep_sunset=$sunset - $current_time))<br />
<br />
# wait until sunset only<br />
if [ $sleep_sunset -gt 0 ]; then<br />
log_info sleep $sleep_sunset<br />
sleep $sleep_sunset<br />
fi<br />
}<br />
<br />
# ------------------------<br />
# MAIN: Script starts here<br />
# ------------------------<br />
<br />
# sunset_stop: wait until (next) sunset<br />
[ "$arg" = "sunset_stop" ] &&<br />
wait_for_sunset<br />
<br />
# stop any runnning deye_inverter_mqtt processes<br />
deye_inverter_mqtt_stop<br />
<br />
# arg stop: don't start again<br />
[ "$arg" = "stop" ] && exit 0<br />
<br />
# wait for (next) daylight<br />
wait_for_sunrise<br />
<br />
# setting date/time also resets daily power register<br />
reset_deye_inverter<br />
<br />
# start deye_mqtt_loop<br />
# check every 15 min for running deye_inverter_mqtt<br />
while :; do<br />
<br />
# wait for sunrise<br />
wait_for_sunrise<br />
<br />
# start deye_inverter_mqtt<br />
get_deye_mqtt_pid<br />
if [ -z "$deye_mqtt_pid" ]; then<br />
deye_inverter_mqtt_start<br />
fi<br />
<br />
# wait 15 min<br />
sleep 900<br />
done<br />
</pre><br />
<br />
=== Contrab und /etc/rc.local ===<br />
Mein Crontab Eintrag:<br />
<pre># stop DEYE monitoring after sunset (FHEM)<br />
0 18 * * * /root/sbin/deye_mqtt_loop.sh sunset_stop 2>/dev/null<br />
</pre><br />
und /etc/rc.local:<br />
<pre># start monitoring of PV-System: DEYE inverter<br />
$(sleep 10; /root/sbin/deye_mqtt_loop.sh 2>dev/null)&<br />
</pre><br />
<br />
== Dashboard ==<br />
So sieht das aus, wenn es den ganzen Tag im April regnet und bedeckt ist:<br />
[[Datei:Solar Bedeckt Regen.jpg|midi]]<br />
<br />
Die Eigenverbrauchsquote habe ich mittels Grafana ermittelt:<br />
[[Datei:Solar Eigenverbrauch.jpg|midi|Eigenverbrauchsquote]]</div>
Neobiker
http://neobiker.de/wiki/index.php?title=DEYE_Wechselrichter&diff=2136
DEYE Wechselrichter
2024-02-03T14:51:52Z
<p>Neobiker: /* Contrab und /etc/rc.local */</p>
<hr />
<div>= Balkonkraftwerk: DEYE Wechselrichter ohne Cloud auslesen und per MQTT monitoren =<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
Der WR bietet verschiedene Möglichkeiten zur Konfiguration und Abfrage der aktuellen Werte:<br />
* einen internen Webserver (http://10.10.10.254)<br />
* die Cloud Anbindung zu Solarman (Solarman Smart App)<br />
* (angepasstes) Modbus Protokoll über Port 8899<br />
* AT+ Befehle über Port 48899<br />
<br />
Ich verwende das Modbus Protokoll zum auslesen des WR, das <br />
# effizient/schnell ist (Performance)<br />
# ohne Internetverbindung auskommt<br />
<br />
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.<br />
<br />
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.<br />
<br />
== Wechselrichter Eigenheiten ==<br />
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. <br />
<br />
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.<br />
<br />
== Installation ==<br />
Das Projekt von Github laden und in einem Verzeichnis speichern, '''Python''' ist normalerweise ja schon installiert. <br />
<br />
<pre>git clone https://github.com/kbialek/deye-inverter-mqtt<br />
pip install paho-mqtt<br />
cd deye-inverter-mqtt</pre><br />
<br />
Unter Debian habe ich im Skript '''deye_cli.sh''' python durch python3 ersetzen müssen:<br />
<pre>#!/bin/bash<br />
set -a; source config.env; set +a<br />
python3 deye_cli.py "$@"<br />
</pre><br />
<br />
=== config.env ===<br />
Die Konfiguration meines DEYE Wechselrichters vom Typ Micro-Inverter:<br />
'''config.env'''<br />
<pre>DEYE_LOGGER_IP_ADDRESS=<IP Wechselrichter im WLAN><br />
DEYE_LOGGER_PORT=8899<br />
DEYE_LOGGER_SERIAL_NUMBER=<Seriennummer des WR><br />
<br />
MQTT_HOST=<IP von MQTT Server><br />
MQTT_PORT=1883<br />
MQTT_USERNAME=<br />
MQTT_PASSWORD=<br />
MQTT_TOPIC_PREFIX=deye<br />
<br />
LOG_LEVEL=ERROR<br />
DEYE_DATA_READ_INTERVAL=60<br />
DEYE_METRIC_GROUPS=micro<br />
</pre><br />
<br />
=== deye_inverter.sh === <br />
Ausserdem habe ich ein kleines ''Wrapper Skript'' geschrieben, um '''Daten des WR''' einfacher '''lesen und schreiben''' zu können:<br><br />
<br />
'''deye_inverter.sh''' [--check <pause>] --read <register> | --write <register> <value><br />
<br />
<pre>#!/bin/bash<br />
# Read / Write DEYE Inverters<br />
# via deye_inverter_mqtt python package<br />
# https://github.com/kbialek/deye-inverter-mqtt<br />
<br />
retries=2 # retry deye_inverter command multiple times<br />
sleep=60 # sleep time between retries<br />
<br />
# locate script in deye_inverter_mqtt directory<br />
cd $(dirname $0)<br />
if [ ! -f ./deye_cli.sh ]; then<br />
echo "Error: ./deye_cli.sh not found."<br />
echo " Please move $(basename $0) in deye-inverter-mqtt directory."<br />
exit 1<br />
fi<br />
. ./config.env<br />
<br />
log_info ()<br />
{<br />
if [ ${LOG_LEVEL} = "INFO" ]; then<br />
logger -t $(basename $0) $@<br />
fi<br />
}<br />
<br />
log_error ()<br />
{<br />
logger -t $(basename $0) $@<br />
echo 2>&1 $@<br />
}<br />
<br />
# read parameters: mode [rw], register, value and optional -c <check pause><br />
pause=0<br />
while [ $# -gt 0 ]; do<br />
case $1 in<br />
-c|--check) shift<br />
[ $# -ge 3 ] || exit 1<br />
pause=$1<br />
;;<br />
-w|--write) mode=w<br />
shift<br />
[ $# -eq 2 ] || exit 1<br />
reg=$1<br />
val=$2<br />
shift<br />
;;<br />
-r|--read) mode=r<br />
shift<br />
[ $# -eq 1 ] || exit 1<br />
reg=$1<br />
;;<br />
esac<br />
shift<br />
done<br />
<br />
# handle offline deye_inverter by<br />
# ${pause} > 0 -> endless loop<br />
# ${pause} = 0 -> error after ${retries}<br />
while true; do<br />
<br />
# try deye_cli.sh $retries times every $sleep secs<br />
check=${retries}<br />
while [ ${check} -gt 0 ]; do<br />
<br />
case ${mode} in<br />
r) result=$(./deye_cli.sh $mode $reg | grep 'int: ')<br />
if [ -n "$result" ]; then<br />
echo "$result"<br />
log_info -t $0 deye_cli.sh $mode $reg<br />
exit 0<br />
fi<br />
;;<br />
w) result=$(./deye_cli.sh $mode $reg $val)<br />
if [ "$result" = "Ok" ]; then<br />
log_info -t $0 deye_cli.sh $mode $reg $val<br />
exit 0<br />
fi<br />
;;<br />
esac<br />
<br />
# wait ${sleep} or ${pause} secs<br />
sleep $(( ${pause} ? ${sleep} : ${pause} ))<br />
<br />
# try ${retries} times<br />
(( check-- ))<br />
done<br />
<br />
# error, or retry endless until wakeup of deye_inverter<br />
if [ ${pause} -eq 0 ]; then<br />
log_error "Error: deye_cli.sh $mode $reg $val failed."<br />
exit 1<br />
fi<br />
<br />
done<br />
</pre><br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
Folgendes Skript starte ich jeden Morgen um 04:00 Uhr per Cron (und in rc.local fürs booten):<br />
<br />
=== deye_mqtt_loop.sh ===<br />
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.<br />
<br />
Ausserdem setzt es beim Start den Tageszähler des Wechselrichters bedarfsweise zurück, bevor die Werte des DEYE Wechselrichters per Endlosschleife abgefragt werden.<br />
<br />
<pre>#!/bin/bash<br />
# reset Daily_Power of DEYE Inverters<br />
# via deye_inverter_mqtt python package<br />
# https://github.com/kbialek/deye-inverter-mqtt<br />
<br />
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin<br />
<br />
# config:<br />
deye_inverter_dir=/opt/deye-inverter-mqtt<br />
fhem_dir=/opt/fhem<br />
horizon="-2" # sunset CIVIL=-6, REAL=0<br />
<br />
debug=info<br />
<br />
myPID=$$<br />
myname=$(basename $0)<br />
arg="$1"<br />
<br />
deye_inverter_sh=$deye_inverter_dir/deye_inverter.sh<br />
deye_inverter_mqtt_cfg=$deye_inverter_dir/config.env<br />
deye_inverter_mqtt_cmd="python3 deye_docker_entrypoint.py"<br />
<br />
sunrise_cmd="perl $fhem_dir/fhem.pl localhost:7072 {sunrise_abs('HORIZON=$horizon')}"<br />
sunset_cmd="perl $fhem_dir/fhem.pl localhost:7072 {sunset_abs('HORIZON=$horizon')}"<br />
<br />
# installation:<br />
# locate FHEM, used for sunrise(), sunset()<br />
if [ ! -x ${fhem_dir/fhem.pl} ]; then<br />
echo "Error: ${fhem_dir} not found."<br />
echo " Is FHEM installed in $fhem_dir ?"<br />
exit 1<br />
fi<br />
<br />
# locate script in deye_inverter_mqtt directory<br />
if [ ! -f ${deye_inverter_mqtt_cfg} ]; then<br />
echo "Error: ${deye_inverter_mqtt_cfg} not found."<br />
echo " Please update \$deye_inverter_sh path in $0"<br />
exit 1<br />
fi<br />
<br />
# read my config<br />
set -a; source ${deye_inverter_mqtt_cfg}; set +a<br />
<br />
[ "$debug" = "full" ] && set -x<br />
<br />
# ----- functions () ------------------------------------<br />
log_info ()<br />
{<br />
[ "$debug" = "yes" -o "$debug" = "true" -o "$debug" = "info" ] &&<br />
echo $myname: $@<br />
[ ${LOG_LEVEL} = "INFO" ] && logger -t $myname $@<br />
}<br />
<br />
log_error ()<br />
{<br />
logger -t $myname $@<br />
echo 1>&2 $@<br />
}<br />
<br />
# store actual date + time into vars<br />
set_date_time_vars ()<br />
{<br />
year=$(date +%y)<br />
month=$(date +%m | sed 's/^0//g')<br />
day=$(date +%d | sed 's/^0//g')<br />
hour=$(date +%H | sed 's/^0//g')<br />
minute=$(date +%m | sed 's/^0//g')<br />
second=$(date +%S | sed 's/^0//g')<br />
}<br />
<br />
# kill deye_inverter_loop ()<br />
kill_old_instance ()<br />
{<br />
# kill old instances of me<br />
my_PS=$(ps ax | grep -v grep | grep "${myname}" | awk '{print $1}')<br />
for ps in $my_PS; do<br />
[ $ps -lt $myPID ] && kill $ps<br />
done<br />
}<br />
<br />
# get deye_inverter_mqtt PIDs<br />
get_deye_mqtt_pid ()<br />
{<br />
deye_mqtt_pid=$(ps ax | grep -v grep | grep "${deye_inverter_mqtt_cmd}" | awk '{print $1}')<br />
}<br />
<br />
# kill deye_inverter_instance ()<br />
kill_deye_inverter_instance ()<br />
{<br />
get_deye_mqtt_pid<br />
if [ ! -z "${deye_mqtt_pid}" ]; then<br />
kill ${deye_mqtt_pid}<br />
log_info "Info: KILL running deye_mqtt ${deye_mqtt_pid}"<br />
fi<br />
<br />
# kill any running deye requests<br />
deye_ps=$(ps ax | grep -v grep | grep "$deye_inverter_sh " | awk '{print $1}')<br />
[ ! -z "$deye_ps" ] && kill $deye_ps<br />
}<br />
<br />
# stop all runnning instances<br />
deye_inverter_mqtt_stop ()<br />
{<br />
kill_old_instance<br />
kill_deye_inverter_instance<br />
}<br />
<br />
# start deye_inverter_mqtt<br />
deye_inverter_mqtt_start ()<br />
{<br />
log_info "Deye MQTT start"<br />
cd ${deye_inverter_dir}<br />
${deye_inverter_mqtt_cmd}<br />
}<br />
<br />
# read year_month register 0x16 = 22<br />
read_year_month ()<br />
{<br />
${deye_inverter_sh} -c 60 -r 22 | sed 's/int: //g' | cut -d, -f1<br />
}<br />
<br />
# read day register 0x17 = 23<br />
read_day ()<br />
{<br />
${deye_inverter_sh} -c 10 -r 23 | sed 's/.*h: //g' | cut -d, -f1<br />
}<br />
<br />
# read Daily power register 0x3c = 60<br />
read_daily_Power ()<br />
{<br />
${deye_inverter_sh} -c 10 -r 60 | sed 's/int: //g' | cut -d, -f1<br />
}<br />
<br />
# read actual power register 0x56 = 86<br />
read_actual_Power ()<br />
{<br />
# doubleRegisterSensor("AC Active Power", 0x56, 0.1, mqtt_topic_suffix='ac/active_power', groups=['string', 'micro'])<br />
# (int.from_bytes(high_word, 'big') * 65536 + int.from_bytes(low_word, 'big')) * self.factor<br />
# val86=${deye_inverter_sh} -c 10 -r 86 | sed 's/int: //g' | cut -d, -f1<br />
# val87=${deye_inverter_sh} -c 10 -r 87 | sed 's/int: //g' | cut -d, -f1<br />
# echo $(( (${val87} * 65536 + ${val86}) * 0.1 ))<br />
<br />
# simple value is enough for our purpose<br />
${deye_inverter_sh} -c 10 -r 86 | sed 's/int: //g' | cut -d, -f1<br />
}<br />
<br />
# write actual date/time into deye inverter<br />
reset_deye_inverter ()<br />
{<br />
# daily reset phase<br />
daily_reset=init<br />
<br />
# ensure by loop, that all register values are written<br />
while [ ${daily_reset} != "done" ]; do<br />
<br />
# wait until we have enough power<br />
while [ "$(read_actual_Power)" -le 10 ]; do<br />
sleep 600<br />
done<br />
<br />
# read Daily power register<br />
dailyPower=$(read_daily_Power)<br />
<br />
# read (old) year_month and day<br />
val22_old=$(read_year_month)<br />
val23_day=$(read_day)<br />
log_info "date/time update initialized"<br />
<br />
# set variables year, month, day, hour, minute, second<br />
set_date_time_vars<br />
<br />
# calculate register 22-24 with date and time vars<br />
val22=$(( ${year} * 256 + ${month} ))<br />
val23=$(( ${day} * 256 + ${hour} ))<br />
val24=$(( ${minute} * 256 + ${second} ))<br />
<br />
# reset power/date/time only once a day or exit loop<br />
[ "$dailyPower" -eq 0 -a<br />
"${val22_old}" = "${val22}" -a<br />
"${val23_day}" = "${day}" ] && break<br />
<br />
# reset daily_power by<br />
# setting register 22-24 with actual date + time<br />
${deye_inverter_sh} -w 22 $val22 || continue<br />
${deye_inverter_sh} -w 23 $val23 || continue<br />
${deye_inverter_sh} -w 24 $val24 || continue<br />
<br />
# test success<br />
# read Daily power register, should be resetted to 0 now<br />
dailyPower=$(read_daily_Power)<br />
<br />
# exit loop if power is updated, else log that reset failed<br />
if [ "$dailyPower" -eq 0 ]; then<br />
daily_reset=done<br />
<br />
### skip loop here, reset doesn't work always :-(<br />
# elif [ $daily_reset = "init" ]; then<br />
# daily_reset=failed<br />
# sleep 300<br />
<br />
else<br />
log_info "daily power reset failed"<br />
break<br />
fi<br />
done<br />
log_info "date/time reset done"<br />
}<br />
<br />
# wait for (next) daylight<br />
wait_for_sunrise ()<br />
{<br />
sunrise=$($sunrise_cmd)<br />
sunset=$($sunset_cmd)<br />
<br />
current_time=$(date +%s)<br />
sunrise=$(date -d"$sunrise" +%s)<br />
sunset=$(date -d"$sunset" +%s)<br />
<br />
# calulate time in secs until (next) sunrise<br />
((sleep_sunrise=$sunrise - $current_time)) # this morning<br />
[ $sleep_sunrise -lt 0 ] &&<br />
((sleep_sunrise=$sunrise + 86400)) # next morning<br />
<br />
# wait for daylight only<br />
if [ $current_time -lt $sunrise -o<br />
$current_time -gt $sunset ]; then<br />
log_info sleep $sleep_sunrise<br />
sleep $sleep_sunrise<br />
fi<br />
}<br />
<br />
# wait for sunset<br />
wait_for_sunset ()<br />
{<br />
sunset=$($sunset_cmd)<br />
<br />
current_time=$(date +%s)<br />
sunset=$(date -d"$sunset" +%s)<br />
<br />
# calulate time in secs until next sunset<br />
((sleep_sunset=$sunset - $current_time))<br />
<br />
# wait until sunset only<br />
if [ $sleep_sunset -gt 0 ]; then<br />
log_info sleep $sleep_sunset<br />
sleep $sleep_sunset<br />
fi<br />
}<br />
<br />
# ------------------------<br />
# MAIN: Script starts here<br />
# ------------------------<br />
<br />
# sunset_stop: wait until (next) sunset<br />
[ "$arg" = "sunset_stop" ] &&<br />
wait_for_sunset<br />
<br />
# stop any runnning deye_inverter_mqtt processes<br />
deye_inverter_mqtt_stop<br />
<br />
# arg stop: don't start again<br />
[ "$arg" = "stop" ] && exit 0<br />
<br />
# wait for (next) daylight<br />
wait_for_sunrise<br />
<br />
# setting date/time also resets daily power register<br />
reset_deye_inverter<br />
<br />
# start deye_mqtt_loop<br />
# check every 15 min for running deye_inverter_mqtt<br />
while :; do<br />
<br />
# wait for sunrise<br />
wait_for_sunrise<br />
<br />
# start deye_inverter_mqtt<br />
get_deye_mqtt_pid<br />
if [ -z "$deye_mqtt_pid" ]; then<br />
deye_inverter_mqtt_start<br />
fi<br />
<br />
# wait 15 min<br />
sleep 900<br />
done<br />
</pre><br />
<br />
=== Contrab und /etc/rc.local ===<br />
Mein Crontab Eintrag:<br />
<pre># stop DEYE monitoring after sunset (FHEM)<br />
0 18 * * * /root/sbin/deye_mqtt_loop.sh sunset_stop 2>/dev/null<br />
</pre><br />
und /etc/rc.local:<br />
<pre># start monitoring of PV-System: DEYE inverter<br />
$(sleep 10; /root/sbin/deye_mqtt_loop.sh 2>dev/null)&<br />
</pre><br />
<br />
== Dashboard ==<br />
So sieht das aus, wenn es den ganzen Tag im April regnet und bedeckt ist:<br />
[[Datei:Solar Bedeckt Regen.jpg|midi]]<br />
<br />
Die Eigenverbrauchsquote habe ich mittels Grafana ermittelt:<br />
[[Datei:Solar Eigenverbrauch.jpg|midi|Eigenverbrauchsquote]]</div>
Neobiker
http://neobiker.de/wiki/index.php?title=DEYE_Wechselrichter&diff=2135
DEYE Wechselrichter
2024-02-03T14:51:36Z
<p>Neobiker: /* Contrab und /etc/rc.local */</p>
<hr />
<div>= Balkonkraftwerk: DEYE Wechselrichter ohne Cloud auslesen und per MQTT monitoren =<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
Der WR bietet verschiedene Möglichkeiten zur Konfiguration und Abfrage der aktuellen Werte:<br />
* einen internen Webserver (http://10.10.10.254)<br />
* die Cloud Anbindung zu Solarman (Solarman Smart App)<br />
* (angepasstes) Modbus Protokoll über Port 8899<br />
* AT+ Befehle über Port 48899<br />
<br />
Ich verwende das Modbus Protokoll zum auslesen des WR, das <br />
# effizient/schnell ist (Performance)<br />
# ohne Internetverbindung auskommt<br />
<br />
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.<br />
<br />
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.<br />
<br />
== Wechselrichter Eigenheiten ==<br />
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. <br />
<br />
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.<br />
<br />
== Installation ==<br />
Das Projekt von Github laden und in einem Verzeichnis speichern, '''Python''' ist normalerweise ja schon installiert. <br />
<br />
<pre>git clone https://github.com/kbialek/deye-inverter-mqtt<br />
pip install paho-mqtt<br />
cd deye-inverter-mqtt</pre><br />
<br />
Unter Debian habe ich im Skript '''deye_cli.sh''' python durch python3 ersetzen müssen:<br />
<pre>#!/bin/bash<br />
set -a; source config.env; set +a<br />
python3 deye_cli.py "$@"<br />
</pre><br />
<br />
=== config.env ===<br />
Die Konfiguration meines DEYE Wechselrichters vom Typ Micro-Inverter:<br />
'''config.env'''<br />
<pre>DEYE_LOGGER_IP_ADDRESS=<IP Wechselrichter im WLAN><br />
DEYE_LOGGER_PORT=8899<br />
DEYE_LOGGER_SERIAL_NUMBER=<Seriennummer des WR><br />
<br />
MQTT_HOST=<IP von MQTT Server><br />
MQTT_PORT=1883<br />
MQTT_USERNAME=<br />
MQTT_PASSWORD=<br />
MQTT_TOPIC_PREFIX=deye<br />
<br />
LOG_LEVEL=ERROR<br />
DEYE_DATA_READ_INTERVAL=60<br />
DEYE_METRIC_GROUPS=micro<br />
</pre><br />
<br />
=== deye_inverter.sh === <br />
Ausserdem habe ich ein kleines ''Wrapper Skript'' geschrieben, um '''Daten des WR''' einfacher '''lesen und schreiben''' zu können:<br><br />
<br />
'''deye_inverter.sh''' [--check <pause>] --read <register> | --write <register> <value><br />
<br />
<pre>#!/bin/bash<br />
# Read / Write DEYE Inverters<br />
# via deye_inverter_mqtt python package<br />
# https://github.com/kbialek/deye-inverter-mqtt<br />
<br />
retries=2 # retry deye_inverter command multiple times<br />
sleep=60 # sleep time between retries<br />
<br />
# locate script in deye_inverter_mqtt directory<br />
cd $(dirname $0)<br />
if [ ! -f ./deye_cli.sh ]; then<br />
echo "Error: ./deye_cli.sh not found."<br />
echo " Please move $(basename $0) in deye-inverter-mqtt directory."<br />
exit 1<br />
fi<br />
. ./config.env<br />
<br />
log_info ()<br />
{<br />
if [ ${LOG_LEVEL} = "INFO" ]; then<br />
logger -t $(basename $0) $@<br />
fi<br />
}<br />
<br />
log_error ()<br />
{<br />
logger -t $(basename $0) $@<br />
echo 2>&1 $@<br />
}<br />
<br />
# read parameters: mode [rw], register, value and optional -c <check pause><br />
pause=0<br />
while [ $# -gt 0 ]; do<br />
case $1 in<br />
-c|--check) shift<br />
[ $# -ge 3 ] || exit 1<br />
pause=$1<br />
;;<br />
-w|--write) mode=w<br />
shift<br />
[ $# -eq 2 ] || exit 1<br />
reg=$1<br />
val=$2<br />
shift<br />
;;<br />
-r|--read) mode=r<br />
shift<br />
[ $# -eq 1 ] || exit 1<br />
reg=$1<br />
;;<br />
esac<br />
shift<br />
done<br />
<br />
# handle offline deye_inverter by<br />
# ${pause} > 0 -> endless loop<br />
# ${pause} = 0 -> error after ${retries}<br />
while true; do<br />
<br />
# try deye_cli.sh $retries times every $sleep secs<br />
check=${retries}<br />
while [ ${check} -gt 0 ]; do<br />
<br />
case ${mode} in<br />
r) result=$(./deye_cli.sh $mode $reg | grep 'int: ')<br />
if [ -n "$result" ]; then<br />
echo "$result"<br />
log_info -t $0 deye_cli.sh $mode $reg<br />
exit 0<br />
fi<br />
;;<br />
w) result=$(./deye_cli.sh $mode $reg $val)<br />
if [ "$result" = "Ok" ]; then<br />
log_info -t $0 deye_cli.sh $mode $reg $val<br />
exit 0<br />
fi<br />
;;<br />
esac<br />
<br />
# wait ${sleep} or ${pause} secs<br />
sleep $(( ${pause} ? ${sleep} : ${pause} ))<br />
<br />
# try ${retries} times<br />
(( check-- ))<br />
done<br />
<br />
# error, or retry endless until wakeup of deye_inverter<br />
if [ ${pause} -eq 0 ]; then<br />
log_error "Error: deye_cli.sh $mode $reg $val failed."<br />
exit 1<br />
fi<br />
<br />
done<br />
</pre><br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
Folgendes Skript starte ich jeden Morgen um 04:00 Uhr per Cron (und in rc.local fürs booten):<br />
<br />
=== deye_mqtt_loop.sh ===<br />
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.<br />
<br />
Ausserdem setzt es beim Start den Tageszähler des Wechselrichters bedarfsweise zurück, bevor die Werte des DEYE Wechselrichters per Endlosschleife abgefragt werden.<br />
<br />
<pre>#!/bin/bash<br />
# reset Daily_Power of DEYE Inverters<br />
# via deye_inverter_mqtt python package<br />
# https://github.com/kbialek/deye-inverter-mqtt<br />
<br />
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin<br />
<br />
# config:<br />
deye_inverter_dir=/opt/deye-inverter-mqtt<br />
fhem_dir=/opt/fhem<br />
horizon="-2" # sunset CIVIL=-6, REAL=0<br />
<br />
debug=info<br />
<br />
myPID=$$<br />
myname=$(basename $0)<br />
arg="$1"<br />
<br />
deye_inverter_sh=$deye_inverter_dir/deye_inverter.sh<br />
deye_inverter_mqtt_cfg=$deye_inverter_dir/config.env<br />
deye_inverter_mqtt_cmd="python3 deye_docker_entrypoint.py"<br />
<br />
sunrise_cmd="perl $fhem_dir/fhem.pl localhost:7072 {sunrise_abs('HORIZON=$horizon')}"<br />
sunset_cmd="perl $fhem_dir/fhem.pl localhost:7072 {sunset_abs('HORIZON=$horizon')}"<br />
<br />
# installation:<br />
# locate FHEM, used for sunrise(), sunset()<br />
if [ ! -x ${fhem_dir/fhem.pl} ]; then<br />
echo "Error: ${fhem_dir} not found."<br />
echo " Is FHEM installed in $fhem_dir ?"<br />
exit 1<br />
fi<br />
<br />
# locate script in deye_inverter_mqtt directory<br />
if [ ! -f ${deye_inverter_mqtt_cfg} ]; then<br />
echo "Error: ${deye_inverter_mqtt_cfg} not found."<br />
echo " Please update \$deye_inverter_sh path in $0"<br />
exit 1<br />
fi<br />
<br />
# read my config<br />
set -a; source ${deye_inverter_mqtt_cfg}; set +a<br />
<br />
[ "$debug" = "full" ] && set -x<br />
<br />
# ----- functions () ------------------------------------<br />
log_info ()<br />
{<br />
[ "$debug" = "yes" -o "$debug" = "true" -o "$debug" = "info" ] &&<br />
echo $myname: $@<br />
[ ${LOG_LEVEL} = "INFO" ] && logger -t $myname $@<br />
}<br />
<br />
log_error ()<br />
{<br />
logger -t $myname $@<br />
echo 1>&2 $@<br />
}<br />
<br />
# store actual date + time into vars<br />
set_date_time_vars ()<br />
{<br />
year=$(date +%y)<br />
month=$(date +%m | sed 's/^0//g')<br />
day=$(date +%d | sed 's/^0//g')<br />
hour=$(date +%H | sed 's/^0//g')<br />
minute=$(date +%m | sed 's/^0//g')<br />
second=$(date +%S | sed 's/^0//g')<br />
}<br />
<br />
# kill deye_inverter_loop ()<br />
kill_old_instance ()<br />
{<br />
# kill old instances of me<br />
my_PS=$(ps ax | grep -v grep | grep "${myname}" | awk '{print $1}')<br />
for ps in $my_PS; do<br />
[ $ps -lt $myPID ] && kill $ps<br />
done<br />
}<br />
<br />
# get deye_inverter_mqtt PIDs<br />
get_deye_mqtt_pid ()<br />
{<br />
deye_mqtt_pid=$(ps ax | grep -v grep | grep "${deye_inverter_mqtt_cmd}" | awk '{print $1}')<br />
}<br />
<br />
# kill deye_inverter_instance ()<br />
kill_deye_inverter_instance ()<br />
{<br />
get_deye_mqtt_pid<br />
if [ ! -z "${deye_mqtt_pid}" ]; then<br />
kill ${deye_mqtt_pid}<br />
log_info "Info: KILL running deye_mqtt ${deye_mqtt_pid}"<br />
fi<br />
<br />
# kill any running deye requests<br />
deye_ps=$(ps ax | grep -v grep | grep "$deye_inverter_sh " | awk '{print $1}')<br />
[ ! -z "$deye_ps" ] && kill $deye_ps<br />
}<br />
<br />
# stop all runnning instances<br />
deye_inverter_mqtt_stop ()<br />
{<br />
kill_old_instance<br />
kill_deye_inverter_instance<br />
}<br />
<br />
# start deye_inverter_mqtt<br />
deye_inverter_mqtt_start ()<br />
{<br />
log_info "Deye MQTT start"<br />
cd ${deye_inverter_dir}<br />
${deye_inverter_mqtt_cmd}<br />
}<br />
<br />
# read year_month register 0x16 = 22<br />
read_year_month ()<br />
{<br />
${deye_inverter_sh} -c 60 -r 22 | sed 's/int: //g' | cut -d, -f1<br />
}<br />
<br />
# read day register 0x17 = 23<br />
read_day ()<br />
{<br />
${deye_inverter_sh} -c 10 -r 23 | sed 's/.*h: //g' | cut -d, -f1<br />
}<br />
<br />
# read Daily power register 0x3c = 60<br />
read_daily_Power ()<br />
{<br />
${deye_inverter_sh} -c 10 -r 60 | sed 's/int: //g' | cut -d, -f1<br />
}<br />
<br />
# read actual power register 0x56 = 86<br />
read_actual_Power ()<br />
{<br />
# doubleRegisterSensor("AC Active Power", 0x56, 0.1, mqtt_topic_suffix='ac/active_power', groups=['string', 'micro'])<br />
# (int.from_bytes(high_word, 'big') * 65536 + int.from_bytes(low_word, 'big')) * self.factor<br />
# val86=${deye_inverter_sh} -c 10 -r 86 | sed 's/int: //g' | cut -d, -f1<br />
# val87=${deye_inverter_sh} -c 10 -r 87 | sed 's/int: //g' | cut -d, -f1<br />
# echo $(( (${val87} * 65536 + ${val86}) * 0.1 ))<br />
<br />
# simple value is enough for our purpose<br />
${deye_inverter_sh} -c 10 -r 86 | sed 's/int: //g' | cut -d, -f1<br />
}<br />
<br />
# write actual date/time into deye inverter<br />
reset_deye_inverter ()<br />
{<br />
# daily reset phase<br />
daily_reset=init<br />
<br />
# ensure by loop, that all register values are written<br />
while [ ${daily_reset} != "done" ]; do<br />
<br />
# wait until we have enough power<br />
while [ "$(read_actual_Power)" -le 10 ]; do<br />
sleep 600<br />
done<br />
<br />
# read Daily power register<br />
dailyPower=$(read_daily_Power)<br />
<br />
# read (old) year_month and day<br />
val22_old=$(read_year_month)<br />
val23_day=$(read_day)<br />
log_info "date/time update initialized"<br />
<br />
# set variables year, month, day, hour, minute, second<br />
set_date_time_vars<br />
<br />
# calculate register 22-24 with date and time vars<br />
val22=$(( ${year} * 256 + ${month} ))<br />
val23=$(( ${day} * 256 + ${hour} ))<br />
val24=$(( ${minute} * 256 + ${second} ))<br />
<br />
# reset power/date/time only once a day or exit loop<br />
[ "$dailyPower" -eq 0 -a<br />
"${val22_old}" = "${val22}" -a<br />
"${val23_day}" = "${day}" ] && break<br />
<br />
# reset daily_power by<br />
# setting register 22-24 with actual date + time<br />
${deye_inverter_sh} -w 22 $val22 || continue<br />
${deye_inverter_sh} -w 23 $val23 || continue<br />
${deye_inverter_sh} -w 24 $val24 || continue<br />
<br />
# test success<br />
# read Daily power register, should be resetted to 0 now<br />
dailyPower=$(read_daily_Power)<br />
<br />
# exit loop if power is updated, else log that reset failed<br />
if [ "$dailyPower" -eq 0 ]; then<br />
daily_reset=done<br />
<br />
### skip loop here, reset doesn't work always :-(<br />
# elif [ $daily_reset = "init" ]; then<br />
# daily_reset=failed<br />
# sleep 300<br />
<br />
else<br />
log_info "daily power reset failed"<br />
break<br />
fi<br />
done<br />
log_info "date/time reset done"<br />
}<br />
<br />
# wait for (next) daylight<br />
wait_for_sunrise ()<br />
{<br />
sunrise=$($sunrise_cmd)<br />
sunset=$($sunset_cmd)<br />
<br />
current_time=$(date +%s)<br />
sunrise=$(date -d"$sunrise" +%s)<br />
sunset=$(date -d"$sunset" +%s)<br />
<br />
# calulate time in secs until (next) sunrise<br />
((sleep_sunrise=$sunrise - $current_time)) # this morning<br />
[ $sleep_sunrise -lt 0 ] &&<br />
((sleep_sunrise=$sunrise + 86400)) # next morning<br />
<br />
# wait for daylight only<br />
if [ $current_time -lt $sunrise -o<br />
$current_time -gt $sunset ]; then<br />
log_info sleep $sleep_sunrise<br />
sleep $sleep_sunrise<br />
fi<br />
}<br />
<br />
# wait for sunset<br />
wait_for_sunset ()<br />
{<br />
sunset=$($sunset_cmd)<br />
<br />
current_time=$(date +%s)<br />
sunset=$(date -d"$sunset" +%s)<br />
<br />
# calulate time in secs until next sunset<br />
((sleep_sunset=$sunset - $current_time))<br />
<br />
# wait until sunset only<br />
if [ $sleep_sunset -gt 0 ]; then<br />
log_info sleep $sleep_sunset<br />
sleep $sleep_sunset<br />
fi<br />
}<br />
<br />
# ------------------------<br />
# MAIN: Script starts here<br />
# ------------------------<br />
<br />
# sunset_stop: wait until (next) sunset<br />
[ "$arg" = "sunset_stop" ] &&<br />
wait_for_sunset<br />
<br />
# stop any runnning deye_inverter_mqtt processes<br />
deye_inverter_mqtt_stop<br />
<br />
# arg stop: don't start again<br />
[ "$arg" = "stop" ] && exit 0<br />
<br />
# wait for (next) daylight<br />
wait_for_sunrise<br />
<br />
# setting date/time also resets daily power register<br />
reset_deye_inverter<br />
<br />
# start deye_mqtt_loop<br />
# check every 15 min for running deye_inverter_mqtt<br />
while :; do<br />
<br />
# wait for sunrise<br />
wait_for_sunrise<br />
<br />
# start deye_inverter_mqtt<br />
get_deye_mqtt_pid<br />
if [ -z "$deye_mqtt_pid" ]; then<br />
deye_inverter_mqtt_start<br />
fi<br />
<br />
# wait 15 min<br />
sleep 900<br />
done<br />
</pre><br />
<br />
=== Contrab und /etc/rc.local ===<br />
Mein Crontab Eintrag:<br />
<pre># stop DEYE monitoring after sunset (FHEM)<br />
0 18 * * * /root/sbin/deye_mqtt.sh sunset_stop 2>/dev/null<br />
</pre><br />
und /etc/rc.local:<br />
<pre># start monitoring of PV-System: DEYE inverter<br />
$(sleep 10; /root/sbin/deye_mqtt_loop.sh 2>dev/null)&<br />
</pre><br />
<br />
== Dashboard ==<br />
So sieht das aus, wenn es den ganzen Tag im April regnet und bedeckt ist:<br />
[[Datei:Solar Bedeckt Regen.jpg|midi]]<br />
<br />
Die Eigenverbrauchsquote habe ich mittels Grafana ermittelt:<br />
[[Datei:Solar Eigenverbrauch.jpg|midi|Eigenverbrauchsquote]]</div>
Neobiker
http://neobiker.de/wiki/index.php?title=DEYE_Wechselrichter&diff=2134
DEYE Wechselrichter
2024-02-03T14:50:17Z
<p>Neobiker: /* deye_inverter.sh */</p>
<hr />
<div>= Balkonkraftwerk: DEYE Wechselrichter ohne Cloud auslesen und per MQTT monitoren =<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
Der WR bietet verschiedene Möglichkeiten zur Konfiguration und Abfrage der aktuellen Werte:<br />
* einen internen Webserver (http://10.10.10.254)<br />
* die Cloud Anbindung zu Solarman (Solarman Smart App)<br />
* (angepasstes) Modbus Protokoll über Port 8899<br />
* AT+ Befehle über Port 48899<br />
<br />
Ich verwende das Modbus Protokoll zum auslesen des WR, das <br />
# effizient/schnell ist (Performance)<br />
# ohne Internetverbindung auskommt<br />
<br />
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.<br />
<br />
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.<br />
<br />
== Wechselrichter Eigenheiten ==<br />
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. <br />
<br />
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.<br />
<br />
== Installation ==<br />
Das Projekt von Github laden und in einem Verzeichnis speichern, '''Python''' ist normalerweise ja schon installiert. <br />
<br />
<pre>git clone https://github.com/kbialek/deye-inverter-mqtt<br />
pip install paho-mqtt<br />
cd deye-inverter-mqtt</pre><br />
<br />
Unter Debian habe ich im Skript '''deye_cli.sh''' python durch python3 ersetzen müssen:<br />
<pre>#!/bin/bash<br />
set -a; source config.env; set +a<br />
python3 deye_cli.py "$@"<br />
</pre><br />
<br />
=== config.env ===<br />
Die Konfiguration meines DEYE Wechselrichters vom Typ Micro-Inverter:<br />
'''config.env'''<br />
<pre>DEYE_LOGGER_IP_ADDRESS=<IP Wechselrichter im WLAN><br />
DEYE_LOGGER_PORT=8899<br />
DEYE_LOGGER_SERIAL_NUMBER=<Seriennummer des WR><br />
<br />
MQTT_HOST=<IP von MQTT Server><br />
MQTT_PORT=1883<br />
MQTT_USERNAME=<br />
MQTT_PASSWORD=<br />
MQTT_TOPIC_PREFIX=deye<br />
<br />
LOG_LEVEL=ERROR<br />
DEYE_DATA_READ_INTERVAL=60<br />
DEYE_METRIC_GROUPS=micro<br />
</pre><br />
<br />
=== deye_inverter.sh === <br />
Ausserdem habe ich ein kleines ''Wrapper Skript'' geschrieben, um '''Daten des WR''' einfacher '''lesen und schreiben''' zu können:<br><br />
<br />
'''deye_inverter.sh''' [--check <pause>] --read <register> | --write <register> <value><br />
<br />
<pre>#!/bin/bash<br />
# Read / Write DEYE Inverters<br />
# via deye_inverter_mqtt python package<br />
# https://github.com/kbialek/deye-inverter-mqtt<br />
<br />
retries=2 # retry deye_inverter command multiple times<br />
sleep=60 # sleep time between retries<br />
<br />
# locate script in deye_inverter_mqtt directory<br />
cd $(dirname $0)<br />
if [ ! -f ./deye_cli.sh ]; then<br />
echo "Error: ./deye_cli.sh not found."<br />
echo " Please move $(basename $0) in deye-inverter-mqtt directory."<br />
exit 1<br />
fi<br />
. ./config.env<br />
<br />
log_info ()<br />
{<br />
if [ ${LOG_LEVEL} = "INFO" ]; then<br />
logger -t $(basename $0) $@<br />
fi<br />
}<br />
<br />
log_error ()<br />
{<br />
logger -t $(basename $0) $@<br />
echo 2>&1 $@<br />
}<br />
<br />
# read parameters: mode [rw], register, value and optional -c <check pause><br />
pause=0<br />
while [ $# -gt 0 ]; do<br />
case $1 in<br />
-c|--check) shift<br />
[ $# -ge 3 ] || exit 1<br />
pause=$1<br />
;;<br />
-w|--write) mode=w<br />
shift<br />
[ $# -eq 2 ] || exit 1<br />
reg=$1<br />
val=$2<br />
shift<br />
;;<br />
-r|--read) mode=r<br />
shift<br />
[ $# -eq 1 ] || exit 1<br />
reg=$1<br />
;;<br />
esac<br />
shift<br />
done<br />
<br />
# handle offline deye_inverter by<br />
# ${pause} > 0 -> endless loop<br />
# ${pause} = 0 -> error after ${retries}<br />
while true; do<br />
<br />
# try deye_cli.sh $retries times every $sleep secs<br />
check=${retries}<br />
while [ ${check} -gt 0 ]; do<br />
<br />
case ${mode} in<br />
r) result=$(./deye_cli.sh $mode $reg | grep 'int: ')<br />
if [ -n "$result" ]; then<br />
echo "$result"<br />
log_info -t $0 deye_cli.sh $mode $reg<br />
exit 0<br />
fi<br />
;;<br />
w) result=$(./deye_cli.sh $mode $reg $val)<br />
if [ "$result" = "Ok" ]; then<br />
log_info -t $0 deye_cli.sh $mode $reg $val<br />
exit 0<br />
fi<br />
;;<br />
esac<br />
<br />
# wait ${sleep} or ${pause} secs<br />
sleep $(( ${pause} ? ${sleep} : ${pause} ))<br />
<br />
# try ${retries} times<br />
(( check-- ))<br />
done<br />
<br />
# error, or retry endless until wakeup of deye_inverter<br />
if [ ${pause} -eq 0 ]; then<br />
log_error "Error: deye_cli.sh $mode $reg $val failed."<br />
exit 1<br />
fi<br />
<br />
done<br />
</pre><br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
Folgendes Skript starte ich jeden Morgen um 04:00 Uhr per Cron (und in rc.local fürs booten):<br />
<br />
=== deye_mqtt_loop.sh ===<br />
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.<br />
<br />
Ausserdem setzt es beim Start den Tageszähler des Wechselrichters bedarfsweise zurück, bevor die Werte des DEYE Wechselrichters per Endlosschleife abgefragt werden.<br />
<br />
<pre>#!/bin/bash<br />
# reset Daily_Power of DEYE Inverters<br />
# via deye_inverter_mqtt python package<br />
# https://github.com/kbialek/deye-inverter-mqtt<br />
<br />
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin<br />
<br />
# config:<br />
deye_inverter_dir=/opt/deye-inverter-mqtt<br />
fhem_dir=/opt/fhem<br />
horizon="-2" # sunset CIVIL=-6, REAL=0<br />
<br />
debug=info<br />
<br />
myPID=$$<br />
myname=$(basename $0)<br />
arg="$1"<br />
<br />
deye_inverter_sh=$deye_inverter_dir/deye_inverter.sh<br />
deye_inverter_mqtt_cfg=$deye_inverter_dir/config.env<br />
deye_inverter_mqtt_cmd="python3 deye_docker_entrypoint.py"<br />
<br />
sunrise_cmd="perl $fhem_dir/fhem.pl localhost:7072 {sunrise_abs('HORIZON=$horizon')}"<br />
sunset_cmd="perl $fhem_dir/fhem.pl localhost:7072 {sunset_abs('HORIZON=$horizon')}"<br />
<br />
# installation:<br />
# locate FHEM, used for sunrise(), sunset()<br />
if [ ! -x ${fhem_dir/fhem.pl} ]; then<br />
echo "Error: ${fhem_dir} not found."<br />
echo " Is FHEM installed in $fhem_dir ?"<br />
exit 1<br />
fi<br />
<br />
# locate script in deye_inverter_mqtt directory<br />
if [ ! -f ${deye_inverter_mqtt_cfg} ]; then<br />
echo "Error: ${deye_inverter_mqtt_cfg} not found."<br />
echo " Please update \$deye_inverter_sh path in $0"<br />
exit 1<br />
fi<br />
<br />
# read my config<br />
set -a; source ${deye_inverter_mqtt_cfg}; set +a<br />
<br />
[ "$debug" = "full" ] && set -x<br />
<br />
# ----- functions () ------------------------------------<br />
log_info ()<br />
{<br />
[ "$debug" = "yes" -o "$debug" = "true" -o "$debug" = "info" ] &&<br />
echo $myname: $@<br />
[ ${LOG_LEVEL} = "INFO" ] && logger -t $myname $@<br />
}<br />
<br />
log_error ()<br />
{<br />
logger -t $myname $@<br />
echo 1>&2 $@<br />
}<br />
<br />
# store actual date + time into vars<br />
set_date_time_vars ()<br />
{<br />
year=$(date +%y)<br />
month=$(date +%m | sed 's/^0//g')<br />
day=$(date +%d | sed 's/^0//g')<br />
hour=$(date +%H | sed 's/^0//g')<br />
minute=$(date +%m | sed 's/^0//g')<br />
second=$(date +%S | sed 's/^0//g')<br />
}<br />
<br />
# kill deye_inverter_loop ()<br />
kill_old_instance ()<br />
{<br />
# kill old instances of me<br />
my_PS=$(ps ax | grep -v grep | grep "${myname}" | awk '{print $1}')<br />
for ps in $my_PS; do<br />
[ $ps -lt $myPID ] && kill $ps<br />
done<br />
}<br />
<br />
# get deye_inverter_mqtt PIDs<br />
get_deye_mqtt_pid ()<br />
{<br />
deye_mqtt_pid=$(ps ax | grep -v grep | grep "${deye_inverter_mqtt_cmd}" | awk '{print $1}')<br />
}<br />
<br />
# kill deye_inverter_instance ()<br />
kill_deye_inverter_instance ()<br />
{<br />
get_deye_mqtt_pid<br />
if [ ! -z "${deye_mqtt_pid}" ]; then<br />
kill ${deye_mqtt_pid}<br />
log_info "Info: KILL running deye_mqtt ${deye_mqtt_pid}"<br />
fi<br />
<br />
# kill any running deye requests<br />
deye_ps=$(ps ax | grep -v grep | grep "$deye_inverter_sh " | awk '{print $1}')<br />
[ ! -z "$deye_ps" ] && kill $deye_ps<br />
}<br />
<br />
# stop all runnning instances<br />
deye_inverter_mqtt_stop ()<br />
{<br />
kill_old_instance<br />
kill_deye_inverter_instance<br />
}<br />
<br />
# start deye_inverter_mqtt<br />
deye_inverter_mqtt_start ()<br />
{<br />
log_info "Deye MQTT start"<br />
cd ${deye_inverter_dir}<br />
${deye_inverter_mqtt_cmd}<br />
}<br />
<br />
# read year_month register 0x16 = 22<br />
read_year_month ()<br />
{<br />
${deye_inverter_sh} -c 60 -r 22 | sed 's/int: //g' | cut -d, -f1<br />
}<br />
<br />
# read day register 0x17 = 23<br />
read_day ()<br />
{<br />
${deye_inverter_sh} -c 10 -r 23 | sed 's/.*h: //g' | cut -d, -f1<br />
}<br />
<br />
# read Daily power register 0x3c = 60<br />
read_daily_Power ()<br />
{<br />
${deye_inverter_sh} -c 10 -r 60 | sed 's/int: //g' | cut -d, -f1<br />
}<br />
<br />
# read actual power register 0x56 = 86<br />
read_actual_Power ()<br />
{<br />
# doubleRegisterSensor("AC Active Power", 0x56, 0.1, mqtt_topic_suffix='ac/active_power', groups=['string', 'micro'])<br />
# (int.from_bytes(high_word, 'big') * 65536 + int.from_bytes(low_word, 'big')) * self.factor<br />
# val86=${deye_inverter_sh} -c 10 -r 86 | sed 's/int: //g' | cut -d, -f1<br />
# val87=${deye_inverter_sh} -c 10 -r 87 | sed 's/int: //g' | cut -d, -f1<br />
# echo $(( (${val87} * 65536 + ${val86}) * 0.1 ))<br />
<br />
# simple value is enough for our purpose<br />
${deye_inverter_sh} -c 10 -r 86 | sed 's/int: //g' | cut -d, -f1<br />
}<br />
<br />
# write actual date/time into deye inverter<br />
reset_deye_inverter ()<br />
{<br />
# daily reset phase<br />
daily_reset=init<br />
<br />
# ensure by loop, that all register values are written<br />
while [ ${daily_reset} != "done" ]; do<br />
<br />
# wait until we have enough power<br />
while [ "$(read_actual_Power)" -le 10 ]; do<br />
sleep 600<br />
done<br />
<br />
# read Daily power register<br />
dailyPower=$(read_daily_Power)<br />
<br />
# read (old) year_month and day<br />
val22_old=$(read_year_month)<br />
val23_day=$(read_day)<br />
log_info "date/time update initialized"<br />
<br />
# set variables year, month, day, hour, minute, second<br />
set_date_time_vars<br />
<br />
# calculate register 22-24 with date and time vars<br />
val22=$(( ${year} * 256 + ${month} ))<br />
val23=$(( ${day} * 256 + ${hour} ))<br />
val24=$(( ${minute} * 256 + ${second} ))<br />
<br />
# reset power/date/time only once a day or exit loop<br />
[ "$dailyPower" -eq 0 -a<br />
"${val22_old}" = "${val22}" -a<br />
"${val23_day}" = "${day}" ] && break<br />
<br />
# reset daily_power by<br />
# setting register 22-24 with actual date + time<br />
${deye_inverter_sh} -w 22 $val22 || continue<br />
${deye_inverter_sh} -w 23 $val23 || continue<br />
${deye_inverter_sh} -w 24 $val24 || continue<br />
<br />
# test success<br />
# read Daily power register, should be resetted to 0 now<br />
dailyPower=$(read_daily_Power)<br />
<br />
# exit loop if power is updated, else log that reset failed<br />
if [ "$dailyPower" -eq 0 ]; then<br />
daily_reset=done<br />
<br />
### skip loop here, reset doesn't work always :-(<br />
# elif [ $daily_reset = "init" ]; then<br />
# daily_reset=failed<br />
# sleep 300<br />
<br />
else<br />
log_info "daily power reset failed"<br />
break<br />
fi<br />
done<br />
log_info "date/time reset done"<br />
}<br />
<br />
# wait for (next) daylight<br />
wait_for_sunrise ()<br />
{<br />
sunrise=$($sunrise_cmd)<br />
sunset=$($sunset_cmd)<br />
<br />
current_time=$(date +%s)<br />
sunrise=$(date -d"$sunrise" +%s)<br />
sunset=$(date -d"$sunset" +%s)<br />
<br />
# calulate time in secs until (next) sunrise<br />
((sleep_sunrise=$sunrise - $current_time)) # this morning<br />
[ $sleep_sunrise -lt 0 ] &&<br />
((sleep_sunrise=$sunrise + 86400)) # next morning<br />
<br />
# wait for daylight only<br />
if [ $current_time -lt $sunrise -o<br />
$current_time -gt $sunset ]; then<br />
log_info sleep $sleep_sunrise<br />
sleep $sleep_sunrise<br />
fi<br />
}<br />
<br />
# wait for sunset<br />
wait_for_sunset ()<br />
{<br />
sunset=$($sunset_cmd)<br />
<br />
current_time=$(date +%s)<br />
sunset=$(date -d"$sunset" +%s)<br />
<br />
# calulate time in secs until next sunset<br />
((sleep_sunset=$sunset - $current_time))<br />
<br />
# wait until sunset only<br />
if [ $sleep_sunset -gt 0 ]; then<br />
log_info sleep $sleep_sunset<br />
sleep $sleep_sunset<br />
fi<br />
}<br />
<br />
# ------------------------<br />
# MAIN: Script starts here<br />
# ------------------------<br />
<br />
# sunset_stop: wait until (next) sunset<br />
[ "$arg" = "sunset_stop" ] &&<br />
wait_for_sunset<br />
<br />
# stop any runnning deye_inverter_mqtt processes<br />
deye_inverter_mqtt_stop<br />
<br />
# arg stop: don't start again<br />
[ "$arg" = "stop" ] && exit 0<br />
<br />
# wait for (next) daylight<br />
wait_for_sunrise<br />
<br />
# setting date/time also resets daily power register<br />
reset_deye_inverter<br />
<br />
# start deye_mqtt_loop<br />
# check every 15 min for running deye_inverter_mqtt<br />
while :; do<br />
<br />
# wait for sunrise<br />
wait_for_sunrise<br />
<br />
# start deye_inverter_mqtt<br />
get_deye_mqtt_pid<br />
if [ -z "$deye_mqtt_pid" ]; then<br />
deye_inverter_mqtt_start<br />
fi<br />
<br />
# wait 15 min<br />
sleep 900<br />
done<br />
</pre><br />
<br />
=== Contrab und /etc/rc.local ===<br />
Mein Crontab Eintrag:<br />
<pre># stop DEYE monitoring after sunset (FHEM)<br />
0 18 * * * /usr/local/sbin/deye_mqtt.sh sunset_stop 2>/dev/null<br />
</pre><br />
und /etc/rc.local:<br />
<pre># start monitoring of PV-System: DEYE inverter<br />
$(sleep 10; /root/sbin/deye_mqtt_loop.sh 2>dev/null)&<br />
</pre><br />
<br />
== Dashboard ==<br />
So sieht das aus, wenn es den ganzen Tag im April regnet und bedeckt ist:<br />
[[Datei:Solar Bedeckt Regen.jpg|midi]]<br />
<br />
Die Eigenverbrauchsquote habe ich mittels Grafana ermittelt:<br />
[[Datei:Solar Eigenverbrauch.jpg|midi|Eigenverbrauchsquote]]</div>
Neobiker
http://neobiker.de/wiki/index.php?title=DEYE_Wechselrichter&diff=2133
DEYE Wechselrichter
2024-02-03T14:49:41Z
<p>Neobiker: /* Contrab und /etc/rc.local */</p>
<hr />
<div>= Balkonkraftwerk: DEYE Wechselrichter ohne Cloud auslesen und per MQTT monitoren =<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
Der WR bietet verschiedene Möglichkeiten zur Konfiguration und Abfrage der aktuellen Werte:<br />
* einen internen Webserver (http://10.10.10.254)<br />
* die Cloud Anbindung zu Solarman (Solarman Smart App)<br />
* (angepasstes) Modbus Protokoll über Port 8899<br />
* AT+ Befehle über Port 48899<br />
<br />
Ich verwende das Modbus Protokoll zum auslesen des WR, das <br />
# effizient/schnell ist (Performance)<br />
# ohne Internetverbindung auskommt<br />
<br />
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.<br />
<br />
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.<br />
<br />
== Wechselrichter Eigenheiten ==<br />
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. <br />
<br />
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.<br />
<br />
== Installation ==<br />
Das Projekt von Github laden und in einem Verzeichnis speichern, '''Python''' ist normalerweise ja schon installiert. <br />
<br />
<pre>git clone https://github.com/kbialek/deye-inverter-mqtt<br />
pip install paho-mqtt<br />
cd deye-inverter-mqtt</pre><br />
<br />
Unter Debian habe ich im Skript '''deye_cli.sh''' python durch python3 ersetzen müssen:<br />
<pre>#!/bin/bash<br />
set -a; source config.env; set +a<br />
python3 deye_cli.py "$@"<br />
</pre><br />
<br />
=== config.env ===<br />
Die Konfiguration meines DEYE Wechselrichters vom Typ Micro-Inverter:<br />
'''config.env'''<br />
<pre>DEYE_LOGGER_IP_ADDRESS=<IP Wechselrichter im WLAN><br />
DEYE_LOGGER_PORT=8899<br />
DEYE_LOGGER_SERIAL_NUMBER=<Seriennummer des WR><br />
<br />
MQTT_HOST=<IP von MQTT Server><br />
MQTT_PORT=1883<br />
MQTT_USERNAME=<br />
MQTT_PASSWORD=<br />
MQTT_TOPIC_PREFIX=deye<br />
<br />
LOG_LEVEL=ERROR<br />
DEYE_DATA_READ_INTERVAL=60<br />
DEYE_METRIC_GROUPS=micro<br />
</pre><br />
<br />
=== deye_inverter.sh === <br />
Ausserdem habe ich ein kleines ''Wrapper Skript'' geschrieben, um '''Daten des WR''' einfacher '''lesen und schreiben''' zu können:<br><br />
<br />
'''deye_inverter.sh''' [--check <pause>] --read <register> | --write <register> <value><br />
<br />
<pre>#!/bin/bash<br />
# Read / Write DEYE Inverters<br />
# via deye_inverter_mqtt python package<br />
# https://github.com/kbialek/deye-inverter-mqtt<br />
<br />
retries=2 # retry deye_inverter command multiple times<br />
sleep=5 # sleep time between retries<br />
<br />
# locate script in deye_inverter_mqtt directory<br />
cd $(dirname $0)<br />
if [ ! -f ./deye_cli.sh ]; then<br />
echo "Error: ./deye_cli.sh not found."<br />
echo " Please move $(basename $0) in deye-inverter-mqtt directory."<br />
exit 1<br />
fi<br />
. ./config.env<br />
<br />
log_info ()<br />
{<br />
if [ ${LOG_LEVEL} = "INFO" ]; then<br />
logger -t $(basename $0) $@<br />
fi<br />
}<br />
<br />
log_error ()<br />
{<br />
logger -t $(basename $0) $@<br />
echo 2>&1 $@<br />
}<br />
<br />
# read parameters: mode [rw], register, value and optional -c <check pause><br />
pause=0<br />
while [ $# -gt 0 ]; do<br />
case $1 in<br />
-c|--check) shift<br />
[ $# -ge 3 ] || exit 1<br />
pause=$1<br />
;;<br />
-w|--write) mode=w<br />
shift<br />
[ $# -eq 2 ] || exit 1<br />
reg=$1<br />
val=$2<br />
shift<br />
;;<br />
-r|--read) mode=r<br />
shift<br />
[ $# -eq 1 ] || exit 1<br />
reg=$1<br />
;;<br />
esac<br />
shift<br />
done<br />
<br />
# handle offline deye_inverter by<br />
# ${pause} > 0 -> endless loop<br />
# ${pause} = 0 -> error after ${retries}<br />
while true; do<br />
<br />
# try deye_cli.sh $retries times every $sleep secs<br />
check=${retries}<br />
while [ ${check} -gt 0 ]; do<br />
<br />
case ${mode} in<br />
r) result=$(./deye_cli.sh $mode $reg | grep 'int: ')<br />
if [ -n "$result" ]; then<br />
echo "$result"<br />
log_info -t $0 deye_cli.sh $mode $reg<br />
exit 0<br />
fi<br />
;;<br />
w) result=$(./deye_cli.sh $mode $reg $val)<br />
if [ "$result" = "Ok" ]; then<br />
log_info -t $0 deye_cli.sh $mode $reg $val<br />
exit 0<br />
fi<br />
;;<br />
esac<br />
<br />
# wait ${sleep} or ${pause} secs<br />
sleep $(( ${pause} ? ${sleep} : ${pause} ))<br />
<br />
# try ${retries} times<br />
(( check-- ))<br />
done<br />
<br />
# error, or retry endless until wakeup of deye_inverter<br />
if [ ${pause} -eq 0 ]; then<br />
log_error "Error: deye_cli.sh $mode $reg $val failed."<br />
exit 1<br />
fi<br />
<br />
done<br />
</pre><br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
Folgendes Skript starte ich jeden Morgen um 04:00 Uhr per Cron (und in rc.local fürs booten):<br />
<br />
=== deye_mqtt_loop.sh ===<br />
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.<br />
<br />
Ausserdem setzt es beim Start den Tageszähler des Wechselrichters bedarfsweise zurück, bevor die Werte des DEYE Wechselrichters per Endlosschleife abgefragt werden.<br />
<br />
<pre>#!/bin/bash<br />
# reset Daily_Power of DEYE Inverters<br />
# via deye_inverter_mqtt python package<br />
# https://github.com/kbialek/deye-inverter-mqtt<br />
<br />
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin<br />
<br />
# config:<br />
deye_inverter_dir=/opt/deye-inverter-mqtt<br />
fhem_dir=/opt/fhem<br />
horizon="-2" # sunset CIVIL=-6, REAL=0<br />
<br />
debug=info<br />
<br />
myPID=$$<br />
myname=$(basename $0)<br />
arg="$1"<br />
<br />
deye_inverter_sh=$deye_inverter_dir/deye_inverter.sh<br />
deye_inverter_mqtt_cfg=$deye_inverter_dir/config.env<br />
deye_inverter_mqtt_cmd="python3 deye_docker_entrypoint.py"<br />
<br />
sunrise_cmd="perl $fhem_dir/fhem.pl localhost:7072 {sunrise_abs('HORIZON=$horizon')}"<br />
sunset_cmd="perl $fhem_dir/fhem.pl localhost:7072 {sunset_abs('HORIZON=$horizon')}"<br />
<br />
# installation:<br />
# locate FHEM, used for sunrise(), sunset()<br />
if [ ! -x ${fhem_dir/fhem.pl} ]; then<br />
echo "Error: ${fhem_dir} not found."<br />
echo " Is FHEM installed in $fhem_dir ?"<br />
exit 1<br />
fi<br />
<br />
# locate script in deye_inverter_mqtt directory<br />
if [ ! -f ${deye_inverter_mqtt_cfg} ]; then<br />
echo "Error: ${deye_inverter_mqtt_cfg} not found."<br />
echo " Please update \$deye_inverter_sh path in $0"<br />
exit 1<br />
fi<br />
<br />
# read my config<br />
set -a; source ${deye_inverter_mqtt_cfg}; set +a<br />
<br />
[ "$debug" = "full" ] && set -x<br />
<br />
# ----- functions () ------------------------------------<br />
log_info ()<br />
{<br />
[ "$debug" = "yes" -o "$debug" = "true" -o "$debug" = "info" ] &&<br />
echo $myname: $@<br />
[ ${LOG_LEVEL} = "INFO" ] && logger -t $myname $@<br />
}<br />
<br />
log_error ()<br />
{<br />
logger -t $myname $@<br />
echo 1>&2 $@<br />
}<br />
<br />
# store actual date + time into vars<br />
set_date_time_vars ()<br />
{<br />
year=$(date +%y)<br />
month=$(date +%m | sed 's/^0//g')<br />
day=$(date +%d | sed 's/^0//g')<br />
hour=$(date +%H | sed 's/^0//g')<br />
minute=$(date +%m | sed 's/^0//g')<br />
second=$(date +%S | sed 's/^0//g')<br />
}<br />
<br />
# kill deye_inverter_loop ()<br />
kill_old_instance ()<br />
{<br />
# kill old instances of me<br />
my_PS=$(ps ax | grep -v grep | grep "${myname}" | awk '{print $1}')<br />
for ps in $my_PS; do<br />
[ $ps -lt $myPID ] && kill $ps<br />
done<br />
}<br />
<br />
# get deye_inverter_mqtt PIDs<br />
get_deye_mqtt_pid ()<br />
{<br />
deye_mqtt_pid=$(ps ax | grep -v grep | grep "${deye_inverter_mqtt_cmd}" | awk '{print $1}')<br />
}<br />
<br />
# kill deye_inverter_instance ()<br />
kill_deye_inverter_instance ()<br />
{<br />
get_deye_mqtt_pid<br />
if [ ! -z "${deye_mqtt_pid}" ]; then<br />
kill ${deye_mqtt_pid}<br />
log_info "Info: KILL running deye_mqtt ${deye_mqtt_pid}"<br />
fi<br />
<br />
# kill any running deye requests<br />
deye_ps=$(ps ax | grep -v grep | grep "$deye_inverter_sh " | awk '{print $1}')<br />
[ ! -z "$deye_ps" ] && kill $deye_ps<br />
}<br />
<br />
# stop all runnning instances<br />
deye_inverter_mqtt_stop ()<br />
{<br />
kill_old_instance<br />
kill_deye_inverter_instance<br />
}<br />
<br />
# start deye_inverter_mqtt<br />
deye_inverter_mqtt_start ()<br />
{<br />
log_info "Deye MQTT start"<br />
cd ${deye_inverter_dir}<br />
${deye_inverter_mqtt_cmd}<br />
}<br />
<br />
# read year_month register 0x16 = 22<br />
read_year_month ()<br />
{<br />
${deye_inverter_sh} -c 60 -r 22 | sed 's/int: //g' | cut -d, -f1<br />
}<br />
<br />
# read day register 0x17 = 23<br />
read_day ()<br />
{<br />
${deye_inverter_sh} -c 10 -r 23 | sed 's/.*h: //g' | cut -d, -f1<br />
}<br />
<br />
# read Daily power register 0x3c = 60<br />
read_daily_Power ()<br />
{<br />
${deye_inverter_sh} -c 10 -r 60 | sed 's/int: //g' | cut -d, -f1<br />
}<br />
<br />
# read actual power register 0x56 = 86<br />
read_actual_Power ()<br />
{<br />
# doubleRegisterSensor("AC Active Power", 0x56, 0.1, mqtt_topic_suffix='ac/active_power', groups=['string', 'micro'])<br />
# (int.from_bytes(high_word, 'big') * 65536 + int.from_bytes(low_word, 'big')) * self.factor<br />
# val86=${deye_inverter_sh} -c 10 -r 86 | sed 's/int: //g' | cut -d, -f1<br />
# val87=${deye_inverter_sh} -c 10 -r 87 | sed 's/int: //g' | cut -d, -f1<br />
# echo $(( (${val87} * 65536 + ${val86}) * 0.1 ))<br />
<br />
# simple value is enough for our purpose<br />
${deye_inverter_sh} -c 10 -r 86 | sed 's/int: //g' | cut -d, -f1<br />
}<br />
<br />
# write actual date/time into deye inverter<br />
reset_deye_inverter ()<br />
{<br />
# daily reset phase<br />
daily_reset=init<br />
<br />
# ensure by loop, that all register values are written<br />
while [ ${daily_reset} != "done" ]; do<br />
<br />
# wait until we have enough power<br />
while [ "$(read_actual_Power)" -le 10 ]; do<br />
sleep 600<br />
done<br />
<br />
# read Daily power register<br />
dailyPower=$(read_daily_Power)<br />
<br />
# read (old) year_month and day<br />
val22_old=$(read_year_month)<br />
val23_day=$(read_day)<br />
log_info "date/time update initialized"<br />
<br />
# set variables year, month, day, hour, minute, second<br />
set_date_time_vars<br />
<br />
# calculate register 22-24 with date and time vars<br />
val22=$(( ${year} * 256 + ${month} ))<br />
val23=$(( ${day} * 256 + ${hour} ))<br />
val24=$(( ${minute} * 256 + ${second} ))<br />
<br />
# reset power/date/time only once a day or exit loop<br />
[ "$dailyPower" -eq 0 -a<br />
"${val22_old}" = "${val22}" -a<br />
"${val23_day}" = "${day}" ] && break<br />
<br />
# reset daily_power by<br />
# setting register 22-24 with actual date + time<br />
${deye_inverter_sh} -w 22 $val22 || continue<br />
${deye_inverter_sh} -w 23 $val23 || continue<br />
${deye_inverter_sh} -w 24 $val24 || continue<br />
<br />
# test success<br />
# read Daily power register, should be resetted to 0 now<br />
dailyPower=$(read_daily_Power)<br />
<br />
# exit loop if power is updated, else log that reset failed<br />
if [ "$dailyPower" -eq 0 ]; then<br />
daily_reset=done<br />
<br />
### skip loop here, reset doesn't work always :-(<br />
# elif [ $daily_reset = "init" ]; then<br />
# daily_reset=failed<br />
# sleep 300<br />
<br />
else<br />
log_info "daily power reset failed"<br />
break<br />
fi<br />
done<br />
log_info "date/time reset done"<br />
}<br />
<br />
# wait for (next) daylight<br />
wait_for_sunrise ()<br />
{<br />
sunrise=$($sunrise_cmd)<br />
sunset=$($sunset_cmd)<br />
<br />
current_time=$(date +%s)<br />
sunrise=$(date -d"$sunrise" +%s)<br />
sunset=$(date -d"$sunset" +%s)<br />
<br />
# calulate time in secs until (next) sunrise<br />
((sleep_sunrise=$sunrise - $current_time)) # this morning<br />
[ $sleep_sunrise -lt 0 ] &&<br />
((sleep_sunrise=$sunrise + 86400)) # next morning<br />
<br />
# wait for daylight only<br />
if [ $current_time -lt $sunrise -o<br />
$current_time -gt $sunset ]; then<br />
log_info sleep $sleep_sunrise<br />
sleep $sleep_sunrise<br />
fi<br />
}<br />
<br />
# wait for sunset<br />
wait_for_sunset ()<br />
{<br />
sunset=$($sunset_cmd)<br />
<br />
current_time=$(date +%s)<br />
sunset=$(date -d"$sunset" +%s)<br />
<br />
# calulate time in secs until next sunset<br />
((sleep_sunset=$sunset - $current_time))<br />
<br />
# wait until sunset only<br />
if [ $sleep_sunset -gt 0 ]; then<br />
log_info sleep $sleep_sunset<br />
sleep $sleep_sunset<br />
fi<br />
}<br />
<br />
# ------------------------<br />
# MAIN: Script starts here<br />
# ------------------------<br />
<br />
# sunset_stop: wait until (next) sunset<br />
[ "$arg" = "sunset_stop" ] &&<br />
wait_for_sunset<br />
<br />
# stop any runnning deye_inverter_mqtt processes<br />
deye_inverter_mqtt_stop<br />
<br />
# arg stop: don't start again<br />
[ "$arg" = "stop" ] && exit 0<br />
<br />
# wait for (next) daylight<br />
wait_for_sunrise<br />
<br />
# setting date/time also resets daily power register<br />
reset_deye_inverter<br />
<br />
# start deye_mqtt_loop<br />
# check every 15 min for running deye_inverter_mqtt<br />
while :; do<br />
<br />
# wait for sunrise<br />
wait_for_sunrise<br />
<br />
# start deye_inverter_mqtt<br />
get_deye_mqtt_pid<br />
if [ -z "$deye_mqtt_pid" ]; then<br />
deye_inverter_mqtt_start<br />
fi<br />
<br />
# wait 15 min<br />
sleep 900<br />
done<br />
</pre><br />
<br />
=== Contrab und /etc/rc.local ===<br />
Mein Crontab Eintrag:<br />
<pre># stop DEYE monitoring after sunset (FHEM)<br />
0 18 * * * /usr/local/sbin/deye_mqtt.sh sunset_stop 2>/dev/null<br />
</pre><br />
und /etc/rc.local:<br />
<pre># start monitoring of PV-System: DEYE inverter<br />
$(sleep 10; /root/sbin/deye_mqtt_loop.sh 2>dev/null)&<br />
</pre><br />
<br />
== Dashboard ==<br />
So sieht das aus, wenn es den ganzen Tag im April regnet und bedeckt ist:<br />
[[Datei:Solar Bedeckt Regen.jpg|midi]]<br />
<br />
Die Eigenverbrauchsquote habe ich mittels Grafana ermittelt:<br />
[[Datei:Solar Eigenverbrauch.jpg|midi|Eigenverbrauchsquote]]</div>
Neobiker
http://neobiker.de/wiki/index.php?title=DEYE_Wechselrichter&diff=2132
DEYE Wechselrichter
2024-02-03T14:48:20Z
<p>Neobiker: /* deye_mqtt_loop.sh */</p>
<hr />
<div>= Balkonkraftwerk: DEYE Wechselrichter ohne Cloud auslesen und per MQTT monitoren =<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
Der WR bietet verschiedene Möglichkeiten zur Konfiguration und Abfrage der aktuellen Werte:<br />
* einen internen Webserver (http://10.10.10.254)<br />
* die Cloud Anbindung zu Solarman (Solarman Smart App)<br />
* (angepasstes) Modbus Protokoll über Port 8899<br />
* AT+ Befehle über Port 48899<br />
<br />
Ich verwende das Modbus Protokoll zum auslesen des WR, das <br />
# effizient/schnell ist (Performance)<br />
# ohne Internetverbindung auskommt<br />
<br />
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.<br />
<br />
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.<br />
<br />
== Wechselrichter Eigenheiten ==<br />
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. <br />
<br />
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.<br />
<br />
== Installation ==<br />
Das Projekt von Github laden und in einem Verzeichnis speichern, '''Python''' ist normalerweise ja schon installiert. <br />
<br />
<pre>git clone https://github.com/kbialek/deye-inverter-mqtt<br />
pip install paho-mqtt<br />
cd deye-inverter-mqtt</pre><br />
<br />
Unter Debian habe ich im Skript '''deye_cli.sh''' python durch python3 ersetzen müssen:<br />
<pre>#!/bin/bash<br />
set -a; source config.env; set +a<br />
python3 deye_cli.py "$@"<br />
</pre><br />
<br />
=== config.env ===<br />
Die Konfiguration meines DEYE Wechselrichters vom Typ Micro-Inverter:<br />
'''config.env'''<br />
<pre>DEYE_LOGGER_IP_ADDRESS=<IP Wechselrichter im WLAN><br />
DEYE_LOGGER_PORT=8899<br />
DEYE_LOGGER_SERIAL_NUMBER=<Seriennummer des WR><br />
<br />
MQTT_HOST=<IP von MQTT Server><br />
MQTT_PORT=1883<br />
MQTT_USERNAME=<br />
MQTT_PASSWORD=<br />
MQTT_TOPIC_PREFIX=deye<br />
<br />
LOG_LEVEL=ERROR<br />
DEYE_DATA_READ_INTERVAL=60<br />
DEYE_METRIC_GROUPS=micro<br />
</pre><br />
<br />
=== deye_inverter.sh === <br />
Ausserdem habe ich ein kleines ''Wrapper Skript'' geschrieben, um '''Daten des WR''' einfacher '''lesen und schreiben''' zu können:<br><br />
<br />
'''deye_inverter.sh''' [--check <pause>] --read <register> | --write <register> <value><br />
<br />
<pre>#!/bin/bash<br />
# Read / Write DEYE Inverters<br />
# via deye_inverter_mqtt python package<br />
# https://github.com/kbialek/deye-inverter-mqtt<br />
<br />
retries=2 # retry deye_inverter command multiple times<br />
sleep=5 # sleep time between retries<br />
<br />
# locate script in deye_inverter_mqtt directory<br />
cd $(dirname $0)<br />
if [ ! -f ./deye_cli.sh ]; then<br />
echo "Error: ./deye_cli.sh not found."<br />
echo " Please move $(basename $0) in deye-inverter-mqtt directory."<br />
exit 1<br />
fi<br />
. ./config.env<br />
<br />
log_info ()<br />
{<br />
if [ ${LOG_LEVEL} = "INFO" ]; then<br />
logger -t $(basename $0) $@<br />
fi<br />
}<br />
<br />
log_error ()<br />
{<br />
logger -t $(basename $0) $@<br />
echo 2>&1 $@<br />
}<br />
<br />
# read parameters: mode [rw], register, value and optional -c <check pause><br />
pause=0<br />
while [ $# -gt 0 ]; do<br />
case $1 in<br />
-c|--check) shift<br />
[ $# -ge 3 ] || exit 1<br />
pause=$1<br />
;;<br />
-w|--write) mode=w<br />
shift<br />
[ $# -eq 2 ] || exit 1<br />
reg=$1<br />
val=$2<br />
shift<br />
;;<br />
-r|--read) mode=r<br />
shift<br />
[ $# -eq 1 ] || exit 1<br />
reg=$1<br />
;;<br />
esac<br />
shift<br />
done<br />
<br />
# handle offline deye_inverter by<br />
# ${pause} > 0 -> endless loop<br />
# ${pause} = 0 -> error after ${retries}<br />
while true; do<br />
<br />
# try deye_cli.sh $retries times every $sleep secs<br />
check=${retries}<br />
while [ ${check} -gt 0 ]; do<br />
<br />
case ${mode} in<br />
r) result=$(./deye_cli.sh $mode $reg | grep 'int: ')<br />
if [ -n "$result" ]; then<br />
echo "$result"<br />
log_info -t $0 deye_cli.sh $mode $reg<br />
exit 0<br />
fi<br />
;;<br />
w) result=$(./deye_cli.sh $mode $reg $val)<br />
if [ "$result" = "Ok" ]; then<br />
log_info -t $0 deye_cli.sh $mode $reg $val<br />
exit 0<br />
fi<br />
;;<br />
esac<br />
<br />
# wait ${sleep} or ${pause} secs<br />
sleep $(( ${pause} ? ${sleep} : ${pause} ))<br />
<br />
# try ${retries} times<br />
(( check-- ))<br />
done<br />
<br />
# error, or retry endless until wakeup of deye_inverter<br />
if [ ${pause} -eq 0 ]; then<br />
log_error "Error: deye_cli.sh $mode $reg $val failed."<br />
exit 1<br />
fi<br />
<br />
done<br />
</pre><br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
Folgendes Skript starte ich jeden Morgen um 04:00 Uhr per Cron (und in rc.local fürs booten):<br />
<br />
=== deye_mqtt_loop.sh ===<br />
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.<br />
<br />
Ausserdem setzt es beim Start den Tageszähler des Wechselrichters bedarfsweise zurück, bevor die Werte des DEYE Wechselrichters per Endlosschleife abgefragt werden.<br />
<br />
<pre>#!/bin/bash<br />
# reset Daily_Power of DEYE Inverters<br />
# via deye_inverter_mqtt python package<br />
# https://github.com/kbialek/deye-inverter-mqtt<br />
<br />
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin<br />
<br />
# config:<br />
deye_inverter_dir=/opt/deye-inverter-mqtt<br />
fhem_dir=/opt/fhem<br />
horizon="-2" # sunset CIVIL=-6, REAL=0<br />
<br />
debug=info<br />
<br />
myPID=$$<br />
myname=$(basename $0)<br />
arg="$1"<br />
<br />
deye_inverter_sh=$deye_inverter_dir/deye_inverter.sh<br />
deye_inverter_mqtt_cfg=$deye_inverter_dir/config.env<br />
deye_inverter_mqtt_cmd="python3 deye_docker_entrypoint.py"<br />
<br />
sunrise_cmd="perl $fhem_dir/fhem.pl localhost:7072 {sunrise_abs('HORIZON=$horizon')}"<br />
sunset_cmd="perl $fhem_dir/fhem.pl localhost:7072 {sunset_abs('HORIZON=$horizon')}"<br />
<br />
# installation:<br />
# locate FHEM, used for sunrise(), sunset()<br />
if [ ! -x ${fhem_dir/fhem.pl} ]; then<br />
echo "Error: ${fhem_dir} not found."<br />
echo " Is FHEM installed in $fhem_dir ?"<br />
exit 1<br />
fi<br />
<br />
# locate script in deye_inverter_mqtt directory<br />
if [ ! -f ${deye_inverter_mqtt_cfg} ]; then<br />
echo "Error: ${deye_inverter_mqtt_cfg} not found."<br />
echo " Please update \$deye_inverter_sh path in $0"<br />
exit 1<br />
fi<br />
<br />
# read my config<br />
set -a; source ${deye_inverter_mqtt_cfg}; set +a<br />
<br />
[ "$debug" = "full" ] && set -x<br />
<br />
# ----- functions () ------------------------------------<br />
log_info ()<br />
{<br />
[ "$debug" = "yes" -o "$debug" = "true" -o "$debug" = "info" ] &&<br />
echo $myname: $@<br />
[ ${LOG_LEVEL} = "INFO" ] && logger -t $myname $@<br />
}<br />
<br />
log_error ()<br />
{<br />
logger -t $myname $@<br />
echo 1>&2 $@<br />
}<br />
<br />
# store actual date + time into vars<br />
set_date_time_vars ()<br />
{<br />
year=$(date +%y)<br />
month=$(date +%m | sed 's/^0//g')<br />
day=$(date +%d | sed 's/^0//g')<br />
hour=$(date +%H | sed 's/^0//g')<br />
minute=$(date +%m | sed 's/^0//g')<br />
second=$(date +%S | sed 's/^0//g')<br />
}<br />
<br />
# kill deye_inverter_loop ()<br />
kill_old_instance ()<br />
{<br />
# kill old instances of me<br />
my_PS=$(ps ax | grep -v grep | grep "${myname}" | awk '{print $1}')<br />
for ps in $my_PS; do<br />
[ $ps -lt $myPID ] && kill $ps<br />
done<br />
}<br />
<br />
# get deye_inverter_mqtt PIDs<br />
get_deye_mqtt_pid ()<br />
{<br />
deye_mqtt_pid=$(ps ax | grep -v grep | grep "${deye_inverter_mqtt_cmd}" | awk '{print $1}')<br />
}<br />
<br />
# kill deye_inverter_instance ()<br />
kill_deye_inverter_instance ()<br />
{<br />
get_deye_mqtt_pid<br />
if [ ! -z "${deye_mqtt_pid}" ]; then<br />
kill ${deye_mqtt_pid}<br />
log_info "Info: KILL running deye_mqtt ${deye_mqtt_pid}"<br />
fi<br />
<br />
# kill any running deye requests<br />
deye_ps=$(ps ax | grep -v grep | grep "$deye_inverter_sh " | awk '{print $1}')<br />
[ ! -z "$deye_ps" ] && kill $deye_ps<br />
}<br />
<br />
# stop all runnning instances<br />
deye_inverter_mqtt_stop ()<br />
{<br />
kill_old_instance<br />
kill_deye_inverter_instance<br />
}<br />
<br />
# start deye_inverter_mqtt<br />
deye_inverter_mqtt_start ()<br />
{<br />
log_info "Deye MQTT start"<br />
cd ${deye_inverter_dir}<br />
${deye_inverter_mqtt_cmd}<br />
}<br />
<br />
# read year_month register 0x16 = 22<br />
read_year_month ()<br />
{<br />
${deye_inverter_sh} -c 60 -r 22 | sed 's/int: //g' | cut -d, -f1<br />
}<br />
<br />
# read day register 0x17 = 23<br />
read_day ()<br />
{<br />
${deye_inverter_sh} -c 10 -r 23 | sed 's/.*h: //g' | cut -d, -f1<br />
}<br />
<br />
# read Daily power register 0x3c = 60<br />
read_daily_Power ()<br />
{<br />
${deye_inverter_sh} -c 10 -r 60 | sed 's/int: //g' | cut -d, -f1<br />
}<br />
<br />
# read actual power register 0x56 = 86<br />
read_actual_Power ()<br />
{<br />
# doubleRegisterSensor("AC Active Power", 0x56, 0.1, mqtt_topic_suffix='ac/active_power', groups=['string', 'micro'])<br />
# (int.from_bytes(high_word, 'big') * 65536 + int.from_bytes(low_word, 'big')) * self.factor<br />
# val86=${deye_inverter_sh} -c 10 -r 86 | sed 's/int: //g' | cut -d, -f1<br />
# val87=${deye_inverter_sh} -c 10 -r 87 | sed 's/int: //g' | cut -d, -f1<br />
# echo $(( (${val87} * 65536 + ${val86}) * 0.1 ))<br />
<br />
# simple value is enough for our purpose<br />
${deye_inverter_sh} -c 10 -r 86 | sed 's/int: //g' | cut -d, -f1<br />
}<br />
<br />
# write actual date/time into deye inverter<br />
reset_deye_inverter ()<br />
{<br />
# daily reset phase<br />
daily_reset=init<br />
<br />
# ensure by loop, that all register values are written<br />
while [ ${daily_reset} != "done" ]; do<br />
<br />
# wait until we have enough power<br />
while [ "$(read_actual_Power)" -le 10 ]; do<br />
sleep 600<br />
done<br />
<br />
# read Daily power register<br />
dailyPower=$(read_daily_Power)<br />
<br />
# read (old) year_month and day<br />
val22_old=$(read_year_month)<br />
val23_day=$(read_day)<br />
log_info "date/time update initialized"<br />
<br />
# set variables year, month, day, hour, minute, second<br />
set_date_time_vars<br />
<br />
# calculate register 22-24 with date and time vars<br />
val22=$(( ${year} * 256 + ${month} ))<br />
val23=$(( ${day} * 256 + ${hour} ))<br />
val24=$(( ${minute} * 256 + ${second} ))<br />
<br />
# reset power/date/time only once a day or exit loop<br />
[ "$dailyPower" -eq 0 -a<br />
"${val22_old}" = "${val22}" -a<br />
"${val23_day}" = "${day}" ] && break<br />
<br />
# reset daily_power by<br />
# setting register 22-24 with actual date + time<br />
${deye_inverter_sh} -w 22 $val22 || continue<br />
${deye_inverter_sh} -w 23 $val23 || continue<br />
${deye_inverter_sh} -w 24 $val24 || continue<br />
<br />
# test success<br />
# read Daily power register, should be resetted to 0 now<br />
dailyPower=$(read_daily_Power)<br />
<br />
# exit loop if power is updated, else log that reset failed<br />
if [ "$dailyPower" -eq 0 ]; then<br />
daily_reset=done<br />
<br />
### skip loop here, reset doesn't work always :-(<br />
# elif [ $daily_reset = "init" ]; then<br />
# daily_reset=failed<br />
# sleep 300<br />
<br />
else<br />
log_info "daily power reset failed"<br />
break<br />
fi<br />
done<br />
log_info "date/time reset done"<br />
}<br />
<br />
# wait for (next) daylight<br />
wait_for_sunrise ()<br />
{<br />
sunrise=$($sunrise_cmd)<br />
sunset=$($sunset_cmd)<br />
<br />
current_time=$(date +%s)<br />
sunrise=$(date -d"$sunrise" +%s)<br />
sunset=$(date -d"$sunset" +%s)<br />
<br />
# calulate time in secs until (next) sunrise<br />
((sleep_sunrise=$sunrise - $current_time)) # this morning<br />
[ $sleep_sunrise -lt 0 ] &&<br />
((sleep_sunrise=$sunrise + 86400)) # next morning<br />
<br />
# wait for daylight only<br />
if [ $current_time -lt $sunrise -o<br />
$current_time -gt $sunset ]; then<br />
log_info sleep $sleep_sunrise<br />
sleep $sleep_sunrise<br />
fi<br />
}<br />
<br />
# wait for sunset<br />
wait_for_sunset ()<br />
{<br />
sunset=$($sunset_cmd)<br />
<br />
current_time=$(date +%s)<br />
sunset=$(date -d"$sunset" +%s)<br />
<br />
# calulate time in secs until next sunset<br />
((sleep_sunset=$sunset - $current_time))<br />
<br />
# wait until sunset only<br />
if [ $sleep_sunset -gt 0 ]; then<br />
log_info sleep $sleep_sunset<br />
sleep $sleep_sunset<br />
fi<br />
}<br />
<br />
# ------------------------<br />
# MAIN: Script starts here<br />
# ------------------------<br />
<br />
# sunset_stop: wait until (next) sunset<br />
[ "$arg" = "sunset_stop" ] &&<br />
wait_for_sunset<br />
<br />
# stop any runnning deye_inverter_mqtt processes<br />
deye_inverter_mqtt_stop<br />
<br />
# arg stop: don't start again<br />
[ "$arg" = "stop" ] && exit 0<br />
<br />
# wait for (next) daylight<br />
wait_for_sunrise<br />
<br />
# setting date/time also resets daily power register<br />
reset_deye_inverter<br />
<br />
# start deye_mqtt_loop<br />
# check every 15 min for running deye_inverter_mqtt<br />
while :; do<br />
<br />
# wait for sunrise<br />
wait_for_sunrise<br />
<br />
# start deye_inverter_mqtt<br />
get_deye_mqtt_pid<br />
if [ -z "$deye_mqtt_pid" ]; then<br />
deye_inverter_mqtt_start<br />
fi<br />
<br />
# wait 15 min<br />
sleep 900<br />
done<br />
</pre><br />
<br />
=== Contrab und /etc/rc.local ===<br />
Mein Crontab Eintrag:<br />
<pre>0 4,22 * * * /root/sbin/deye_mqtt_loop.sh 2>/dev/null &</pre><br />
und /etc/rc.local:<br />
<pre># start monitoring of PV-System: DEYE inverter<br />
$(sleep 20; /root/sbin/deye_mqtt_loop.sh 2>dev/null)&<br />
</pre><br />
<br />
== Dashboard ==<br />
So sieht das aus, wenn es den ganzen Tag im April regnet und bedeckt ist:<br />
[[Datei:Solar Bedeckt Regen.jpg|midi]]<br />
<br />
Die Eigenverbrauchsquote habe ich mittels Grafana ermittelt:<br />
[[Datei:Solar Eigenverbrauch.jpg|midi|Eigenverbrauchsquote]]</div>
Neobiker
http://neobiker.de/wiki/index.php?title=DEYE_Wechselrichter&diff=2131
DEYE Wechselrichter
2024-02-03T14:45:27Z
<p>Neobiker: /* deye_mqtt_loop.sh */</p>
<hr />
<div>= Balkonkraftwerk: DEYE Wechselrichter ohne Cloud auslesen und per MQTT monitoren =<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
Der WR bietet verschiedene Möglichkeiten zur Konfiguration und Abfrage der aktuellen Werte:<br />
* einen internen Webserver (http://10.10.10.254)<br />
* die Cloud Anbindung zu Solarman (Solarman Smart App)<br />
* (angepasstes) Modbus Protokoll über Port 8899<br />
* AT+ Befehle über Port 48899<br />
<br />
Ich verwende das Modbus Protokoll zum auslesen des WR, das <br />
# effizient/schnell ist (Performance)<br />
# ohne Internetverbindung auskommt<br />
<br />
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.<br />
<br />
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.<br />
<br />
== Wechselrichter Eigenheiten ==<br />
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. <br />
<br />
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.<br />
<br />
== Installation ==<br />
Das Projekt von Github laden und in einem Verzeichnis speichern, '''Python''' ist normalerweise ja schon installiert. <br />
<br />
<pre>git clone https://github.com/kbialek/deye-inverter-mqtt<br />
pip install paho-mqtt<br />
cd deye-inverter-mqtt</pre><br />
<br />
Unter Debian habe ich im Skript '''deye_cli.sh''' python durch python3 ersetzen müssen:<br />
<pre>#!/bin/bash<br />
set -a; source config.env; set +a<br />
python3 deye_cli.py "$@"<br />
</pre><br />
<br />
=== config.env ===<br />
Die Konfiguration meines DEYE Wechselrichters vom Typ Micro-Inverter:<br />
'''config.env'''<br />
<pre>DEYE_LOGGER_IP_ADDRESS=<IP Wechselrichter im WLAN><br />
DEYE_LOGGER_PORT=8899<br />
DEYE_LOGGER_SERIAL_NUMBER=<Seriennummer des WR><br />
<br />
MQTT_HOST=<IP von MQTT Server><br />
MQTT_PORT=1883<br />
MQTT_USERNAME=<br />
MQTT_PASSWORD=<br />
MQTT_TOPIC_PREFIX=deye<br />
<br />
LOG_LEVEL=ERROR<br />
DEYE_DATA_READ_INTERVAL=60<br />
DEYE_METRIC_GROUPS=micro<br />
</pre><br />
<br />
=== deye_inverter.sh === <br />
Ausserdem habe ich ein kleines ''Wrapper Skript'' geschrieben, um '''Daten des WR''' einfacher '''lesen und schreiben''' zu können:<br><br />
<br />
'''deye_inverter.sh''' [--check <pause>] --read <register> | --write <register> <value><br />
<br />
<pre>#!/bin/bash<br />
# Read / Write DEYE Inverters<br />
# via deye_inverter_mqtt python package<br />
# https://github.com/kbialek/deye-inverter-mqtt<br />
<br />
retries=2 # retry deye_inverter command multiple times<br />
sleep=5 # sleep time between retries<br />
<br />
# locate script in deye_inverter_mqtt directory<br />
cd $(dirname $0)<br />
if [ ! -f ./deye_cli.sh ]; then<br />
echo "Error: ./deye_cli.sh not found."<br />
echo " Please move $(basename $0) in deye-inverter-mqtt directory."<br />
exit 1<br />
fi<br />
. ./config.env<br />
<br />
log_info ()<br />
{<br />
if [ ${LOG_LEVEL} = "INFO" ]; then<br />
logger -t $(basename $0) $@<br />
fi<br />
}<br />
<br />
log_error ()<br />
{<br />
logger -t $(basename $0) $@<br />
echo 2>&1 $@<br />
}<br />
<br />
# read parameters: mode [rw], register, value and optional -c <check pause><br />
pause=0<br />
while [ $# -gt 0 ]; do<br />
case $1 in<br />
-c|--check) shift<br />
[ $# -ge 3 ] || exit 1<br />
pause=$1<br />
;;<br />
-w|--write) mode=w<br />
shift<br />
[ $# -eq 2 ] || exit 1<br />
reg=$1<br />
val=$2<br />
shift<br />
;;<br />
-r|--read) mode=r<br />
shift<br />
[ $# -eq 1 ] || exit 1<br />
reg=$1<br />
;;<br />
esac<br />
shift<br />
done<br />
<br />
# handle offline deye_inverter by<br />
# ${pause} > 0 -> endless loop<br />
# ${pause} = 0 -> error after ${retries}<br />
while true; do<br />
<br />
# try deye_cli.sh $retries times every $sleep secs<br />
check=${retries}<br />
while [ ${check} -gt 0 ]; do<br />
<br />
case ${mode} in<br />
r) result=$(./deye_cli.sh $mode $reg | grep 'int: ')<br />
if [ -n "$result" ]; then<br />
echo "$result"<br />
log_info -t $0 deye_cli.sh $mode $reg<br />
exit 0<br />
fi<br />
;;<br />
w) result=$(./deye_cli.sh $mode $reg $val)<br />
if [ "$result" = "Ok" ]; then<br />
log_info -t $0 deye_cli.sh $mode $reg $val<br />
exit 0<br />
fi<br />
;;<br />
esac<br />
<br />
# wait ${sleep} or ${pause} secs<br />
sleep $(( ${pause} ? ${sleep} : ${pause} ))<br />
<br />
# try ${retries} times<br />
(( check-- ))<br />
done<br />
<br />
# error, or retry endless until wakeup of deye_inverter<br />
if [ ${pause} -eq 0 ]; then<br />
log_error "Error: deye_cli.sh $mode $reg $val failed."<br />
exit 1<br />
fi<br />
<br />
done<br />
</pre><br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
Folgendes Skript starte ich jeden Morgen um 04:00 Uhr per Cron (und in rc.local fürs booten):<br />
<br />
=== deye_mqtt_loop.sh ===<br />
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.<br />
<br />
Ausserdem setzt es beim Start den Tageszähler des Wechselrichters bedarfsweise zurück, bevor die Werte des DEYE Wechselrichters per Endlosschleife abgefragt werden.<br />
<br />
<pre>#!/bin/bash<br />
# reset Daily_Power of DEYE Inverters<br />
# via deye_inverter_mqtt python package<br />
# https://github.com/kbialek/deye-inverter-mqtt<br />
<br />
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin<br />
<br />
# config:<br />
deye_inverter_dir=/opt/deye-inverter-mqtt<br />
fhem_dir=/opt/fhem<br />
horizon="-2" # sunset CIVIL=-6, REAL=0<br />
<br />
debug=info<br />
<br />
myPID=$$<br />
myname=$(basename $0)<br />
arg="$1"<br />
<br />
deye_inverter_sh=$deye_inverter_dir/deye_inverter.sh<br />
deye_inverter_mqtt_cfg=$deye_inverter_dir/config.env<br />
deye_inverter_mqtt_cmd="python3 deye_docker_entrypoint.py"<br />
<br />
sunrise_cmd="perl $fhem_dir/fhem.pl localhost:7072 {sunrise_abs('HORIZON=$horizon')}"<br />
sunset_cmd="perl $fhem_dir/fhem.pl localhost:7072 {sunset_abs('HORIZON=$horizon')}"<br />
<br />
# installation:<br />
# locate FHEM, used for sunrise(), sunset()<br />
if [ ! -x ${fhem_dir/fhem.pl} ]; then<br />
echo "Error: ${fhem_dir} not found."<br />
echo " Is FHEM installed in $fhem_dir ?"<br />
exit 1<br />
fi<br />
<br />
# locate script in deye_inverter_mqtt directory<br />
if [ ! -f ${deye_inverter_mqtt_cfg} ]; then<br />
echo "Error: ${deye_inverter_mqtt_cfg} not found."<br />
echo " Please update \$deye_inverter_sh path in $0"<br />
exit 1<br />
fi<br />
<br />
# read my config<br />
set -a; source ${deye_inverter_mqtt_cfg}; set +a<br />
<br />
[ "$debug" = "full" ] && set -x<br />
<br />
# ----- functions () ------------------------------------<br />
log_info ()<br />
{<br />
[ "$debug" = "yes" -o "$debug" = "true" -o "$debug" = "info" ] &&<br />
echo $myname: $@<br />
[ ${LOG_LEVEL} = "INFO" ] && logger -t $myname $@<br />
}<br />
<br />
log_error ()<br />
{<br />
logger -t $myname $@<br />
echo 1>&2 $@<br />
}<br />
<br />
# store actual date + time into vars<br />
set_date_time_vars ()<br />
{<br />
year=$(date +%y)<br />
month=$(date +%m | sed 's/^0//g')<br />
day=$(date +%d | sed 's/^0//g')<br />
hour=$(date +%H | sed 's/^0//g')<br />
minute=$(date +%m | sed 's/^0//g')<br />
second=$(date +%S | sed 's/^0//g')<br />
}<br />
<br />
# kill deye_inverter_loop ()<br />
kill_old_instance ()<br />
{<br />
# kill old instances of me<br />
my_PS=$(ps ax | grep -v grep | grep "${myname}" | awk '{print $1}')<br />
for ps in $my_PS; do<br />
[ $ps -lt $myPID ] && kill $ps<br />
done<br />
}<br />
<br />
# get deye_inverter_mqtt PIDs<br />
get_deye_mqtt_pid ()<br />
{<br />
deye_mqtt_pid=$(ps ax | grep -v grep | grep "${deye_inverter_mqtt_cmd}" | awk '{print $1}')<br />
}<br />
<br />
# kill deye_inverter_instance ()<br />
kill_deye_inverter_instance ()<br />
{<br />
get_deye_mqtt_pid<br />
if [ ! -z "${deye_mqtt_pid}" ]; then<br />
kill ${deye_mqtt_pid}<br />
log_info "Info: KILL running deye_mqtt ${deye_mqtt_pid}"<br />
fi<br />
<br />
# kill any running deye requests<br />
deye_ps=$(ps ax | grep -v grep | grep "$deye_inverter_sh " | awk '{print $1}')<br />
[ ! -z "$deye_ps" ] && kill $deye_ps<br />
}<br />
<br />
# stop all runnning instances<br />
deye_inverter_mqtt_stop ()<br />
{<br />
kill_old_instance<br />
kill_deye_inverter_instance<br />
}<br />
<br />
# start deye_inverter_mqtt<br />
deye_inverter_mqtt_start ()<br />
{<br />
log_info "Deye MQTT start"<br />
cd ${deye_inverter_dir}<br />
${deye_inverter_mqtt_cmd}<br />
}<br />
<br />
# read year_month register 0x16 = 22<br />
read_year_month ()<br />
{<br />
${deye_inverter_sh} -c 60 -r 22 | sed 's/int: //g' | cut -d, -f1<br />
}<br />
<br />
# read day register 0x17 = 23<br />
read_day ()<br />
{<br />
${deye_inverter_sh} -c 10 -r 23 | sed 's/.*h: //g' | cut -d, -f1<br />
}<br />
<br />
# read Daily power register 0x3c = 60<br />
read_daily_Power ()<br />
{<br />
${deye_inverter_sh} -c 10 -r 60 | sed 's/int: //g' | cut -d, -f1<br />
}<br />
<br />
# read actual power register 0x56 = 86<br />
read_actual_Power ()<br />
{<br />
# doubleRegisterSensor("AC Active Power", 0x56, 0.1, mqtt_topic_suffix='ac/active_power', groups=['string', 'micro'])<br />
# (int.from_bytes(high_word, 'big') * 65536 + int.from_bytes(low_word, 'big')) * self.factor<br />
# val86=${deye_inverter_sh} -c 10 -r 86 | sed 's/int: //g' | cut -d, -f1<br />
# val87=${deye_inverter_sh} -c 10 -r 87 | sed 's/int: //g' | cut -d, -f1<br />
# echo $(( (${val87} * 65536 + ${val86}) * 0.1 ))<br />
<br />
# simple value is enough for our purpose<br />
${deye_inverter_sh} -c 10 -r 86 | sed 's/int: //g' | cut -d, -f1<br />
}<br />
<br />
# write actual date/time into deye inverter<br />
reset_deye_inverter ()<br />
{<br />
# daily reset phase<br />
daily_reset=init<br />
<br />
# ensure by loop, that all register values are written<br />
while [ ${daily_reset} != "done" ]; do<br />
<br />
# wait until we have enough power<br />
while [ "$(read_actual_Power)" -le 10 ]; do<br />
sleep 600<br />
done<br />
<br />
# read Daily power register<br />
dailyPower=$(read_daily_Power)<br />
<br />
# read (old) year_month and day<br />
val22_old=$(read_year_month)<br />
val23_day=$(read_day)<br />
log_info "date/time update initialized"<br />
<br />
# set variables year, month, day, hour, minute, second<br />
set_date_time_vars<br />
<br />
# calculate register 22-24 with date and time vars<br />
val22=$(( ${year} * 256 + ${month} ))<br />
val23=$(( ${day} * 256 + ${hour} ))<br />
val24=$(( ${minute} * 256 + ${second} ))<br />
<br />
# reset power/date/time only once a day or exit loop<br />
[ "$dailyPower" -eq 0 -a<br />
"${val22_old}" = "${val22}" -a<br />
"${val23_day}" = "${day}" ] && break<br />
<br />
# reset daily_power by<br />
# setting register 22-24 with actual date + time<br />
${deye_inverter_sh} -w 22 $val22 || continue<br />
${deye_inverter_sh} -w 23 $val23 || continue<br />
${deye_inverter_sh} -w 24 $val24 || continue<br />
<br />
# test success<br />
# read Daily power register, should be resetted to 0 now<br />
dailyPower=$(read_daily_Power)<br />
<br />
# exit loop if power is updated, else log that reset failed<br />
if [ "$dailyPower" -eq 0 ]; then<br />
daily_reset=done<br />
<br />
### skip loop here, reset doesn't work always :-(<br />
# elif [ $daily_reset = "init" ]; then<br />
# daily_reset=failed<br />
# sleep 300<br />
<br />
else<br />
log_info "daily power reset failed"<br />
break<br />
fi<br />
done<br />
log_info "date/time reset done"<br />
}<br />
<br />
# wait for (next) daylight<br />
wait_for_sunrise ()<br />
{<br />
sunrise=$($sunrise_cmd)<br />
sunset=$($sunset_cmd)<br />
<br />
current_time=$(date +%s)<br />
sunrise=$(date -d"$sunrise" +%s)<br />
sunset=$(date -d"$sunset" +%s)<br />
<br />
# calulate time in secs until (next) sunrise<br />
((sleep_sunrise=$sunrise - $current_time)) # this morning<br />
[ $sleep_sunrise -lt 0 ] &&<br />
((sleep_sunrise=$sunrise + 86400)) # next morning<br />
<br />
# wait for daylight only<br />
if [ $current_time -lt $sunrise -o<br />
$current_time -gt $sunset ]; then<br />
log_info sleep $sleep_sunrise<br />
sleep $sleep_sunrise<br />
fi<br />
}<br />
<br />
# wait for sunset<br />
wait_for_sunset ()<br />
{<br />
sunset=$($sunset_cmd)<br />
<br />
current_time=$(date +%s)<br />
sunset=$(date -d"$sunset" +%s)<br />
<br />
# calulate time in secs until next sunset<br />
((sleep_sunset=$sunset - $current_time))<br />
<br />
# wait until sunset only<br />
if [ $sleep_sunset -gt 0 ]; then<br />
log_info sleep $sleep_sunset<br />
sleep $sleep_sunset<br />
fi<br />
}<br />
<br />
# ------------------------<br />
# MAIN: Script starts here<br />
# ------------------------<br />
<br />
# sunset_stop: wait until (next) sunset<br />
[ "$arg" = "sunset_stop" ] &&<br />
wait_for_sunset<br />
<br />
# stop any runnning deye_inverter_mqtt processes<br />
deye_inverter_mqtt_stop<br />
<br />
# arg stop: don't start again<br />
[ "$arg" = "stop" ] && exit 0<br />
<br />
# wait for (next) daylight<br />
wait_for_sunrise<br />
<br />
# setting date/time also resets daily power register<br />
reset_deye_inverter<br />
<br />
# start deye_mqtt_loop<br />
# check every 15 min for running deye_inverter_mqtt<br />
while :; do<br />
<br />
# wait for sunrise<br />
wait_for_sunrise<br />
<br />
# start deye_inverter_mqtt<br />
get_deye_mqtt_pid<br />
if [ -z "$deye_mqtt_pid" ]; then<br />
deye_inverter_mqtt_start<br />
fi<br />
<br />
# wait 15 min<br />
sleep 900<br />
done<br />
</pre><br />
<br />
=== Contrab und /etc/rc.local ===<br />
Mein Crontab Eintrag:<br />
<pre>0 4,22 * * * /root/sbin/deye_mqtt_loop.sh 2>/dev/null &</pre><br />
und /etc/rc.local:<br />
<pre># start monitoring of PV-System: DEYE inverter<br />
$(sleep 20; /root/sbin/deye_mqtt_loop.sh 2>dev/null)&<br />
</pre><br />
<br />
== Dashboard ==<br />
So sieht das aus, wenn es den ganzen Tag im April regnet und bedeckt ist:<br />
[[Datei:Solar Bedeckt Regen.jpg|midi]]<br />
<br />
Die Eigenverbrauchsquote habe ich mittels Grafana ermittelt:<br />
[[Datei:Solar Eigenverbrauch.jpg|midi|Eigenverbrauchsquote]]</div>
Neobiker
http://neobiker.de/wiki/index.php?title=DEYE_Wechselrichter&diff=2130
DEYE Wechselrichter
2024-01-22T19:08:09Z
<p>Neobiker: /* deye_mqtt_loop.sh */</p>
<hr />
<div>= Balkonkraftwerk: DEYE Wechselrichter ohne Cloud auslesen und per MQTT monitoren =<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
Der WR bietet verschiedene Möglichkeiten zur Konfiguration und Abfrage der aktuellen Werte:<br />
* einen internen Webserver (http://10.10.10.254)<br />
* die Cloud Anbindung zu Solarman (Solarman Smart App)<br />
* (angepasstes) Modbus Protokoll über Port 8899<br />
* AT+ Befehle über Port 48899<br />
<br />
Ich verwende das Modbus Protokoll zum auslesen des WR, das <br />
# effizient/schnell ist (Performance)<br />
# ohne Internetverbindung auskommt<br />
<br />
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.<br />
<br />
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.<br />
<br />
== Wechselrichter Eigenheiten ==<br />
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. <br />
<br />
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.<br />
<br />
== Installation ==<br />
Das Projekt von Github laden und in einem Verzeichnis speichern, '''Python''' ist normalerweise ja schon installiert. <br />
<br />
<pre>git clone https://github.com/kbialek/deye-inverter-mqtt<br />
pip install paho-mqtt<br />
cd deye-inverter-mqtt</pre><br />
<br />
Unter Debian habe ich im Skript '''deye_cli.sh''' python durch python3 ersetzen müssen:<br />
<pre>#!/bin/bash<br />
set -a; source config.env; set +a<br />
python3 deye_cli.py "$@"<br />
</pre><br />
<br />
=== config.env ===<br />
Die Konfiguration meines DEYE Wechselrichters vom Typ Micro-Inverter:<br />
'''config.env'''<br />
<pre>DEYE_LOGGER_IP_ADDRESS=<IP Wechselrichter im WLAN><br />
DEYE_LOGGER_PORT=8899<br />
DEYE_LOGGER_SERIAL_NUMBER=<Seriennummer des WR><br />
<br />
MQTT_HOST=<IP von MQTT Server><br />
MQTT_PORT=1883<br />
MQTT_USERNAME=<br />
MQTT_PASSWORD=<br />
MQTT_TOPIC_PREFIX=deye<br />
<br />
LOG_LEVEL=ERROR<br />
DEYE_DATA_READ_INTERVAL=60<br />
DEYE_METRIC_GROUPS=micro<br />
</pre><br />
<br />
=== deye_inverter.sh === <br />
Ausserdem habe ich ein kleines ''Wrapper Skript'' geschrieben, um '''Daten des WR''' einfacher '''lesen und schreiben''' zu können:<br><br />
<br />
'''deye_inverter.sh''' [--check <pause>] --read <register> | --write <register> <value><br />
<br />
<pre>#!/bin/bash<br />
# Read / Write DEYE Inverters<br />
# via deye_inverter_mqtt python package<br />
# https://github.com/kbialek/deye-inverter-mqtt<br />
<br />
retries=2 # retry deye_inverter command multiple times<br />
sleep=5 # sleep time between retries<br />
<br />
# locate script in deye_inverter_mqtt directory<br />
cd $(dirname $0)<br />
if [ ! -f ./deye_cli.sh ]; then<br />
echo "Error: ./deye_cli.sh not found."<br />
echo " Please move $(basename $0) in deye-inverter-mqtt directory."<br />
exit 1<br />
fi<br />
. ./config.env<br />
<br />
log_info ()<br />
{<br />
if [ ${LOG_LEVEL} = "INFO" ]; then<br />
logger -t $(basename $0) $@<br />
fi<br />
}<br />
<br />
log_error ()<br />
{<br />
logger -t $(basename $0) $@<br />
echo 2>&1 $@<br />
}<br />
<br />
# read parameters: mode [rw], register, value and optional -c <check pause><br />
pause=0<br />
while [ $# -gt 0 ]; do<br />
case $1 in<br />
-c|--check) shift<br />
[ $# -ge 3 ] || exit 1<br />
pause=$1<br />
;;<br />
-w|--write) mode=w<br />
shift<br />
[ $# -eq 2 ] || exit 1<br />
reg=$1<br />
val=$2<br />
shift<br />
;;<br />
-r|--read) mode=r<br />
shift<br />
[ $# -eq 1 ] || exit 1<br />
reg=$1<br />
;;<br />
esac<br />
shift<br />
done<br />
<br />
# handle offline deye_inverter by<br />
# ${pause} > 0 -> endless loop<br />
# ${pause} = 0 -> error after ${retries}<br />
while true; do<br />
<br />
# try deye_cli.sh $retries times every $sleep secs<br />
check=${retries}<br />
while [ ${check} -gt 0 ]; do<br />
<br />
case ${mode} in<br />
r) result=$(./deye_cli.sh $mode $reg | grep 'int: ')<br />
if [ -n "$result" ]; then<br />
echo "$result"<br />
log_info -t $0 deye_cli.sh $mode $reg<br />
exit 0<br />
fi<br />
;;<br />
w) result=$(./deye_cli.sh $mode $reg $val)<br />
if [ "$result" = "Ok" ]; then<br />
log_info -t $0 deye_cli.sh $mode $reg $val<br />
exit 0<br />
fi<br />
;;<br />
esac<br />
<br />
# wait ${sleep} or ${pause} secs<br />
sleep $(( ${pause} ? ${sleep} : ${pause} ))<br />
<br />
# try ${retries} times<br />
(( check-- ))<br />
done<br />
<br />
# error, or retry endless until wakeup of deye_inverter<br />
if [ ${pause} -eq 0 ]; then<br />
log_error "Error: deye_cli.sh $mode $reg $val failed."<br />
exit 1<br />
fi<br />
<br />
done<br />
</pre><br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
Folgendes Skript starte ich jeden Morgen um 04:00 Uhr per Cron (und in rc.local fürs booten):<br />
<br />
=== deye_mqtt_loop.sh ===<br />
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.<br />
<br />
Ausserdem setzt es beim Start den Tageszähler des Wechselrichters bedarfsweise zurück, bevor die Werte des DEYE Wechselrichters per Endlosschleife abgefragt werden.<br />
<br />
<pre>#!/bin/bash<br />
# reset Daily_Power of DEYE Inverters<br />
# via deye_inverter_mqtt python package<br />
# https://github.com/kbialek/deye-inverter-mqtt<br />
<br />
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin<br />
<br />
debug=<br />
<br />
# config:<br />
deye_inverter_sh=/root/deye-inverter-mqtt/deye_inverter.sh<br />
deye_inverter_mqtt_cmd="python3 $(dirname ${deye_inverter_sh})/deye_docker_entrypoint.py"<br />
deye_inverter_mqtt_cfg=$(dirname ${deye_inverter_sh})/config.env<br />
<br />
myPID=$$<br />
myname=$(basename $0)<br />
<br />
# installation:<br />
# locate script in deye_inverter_mqtt directory<br />
if [ ! -f ${deye_inverter_mqtt_cfg} ]; then<br />
echo "Error: ${deye_inverter_mqtt_cfg} not found."<br />
echo " Please update \$deye_inverter_sh path in $0"<br />
exit 1<br />
fi<br />
<br />
# read my config<br />
set -a; source ${deye_inverter_mqtt_cfg}; set +a<br />
<br />
# ----- functions () ------------------------------------<br />
log_info ()<br />
{<br />
[ "$debug" = "yes" -o "$debug" = "true" ] && echo $myname: $@<br />
[ ${LOG_LEVEL} = "INFO" ] && logger -t $myname $@<br />
}<br />
<br />
log_error ()<br />
{<br />
logger -t $myname $@<br />
echo 1>&2 $@<br />
}<br />
<br />
# store actual date + time into vars<br />
get_date_time ()<br />
{<br />
year=$(date +%y)<br />
month=$(date +%m | sed 's/^0//g')<br />
day=$(date +%d | sed 's/^0//g')<br />
hour=$(date +%H | sed 's/^0//g')<br />
minute=$(date +%m | sed 's/^0//g')<br />
second=$(date +%S | sed 's/^0//g')<br />
}<br />
<br />
# get deye_inverter_mqtt PIDs<br />
get_deye_mqtt_pid ()<br />
{<br />
deye_mqtt_pid=$(ps ax | grep -v grep | grep "${deye_inverter_mqtt_cmd}" | awk '{print $1}')<br />
}<br />
<br />
# kill deye_inverter_loop ()<br />
kill_deye_inverter_loop ()<br />
{<br />
# kill old instances of me<br />
my_PS=$(ps ax | grep -v grep | grep ${myname} | awk '{print $1}')<br />
for ps in $my_PS; do<br />
[ $ps -lt $myPID ] && kill $ps<br />
done<br />
}<br />
<br />
# kill deye_inverter_instance ()<br />
kill_deye_inverter_instance ()<br />
{<br />
get_deye_mqtt_pid<br />
if [ ! -z "${deye_mqtt_pid}" ]; then<br />
kill ${deye_mqtt_pid}<br />
log_info "Info: KILL running deye_mqtt ${deye_mqtt_pid}"<br />
fi<br />
<br />
# kill any running deye_requests<br />
my_PS=$(ps ax | grep -v grep | grep "${deye_inverter_sh}" | awk '{print $1}')<br />
[ ! -z "$my_PS" ] && kill $my_PS<br />
}<br />
<br />
# stop all runnning instances<br />
deye_inverter_mqtt_stop ()<br />
{<br />
kill_deye_inverter_loop<br />
kill_deye_inverter_instance<br />
}<br />
<br />
# start deye_inverter_mqtt<br />
deye_inverter_mqtt_start ()<br />
{<br />
log_info "MQTT start"<br />
${deye_inverter_mqtt_cmd}<br />
}<br />
<br />
# read Daily power register<br />
read_daily_Power ()<br />
{<br />
${deye_inverter_sh} -c 10 -r 60 | sed 's/int: //g' | cut -d, -f1<br />
}<br />
<br />
# read actual power register<br />
read_actual_Power ()<br />
{<br />
# doubleRegisterSensor("AC Active Power", 0x56, 0.1, mqtt_topic_suffix='ac/active_power', groups=['string', 'micro'])<br />
# (int.from_bytes(high_word, 'big') * 65536 + int.from_bytes(low_word, 'big')) * self.factor<br />
# val86=${deye_inverter_sh} -c 10 -r 86 | sed 's/int: //g' | cut -d, -f1<br />
# val87=${deye_inverter_sh} -c 10 -r 87 | sed 's/int: //g' | cut -d, -f1<br />
# echo $(( (${val87} * 65536 + ${val86}) * 0.1 ))<br />
<br />
$(( $(${deye_inverter_sh} -c 10 -r 86 | sed 's/int: //g' | cut -d, -f1 ) / 10 ))<br />
}<br />
<br />
# wait until 04:00 - 22:00<br />
wait_for_operative_window ()<br />
{<br />
get_date_time<br />
<br />
# calulate time in secs until 04:00<br />
if [ $hour -ge 4 ]; then<br />
((sleep_time=((((28-$hour)*60)-$minute)*60)-$second))<br />
else<br />
((sleep_time=((((4-$hour)*60)-$minute)*60)-$second))<br />
fi<br />
<br />
# wait for operative time<br />
if [ $hour -lt 4 -o $hour -ge 22 ]; then<br />
log_info $sleep_time<br />
sleep $sleep_time<br />
fi<br />
}<br />
<br />
# write actual date/time into deye inverter<br />
reset_deye_inverter ()<br />
{<br />
# wait until deye inverter starts by reading (old) year/month (register 22)<br />
val22_old=$(${deye_inverter_sh} -c 60 -r 22 | sed 's/int: //g' | cut -d, -f1)<br />
log_info "date/time update initialized"<br />
<br />
# read Daily power register<br />
dailyPower=$(read_daily_Power)<br />
<br />
# daily reset phase<br />
daily_reset=init<br />
<br />
# ensure by loop, that all register values are written<br />
while [ ${daily_reset} != "done" ]; do<br />
<br />
# read (old) day<br />
val23_day=$(${deye_inverter_sh} -c 10 -r 23 | sed 's/.*h: //g' | cut -d, -f1)<br />
<br />
# set actual year, month, day, hour, minute, second<br />
get_date_time<br />
<br />
# calculate register 22-24 by date and time<br />
val22=$(( ${year} * 256 + ${month} ))<br />
val23=$(( ${day} * 256 + ${hour} ))<br />
val24=$(( ${minute} * 256 + ${second} ))<br />
<br />
# reset power/date/time only once a day or exit loop<br />
[ "$dailyPower" -eq 0 -a "${val22_old}" = "${val22}" -a "${val23_day}" = "${day}" ] && break<br />
<br />
# wait until we have enough light (4W)<br />
while [ $(read_actual_Power) -le 3 ]; do<br />
sleep 300<br />
done<br />
<br />
# reset daily_power by<br />
# setting register 22-24 with actual date + time<br />
${deye_inverter_sh} -w 22 $val22 || continue<br />
${deye_inverter_sh} -w 23 $val23 || continue<br />
${deye_inverter_sh} -w 24 $val24 || continue<br />
<br />
# test success<br />
# read Daily power register, should be resetted to 0 now<br />
dailyPower=$(read_daily_Power)<br />
<br />
# exit loop if power is updated, else log that reset failed<br />
if [ "$dailyPower" -eq 0 ]; then<br />
daily_reset=done<br />
<br />
### skip loop here, reset doesn't work always :-(<br />
# elif [ $daily_reset = "init" ]; then<br />
# daily_reset=failed<br />
# sleep 60<br />
<br />
else<br />
log_info "daily power reset failed"<br />
break<br />
fi<br />
done<br />
log_info "date/time updated"<br />
}<br />
<br />
# ------------------------<br />
# MAIN: Script starts here<br />
# ------------------------<br />
<br />
# stop any runnning deye_inverter_mqtt processes<br />
deye_inverter_mqtt_stop<br />
<br />
# wait until 04:00 - 22:00<br />
wait_for_operative_window<br />
<br />
# setting date/time also resets daily power register<br />
reset_deye_inverter<br />
<br />
# start deye_mqtt_loop<br />
while get_date_time; do<br />
<br />
# wait for next operative window 04:00 - 22:00<br />
[ $hour -lt 4 -o $hour -le 22 ] && \<br />
wait_for_operative_window<br />
<br />
# start deye_inverter_mqtt<br />
get_deye_mqtt_pid<br />
[ -z "$deye_mqtt_pid" ] && deye_inverter_mqtt_start<br />
<br />
done<br />
<br />
</pre><br />
<br />
=== Contrab und /etc/rc.local ===<br />
Mein Crontab Eintrag:<br />
<pre>0 4,22 * * * /root/sbin/deye_mqtt_loop.sh 2>/dev/null &</pre><br />
und /etc/rc.local:<br />
<pre># start monitoring of PV-System: DEYE inverter<br />
$(sleep 20; /root/sbin/deye_mqtt_loop.sh 2>dev/null)&<br />
</pre><br />
<br />
== Dashboard ==<br />
So sieht das aus, wenn es den ganzen Tag im April regnet und bedeckt ist:<br />
[[Datei:Solar Bedeckt Regen.jpg|midi]]<br />
<br />
Die Eigenverbrauchsquote habe ich mittels Grafana ermittelt:<br />
[[Datei:Solar Eigenverbrauch.jpg|midi|Eigenverbrauchsquote]]</div>
Neobiker
http://neobiker.de/wiki/index.php?title=DEYE_Wechselrichter&diff=2129
DEYE Wechselrichter
2024-01-07T17:50:19Z
<p>Neobiker: /* Installation */</p>
<hr />
<div>= Balkonkraftwerk: DEYE Wechselrichter ohne Cloud auslesen und per MQTT monitoren =<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
Der WR bietet verschiedene Möglichkeiten zur Konfiguration und Abfrage der aktuellen Werte:<br />
* einen internen Webserver (http://10.10.10.254)<br />
* die Cloud Anbindung zu Solarman (Solarman Smart App)<br />
* (angepasstes) Modbus Protokoll über Port 8899<br />
* AT+ Befehle über Port 48899<br />
<br />
Ich verwende das Modbus Protokoll zum auslesen des WR, das <br />
# effizient/schnell ist (Performance)<br />
# ohne Internetverbindung auskommt<br />
<br />
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.<br />
<br />
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.<br />
<br />
== Wechselrichter Eigenheiten ==<br />
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. <br />
<br />
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.<br />
<br />
== Installation ==<br />
Das Projekt von Github laden und in einem Verzeichnis speichern, '''Python''' ist normalerweise ja schon installiert. <br />
<br />
<pre>git clone https://github.com/kbialek/deye-inverter-mqtt<br />
pip install paho-mqtt<br />
cd deye-inverter-mqtt</pre><br />
<br />
Unter Debian habe ich im Skript '''deye_cli.sh''' python durch python3 ersetzen müssen:<br />
<pre>#!/bin/bash<br />
set -a; source config.env; set +a<br />
python3 deye_cli.py "$@"<br />
</pre><br />
<br />
=== config.env ===<br />
Die Konfiguration meines DEYE Wechselrichters vom Typ Micro-Inverter:<br />
'''config.env'''<br />
<pre>DEYE_LOGGER_IP_ADDRESS=<IP Wechselrichter im WLAN><br />
DEYE_LOGGER_PORT=8899<br />
DEYE_LOGGER_SERIAL_NUMBER=<Seriennummer des WR><br />
<br />
MQTT_HOST=<IP von MQTT Server><br />
MQTT_PORT=1883<br />
MQTT_USERNAME=<br />
MQTT_PASSWORD=<br />
MQTT_TOPIC_PREFIX=deye<br />
<br />
LOG_LEVEL=ERROR<br />
DEYE_DATA_READ_INTERVAL=60<br />
DEYE_METRIC_GROUPS=micro<br />
</pre><br />
<br />
=== deye_inverter.sh === <br />
Ausserdem habe ich ein kleines ''Wrapper Skript'' geschrieben, um '''Daten des WR''' einfacher '''lesen und schreiben''' zu können:<br><br />
<br />
'''deye_inverter.sh''' [--check <pause>] --read <register> | --write <register> <value><br />
<br />
<pre>#!/bin/bash<br />
# Read / Write DEYE Inverters<br />
# via deye_inverter_mqtt python package<br />
# https://github.com/kbialek/deye-inverter-mqtt<br />
<br />
retries=2 # retry deye_inverter command multiple times<br />
sleep=5 # sleep time between retries<br />
<br />
# locate script in deye_inverter_mqtt directory<br />
cd $(dirname $0)<br />
if [ ! -f ./deye_cli.sh ]; then<br />
echo "Error: ./deye_cli.sh not found."<br />
echo " Please move $(basename $0) in deye-inverter-mqtt directory."<br />
exit 1<br />
fi<br />
. ./config.env<br />
<br />
log_info ()<br />
{<br />
if [ ${LOG_LEVEL} = "INFO" ]; then<br />
logger -t $(basename $0) $@<br />
fi<br />
}<br />
<br />
log_error ()<br />
{<br />
logger -t $(basename $0) $@<br />
echo 2>&1 $@<br />
}<br />
<br />
# read parameters: mode [rw], register, value and optional -c <check pause><br />
pause=0<br />
while [ $# -gt 0 ]; do<br />
case $1 in<br />
-c|--check) shift<br />
[ $# -ge 3 ] || exit 1<br />
pause=$1<br />
;;<br />
-w|--write) mode=w<br />
shift<br />
[ $# -eq 2 ] || exit 1<br />
reg=$1<br />
val=$2<br />
shift<br />
;;<br />
-r|--read) mode=r<br />
shift<br />
[ $# -eq 1 ] || exit 1<br />
reg=$1<br />
;;<br />
esac<br />
shift<br />
done<br />
<br />
# handle offline deye_inverter by<br />
# ${pause} > 0 -> endless loop<br />
# ${pause} = 0 -> error after ${retries}<br />
while true; do<br />
<br />
# try deye_cli.sh $retries times every $sleep secs<br />
check=${retries}<br />
while [ ${check} -gt 0 ]; do<br />
<br />
case ${mode} in<br />
r) result=$(./deye_cli.sh $mode $reg | grep 'int: ')<br />
if [ -n "$result" ]; then<br />
echo "$result"<br />
log_info -t $0 deye_cli.sh $mode $reg<br />
exit 0<br />
fi<br />
;;<br />
w) result=$(./deye_cli.sh $mode $reg $val)<br />
if [ "$result" = "Ok" ]; then<br />
log_info -t $0 deye_cli.sh $mode $reg $val<br />
exit 0<br />
fi<br />
;;<br />
esac<br />
<br />
# wait ${sleep} or ${pause} secs<br />
sleep $(( ${pause} ? ${sleep} : ${pause} ))<br />
<br />
# try ${retries} times<br />
(( check-- ))<br />
done<br />
<br />
# error, or retry endless until wakeup of deye_inverter<br />
if [ ${pause} -eq 0 ]; then<br />
log_error "Error: deye_cli.sh $mode $reg $val failed."<br />
exit 1<br />
fi<br />
<br />
done<br />
</pre><br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
Folgendes Skript starte ich jeden Morgen um 04:00 Uhr per Cron (und in rc.local fürs booten):<br />
<br />
=== deye_mqtt_loop.sh ===<br />
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.<br />
<br />
Ausserdem setzt es beim Start den Tageszähler des Wechselrichters bedarfsweise zurück, bevor die Werte des DEYE Wechselrichters per Endlosschleife abgefragt werden.<br />
<br />
<pre>#!/bin/bash<br />
# reset Daily_Power of DEYE Inverters<br />
# via deye_inverter_mqtt python package<br />
# https://github.com/kbialek/deye-inverter-mqtt<br />
<br />
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin<br />
<br />
debug=<br />
<br />
# config:<br />
deye_inverter_sh=/root/deye-inverter-mqtt/deye_inverter.sh<br />
deye_inverter_mqtt_cmd="python3 $(dirname ${deye_inverter_sh})/deye_docker_entrypoint.py"<br />
deye_inverter_mqtt_cfg=$(dirname ${deye_inverter_sh})/config.env<br />
<br />
myPID=$$<br />
myname=$(basename $0)<br />
<br />
# installation:<br />
# locate script in deye_inverter_mqtt directory<br />
if [ ! -f ${deye_inverter_mqtt_cfg} ]; then<br />
echo "Error: ${deye_inverter_mqtt_cfg} not found."<br />
echo " Please update \$deye_inverter_sh path in $0"<br />
exit 1<br />
fi<br />
<br />
# read my config<br />
set -a; source ${deye_inverter_mqtt_cfg}; set +a<br />
<br />
# ----- functions () ------------------------------------<br />
log_info ()<br />
{<br />
[ "$debug" = "yes" -o "$debug" = "true" ] && echo $myname: $@<br />
[ ${LOG_LEVEL} = "INFO" ] && logger -t $myname $@<br />
}<br />
<br />
log_error ()<br />
{<br />
logger -t $myname $@<br />
echo 1>&2 $@<br />
}<br />
<br />
# store actual date + time into vars<br />
get_date_time ()<br />
{<br />
year=$(date +%y)<br />
month=$(date +%m | sed 's/^0//g')<br />
day=$(date +%d | sed 's/^0//g')<br />
hour=$(date +%H | sed 's/^0//g')<br />
minute=$(date +%m | sed 's/^0//g')<br />
second=$(date +%S | sed 's/^0//g')<br />
}<br />
<br />
# get deye_inverter_mqtt PIDs<br />
get_deye_mqtt_pid ()<br />
{<br />
deye_mqtt_pid=$(ps ax | grep -v grep | grep "${deye_inverter_mqtt_cmd}" | awk '{print $1}')<br />
}<br />
<br />
# kill deye_inverter_loop ()<br />
kill_deye_inverter_loop ()<br />
{<br />
# kill old instances of me<br />
my_PS=$(ps ax | grep -v grep | grep ${myname} | awk '{print $1}')<br />
for ps in $my_PS; do<br />
[ $ps -lt $myPID ] && kill $ps<br />
done<br />
}<br />
<br />
# kill deye_inverter_instance ()<br />
kill_deye_inverter_instance ()<br />
{<br />
get_deye_mqtt_pid<br />
if [ ! -z "${deye_mqtt_pid}" ]; then<br />
kill ${deye_mqtt_pid}<br />
log_info "Info: KILL running deye_mqtt ${deye_mqtt_pid}"<br />
fi<br />
<br />
# kill any running deye_requests<br />
my_PS=$(ps ax | grep -v grep | grep "${deye_inverter_sh}" | awk '{print $1}')<br />
[ ! -z "$my_PS" ] && kill $my_PS<br />
}<br />
<br />
# stop all runnning instances<br />
deye_inverter_mqtt_stop ()<br />
{<br />
kill_deye_inverter_loop<br />
kill_deye_inverter_instance<br />
}<br />
<br />
# start deye_inverter_mqtt<br />
deye_inverter_mqtt_start ()<br />
{<br />
log_info "MQTT start"<br />
${deye_inverter_mqtt_cmd}<br />
}<br />
<br />
# read Daily power register<br />
read_daily_Power ()<br />
{<br />
${deye_inverter_sh} -c 10 -r 60 | sed 's/int: //g' | cut -d, -f1<br />
}<br />
<br />
# wait until 04:00 - 22:00<br />
wait_for_operative_window ()<br />
{<br />
get_date_time<br />
<br />
# calulate time in secs until 04:00<br />
if [ $hour -ge 4 ]; then<br />
((sleep_time=((((28-$hour)*60)-$minute)*60)-$second))<br />
else<br />
((sleep_time=((((4-$hour)*60)-$minute)*60)-$second))<br />
fi<br />
<br />
# wait for operative time<br />
if [ $hour -lt 4 -o $hour -ge 22 ]; then<br />
log_info $sleep_time<br />
sleep $sleep_time<br />
fi<br />
}<br />
<br />
# write actual date/time into deye inverter<br />
reset_deye_inverter ()<br />
{<br />
# wait until deye inverter starts by reading (old) year/month (register 22)<br />
val22_old=$(${deye_inverter_sh} -c 60 -r 22 | sed 's/int: //g' | cut -d, -f1)<br />
log_info "date/time update initialized"<br />
<br />
# read Daily power register<br />
dailyPower=$(read_daily_Power)<br />
<br />
# daily reset phase<br />
daily_reset=init<br />
<br />
# ensure by loop, that all register values are written<br />
while [ ${daily_reset} != "done" ]; do<br />
<br />
# read (old) day<br />
val23_day=$(${deye_inverter_sh} -c 10 -r 23 | sed 's/.*h: //g' | cut -d, -f1)<br />
<br />
# set actual year, month, day, hour, minute, second<br />
get_date_time<br />
<br />
# calculate register 22-24 by date and time<br />
val22=$(( ${year} * 256 + ${month} ))<br />
val23=$(( ${day} * 256 + ${hour} ))<br />
val24=$(( ${minute} * 256 + ${second} ))<br />
<br />
# reset power/date/time only once a day or exit loop<br />
[ "$dailyPower" -eq 0 -a "${val22_old}" = "${val22}" -a "${val23_day}" = "${day}" ] && break<br />
<br />
# reset daily_power by<br />
# setting register 22-24 with actual date + time<br />
${deye_inverter_sh} -w 22 $val22 || continue<br />
${deye_inverter_sh} -w 23 $val23 || continue<br />
${deye_inverter_sh} -w 24 $val24 || continue<br />
<br />
# test success<br />
# read Daily power register, should be resetted to 0 now<br />
dailyPower=$(read_daily_Power)<br />
<br />
# exit loop if power is updated, else log that reset failed<br />
if [ "$dailyPower" -eq 0 ]; then<br />
daily_reset=done<br />
<br />
### skip loop here, reset doesn't work always :-(<br />
# elif [ $daily_reset = "init" ]; then<br />
# daily_reset=failed<br />
# sleep 60<br />
<br />
else<br />
log_info "daily power reset failed"<br />
break<br />
fi<br />
done<br />
log_info "date/time updated"<br />
}<br />
<br />
# ------------------------<br />
# MAIN: Script starts here<br />
# ------------------------<br />
<br />
# stop any runnning deye_inverter_mqtt processes<br />
deye_inverter_mqtt_stop<br />
<br />
# wait until 04:00 - 22:00<br />
wait_for_operative_window<br />
<br />
# setting date/time also resets daily power register<br />
reset_deye_inverter<br />
<br />
# start deye_mqtt_loop<br />
while get_date_time; do<br />
<br />
# wait for next operative window 04:00 - 22:00<br />
[ $hour -lt 4 -o $hour -le 22 ] && \<br />
wait_for_operative_window<br />
<br />
# start deye_inverter_mqtt<br />
get_deye_mqtt_pid<br />
[ -z "$deye_mqtt_pid" ] && deye_inverter_mqtt_start<br />
<br />
done<br />
</pre><br />
<br />
=== Contrab und /etc/rc.local ===<br />
Mein Crontab Eintrag:<br />
<pre>0 4,22 * * * /root/sbin/deye_mqtt_loop.sh 2>/dev/null &</pre><br />
und /etc/rc.local:<br />
<pre># start monitoring of PV-System: DEYE inverter<br />
$(sleep 20; /root/sbin/deye_mqtt_loop.sh 2>dev/null)&<br />
</pre><br />
<br />
== Dashboard ==<br />
So sieht das aus, wenn es den ganzen Tag im April regnet und bedeckt ist:<br />
[[Datei:Solar Bedeckt Regen.jpg|midi]]<br />
<br />
Die Eigenverbrauchsquote habe ich mittels Grafana ermittelt:<br />
[[Datei:Solar Eigenverbrauch.jpg|midi|Eigenverbrauchsquote]]</div>
Neobiker
http://neobiker.de/wiki/index.php?title=DEYE_Wechselrichter&diff=2128
DEYE Wechselrichter
2024-01-07T14:19:27Z
<p>Neobiker: /* Contrab und /etc/rc.local */</p>
<hr />
<div>= Balkonkraftwerk: DEYE Wechselrichter ohne Cloud auslesen und per MQTT monitoren =<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
Der WR bietet verschiedene Möglichkeiten zur Konfiguration und Abfrage der aktuellen Werte:<br />
* einen internen Webserver (http://10.10.10.254)<br />
* die Cloud Anbindung zu Solarman (Solarman Smart App)<br />
* (angepasstes) Modbus Protokoll über Port 8899<br />
* AT+ Befehle über Port 48899<br />
<br />
Ich verwende das Modbus Protokoll zum auslesen des WR, das <br />
# effizient/schnell ist (Performance)<br />
# ohne Internetverbindung auskommt<br />
<br />
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.<br />
<br />
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.<br />
<br />
== Wechselrichter Eigenheiten ==<br />
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. <br />
<br />
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.<br />
<br />
== Installation ==<br />
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.<br />
<pre>git clone https://github.com/kbialek/deye-inverter-mqtt<br />
pip install paho-mqtt<br />
cd deye-inverter-mqtt</pre><br />
<br />
Unter Debian habe ich im Skript '''deye_cli.sh''' python durch python3 ersetzen müssen:<br />
<pre>#!/bin/bash<br />
set -a; source config.env; set +a<br />
python3 deye_cli.py "$@"<br />
</pre><br />
<br />
=== config.env ===<br />
Die Konfiguration meines DEYE Wechselrichters vom Typ Micro-Inverter:<br />
'''config.env'''<br />
<pre>DEYE_LOGGER_IP_ADDRESS=<IP Wechselrichter im WLAN><br />
DEYE_LOGGER_PORT=8899<br />
DEYE_LOGGER_SERIAL_NUMBER=<Seriennummer des WR><br />
<br />
MQTT_HOST=<IP von MQTT Server><br />
MQTT_PORT=1883<br />
MQTT_USERNAME=<br />
MQTT_PASSWORD=<br />
MQTT_TOPIC_PREFIX=deye<br />
<br />
LOG_LEVEL=ERROR<br />
DEYE_DATA_READ_INTERVAL=60<br />
DEYE_METRIC_GROUPS=micro<br />
</pre><br />
<br />
=== deye_inverter.sh === <br />
Ausserdem habe ich ein kleines ''Wrapper Skript'' geschrieben, um '''Daten des WR''' einfacher '''lesen und schreiben''' zu können:<br><br />
<br />
'''deye_inverter.sh''' [--check <pause>] --read <register> | --write <register> <value><br />
<br />
<pre>#!/bin/bash<br />
# Read / Write DEYE Inverters<br />
# via deye_inverter_mqtt python package<br />
# https://github.com/kbialek/deye-inverter-mqtt<br />
<br />
retries=2 # retry deye_inverter command multiple times<br />
sleep=5 # sleep time between retries<br />
<br />
# locate script in deye_inverter_mqtt directory<br />
cd $(dirname $0)<br />
if [ ! -f ./deye_cli.sh ]; then<br />
echo "Error: ./deye_cli.sh not found."<br />
echo " Please move $(basename $0) in deye-inverter-mqtt directory."<br />
exit 1<br />
fi<br />
. ./config.env<br />
<br />
log_info ()<br />
{<br />
if [ ${LOG_LEVEL} = "INFO" ]; then<br />
logger -t $(basename $0) $@<br />
fi<br />
}<br />
<br />
log_error ()<br />
{<br />
logger -t $(basename $0) $@<br />
echo 2>&1 $@<br />
}<br />
<br />
# read parameters: mode [rw], register, value and optional -c <check pause><br />
pause=0<br />
while [ $# -gt 0 ]; do<br />
case $1 in<br />
-c|--check) shift<br />
[ $# -ge 3 ] || exit 1<br />
pause=$1<br />
;;<br />
-w|--write) mode=w<br />
shift<br />
[ $# -eq 2 ] || exit 1<br />
reg=$1<br />
val=$2<br />
shift<br />
;;<br />
-r|--read) mode=r<br />
shift<br />
[ $# -eq 1 ] || exit 1<br />
reg=$1<br />
;;<br />
esac<br />
shift<br />
done<br />
<br />
# handle offline deye_inverter by<br />
# ${pause} > 0 -> endless loop<br />
# ${pause} = 0 -> error after ${retries}<br />
while true; do<br />
<br />
# try deye_cli.sh $retries times every $sleep secs<br />
check=${retries}<br />
while [ ${check} -gt 0 ]; do<br />
<br />
case ${mode} in<br />
r) result=$(./deye_cli.sh $mode $reg | grep 'int: ')<br />
if [ -n "$result" ]; then<br />
echo "$result"<br />
log_info -t $0 deye_cli.sh $mode $reg<br />
exit 0<br />
fi<br />
;;<br />
w) result=$(./deye_cli.sh $mode $reg $val)<br />
if [ "$result" = "Ok" ]; then<br />
log_info -t $0 deye_cli.sh $mode $reg $val<br />
exit 0<br />
fi<br />
;;<br />
esac<br />
<br />
# wait ${sleep} or ${pause} secs<br />
sleep $(( ${pause} ? ${sleep} : ${pause} ))<br />
<br />
# try ${retries} times<br />
(( check-- ))<br />
done<br />
<br />
# error, or retry endless until wakeup of deye_inverter<br />
if [ ${pause} -eq 0 ]; then<br />
log_error "Error: deye_cli.sh $mode $reg $val failed."<br />
exit 1<br />
fi<br />
<br />
done<br />
</pre><br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
Folgendes Skript starte ich jeden Morgen um 04:00 Uhr per Cron (und in rc.local fürs booten):<br />
<br />
=== deye_mqtt_loop.sh ===<br />
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.<br />
<br />
Ausserdem setzt es beim Start den Tageszähler des Wechselrichters bedarfsweise zurück, bevor die Werte des DEYE Wechselrichters per Endlosschleife abgefragt werden.<br />
<br />
<pre>#!/bin/bash<br />
# reset Daily_Power of DEYE Inverters<br />
# via deye_inverter_mqtt python package<br />
# https://github.com/kbialek/deye-inverter-mqtt<br />
<br />
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin<br />
<br />
debug=<br />
<br />
# config:<br />
deye_inverter_sh=/root/deye-inverter-mqtt/deye_inverter.sh<br />
deye_inverter_mqtt_cmd="python3 $(dirname ${deye_inverter_sh})/deye_docker_entrypoint.py"<br />
deye_inverter_mqtt_cfg=$(dirname ${deye_inverter_sh})/config.env<br />
<br />
myPID=$$<br />
myname=$(basename $0)<br />
<br />
# installation:<br />
# locate script in deye_inverter_mqtt directory<br />
if [ ! -f ${deye_inverter_mqtt_cfg} ]; then<br />
echo "Error: ${deye_inverter_mqtt_cfg} not found."<br />
echo " Please update \$deye_inverter_sh path in $0"<br />
exit 1<br />
fi<br />
<br />
# read my config<br />
set -a; source ${deye_inverter_mqtt_cfg}; set +a<br />
<br />
# ----- functions () ------------------------------------<br />
log_info ()<br />
{<br />
[ "$debug" = "yes" -o "$debug" = "true" ] && echo $myname: $@<br />
[ ${LOG_LEVEL} = "INFO" ] && logger -t $myname $@<br />
}<br />
<br />
log_error ()<br />
{<br />
logger -t $myname $@<br />
echo 1>&2 $@<br />
}<br />
<br />
# store actual date + time into vars<br />
get_date_time ()<br />
{<br />
year=$(date +%y)<br />
month=$(date +%m | sed 's/^0//g')<br />
day=$(date +%d | sed 's/^0//g')<br />
hour=$(date +%H | sed 's/^0//g')<br />
minute=$(date +%m | sed 's/^0//g')<br />
second=$(date +%S | sed 's/^0//g')<br />
}<br />
<br />
# get deye_inverter_mqtt PIDs<br />
get_deye_mqtt_pid ()<br />
{<br />
deye_mqtt_pid=$(ps ax | grep -v grep | grep "${deye_inverter_mqtt_cmd}" | awk '{print $1}')<br />
}<br />
<br />
# kill deye_inverter_loop ()<br />
kill_deye_inverter_loop ()<br />
{<br />
# kill old instances of me<br />
my_PS=$(ps ax | grep -v grep | grep ${myname} | awk '{print $1}')<br />
for ps in $my_PS; do<br />
[ $ps -lt $myPID ] && kill $ps<br />
done<br />
}<br />
<br />
# kill deye_inverter_instance ()<br />
kill_deye_inverter_instance ()<br />
{<br />
get_deye_mqtt_pid<br />
if [ ! -z "${deye_mqtt_pid}" ]; then<br />
kill ${deye_mqtt_pid}<br />
log_info "Info: KILL running deye_mqtt ${deye_mqtt_pid}"<br />
fi<br />
<br />
# kill any running deye_requests<br />
my_PS=$(ps ax | grep -v grep | grep "${deye_inverter_sh}" | awk '{print $1}')<br />
[ ! -z "$my_PS" ] && kill $my_PS<br />
}<br />
<br />
# stop all runnning instances<br />
deye_inverter_mqtt_stop ()<br />
{<br />
kill_deye_inverter_loop<br />
kill_deye_inverter_instance<br />
}<br />
<br />
# start deye_inverter_mqtt<br />
deye_inverter_mqtt_start ()<br />
{<br />
log_info "MQTT start"<br />
${deye_inverter_mqtt_cmd}<br />
}<br />
<br />
# read Daily power register<br />
read_daily_Power ()<br />
{<br />
${deye_inverter_sh} -c 10 -r 60 | sed 's/int: //g' | cut -d, -f1<br />
}<br />
<br />
# wait until 04:00 - 22:00<br />
wait_for_operative_window ()<br />
{<br />
get_date_time<br />
<br />
# calulate time in secs until 04:00<br />
if [ $hour -ge 4 ]; then<br />
((sleep_time=((((28-$hour)*60)-$minute)*60)-$second))<br />
else<br />
((sleep_time=((((4-$hour)*60)-$minute)*60)-$second))<br />
fi<br />
<br />
# wait for operative time<br />
if [ $hour -lt 4 -o $hour -ge 22 ]; then<br />
log_info $sleep_time<br />
sleep $sleep_time<br />
fi<br />
}<br />
<br />
# write actual date/time into deye inverter<br />
reset_deye_inverter ()<br />
{<br />
# wait until deye inverter starts by reading (old) year/month (register 22)<br />
val22_old=$(${deye_inverter_sh} -c 60 -r 22 | sed 's/int: //g' | cut -d, -f1)<br />
log_info "date/time update initialized"<br />
<br />
# read Daily power register<br />
dailyPower=$(read_daily_Power)<br />
<br />
# daily reset phase<br />
daily_reset=init<br />
<br />
# ensure by loop, that all register values are written<br />
while [ ${daily_reset} != "done" ]; do<br />
<br />
# read (old) day<br />
val23_day=$(${deye_inverter_sh} -c 10 -r 23 | sed 's/.*h: //g' | cut -d, -f1)<br />
<br />
# set actual year, month, day, hour, minute, second<br />
get_date_time<br />
<br />
# calculate register 22-24 by date and time<br />
val22=$(( ${year} * 256 + ${month} ))<br />
val23=$(( ${day} * 256 + ${hour} ))<br />
val24=$(( ${minute} * 256 + ${second} ))<br />
<br />
# reset power/date/time only once a day or exit loop<br />
[ "$dailyPower" -eq 0 -a "${val22_old}" = "${val22}" -a "${val23_day}" = "${day}" ] && break<br />
<br />
# reset daily_power by<br />
# setting register 22-24 with actual date + time<br />
${deye_inverter_sh} -w 22 $val22 || continue<br />
${deye_inverter_sh} -w 23 $val23 || continue<br />
${deye_inverter_sh} -w 24 $val24 || continue<br />
<br />
# test success<br />
# read Daily power register, should be resetted to 0 now<br />
dailyPower=$(read_daily_Power)<br />
<br />
# exit loop if power is updated, else log that reset failed<br />
if [ "$dailyPower" -eq 0 ]; then<br />
daily_reset=done<br />
<br />
### skip loop here, reset doesn't work always :-(<br />
# elif [ $daily_reset = "init" ]; then<br />
# daily_reset=failed<br />
# sleep 60<br />
<br />
else<br />
log_info "daily power reset failed"<br />
break<br />
fi<br />
done<br />
log_info "date/time updated"<br />
}<br />
<br />
# ------------------------<br />
# MAIN: Script starts here<br />
# ------------------------<br />
<br />
# stop any runnning deye_inverter_mqtt processes<br />
deye_inverter_mqtt_stop<br />
<br />
# wait until 04:00 - 22:00<br />
wait_for_operative_window<br />
<br />
# setting date/time also resets daily power register<br />
reset_deye_inverter<br />
<br />
# start deye_mqtt_loop<br />
while get_date_time; do<br />
<br />
# wait for next operative window 04:00 - 22:00<br />
[ $hour -lt 4 -o $hour -le 22 ] && \<br />
wait_for_operative_window<br />
<br />
# start deye_inverter_mqtt<br />
get_deye_mqtt_pid<br />
[ -z "$deye_mqtt_pid" ] && deye_inverter_mqtt_start<br />
<br />
done<br />
</pre><br />
<br />
=== Contrab und /etc/rc.local ===<br />
Mein Crontab Eintrag:<br />
<pre>0 4,22 * * * /root/sbin/deye_mqtt_loop.sh 2>/dev/null &</pre><br />
und /etc/rc.local:<br />
<pre># start monitoring of PV-System: DEYE inverter<br />
$(sleep 20; /root/sbin/deye_mqtt_loop.sh 2>dev/null)&<br />
</pre><br />
<br />
== Dashboard ==<br />
So sieht das aus, wenn es den ganzen Tag im April regnet und bedeckt ist:<br />
[[Datei:Solar Bedeckt Regen.jpg|midi]]<br />
<br />
Die Eigenverbrauchsquote habe ich mittels Grafana ermittelt:<br />
[[Datei:Solar Eigenverbrauch.jpg|midi|Eigenverbrauchsquote]]</div>
Neobiker
http://neobiker.de/wiki/index.php?title=DEYE_Wechselrichter&diff=2127
DEYE Wechselrichter
2024-01-07T14:12:41Z
<p>Neobiker: /* deye_mqtt_loop.sh */</p>
<hr />
<div>= Balkonkraftwerk: DEYE Wechselrichter ohne Cloud auslesen und per MQTT monitoren =<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
Der WR bietet verschiedene Möglichkeiten zur Konfiguration und Abfrage der aktuellen Werte:<br />
* einen internen Webserver (http://10.10.10.254)<br />
* die Cloud Anbindung zu Solarman (Solarman Smart App)<br />
* (angepasstes) Modbus Protokoll über Port 8899<br />
* AT+ Befehle über Port 48899<br />
<br />
Ich verwende das Modbus Protokoll zum auslesen des WR, das <br />
# effizient/schnell ist (Performance)<br />
# ohne Internetverbindung auskommt<br />
<br />
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.<br />
<br />
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.<br />
<br />
== Wechselrichter Eigenheiten ==<br />
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. <br />
<br />
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.<br />
<br />
== Installation ==<br />
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.<br />
<pre>git clone https://github.com/kbialek/deye-inverter-mqtt<br />
pip install paho-mqtt<br />
cd deye-inverter-mqtt</pre><br />
<br />
Unter Debian habe ich im Skript '''deye_cli.sh''' python durch python3 ersetzen müssen:<br />
<pre>#!/bin/bash<br />
set -a; source config.env; set +a<br />
python3 deye_cli.py "$@"<br />
</pre><br />
<br />
=== config.env ===<br />
Die Konfiguration meines DEYE Wechselrichters vom Typ Micro-Inverter:<br />
'''config.env'''<br />
<pre>DEYE_LOGGER_IP_ADDRESS=<IP Wechselrichter im WLAN><br />
DEYE_LOGGER_PORT=8899<br />
DEYE_LOGGER_SERIAL_NUMBER=<Seriennummer des WR><br />
<br />
MQTT_HOST=<IP von MQTT Server><br />
MQTT_PORT=1883<br />
MQTT_USERNAME=<br />
MQTT_PASSWORD=<br />
MQTT_TOPIC_PREFIX=deye<br />
<br />
LOG_LEVEL=ERROR<br />
DEYE_DATA_READ_INTERVAL=60<br />
DEYE_METRIC_GROUPS=micro<br />
</pre><br />
<br />
=== deye_inverter.sh === <br />
Ausserdem habe ich ein kleines ''Wrapper Skript'' geschrieben, um '''Daten des WR''' einfacher '''lesen und schreiben''' zu können:<br><br />
<br />
'''deye_inverter.sh''' [--check <pause>] --read <register> | --write <register> <value><br />
<br />
<pre>#!/bin/bash<br />
# Read / Write DEYE Inverters<br />
# via deye_inverter_mqtt python package<br />
# https://github.com/kbialek/deye-inverter-mqtt<br />
<br />
retries=2 # retry deye_inverter command multiple times<br />
sleep=5 # sleep time between retries<br />
<br />
# locate script in deye_inverter_mqtt directory<br />
cd $(dirname $0)<br />
if [ ! -f ./deye_cli.sh ]; then<br />
echo "Error: ./deye_cli.sh not found."<br />
echo " Please move $(basename $0) in deye-inverter-mqtt directory."<br />
exit 1<br />
fi<br />
. ./config.env<br />
<br />
log_info ()<br />
{<br />
if [ ${LOG_LEVEL} = "INFO" ]; then<br />
logger -t $(basename $0) $@<br />
fi<br />
}<br />
<br />
log_error ()<br />
{<br />
logger -t $(basename $0) $@<br />
echo 2>&1 $@<br />
}<br />
<br />
# read parameters: mode [rw], register, value and optional -c <check pause><br />
pause=0<br />
while [ $# -gt 0 ]; do<br />
case $1 in<br />
-c|--check) shift<br />
[ $# -ge 3 ] || exit 1<br />
pause=$1<br />
;;<br />
-w|--write) mode=w<br />
shift<br />
[ $# -eq 2 ] || exit 1<br />
reg=$1<br />
val=$2<br />
shift<br />
;;<br />
-r|--read) mode=r<br />
shift<br />
[ $# -eq 1 ] || exit 1<br />
reg=$1<br />
;;<br />
esac<br />
shift<br />
done<br />
<br />
# handle offline deye_inverter by<br />
# ${pause} > 0 -> endless loop<br />
# ${pause} = 0 -> error after ${retries}<br />
while true; do<br />
<br />
# try deye_cli.sh $retries times every $sleep secs<br />
check=${retries}<br />
while [ ${check} -gt 0 ]; do<br />
<br />
case ${mode} in<br />
r) result=$(./deye_cli.sh $mode $reg | grep 'int: ')<br />
if [ -n "$result" ]; then<br />
echo "$result"<br />
log_info -t $0 deye_cli.sh $mode $reg<br />
exit 0<br />
fi<br />
;;<br />
w) result=$(./deye_cli.sh $mode $reg $val)<br />
if [ "$result" = "Ok" ]; then<br />
log_info -t $0 deye_cli.sh $mode $reg $val<br />
exit 0<br />
fi<br />
;;<br />
esac<br />
<br />
# wait ${sleep} or ${pause} secs<br />
sleep $(( ${pause} ? ${sleep} : ${pause} ))<br />
<br />
# try ${retries} times<br />
(( check-- ))<br />
done<br />
<br />
# error, or retry endless until wakeup of deye_inverter<br />
if [ ${pause} -eq 0 ]; then<br />
log_error "Error: deye_cli.sh $mode $reg $val failed."<br />
exit 1<br />
fi<br />
<br />
done<br />
</pre><br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
Folgendes Skript starte ich jeden Morgen um 04:00 Uhr per Cron (und in rc.local fürs booten):<br />
<br />
=== deye_mqtt_loop.sh ===<br />
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.<br />
<br />
Ausserdem setzt es beim Start den Tageszähler des Wechselrichters bedarfsweise zurück, bevor die Werte des DEYE Wechselrichters per Endlosschleife abgefragt werden.<br />
<br />
<pre>#!/bin/bash<br />
# reset Daily_Power of DEYE Inverters<br />
# via deye_inverter_mqtt python package<br />
# https://github.com/kbialek/deye-inverter-mqtt<br />
<br />
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin<br />
<br />
debug=<br />
<br />
# config:<br />
deye_inverter_sh=/root/deye-inverter-mqtt/deye_inverter.sh<br />
deye_inverter_mqtt_cmd="python3 $(dirname ${deye_inverter_sh})/deye_docker_entrypoint.py"<br />
deye_inverter_mqtt_cfg=$(dirname ${deye_inverter_sh})/config.env<br />
<br />
myPID=$$<br />
myname=$(basename $0)<br />
<br />
# installation:<br />
# locate script in deye_inverter_mqtt directory<br />
if [ ! -f ${deye_inverter_mqtt_cfg} ]; then<br />
echo "Error: ${deye_inverter_mqtt_cfg} not found."<br />
echo " Please update \$deye_inverter_sh path in $0"<br />
exit 1<br />
fi<br />
<br />
# read my config<br />
set -a; source ${deye_inverter_mqtt_cfg}; set +a<br />
<br />
# ----- functions () ------------------------------------<br />
log_info ()<br />
{<br />
[ "$debug" = "yes" -o "$debug" = "true" ] && echo $myname: $@<br />
[ ${LOG_LEVEL} = "INFO" ] && logger -t $myname $@<br />
}<br />
<br />
log_error ()<br />
{<br />
logger -t $myname $@<br />
echo 1>&2 $@<br />
}<br />
<br />
# store actual date + time into vars<br />
get_date_time ()<br />
{<br />
year=$(date +%y)<br />
month=$(date +%m | sed 's/^0//g')<br />
day=$(date +%d | sed 's/^0//g')<br />
hour=$(date +%H | sed 's/^0//g')<br />
minute=$(date +%m | sed 's/^0//g')<br />
second=$(date +%S | sed 's/^0//g')<br />
}<br />
<br />
# get deye_inverter_mqtt PIDs<br />
get_deye_mqtt_pid ()<br />
{<br />
deye_mqtt_pid=$(ps ax | grep -v grep | grep "${deye_inverter_mqtt_cmd}" | awk '{print $1}')<br />
}<br />
<br />
# kill deye_inverter_loop ()<br />
kill_deye_inverter_loop ()<br />
{<br />
# kill old instances of me<br />
my_PS=$(ps ax | grep -v grep | grep ${myname} | awk '{print $1}')<br />
for ps in $my_PS; do<br />
[ $ps -lt $myPID ] && kill $ps<br />
done<br />
}<br />
<br />
# kill deye_inverter_instance ()<br />
kill_deye_inverter_instance ()<br />
{<br />
get_deye_mqtt_pid<br />
if [ ! -z "${deye_mqtt_pid}" ]; then<br />
kill ${deye_mqtt_pid}<br />
log_info "Info: KILL running deye_mqtt ${deye_mqtt_pid}"<br />
fi<br />
<br />
# kill any running deye_requests<br />
my_PS=$(ps ax | grep -v grep | grep "${deye_inverter_sh}" | awk '{print $1}')<br />
[ ! -z "$my_PS" ] && kill $my_PS<br />
}<br />
<br />
# stop all runnning instances<br />
deye_inverter_mqtt_stop ()<br />
{<br />
kill_deye_inverter_loop<br />
kill_deye_inverter_instance<br />
}<br />
<br />
# start deye_inverter_mqtt<br />
deye_inverter_mqtt_start ()<br />
{<br />
log_info "MQTT start"<br />
${deye_inverter_mqtt_cmd}<br />
}<br />
<br />
# read Daily power register<br />
read_daily_Power ()<br />
{<br />
${deye_inverter_sh} -c 10 -r 60 | sed 's/int: //g' | cut -d, -f1<br />
}<br />
<br />
# wait until 04:00 - 22:00<br />
wait_for_operative_window ()<br />
{<br />
get_date_time<br />
<br />
# calulate time in secs until 04:00<br />
if [ $hour -ge 4 ]; then<br />
((sleep_time=((((28-$hour)*60)-$minute)*60)-$second))<br />
else<br />
((sleep_time=((((4-$hour)*60)-$minute)*60)-$second))<br />
fi<br />
<br />
# wait for operative time<br />
if [ $hour -lt 4 -o $hour -ge 22 ]; then<br />
log_info $sleep_time<br />
sleep $sleep_time<br />
fi<br />
}<br />
<br />
# write actual date/time into deye inverter<br />
reset_deye_inverter ()<br />
{<br />
# wait until deye inverter starts by reading (old) year/month (register 22)<br />
val22_old=$(${deye_inverter_sh} -c 60 -r 22 | sed 's/int: //g' | cut -d, -f1)<br />
log_info "date/time update initialized"<br />
<br />
# read Daily power register<br />
dailyPower=$(read_daily_Power)<br />
<br />
# daily reset phase<br />
daily_reset=init<br />
<br />
# ensure by loop, that all register values are written<br />
while [ ${daily_reset} != "done" ]; do<br />
<br />
# read (old) day<br />
val23_day=$(${deye_inverter_sh} -c 10 -r 23 | sed 's/.*h: //g' | cut -d, -f1)<br />
<br />
# set actual year, month, day, hour, minute, second<br />
get_date_time<br />
<br />
# calculate register 22-24 by date and time<br />
val22=$(( ${year} * 256 + ${month} ))<br />
val23=$(( ${day} * 256 + ${hour} ))<br />
val24=$(( ${minute} * 256 + ${second} ))<br />
<br />
# reset power/date/time only once a day or exit loop<br />
[ "$dailyPower" -eq 0 -a "${val22_old}" = "${val22}" -a "${val23_day}" = "${day}" ] && break<br />
<br />
# reset daily_power by<br />
# setting register 22-24 with actual date + time<br />
${deye_inverter_sh} -w 22 $val22 || continue<br />
${deye_inverter_sh} -w 23 $val23 || continue<br />
${deye_inverter_sh} -w 24 $val24 || continue<br />
<br />
# test success<br />
# read Daily power register, should be resetted to 0 now<br />
dailyPower=$(read_daily_Power)<br />
<br />
# exit loop if power is updated, else log that reset failed<br />
if [ "$dailyPower" -eq 0 ]; then<br />
daily_reset=done<br />
<br />
### skip loop here, reset doesn't work always :-(<br />
# elif [ $daily_reset = "init" ]; then<br />
# daily_reset=failed<br />
# sleep 60<br />
<br />
else<br />
log_info "daily power reset failed"<br />
break<br />
fi<br />
done<br />
log_info "date/time updated"<br />
}<br />
<br />
# ------------------------<br />
# MAIN: Script starts here<br />
# ------------------------<br />
<br />
# stop any runnning deye_inverter_mqtt processes<br />
deye_inverter_mqtt_stop<br />
<br />
# wait until 04:00 - 22:00<br />
wait_for_operative_window<br />
<br />
# setting date/time also resets daily power register<br />
reset_deye_inverter<br />
<br />
# start deye_mqtt_loop<br />
while get_date_time; do<br />
<br />
# wait for next operative window 04:00 - 22:00<br />
[ $hour -lt 4 -o $hour -le 22 ] && \<br />
wait_for_operative_window<br />
<br />
# start deye_inverter_mqtt<br />
get_deye_mqtt_pid<br />
[ -z "$deye_mqtt_pid" ] && deye_inverter_mqtt_start<br />
<br />
done<br />
</pre><br />
<br />
=== Contrab und /etc/rc.local ===<br />
Mein Crontab Eintrag:<br />
<pre>0 4 * * * /root/sbin/deye_mqtt_loop.sh 2>/dev/null &</pre><br />
und /etc/rc.local:<br />
<pre># start monitoring of PV-System: DEYE inverter<br />
$(sleep 20; /root/sbin/deye_mqtt_loop.sh 2>dev/null)&<br />
</pre><br />
<br />
== Dashboard ==<br />
So sieht das aus, wenn es den ganzen Tag im April regnet und bedeckt ist:<br />
[[Datei:Solar Bedeckt Regen.jpg|midi]]<br />
<br />
Die Eigenverbrauchsquote habe ich mittels Grafana ermittelt:<br />
[[Datei:Solar Eigenverbrauch.jpg|midi|Eigenverbrauchsquote]]</div>
Neobiker
http://neobiker.de/wiki/index.php?title=DEYE_Wechselrichter&diff=2126
DEYE Wechselrichter
2024-01-07T14:09:05Z
<p>Neobiker: /* deye_mqtt_loop.sh */</p>
<hr />
<div>= Balkonkraftwerk: DEYE Wechselrichter ohne Cloud auslesen und per MQTT monitoren =<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
Der WR bietet verschiedene Möglichkeiten zur Konfiguration und Abfrage der aktuellen Werte:<br />
* einen internen Webserver (http://10.10.10.254)<br />
* die Cloud Anbindung zu Solarman (Solarman Smart App)<br />
* (angepasstes) Modbus Protokoll über Port 8899<br />
* AT+ Befehle über Port 48899<br />
<br />
Ich verwende das Modbus Protokoll zum auslesen des WR, das <br />
# effizient/schnell ist (Performance)<br />
# ohne Internetverbindung auskommt<br />
<br />
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.<br />
<br />
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.<br />
<br />
== Wechselrichter Eigenheiten ==<br />
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. <br />
<br />
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.<br />
<br />
== Installation ==<br />
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.<br />
<pre>git clone https://github.com/kbialek/deye-inverter-mqtt<br />
pip install paho-mqtt<br />
cd deye-inverter-mqtt</pre><br />
<br />
Unter Debian habe ich im Skript '''deye_cli.sh''' python durch python3 ersetzen müssen:<br />
<pre>#!/bin/bash<br />
set -a; source config.env; set +a<br />
python3 deye_cli.py "$@"<br />
</pre><br />
<br />
=== config.env ===<br />
Die Konfiguration meines DEYE Wechselrichters vom Typ Micro-Inverter:<br />
'''config.env'''<br />
<pre>DEYE_LOGGER_IP_ADDRESS=<IP Wechselrichter im WLAN><br />
DEYE_LOGGER_PORT=8899<br />
DEYE_LOGGER_SERIAL_NUMBER=<Seriennummer des WR><br />
<br />
MQTT_HOST=<IP von MQTT Server><br />
MQTT_PORT=1883<br />
MQTT_USERNAME=<br />
MQTT_PASSWORD=<br />
MQTT_TOPIC_PREFIX=deye<br />
<br />
LOG_LEVEL=ERROR<br />
DEYE_DATA_READ_INTERVAL=60<br />
DEYE_METRIC_GROUPS=micro<br />
</pre><br />
<br />
=== deye_inverter.sh === <br />
Ausserdem habe ich ein kleines ''Wrapper Skript'' geschrieben, um '''Daten des WR''' einfacher '''lesen und schreiben''' zu können:<br><br />
<br />
'''deye_inverter.sh''' [--check <pause>] --read <register> | --write <register> <value><br />
<br />
<pre>#!/bin/bash<br />
# Read / Write DEYE Inverters<br />
# via deye_inverter_mqtt python package<br />
# https://github.com/kbialek/deye-inverter-mqtt<br />
<br />
retries=2 # retry deye_inverter command multiple times<br />
sleep=5 # sleep time between retries<br />
<br />
# locate script in deye_inverter_mqtt directory<br />
cd $(dirname $0)<br />
if [ ! -f ./deye_cli.sh ]; then<br />
echo "Error: ./deye_cli.sh not found."<br />
echo " Please move $(basename $0) in deye-inverter-mqtt directory."<br />
exit 1<br />
fi<br />
. ./config.env<br />
<br />
log_info ()<br />
{<br />
if [ ${LOG_LEVEL} = "INFO" ]; then<br />
logger -t $(basename $0) $@<br />
fi<br />
}<br />
<br />
log_error ()<br />
{<br />
logger -t $(basename $0) $@<br />
echo 2>&1 $@<br />
}<br />
<br />
# read parameters: mode [rw], register, value and optional -c <check pause><br />
pause=0<br />
while [ $# -gt 0 ]; do<br />
case $1 in<br />
-c|--check) shift<br />
[ $# -ge 3 ] || exit 1<br />
pause=$1<br />
;;<br />
-w|--write) mode=w<br />
shift<br />
[ $# -eq 2 ] || exit 1<br />
reg=$1<br />
val=$2<br />
shift<br />
;;<br />
-r|--read) mode=r<br />
shift<br />
[ $# -eq 1 ] || exit 1<br />
reg=$1<br />
;;<br />
esac<br />
shift<br />
done<br />
<br />
# handle offline deye_inverter by<br />
# ${pause} > 0 -> endless loop<br />
# ${pause} = 0 -> error after ${retries}<br />
while true; do<br />
<br />
# try deye_cli.sh $retries times every $sleep secs<br />
check=${retries}<br />
while [ ${check} -gt 0 ]; do<br />
<br />
case ${mode} in<br />
r) result=$(./deye_cli.sh $mode $reg | grep 'int: ')<br />
if [ -n "$result" ]; then<br />
echo "$result"<br />
log_info -t $0 deye_cli.sh $mode $reg<br />
exit 0<br />
fi<br />
;;<br />
w) result=$(./deye_cli.sh $mode $reg $val)<br />
if [ "$result" = "Ok" ]; then<br />
log_info -t $0 deye_cli.sh $mode $reg $val<br />
exit 0<br />
fi<br />
;;<br />
esac<br />
<br />
# wait ${sleep} or ${pause} secs<br />
sleep $(( ${pause} ? ${sleep} : ${pause} ))<br />
<br />
# try ${retries} times<br />
(( check-- ))<br />
done<br />
<br />
# error, or retry endless until wakeup of deye_inverter<br />
if [ ${pause} -eq 0 ]; then<br />
log_error "Error: deye_cli.sh $mode $reg $val failed."<br />
exit 1<br />
fi<br />
<br />
done<br />
</pre><br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
Folgendes Skript starte ich jeden Morgen um 04:00 Uhr per Cron (und in rc.local fürs booten):<br />
<br />
=== deye_mqtt_loop.sh ===<br />
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.<br />
<br />
Ausserdem setzt es beim Start den Tageszähler des Wechselrichters bedarfsweise zurück, bevor die Werte des DEYE Wechselrichters per Endlosschleife abgefragt werden.<br />
<br />
<pre>#!/bin/bash<br />
# reset Daily_Power of DEYE Inverters<br />
# via deye_inverter_mqtt python package<br />
# https://github.com/kbialek/deye-inverter-mqtt<br />
<br />
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin<br />
<br />
debug=<br />
<br />
# config:<br />
deye_inverter_sh=/root/deye-inverter-mqtt/deye_inverter.sh<br />
deye_inverter_mqtt_cmd="python3 $(dirname ${deye_inverter_sh})/deye_docker_entrypoint.py"<br />
deye_inverter_mqtt_cfg=$(dirname ${deye_inverter_sh})/config.env<br />
<br />
myPID=$$<br />
myname=$(basename $0)<br />
<br />
# installation:<br />
# locate script in deye_inverter_mqtt directory<br />
if [ ! -f ${deye_inverter_mqtt_cfg} ]; then<br />
echo "Error: ${deye_inverter_mqtt_cfg} not found."<br />
echo " Please update \$deye_inverter_sh path in $0"<br />
exit 1<br />
fi<br />
<br />
# read my config<br />
set -a; source ${deye_inverter_mqtt_cfg}; set +a<br />
<br />
# ----- functions () ------------------------------------<br />
log_info ()<br />
{<br />
[ "$debug" = "yes" -o "$debug" = "true" ] && echo $myname: $@<br />
[ ${LOG_LEVEL} = "INFO" ] && logger -t $myname $@<br />
}<br />
<br />
log_error ()<br />
{<br />
logger -t $myname $@<br />
echo 1>&2 $@<br />
}<br />
<br />
# store actual date + time into vars<br />
get_date_time ()<br />
{<br />
year=$(date +%y)<br />
month=$(date +%m | sed 's/^0//g')<br />
day=$(date +%d | sed 's/^0//g')<br />
hour=$(date +%H | sed 's/^0//g')<br />
minute=$(date +%m | sed 's/^0//g')<br />
second=$(date +%S | sed 's/^0//g')<br />
}<br />
<br />
# get deye_inverter_mqtt PIDs<br />
get_deye_mqtt_pid ()<br />
{<br />
deye_mqtt_pid=$(ps ax | grep -v grep | grep "${deye_inverter_mqtt_cmd}" | awk '{print $1}')<br />
}<br />
<br />
# kill deye_inverter_loop ()<br />
kill_deye_inverter_loop ()<br />
{<br />
# kill old instances of me<br />
my_PS=$(ps ax | grep -v grep | grep ${myname} | awk '{print $1}')<br />
for ps in $my_PS; do<br />
[ $ps -lt $myPID ] && kill $ps<br />
done<br />
}<br />
<br />
# kill deye_inverter_instance ()<br />
kill_deye_inverter_instance ()<br />
{<br />
get_deye_mqtt_pid<br />
if [ ! -z "${deye_mqtt_pid}" ]; then<br />
kill ${deye_mqtt_pid}<br />
log_info "Info: KILL running deye_mqtt ${deye_mqtt_pid}"<br />
fi<br />
<br />
# kill any running deye_requests<br />
my_PS=$(ps ax | grep -v grep | grep "${deye_inverter_sh}" | awk '{print $1}')<br />
[ ! -z "$my_PS" ] && kill $my_PS<br />
}<br />
<br />
# stop all runnning instances<br />
deye_inverter_mqtt_stop ()<br />
{<br />
kill_deye_inverter_loop<br />
kill_deye_inverter_instance<br />
}<br />
<br />
# start deye_inverter_mqtt<br />
deye_inverter_mqtt_start ()<br />
{<br />
log_info "MQTT start"<br />
${deye_inverter_mqtt_cmd}<br />
}<br />
<br />
# read Daily power register<br />
read_daily_Power ()<br />
{<br />
${deye_inverter_sh} -c 10 -r 60 | sed 's/int: //g' | cut -d, -f1<br />
}<br />
<br />
# wait until 04:00 - 22:00<br />
wait_for_operative_window ()<br />
{<br />
get_date_time<br />
<br />
# calulate time in secs until 04:00<br />
if [ $hour -ge 4 ]; then<br />
((sleep_time=((((28-$hour)*60)-$minute)*60)-$second))<br />
else<br />
((sleep_time=((((4-$hour)*60)-$minute)*60)-$second))<br />
fi<br />
<br />
# wait for operative time<br />
if [ $hour -lt 4 -o $hour -ge 22 ]; then<br />
log_info $sleep_time<br />
sleep $sleep_time<br />
fi<br />
}<br />
<br />
# write actual date/time into deye inverter<br />
reset_deye_inverter ()<br />
{<br />
# wait until deye inverter starts by reading (old) year/month (register 22)<br />
val22_old=$(${deye_inverter_sh} -c 60 -r 22 | sed 's/int: //g' | cut -d, -f1)<br />
log_info "date/time update initialized"<br />
<br />
# read Daily power register<br />
dailyPower=$(read_daily_Power)<br />
<br />
# daily reset phase<br />
daily_reset=init<br />
<br />
# ensure by loop, that all register values are written<br />
while [ ${daily_reset} != "done" ]; do<br />
<br />
# read (old) day<br />
val23_day=$(${deye_inverter_sh} -c 10 -r 23 | sed 's/.*h: //g' | cut -d, -f1)<br />
<br />
# set actual year, month, day, hour, minute, second<br />
get_date_time<br />
<br />
# calculate register 22-24 by date and time<br />
val22=$(( ${year} * 256 + ${month} ))<br />
val23=$(( ${day} * 256 + ${hour} ))<br />
val24=$(( ${minute} * 256 + ${second} ))<br />
<br />
# reset power/date/time only once a day or exit loop<br />
[ "$dailyPower" -eq 0 -a "${val22_old}" = "${val22}" -a "${val23_day}" = "${day}" ] && break<br />
<br />
# reset daily_power by<br />
# setting register 22-24 with actual date + time<br />
${deye_inverter_sh} -w 22 $val22 || continue<br />
${deye_inverter_sh} -w 23 $val23 || continue<br />
${deye_inverter_sh} -w 24 $val24 || continue<br />
<br />
# test success<br />
# read Daily power register, should be resetted to 0 now<br />
dailyPower=$(read_daily_Power)<br />
<br />
# exit loop if power is updated, else log that reset failed<br />
if [ "$dailyPower" -eq 0 ]; then<br />
daily_reset=done<br />
<br />
### skip loop here, reset doesn't work always :-(<br />
# elif [ $daily_reset = "init" ]; then<br />
# daily_reset=failed<br />
# sleep 60<br />
<br />
else<br />
log_info "daily power reset failed"<br />
break<br />
fi<br />
done<br />
log_info "date/time updated"<br />
}<br />
<br />
# ------------------------<br />
# MAIN: Script starts here<br />
# ------------------------<br />
<br />
# stop any runnning deye_inverter_mqtt processes<br />
deye_inverter_mqtt_stop<br />
<br />
# wait until 04:00 - 22:00<br />
wait_for_operative_window<br />
<br />
# setting sate/time also resets daily power register<br />
reset_deye_inverter<br />
<br />
# start deye_mqtt_loop<br />
while get_date_time; do<br />
<br />
# wait for next operative window 04:00 - 22:00<br />
[ $hour -lt 4 -o $hour -le 22 ] && \<br />
wait_for_operative_window<br />
<br />
# start deye_inverter_mqtt<br />
get_deye_mqtt_pid<br />
[ -z "$deye_mqtt_pid" ] && deye_inverter_mqtt_start<br />
<br />
done<br />
</pre><br />
<br />
=== Contrab und /etc/rc.local ===<br />
Mein Crontab Eintrag:<br />
<pre>0 4 * * * /root/sbin/deye_mqtt_loop.sh 2>/dev/null &</pre><br />
und /etc/rc.local:<br />
<pre># start monitoring of PV-System: DEYE inverter<br />
$(sleep 20; /root/sbin/deye_mqtt_loop.sh 2>dev/null)&<br />
</pre><br />
<br />
== Dashboard ==<br />
So sieht das aus, wenn es den ganzen Tag im April regnet und bedeckt ist:<br />
[[Datei:Solar Bedeckt Regen.jpg|midi]]<br />
<br />
Die Eigenverbrauchsquote habe ich mittels Grafana ermittelt:<br />
[[Datei:Solar Eigenverbrauch.jpg|midi|Eigenverbrauchsquote]]</div>
Neobiker
http://neobiker.de/wiki/index.php?title=DEYE_Wechselrichter&diff=2125
DEYE Wechselrichter
2024-01-07T14:06:21Z
<p>Neobiker: /* deye_mqtt_loop.sh */</p>
<hr />
<div>= Balkonkraftwerk: DEYE Wechselrichter ohne Cloud auslesen und per MQTT monitoren =<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
Der WR bietet verschiedene Möglichkeiten zur Konfiguration und Abfrage der aktuellen Werte:<br />
* einen internen Webserver (http://10.10.10.254)<br />
* die Cloud Anbindung zu Solarman (Solarman Smart App)<br />
* (angepasstes) Modbus Protokoll über Port 8899<br />
* AT+ Befehle über Port 48899<br />
<br />
Ich verwende das Modbus Protokoll zum auslesen des WR, das <br />
# effizient/schnell ist (Performance)<br />
# ohne Internetverbindung auskommt<br />
<br />
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.<br />
<br />
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.<br />
<br />
== Wechselrichter Eigenheiten ==<br />
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. <br />
<br />
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.<br />
<br />
== Installation ==<br />
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.<br />
<pre>git clone https://github.com/kbialek/deye-inverter-mqtt<br />
pip install paho-mqtt<br />
cd deye-inverter-mqtt</pre><br />
<br />
Unter Debian habe ich im Skript '''deye_cli.sh''' python durch python3 ersetzen müssen:<br />
<pre>#!/bin/bash<br />
set -a; source config.env; set +a<br />
python3 deye_cli.py "$@"<br />
</pre><br />
<br />
=== config.env ===<br />
Die Konfiguration meines DEYE Wechselrichters vom Typ Micro-Inverter:<br />
'''config.env'''<br />
<pre>DEYE_LOGGER_IP_ADDRESS=<IP Wechselrichter im WLAN><br />
DEYE_LOGGER_PORT=8899<br />
DEYE_LOGGER_SERIAL_NUMBER=<Seriennummer des WR><br />
<br />
MQTT_HOST=<IP von MQTT Server><br />
MQTT_PORT=1883<br />
MQTT_USERNAME=<br />
MQTT_PASSWORD=<br />
MQTT_TOPIC_PREFIX=deye<br />
<br />
LOG_LEVEL=ERROR<br />
DEYE_DATA_READ_INTERVAL=60<br />
DEYE_METRIC_GROUPS=micro<br />
</pre><br />
<br />
=== deye_inverter.sh === <br />
Ausserdem habe ich ein kleines ''Wrapper Skript'' geschrieben, um '''Daten des WR''' einfacher '''lesen und schreiben''' zu können:<br><br />
<br />
'''deye_inverter.sh''' [--check <pause>] --read <register> | --write <register> <value><br />
<br />
<pre>#!/bin/bash<br />
# Read / Write DEYE Inverters<br />
# via deye_inverter_mqtt python package<br />
# https://github.com/kbialek/deye-inverter-mqtt<br />
<br />
retries=2 # retry deye_inverter command multiple times<br />
sleep=5 # sleep time between retries<br />
<br />
# locate script in deye_inverter_mqtt directory<br />
cd $(dirname $0)<br />
if [ ! -f ./deye_cli.sh ]; then<br />
echo "Error: ./deye_cli.sh not found."<br />
echo " Please move $(basename $0) in deye-inverter-mqtt directory."<br />
exit 1<br />
fi<br />
. ./config.env<br />
<br />
log_info ()<br />
{<br />
if [ ${LOG_LEVEL} = "INFO" ]; then<br />
logger -t $(basename $0) $@<br />
fi<br />
}<br />
<br />
log_error ()<br />
{<br />
logger -t $(basename $0) $@<br />
echo 2>&1 $@<br />
}<br />
<br />
# read parameters: mode [rw], register, value and optional -c <check pause><br />
pause=0<br />
while [ $# -gt 0 ]; do<br />
case $1 in<br />
-c|--check) shift<br />
[ $# -ge 3 ] || exit 1<br />
pause=$1<br />
;;<br />
-w|--write) mode=w<br />
shift<br />
[ $# -eq 2 ] || exit 1<br />
reg=$1<br />
val=$2<br />
shift<br />
;;<br />
-r|--read) mode=r<br />
shift<br />
[ $# -eq 1 ] || exit 1<br />
reg=$1<br />
;;<br />
esac<br />
shift<br />
done<br />
<br />
# handle offline deye_inverter by<br />
# ${pause} > 0 -> endless loop<br />
# ${pause} = 0 -> error after ${retries}<br />
while true; do<br />
<br />
# try deye_cli.sh $retries times every $sleep secs<br />
check=${retries}<br />
while [ ${check} -gt 0 ]; do<br />
<br />
case ${mode} in<br />
r) result=$(./deye_cli.sh $mode $reg | grep 'int: ')<br />
if [ -n "$result" ]; then<br />
echo "$result"<br />
log_info -t $0 deye_cli.sh $mode $reg<br />
exit 0<br />
fi<br />
;;<br />
w) result=$(./deye_cli.sh $mode $reg $val)<br />
if [ "$result" = "Ok" ]; then<br />
log_info -t $0 deye_cli.sh $mode $reg $val<br />
exit 0<br />
fi<br />
;;<br />
esac<br />
<br />
# wait ${sleep} or ${pause} secs<br />
sleep $(( ${pause} ? ${sleep} : ${pause} ))<br />
<br />
# try ${retries} times<br />
(( check-- ))<br />
done<br />
<br />
# error, or retry endless until wakeup of deye_inverter<br />
if [ ${pause} -eq 0 ]; then<br />
log_error "Error: deye_cli.sh $mode $reg $val failed."<br />
exit 1<br />
fi<br />
<br />
done<br />
</pre><br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
Folgendes Skript starte ich jeden Morgen um 04:00 Uhr per Cron (und in rc.local fürs booten):<br />
<br />
=== deye_mqtt_loop.sh ===<br />
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.<br />
<br />
Ausserdem setzt es beim Start den Tageszähler des Wechselrichters bedarfsweise zurück, bevor die Werte per Endlosschleife abgefragt werden.<br />
<br />
<pre>#!/bin/bash<br />
# reset Daily_Power of DEYE Inverters<br />
# via deye_inverter_mqtt python package<br />
# https://github.com/kbialek/deye-inverter-mqtt<br />
<br />
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin<br />
<br />
debug=<br />
<br />
# config:<br />
deye_inverter_sh=/root/deye-inverter-mqtt/deye_inverter.sh<br />
deye_inverter_mqtt_cmd="python3 $(dirname ${deye_inverter_sh})/deye_docker_entrypoint.py"<br />
deye_inverter_mqtt_cfg=$(dirname ${deye_inverter_sh})/config.env<br />
<br />
myPID=$$<br />
myname=$(basename $0)<br />
<br />
# installation:<br />
# locate script in deye_inverter_mqtt directory<br />
if [ ! -f ${deye_inverter_mqtt_cfg} ]; then<br />
echo "Error: ${deye_inverter_mqtt_cfg} not found."<br />
echo " Please update \$deye_inverter_sh path in $0"<br />
exit 1<br />
fi<br />
<br />
# read my config<br />
set -a; source ${deye_inverter_mqtt_cfg}; set +a<br />
<br />
# ----- functions () ------------------------------------<br />
log_info ()<br />
{<br />
[ "$debug" = "yes" -o "$debug" = "true" ] && echo $myname: $@<br />
[ ${LOG_LEVEL} = "INFO" ] && logger -t $myname $@<br />
}<br />
<br />
log_error ()<br />
{<br />
logger -t $myname $@<br />
echo 1>&2 $@<br />
}<br />
<br />
# store actual date + time into vars<br />
get_date_time ()<br />
{<br />
year=$(date +%y)<br />
month=$(date +%m | sed 's/^0//g')<br />
day=$(date +%d | sed 's/^0//g')<br />
hour=$(date +%H | sed 's/^0//g')<br />
minute=$(date +%m | sed 's/^0//g')<br />
second=$(date +%S | sed 's/^0//g')<br />
}<br />
<br />
# get deye_inverter_mqtt PIDs<br />
get_deye_mqtt_pid ()<br />
{<br />
deye_mqtt_pid=$(ps ax | grep -v grep | grep "${deye_inverter_mqtt_cmd}" | awk '{print $1}')<br />
}<br />
<br />
# kill deye_inverter_loop ()<br />
kill_deye_inverter_loop ()<br />
{<br />
# kill old instances of me<br />
my_PS=$(ps ax | grep -v grep | grep ${myname} | awk '{print $1}')<br />
for ps in $my_PS; do<br />
[ $ps -lt $myPID ] && kill $ps<br />
done<br />
}<br />
<br />
# kill deye_inverter_instance ()<br />
kill_deye_inverter_instance ()<br />
{<br />
get_deye_mqtt_pid<br />
if [ ! -z "${deye_mqtt_pid}" ]; then<br />
kill ${deye_mqtt_pid}<br />
log_info "Info: KILL running deye_mqtt ${deye_mqtt_pid}"<br />
fi<br />
<br />
# kill any running deye_requests<br />
my_PS=$(ps ax | grep -v grep | grep "${deye_inverter_sh}" | awk '{print $1}')<br />
[ ! -z "$my_PS" ] && kill $my_PS<br />
}<br />
<br />
# stop all runnning instances<br />
deye_inverter_mqtt_stop ()<br />
{<br />
kill_deye_inverter_loop<br />
kill_deye_inverter_instance<br />
}<br />
<br />
# start deye_inverter_mqtt<br />
deye_inverter_mqtt_start ()<br />
{<br />
log_info "MQTT start"<br />
${deye_inverter_mqtt_cmd}<br />
}<br />
<br />
# read Daily power register<br />
read_daily_Power ()<br />
{<br />
${deye_inverter_sh} -c 10 -r 60 | sed 's/int: //g' | cut -d, -f1<br />
}<br />
<br />
# wait until 04:00 - 22:00<br />
wait_for_operative_window ()<br />
{<br />
get_date_time<br />
<br />
# calulate time in secs until 04:00<br />
if [ $hour -ge 4 ]; then<br />
((sleep_time=((((28-$hour)*60)-$minute)*60)-$second))<br />
else<br />
((sleep_time=((((4-$hour)*60)-$minute)*60)-$second))<br />
fi<br />
<br />
# wait for operative time<br />
if [ $hour -lt 4 -o $hour -ge 22 ]; then<br />
log_info $sleep_time<br />
sleep $sleep_time<br />
fi<br />
}<br />
<br />
# write actual date/time into deye inverter<br />
reset_deye_inverter ()<br />
{<br />
# wait until deye inverter starts by reading (old) year/month (register 22)<br />
val22_old=$(${deye_inverter_sh} -c 60 -r 22 | sed 's/int: //g' | cut -d, -f1)<br />
log_info "date/time update initialized"<br />
<br />
# read Daily power register<br />
dailyPower=$(read_daily_Power)<br />
<br />
# daily reset phase<br />
daily_reset=init<br />
<br />
# ensure by loop, that all register values are written<br />
while [ ${daily_reset} != "done" ]; do<br />
<br />
# read (old) day<br />
val23_day=$(${deye_inverter_sh} -c 10 -r 23 | sed 's/.*h: //g' | cut -d, -f1)<br />
<br />
# set actual year, month, day, hour, minute, second<br />
get_date_time<br />
<br />
# calculate register 22-24 by date and time<br />
val22=$(( ${year} * 256 + ${month} ))<br />
val23=$(( ${day} * 256 + ${hour} ))<br />
val24=$(( ${minute} * 256 + ${second} ))<br />
<br />
# reset power/date/time only once a day or exit loop<br />
[ "$dailyPower" -eq 0 -a "${val22_old}" = "${val22}" -a "${val23_day}" = "${day}" ] && break<br />
<br />
# reset daily_power by<br />
# setting register 22-24 with actual date + time<br />
${deye_inverter_sh} -w 22 $val22 || continue<br />
${deye_inverter_sh} -w 23 $val23 || continue<br />
${deye_inverter_sh} -w 24 $val24 || continue<br />
<br />
# test success<br />
# read Daily power register, should be resetted to 0 now<br />
dailyPower=$(read_daily_Power)<br />
<br />
# exit loop if power is updated, else log that reset failed<br />
if [ "$dailyPower" -eq 0 ]; then<br />
daily_reset=done<br />
<br />
### skip loop here, reset doesn't work always :-(<br />
# elif [ $daily_reset = "init" ]; then<br />
# daily_reset=failed<br />
# sleep 60<br />
<br />
else<br />
log_info "daily power reset failed"<br />
break<br />
fi<br />
done<br />
log_info "date/time updated"<br />
}<br />
<br />
# ------------------------<br />
# MAIN: Script starts here<br />
# ------------------------<br />
<br />
# stop any runnning deye_inverter_mqtt processes<br />
deye_inverter_mqtt_stop<br />
<br />
# wait until 04:00 - 22:00<br />
wait_for_operative_window<br />
<br />
# setting sate/time also resets daily power register<br />
reset_deye_inverter<br />
<br />
# start deye_mqtt_loop<br />
while get_date_time; do<br />
<br />
# wait for next operative window 04:00 - 22:00<br />
[ $hour -lt 4 -o $hour -le 22 ] && \<br />
wait_for_operative_window<br />
<br />
# start deye_inverter_mqtt<br />
get_deye_mqtt_pid<br />
[ -z "$deye_mqtt_pid" ] && deye_inverter_mqtt_start<br />
<br />
done<br />
</pre><br />
<br />
=== Contrab und /etc/rc.local ===<br />
Mein Crontab Eintrag:<br />
<pre>0 4 * * * /root/sbin/deye_mqtt_loop.sh 2>/dev/null &</pre><br />
und /etc/rc.local:<br />
<pre># start monitoring of PV-System: DEYE inverter<br />
$(sleep 20; /root/sbin/deye_mqtt_loop.sh 2>dev/null)&<br />
</pre><br />
<br />
== Dashboard ==<br />
So sieht das aus, wenn es den ganzen Tag im April regnet und bedeckt ist:<br />
[[Datei:Solar Bedeckt Regen.jpg|midi]]<br />
<br />
Die Eigenverbrauchsquote habe ich mittels Grafana ermittelt:<br />
[[Datei:Solar Eigenverbrauch.jpg|midi|Eigenverbrauchsquote]]</div>
Neobiker
http://neobiker.de/wiki/index.php?title=DEYE_Wechselrichter&diff=2124
DEYE Wechselrichter
2024-01-07T14:03:29Z
<p>Neobiker: /* deye_mqtt_loop.sh */</p>
<hr />
<div>= Balkonkraftwerk: DEYE Wechselrichter ohne Cloud auslesen und per MQTT monitoren =<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
Der WR bietet verschiedene Möglichkeiten zur Konfiguration und Abfrage der aktuellen Werte:<br />
* einen internen Webserver (http://10.10.10.254)<br />
* die Cloud Anbindung zu Solarman (Solarman Smart App)<br />
* (angepasstes) Modbus Protokoll über Port 8899<br />
* AT+ Befehle über Port 48899<br />
<br />
Ich verwende das Modbus Protokoll zum auslesen des WR, das <br />
# effizient/schnell ist (Performance)<br />
# ohne Internetverbindung auskommt<br />
<br />
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.<br />
<br />
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.<br />
<br />
== Wechselrichter Eigenheiten ==<br />
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. <br />
<br />
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.<br />
<br />
== Installation ==<br />
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.<br />
<pre>git clone https://github.com/kbialek/deye-inverter-mqtt<br />
pip install paho-mqtt<br />
cd deye-inverter-mqtt</pre><br />
<br />
Unter Debian habe ich im Skript '''deye_cli.sh''' python durch python3 ersetzen müssen:<br />
<pre>#!/bin/bash<br />
set -a; source config.env; set +a<br />
python3 deye_cli.py "$@"<br />
</pre><br />
<br />
=== config.env ===<br />
Die Konfiguration meines DEYE Wechselrichters vom Typ Micro-Inverter:<br />
'''config.env'''<br />
<pre>DEYE_LOGGER_IP_ADDRESS=<IP Wechselrichter im WLAN><br />
DEYE_LOGGER_PORT=8899<br />
DEYE_LOGGER_SERIAL_NUMBER=<Seriennummer des WR><br />
<br />
MQTT_HOST=<IP von MQTT Server><br />
MQTT_PORT=1883<br />
MQTT_USERNAME=<br />
MQTT_PASSWORD=<br />
MQTT_TOPIC_PREFIX=deye<br />
<br />
LOG_LEVEL=ERROR<br />
DEYE_DATA_READ_INTERVAL=60<br />
DEYE_METRIC_GROUPS=micro<br />
</pre><br />
<br />
=== deye_inverter.sh === <br />
Ausserdem habe ich ein kleines ''Wrapper Skript'' geschrieben, um '''Daten des WR''' einfacher '''lesen und schreiben''' zu können:<br><br />
<br />
'''deye_inverter.sh''' [--check <pause>] --read <register> | --write <register> <value><br />
<br />
<pre>#!/bin/bash<br />
# Read / Write DEYE Inverters<br />
# via deye_inverter_mqtt python package<br />
# https://github.com/kbialek/deye-inverter-mqtt<br />
<br />
retries=2 # retry deye_inverter command multiple times<br />
sleep=5 # sleep time between retries<br />
<br />
# locate script in deye_inverter_mqtt directory<br />
cd $(dirname $0)<br />
if [ ! -f ./deye_cli.sh ]; then<br />
echo "Error: ./deye_cli.sh not found."<br />
echo " Please move $(basename $0) in deye-inverter-mqtt directory."<br />
exit 1<br />
fi<br />
. ./config.env<br />
<br />
log_info ()<br />
{<br />
if [ ${LOG_LEVEL} = "INFO" ]; then<br />
logger -t $(basename $0) $@<br />
fi<br />
}<br />
<br />
log_error ()<br />
{<br />
logger -t $(basename $0) $@<br />
echo 2>&1 $@<br />
}<br />
<br />
# read parameters: mode [rw], register, value and optional -c <check pause><br />
pause=0<br />
while [ $# -gt 0 ]; do<br />
case $1 in<br />
-c|--check) shift<br />
[ $# -ge 3 ] || exit 1<br />
pause=$1<br />
;;<br />
-w|--write) mode=w<br />
shift<br />
[ $# -eq 2 ] || exit 1<br />
reg=$1<br />
val=$2<br />
shift<br />
;;<br />
-r|--read) mode=r<br />
shift<br />
[ $# -eq 1 ] || exit 1<br />
reg=$1<br />
;;<br />
esac<br />
shift<br />
done<br />
<br />
# handle offline deye_inverter by<br />
# ${pause} > 0 -> endless loop<br />
# ${pause} = 0 -> error after ${retries}<br />
while true; do<br />
<br />
# try deye_cli.sh $retries times every $sleep secs<br />
check=${retries}<br />
while [ ${check} -gt 0 ]; do<br />
<br />
case ${mode} in<br />
r) result=$(./deye_cli.sh $mode $reg | grep 'int: ')<br />
if [ -n "$result" ]; then<br />
echo "$result"<br />
log_info -t $0 deye_cli.sh $mode $reg<br />
exit 0<br />
fi<br />
;;<br />
w) result=$(./deye_cli.sh $mode $reg $val)<br />
if [ "$result" = "Ok" ]; then<br />
log_info -t $0 deye_cli.sh $mode $reg $val<br />
exit 0<br />
fi<br />
;;<br />
esac<br />
<br />
# wait ${sleep} or ${pause} secs<br />
sleep $(( ${pause} ? ${sleep} : ${pause} ))<br />
<br />
# try ${retries} times<br />
(( check-- ))<br />
done<br />
<br />
# error, or retry endless until wakeup of deye_inverter<br />
if [ ${pause} -eq 0 ]; then<br />
log_error "Error: deye_cli.sh $mode $reg $val failed."<br />
exit 1<br />
fi<br />
<br />
done<br />
</pre><br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
Folgendes Skript starte ich jeden Morgen um 04:00 Uhr per Cron (und in rc.local fürs booten):<br />
<br />
=== deye_mqtt_loop.sh ===<br />
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.<br />
<br />
Ausserdem setzt es beim Start den Tageszähler des Wechselrichters bedarfsweise zurück, bevor die Werte per Endlosschleife abgefragt werden.<br />
<br />
<pre>#!/bin/bash<br />
# reset Daily_Power of DEYE Inverters<br />
# via deye_inverter_mqtt python package<br />
# https://github.com/kbialek/deye-inverter-mqtt<br />
<br />
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin<br />
<br />
debug=<br />
<br />
# config:<br />
deye_inverter_sh=/root/deye-inverter-mqtt/deye_inverter.sh<br />
deye_inverter_mqtt_cmd="python3 $(dirname ${deye_inverter_sh})/deye_docker_entrypoint.py"<br />
deye_inverter_mqtt_cfg=$(dirname ${deye_inverter_sh})/config.env<br />
<br />
myPID=$$<br />
myname=$(basename $0)<br />
<br />
# installation:<br />
# locate script in deye_inverter_mqtt directory<br />
if [ ! -f ${deye_inverter_mqtt_cfg} ]; then<br />
echo "Error: ${deye_inverter_mqtt_cfg} not found."<br />
echo " Please update \$deye_inverter_sh path in $0"<br />
exit 1<br />
fi<br />
<br />
# read my config<br />
set -a; source ${deye_inverter_mqtt_cfg}; set +a<br />
<br />
# ----- functions () ------------------------------------<br />
log_info ()<br />
{<br />
[ "$debug" = "yes" -o "$debug" = "true" ] && echo $myname: $@<br />
[ ${LOG_LEVEL} = "INFO" ] && logger -t $myname $@<br />
}<br />
<br />
log_error ()<br />
{<br />
logger -t $myname $@<br />
echo 1>&2 $@<br />
}<br />
<br />
# store actual date + time into vars<br />
get_date_time ()<br />
{<br />
year=$(date +%y)<br />
month=$(date +%m | sed 's/^0//g')<br />
day=$(date +%d | sed 's/^0//g')<br />
hour=$(date +%H | sed 's/^0//g')<br />
minute=$(date +%m | sed 's/^0//g')<br />
second=$(date +%S | sed 's/^0//g')<br />
}<br />
<br />
# get deye_inverter_mqtt PIDs<br />
get_deye_mqtt_pid ()<br />
{<br />
deye_mqtt_pid=$(ps ax | grep -v grep | grep "${deye_inverter_mqtt_cmd}" | awk '{print $1}')<br />
}<br />
<br />
# kill deye_inverter_loop ()<br />
kill_deye_inverter_loop ()<br />
{<br />
# kill old instances of me<br />
my_PS=$(ps ax | grep -v grep | grep ${myname} | awk '{print $1}')<br />
for ps in $my_PS; do<br />
[ $ps -lt $myPID ] && kill $ps<br />
done<br />
}<br />
<br />
# kill deye_inverter_instance ()<br />
kill_deye_inverter_instance ()<br />
{<br />
get_deye_mqtt_pid<br />
if [ ! -z "${deye_mqtt_pid}" ]; then<br />
kill ${deye_mqtt_pid}<br />
log_info "Info: KILL running deye_mqtt ${deye_mqtt_pid}"<br />
fi<br />
<br />
# kill any running deye_requests<br />
my_PS=$(ps ax | grep -v grep | grep "${deye_inverter_sh}" | awk '{print $1}')<br />
[ ! -z "$my_PS" ] && kill $my_PS<br />
}<br />
<br />
# stop all runnning instances<br />
deye_inverter_mqtt_stop ()<br />
{<br />
kill_deye_inverter_loop<br />
kill_deye_inverter_instance<br />
}<br />
<br />
# start deye_inverter_mqtt<br />
deye_inverter_mqtt_start ()<br />
{<br />
log_info "MQTT start"<br />
${deye_inverter_mqtt_cmd}<br />
}<br />
<br />
# read Daily power register<br />
read_daily_Power ()<br />
{<br />
${deye_inverter_sh} -c 10 -r 60 | sed 's/int: //g' | cut -d, -f1<br />
}<br />
<br />
# wait until 04:00 - 22:00<br />
wait_for_operative_window ()<br />
{<br />
get_date_time<br />
<br />
# calulate time in secs until 04:00<br />
if [ $hour -ge 4 ]; then<br />
((sleep_time=((((28-$hour)*60)-$minute)*60)-$second))<br />
else<br />
((sleep_time=((((4-$hour)*60)-$minute)*60)-$second))<br />
fi<br />
<br />
# wait for operative time<br />
if [ $hour -lt 4 -o $hour -ge 22 ]; then<br />
log_info $sleep_time<br />
sleep $sleep_time<br />
fi<br />
}<br />
<br />
# write actual date/time into deye inverter<br />
reset_deye_inverter ()<br />
{<br />
<br />
# wait until deye inverter starts by reading (old) year/month (register 22)<br />
val22_old=$(${deye_inverter_sh} -c 60 -r 22 | sed 's/int: //g' | cut -d, -f1)<br />
log_info "date/time update initialized"<br />
<br />
# read Daily power register<br />
dailyPower=$(read_daily_Power)<br />
<br />
# daily reset phase<br />
daily_reset=init<br />
# ensure by loop, that all register values are written<br />
while [ ${daily_reset} != "done" ]; do<br />
<br />
# read (old) day<br />
val23_day=$(${deye_inverter_sh} -c 10 -r 23 | sed 's/.*h: //g' | cut -d, -f1)<br />
<br />
# set actual year, month, day, hour, minute, second<br />
get_date_time<br />
<br />
# calculate register 22-24 by date and time<br />
val22=$(( ${year} * 256 + ${month} ))<br />
val23=$(( ${day} * 256 + ${hour} ))<br />
val24=$(( ${minute} * 256 + ${second} ))<br />
<br />
# reset power/date/time only once a day or exit loop<br />
[ "$dailyPower" -eq 0 -a "${val22_old}" = "${val22}" -a "${val23_day}" = "${day}" ] && break<br />
<br />
# reset daily_power by<br />
# setting register 22-24 with actual date + time<br />
${deye_inverter_sh} -w 22 $val22 || continue<br />
${deye_inverter_sh} -w 23 $val23 || continue<br />
${deye_inverter_sh} -w 24 $val24 || continue<br />
<br />
# test success<br />
# read Daily power register, should be resetted to 0 now<br />
dailyPower=$(read_daily_Power)<br />
<br />
# exit loop if power is updated, else log that reset failed<br />
if [ "$dailyPower" -eq 0 ]; then<br />
daily_reset=done<br />
<br />
### skip loop here, reset doesn't work always :-(<br />
# elif [ $daily_reset = "init" ]; then<br />
# daily_reset=failed<br />
# sleep 60<br />
<br />
else<br />
log_info "daily power reset failed"<br />
break<br />
fi<br />
done<br />
log_info "date/time updated"<br />
}<br />
<br />
# ------------------------<br />
# MAIN: Script starts here<br />
# ------------------------<br />
<br />
# stop any runnning deye_inverter_mqtt processes<br />
deye_inverter_mqtt_stop<br />
<br />
# wait until 04:00 - 22:00<br />
wait_for_operative_window<br />
<br />
# setting sate/time also resets daily power register<br />
reset_deye_inverter<br />
<br />
# start deye_mqtt_loop<br />
while get_date_time; do<br />
<br />
# wait for next operative window 04:00 - 22:00<br />
[ $hour -lt 4 -o $hour -le 22 ] && \<br />
wait_for_operative_window<br />
<br />
# start deye_inverter_mqtt<br />
get_deye_mqtt_pid<br />
[ -z "$deye_mqtt_pid" ] && deye_inverter_mqtt_start<br />
<br />
done<br />
</pre><br />
<br />
=== Contrab und /etc/rc.local ===<br />
Mein Crontab Eintrag:<br />
<pre>0 4 * * * /root/sbin/deye_mqtt_loop.sh 2>/dev/null &</pre><br />
und /etc/rc.local:<br />
<pre># start monitoring of PV-System: DEYE inverter<br />
$(sleep 20; /root/sbin/deye_mqtt_loop.sh 2>dev/null)&<br />
</pre><br />
<br />
== Dashboard ==<br />
So sieht das aus, wenn es den ganzen Tag im April regnet und bedeckt ist:<br />
[[Datei:Solar Bedeckt Regen.jpg|midi]]<br />
<br />
Die Eigenverbrauchsquote habe ich mittels Grafana ermittelt:<br />
[[Datei:Solar Eigenverbrauch.jpg|midi|Eigenverbrauchsquote]]</div>
Neobiker
http://neobiker.de/wiki/index.php?title=DEYE_Wechselrichter&diff=2123
DEYE Wechselrichter
2024-01-07T13:44:12Z
<p>Neobiker: /* Contrab und /etc/rc.local */</p>
<hr />
<div>= Balkonkraftwerk: DEYE Wechselrichter ohne Cloud auslesen und per MQTT monitoren =<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
Der WR bietet verschiedene Möglichkeiten zur Konfiguration und Abfrage der aktuellen Werte:<br />
* einen internen Webserver (http://10.10.10.254)<br />
* die Cloud Anbindung zu Solarman (Solarman Smart App)<br />
* (angepasstes) Modbus Protokoll über Port 8899<br />
* AT+ Befehle über Port 48899<br />
<br />
Ich verwende das Modbus Protokoll zum auslesen des WR, das <br />
# effizient/schnell ist (Performance)<br />
# ohne Internetverbindung auskommt<br />
<br />
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.<br />
<br />
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.<br />
<br />
== Wechselrichter Eigenheiten ==<br />
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. <br />
<br />
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.<br />
<br />
== Installation ==<br />
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.<br />
<pre>git clone https://github.com/kbialek/deye-inverter-mqtt<br />
pip install paho-mqtt<br />
cd deye-inverter-mqtt</pre><br />
<br />
Unter Debian habe ich im Skript '''deye_cli.sh''' python durch python3 ersetzen müssen:<br />
<pre>#!/bin/bash<br />
set -a; source config.env; set +a<br />
python3 deye_cli.py "$@"<br />
</pre><br />
<br />
=== config.env ===<br />
Die Konfiguration meines DEYE Wechselrichters vom Typ Micro-Inverter:<br />
'''config.env'''<br />
<pre>DEYE_LOGGER_IP_ADDRESS=<IP Wechselrichter im WLAN><br />
DEYE_LOGGER_PORT=8899<br />
DEYE_LOGGER_SERIAL_NUMBER=<Seriennummer des WR><br />
<br />
MQTT_HOST=<IP von MQTT Server><br />
MQTT_PORT=1883<br />
MQTT_USERNAME=<br />
MQTT_PASSWORD=<br />
MQTT_TOPIC_PREFIX=deye<br />
<br />
LOG_LEVEL=ERROR<br />
DEYE_DATA_READ_INTERVAL=60<br />
DEYE_METRIC_GROUPS=micro<br />
</pre><br />
<br />
=== deye_inverter.sh === <br />
Ausserdem habe ich ein kleines ''Wrapper Skript'' geschrieben, um '''Daten des WR''' einfacher '''lesen und schreiben''' zu können:<br><br />
<br />
'''deye_inverter.sh''' [--check <pause>] --read <register> | --write <register> <value><br />
<br />
<pre>#!/bin/bash<br />
# Read / Write DEYE Inverters<br />
# via deye_inverter_mqtt python package<br />
# https://github.com/kbialek/deye-inverter-mqtt<br />
<br />
retries=2 # retry deye_inverter command multiple times<br />
sleep=5 # sleep time between retries<br />
<br />
# locate script in deye_inverter_mqtt directory<br />
cd $(dirname $0)<br />
if [ ! -f ./deye_cli.sh ]; then<br />
echo "Error: ./deye_cli.sh not found."<br />
echo " Please move $(basename $0) in deye-inverter-mqtt directory."<br />
exit 1<br />
fi<br />
. ./config.env<br />
<br />
log_info ()<br />
{<br />
if [ ${LOG_LEVEL} = "INFO" ]; then<br />
logger -t $(basename $0) $@<br />
fi<br />
}<br />
<br />
log_error ()<br />
{<br />
logger -t $(basename $0) $@<br />
echo 2>&1 $@<br />
}<br />
<br />
# read parameters: mode [rw], register, value and optional -c <check pause><br />
pause=0<br />
while [ $# -gt 0 ]; do<br />
case $1 in<br />
-c|--check) shift<br />
[ $# -ge 3 ] || exit 1<br />
pause=$1<br />
;;<br />
-w|--write) mode=w<br />
shift<br />
[ $# -eq 2 ] || exit 1<br />
reg=$1<br />
val=$2<br />
shift<br />
;;<br />
-r|--read) mode=r<br />
shift<br />
[ $# -eq 1 ] || exit 1<br />
reg=$1<br />
;;<br />
esac<br />
shift<br />
done<br />
<br />
# handle offline deye_inverter by<br />
# ${pause} > 0 -> endless loop<br />
# ${pause} = 0 -> error after ${retries}<br />
while true; do<br />
<br />
# try deye_cli.sh $retries times every $sleep secs<br />
check=${retries}<br />
while [ ${check} -gt 0 ]; do<br />
<br />
case ${mode} in<br />
r) result=$(./deye_cli.sh $mode $reg | grep 'int: ')<br />
if [ -n "$result" ]; then<br />
echo "$result"<br />
log_info -t $0 deye_cli.sh $mode $reg<br />
exit 0<br />
fi<br />
;;<br />
w) result=$(./deye_cli.sh $mode $reg $val)<br />
if [ "$result" = "Ok" ]; then<br />
log_info -t $0 deye_cli.sh $mode $reg $val<br />
exit 0<br />
fi<br />
;;<br />
esac<br />
<br />
# wait ${sleep} or ${pause} secs<br />
sleep $(( ${pause} ? ${sleep} : ${pause} ))<br />
<br />
# try ${retries} times<br />
(( check-- ))<br />
done<br />
<br />
# error, or retry endless until wakeup of deye_inverter<br />
if [ ${pause} -eq 0 ]; then<br />
log_error "Error: deye_cli.sh $mode $reg $val failed."<br />
exit 1<br />
fi<br />
<br />
done<br />
</pre><br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
Folgendes Skript starte ich jeden Morgen um 04:00 Uhr per Cron (und in rc.local fürs booten):<br />
<br />
=== deye_mqtt_loop.sh ===<br />
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.<br />
<br />
Ausserdem setzt es beim Start den Tageszähler des Wechselrichters bedarfsweise zurück, bevor die Werte per Endlosschleife abgefragt werden.<br />
<br />
<pre>#!/bin/bash<br />
# reset Daily_Power of DEYE Inverters<br />
# via deye_inverter_mqtt python package<br />
# https://github.com/kbialek/deye-inverter-mqtt<br />
<br />
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin<br />
<br />
debug=<br />
<br />
# config:<br />
deye_inverter_sh=/root/deye-inverter-mqtt/deye_inverter.sh<br />
deye_inverter_mqtt_cmd="python3 $(dirname ${deye_inverter_sh})/deye_docker_entrypoint.py"<br />
deye_inverter_mqtt_cfg=$(dirname ${deye_inverter_sh})/config.env<br />
<br />
myPID=$$<br />
myname=$(basename $0)<br />
<br />
# installation:<br />
# locate script in deye_inverter_mqtt directory<br />
if [ ! -f ${deye_inverter_mqtt_cfg} ]; then<br />
echo "Error: ${deye_inverter_mqtt_cfg} not found."<br />
echo " Please update \$deye_inverter_sh path in $0"<br />
exit 1<br />
fi<br />
<br />
# read my config<br />
set -a; source ${deye_inverter_mqtt_cfg}; set +a<br />
<br />
# ----- functions () ------------------------------------<br />
log_info ()<br />
{<br />
[ "$debug" = "yes" -o "$debug" = "true" ] && echo $myname: $@<br />
[ ${LOG_LEVEL} = "INFO" ] && logger -t $myname $@<br />
}<br />
<br />
log_error ()<br />
{<br />
logger -t $myname $@<br />
echo 1>&2 $@<br />
}<br />
<br />
# store actual date + time into vars<br />
get_date_time ()<br />
{<br />
year=$(date +%y)<br />
month=$(date +%m | sed 's/^0//g')<br />
day=$(date +%d | sed 's/^0//g')<br />
hour=$(date +%H | sed 's/^0//g')<br />
minute=$(date +%m | sed 's/^0//g')<br />
second=$(date +%S | sed 's/^0//g')<br />
}<br />
<br />
# get deye_inverter_mqtt PIDs<br />
get_deye_mqtt_pid ()<br />
{<br />
deye_mqtt_pid=$(ps ax | grep -v grep | grep "${deye_inverter_mqtt_cmd}" | awk '{print $1}')<br />
}<br />
<br />
# kill deye_inverter_loop ()<br />
kill_deye_inverter_loop ()<br />
{<br />
# kill old instances of me<br />
my_PS=$(ps ax | grep -v grep | grep ${myname} | awk '{print $1}')<br />
for ps in $my_PS; do<br />
[ $ps -lt $myPID ] && kill $ps<br />
done<br />
}<br />
<br />
# kill deye_inverter_instance ()<br />
kill_deye_inverter_instance ()<br />
{<br />
get_deye_mqtt_pid<br />
if [ ! -z "${deye_mqtt_pid}" ]; then<br />
kill ${deye_mqtt_pid}<br />
log_info "Info: KILL running deye_mqtt ${deye_mqtt_pid}"<br />
fi<br />
<br />
# kill any running deye_requests<br />
my_PS=$(ps ax | grep -v grep | grep "${deye_inverter_sh}" | awk '{print $1}')<br />
[ ! -z "$my_PS" ] && kill $my_PS<br />
}<br />
<br />
# stop all runnning instances<br />
deye_inverter_mqtt_stop ()<br />
{<br />
kill_deye_inverter_loop<br />
kill_deye_inverter_instance<br />
}<br />
<br />
# start deye_inverter_mqtt<br />
deye_inverter_mqtt_start ()<br />
{<br />
log_info "MQTT start"<br />
${deye_inverter_mqtt_cmd}<br />
}<br />
<br />
# read Daily power register<br />
read_daily_Power ()<br />
{<br />
${deye_inverter_sh} -c 10 -r 60 | sed 's/int: //g' | cut -d, -f1<br />
}<br />
<br />
# wait until 04:00 - 22:00<br />
wait_for_operative_window ()<br />
{<br />
get_date_time<br />
<br />
# calulate time in secs until 04:00<br />
if [ $hour -ge 4 ]; then<br />
((sleep_time=((((28-$hour)*60)-$minute)*60)-$second))<br />
else<br />
((sleep_time=((((4-$hour)*60)-$minute)*60)-$second))<br />
fi<br />
<br />
# wait for operative time<br />
if [ $hour -lt 4 -o $hour -ge 22 ]; then<br />
log_info $sleep_time<br />
sleep $sleep_time<br />
fi<br />
}<br />
<br />
# ------------------------<br />
# MAIN: Script starts here<br />
# ------------------------<br />
<br />
# stop any runnning deye_inverter_mqtt processes<br />
deye_inverter_mqtt_stop<br />
<br />
# wait until 04:00 - 22:00<br />
wait_for_operative_window<br />
<br />
# wait until deye inverter starts by reading (old) year/month (register 22)<br />
val22_old=$(${deye_inverter_sh} -c 60 -r 22 | sed 's/int: //g' | cut -d, -f1)<br />
log_info "date/time update initialized"<br />
<br />
# read Daily power register<br />
dailyPower=$(read_daily_Power)<br />
<br />
# daily reset phase<br />
daily_reset=init<br />
<br />
# ensure by loop, that all register values are written<br />
while [ ${daily_reset} != "done" ]; do<br />
<br />
# read (old) day<br />
val23_day=$(${deye_inverter_sh} -c 10 -r 23 | sed 's/.*h: //g' | cut -d, -f1)<br />
<br />
# set actual year, month, day, hour, minute, second<br />
get_date_time<br />
<br />
# calculate register 22-24 by date and time<br />
val22=$(( ${year} * 256 + ${month} ))<br />
val23=$(( ${day} * 256 + ${hour} ))<br />
val24=$(( ${minute} * 256 + ${second} ))<br />
<br />
# reset power/date/time only once a day or exit loop<br />
[ "$dailyPower" -eq 0 -a "${val22_old}" = "${val22}" -a "${val23_day}" = "${day}" ] && break<br />
<br />
# reset daily_power by<br />
# setting register 22-24 with actual date + time<br />
${deye_inverter_sh} -w 22 $val22 || continue<br />
${deye_inverter_sh} -w 23 $val23 || continue<br />
${deye_inverter_sh} -w 24 $val24 || continue<br />
<br />
# test success<br />
# read Daily power register, should be resetted to 0 now<br />
dailyPower=$(read_daily_Power)<br />
<br />
# exit loop if power is updated, else log that reset failed<br />
if [ "$dailyPower" -eq 0 ]; then<br />
daily_reset=done<br />
<br />
### skip loop here, reset doesn't work always :-(<br />
# elif [ $daily_reset = "init" ]; then<br />
# daily_reset=failed<br />
# sleep 60<br />
<br />
else<br />
log_info "daily power reset failed"<br />
break<br />
fi<br />
done<br />
log_info "date/time updated"<br />
<br />
# start deye_mqtt_loop between 04:00 - 22:00<br />
while get_date_time; do<br />
[ $hour -le 22 ] || break<br />
[ $hour -lt 4 ] && break<br />
<br />
# restart<br />
get_deye_mqtt_pid<br />
[ -z "$deye_mqtt_pid" ] && deye_inverter_mqtt_start<br />
<br />
sleep 600<br />
done<br />
</pre><br />
<br />
=== Contrab und /etc/rc.local ===<br />
Mein Crontab Eintrag:<br />
<pre>0 4 * * * /root/sbin/deye_mqtt_loop.sh 2>/dev/null &</pre><br />
und /etc/rc.local:<br />
<pre># start monitoring of PV-System: DEYE inverter<br />
$(sleep 20; /root/sbin/deye_mqtt_loop.sh 2>dev/null)&<br />
</pre><br />
<br />
== Dashboard ==<br />
So sieht das aus, wenn es den ganzen Tag im April regnet und bedeckt ist:<br />
[[Datei:Solar Bedeckt Regen.jpg|midi]]<br />
<br />
Die Eigenverbrauchsquote habe ich mittels Grafana ermittelt:<br />
[[Datei:Solar Eigenverbrauch.jpg|midi|Eigenverbrauchsquote]]</div>
Neobiker
http://neobiker.de/wiki/index.php?title=DEYE_Wechselrichter&diff=2122
DEYE Wechselrichter
2024-01-07T13:41:31Z
<p>Neobiker: /* Contrab und /etc/rc.local */</p>
<hr />
<div>= Balkonkraftwerk: DEYE Wechselrichter ohne Cloud auslesen und per MQTT monitoren =<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
Der WR bietet verschiedene Möglichkeiten zur Konfiguration und Abfrage der aktuellen Werte:<br />
* einen internen Webserver (http://10.10.10.254)<br />
* die Cloud Anbindung zu Solarman (Solarman Smart App)<br />
* (angepasstes) Modbus Protokoll über Port 8899<br />
* AT+ Befehle über Port 48899<br />
<br />
Ich verwende das Modbus Protokoll zum auslesen des WR, das <br />
# effizient/schnell ist (Performance)<br />
# ohne Internetverbindung auskommt<br />
<br />
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.<br />
<br />
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.<br />
<br />
== Wechselrichter Eigenheiten ==<br />
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. <br />
<br />
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.<br />
<br />
== Installation ==<br />
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.<br />
<pre>git clone https://github.com/kbialek/deye-inverter-mqtt<br />
pip install paho-mqtt<br />
cd deye-inverter-mqtt</pre><br />
<br />
Unter Debian habe ich im Skript '''deye_cli.sh''' python durch python3 ersetzen müssen:<br />
<pre>#!/bin/bash<br />
set -a; source config.env; set +a<br />
python3 deye_cli.py "$@"<br />
</pre><br />
<br />
=== config.env ===<br />
Die Konfiguration meines DEYE Wechselrichters vom Typ Micro-Inverter:<br />
'''config.env'''<br />
<pre>DEYE_LOGGER_IP_ADDRESS=<IP Wechselrichter im WLAN><br />
DEYE_LOGGER_PORT=8899<br />
DEYE_LOGGER_SERIAL_NUMBER=<Seriennummer des WR><br />
<br />
MQTT_HOST=<IP von MQTT Server><br />
MQTT_PORT=1883<br />
MQTT_USERNAME=<br />
MQTT_PASSWORD=<br />
MQTT_TOPIC_PREFIX=deye<br />
<br />
LOG_LEVEL=ERROR<br />
DEYE_DATA_READ_INTERVAL=60<br />
DEYE_METRIC_GROUPS=micro<br />
</pre><br />
<br />
=== deye_inverter.sh === <br />
Ausserdem habe ich ein kleines ''Wrapper Skript'' geschrieben, um '''Daten des WR''' einfacher '''lesen und schreiben''' zu können:<br><br />
<br />
'''deye_inverter.sh''' [--check <pause>] --read <register> | --write <register> <value><br />
<br />
<pre>#!/bin/bash<br />
# Read / Write DEYE Inverters<br />
# via deye_inverter_mqtt python package<br />
# https://github.com/kbialek/deye-inverter-mqtt<br />
<br />
retries=2 # retry deye_inverter command multiple times<br />
sleep=5 # sleep time between retries<br />
<br />
# locate script in deye_inverter_mqtt directory<br />
cd $(dirname $0)<br />
if [ ! -f ./deye_cli.sh ]; then<br />
echo "Error: ./deye_cli.sh not found."<br />
echo " Please move $(basename $0) in deye-inverter-mqtt directory."<br />
exit 1<br />
fi<br />
. ./config.env<br />
<br />
log_info ()<br />
{<br />
if [ ${LOG_LEVEL} = "INFO" ]; then<br />
logger -t $(basename $0) $@<br />
fi<br />
}<br />
<br />
log_error ()<br />
{<br />
logger -t $(basename $0) $@<br />
echo 2>&1 $@<br />
}<br />
<br />
# read parameters: mode [rw], register, value and optional -c <check pause><br />
pause=0<br />
while [ $# -gt 0 ]; do<br />
case $1 in<br />
-c|--check) shift<br />
[ $# -ge 3 ] || exit 1<br />
pause=$1<br />
;;<br />
-w|--write) mode=w<br />
shift<br />
[ $# -eq 2 ] || exit 1<br />
reg=$1<br />
val=$2<br />
shift<br />
;;<br />
-r|--read) mode=r<br />
shift<br />
[ $# -eq 1 ] || exit 1<br />
reg=$1<br />
;;<br />
esac<br />
shift<br />
done<br />
<br />
# handle offline deye_inverter by<br />
# ${pause} > 0 -> endless loop<br />
# ${pause} = 0 -> error after ${retries}<br />
while true; do<br />
<br />
# try deye_cli.sh $retries times every $sleep secs<br />
check=${retries}<br />
while [ ${check} -gt 0 ]; do<br />
<br />
case ${mode} in<br />
r) result=$(./deye_cli.sh $mode $reg | grep 'int: ')<br />
if [ -n "$result" ]; then<br />
echo "$result"<br />
log_info -t $0 deye_cli.sh $mode $reg<br />
exit 0<br />
fi<br />
;;<br />
w) result=$(./deye_cli.sh $mode $reg $val)<br />
if [ "$result" = "Ok" ]; then<br />
log_info -t $0 deye_cli.sh $mode $reg $val<br />
exit 0<br />
fi<br />
;;<br />
esac<br />
<br />
# wait ${sleep} or ${pause} secs<br />
sleep $(( ${pause} ? ${sleep} : ${pause} ))<br />
<br />
# try ${retries} times<br />
(( check-- ))<br />
done<br />
<br />
# error, or retry endless until wakeup of deye_inverter<br />
if [ ${pause} -eq 0 ]; then<br />
log_error "Error: deye_cli.sh $mode $reg $val failed."<br />
exit 1<br />
fi<br />
<br />
done<br />
</pre><br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
Folgendes Skript starte ich jeden Morgen um 04:00 Uhr per Cron (und in rc.local fürs booten):<br />
<br />
=== deye_mqtt_loop.sh ===<br />
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.<br />
<br />
Ausserdem setzt es beim Start den Tageszähler des Wechselrichters bedarfsweise zurück, bevor die Werte per Endlosschleife abgefragt werden.<br />
<br />
<pre>#!/bin/bash<br />
# reset Daily_Power of DEYE Inverters<br />
# via deye_inverter_mqtt python package<br />
# https://github.com/kbialek/deye-inverter-mqtt<br />
<br />
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin<br />
<br />
debug=<br />
<br />
# config:<br />
deye_inverter_sh=/root/deye-inverter-mqtt/deye_inverter.sh<br />
deye_inverter_mqtt_cmd="python3 $(dirname ${deye_inverter_sh})/deye_docker_entrypoint.py"<br />
deye_inverter_mqtt_cfg=$(dirname ${deye_inverter_sh})/config.env<br />
<br />
myPID=$$<br />
myname=$(basename $0)<br />
<br />
# installation:<br />
# locate script in deye_inverter_mqtt directory<br />
if [ ! -f ${deye_inverter_mqtt_cfg} ]; then<br />
echo "Error: ${deye_inverter_mqtt_cfg} not found."<br />
echo " Please update \$deye_inverter_sh path in $0"<br />
exit 1<br />
fi<br />
<br />
# read my config<br />
set -a; source ${deye_inverter_mqtt_cfg}; set +a<br />
<br />
# ----- functions () ------------------------------------<br />
log_info ()<br />
{<br />
[ "$debug" = "yes" -o "$debug" = "true" ] && echo $myname: $@<br />
[ ${LOG_LEVEL} = "INFO" ] && logger -t $myname $@<br />
}<br />
<br />
log_error ()<br />
{<br />
logger -t $myname $@<br />
echo 1>&2 $@<br />
}<br />
<br />
# store actual date + time into vars<br />
get_date_time ()<br />
{<br />
year=$(date +%y)<br />
month=$(date +%m | sed 's/^0//g')<br />
day=$(date +%d | sed 's/^0//g')<br />
hour=$(date +%H | sed 's/^0//g')<br />
minute=$(date +%m | sed 's/^0//g')<br />
second=$(date +%S | sed 's/^0//g')<br />
}<br />
<br />
# get deye_inverter_mqtt PIDs<br />
get_deye_mqtt_pid ()<br />
{<br />
deye_mqtt_pid=$(ps ax | grep -v grep | grep "${deye_inverter_mqtt_cmd}" | awk '{print $1}')<br />
}<br />
<br />
# kill deye_inverter_loop ()<br />
kill_deye_inverter_loop ()<br />
{<br />
# kill old instances of me<br />
my_PS=$(ps ax | grep -v grep | grep ${myname} | awk '{print $1}')<br />
for ps in $my_PS; do<br />
[ $ps -lt $myPID ] && kill $ps<br />
done<br />
}<br />
<br />
# kill deye_inverter_instance ()<br />
kill_deye_inverter_instance ()<br />
{<br />
get_deye_mqtt_pid<br />
if [ ! -z "${deye_mqtt_pid}" ]; then<br />
kill ${deye_mqtt_pid}<br />
log_info "Info: KILL running deye_mqtt ${deye_mqtt_pid}"<br />
fi<br />
<br />
# kill any running deye_requests<br />
my_PS=$(ps ax | grep -v grep | grep "${deye_inverter_sh}" | awk '{print $1}')<br />
[ ! -z "$my_PS" ] && kill $my_PS<br />
}<br />
<br />
# stop all runnning instances<br />
deye_inverter_mqtt_stop ()<br />
{<br />
kill_deye_inverter_loop<br />
kill_deye_inverter_instance<br />
}<br />
<br />
# start deye_inverter_mqtt<br />
deye_inverter_mqtt_start ()<br />
{<br />
log_info "MQTT start"<br />
${deye_inverter_mqtt_cmd}<br />
}<br />
<br />
# read Daily power register<br />
read_daily_Power ()<br />
{<br />
${deye_inverter_sh} -c 10 -r 60 | sed 's/int: //g' | cut -d, -f1<br />
}<br />
<br />
# wait until 04:00 - 22:00<br />
wait_for_operative_window ()<br />
{<br />
get_date_time<br />
<br />
# calulate time in secs until 04:00<br />
if [ $hour -ge 4 ]; then<br />
((sleep_time=((((28-$hour)*60)-$minute)*60)-$second))<br />
else<br />
((sleep_time=((((4-$hour)*60)-$minute)*60)-$second))<br />
fi<br />
<br />
# wait for operative time<br />
if [ $hour -lt 4 -o $hour -ge 22 ]; then<br />
log_info $sleep_time<br />
sleep $sleep_time<br />
fi<br />
}<br />
<br />
# ------------------------<br />
# MAIN: Script starts here<br />
# ------------------------<br />
<br />
# stop any runnning deye_inverter_mqtt processes<br />
deye_inverter_mqtt_stop<br />
<br />
# wait until 04:00 - 22:00<br />
wait_for_operative_window<br />
<br />
# wait until deye inverter starts by reading (old) year/month (register 22)<br />
val22_old=$(${deye_inverter_sh} -c 60 -r 22 | sed 's/int: //g' | cut -d, -f1)<br />
log_info "date/time update initialized"<br />
<br />
# read Daily power register<br />
dailyPower=$(read_daily_Power)<br />
<br />
# daily reset phase<br />
daily_reset=init<br />
<br />
# ensure by loop, that all register values are written<br />
while [ ${daily_reset} != "done" ]; do<br />
<br />
# read (old) day<br />
val23_day=$(${deye_inverter_sh} -c 10 -r 23 | sed 's/.*h: //g' | cut -d, -f1)<br />
<br />
# set actual year, month, day, hour, minute, second<br />
get_date_time<br />
<br />
# calculate register 22-24 by date and time<br />
val22=$(( ${year} * 256 + ${month} ))<br />
val23=$(( ${day} * 256 + ${hour} ))<br />
val24=$(( ${minute} * 256 + ${second} ))<br />
<br />
# reset power/date/time only once a day or exit loop<br />
[ "$dailyPower" -eq 0 -a "${val22_old}" = "${val22}" -a "${val23_day}" = "${day}" ] && break<br />
<br />
# reset daily_power by<br />
# setting register 22-24 with actual date + time<br />
${deye_inverter_sh} -w 22 $val22 || continue<br />
${deye_inverter_sh} -w 23 $val23 || continue<br />
${deye_inverter_sh} -w 24 $val24 || continue<br />
<br />
# test success<br />
# read Daily power register, should be resetted to 0 now<br />
dailyPower=$(read_daily_Power)<br />
<br />
# exit loop if power is updated, else log that reset failed<br />
if [ "$dailyPower" -eq 0 ]; then<br />
daily_reset=done<br />
<br />
### skip loop here, reset doesn't work always :-(<br />
# elif [ $daily_reset = "init" ]; then<br />
# daily_reset=failed<br />
# sleep 60<br />
<br />
else<br />
log_info "daily power reset failed"<br />
break<br />
fi<br />
done<br />
log_info "date/time updated"<br />
<br />
# start deye_mqtt_loop between 04:00 - 22:00<br />
while get_date_time; do<br />
[ $hour -le 22 ] || break<br />
[ $hour -lt 4 ] && break<br />
<br />
# restart<br />
get_deye_mqtt_pid<br />
[ -z "$deye_mqtt_pid" ] && deye_inverter_mqtt_start<br />
<br />
sleep 600<br />
done<br />
</pre><br />
<br />
=== Contrab und /etc/rc.local ===<br />
Mein Crontab Eintrag:<br />
<pre>0 4 * * * /root/sbin/deye_mqtt_loop.sh 2>/dev/null &</pre><br />
und /etc/rc.local:<br />
<pre># start monitoring of PV-System: DEYE inverter<br />
$(sleep 20; /root/sbin/deye_mqtt_loop.sh 2>dev/null)&<br />
</pre><br />
<br />
So sieht das aus, wenn es den ganzen Tag im April regnet und bedeckt ist:<br />
[[Datei:Solar Bedeckt Regen.jpg|midi]]<br />
<br />
Die Eigenverbrauchsquote habe ich mittels Grafana ermittelt:<br />
[[Datei:Solar Eigenverbrauch.jpg|midi|Eigenverbrauchsquote]]</div>
Neobiker
http://neobiker.de/wiki/index.php?title=DEYE_Wechselrichter&diff=2121
DEYE Wechselrichter
2024-01-07T13:39:00Z
<p>Neobiker: /* deye_mqtt_loop.sh */</p>
<hr />
<div>= Balkonkraftwerk: DEYE Wechselrichter ohne Cloud auslesen und per MQTT monitoren =<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
Der WR bietet verschiedene Möglichkeiten zur Konfiguration und Abfrage der aktuellen Werte:<br />
* einen internen Webserver (http://10.10.10.254)<br />
* die Cloud Anbindung zu Solarman (Solarman Smart App)<br />
* (angepasstes) Modbus Protokoll über Port 8899<br />
* AT+ Befehle über Port 48899<br />
<br />
Ich verwende das Modbus Protokoll zum auslesen des WR, das <br />
# effizient/schnell ist (Performance)<br />
# ohne Internetverbindung auskommt<br />
<br />
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.<br />
<br />
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.<br />
<br />
== Wechselrichter Eigenheiten ==<br />
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. <br />
<br />
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.<br />
<br />
== Installation ==<br />
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.<br />
<pre>git clone https://github.com/kbialek/deye-inverter-mqtt<br />
pip install paho-mqtt<br />
cd deye-inverter-mqtt</pre><br />
<br />
Unter Debian habe ich im Skript '''deye_cli.sh''' python durch python3 ersetzen müssen:<br />
<pre>#!/bin/bash<br />
set -a; source config.env; set +a<br />
python3 deye_cli.py "$@"<br />
</pre><br />
<br />
=== config.env ===<br />
Die Konfiguration meines DEYE Wechselrichters vom Typ Micro-Inverter:<br />
'''config.env'''<br />
<pre>DEYE_LOGGER_IP_ADDRESS=<IP Wechselrichter im WLAN><br />
DEYE_LOGGER_PORT=8899<br />
DEYE_LOGGER_SERIAL_NUMBER=<Seriennummer des WR><br />
<br />
MQTT_HOST=<IP von MQTT Server><br />
MQTT_PORT=1883<br />
MQTT_USERNAME=<br />
MQTT_PASSWORD=<br />
MQTT_TOPIC_PREFIX=deye<br />
<br />
LOG_LEVEL=ERROR<br />
DEYE_DATA_READ_INTERVAL=60<br />
DEYE_METRIC_GROUPS=micro<br />
</pre><br />
<br />
=== deye_inverter.sh === <br />
Ausserdem habe ich ein kleines ''Wrapper Skript'' geschrieben, um '''Daten des WR''' einfacher '''lesen und schreiben''' zu können:<br><br />
<br />
'''deye_inverter.sh''' [--check <pause>] --read <register> | --write <register> <value><br />
<br />
<pre>#!/bin/bash<br />
# Read / Write DEYE Inverters<br />
# via deye_inverter_mqtt python package<br />
# https://github.com/kbialek/deye-inverter-mqtt<br />
<br />
retries=2 # retry deye_inverter command multiple times<br />
sleep=5 # sleep time between retries<br />
<br />
# locate script in deye_inverter_mqtt directory<br />
cd $(dirname $0)<br />
if [ ! -f ./deye_cli.sh ]; then<br />
echo "Error: ./deye_cli.sh not found."<br />
echo " Please move $(basename $0) in deye-inverter-mqtt directory."<br />
exit 1<br />
fi<br />
. ./config.env<br />
<br />
log_info ()<br />
{<br />
if [ ${LOG_LEVEL} = "INFO" ]; then<br />
logger -t $(basename $0) $@<br />
fi<br />
}<br />
<br />
log_error ()<br />
{<br />
logger -t $(basename $0) $@<br />
echo 2>&1 $@<br />
}<br />
<br />
# read parameters: mode [rw], register, value and optional -c <check pause><br />
pause=0<br />
while [ $# -gt 0 ]; do<br />
case $1 in<br />
-c|--check) shift<br />
[ $# -ge 3 ] || exit 1<br />
pause=$1<br />
;;<br />
-w|--write) mode=w<br />
shift<br />
[ $# -eq 2 ] || exit 1<br />
reg=$1<br />
val=$2<br />
shift<br />
;;<br />
-r|--read) mode=r<br />
shift<br />
[ $# -eq 1 ] || exit 1<br />
reg=$1<br />
;;<br />
esac<br />
shift<br />
done<br />
<br />
# handle offline deye_inverter by<br />
# ${pause} > 0 -> endless loop<br />
# ${pause} = 0 -> error after ${retries}<br />
while true; do<br />
<br />
# try deye_cli.sh $retries times every $sleep secs<br />
check=${retries}<br />
while [ ${check} -gt 0 ]; do<br />
<br />
case ${mode} in<br />
r) result=$(./deye_cli.sh $mode $reg | grep 'int: ')<br />
if [ -n "$result" ]; then<br />
echo "$result"<br />
log_info -t $0 deye_cli.sh $mode $reg<br />
exit 0<br />
fi<br />
;;<br />
w) result=$(./deye_cli.sh $mode $reg $val)<br />
if [ "$result" = "Ok" ]; then<br />
log_info -t $0 deye_cli.sh $mode $reg $val<br />
exit 0<br />
fi<br />
;;<br />
esac<br />
<br />
# wait ${sleep} or ${pause} secs<br />
sleep $(( ${pause} ? ${sleep} : ${pause} ))<br />
<br />
# try ${retries} times<br />
(( check-- ))<br />
done<br />
<br />
# error, or retry endless until wakeup of deye_inverter<br />
if [ ${pause} -eq 0 ]; then<br />
log_error "Error: deye_cli.sh $mode $reg $val failed."<br />
exit 1<br />
fi<br />
<br />
done<br />
</pre><br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
Folgendes Skript starte ich jeden Morgen um 04:00 Uhr per Cron (und in rc.local fürs booten):<br />
<br />
=== deye_mqtt_loop.sh ===<br />
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.<br />
<br />
Ausserdem setzt es beim Start den Tageszähler des Wechselrichters bedarfsweise zurück, bevor die Werte per Endlosschleife abgefragt werden.<br />
<br />
<pre>#!/bin/bash<br />
# reset Daily_Power of DEYE Inverters<br />
# via deye_inverter_mqtt python package<br />
# https://github.com/kbialek/deye-inverter-mqtt<br />
<br />
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin<br />
<br />
debug=<br />
<br />
# config:<br />
deye_inverter_sh=/root/deye-inverter-mqtt/deye_inverter.sh<br />
deye_inverter_mqtt_cmd="python3 $(dirname ${deye_inverter_sh})/deye_docker_entrypoint.py"<br />
deye_inverter_mqtt_cfg=$(dirname ${deye_inverter_sh})/config.env<br />
<br />
myPID=$$<br />
myname=$(basename $0)<br />
<br />
# installation:<br />
# locate script in deye_inverter_mqtt directory<br />
if [ ! -f ${deye_inverter_mqtt_cfg} ]; then<br />
echo "Error: ${deye_inverter_mqtt_cfg} not found."<br />
echo " Please update \$deye_inverter_sh path in $0"<br />
exit 1<br />
fi<br />
<br />
# read my config<br />
set -a; source ${deye_inverter_mqtt_cfg}; set +a<br />
<br />
# ----- functions () ------------------------------------<br />
log_info ()<br />
{<br />
[ "$debug" = "yes" -o "$debug" = "true" ] && echo $myname: $@<br />
[ ${LOG_LEVEL} = "INFO" ] && logger -t $myname $@<br />
}<br />
<br />
log_error ()<br />
{<br />
logger -t $myname $@<br />
echo 1>&2 $@<br />
}<br />
<br />
# store actual date + time into vars<br />
get_date_time ()<br />
{<br />
year=$(date +%y)<br />
month=$(date +%m | sed 's/^0//g')<br />
day=$(date +%d | sed 's/^0//g')<br />
hour=$(date +%H | sed 's/^0//g')<br />
minute=$(date +%m | sed 's/^0//g')<br />
second=$(date +%S | sed 's/^0//g')<br />
}<br />
<br />
# get deye_inverter_mqtt PIDs<br />
get_deye_mqtt_pid ()<br />
{<br />
deye_mqtt_pid=$(ps ax | grep -v grep | grep "${deye_inverter_mqtt_cmd}" | awk '{print $1}')<br />
}<br />
<br />
# kill deye_inverter_loop ()<br />
kill_deye_inverter_loop ()<br />
{<br />
# kill old instances of me<br />
my_PS=$(ps ax | grep -v grep | grep ${myname} | awk '{print $1}')<br />
for ps in $my_PS; do<br />
[ $ps -lt $myPID ] && kill $ps<br />
done<br />
}<br />
<br />
# kill deye_inverter_instance ()<br />
kill_deye_inverter_instance ()<br />
{<br />
get_deye_mqtt_pid<br />
if [ ! -z "${deye_mqtt_pid}" ]; then<br />
kill ${deye_mqtt_pid}<br />
log_info "Info: KILL running deye_mqtt ${deye_mqtt_pid}"<br />
fi<br />
<br />
# kill any running deye_requests<br />
my_PS=$(ps ax | grep -v grep | grep "${deye_inverter_sh}" | awk '{print $1}')<br />
[ ! -z "$my_PS" ] && kill $my_PS<br />
}<br />
<br />
# stop all runnning instances<br />
deye_inverter_mqtt_stop ()<br />
{<br />
kill_deye_inverter_loop<br />
kill_deye_inverter_instance<br />
}<br />
<br />
# start deye_inverter_mqtt<br />
deye_inverter_mqtt_start ()<br />
{<br />
log_info "MQTT start"<br />
${deye_inverter_mqtt_cmd}<br />
}<br />
<br />
# read Daily power register<br />
read_daily_Power ()<br />
{<br />
${deye_inverter_sh} -c 10 -r 60 | sed 's/int: //g' | cut -d, -f1<br />
}<br />
<br />
# wait until 04:00 - 22:00<br />
wait_for_operative_window ()<br />
{<br />
get_date_time<br />
<br />
# calulate time in secs until 04:00<br />
if [ $hour -ge 4 ]; then<br />
((sleep_time=((((28-$hour)*60)-$minute)*60)-$second))<br />
else<br />
((sleep_time=((((4-$hour)*60)-$minute)*60)-$second))<br />
fi<br />
<br />
# wait for operative time<br />
if [ $hour -lt 4 -o $hour -ge 22 ]; then<br />
log_info $sleep_time<br />
sleep $sleep_time<br />
fi<br />
}<br />
<br />
# ------------------------<br />
# MAIN: Script starts here<br />
# ------------------------<br />
<br />
# stop any runnning deye_inverter_mqtt processes<br />
deye_inverter_mqtt_stop<br />
<br />
# wait until 04:00 - 22:00<br />
wait_for_operative_window<br />
<br />
# wait until deye inverter starts by reading (old) year/month (register 22)<br />
val22_old=$(${deye_inverter_sh} -c 60 -r 22 | sed 's/int: //g' | cut -d, -f1)<br />
log_info "date/time update initialized"<br />
<br />
# read Daily power register<br />
dailyPower=$(read_daily_Power)<br />
<br />
# daily reset phase<br />
daily_reset=init<br />
<br />
# ensure by loop, that all register values are written<br />
while [ ${daily_reset} != "done" ]; do<br />
<br />
# read (old) day<br />
val23_day=$(${deye_inverter_sh} -c 10 -r 23 | sed 's/.*h: //g' | cut -d, -f1)<br />
<br />
# set actual year, month, day, hour, minute, second<br />
get_date_time<br />
<br />
# calculate register 22-24 by date and time<br />
val22=$(( ${year} * 256 + ${month} ))<br />
val23=$(( ${day} * 256 + ${hour} ))<br />
val24=$(( ${minute} * 256 + ${second} ))<br />
<br />
# reset power/date/time only once a day or exit loop<br />
[ "$dailyPower" -eq 0 -a "${val22_old}" = "${val22}" -a "${val23_day}" = "${day}" ] && break<br />
<br />
# reset daily_power by<br />
# setting register 22-24 with actual date + time<br />
${deye_inverter_sh} -w 22 $val22 || continue<br />
${deye_inverter_sh} -w 23 $val23 || continue<br />
${deye_inverter_sh} -w 24 $val24 || continue<br />
<br />
# test success<br />
# read Daily power register, should be resetted to 0 now<br />
dailyPower=$(read_daily_Power)<br />
<br />
# exit loop if power is updated, else log that reset failed<br />
if [ "$dailyPower" -eq 0 ]; then<br />
daily_reset=done<br />
<br />
### skip loop here, reset doesn't work always :-(<br />
# elif [ $daily_reset = "init" ]; then<br />
# daily_reset=failed<br />
# sleep 60<br />
<br />
else<br />
log_info "daily power reset failed"<br />
break<br />
fi<br />
done<br />
log_info "date/time updated"<br />
<br />
# start deye_mqtt_loop between 04:00 - 22:00<br />
while get_date_time; do<br />
[ $hour -le 22 ] || break<br />
[ $hour -lt 4 ] && break<br />
<br />
# restart<br />
get_deye_mqtt_pid<br />
[ -z "$deye_mqtt_pid" ] && deye_inverter_mqtt_start<br />
<br />
sleep 600<br />
done<br />
</pre><br />
<br />
=== Contrab und /etc/rc.local ===<br />
Mein Crontab Eintrag:<br />
<pre>0 4 * * * /root/sbin/deye_mqtt_loop.sh 2>/dev/null &</pre><br />
und /etc/rc.local:<br />
<pre># start monitoring of PV-System: DEYE inverter<br />
$(sleep 20; cd /root/deye-inverter-mqtt; screen -d -m ./deye_mqtt_loop.sh)&<br />
</pre><br />
<br />
So sieht das aus, wenn es den ganzen Tag im April regnet und bedeckt ist:<br />
[[Datei:Solar Bedeckt Regen.jpg|midi]]<br />
<br />
Die Eigenverbrauchsquote habe ich mittels Grafana ermittelt:<br />
[[Datei:Solar Eigenverbrauch.jpg|midi|Eigenverbrauchsquote]]</div>
Neobiker
http://neobiker.de/wiki/index.php?title=DEYE_Wechselrichter&diff=2120
DEYE Wechselrichter
2024-01-07T13:37:11Z
<p>Neobiker: /* deye_mqtt_loop.sh */</p>
<hr />
<div>= Balkonkraftwerk: DEYE Wechselrichter ohne Cloud auslesen und per MQTT monitoren =<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
Der WR bietet verschiedene Möglichkeiten zur Konfiguration und Abfrage der aktuellen Werte:<br />
* einen internen Webserver (http://10.10.10.254)<br />
* die Cloud Anbindung zu Solarman (Solarman Smart App)<br />
* (angepasstes) Modbus Protokoll über Port 8899<br />
* AT+ Befehle über Port 48899<br />
<br />
Ich verwende das Modbus Protokoll zum auslesen des WR, das <br />
# effizient/schnell ist (Performance)<br />
# ohne Internetverbindung auskommt<br />
<br />
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.<br />
<br />
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.<br />
<br />
== Wechselrichter Eigenheiten ==<br />
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. <br />
<br />
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.<br />
<br />
== Installation ==<br />
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.<br />
<pre>git clone https://github.com/kbialek/deye-inverter-mqtt<br />
pip install paho-mqtt<br />
cd deye-inverter-mqtt</pre><br />
<br />
Unter Debian habe ich im Skript '''deye_cli.sh''' python durch python3 ersetzen müssen:<br />
<pre>#!/bin/bash<br />
set -a; source config.env; set +a<br />
python3 deye_cli.py "$@"<br />
</pre><br />
<br />
=== config.env ===<br />
Die Konfiguration meines DEYE Wechselrichters vom Typ Micro-Inverter:<br />
'''config.env'''<br />
<pre>DEYE_LOGGER_IP_ADDRESS=<IP Wechselrichter im WLAN><br />
DEYE_LOGGER_PORT=8899<br />
DEYE_LOGGER_SERIAL_NUMBER=<Seriennummer des WR><br />
<br />
MQTT_HOST=<IP von MQTT Server><br />
MQTT_PORT=1883<br />
MQTT_USERNAME=<br />
MQTT_PASSWORD=<br />
MQTT_TOPIC_PREFIX=deye<br />
<br />
LOG_LEVEL=ERROR<br />
DEYE_DATA_READ_INTERVAL=60<br />
DEYE_METRIC_GROUPS=micro<br />
</pre><br />
<br />
=== deye_inverter.sh === <br />
Ausserdem habe ich ein kleines ''Wrapper Skript'' geschrieben, um '''Daten des WR''' einfacher '''lesen und schreiben''' zu können:<br><br />
<br />
'''deye_inverter.sh''' [--check <pause>] --read <register> | --write <register> <value><br />
<br />
<pre>#!/bin/bash<br />
# Read / Write DEYE Inverters<br />
# via deye_inverter_mqtt python package<br />
# https://github.com/kbialek/deye-inverter-mqtt<br />
<br />
retries=2 # retry deye_inverter command multiple times<br />
sleep=5 # sleep time between retries<br />
<br />
# locate script in deye_inverter_mqtt directory<br />
cd $(dirname $0)<br />
if [ ! -f ./deye_cli.sh ]; then<br />
echo "Error: ./deye_cli.sh not found."<br />
echo " Please move $(basename $0) in deye-inverter-mqtt directory."<br />
exit 1<br />
fi<br />
. ./config.env<br />
<br />
log_info ()<br />
{<br />
if [ ${LOG_LEVEL} = "INFO" ]; then<br />
logger -t $(basename $0) $@<br />
fi<br />
}<br />
<br />
log_error ()<br />
{<br />
logger -t $(basename $0) $@<br />
echo 2>&1 $@<br />
}<br />
<br />
# read parameters: mode [rw], register, value and optional -c <check pause><br />
pause=0<br />
while [ $# -gt 0 ]; do<br />
case $1 in<br />
-c|--check) shift<br />
[ $# -ge 3 ] || exit 1<br />
pause=$1<br />
;;<br />
-w|--write) mode=w<br />
shift<br />
[ $# -eq 2 ] || exit 1<br />
reg=$1<br />
val=$2<br />
shift<br />
;;<br />
-r|--read) mode=r<br />
shift<br />
[ $# -eq 1 ] || exit 1<br />
reg=$1<br />
;;<br />
esac<br />
shift<br />
done<br />
<br />
# handle offline deye_inverter by<br />
# ${pause} > 0 -> endless loop<br />
# ${pause} = 0 -> error after ${retries}<br />
while true; do<br />
<br />
# try deye_cli.sh $retries times every $sleep secs<br />
check=${retries}<br />
while [ ${check} -gt 0 ]; do<br />
<br />
case ${mode} in<br />
r) result=$(./deye_cli.sh $mode $reg | grep 'int: ')<br />
if [ -n "$result" ]; then<br />
echo "$result"<br />
log_info -t $0 deye_cli.sh $mode $reg<br />
exit 0<br />
fi<br />
;;<br />
w) result=$(./deye_cli.sh $mode $reg $val)<br />
if [ "$result" = "Ok" ]; then<br />
log_info -t $0 deye_cli.sh $mode $reg $val<br />
exit 0<br />
fi<br />
;;<br />
esac<br />
<br />
# wait ${sleep} or ${pause} secs<br />
sleep $(( ${pause} ? ${sleep} : ${pause} ))<br />
<br />
# try ${retries} times<br />
(( check-- ))<br />
done<br />
<br />
# error, or retry endless until wakeup of deye_inverter<br />
if [ ${pause} -eq 0 ]; then<br />
log_error "Error: deye_cli.sh $mode $reg $val failed."<br />
exit 1<br />
fi<br />
<br />
done<br />
</pre><br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
Folgendes Skript starte ich jeden Morgen um 04:00 Uhr per Cron (und in rc.local fürs booten):<br />
<br />
=== deye_mqtt_loop.sh ===<br />
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.<br />
<pre>#!/bin/bash<br />
# reset Daily_Power of DEYE Inverters<br />
# via deye_inverter_mqtt python package<br />
# https://github.com/kbialek/deye-inverter-mqtt<br />
<br />
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin<br />
<br />
debug=<br />
<br />
# config:<br />
deye_inverter_sh=/root/deye-inverter-mqtt/deye_inverter.sh<br />
deye_inverter_mqtt_cmd="python3 $(dirname ${deye_inverter_sh})/deye_docker_entrypoint.py"<br />
deye_inverter_mqtt_cfg=$(dirname ${deye_inverter_sh})/config.env<br />
<br />
myPID=$$<br />
myname=$(basename $0)<br />
<br />
# installation:<br />
# locate script in deye_inverter_mqtt directory<br />
if [ ! -f ${deye_inverter_mqtt_cfg} ]; then<br />
echo "Error: ${deye_inverter_mqtt_cfg} not found."<br />
echo " Please update \$deye_inverter_sh path in $0"<br />
exit 1<br />
fi<br />
<br />
# read my config<br />
set -a; source ${deye_inverter_mqtt_cfg}; set +a<br />
<br />
# ----- functions () ------------------------------------<br />
log_info ()<br />
{<br />
[ "$debug" = "yes" -o "$debug" = "true" ] && echo $myname: $@<br />
[ ${LOG_LEVEL} = "INFO" ] && logger -t $myname $@<br />
}<br />
<br />
log_error ()<br />
{<br />
logger -t $myname $@<br />
echo 1>&2 $@<br />
}<br />
<br />
# store actual date + time into vars<br />
get_date_time ()<br />
{<br />
year=$(date +%y)<br />
month=$(date +%m | sed 's/^0//g')<br />
day=$(date +%d | sed 's/^0//g')<br />
hour=$(date +%H | sed 's/^0//g')<br />
minute=$(date +%m | sed 's/^0//g')<br />
second=$(date +%S | sed 's/^0//g')<br />
}<br />
<br />
# get deye_inverter_mqtt PIDs<br />
get_deye_mqtt_pid ()<br />
{<br />
deye_mqtt_pid=$(ps ax | grep -v grep | grep "${deye_inverter_mqtt_cmd}" | awk '{print $1}')<br />
}<br />
<br />
# kill deye_inverter_loop ()<br />
kill_deye_inverter_loop ()<br />
{<br />
# kill old instances of me<br />
my_PS=$(ps ax | grep -v grep | grep ${myname} | awk '{print $1}')<br />
for ps in $my_PS; do<br />
[ $ps -lt $myPID ] && kill $ps<br />
done<br />
}<br />
<br />
# kill deye_inverter_instance ()<br />
kill_deye_inverter_instance ()<br />
{<br />
get_deye_mqtt_pid<br />
if [ ! -z "${deye_mqtt_pid}" ]; then<br />
kill ${deye_mqtt_pid}<br />
log_info "Info: KILL running deye_mqtt ${deye_mqtt_pid}"<br />
fi<br />
<br />
# kill any running deye_requests<br />
my_PS=$(ps ax | grep -v grep | grep "${deye_inverter_sh}" | awk '{print $1}')<br />
[ ! -z "$my_PS" ] && kill $my_PS<br />
}<br />
<br />
# stop all runnning instances<br />
deye_inverter_mqtt_stop ()<br />
{<br />
kill_deye_inverter_loop<br />
kill_deye_inverter_instance<br />
}<br />
<br />
# start deye_inverter_mqtt<br />
deye_inverter_mqtt_start ()<br />
{<br />
log_info "MQTT start"<br />
${deye_inverter_mqtt_cmd}<br />
}<br />
<br />
# read Daily power register<br />
read_daily_Power ()<br />
{<br />
${deye_inverter_sh} -c 10 -r 60 | sed 's/int: //g' | cut -d, -f1<br />
}<br />
<br />
# wait until 04:00 - 22:00<br />
wait_for_operative_window ()<br />
{<br />
get_date_time<br />
<br />
# calulate time in secs until 04:00<br />
if [ $hour -ge 4 ]; then<br />
((sleep_time=((((28-$hour)*60)-$minute)*60)-$second))<br />
else<br />
((sleep_time=((((4-$hour)*60)-$minute)*60)-$second))<br />
fi<br />
<br />
# wait for operative time<br />
if [ $hour -lt 4 -o $hour -ge 22 ]; then<br />
log_info $sleep_time<br />
sleep $sleep_time<br />
fi<br />
}<br />
<br />
# ------------------------<br />
# MAIN: Script starts here<br />
# ------------------------<br />
<br />
# stop any runnning deye_inverter_mqtt processes<br />
deye_inverter_mqtt_stop<br />
<br />
# wait until 04:00 - 22:00<br />
wait_for_operative_window<br />
<br />
# wait until deye inverter starts by reading (old) year/month (register 22)<br />
val22_old=$(${deye_inverter_sh} -c 60 -r 22 | sed 's/int: //g' | cut -d, -f1)<br />
log_info "date/time update initialized"<br />
<br />
# read Daily power register<br />
dailyPower=$(read_daily_Power)<br />
<br />
# daily reset phase<br />
daily_reset=init<br />
<br />
# ensure by loop, that all register values are written<br />
while [ ${daily_reset} != "done" ]; do<br />
<br />
# read (old) day<br />
val23_day=$(${deye_inverter_sh} -c 10 -r 23 | sed 's/.*h: //g' | cut -d, -f1)<br />
<br />
# set actual year, month, day, hour, minute, second<br />
get_date_time<br />
<br />
# calculate register 22-24 by date and time<br />
val22=$(( ${year} * 256 + ${month} ))<br />
val23=$(( ${day} * 256 + ${hour} ))<br />
val24=$(( ${minute} * 256 + ${second} ))<br />
<br />
# reset power/date/time only once a day or exit loop<br />
[ "$dailyPower" -eq 0 -a "${val22_old}" = "${val22}" -a "${val23_day}" = "${day}" ] && break<br />
<br />
# reset daily_power by<br />
# setting register 22-24 with actual date + time<br />
${deye_inverter_sh} -w 22 $val22 || continue<br />
${deye_inverter_sh} -w 23 $val23 || continue<br />
${deye_inverter_sh} -w 24 $val24 || continue<br />
<br />
# test success<br />
# read Daily power register, should be resetted to 0 now<br />
dailyPower=$(read_daily_Power)<br />
<br />
# exit loop if power is updated, else log that reset failed<br />
if [ "$dailyPower" -eq 0 ]; then<br />
daily_reset=done<br />
<br />
### skip loop here, reset doesn't work always :-(<br />
# elif [ $daily_reset = "init" ]; then<br />
# daily_reset=failed<br />
# sleep 60<br />
<br />
else<br />
log_info "daily power reset failed"<br />
break<br />
fi<br />
done<br />
log_info "date/time updated"<br />
<br />
# start deye_mqtt_loop between 04:00 - 22:00<br />
while get_date_time; do<br />
[ $hour -le 22 ] || break<br />
[ $hour -lt 4 ] && break<br />
<br />
# restart<br />
get_deye_mqtt_pid<br />
[ -z "$deye_mqtt_pid" ] && deye_inverter_mqtt_start<br />
<br />
sleep 600<br />
done<br />
</pre><br />
<br />
=== Contrab und /etc/rc.local ===<br />
Mein Crontab Eintrag:<br />
<pre>0 4 * * * /root/sbin/deye_mqtt_loop.sh 2>/dev/null &</pre><br />
und /etc/rc.local:<br />
<pre># start monitoring of PV-System: DEYE inverter<br />
$(sleep 20; cd /root/deye-inverter-mqtt; screen -d -m ./deye_mqtt_loop.sh)&<br />
</pre><br />
<br />
So sieht das aus, wenn es den ganzen Tag im April regnet und bedeckt ist:<br />
[[Datei:Solar Bedeckt Regen.jpg|midi]]<br />
<br />
Die Eigenverbrauchsquote habe ich mittels Grafana ermittelt:<br />
[[Datei:Solar Eigenverbrauch.jpg|midi|Eigenverbrauchsquote]]</div>
Neobiker
http://neobiker.de/wiki/index.php?title=DEYE_Wechselrichter&diff=2119
DEYE Wechselrichter
2024-01-07T13:36:52Z
<p>Neobiker: /* deye_mqtt_loop.sh */</p>
<hr />
<div>= Balkonkraftwerk: DEYE Wechselrichter ohne Cloud auslesen und per MQTT monitoren =<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
Der WR bietet verschiedene Möglichkeiten zur Konfiguration und Abfrage der aktuellen Werte:<br />
* einen internen Webserver (http://10.10.10.254)<br />
* die Cloud Anbindung zu Solarman (Solarman Smart App)<br />
* (angepasstes) Modbus Protokoll über Port 8899<br />
* AT+ Befehle über Port 48899<br />
<br />
Ich verwende das Modbus Protokoll zum auslesen des WR, das <br />
# effizient/schnell ist (Performance)<br />
# ohne Internetverbindung auskommt<br />
<br />
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.<br />
<br />
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.<br />
<br />
== Wechselrichter Eigenheiten ==<br />
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. <br />
<br />
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.<br />
<br />
== Installation ==<br />
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.<br />
<pre>git clone https://github.com/kbialek/deye-inverter-mqtt<br />
pip install paho-mqtt<br />
cd deye-inverter-mqtt</pre><br />
<br />
Unter Debian habe ich im Skript '''deye_cli.sh''' python durch python3 ersetzen müssen:<br />
<pre>#!/bin/bash<br />
set -a; source config.env; set +a<br />
python3 deye_cli.py "$@"<br />
</pre><br />
<br />
=== config.env ===<br />
Die Konfiguration meines DEYE Wechselrichters vom Typ Micro-Inverter:<br />
'''config.env'''<br />
<pre>DEYE_LOGGER_IP_ADDRESS=<IP Wechselrichter im WLAN><br />
DEYE_LOGGER_PORT=8899<br />
DEYE_LOGGER_SERIAL_NUMBER=<Seriennummer des WR><br />
<br />
MQTT_HOST=<IP von MQTT Server><br />
MQTT_PORT=1883<br />
MQTT_USERNAME=<br />
MQTT_PASSWORD=<br />
MQTT_TOPIC_PREFIX=deye<br />
<br />
LOG_LEVEL=ERROR<br />
DEYE_DATA_READ_INTERVAL=60<br />
DEYE_METRIC_GROUPS=micro<br />
</pre><br />
<br />
=== deye_inverter.sh === <br />
Ausserdem habe ich ein kleines ''Wrapper Skript'' geschrieben, um '''Daten des WR''' einfacher '''lesen und schreiben''' zu können:<br><br />
<br />
'''deye_inverter.sh''' [--check <pause>] --read <register> | --write <register> <value><br />
<br />
<pre>#!/bin/bash<br />
# Read / Write DEYE Inverters<br />
# via deye_inverter_mqtt python package<br />
# https://github.com/kbialek/deye-inverter-mqtt<br />
<br />
retries=2 # retry deye_inverter command multiple times<br />
sleep=5 # sleep time between retries<br />
<br />
# locate script in deye_inverter_mqtt directory<br />
cd $(dirname $0)<br />
if [ ! -f ./deye_cli.sh ]; then<br />
echo "Error: ./deye_cli.sh not found."<br />
echo " Please move $(basename $0) in deye-inverter-mqtt directory."<br />
exit 1<br />
fi<br />
. ./config.env<br />
<br />
log_info ()<br />
{<br />
if [ ${LOG_LEVEL} = "INFO" ]; then<br />
logger -t $(basename $0) $@<br />
fi<br />
}<br />
<br />
log_error ()<br />
{<br />
logger -t $(basename $0) $@<br />
echo 2>&1 $@<br />
}<br />
<br />
# read parameters: mode [rw], register, value and optional -c <check pause><br />
pause=0<br />
while [ $# -gt 0 ]; do<br />
case $1 in<br />
-c|--check) shift<br />
[ $# -ge 3 ] || exit 1<br />
pause=$1<br />
;;<br />
-w|--write) mode=w<br />
shift<br />
[ $# -eq 2 ] || exit 1<br />
reg=$1<br />
val=$2<br />
shift<br />
;;<br />
-r|--read) mode=r<br />
shift<br />
[ $# -eq 1 ] || exit 1<br />
reg=$1<br />
;;<br />
esac<br />
shift<br />
done<br />
<br />
# handle offline deye_inverter by<br />
# ${pause} > 0 -> endless loop<br />
# ${pause} = 0 -> error after ${retries}<br />
while true; do<br />
<br />
# try deye_cli.sh $retries times every $sleep secs<br />
check=${retries}<br />
while [ ${check} -gt 0 ]; do<br />
<br />
case ${mode} in<br />
r) result=$(./deye_cli.sh $mode $reg | grep 'int: ')<br />
if [ -n "$result" ]; then<br />
echo "$result"<br />
log_info -t $0 deye_cli.sh $mode $reg<br />
exit 0<br />
fi<br />
;;<br />
w) result=$(./deye_cli.sh $mode $reg $val)<br />
if [ "$result" = "Ok" ]; then<br />
log_info -t $0 deye_cli.sh $mode $reg $val<br />
exit 0<br />
fi<br />
;;<br />
esac<br />
<br />
# wait ${sleep} or ${pause} secs<br />
sleep $(( ${pause} ? ${sleep} : ${pause} ))<br />
<br />
# try ${retries} times<br />
(( check-- ))<br />
done<br />
<br />
# error, or retry endless until wakeup of deye_inverter<br />
if [ ${pause} -eq 0 ]; then<br />
log_error "Error: deye_cli.sh $mode $reg $val failed."<br />
exit 1<br />
fi<br />
<br />
done<br />
</pre><br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
Folgendes Skript starte ich jeden Morgen um 04:00 Uhr per Cron (und in rc.local fürs booten):<br />
<br />
=== deye_mqtt_loop.sh ===<br />
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.<br />
<pre>#!/bin/bash<br />
# reset Daily_Power of DEYE Inverters<br />
# via deye_inverter_mqtt python package<br />
# https://github.com/kbialek/deye-inverter-mqtt<br />
<br />
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin<br />
<br />
debug=<br />
<br />
# config:<br />
deye_inverter_sh=/root/deye-inverter-mqtt/deye_inverter.sh<br />
deye_inverter_mqtt_cmd="python3 $(dirname ${deye_inverter_sh})/deye_docker_entrypoint.py"<br />
deye_inverter_mqtt_cfg=$(dirname ${deye_inverter_sh})/config.env<br />
<br />
myPID=$$<br />
myname=$(basename $0)<br />
<br />
# installation:<br />
# locate script in deye_inverter_mqtt directory<br />
if [ ! -f ${deye_inverter_mqtt_cfg} ]; then<br />
echo "Error: ${deye_inverter_mqtt_cfg} not found."<br />
echo " Please update \$deye_inverter_sh path in $0"<br />
exit 1<br />
fi<br />
<br />
# read my config<br />
set -a; source ${deye_inverter_mqtt_cfg}; set +a<br />
<br />
# ----- functions () ------------------------------------<br />
log_info ()<br />
{<br />
[ "$debug" = "yes" -o "$debug" = "true" ] && echo $myname: $@<br />
[ ${LOG_LEVEL} = "INFO" ] && logger -t $myname $@<br />
}<br />
<br />
log_error ()<br />
{<br />
logger -t $myname $@<br />
echo 1>&2 $@<br />
}<br />
<br />
# store actual date + time into vars<br />
get_date_time ()<br />
{<br />
year=$(date +%y)<br />
month=$(date +%m | sed 's/^0//g')<br />
day=$(date +%d | sed 's/^0//g')<br />
hour=$(date +%H | sed 's/^0//g')<br />
minute=$(date +%m | sed 's/^0//g')<br />
second=$(date +%S | sed 's/^0//g')<br />
}<br />
<br />
# get deye_inverter_mqtt PIDs<br />
get_deye_mqtt_pid ()<br />
{<br />
deye_mqtt_pid=$(ps ax | grep -v grep | grep "${deye_inverter_mqtt_cmd}" | awk '{print $1}')<br />
}<br />
<br />
# kill deye_inverter_loop ()<br />
kill_deye_inverter_loop ()<br />
{<br />
# kill old instances of me<br />
my_PS=$(ps ax | grep -v grep | grep ${myname} | awk '{print $1}')<br />
for ps in $my_PS; do<br />
[ $ps -lt $myPID ] && kill $ps<br />
done<br />
}<br />
<br />
# kill deye_inverter_instance ()<br />
kill_deye_inverter_instance ()<br />
{<br />
get_deye_mqtt_pid<br />
if [ ! -z "${deye_mqtt_pid}" ]; then<br />
kill ${deye_mqtt_pid}<br />
log_info "Info: KILL running deye_mqtt ${deye_mqtt_pid}"<br />
fi<br />
<br />
# kill any running deye_requests<br />
my_PS=$(ps ax | grep -v grep | grep "${deye_inverter_sh}" | awk '{print $1}')<br />
[ ! -z "$my_PS" ] && kill $my_PS<br />
}<br />
<br />
# stop all runnning instances<br />
deye_inverter_mqtt_stop ()<br />
{<br />
kill_deye_inverter_loop<br />
kill_deye_inverter_instance<br />
}<br />
<br />
# start deye_inverter_mqtt<br />
deye_inverter_mqtt_start ()<br />
{<br />
log_info "MQTT start"<br />
${deye_inverter_mqtt_cmd}<br />
}<br />
<br />
# read Daily power register<br />
read_daily_Power ()<br />
{<br />
${deye_inverter_sh} -c 10 -r 60 | sed 's/int: //g' | cut -d, -f1<br />
}<br />
<br />
# wait until 04:00 - 22:00<br />
wait_for_operative_window ()<br />
{<br />
get_date_time<br />
<br />
# calulate time in secs until 04:00<br />
if [ $hour -ge 4 ]; then<br />
((sleep_time=((((28-$hour)*60)-$minute)*60)-$second))<br />
else<br />
((sleep_time=((((4-$hour)*60)-$minute)*60)-$second))<br />
fi<br />
<br />
# wait for operative time<br />
if [ $hour -lt 4 -o $hour -ge 22 ]; then<br />
log_info $sleep_time<br />
sleep $sleep_time<br />
fi<br />
}<br />
<br />
# ------------------------<br />
# MAIN: Script starts here<br />
# ------------------------<br />
<br />
# stop any runnning deye_inverter_mqtt processes<br />
deye_inverter_mqtt_stop<br />
<br />
# wait until 04:00 - 22:00<br />
wait_for_operative_window<br />
<br />
# wait until deye inverter starts by reading (old) year/month (register 22)<br />
val22_old=$(${deye_inverter_sh} -c 60 -r 22 | sed 's/int: //g' | cut -d, -f1)<br />
log_info "date/time update initialized"<br />
<br />
# read Daily power register<br />
dailyPower=$(read_daily_Power)<br />
<br />
# daily reset phase<br />
daily_reset=init<br />
<br />
# ensure by loop, that all register values are written<br />
while [ ${daily_reset} != "done" ]; do<br />
<br />
# read (old) day<br />
val23_day=$(${deye_inverter_sh} -c 10 -r 23 | sed 's/.*h: //g' | cut -d, -f1)<br />
<br />
# set actual year, month, day, hour, minute, second<br />
get_date_time<br />
<br />
# calculate register 22-24 by date and time<br />
val22=$(( ${year} * 256 + ${month} ))<br />
val23=$(( ${day} * 256 + ${hour} ))<br />
val24=$(( ${minute} * 256 + ${second} ))<br />
<br />
# reset power/date/time only once a day or exit loop<br />
[ "$dailyPower" -eq 0 -a "${val22_old}" = "${val22}" -a "${val23_day}" = "${day}" ] && break<br />
<br />
# reset daily_power by<br />
# setting register 22-24 with actual date + time<br />
${deye_inverter_sh} -w 22 $val22 || continue<br />
${deye_inverter_sh} -w 23 $val23 || continue<br />
${deye_inverter_sh} -w 24 $val24 || continue<br />
<br />
# test success<br />
# read Daily power register, should be resetted to 0 now<br />
dailyPower=$(read_daily_Power)<br />
<br />
# exit loop if power is updated, else log that reset failed<br />
if [ "$dailyPower" -eq 0 ]; then<br />
daily_reset=done<br />
<br />
### skip loop here, reset doesn't work always :-(<br />
# elif [ $daily_reset = "init" ]; then<br />
# daily_reset=failed<br />
# sleep 60<br />
<br />
else<br />
log_info "daily power reset failed"<br />
break<br />
fi<br />
done<br />
log_info "date/time updated"<br />
<br />
# start deye_mqtt_loop between 04:00 - 22:00<br />
while get_date_time; do<br />
[ $hour -le 22 ] || break<br />
[ $hour -lt 4 ] && break<br />
<br />
# restart<br />
get_deye_mqtt_pid<br />
[ -z "$deye_mqtt_pid" ] && deye_inverter_mqtt_start<br />
<br />
sleep 600<br />
done<br />
</pre><br />
<br />
=== Contrab und /etc/rc.local ===<br />
Mein Crontab Eintrag:<br />
<pre>0 4 * * * /root/sbin/deye_mqtt_loop.sh 2>/dev/null &</pre><br />
und /etc/rc.local:<br />
<pre># start monitoring of PV-System: DEYE inverter<br />
$(sleep 20; cd /root/deye-inverter-mqtt; screen -d -m ./deye_mqtt_loop.sh)&<br />
</pre><br />
<br />
So sieht das aus, wenn es den ganzen Tag im April regnet und bedeckt ist:<br />
[[Datei:Solar Bedeckt Regen.jpg|midi]]<br />
<br />
Die Eigenverbrauchsquote habe ich mittels Grafana ermittelt:<br />
[[Datei:Solar Eigenverbrauch.jpg|midi|Eigenverbrauchsquote]]</div>
Neobiker
http://neobiker.de/wiki/index.php?title=DEYE_Wechselrichter&diff=2118
DEYE Wechselrichter
2024-01-07T13:34:59Z
<p>Neobiker: /* deye_inverter.sh */</p>
<hr />
<div>= Balkonkraftwerk: DEYE Wechselrichter ohne Cloud auslesen und per MQTT monitoren =<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
Der WR bietet verschiedene Möglichkeiten zur Konfiguration und Abfrage der aktuellen Werte:<br />
* einen internen Webserver (http://10.10.10.254)<br />
* die Cloud Anbindung zu Solarman (Solarman Smart App)<br />
* (angepasstes) Modbus Protokoll über Port 8899<br />
* AT+ Befehle über Port 48899<br />
<br />
Ich verwende das Modbus Protokoll zum auslesen des WR, das <br />
# effizient/schnell ist (Performance)<br />
# ohne Internetverbindung auskommt<br />
<br />
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.<br />
<br />
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.<br />
<br />
== Wechselrichter Eigenheiten ==<br />
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. <br />
<br />
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.<br />
<br />
== Installation ==<br />
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.<br />
<pre>git clone https://github.com/kbialek/deye-inverter-mqtt<br />
pip install paho-mqtt<br />
cd deye-inverter-mqtt</pre><br />
<br />
Unter Debian habe ich im Skript '''deye_cli.sh''' python durch python3 ersetzen müssen:<br />
<pre>#!/bin/bash<br />
set -a; source config.env; set +a<br />
python3 deye_cli.py "$@"<br />
</pre><br />
<br />
=== config.env ===<br />
Die Konfiguration meines DEYE Wechselrichters vom Typ Micro-Inverter:<br />
'''config.env'''<br />
<pre>DEYE_LOGGER_IP_ADDRESS=<IP Wechselrichter im WLAN><br />
DEYE_LOGGER_PORT=8899<br />
DEYE_LOGGER_SERIAL_NUMBER=<Seriennummer des WR><br />
<br />
MQTT_HOST=<IP von MQTT Server><br />
MQTT_PORT=1883<br />
MQTT_USERNAME=<br />
MQTT_PASSWORD=<br />
MQTT_TOPIC_PREFIX=deye<br />
<br />
LOG_LEVEL=ERROR<br />
DEYE_DATA_READ_INTERVAL=60<br />
DEYE_METRIC_GROUPS=micro<br />
</pre><br />
<br />
=== deye_inverter.sh === <br />
Ausserdem habe ich ein kleines ''Wrapper Skript'' geschrieben, um '''Daten des WR''' einfacher '''lesen und schreiben''' zu können:<br><br />
<br />
'''deye_inverter.sh''' [--check <pause>] --read <register> | --write <register> <value><br />
<br />
<pre>#!/bin/bash<br />
# Read / Write DEYE Inverters<br />
# via deye_inverter_mqtt python package<br />
# https://github.com/kbialek/deye-inverter-mqtt<br />
<br />
retries=2 # retry deye_inverter command multiple times<br />
sleep=5 # sleep time between retries<br />
<br />
# locate script in deye_inverter_mqtt directory<br />
cd $(dirname $0)<br />
if [ ! -f ./deye_cli.sh ]; then<br />
echo "Error: ./deye_cli.sh not found."<br />
echo " Please move $(basename $0) in deye-inverter-mqtt directory."<br />
exit 1<br />
fi<br />
. ./config.env<br />
<br />
log_info ()<br />
{<br />
if [ ${LOG_LEVEL} = "INFO" ]; then<br />
logger -t $(basename $0) $@<br />
fi<br />
}<br />
<br />
log_error ()<br />
{<br />
logger -t $(basename $0) $@<br />
echo 2>&1 $@<br />
}<br />
<br />
# read parameters: mode [rw], register, value and optional -c <check pause><br />
pause=0<br />
while [ $# -gt 0 ]; do<br />
case $1 in<br />
-c|--check) shift<br />
[ $# -ge 3 ] || exit 1<br />
pause=$1<br />
;;<br />
-w|--write) mode=w<br />
shift<br />
[ $# -eq 2 ] || exit 1<br />
reg=$1<br />
val=$2<br />
shift<br />
;;<br />
-r|--read) mode=r<br />
shift<br />
[ $# -eq 1 ] || exit 1<br />
reg=$1<br />
;;<br />
esac<br />
shift<br />
done<br />
<br />
# handle offline deye_inverter by<br />
# ${pause} > 0 -> endless loop<br />
# ${pause} = 0 -> error after ${retries}<br />
while true; do<br />
<br />
# try deye_cli.sh $retries times every $sleep secs<br />
check=${retries}<br />
while [ ${check} -gt 0 ]; do<br />
<br />
case ${mode} in<br />
r) result=$(./deye_cli.sh $mode $reg | grep 'int: ')<br />
if [ -n "$result" ]; then<br />
echo "$result"<br />
log_info -t $0 deye_cli.sh $mode $reg<br />
exit 0<br />
fi<br />
;;<br />
w) result=$(./deye_cli.sh $mode $reg $val)<br />
if [ "$result" = "Ok" ]; then<br />
log_info -t $0 deye_cli.sh $mode $reg $val<br />
exit 0<br />
fi<br />
;;<br />
esac<br />
<br />
# wait ${sleep} or ${pause} secs<br />
sleep $(( ${pause} ? ${sleep} : ${pause} ))<br />
<br />
# try ${retries} times<br />
(( check-- ))<br />
done<br />
<br />
# error, or retry endless until wakeup of deye_inverter<br />
if [ ${pause} -eq 0 ]; then<br />
log_error "Error: deye_cli.sh $mode $reg $val failed."<br />
exit 1<br />
fi<br />
<br />
done<br />
</pre><br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
Folgendes Skript starte ich jeden Morgen um 04:00 Uhr per Cron (und in rc.local fürs booten):<br />
<br />
=== deye_mqtt_loop.sh ===<br />
Dieses Script liegt bei mir unter /root/sbin und startet eine Endlosschleife.<br />
<pre>#!/bin/bash<br />
# reset Daily_Power of DEYE Inverters<br />
# via deye_inverter_mqtt python package<br />
# https://github.com/kbialek/deye-inverter-mqtt<br />
<br />
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin<br />
<br />
debug=<br />
<br />
# config:<br />
deye_inverter_sh=/root/deye-inverter-mqtt/deye_inverter.sh<br />
deye_inverter_mqtt_cmd="python3 $(dirname ${deye_inverter_sh})/deye_docker_entrypoint.py"<br />
deye_inverter_mqtt_cfg=$(dirname ${deye_inverter_sh})/config.env<br />
<br />
myPID=$$<br />
myname=$(basename $0)<br />
<br />
# installation:<br />
# locate script in deye_inverter_mqtt directory<br />
if [ ! -f ${deye_inverter_mqtt_cfg} ]; then<br />
echo "Error: ${deye_inverter_mqtt_cfg} not found."<br />
echo " Please update \$deye_inverter_sh path in $0"<br />
exit 1<br />
fi<br />
<br />
# read my config<br />
set -a; source ${deye_inverter_mqtt_cfg}; set +a<br />
<br />
# ----- functions () ------------------------------------<br />
log_info ()<br />
{<br />
[ "$debug" = "yes" -o "$debug" = "true" ] && echo $myname: $@<br />
[ ${LOG_LEVEL} = "INFO" ] && logger -t $myname $@<br />
}<br />
<br />
log_error ()<br />
{<br />
logger -t $myname $@<br />
echo 1>&2 $@<br />
}<br />
<br />
# store actual date + time into vars<br />
get_date_time ()<br />
{<br />
year=$(date +%y)<br />
month=$(date +%m | sed 's/^0//g')<br />
day=$(date +%d | sed 's/^0//g')<br />
hour=$(date +%H | sed 's/^0//g')<br />
minute=$(date +%m | sed 's/^0//g')<br />
second=$(date +%S | sed 's/^0//g')<br />
}<br />
<br />
# get deye_inverter_mqtt PIDs<br />
get_deye_mqtt_pid ()<br />
{<br />
deye_mqtt_pid=$(ps ax | grep -v grep | grep "${deye_inverter_mqtt_cmd}" | awk '{print $1}')<br />
}<br />
<br />
# kill deye_inverter_loop ()<br />
kill_deye_inverter_loop ()<br />
{<br />
# kill old instances of me<br />
my_PS=$(ps ax | grep -v grep | grep ${myname} | awk '{print $1}')<br />
for ps in $my_PS; do<br />
[ $ps -lt $myPID ] && kill $ps<br />
done<br />
}<br />
<br />
# kill deye_inverter_instance ()<br />
kill_deye_inverter_instance ()<br />
{<br />
get_deye_mqtt_pid<br />
if [ ! -z "${deye_mqtt_pid}" ]; then<br />
kill ${deye_mqtt_pid}<br />
log_info "Info: KILL running deye_mqtt ${deye_mqtt_pid}"<br />
fi<br />
<br />
# kill any running deye_requests<br />
my_PS=$(ps ax | grep -v grep | grep "${deye_inverter_sh}" | awk '{print $1}')<br />
[ ! -z "$my_PS" ] && kill $my_PS<br />
}<br />
<br />
# stop all runnning instances<br />
deye_inverter_mqtt_stop ()<br />
{<br />
kill_deye_inverter_loop<br />
kill_deye_inverter_instance<br />
}<br />
<br />
# start deye_inverter_mqtt<br />
deye_inverter_mqtt_start ()<br />
{<br />
log_info "MQTT start"<br />
${deye_inverter_mqtt_cmd}<br />
}<br />
<br />
# read Daily power register<br />
read_daily_Power ()<br />
{<br />
${deye_inverter_sh} -c 10 -r 60 | sed 's/int: //g' | cut -d, -f1<br />
}<br />
<br />
# wait until 04:00 - 22:00<br />
wait_for_operative_window ()<br />
{<br />
get_date_time<br />
<br />
# calulate time in secs until 04:00<br />
if [ $hour -ge 4 ]; then<br />
((sleep_time=((((28-$hour)*60)-$minute)*60)-$second))<br />
else<br />
((sleep_time=((((4-$hour)*60)-$minute)*60)-$second))<br />
fi<br />
<br />
# wait for operative time<br />
if [ $hour -lt 4 -o $hour -ge 22 ]; then<br />
log_info $sleep_time<br />
sleep $sleep_time<br />
fi<br />
}<br />
<br />
# ------------------------<br />
# MAIN: Script starts here<br />
# ------------------------<br />
<br />
# stop any runnning deye_inverter_mqtt processes<br />
deye_inverter_mqtt_stop<br />
<br />
# wait until 04:00 - 22:00<br />
wait_for_operative_window<br />
<br />
# wait until deye inverter starts by reading (old) year/month (register 22)<br />
val22_old=$(${deye_inverter_sh} -c 60 -r 22 | sed 's/int: //g' | cut -d, -f1)<br />
log_info "date/time update initialized"<br />
<br />
# read Daily power register<br />
dailyPower=$(read_daily_Power)<br />
<br />
# daily reset phase<br />
daily_reset=init<br />
<br />
# ensure by loop, that all register values are written<br />
while [ ${daily_reset} != "done" ]; do<br />
<br />
# read (old) day<br />
val23_day=$(${deye_inverter_sh} -c 10 -r 23 | sed 's/.*h: //g' | cut -d, -f1)<br />
<br />
# set actual year, month, day, hour, minute, second<br />
get_date_time<br />
<br />
# calculate register 22-24 by date and time<br />
val22=$(( ${year} * 256 + ${month} ))<br />
val23=$(( ${day} * 256 + ${hour} ))<br />
val24=$(( ${minute} * 256 + ${second} ))<br />
<br />
# reset power/date/time only once a day or exit loop<br />
[ "$dailyPower" -eq 0 -a "${val22_old}" = "${val22}" -a "${val23_day}" = "${day}" ] && break<br />
<br />
# reset daily_power by<br />
# setting register 22-24 with actual date + time<br />
${deye_inverter_sh} -w 22 $val22 || continue<br />
${deye_inverter_sh} -w 23 $val23 || continue<br />
${deye_inverter_sh} -w 24 $val24 || continue<br />
<br />
# test success<br />
# read Daily power register, should be resetted to 0 now<br />
dailyPower=$(read_daily_Power)<br />
<br />
# exit loop if power is updated, else log that reset failed<br />
if [ "$dailyPower" -eq 0 ]; then<br />
daily_reset=done<br />
<br />
### skip loop here, reset doesn't work always :-(<br />
# elif [ $daily_reset = "init" ]; then<br />
# daily_reset=failed<br />
# sleep 60<br />
<br />
else<br />
log_info "daily power reset failed"<br />
break<br />
fi<br />
done<br />
log_info "date/time updated"<br />
<br />
# start deye_mqtt_loop between 04:00 - 22:00<br />
while get_date_time; do<br />
[ $hour -le 22 ] || break<br />
[ $hour -lt 4 ] && break<br />
<br />
# restart<br />
get_deye_mqtt_pid<br />
[ -z "$deye_mqtt_pid" ] && deye_inverter_mqtt_start<br />
<br />
sleep 600<br />
done<br />
</pre><br />
<br />
=== Contrab und /etc/rc.local ===<br />
Mein Crontab Eintrag:<br />
<pre>0 4 * * * /root/sbin/deye_mqtt_loop.sh 2>/dev/null &</pre><br />
und /etc/rc.local:<br />
<pre># start monitoring of PV-System: DEYE inverter<br />
$(sleep 20; cd /root/deye-inverter-mqtt; screen -d -m ./deye_mqtt_loop.sh)&<br />
</pre><br />
<br />
So sieht das aus, wenn es den ganzen Tag im April regnet und bedeckt ist:<br />
[[Datei:Solar Bedeckt Regen.jpg|midi]]<br />
<br />
Die Eigenverbrauchsquote habe ich mittels Grafana ermittelt:<br />
[[Datei:Solar Eigenverbrauch.jpg|midi|Eigenverbrauchsquote]]</div>
Neobiker
http://neobiker.de/wiki/index.php?title=DEYE_Wechselrichter&diff=2117
DEYE Wechselrichter
2024-01-07T13:34:30Z
<p>Neobiker: /* Installation */</p>
<hr />
<div>= Balkonkraftwerk: DEYE Wechselrichter ohne Cloud auslesen und per MQTT monitoren =<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
Der WR bietet verschiedene Möglichkeiten zur Konfiguration und Abfrage der aktuellen Werte:<br />
* einen internen Webserver (http://10.10.10.254)<br />
* die Cloud Anbindung zu Solarman (Solarman Smart App)<br />
* (angepasstes) Modbus Protokoll über Port 8899<br />
* AT+ Befehle über Port 48899<br />
<br />
Ich verwende das Modbus Protokoll zum auslesen des WR, das <br />
# effizient/schnell ist (Performance)<br />
# ohne Internetverbindung auskommt<br />
<br />
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.<br />
<br />
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.<br />
<br />
== Wechselrichter Eigenheiten ==<br />
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. <br />
<br />
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.<br />
<br />
== Installation ==<br />
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.<br />
<pre>git clone https://github.com/kbialek/deye-inverter-mqtt<br />
pip install paho-mqtt<br />
cd deye-inverter-mqtt</pre><br />
<br />
Unter Debian habe ich im Skript '''deye_cli.sh''' python durch python3 ersetzen müssen:<br />
<pre>#!/bin/bash<br />
set -a; source config.env; set +a<br />
python3 deye_cli.py "$@"<br />
</pre><br />
<br />
=== config.env ===<br />
Die Konfiguration meines DEYE Wechselrichters vom Typ Micro-Inverter:<br />
'''config.env'''<br />
<pre>DEYE_LOGGER_IP_ADDRESS=<IP Wechselrichter im WLAN><br />
DEYE_LOGGER_PORT=8899<br />
DEYE_LOGGER_SERIAL_NUMBER=<Seriennummer des WR><br />
<br />
MQTT_HOST=<IP von MQTT Server><br />
MQTT_PORT=1883<br />
MQTT_USERNAME=<br />
MQTT_PASSWORD=<br />
MQTT_TOPIC_PREFIX=deye<br />
<br />
LOG_LEVEL=ERROR<br />
DEYE_DATA_READ_INTERVAL=60<br />
DEYE_METRIC_GROUPS=micro<br />
</pre><br />
<br />
=== deye_inverter.sh === <br />
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><br />
<br />
<pre>#!/bin/bash<br />
# Read / Write DEYE Inverters<br />
# via deye_inverter_mqtt python package<br />
# https://github.com/kbialek/deye-inverter-mqtt<br />
<br />
retries=2 # retry deye_inverter command multiple times<br />
sleep=5 # sleep time between retries<br />
<br />
# locate script in deye_inverter_mqtt directory<br />
cd $(dirname $0)<br />
if [ ! -f ./deye_cli.sh ]; then<br />
echo "Error: ./deye_cli.sh not found."<br />
echo " Please move $(basename $0) in deye-inverter-mqtt directory."<br />
exit 1<br />
fi<br />
. ./config.env<br />
<br />
log_info ()<br />
{<br />
if [ ${LOG_LEVEL} = "INFO" ]; then<br />
logger -t $(basename $0) $@<br />
fi<br />
}<br />
<br />
log_error ()<br />
{<br />
logger -t $(basename $0) $@<br />
echo 2>&1 $@<br />
}<br />
<br />
# read parameters: mode [rw], register, value and optional -c <check pause><br />
pause=0<br />
while [ $# -gt 0 ]; do<br />
case $1 in<br />
-c|--check) shift<br />
[ $# -ge 3 ] || exit 1<br />
pause=$1<br />
;;<br />
-w|--write) mode=w<br />
shift<br />
[ $# -eq 2 ] || exit 1<br />
reg=$1<br />
val=$2<br />
shift<br />
;;<br />
-r|--read) mode=r<br />
shift<br />
[ $# -eq 1 ] || exit 1<br />
reg=$1<br />
;;<br />
esac<br />
shift<br />
done<br />
<br />
# handle offline deye_inverter by<br />
# ${pause} > 0 -> endless loop<br />
# ${pause} = 0 -> error after ${retries}<br />
while true; do<br />
<br />
# try deye_cli.sh $retries times every $sleep secs<br />
check=${retries}<br />
while [ ${check} -gt 0 ]; do<br />
<br />
case ${mode} in<br />
r) result=$(./deye_cli.sh $mode $reg | grep 'int: ')<br />
if [ -n "$result" ]; then<br />
echo "$result"<br />
log_info -t $0 deye_cli.sh $mode $reg<br />
exit 0<br />
fi<br />
;;<br />
w) result=$(./deye_cli.sh $mode $reg $val)<br />
if [ "$result" = "Ok" ]; then<br />
log_info -t $0 deye_cli.sh $mode $reg $val<br />
exit 0<br />
fi<br />
;;<br />
esac<br />
<br />
# wait ${sleep} or ${pause} secs<br />
sleep $(( ${pause} ? ${sleep} : ${pause} ))<br />
<br />
# try ${retries} times<br />
(( check-- ))<br />
done<br />
<br />
# error, or retry endless until wakeup of deye_inverter<br />
if [ ${pause} -eq 0 ]; then<br />
log_error "Error: deye_cli.sh $mode $reg $val failed."<br />
exit 1<br />
fi<br />
<br />
done<br />
</pre><br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
Folgendes Skript starte ich jeden Morgen um 04:00 Uhr per Cron (und in rc.local fürs booten):<br />
<br />
=== deye_mqtt_loop.sh ===<br />
Dieses Script liegt bei mir unter /root/sbin und startet eine Endlosschleife.<br />
<pre>#!/bin/bash<br />
# reset Daily_Power of DEYE Inverters<br />
# via deye_inverter_mqtt python package<br />
# https://github.com/kbialek/deye-inverter-mqtt<br />
<br />
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin<br />
<br />
debug=<br />
<br />
# config:<br />
deye_inverter_sh=/root/deye-inverter-mqtt/deye_inverter.sh<br />
deye_inverter_mqtt_cmd="python3 $(dirname ${deye_inverter_sh})/deye_docker_entrypoint.py"<br />
deye_inverter_mqtt_cfg=$(dirname ${deye_inverter_sh})/config.env<br />
<br />
myPID=$$<br />
myname=$(basename $0)<br />
<br />
# installation:<br />
# locate script in deye_inverter_mqtt directory<br />
if [ ! -f ${deye_inverter_mqtt_cfg} ]; then<br />
echo "Error: ${deye_inverter_mqtt_cfg} not found."<br />
echo " Please update \$deye_inverter_sh path in $0"<br />
exit 1<br />
fi<br />
<br />
# read my config<br />
set -a; source ${deye_inverter_mqtt_cfg}; set +a<br />
<br />
# ----- functions () ------------------------------------<br />
log_info ()<br />
{<br />
[ "$debug" = "yes" -o "$debug" = "true" ] && echo $myname: $@<br />
[ ${LOG_LEVEL} = "INFO" ] && logger -t $myname $@<br />
}<br />
<br />
log_error ()<br />
{<br />
logger -t $myname $@<br />
echo 1>&2 $@<br />
}<br />
<br />
# store actual date + time into vars<br />
get_date_time ()<br />
{<br />
year=$(date +%y)<br />
month=$(date +%m | sed 's/^0//g')<br />
day=$(date +%d | sed 's/^0//g')<br />
hour=$(date +%H | sed 's/^0//g')<br />
minute=$(date +%m | sed 's/^0//g')<br />
second=$(date +%S | sed 's/^0//g')<br />
}<br />
<br />
# get deye_inverter_mqtt PIDs<br />
get_deye_mqtt_pid ()<br />
{<br />
deye_mqtt_pid=$(ps ax | grep -v grep | grep "${deye_inverter_mqtt_cmd}" | awk '{print $1}')<br />
}<br />
<br />
# kill deye_inverter_loop ()<br />
kill_deye_inverter_loop ()<br />
{<br />
# kill old instances of me<br />
my_PS=$(ps ax | grep -v grep | grep ${myname} | awk '{print $1}')<br />
for ps in $my_PS; do<br />
[ $ps -lt $myPID ] && kill $ps<br />
done<br />
}<br />
<br />
# kill deye_inverter_instance ()<br />
kill_deye_inverter_instance ()<br />
{<br />
get_deye_mqtt_pid<br />
if [ ! -z "${deye_mqtt_pid}" ]; then<br />
kill ${deye_mqtt_pid}<br />
log_info "Info: KILL running deye_mqtt ${deye_mqtt_pid}"<br />
fi<br />
<br />
# kill any running deye_requests<br />
my_PS=$(ps ax | grep -v grep | grep "${deye_inverter_sh}" | awk '{print $1}')<br />
[ ! -z "$my_PS" ] && kill $my_PS<br />
}<br />
<br />
# stop all runnning instances<br />
deye_inverter_mqtt_stop ()<br />
{<br />
kill_deye_inverter_loop<br />
kill_deye_inverter_instance<br />
}<br />
<br />
# start deye_inverter_mqtt<br />
deye_inverter_mqtt_start ()<br />
{<br />
log_info "MQTT start"<br />
${deye_inverter_mqtt_cmd}<br />
}<br />
<br />
# read Daily power register<br />
read_daily_Power ()<br />
{<br />
${deye_inverter_sh} -c 10 -r 60 | sed 's/int: //g' | cut -d, -f1<br />
}<br />
<br />
# wait until 04:00 - 22:00<br />
wait_for_operative_window ()<br />
{<br />
get_date_time<br />
<br />
# calulate time in secs until 04:00<br />
if [ $hour -ge 4 ]; then<br />
((sleep_time=((((28-$hour)*60)-$minute)*60)-$second))<br />
else<br />
((sleep_time=((((4-$hour)*60)-$minute)*60)-$second))<br />
fi<br />
<br />
# wait for operative time<br />
if [ $hour -lt 4 -o $hour -ge 22 ]; then<br />
log_info $sleep_time<br />
sleep $sleep_time<br />
fi<br />
}<br />
<br />
# ------------------------<br />
# MAIN: Script starts here<br />
# ------------------------<br />
<br />
# stop any runnning deye_inverter_mqtt processes<br />
deye_inverter_mqtt_stop<br />
<br />
# wait until 04:00 - 22:00<br />
wait_for_operative_window<br />
<br />
# wait until deye inverter starts by reading (old) year/month (register 22)<br />
val22_old=$(${deye_inverter_sh} -c 60 -r 22 | sed 's/int: //g' | cut -d, -f1)<br />
log_info "date/time update initialized"<br />
<br />
# read Daily power register<br />
dailyPower=$(read_daily_Power)<br />
<br />
# daily reset phase<br />
daily_reset=init<br />
<br />
# ensure by loop, that all register values are written<br />
while [ ${daily_reset} != "done" ]; do<br />
<br />
# read (old) day<br />
val23_day=$(${deye_inverter_sh} -c 10 -r 23 | sed 's/.*h: //g' | cut -d, -f1)<br />
<br />
# set actual year, month, day, hour, minute, second<br />
get_date_time<br />
<br />
# calculate register 22-24 by date and time<br />
val22=$(( ${year} * 256 + ${month} ))<br />
val23=$(( ${day} * 256 + ${hour} ))<br />
val24=$(( ${minute} * 256 + ${second} ))<br />
<br />
# reset power/date/time only once a day or exit loop<br />
[ "$dailyPower" -eq 0 -a "${val22_old}" = "${val22}" -a "${val23_day}" = "${day}" ] && break<br />
<br />
# reset daily_power by<br />
# setting register 22-24 with actual date + time<br />
${deye_inverter_sh} -w 22 $val22 || continue<br />
${deye_inverter_sh} -w 23 $val23 || continue<br />
${deye_inverter_sh} -w 24 $val24 || continue<br />
<br />
# test success<br />
# read Daily power register, should be resetted to 0 now<br />
dailyPower=$(read_daily_Power)<br />
<br />
# exit loop if power is updated, else log that reset failed<br />
if [ "$dailyPower" -eq 0 ]; then<br />
daily_reset=done<br />
<br />
### skip loop here, reset doesn't work always :-(<br />
# elif [ $daily_reset = "init" ]; then<br />
# daily_reset=failed<br />
# sleep 60<br />
<br />
else<br />
log_info "daily power reset failed"<br />
break<br />
fi<br />
done<br />
log_info "date/time updated"<br />
<br />
# start deye_mqtt_loop between 04:00 - 22:00<br />
while get_date_time; do<br />
[ $hour -le 22 ] || break<br />
[ $hour -lt 4 ] && break<br />
<br />
# restart<br />
get_deye_mqtt_pid<br />
[ -z "$deye_mqtt_pid" ] && deye_inverter_mqtt_start<br />
<br />
sleep 600<br />
done<br />
</pre><br />
<br />
=== Contrab und /etc/rc.local ===<br />
Mein Crontab Eintrag:<br />
<pre>0 4 * * * /root/sbin/deye_mqtt_loop.sh 2>/dev/null &</pre><br />
und /etc/rc.local:<br />
<pre># start monitoring of PV-System: DEYE inverter<br />
$(sleep 20; cd /root/deye-inverter-mqtt; screen -d -m ./deye_mqtt_loop.sh)&<br />
</pre><br />
<br />
So sieht das aus, wenn es den ganzen Tag im April regnet und bedeckt ist:<br />
[[Datei:Solar Bedeckt Regen.jpg|midi]]<br />
<br />
Die Eigenverbrauchsquote habe ich mittels Grafana ermittelt:<br />
[[Datei:Solar Eigenverbrauch.jpg|midi|Eigenverbrauchsquote]]</div>
Neobiker
http://neobiker.de/wiki/index.php?title=DEYE_Wechselrichter&diff=2116
DEYE Wechselrichter
2024-01-07T13:33:41Z
<p>Neobiker: /* config.env */</p>
<hr />
<div>= Balkonkraftwerk: DEYE Wechselrichter ohne Cloud auslesen und per MQTT monitoren =<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
Der WR bietet verschiedene Möglichkeiten zur Konfiguration und Abfrage der aktuellen Werte:<br />
* einen internen Webserver (http://10.10.10.254)<br />
* die Cloud Anbindung zu Solarman (Solarman Smart App)<br />
* (angepasstes) Modbus Protokoll über Port 8899<br />
* AT+ Befehle über Port 48899<br />
<br />
Ich verwende das Modbus Protokoll zum auslesen des WR, das <br />
# effizient/schnell ist (Performance)<br />
# ohne Internetverbindung auskommt<br />
<br />
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.<br />
<br />
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.<br />
<br />
== Wechselrichter Eigenheiten ==<br />
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. <br />
<br />
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.<br />
<br />
== Installation ==<br />
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.<br />
<pre>git clone https://github.com/kbialek/deye-inverter-mqtt<br />
pip install paho-mqtt<br />
cd deye-inverter-mqtt</pre><br />
<br />
Unter Debian habe ich im Skript '''deye_cli.sh''' python durch python3 ersetzen müssen:<br />
<pre>#!/bin/bash<br />
set -a; source config.env; set +a<br />
python3 deye_cli.py "$@"<br />
</pre><br />
<br />
Die Konfiguration meines DEYE Wechselrichters vom Typ Micro-Inverter:<br />
<br />
=== config.env ===<br />
<pre>DEYE_LOGGER_IP_ADDRESS=<IP Wechselrichter im WLAN><br />
DEYE_LOGGER_PORT=8899<br />
DEYE_LOGGER_SERIAL_NUMBER=<Seriennummer des WR><br />
<br />
MQTT_HOST=<IP von MQTT Server><br />
MQTT_PORT=1883<br />
MQTT_USERNAME=<br />
MQTT_PASSWORD=<br />
MQTT_TOPIC_PREFIX=deye<br />
<br />
LOG_LEVEL=ERROR<br />
DEYE_DATA_READ_INTERVAL=60<br />
DEYE_METRIC_GROUPS=micro<br />
</pre><br />
<br />
=== deye_inverter.sh === <br />
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><br />
<br />
<pre>#!/bin/bash<br />
# Read / Write DEYE Inverters<br />
# via deye_inverter_mqtt python package<br />
# https://github.com/kbialek/deye-inverter-mqtt<br />
<br />
retries=2 # retry deye_inverter command multiple times<br />
sleep=5 # sleep time between retries<br />
<br />
# locate script in deye_inverter_mqtt directory<br />
cd $(dirname $0)<br />
if [ ! -f ./deye_cli.sh ]; then<br />
echo "Error: ./deye_cli.sh not found."<br />
echo " Please move $(basename $0) in deye-inverter-mqtt directory."<br />
exit 1<br />
fi<br />
. ./config.env<br />
<br />
log_info ()<br />
{<br />
if [ ${LOG_LEVEL} = "INFO" ]; then<br />
logger -t $(basename $0) $@<br />
fi<br />
}<br />
<br />
log_error ()<br />
{<br />
logger -t $(basename $0) $@<br />
echo 2>&1 $@<br />
}<br />
<br />
# read parameters: mode [rw], register, value and optional -c <check pause><br />
pause=0<br />
while [ $# -gt 0 ]; do<br />
case $1 in<br />
-c|--check) shift<br />
[ $# -ge 3 ] || exit 1<br />
pause=$1<br />
;;<br />
-w|--write) mode=w<br />
shift<br />
[ $# -eq 2 ] || exit 1<br />
reg=$1<br />
val=$2<br />
shift<br />
;;<br />
-r|--read) mode=r<br />
shift<br />
[ $# -eq 1 ] || exit 1<br />
reg=$1<br />
;;<br />
esac<br />
shift<br />
done<br />
<br />
# handle offline deye_inverter by<br />
# ${pause} > 0 -> endless loop<br />
# ${pause} = 0 -> error after ${retries}<br />
while true; do<br />
<br />
# try deye_cli.sh $retries times every $sleep secs<br />
check=${retries}<br />
while [ ${check} -gt 0 ]; do<br />
<br />
case ${mode} in<br />
r) result=$(./deye_cli.sh $mode $reg | grep 'int: ')<br />
if [ -n "$result" ]; then<br />
echo "$result"<br />
log_info -t $0 deye_cli.sh $mode $reg<br />
exit 0<br />
fi<br />
;;<br />
w) result=$(./deye_cli.sh $mode $reg $val)<br />
if [ "$result" = "Ok" ]; then<br />
log_info -t $0 deye_cli.sh $mode $reg $val<br />
exit 0<br />
fi<br />
;;<br />
esac<br />
<br />
# wait ${sleep} or ${pause} secs<br />
sleep $(( ${pause} ? ${sleep} : ${pause} ))<br />
<br />
# try ${retries} times<br />
(( check-- ))<br />
done<br />
<br />
# error, or retry endless until wakeup of deye_inverter<br />
if [ ${pause} -eq 0 ]; then<br />
log_error "Error: deye_cli.sh $mode $reg $val failed."<br />
exit 1<br />
fi<br />
<br />
done<br />
</pre><br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
Folgendes Skript starte ich jeden Morgen um 04:00 Uhr per Cron (und in rc.local fürs booten):<br />
<br />
=== deye_mqtt_loop.sh ===<br />
Dieses Script liegt bei mir unter /root/sbin und startet eine Endlosschleife.<br />
<pre>#!/bin/bash<br />
# reset Daily_Power of DEYE Inverters<br />
# via deye_inverter_mqtt python package<br />
# https://github.com/kbialek/deye-inverter-mqtt<br />
<br />
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin<br />
<br />
debug=<br />
<br />
# config:<br />
deye_inverter_sh=/root/deye-inverter-mqtt/deye_inverter.sh<br />
deye_inverter_mqtt_cmd="python3 $(dirname ${deye_inverter_sh})/deye_docker_entrypoint.py"<br />
deye_inverter_mqtt_cfg=$(dirname ${deye_inverter_sh})/config.env<br />
<br />
myPID=$$<br />
myname=$(basename $0)<br />
<br />
# installation:<br />
# locate script in deye_inverter_mqtt directory<br />
if [ ! -f ${deye_inverter_mqtt_cfg} ]; then<br />
echo "Error: ${deye_inverter_mqtt_cfg} not found."<br />
echo " Please update \$deye_inverter_sh path in $0"<br />
exit 1<br />
fi<br />
<br />
# read my config<br />
set -a; source ${deye_inverter_mqtt_cfg}; set +a<br />
<br />
# ----- functions () ------------------------------------<br />
log_info ()<br />
{<br />
[ "$debug" = "yes" -o "$debug" = "true" ] && echo $myname: $@<br />
[ ${LOG_LEVEL} = "INFO" ] && logger -t $myname $@<br />
}<br />
<br />
log_error ()<br />
{<br />
logger -t $myname $@<br />
echo 1>&2 $@<br />
}<br />
<br />
# store actual date + time into vars<br />
get_date_time ()<br />
{<br />
year=$(date +%y)<br />
month=$(date +%m | sed 's/^0//g')<br />
day=$(date +%d | sed 's/^0//g')<br />
hour=$(date +%H | sed 's/^0//g')<br />
minute=$(date +%m | sed 's/^0//g')<br />
second=$(date +%S | sed 's/^0//g')<br />
}<br />
<br />
# get deye_inverter_mqtt PIDs<br />
get_deye_mqtt_pid ()<br />
{<br />
deye_mqtt_pid=$(ps ax | grep -v grep | grep "${deye_inverter_mqtt_cmd}" | awk '{print $1}')<br />
}<br />
<br />
# kill deye_inverter_loop ()<br />
kill_deye_inverter_loop ()<br />
{<br />
# kill old instances of me<br />
my_PS=$(ps ax | grep -v grep | grep ${myname} | awk '{print $1}')<br />
for ps in $my_PS; do<br />
[ $ps -lt $myPID ] && kill $ps<br />
done<br />
}<br />
<br />
# kill deye_inverter_instance ()<br />
kill_deye_inverter_instance ()<br />
{<br />
get_deye_mqtt_pid<br />
if [ ! -z "${deye_mqtt_pid}" ]; then<br />
kill ${deye_mqtt_pid}<br />
log_info "Info: KILL running deye_mqtt ${deye_mqtt_pid}"<br />
fi<br />
<br />
# kill any running deye_requests<br />
my_PS=$(ps ax | grep -v grep | grep "${deye_inverter_sh}" | awk '{print $1}')<br />
[ ! -z "$my_PS" ] && kill $my_PS<br />
}<br />
<br />
# stop all runnning instances<br />
deye_inverter_mqtt_stop ()<br />
{<br />
kill_deye_inverter_loop<br />
kill_deye_inverter_instance<br />
}<br />
<br />
# start deye_inverter_mqtt<br />
deye_inverter_mqtt_start ()<br />
{<br />
log_info "MQTT start"<br />
${deye_inverter_mqtt_cmd}<br />
}<br />
<br />
# read Daily power register<br />
read_daily_Power ()<br />
{<br />
${deye_inverter_sh} -c 10 -r 60 | sed 's/int: //g' | cut -d, -f1<br />
}<br />
<br />
# wait until 04:00 - 22:00<br />
wait_for_operative_window ()<br />
{<br />
get_date_time<br />
<br />
# calulate time in secs until 04:00<br />
if [ $hour -ge 4 ]; then<br />
((sleep_time=((((28-$hour)*60)-$minute)*60)-$second))<br />
else<br />
((sleep_time=((((4-$hour)*60)-$minute)*60)-$second))<br />
fi<br />
<br />
# wait for operative time<br />
if [ $hour -lt 4 -o $hour -ge 22 ]; then<br />
log_info $sleep_time<br />
sleep $sleep_time<br />
fi<br />
}<br />
<br />
# ------------------------<br />
# MAIN: Script starts here<br />
# ------------------------<br />
<br />
# stop any runnning deye_inverter_mqtt processes<br />
deye_inverter_mqtt_stop<br />
<br />
# wait until 04:00 - 22:00<br />
wait_for_operative_window<br />
<br />
# wait until deye inverter starts by reading (old) year/month (register 22)<br />
val22_old=$(${deye_inverter_sh} -c 60 -r 22 | sed 's/int: //g' | cut -d, -f1)<br />
log_info "date/time update initialized"<br />
<br />
# read Daily power register<br />
dailyPower=$(read_daily_Power)<br />
<br />
# daily reset phase<br />
daily_reset=init<br />
<br />
# ensure by loop, that all register values are written<br />
while [ ${daily_reset} != "done" ]; do<br />
<br />
# read (old) day<br />
val23_day=$(${deye_inverter_sh} -c 10 -r 23 | sed 's/.*h: //g' | cut -d, -f1)<br />
<br />
# set actual year, month, day, hour, minute, second<br />
get_date_time<br />
<br />
# calculate register 22-24 by date and time<br />
val22=$(( ${year} * 256 + ${month} ))<br />
val23=$(( ${day} * 256 + ${hour} ))<br />
val24=$(( ${minute} * 256 + ${second} ))<br />
<br />
# reset power/date/time only once a day or exit loop<br />
[ "$dailyPower" -eq 0 -a "${val22_old}" = "${val22}" -a "${val23_day}" = "${day}" ] && break<br />
<br />
# reset daily_power by<br />
# setting register 22-24 with actual date + time<br />
${deye_inverter_sh} -w 22 $val22 || continue<br />
${deye_inverter_sh} -w 23 $val23 || continue<br />
${deye_inverter_sh} -w 24 $val24 || continue<br />
<br />
# test success<br />
# read Daily power register, should be resetted to 0 now<br />
dailyPower=$(read_daily_Power)<br />
<br />
# exit loop if power is updated, else log that reset failed<br />
if [ "$dailyPower" -eq 0 ]; then<br />
daily_reset=done<br />
<br />
### skip loop here, reset doesn't work always :-(<br />
# elif [ $daily_reset = "init" ]; then<br />
# daily_reset=failed<br />
# sleep 60<br />
<br />
else<br />
log_info "daily power reset failed"<br />
break<br />
fi<br />
done<br />
log_info "date/time updated"<br />
<br />
# start deye_mqtt_loop between 04:00 - 22:00<br />
while get_date_time; do<br />
[ $hour -le 22 ] || break<br />
[ $hour -lt 4 ] && break<br />
<br />
# restart<br />
get_deye_mqtt_pid<br />
[ -z "$deye_mqtt_pid" ] && deye_inverter_mqtt_start<br />
<br />
sleep 600<br />
done<br />
</pre><br />
<br />
=== Contrab und /etc/rc.local ===<br />
Mein Crontab Eintrag:<br />
<pre>0 4 * * * /root/sbin/deye_mqtt_loop.sh 2>/dev/null &</pre><br />
und /etc/rc.local:<br />
<pre># start monitoring of PV-System: DEYE inverter<br />
$(sleep 20; cd /root/deye-inverter-mqtt; screen -d -m ./deye_mqtt_loop.sh)&<br />
</pre><br />
<br />
So sieht das aus, wenn es den ganzen Tag im April regnet und bedeckt ist:<br />
[[Datei:Solar Bedeckt Regen.jpg|midi]]<br />
<br />
Die Eigenverbrauchsquote habe ich mittels Grafana ermittelt:<br />
[[Datei:Solar Eigenverbrauch.jpg|midi|Eigenverbrauchsquote]]</div>
Neobiker
http://neobiker.de/wiki/index.php?title=DEYE_Wechselrichter&diff=2115
DEYE Wechselrichter
2024-01-07T13:32:26Z
<p>Neobiker: /* Installation */</p>
<hr />
<div>= Balkonkraftwerk: DEYE Wechselrichter ohne Cloud auslesen und per MQTT monitoren =<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
Der WR bietet verschiedene Möglichkeiten zur Konfiguration und Abfrage der aktuellen Werte:<br />
* einen internen Webserver (http://10.10.10.254)<br />
* die Cloud Anbindung zu Solarman (Solarman Smart App)<br />
* (angepasstes) Modbus Protokoll über Port 8899<br />
* AT+ Befehle über Port 48899<br />
<br />
Ich verwende das Modbus Protokoll zum auslesen des WR, das <br />
# effizient/schnell ist (Performance)<br />
# ohne Internetverbindung auskommt<br />
<br />
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.<br />
<br />
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.<br />
<br />
== Wechselrichter Eigenheiten ==<br />
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. <br />
<br />
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.<br />
<br />
== Installation ==<br />
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.<br />
<pre>git clone https://github.com/kbialek/deye-inverter-mqtt<br />
pip install paho-mqtt<br />
cd deye-inverter-mqtt</pre><br />
<br />
Unter Debian habe ich im Skript '''deye_cli.sh''' python durch python3 ersetzen müssen:<br />
<pre>#!/bin/bash<br />
set -a; source config.env; set +a<br />
python3 deye_cli.py "$@"<br />
</pre><br />
<br />
Die Konfiguration meines DEYE Wechselrichters vom Typ Micro-Inverter:<br />
<br />
=== config.env ===<br />
<pre>DEYE_LOGGER_IP_ADDRESS=<IP Wechselrichter im WLAN><br />
DEYE_LOGGER_PORT=8899<br />
DEYE_LOGGER_SERIAL_NUMBER=<Seriennummer des WR><br />
<br />
MQTT_HOST=<IP von MQTT Server><br />
MQTT_PORT=1883<br />
MQTT_USERNAME=<br />
MQTT_PASSWORD=<br />
MQTT_TOPIC_PREFIX=deye<br />
<br />
LOG_LEVEL=ERROR<br />
DEYE_DATA_READ_INTERVAL=60<br />
DEYE_METRIC_GROUPS=micro<br />
</pre><br />
<br />
Ausserdem habe ich ein kleines ''Wrapper Skript'' geschrieben, um '''Daten des WR''' einfacher '''lesen und schreiben''' zu können:<br />
<br />
=== deye_inverter.sh === [--check <pause>] --read <register> | --write <register> <value><br />
<pre>#!/bin/bash<br />
# Read / Write DEYE Inverters<br />
# via deye_inverter_mqtt python package<br />
# https://github.com/kbialek/deye-inverter-mqtt<br />
<br />
retries=2 # retry deye_inverter command multiple times<br />
sleep=5 # sleep time between retries<br />
<br />
# locate script in deye_inverter_mqtt directory<br />
cd $(dirname $0)<br />
if [ ! -f ./deye_cli.sh ]; then<br />
echo "Error: ./deye_cli.sh not found."<br />
echo " Please move $(basename $0) in deye-inverter-mqtt directory."<br />
exit 1<br />
fi<br />
. ./config.env<br />
<br />
log_info ()<br />
{<br />
if [ ${LOG_LEVEL} = "INFO" ]; then<br />
logger -t $(basename $0) $@<br />
fi<br />
}<br />
<br />
log_error ()<br />
{<br />
logger -t $(basename $0) $@<br />
echo 2>&1 $@<br />
}<br />
<br />
# read parameters: mode [rw], register, value and optional -c <check pause><br />
pause=0<br />
while [ $# -gt 0 ]; do<br />
case $1 in<br />
-c|--check) shift<br />
[ $# -ge 3 ] || exit 1<br />
pause=$1<br />
;;<br />
-w|--write) mode=w<br />
shift<br />
[ $# -eq 2 ] || exit 1<br />
reg=$1<br />
val=$2<br />
shift<br />
;;<br />
-r|--read) mode=r<br />
shift<br />
[ $# -eq 1 ] || exit 1<br />
reg=$1<br />
;;<br />
esac<br />
shift<br />
done<br />
<br />
# handle offline deye_inverter by<br />
# ${pause} > 0 -> endless loop<br />
# ${pause} = 0 -> error after ${retries}<br />
while true; do<br />
<br />
# try deye_cli.sh $retries times every $sleep secs<br />
check=${retries}<br />
while [ ${check} -gt 0 ]; do<br />
<br />
case ${mode} in<br />
r) result=$(./deye_cli.sh $mode $reg | grep 'int: ')<br />
if [ -n "$result" ]; then<br />
echo "$result"<br />
log_info -t $0 deye_cli.sh $mode $reg<br />
exit 0<br />
fi<br />
;;<br />
w) result=$(./deye_cli.sh $mode $reg $val)<br />
if [ "$result" = "Ok" ]; then<br />
log_info -t $0 deye_cli.sh $mode $reg $val<br />
exit 0<br />
fi<br />
;;<br />
esac<br />
<br />
# wait ${sleep} or ${pause} secs<br />
sleep $(( ${pause} ? ${sleep} : ${pause} ))<br />
<br />
# try ${retries} times<br />
(( check-- ))<br />
done<br />
<br />
# error, or retry endless until wakeup of deye_inverter<br />
if [ ${pause} -eq 0 ]; then<br />
log_error "Error: deye_cli.sh $mode $reg $val failed."<br />
exit 1<br />
fi<br />
<br />
done<br />
</pre><br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
Folgendes Skript starte ich jeden Morgen um 04:00 Uhr per Cron (und in rc.local fürs booten):<br />
<br />
=== deye_mqtt_loop.sh ===<br />
Dieses Script liegt bei mir unter /root/sbin und startet eine Endlosschleife.<br />
<pre>#!/bin/bash<br />
# reset Daily_Power of DEYE Inverters<br />
# via deye_inverter_mqtt python package<br />
# https://github.com/kbialek/deye-inverter-mqtt<br />
<br />
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin<br />
<br />
debug=<br />
<br />
# config:<br />
deye_inverter_sh=/root/deye-inverter-mqtt/deye_inverter.sh<br />
deye_inverter_mqtt_cmd="python3 $(dirname ${deye_inverter_sh})/deye_docker_entrypoint.py"<br />
deye_inverter_mqtt_cfg=$(dirname ${deye_inverter_sh})/config.env<br />
<br />
myPID=$$<br />
myname=$(basename $0)<br />
<br />
# installation:<br />
# locate script in deye_inverter_mqtt directory<br />
if [ ! -f ${deye_inverter_mqtt_cfg} ]; then<br />
echo "Error: ${deye_inverter_mqtt_cfg} not found."<br />
echo " Please update \$deye_inverter_sh path in $0"<br />
exit 1<br />
fi<br />
<br />
# read my config<br />
set -a; source ${deye_inverter_mqtt_cfg}; set +a<br />
<br />
# ----- functions () ------------------------------------<br />
log_info ()<br />
{<br />
[ "$debug" = "yes" -o "$debug" = "true" ] && echo $myname: $@<br />
[ ${LOG_LEVEL} = "INFO" ] && logger -t $myname $@<br />
}<br />
<br />
log_error ()<br />
{<br />
logger -t $myname $@<br />
echo 1>&2 $@<br />
}<br />
<br />
# store actual date + time into vars<br />
get_date_time ()<br />
{<br />
year=$(date +%y)<br />
month=$(date +%m | sed 's/^0//g')<br />
day=$(date +%d | sed 's/^0//g')<br />
hour=$(date +%H | sed 's/^0//g')<br />
minute=$(date +%m | sed 's/^0//g')<br />
second=$(date +%S | sed 's/^0//g')<br />
}<br />
<br />
# get deye_inverter_mqtt PIDs<br />
get_deye_mqtt_pid ()<br />
{<br />
deye_mqtt_pid=$(ps ax | grep -v grep | grep "${deye_inverter_mqtt_cmd}" | awk '{print $1}')<br />
}<br />
<br />
# kill deye_inverter_loop ()<br />
kill_deye_inverter_loop ()<br />
{<br />
# kill old instances of me<br />
my_PS=$(ps ax | grep -v grep | grep ${myname} | awk '{print $1}')<br />
for ps in $my_PS; do<br />
[ $ps -lt $myPID ] && kill $ps<br />
done<br />
}<br />
<br />
# kill deye_inverter_instance ()<br />
kill_deye_inverter_instance ()<br />
{<br />
get_deye_mqtt_pid<br />
if [ ! -z "${deye_mqtt_pid}" ]; then<br />
kill ${deye_mqtt_pid}<br />
log_info "Info: KILL running deye_mqtt ${deye_mqtt_pid}"<br />
fi<br />
<br />
# kill any running deye_requests<br />
my_PS=$(ps ax | grep -v grep | grep "${deye_inverter_sh}" | awk '{print $1}')<br />
[ ! -z "$my_PS" ] && kill $my_PS<br />
}<br />
<br />
# stop all runnning instances<br />
deye_inverter_mqtt_stop ()<br />
{<br />
kill_deye_inverter_loop<br />
kill_deye_inverter_instance<br />
}<br />
<br />
# start deye_inverter_mqtt<br />
deye_inverter_mqtt_start ()<br />
{<br />
log_info "MQTT start"<br />
${deye_inverter_mqtt_cmd}<br />
}<br />
<br />
# read Daily power register<br />
read_daily_Power ()<br />
{<br />
${deye_inverter_sh} -c 10 -r 60 | sed 's/int: //g' | cut -d, -f1<br />
}<br />
<br />
# wait until 04:00 - 22:00<br />
wait_for_operative_window ()<br />
{<br />
get_date_time<br />
<br />
# calulate time in secs until 04:00<br />
if [ $hour -ge 4 ]; then<br />
((sleep_time=((((28-$hour)*60)-$minute)*60)-$second))<br />
else<br />
((sleep_time=((((4-$hour)*60)-$minute)*60)-$second))<br />
fi<br />
<br />
# wait for operative time<br />
if [ $hour -lt 4 -o $hour -ge 22 ]; then<br />
log_info $sleep_time<br />
sleep $sleep_time<br />
fi<br />
}<br />
<br />
# ------------------------<br />
# MAIN: Script starts here<br />
# ------------------------<br />
<br />
# stop any runnning deye_inverter_mqtt processes<br />
deye_inverter_mqtt_stop<br />
<br />
# wait until 04:00 - 22:00<br />
wait_for_operative_window<br />
<br />
# wait until deye inverter starts by reading (old) year/month (register 22)<br />
val22_old=$(${deye_inverter_sh} -c 60 -r 22 | sed 's/int: //g' | cut -d, -f1)<br />
log_info "date/time update initialized"<br />
<br />
# read Daily power register<br />
dailyPower=$(read_daily_Power)<br />
<br />
# daily reset phase<br />
daily_reset=init<br />
<br />
# ensure by loop, that all register values are written<br />
while [ ${daily_reset} != "done" ]; do<br />
<br />
# read (old) day<br />
val23_day=$(${deye_inverter_sh} -c 10 -r 23 | sed 's/.*h: //g' | cut -d, -f1)<br />
<br />
# set actual year, month, day, hour, minute, second<br />
get_date_time<br />
<br />
# calculate register 22-24 by date and time<br />
val22=$(( ${year} * 256 + ${month} ))<br />
val23=$(( ${day} * 256 + ${hour} ))<br />
val24=$(( ${minute} * 256 + ${second} ))<br />
<br />
# reset power/date/time only once a day or exit loop<br />
[ "$dailyPower" -eq 0 -a "${val22_old}" = "${val22}" -a "${val23_day}" = "${day}" ] && break<br />
<br />
# reset daily_power by<br />
# setting register 22-24 with actual date + time<br />
${deye_inverter_sh} -w 22 $val22 || continue<br />
${deye_inverter_sh} -w 23 $val23 || continue<br />
${deye_inverter_sh} -w 24 $val24 || continue<br />
<br />
# test success<br />
# read Daily power register, should be resetted to 0 now<br />
dailyPower=$(read_daily_Power)<br />
<br />
# exit loop if power is updated, else log that reset failed<br />
if [ "$dailyPower" -eq 0 ]; then<br />
daily_reset=done<br />
<br />
### skip loop here, reset doesn't work always :-(<br />
# elif [ $daily_reset = "init" ]; then<br />
# daily_reset=failed<br />
# sleep 60<br />
<br />
else<br />
log_info "daily power reset failed"<br />
break<br />
fi<br />
done<br />
log_info "date/time updated"<br />
<br />
# start deye_mqtt_loop between 04:00 - 22:00<br />
while get_date_time; do<br />
[ $hour -le 22 ] || break<br />
[ $hour -lt 4 ] && break<br />
<br />
# restart<br />
get_deye_mqtt_pid<br />
[ -z "$deye_mqtt_pid" ] && deye_inverter_mqtt_start<br />
<br />
sleep 600<br />
done<br />
</pre><br />
<br />
=== Contrab und /etc/rc.local ===<br />
Mein Crontab Eintrag:<br />
<pre>0 4 * * * /root/sbin/deye_mqtt_loop.sh 2>/dev/null &</pre><br />
und /etc/rc.local:<br />
<pre># start monitoring of PV-System: DEYE inverter<br />
$(sleep 20; cd /root/deye-inverter-mqtt; screen -d -m ./deye_mqtt_loop.sh)&<br />
</pre><br />
<br />
So sieht das aus, wenn es den ganzen Tag im April regnet und bedeckt ist:<br />
[[Datei:Solar Bedeckt Regen.jpg|midi]]<br />
<br />
Die Eigenverbrauchsquote habe ich mittels Grafana ermittelt:<br />
[[Datei:Solar Eigenverbrauch.jpg|midi|Eigenverbrauchsquote]]</div>
Neobiker
http://neobiker.de/wiki/index.php?title=DEYE_Wechselrichter&diff=2114
DEYE Wechselrichter
2024-01-07T13:27:42Z
<p>Neobiker: /* Installation */</p>
<hr />
<div>= Balkonkraftwerk: DEYE Wechselrichter ohne Cloud auslesen und per MQTT monitoren =<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
Der WR bietet verschiedene Möglichkeiten zur Konfiguration und Abfrage der aktuellen Werte:<br />
* einen internen Webserver (http://10.10.10.254)<br />
* die Cloud Anbindung zu Solarman (Solarman Smart App)<br />
* (angepasstes) Modbus Protokoll über Port 8899<br />
* AT+ Befehle über Port 48899<br />
<br />
Ich verwende das Modbus Protokoll zum auslesen des WR, das <br />
# effizient/schnell ist (Performance)<br />
# ohne Internetverbindung auskommt<br />
<br />
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.<br />
<br />
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.<br />
<br />
== Wechselrichter Eigenheiten ==<br />
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. <br />
<br />
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.<br />
<br />
== Installation ==<br />
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.<br />
<pre>git clone https://github.com/kbialek/deye-inverter-mqtt<br />
pip install paho-mqtt<br />
cd deye-inverter-mqtt</pre><br />
<br />
Unter Debian habe ich im Skript '''deye_cli.sh''' python durch python3 ersetzen müssen:<br />
<pre>#!/bin/bash<br />
set -a; source config.env; set +a<br />
python3 deye_cli.py "$@"<br />
</pre><br />
<br />
Die Konfiguration meines DEYE Wechselrichters vom Typ Micro-Inverter:<br />
<br />
'''config.env'''<br />
<pre>DEYE_LOGGER_IP_ADDRESS=<IP Wechselrichter im WLAN><br />
DEYE_LOGGER_PORT=8899<br />
DEYE_LOGGER_SERIAL_NUMBER=<Seriennummer des WR><br />
<br />
MQTT_HOST=<IP von MQTT Server><br />
MQTT_PORT=1883<br />
MQTT_USERNAME=<br />
MQTT_PASSWORD=<br />
MQTT_TOPIC_PREFIX=deye<br />
<br />
LOG_LEVEL=ERROR<br />
DEYE_DATA_READ_INTERVAL=60<br />
DEYE_METRIC_GROUPS=micro<br />
</pre><br />
<br />
Ausserdem habe ich ein kleines ''Wrapper Skript'' geschrieben, um '''Daten des WR''' einfacher '''lesen und schreiben''' zu können:<br />
<br />
'''deye_inverter.sh''' [--check <pause>] --read <register> | --write <register> <value><br />
<pre>#!/bin/bash<br />
# Read / Write DEYE Inverters<br />
# via deye_inverter_mqtt python package<br />
# https://github.com/kbialek/deye-inverter-mqtt<br />
<br />
retries=2 # retry deye_inverter command multiple times<br />
sleep=5 # sleep time between retries<br />
<br />
# locate script in deye_inverter_mqtt directory<br />
cd $(dirname $0)<br />
if [ ! -f ./deye_cli.sh ]; then<br />
echo "Error: ./deye_cli.sh not found."<br />
echo " Please move $(basename $0) in deye-inverter-mqtt directory."<br />
exit 1<br />
fi<br />
. ./config.env<br />
<br />
log_info ()<br />
{<br />
if [ ${LOG_LEVEL} = "INFO" ]; then<br />
logger -t $(basename $0) $@<br />
fi<br />
}<br />
<br />
log_error ()<br />
{<br />
logger -t $(basename $0) $@<br />
echo 2>&1 $@<br />
}<br />
<br />
# read parameters: mode [rw], register, value and optional -c <check pause><br />
pause=0<br />
while [ $# -gt 0 ]; do<br />
case $1 in<br />
-c|--check) shift<br />
[ $# -ge 3 ] || exit 1<br />
pause=$1<br />
;;<br />
-w|--write) mode=w<br />
shift<br />
[ $# -eq 2 ] || exit 1<br />
reg=$1<br />
val=$2<br />
shift<br />
;;<br />
-r|--read) mode=r<br />
shift<br />
[ $# -eq 1 ] || exit 1<br />
reg=$1<br />
;;<br />
esac<br />
shift<br />
done<br />
<br />
# handle offline deye_inverter by<br />
# ${pause} > 0 -> endless loop<br />
# ${pause} = 0 -> error after ${retries}<br />
while true; do<br />
<br />
# try deye_cli.sh $retries times every $sleep secs<br />
check=${retries}<br />
while [ ${check} -gt 0 ]; do<br />
<br />
case ${mode} in<br />
r) result=$(./deye_cli.sh $mode $reg | grep 'int: ')<br />
if [ -n "$result" ]; then<br />
echo "$result"<br />
log_info -t $0 deye_cli.sh $mode $reg<br />
exit 0<br />
fi<br />
;;<br />
w) result=$(./deye_cli.sh $mode $reg $val)<br />
if [ "$result" = "Ok" ]; then<br />
log_info -t $0 deye_cli.sh $mode $reg $val<br />
exit 0<br />
fi<br />
;;<br />
esac<br />
<br />
# wait ${sleep} or ${pause} secs<br />
sleep $(( ${pause} ? ${sleep} : ${pause} ))<br />
<br />
# try ${retries} times<br />
(( check-- ))<br />
done<br />
<br />
# error, or retry endless until wakeup of deye_inverter<br />
if [ ${pause} -eq 0 ]; then<br />
log_error "Error: deye_cli.sh $mode $reg $val failed."<br />
exit 1<br />
fi<br />
<br />
done<br />
</pre><br />
<br />
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.<br />
<br />
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.<br />
<br />
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.<br />
<br />
Folgendes Skript starte ich jeden Morgen um 04:00 Uhr per Cron (und in rc.local fürs booten):<br />
<br />
'''deye_mqtt_loop.sh'''<br />
<pre>#!/bin/bash<br />
# reset Daily_Power of DEYE Inverters<br />
# via deye_inverter_mqtt python package<br />
# https://github.com/kbialek/deye-inverter-mqtt<br />
<br />
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin<br />
<br />
debug=<br />
<br />
# config:<br />
deye_inverter_sh=/root/deye-inverter-mqtt/deye_inverter.sh<br />
deye_inverter_mqtt_cmd="python3 $(dirname ${deye_inverter_sh})/deye_docker_entrypoint.py"<br />
deye_inverter_mqtt_cfg=$(dirname ${deye_inverter_sh})/config.env<br />
<br />
myPID=$$<br />
myname=$(basename $0)<br />
<br />
# installation:<br />
# locate script in deye_inverter_mqtt directory<br />
if [ ! -f ${deye_inverter_mqtt_cfg} ]; then<br />
echo "Error: ${deye_inverter_mqtt_cfg} not found."<br />
echo " Please update \$deye_inverter_sh path in $0"<br />
exit 1<br />
fi<br />
<br />
# read my config<br />
set -a; source ${deye_inverter_mqtt_cfg}; set +a<br />
<br />
# ----- functions () ------------------------------------<br />
log_info ()<br />
{<br />
[ "$debug" = "yes" -o "$debug" = "true" ] && echo $myname: $@<br />
[ ${LOG_LEVEL} = "INFO" ] && logger -t $myname $@<br />
}<br />
<br />
log_error ()<br />
{<br />
logger -t $myname $@<br />
echo 1>&2 $@<br />
}<br />
<br />
# store actual date + time into vars<br />
get_date_time ()<br />
{<br />
year=$(date +%y)<br />
month=$(date +%m | sed 's/^0//g')<br />
day=$(date +%d | sed 's/^0//g')<br />
hour=$(date +%H | sed 's/^0//g')<br />
minute=$(date +%m | sed 's/^0//g')<br />
second=$(date +%S | sed 's/^0//g')<br />
}<br />
<br />
# get deye_inverter_mqtt PIDs<br />
get_deye_mqtt_pid ()<br />
{<br />
deye_mqtt_pid=$(ps ax | grep -v grep | grep "${deye_inverter_mqtt_cmd}" | awk '{print $1}')<br />
}<br />
<br />
# kill deye_inverter_loop ()<br />
kill_deye_inverter_loop ()<br />
{<br />
# kill old instances of me<br />
my_PS=$(ps ax | grep -v grep | grep ${myname} | awk '{print $1}')<br />
for ps in $my_PS; do<br />
[ $ps -lt $myPID ] && kill $ps<br />
done<br />
}<br />
<br />
# kill deye_inverter_instance ()<br />
kill_deye_inverter_instance ()<br />
{<br />
get_deye_mqtt_pid<br />
if [ ! -z "${deye_mqtt_pid}" ]; then<br />
kill ${deye_mqtt_pid}<br />
log_info "Info: KILL running deye_mqtt ${deye_mqtt_pid}"<br />
fi<br />
<br />
# kill any running deye_requests<br />
my_PS=$(ps ax | grep -v grep | grep "${deye_inverter_sh}" | awk '{print $1}')<br />
[ ! -z "$my_PS" ] && kill $my_PS<br />
}<br />
<br />
# stop all runnning instances<br />
deye_inverter_mqtt_stop ()<br />
{<br />
kill_deye_inverter_loop<br />
kill_deye_inverter_instance<br />
}<br />
<br />
# start deye_inverter_mqtt<br />
deye_inverter_mqtt_start ()<br />
{<br />
log_info "MQTT start"<br />
${deye_inverter_mqtt_cmd}<br />
}<br />
<br />
# read Daily power register<br />
read_daily_Power ()<br />
{<br />
${deye_inverter_sh} -c 10 -r 60 | sed 's/int: //g' | cut -d, -f1<br />
}<br />
<br />
# wait until 04:00 - 22:00<br />
wait_for_operative_window ()<br />
{<br />
get_date_time<br />
<br />
# calulate time in secs until 04:00<br />
if [ $hour -ge 4 ]; then<br />
((sleep_time=((((28-$hour)*60)-$minute)*60)-$second))<br />
else<br />
((sleep_time=((((4-$hour)*60)-$minute)*60)-$second))<br />
fi<br />
<br />
# wait for operative time<br />
if [ $hour -lt 4 -o $hour -ge 22 ]; then<br />
log_info $sleep_time<br />
sleep $sleep_time<br />
fi<br />
}<br />
<br />
# ------------------------<br />
# MAIN: Script starts here<br />
# ------------------------<br />
<br />
# stop any runnning deye_inverter_mqtt processes<br />
deye_inverter_mqtt_stop<br />
<br />
# wait until 04:00 - 22:00<br />
wait_for_operative_window<br />
<br />
# wait until deye inverter starts by reading (old) year/month (register 22)<br />
val22_old=$(${deye_inverter_sh} -c 60 -r 22 | sed 's/int: //g' | cut -d, -f1)<br />
log_info "date/time update initialized"<br />
<br />
# read Daily power register<br />
dailyPower=$(read_daily_Power)<br />
<br />
# daily reset phase<br />
daily_reset=init<br />
<br />
# ensure by loop, that all register values are written<br />
while [ ${daily_reset} != "done" ]; do<br />
<br />
# read (old) day<br />
val23_day=$(${deye_inverter_sh} -c 10 -r 23 | sed 's/.*h: //g' | cut -d, -f1)<br />
<br />
# set actual year, month, day, hour, minute, second<br />
get_date_time<br />
<br />
# calculate register 22-24 by date and time<br />
val22=$(( ${year} * 256 + ${month} ))<br />
val23=$(( ${day} * 256 + ${hour} ))<br />
val24=$(( ${minute} * 256 + ${second} ))<br />
<br />
# reset power/date/time only once a day or exit loop<br />
[ "$dailyPower" -eq 0 -a "${val22_old}" = "${val22}" -a "${val23_day}" = "${day}" ] && break<br />
<br />
# reset daily_power by<br />
# setting register 22-24 with actual date + time<br />
${deye_inverter_sh} -w 22 $val22 || continue<br />
${deye_inverter_sh} -w 23 $val23 || continue<br />
${deye_inverter_sh} -w 24 $val24 || continue<br />
<br />
# test success<br />
# read Daily power register, should be resetted to 0 now<br />
dailyPower=$(read_daily_Power)<br />
<br />
# exit loop if power is updated, else log that reset failed<br />
if [ "$dailyPower" -eq 0 ]; then<br />
daily_reset=done<br />
<br />
### skip loop here, reset doesn't work always :-(<br />
# elif [ $daily_reset = "init" ]; then<br />
# daily_reset=failed<br />
# sleep 60<br />
<br />
else<br />
log_info "daily power reset failed"<br />
break<br />
fi<br />
done<br />
log_info "date/time updated"<br />
<br />
# start deye_mqtt_loop between 04:00 - 22:00<br />
while get_date_time; do<br />
[ $hour -le 22 ] || break<br />
[ $hour -lt 4 ] && break<br />
<br />
# restart<br />
get_deye_mqtt_pid<br />
[ -z "$deye_mqtt_pid" ] && deye_inverter_mqtt_start<br />
<br />
sleep 600<br />
done<br />
</pre><br />
<br />
Mein Crontab Eintrag:<br />
<pre>0 4 * * * /root/deye-inverter-mqtt/deye_mqtt_loop.sh</pre><br />
und /etc/rc.local:<br />
<pre># start monitoring of PV-System: DEYE inverter<br />
$(sleep 20; cd /root/deye-inverter-mqtt; screen -d -m ./deye_mqtt_loop.sh)&<br />
<br />
</pre><br />
<br />
So sieht das aus, wenn es den ganzen Tag im April regnet und bedeckt ist:<br />
[[Datei:Solar Bedeckt Regen.jpg|midi]]<br />
<br />
Die Eigenverbrauchsquote habe ich mittels Grafana ermittelt:<br />
[[Datei:Solar Eigenverbrauch.jpg|midi|Eigenverbrauchsquote]]</div>
Neobiker
http://neobiker.de/wiki/index.php?title=Kopano_to_grommunio_HowTo&diff=2113
Kopano to grommunio HowTo
2024-01-07T13:23:09Z
<p>Neobiker: /* Mail Relay Server */</p>
<hr />
<div>= Installation auf Debian 12 =<br />
Eine fertige grommunio Appliance ist auf Basis von S.u.S.E. erstellt und steht u.a. als ISO download zur Verfügung. Da ich derzeit ausschliesslich Debian basierte Systeme verwende, bevorzuge ich eine grommunio Installation auf Debian 12 (bookworm).<br />
<br />
Im grommunio Forum findet sich ein Diskussionsthread für die [https://community.grommunio.com/d/447-debian-11-clean-install-script grommunio Installation auf Debian 12 durch ein Script]. Diese läuft auf einer neu erstellten VM auf Proxmox Server fehlerfrei durch. In einem LXC Container gab es Probleme, u.a. klemmte der POP3 Service.<br />
<br />
Im Anschluss an das Installationsskript habe ich noch folgendes Script unter '''Additions''' - ''Fix Grommunio Admin Live Status page'' ausgeführt, welches ebenfalls funktionierte und die entsprechende Seite im Web Interface reparierte.<br />
<br />
= Konfiguration =<br />
Kopano Core ist bei mir als Applikation auf meinem UCS File- und Mailserver installiert. Die Benutzerkonten und deren Konfiguration werden von UCS verwaltet und stehen per LDAP zur Verfügung.<br />
<br />
<pre># mein UCS-Kopano Server<br />
kopano_server=ucsmail<br />
echo "kopano_server=ucsmail" >> ~/.bashrc<br />
<br />
# ssh connections without prompts<br />
ssh-keygen<br />
ssh-copy-id $kopano_server<br />
<br />
# mount /var/lib/kopano/attachments per sshfs<br />
if [ -e /etc/debian_version ] then<br />
apt install sshfs screen<br />
else<br />
zypper in sshfs # (grommunio app/iso installation)<br />
fi<br />
mkdir -p /mnt/kopano_attachments<br />
sshfs $kopano_server:/var/lib/kopano/attachments /mnt/kopano_attachments<br />
</pre><br />
<br />
== Zertifikate ==<br />
Falls ein '''[https://help.univention.com/t/ssl-zertifikat-und-dns-alias/5780/7 Server Zertifikat von UCS]''' für den '''grommunio''' server erzeugt wurde kann das verwendet werden.<br />
<br />
Ich erstelle hier ein neues Zertifikat für den grommunio Server, der als DNS-Alternativen jeweils '''autodiscover''' (Outlook) und '''autoconfig''' (Android) Einträge für jede meiner Email Domains enthält. Damit erfolgt die Mail-Client Konfiguration automatisch, sofern der Rechner sich in einem '''lokalem''' Netzsegment befindet.<br />
<br />
Background:<br><br />
Meine Emails werden extern gehostet, ich importiere sie per Fetchmail. Daher bin ich nicht in der Lage diese Autodiscover/Autoconf Einträge im (externen) DNS meines Hosters zu erstellen. Die entsprechende '''lokale DNS Auflösung''' übernimmt mein '''Unbound DNS''' Server mittels '''Overwrite''' Einträgen, welche auf den grommunio Server verweisen. Daher müssen diese DNS Alias Namen in dessen Zertifikat vorhanden sein.<br />
<br />
<pre>ssh ucs # UCS Master!<br />
univention-certificate new -name grommunio.domain.de<br />
cd /etc/univention/ssl/grommunio.domain.de<br />
<br />
# add DNS Aliases<br />
vi openssl.cnf<br />
# subjectAltName = DNS:grommunio.domain.de, DNS:grommunio <br />
# add all your email domains autodiscover/autoconfig entries: DNS:autodiscover.email_domain.de, DNS:autoconfig.email_domain.de<br />
<br />
DEFAULT_CRL_DAYS="$(/usr/sbin/univention-config-registry get ssl/crl/validity)"<br />
DEFAULT_DAYS="$(/usr/sbin/univention-config-registry get ssl/default/days)"<br />
DEFAULT_MD="$(/usr/sbin/univention-config-registry get ssl/default/hashfunction)"<br />
DEFAULT_BITS="$(/usr/sbin/univention-config-registry get ssl/default/bits)"<br />
export DEFAULT_MD DEFAULT_BITS DEFAULT_CRL_DAYS DEFAULT_DAYS<br />
openssl req -new -config openssl.cnf -key private.key -out req.pem<br />
<br />
univention-certificate renew -name grommunio.domain.de -days $DEFAULT_DAYS<br />
</pre><br />
<br />
Damit funktioniert die Anmeldung am Webinterface auch per LDAP mit starttls, ansonsten nur LDAP ohne starttls oder halt mit SQL Accounts.<br />
<pre><br />
cd /etc/grommunio-common/ssl<br />
scp $kopano_server:/etc/univention/ssl/grommunio/cert.pem server-bundle.pem<br />
scp $kopano_server:/etc/univention/ssl/grommunio/private.key server.key<br />
chown gromox:gromox server*<br />
chmod 660 server*<br />
<br />
# Root CA<br />
if [ -e /etc/debian_version ] then<br />
scp $kopano_server:/etc/univention/ssl/ucsCA/CAcert.pem /usr/local/share/ca-certificates/my-custom-ca/<br />
else<br />
# SuSE appliance location<br />
scp $kopano_server:/etc/univention/ssl/ucsCA/CAcert.pem /etc/pki/trust/anchors/<br />
fi<br />
update-ca-certificates<br />
<br />
systemctl restart nginx<br />
<br />
# autodiscover Eintrag testen<br />
PASS='email_passwort' gromox-dscli -ve user@domainname<br />
</pre><br />
<br />
== LDAP ==<br />
Die LDAP Konfiguration kann mit dem UCS Template im Webinterface vorgenommen werden.<br />
Ich übernehme einige Kopano Werte:<br />
<pre><br />
ssh $kopano_server grep -e ^ldap_uri -e ^ldap_bind -e ^ldap_search_base -e ^ldap_user_search -e ^ldap_group_search /etc/kopano/ldap.cfg<br />
<br />
ldap_uri = ldap://ucsmail.domain.de:7389/<br />
ldap_bind_user = cn=ucsmail,cn=dc,cn=computers,dc=domain,dc=de<br />
ldap_bind_passwd = xxxxxxxxxxxxxx<br />
ldap_search_base = dc=domain,dc=de<br />
ldap_user_search_filter = (kopanoAccount=1)<br />
ldap_group_search_filter = (&(kopanoAccount=1)(objectClass=kopano-group))<br />
<br />
# configure LDAP accordingly<br />
grommunio-admin ldap configure<br />
<br />
# test: list users<br />
grommunio-admin ldap search<br />
ID Type E-Mail Name<br />
2222222222-11-10111-22222-3333333333 user user@domainname User Name<br />
</pre><br />
<br />
== Email Domains und User ==<br />
Debian: das Anlegen einer Domain im Web-Interface schlägt direkt fehl - "Bad Request".<br><br />
Nicht schlimm, aber vielleicht nehme ich doch lieber die fertige Appliance auf Basis von S.u.S.E. ?<br />
<br />
Per Kommandozeile funktioniert es jedenfalls.<br />
<pre><br />
domains=$(ssh $kopano_server ucr get mail/hosteddomains)<br />
users=$(grommunio-admin ldap search)<br />
<br />
# create email domains<br />
for domain in $domains; do<br />
u_cnt=$(echo "$users" | grep -c $domain)<br />
[ $u_cnt -gt 0 ] && grommunio-admin domain create -u $u_cnt $domain<br />
done<br />
<br />
# import and sync LDAP users<br />
grommunio-admin ldap downsync -c<br />
</pre><br />
Der Import der User funktioniert jedenfalls auch über das Webinterface.<br />
<br />
=== Export - Import via Outlook .pst ===<br />
Da ich nur 5 Konten habe, ist diese Methode auch nicht ganz abwegig:<br />
<br />
<pre>gromox-pff2mt /tmp/neobiker_outlook.pst | gromox-mt2exm -u neobiker@neobiker.de<br />
</pre><br />
<br />
=== Import von Emails aus Kopano ===<br />
Der Import landet (leider) in einem separatem Verzeichnis:<br />
[[Datei:Grommunio Import.jpg|mini]]<br />
<br />
Das ist nicht ganz das was ich mir für einen Import vorstelle: Jeder User muss alle seine Inhalte manuell auf dem Server in die Hauptverzeichnisse verschieben ... ?<br />
<br />
Ansonsten hat der Import prinzipiell für mein Neobiker Postfach funktioniert.<br />
<br />
<pre># gromox-kdb2mt — Utility for analysis/import of Kopano mailboxes<br />
# gromox-mt2exm — Utility for importing various mail items<br />
<br />
mkdir -p /mnt/kopano_attachments<br />
sshfs $kopano_server:/var/lib/kopano/attachments /mnt/kopano_attachments<br />
<br />
# detach (CTRL-A d) the SSH tunnel to SQL server (who only accepts localhost conections)<br />
screen ssh -L 12345:localhost:3306 "root@${kopano_server}"<br />
<br />
# EXAMPLE for user neobiker = neobiker@neobiker.de<br />
# ------------------------------------------------<br />
# ENVIRONMENT variable is used for SQL password<br />
# either EXPORT it or write in one line with cmd<br />
export_mbox="gromox-kdb2mt --sql-host 127.0.0.1 --sql-port 12345 --src-attach /mnt/kopano_attachments"<br />
SQLPASS=$(ssh $kopano_server cat /etc/mysql.secret)<br />
<br />
SQLPASS=$SQLPASS $export_mbox --mbox-mro neobiker | gromox-mt2exm -u neobiker@neobiker.de<br />
<br />
kdb2mt: No ACLs will be extracted.<br />
kdb Server GUID: xxxxxxxxxxxxxxxxxxxxxxxxxxx<br />
Database schema is kdb-118<br />
Store GUID for MRO "neobiker": xxxxxxxxxxxxxxxxxxxxxxxxx<br />
Processing folder "" (7 elements)...<br />
Processing folder "IPM_SUBTREE" (19 elements)...<br />
Processing folder "Posteingang" (791 elements)...<br />
Processing folder "Postausgang" (0 elements)...<br />
Processing folder "Gelöschte Objekte" (1 elements)...<br />
Processing folder "Gesendete Objekte" (1 elements)...<br />
Processing folder "Kontakte" (0 elements)...<br />
Processing folder "Kalender" (1 elements)...<br />
Processing folder "Entwürfe" (0 elements)...<br />
Processing folder "Journal" (0 elements)...<br />
Processing folder "Notizen" (0 elements)...<br />
Processing folder "Aufgaben" (0 elements)...<br />
Processing folder "Junk E-Mail" (44 elements)...<br />
Processing folder "RSS Feeds" (0 elements)...<br />
Processing folder "Konversationseinstellungen" (0 elements)...<br />
Processing folder "Quickstep Einstellungen" (0 elements)...<br />
Processing folder "Vorgeschlagene Kontakte" (0 elements)...<br />
Processing folder "Spambox" (0 elements)...<br />
Processing folder "Junk-E-Mail" (0 elements)...<br />
Processing folder "Gelöschte Elemente" (0 elements)...<br />
Processing folder "Gesendete Elemente" (0 elements)...<br />
Processing folder "IPM_COMMON_VIEWS" (2 elements)...<br />
Processing folder "IPM_VIEWS" (0 elements)...<br />
Processing folder "FINDER_ROOT" (0 elements)...<br />
Processing folder "Verknüpfung" (0 elements)...<br />
Processing folder "Schedule" (0 elements)...<br />
Processing folder "Freebusy Data" (1 elements)...<br />
</pre><br />
<br />
=== Import aller Email Konten ===<br />
<br />
<pre><br />
export_mbox="gromox-kdb2mt --sql-host 127.0.0.1 --sql-port 12345 --src-attach /mnt/kopano_attachments"<br />
SQLPASS=$(ssh $kopano_server cat /etc/mysql.secret)<br />
<br />
# LOOP over all email accounts (except SYSTEM)<br />
# --------------------------------------------<br />
users=$(ssh $kopano_server kopano-admin -l | awk '{print $1}' | tail -n +4 | grep -v SYSTEM)<br />
<br />
for user in ${users}; do<br />
<br />
details=$(ssh $kopano_server kopano-admin --details $user)<br />
email=$(echo "${details}" $user | awk '/Emailaddress:/ {print $2}')<br />
guid=$(echo "${details}" $user | awk '/Store GUID:/ {print $3}')<br />
<br />
SQLPASS=$SQLPASS $export_mbox --mbox-guid $guid | gromox-mt2exm -u $email<br />
<br />
done<br />
<br />
# stop ssh tunnel -> exit ssh on kopano server<br />
screen -r<br />
<br />
# unmount kopano attachments<br />
umount /mnt/kopano_attachments<br />
</pre><br />
<br />
== Datenablage ==<br />
Eventuell möchte ich ein separates Filesystem für die Daten verwenden.<br><br />
Die Daten liegen unter ''/var/lib/mysql*'' und ''/var/lib/grom*'' ?<br />
<br />
<pre>grommunio:/var/lib # ls mysql* grom*<br />
grommunio-admin-api:<br />
auth-private.pem auth-public.pem<br />
<br />
grommunio-antispam:<br />
110ec84de81cc98813b71d34d42dedbc199bd687.map 65b4e39783c10c8ed89dbec4983259f22418f34d.map ea942f35f2c82e84bdb7b8ceb34537f7dbe986eb.map stats.ucl<br />
2368c73b937d98513ed72c4b04f4247bda43fdb5.map 877aa38dd77ed707d20b2f77d46b905b69a6295a.map f3933e67d5f9574a9eaf78cd0a8e99140ecc502e.map<br />
2e66b2415ec9b99aaa155761c211e51def12016e.map 8fe64ecd161a46f91a78ece93609cb863f9f42f2.map rspamd.rrd<br />
3b1eab0b21b32c970b8fae4a9010a539879368e3.map cb5a8189726ac3a8880e1c44ed6220c6d794521b.map rspamd.sock<br />
<br />
grommunio-dav:<br />
<br />
grommunio-files:<br />
apps-external config data sessions tmp<br />
<br />
grommunio-web:<br />
session sqlite-index tmp<br />
<br />
gromox:<br />
domain queue timer.txt user<br />
<br />
mysql:<br />
aria_log.00000001 ddl_recovery.log grommunio ib_buffer_pool ib_logfile0 multi-master.info mysql_upgrade_info sys<br />
aria_log_control grofiles grommunio.pid ibdata1 ibtmp1 mysql performance_schema test<br />
<br />
mysql-files:<br />
</pre><br />
<br />
== Postfix ==<br />
Mail Delivery Agent (MDA) Konfiguration <br />
* zum versenden von Emails (external relay host)<br />
* Mailaddress rewritings (neobiker@local -> neobiker@neobiker.de)<br />
<br />
Vergleich der wichtigsten Konfigurationsparameter<br />
<pre>grep -v ^# /etc/postfix/main.cf | grep [[:alnum:]] | sed -e 's/^\W+//g' | sort > postfix_config_sorted_grommunio<br />
ssh $kopano_server "grep -v ^# /etc/postfix/main.cf | grep [[:alnum:]] | sed -e 's/^\W+//g' | sort" > postfix_config_sorted_kopano<br />
<br />
diff -y postfix_config<br />
...<br />
<br />
# most relevant entries (differs/unconfigured)<br />
masquerade_domains = $mydomain<br />
content_filter = smtp-amavis:[127.0.0.1]:10024<br />
disable_vrfy_command = no<br />
mailbox_size_limit<br />
message_size_limit<br />
relay_domains = $mydestination<br />
relayhost = smtp.mailhoster.de<br />
smtpd_tls_auth_only = yes<br />
smtpd_tls_cert_file = /etc/univention/ssl/ucsmail...<br />
smtpd_use_tls = yes<br />
smtp_sasl_password_maps = hash:/etc/postfix/smtp_auth<br />
smtp_sasl_security_options = noanonymous<br />
<br />
</pre><br />
Vermutlich muss ich mich nur um den externen Relay Host kümmern.<br />
<br />
<br />
=== Mail Relay Server ===<br />
Postfix can use a mail relay server.<br />
<br />
<pre>ssh $kopano_server ucr get mail/relayhost</pre><br />
<br />
<pre># authentification on relay host<br />
# format: FQDN-Relayhost username:password<br />
ssh $kopano_server cat /etc/postfix/smtp_auth >> /etc/postfix/sasl_passwd<br />
<br />
vi /etc/postfix/main.cf<br />
relayhost = [smtp.provider.de]<br />
smtp_sasl_auth_enable = yes<br />
smtp_sasl_security_options = noanonymous<br />
smtp_sasl_password_maps = lmdb:/etc/postfix/sasl_passwd`<br />
smtp_use_tls = yes<br />
<br />
postmap /etc/postfix/sasl_passwd<br />
systemctl restart postfix<br />
</pre><br />
Das hat funktioniert:<br />
* Email nach EXTERN ging raus.<br />
* Email nach intern -> keine Postfix Aktion (nur intern innerhalb grommunio)<br />
<br />
Doku grommunio: [https://docs.grommunio.com/admin/administration.html#adding-a-file relayhost]<br />
<br />
Eine andere Info dazu [https://andersgood.de/kurz-notiert/smtp-relayhost-in-grommunio-konfigurieren hier].<br />
<br />
'''ACHTUNG''': Appliance (SuSE) -> Ich vermute stark, dass ein versehentliches ''config.postfix'' auf dem server die Konfiguration zerschiesst, da man das wohl in der Setupdatei /etc/sysconfig/postfix (SuSE) eintragen sollte.<br />
Umso unverständlicher, warum die Doku zu Relay Server bei grommunio so schwach ist.<br />
<br />
=== Mail Address rewritings ===<br />
<br />
Nix zu tun, denke ich.<br />
<br />
== Fetchmail ==<br />
Fetchmail Konfiguration zum abholen der Emails von meinem Email Provider.<br />
Emails werden entweder von UCS '''oder ''' grommunio abgeholt und and Postfix übergeben!<br />
<br />
Auf UCS für jeden Benutzer deaktivieren <br><br />
-> ''Advanced settings ‣ Remote mail retrieval (single)''<br><br />
oder Postfix auf UCS zur weiterleitung an grommunio konfigurieren.<br />
<br />
<pre>systemctl stop fetchmail<br />
systemctl disable fetchmail<br />
ucr set fetchmail/autostart=false<br />
<br />
# fetchmail config and passwords<br />
cat /etc/fetchmailrc<br />
</pre><br />
<br />
== Proxy Konfiguration (Nginx) ==<br />
Ich habe für jede Email Domain einen deSEC (DynDNS) Eintrag auf mein Internetgateway (Nginx auf OPNSense) erstellt.<br />
Dafür verwende ich ein Let's Encrypt Zertifikat, welches die Domains als DNS Alternate Names beinhaltet.<br />
Das Webinterface von grommunio ist unter ''/web'' erreichbar, während in der Übergangszeit Kopano unter ''/webapp'' erreichbar bleibt.<br />
Active Sync für Mobile Devices (URL ist identisch) kann damit immerhin pro Email Domain separat umgestellt werden. Outlook ist bei mir derzeit nur intern erreichbar, also extern nur über VPN (oder halt das Webinterface).</div>
Neobiker
http://neobiker.de/wiki/index.php?title=Kopano_to_grommunio_HowTo&diff=2112
Kopano to grommunio HowTo
2024-01-07T13:22:32Z
<p>Neobiker: /* Mail Relay Server */</p>
<hr />
<div>= Installation auf Debian 12 =<br />
Eine fertige grommunio Appliance ist auf Basis von S.u.S.E. erstellt und steht u.a. als ISO download zur Verfügung. Da ich derzeit ausschliesslich Debian basierte Systeme verwende, bevorzuge ich eine grommunio Installation auf Debian 12 (bookworm).<br />
<br />
Im grommunio Forum findet sich ein Diskussionsthread für die [https://community.grommunio.com/d/447-debian-11-clean-install-script grommunio Installation auf Debian 12 durch ein Script]. Diese läuft auf einer neu erstellten VM auf Proxmox Server fehlerfrei durch. In einem LXC Container gab es Probleme, u.a. klemmte der POP3 Service.<br />
<br />
Im Anschluss an das Installationsskript habe ich noch folgendes Script unter '''Additions''' - ''Fix Grommunio Admin Live Status page'' ausgeführt, welches ebenfalls funktionierte und die entsprechende Seite im Web Interface reparierte.<br />
<br />
= Konfiguration =<br />
Kopano Core ist bei mir als Applikation auf meinem UCS File- und Mailserver installiert. Die Benutzerkonten und deren Konfiguration werden von UCS verwaltet und stehen per LDAP zur Verfügung.<br />
<br />
<pre># mein UCS-Kopano Server<br />
kopano_server=ucsmail<br />
echo "kopano_server=ucsmail" >> ~/.bashrc<br />
<br />
# ssh connections without prompts<br />
ssh-keygen<br />
ssh-copy-id $kopano_server<br />
<br />
# mount /var/lib/kopano/attachments per sshfs<br />
if [ -e /etc/debian_version ] then<br />
apt install sshfs screen<br />
else<br />
zypper in sshfs # (grommunio app/iso installation)<br />
fi<br />
mkdir -p /mnt/kopano_attachments<br />
sshfs $kopano_server:/var/lib/kopano/attachments /mnt/kopano_attachments<br />
</pre><br />
<br />
== Zertifikate ==<br />
Falls ein '''[https://help.univention.com/t/ssl-zertifikat-und-dns-alias/5780/7 Server Zertifikat von UCS]''' für den '''grommunio''' server erzeugt wurde kann das verwendet werden.<br />
<br />
Ich erstelle hier ein neues Zertifikat für den grommunio Server, der als DNS-Alternativen jeweils '''autodiscover''' (Outlook) und '''autoconfig''' (Android) Einträge für jede meiner Email Domains enthält. Damit erfolgt die Mail-Client Konfiguration automatisch, sofern der Rechner sich in einem '''lokalem''' Netzsegment befindet.<br />
<br />
Background:<br><br />
Meine Emails werden extern gehostet, ich importiere sie per Fetchmail. Daher bin ich nicht in der Lage diese Autodiscover/Autoconf Einträge im (externen) DNS meines Hosters zu erstellen. Die entsprechende '''lokale DNS Auflösung''' übernimmt mein '''Unbound DNS''' Server mittels '''Overwrite''' Einträgen, welche auf den grommunio Server verweisen. Daher müssen diese DNS Alias Namen in dessen Zertifikat vorhanden sein.<br />
<br />
<pre>ssh ucs # UCS Master!<br />
univention-certificate new -name grommunio.domain.de<br />
cd /etc/univention/ssl/grommunio.domain.de<br />
<br />
# add DNS Aliases<br />
vi openssl.cnf<br />
# subjectAltName = DNS:grommunio.domain.de, DNS:grommunio <br />
# add all your email domains autodiscover/autoconfig entries: DNS:autodiscover.email_domain.de, DNS:autoconfig.email_domain.de<br />
<br />
DEFAULT_CRL_DAYS="$(/usr/sbin/univention-config-registry get ssl/crl/validity)"<br />
DEFAULT_DAYS="$(/usr/sbin/univention-config-registry get ssl/default/days)"<br />
DEFAULT_MD="$(/usr/sbin/univention-config-registry get ssl/default/hashfunction)"<br />
DEFAULT_BITS="$(/usr/sbin/univention-config-registry get ssl/default/bits)"<br />
export DEFAULT_MD DEFAULT_BITS DEFAULT_CRL_DAYS DEFAULT_DAYS<br />
openssl req -new -config openssl.cnf -key private.key -out req.pem<br />
<br />
univention-certificate renew -name grommunio.domain.de -days $DEFAULT_DAYS<br />
</pre><br />
<br />
Damit funktioniert die Anmeldung am Webinterface auch per LDAP mit starttls, ansonsten nur LDAP ohne starttls oder halt mit SQL Accounts.<br />
<pre><br />
cd /etc/grommunio-common/ssl<br />
scp $kopano_server:/etc/univention/ssl/grommunio/cert.pem server-bundle.pem<br />
scp $kopano_server:/etc/univention/ssl/grommunio/private.key server.key<br />
chown gromox:gromox server*<br />
chmod 660 server*<br />
<br />
# Root CA<br />
if [ -e /etc/debian_version ] then<br />
scp $kopano_server:/etc/univention/ssl/ucsCA/CAcert.pem /usr/local/share/ca-certificates/my-custom-ca/<br />
else<br />
# SuSE appliance location<br />
scp $kopano_server:/etc/univention/ssl/ucsCA/CAcert.pem /etc/pki/trust/anchors/<br />
fi<br />
update-ca-certificates<br />
<br />
systemctl restart nginx<br />
<br />
# autodiscover Eintrag testen<br />
PASS='email_passwort' gromox-dscli -ve user@domainname<br />
</pre><br />
<br />
== LDAP ==<br />
Die LDAP Konfiguration kann mit dem UCS Template im Webinterface vorgenommen werden.<br />
Ich übernehme einige Kopano Werte:<br />
<pre><br />
ssh $kopano_server grep -e ^ldap_uri -e ^ldap_bind -e ^ldap_search_base -e ^ldap_user_search -e ^ldap_group_search /etc/kopano/ldap.cfg<br />
<br />
ldap_uri = ldap://ucsmail.domain.de:7389/<br />
ldap_bind_user = cn=ucsmail,cn=dc,cn=computers,dc=domain,dc=de<br />
ldap_bind_passwd = xxxxxxxxxxxxxx<br />
ldap_search_base = dc=domain,dc=de<br />
ldap_user_search_filter = (kopanoAccount=1)<br />
ldap_group_search_filter = (&(kopanoAccount=1)(objectClass=kopano-group))<br />
<br />
# configure LDAP accordingly<br />
grommunio-admin ldap configure<br />
<br />
# test: list users<br />
grommunio-admin ldap search<br />
ID Type E-Mail Name<br />
2222222222-11-10111-22222-3333333333 user user@domainname User Name<br />
</pre><br />
<br />
== Email Domains und User ==<br />
Debian: das Anlegen einer Domain im Web-Interface schlägt direkt fehl - "Bad Request".<br><br />
Nicht schlimm, aber vielleicht nehme ich doch lieber die fertige Appliance auf Basis von S.u.S.E. ?<br />
<br />
Per Kommandozeile funktioniert es jedenfalls.<br />
<pre><br />
domains=$(ssh $kopano_server ucr get mail/hosteddomains)<br />
users=$(grommunio-admin ldap search)<br />
<br />
# create email domains<br />
for domain in $domains; do<br />
u_cnt=$(echo "$users" | grep -c $domain)<br />
[ $u_cnt -gt 0 ] && grommunio-admin domain create -u $u_cnt $domain<br />
done<br />
<br />
# import and sync LDAP users<br />
grommunio-admin ldap downsync -c<br />
</pre><br />
Der Import der User funktioniert jedenfalls auch über das Webinterface.<br />
<br />
=== Export - Import via Outlook .pst ===<br />
Da ich nur 5 Konten habe, ist diese Methode auch nicht ganz abwegig:<br />
<br />
<pre>gromox-pff2mt /tmp/neobiker_outlook.pst | gromox-mt2exm -u neobiker@neobiker.de<br />
</pre><br />
<br />
=== Import von Emails aus Kopano ===<br />
Der Import landet (leider) in einem separatem Verzeichnis:<br />
[[Datei:Grommunio Import.jpg|mini]]<br />
<br />
Das ist nicht ganz das was ich mir für einen Import vorstelle: Jeder User muss alle seine Inhalte manuell auf dem Server in die Hauptverzeichnisse verschieben ... ?<br />
<br />
Ansonsten hat der Import prinzipiell für mein Neobiker Postfach funktioniert.<br />
<br />
<pre># gromox-kdb2mt — Utility for analysis/import of Kopano mailboxes<br />
# gromox-mt2exm — Utility for importing various mail items<br />
<br />
mkdir -p /mnt/kopano_attachments<br />
sshfs $kopano_server:/var/lib/kopano/attachments /mnt/kopano_attachments<br />
<br />
# detach (CTRL-A d) the SSH tunnel to SQL server (who only accepts localhost conections)<br />
screen ssh -L 12345:localhost:3306 "root@${kopano_server}"<br />
<br />
# EXAMPLE for user neobiker = neobiker@neobiker.de<br />
# ------------------------------------------------<br />
# ENVIRONMENT variable is used for SQL password<br />
# either EXPORT it or write in one line with cmd<br />
export_mbox="gromox-kdb2mt --sql-host 127.0.0.1 --sql-port 12345 --src-attach /mnt/kopano_attachments"<br />
SQLPASS=$(ssh $kopano_server cat /etc/mysql.secret)<br />
<br />
SQLPASS=$SQLPASS $export_mbox --mbox-mro neobiker | gromox-mt2exm -u neobiker@neobiker.de<br />
<br />
kdb2mt: No ACLs will be extracted.<br />
kdb Server GUID: xxxxxxxxxxxxxxxxxxxxxxxxxxx<br />
Database schema is kdb-118<br />
Store GUID for MRO "neobiker": xxxxxxxxxxxxxxxxxxxxxxxxx<br />
Processing folder "" (7 elements)...<br />
Processing folder "IPM_SUBTREE" (19 elements)...<br />
Processing folder "Posteingang" (791 elements)...<br />
Processing folder "Postausgang" (0 elements)...<br />
Processing folder "Gelöschte Objekte" (1 elements)...<br />
Processing folder "Gesendete Objekte" (1 elements)...<br />
Processing folder "Kontakte" (0 elements)...<br />
Processing folder "Kalender" (1 elements)...<br />
Processing folder "Entwürfe" (0 elements)...<br />
Processing folder "Journal" (0 elements)...<br />
Processing folder "Notizen" (0 elements)...<br />
Processing folder "Aufgaben" (0 elements)...<br />
Processing folder "Junk E-Mail" (44 elements)...<br />
Processing folder "RSS Feeds" (0 elements)...<br />
Processing folder "Konversationseinstellungen" (0 elements)...<br />
Processing folder "Quickstep Einstellungen" (0 elements)...<br />
Processing folder "Vorgeschlagene Kontakte" (0 elements)...<br />
Processing folder "Spambox" (0 elements)...<br />
Processing folder "Junk-E-Mail" (0 elements)...<br />
Processing folder "Gelöschte Elemente" (0 elements)...<br />
Processing folder "Gesendete Elemente" (0 elements)...<br />
Processing folder "IPM_COMMON_VIEWS" (2 elements)...<br />
Processing folder "IPM_VIEWS" (0 elements)...<br />
Processing folder "FINDER_ROOT" (0 elements)...<br />
Processing folder "Verknüpfung" (0 elements)...<br />
Processing folder "Schedule" (0 elements)...<br />
Processing folder "Freebusy Data" (1 elements)...<br />
</pre><br />
<br />
=== Import aller Email Konten ===<br />
<br />
<pre><br />
export_mbox="gromox-kdb2mt --sql-host 127.0.0.1 --sql-port 12345 --src-attach /mnt/kopano_attachments"<br />
SQLPASS=$(ssh $kopano_server cat /etc/mysql.secret)<br />
<br />
# LOOP over all email accounts (except SYSTEM)<br />
# --------------------------------------------<br />
users=$(ssh $kopano_server kopano-admin -l | awk '{print $1}' | tail -n +4 | grep -v SYSTEM)<br />
<br />
for user in ${users}; do<br />
<br />
details=$(ssh $kopano_server kopano-admin --details $user)<br />
email=$(echo "${details}" $user | awk '/Emailaddress:/ {print $2}')<br />
guid=$(echo "${details}" $user | awk '/Store GUID:/ {print $3}')<br />
<br />
SQLPASS=$SQLPASS $export_mbox --mbox-guid $guid | gromox-mt2exm -u $email<br />
<br />
done<br />
<br />
# stop ssh tunnel -> exit ssh on kopano server<br />
screen -r<br />
<br />
# unmount kopano attachments<br />
umount /mnt/kopano_attachments<br />
</pre><br />
<br />
== Datenablage ==<br />
Eventuell möchte ich ein separates Filesystem für die Daten verwenden.<br><br />
Die Daten liegen unter ''/var/lib/mysql*'' und ''/var/lib/grom*'' ?<br />
<br />
<pre>grommunio:/var/lib # ls mysql* grom*<br />
grommunio-admin-api:<br />
auth-private.pem auth-public.pem<br />
<br />
grommunio-antispam:<br />
110ec84de81cc98813b71d34d42dedbc199bd687.map 65b4e39783c10c8ed89dbec4983259f22418f34d.map ea942f35f2c82e84bdb7b8ceb34537f7dbe986eb.map stats.ucl<br />
2368c73b937d98513ed72c4b04f4247bda43fdb5.map 877aa38dd77ed707d20b2f77d46b905b69a6295a.map f3933e67d5f9574a9eaf78cd0a8e99140ecc502e.map<br />
2e66b2415ec9b99aaa155761c211e51def12016e.map 8fe64ecd161a46f91a78ece93609cb863f9f42f2.map rspamd.rrd<br />
3b1eab0b21b32c970b8fae4a9010a539879368e3.map cb5a8189726ac3a8880e1c44ed6220c6d794521b.map rspamd.sock<br />
<br />
grommunio-dav:<br />
<br />
grommunio-files:<br />
apps-external config data sessions tmp<br />
<br />
grommunio-web:<br />
session sqlite-index tmp<br />
<br />
gromox:<br />
domain queue timer.txt user<br />
<br />
mysql:<br />
aria_log.00000001 ddl_recovery.log grommunio ib_buffer_pool ib_logfile0 multi-master.info mysql_upgrade_info sys<br />
aria_log_control grofiles grommunio.pid ibdata1 ibtmp1 mysql performance_schema test<br />
<br />
mysql-files:<br />
</pre><br />
<br />
== Postfix ==<br />
Mail Delivery Agent (MDA) Konfiguration <br />
* zum versenden von Emails (external relay host)<br />
* Mailaddress rewritings (neobiker@local -> neobiker@neobiker.de)<br />
<br />
Vergleich der wichtigsten Konfigurationsparameter<br />
<pre>grep -v ^# /etc/postfix/main.cf | grep [[:alnum:]] | sed -e 's/^\W+//g' | sort > postfix_config_sorted_grommunio<br />
ssh $kopano_server "grep -v ^# /etc/postfix/main.cf | grep [[:alnum:]] | sed -e 's/^\W+//g' | sort" > postfix_config_sorted_kopano<br />
<br />
diff -y postfix_config<br />
...<br />
<br />
# most relevant entries (differs/unconfigured)<br />
masquerade_domains = $mydomain<br />
content_filter = smtp-amavis:[127.0.0.1]:10024<br />
disable_vrfy_command = no<br />
mailbox_size_limit<br />
message_size_limit<br />
relay_domains = $mydestination<br />
relayhost = smtp.mailhoster.de<br />
smtpd_tls_auth_only = yes<br />
smtpd_tls_cert_file = /etc/univention/ssl/ucsmail...<br />
smtpd_use_tls = yes<br />
smtp_sasl_password_maps = hash:/etc/postfix/smtp_auth<br />
smtp_sasl_security_options = noanonymous<br />
<br />
</pre><br />
Vermutlich muss ich mich nur um den externen Relay Host kümmern.<br />
<br />
<br />
=== Mail Relay Server ===<br />
Postfix can use a mail relay server.<br />
<br />
<pre>ssh $kopano_server ucr get mail/relayhost</pre><br />
<br />
<pre># authentification on relay host<br />
# format: FQDN-Relayhost username:password<br />
ssh $kopano_server cat /etc/postfix/smtp_auth >> sasl_passwd<br />
<br />
vi /etc/postfix/main.cf<br />
relayhost = [smtp.provider.de]<br />
smtp_sasl_auth_enable = yes<br />
smtp_sasl_security_options = noanonymous<br />
smtp_sasl_password_maps = lmdb:/etc/postfix/sasl_passwd`<br />
smtp_use_tls = yes<br />
<br />
postmap /etc/postfix/sasl_passwd<br />
systemctl restart postfix<br />
</pre><br />
Das hat funktioniert:<br />
* Email nach EXTERN ging raus.<br />
* Email nach intern -> keine Postfix Aktion (nur intern innerhalb grommunio)<br />
<br />
Doku grommunio: [https://docs.grommunio.com/admin/administration.html#adding-a-file relayhost]<br />
<br />
Eine andere Info dazu [https://andersgood.de/kurz-notiert/smtp-relayhost-in-grommunio-konfigurieren hier].<br />
<br />
'''ACHTUNG''': Appliance (SuSE) -> Ich vermute stark, dass ein versehentliches ''config.postfix'' auf dem server die Konfiguration zerschiesst, da man das wohl in der Setupdatei /etc/sysconfig/postfix (SuSE) eintragen sollte.<br />
Umso unverständlicher, warum die Doku zu Relay Server bei grommunio so schwach ist.<br />
<br />
=== Mail Address rewritings ===<br />
<br />
Nix zu tun, denke ich.<br />
<br />
== Fetchmail ==<br />
Fetchmail Konfiguration zum abholen der Emails von meinem Email Provider.<br />
Emails werden entweder von UCS '''oder ''' grommunio abgeholt und and Postfix übergeben!<br />
<br />
Auf UCS für jeden Benutzer deaktivieren <br><br />
-> ''Advanced settings ‣ Remote mail retrieval (single)''<br><br />
oder Postfix auf UCS zur weiterleitung an grommunio konfigurieren.<br />
<br />
<pre>systemctl stop fetchmail<br />
systemctl disable fetchmail<br />
ucr set fetchmail/autostart=false<br />
<br />
# fetchmail config and passwords<br />
cat /etc/fetchmailrc<br />
</pre><br />
<br />
== Proxy Konfiguration (Nginx) ==<br />
Ich habe für jede Email Domain einen deSEC (DynDNS) Eintrag auf mein Internetgateway (Nginx auf OPNSense) erstellt.<br />
Dafür verwende ich ein Let's Encrypt Zertifikat, welches die Domains als DNS Alternate Names beinhaltet.<br />
Das Webinterface von grommunio ist unter ''/web'' erreichbar, während in der Übergangszeit Kopano unter ''/webapp'' erreichbar bleibt.<br />
Active Sync für Mobile Devices (URL ist identisch) kann damit immerhin pro Email Domain separat umgestellt werden. Outlook ist bei mir derzeit nur intern erreichbar, also extern nur über VPN (oder halt das Webinterface).</div>
Neobiker
http://neobiker.de/wiki/index.php?title=Kopano_to_grommunio_HowTo&diff=2111
Kopano to grommunio HowTo
2024-01-05T17:04:07Z
<p>Neobiker: /* Mail Relay Server */</p>
<hr />
<div>= Installation auf Debian 12 =<br />
Eine fertige grommunio Appliance ist auf Basis von S.u.S.E. erstellt und steht u.a. als ISO download zur Verfügung. Da ich derzeit ausschliesslich Debian basierte Systeme verwende, bevorzuge ich eine grommunio Installation auf Debian 12 (bookworm).<br />
<br />
Im grommunio Forum findet sich ein Diskussionsthread für die [https://community.grommunio.com/d/447-debian-11-clean-install-script grommunio Installation auf Debian 12 durch ein Script]. Diese läuft auf einer neu erstellten VM auf Proxmox Server fehlerfrei durch. In einem LXC Container gab es Probleme, u.a. klemmte der POP3 Service.<br />
<br />
Im Anschluss an das Installationsskript habe ich noch folgendes Script unter '''Additions''' - ''Fix Grommunio Admin Live Status page'' ausgeführt, welches ebenfalls funktionierte und die entsprechende Seite im Web Interface reparierte.<br />
<br />
= Konfiguration =<br />
Kopano Core ist bei mir als Applikation auf meinem UCS File- und Mailserver installiert. Die Benutzerkonten und deren Konfiguration werden von UCS verwaltet und stehen per LDAP zur Verfügung.<br />
<br />
<pre># mein UCS-Kopano Server<br />
kopano_server=ucsmail<br />
echo "kopano_server=ucsmail" >> ~/.bashrc<br />
<br />
# ssh connections without prompts<br />
ssh-keygen<br />
ssh-copy-id $kopano_server<br />
<br />
# mount /var/lib/kopano/attachments per sshfs<br />
if [ -e /etc/debian_version ] then<br />
apt install sshfs screen<br />
else<br />
zypper in sshfs # (grommunio app/iso installation)<br />
fi<br />
mkdir -p /mnt/kopano_attachments<br />
sshfs $kopano_server:/var/lib/kopano/attachments /mnt/kopano_attachments<br />
</pre><br />
<br />
== Zertifikate ==<br />
Falls ein '''[https://help.univention.com/t/ssl-zertifikat-und-dns-alias/5780/7 Server Zertifikat von UCS]''' für den '''grommunio''' server erzeugt wurde kann das verwendet werden.<br />
<br />
Ich erstelle hier ein neues Zertifikat für den grommunio Server, der als DNS-Alternativen jeweils '''autodiscover''' (Outlook) und '''autoconfig''' (Android) Einträge für jede meiner Email Domains enthält. Damit erfolgt die Mail-Client Konfiguration automatisch, sofern der Rechner sich in einem '''lokalem''' Netzsegment befindet.<br />
<br />
Background:<br><br />
Meine Emails werden extern gehostet, ich importiere sie per Fetchmail. Daher bin ich nicht in der Lage diese Autodiscover/Autoconf Einträge im (externen) DNS meines Hosters zu erstellen. Die entsprechende '''lokale DNS Auflösung''' übernimmt mein '''Unbound DNS''' Server mittels '''Overwrite''' Einträgen, welche auf den grommunio Server verweisen. Daher müssen diese DNS Alias Namen in dessen Zertifikat vorhanden sein.<br />
<br />
<pre>ssh ucs # UCS Master!<br />
univention-certificate new -name grommunio.domain.de<br />
cd /etc/univention/ssl/grommunio.domain.de<br />
<br />
# add DNS Aliases<br />
vi openssl.cnf<br />
# subjectAltName = DNS:grommunio.domain.de, DNS:grommunio <br />
# add all your email domains autodiscover/autoconfig entries: DNS:autodiscover.email_domain.de, DNS:autoconfig.email_domain.de<br />
<br />
DEFAULT_CRL_DAYS="$(/usr/sbin/univention-config-registry get ssl/crl/validity)"<br />
DEFAULT_DAYS="$(/usr/sbin/univention-config-registry get ssl/default/days)"<br />
DEFAULT_MD="$(/usr/sbin/univention-config-registry get ssl/default/hashfunction)"<br />
DEFAULT_BITS="$(/usr/sbin/univention-config-registry get ssl/default/bits)"<br />
export DEFAULT_MD DEFAULT_BITS DEFAULT_CRL_DAYS DEFAULT_DAYS<br />
openssl req -new -config openssl.cnf -key private.key -out req.pem<br />
<br />
univention-certificate renew -name grommunio.domain.de -days $DEFAULT_DAYS<br />
</pre><br />
<br />
Damit funktioniert die Anmeldung am Webinterface auch per LDAP mit starttls, ansonsten nur LDAP ohne starttls oder halt mit SQL Accounts.<br />
<pre><br />
cd /etc/grommunio-common/ssl<br />
scp $kopano_server:/etc/univention/ssl/grommunio/cert.pem server-bundle.pem<br />
scp $kopano_server:/etc/univention/ssl/grommunio/private.key server.key<br />
chown gromox:gromox server*<br />
chmod 660 server*<br />
<br />
# Root CA<br />
if [ -e /etc/debian_version ] then<br />
scp $kopano_server:/etc/univention/ssl/ucsCA/CAcert.pem /usr/local/share/ca-certificates/my-custom-ca/<br />
else<br />
# SuSE appliance location<br />
scp $kopano_server:/etc/univention/ssl/ucsCA/CAcert.pem /etc/pki/trust/anchors/<br />
fi<br />
update-ca-certificates<br />
<br />
systemctl restart nginx<br />
<br />
# autodiscover Eintrag testen<br />
PASS='email_passwort' gromox-dscli -ve user@domainname<br />
</pre><br />
<br />
== LDAP ==<br />
Die LDAP Konfiguration kann mit dem UCS Template im Webinterface vorgenommen werden.<br />
Ich übernehme einige Kopano Werte:<br />
<pre><br />
ssh $kopano_server grep -e ^ldap_uri -e ^ldap_bind -e ^ldap_search_base -e ^ldap_user_search -e ^ldap_group_search /etc/kopano/ldap.cfg<br />
<br />
ldap_uri = ldap://ucsmail.domain.de:7389/<br />
ldap_bind_user = cn=ucsmail,cn=dc,cn=computers,dc=domain,dc=de<br />
ldap_bind_passwd = xxxxxxxxxxxxxx<br />
ldap_search_base = dc=domain,dc=de<br />
ldap_user_search_filter = (kopanoAccount=1)<br />
ldap_group_search_filter = (&(kopanoAccount=1)(objectClass=kopano-group))<br />
<br />
# configure LDAP accordingly<br />
grommunio-admin ldap configure<br />
<br />
# test: list users<br />
grommunio-admin ldap search<br />
ID Type E-Mail Name<br />
2222222222-11-10111-22222-3333333333 user user@domainname User Name<br />
</pre><br />
<br />
== Email Domains und User ==<br />
Debian: das Anlegen einer Domain im Web-Interface schlägt direkt fehl - "Bad Request".<br><br />
Nicht schlimm, aber vielleicht nehme ich doch lieber die fertige Appliance auf Basis von S.u.S.E. ?<br />
<br />
Per Kommandozeile funktioniert es jedenfalls.<br />
<pre><br />
domains=$(ssh $kopano_server ucr get mail/hosteddomains)<br />
users=$(grommunio-admin ldap search)<br />
<br />
# create email domains<br />
for domain in $domains; do<br />
u_cnt=$(echo "$users" | grep -c $domain)<br />
[ $u_cnt -gt 0 ] && grommunio-admin domain create -u $u_cnt $domain<br />
done<br />
<br />
# import and sync LDAP users<br />
grommunio-admin ldap downsync -c<br />
</pre><br />
Der Import der User funktioniert jedenfalls auch über das Webinterface.<br />
<br />
=== Export - Import via Outlook .pst ===<br />
Da ich nur 5 Konten habe, ist diese Methode auch nicht ganz abwegig:<br />
<br />
<pre>gromox-pff2mt /tmp/neobiker_outlook.pst | gromox-mt2exm -u neobiker@neobiker.de<br />
</pre><br />
<br />
=== Import von Emails aus Kopano ===<br />
Der Import landet (leider) in einem separatem Verzeichnis:<br />
[[Datei:Grommunio Import.jpg|mini]]<br />
<br />
Das ist nicht ganz das was ich mir für einen Import vorstelle: Jeder User muss alle seine Inhalte manuell auf dem Server in die Hauptverzeichnisse verschieben ... ?<br />
<br />
Ansonsten hat der Import prinzipiell für mein Neobiker Postfach funktioniert.<br />
<br />
<pre># gromox-kdb2mt — Utility for analysis/import of Kopano mailboxes<br />
# gromox-mt2exm — Utility for importing various mail items<br />
<br />
mkdir -p /mnt/kopano_attachments<br />
sshfs $kopano_server:/var/lib/kopano/attachments /mnt/kopano_attachments<br />
<br />
# detach (CTRL-A d) the SSH tunnel to SQL server (who only accepts localhost conections)<br />
screen ssh -L 12345:localhost:3306 "root@${kopano_server}"<br />
<br />
# EXAMPLE for user neobiker = neobiker@neobiker.de<br />
# ------------------------------------------------<br />
# ENVIRONMENT variable is used for SQL password<br />
# either EXPORT it or write in one line with cmd<br />
export_mbox="gromox-kdb2mt --sql-host 127.0.0.1 --sql-port 12345 --src-attach /mnt/kopano_attachments"<br />
SQLPASS=$(ssh $kopano_server cat /etc/mysql.secret)<br />
<br />
SQLPASS=$SQLPASS $export_mbox --mbox-mro neobiker | gromox-mt2exm -u neobiker@neobiker.de<br />
<br />
kdb2mt: No ACLs will be extracted.<br />
kdb Server GUID: xxxxxxxxxxxxxxxxxxxxxxxxxxx<br />
Database schema is kdb-118<br />
Store GUID for MRO "neobiker": xxxxxxxxxxxxxxxxxxxxxxxxx<br />
Processing folder "" (7 elements)...<br />
Processing folder "IPM_SUBTREE" (19 elements)...<br />
Processing folder "Posteingang" (791 elements)...<br />
Processing folder "Postausgang" (0 elements)...<br />
Processing folder "Gelöschte Objekte" (1 elements)...<br />
Processing folder "Gesendete Objekte" (1 elements)...<br />
Processing folder "Kontakte" (0 elements)...<br />
Processing folder "Kalender" (1 elements)...<br />
Processing folder "Entwürfe" (0 elements)...<br />
Processing folder "Journal" (0 elements)...<br />
Processing folder "Notizen" (0 elements)...<br />
Processing folder "Aufgaben" (0 elements)...<br />
Processing folder "Junk E-Mail" (44 elements)...<br />
Processing folder "RSS Feeds" (0 elements)...<br />
Processing folder "Konversationseinstellungen" (0 elements)...<br />
Processing folder "Quickstep Einstellungen" (0 elements)...<br />
Processing folder "Vorgeschlagene Kontakte" (0 elements)...<br />
Processing folder "Spambox" (0 elements)...<br />
Processing folder "Junk-E-Mail" (0 elements)...<br />
Processing folder "Gelöschte Elemente" (0 elements)...<br />
Processing folder "Gesendete Elemente" (0 elements)...<br />
Processing folder "IPM_COMMON_VIEWS" (2 elements)...<br />
Processing folder "IPM_VIEWS" (0 elements)...<br />
Processing folder "FINDER_ROOT" (0 elements)...<br />
Processing folder "Verknüpfung" (0 elements)...<br />
Processing folder "Schedule" (0 elements)...<br />
Processing folder "Freebusy Data" (1 elements)...<br />
</pre><br />
<br />
=== Import aller Email Konten ===<br />
<br />
<pre><br />
export_mbox="gromox-kdb2mt --sql-host 127.0.0.1 --sql-port 12345 --src-attach /mnt/kopano_attachments"<br />
SQLPASS=$(ssh $kopano_server cat /etc/mysql.secret)<br />
<br />
# LOOP over all email accounts (except SYSTEM)<br />
# --------------------------------------------<br />
users=$(ssh $kopano_server kopano-admin -l | awk '{print $1}' | tail -n +4 | grep -v SYSTEM)<br />
<br />
for user in ${users}; do<br />
<br />
details=$(ssh $kopano_server kopano-admin --details $user)<br />
email=$(echo "${details}" $user | awk '/Emailaddress:/ {print $2}')<br />
guid=$(echo "${details}" $user | awk '/Store GUID:/ {print $3}')<br />
<br />
SQLPASS=$SQLPASS $export_mbox --mbox-guid $guid | gromox-mt2exm -u $email<br />
<br />
done<br />
<br />
# stop ssh tunnel -> exit ssh on kopano server<br />
screen -r<br />
<br />
# unmount kopano attachments<br />
umount /mnt/kopano_attachments<br />
</pre><br />
<br />
== Datenablage ==<br />
Eventuell möchte ich ein separates Filesystem für die Daten verwenden.<br><br />
Die Daten liegen unter ''/var/lib/mysql*'' und ''/var/lib/grom*'' ?<br />
<br />
<pre>grommunio:/var/lib # ls mysql* grom*<br />
grommunio-admin-api:<br />
auth-private.pem auth-public.pem<br />
<br />
grommunio-antispam:<br />
110ec84de81cc98813b71d34d42dedbc199bd687.map 65b4e39783c10c8ed89dbec4983259f22418f34d.map ea942f35f2c82e84bdb7b8ceb34537f7dbe986eb.map stats.ucl<br />
2368c73b937d98513ed72c4b04f4247bda43fdb5.map 877aa38dd77ed707d20b2f77d46b905b69a6295a.map f3933e67d5f9574a9eaf78cd0a8e99140ecc502e.map<br />
2e66b2415ec9b99aaa155761c211e51def12016e.map 8fe64ecd161a46f91a78ece93609cb863f9f42f2.map rspamd.rrd<br />
3b1eab0b21b32c970b8fae4a9010a539879368e3.map cb5a8189726ac3a8880e1c44ed6220c6d794521b.map rspamd.sock<br />
<br />
grommunio-dav:<br />
<br />
grommunio-files:<br />
apps-external config data sessions tmp<br />
<br />
grommunio-web:<br />
session sqlite-index tmp<br />
<br />
gromox:<br />
domain queue timer.txt user<br />
<br />
mysql:<br />
aria_log.00000001 ddl_recovery.log grommunio ib_buffer_pool ib_logfile0 multi-master.info mysql_upgrade_info sys<br />
aria_log_control grofiles grommunio.pid ibdata1 ibtmp1 mysql performance_schema test<br />
<br />
mysql-files:<br />
</pre><br />
<br />
== Postfix ==<br />
Mail Delivery Agent (MDA) Konfiguration <br />
* zum versenden von Emails (external relay host)<br />
* Mailaddress rewritings (neobiker@local -> neobiker@neobiker.de)<br />
<br />
Vergleich der wichtigsten Konfigurationsparameter<br />
<pre>grep -v ^# /etc/postfix/main.cf | grep [[:alnum:]] | sed -e 's/^\W+//g' | sort > postfix_config_sorted_grommunio<br />
ssh $kopano_server "grep -v ^# /etc/postfix/main.cf | grep [[:alnum:]] | sed -e 's/^\W+//g' | sort" > postfix_config_sorted_kopano<br />
<br />
diff -y postfix_config<br />
...<br />
<br />
# most relevant entries (differs/unconfigured)<br />
masquerade_domains = $mydomain<br />
content_filter = smtp-amavis:[127.0.0.1]:10024<br />
disable_vrfy_command = no<br />
mailbox_size_limit<br />
message_size_limit<br />
relay_domains = $mydestination<br />
relayhost = smtp.mailhoster.de<br />
smtpd_tls_auth_only = yes<br />
smtpd_tls_cert_file = /etc/univention/ssl/ucsmail...<br />
smtpd_use_tls = yes<br />
smtp_sasl_password_maps = hash:/etc/postfix/smtp_auth<br />
smtp_sasl_security_options = noanonymous<br />
<br />
</pre><br />
Vermutlich muss ich mich nur um den externen Relay Host kümmern.<br />
<br />
<br />
=== Mail Relay Server ===<br />
Postfix can use a mail relay server.<br />
<br />
<pre>ssh $kopano_server ucr get mail/relayhost</pre><br />
<br />
<pre># authentification on relay host<br />
/etc/postfix/smtp_auth<br />
# format<br />
# FQDN-Relayhost username:password<br />
<br />
postmap /etc/postfix/smtp_auth<br />
postmap /etc/postfix/tls_policy<br />
service postfix reload<br />
</pre><br />
Das hat funktioniert:<br />
* Email nach EXTERN ging raus.<br />
* Email nach intern -> keine Postfix Aktion (nur intern innerhalb grommunio)<br />
<br />
Doku grommunio: [https://docs.grommunio.com/admin/administration.html#adding-a-file relayhost]<br />
<br />
Eine andere Info dazu [https://andersgood.de/kurz-notiert/smtp-relayhost-in-grommunio-konfigurieren hier].<br />
<br />
'''ACHTUNG''': Appliance (SuSE) -> Ich vermute stark, dass ein versehentliches ''config.postfix'' auf dem server die Konfiguration zerschiesst, da man das wohl in der Setupdatei /etc/sysconfig/postfix (SuSE) eintragen sollte.<br />
Umso unverständlicher, warum die Doku zu Relay Server bei grommunio so schwach ist.<br />
<br />
=== Mail Address rewritings ===<br />
<br />
Nix zu tun, denke ich.<br />
<br />
== Fetchmail ==<br />
Fetchmail Konfiguration zum abholen der Emails von meinem Email Provider.<br />
Emails werden entweder von UCS '''oder ''' grommunio abgeholt und and Postfix übergeben!<br />
<br />
Auf UCS für jeden Benutzer deaktivieren <br><br />
-> ''Advanced settings ‣ Remote mail retrieval (single)''<br><br />
oder Postfix auf UCS zur weiterleitung an grommunio konfigurieren.<br />
<br />
<pre>systemctl stop fetchmail<br />
systemctl disable fetchmail<br />
ucr set fetchmail/autostart=false<br />
<br />
# fetchmail config and passwords<br />
cat /etc/fetchmailrc<br />
</pre><br />
<br />
== Proxy Konfiguration (Nginx) ==<br />
Ich habe für jede Email Domain einen deSEC (DynDNS) Eintrag auf mein Internetgateway (Nginx auf OPNSense) erstellt.<br />
Dafür verwende ich ein Let's Encrypt Zertifikat, welches die Domains als DNS Alternate Names beinhaltet.<br />
Das Webinterface von grommunio ist unter ''/web'' erreichbar, während in der Übergangszeit Kopano unter ''/webapp'' erreichbar bleibt.<br />
Active Sync für Mobile Devices (URL ist identisch) kann damit immerhin pro Email Domain separat umgestellt werden. Outlook ist bei mir derzeit nur intern erreichbar, also extern nur über VPN (oder halt das Webinterface).</div>
Neobiker
http://neobiker.de/wiki/index.php?title=Kopano_to_grommunio_HowTo&diff=2110
Kopano to grommunio HowTo
2024-01-05T16:59:57Z
<p>Neobiker: /* Mail Relay Server */</p>
<hr />
<div>= Installation auf Debian 12 =<br />
Eine fertige grommunio Appliance ist auf Basis von S.u.S.E. erstellt und steht u.a. als ISO download zur Verfügung. Da ich derzeit ausschliesslich Debian basierte Systeme verwende, bevorzuge ich eine grommunio Installation auf Debian 12 (bookworm).<br />
<br />
Im grommunio Forum findet sich ein Diskussionsthread für die [https://community.grommunio.com/d/447-debian-11-clean-install-script grommunio Installation auf Debian 12 durch ein Script]. Diese läuft auf einer neu erstellten VM auf Proxmox Server fehlerfrei durch. In einem LXC Container gab es Probleme, u.a. klemmte der POP3 Service.<br />
<br />
Im Anschluss an das Installationsskript habe ich noch folgendes Script unter '''Additions''' - ''Fix Grommunio Admin Live Status page'' ausgeführt, welches ebenfalls funktionierte und die entsprechende Seite im Web Interface reparierte.<br />
<br />
= Konfiguration =<br />
Kopano Core ist bei mir als Applikation auf meinem UCS File- und Mailserver installiert. Die Benutzerkonten und deren Konfiguration werden von UCS verwaltet und stehen per LDAP zur Verfügung.<br />
<br />
<pre># mein UCS-Kopano Server<br />
kopano_server=ucsmail<br />
echo "kopano_server=ucsmail" >> ~/.bashrc<br />
<br />
# ssh connections without prompts<br />
ssh-keygen<br />
ssh-copy-id $kopano_server<br />
<br />
# mount /var/lib/kopano/attachments per sshfs<br />
if [ -e /etc/debian_version ] then<br />
apt install sshfs screen<br />
else<br />
zypper in sshfs # (grommunio app/iso installation)<br />
fi<br />
mkdir -p /mnt/kopano_attachments<br />
sshfs $kopano_server:/var/lib/kopano/attachments /mnt/kopano_attachments<br />
</pre><br />
<br />
== Zertifikate ==<br />
Falls ein '''[https://help.univention.com/t/ssl-zertifikat-und-dns-alias/5780/7 Server Zertifikat von UCS]''' für den '''grommunio''' server erzeugt wurde kann das verwendet werden.<br />
<br />
Ich erstelle hier ein neues Zertifikat für den grommunio Server, der als DNS-Alternativen jeweils '''autodiscover''' (Outlook) und '''autoconfig''' (Android) Einträge für jede meiner Email Domains enthält. Damit erfolgt die Mail-Client Konfiguration automatisch, sofern der Rechner sich in einem '''lokalem''' Netzsegment befindet.<br />
<br />
Background:<br><br />
Meine Emails werden extern gehostet, ich importiere sie per Fetchmail. Daher bin ich nicht in der Lage diese Autodiscover/Autoconf Einträge im (externen) DNS meines Hosters zu erstellen. Die entsprechende '''lokale DNS Auflösung''' übernimmt mein '''Unbound DNS''' Server mittels '''Overwrite''' Einträgen, welche auf den grommunio Server verweisen. Daher müssen diese DNS Alias Namen in dessen Zertifikat vorhanden sein.<br />
<br />
<pre>ssh ucs # UCS Master!<br />
univention-certificate new -name grommunio.domain.de<br />
cd /etc/univention/ssl/grommunio.domain.de<br />
<br />
# add DNS Aliases<br />
vi openssl.cnf<br />
# subjectAltName = DNS:grommunio.domain.de, DNS:grommunio <br />
# add all your email domains autodiscover/autoconfig entries: DNS:autodiscover.email_domain.de, DNS:autoconfig.email_domain.de<br />
<br />
DEFAULT_CRL_DAYS="$(/usr/sbin/univention-config-registry get ssl/crl/validity)"<br />
DEFAULT_DAYS="$(/usr/sbin/univention-config-registry get ssl/default/days)"<br />
DEFAULT_MD="$(/usr/sbin/univention-config-registry get ssl/default/hashfunction)"<br />
DEFAULT_BITS="$(/usr/sbin/univention-config-registry get ssl/default/bits)"<br />
export DEFAULT_MD DEFAULT_BITS DEFAULT_CRL_DAYS DEFAULT_DAYS<br />
openssl req -new -config openssl.cnf -key private.key -out req.pem<br />
<br />
univention-certificate renew -name grommunio.domain.de -days $DEFAULT_DAYS<br />
</pre><br />
<br />
Damit funktioniert die Anmeldung am Webinterface auch per LDAP mit starttls, ansonsten nur LDAP ohne starttls oder halt mit SQL Accounts.<br />
<pre><br />
cd /etc/grommunio-common/ssl<br />
scp $kopano_server:/etc/univention/ssl/grommunio/cert.pem server-bundle.pem<br />
scp $kopano_server:/etc/univention/ssl/grommunio/private.key server.key<br />
chown gromox:gromox server*<br />
chmod 660 server*<br />
<br />
# Root CA<br />
if [ -e /etc/debian_version ] then<br />
scp $kopano_server:/etc/univention/ssl/ucsCA/CAcert.pem /usr/local/share/ca-certificates/my-custom-ca/<br />
else<br />
# SuSE appliance location<br />
scp $kopano_server:/etc/univention/ssl/ucsCA/CAcert.pem /etc/pki/trust/anchors/<br />
fi<br />
update-ca-certificates<br />
<br />
systemctl restart nginx<br />
<br />
# autodiscover Eintrag testen<br />
PASS='email_passwort' gromox-dscli -ve user@domainname<br />
</pre><br />
<br />
== LDAP ==<br />
Die LDAP Konfiguration kann mit dem UCS Template im Webinterface vorgenommen werden.<br />
Ich übernehme einige Kopano Werte:<br />
<pre><br />
ssh $kopano_server grep -e ^ldap_uri -e ^ldap_bind -e ^ldap_search_base -e ^ldap_user_search -e ^ldap_group_search /etc/kopano/ldap.cfg<br />
<br />
ldap_uri = ldap://ucsmail.domain.de:7389/<br />
ldap_bind_user = cn=ucsmail,cn=dc,cn=computers,dc=domain,dc=de<br />
ldap_bind_passwd = xxxxxxxxxxxxxx<br />
ldap_search_base = dc=domain,dc=de<br />
ldap_user_search_filter = (kopanoAccount=1)<br />
ldap_group_search_filter = (&(kopanoAccount=1)(objectClass=kopano-group))<br />
<br />
# configure LDAP accordingly<br />
grommunio-admin ldap configure<br />
<br />
# test: list users<br />
grommunio-admin ldap search<br />
ID Type E-Mail Name<br />
2222222222-11-10111-22222-3333333333 user user@domainname User Name<br />
</pre><br />
<br />
== Email Domains und User ==<br />
Debian: das Anlegen einer Domain im Web-Interface schlägt direkt fehl - "Bad Request".<br><br />
Nicht schlimm, aber vielleicht nehme ich doch lieber die fertige Appliance auf Basis von S.u.S.E. ?<br />
<br />
Per Kommandozeile funktioniert es jedenfalls.<br />
<pre><br />
domains=$(ssh $kopano_server ucr get mail/hosteddomains)<br />
users=$(grommunio-admin ldap search)<br />
<br />
# create email domains<br />
for domain in $domains; do<br />
u_cnt=$(echo "$users" | grep -c $domain)<br />
[ $u_cnt -gt 0 ] && grommunio-admin domain create -u $u_cnt $domain<br />
done<br />
<br />
# import and sync LDAP users<br />
grommunio-admin ldap downsync -c<br />
</pre><br />
Der Import der User funktioniert jedenfalls auch über das Webinterface.<br />
<br />
=== Export - Import via Outlook .pst ===<br />
Da ich nur 5 Konten habe, ist diese Methode auch nicht ganz abwegig:<br />
<br />
<pre>gromox-pff2mt /tmp/neobiker_outlook.pst | gromox-mt2exm -u neobiker@neobiker.de<br />
</pre><br />
<br />
=== Import von Emails aus Kopano ===<br />
Der Import landet (leider) in einem separatem Verzeichnis:<br />
[[Datei:Grommunio Import.jpg|mini]]<br />
<br />
Das ist nicht ganz das was ich mir für einen Import vorstelle: Jeder User muss alle seine Inhalte manuell auf dem Server in die Hauptverzeichnisse verschieben ... ?<br />
<br />
Ansonsten hat der Import prinzipiell für mein Neobiker Postfach funktioniert.<br />
<br />
<pre># gromox-kdb2mt — Utility for analysis/import of Kopano mailboxes<br />
# gromox-mt2exm — Utility for importing various mail items<br />
<br />
mkdir -p /mnt/kopano_attachments<br />
sshfs $kopano_server:/var/lib/kopano/attachments /mnt/kopano_attachments<br />
<br />
# detach (CTRL-A d) the SSH tunnel to SQL server (who only accepts localhost conections)<br />
screen ssh -L 12345:localhost:3306 "root@${kopano_server}"<br />
<br />
# EXAMPLE for user neobiker = neobiker@neobiker.de<br />
# ------------------------------------------------<br />
# ENVIRONMENT variable is used for SQL password<br />
# either EXPORT it or write in one line with cmd<br />
export_mbox="gromox-kdb2mt --sql-host 127.0.0.1 --sql-port 12345 --src-attach /mnt/kopano_attachments"<br />
SQLPASS=$(ssh $kopano_server cat /etc/mysql.secret)<br />
<br />
SQLPASS=$SQLPASS $export_mbox --mbox-mro neobiker | gromox-mt2exm -u neobiker@neobiker.de<br />
<br />
kdb2mt: No ACLs will be extracted.<br />
kdb Server GUID: xxxxxxxxxxxxxxxxxxxxxxxxxxx<br />
Database schema is kdb-118<br />
Store GUID for MRO "neobiker": xxxxxxxxxxxxxxxxxxxxxxxxx<br />
Processing folder "" (7 elements)...<br />
Processing folder "IPM_SUBTREE" (19 elements)...<br />
Processing folder "Posteingang" (791 elements)...<br />
Processing folder "Postausgang" (0 elements)...<br />
Processing folder "Gelöschte Objekte" (1 elements)...<br />
Processing folder "Gesendete Objekte" (1 elements)...<br />
Processing folder "Kontakte" (0 elements)...<br />
Processing folder "Kalender" (1 elements)...<br />
Processing folder "Entwürfe" (0 elements)...<br />
Processing folder "Journal" (0 elements)...<br />
Processing folder "Notizen" (0 elements)...<br />
Processing folder "Aufgaben" (0 elements)...<br />
Processing folder "Junk E-Mail" (44 elements)...<br />
Processing folder "RSS Feeds" (0 elements)...<br />
Processing folder "Konversationseinstellungen" (0 elements)...<br />
Processing folder "Quickstep Einstellungen" (0 elements)...<br />
Processing folder "Vorgeschlagene Kontakte" (0 elements)...<br />
Processing folder "Spambox" (0 elements)...<br />
Processing folder "Junk-E-Mail" (0 elements)...<br />
Processing folder "Gelöschte Elemente" (0 elements)...<br />
Processing folder "Gesendete Elemente" (0 elements)...<br />
Processing folder "IPM_COMMON_VIEWS" (2 elements)...<br />
Processing folder "IPM_VIEWS" (0 elements)...<br />
Processing folder "FINDER_ROOT" (0 elements)...<br />
Processing folder "Verknüpfung" (0 elements)...<br />
Processing folder "Schedule" (0 elements)...<br />
Processing folder "Freebusy Data" (1 elements)...<br />
</pre><br />
<br />
=== Import aller Email Konten ===<br />
<br />
<pre><br />
export_mbox="gromox-kdb2mt --sql-host 127.0.0.1 --sql-port 12345 --src-attach /mnt/kopano_attachments"<br />
SQLPASS=$(ssh $kopano_server cat /etc/mysql.secret)<br />
<br />
# LOOP over all email accounts (except SYSTEM)<br />
# --------------------------------------------<br />
users=$(ssh $kopano_server kopano-admin -l | awk '{print $1}' | tail -n +4 | grep -v SYSTEM)<br />
<br />
for user in ${users}; do<br />
<br />
details=$(ssh $kopano_server kopano-admin --details $user)<br />
email=$(echo "${details}" $user | awk '/Emailaddress:/ {print $2}')<br />
guid=$(echo "${details}" $user | awk '/Store GUID:/ {print $3}')<br />
<br />
SQLPASS=$SQLPASS $export_mbox --mbox-guid $guid | gromox-mt2exm -u $email<br />
<br />
done<br />
<br />
# stop ssh tunnel -> exit ssh on kopano server<br />
screen -r<br />
<br />
# unmount kopano attachments<br />
umount /mnt/kopano_attachments<br />
</pre><br />
<br />
== Datenablage ==<br />
Eventuell möchte ich ein separates Filesystem für die Daten verwenden.<br><br />
Die Daten liegen unter ''/var/lib/mysql*'' und ''/var/lib/grom*'' ?<br />
<br />
<pre>grommunio:/var/lib # ls mysql* grom*<br />
grommunio-admin-api:<br />
auth-private.pem auth-public.pem<br />
<br />
grommunio-antispam:<br />
110ec84de81cc98813b71d34d42dedbc199bd687.map 65b4e39783c10c8ed89dbec4983259f22418f34d.map ea942f35f2c82e84bdb7b8ceb34537f7dbe986eb.map stats.ucl<br />
2368c73b937d98513ed72c4b04f4247bda43fdb5.map 877aa38dd77ed707d20b2f77d46b905b69a6295a.map f3933e67d5f9574a9eaf78cd0a8e99140ecc502e.map<br />
2e66b2415ec9b99aaa155761c211e51def12016e.map 8fe64ecd161a46f91a78ece93609cb863f9f42f2.map rspamd.rrd<br />
3b1eab0b21b32c970b8fae4a9010a539879368e3.map cb5a8189726ac3a8880e1c44ed6220c6d794521b.map rspamd.sock<br />
<br />
grommunio-dav:<br />
<br />
grommunio-files:<br />
apps-external config data sessions tmp<br />
<br />
grommunio-web:<br />
session sqlite-index tmp<br />
<br />
gromox:<br />
domain queue timer.txt user<br />
<br />
mysql:<br />
aria_log.00000001 ddl_recovery.log grommunio ib_buffer_pool ib_logfile0 multi-master.info mysql_upgrade_info sys<br />
aria_log_control grofiles grommunio.pid ibdata1 ibtmp1 mysql performance_schema test<br />
<br />
mysql-files:<br />
</pre><br />
<br />
== Postfix ==<br />
Mail Delivery Agent (MDA) Konfiguration <br />
* zum versenden von Emails (external relay host)<br />
* Mailaddress rewritings (neobiker@local -> neobiker@neobiker.de)<br />
<br />
Vergleich der wichtigsten Konfigurationsparameter<br />
<pre>grep -v ^# /etc/postfix/main.cf | grep [[:alnum:]] | sed -e 's/^\W+//g' | sort > postfix_config_sorted_grommunio<br />
ssh $kopano_server "grep -v ^# /etc/postfix/main.cf | grep [[:alnum:]] | sed -e 's/^\W+//g' | sort" > postfix_config_sorted_kopano<br />
<br />
diff -y postfix_config<br />
...<br />
<br />
# most relevant entries (differs/unconfigured)<br />
masquerade_domains = $mydomain<br />
content_filter = smtp-amavis:[127.0.0.1]:10024<br />
disable_vrfy_command = no<br />
mailbox_size_limit<br />
message_size_limit<br />
relay_domains = $mydestination<br />
relayhost = smtp.mailhoster.de<br />
smtpd_tls_auth_only = yes<br />
smtpd_tls_cert_file = /etc/univention/ssl/ucsmail...<br />
smtpd_use_tls = yes<br />
smtp_sasl_password_maps = hash:/etc/postfix/smtp_auth<br />
smtp_sasl_security_options = noanonymous<br />
<br />
</pre><br />
Vermutlich muss ich mich nur um den externen Relay Host kümmern.<br />
<br />
<br />
=== Mail Relay Server ===<br />
Postfix can use a mail relay server.<br />
<br />
<pre>ssh $kopano_server ucr get mail/relayhost</pre><br />
<br />
<pre># authentification on relay host<br />
/etc/postfix/smtp_auth<br />
# format<br />
# FQDN-Relayhost username:password<br />
<br />
postmap /etc/postfix/smtp_auth<br />
postmap /etc/postfix/tls_policy<br />
service postfix reload<br />
</pre><br />
Das hat funktioniert:<br />
* Email nach EXTERN ging raus.<br />
* Email nach intern -> keine Postfix Aktion (nur intern innerhalb grommunio)<br />
<br />
Doku grommunio: [https://docs.grommunio.com/admin/administration.html#adding-a-file relayhost]<br />
<br />
Eine andere Info dazu [https://andersgood.de/kurz-notiert/smtp-relayhost-in-grommunio-konfigurieren hier].<br />
<br />
=== Mail Address rewritings ===<br />
<br />
Nix zu tun, denke ich.<br />
<br />
== Fetchmail ==<br />
Fetchmail Konfiguration zum abholen der Emails von meinem Email Provider.<br />
Emails werden entweder von UCS '''oder ''' grommunio abgeholt und and Postfix übergeben!<br />
<br />
Auf UCS für jeden Benutzer deaktivieren <br><br />
-> ''Advanced settings ‣ Remote mail retrieval (single)''<br><br />
oder Postfix auf UCS zur weiterleitung an grommunio konfigurieren.<br />
<br />
<pre>systemctl stop fetchmail<br />
systemctl disable fetchmail<br />
ucr set fetchmail/autostart=false<br />
<br />
# fetchmail config and passwords<br />
cat /etc/fetchmailrc<br />
</pre><br />
<br />
== Proxy Konfiguration (Nginx) ==<br />
Ich habe für jede Email Domain einen deSEC (DynDNS) Eintrag auf mein Internetgateway (Nginx auf OPNSense) erstellt.<br />
Dafür verwende ich ein Let's Encrypt Zertifikat, welches die Domains als DNS Alternate Names beinhaltet.<br />
Das Webinterface von grommunio ist unter ''/web'' erreichbar, während in der Übergangszeit Kopano unter ''/webapp'' erreichbar bleibt.<br />
Active Sync für Mobile Devices (URL ist identisch) kann damit immerhin pro Email Domain separat umgestellt werden. Outlook ist bei mir derzeit nur intern erreichbar, also extern nur über VPN (oder halt das Webinterface).</div>
Neobiker
http://neobiker.de/wiki/index.php?title=Kopano_to_grommunio_HowTo&diff=2109
Kopano to grommunio HowTo
2024-01-05T16:58:31Z
<p>Neobiker: /* Mail Address rewritings */</p>
<hr />
<div>= Installation auf Debian 12 =<br />
Eine fertige grommunio Appliance ist auf Basis von S.u.S.E. erstellt und steht u.a. als ISO download zur Verfügung. Da ich derzeit ausschliesslich Debian basierte Systeme verwende, bevorzuge ich eine grommunio Installation auf Debian 12 (bookworm).<br />
<br />
Im grommunio Forum findet sich ein Diskussionsthread für die [https://community.grommunio.com/d/447-debian-11-clean-install-script grommunio Installation auf Debian 12 durch ein Script]. Diese läuft auf einer neu erstellten VM auf Proxmox Server fehlerfrei durch. In einem LXC Container gab es Probleme, u.a. klemmte der POP3 Service.<br />
<br />
Im Anschluss an das Installationsskript habe ich noch folgendes Script unter '''Additions''' - ''Fix Grommunio Admin Live Status page'' ausgeführt, welches ebenfalls funktionierte und die entsprechende Seite im Web Interface reparierte.<br />
<br />
= Konfiguration =<br />
Kopano Core ist bei mir als Applikation auf meinem UCS File- und Mailserver installiert. Die Benutzerkonten und deren Konfiguration werden von UCS verwaltet und stehen per LDAP zur Verfügung.<br />
<br />
<pre># mein UCS-Kopano Server<br />
kopano_server=ucsmail<br />
echo "kopano_server=ucsmail" >> ~/.bashrc<br />
<br />
# ssh connections without prompts<br />
ssh-keygen<br />
ssh-copy-id $kopano_server<br />
<br />
# mount /var/lib/kopano/attachments per sshfs<br />
if [ -e /etc/debian_version ] then<br />
apt install sshfs screen<br />
else<br />
zypper in sshfs # (grommunio app/iso installation)<br />
fi<br />
mkdir -p /mnt/kopano_attachments<br />
sshfs $kopano_server:/var/lib/kopano/attachments /mnt/kopano_attachments<br />
</pre><br />
<br />
== Zertifikate ==<br />
Falls ein '''[https://help.univention.com/t/ssl-zertifikat-und-dns-alias/5780/7 Server Zertifikat von UCS]''' für den '''grommunio''' server erzeugt wurde kann das verwendet werden.<br />
<br />
Ich erstelle hier ein neues Zertifikat für den grommunio Server, der als DNS-Alternativen jeweils '''autodiscover''' (Outlook) und '''autoconfig''' (Android) Einträge für jede meiner Email Domains enthält. Damit erfolgt die Mail-Client Konfiguration automatisch, sofern der Rechner sich in einem '''lokalem''' Netzsegment befindet.<br />
<br />
Background:<br><br />
Meine Emails werden extern gehostet, ich importiere sie per Fetchmail. Daher bin ich nicht in der Lage diese Autodiscover/Autoconf Einträge im (externen) DNS meines Hosters zu erstellen. Die entsprechende '''lokale DNS Auflösung''' übernimmt mein '''Unbound DNS''' Server mittels '''Overwrite''' Einträgen, welche auf den grommunio Server verweisen. Daher müssen diese DNS Alias Namen in dessen Zertifikat vorhanden sein.<br />
<br />
<pre>ssh ucs # UCS Master!<br />
univention-certificate new -name grommunio.domain.de<br />
cd /etc/univention/ssl/grommunio.domain.de<br />
<br />
# add DNS Aliases<br />
vi openssl.cnf<br />
# subjectAltName = DNS:grommunio.domain.de, DNS:grommunio <br />
# add all your email domains autodiscover/autoconfig entries: DNS:autodiscover.email_domain.de, DNS:autoconfig.email_domain.de<br />
<br />
DEFAULT_CRL_DAYS="$(/usr/sbin/univention-config-registry get ssl/crl/validity)"<br />
DEFAULT_DAYS="$(/usr/sbin/univention-config-registry get ssl/default/days)"<br />
DEFAULT_MD="$(/usr/sbin/univention-config-registry get ssl/default/hashfunction)"<br />
DEFAULT_BITS="$(/usr/sbin/univention-config-registry get ssl/default/bits)"<br />
export DEFAULT_MD DEFAULT_BITS DEFAULT_CRL_DAYS DEFAULT_DAYS<br />
openssl req -new -config openssl.cnf -key private.key -out req.pem<br />
<br />
univention-certificate renew -name grommunio.domain.de -days $DEFAULT_DAYS<br />
</pre><br />
<br />
Damit funktioniert die Anmeldung am Webinterface auch per LDAP mit starttls, ansonsten nur LDAP ohne starttls oder halt mit SQL Accounts.<br />
<pre><br />
cd /etc/grommunio-common/ssl<br />
scp $kopano_server:/etc/univention/ssl/grommunio/cert.pem server-bundle.pem<br />
scp $kopano_server:/etc/univention/ssl/grommunio/private.key server.key<br />
chown gromox:gromox server*<br />
chmod 660 server*<br />
<br />
# Root CA<br />
if [ -e /etc/debian_version ] then<br />
scp $kopano_server:/etc/univention/ssl/ucsCA/CAcert.pem /usr/local/share/ca-certificates/my-custom-ca/<br />
else<br />
# SuSE appliance location<br />
scp $kopano_server:/etc/univention/ssl/ucsCA/CAcert.pem /etc/pki/trust/anchors/<br />
fi<br />
update-ca-certificates<br />
<br />
systemctl restart nginx<br />
<br />
# autodiscover Eintrag testen<br />
PASS='email_passwort' gromox-dscli -ve user@domainname<br />
</pre><br />
<br />
== LDAP ==<br />
Die LDAP Konfiguration kann mit dem UCS Template im Webinterface vorgenommen werden.<br />
Ich übernehme einige Kopano Werte:<br />
<pre><br />
ssh $kopano_server grep -e ^ldap_uri -e ^ldap_bind -e ^ldap_search_base -e ^ldap_user_search -e ^ldap_group_search /etc/kopano/ldap.cfg<br />
<br />
ldap_uri = ldap://ucsmail.domain.de:7389/<br />
ldap_bind_user = cn=ucsmail,cn=dc,cn=computers,dc=domain,dc=de<br />
ldap_bind_passwd = xxxxxxxxxxxxxx<br />
ldap_search_base = dc=domain,dc=de<br />
ldap_user_search_filter = (kopanoAccount=1)<br />
ldap_group_search_filter = (&(kopanoAccount=1)(objectClass=kopano-group))<br />
<br />
# configure LDAP accordingly<br />
grommunio-admin ldap configure<br />
<br />
# test: list users<br />
grommunio-admin ldap search<br />
ID Type E-Mail Name<br />
2222222222-11-10111-22222-3333333333 user user@domainname User Name<br />
</pre><br />
<br />
== Email Domains und User ==<br />
Debian: das Anlegen einer Domain im Web-Interface schlägt direkt fehl - "Bad Request".<br><br />
Nicht schlimm, aber vielleicht nehme ich doch lieber die fertige Appliance auf Basis von S.u.S.E. ?<br />
<br />
Per Kommandozeile funktioniert es jedenfalls.<br />
<pre><br />
domains=$(ssh $kopano_server ucr get mail/hosteddomains)<br />
users=$(grommunio-admin ldap search)<br />
<br />
# create email domains<br />
for domain in $domains; do<br />
u_cnt=$(echo "$users" | grep -c $domain)<br />
[ $u_cnt -gt 0 ] && grommunio-admin domain create -u $u_cnt $domain<br />
done<br />
<br />
# import and sync LDAP users<br />
grommunio-admin ldap downsync -c<br />
</pre><br />
Der Import der User funktioniert jedenfalls auch über das Webinterface.<br />
<br />
=== Export - Import via Outlook .pst ===<br />
Da ich nur 5 Konten habe, ist diese Methode auch nicht ganz abwegig:<br />
<br />
<pre>gromox-pff2mt /tmp/neobiker_outlook.pst | gromox-mt2exm -u neobiker@neobiker.de<br />
</pre><br />
<br />
=== Import von Emails aus Kopano ===<br />
Der Import landet (leider) in einem separatem Verzeichnis:<br />
[[Datei:Grommunio Import.jpg|mini]]<br />
<br />
Das ist nicht ganz das was ich mir für einen Import vorstelle: Jeder User muss alle seine Inhalte manuell auf dem Server in die Hauptverzeichnisse verschieben ... ?<br />
<br />
Ansonsten hat der Import prinzipiell für mein Neobiker Postfach funktioniert.<br />
<br />
<pre># gromox-kdb2mt — Utility for analysis/import of Kopano mailboxes<br />
# gromox-mt2exm — Utility for importing various mail items<br />
<br />
mkdir -p /mnt/kopano_attachments<br />
sshfs $kopano_server:/var/lib/kopano/attachments /mnt/kopano_attachments<br />
<br />
# detach (CTRL-A d) the SSH tunnel to SQL server (who only accepts localhost conections)<br />
screen ssh -L 12345:localhost:3306 "root@${kopano_server}"<br />
<br />
# EXAMPLE for user neobiker = neobiker@neobiker.de<br />
# ------------------------------------------------<br />
# ENVIRONMENT variable is used for SQL password<br />
# either EXPORT it or write in one line with cmd<br />
export_mbox="gromox-kdb2mt --sql-host 127.0.0.1 --sql-port 12345 --src-attach /mnt/kopano_attachments"<br />
SQLPASS=$(ssh $kopano_server cat /etc/mysql.secret)<br />
<br />
SQLPASS=$SQLPASS $export_mbox --mbox-mro neobiker | gromox-mt2exm -u neobiker@neobiker.de<br />
<br />
kdb2mt: No ACLs will be extracted.<br />
kdb Server GUID: xxxxxxxxxxxxxxxxxxxxxxxxxxx<br />
Database schema is kdb-118<br />
Store GUID for MRO "neobiker": xxxxxxxxxxxxxxxxxxxxxxxxx<br />
Processing folder "" (7 elements)...<br />
Processing folder "IPM_SUBTREE" (19 elements)...<br />
Processing folder "Posteingang" (791 elements)...<br />
Processing folder "Postausgang" (0 elements)...<br />
Processing folder "Gelöschte Objekte" (1 elements)...<br />
Processing folder "Gesendete Objekte" (1 elements)...<br />
Processing folder "Kontakte" (0 elements)...<br />
Processing folder "Kalender" (1 elements)...<br />
Processing folder "Entwürfe" (0 elements)...<br />
Processing folder "Journal" (0 elements)...<br />
Processing folder "Notizen" (0 elements)...<br />
Processing folder "Aufgaben" (0 elements)...<br />
Processing folder "Junk E-Mail" (44 elements)...<br />
Processing folder "RSS Feeds" (0 elements)...<br />
Processing folder "Konversationseinstellungen" (0 elements)...<br />
Processing folder "Quickstep Einstellungen" (0 elements)...<br />
Processing folder "Vorgeschlagene Kontakte" (0 elements)...<br />
Processing folder "Spambox" (0 elements)...<br />
Processing folder "Junk-E-Mail" (0 elements)...<br />
Processing folder "Gelöschte Elemente" (0 elements)...<br />
Processing folder "Gesendete Elemente" (0 elements)...<br />
Processing folder "IPM_COMMON_VIEWS" (2 elements)...<br />
Processing folder "IPM_VIEWS" (0 elements)...<br />
Processing folder "FINDER_ROOT" (0 elements)...<br />
Processing folder "Verknüpfung" (0 elements)...<br />
Processing folder "Schedule" (0 elements)...<br />
Processing folder "Freebusy Data" (1 elements)...<br />
</pre><br />
<br />
=== Import aller Email Konten ===<br />
<br />
<pre><br />
export_mbox="gromox-kdb2mt --sql-host 127.0.0.1 --sql-port 12345 --src-attach /mnt/kopano_attachments"<br />
SQLPASS=$(ssh $kopano_server cat /etc/mysql.secret)<br />
<br />
# LOOP over all email accounts (except SYSTEM)<br />
# --------------------------------------------<br />
users=$(ssh $kopano_server kopano-admin -l | awk '{print $1}' | tail -n +4 | grep -v SYSTEM)<br />
<br />
for user in ${users}; do<br />
<br />
details=$(ssh $kopano_server kopano-admin --details $user)<br />
email=$(echo "${details}" $user | awk '/Emailaddress:/ {print $2}')<br />
guid=$(echo "${details}" $user | awk '/Store GUID:/ {print $3}')<br />
<br />
SQLPASS=$SQLPASS $export_mbox --mbox-guid $guid | gromox-mt2exm -u $email<br />
<br />
done<br />
<br />
# stop ssh tunnel -> exit ssh on kopano server<br />
screen -r<br />
<br />
# unmount kopano attachments<br />
umount /mnt/kopano_attachments<br />
</pre><br />
<br />
== Datenablage ==<br />
Eventuell möchte ich ein separates Filesystem für die Daten verwenden.<br><br />
Die Daten liegen unter ''/var/lib/mysql*'' und ''/var/lib/grom*'' ?<br />
<br />
<pre>grommunio:/var/lib # ls mysql* grom*<br />
grommunio-admin-api:<br />
auth-private.pem auth-public.pem<br />
<br />
grommunio-antispam:<br />
110ec84de81cc98813b71d34d42dedbc199bd687.map 65b4e39783c10c8ed89dbec4983259f22418f34d.map ea942f35f2c82e84bdb7b8ceb34537f7dbe986eb.map stats.ucl<br />
2368c73b937d98513ed72c4b04f4247bda43fdb5.map 877aa38dd77ed707d20b2f77d46b905b69a6295a.map f3933e67d5f9574a9eaf78cd0a8e99140ecc502e.map<br />
2e66b2415ec9b99aaa155761c211e51def12016e.map 8fe64ecd161a46f91a78ece93609cb863f9f42f2.map rspamd.rrd<br />
3b1eab0b21b32c970b8fae4a9010a539879368e3.map cb5a8189726ac3a8880e1c44ed6220c6d794521b.map rspamd.sock<br />
<br />
grommunio-dav:<br />
<br />
grommunio-files:<br />
apps-external config data sessions tmp<br />
<br />
grommunio-web:<br />
session sqlite-index tmp<br />
<br />
gromox:<br />
domain queue timer.txt user<br />
<br />
mysql:<br />
aria_log.00000001 ddl_recovery.log grommunio ib_buffer_pool ib_logfile0 multi-master.info mysql_upgrade_info sys<br />
aria_log_control grofiles grommunio.pid ibdata1 ibtmp1 mysql performance_schema test<br />
<br />
mysql-files:<br />
</pre><br />
<br />
== Postfix ==<br />
Mail Delivery Agent (MDA) Konfiguration <br />
* zum versenden von Emails (external relay host)<br />
* Mailaddress rewritings (neobiker@local -> neobiker@neobiker.de)<br />
<br />
Vergleich der wichtigsten Konfigurationsparameter<br />
<pre>grep -v ^# /etc/postfix/main.cf | grep [[:alnum:]] | sed -e 's/^\W+//g' | sort > postfix_config_sorted_grommunio<br />
ssh $kopano_server "grep -v ^# /etc/postfix/main.cf | grep [[:alnum:]] | sed -e 's/^\W+//g' | sort" > postfix_config_sorted_kopano<br />
<br />
diff -y postfix_config<br />
...<br />
<br />
# most relevant entries (differs/unconfigured)<br />
masquerade_domains = $mydomain<br />
content_filter = smtp-amavis:[127.0.0.1]:10024<br />
disable_vrfy_command = no<br />
mailbox_size_limit<br />
message_size_limit<br />
relay_domains = $mydestination<br />
relayhost = smtp.mailhoster.de<br />
smtpd_tls_auth_only = yes<br />
smtpd_tls_cert_file = /etc/univention/ssl/ucsmail...<br />
smtpd_use_tls = yes<br />
smtp_sasl_password_maps = hash:/etc/postfix/smtp_auth<br />
smtp_sasl_security_options = noanonymous<br />
<br />
</pre><br />
Vermutlich muss ich mich nur um den externen Relay Host kümmern.<br />
<br />
<br />
=== Mail Relay Server ===<br />
Postfix can use a mail relay server.<br />
<br />
<pre>ssh $kopano_server ucr get mail/relayhost</pre><br />
<br />
<pre># authentification on relay host<br />
/etc/postfix/smtp_auth<br />
# format<br />
# FQDN-Relayhost username:password<br />
<br />
postmap /etc/postfix/smtp_auth<br />
postmap /etc/postfix/tls_policy<br />
service postfix reload<br />
</pre><br />
<br />
Doku grommunio: [https://docs.grommunio.com/admin/administration.html#adding-a-file relayhost]<br />
<br />
Eine andere Info dazu [https://andersgood.de/kurz-notiert/smtp-relayhost-in-grommunio-konfigurieren hier].<br />
Das hat funktioniert:<br />
* Email nach EXTERN ging raus.<br />
* Email nach intern -> noch zu klären (masquerade_domains)<br />
<br />
=== Mail Address rewritings ===<br />
<br />
Nix zu tun, denke ich.<br />
<br />
== Fetchmail ==<br />
Fetchmail Konfiguration zum abholen der Emails von meinem Email Provider.<br />
Emails werden entweder von UCS '''oder ''' grommunio abgeholt und and Postfix übergeben!<br />
<br />
Auf UCS für jeden Benutzer deaktivieren <br><br />
-> ''Advanced settings ‣ Remote mail retrieval (single)''<br><br />
oder Postfix auf UCS zur weiterleitung an grommunio konfigurieren.<br />
<br />
<pre>systemctl stop fetchmail<br />
systemctl disable fetchmail<br />
ucr set fetchmail/autostart=false<br />
<br />
# fetchmail config and passwords<br />
cat /etc/fetchmailrc<br />
</pre><br />
<br />
== Proxy Konfiguration (Nginx) ==<br />
Ich habe für jede Email Domain einen deSEC (DynDNS) Eintrag auf mein Internetgateway (Nginx auf OPNSense) erstellt.<br />
Dafür verwende ich ein Let's Encrypt Zertifikat, welches die Domains als DNS Alternate Names beinhaltet.<br />
Das Webinterface von grommunio ist unter ''/web'' erreichbar, während in der Übergangszeit Kopano unter ''/webapp'' erreichbar bleibt.<br />
Active Sync für Mobile Devices (URL ist identisch) kann damit immerhin pro Email Domain separat umgestellt werden. Outlook ist bei mir derzeit nur intern erreichbar, also extern nur über VPN (oder halt das Webinterface).</div>
Neobiker
http://neobiker.de/wiki/index.php?title=Kopano_to_grommunio_HowTo&diff=2108
Kopano to grommunio HowTo
2024-01-05T16:43:45Z
<p>Neobiker: /* Mail Relay Server */</p>
<hr />
<div>= Installation auf Debian 12 =<br />
Eine fertige grommunio Appliance ist auf Basis von S.u.S.E. erstellt und steht u.a. als ISO download zur Verfügung. Da ich derzeit ausschliesslich Debian basierte Systeme verwende, bevorzuge ich eine grommunio Installation auf Debian 12 (bookworm).<br />
<br />
Im grommunio Forum findet sich ein Diskussionsthread für die [https://community.grommunio.com/d/447-debian-11-clean-install-script grommunio Installation auf Debian 12 durch ein Script]. Diese läuft auf einer neu erstellten VM auf Proxmox Server fehlerfrei durch. In einem LXC Container gab es Probleme, u.a. klemmte der POP3 Service.<br />
<br />
Im Anschluss an das Installationsskript habe ich noch folgendes Script unter '''Additions''' - ''Fix Grommunio Admin Live Status page'' ausgeführt, welches ebenfalls funktionierte und die entsprechende Seite im Web Interface reparierte.<br />
<br />
= Konfiguration =<br />
Kopano Core ist bei mir als Applikation auf meinem UCS File- und Mailserver installiert. Die Benutzerkonten und deren Konfiguration werden von UCS verwaltet und stehen per LDAP zur Verfügung.<br />
<br />
<pre># mein UCS-Kopano Server<br />
kopano_server=ucsmail<br />
echo "kopano_server=ucsmail" >> ~/.bashrc<br />
<br />
# ssh connections without prompts<br />
ssh-keygen<br />
ssh-copy-id $kopano_server<br />
<br />
# mount /var/lib/kopano/attachments per sshfs<br />
if [ -e /etc/debian_version ] then<br />
apt install sshfs screen<br />
else<br />
zypper in sshfs # (grommunio app/iso installation)<br />
fi<br />
mkdir -p /mnt/kopano_attachments<br />
sshfs $kopano_server:/var/lib/kopano/attachments /mnt/kopano_attachments<br />
</pre><br />
<br />
== Zertifikate ==<br />
Falls ein '''[https://help.univention.com/t/ssl-zertifikat-und-dns-alias/5780/7 Server Zertifikat von UCS]''' für den '''grommunio''' server erzeugt wurde kann das verwendet werden.<br />
<br />
Ich erstelle hier ein neues Zertifikat für den grommunio Server, der als DNS-Alternativen jeweils '''autodiscover''' (Outlook) und '''autoconfig''' (Android) Einträge für jede meiner Email Domains enthält. Damit erfolgt die Mail-Client Konfiguration automatisch, sofern der Rechner sich in einem '''lokalem''' Netzsegment befindet.<br />
<br />
Background:<br><br />
Meine Emails werden extern gehostet, ich importiere sie per Fetchmail. Daher bin ich nicht in der Lage diese Autodiscover/Autoconf Einträge im (externen) DNS meines Hosters zu erstellen. Die entsprechende '''lokale DNS Auflösung''' übernimmt mein '''Unbound DNS''' Server mittels '''Overwrite''' Einträgen, welche auf den grommunio Server verweisen. Daher müssen diese DNS Alias Namen in dessen Zertifikat vorhanden sein.<br />
<br />
<pre>ssh ucs # UCS Master!<br />
univention-certificate new -name grommunio.domain.de<br />
cd /etc/univention/ssl/grommunio.domain.de<br />
<br />
# add DNS Aliases<br />
vi openssl.cnf<br />
# subjectAltName = DNS:grommunio.domain.de, DNS:grommunio <br />
# add all your email domains autodiscover/autoconfig entries: DNS:autodiscover.email_domain.de, DNS:autoconfig.email_domain.de<br />
<br />
DEFAULT_CRL_DAYS="$(/usr/sbin/univention-config-registry get ssl/crl/validity)"<br />
DEFAULT_DAYS="$(/usr/sbin/univention-config-registry get ssl/default/days)"<br />
DEFAULT_MD="$(/usr/sbin/univention-config-registry get ssl/default/hashfunction)"<br />
DEFAULT_BITS="$(/usr/sbin/univention-config-registry get ssl/default/bits)"<br />
export DEFAULT_MD DEFAULT_BITS DEFAULT_CRL_DAYS DEFAULT_DAYS<br />
openssl req -new -config openssl.cnf -key private.key -out req.pem<br />
<br />
univention-certificate renew -name grommunio.domain.de -days $DEFAULT_DAYS<br />
</pre><br />
<br />
Damit funktioniert die Anmeldung am Webinterface auch per LDAP mit starttls, ansonsten nur LDAP ohne starttls oder halt mit SQL Accounts.<br />
<pre><br />
cd /etc/grommunio-common/ssl<br />
scp $kopano_server:/etc/univention/ssl/grommunio/cert.pem server-bundle.pem<br />
scp $kopano_server:/etc/univention/ssl/grommunio/private.key server.key<br />
chown gromox:gromox server*<br />
chmod 660 server*<br />
<br />
# Root CA<br />
if [ -e /etc/debian_version ] then<br />
scp $kopano_server:/etc/univention/ssl/ucsCA/CAcert.pem /usr/local/share/ca-certificates/my-custom-ca/<br />
else<br />
# SuSE appliance location<br />
scp $kopano_server:/etc/univention/ssl/ucsCA/CAcert.pem /etc/pki/trust/anchors/<br />
fi<br />
update-ca-certificates<br />
<br />
systemctl restart nginx<br />
<br />
# autodiscover Eintrag testen<br />
PASS='email_passwort' gromox-dscli -ve user@domainname<br />
</pre><br />
<br />
== LDAP ==<br />
Die LDAP Konfiguration kann mit dem UCS Template im Webinterface vorgenommen werden.<br />
Ich übernehme einige Kopano Werte:<br />
<pre><br />
ssh $kopano_server grep -e ^ldap_uri -e ^ldap_bind -e ^ldap_search_base -e ^ldap_user_search -e ^ldap_group_search /etc/kopano/ldap.cfg<br />
<br />
ldap_uri = ldap://ucsmail.domain.de:7389/<br />
ldap_bind_user = cn=ucsmail,cn=dc,cn=computers,dc=domain,dc=de<br />
ldap_bind_passwd = xxxxxxxxxxxxxx<br />
ldap_search_base = dc=domain,dc=de<br />
ldap_user_search_filter = (kopanoAccount=1)<br />
ldap_group_search_filter = (&(kopanoAccount=1)(objectClass=kopano-group))<br />
<br />
# configure LDAP accordingly<br />
grommunio-admin ldap configure<br />
<br />
# test: list users<br />
grommunio-admin ldap search<br />
ID Type E-Mail Name<br />
2222222222-11-10111-22222-3333333333 user user@domainname User Name<br />
</pre><br />
<br />
== Email Domains und User ==<br />
Debian: das Anlegen einer Domain im Web-Interface schlägt direkt fehl - "Bad Request".<br><br />
Nicht schlimm, aber vielleicht nehme ich doch lieber die fertige Appliance auf Basis von S.u.S.E. ?<br />
<br />
Per Kommandozeile funktioniert es jedenfalls.<br />
<pre><br />
domains=$(ssh $kopano_server ucr get mail/hosteddomains)<br />
users=$(grommunio-admin ldap search)<br />
<br />
# create email domains<br />
for domain in $domains; do<br />
u_cnt=$(echo "$users" | grep -c $domain)<br />
[ $u_cnt -gt 0 ] && grommunio-admin domain create -u $u_cnt $domain<br />
done<br />
<br />
# import and sync LDAP users<br />
grommunio-admin ldap downsync -c<br />
</pre><br />
Der Import der User funktioniert jedenfalls auch über das Webinterface.<br />
<br />
=== Export - Import via Outlook .pst ===<br />
Da ich nur 5 Konten habe, ist diese Methode auch nicht ganz abwegig:<br />
<br />
<pre>gromox-pff2mt /tmp/neobiker_outlook.pst | gromox-mt2exm -u neobiker@neobiker.de<br />
</pre><br />
<br />
=== Import von Emails aus Kopano ===<br />
Der Import landet (leider) in einem separatem Verzeichnis:<br />
[[Datei:Grommunio Import.jpg|mini]]<br />
<br />
Das ist nicht ganz das was ich mir für einen Import vorstelle: Jeder User muss alle seine Inhalte manuell auf dem Server in die Hauptverzeichnisse verschieben ... ?<br />
<br />
Ansonsten hat der Import prinzipiell für mein Neobiker Postfach funktioniert.<br />
<br />
<pre># gromox-kdb2mt — Utility for analysis/import of Kopano mailboxes<br />
# gromox-mt2exm — Utility for importing various mail items<br />
<br />
mkdir -p /mnt/kopano_attachments<br />
sshfs $kopano_server:/var/lib/kopano/attachments /mnt/kopano_attachments<br />
<br />
# detach (CTRL-A d) the SSH tunnel to SQL server (who only accepts localhost conections)<br />
screen ssh -L 12345:localhost:3306 "root@${kopano_server}"<br />
<br />
# EXAMPLE for user neobiker = neobiker@neobiker.de<br />
# ------------------------------------------------<br />
# ENVIRONMENT variable is used for SQL password<br />
# either EXPORT it or write in one line with cmd<br />
export_mbox="gromox-kdb2mt --sql-host 127.0.0.1 --sql-port 12345 --src-attach /mnt/kopano_attachments"<br />
SQLPASS=$(ssh $kopano_server cat /etc/mysql.secret)<br />
<br />
SQLPASS=$SQLPASS $export_mbox --mbox-mro neobiker | gromox-mt2exm -u neobiker@neobiker.de<br />
<br />
kdb2mt: No ACLs will be extracted.<br />
kdb Server GUID: xxxxxxxxxxxxxxxxxxxxxxxxxxx<br />
Database schema is kdb-118<br />
Store GUID for MRO "neobiker": xxxxxxxxxxxxxxxxxxxxxxxxx<br />
Processing folder "" (7 elements)...<br />
Processing folder "IPM_SUBTREE" (19 elements)...<br />
Processing folder "Posteingang" (791 elements)...<br />
Processing folder "Postausgang" (0 elements)...<br />
Processing folder "Gelöschte Objekte" (1 elements)...<br />
Processing folder "Gesendete Objekte" (1 elements)...<br />
Processing folder "Kontakte" (0 elements)...<br />
Processing folder "Kalender" (1 elements)...<br />
Processing folder "Entwürfe" (0 elements)...<br />
Processing folder "Journal" (0 elements)...<br />
Processing folder "Notizen" (0 elements)...<br />
Processing folder "Aufgaben" (0 elements)...<br />
Processing folder "Junk E-Mail" (44 elements)...<br />
Processing folder "RSS Feeds" (0 elements)...<br />
Processing folder "Konversationseinstellungen" (0 elements)...<br />
Processing folder "Quickstep Einstellungen" (0 elements)...<br />
Processing folder "Vorgeschlagene Kontakte" (0 elements)...<br />
Processing folder "Spambox" (0 elements)...<br />
Processing folder "Junk-E-Mail" (0 elements)...<br />
Processing folder "Gelöschte Elemente" (0 elements)...<br />
Processing folder "Gesendete Elemente" (0 elements)...<br />
Processing folder "IPM_COMMON_VIEWS" (2 elements)...<br />
Processing folder "IPM_VIEWS" (0 elements)...<br />
Processing folder "FINDER_ROOT" (0 elements)...<br />
Processing folder "Verknüpfung" (0 elements)...<br />
Processing folder "Schedule" (0 elements)...<br />
Processing folder "Freebusy Data" (1 elements)...<br />
</pre><br />
<br />
=== Import aller Email Konten ===<br />
<br />
<pre><br />
export_mbox="gromox-kdb2mt --sql-host 127.0.0.1 --sql-port 12345 --src-attach /mnt/kopano_attachments"<br />
SQLPASS=$(ssh $kopano_server cat /etc/mysql.secret)<br />
<br />
# LOOP over all email accounts (except SYSTEM)<br />
# --------------------------------------------<br />
users=$(ssh $kopano_server kopano-admin -l | awk '{print $1}' | tail -n +4 | grep -v SYSTEM)<br />
<br />
for user in ${users}; do<br />
<br />
details=$(ssh $kopano_server kopano-admin --details $user)<br />
email=$(echo "${details}" $user | awk '/Emailaddress:/ {print $2}')<br />
guid=$(echo "${details}" $user | awk '/Store GUID:/ {print $3}')<br />
<br />
SQLPASS=$SQLPASS $export_mbox --mbox-guid $guid | gromox-mt2exm -u $email<br />
<br />
done<br />
<br />
# stop ssh tunnel -> exit ssh on kopano server<br />
screen -r<br />
<br />
# unmount kopano attachments<br />
umount /mnt/kopano_attachments<br />
</pre><br />
<br />
== Datenablage ==<br />
Eventuell möchte ich ein separates Filesystem für die Daten verwenden.<br><br />
Die Daten liegen unter ''/var/lib/mysql*'' und ''/var/lib/grom*'' ?<br />
<br />
<pre>grommunio:/var/lib # ls mysql* grom*<br />
grommunio-admin-api:<br />
auth-private.pem auth-public.pem<br />
<br />
grommunio-antispam:<br />
110ec84de81cc98813b71d34d42dedbc199bd687.map 65b4e39783c10c8ed89dbec4983259f22418f34d.map ea942f35f2c82e84bdb7b8ceb34537f7dbe986eb.map stats.ucl<br />
2368c73b937d98513ed72c4b04f4247bda43fdb5.map 877aa38dd77ed707d20b2f77d46b905b69a6295a.map f3933e67d5f9574a9eaf78cd0a8e99140ecc502e.map<br />
2e66b2415ec9b99aaa155761c211e51def12016e.map 8fe64ecd161a46f91a78ece93609cb863f9f42f2.map rspamd.rrd<br />
3b1eab0b21b32c970b8fae4a9010a539879368e3.map cb5a8189726ac3a8880e1c44ed6220c6d794521b.map rspamd.sock<br />
<br />
grommunio-dav:<br />
<br />
grommunio-files:<br />
apps-external config data sessions tmp<br />
<br />
grommunio-web:<br />
session sqlite-index tmp<br />
<br />
gromox:<br />
domain queue timer.txt user<br />
<br />
mysql:<br />
aria_log.00000001 ddl_recovery.log grommunio ib_buffer_pool ib_logfile0 multi-master.info mysql_upgrade_info sys<br />
aria_log_control grofiles grommunio.pid ibdata1 ibtmp1 mysql performance_schema test<br />
<br />
mysql-files:<br />
</pre><br />
<br />
== Postfix ==<br />
Mail Delivery Agent (MDA) Konfiguration <br />
* zum versenden von Emails (external relay host)<br />
* Mailaddress rewritings (neobiker@local -> neobiker@neobiker.de)<br />
<br />
Vergleich der wichtigsten Konfigurationsparameter<br />
<pre>grep -v ^# /etc/postfix/main.cf | grep [[:alnum:]] | sed -e 's/^\W+//g' | sort > postfix_config_sorted_grommunio<br />
ssh $kopano_server "grep -v ^# /etc/postfix/main.cf | grep [[:alnum:]] | sed -e 's/^\W+//g' | sort" > postfix_config_sorted_kopano<br />
<br />
diff -y postfix_config<br />
...<br />
<br />
# most relevant entries (differs/unconfigured)<br />
masquerade_domains = $mydomain<br />
content_filter = smtp-amavis:[127.0.0.1]:10024<br />
disable_vrfy_command = no<br />
mailbox_size_limit<br />
message_size_limit<br />
relay_domains = $mydestination<br />
relayhost = smtp.mailhoster.de<br />
smtpd_tls_auth_only = yes<br />
smtpd_tls_cert_file = /etc/univention/ssl/ucsmail...<br />
smtpd_use_tls = yes<br />
smtp_sasl_password_maps = hash:/etc/postfix/smtp_auth<br />
smtp_sasl_security_options = noanonymous<br />
<br />
</pre><br />
Vermutlich muss ich mich nur um den externen Relay Host kümmern.<br />
<br />
<br />
=== Mail Relay Server ===<br />
Postfix can use a mail relay server.<br />
<br />
<pre>ssh $kopano_server ucr get mail/relayhost</pre><br />
<br />
<pre># authentification on relay host<br />
/etc/postfix/smtp_auth<br />
# format<br />
# FQDN-Relayhost username:password<br />
<br />
postmap /etc/postfix/smtp_auth<br />
postmap /etc/postfix/tls_policy<br />
service postfix reload<br />
</pre><br />
<br />
Doku grommunio: [https://docs.grommunio.com/admin/administration.html#adding-a-file relayhost]<br />
<br />
Eine andere Info dazu [https://andersgood.de/kurz-notiert/smtp-relayhost-in-grommunio-konfigurieren hier].<br />
Das hat funktioniert:<br />
* Email nach EXTERN ging raus.<br />
* Email nach intern -> noch zu klären (masquerade_domains)<br />
<br />
=== Mail Address rewritings ===<br />
<br />
to be done<br />
<pre>domains=$(ssh ucsmail ucr get mail/hosteddomains)<br />
</pre><br />
<br />
== Fetchmail ==<br />
Fetchmail Konfiguration zum abholen der Emails von meinem Email Provider.<br />
Emails werden entweder von UCS '''oder ''' grommunio abgeholt und and Postfix übergeben!<br />
<br />
Auf UCS für jeden Benutzer deaktivieren <br><br />
-> ''Advanced settings ‣ Remote mail retrieval (single)''<br><br />
oder Postfix auf UCS zur weiterleitung an grommunio konfigurieren.<br />
<br />
<pre>systemctl stop fetchmail<br />
systemctl disable fetchmail<br />
ucr set fetchmail/autostart=false<br />
<br />
# fetchmail config and passwords<br />
cat /etc/fetchmailrc<br />
</pre><br />
<br />
== Proxy Konfiguration (Nginx) ==<br />
Ich habe für jede Email Domain einen deSEC (DynDNS) Eintrag auf mein Internetgateway (Nginx auf OPNSense) erstellt.<br />
Dafür verwende ich ein Let's Encrypt Zertifikat, welches die Domains als DNS Alternate Names beinhaltet.<br />
Das Webinterface von grommunio ist unter ''/web'' erreichbar, während in der Übergangszeit Kopano unter ''/webapp'' erreichbar bleibt.<br />
Active Sync für Mobile Devices (URL ist identisch) kann damit immerhin pro Email Domain separat umgestellt werden. Outlook ist bei mir derzeit nur intern erreichbar, also extern nur über VPN (oder halt das Webinterface).</div>
Neobiker
http://neobiker.de/wiki/index.php?title=Kopano_to_grommunio_HowTo&diff=2107
Kopano to grommunio HowTo
2024-01-05T16:42:57Z
<p>Neobiker: /* Mail Relay Server */</p>
<hr />
<div>= Installation auf Debian 12 =<br />
Eine fertige grommunio Appliance ist auf Basis von S.u.S.E. erstellt und steht u.a. als ISO download zur Verfügung. Da ich derzeit ausschliesslich Debian basierte Systeme verwende, bevorzuge ich eine grommunio Installation auf Debian 12 (bookworm).<br />
<br />
Im grommunio Forum findet sich ein Diskussionsthread für die [https://community.grommunio.com/d/447-debian-11-clean-install-script grommunio Installation auf Debian 12 durch ein Script]. Diese läuft auf einer neu erstellten VM auf Proxmox Server fehlerfrei durch. In einem LXC Container gab es Probleme, u.a. klemmte der POP3 Service.<br />
<br />
Im Anschluss an das Installationsskript habe ich noch folgendes Script unter '''Additions''' - ''Fix Grommunio Admin Live Status page'' ausgeführt, welches ebenfalls funktionierte und die entsprechende Seite im Web Interface reparierte.<br />
<br />
= Konfiguration =<br />
Kopano Core ist bei mir als Applikation auf meinem UCS File- und Mailserver installiert. Die Benutzerkonten und deren Konfiguration werden von UCS verwaltet und stehen per LDAP zur Verfügung.<br />
<br />
<pre># mein UCS-Kopano Server<br />
kopano_server=ucsmail<br />
echo "kopano_server=ucsmail" >> ~/.bashrc<br />
<br />
# ssh connections without prompts<br />
ssh-keygen<br />
ssh-copy-id $kopano_server<br />
<br />
# mount /var/lib/kopano/attachments per sshfs<br />
if [ -e /etc/debian_version ] then<br />
apt install sshfs screen<br />
else<br />
zypper in sshfs # (grommunio app/iso installation)<br />
fi<br />
mkdir -p /mnt/kopano_attachments<br />
sshfs $kopano_server:/var/lib/kopano/attachments /mnt/kopano_attachments<br />
</pre><br />
<br />
== Zertifikate ==<br />
Falls ein '''[https://help.univention.com/t/ssl-zertifikat-und-dns-alias/5780/7 Server Zertifikat von UCS]''' für den '''grommunio''' server erzeugt wurde kann das verwendet werden.<br />
<br />
Ich erstelle hier ein neues Zertifikat für den grommunio Server, der als DNS-Alternativen jeweils '''autodiscover''' (Outlook) und '''autoconfig''' (Android) Einträge für jede meiner Email Domains enthält. Damit erfolgt die Mail-Client Konfiguration automatisch, sofern der Rechner sich in einem '''lokalem''' Netzsegment befindet.<br />
<br />
Background:<br><br />
Meine Emails werden extern gehostet, ich importiere sie per Fetchmail. Daher bin ich nicht in der Lage diese Autodiscover/Autoconf Einträge im (externen) DNS meines Hosters zu erstellen. Die entsprechende '''lokale DNS Auflösung''' übernimmt mein '''Unbound DNS''' Server mittels '''Overwrite''' Einträgen, welche auf den grommunio Server verweisen. Daher müssen diese DNS Alias Namen in dessen Zertifikat vorhanden sein.<br />
<br />
<pre>ssh ucs # UCS Master!<br />
univention-certificate new -name grommunio.domain.de<br />
cd /etc/univention/ssl/grommunio.domain.de<br />
<br />
# add DNS Aliases<br />
vi openssl.cnf<br />
# subjectAltName = DNS:grommunio.domain.de, DNS:grommunio <br />
# add all your email domains autodiscover/autoconfig entries: DNS:autodiscover.email_domain.de, DNS:autoconfig.email_domain.de<br />
<br />
DEFAULT_CRL_DAYS="$(/usr/sbin/univention-config-registry get ssl/crl/validity)"<br />
DEFAULT_DAYS="$(/usr/sbin/univention-config-registry get ssl/default/days)"<br />
DEFAULT_MD="$(/usr/sbin/univention-config-registry get ssl/default/hashfunction)"<br />
DEFAULT_BITS="$(/usr/sbin/univention-config-registry get ssl/default/bits)"<br />
export DEFAULT_MD DEFAULT_BITS DEFAULT_CRL_DAYS DEFAULT_DAYS<br />
openssl req -new -config openssl.cnf -key private.key -out req.pem<br />
<br />
univention-certificate renew -name grommunio.domain.de -days $DEFAULT_DAYS<br />
</pre><br />
<br />
Damit funktioniert die Anmeldung am Webinterface auch per LDAP mit starttls, ansonsten nur LDAP ohne starttls oder halt mit SQL Accounts.<br />
<pre><br />
cd /etc/grommunio-common/ssl<br />
scp $kopano_server:/etc/univention/ssl/grommunio/cert.pem server-bundle.pem<br />
scp $kopano_server:/etc/univention/ssl/grommunio/private.key server.key<br />
chown gromox:gromox server*<br />
chmod 660 server*<br />
<br />
# Root CA<br />
if [ -e /etc/debian_version ] then<br />
scp $kopano_server:/etc/univention/ssl/ucsCA/CAcert.pem /usr/local/share/ca-certificates/my-custom-ca/<br />
else<br />
# SuSE appliance location<br />
scp $kopano_server:/etc/univention/ssl/ucsCA/CAcert.pem /etc/pki/trust/anchors/<br />
fi<br />
update-ca-certificates<br />
<br />
systemctl restart nginx<br />
<br />
# autodiscover Eintrag testen<br />
PASS='email_passwort' gromox-dscli -ve user@domainname<br />
</pre><br />
<br />
== LDAP ==<br />
Die LDAP Konfiguration kann mit dem UCS Template im Webinterface vorgenommen werden.<br />
Ich übernehme einige Kopano Werte:<br />
<pre><br />
ssh $kopano_server grep -e ^ldap_uri -e ^ldap_bind -e ^ldap_search_base -e ^ldap_user_search -e ^ldap_group_search /etc/kopano/ldap.cfg<br />
<br />
ldap_uri = ldap://ucsmail.domain.de:7389/<br />
ldap_bind_user = cn=ucsmail,cn=dc,cn=computers,dc=domain,dc=de<br />
ldap_bind_passwd = xxxxxxxxxxxxxx<br />
ldap_search_base = dc=domain,dc=de<br />
ldap_user_search_filter = (kopanoAccount=1)<br />
ldap_group_search_filter = (&(kopanoAccount=1)(objectClass=kopano-group))<br />
<br />
# configure LDAP accordingly<br />
grommunio-admin ldap configure<br />
<br />
# test: list users<br />
grommunio-admin ldap search<br />
ID Type E-Mail Name<br />
2222222222-11-10111-22222-3333333333 user user@domainname User Name<br />
</pre><br />
<br />
== Email Domains und User ==<br />
Debian: das Anlegen einer Domain im Web-Interface schlägt direkt fehl - "Bad Request".<br><br />
Nicht schlimm, aber vielleicht nehme ich doch lieber die fertige Appliance auf Basis von S.u.S.E. ?<br />
<br />
Per Kommandozeile funktioniert es jedenfalls.<br />
<pre><br />
domains=$(ssh $kopano_server ucr get mail/hosteddomains)<br />
users=$(grommunio-admin ldap search)<br />
<br />
# create email domains<br />
for domain in $domains; do<br />
u_cnt=$(echo "$users" | grep -c $domain)<br />
[ $u_cnt -gt 0 ] && grommunio-admin domain create -u $u_cnt $domain<br />
done<br />
<br />
# import and sync LDAP users<br />
grommunio-admin ldap downsync -c<br />
</pre><br />
Der Import der User funktioniert jedenfalls auch über das Webinterface.<br />
<br />
=== Export - Import via Outlook .pst ===<br />
Da ich nur 5 Konten habe, ist diese Methode auch nicht ganz abwegig:<br />
<br />
<pre>gromox-pff2mt /tmp/neobiker_outlook.pst | gromox-mt2exm -u neobiker@neobiker.de<br />
</pre><br />
<br />
=== Import von Emails aus Kopano ===<br />
Der Import landet (leider) in einem separatem Verzeichnis:<br />
[[Datei:Grommunio Import.jpg|mini]]<br />
<br />
Das ist nicht ganz das was ich mir für einen Import vorstelle: Jeder User muss alle seine Inhalte manuell auf dem Server in die Hauptverzeichnisse verschieben ... ?<br />
<br />
Ansonsten hat der Import prinzipiell für mein Neobiker Postfach funktioniert.<br />
<br />
<pre># gromox-kdb2mt — Utility for analysis/import of Kopano mailboxes<br />
# gromox-mt2exm — Utility for importing various mail items<br />
<br />
mkdir -p /mnt/kopano_attachments<br />
sshfs $kopano_server:/var/lib/kopano/attachments /mnt/kopano_attachments<br />
<br />
# detach (CTRL-A d) the SSH tunnel to SQL server (who only accepts localhost conections)<br />
screen ssh -L 12345:localhost:3306 "root@${kopano_server}"<br />
<br />
# EXAMPLE for user neobiker = neobiker@neobiker.de<br />
# ------------------------------------------------<br />
# ENVIRONMENT variable is used for SQL password<br />
# either EXPORT it or write in one line with cmd<br />
export_mbox="gromox-kdb2mt --sql-host 127.0.0.1 --sql-port 12345 --src-attach /mnt/kopano_attachments"<br />
SQLPASS=$(ssh $kopano_server cat /etc/mysql.secret)<br />
<br />
SQLPASS=$SQLPASS $export_mbox --mbox-mro neobiker | gromox-mt2exm -u neobiker@neobiker.de<br />
<br />
kdb2mt: No ACLs will be extracted.<br />
kdb Server GUID: xxxxxxxxxxxxxxxxxxxxxxxxxxx<br />
Database schema is kdb-118<br />
Store GUID for MRO "neobiker": xxxxxxxxxxxxxxxxxxxxxxxxx<br />
Processing folder "" (7 elements)...<br />
Processing folder "IPM_SUBTREE" (19 elements)...<br />
Processing folder "Posteingang" (791 elements)...<br />
Processing folder "Postausgang" (0 elements)...<br />
Processing folder "Gelöschte Objekte" (1 elements)...<br />
Processing folder "Gesendete Objekte" (1 elements)...<br />
Processing folder "Kontakte" (0 elements)...<br />
Processing folder "Kalender" (1 elements)...<br />
Processing folder "Entwürfe" (0 elements)...<br />
Processing folder "Journal" (0 elements)...<br />
Processing folder "Notizen" (0 elements)...<br />
Processing folder "Aufgaben" (0 elements)...<br />
Processing folder "Junk E-Mail" (44 elements)...<br />
Processing folder "RSS Feeds" (0 elements)...<br />
Processing folder "Konversationseinstellungen" (0 elements)...<br />
Processing folder "Quickstep Einstellungen" (0 elements)...<br />
Processing folder "Vorgeschlagene Kontakte" (0 elements)...<br />
Processing folder "Spambox" (0 elements)...<br />
Processing folder "Junk-E-Mail" (0 elements)...<br />
Processing folder "Gelöschte Elemente" (0 elements)...<br />
Processing folder "Gesendete Elemente" (0 elements)...<br />
Processing folder "IPM_COMMON_VIEWS" (2 elements)...<br />
Processing folder "IPM_VIEWS" (0 elements)...<br />
Processing folder "FINDER_ROOT" (0 elements)...<br />
Processing folder "Verknüpfung" (0 elements)...<br />
Processing folder "Schedule" (0 elements)...<br />
Processing folder "Freebusy Data" (1 elements)...<br />
</pre><br />
<br />
=== Import aller Email Konten ===<br />
<br />
<pre><br />
export_mbox="gromox-kdb2mt --sql-host 127.0.0.1 --sql-port 12345 --src-attach /mnt/kopano_attachments"<br />
SQLPASS=$(ssh $kopano_server cat /etc/mysql.secret)<br />
<br />
# LOOP over all email accounts (except SYSTEM)<br />
# --------------------------------------------<br />
users=$(ssh $kopano_server kopano-admin -l | awk '{print $1}' | tail -n +4 | grep -v SYSTEM)<br />
<br />
for user in ${users}; do<br />
<br />
details=$(ssh $kopano_server kopano-admin --details $user)<br />
email=$(echo "${details}" $user | awk '/Emailaddress:/ {print $2}')<br />
guid=$(echo "${details}" $user | awk '/Store GUID:/ {print $3}')<br />
<br />
SQLPASS=$SQLPASS $export_mbox --mbox-guid $guid | gromox-mt2exm -u $email<br />
<br />
done<br />
<br />
# stop ssh tunnel -> exit ssh on kopano server<br />
screen -r<br />
<br />
# unmount kopano attachments<br />
umount /mnt/kopano_attachments<br />
</pre><br />
<br />
== Datenablage ==<br />
Eventuell möchte ich ein separates Filesystem für die Daten verwenden.<br><br />
Die Daten liegen unter ''/var/lib/mysql*'' und ''/var/lib/grom*'' ?<br />
<br />
<pre>grommunio:/var/lib # ls mysql* grom*<br />
grommunio-admin-api:<br />
auth-private.pem auth-public.pem<br />
<br />
grommunio-antispam:<br />
110ec84de81cc98813b71d34d42dedbc199bd687.map 65b4e39783c10c8ed89dbec4983259f22418f34d.map ea942f35f2c82e84bdb7b8ceb34537f7dbe986eb.map stats.ucl<br />
2368c73b937d98513ed72c4b04f4247bda43fdb5.map 877aa38dd77ed707d20b2f77d46b905b69a6295a.map f3933e67d5f9574a9eaf78cd0a8e99140ecc502e.map<br />
2e66b2415ec9b99aaa155761c211e51def12016e.map 8fe64ecd161a46f91a78ece93609cb863f9f42f2.map rspamd.rrd<br />
3b1eab0b21b32c970b8fae4a9010a539879368e3.map cb5a8189726ac3a8880e1c44ed6220c6d794521b.map rspamd.sock<br />
<br />
grommunio-dav:<br />
<br />
grommunio-files:<br />
apps-external config data sessions tmp<br />
<br />
grommunio-web:<br />
session sqlite-index tmp<br />
<br />
gromox:<br />
domain queue timer.txt user<br />
<br />
mysql:<br />
aria_log.00000001 ddl_recovery.log grommunio ib_buffer_pool ib_logfile0 multi-master.info mysql_upgrade_info sys<br />
aria_log_control grofiles grommunio.pid ibdata1 ibtmp1 mysql performance_schema test<br />
<br />
mysql-files:<br />
</pre><br />
<br />
== Postfix ==<br />
Mail Delivery Agent (MDA) Konfiguration <br />
* zum versenden von Emails (external relay host)<br />
* Mailaddress rewritings (neobiker@local -> neobiker@neobiker.de)<br />
<br />
Vergleich der wichtigsten Konfigurationsparameter<br />
<pre>grep -v ^# /etc/postfix/main.cf | grep [[:alnum:]] | sed -e 's/^\W+//g' | sort > postfix_config_sorted_grommunio<br />
ssh $kopano_server "grep -v ^# /etc/postfix/main.cf | grep [[:alnum:]] | sed -e 's/^\W+//g' | sort" > postfix_config_sorted_kopano<br />
<br />
diff -y postfix_config<br />
...<br />
<br />
# most relevant entries (differs/unconfigured)<br />
masquerade_domains = $mydomain<br />
content_filter = smtp-amavis:[127.0.0.1]:10024<br />
disable_vrfy_command = no<br />
mailbox_size_limit<br />
message_size_limit<br />
relay_domains = $mydestination<br />
relayhost = smtp.mailhoster.de<br />
smtpd_tls_auth_only = yes<br />
smtpd_tls_cert_file = /etc/univention/ssl/ucsmail...<br />
smtpd_use_tls = yes<br />
smtp_sasl_password_maps = hash:/etc/postfix/smtp_auth<br />
smtp_sasl_security_options = noanonymous<br />
<br />
</pre><br />
Vermutlich muss ich mich nur um den externen Relay Host kümmern.<br />
<br />
<br />
=== Mail Relay Server ===<br />
Postfix can use a mail relay server.<br />
<br />
<pre>ssh $kopano_server ucr get mail/relayhost</pre><br />
<br />
<pre># authentification on relay host<br />
/etc/postfix/smtp_auth<br />
# format<br />
# FQDN-Relayhost username:password<br />
<br />
postmap /etc/postfix/smtp_auth<br />
postmap /etc/postfix/tls_policy<br />
service postfix reload<br />
</pre><br />
<br />
Doku grommunio: [https://docs.grommunio.com/admin/administration.html#adding-a-file relayhost]<br />
<br />
Eine andere Info dazu [https://andersgood.de/kurz-notiert/smtp-relayhost-in-grommunio-konfigurieren hier].<br />
Das hat funktioniert:<br />
* Email nach EXTERN ging raus.<br />
* Email nach intern -> noch zu klären<br />
<br />
=== Mail Address rewritings ===<br />
<br />
to be done<br />
<pre>domains=$(ssh ucsmail ucr get mail/hosteddomains)<br />
</pre><br />
<br />
== Fetchmail ==<br />
Fetchmail Konfiguration zum abholen der Emails von meinem Email Provider.<br />
Emails werden entweder von UCS '''oder ''' grommunio abgeholt und and Postfix übergeben!<br />
<br />
Auf UCS für jeden Benutzer deaktivieren <br><br />
-> ''Advanced settings ‣ Remote mail retrieval (single)''<br><br />
oder Postfix auf UCS zur weiterleitung an grommunio konfigurieren.<br />
<br />
<pre>systemctl stop fetchmail<br />
systemctl disable fetchmail<br />
ucr set fetchmail/autostart=false<br />
<br />
# fetchmail config and passwords<br />
cat /etc/fetchmailrc<br />
</pre><br />
<br />
== Proxy Konfiguration (Nginx) ==<br />
Ich habe für jede Email Domain einen deSEC (DynDNS) Eintrag auf mein Internetgateway (Nginx auf OPNSense) erstellt.<br />
Dafür verwende ich ein Let's Encrypt Zertifikat, welches die Domains als DNS Alternate Names beinhaltet.<br />
Das Webinterface von grommunio ist unter ''/web'' erreichbar, während in der Übergangszeit Kopano unter ''/webapp'' erreichbar bleibt.<br />
Active Sync für Mobile Devices (URL ist identisch) kann damit immerhin pro Email Domain separat umgestellt werden. Outlook ist bei mir derzeit nur intern erreichbar, also extern nur über VPN (oder halt das Webinterface).</div>
Neobiker
http://neobiker.de/wiki/index.php?title=Kopano_to_grommunio_HowTo&diff=2106
Kopano to grommunio HowTo
2024-01-05T16:41:07Z
<p>Neobiker: /* Mail Relay Server */</p>
<hr />
<div>= Installation auf Debian 12 =<br />
Eine fertige grommunio Appliance ist auf Basis von S.u.S.E. erstellt und steht u.a. als ISO download zur Verfügung. Da ich derzeit ausschliesslich Debian basierte Systeme verwende, bevorzuge ich eine grommunio Installation auf Debian 12 (bookworm).<br />
<br />
Im grommunio Forum findet sich ein Diskussionsthread für die [https://community.grommunio.com/d/447-debian-11-clean-install-script grommunio Installation auf Debian 12 durch ein Script]. Diese läuft auf einer neu erstellten VM auf Proxmox Server fehlerfrei durch. In einem LXC Container gab es Probleme, u.a. klemmte der POP3 Service.<br />
<br />
Im Anschluss an das Installationsskript habe ich noch folgendes Script unter '''Additions''' - ''Fix Grommunio Admin Live Status page'' ausgeführt, welches ebenfalls funktionierte und die entsprechende Seite im Web Interface reparierte.<br />
<br />
= Konfiguration =<br />
Kopano Core ist bei mir als Applikation auf meinem UCS File- und Mailserver installiert. Die Benutzerkonten und deren Konfiguration werden von UCS verwaltet und stehen per LDAP zur Verfügung.<br />
<br />
<pre># mein UCS-Kopano Server<br />
kopano_server=ucsmail<br />
echo "kopano_server=ucsmail" >> ~/.bashrc<br />
<br />
# ssh connections without prompts<br />
ssh-keygen<br />
ssh-copy-id $kopano_server<br />
<br />
# mount /var/lib/kopano/attachments per sshfs<br />
if [ -e /etc/debian_version ] then<br />
apt install sshfs screen<br />
else<br />
zypper in sshfs # (grommunio app/iso installation)<br />
fi<br />
mkdir -p /mnt/kopano_attachments<br />
sshfs $kopano_server:/var/lib/kopano/attachments /mnt/kopano_attachments<br />
</pre><br />
<br />
== Zertifikate ==<br />
Falls ein '''[https://help.univention.com/t/ssl-zertifikat-und-dns-alias/5780/7 Server Zertifikat von UCS]''' für den '''grommunio''' server erzeugt wurde kann das verwendet werden.<br />
<br />
Ich erstelle hier ein neues Zertifikat für den grommunio Server, der als DNS-Alternativen jeweils '''autodiscover''' (Outlook) und '''autoconfig''' (Android) Einträge für jede meiner Email Domains enthält. Damit erfolgt die Mail-Client Konfiguration automatisch, sofern der Rechner sich in einem '''lokalem''' Netzsegment befindet.<br />
<br />
Background:<br><br />
Meine Emails werden extern gehostet, ich importiere sie per Fetchmail. Daher bin ich nicht in der Lage diese Autodiscover/Autoconf Einträge im (externen) DNS meines Hosters zu erstellen. Die entsprechende '''lokale DNS Auflösung''' übernimmt mein '''Unbound DNS''' Server mittels '''Overwrite''' Einträgen, welche auf den grommunio Server verweisen. Daher müssen diese DNS Alias Namen in dessen Zertifikat vorhanden sein.<br />
<br />
<pre>ssh ucs # UCS Master!<br />
univention-certificate new -name grommunio.domain.de<br />
cd /etc/univention/ssl/grommunio.domain.de<br />
<br />
# add DNS Aliases<br />
vi openssl.cnf<br />
# subjectAltName = DNS:grommunio.domain.de, DNS:grommunio <br />
# add all your email domains autodiscover/autoconfig entries: DNS:autodiscover.email_domain.de, DNS:autoconfig.email_domain.de<br />
<br />
DEFAULT_CRL_DAYS="$(/usr/sbin/univention-config-registry get ssl/crl/validity)"<br />
DEFAULT_DAYS="$(/usr/sbin/univention-config-registry get ssl/default/days)"<br />
DEFAULT_MD="$(/usr/sbin/univention-config-registry get ssl/default/hashfunction)"<br />
DEFAULT_BITS="$(/usr/sbin/univention-config-registry get ssl/default/bits)"<br />
export DEFAULT_MD DEFAULT_BITS DEFAULT_CRL_DAYS DEFAULT_DAYS<br />
openssl req -new -config openssl.cnf -key private.key -out req.pem<br />
<br />
univention-certificate renew -name grommunio.domain.de -days $DEFAULT_DAYS<br />
</pre><br />
<br />
Damit funktioniert die Anmeldung am Webinterface auch per LDAP mit starttls, ansonsten nur LDAP ohne starttls oder halt mit SQL Accounts.<br />
<pre><br />
cd /etc/grommunio-common/ssl<br />
scp $kopano_server:/etc/univention/ssl/grommunio/cert.pem server-bundle.pem<br />
scp $kopano_server:/etc/univention/ssl/grommunio/private.key server.key<br />
chown gromox:gromox server*<br />
chmod 660 server*<br />
<br />
# Root CA<br />
if [ -e /etc/debian_version ] then<br />
scp $kopano_server:/etc/univention/ssl/ucsCA/CAcert.pem /usr/local/share/ca-certificates/my-custom-ca/<br />
else<br />
# SuSE appliance location<br />
scp $kopano_server:/etc/univention/ssl/ucsCA/CAcert.pem /etc/pki/trust/anchors/<br />
fi<br />
update-ca-certificates<br />
<br />
systemctl restart nginx<br />
<br />
# autodiscover Eintrag testen<br />
PASS='email_passwort' gromox-dscli -ve user@domainname<br />
</pre><br />
<br />
== LDAP ==<br />
Die LDAP Konfiguration kann mit dem UCS Template im Webinterface vorgenommen werden.<br />
Ich übernehme einige Kopano Werte:<br />
<pre><br />
ssh $kopano_server grep -e ^ldap_uri -e ^ldap_bind -e ^ldap_search_base -e ^ldap_user_search -e ^ldap_group_search /etc/kopano/ldap.cfg<br />
<br />
ldap_uri = ldap://ucsmail.domain.de:7389/<br />
ldap_bind_user = cn=ucsmail,cn=dc,cn=computers,dc=domain,dc=de<br />
ldap_bind_passwd = xxxxxxxxxxxxxx<br />
ldap_search_base = dc=domain,dc=de<br />
ldap_user_search_filter = (kopanoAccount=1)<br />
ldap_group_search_filter = (&(kopanoAccount=1)(objectClass=kopano-group))<br />
<br />
# configure LDAP accordingly<br />
grommunio-admin ldap configure<br />
<br />
# test: list users<br />
grommunio-admin ldap search<br />
ID Type E-Mail Name<br />
2222222222-11-10111-22222-3333333333 user user@domainname User Name<br />
</pre><br />
<br />
== Email Domains und User ==<br />
Debian: das Anlegen einer Domain im Web-Interface schlägt direkt fehl - "Bad Request".<br><br />
Nicht schlimm, aber vielleicht nehme ich doch lieber die fertige Appliance auf Basis von S.u.S.E. ?<br />
<br />
Per Kommandozeile funktioniert es jedenfalls.<br />
<pre><br />
domains=$(ssh $kopano_server ucr get mail/hosteddomains)<br />
users=$(grommunio-admin ldap search)<br />
<br />
# create email domains<br />
for domain in $domains; do<br />
u_cnt=$(echo "$users" | grep -c $domain)<br />
[ $u_cnt -gt 0 ] && grommunio-admin domain create -u $u_cnt $domain<br />
done<br />
<br />
# import and sync LDAP users<br />
grommunio-admin ldap downsync -c<br />
</pre><br />
Der Import der User funktioniert jedenfalls auch über das Webinterface.<br />
<br />
=== Export - Import via Outlook .pst ===<br />
Da ich nur 5 Konten habe, ist diese Methode auch nicht ganz abwegig:<br />
<br />
<pre>gromox-pff2mt /tmp/neobiker_outlook.pst | gromox-mt2exm -u neobiker@neobiker.de<br />
</pre><br />
<br />
=== Import von Emails aus Kopano ===<br />
Der Import landet (leider) in einem separatem Verzeichnis:<br />
[[Datei:Grommunio Import.jpg|mini]]<br />
<br />
Das ist nicht ganz das was ich mir für einen Import vorstelle: Jeder User muss alle seine Inhalte manuell auf dem Server in die Hauptverzeichnisse verschieben ... ?<br />
<br />
Ansonsten hat der Import prinzipiell für mein Neobiker Postfach funktioniert.<br />
<br />
<pre># gromox-kdb2mt — Utility for analysis/import of Kopano mailboxes<br />
# gromox-mt2exm — Utility for importing various mail items<br />
<br />
mkdir -p /mnt/kopano_attachments<br />
sshfs $kopano_server:/var/lib/kopano/attachments /mnt/kopano_attachments<br />
<br />
# detach (CTRL-A d) the SSH tunnel to SQL server (who only accepts localhost conections)<br />
screen ssh -L 12345:localhost:3306 "root@${kopano_server}"<br />
<br />
# EXAMPLE for user neobiker = neobiker@neobiker.de<br />
# ------------------------------------------------<br />
# ENVIRONMENT variable is used for SQL password<br />
# either EXPORT it or write in one line with cmd<br />
export_mbox="gromox-kdb2mt --sql-host 127.0.0.1 --sql-port 12345 --src-attach /mnt/kopano_attachments"<br />
SQLPASS=$(ssh $kopano_server cat /etc/mysql.secret)<br />
<br />
SQLPASS=$SQLPASS $export_mbox --mbox-mro neobiker | gromox-mt2exm -u neobiker@neobiker.de<br />
<br />
kdb2mt: No ACLs will be extracted.<br />
kdb Server GUID: xxxxxxxxxxxxxxxxxxxxxxxxxxx<br />
Database schema is kdb-118<br />
Store GUID for MRO "neobiker": xxxxxxxxxxxxxxxxxxxxxxxxx<br />
Processing folder "" (7 elements)...<br />
Processing folder "IPM_SUBTREE" (19 elements)...<br />
Processing folder "Posteingang" (791 elements)...<br />
Processing folder "Postausgang" (0 elements)...<br />
Processing folder "Gelöschte Objekte" (1 elements)...<br />
Processing folder "Gesendete Objekte" (1 elements)...<br />
Processing folder "Kontakte" (0 elements)...<br />
Processing folder "Kalender" (1 elements)...<br />
Processing folder "Entwürfe" (0 elements)...<br />
Processing folder "Journal" (0 elements)...<br />
Processing folder "Notizen" (0 elements)...<br />
Processing folder "Aufgaben" (0 elements)...<br />
Processing folder "Junk E-Mail" (44 elements)...<br />
Processing folder "RSS Feeds" (0 elements)...<br />
Processing folder "Konversationseinstellungen" (0 elements)...<br />
Processing folder "Quickstep Einstellungen" (0 elements)...<br />
Processing folder "Vorgeschlagene Kontakte" (0 elements)...<br />
Processing folder "Spambox" (0 elements)...<br />
Processing folder "Junk-E-Mail" (0 elements)...<br />
Processing folder "Gelöschte Elemente" (0 elements)...<br />
Processing folder "Gesendete Elemente" (0 elements)...<br />
Processing folder "IPM_COMMON_VIEWS" (2 elements)...<br />
Processing folder "IPM_VIEWS" (0 elements)...<br />
Processing folder "FINDER_ROOT" (0 elements)...<br />
Processing folder "Verknüpfung" (0 elements)...<br />
Processing folder "Schedule" (0 elements)...<br />
Processing folder "Freebusy Data" (1 elements)...<br />
</pre><br />
<br />
=== Import aller Email Konten ===<br />
<br />
<pre><br />
export_mbox="gromox-kdb2mt --sql-host 127.0.0.1 --sql-port 12345 --src-attach /mnt/kopano_attachments"<br />
SQLPASS=$(ssh $kopano_server cat /etc/mysql.secret)<br />
<br />
# LOOP over all email accounts (except SYSTEM)<br />
# --------------------------------------------<br />
users=$(ssh $kopano_server kopano-admin -l | awk '{print $1}' | tail -n +4 | grep -v SYSTEM)<br />
<br />
for user in ${users}; do<br />
<br />
details=$(ssh $kopano_server kopano-admin --details $user)<br />
email=$(echo "${details}" $user | awk '/Emailaddress:/ {print $2}')<br />
guid=$(echo "${details}" $user | awk '/Store GUID:/ {print $3}')<br />
<br />
SQLPASS=$SQLPASS $export_mbox --mbox-guid $guid | gromox-mt2exm -u $email<br />
<br />
done<br />
<br />
# stop ssh tunnel -> exit ssh on kopano server<br />
screen -r<br />
<br />
# unmount kopano attachments<br />
umount /mnt/kopano_attachments<br />
</pre><br />
<br />
== Datenablage ==<br />
Eventuell möchte ich ein separates Filesystem für die Daten verwenden.<br><br />
Die Daten liegen unter ''/var/lib/mysql*'' und ''/var/lib/grom*'' ?<br />
<br />
<pre>grommunio:/var/lib # ls mysql* grom*<br />
grommunio-admin-api:<br />
auth-private.pem auth-public.pem<br />
<br />
grommunio-antispam:<br />
110ec84de81cc98813b71d34d42dedbc199bd687.map 65b4e39783c10c8ed89dbec4983259f22418f34d.map ea942f35f2c82e84bdb7b8ceb34537f7dbe986eb.map stats.ucl<br />
2368c73b937d98513ed72c4b04f4247bda43fdb5.map 877aa38dd77ed707d20b2f77d46b905b69a6295a.map f3933e67d5f9574a9eaf78cd0a8e99140ecc502e.map<br />
2e66b2415ec9b99aaa155761c211e51def12016e.map 8fe64ecd161a46f91a78ece93609cb863f9f42f2.map rspamd.rrd<br />
3b1eab0b21b32c970b8fae4a9010a539879368e3.map cb5a8189726ac3a8880e1c44ed6220c6d794521b.map rspamd.sock<br />
<br />
grommunio-dav:<br />
<br />
grommunio-files:<br />
apps-external config data sessions tmp<br />
<br />
grommunio-web:<br />
session sqlite-index tmp<br />
<br />
gromox:<br />
domain queue timer.txt user<br />
<br />
mysql:<br />
aria_log.00000001 ddl_recovery.log grommunio ib_buffer_pool ib_logfile0 multi-master.info mysql_upgrade_info sys<br />
aria_log_control grofiles grommunio.pid ibdata1 ibtmp1 mysql performance_schema test<br />
<br />
mysql-files:<br />
</pre><br />
<br />
== Postfix ==<br />
Mail Delivery Agent (MDA) Konfiguration <br />
* zum versenden von Emails (external relay host)<br />
* Mailaddress rewritings (neobiker@local -> neobiker@neobiker.de)<br />
<br />
Vergleich der wichtigsten Konfigurationsparameter<br />
<pre>grep -v ^# /etc/postfix/main.cf | grep [[:alnum:]] | sed -e 's/^\W+//g' | sort > postfix_config_sorted_grommunio<br />
ssh $kopano_server "grep -v ^# /etc/postfix/main.cf | grep [[:alnum:]] | sed -e 's/^\W+//g' | sort" > postfix_config_sorted_kopano<br />
<br />
diff -y postfix_config<br />
...<br />
<br />
# most relevant entries (differs/unconfigured)<br />
masquerade_domains = $mydomain<br />
content_filter = smtp-amavis:[127.0.0.1]:10024<br />
disable_vrfy_command = no<br />
mailbox_size_limit<br />
message_size_limit<br />
relay_domains = $mydestination<br />
relayhost = smtp.mailhoster.de<br />
smtpd_tls_auth_only = yes<br />
smtpd_tls_cert_file = /etc/univention/ssl/ucsmail...<br />
smtpd_use_tls = yes<br />
smtp_sasl_password_maps = hash:/etc/postfix/smtp_auth<br />
smtp_sasl_security_options = noanonymous<br />
<br />
</pre><br />
Vermutlich muss ich mich nur um den externen Relay Host kümmern.<br />
<br />
<br />
=== Mail Relay Server ===<br />
Postfix can use a mail relay server.<br />
<br />
<pre>ssh $kopano_server ucr get mail/relayhost</pre><br />
<br />
<pre># authentification on relay host<br />
/etc/postfix/smtp_auth<br />
# format<br />
# FQDN-Relayhost username:password<br />
<br />
postmap /etc/postfix/smtp_auth<br />
postmap /etc/postfix/tls_policy<br />
service postfix reload<br />
</pre><br />
<br />
Doku grommunio: [https://docs.grommunio.com/admin/administration.html#adding-a-file relayhost]<br />
<br />
Eine andere Info dazu [https://andersgood.de/kurz-notiert/smtp-relayhost-in-grommunio-konfigurieren hier].<br />
Das hat funktioniert - Email ging raus.<br />
<br />
=== Mail Address rewritings ===<br />
<br />
to be done<br />
<pre>domains=$(ssh ucsmail ucr get mail/hosteddomains)<br />
</pre><br />
<br />
== Fetchmail ==<br />
Fetchmail Konfiguration zum abholen der Emails von meinem Email Provider.<br />
Emails werden entweder von UCS '''oder ''' grommunio abgeholt und and Postfix übergeben!<br />
<br />
Auf UCS für jeden Benutzer deaktivieren <br><br />
-> ''Advanced settings ‣ Remote mail retrieval (single)''<br><br />
oder Postfix auf UCS zur weiterleitung an grommunio konfigurieren.<br />
<br />
<pre>systemctl stop fetchmail<br />
systemctl disable fetchmail<br />
ucr set fetchmail/autostart=false<br />
<br />
# fetchmail config and passwords<br />
cat /etc/fetchmailrc<br />
</pre><br />
<br />
== Proxy Konfiguration (Nginx) ==<br />
Ich habe für jede Email Domain einen deSEC (DynDNS) Eintrag auf mein Internetgateway (Nginx auf OPNSense) erstellt.<br />
Dafür verwende ich ein Let's Encrypt Zertifikat, welches die Domains als DNS Alternate Names beinhaltet.<br />
Das Webinterface von grommunio ist unter ''/web'' erreichbar, während in der Übergangszeit Kopano unter ''/webapp'' erreichbar bleibt.<br />
Active Sync für Mobile Devices (URL ist identisch) kann damit immerhin pro Email Domain separat umgestellt werden. Outlook ist bei mir derzeit nur intern erreichbar, also extern nur über VPN (oder halt das Webinterface).</div>
Neobiker
http://neobiker.de/wiki/index.php?title=Kopano_to_grommunio_HowTo&diff=2105
Kopano to grommunio HowTo
2024-01-05T16:37:32Z
<p>Neobiker: /* Mail Relay Server */</p>
<hr />
<div>= Installation auf Debian 12 =<br />
Eine fertige grommunio Appliance ist auf Basis von S.u.S.E. erstellt und steht u.a. als ISO download zur Verfügung. Da ich derzeit ausschliesslich Debian basierte Systeme verwende, bevorzuge ich eine grommunio Installation auf Debian 12 (bookworm).<br />
<br />
Im grommunio Forum findet sich ein Diskussionsthread für die [https://community.grommunio.com/d/447-debian-11-clean-install-script grommunio Installation auf Debian 12 durch ein Script]. Diese läuft auf einer neu erstellten VM auf Proxmox Server fehlerfrei durch. In einem LXC Container gab es Probleme, u.a. klemmte der POP3 Service.<br />
<br />
Im Anschluss an das Installationsskript habe ich noch folgendes Script unter '''Additions''' - ''Fix Grommunio Admin Live Status page'' ausgeführt, welches ebenfalls funktionierte und die entsprechende Seite im Web Interface reparierte.<br />
<br />
= Konfiguration =<br />
Kopano Core ist bei mir als Applikation auf meinem UCS File- und Mailserver installiert. Die Benutzerkonten und deren Konfiguration werden von UCS verwaltet und stehen per LDAP zur Verfügung.<br />
<br />
<pre># mein UCS-Kopano Server<br />
kopano_server=ucsmail<br />
echo "kopano_server=ucsmail" >> ~/.bashrc<br />
<br />
# ssh connections without prompts<br />
ssh-keygen<br />
ssh-copy-id $kopano_server<br />
<br />
# mount /var/lib/kopano/attachments per sshfs<br />
if [ -e /etc/debian_version ] then<br />
apt install sshfs screen<br />
else<br />
zypper in sshfs # (grommunio app/iso installation)<br />
fi<br />
mkdir -p /mnt/kopano_attachments<br />
sshfs $kopano_server:/var/lib/kopano/attachments /mnt/kopano_attachments<br />
</pre><br />
<br />
== Zertifikate ==<br />
Falls ein '''[https://help.univention.com/t/ssl-zertifikat-und-dns-alias/5780/7 Server Zertifikat von UCS]''' für den '''grommunio''' server erzeugt wurde kann das verwendet werden.<br />
<br />
Ich erstelle hier ein neues Zertifikat für den grommunio Server, der als DNS-Alternativen jeweils '''autodiscover''' (Outlook) und '''autoconfig''' (Android) Einträge für jede meiner Email Domains enthält. Damit erfolgt die Mail-Client Konfiguration automatisch, sofern der Rechner sich in einem '''lokalem''' Netzsegment befindet.<br />
<br />
Background:<br><br />
Meine Emails werden extern gehostet, ich importiere sie per Fetchmail. Daher bin ich nicht in der Lage diese Autodiscover/Autoconf Einträge im (externen) DNS meines Hosters zu erstellen. Die entsprechende '''lokale DNS Auflösung''' übernimmt mein '''Unbound DNS''' Server mittels '''Overwrite''' Einträgen, welche auf den grommunio Server verweisen. Daher müssen diese DNS Alias Namen in dessen Zertifikat vorhanden sein.<br />
<br />
<pre>ssh ucs # UCS Master!<br />
univention-certificate new -name grommunio.domain.de<br />
cd /etc/univention/ssl/grommunio.domain.de<br />
<br />
# add DNS Aliases<br />
vi openssl.cnf<br />
# subjectAltName = DNS:grommunio.domain.de, DNS:grommunio <br />
# add all your email domains autodiscover/autoconfig entries: DNS:autodiscover.email_domain.de, DNS:autoconfig.email_domain.de<br />
<br />
DEFAULT_CRL_DAYS="$(/usr/sbin/univention-config-registry get ssl/crl/validity)"<br />
DEFAULT_DAYS="$(/usr/sbin/univention-config-registry get ssl/default/days)"<br />
DEFAULT_MD="$(/usr/sbin/univention-config-registry get ssl/default/hashfunction)"<br />
DEFAULT_BITS="$(/usr/sbin/univention-config-registry get ssl/default/bits)"<br />
export DEFAULT_MD DEFAULT_BITS DEFAULT_CRL_DAYS DEFAULT_DAYS<br />
openssl req -new -config openssl.cnf -key private.key -out req.pem<br />
<br />
univention-certificate renew -name grommunio.domain.de -days $DEFAULT_DAYS<br />
</pre><br />
<br />
Damit funktioniert die Anmeldung am Webinterface auch per LDAP mit starttls, ansonsten nur LDAP ohne starttls oder halt mit SQL Accounts.<br />
<pre><br />
cd /etc/grommunio-common/ssl<br />
scp $kopano_server:/etc/univention/ssl/grommunio/cert.pem server-bundle.pem<br />
scp $kopano_server:/etc/univention/ssl/grommunio/private.key server.key<br />
chown gromox:gromox server*<br />
chmod 660 server*<br />
<br />
# Root CA<br />
if [ -e /etc/debian_version ] then<br />
scp $kopano_server:/etc/univention/ssl/ucsCA/CAcert.pem /usr/local/share/ca-certificates/my-custom-ca/<br />
else<br />
# SuSE appliance location<br />
scp $kopano_server:/etc/univention/ssl/ucsCA/CAcert.pem /etc/pki/trust/anchors/<br />
fi<br />
update-ca-certificates<br />
<br />
systemctl restart nginx<br />
<br />
# autodiscover Eintrag testen<br />
PASS='email_passwort' gromox-dscli -ve user@domainname<br />
</pre><br />
<br />
== LDAP ==<br />
Die LDAP Konfiguration kann mit dem UCS Template im Webinterface vorgenommen werden.<br />
Ich übernehme einige Kopano Werte:<br />
<pre><br />
ssh $kopano_server grep -e ^ldap_uri -e ^ldap_bind -e ^ldap_search_base -e ^ldap_user_search -e ^ldap_group_search /etc/kopano/ldap.cfg<br />
<br />
ldap_uri = ldap://ucsmail.domain.de:7389/<br />
ldap_bind_user = cn=ucsmail,cn=dc,cn=computers,dc=domain,dc=de<br />
ldap_bind_passwd = xxxxxxxxxxxxxx<br />
ldap_search_base = dc=domain,dc=de<br />
ldap_user_search_filter = (kopanoAccount=1)<br />
ldap_group_search_filter = (&(kopanoAccount=1)(objectClass=kopano-group))<br />
<br />
# configure LDAP accordingly<br />
grommunio-admin ldap configure<br />
<br />
# test: list users<br />
grommunio-admin ldap search<br />
ID Type E-Mail Name<br />
2222222222-11-10111-22222-3333333333 user user@domainname User Name<br />
</pre><br />
<br />
== Email Domains und User ==<br />
Debian: das Anlegen einer Domain im Web-Interface schlägt direkt fehl - "Bad Request".<br><br />
Nicht schlimm, aber vielleicht nehme ich doch lieber die fertige Appliance auf Basis von S.u.S.E. ?<br />
<br />
Per Kommandozeile funktioniert es jedenfalls.<br />
<pre><br />
domains=$(ssh $kopano_server ucr get mail/hosteddomains)<br />
users=$(grommunio-admin ldap search)<br />
<br />
# create email domains<br />
for domain in $domains; do<br />
u_cnt=$(echo "$users" | grep -c $domain)<br />
[ $u_cnt -gt 0 ] && grommunio-admin domain create -u $u_cnt $domain<br />
done<br />
<br />
# import and sync LDAP users<br />
grommunio-admin ldap downsync -c<br />
</pre><br />
Der Import der User funktioniert jedenfalls auch über das Webinterface.<br />
<br />
=== Export - Import via Outlook .pst ===<br />
Da ich nur 5 Konten habe, ist diese Methode auch nicht ganz abwegig:<br />
<br />
<pre>gromox-pff2mt /tmp/neobiker_outlook.pst | gromox-mt2exm -u neobiker@neobiker.de<br />
</pre><br />
<br />
=== Import von Emails aus Kopano ===<br />
Der Import landet (leider) in einem separatem Verzeichnis:<br />
[[Datei:Grommunio Import.jpg|mini]]<br />
<br />
Das ist nicht ganz das was ich mir für einen Import vorstelle: Jeder User muss alle seine Inhalte manuell auf dem Server in die Hauptverzeichnisse verschieben ... ?<br />
<br />
Ansonsten hat der Import prinzipiell für mein Neobiker Postfach funktioniert.<br />
<br />
<pre># gromox-kdb2mt — Utility for analysis/import of Kopano mailboxes<br />
# gromox-mt2exm — Utility for importing various mail items<br />
<br />
mkdir -p /mnt/kopano_attachments<br />
sshfs $kopano_server:/var/lib/kopano/attachments /mnt/kopano_attachments<br />
<br />
# detach (CTRL-A d) the SSH tunnel to SQL server (who only accepts localhost conections)<br />
screen ssh -L 12345:localhost:3306 "root@${kopano_server}"<br />
<br />
# EXAMPLE for user neobiker = neobiker@neobiker.de<br />
# ------------------------------------------------<br />
# ENVIRONMENT variable is used for SQL password<br />
# either EXPORT it or write in one line with cmd<br />
export_mbox="gromox-kdb2mt --sql-host 127.0.0.1 --sql-port 12345 --src-attach /mnt/kopano_attachments"<br />
SQLPASS=$(ssh $kopano_server cat /etc/mysql.secret)<br />
<br />
SQLPASS=$SQLPASS $export_mbox --mbox-mro neobiker | gromox-mt2exm -u neobiker@neobiker.de<br />
<br />
kdb2mt: No ACLs will be extracted.<br />
kdb Server GUID: xxxxxxxxxxxxxxxxxxxxxxxxxxx<br />
Database schema is kdb-118<br />
Store GUID for MRO "neobiker": xxxxxxxxxxxxxxxxxxxxxxxxx<br />
Processing folder "" (7 elements)...<br />
Processing folder "IPM_SUBTREE" (19 elements)...<br />
Processing folder "Posteingang" (791 elements)...<br />
Processing folder "Postausgang" (0 elements)...<br />
Processing folder "Gelöschte Objekte" (1 elements)...<br />
Processing folder "Gesendete Objekte" (1 elements)...<br />
Processing folder "Kontakte" (0 elements)...<br />
Processing folder "Kalender" (1 elements)...<br />
Processing folder "Entwürfe" (0 elements)...<br />
Processing folder "Journal" (0 elements)...<br />
Processing folder "Notizen" (0 elements)...<br />
Processing folder "Aufgaben" (0 elements)...<br />
Processing folder "Junk E-Mail" (44 elements)...<br />
Processing folder "RSS Feeds" (0 elements)...<br />
Processing folder "Konversationseinstellungen" (0 elements)...<br />
Processing folder "Quickstep Einstellungen" (0 elements)...<br />
Processing folder "Vorgeschlagene Kontakte" (0 elements)...<br />
Processing folder "Spambox" (0 elements)...<br />
Processing folder "Junk-E-Mail" (0 elements)...<br />
Processing folder "Gelöschte Elemente" (0 elements)...<br />
Processing folder "Gesendete Elemente" (0 elements)...<br />
Processing folder "IPM_COMMON_VIEWS" (2 elements)...<br />
Processing folder "IPM_VIEWS" (0 elements)...<br />
Processing folder "FINDER_ROOT" (0 elements)...<br />
Processing folder "Verknüpfung" (0 elements)...<br />
Processing folder "Schedule" (0 elements)...<br />
Processing folder "Freebusy Data" (1 elements)...<br />
</pre><br />
<br />
=== Import aller Email Konten ===<br />
<br />
<pre><br />
export_mbox="gromox-kdb2mt --sql-host 127.0.0.1 --sql-port 12345 --src-attach /mnt/kopano_attachments"<br />
SQLPASS=$(ssh $kopano_server cat /etc/mysql.secret)<br />
<br />
# LOOP over all email accounts (except SYSTEM)<br />
# --------------------------------------------<br />
users=$(ssh $kopano_server kopano-admin -l | awk '{print $1}' | tail -n +4 | grep -v SYSTEM)<br />
<br />
for user in ${users}; do<br />
<br />
details=$(ssh $kopano_server kopano-admin --details $user)<br />
email=$(echo "${details}" $user | awk '/Emailaddress:/ {print $2}')<br />
guid=$(echo "${details}" $user | awk '/Store GUID:/ {print $3}')<br />
<br />
SQLPASS=$SQLPASS $export_mbox --mbox-guid $guid | gromox-mt2exm -u $email<br />
<br />
done<br />
<br />
# stop ssh tunnel -> exit ssh on kopano server<br />
screen -r<br />
<br />
# unmount kopano attachments<br />
umount /mnt/kopano_attachments<br />
</pre><br />
<br />
== Datenablage ==<br />
Eventuell möchte ich ein separates Filesystem für die Daten verwenden.<br><br />
Die Daten liegen unter ''/var/lib/mysql*'' und ''/var/lib/grom*'' ?<br />
<br />
<pre>grommunio:/var/lib # ls mysql* grom*<br />
grommunio-admin-api:<br />
auth-private.pem auth-public.pem<br />
<br />
grommunio-antispam:<br />
110ec84de81cc98813b71d34d42dedbc199bd687.map 65b4e39783c10c8ed89dbec4983259f22418f34d.map ea942f35f2c82e84bdb7b8ceb34537f7dbe986eb.map stats.ucl<br />
2368c73b937d98513ed72c4b04f4247bda43fdb5.map 877aa38dd77ed707d20b2f77d46b905b69a6295a.map f3933e67d5f9574a9eaf78cd0a8e99140ecc502e.map<br />
2e66b2415ec9b99aaa155761c211e51def12016e.map 8fe64ecd161a46f91a78ece93609cb863f9f42f2.map rspamd.rrd<br />
3b1eab0b21b32c970b8fae4a9010a539879368e3.map cb5a8189726ac3a8880e1c44ed6220c6d794521b.map rspamd.sock<br />
<br />
grommunio-dav:<br />
<br />
grommunio-files:<br />
apps-external config data sessions tmp<br />
<br />
grommunio-web:<br />
session sqlite-index tmp<br />
<br />
gromox:<br />
domain queue timer.txt user<br />
<br />
mysql:<br />
aria_log.00000001 ddl_recovery.log grommunio ib_buffer_pool ib_logfile0 multi-master.info mysql_upgrade_info sys<br />
aria_log_control grofiles grommunio.pid ibdata1 ibtmp1 mysql performance_schema test<br />
<br />
mysql-files:<br />
</pre><br />
<br />
== Postfix ==<br />
Mail Delivery Agent (MDA) Konfiguration <br />
* zum versenden von Emails (external relay host)<br />
* Mailaddress rewritings (neobiker@local -> neobiker@neobiker.de)<br />
<br />
Vergleich der wichtigsten Konfigurationsparameter<br />
<pre>grep -v ^# /etc/postfix/main.cf | grep [[:alnum:]] | sed -e 's/^\W+//g' | sort > postfix_config_sorted_grommunio<br />
ssh $kopano_server "grep -v ^# /etc/postfix/main.cf | grep [[:alnum:]] | sed -e 's/^\W+//g' | sort" > postfix_config_sorted_kopano<br />
<br />
diff -y postfix_config<br />
...<br />
<br />
# most relevant entries (differs/unconfigured)<br />
masquerade_domains = $mydomain<br />
content_filter = smtp-amavis:[127.0.0.1]:10024<br />
disable_vrfy_command = no<br />
mailbox_size_limit<br />
message_size_limit<br />
relay_domains = $mydestination<br />
relayhost = smtp.mailhoster.de<br />
smtpd_tls_auth_only = yes<br />
smtpd_tls_cert_file = /etc/univention/ssl/ucsmail...<br />
smtpd_use_tls = yes<br />
smtp_sasl_password_maps = hash:/etc/postfix/smtp_auth<br />
smtp_sasl_security_options = noanonymous<br />
<br />
</pre><br />
Vermutlich muss ich mich nur um den externen Relay Host kümmern.<br />
<br />
<br />
=== Mail Relay Server ===<br />
Postfix can use a mail relay server.<br />
<br />
<pre>ssh $kopano_server ucr get mail/relayhost</pre><br />
<br />
<pre># authentification on relay host<br />
/etc/postfix/smtp_auth<br />
# format<br />
# FQDN-Relayhost username:password<br />
<br />
postmap /etc/postfix/smtp_auth<br />
postmap /etc/postfix/tls_policy<br />
service postfix reload<br />
</pre><br />
<br />
Doku grommunio: [https://docs.grommunio.com/admin/administration.html#adding-a-file relayhost]<br />
<br />
Eine andere Info dazu [https://andersgood.de/kurz-notiert/smtp-relayhost-in-grommunio-konfigurieren hier].<br />
Das hat aber nicht funktioniert.<br />
<br />
=== Mail Address rewritings ===<br />
<br />
to be done<br />
<pre>domains=$(ssh ucsmail ucr get mail/hosteddomains)<br />
</pre><br />
<br />
== Fetchmail ==<br />
Fetchmail Konfiguration zum abholen der Emails von meinem Email Provider.<br />
Emails werden entweder von UCS '''oder ''' grommunio abgeholt und and Postfix übergeben!<br />
<br />
Auf UCS für jeden Benutzer deaktivieren <br><br />
-> ''Advanced settings ‣ Remote mail retrieval (single)''<br><br />
oder Postfix auf UCS zur weiterleitung an grommunio konfigurieren.<br />
<br />
<pre>systemctl stop fetchmail<br />
systemctl disable fetchmail<br />
ucr set fetchmail/autostart=false<br />
<br />
# fetchmail config and passwords<br />
cat /etc/fetchmailrc<br />
</pre><br />
<br />
== Proxy Konfiguration (Nginx) ==<br />
Ich habe für jede Email Domain einen deSEC (DynDNS) Eintrag auf mein Internetgateway (Nginx auf OPNSense) erstellt.<br />
Dafür verwende ich ein Let's Encrypt Zertifikat, welches die Domains als DNS Alternate Names beinhaltet.<br />
Das Webinterface von grommunio ist unter ''/web'' erreichbar, während in der Übergangszeit Kopano unter ''/webapp'' erreichbar bleibt.<br />
Active Sync für Mobile Devices (URL ist identisch) kann damit immerhin pro Email Domain separat umgestellt werden. Outlook ist bei mir derzeit nur intern erreichbar, also extern nur über VPN (oder halt das Webinterface).</div>
Neobiker
http://neobiker.de/wiki/index.php?title=Kopano_to_grommunio_HowTo&diff=2104
Kopano to grommunio HowTo
2024-01-05T16:36:51Z
<p>Neobiker: /* Fetchmail */</p>
<hr />
<div>= Installation auf Debian 12 =<br />
Eine fertige grommunio Appliance ist auf Basis von S.u.S.E. erstellt und steht u.a. als ISO download zur Verfügung. Da ich derzeit ausschliesslich Debian basierte Systeme verwende, bevorzuge ich eine grommunio Installation auf Debian 12 (bookworm).<br />
<br />
Im grommunio Forum findet sich ein Diskussionsthread für die [https://community.grommunio.com/d/447-debian-11-clean-install-script grommunio Installation auf Debian 12 durch ein Script]. Diese läuft auf einer neu erstellten VM auf Proxmox Server fehlerfrei durch. In einem LXC Container gab es Probleme, u.a. klemmte der POP3 Service.<br />
<br />
Im Anschluss an das Installationsskript habe ich noch folgendes Script unter '''Additions''' - ''Fix Grommunio Admin Live Status page'' ausgeführt, welches ebenfalls funktionierte und die entsprechende Seite im Web Interface reparierte.<br />
<br />
= Konfiguration =<br />
Kopano Core ist bei mir als Applikation auf meinem UCS File- und Mailserver installiert. Die Benutzerkonten und deren Konfiguration werden von UCS verwaltet und stehen per LDAP zur Verfügung.<br />
<br />
<pre># mein UCS-Kopano Server<br />
kopano_server=ucsmail<br />
echo "kopano_server=ucsmail" >> ~/.bashrc<br />
<br />
# ssh connections without prompts<br />
ssh-keygen<br />
ssh-copy-id $kopano_server<br />
<br />
# mount /var/lib/kopano/attachments per sshfs<br />
if [ -e /etc/debian_version ] then<br />
apt install sshfs screen<br />
else<br />
zypper in sshfs # (grommunio app/iso installation)<br />
fi<br />
mkdir -p /mnt/kopano_attachments<br />
sshfs $kopano_server:/var/lib/kopano/attachments /mnt/kopano_attachments<br />
</pre><br />
<br />
== Zertifikate ==<br />
Falls ein '''[https://help.univention.com/t/ssl-zertifikat-und-dns-alias/5780/7 Server Zertifikat von UCS]''' für den '''grommunio''' server erzeugt wurde kann das verwendet werden.<br />
<br />
Ich erstelle hier ein neues Zertifikat für den grommunio Server, der als DNS-Alternativen jeweils '''autodiscover''' (Outlook) und '''autoconfig''' (Android) Einträge für jede meiner Email Domains enthält. Damit erfolgt die Mail-Client Konfiguration automatisch, sofern der Rechner sich in einem '''lokalem''' Netzsegment befindet.<br />
<br />
Background:<br><br />
Meine Emails werden extern gehostet, ich importiere sie per Fetchmail. Daher bin ich nicht in der Lage diese Autodiscover/Autoconf Einträge im (externen) DNS meines Hosters zu erstellen. Die entsprechende '''lokale DNS Auflösung''' übernimmt mein '''Unbound DNS''' Server mittels '''Overwrite''' Einträgen, welche auf den grommunio Server verweisen. Daher müssen diese DNS Alias Namen in dessen Zertifikat vorhanden sein.<br />
<br />
<pre>ssh ucs # UCS Master!<br />
univention-certificate new -name grommunio.domain.de<br />
cd /etc/univention/ssl/grommunio.domain.de<br />
<br />
# add DNS Aliases<br />
vi openssl.cnf<br />
# subjectAltName = DNS:grommunio.domain.de, DNS:grommunio <br />
# add all your email domains autodiscover/autoconfig entries: DNS:autodiscover.email_domain.de, DNS:autoconfig.email_domain.de<br />
<br />
DEFAULT_CRL_DAYS="$(/usr/sbin/univention-config-registry get ssl/crl/validity)"<br />
DEFAULT_DAYS="$(/usr/sbin/univention-config-registry get ssl/default/days)"<br />
DEFAULT_MD="$(/usr/sbin/univention-config-registry get ssl/default/hashfunction)"<br />
DEFAULT_BITS="$(/usr/sbin/univention-config-registry get ssl/default/bits)"<br />
export DEFAULT_MD DEFAULT_BITS DEFAULT_CRL_DAYS DEFAULT_DAYS<br />
openssl req -new -config openssl.cnf -key private.key -out req.pem<br />
<br />
univention-certificate renew -name grommunio.domain.de -days $DEFAULT_DAYS<br />
</pre><br />
<br />
Damit funktioniert die Anmeldung am Webinterface auch per LDAP mit starttls, ansonsten nur LDAP ohne starttls oder halt mit SQL Accounts.<br />
<pre><br />
cd /etc/grommunio-common/ssl<br />
scp $kopano_server:/etc/univention/ssl/grommunio/cert.pem server-bundle.pem<br />
scp $kopano_server:/etc/univention/ssl/grommunio/private.key server.key<br />
chown gromox:gromox server*<br />
chmod 660 server*<br />
<br />
# Root CA<br />
if [ -e /etc/debian_version ] then<br />
scp $kopano_server:/etc/univention/ssl/ucsCA/CAcert.pem /usr/local/share/ca-certificates/my-custom-ca/<br />
else<br />
# SuSE appliance location<br />
scp $kopano_server:/etc/univention/ssl/ucsCA/CAcert.pem /etc/pki/trust/anchors/<br />
fi<br />
update-ca-certificates<br />
<br />
systemctl restart nginx<br />
<br />
# autodiscover Eintrag testen<br />
PASS='email_passwort' gromox-dscli -ve user@domainname<br />
</pre><br />
<br />
== LDAP ==<br />
Die LDAP Konfiguration kann mit dem UCS Template im Webinterface vorgenommen werden.<br />
Ich übernehme einige Kopano Werte:<br />
<pre><br />
ssh $kopano_server grep -e ^ldap_uri -e ^ldap_bind -e ^ldap_search_base -e ^ldap_user_search -e ^ldap_group_search /etc/kopano/ldap.cfg<br />
<br />
ldap_uri = ldap://ucsmail.domain.de:7389/<br />
ldap_bind_user = cn=ucsmail,cn=dc,cn=computers,dc=domain,dc=de<br />
ldap_bind_passwd = xxxxxxxxxxxxxx<br />
ldap_search_base = dc=domain,dc=de<br />
ldap_user_search_filter = (kopanoAccount=1)<br />
ldap_group_search_filter = (&(kopanoAccount=1)(objectClass=kopano-group))<br />
<br />
# configure LDAP accordingly<br />
grommunio-admin ldap configure<br />
<br />
# test: list users<br />
grommunio-admin ldap search<br />
ID Type E-Mail Name<br />
2222222222-11-10111-22222-3333333333 user user@domainname User Name<br />
</pre><br />
<br />
== Email Domains und User ==<br />
Debian: das Anlegen einer Domain im Web-Interface schlägt direkt fehl - "Bad Request".<br><br />
Nicht schlimm, aber vielleicht nehme ich doch lieber die fertige Appliance auf Basis von S.u.S.E. ?<br />
<br />
Per Kommandozeile funktioniert es jedenfalls.<br />
<pre><br />
domains=$(ssh $kopano_server ucr get mail/hosteddomains)<br />
users=$(grommunio-admin ldap search)<br />
<br />
# create email domains<br />
for domain in $domains; do<br />
u_cnt=$(echo "$users" | grep -c $domain)<br />
[ $u_cnt -gt 0 ] && grommunio-admin domain create -u $u_cnt $domain<br />
done<br />
<br />
# import and sync LDAP users<br />
grommunio-admin ldap downsync -c<br />
</pre><br />
Der Import der User funktioniert jedenfalls auch über das Webinterface.<br />
<br />
=== Export - Import via Outlook .pst ===<br />
Da ich nur 5 Konten habe, ist diese Methode auch nicht ganz abwegig:<br />
<br />
<pre>gromox-pff2mt /tmp/neobiker_outlook.pst | gromox-mt2exm -u neobiker@neobiker.de<br />
</pre><br />
<br />
=== Import von Emails aus Kopano ===<br />
Der Import landet (leider) in einem separatem Verzeichnis:<br />
[[Datei:Grommunio Import.jpg|mini]]<br />
<br />
Das ist nicht ganz das was ich mir für einen Import vorstelle: Jeder User muss alle seine Inhalte manuell auf dem Server in die Hauptverzeichnisse verschieben ... ?<br />
<br />
Ansonsten hat der Import prinzipiell für mein Neobiker Postfach funktioniert.<br />
<br />
<pre># gromox-kdb2mt — Utility for analysis/import of Kopano mailboxes<br />
# gromox-mt2exm — Utility for importing various mail items<br />
<br />
mkdir -p /mnt/kopano_attachments<br />
sshfs $kopano_server:/var/lib/kopano/attachments /mnt/kopano_attachments<br />
<br />
# detach (CTRL-A d) the SSH tunnel to SQL server (who only accepts localhost conections)<br />
screen ssh -L 12345:localhost:3306 "root@${kopano_server}"<br />
<br />
# EXAMPLE for user neobiker = neobiker@neobiker.de<br />
# ------------------------------------------------<br />
# ENVIRONMENT variable is used for SQL password<br />
# either EXPORT it or write in one line with cmd<br />
export_mbox="gromox-kdb2mt --sql-host 127.0.0.1 --sql-port 12345 --src-attach /mnt/kopano_attachments"<br />
SQLPASS=$(ssh $kopano_server cat /etc/mysql.secret)<br />
<br />
SQLPASS=$SQLPASS $export_mbox --mbox-mro neobiker | gromox-mt2exm -u neobiker@neobiker.de<br />
<br />
kdb2mt: No ACLs will be extracted.<br />
kdb Server GUID: xxxxxxxxxxxxxxxxxxxxxxxxxxx<br />
Database schema is kdb-118<br />
Store GUID for MRO "neobiker": xxxxxxxxxxxxxxxxxxxxxxxxx<br />
Processing folder "" (7 elements)...<br />
Processing folder "IPM_SUBTREE" (19 elements)...<br />
Processing folder "Posteingang" (791 elements)...<br />
Processing folder "Postausgang" (0 elements)...<br />
Processing folder "Gelöschte Objekte" (1 elements)...<br />
Processing folder "Gesendete Objekte" (1 elements)...<br />
Processing folder "Kontakte" (0 elements)...<br />
Processing folder "Kalender" (1 elements)...<br />
Processing folder "Entwürfe" (0 elements)...<br />
Processing folder "Journal" (0 elements)...<br />
Processing folder "Notizen" (0 elements)...<br />
Processing folder "Aufgaben" (0 elements)...<br />
Processing folder "Junk E-Mail" (44 elements)...<br />
Processing folder "RSS Feeds" (0 elements)...<br />
Processing folder "Konversationseinstellungen" (0 elements)...<br />
Processing folder "Quickstep Einstellungen" (0 elements)...<br />
Processing folder "Vorgeschlagene Kontakte" (0 elements)...<br />
Processing folder "Spambox" (0 elements)...<br />
Processing folder "Junk-E-Mail" (0 elements)...<br />
Processing folder "Gelöschte Elemente" (0 elements)...<br />
Processing folder "Gesendete Elemente" (0 elements)...<br />
Processing folder "IPM_COMMON_VIEWS" (2 elements)...<br />
Processing folder "IPM_VIEWS" (0 elements)...<br />
Processing folder "FINDER_ROOT" (0 elements)...<br />
Processing folder "Verknüpfung" (0 elements)...<br />
Processing folder "Schedule" (0 elements)...<br />
Processing folder "Freebusy Data" (1 elements)...<br />
</pre><br />
<br />
=== Import aller Email Konten ===<br />
<br />
<pre><br />
export_mbox="gromox-kdb2mt --sql-host 127.0.0.1 --sql-port 12345 --src-attach /mnt/kopano_attachments"<br />
SQLPASS=$(ssh $kopano_server cat /etc/mysql.secret)<br />
<br />
# LOOP over all email accounts (except SYSTEM)<br />
# --------------------------------------------<br />
users=$(ssh $kopano_server kopano-admin -l | awk '{print $1}' | tail -n +4 | grep -v SYSTEM)<br />
<br />
for user in ${users}; do<br />
<br />
details=$(ssh $kopano_server kopano-admin --details $user)<br />
email=$(echo "${details}" $user | awk '/Emailaddress:/ {print $2}')<br />
guid=$(echo "${details}" $user | awk '/Store GUID:/ {print $3}')<br />
<br />
SQLPASS=$SQLPASS $export_mbox --mbox-guid $guid | gromox-mt2exm -u $email<br />
<br />
done<br />
<br />
# stop ssh tunnel -> exit ssh on kopano server<br />
screen -r<br />
<br />
# unmount kopano attachments<br />
umount /mnt/kopano_attachments<br />
</pre><br />
<br />
== Datenablage ==<br />
Eventuell möchte ich ein separates Filesystem für die Daten verwenden.<br><br />
Die Daten liegen unter ''/var/lib/mysql*'' und ''/var/lib/grom*'' ?<br />
<br />
<pre>grommunio:/var/lib # ls mysql* grom*<br />
grommunio-admin-api:<br />
auth-private.pem auth-public.pem<br />
<br />
grommunio-antispam:<br />
110ec84de81cc98813b71d34d42dedbc199bd687.map 65b4e39783c10c8ed89dbec4983259f22418f34d.map ea942f35f2c82e84bdb7b8ceb34537f7dbe986eb.map stats.ucl<br />
2368c73b937d98513ed72c4b04f4247bda43fdb5.map 877aa38dd77ed707d20b2f77d46b905b69a6295a.map f3933e67d5f9574a9eaf78cd0a8e99140ecc502e.map<br />
2e66b2415ec9b99aaa155761c211e51def12016e.map 8fe64ecd161a46f91a78ece93609cb863f9f42f2.map rspamd.rrd<br />
3b1eab0b21b32c970b8fae4a9010a539879368e3.map cb5a8189726ac3a8880e1c44ed6220c6d794521b.map rspamd.sock<br />
<br />
grommunio-dav:<br />
<br />
grommunio-files:<br />
apps-external config data sessions tmp<br />
<br />
grommunio-web:<br />
session sqlite-index tmp<br />
<br />
gromox:<br />
domain queue timer.txt user<br />
<br />
mysql:<br />
aria_log.00000001 ddl_recovery.log grommunio ib_buffer_pool ib_logfile0 multi-master.info mysql_upgrade_info sys<br />
aria_log_control grofiles grommunio.pid ibdata1 ibtmp1 mysql performance_schema test<br />
<br />
mysql-files:<br />
</pre><br />
<br />
== Postfix ==<br />
Mail Delivery Agent (MDA) Konfiguration <br />
* zum versenden von Emails (external relay host)<br />
* Mailaddress rewritings (neobiker@local -> neobiker@neobiker.de)<br />
<br />
Vergleich der wichtigsten Konfigurationsparameter<br />
<pre>grep -v ^# /etc/postfix/main.cf | grep [[:alnum:]] | sed -e 's/^\W+//g' | sort > postfix_config_sorted_grommunio<br />
ssh $kopano_server "grep -v ^# /etc/postfix/main.cf | grep [[:alnum:]] | sed -e 's/^\W+//g' | sort" > postfix_config_sorted_kopano<br />
<br />
diff -y postfix_config<br />
...<br />
<br />
# most relevant entries (differs/unconfigured)<br />
masquerade_domains = $mydomain<br />
content_filter = smtp-amavis:[127.0.0.1]:10024<br />
disable_vrfy_command = no<br />
mailbox_size_limit<br />
message_size_limit<br />
relay_domains = $mydestination<br />
relayhost = smtp.mailhoster.de<br />
smtpd_tls_auth_only = yes<br />
smtpd_tls_cert_file = /etc/univention/ssl/ucsmail...<br />
smtpd_use_tls = yes<br />
smtp_sasl_password_maps = hash:/etc/postfix/smtp_auth<br />
smtp_sasl_security_options = noanonymous<br />
<br />
</pre><br />
Vermutlich muss ich mich nur um den externen Relay Host kümmern.<br />
<br />
<br />
=== Mail Relay Server ===<br />
Postfix can use a mail relay server.<br />
<br />
<pre>ssh $kopano_server ucr get mail/relayhost</pre><br />
<br />
<pre># authentification on relay host<br />
/etc/postfix/smtp_auth<br />
# format<br />
# FQDN-Relayhost username:password<br />
<br />
postmap /etc/postfix/smtp_auth<br />
postmap /etc/postfix/tls_policy<br />
service postfix reload<br />
</pre><br />
<br />
=== Mail Address rewritings ===<br />
<br />
to be done<br />
<pre>domains=$(ssh ucsmail ucr get mail/hosteddomains)<br />
</pre><br />
<br />
== Fetchmail ==<br />
Fetchmail Konfiguration zum abholen der Emails von meinem Email Provider.<br />
Emails werden entweder von UCS '''oder ''' grommunio abgeholt und and Postfix übergeben!<br />
<br />
Auf UCS für jeden Benutzer deaktivieren <br><br />
-> ''Advanced settings ‣ Remote mail retrieval (single)''<br><br />
oder Postfix auf UCS zur weiterleitung an grommunio konfigurieren.<br />
<br />
<pre>systemctl stop fetchmail<br />
systemctl disable fetchmail<br />
ucr set fetchmail/autostart=false<br />
<br />
# fetchmail config and passwords<br />
cat /etc/fetchmailrc<br />
</pre><br />
<br />
== Proxy Konfiguration (Nginx) ==<br />
Ich habe für jede Email Domain einen deSEC (DynDNS) Eintrag auf mein Internetgateway (Nginx auf OPNSense) erstellt.<br />
Dafür verwende ich ein Let's Encrypt Zertifikat, welches die Domains als DNS Alternate Names beinhaltet.<br />
Das Webinterface von grommunio ist unter ''/web'' erreichbar, während in der Übergangszeit Kopano unter ''/webapp'' erreichbar bleibt.<br />
Active Sync für Mobile Devices (URL ist identisch) kann damit immerhin pro Email Domain separat umgestellt werden. Outlook ist bei mir derzeit nur intern erreichbar, also extern nur über VPN (oder halt das Webinterface).</div>
Neobiker
http://neobiker.de/wiki/index.php?title=Kopano_to_grommunio_HowTo&diff=2103
Kopano to grommunio HowTo
2024-01-05T16:36:23Z
<p>Neobiker: /* Fetchmail */</p>
<hr />
<div>= Installation auf Debian 12 =<br />
Eine fertige grommunio Appliance ist auf Basis von S.u.S.E. erstellt und steht u.a. als ISO download zur Verfügung. Da ich derzeit ausschliesslich Debian basierte Systeme verwende, bevorzuge ich eine grommunio Installation auf Debian 12 (bookworm).<br />
<br />
Im grommunio Forum findet sich ein Diskussionsthread für die [https://community.grommunio.com/d/447-debian-11-clean-install-script grommunio Installation auf Debian 12 durch ein Script]. Diese läuft auf einer neu erstellten VM auf Proxmox Server fehlerfrei durch. In einem LXC Container gab es Probleme, u.a. klemmte der POP3 Service.<br />
<br />
Im Anschluss an das Installationsskript habe ich noch folgendes Script unter '''Additions''' - ''Fix Grommunio Admin Live Status page'' ausgeführt, welches ebenfalls funktionierte und die entsprechende Seite im Web Interface reparierte.<br />
<br />
= Konfiguration =<br />
Kopano Core ist bei mir als Applikation auf meinem UCS File- und Mailserver installiert. Die Benutzerkonten und deren Konfiguration werden von UCS verwaltet und stehen per LDAP zur Verfügung.<br />
<br />
<pre># mein UCS-Kopano Server<br />
kopano_server=ucsmail<br />
echo "kopano_server=ucsmail" >> ~/.bashrc<br />
<br />
# ssh connections without prompts<br />
ssh-keygen<br />
ssh-copy-id $kopano_server<br />
<br />
# mount /var/lib/kopano/attachments per sshfs<br />
if [ -e /etc/debian_version ] then<br />
apt install sshfs screen<br />
else<br />
zypper in sshfs # (grommunio app/iso installation)<br />
fi<br />
mkdir -p /mnt/kopano_attachments<br />
sshfs $kopano_server:/var/lib/kopano/attachments /mnt/kopano_attachments<br />
</pre><br />
<br />
== Zertifikate ==<br />
Falls ein '''[https://help.univention.com/t/ssl-zertifikat-und-dns-alias/5780/7 Server Zertifikat von UCS]''' für den '''grommunio''' server erzeugt wurde kann das verwendet werden.<br />
<br />
Ich erstelle hier ein neues Zertifikat für den grommunio Server, der als DNS-Alternativen jeweils '''autodiscover''' (Outlook) und '''autoconfig''' (Android) Einträge für jede meiner Email Domains enthält. Damit erfolgt die Mail-Client Konfiguration automatisch, sofern der Rechner sich in einem '''lokalem''' Netzsegment befindet.<br />
<br />
Background:<br><br />
Meine Emails werden extern gehostet, ich importiere sie per Fetchmail. Daher bin ich nicht in der Lage diese Autodiscover/Autoconf Einträge im (externen) DNS meines Hosters zu erstellen. Die entsprechende '''lokale DNS Auflösung''' übernimmt mein '''Unbound DNS''' Server mittels '''Overwrite''' Einträgen, welche auf den grommunio Server verweisen. Daher müssen diese DNS Alias Namen in dessen Zertifikat vorhanden sein.<br />
<br />
<pre>ssh ucs # UCS Master!<br />
univention-certificate new -name grommunio.domain.de<br />
cd /etc/univention/ssl/grommunio.domain.de<br />
<br />
# add DNS Aliases<br />
vi openssl.cnf<br />
# subjectAltName = DNS:grommunio.domain.de, DNS:grommunio <br />
# add all your email domains autodiscover/autoconfig entries: DNS:autodiscover.email_domain.de, DNS:autoconfig.email_domain.de<br />
<br />
DEFAULT_CRL_DAYS="$(/usr/sbin/univention-config-registry get ssl/crl/validity)"<br />
DEFAULT_DAYS="$(/usr/sbin/univention-config-registry get ssl/default/days)"<br />
DEFAULT_MD="$(/usr/sbin/univention-config-registry get ssl/default/hashfunction)"<br />
DEFAULT_BITS="$(/usr/sbin/univention-config-registry get ssl/default/bits)"<br />
export DEFAULT_MD DEFAULT_BITS DEFAULT_CRL_DAYS DEFAULT_DAYS<br />
openssl req -new -config openssl.cnf -key private.key -out req.pem<br />
<br />
univention-certificate renew -name grommunio.domain.de -days $DEFAULT_DAYS<br />
</pre><br />
<br />
Damit funktioniert die Anmeldung am Webinterface auch per LDAP mit starttls, ansonsten nur LDAP ohne starttls oder halt mit SQL Accounts.<br />
<pre><br />
cd /etc/grommunio-common/ssl<br />
scp $kopano_server:/etc/univention/ssl/grommunio/cert.pem server-bundle.pem<br />
scp $kopano_server:/etc/univention/ssl/grommunio/private.key server.key<br />
chown gromox:gromox server*<br />
chmod 660 server*<br />
<br />
# Root CA<br />
if [ -e /etc/debian_version ] then<br />
scp $kopano_server:/etc/univention/ssl/ucsCA/CAcert.pem /usr/local/share/ca-certificates/my-custom-ca/<br />
else<br />
# SuSE appliance location<br />
scp $kopano_server:/etc/univention/ssl/ucsCA/CAcert.pem /etc/pki/trust/anchors/<br />
fi<br />
update-ca-certificates<br />
<br />
systemctl restart nginx<br />
<br />
# autodiscover Eintrag testen<br />
PASS='email_passwort' gromox-dscli -ve user@domainname<br />
</pre><br />
<br />
== LDAP ==<br />
Die LDAP Konfiguration kann mit dem UCS Template im Webinterface vorgenommen werden.<br />
Ich übernehme einige Kopano Werte:<br />
<pre><br />
ssh $kopano_server grep -e ^ldap_uri -e ^ldap_bind -e ^ldap_search_base -e ^ldap_user_search -e ^ldap_group_search /etc/kopano/ldap.cfg<br />
<br />
ldap_uri = ldap://ucsmail.domain.de:7389/<br />
ldap_bind_user = cn=ucsmail,cn=dc,cn=computers,dc=domain,dc=de<br />
ldap_bind_passwd = xxxxxxxxxxxxxx<br />
ldap_search_base = dc=domain,dc=de<br />
ldap_user_search_filter = (kopanoAccount=1)<br />
ldap_group_search_filter = (&(kopanoAccount=1)(objectClass=kopano-group))<br />
<br />
# configure LDAP accordingly<br />
grommunio-admin ldap configure<br />
<br />
# test: list users<br />
grommunio-admin ldap search<br />
ID Type E-Mail Name<br />
2222222222-11-10111-22222-3333333333 user user@domainname User Name<br />
</pre><br />
<br />
== Email Domains und User ==<br />
Debian: das Anlegen einer Domain im Web-Interface schlägt direkt fehl - "Bad Request".<br><br />
Nicht schlimm, aber vielleicht nehme ich doch lieber die fertige Appliance auf Basis von S.u.S.E. ?<br />
<br />
Per Kommandozeile funktioniert es jedenfalls.<br />
<pre><br />
domains=$(ssh $kopano_server ucr get mail/hosteddomains)<br />
users=$(grommunio-admin ldap search)<br />
<br />
# create email domains<br />
for domain in $domains; do<br />
u_cnt=$(echo "$users" | grep -c $domain)<br />
[ $u_cnt -gt 0 ] && grommunio-admin domain create -u $u_cnt $domain<br />
done<br />
<br />
# import and sync LDAP users<br />
grommunio-admin ldap downsync -c<br />
</pre><br />
Der Import der User funktioniert jedenfalls auch über das Webinterface.<br />
<br />
=== Export - Import via Outlook .pst ===<br />
Da ich nur 5 Konten habe, ist diese Methode auch nicht ganz abwegig:<br />
<br />
<pre>gromox-pff2mt /tmp/neobiker_outlook.pst | gromox-mt2exm -u neobiker@neobiker.de<br />
</pre><br />
<br />
=== Import von Emails aus Kopano ===<br />
Der Import landet (leider) in einem separatem Verzeichnis:<br />
[[Datei:Grommunio Import.jpg|mini]]<br />
<br />
Das ist nicht ganz das was ich mir für einen Import vorstelle: Jeder User muss alle seine Inhalte manuell auf dem Server in die Hauptverzeichnisse verschieben ... ?<br />
<br />
Ansonsten hat der Import prinzipiell für mein Neobiker Postfach funktioniert.<br />
<br />
<pre># gromox-kdb2mt — Utility for analysis/import of Kopano mailboxes<br />
# gromox-mt2exm — Utility for importing various mail items<br />
<br />
mkdir -p /mnt/kopano_attachments<br />
sshfs $kopano_server:/var/lib/kopano/attachments /mnt/kopano_attachments<br />
<br />
# detach (CTRL-A d) the SSH tunnel to SQL server (who only accepts localhost conections)<br />
screen ssh -L 12345:localhost:3306 "root@${kopano_server}"<br />
<br />
# EXAMPLE for user neobiker = neobiker@neobiker.de<br />
# ------------------------------------------------<br />
# ENVIRONMENT variable is used for SQL password<br />
# either EXPORT it or write in one line with cmd<br />
export_mbox="gromox-kdb2mt --sql-host 127.0.0.1 --sql-port 12345 --src-attach /mnt/kopano_attachments"<br />
SQLPASS=$(ssh $kopano_server cat /etc/mysql.secret)<br />
<br />
SQLPASS=$SQLPASS $export_mbox --mbox-mro neobiker | gromox-mt2exm -u neobiker@neobiker.de<br />
<br />
kdb2mt: No ACLs will be extracted.<br />
kdb Server GUID: xxxxxxxxxxxxxxxxxxxxxxxxxxx<br />
Database schema is kdb-118<br />
Store GUID for MRO "neobiker": xxxxxxxxxxxxxxxxxxxxxxxxx<br />
Processing folder "" (7 elements)...<br />
Processing folder "IPM_SUBTREE" (19 elements)...<br />
Processing folder "Posteingang" (791 elements)...<br />
Processing folder "Postausgang" (0 elements)...<br />
Processing folder "Gelöschte Objekte" (1 elements)...<br />
Processing folder "Gesendete Objekte" (1 elements)...<br />
Processing folder "Kontakte" (0 elements)...<br />
Processing folder "Kalender" (1 elements)...<br />
Processing folder "Entwürfe" (0 elements)...<br />
Processing folder "Journal" (0 elements)...<br />
Processing folder "Notizen" (0 elements)...<br />
Processing folder "Aufgaben" (0 elements)...<br />
Processing folder "Junk E-Mail" (44 elements)...<br />
Processing folder "RSS Feeds" (0 elements)...<br />
Processing folder "Konversationseinstellungen" (0 elements)...<br />
Processing folder "Quickstep Einstellungen" (0 elements)...<br />
Processing folder "Vorgeschlagene Kontakte" (0 elements)...<br />
Processing folder "Spambox" (0 elements)...<br />
Processing folder "Junk-E-Mail" (0 elements)...<br />
Processing folder "Gelöschte Elemente" (0 elements)...<br />
Processing folder "Gesendete Elemente" (0 elements)...<br />
Processing folder "IPM_COMMON_VIEWS" (2 elements)...<br />
Processing folder "IPM_VIEWS" (0 elements)...<br />
Processing folder "FINDER_ROOT" (0 elements)...<br />
Processing folder "Verknüpfung" (0 elements)...<br />
Processing folder "Schedule" (0 elements)...<br />
Processing folder "Freebusy Data" (1 elements)...<br />
</pre><br />
<br />
=== Import aller Email Konten ===<br />
<br />
<pre><br />
export_mbox="gromox-kdb2mt --sql-host 127.0.0.1 --sql-port 12345 --src-attach /mnt/kopano_attachments"<br />
SQLPASS=$(ssh $kopano_server cat /etc/mysql.secret)<br />
<br />
# LOOP over all email accounts (except SYSTEM)<br />
# --------------------------------------------<br />
users=$(ssh $kopano_server kopano-admin -l | awk '{print $1}' | tail -n +4 | grep -v SYSTEM)<br />
<br />
for user in ${users}; do<br />
<br />
details=$(ssh $kopano_server kopano-admin --details $user)<br />
email=$(echo "${details}" $user | awk '/Emailaddress:/ {print $2}')<br />
guid=$(echo "${details}" $user | awk '/Store GUID:/ {print $3}')<br />
<br />
SQLPASS=$SQLPASS $export_mbox --mbox-guid $guid | gromox-mt2exm -u $email<br />
<br />
done<br />
<br />
# stop ssh tunnel -> exit ssh on kopano server<br />
screen -r<br />
<br />
# unmount kopano attachments<br />
umount /mnt/kopano_attachments<br />
</pre><br />
<br />
== Datenablage ==<br />
Eventuell möchte ich ein separates Filesystem für die Daten verwenden.<br><br />
Die Daten liegen unter ''/var/lib/mysql*'' und ''/var/lib/grom*'' ?<br />
<br />
<pre>grommunio:/var/lib # ls mysql* grom*<br />
grommunio-admin-api:<br />
auth-private.pem auth-public.pem<br />
<br />
grommunio-antispam:<br />
110ec84de81cc98813b71d34d42dedbc199bd687.map 65b4e39783c10c8ed89dbec4983259f22418f34d.map ea942f35f2c82e84bdb7b8ceb34537f7dbe986eb.map stats.ucl<br />
2368c73b937d98513ed72c4b04f4247bda43fdb5.map 877aa38dd77ed707d20b2f77d46b905b69a6295a.map f3933e67d5f9574a9eaf78cd0a8e99140ecc502e.map<br />
2e66b2415ec9b99aaa155761c211e51def12016e.map 8fe64ecd161a46f91a78ece93609cb863f9f42f2.map rspamd.rrd<br />
3b1eab0b21b32c970b8fae4a9010a539879368e3.map cb5a8189726ac3a8880e1c44ed6220c6d794521b.map rspamd.sock<br />
<br />
grommunio-dav:<br />
<br />
grommunio-files:<br />
apps-external config data sessions tmp<br />
<br />
grommunio-web:<br />
session sqlite-index tmp<br />
<br />
gromox:<br />
domain queue timer.txt user<br />
<br />
mysql:<br />
aria_log.00000001 ddl_recovery.log grommunio ib_buffer_pool ib_logfile0 multi-master.info mysql_upgrade_info sys<br />
aria_log_control grofiles grommunio.pid ibdata1 ibtmp1 mysql performance_schema test<br />
<br />
mysql-files:<br />
</pre><br />
<br />
== Postfix ==<br />
Mail Delivery Agent (MDA) Konfiguration <br />
* zum versenden von Emails (external relay host)<br />
* Mailaddress rewritings (neobiker@local -> neobiker@neobiker.de)<br />
<br />
Vergleich der wichtigsten Konfigurationsparameter<br />
<pre>grep -v ^# /etc/postfix/main.cf | grep [[:alnum:]] | sed -e 's/^\W+//g' | sort > postfix_config_sorted_grommunio<br />
ssh $kopano_server "grep -v ^# /etc/postfix/main.cf | grep [[:alnum:]] | sed -e 's/^\W+//g' | sort" > postfix_config_sorted_kopano<br />
<br />
diff -y postfix_config<br />
...<br />
<br />
# most relevant entries (differs/unconfigured)<br />
masquerade_domains = $mydomain<br />
content_filter = smtp-amavis:[127.0.0.1]:10024<br />
disable_vrfy_command = no<br />
mailbox_size_limit<br />
message_size_limit<br />
relay_domains = $mydestination<br />
relayhost = smtp.mailhoster.de<br />
smtpd_tls_auth_only = yes<br />
smtpd_tls_cert_file = /etc/univention/ssl/ucsmail...<br />
smtpd_use_tls = yes<br />
smtp_sasl_password_maps = hash:/etc/postfix/smtp_auth<br />
smtp_sasl_security_options = noanonymous<br />
<br />
</pre><br />
Vermutlich muss ich mich nur um den externen Relay Host kümmern.<br />
<br />
<br />
=== Mail Relay Server ===<br />
Postfix can use a mail relay server.<br />
<br />
<pre>ssh $kopano_server ucr get mail/relayhost</pre><br />
<br />
<pre># authentification on relay host<br />
/etc/postfix/smtp_auth<br />
# format<br />
# FQDN-Relayhost username:password<br />
<br />
postmap /etc/postfix/smtp_auth<br />
postmap /etc/postfix/tls_policy<br />
service postfix reload<br />
</pre><br />
<br />
=== Mail Address rewritings ===<br />
<br />
to be done<br />
<pre>domains=$(ssh ucsmail ucr get mail/hosteddomains)<br />
</pre><br />
<br />
== Fetchmail ==<br />
Fetchmail Konfiguration zum abholen der Emails von meinem Email Provider.<br />
Emails werden entweder von UCS '''oder ''' grommunio abgeholt und and Postfix übergeben!<br />
<br />
Auf UCS für jeden Benutzer deaktivieren <br><br />
-> ''Advanced settings ‣ Remote mail retrieval (single)''<br><br />
oder Postfix auf UCS zur weiterleitung an grommunio konfigurieren.<br />
<br />
<pre>systemctl stop fetchmail<br />
systemctl disable fetchmail<br />
ucr set fetchmail/autostart=false<br />
<br />
# fetchmail config and passwords<br />
cat /etc/fetchmailrc<br />
</pre><br />
<br />
Doku grommunio: [https://docs.grommunio.com/admin/administration.html#adding-a-file relayhost]<br />
<br />
Eine andere Info dazu [https://andersgood.de/kurz-notiert/smtp-relayhost-in-grommunio-konfigurieren hier].<br />
<br />
== Proxy Konfiguration (Nginx) ==<br />
Ich habe für jede Email Domain einen deSEC (DynDNS) Eintrag auf mein Internetgateway (Nginx auf OPNSense) erstellt.<br />
Dafür verwende ich ein Let's Encrypt Zertifikat, welches die Domains als DNS Alternate Names beinhaltet.<br />
Das Webinterface von grommunio ist unter ''/web'' erreichbar, während in der Übergangszeit Kopano unter ''/webapp'' erreichbar bleibt.<br />
Active Sync für Mobile Devices (URL ist identisch) kann damit immerhin pro Email Domain separat umgestellt werden. Outlook ist bei mir derzeit nur intern erreichbar, also extern nur über VPN (oder halt das Webinterface).</div>
Neobiker