Die Integration von SOLARMAN in openHAB ermöglicht Hausbesitzern eine effiziente Überwachung und Steuerung ihrer Solaranlage. In diesem Beitrag werden verschiedene Ansätze vorgestellt, wie die Daten des Wechselrichters in die Open-Source-Plattform integriert werden können. Die Integration wird über die SOLARMMANPV API realisiert. Damit können viele Wechselrichter (hier Deye oder Bosswerk) in openHAB integriert werden.
Aktivierung API
Im ersten Schritt muss die API von SOLARMANPV für die externe Nutzung freigeschaltet werden. Dazu genügt eine kurze E-Mail an den Kundenservice (customerservice@solarmanpv.com):
Hello Solarman-Support,
I need access to the Solarman-API.
Account: xxx
Best regards,
xxx
Installation
Die Installation in openHAB 4.2.x erfolgt wie gewohnt über das Webinterface:
- Add-on Store – Binding – HTTP-Binding – INSTALL
In den früheren Versionen war teilweise das HTTP-Binding von SmartHome/J zum Teil notwendig. In den alten Versionen gab es auch Probleme mit der Token-Authentifizierung von SOLARMANPV (das ist aber in den aktuellen Versionen behoben).
Vor der ersten Integration in openHAB kann der Webservice mit den entsprechenden Tools getestet werden. Dafür habe ich Postman verwendet.
Thing
Eine mögliche SOLARMAN.things kann wie folgt aussehen (die Werte mit xxx müssen durch eigene Werte ersetzt werden):
Thing http:url:solarmanpv_account "SOLARMANPV - Account" [
baseURL="https://globalapi.solarmanpv.com/account/v1.0/token?appId=xxx&language=en",
contentType="application/json",
authMode="BASIC",
stateMethod="POST",
commandMethod="GET",
// 86400 Sekunden = 24 Std. - 4752000 Sekunden = 55 Tage (60 Tage läuft ein Token)
// 2592000 Sekunden = 30 Tage
refresh=4752000] {
Channels:
Type string : config "config" [ stateContent="{ \"appSecret\" : \"xxx\", \"email\" : \"xxx@xxx.de\", \"password\" : \"xxx\" }" ]
}
Thing http:url:solarmanpv_station "SOLARMANPV - Station" [
baseURL="https://globalapi.solarmanpv.com/station/v1.0/list?language=en",
contentType="application/json",
authMode="TOKEN",
password="xxx",
stateMethod="POST",
refresh=60] {
Channels:
// Fehlerbehandlung
Type string : msg "msg" [ stateTransformation="JSONPATH:$.msg", stateContent="{ \"page\": 1, \"size\": 50 }" ]
Type string : code "code" [ stateTransformation="JSONPATH:$.code", stateContent="{ \"page\": 1, \"size\": 50 }" ]
Type string : success "success" [ stateTransformation="JSONPATH:$.success", stateContent="{ \"page\": 1, \"size\": 50 }" ]
Type string : requestId "requestId" [ stateTransformation="JSONPATH:$.requestId", stateContent="{ \"page\": 1, \"size\": 50 }" ]
// Anzahl der Stationen
Type string : total "total" [ stateTransformation="JSONPATH:$.total", stateContent="{ \"page\": 1, \"size\": 50 }" ]
// xxx- Bosswerk BW-HY 3600 - Haus Süd
Type number : stationList_xxx_id "stationList_xxx_id" [ stateTransformation="JSONPATH:$.stationList[1].id", stateContent="{ \"page\": 1, \"size\": 50 }" ]
Type string : stationList_xxx_name "stationList_xxx_name" [ stateTransformation="JSONPATH:$.stationList[1].name", stateContent="{ \"page\": 1, \"size\": 50 }" ]
Type number : stationList_xxx_installedCapacity "stationList_xxx_installedCapacity" [ stateTransformation="JSONPATH:$.stationList[1].installedCapacity", stateContent="{ \"page\": 1, \"size\": 50 }" ]
Type number : stationList_xxx_batterySoc "stationList_xxx_batterySoc" [ stateTransformation="JSONPATH:$.stationList[1].batterySoc", stateContent="{ \"page\": 1, \"size\": 50 }" ]
Type string : stationList_xxx_networkStatus "stationList_xxx_networkStatus" [ stateTransformation="JSONPATH:$.stationList[1].networkStatus", stateContent="{ \"page\": 1, \"size\": 50 }" ]
Type number : stationList_xxx_generationPower "stationList_xxx_generationPower" [ stateTransformation="JSONPATH:$.stationList[1].generationPower", stateContent="{ \"page\": 1, \"size\": 50 }" ]
Type string : stationList_xxx_lastUpdateTime "stationList_xxx_lastUpdateTime" [ stateTransformation="JSONPATH:$.stationList[1].lastUpdateTime", stateContent="{ \"page\": 1, \"size\": 50 }" ]
}
Thing http:url:solarmanpv_realTime "SOLARMANPV - realTime" [
baseURL="https://globalapi.solarmanpv.com/station/v1.0/realTime?language=en",
contentType="application/json",
authMode="TOKEN",
password="xxx",
stateMethod="POST",
refresh=60] {
Channels:
// xxx - Bosswerk BW-HY 3600 - Haus Süd
Type string : realTime_xxx_code "realTime_xxx_code" [ stateTransformation="JSONPATH:$.code", stateContent="{\"stationId\": xxx}" ]
Type string : realTime_xxx_msg "realTime_xxx_msg" [ stateTransformation="JSONPATH:$.msg", stateContent="{\"stationId\": xxx}" ]
Type string : realTime_xxx_success "realTime_xxx_success" [ stateTransformation="JSONPATH:$.success", stateContent="{\"stationId\": xxx}" ]
Type string : realTime_xxx_requestId "realTime_xxx_requestId" [ stateTransformation="JSONPATH:$.requestId", stateContent="{\"stationId\": xxx}" ]
Type number : realTime_xxx_generationPower "realTime_xxx_generationPower" [ stateTransformation="JSONPATH:$.generationPower", stateContent="{\"stationId\": xxx}" ]
Type number : realTime_xxx_usePower "realTime_xxx_usePower" [ stateTransformation="JSONPATH:$.usePower", stateContent="{\"stationId\": xxx}" ]
Type number : realTime_xxx_purchasePower "realTime_xxx_purchasePower" [ stateTransformation="JSONPATH:$.purchasePower", stateContent="{\"stationId\": xxx}" ]
Type number : realTime_xxx_batteryPower "realTime_xxx_batteryPower" [ stateTransformation="JSONPATH:$.batteryPower", stateContent="{\"stationId\": xxx}" ]
Type number : realTime_xxx_batterySoc "realTime_xxx_batterySoc" [ stateTransformation="JSONPATH:$.batterySoc", stateContent="{\"stationId\": xxx}" ]
Type number : realTime_xxx_generationTotal "realTime_xxx_generationTotal" [ stateTransformation="JSONPATH:$.generationTotal", stateContent="{\"stationId\": xxx}" ]
Type string : realTime_xxx_lastUpdateTime "realTime_xxx_lastUpdateTime" [ stateTransformation="JSONPATH:$.lastUpdateTime", stateContent="{\"stationId\": xxx}" ]
}
Zwei Webservices werden angesprochen:
- Account – optionale Nutzung für das Auslesen der Token (funktionktioniert, erfordert aber manuelle Eingriffe alle 90 Tage)
- realTime – Auslesen der Werte von der API
Items
Die SOLARMAN.items kann wie folgt erstellt werden:
//
// https://globalapi.solarmanpv.com/account/v1.0/token?appId=xxx&language=en
// 23.12.2023
//
String solarmanpv_account_config "SOLARMANPV - Account - config" { channel="http:url:solarmanpv_account:config"}
//
// https://globalapi.solarmanpv.com/station/v1.0/list?language=en
//
// Fehlerbehandlung
String solarmanpv_station_msg "SOLARMANPV - Station - msg" { channel="http:url:solarmanpv_station:msg"}
String solarmanpv_station_code "SOLARMANPV - Station - code" { channel="http:url:solarmanpv_station:code"}
String solarmanpv_station_success "SOLARMANPV - Station - success" { channel="http:url:solarmanpv_station:success"}
String solarmanpv_station_requestId "SOLARMANPV - Station - requestId" { channel="http:url:solarmanpv_station:requestId"}
// Anzahl der Stationen
String solarmanpv_station_total "SOLARMANPV - Station - total" { channel="http:url:solarmanpv_station:total"}
// xxx - Bosswerk BW-HY 3600 - Haus Süd
Number solarmanpv_stationList_xxx_id "SOLARMANPV - stationList - xxx - id" { channel="http:url:solarmanpv_station:stationList_xxx_id"}
String solarmanpv_stationList_xxx_name "SOLARMANPV - stationList - xxx - name" { channel="http:url:solarmanpv_station:stationList_xxx_name"}
Number solarmanpv_stationList_xxx_installedCapacity "SOLARMANPV - stationList - xxx - installedCapacity" { channel="http:url:solarmanpv_station:stationList_xxx_installedCapacity"}
Number solarmanpv_stationList_xxx_batterySoc "SOLARMANPV - stationList - xxx - batterySoc" { channel="http:url:solarmanpv_station:stationList_xxx_batterySoc"}
String solarmanpv_stationList_xxx_networkStatus "SOLARMANPV - stationList - xxx - networkStatus" { channel="http:url:solarmanpv_station:stationList_xxx_networkStatus"}
Number solarmanpv_stationList_xxx_generationPower "SOLARMANPV - stationList - xxx - generationPower" { channel="http:url:solarmanpv_station:stationList_xxx_generationPower"}
DateTime solarmanpv_stationList_xxx_lastUpdateTime "SOLARMANPV - stationList - xxx - lastUpdateTime" { channel="http:url:solarmanpv_station:stationList_xxx_lastUpdateTime"}
//
// https://globalapi.solarmanpv.com/station/v1.0/realTime?language=en
//
// Gesamte Übersicht
Number solarmanpv_realTime_generationTotal "SOLARMANPV - stationList - generationTotal"
// xxx - Bosswerk BW-HY 3600 - Haus Süd
String solarmanpv_realTime_xxx_code "SOLARMANPV - stationList - xxx - code" { channel="http:url:solarmanpv_realTime:realTime_xxx_code"}
String solarmanpv_realTime_xxx_msg "SOLARMANPV - stationList - xxx - msg" { channel="http:url:solarmanpv_realTime:realTime_xxx_msg"}
String solarmanpv_realTime_xxx_success "SOLARMANPV - stationList - xxx - success" { channel="http:url:solarmanpv_realTime:realTime_xxx_success"}
String solarmanpv_realTime_xxx_requestId "SOLARMANPV - stationList - xxx - requestId" { channel="http:url:solarmanpv_realTime:realTime_xxx_requestId"}
Number solarmanpv_realTime_xxx_generationPower "SOLARMANPV - stationList - xxx - generationPower" { channel="http:url:solarmanpv_realTime:realTime_xxx_generationPower"}
Number solarmanpv_realTime_xxx_usePower "SOLARMANPV - stationList - xxx - usePower" { channel="http:url:solarmanpv_realTime:realTime_xxx_usePower"}
Number solarmanpv_realTime_xxx_purchasePower "SOLARMANPV - stationList - xxx - purchasePower" { channel="http:url:solarmanpv_realTime:realTime_xxx_purchasePower"}
Number solarmanpv_realTime_xxx_batteryPower "SOLARMANPV - stationList - xxx - batteryPower" { channel="http:url:solarmanpv_realTime:realTime_xxx_batteryPower"}
Number solarmanpv_realTime_xxx_batterySoc "SOLARMANPV - stationList - xxx - batterySoc" { channel="http:url:solarmanpv_realTime:realTime_xxx_batterySoc"}
Number solarmanpv_realTime_xxx_generationTotal "SOLARMANPV - stationList - xxx - generationTotal" { channel="http:url:solarmanpv_realTime:realTime_xxx_generationTotal"}
DateTime solarmanpv_realTime_xxx_lastUpdateTime "SOLARMANPV - stationList - xxx - lastUpdateTime" { channel="http:url:solarmanpv_realTime:realTime_xxx_lastUpdateTime"}
Rules
Nun kann man in der Regeldatei SOLARMAN.rules seine entsprechenden Regeln parametrieren:
rule "SOLARMANPV generation total"
when
Item solarmanpv_realTime_xxx_generationTotal received update
then
logInfo("INFO", "SOLARMAN.rules - Gesamtproduktion Start: " + solarmanpv_realTime_xxx_generationTotal.state)
solarmanpv_realTime_generationTotal.postUpdate((solarmanpv_realTime_xxx_generationTotal.state as Number) +
(solarmanpv_realTime_xxx_generationTotal.state as Number) +
(solarmanpv_realTime_xxx_generationTotal.state as Number) +
(solarmanpv_realTime_xxx_generationTotal.state as Number))
logInfo("INFO", "SOLARMAN.rules - Gesamtproduktion (Total kWh): " + solarmanpv_realTime_generationTotal.state)
end
// 08.02.2024 - Wenn Item mit Refresh Token von SOLARMAN PV geändert wird, Regel ausführen
rule "SOLARMANPV refresh token"
when
Item solarmanpv_account_config received update
then
//logInfo("INFO", "SOLARMAN.rules - JSON: " + solarmanpv_account_config)
val access_token = transform("JSONPATH", "$.access_token", solarmanpv_account_config.state.toString)
val expires_in = transform("JSONPATH", "$.expires_in", solarmanpv_account_config.state.toString)
val double expiresMinutes = Double.parseDouble(expires_in.toString) / 60
val double expiresHours = Double.parseDouble(expiresMinutes.toString) / 60
val double expiresDays = Double.parseDouble(expiresHours.toString) / 24
// TEST NOCH OFFEN - expires_in muss DOUBLE sein
//var test = 0;
//test = Math.floor(expires_in)
//minutes = Math.floor(dur / 60);
//hours = Math.floor(minutes / 60);
//days = Math.floor(hours / 24);
//logInfo("INFO", "SOLARMAN.rules - Access Token: " + access_token)
logInfo("INFO", "SOLARMAN.rules - Expires In (Sekunden): " + expires_in)
logInfo("INFO", "SOLARMAN.rules - Expires In (Tage): " + expiresDays)
val telegramAction = getActions("telegram","telegram:telegramBot:HA_Bot")
telegramAction.sendTelegram(Long::parseLong(TELEGRAM_CHANNEL_SMARTHOME_ADMIN.label), "SOLARMAN.rules - Expires In (Tage): " + expiresDays)
end
In dieser Regel summiere ich verschiedene Werte meiner Wechselrichter in einer Gesamtübersicht.
Außerdem schreibe ich eine Nachricht in eine Telegram-Gruppe, wenn der Token von SOLARMANPV nach ca. 90 Tagen abläuft.
Haus.sitemap
Text label="PV" icon="sun" {
Frame label="Gesamtübersicht" {
Text item=solarmanpv_realTime_generationTotal label="Gesamtproduktion (kWh) [%s]" icon="energy"
}
Frame label="PV-Anlagen & Wechselrichter" {
Text label="Bosswerk BW-HY 3600 - Haus Süd" icon="settings" {
Frame label="Stationsdaten" {
Text item=solarmanpv_stationList_xxx_name label="Name der Anlage [%s]" icon=""
Text item=solarmanpv_stationList_xxx_installedCapacity label="Installierte Kapazität (kWp) [%s]" icon=""
Text item=solarmanpv_stationList_xxx_batterySoc label="Batterie [%s]" icon=""
Text item=solarmanpv_stationList_xxx_networkStatus label="Status [%s]" icon=""
Text item=solarmanpv_stationList_xxx_generationPower label="Produktion [%s]" icon=""
Text item=solarmanpv_stationList_xxx_lastUpdateTime label="Aktualisiert [%1$td.%1$tm.%1$tY %1$tH:%1$tM:%1$tS]" icon=""
}
Frame label="Echtzeitdaten" {
Text item=solarmanpv_realTime_xxx_generationPower label="Produktion [%s]" icon=""
Text item=solarmanpv_realTime_xxx_usePower label="Verbrauch [%s]" icon=""
Text item=solarmanpv_realTime_xxx_purchasePower label="Netz [%s]" icon=""
Text item=solarmanpv_realTime_xxx_batteryPower label="Batterie [%s]" icon=""
Text item=solarmanpv_realTime_xxx_batterySoc label="Batteriestand [%s]" icon=""
Text item=solarmanpv_realTime_xxx_generationTotal label="Gesamtproduktion [%s]" icon=""
Text item=solarmanpv_realTime_xxx_lastUpdateTime label="Aktualisiert [%1$td.%1$tm.%1$tY %1$tH:%1$tM:%1$tS]" icon=""
}
Frame label="Gesamtproduktion" {
Switch item=Chart_Zeitraum_D_W_M_Y label="" mappings=[0="Tag", 1="Woche", 2="Monat", 3="Jahr"]
Chart item=solarmanpv_realTime_xxx_generationTotal service="rrd4j" period=D refresh=15000 visibility=[Chart_Zeitraum_D_W_M_Y==0, Chart_Zeitraum_D_W_M_Y=="Uninitialized"]
Chart item=solarmanpv_realTime_xxx_generationTotal service="rrd4j" period=W refresh=15000 visibility=[Chart_Zeitraum_D_W_M_Y==1]
Chart item=solarmanpv_realTime_xxx_generationTotal service="rrd4j" period=M refresh=15000 visibility=[Chart_Zeitraum_D_W_M_Y==2]
Chart item=solarmanpv_realTime_xxx_generationTotal service="rrd4j" period=Y refresh=15000 visibility=[Chart_Zeitraum_D_W_M_Y==3]
}
}
}
}
Fazit
Die Integration von SOLARMAN in openHAB war für mich etwas komplizierter. Es gab kein fertiges Binding, das ich verwenden konnte, d.h. ich musste zu einer manuellen Integration der Webservices per HTTP-Binding übergehen.
In der frühen Phase der Integration gab es dann noch das Problem, dass der Token von SOLARMAN leider nicht mit dem Binding funktionierte. Das wurde dann aber in Zusammenarbeit mit dem sehr aktiven Entwickler gelöst und in den nächsten Versionen behoben.
Momentan habe ich noch das Problem, dass der Token alle 90 Tage abläuft. Ich kann den Token zwar auslesen, aber ich habe noch keine Möglichkeit gefunden, den Token dann auch entsprechend automatisiert in den Items von openHAB zu hinterlegen (dafür schicke ich mir grob vor dem Ablaufdatum eine Nachricht und passe es noch manuell an).
Die technische Integration von SOLARMAN in openHAB ist mit den aktuellen Versionen inzwischen recht einfach möglich. Ein einfaches Beispielszenario ist schnell eingerichtet. Die Integration läuft nun seit einiger Zeit sehr stabil und ich bin sehr zufrieden damit.