openHAB und SOLARMAN: Integrationsschritte im Detail

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:

  1. Account – optionale Nutzung fĂŒr das Auslesen der Token (funktionktioniert, erfordert aber manuelle Eingriffe alle 90 Tage)
  2. 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.

0 Kommentare

Hinterlasse einen Kommentar

An der Diskussion beteiligen?
Hinterlasse uns deinen Kommentar!

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

Diese Website verwendet Akismet, um Spam zu reduzieren. Erfahre mehr darĂŒber, wie deine Kommentardaten verarbeitet werden.