diff --git a/fhem/FHEM/98_vitoconnect.pm b/fhem/FHEM/98_vitoconnect.pm index d40aaf474..0f4539d39 100644 --- a/fhem/FHEM/98_vitoconnect.pm +++ b/fhem/FHEM/98_vitoconnect.pm @@ -18,194 +18,65 @@ # You should have received a copy of the GNU General Public License # along with fhem. If not, see . # -############################################################################## -# Changelog: -# -# 2018-11-24 initial version -# 2018-12-11 non-blocking -# Reading "status" in "state" umbenannt -# 2018-12-23 Neue Werte in der API werden unter ihrem JSON Name als Reading eingetragen -# Neue Readings: -# heating.boiler.sensors.temperature.commonSupply.status error -# heating.boiler.temperature.value 48.1 -# heating.burner.modulation.value 11 -# heating.burner.statistics.hours 933.336666666667 -# heating.burner.statistics.starts 2717 -# heating.circuits.0.circulation.pump.status on -# heating.dhw.charging.active 0 -# heating.dhw.pumps.circulation.schedule.active 1 -# heating.dhw.pumps.circulation.schedule.entries sun mode:on end:22:30 start:04:30 position:0, fri end:22:30 mode:on position:0 start:04:30, -# mon mode:on end:22:30 start:04:30 position:0, -# wed start:04:30 position:0 end:22:30 mode:on, thu mode:on end:22:30 position:0 start:04:30, sat end:22:30 mode:on position:0 start:04:30, -# tue position:0 start:04:30 end:22:30 mode:on, -# heating.dhw.pumps.circulation.status on -# heating.dhw.pumps.primary.status off -# heating.dhw.sensors.temperature.outlet.status error -# heating.dhw.temperature.main.value 53 -# 2018-12-30 initial offical release -# remove special characters from readings -# some internal improvements suggested by CoolTux -# 2019-01-01 "disabled" implemented -# "set update implemented -# renamed "WW-onTimeCharge_aktiv" into "WW-einmaliges_Aufladen_aktiv" -# Attribute vitoconnect_raw_readings:0,1 " and ."vitoconnect_actions_active:0,1 " implemented -# "set clearReadings" implemented -# 2019-01-05 Passwort wird im KeyValue gespeichert statt im Klartext -# Action "oneTimeCharge" implemented -# 2019-01-14 installation, code and gw in den Internals unsichtbar gemacht -# Reading "counter" entfernt (ist weiterhin in Internals sichtbar) -# Reading WW-einmaliges_Aufladen_active umbenannt in WW-einmaliges_Aufladen -# Befehle zum setzen von -# HK1-Betriebsart -# HK2-Betriebsart -# HK1-Solltemperatur_normal -# HK2-Solltemperatur_normal -# HK1-Solltemperatur_reduziert -# HK2-Solltemperatur_reduziert -# WW-einmaliges_Aufladen -# Bedienfehler (z.B. Ausführung einer Befehls für HK2, wenn die Hezung nur einen Heizkreis hat) -# führen zu einem "Bad Gateway" Fehlermeldung in Logfile -# Achtung: Keine Prüfung ob Befehle sinnvoll und oder erlaubt sind! Nutzung auf eigene Gefahr! -# 2019-01-15 Fehler bei der Befehlsausführung gefixt -# 2019-01-22 Klartext für Readings für HK3 und heating.dhw.charging.level.* hinzugefügt -# set's für HK2 implementiert -# set für Slope und Shift implementiert -# set WW-Haupttemperatur und WW-Solltemperatur implementiert -# set HK1-Solltemperatur_comfort_aktiv HK1-Solltemperatur_comfort implementiert -# set HK1-Solltemperatur_eco implementiert (set HK1-Solltemperatur_eco_aktiv scheint es nicht zu geben?!) -# vor einem set vitoconnect update den alten Timer löschen -# set vitoconnect logResponseOnce implementiert (eventuell werden zusätzliche perl Pakete benötigt?) -# 2019-01-26 Fehler, dass HK3 Readings auf HK2 gemappt wurden gefixt -# 2019-02-17 Readings für den Stromverbrauch (heating.power.consumption.*) und -# Raumtemperatur (heating.circuits.?.sensors.temperature.room.value) ergänzt -# set-Befehle für HKs werden nur noch angezeigt, wenn der HK auch aktiv ist -# Wiki aktualisiert -# 2019-02-27 stacktrace-Fehler (hoffentlich) behoben -# Betriebsarten "heating" und "active" ergänzt -# 2019-03-02 Readings für heating.boiler.sensors.temperature.commonSupply.value und -# heating.circuits.1.operating.modes.heating.active hinzugefügt -# Typo fixed ("Brenner_Be-t-riebsstunden") -# 2019-03-29 neue Readings: -# heating.circuits.1.operating.modes.dhwAndHeatingCooling.active 1 -# heating.circuits.1.operating.modes.normalStandby.active 0 -# heating.circuits.1.operating.programs.fixed.active 0 -# heating.compressor.active 0 -# heating.dhw.temperature.hysteresis.value 5 -# heating.dhw.temperature.temp2.value 60 -# Passwort wird bei "define" nur noch gesetzt, wenn noch kein Passwort gespeichert war -# Attribut "model" implementiert -# 2019-04-26 neue Readings für -# heating.gas.consumption.dhw.unit kilowattHour -# heating.gas.consumption.heating.unit kilowattHour -# heating.power.consumption.unit kilowattHour -# Typo in WW-Zirkulationspumpe_Zeitsteuerung_aktiv fixt -# 2019-06-01 neue Readings für -# heating.solar.power.production.day 3.984,3.797,5.8,5.5,6.771,5.77,5.441,9.477 -# heating.solar.power.production.month -# heating.solar.power.production.unit kilowattHour -# heating.solar.power.production.week -# heating.solar.power.production.year -# heating.circuits.X.name (wird im Moment noch nicht von der API gefüllt!) -# Format der "Schedule" Readings in JSON geändert -# das Format von HKx-Urlaub_Start und _Ende ist jetzt YYYY-MM-TT. -# Wenn noch kein Urlaub aktiviert wurde, wird bei -# HKx-Urlaub_Start das Datum für _Ende auf den Folgetag gesetzt -# Dafür werden die Perl Module DateTime, Time:Piece und Time::Seconds -# benötigt (installieren mit apt install libdatetime-perl!) -# -# 2019-08-11 Dokumentation aktualisiert -# Das Reading 'stat' zeigt jetzt den "aggregatedStatus" an, der von der API geliefert wird -# Bsp: "Offline", "WorksProperly" -# Readings werden nur noch aktualisiert (und ein entsprechendes Event erzeugt), -# wenn sich ihr Wert geändert hat. "state" wird immer aktualisiert. -# Reading für Solarunterstützung hinzugefügt: -# "heating.solar.active" => "Solar_aktiv", -# "heating.solar.pumps.circuit.status" => "Solar_Pumpe_Status", -# "heating.solar.rechargeSuppression.status" => "Solar_Aufladeunterdrueckung_Status", -# "heating.solar.sensors.power.status" => "Solar_Sensor_Power_Status", -# "heating.solar.sensors.power.value" => "Solar_Sensor_Power", -# "heating.solar.sensors.temperature.collector.status" => "Solar_Sensor_Temperatur_Kollektor_Status", -# "heating.solar.sensors.temperature.collector.value" => "Solar_Sensor_Temperatur_Kollektor", -# "heating.solar.sensors.temperature.dhw.status" => "Solar_Sensor_Temperatur_WW_Status", -# "heating.solar.sensors.temperature.dhw.value" => "Solar_Sensor_Temperatur_WW", -# "heating.solar.statistics.hours" => "Solar_Sensor_Statistik_Stunden" -# ErrorListChanges (Fehlereintraege_Historie und Fehlereintraege_aktive) werden jetzt im JSON -# JSON Format ausgegeben (z.B.: "{"new":[],"current":[],"gone":[]}") -# -# 2019-09-07 Readings werden wieder erzeugt auch wenn sich der Wert nicht ändert -# -# 2019-11-23 Readings für "heating.power.consumption.total.*" hinzugefügt. Scheint identisch mit "heating.power.consumption.*" -# Behoben: Readings wurden nicht mehr aktualisiert, wenn in getResourceCallback die Resource nicht als JSON interpretiert werden konnte (Forum: #390) -# Behoben: vitoconnect bringt FHEM zum Absturz in Zeile 1376 (Forum: #391) -# Überwachung der Aktualität: Zeitpunkt des letzten Updates wird in State angezeigt (Forum #397) -# -# 2019-12-25 heating.solar.power.cumulativeProduced.value, heating.circuits.X.geofencing.active, heating.circuits.X.geofencing.status hinzugefügt -# Behoben: Readings wurden nicht mehr aktualisiert, wenn Resource an weiteren Stellen nicht als JSON interpretiert werden konnte(Forum: #390) -# -# 2020-03-02 Bei Aktionen wird nicht mehr auf defined($data) sondern auf ne "" getestet. -# 2020-04-05 s.o. 2. Versuch -# -# 2020-04-09 my $dir = path(AttrVal("global","logdir","log")); -# -# 2020-04-17 "Viessmann" Tippfehler gefixt -# Prototypen und "undef"s entfernt -# -# 2020-04-22 Reading heating.boiler.temperature.unit heating.operating.programs.holiday.active -# heating.operating.programs.holiday.end heating.operating.programs.holiday.start -# set Befehle hinzugefügt: Urlaub_Start, Urlaub_Ende, Urlaub_unschedule -# HKx-Name, HKx-Zeitsteuerung_Heizung, WW-Zeitplan, WW-Zirkulationspumpe_Zeitplan -# -# 2020-04-23 Refactoring (kein Einloggen mehr beim Ausführen einer Aktion) -# 2020-05-20 Neue Readings: -# heating.boiler.sensors.temperature.main.unit celsius -# heating.circuits.0.sensors.temperature.supply.unit celsius -# heating.dhw.sensors.temperature.hotWaterStorage.unit celsius -# heating.dhw.sensors.temperature.outlet.unit celsius -# heating.sensors.temperature.outside.unit celsius -# Fehlerbehandlung verbessert -# nur noch einloggen, wenn nötig (Token läuft nach 1h aus.) -# -# 2020-06-25 Fehlerbehandlung für API (statusCode 401 (UNAUTHORIZED), 404 (DEVICE_NOT_FOUND) -# und 429 (RATE_LIMIT_EXCEEDED) und 502 (DEVICE_COMMUNICATION_ERROR) -# Neue Readings für Vitodens 200-W B2HF-19 und Brennstoffzelle von Viessmann (PA2) -# Information aus dem GW auslesen (Attribut "vitoconnect_gw_readings" auf "1" setzen; -# noch unvollständig!) -# -# 2020-07-06 readings for heating.power.production.demandCoverage.* fixed -# bei logResponseOnce wird bei getCode angefangen damit auch gw.json neu erzeugt wird -# -# 2020-11-26 Bugfix für einige "set"-Kommandos für HK2 und HK3 -# -# 2020-12-21 Neue Readings "heating.power.production.current.status" => "Stromproduktion_aktueller_Status", -# "heating.power.production.current.value" => "Stromproduktion", -# "heating.sensors.power.output.status" => "Sensor_Stromproduktion_Status", -# "heating.sensors.power.output.value" => "Sensor_Stromproduktion" und -# "heating.circuits.X.operating.programs.Y.demand" => -# "HK(X+1)-Solltemperatur_Y_Anforderung" (X=0,1,2 und Y=normal,reduced,comfort) -# 2021-02-21 Umstieg auf Endpoint v2 zur Authorization -# *experimentell* Attribut vitoconnect_device -# Workaround für Forum #561 -# Neue Readings für "*ValueReadAt" -# -# 2021-07-19 Anpassungen für privaten apiKey. Redirect URIs muss "http://localhost:4200/" sein. -# Nutzung des refresh_token -# -# 2021-07-19 neue Readings für heating.burners.0.* -# -# ToDo: timeout, intervall konfigurierbar machen -# Attribute implementieren und dokumentieren -# Mehrsprachigkeit -# Auswerten der Readings in getCode usw. -# devices/0 ? Was, wenn es mehrere Devices gibt? -# nach einem set Befehl Readings aktualisieren, vorher alten Timer löschen -# heating.circuits.0.operating.programs.holiday.changeEndDate action: end implementieren? -# + +# https://wiki.fhem.de/wiki/DevelopmentModuleAPI +# https://forum.fhem.de/index.php/topic,93664.0.html +# https://www.viessmann-community.com/t5/Announcements/Important-adjustment-in-IoT-features-Split-heating-circuits-and/td-p/281527 +# https://forum.fhem.de/index.php/topic,93664.msg1257651.html#msg1257651 +# https://www.viessmann-community.com/t5/Getting-started-programming-with/Syntax-for-setting-a-value/td-p/374222 +# https://forum.fhem.de/index.php?msg=1326376 + +sub vitoconnect_Initialize; # Modul initialisieren und Namen zusätzlicher Funktionen bekannt geben +sub vitoconnect_Define; # wird beim 'define' eines Gerätes aufgerufen +sub vitoconnect_Undef; # wird beim Löschen einer Geräteinstanz aufgerufen +sub vitoconnect_Get; # bisher kein 'get' implementiert +sub vitoconnect_Set; # Implementierung set-Befehle +sub vitoconnect_Set_New; # Implementierung set-Befehle New dynamisch auf raw readings +sub vitoconnect_Set_SVN; # Implementierung set-Befehle SVN +sub vitoconnect_Set_Roger; # Implementierung set-Befehle Roger +sub vitoconnect_Attr; # Attribute setzen/ändern/löschen + +sub vitoconnect_GetUpdate; # Abfrage aller Werte starten + +sub vitoconnect_getCode; # Werte für: Access-Token, Install-ID, Gateway anfragen +sub vitoconnect_getCodeCallback; # Rückgabe: Access-Token, Install-ID, Gateway von vitoconnect_getCode Anfrage + +sub vitoconnect_getAccessToken; # Access & Refresh-Token holen +sub vitoconnect_getAccessTokenCallback; # Access & Refresh-Token speichern, Antwort auf: vitoconnect_getAccessToken + +sub vitoconnect_getRefresh; # neuen Access-Token anfragen +sub vitoconnect_getRefreshCallback; # neuen Access-Token speichern + +sub vitoconnect_getGw; # Abfrage Gateway-Serial +sub vitoconnect_getGwCallback; # Gateway-Serial speichern, Anwort von Abfrage Gateway-Serial + +sub vitoconnect_getInstallation; # Abfrage Install-ID +sub vitoconnect_getInstallationCallback;# Install-ID speichern, Antwort von Abfrage Install-ID + +sub vitoconnect_getDevice; # Abfrage Device-ID +sub vitoconnect_getDeviceCallback; # Device-ID speichern, Anwort von Abfrage Device-ID + +sub vitoconnect_getFeatures; # Abruf GW Features +sub vitoconnect_getFeaturesCallback; # gw_features speichern + +sub vitoconnect_errorHandling; # Errors bearbeiten für alle Calls +sub vitoconnect_getResource; # API call for all Gateways +sub vitoconnect_getResourceCallback; # Get all API readings +sub vitoconnect_getPowerLast; # Write the power reading of the full last day to the DB + +sub vitoconnect_action; # + +sub vitoconnect_StoreKeyValue; # Werte verschlüsselt speichern +sub vitoconnect_ReadKeyValue; # verschlüsselte Werte auslesen +sub DeleteKeyValue; # verschlüsselte Werte löschen + package main; use strict; use warnings; use Time::HiRes qw(gettimeofday); use JSON; +use JSON::XS qw( decode_json ); use HttpUtils; use Encode qw(decode encode); use Data::Dumper; @@ -214,12 +85,48 @@ use DateTime; use Time::Piece; use Time::Seconds; +eval "use FHEM::Meta;1" or my $modMetaAbsent = 1; ## no critic 'eval' +use FHEM::SynoModules::SMUtils qw ( + moduleVersion + ); # Hilfsroutinen Modul + +my %vNotesIntern = ( + "0.6.2" => "28.01.2025 Very small bugfixes ", + "0.6.1" => "28.01.2025 Rework of module documentation", + "0.6.0" => "23.01.2025 Total rebuild of initialization and gw handling. In case of more than one installation or gw you have to set it via". + "selectDevice in the set of the device. The attributes vitoconnect_serial and vitoconnect_installationID will be populated". + "handling of getting installation and serial changed. StoredValues are now deleted. Other fixes and developments", + "0.5.0" => "02.01.2025 Added attribute installationID, in case you use two installations, see https://forum.fhem.de/index.php?msg=1329165", + "0.4.2" => "31.12.2024 Small fix for Vitoladens 300C, heating.circuits.0.operating.programs.comfort", + "0.4.1" => "30.12.2024 Bug fixes, fixed Releasenotes, changed debugging texts and messages in Set_New", + "0.4.0" => "28.12.2024 Fixed setNew to work again automatically in case of one serial in gateways,". + "for more than one serial you have to define the serial you want to use", + "0.3.2" => "27.12.2024 Set in case of activate and deactivate request the active value of the reading", + "0.3.1" => "19.12.2024 New attribute vitoconnect_disable_raw_readings", + "0.3.0" => "18.12.2024 Fix setter new for cases where more than one gateway is actively pulled in 2 devices.", + "0.2.1" => "16.12.2024 German and English texts in UI", + "0.2.0" => "14.12.2024 FVersion introduced, a bit of code beautifying". + "sort keys per reading to ensure power readings are in the right order, day before dayvalue", + "0.1.1" => "12.12.2024 In case of more than one Gateway only allow Set_New if serial is provided. ". + "Get Object and Hash in Array readings. E.g. device.messages.errors.raw. ". + "In case of expired token (every hour) do not do uncessary gateway calls, just get the new token. ". + "This will safe API calls and reduce the API overhead. ", + "0.1.0" => "12.12.2024 first release with Version. " +); + my $client_secret = "2e21faa1-db2c-4d0b-a10f-575fd372bc8c-575fd372bc8c"; my $callback_uri = "http://localhost:4200/"; -my $apiURLBase = "https://api.viessmann-platform.io/iot/v1/equipment/"; my $apiURL = "https://api.viessmann.com/iot/v1/equipment/"; +my $iotURL_V1 = "https://api.viessmann.com/iot/v1/equipment/"; +my $iotURL_V2 = "https://api.viessmann.com/iot/v2/features/"; -my $RequestList = { +my $RequestListMapping; # Über das Attribut Mapping definierte Readings zum überschreiben der RequestList +my %translations; # Über das Attribut translations definierte Readings zum überschreiben der RequestList + + +# Feste Readings, orignal Verhalten des Moduls, können über RequestListMapping oder translations überschrieben werden. +# letzte SVN Version vor meinen Änderungen am 2024-11-16 oder letzte Version von Roger vom 8. November (https://forum.fhem.de/index.php?msg=1292441). +my $RequestListSvn = { "heating.boiler.serial.value" => "Kessel_Seriennummer", "heating.boiler.temperature.value" => "Kessel_Solltemperatur", "heating.boiler.sensors.temperature.commonSupply.status" => @@ -812,39 +719,579 @@ my $RequestList = { "heating.solar.power.production.year" => "Solarproduktion/Jahr" }; +my $RequestListRoger = { + "device.serial.value" => "Seriennummer", + "device.messages.errors.raw.entries" => "Fehlermeldungen", + + "heating.boiler.serial.value" => "Kessel_Seriennummer", + "heating.boiler.temperature.value" => "Kessel_Solltemp__C", + "heating.boiler.sensors.temperature.commonSupply.status" => "Kessel_Common_Supply", + "heating.boiler.sensors.temperature.commonSupply.unit" => "Kessel_Common_Supply_Temp_Einheit", + "heating.boiler.sensors.temperature.commonSupply.value" => "Kessel_Common_Supply_Temp__C", + "heating.boiler.sensors.temperature.main.status" => "Kessel_Status", + "heating.boiler.sensors.temperature.main.value" => "Kessel_Temp__C", + "heating.boiler.sensors.temperature.main.unit" => "Kessel_Temp_Einheit", + "heating.boiler.temperature.unit" => "Kesseltemp_Einheit", + + "heating.device.time.offset.value" => "Device_Time_Offset", + "heating.sensors.temperature.outside.status" => "Aussen_Status", + "heating.sensors.temperature.outside.unit" => "Temp_aussen_Einheit", + "heating.sensors.temperature.outside.value" => "Temp_aussen__C", + + "heating.burners.0.active" => "Brenner_1_aktiv", + "heating.burners.0.statistics.starts" => "Brenner_1_Starts", + "heating.burners.0.statistics.hours" => "Brenner_1_Betriebsstunden__h", + "heating.burners.0.modulation.value" => "Brenner_1_Modulation__Prz", + "heating.burners.0.modulation.unit" => "Brenner_1_Modulation_Einheit", + + + + "heating.burner.active" => "Brenner_aktiv", + "heating.burner.automatic.status" => "Brenner_Status", + "heating.burner.automatic.errorCode" => "Brenner_Fehlercode", + "heating.burner.current.power.value" => "Brenner_Leistung", + "heating.burner.modulation.value" => "Brenner_Modulation", + "heating.burner.statistics.hours" => "Brenner_Betriebsstunden__h", + "heating.burner.statistics.starts" => "Brenner_Starts", + + "heating.sensors.volumetricFlow.allengra.status" => "Heiz_Volumenstrom_Status", + "heating.sensors.volumetricFlow.allengra.value" => "Heiz_Volumenstrom__l/h", + + "heating.circuits.enabled" => "aktive_Heizkreise", + "heating.circuits.0.name" => "HK1_Name", + "heating.circuits.0.operating.modes.active.value" => "HK1_Betriebsart", + "heating.circuits.0.active" => "HK1_aktiv", + "heating.circuits.0.type" => "HK1_Typ", + "heating.circuits.0.circulation.pump.status" => "HK1_Zirkulationspumpe", + "heating.circuits.0.circulation.schedule.active" => "HK1_Zeitsteuerung_Zirkulation_aktiv", + "heating.circuits.0.circulation.schedule.entries" => "HK1_Zeitsteuerung_Zirkulation", + "heating.circuits.0.frostprotection.status" => "HK1_Frostschutz_Status", + "heating.circuits.0.geofencing.active" => "HK1_Geofencing", + "heating.circuits.0.geofencing.status" => "HK1_Geofencing_Status", + "heating.circuits.0.heating.curve.shift" => "HK1_Heizkurve_Niveau", + "heating.circuits.0.heating.curve.slope" => "HK1_Heizkurve_Steigung", + "heating.circuits.0.heating.schedule.active" => "HK1_Zeitsteuerung_Heizung_aktiv", + "heating.circuits.0.heating.schedule.entries" => "HK1_Zeitsteuerung_Heizung", + + "heating.circuits.0.operating.modes.dhwAndHeatingCooling.active" => "HK1_WW_und_Heizen_Kuehlen_aktiv", + "heating.circuits.0.operating.modes.forcedNormal.active" => "HK1_Soll_Temp_erzwungen", + "heating.circuits.0.operating.modes.forcedReduced.active" => "HK1_Reduzierte_Temp_erzwungen", + "heating.circuits.0.operating.modes.heating.active" => "HK1_heizen_aktiv", + "heating.circuits.0.operating.modes.normalStandby.active" => "HK1_Normal_Standby_aktiv", + "heating.circuits.0.operating.modes.standby.active" => "HK1_Standby_aktiv", + "heating.circuits.0.operating.programs.active.value" => "HK1_Programmstatus", + "heating.circuits.0.operating.programs.comfort.active" => "HK1_Soll_Temp_comfort_aktiv", + "heating.circuits.0.operating.programs.comfort.demand" => "HK1_Soll_Temp_comfort_Anforderung", + "heating.circuits.0.operating.programs.comfort.temperature" => "HK1_Soll_Temp_comfort__C", + "heating.circuits.0.operating.programs.eco.active" => "HK1_Soll_Temp_eco_aktiv", + "heating.circuits.0.operating.programs.eco.temperature" => "HK1_Soll_Temp_eco__C", + "heating.circuits.0.operating.programs.external.active" => "HK1_External_aktiv", + "heating.circuits.0.operating.programs.external.temperature" => "HK1_External_Temp", + "heating.circuits.0.operating.programs.fixed.active" => "HK1_Fixed_aktiv", + "heating.circuits.0.operating.programs.forcedLastFromSchedule.active" => "HK1_forcedLastFromSchedule_aktiv", + "heating.circuits.0.operating.programs.holidayAtHome.active" => "HK1_HolidayAtHome_aktiv", + "heating.circuits.0.operating.programs.holidayAtHome.end" => "HK1_HolidayAtHome_Ende", + "heating.circuits.0.operating.programs.holidayAtHome.start" => "HK1_HolidayAtHome_Start", + "heating.circuits.0.operating.programs.holiday.active" => "HK1_Urlaub_aktiv", + "heating.circuits.0.operating.programs.holiday.start" => "HK1_Urlaub_Start_Zeit", + "heating.circuits.0.operating.programs.holiday.end" => "HK1_Urlaub_Ende_Zeit", + "heating.circuits.0.operating.programs.normal.active" => "HK1_Soll_Temp_aktiv", + "heating.circuits.0.operating.programs.normal.demand" => "HK1_Soll_Temp_Anforderung", + "heating.circuits.0.operating.programs.normal.temperature" => "HK1_Soll_Temp_normal", + "heating.circuits.0.operating.programs.reduced.active" => "HK1_Soll_Temp_reduziert_aktiv", + "heating.circuits.0.operating.programs.reduced.demand" => "HK1_Soll_Temp_reduziert_Anforderung", + "heating.circuits.0.operating.programs.reduced.temperature" => "HK1_Soll_Temp_reduziert", + "heating.circuits.0.operating.programs.summerEco.active" => "HK1_Soll_Temp_SummerEco_aktiv", + "heating.circuits.0.operating.programs.standby.active" => "HK1_Standby_aktiv", + "heating.circuits.0.zone.mode.active" => "HK1_ZoneMode_aktive", + "heating.circuits.0.sensors.temperature.room.status" => "HK1_Raum_Status", + "heating.circuits.0.sensors.temperature.room.value" => "HK1_Raum_Temp", + "heating.circuits.0.sensors.temperature.supply.status" => "HK1_Vorlauf_Temp_Status", + "heating.circuits.0.sensors.temperature.supply.unit" => "HK1_Vorlauf_Temp_Einheit", + "heating.circuits.0.sensors.temperature.supply.value" => "HK1_Vorlauf_Temp__C", + "heating.circuits.0.zone.mode.active" => "HK1_ZoneMode_aktive", + + "heating.dhw.operating.modes.active.value" => "WW_Betriebsart", + "heating.dhw.operating.modes.balanced.active" => "WW_Betriebsart_balanced", + "heating.dhw.operating.modes.off.active" => "WW_Betriebsart_off", + "heating.dhw.temperature.main.value" => "WW_Temp_Soll__C", + "heating.dhw.sensors.temperature.hotWaterStorage.value" => "WW_Temp_Ist__C", + "heating.dhw.sensors.temperature.hotWaterStorage.unit" => "WW_Temp_Ist_Einheit", + "heating.dhw.oneTimeCharge.active" => "WW_einmaliges_Aufladen", + "heating.dhw.sensors.temperature.dhwCylinder.value" => "WW_Temp__C", + "heating.dhw.sensors.temperature.dhwCylinder.status" => "WW_Temp_Status", + "heating.dhw.hygiene.active" => "WW_Hygiene_laeft", + "heating.dhw.hygiene.enabled" => "WW_Hygiene_enabled", + "heating.dhw.hygiene.trigger.startHour" => "WW_Hygiene_Start__hh", + "heating.dhw.hygiene.trigger.startMinute" => "WW_Hygiene_Start__mm", + "heating.dhw.hygiene.trigger.weekdays" => "WW_Hygiene_Start__dd", + "heating.dhw.temperature.hygiene.value" => "WW_Hygiene_Temp__C", + + "heating.dhw.pumps.circulation.schedule.active" => "WW_Zirkulationspumpe_Zeitsteuerung_aktiv", + "heating.dhw.pumps.circulation.schedule.entries" => "WW_Zirkulationspumpe_Zeitplan", + "heating.dhw.pumps.circulation.status" => "WW_Zirkulationspumpe_Status", + "heating.dhw.pumps.primary.status" => "WW_Zirkulationspumpe_primaer", + "heating.dhw.sensors.temperature.outlet.status" => "WW_Sensoren_Auslauf_Status", + "heating.dhw.sensors.temperature.outlet.unit" => "WW_Sensoren_Auslauf_Wert_Einheit", + "heating.dhw.sensors.temperature.outlet.value" => "WW_Sensoren_Auslauf_Wert", + "heating.dhw.temperature.hysteresis.value" => "WW_Hysterese", + "heating.dhw.sensors.temperature.hotWaterStorage.status" => "WW_Temp_aktiv", +# "heating.dhw.temperature.value" => "WW_Solltemp__C", + "heating.dhw.schedule.active" => "WW_zeitgesteuert_aktiv", + "heating.dhw.schedule.entries" => "WW_Zeitplan", + "heating.dhw.temperature.temp2.value" => "WW_Temp2__C", + + "heating.gas.consumption.summary.dhw.currentDay" => "Gas_WW_Day__m3", + "heating.gas.consumption.summary.dhw.lastSevenDays" => "Gas_WW_7dLast__m3", + "heating.gas.consumption.summary.dhw.currentMonth" => "Gas_WW_Month__m3", + "heating.gas.consumption.summary.dhw.lastMonth" => "Gas_WW_MonthLast__m3", + "heating.gas.consumption.summary.dhw.currentYear" => "Gas_WW_Year__m3", + "heating.gas.consumption.summary.dhw.lastYear" => "Gas_WW_YearLast__m3", + + "heating.gas.consumption.summary.heating.currentDay" => "Gas_Day__m3", + "heating.gas.consumption.summary.heating.lastSevenDays" => "Gas_7dLast__m3", + "heating.gas.consumption.summary.heating.currentMonth" => "Gas_Month__m3", + "heating.gas.consumption.summary.heating.lastMonth" => "Gas_MonthLast__m3", + "heating.gas.consumption.summary.heating.currentYear" => "Gas_Year__m3", + "heating.gas.consumption.summary.heating.lastYear" => "Gas_YearLast__m3", + + "heating.gas.consumption.dhw.day" => "Gas_WW_Tage__m3", + "heating.gas.consumption.dhw.dayValueReadAt" => "Gas_WW_Tage_Zeit", + "heating.gas.consumption.dhw.week" => "Gas_WW_Wochen__m3", + "heating.gas.consumption.dhw.weekValueReadAt" => "Gas_WW_Wochen_Zeit", + "heating.gas.consumption.dhw.month" => "Gas_WW_Monate__m3", + "heating.gas.consumption.dhw.monthValueReadAt" => "Gas_WW_Monate_Zeit", + "heating.gas.consumption.dhw.year" => "Gas_WW_Jahre__m3", + "heating.gas.consumption.dhw.yearValueReadAt" => "Gas_WW_Jahre_Zeit", + "heating.gas.consumption.dhw.unit" => "Gas_WW_Einheit", + + "heating.gas.consumption.heating.day" => "Gas_Heiz_Tage__m3", + "heating.gas.consumption.heating.dayValueReadAt" => "Gas_Heiz_Tage_Zeit", + "heating.gas.consumption.heating.week" => "Gas_Heiz_Wochen__m3", + "heating.gas.consumption.heating.weekValueReadAt" => "Gas_Heiz_Wochen_Zeit", + "heating.gas.consumption.heating.month" => "Gas_Heiz_Monate__m3", + "heating.gas.consumption.heating.monthValueReadAt" => "Gas_Heiz_Monate_Zeit", + "heating.gas.consumption.heating.year" => "Gas_Heiz_Jahre__m3", + "heating.gas.consumption.heating.yearValueReadAt" => "Gas_Heiz_Jahre_Zeit", + "heating.gas.consumption.heating.unit" => "Gas_Heiz_Einheit", + + "heating.gas.consumption.total.day" => "Gas_Total_Tage__m3", + "heating.gas.consumption.total.dayValueReadAt" => "Gas_Total_Tage_Zeit", + "heating.gas.consumption.total.week" => "Gas_Total_Wochen__m3", + "heating.gas.consumption.total.weekValueReadAt" => "Gas_Total_Wochen_Zeit", + "heating.gas.consumption.total.month" => "Gas_Total_Monate__m3", + "heating.gas.consumption.total.monthValueReadAt" => "Gas_Total_Monate_Zeit", + "heating.gas.consumption.total.year" => "Gas_Total_Jahre__m3", + "heating.gas.consumption.total.yearValueReadAt" => "Gas_Total_Jahre_Zeit", + "heating.gas.consumption.total.unit" => "Gas_Total_Einheit", + + "heating.power.consumption.summary.dhw.currentDay" => "Strom_WW_Day__kWh", + "heating.power.consumption.summary.dhw.lastSevenDays" => "Strom_WW_7dLast__kWh", + "heating.power.consumption.summary.dhw.currentMonth" => "Strom_WW_Month__kWh", + "heating.power.consumption.summary.dhw.lastMonth" => "Strom_WW_MonthLast__kWh", + "heating.power.consumption.summary.dhw.currentYear" => "Strom_WW_Year__kWh", + "heating.power.consumption.summary.dhw.lastYear" => "Strom_WW_YearLast__kWh", + + "heating.power.consumption.summary.heating.currentDay" => "Strom_Heiz_Day__kWh", + "heating.power.consumption.summary.heating.lastSevenDays" => "Strom_Heiz_7dLast__kWh", + "heating.power.consumption.summary.heating.currentMonth" => "Strom_Heiz_Month__kWh", + "heating.power.consumption.summary.heating.lastMonth" => "Strom_Heiz_MonthLast__kWh", + "heating.power.consumption.summary.heating.currentYear" => "Strom_Heiz_Year__kWh", + "heating.power.consumption.summary.heating.lastYear" => "Strom_Heiz_YearLast__kWh", + + "heating.circuits.3.heating.curve.shift" => "HK4_Heizkurve_Niveau", + "heating.circuits.3.heating.curve.slope" => "HK4_Heizkurve_Steigung", + "heating.circuits.3.geofencing.active" => "HK4_Geofencing", + "heating.circuits.3.geofencing.status" => "HK4_Geofencing_Status", + "heating.circuits.3.operating.programs.summerEco.active" => "HK4_Solltemperatur_SummerEco_aktiv", + "heating.circuits.3.zone.mode.active" => "HK4_ZoneMode_aktive", + + + "heating.circuits.1.active" => "HK2_aktiv", + "heating.circuits.1.type" => "HK2_Typ", + "heating.circuits.1.circulation.pump.status" => "HK2_Zirkulationspumpe", + "heating.circuits.1.circulation.schedule.active" => "HK2_Zeitsteuerung_Zirkulation_aktiv", + "heating.circuits.1.circulation.schedule.entries" => "HK2_Zeitsteuerung_Zirkulation", + "heating.circuits.1.frostprotection.status" => "HK2_Frostschutz_Status", + "heating.circuits.1.geofencing.active" => "HK2_Geofencing", + "heating.circuits.1.geofencing.status" => "HK2_Geofencing_Status", + "heating.circuits.1.heating.curve.shift" => "HK2_Heizkurve_Niveau", + "heating.circuits.1.heating.curve.slope" => "HK2_Heizkurve_Steigung", + "heating.circuits.1.heating.schedule.active" => "HK2_Zeitsteuerung_Heizung_aktiv", + "heating.circuits.1.heating.schedule.entries" => "HK2_Zeitsteuerung_Heizung", + "heating.circuits.1.name" => "HK2_Name", + "heating.circuits.1.operating.modes.active.value" => "HK2_Betriebsart", + "heating.circuits.1.operating.modes.dhw.active" => "HK2_WW_aktiv", + "heating.circuits.1.operating.modes.dhwAndHeating.active" => "HK2_WW_und_Heizen_aktiv", + "heating.circuits.1.operating.modes.dhwAndHeatingCooling.active" => "HK2_WW_und_Heizen_Kuehlen_aktiv", + "heating.circuits.1.operating.modes.forcedNormal.active" => "HK2_Solltemperatur_erzwungen", + "heating.circuits.1.operating.modes.forcedReduced.active" => "HK2_Reduzierte_Temperatur_erzwungen", + "heating.circuits.1.operating.modes.heating.active" => "HK2_heizen_aktiv", + "heating.circuits.1.operating.modes.normalStandby.active" => "HK2_Normal_Standby_aktiv", + "heating.circuits.1.operating.modes.standby.active" => "HK2_Standby_aktiv", + "heating.circuits.1.operating.programs.active.value" => "HK2_Programmstatus", + "heating.circuits.1.operating.programs.comfort.active" => "HK2_Solltemperatur_comfort_aktiv", + "heating.circuits.1.operating.programs.comfort.demand" => + "HK2-Solltemperatur_comfort_Anforderung", + "heating.circuits.1.operating.programs.comfort.temperature" => + "HK2-Solltemperatur_comfort", + "heating.circuits.1.operating.programs.eco.active" => + "HK2-Solltemperatur_eco_aktiv", + "heating.circuits.1.operating.programs.eco.temperature" => + "HK2-Solltemperatur_eco", + "heating.circuits.1.operating.programs.external.active" => + "HK2-External_aktiv", + "heating.circuits.1.operating.programs.external.temperature" => + "HK2-External_Temperatur", + "heating.circuits.1.operating.programs.fixed.active" => "HK2-Fixed_aktiv", + "heating.circuits.1.operating.programs.forcedLastFromSchedule.active" => + "HK2-forcedLastFromSchedule_aktiv", + "heating.circuits.1.operating.programs.holidayAtHome.active" => + "HK2-HolidayAtHome_aktiv", + "heating.circuits.1.operating.programs.holidayAtHome.end" => "HK2-HolidayAtHome_Ende", + "heating.circuits.1.operating.programs.holidayAtHome.start" => "HK2-HolidayAtHome_Start", + "heating.circuits.1.operating.programs.holiday.active" => "HK2_Urlaub_aktiv", + "heating.circuits.1.operating.programs.holiday.start" => "HK2_Urlaub_Start_Zeit", + "heating.circuits.1.operating.programs.holiday.end" => "HK2_Urlaub_Ende_Zeit", + "heating.circuits.1.operating.programs.normal.active" => + "HK2-Solltemperatur_aktiv", + "heating.circuits.1.operating.programs.normal.demand" => + "HK2-Solltemperatur_Anforderung", + "heating.circuits.1.operating.programs.normal.temperature" => + "HK2-Solltemperatur_normal", + "heating.circuits.1.operating.programs.reduced.active" => + "HK2-Solltemperatur_reduziert_aktiv", + "heating.circuits.1.operating.programs.reduced.demand" => + "HK2-Solltemperatur_reduziert_Anforderung", + "heating.circuits.1.operating.programs.reduced.temperature" => + "HK2-Solltemperatur_reduziert", + "heating.circuits.1.operating.programs.summerEco.active" => + "HK2-Solltemperatur_SummerEco_aktiv", + "heating.circuits.1.operating.programs.standby.active" => + "HK2-Standby_aktiv", + "heating.circuits.1.sensors.temperature.room.status" => "HK2-Raum_Status", + "heating.circuits.1.sensors.temperature.room.value" => + "HK2-Raum_Temperatur", + "heating.circuits.1.sensors.temperature.supply.status" => + "HK2-Vorlauftemperatur_aktiv", + "heating.circuits.1.sensors.temperature.supply.unit" => + "HK2-Vorlauftemperatur_Einheit", + "heating.circuits.1.sensors.temperature.supply.value" => + "HK2-Vorlauftemperatur", + "heating.circuits.1.zone.mode.active" => "HK2-ZoneMode_aktive", + + "heating.circuits.2.active" => "HK3_aktiv", + "heating.circuits.2.type" => "HK3_Typ", + "heating.circuits.2.circulation.pump.status" => "HK3_Zirkulationspumpe", + "heating.circuits.2.circulation.schedule.active" =>"HK3_Zeitsteuerung_Zirkulation_aktiv", + "heating.circuits.2.circulation.schedule.entries" =>"HK3_Zeitsteuerung_Zirkulation", + "heating.circuits.2.frostprotection.status" => "HK3_Frostschutz_Status", + "heating.circuits.2.geofencing.active" => "HK3_Geofencing", + "heating.circuits.2.geofencing.status" => "HK3_Geofencing_Status", + "heating.circuits.2.heating.curve.shift" => "HK3_Heizkurve_Niveau", + "heating.circuits.2.heating.curve.slope" => "HK3_Heizkurve_Steigung", + "heating.circuits.2.heating.schedule.active" => "HK3-Zeitsteuerung_Heizung_aktiv", + "heating.circuits.2.heating.schedule.entries" => "HK3_Zeitsteuerung_Heizung", + "heating.circuits.2.name" => "HK3_Name", + "heating.circuits.2.operating.modes.active.value" => "HK3_Betriebsart", + "heating.circuits.2.operating.modes.dhw.active" => "HK3_WW_aktiv", + "heating.circuits.2.operating.modes.dhwAndHeating.active" => "HK3_WW_und_Heizen_aktiv", + "heating.circuits.2.operating.modes.dhwAndHeatingCooling.active" => "HK3-WW_und_Heizen_Kuehlen_aktiv", + "heating.circuits.2.operating.modes.forcedNormal.active" => "HK3-Solltemperatur_erzwungen", + "heating.circuits.2.operating.modes.forcedReduced.active" => "HK3-Reduzierte_Temperatur_erzwungen", + "heating.circuits.2.operating.modes.heating.active" => "HK3-heizen_aktiv", + "heating.circuits.2.operating.modes.normalStandby.active" => "HK3-Normal_Standby_aktiv", + "heating.circuits.2.operating.modes.standby.active" => "HK3-Standby_aktiv", + "heating.circuits.2.operating.programs.active.value" => "HK3-Programmstatus", + "heating.circuits.2.operating.programs.comfort.active" => "HK3-Solltemperatur_comfort_aktiv", + "heating.circuits.2.operating.programs.comfort.demand" => "HK3-Solltemperatur_comfort_Anforderung", + "heating.circuits.2.operating.programs.comfort.temperature" => "HK3-Solltemperatur_comfort", + "heating.circuits.2.operating.programs.eco.active" => "HK3-Solltemperatur_eco_aktiv", + "heating.circuits.2.operating.programs.eco.temperature" => "HK3-Solltemperatur_eco", + "heating.circuits.2.operating.programs.external.active" => "HK3-External_aktiv", + "heating.circuits.2.operating.programs.external.temperature" => "HK3-External_Temperatur", + "heating.circuits.2.operating.programs.fixed.active" => "HK3-Fixed_aktiv", + "heating.circuits.2.operating.programs.forcedLastFromSchedule.active" => "HK3-forcedLastFromSchedule_aktiv", + "heating.circuits.2.operating.programs.holidayAtHome.active" => "HK3-HolidayAtHome_aktiv", + "heating.circuits.2.operating.programs.holidayAtHome.end" => "HK3-HolidayAtHome_Ende", + "heating.circuits.2.operating.programs.holidayAtHome.start" => "HK3-HolidayAtHome_Start", + "heating.circuits.2.operating.programs.holiday.active" => "HK3_Urlaub_aktiv", + "heating.circuits.2.operating.programs.holiday.start" => "HK3_Urlaub_Start_Zeit", + "heating.circuits.2.operating.programs.holiday.end" => "HK3_Urlaub_Ende_Zeit", + "heating.circuits.2.operating.programs.normal.active" => + "HK3-Solltemperatur_aktiv", + "heating.circuits.2.operating.programs.normal.demand" => + "HK3-Solltemperatur_Anforderung", + "heating.circuits.2.operating.programs.normal.temperature" => + "HK3-Solltemperatur_normal", + "heating.circuits.2.operating.programs.reduced.active" => + "HK3-Solltemperatur_reduziert_aktiv", + "heating.circuits.2.operating.programs.reduced.demand" => + "HK3-Solltemperatur_reduziert_Anforderung", + "heating.circuits.2.operating.programs.reduced.temperature" => + "HK3-Solltemperatur_reduziert", + "heating.circuits.2.operating.programs.summerEco.active" => + "HK3-Solltemperatur_SummerEco_aktiv", + "heating.circuits.2.operating.programs.standby.active" => + "HK3-Standby_aktiv", + "heating.circuits.2.sensors.temperature.room.status" => "HK3-Raum_Status", + "heating.circuits.2.sensors.temperature.room.value" => + "HK3-Raum_Temperatur", + "heating.circuits.2.sensors.temperature.supply.status" => + "HK3-Vorlauftemperatur_aktiv", + "heating.circuits.2.sensors.temperature.supply.unit" => + "HK3-Vorlauftemperatur_Einheit", + "heating.circuits.2.sensors.temperature.supply.value" => "HK3-Vorlauftemperatur", + "heating.circuits.2.zone.mode.active" => "HK2-ZoneMode_aktive", + + "heating.compressor.active" => "Kompressor_aktiv", + "heating.configuration.multiFamilyHouse.active" => "Mehrfamilenhaus_aktiv", + "heating.configuration.regulation.mode" => "Regulationmode", + "heating.controller.serial.value" => "Controller_Seriennummer", + "heating.dhw.active" => "WW_aktiv", + "heating.dhw.status" => "WW_Status", + "heating.dhw.charging.active" => "WW_Aufladung", + + "heating.dhw.charging.level.bottom" => "WW_Speichertemperatur_unten", + "heating.dhw.charging.level.middle" => "WW_Speichertemperatur_mitte", + "heating.dhw.charging.level.top" => "WW_Speichertemperatur_oben", + "heating.dhw.charging.level.value" => "WW_Speicherladung", + + "heating.errors.active.entries" => "Fehlereintraege_aktive", + "heating.errors.history.entries" => "Fehlereintraege_Historie", + + "heating.flue.sensors.temperature.main.status" => "Abgassensor_Status", + "heating.flue.sensors.temperature.main.unit" => "Abgassensor_Temperatur_Einheit", + "heating.flue.sensors.temperature.main.value" => "Abgassensor_Temperatur", + + "heating.fuelCell.operating.modes.active.value" => "Brennstoffzelle_Mode", + "heating.fuelCell.operating.modes.ecological.active" => "Brennstoffzelle_Mode_Ecological", + "heating.fuelCell.operating.modes.economical.active" => "Brennstoffzelle_Mode_Economical", + "heating.fuelCell.operating.modes.heatControlled.active" => "Brennstoffzelle_wärmegesteuert", + "heating.fuelCell.operating.modes.maintenance.active" => "Brennstoffzelle_Wartung", + "heating.fuelCell.operating.modes.standby.active" => "Brennstoffzelle_Standby", + "heating.fuelCell.operating.phase.value" => "Brennstoffzelle_Phase", + "heating.fuelCell.power.production.day" => "Brennstoffzelle_Stromproduktion/Tag", + "heating.fuelCell.power.production.month" => "Brennstoffzelle_Stromproduktion/Monat", + "heating.fuelCell.power.production.unit" => "Brennstoffzelle_Stromproduktion_Einheit", + "heating.fuelCell.power.production.week" => "Brennstoffzelle_Stromproduktion/Woche", + "heating.fuelCell.power.production.year" => "Brennstoffzelle_Stromproduktion/Jahr", + "heating.fuelCell.sensors.temperature.return.status" => "Brennstoffzelle_Temperatur_Ruecklauf_Status", + "heating.fuelCell.sensors.temperature.return.unit" => "Brennstoffzelle_Temperatur_Ruecklauf_Einheit", + "heating.fuelCell.sensors.temperature.return.value" => "Brennstoffzelle_Temperatur_Ruecklauf", + "heating.fuelCell.sensors.temperature.supply.status" => "Brennstoffzelle_Temperatur_Vorlauf_Status", + "heating.fuelCell.sensors.temperature.supply.unit" => "Brennstoffzelle_Temperatur_Vorlauf_Einheit", + "heating.fuelCell.sensors.temperature.supply.value" => "Brennstoffzelle_Temperatur_Vorlauf", + "heating.fuelCell.statistics.availabilityRate" => "Brennstoffzelle_Statistic_Verfügbarkeit", + "heating.fuelCell.statistics.insertions" => "Brennstoffzelle_Statistic_Einschub", + "heating.fuelCell.statistics.operationHours" => "Brennstoffzelle_Statistic_Bestriebsstunden", + "heating.fuelCell.statistics.productionHours" => "Brennstoffzelle_Statistic_Produktionsstunden", + "heating.fuelCell.statistics.productionStarts" => "Brennstoffzelle_Statistic_Produktionsstarts", + + "heating.gas.consumption.fuelCell.day" => "Gas_Brennstoffzelle/Tag", + "heating.gas.consumption.fuelCell.week" => "Gas_Brennstoffzelle/Woche", + "heating.gas.consumption.fuelCell.month" => "Gas_Brennstoffzelle/Monat", + "heating.gas.consumption.fuelCell.year" => "Gas_Brennstoffzelle/Jahr", + "heating.gas.consumption.fuelCell.unit" => "Gas_Brennstoffzelle/Einheit", + + "heating.heat.production.day" => "Wärmeproduktion/Tag", + "heating.heat.production.month" => "Wärmeproduktion/Woche", + "heating.heat.production.unit" => "Wärmeproduktion/Einheit", + "heating.heat.production.week" => "Wärmeproduktion/Woche", + "heating.heat.production.year" => "Wärmeproduktion/Jahr", + + "heating.operating.programs.holiday.active" => "Urlaub_aktiv", + "heating.operating.programs.holiday.end" => "Urlaub_Ende_Zeit", + "heating.operating.programs.holiday.start" => "Urlaub_Start_Zeit", + + "heating.operating.programs.holidayAtHome.active" => "HolidayAtHome_aktiv", + "heating.operating.programs.holidayAtHome.end" => "HolidayAtHome_Ende", + "heating.operating.programs.holidayAtHome.start" => "HolidayAtHome_Start", + + "heating.power.consumption.day" => "Stromverbrauch_Tag", + "heating.power.consumption.month" => "Stromverbrauch_Monat", + "heating.power.consumption.week" => "Stromverbrauch_Woche", + "heating.power.consumption.year" => "Stromverbrauch_Jahr", + "heating.power.consumption.unit" => "Stromverbrauch_Einheit", + + "heating.power.consumption.dhw.day" => "Strom_WW_Tage", + "heating.power.consumption.dhw.dayValueReadAt" => "Strom_WW_Tage_Zeit", + "heating.power.consumption.dhw.week" => "Strom_WW_Wochen", + "heating.power.consumption.dhw.weekValueReadAt" => "Strom_WW_Wochen_Zeit", + "heating.power.consumption.dhw.month" => "Strom_WW_Monate", + "heating.power.consumption.dhw.monthValueReadAt" => "Strom_WW_Monate_Zeit", + "heating.power.consumption.dhw.year" => "Strom_WW_Jahre", + "heating.power.consumption.dhw.yearValueReadAt" => "Strom_WW_Jahre_Zeit", + "heating.power.consumption.dhw.unit" => "Strom_WW_Einheit", + + "heating.power.consumption.heating.day" => "Strom_Heizung_Tage__kWh", + "heating.power.consumption.heating.dayValueReadAt" => "Strom_Heizung_Tage_Zeit", + "heating.power.consumption.heating.week" => "Strom_Heizung_Wochen__kWh", + "heating.power.consumption.heating.weekValueReadAt" => "Strom_Heizung_Wochen_Zeit", + "heating.power.consumption.heating.month" => "Strom_Heizung_Monate__kWh", + "heating.power.consumption.heating.monthValueReadAt"=> "Strom_Heizung_Monate_Zeit", + "heating.power.consumption.heating.year" => "Strom_Heizung_Jahre__kWh", + "heating.power.consumption.heating.yearValueReadAt" => "Strom_Heizung_Jahre_Zeit", + "heating.power.consumption.heating.unit" => "Strom_Heizung_Einheit", + + "heating.power.consumption.total.day" => "Strom_Total_Tage__kWh", + "heating.power.consumption.total.dayValueReadAt" => "Strom_Total_Tage_Zeit", + "heating.power.consumption.total.week" => "Strom_Total_Wochen__kWh", + "heating.power.consumption.total.weekValueReadAt" => "Strom_Total_Wochen_Zeit", + "heating.power.consumption.total.month" => "Strom_Total_Monate__kWh", + "heating.power.consumption.total.monthValueReadAt" => "Strom_Total_Monate_Zeit", + "heating.power.consumption.total.year" => "Strom_Total_Jahre__kWh", + "heating.power.consumption.total.yearValueReadAt" => "Strom_Total_Jahre_Zeit", + "heating.power.consumption.total.unit" => "Strom_Total_Einheit", + + "heating.power.production.current.status" => "Stromproduktion_aktueller_Status", + "heating.power.production.current.value" => "Stromproduktion", + + "heating.power.production.demandCoverage.current.unit" => "Stromproduktion_Bedarfsabdeckung/Einheit", + "heating.power.production.demandCoverage.current.value" => "Stromproduktion_Bedarfsabdeckung", + "heating.power.production.demandCoverage.total.day" => "Stromproduktion_Bedarfsabdeckung_total/Tag", + "heating.power.production.demandCoverage.total.month" => "Stromproduktion_Bedarfsabdeckung_total/Monat", + "heating.power.production.demandCoverage.total.unit" => "Stromproduktion_Bedarfsabdeckung_total/Einheit", + "heating.power.production.demandCoverage.total.week" => "Stromproduktion_Bedarfsabdeckung_total/Woche", + "heating.power.production.demandCoverage.total.year" => "Stromproduktion_Bedarfsabdeckung_total/Jahr", + + "heating.power.production.day" => "Stromproduktion_Total/Tag", + "heating.power.production.month" => "Stromproduktion_Total/Monat", + "heating.power.production.productionCoverage.current.unit" => + "Stromproduktion_Produktionsabdeckung/Einheit", + "heating.power.production.productionCoverage.current.value" => + "Stromproduktion_Produktionsabdeckung", + "heating.power.production.productionCoverage.total.day" => + "Stromproduktion_Produktionsabdeckung_Total/Tag", + "heating.power.production.productionCoverage.total.month" => + "Stromproduktion_Produktionsabdeckung_Total/Monat", + "heating.power.production.productionCoverage.total.unit" => + "Stromproduktion_Produktionsabdeckung_Total/Einheit", + "heating.power.production.productionCoverage.total.week" => + "Stromproduktion_Produktionsabdeckung_Total/Woche", + "heating.power.production.productionCoverage.total.year" => + "Stromproduktion_Produktionsabdeckung_Total/Jahr", + "heating.power.production.unit" => "Stromproduktion_Total/Einheit", + "heating.power.production.week" => "Stromproduktion_Total/Woche", + "heating.power.production.year" => "Stromproduktion_Total/Jahr", + + "heating.power.purchase.current.unit" => "Stromkauf/Einheit", + "heating.power.purchase.current.value" => "Stromkauf", + "heating.power.sold.current.unit" => "Stromverkauf/Einheit", + "heating.power.sold.current.value" => "Stromverkauf", + "heating.power.sold.day" => "Stromverkauf/Tag", + "heating.power.sold.month" => "Stromverkauf/Monat", + "heating.power.sold.unit" => "Stromverkauf/Einheit", + "heating.power.sold.week" => "Stromverkauf/Woche", + "heating.power.sold.year" => "Stromverkauf/Jahr", + + "heating.sensors.pressure.supply.status" => "Drucksensor_Vorlauf_Status", + "heating.sensors.pressure.supply.unit" => "Drucksensor_Vorlauf/Einheit", + "heating.sensors.pressure.supply.value" => "Drucksensor_Vorlauf", + + "heating.sensors.power.output.status" => "Sensor_Stromproduktion_Status", + "heating.sensors.power.output.value" => "Sensor_Stromproduktion", + + "heating.sensors.temperature.outside.statusWired" => "Aussen_StatusWired", + "heating.sensors.temperature.outside.statusWireless" => + "Aussen_StatusWireless", + + "heating.service.timeBased.serviceDue" => "Service_faellig", + "heating.service.timeBased.serviceIntervalMonths" => + "Service_Intervall_Monate", + "heating.service.timeBased.activeMonthSinceLastService" => + "Service_Monate_aktiv_seit_letzten_Service", + "heating.service.timeBased.lastService" => "Service_Letzter", + "heating.service.burnerBased.serviceDue" => + "Service_fällig_brennerbasiert", + "heating.service.burnerBased.serviceIntervalBurnerHours" => + "Service_Intervall_Betriebsstunden", + "heating.service.burnerBased.activeBurnerHoursSinceLastService" => + "Service_Betriebsstunden_seit_letzten", + "heating.service.burnerBased.lastService" => + "Service_Letzter_brennerbasiert", + + "heating.solar.active" => "Solar_aktiv", + "heating.solar.pumps.circuit.status" => "Solar_Pumpe_Status", + "heating.solar.rechargeSuppression.status" => + "Solar_Aufladeunterdrueckung_Status", + "heating.solar.sensors.power.status" => "Solar_Sensor_Power_Status", + "heating.solar.sensors.power.value" => "Solar_Sensor_Power", + "heating.solar.sensors.temperature.collector.status" => + "Solar_Sensor_Temperatur_Kollektor_Status", + "heating.solar.sensors.temperature.collector.value" => + "Solar_Sensor_Temperatur_Kollektor", + "heating.solar.sensors.temperature.dhw.status" => + "Solar_Sensor_Temperatur_WW_Status", + "heating.solar.sensors.temperature.dhw.value" => + "Solar_Sensor_Temperatur_WW", + "heating.solar.statistics.hours" => "Solar_Sensor_Statistik_Stunden", + + "heating.solar.power.cumulativeProduced.value" => + "Solarproduktion_Gesamtertrag", + "heating.solar.power.production.month" => "Solarproduktion/Monat", + "heating.solar.power.production.day" => "Solarproduktion/Tag", + "heating.solar.power.production.unit" => "Solarproduktion/Einheit", + "heating.solar.power.production.week" => "Solarproduktion/Woche", + "heating.solar.power.production.year" => "Solarproduktion/Jahr" +}; + + +##################################################################################################################### +# Modul initialisieren und Namen zusätzlicher Funktionen bekannt geben +##################################################################################################################### sub vitoconnect_Initialize { my ($hash) = @_; - $hash->{DefFn} = \&vitoconnect_Define; - $hash->{UndefFn} = \&vitoconnect_Undef; - $hash->{SetFn} = \&vitoconnect_Set; - $hash->{GetFn} = \&vitoconnect_Get; - $hash->{AttrFn} = \&vitoconnect_Attr; + $hash->{DefFn} = \&vitoconnect_Define; # wird beim 'define' eines Gerätes aufgerufen + $hash->{UndefFn} = \&vitoconnect_Undef; # # wird beim Löschen einer Geräteinstanz aufgerufen + $hash->{DeleteFn} = \&DeleteKeyValue; + $hash->{SetFn} = \&vitoconnect_Set; # set-Befehle + $hash->{GetFn} = \&vitoconnect_Get; # get-Befehle + $hash->{AttrFn} = \&vitoconnect_Attr; # Attribute setzen/ändern/löschen $hash->{ReadFn} = \&vitoconnect_Read; $hash->{AttrList} = "disable:0,1 " - . "mapping:textField-long " - . "model:Vitodens_200-W_(B2HB),Vitodens_200-W_(B2KB)," - . "Vitotronic_200_(HO1),Vitotronic_200_(HO1A),Vitotronic_200_(HO1B),Vitotronic_200_(HO1D)," - . "Vitotronic_200_(HO2B)," - . "Vitotronic_200_RF_(HO1C),Vitotronic_200_RF_(HO1E)," - . "Vitotronic_200_(KO1B),Vitotronic_200_(KO2B),Vitotronic_200_(KW6),Vitotronic_200_(KW6A)," - . "Vitotronic_200_(KW6B),Vitotronic_200_(KW1),Vitotronic_200_(KW2),Vitotronic_200_(KW4)," - . "Vitotronic_200_(KW5)," - . "Vitotronic_300_(KW3),Vitotronic_200_(WO1A),Vitotronic_200_(WO1B),Vitotronic_200_(WO1C)," - . "Vitoligno_300-C,Vitoligno_200-S,Vitoligno_300-P_mit_Vitotronic_200_(FO1),Vitoligno_250-S," - . "Vitoligno_300-S " - . "vitoconnect_raw_readings:0,1 " - . "vitoconnect_gw_readings:0,1 " + . "vitoconnect_mappings:textField-long " + . "vitoconnect_translations:textField-long " + . "vitoconnect_mapping_roger:0,1 " + . "vitoconnect_raw_readings:0,1 " # Liefert nur die raw readings und verhindert das mappen wenn gesetzt + . "vitoconnect_disable_raw_readings:0,1 " # Wird ein mapping verwendet können die weiteren RAW Readings ausgeblendet werden + . "vitoconnect_gw_readings:0,1 " # Schreibt die GW readings als Reading ins Device . "vitoconnect_actions_active:0,1 " - . "vitoconnect_device:0,1 " + . "vitoconnect_device:0,1 " # Hier kann Device 0 oder 1 angesprochen worden, default ist 0 und ich habe keinen GW mit Device 1 + . "vitoconnect_serial:textField-long " # Legt fest welcher Gateway abgefragt werden soll, wenn nicht gesetzt werden alle abgefragt + . "vitoconnect_installationID:textField-long " # Legt fest welche Installation abgefragt werden soll, muss zur serial passen . "vitoconnect_timeout:selectnumbers,10,1.0,30,0,lin " . $readingFnAttributes; + + eval { FHEM::Meta::InitMod( __FILE__, $hash ) }; ## no critic 'eval' return; } + +##################################################################################################################### +# wird beim 'define' eines Gerätes aufgerufen +##################################################################################################################### sub vitoconnect_Define { my ( $hash, $def ) = @_; my $name = $hash->{NAME}; + my $type = $hash->{TYPE}; + + my $params = { + hash => $hash, + name => $name, + type => $type, + notes => \%vNotesIntern, + useAPI => 0, + useSMUtils => 1, + useErrCodes => 0, + useCTZ => 0, + }; + + use version 0.77; our $VERSION = moduleVersion ($params); # Versionsinformationen setzen + delete $params->{hash}; + + my @param = split( '[ \t]+', $def ); if ( int(@param) < 5 ) { @@ -857,67 +1304,345 @@ sub vitoconnect_Define { $hash->{counter} = 0; $hash->{timeout} = 15; $hash->{".access_token"} = ""; - $hash->{".installation"} = ""; - $hash->{".gw"} = ""; + $hash->{devices} = []; $hash->{"Redirect_URI"} = $callback_uri; - my $isiwebpasswd = vitoconnect_ReadKeyValue( $hash, "passwd" ); - if ( $isiwebpasswd eq "" ) { - my $err = vitoconnect_StoreKeyValue( $hash, "passwd", $param[3] ); + my $isiwebpasswd = vitoconnect_ReadKeyValue($hash,"passwd"); # verschlüsseltes Kennwort auslesen + if ($isiwebpasswd eq "") { # Kennwort (noch) nicht gespeichert + my $err = vitoconnect_StoreKeyValue($hash,"passwd",$param[3]); # Kennwort verschlüsselt speichern return $err if ($err); } - else { - Log3 $name, 3, "$name - Passwort war bereits gespeichert"; + else { # Kennwort schon gespeichert + Log3($name,3,$name." - Passwort war bereits gespeichert"); } - $hash->{apiKey} = vitoconnect_ReadKeyValue( $hash, "apiKey" ); - InternalTimer( gettimeofday() + 10, "vitoconnect_GetUpdate", $hash ); + $hash->{apiKey} = vitoconnect_ReadKeyValue($hash,"apiKey"); # verschlüsselten apiKey auslesen + RemoveInternalTimer($hash); # Timer löschen, z.b. bei intervall change + InternalTimer(gettimeofday() + 10,"vitoconnect_GetUpdate",$hash); # nach 10s return; } + +##################################################################################################################### +# wird beim Löschen einer Geräteinstanz aufgerufen +##################################################################################################################### sub vitoconnect_Undef { - my ( $hash, $arg ) = @_; - RemoveInternalTimer($hash); + my ($hash,$arg ) = @_; # Übergabe-Parameter + RemoveInternalTimer($hash); # Timer löschen return; } + +##################################################################################################################### +# bisher kein 'get' implementiert +##################################################################################################################### sub vitoconnect_Get { - my ( $hash, $name, $opt, @args ) = @_; - return "get $name needs at least one argument" unless ( defined($opt) ); + my ($hash,$name,$opt,@args ) = @_; # Übergabe-Parameter + return "get ".$name." needs at least one argument" unless (defined($opt) ); return; } + +##################################################################################################################### +# Implementierung set-Befehle +##################################################################################################################### sub vitoconnect_Set { - my ( $hash, $name, $opt, @args ) = @_; - return "set $name needs at least one argument" unless ( defined($opt) ); - if ( $opt eq "update" ) { - RemoveInternalTimer($hash); - vitoconnect_GetUpdate($hash); + my ($hash,$name,$opt,@args ) = @_; # Übergabe-Parameter + + # Standard Parameter setzen + my $val = "unknown value $opt, choose one of update:noArg clearReadings:noArg password apiKey logResponseOnce:noArg "; + Log(5,$name.", -vitoconnect_Set started: ". $opt); #debug + + # Setter für die Geräteauswahl dynamisch erstellen + Log3($name,4,$name." - Set devices: ".$hash->{devices}); + if (defined $hash->{devices} && ref($hash->{devices}) eq 'HASH' && keys %{$hash->{devices}} > 0) { + my @device_serials = keys %{$hash->{devices}}; + $val .= " selectDevice:" . join(",", @device_serials); + } else { + $val .= " selectDevice:noArg" + } + $val .= " "; + Log3($name,5,$name." - Set val: $val, Set Opt: $opt"); + + # Hier richtig? + return "set ".$name." needs at least one argument" unless (defined($opt) ); + + # Setter für Device Werte rufen + if (AttrVal( $name, 'vitoconnect_raw_readings', 0 ) eq "1" ) { + #use new dynamic parsing of JSON to get raw setters + $val .= vitoconnect_Set_New ($hash,$name,$opt,@args); + } + elsif (AttrVal( $name, 'vitoconnect_mapping_roger', 0 ) eq "1" ) { + #use roger setters + $val .= vitoconnect_Set_Roger ($hash,$name,$opt,@args); + } + else { + #use svn setters + $val .= vitoconnect_Set_SVN ($hash,$name,$opt,@args); + } + + + + if ($opt eq "update") { # set update: update readings immeadiatlely + RemoveInternalTimer($hash); # bisherigen Timer löschen + vitoconnect_GetUpdate($hash); # neue Abfrage starten return; } - elsif ( $opt eq "logResponseOnce" ) { - $hash->{".logResponseOnce"} = 1; - RemoveInternalTimer($hash); - vitoconnect_getCode($hash); + elsif ($opt eq "logResponseOnce" ) { # set logResponseOnce: dumps the json response of Viessmann server to entities.json, gw.json, actions.json in FHEM log directory + $hash->{".logResponseOnce"} = 1; # in 'Internals' merken + RemoveInternalTimer($hash); # bisherigen Timer löschen + vitoconnect_getCode($hash); # Werte für: Access-Token, Install-ID, Gateway anfragen return; } - elsif ( $opt eq "clearReadings" ) { - AnalyzeCommand( $hash, "deletereading $name .*" ); + elsif ($opt eq "clearReadings" ) { # set clearReadings: clear all readings immeadiatlely + AnalyzeCommand($hash,"deletereading ".$name." .*"); return; } - elsif ( $opt eq "password" ) { - my $err = vitoconnect_StoreKeyValue( $hash, "passwd", $args[0] ); + elsif ($opt eq "password" ) { # set password: store password in key store + my $err = vitoconnect_StoreKeyValue($hash,"passwd",$args[0]); # Kennwort verschlüsselt speichern return $err if ($err); - vitoconnect_getCode($hash); + vitoconnect_getCode($hash); # Werte für: Access-Token, Install-ID, Gateway anfragen return; } - elsif ( $opt eq "apiKey" ) { + elsif ($opt eq "apiKey" ) { # set apiKey: bisher keine Beschreibung $hash->{apiKey} = $args[0]; - my $err = vitoconnect_StoreKeyValue( $hash, "apiKey", $args[0] ); + my $err = vitoconnect_StoreKeyValue($hash,"apiKey",$args[0]); # apiKey verschlüsselt speichern RemoveInternalTimer($hash); - vitoconnect_getCode($hash); + vitoconnect_getCode($hash); # Werte für: Access-Token, Install-ID, Gateway anfragen return; } - elsif ( $opt eq "HK1-Heizkurve-Niveau" ) { + elsif ($opt eq "selectDevice" ) { # set selectDevice: Bei mehreren Devices eines auswählen + Log3($name,4,$name." - Set selectedDevice serial: ".$args[0]); + if (defined $args[0] && $args[0] ne '') { + my $serial = $args[0]; + my %devices = %{ $hash->{devices} }; + if (exists $devices{$serial}) { + my $installationId = $devices{$serial}{installationId}; + Log3($name,5,$name." - Set selectedDevice: instID: $installationId, serial $serial"); + CommandAttr (undef, "$name vitoconnect_installationID $installationId"); + CommandAttr (undef, "$name vitoconnect_serial $serial"); + } + $hash->{selectedDevice} = $serial; + RemoveInternalTimer($hash); # bisherigen Timer löschen + vitoconnect_GetUpdate($hash); # neue Abfrage starten + } else { + readingsSingleUpdate($hash,"state","Kein Gateway/Device gefunden, bitte Setup überprüfen",1); + } + return; + } + + +return $val; +} + + +##################################################################################################################### +# Implementierung set-Befehle neue logik aus raw readings +##################################################################################################################### +sub vitoconnect_Set_New { + my ($hash, $name, $opt, @args) = @_; + my $gw = AttrVal( $name, 'vitoconnect_serial', 0 ); + my $val = ""; + + my $Response = $hash->{".response_$gw"}; + if ($Response) { # Überprüfen, ob $Response Daten enthält + my $data; + eval { $data = decode_json($Response); }; + if ($@) { + # JSON-Dekodierung fehlgeschlagen, nur Standardoptionen zurückgeben + return $val; + } + + foreach my $item (@{$data->{'data'}}) { + + if (exists $item->{commands}) { + my $feature = $item->{feature}; + Log(5,$name.",vitoconnect_Set_New feature: ". $feature); + + foreach my $commandName (sort keys %{$item->{commands}}) { #<====== Loop Commands, sort necessary for activate temperature for burners, see below + my $commandNr = keys %{$item->{commands}}; + my @propertyKeys = keys %{$item->{properties}}; + my $propertyKeysNr = keys %{$item->{properties}}; + my $paramNr = keys %{$item->{commands}{$commandName}{params}}; + + Log(5,$name.", -vitoconnect_Set_New isExecutable: ". $item->{commands}{$commandName}{isExecutable}); + if ($item->{commands}{$commandName}{isExecutable} == 0) { + Log(5,$name.", -vitoconnect_Set_New $commandName nicht ausführbar"); + next; #diser Befehl ist nicht ausführbar, nächster + } + + Log(5,$name.", -vitoconnect_Set_New feature: ". $feature); + Log(5,$name.", -vitoconnect_Set_New commandNr: ". $commandNr); + Log(5,$name.", -vitoconnect_Set_New commandname: ". $commandName); + my $readingNamePrep; + if ($commandNr == 1 and $propertyKeysNr == 1) { # Ein command value = property z.B. heating.circuits.0.operating.modes.active + $readingNamePrep .= $feature.".". $propertyKeys[0]; + } elsif ( $commandName eq "setTemperature" ) { + $readingNamePrep .= $feature.".temperature"; #<------- setTemperature only 1 param, so it can be defined here, + # for burner Vitoladens 300C, heating.circuits.0.operating.programs.comfort + # activate (temperature), deactivate(noArg), setTemperature (targetTemperature) only one can work with value provided + # Activate should work, and is, since commands are sorted + } elsif ( $commandName eq "setHysteresis" ) { #<------- setHysteresis very special mapping, must be predefined + $readingNamePrep .= $feature.".value"; + } elsif ( $commandName eq "setHysteresisSwitchOnValue" ) { #<------- setHysteresis very special mapping, must be predefined + $readingNamePrep .= $feature.".switchOnValue"; + } elsif ( $commandName eq "setHysteresisSwitchOffValue" ) { #<------- setHysteresis very special mapping, must be predefined + $readingNamePrep .= $feature.".switchOffValue"; + } elsif ( $commandName eq "setMin" ) { + $readingNamePrep .= $feature.".min"; #<------- setMin/setMax very special mapping, must be predefined + } elsif ( $commandName eq "setMax" ) { + $readingNamePrep .= $feature.".max"; + } elsif ( $commandName eq "setSchedule" ) { #<------- setSchedule very special mapping, must be predefined + $readingNamePrep .= $feature.".entries"; + } elsif ( $commandName eq "setLevels" ) { + # duplicate, setMin, setMax can do this https://api.viessmann.com/iot/v2/features/installations/2772216/gateways/7736172146035226/devices/0/features/heating.circuits.0.temperature.levels/commands/setLevels + next; + } + else { + # all other cases, will be defined in param loop + } + if(defined($readingNamePrep)) + { + Log(5,$name.", -vitoconnect_Set_New readingNamePrep: ". $readingNamePrep); + } + + if ($paramNr > 2) { #<------- more then 2 parameters, with unsorted JSON can not be handled, but also do not exist at the moment + Log(5,$name.", -vitoconnect_Set_New mehr als 2 Parameter in Command $commandName, kann nicht berechnet werden"); + next; + } elsif ($paramNr == 0){ #<------- no parameters, create here, param loop will not be executed + $readingNamePrep .= $feature.".".$commandName; + $val .= "$readingNamePrep:noArg "; + + # Set execution + if ($opt eq $readingNamePrep) { + my $uri = $item->{commands}->{$commandName}->{'uri'}; + my ($shortUri) = $uri =~ m|.*features/(.*)|; #<=== URI ohne gateway zeug + Log(4,$name.", -vitoconnect_Set_New, 0 param, short uri: ".$shortUri); + vitoconnect_action($hash, + $shortUri, + "{}", + $name, $opt, @args + ); + return; + } + } + + # 1 oder 2 Params, all other cases see above + my @params = keys %{$item->{commands}{$commandName}{params}}; + foreach my $paramName (@params) { #<==== Loop params + + my $otherParam; + my $otherReadingName; + if ($paramNr == 2) { + $otherParam = $params[0] eq $paramName ? $params[1] : $params[0]; + } + + my $readingName = $readingNamePrep; + if (!defined($readingName)) { #<==== Bisher noch kein Reading gefunden, z.B. setCurve + $readingName = $feature.".".$paramName; + if (defined($otherParam)) { + $otherReadingName = $feature.".".$otherParam; + } + } + + my $param = $item->{commands}{$commandName}{params}{$paramName}; + + # fill $val + if ($param->{type} eq 'number') { + $val .= $readingName.":slider," . ($param->{constraints}{min}) . "," . ($param->{constraints}{stepping}) . "," . ($param->{constraints}{max}); + # Schauen ob float für slider + if ($param->{constraints}{stepping} =~ m/\./) { + $val .= ",1 "; + } else { + $val .= " "; + } + } + elsif ($param->{'type'} eq 'string') { + if ($commandName eq "setMode") { + my $enum = $param->{constraints}->{'enum'}; + Log(5,$name.", -vitoconnect_Set_New enum: ". $enum); + my $enumNr = scalar @$enum; + Log(5,$name.", -vitoconnect_Set_New enumNr: ". $enumNr); + + my $i = 1; + $val .= $readingName.":"; + foreach my $value (@$enum) { + if ($i < $enumNr) { + $val .= $value.","; + } else { + $val .= $value." "; + } + $i++; + } + } else { + $val .= $readingName.":textField-long "; + } + + } elsif ($param->{'type'} eq 'Schedule') { + $val .= $readingName.":textField-long "; + } elsif ($param->{'type'} eq 'boolean') { + $val .= "$readingName "; + } else { + # Ohne type direkter befehl ohne args + $val .= "$readingName:noArg "; + Log(5,$name.", -vitoconnect_Set_New unknown type: ".$readingName); + } + + Log(5,$name.", -vitoconnect_Set_New exec, opt:".$opt.", readingName:".$readingName); + # Set execution + if ($opt eq $readingName) { + + my $data; + my $otherData = ''; + if ($param->{type} eq 'number') { + $data = "{\"$paramName\":@args"; + } else { + $data = "{\"$paramName\":\"@args\""; + } + + # 2 params, one can be set the other must just be read and handed overload + # This logic ensures that we get the correct names in an unsortet JSON + if (defined($otherReadingName)) { + my $otherValue = ReadingsVal($name,$otherReadingName,""); + if ($param->{type} eq 'number') { + $otherData = ",\"$otherParam\":$otherValue"; + } else { + $otherData = ",\"$otherParam\":\"$otherValue\""; + } + } + $data .= $otherData . '}'; + my $uri = $item->{commands}->{$commandName}->{'uri'}; + my ($shortUri) = $uri =~ m|.*features/(.*)|; #<=== URI ohne gateway zeug + Log(4,$name.", -vitoconnect_Set_New, short uri:".$shortUri.", data:".$data); + vitoconnect_action($hash, + $shortUri, + $data, + $name, $opt, @args + ); + return; + } + } + } + } + } + } + + + # Rückgabe der dynamisch erstellten $val Variable + Log(5,$name.", -vitoconnect_Set_New val: ". $val); + Log(5,$name.", -vitoconnect_Set_New ended "); + + return $val; +} + + +##################################################################################################################### +# Implementierung set-Befehle alte logik fixes mapping letzte SVN Version +##################################################################################################################### +sub vitoconnect_Set_SVN { + my ($hash,$name,$opt,@args ) = @_; # Übergabe-Parameter + # SVN mapping original handling of modul + + if ( $opt eq "HK1-Heizkurve-Niveau" ) { my $slope = ReadingsVal( $name, "HK1-Heizkurve-Steigung", "" ); vitoconnect_action( $hash, @@ -1342,10 +2067,7 @@ sub vitoconnect_Set { return; } - my $val = - "unknown value $opt, choose one of update:noArg clearReadings:noArg " - . "password apiKey logResponseOnce:noArg " - . "WW-einmaliges_Aufladen:activate,deactivate " + my $val = "WW-einmaliges_Aufladen:activate,deactivate " . "WW-Zirkulationspumpe_Zeitplan:textField-long " . "WW-Zeitplan:textField-long " . "WW-Haupttemperatur:slider,10,1,60 " @@ -1403,85 +2125,644 @@ sub vitoconnect_Set { . "HK3-Solltemperatur_reduziert:slider,3,1,37 " . "HK3-Name "; } + return $val; } -sub vitoconnect_Attr { - my ( $cmd, $name, $attr_name, $attr_value ) = @_; - if ( $cmd eq "set" ) { - if ( $attr_name eq "vitoconnect_raw_readings" ) { - if ( $attr_value !~ /^0|1$/ ) { - my $err = - "Invalid argument $attr_value to $attr_name. Must be 0 or 1."; - Log 1, "$name - " . $err; - return $err; - } - } - elsif ( $attr_name eq "vitoconnect_gw_readings" ) { - if ( $attr_value !~ /^0|1$/ ) { - my $err = - "Invalid argument $attr_value to $attr_name. Must be 0 or 1."; - Log 1, "$name - " . $err; - return $err; - } - } - elsif ( $attr_name eq "vitoconnect_actions_active" ) { - if ( $attr_value !~ /^0|1$/ ) { - my $err = - "Invalid argument $attr_value to $attr_name. Must be 0 or 1."; - Log 1, "$name - " . $err; - return $err; - } - } - elsif ( $attr_name eq "mapping" ) { - # $RequestList2 = "$attr_value"; +##################################################################################################################### +# Implementierung set-Befehle alte logik fixes mapping von Roger letzte Version +##################################################################################################################### +sub vitoconnect_Set_Roger { + my ($hash,$name,$opt,@args ) = @_; # Übergabe-Parameter + + if ($opt eq "HK1_Betriebsart" ) { # set HKn_Betriebsart: sets HKn_Betriebsart to heating,standby + vitoconnect_action($hash, + "heating.circuits.0.operating.modes.active/commands/setMode", + "{\"mode\":\"$args[0]\"}", + $name,$opt,@args + ); + return; + } + elsif ($opt eq "HK1_Soll_Temp_normal" ) { # set HK1_Soll_Temp_normal: sets the normale target temperature for HKn, where targetTemperature is an integer between 3 and 37 + vitoconnect_action($hash, + "heating.circuits.0.operating.programs.normal/commands/setTemperature", + "{\"targetTemperature\":$args[0]}", + $name,$opt,@args + ); + return; + } + elsif ($opt eq "HK1_Soll_Temp_reduziert" ) { # set HK1_Soll_Temp_reduziert: sets the reduced target temperature for HKn, where targetTemperature is an integer between 3 and 37 + vitoconnect_action($hash, + "heating.circuits.0.operating.programs.reduced/commands/setTemperature", + "{\"targetTemperature\":$args[0]}", + $name,$opt,@args + ); + return; + } + elsif ($opt eq "HK1_Soll_Temp_comfort" ) { # set HK1_Soll_Temp_comfort: set comfort target temperatur for HKn + vitoconnect_action($hash, + "heating.circuits.0.operating.programs.comfort/commands/setTemperature", + "{\"targetTemperature\":$args[0]}", + $name,$opt,@args + ); + return; + } + elsif ($opt eq "HK1_Soll_Temp_comfort_aktiv" ) { # set HK1_Soll_Temp_comfort_aktiv: activate/deactivate comfort temperature for HKn + vitoconnect_action($hash, + "heating.circuits.0.operating.programs.comfort/commands/$args[0]", + "{}", + $name,$opt,@args + ); + return; + } + elsif ($opt eq "HK1_Soll_Temp_eco_aktiv" ) { # set HK1_Soll_Temp_eco_aktiv: activate/deactivate eco temperature for HKn + vitoconnect_action($hash, + "heating.circuits.0.operating.programs.eco/commands/$args[0]", + "{}", + $name,$opt,@args + ); + return; + } + elsif ($opt eq "WW_Betriebsart" ) { # set HKn_Betriebsart: sets WW_Betriebsart to balanced,off + vitoconnect_action($hash, + "heating.dhw.operating.modes.active/commands/setMode", + "{\"mode\":\"$args[0]\"}", + $name,$opt,@args + ); + return; + } + elsif ($opt eq "WW_einmaliges_Aufladen" ) { # set WW_einmaliges_Aufladen: activate or deactivate one time charge for hot water + vitoconnect_action($hash, + "heating.dhw.oneTimeCharge/commands/$args[0]", + "{}", + $name,$opt,@args + ); + return; + } + elsif ($opt eq "WW_Solltemperatur" ) { # set WW_Solltemperatur: sets hot water main temperature to targetTemperature, targetTemperature is an integer between 10 and 60 + vitoconnect_action($hash, + "heating.dhw.temperature.main/commands/setTargetTemperature", + "{\"temperature\":$args[0]}", + $name,$opt,@args + ); + return; + } + elsif ($opt eq "WW_Zirkulationspumpe_Zeitplan" ) { # set WW_Zirkulationspumpe_Zeitplan: sets the schedule in JSON format for hot water circulation pump + vitoconnect_action($hash, + "heating.dhw.pumps.circulation.schedule/commands/setSchedule", + "{\"newSchedule\":@args}", + $name,$opt,@args + ); + return; + } + elsif ($opt eq "WW_Zeitplan" ) { # set WW_Zeitplan: sets the schedule in JSON format for hot water + vitoconnect_action($hash, + "heating.dhw.schedule/commands/setSchedule", + "{\"newSchedule\":@args}", + $name,$opt,@args + ); + return; + } +# elsif ($opt eq "WW_Solltemperatur" ) { # set WW_Solltemperatur: sets hot water temperature to targetTemperature, targetTemperature is an integer between 10 and 60 +# vitoconnect_action($hash, +# "heating.dhw.temperature/commands/commands/setTargetTemperature", +# "{\"temperature\":$args[0]}", +# $name,$opt,@args +# ); +# return; +# } + elsif ($opt eq "WW_Temperatur_2" ) { # set WW_Temperatur_2: sets hot water 2 temperature to targetTemperature, targetTemperature is an integer between 10 and 60 + vitoconnect_action($hash, + "heating.dhw.temperature.temp2/commands/setTargetTemperature", + "{\"temperature\":$args[0]}", + $name,$opt,@args + ); + return; + } + elsif ($opt eq "Urlaub_Start_Zeit" ) { # set Urlaub_Start_Zeit: set holiday start time, start has to look like this: 2019-02-02 + my $end = ReadingsVal($name,"Urlaub_Ende_Zeit",""); + if ($end eq "") { + my $t = Time::Piece->strptime( $args[0], "%Y-%m-%d" ); + $t += ONE_DAY; + $end = $t->strftime("%Y-%m-%d"); } - elsif ( $attr_name eq "disable" ) { + vitoconnect_action($hash, + "heating.operating.programs.holiday/commands/schedule", + "{\"start\":\"$args[0]\",\"end\":\"$end\"}", + $name,$opt,@args + ); + return; + } + elsif ($opt eq "Urlaub_Ende_Zeit" ) { # set Urlaub_Ende_Zeit: set holiday end time, end has to look like this: 2019-02-16 + my $start = ReadingsVal($name,"Urlaub_Start_Zeit",""); + vitoconnect_action($hash, + "heating.operating.programs.holiday/commands/schedule", + "{\"start\":\"$start\",\"end\":\"$args[0]\"}", + $name,$opt,@args + ); + return; + } + elsif ($opt eq "Urlaub_stop" ) { # set Urlaub_stop: remove holiday start and end time + vitoconnect_action($hash, + "heating.operating.programs.holiday/commands/unschedule", + "{}", + $name,$opt,@args + ); + return; + } + elsif ($opt eq "HK1_Name" ) { # set HK1_Name: sets the name of the circuit for HKn + vitoconnect_action($hash, + "heating.circuits.0/commands/setName", + "{\"name\":\"@args\"}", + $name,$opt,@args + ); + return; + } + elsif ($opt eq "HK2_Name" ) { # set HK2_Name: sets the name of the circuit for HKn + vitoconnect_action($hash, + "heating.circuits.1/commands/setName", + "{\"name\":\"@args\"}", + $name,$opt,@args + ); + return; + } + elsif ($opt eq "HK3_Name" ) { # set HK3_Name: sets the name of the circuit for HKn + vitoconnect_action($hash, + "heating.circuits.2/commands/setName", + "{\"name\":\"@args\"}", + $name,$opt,@args + ); + return; + } + elsif ($opt eq "HK1_Heizkurve_Niveau" ) { # set HK1_Heizkurve_Niveau: set shift of heating curve for HKn + my $slope = ReadingsVal($name,"HK1_Heizkurve_Steigung",""); + vitoconnect_action($hash, + "heating.circuits.0.heating.curve/commands/setCurve", + "{\"shift\":$args[0],\"slope\":$slope}", + $name,$opt,@args + ); + return; + } + elsif ($opt eq "HK2_Heizkurve_Niveau" ) { # set HK2_Heizkurve_Niveau: set shift of heating curve for HKn + my $slope = ReadingsVal($name,"HK2_Heizkurve_Steigung",""); + vitoconnect_action($hash, + "heating.circuits.1.heating.curve/commands/setCurve", + "{\"shift\":$args[0],\"slope\":$slope}", + $name,$opt,@args + ); + return; + } + elsif ($opt eq "HK3_Heizkurve_Niveau" ) { # set HK3_Heizkurve_Niveau: set shift of heating curve for HKn + my $slope = ReadingsVal($name,"HK3_Heizkurve_Steigung",""); + vitoconnect_action($hash, + "heating.circuits.2.heating.curve/commands/setCurve", + "{\"shift\":$args[0],\"slope\":$slope}", + $name,$opt,@args + ); + return; + } + elsif ($opt eq "HK1_Heizkurve_Steigung" ) { # set HK1_Heizkurve_Steigung: set slope of heating curve for HKn + my $shift = ReadingsVal($name,"HK1_Heizkurve_Niveau",""); + vitoconnect_action($hash, + "heating.circuits.0.heating.curve/commands/setCurve", + "{\"shift\":$shift,\"slope\":$args[0]}", + $name,$opt,@args + ); + return; + } + elsif ($opt eq "HK2_Heizkurve_Steigung" ) { # set HK2_Heizkurve_Steigung: set slope of heating curve for HKn + my $shift = ReadingsVal($name,"HK2-Heizkurve-Niveau",""); + vitoconnect_action($hash, + "heating.circuits.1.heating.curve/commands/setCurve", + "{\"shift\":$shift,\"slope\":$args[0]}", + $name,$opt,@args + ); + return; + } + elsif ($opt eq "HK3_Heizkurve_Steigung" ) { # set HK3_Heizkurve_Steigung: set slope of heating curve for HKn + my $shift = ReadingsVal($name,"HK3-Heizkurve-Niveau",""); + vitoconnect_action($hash, + "heating.circuits.2.heating.curve/commands/setCurve", + "{\"shift\":$shift,\"slope\":$args[0]}", + $name,$opt,@args + ); + return; + } + elsif ($opt eq "HK1_Urlaub_Start_Zeit" ) { # set HK1_Urlaub_Start_Zeit: set holiday start time for HKn, start has to look like this: 2019-02-16 + my $end = ReadingsVal($name,"HK1_Urlaub_Ende_Zeit",""); + if ($end eq "") { + my $t = Time::Piece->strptime( $args[0], "%Y-%m-%d" ); + $t += ONE_DAY; + $end = $t->strftime("%Y-%m-%d"); } - elsif ( $attr_name eq "verbose" ) { + vitoconnect_action($hash, + "heating.circuits.0.operating.programs.holiday/commands/schedule", + "{\"start\":\"$args[0]\",\"end\":\"$end\"}", + $name,$opt,@args + ); + return; + } + elsif ($opt eq "HK2_Urlaub_Start_Zeit" ) { # set HK2_Urlaub_Start_Zeit: set holiday start time for HKn, start has to look like this: 2019-02-16 + my $end = ReadingsVal($name,"HK2_Urlaub_Ende_Zeit",""); + if ($end eq "") { + my $t = Time::Piece->strptime( $args[0], "%Y-%m-%d" ); + $t += ONE_DAY; + $end = $t->strftime("%Y-%m-%d"); } - else { + vitoconnect_action($hash, + "heating.circuits.1.operating.programs.holiday/commands/schedule", + "{\"start\":\"$args[0]\",\"end\":\"$end\"}", + $name,$opt,@args + ); + return; + } + elsif ($opt eq "HK3_Urlaub_Start_Zeit" ) { # set HK3-HK3_Urlaub_Start_Zeit: set holiday start time for HKn, start has to look like this: 2019-02-16 + my $end = ReadingsVal($name,"HK3_Urlaub_Ende_Zeit",""); + if ($end eq "") { + my $t = Time::Piece->strptime( $args[0], "%Y-%m-%d" ); + $t += ONE_DAY; + $end = $t->strftime("%Y-%m-%d"); + } + vitoconnect_action($hash, + "heating.circuits.2.operating.programs.holiday/commands/schedule", + "{\"start\":\"$args[0]\",\"end\":\"$end\"}", + $name,$opt,@args + ); + return; + } + elsif ($opt eq "HK1_Urlaub_Ende_Zeit" ) { # set HK1_Urlaub_Ende_Zeit: set holiday end time for HKn, end has to look like this: 2019-02-16 + my $start = ReadingsVal($name,"HK1_Urlaub_Start_Zeit",""); + vitoconnect_action($hash, + "heating.circuits.0.operating.programs.holiday/commands/schedule", + "{\"start\":\"$start\",\"end\":\"$args[0]\"}", + $name,$opt,@args + ); + return; + } + elsif ($opt eq "HK2_Urlaub_Ende_Zeit" ) { # set HK2_Urlaub_Ende_Zeit: set holiday end time for HKn, end has to look like this: 2019-02-16 + my $start = ReadingsVal($name,"HK2_Urlaub_Start_Zeit",""); + vitoconnect_action($hash, + "heating.circuits.1.operating.programs.holiday/commands/schedule", + "{\"start\":\"$start\",\"end\":\"$args[0]\"}", + $name,$opt,@args + ); + return; + } + elsif ($opt eq "HK3_Urlaub_Ende_Zeit" ) { # set HK3_Urlaub_Ende_Zeit: set holiday end time for HKn, end has to look like this: 2019-02-16 + my $start = ReadingsVal($name,"HK3_Urlaub_Start_Zeit",""); + vitoconnect_action($hash, + "heating.circuits.2.operating.programs.holiday/commands/schedule", + "{\"start\":\"$start\",\"end\":\"$args[0]\"}", + $name,$opt,@args + ); + return; + } + elsif ($opt eq "HK1_Urlaub_stop" ) { # set HK1_Urlaub_stop: remove holiday start and end time for HKn + vitoconnect_action($hash, + "heating.circuits.0.operating.programs.holiday/commands/unschedule", + "{}", + $name,$opt,@args + ); + return; + } + elsif ($opt eq "HK2_Urlaub_stop" ) { # set HK2_Urlaub_stop: remove holiday start and end time for HKn + vitoconnect_action($hash, + "heating.circuits.1.operating.programs.holiday/commands/unschedule", + "{}", + $name,$opt,@args + ); + return; + } + elsif ($opt eq "HK3_Urlaub_stop" ) { # set HK3_Urlaub_stop: remove holiday start and end time for HKn + vitoconnect_action($hash, + "heating.circuits.2.operating.programs.holiday/commands/unschedule", + "{}", + $name,$opt,@args + ); + return; + } + elsif ($opt eq "HK1_Zeitsteuerung_Heizung" ) { # set HK1_Zeitsteuerung_Heizung: sets the heating schedule in JSON format for HKn + vitoconnect_action($hash, + "heating.circuits.0.heating.schedule/commands/setSchedule", + "{\"newSchedule\":@args}", + $name,$opt,@args + ); + return; + } + elsif ($opt eq "HK2-Zeitsteuerung_Heizung" ) { # set HK2-Zeitsteuerung_Heizung: sets the heating schedule in JSON format for HKn + vitoconnect_action($hash, + "heating.circuits.1.heating.schedule/commands/setSchedule", + "{\"newSchedule\":@args}", + $name,$opt,@args + ); + return; + } + elsif ($opt eq "HK3-Zeitsteuerung_Heizung" ) { # set HK3-Zeitsteuerung_Heizung: sets the heating schedule in JSON format for HKn + vitoconnect_action($hash, + "heating.circuits.2.heating.schedule/commands/setSchedule", + "{\"newSchedule\":@args}", + $name,$opt,@args + ); + return; + } + elsif ($opt eq "HK2_Betriebsart" ) { # set HK2-Betriebsart: sets HKn_Betriebsart to heating,standby + vitoconnect_action($hash, + "heating.circuits.1.operating.modes.active/commands/setMode", + "{\"mode\":\"$args[0]\"}", + $name,$opt,@args + ); + return; + } + elsif ($opt eq "HK3_Betriebsart" ) { # set HK3-Betriebsart: sets HKn_Betriebsart to heating,standby + vitoconnect_action($hash, + "heating.circuits.2.operating.modes.active/commands/setMode", + "{\"mode\":\"$args[0]\"}", + $name,$opt,@args + ); + return; + } + elsif ($opt eq "HK2-Solltemperatur_comfort_aktiv" ) { # set HK2-Solltemperatur_comfort_aktiv: activate/deactivate comfort temperature for HKn + vitoconnect_action($hash, + "heating.circuits.1.operating.programs.comfort/commands/$args[0]", + "{}", + $name,$opt,@args + ); + return; + } + elsif ($opt eq "HK3-Solltemperatur_comfort_aktiv" ) { # set HK3-Solltemperatur_comfort_aktiv: activate/deactivate comfort temperature for HKn + vitoconnect_action($hash, + "heating.circuits.2.operating.programs.comfort/commands/$args[0]", + "{}", + $name,$opt,@args + ); + return; + } + elsif ($opt eq "HK2-Solltemperatur_comfort" ) { # set HK2-Solltemperatur_comfort: set comfort target temperatur for HKn + vitoconnect_action($hash, + "heating.circuits.1.operating.programs.comfort/commands/setTemperature", + "{\"targetTemperature\":$args[0]}", + $name,$opt,@args + ); + return; + } + elsif ($opt eq "HK3-Solltemperatur_comfort" ) { # set HK3-Solltemperatur_comfort: set comfort target temperatur for HKn + vitoconnect_action($hash, + "heating.circuits.2.operating.programs.comfort/commands/setTemperature", + "{\"targetTemperature\":$args[0]}", + $name,$opt,@args + ); + return; + } + elsif ($opt eq "HK2_Solltemperatur_eco_aktiv" ) { # set HK2_Solltemperatur_eco_aktiv: activate/deactivate eco temperature for HKn + vitoconnect_action($hash, + "heating.circuits.1.operating.programs.eco/commands/$args[0]", + "{}", + $name,$opt,@args + ); + return; + } + elsif ($opt eq "HK3_Solltemperatur_eco_aktiv" ) { # set HK3_Solltemperatur_eco_aktiv: activate/deactivate eco temperature for HKn + vitoconnect_action($hash, + "heating.circuits.2.operating.programs.eco/commands/$args[0]", + "{}", + $name,$opt,@args + ); + return; + } + elsif ($opt eq "HK2_Solltemperatur_normal" ) { # set HK2_Solltemperatur_normal: sets the normale target temperature for HKn, where targetTemperature is an integer between 3 and 37 + vitoconnect_action($hash, + "heating.circuits.1.operating.programs.normal/commands/setTemperature", + "{\"targetTemperature\":$args[0]}", + $name,$opt,@args + ); + return; + } + elsif ($opt eq "HK3_Solltemperatur_normal" ) { # set HK3_Solltemperatur_normal: sets the normale target temperature for HKn, where targetTemperature is an integer between 3 and 37 + vitoconnect_action($hash, + "heating.circuits.2.operating.programs.normal/commands/setTemperature", + "{\"targetTemperature\":$args[0]}", + $name,$opt,@args + ); + return; + } + elsif ($opt eq "HK2_Solltemperatur_reduziert" ) { # set HK2_Solltemperatur_reduziert: sets the reduced target temperature for HKn, where targetTemperature is an integer between 3 and 37 + vitoconnect_action($hash, + "heating.circuits.1.operating.programs.reduced/commands/setTemperature", + "{\"targetTemperature\":$args[0]}", + $name,$opt,@args + ); + return; + } + elsif ($opt eq "HK3_Solltemperatur_reduziert" ) { # set HK3_Solltemperatur_reduziert: sets the reduced target temperature for HKn, where targetTemperature is an integer between 3 and 37 + vitoconnect_action($hash, + "heating.circuits.2.operating.programs.reduced/commands/setTemperature", + "{\"targetTemperature\":$args[0]}" + ,$name,$opt,@args + ); + return; + } + + my $val = "WW_einmaliges_Aufladen:activate,deactivate " + ."WW_Zirkulationspumpe_Zeitplan:textField-long " + ."WW_Zeitplan:textField-long " +# ."WW_Haupttemperatur:slider,10,1,60 " + ."WW_Solltemperatur:slider,10,1,60 " + ."WW_Temperatur_2:slider,10,1,60 " + ."WW_Betriebsart:balanced,off " + ."Urlaub_Start_Zeit " + ."Urlaub_Ende_Zeit " + ."Urlaub_stop:noArg "; + + if (ReadingsVal($name,"HK1_aktiv","0") eq "1") { + $val .= + "HK1_Heizkurve_Niveau:slider,-13,1,40 " + ."HK1_Heizkurve_Steigung:slider,0.2,0.1,3.5,1 " + ."HK1_Zeitsteuerung_Heizung:textField-long " + ."HK1_Urlaub_Start_Zeit " + ."HK1_Urlaub_Ende_Zeit " + ."HK1_Urlaub_stop:noArg " + ."HK1_Betriebsart:active,standby " + ."HK1_Soll_Temp_comfort_aktiv:activate,deactivate " + ."HK1_Soll_Temp_comfort:slider,4,1,37 " + ."HK1_Soll_Temp_eco_aktiv:activate,deactivate " + ."HK1_Soll_Temp_normal:slider,3,1,37 " + ."HK1_Soll_Temp_reduziert:slider,3,1,37 " + ."HK1_Name "; + } + if (ReadingsVal($name,"HK2_aktiv","0") eq "1") { + $val .= + "HK2_Heizkurve_Niveau:slider,-13,1,40 " + . "HK2_Heizkurve_Steigung:slider,0.2,0.1,3.5,1 " + . "HK2_Zeitsteuerung_Heizung:textField-long " + . "HK2_Urlaub_Start_Zeit " + . "HK2_Urlaub_Ende_Zeit " + . "HK2_Urlaub_stop:noArg " + . "HK2_Betriebsart:active,standby,heating,dhw,dhwAndHeating,forcedReduced,forcedNormal " + . "HK2_Solltemperatur_comfort_aktiv:activate,deactivate " + . "HK2_Solltemperatur_comfort:slider,4,1,37 " + . "HK2_Solltemperatur_eco_aktiv:activate,deactivate " + . "HK2_Solltemperatur_normal:slider,3,1,37 " + . "HK2_Solltemperatur_reduziert:slider,3,1,37 " + . "HK2_Name "; + } + if (ReadingsVal($name,"HK3_aktiv","0") eq "1") { + $val .= + "HK3_Heizkurve_Niveau:slider,-13,1,40 " + . "HK3_Heizkurve_Steigung:slider,0.2,0.1,3.5,1 " + . "HK3_Zeitsteuerung_Heizung:textField-long " + . "HK3_Urlaub_Start_Zeit " + . "HK3_Urlaub_Ende_Zeit " + . "HK3_Urlaub_stop:noArg " + . "HK3_Betriebsart:active,standby,heating,dhw,dhwAndHeating,forcedReduced,forcedNormal " + . "HK3_Solltemperatur_comfort_aktiv:activate,deactivate " + . "HK3_Solltemperatur_comfort:slider,4,1,37 " + . "HK3_Solltemperatur_eco_aktiv:activate,deactivate " + . "HK3_Solltemperatur_normal:slider,3,1,37 " + . "HK3_Solltemperatur_reduziert:slider,3,1,37 " + . "HK3_Name "; + } + + return $val; +} + + +##################################################################################################################### +# Attribute setzen/ändern/löschen +##################################################################################################################### +sub vitoconnect_Attr { + my ($cmd,$name,$attr_name,$attr_value ) = @_; + + Log(5,$name.", ".$cmd ." vitoconnect_: ".$attr_name." value: ".$attr_value); + if ($cmd eq "set") { + if ($attr_name eq "vitoconnect_raw_readings" ) { + if ($attr_value !~ /^0|1$/) { + my $err = "Invalid argument ".$attr_value." to ".$attr_name.". Must be 0 or 1."; + Log(1,$name.", vitoconnect_Attr: ".$err); + return $err; + } + } + elsif ($attr_name eq "vitoconnect_disable_raw_readings") { + if ( $attr_value !~ /^0|1$/ ) { + my $err = "Invalid argument ".$attr_value." to ".$attr_name.". Must be 0 or 1."; + Log(1,$name.", vitoconnect_Attr: ".$err); + return $err; + } + } + elsif ($attr_name eq "vitoconnect_gw_readings") { + if ( $attr_value !~ /^0|1$/ ) { + my $err = "Invalid argument ".$attr_value." to ".$attr_name.". Must be 0 or 1."; + Log(1,$name.", vitoconnect_Attr: ".$err); + return $err; + } + } + elsif ($attr_name eq "vitoconnect_actions_active") { + if ($attr_value !~ /^0|1$/) { + my $err = "Invalid argument ".$attr_value." to ".$attr_name.". Must be 0 or 1."; + Log(1,$name.", vitoconnect_Attr: ".$err); + return $err; + } + } + elsif ($attr_name eq "vitoconnect_mappings") { + $RequestListMapping = eval $attr_value; + if ($@) { + # Fehlerbehandlung + my $err = "Invalid argument: $@\n"; + return $err; + } + } + elsif ($attr_name eq "vitoconnect_translations") { + %translations = eval $attr_value; + if ($@) { + # Fehlerbehandlung + my $err = "Invalid argument: $@\n"; + return $err; + } + } + elsif ($attr_name eq "vitoconnect_mapping_roger") { + if ($attr_value !~ /^0|1$/) { + my $err = "Invalid argument ".$attr_value." to ".$attr_name.". Must be 0 or 1."; + Log(1,$name.", vitoconnect_Attr: ".$err); + return $err; + } + } + elsif ($attr_name eq "vitoconnect_serial") { + # Zur Zeit kein prüfung, einfacher String + if (length($attr_value) != 16) { + my $err = "Invalid argument ".$attr_value." to ".$attr_name.". Must be 16 characters long."; + Log(5,$name.", vitoconnect_Attr: ".$err); + return $err; + } + } + elsif ($attr_name eq "vitoconnect_installationID") { + # Zur Zeit kein prüfung, einfacher String + if (length($attr_value) < 5) { + my $err = "Invalid argument ".$attr_value." to ".$attr_name.". Must be at least 5 characters long."; + Log(5,$name.", vitoconnect_Attr: ".$err); + return $err; + } + } + elsif ($attr_name eq "disable") { + } + elsif ($attr_name eq "verbose") { + } + else { # return "Unknown attr $attr_name"; } } + elsif ($cmd eq "del") { + if ($attr_name eq "vitoconnect_mappings") { + undef $RequestListMapping; + } + elsif ($attr_name eq "vitoconnect_translations") { + undef %translations; + } + } return; } -# Subs + +##################################################################################################################### +# # Abfrage aller Werte starten +##################################################################################################################### sub vitoconnect_GetUpdate { - my ($hash) = @_; + my ($hash) = @_;# Übergabe-Parameter my $name = $hash->{NAME}; - Log3 $name, 4, "$name - GetUpdate called ..."; - if ( IsDisabled($name) ) { - Log3 $name, 4, "$name - device disabled"; - InternalTimer( gettimeofday() + $hash->{intervall}, - "vitoconnect_GetUpdate", $hash ); + Log3($name,4,$name." - GetUpdate called ..."); + if (IsDisabled($name)) { # Device disabled + Log3($name,4,$name." - device disabled"); + InternalTimer(gettimeofday() + $hash->{intervall},"vitoconnect_GetUpdate",$hash); # nach Intervall erneut versuchen + return; } - else { + else { # Device nicht disabled vitoconnect_getResource($hash); } return; } + +##################################################################################################################### +# Werte für: Access-Token, Install-ID, Gateway anfragen +##################################################################################################################### sub vitoconnect_getCode { - my ($hash) = @_; + my ($hash) = @_; # Übergabe-Parameter my $name = $hash->{NAME}; - my $isiwebpasswd = vitoconnect_ReadKeyValue( $hash, "passwd" ); + my $isiwebpasswd = vitoconnect_ReadKeyValue($hash,"passwd"); # verschlüsseltes Kennwort auslesen my $client_id = $hash->{apiKey}; - if ( !defined($client_id) ) { - Log3 $name, 1, "$name - set apiKey first"; - readingsSingleUpdate( $hash, "state", "Set apiKey to continue", 1 ); + if (!defined($client_id)) { # $client_id/apiKey nicht definiert + Log3($name,1,$name." - set apiKey first"); # Fehlermeldung ins Log + readingsSingleUpdate($hash,"state","Set apiKey to continue",1); # Reading 'state' setzen return; } my $authorizeURL = 'https://iam.viessmann.com/idp/v2/authorize'; my $param = { - url => "$authorizeURL?client_id=$client_id" - . "&redirect_uri=$callback_uri&" - . "code_challenge=2e21faa1-db2c-4d0b-a10f-575fd372bc8c-575fd372bc8c&" - . "&scope=IoT%20User%20offline_access" - . "&response_type=code", + url => $authorizeURL + ."?client_id=".$client_id + ."&redirect_uri=".$callback_uri."&" + ."code_challenge=2e21faa1-db2c-4d0b-a10f-575fd372bc8c-575fd372bc8c&" + ."&scope=IoT%20User%20offline_access" + ."&response_type=code", hash => $hash, header => "Content-Type: application/x-www-form-urlencoded", ignoreredirects => 1, @@ -1495,61 +2776,69 @@ sub vitoconnect_getCode { #Log3 $name, 4, "$name - user=$param->{user} passwd=$param->{pwd}"; #Log3 $name, 5, Dumper($hash); - HttpUtils_NonblockingGet($param); + HttpUtils_NonblockingGet($param); # Anwort an: vitoconnect_getCodeCallback() return; } + +##################################################################################################################### +# Rückgabe: Access-Token, Install-ID, Gateway von vitoconnect_getCode Anfrage +##################################################################################################################### sub vitoconnect_getCodeCallback { - my ( $param, $err, $response_body ) = @_; + my ($param,$err,$response_body ) = @_; # Übergabe-Parameter my $hash = $param->{hash}; my $name = $hash->{NAME}; - if ( $err eq "" ) { - Log3 $name, 4, "$name - getCodeCallback went ok"; - Log3 $name, 5, "$name - Received response: $response_body"; + if ($err eq "") { # Antwort kein Fehler + Log3($name,4,$name." - getCodeCallback went ok"); + Log3($name,5,$name." - Received response: ".$response_body); $response_body =~ /code=(.*)"/; - $hash->{".code"} = $1; - Log3 $name, 4, "$name - code: " . $hash->{".code"}; - if ( $hash->{".code"} && $hash->{".code"} ne "4" ) { - $hash->{login} = "ok"; + $hash->{".code"} = $1; # in Internal '.code' speichern + Log3($name,4,$name." - code: ".$hash->{".code"}); + if ( $hash->{".code"} && $hash->{".code"} ne "4" ) { + $hash->{login} = "ok"; # Internal 'login' } else { - $hash->{login} = "failure"; + $hash->{login} = "failure"; # Internal 'login' } } - else { - Log3 $name, 1, "$name - An error occured: $err"; + else { # Fehler als Antwort + Log3($name,1,$name.", vitoconnect_getCodeCallback - An error occured: ".$err); $hash->{login} = "failure"; } - if ( $hash->{login} eq "ok" ) { - readingsSingleUpdate( $hash, "state", "login ok", 1 ); - vitoconnect_getAccessToken($hash); + + if ( $hash->{login} eq "ok" ) { # Login hat geklappt + readingsSingleUpdate($hash,"state","login ok",1); # Reading 'state' setzen + vitoconnect_getAccessToken($hash); # Access & Refresh-Token holen } - else { - readingsSingleUpdate( $hash, "state", - "Login failure. Check password and apiKey", 1 ); - Log3 $name, 1, "$name - Login failure. Check password and apiKey"; - InternalTimer( gettimeofday() + $hash->{intervall}, - "vitoconnect_GetUpdate", $hash ); # Forum: #880 + else { # Fehler beim Login + readingsSingleUpdate($hash,"state","Login failure. Check password and apiKey",1); # Reading 'state' setzen + Log3($name,1,$name." - Login failure. Check password and apiKey"); + InternalTimer(gettimeofday() + $hash->{intervall},"vitoconnect_GetUpdate",$hash); # Forum: #880 + return; } return; } + +##################################################################################################################### +# Access & Refresh-Token holen +##################################################################################################################### sub vitoconnect_getAccessToken { - my ($hash) = @_; - my $name = $hash->{NAME}; - my $client_id = $hash->{apiKey}; + my ($hash) = @_; # Übergabe-Parameter + my $name = $hash->{NAME}; # Device-Name + my $client_id = $hash->{apiKey}; # Internal: apiKey my $param = { url => "https://iam.viessmann.com/idp/v2/token", hash => $hash, header => "Content-Type: application/x-www-form-urlencoded", data => "grant_type=authorization_code" - . "&code_verifier=" - . $client_secret - . "&client_id=$client_id" - . "&redirect_uri=$callback_uri" - . "&code=" - . $hash->{".code"}, + . "&code_verifier=" + . $client_secret + . "&client_id=$client_id" + . "&redirect_uri=$callback_uri" + . "&code=" + . $hash->{".code"}, sslargs => { SSL_verify_mode => 0 }, method => "POST", timeout => $hash->{timeout}, @@ -1557,49 +2846,55 @@ sub vitoconnect_getAccessToken { }; #Log3 $name, 1, "$name - " . $param->{"data"}; - HttpUtils_NonblockingGet($param); + HttpUtils_NonblockingGet($param); # Anwort an: vitoconnect_getAccessTokenCallback() return; } -sub vitoconnect_getAccessTokenCallback { - my ( $param, $err, $response_body ) = @_; - my $hash = $param->{hash}; - my $name = $hash->{NAME}; - if ( $err eq "" ) { - Log3 $name, 4, "$name - getAccessTokenCallback went ok"; - Log3 $name, 5, "$name - Received response: $response_body\n"; - my $decode_json = eval { decode_json($response_body) }; - if ($@) { - Log3 $name, 1, "$name - JSON error while request: $@"; - InternalTimer( gettimeofday() + $hash->{intervall}, - "vitoconnect_GetUpdate", $hash ); +##################################################################################################################### +# Access & Refresh-Token speichern, Antwort auf: vitoconnect_getAccessToken +##################################################################################################################### +sub vitoconnect_getAccessTokenCallback { + my ($param,$err,$response_body) = @_; # Übergabe-Parameter + my $hash = $param->{hash}; + my $name = $hash->{NAME}; # Device-Name + + if ($err eq "") { # kein Fehler bei Antwort + Log3($name,4,$name." - getAccessTokenCallback went ok"); + Log3($name,5,$name." - Received response: ".$response_body."\n"); + my $decode_json = eval {decode_json($response_body)}; + if ($@) { + Log3($name,1,$name.", vitoconnect_getAccessTokenCallback: JSON error while request: ".$@); + InternalTimer(gettimeofday() + $hash->{intervall},"vitoconnect_GetUpdate",$hash); return; } - my $access_token = $decode_json->{"access_token"}; - if ( $access_token ne "" ) { - $hash->{".access_token"} = $access_token; - $hash->{"refresh_token"} = $decode_json->{"refresh_token"}; + my $access_token = $decode_json->{"access_token"}; # aus JSON dekodieren + if ($access_token ne "") { + $hash->{".access_token"} = $access_token; # in Internals speichern + $hash->{"refresh_token"} = $decode_json->{"refresh_token"}; # in Internals speichern - Log3 $name, 4, - "$name - Access Token: " . substr( $access_token, 0, 20 ) . "..."; - vitoconnect_getGw($hash); + Log3($name,4,$name." - Access Token: ".substr($access_token,0,20)."..."); + vitoconnect_getGw($hash); # Abfrage Gateway-Serial } - else { - Log3 $name, 1, "$name - Access Token: nicht definiert"; - Log3 $name, 5, "$name - Received response: $response_body\n"; - InternalTimer( gettimeofday() + $hash->{intervall}, - "vitoconnect_GetUpdate", $hash ); + else { + Log3($name,1,$name." - Access Token: nicht definiert"); + Log3($name,5,$name." - Received response: ".$response_body."\n"); + InternalTimer(gettimeofday() + $hash->{intervall},"vitoconnect_GetUpdate",$hash); + return; } } - else { - Log3 $name, 1, "$name - getAccessToken: An error occured: $err"; - InternalTimer( gettimeofday() + $hash->{intervall}, - "vitoconnect_GetUpdate", $hash ); + else { # Fehler bei Antwort + Log3($name,1,$name.",vitoconnect_getAccessTokenCallback - getAccessToken: An error occured: ".$err); + InternalTimer(gettimeofday() + $hash->{intervall},"vitoconnect_GetUpdate",$hash); + return; } return; } + +##################################################################################################################### +# neuen Access-Token anfragen +##################################################################################################################### sub vitoconnect_getRefresh { my ($hash) = @_; my $name = $hash->{NAME}; @@ -1623,54 +2918,62 @@ sub vitoconnect_getRefresh { return; } + +##################################################################################################################### +# neuen Access-Token speichern +##################################################################################################################### sub vitoconnect_getRefreshCallback { - my ( $param, $err, $response_body ) = @_; + my ($param,$err,$response_body) = @_; # Übergabe-Parameter my $hash = $param->{hash}; my $name = $hash->{NAME}; - if ( $err eq "" ) { - Log3 $name, 4, "$name - getRefreshCallback went ok"; - Log3 $name, 5, "$name - Received response: $response_body\n"; - my $decode_json = eval { decode_json($response_body) }; - if ($@) { - Log3 $name, 1, "$name - JSON error while request: $@"; - InternalTimer( gettimeofday() + $hash->{intervall}, - "vitoconnect_GetUpdate", $hash ); + if ($err eq "") { + Log3($name,4,$name.". - getRefreshCallback went ok"); + Log3($name,5,$name." - Received response: ".$response_body."\n"); + my $decode_json = eval {decode_json($response_body)}; + if ($@) { # Fehler aufgetreten + Log3($name,1,$name.", vitoconnect_getRefreshCallback: JSON error while request: ".$@); + InternalTimer(gettimeofday() + $hash->{intervall},"vitoconnect_GetUpdate",$hash); return; } my $access_token = $decode_json->{"access_token"}; - if ( $access_token ne "" ) { - $hash->{".access_token"} = $access_token; - - #$hash->{"refresh_token"} = $decode_json->{"refresh_token"}; - - Log3 $name, 4, - "$name - Access Token: " . substr( $access_token, 0, 20 ) . "..."; - vitoconnect_getGw($hash); + if ($access_token ne "") { # kein Fehler + $hash->{".access_token"} = $access_token; # in Internal merken + Log3($name,4,$name." - Access Token: ".substr($access_token,0,20)."..."); + #vitoconnect_getGw($hash); # Abfrage Gateway-Serial + # directly call get resource to save API calls + vitoconnect_getResource($hash); } else { Log3 $name, 1, "$name - Access Token: nicht definiert"; Log3 $name, 5, "$name - Received response: $response_body\n"; - InternalTimer( gettimeofday() + $hash->{intervall}, - "vitoconnect_GetUpdate", $hash ); # zurück zu getCode? + InternalTimer(gettimeofday() + $hash->{intervall},"vitoconnect_GetUpdate",$hash); # zurück zu getCode? + return; } } else { Log3 $name, 1, "$name - getRefresh: An error occured: $err"; - InternalTimer( gettimeofday() + $hash->{intervall}, - "vitoconnect_GetUpdate", $hash ); + InternalTimer(gettimeofday() + $hash->{intervall},"vitoconnect_GetUpdate",$hash); + return; } return; } + +##################################################################################################################### +# Abfrage Gateway-Serial +# https://documentation.viessmann.com/static/iot/overview +##################################################################################################################### sub vitoconnect_getGw { - my ($hash) = @_; + my ($hash) = @_; # Übergabe-Parameter my $name = $hash->{NAME}; my $access_token = $hash->{".access_token"}; my $param = { - url => $apiURL . "gateways", +# url => $apiURL + url => $iotURL_V1 + ."gateways", hash => $hash, - header => "Authorization: Bearer $access_token", + header => "Authorization: Bearer ".$access_token, timeout => $hash->{timeout}, sslargs => { SSL_verify_mode => 0 }, callback => \&vitoconnect_getGwCallback @@ -1679,274 +2982,718 @@ sub vitoconnect_getGw { return; } + +##################################################################################################################### +# Gateway-Serial speichern, Anwort von Abfrage Gateway-Serial +##################################################################################################################### sub vitoconnect_getGwCallback { - my ( $param, $err, $response_body ) = @_; + my ($param,$err,$response_body) = @_; # Übergabe-Parameter my $hash = $param->{hash}; my $name = $hash->{NAME}; - if ( $err eq "" ) { - Log3 $name, 4, "$name - getGwCallback went ok"; - Log3 $name, 5, "$name - Received response: $response_body\n"; - my $items = eval { decode_json($response_body) }; - if ($@) { - readingsSingleUpdate( $hash, "state", - "JSON error while request: $@", 1 ); - Log3 $name, 1, "$name - JSON error while request: $@"; - InternalTimer( gettimeofday() + $hash->{intervall}, - "vitoconnect_GetUpdate", $hash ); + if ($err eq "") { # kein Fehler aufgetreten + Log3($name,4,$name." - getGwCallback went ok"); + Log3($name,5,$name." - Received response: ".$response_body."\n"); + my $items = eval {decode_json($response_body)}; + if ($@) { # Fehler beim JSON dekodieren + readingsSingleUpdate($hash,"state","JSON error while request: ".$@,1); # Reading 'state' + Log3($name,1,$name.", vitoconnect_getGwCallback: JSON error while request: ".$@); + InternalTimer(gettimeofday() + $hash->{intervall},"vitoconnect_GetUpdate",$hash); return; } - if ( $hash->{".logResponseOnce"} ) { - my $dir = path( AttrVal( "global", "logdir", "log" ) ); + $err = vitoconnect_errorHandling($hash,$items); + if ($err ==1){ + return; + } + + if ($hash->{".logResponseOnce"} ) { + my $dir = path( AttrVal("global","logdir","log")); my $file = $dir->child("gw.json"); my $file_handle = $file->openw_utf8(); - $file_handle->print( Dumper($items) ); + $file_handle->print(Dumper($items)); # Datei 'gw.json' schreiben + Log3($name,3,$name." Datei: ".$dir."/".$file." geschrieben"); + } + + # Alle Gateways holen und in hash schreiben, immer machen falls neue Geräte hinzu kommen + my %devices; + + # Über jedes Gateway-Element in der JSON-Datenstruktur iterieren + foreach my $gateway (@{$items->{data}}) { + if (defined $gateway->{serial} && defined $gateway->{installationId}) { + $devices{$gateway->{serial}} = { + installationId => $gateway->{installationId}, + gatewayType => $gateway->{gatewayType}, + version => $gateway->{version} + }; + } + } + + $hash->{devices} = { %devices }; + + if ( defined(AttrVal( $name, 'vitoconnect_installationID', 0 )) + && AttrVal( $name, 'vitoconnect_installationID', 0 ) ne "" + && AttrVal( $name, 'vitoconnect_installationID', 0 ) != 0 + && defined(AttrVal( $name, 'vitoconnect_serial', 0 )) + && AttrVal( $name, 'vitoconnect_serial', 0 ) ne "" + && AttrVal( $name, 'vitoconnect_serial', 0 ) != 0 ) { + # Attribute sind gesetzt, nichts zu tun + Log3($name,5,$name." - getGW all atributes set already attr: instID: ".AttrVal( $name, 'vitoconnect_installationID', 0 ). + ", serial: ".AttrVal( $name, 'vitoconnect_serial', 0 )); + } else + { + # Prüfungen der Gateways und weiteres vorgehen + my $num_devices = scalar keys %devices; + + if ($num_devices == 0) { + readingsSingleUpdate($hash,"state","Keine Gateways/Devices gefunden, Account prüfen",1); + return; + } elsif ($num_devices == 1) { + readingsSingleUpdate($hash,"state","Genau ein Gateway/Device gefunden",1); + + my ($serial) = keys %devices; + my $installationId = $devices{$serial}->{installationId}; + Log3($name,4,$name." - getGW exactly one Device found set attr: instID: $installationId, serial $serial"); + CommandAttr (undef, "$name vitoconnect_installationID $installationId"); + CommandAttr (undef, "$name vitoconnect_serial $serial"); + } else { + readingsSingleUpdate($hash,"state","Mehrere Gateways/Devices gefunden, bitte eines auswählen über selectDevice",1); + return; + } + } + + if (AttrVal( $name, 'vitoconnect_gw_readings', 0 ) eq "1") { + readingsSingleUpdate($hash,"gw",$response_body,1); # im Reading 'gw' merken + readingsSingleUpdate($hash,"number_of_gateways",scalar keys %devices,1); + } + + # Alle Infos besorgt, rest nur für logResponceOnce + if ($hash->{".logResponseOnce"} ) { + vitoconnect_getInstallation($hash); + vitoconnect_getInstallationFeatures($hash); + } else { + vitoconnect_getResource($hash); } - $hash->{".gw"} = $items->{data}[0]->{serial}; - readingsSingleUpdate( $hash, "gw", $response_body, 1 ); - vitoconnect_getInstallation($hash); } - else { - Log3 $name, 1, "$name - An error occured: $err"; - InternalTimer( gettimeofday() + $hash->{intervall}, - "vitoconnect_GetUpdate", $hash ); + else { # Fehler aufgetreten + Log3($name,1,$name." - An error occured: ".$err); + InternalTimer(gettimeofday() + $hash->{intervall},"vitoconnect_GetUpdate",$hash); } return; } + +##################################################################################################################### +# Abfrage Install-ID +# https://documentation.viessmann.com/static/iot/overview +##################################################################################################################### sub vitoconnect_getInstallation { my ($hash) = @_; my $name = $hash->{NAME}; my $access_token = $hash->{".access_token"}; my $param = { - url => $apiURL . "installations", +# url => $apiURL + url => $iotURL_V1 + ."installations", hash => $hash, - header => "Authorization: Bearer $access_token", + header => "Authorization: Bearer ".$access_token, timeout => $hash->{timeout}, sslargs => { SSL_verify_mode => 0 }, callback => \&vitoconnect_getInstallationCallback - }; + }; HttpUtils_NonblockingGet($param); return; } + +##################################################################################################################### +# Install-ID speichern, Antwort von Abfrage Install-ID +##################################################################################################################### sub vitoconnect_getInstallationCallback { my ( $param, $err, $response_body ) = @_; my $hash = $param->{hash}; my $name = $hash->{NAME}; + my $gw = AttrVal( $name, 'vitoconnect_serial', 0 ); - if ( $err eq "" ) { + if ($err eq "") { Log3 $name, 4, "$name - getInstallationCallback went ok"; Log3 $name, 5, "$name - Received response: $response_body"; my $items = eval { decode_json($response_body) }; if ($@) { - readingsSingleUpdate( $hash, "state", - "JSON error while request: $@", 1 ); - Log3 $name, 1, "$name - JSON error while request: $@"; - InternalTimer( gettimeofday() + $hash->{intervall}, - "vitoconnect_GetUpdate", $hash ); + readingsSingleUpdate( $hash, "state","JSON error while request: ".$@,1); + Log3($name,1,$name.", vitoconnect_getInstallationCallback: JSON error while request: ".$@); + InternalTimer(gettimeofday() + $hash->{intervall},"vitoconnect_GetUpdate",$hash); return; } - if ( $hash->{".logResponseOnce"} ) { - my $dir = path( AttrVal( "global", "logdir", "log" ) ); - my $file = $dir->child("installation.json"); + if ($hash->{".logResponseOnce"}) { + my $dir = path( AttrVal("global","logdir","log")); + my $file = $dir->child("installation_" . $gw . ".json"); my $file_handle = $file->openw_utf8(); - $file_handle->print( Dumper($items) ); + $file_handle->print(Dumper($items)); # Datei 'installation.json' schreiben + Log3($name,3,$name." Datei: ".$dir."/".$file." geschrieben"); } - my $id = $items->{data}[0]->{id}; - if ( $id == "" ) { - Log3 $name, 1, "$name - Something went wrong. Will retry"; - InternalTimer( gettimeofday() + $hash->{intervall}, - "vitoconnect_GetUpdate", $hash ); - } - else { - $hash->{".installation"} = $items->{data}[0]->{id}; - readingsSingleUpdate( $hash, "installation", $response_body, 1 ); - vitoconnect_getDevice($hash); + + if (AttrVal( $name, 'vitoconnect_gw_readings', 0 ) eq "1") { + readingsSingleUpdate( $hash, "installation", $response_body, 1 ); } + + vitoconnect_getDevice($hash); + } else { Log3 $name, 1, "$name - An error occured: $err"; - InternalTimer( gettimeofday() + $hash->{intervall}, - "vitoconnect_GetUpdate", $hash ); + InternalTimer(gettimeofday() + $hash->{intervall},"vitoconnect_GetUpdate",$hash); } return; } + +##################################################################################################################### +# Abfrage von Install-features speichern +##################################################################################################################### +sub vitoconnect_getInstallationFeatures { + my ($hash) = @_; + my $name = $hash->{NAME}; + my $access_token = $hash->{".access_token"}; + my $installation = AttrVal( $name, 'vitoconnect_installationID', 0 ); + + + # installation features #Fixme call only once + my $param = { +# url => $apiURL + url => $iotURL_V2 + ."installations/".$installation."/features", + hash => $hash, + header => "Authorization: Bearer ".$access_token, + timeout => $hash->{timeout}, + sslargs => { SSL_verify_mode => 0 }, + callback => \&vitoconnect_getInstallationFeaturesCallback + }; + + HttpUtils_NonblockingGet($param); + return; +} + + +##################################################################################################################### +#Install-features speichern +##################################################################################################################### +sub vitoconnect_getInstallationFeaturesCallback { + my ( $param, $err, $response_body ) = @_; + my $hash = $param->{hash}; + my $name = $hash->{NAME}; + my $gw = AttrVal( $name, 'vitoconnect_serial', 0 ); + + my $decode_json = eval {decode_json($response_body)}; + if ((defined($err) && $err ne "") || (defined($decode_json->{statusCode}) && $decode_json->{statusCode} ne "")) { # Fehler aufgetreten + Log3($name,1,$name.",vitoconnect_getFeatures: Fehler während installation features: ".$err." :: ".$response_body); + $err = vitoconnect_errorHandling($hash,$decode_json); + if ($err ==1){ + return; + } + } + else { # kein Fehler aufgetreten + + if ($hash->{".logResponseOnce"}) { + my $dir = path( AttrVal("global","logdir","log")); + my $file = $dir->child("installation_features_" . $gw . ".json"); + my $file_handle = $file->openw_utf8(); + $file_handle->print(Dumper($decode_json)); # Datei 'installation.json' schreiben + Log3($name,3,$name." Datei: ".$dir."/".$file." geschrieben"); + } + + if (AttrVal( $name, 'vitoconnect_gw_readings', 0 ) eq "1") { + readingsSingleUpdate($hash,"installation_features",$response_body,1); # im Reading 'installation_features' merken + } + + return; + } +} + + +##################################################################################################################### +# Abfrage Device-ID +##################################################################################################################### sub vitoconnect_getDevice { my ($hash) = @_; my $name = $hash->{NAME}; my $access_token = $hash->{".access_token"}; - my $installation = $hash->{".installation"}; - my $gw = $hash->{".gw"}; + my $installation = AttrVal( $name, 'vitoconnect_installationID', 0 ); + my $gw = AttrVal( $name, 'vitoconnect_serial', 0 ); + + Log(5,$name.", --getDevice gw for call set: ".$gw); + my $param = { - url => $apiURL . "installations/$installation/gateways/$gw/devices", + url => $iotURL_V1 + ."installations/".$installation."/gateways/".$gw."/devices", hash => $hash, - header => "Authorization: Bearer $access_token", + header => "Authorization: Bearer ".$access_token, timeout => $hash->{timeout}, sslargs => { SSL_verify_mode => 0 }, callback => \&vitoconnect_getDeviceCallback }; HttpUtils_NonblockingGet($param); + return; } + +##################################################################################################################### +# Device-ID speichern, Anwort von Abfrage Device-ID +##################################################################################################################### sub vitoconnect_getDeviceCallback { my ( $param, $err, $response_body ) = @_; my $hash = $param->{hash}; my $name = $hash->{NAME}; + my $gw = AttrVal( $name, 'vitoconnect_serial', 0 ); - if ( $err eq "" ) { + Log(5,$name.", -getDeviceCallback get device gw: ".$gw); + if ($err eq "") { Log3 $name, 4, "$name - getDeviceCallback went ok"; Log3 $name, 5, "$name - Received response: $response_body\n"; my $items = eval { decode_json($response_body) }; - if ($@) { - readingsSingleUpdate( $hash, "state", - "JSON error while request: $@", 1 ); - Log3 $name, 1, "$name - JSON error while request: $@"; - InternalTimer( gettimeofday() + $hash->{intervall}, - "vitoconnect_GetUpdate", $hash ); + if ($@) { + RemoveInternalTimer($hash); + readingsSingleUpdate($hash,"state","JSON error while request: ".$@,1); + Log3($name,1,$name.", vitoconnect_getDeviceCallback: JSON error while request: ".$@); + InternalTimer(gettimeofday() + $hash->{intervall},"vitoconnect_GetUpdate",$hash); return; } - if ( $hash->{".logResponseOnce"} ) { - my $dir = path( AttrVal( "global", "logdir", "log" ) ); - my $file = $dir->child("device.json"); + if ( $hash->{".logResponseOnce"} ) { + my $dir = path( AttrVal("global","logdir","log")); + my $filename = "device_" . $gw . ".json"; + my $file = $dir->child($filename); my $file_handle = $file->openw_utf8(); - $file_handle->print( Dumper($items) ); + $file_handle->print(Dumper($items)); # Datei 'device.json' schreiben + Log3($name,3,$name." Datei: ".$dir."/".$file." geschrieben"); + } + if (AttrVal( $name, 'vitoconnect_gw_readings', 0 ) eq "1") { + readingsSingleUpdate($hash,"device",$response_body,1); # im Reading 'device' merken } - readingsSingleUpdate( $hash, "device", $response_body, 1 ); vitoconnect_getFeatures($hash); + } + else { + if ((defined($err) && $err ne "")) { # Fehler aufgetreten + Log3($name,1,$name." - An error occured: ".$err); + } else { + Log3($name,1,$name." - An undefined error occured"); + } + RemoveInternalTimer($hash); + InternalTimer(gettimeofday() + $hash->{intervall},"vitoconnect_GetUpdate",$hash); + } + return; +} + + +##################################################################################################################### +# Abruf GW Features, Anwort von Abfrage Device-ID +# https://documentation.viessmann.com/static/iot/overview +##################################################################################################################### +sub vitoconnect_getFeatures { + my ($hash) = shift; # Übergabe-Parameter + my $name = $hash->{NAME}; + my $access_token = $hash->{".access_token"}; + my $installation = AttrVal( $name, 'vitoconnect_installationID', 0 ); + my $gw = AttrVal( $name, 'vitoconnect_serial', 0 ); + my $dev = AttrVal($name,'vitoconnect_device',0); # Attribut: vitoconnect_device (0,1), Standard: 0 + + Log3($name,4,$name." - getFeatures went ok"); + +# Gateway features + my $param = { + url => $iotURL_V2 + ."installations/".$installation."/gateways/".$gw."/features", + hash => $hash, + header => "Authorization: Bearer ".$access_token, + timeout => $hash->{timeout}, + sslargs => { SSL_verify_mode => 0 }, + callback => \&vitoconnect_getFeaturesCallback + }; + + HttpUtils_NonblockingGet($param); + return; +} + + +##################################################################################################################### +# GW Features speichern +# https://documentation.viessmann.com/static/iot/overview +##################################################################################################################### +sub vitoconnect_getFeaturesCallback { + my ( $param, $err, $response_body ) = @_; + my $hash = $param->{hash}; + my $name = $hash->{NAME}; + my $gw = AttrVal( $name, 'vitoconnect_serial', 0 ); + + my $decode_json = eval {decode_json($response_body)}; + + if ((defined($err) && $err ne "") || (defined($decode_json->{statusCode}) && $decode_json->{statusCode} ne "")) { # Fehler aufgetreten + Log3($name,1,$name.",vitoconnect_getFeatures: Fehler während Gateway features: ".$err." :: ".$response_body); + $err = vitoconnect_errorHandling($hash,$decode_json); + if ($err ==1){ + return; + } + } + else { # kein Fehler aufgetreten + + if ($hash->{".logResponseOnce"}) { + my $dir = path( AttrVal("global","logdir","log")); + my $file = $dir->child("gw_features_" . $gw . ".json"); + my $file_handle = $file->openw_utf8(); + $file_handle->print(Dumper($decode_json)); # Datei 'installation.json' schreiben + Log3($name,3,$name." Datei: ".$dir."/".$file." geschrieben"); + } + + if (AttrVal( $name, 'vitoconnect_gw_readings', 0 ) eq "1") { + readingsSingleUpdate($hash,"gw_features",$response_body,1); # im Reading 'gw_features' merken + } vitoconnect_getResource($hash); } - else { - Log3 $name, 1, "$name - An error occured: $err"; - InternalTimer( gettimeofday() + $hash->{intervall}, - "vitoconnect_GetUpdate", $hash ); - } - return; } -sub vitoconnect_getFeatures { - my ($hash) = @_; - my $name = $hash->{NAME}; - my $access_token = $hash->{".access_token"}; - my $installation = $hash->{".installation"}; - my $gw = $hash->{".gw"}; - my $dev = AttrVal( $name, 'vitoconnect_device', 0 ); - - Log3 $name, 4, "$name - getFeatures went ok"; - - # Service Documents -ToDo - - # Gateway features - my $param = { - url => $apiURL . "installations/$installation/gateways/$gw/features", - hash => $hash, - header => "Authorization: Bearer $access_token", - timeout => $hash->{timeout}, - sslargs => { SSL_verify_mode => 0 }, - }; - ( my $err, my $msg ) = HttpUtils_BlockingGet($param); - my $decode_json = eval { decode_json($msg) }; - if ( $err ne "" || $decode_json->{statusCode} ne "" ) { - Log3 $name, 1, - "$name - Fehler während " . "Gateway features: $err :: $msg"; - } - else { - readingsSingleUpdate( $hash, "gw_features", $msg, 1 ); - } - - # installation features - my $param = { - url => $apiURL . "installations/$installation/features", - hash => $hash, - header => "Authorization: Bearer $access_token", - timeout => $hash->{timeout}, - sslargs => { SSL_verify_mode => 0 }, - }; - ( my $err, my $msg ) = HttpUtils_BlockingGet($param); - my $decode_json = eval { decode_json($msg) }; - if ( $err ne "" || $decode_json->{statusCode} ne "" ) { - Log3 $name, 1, - "$name - Fehler während " . "installation features: $err :: $msg"; - } - else { - readingsSingleUpdate( $hash, "installation_features", $msg, 1 ); - } - - #Events - # my $param = { - # url => "https://api.viessmann.com/iot/v1/events-history/events", - # hash => $hash, - # header => "Authorization: Bearer $access_token", - # data => "gatewaySerial=$gw", - # method => "POST", - # timeout => $hash->{timeout} , - # sslargs => { SSL_verify_mode => 0 }, - # }; - # ( my $err, my $msg ) = HttpUtils_BlockingGet($param); - # my $decode_json = eval { decode_json($msg) }; - # if ( $err ne "" || $decode_json->{statusCode} ne "" ) { - # Log3 $name, 1, "$name - Fehler während " - # . "events: $err :: $msg"; - # } - # else { - # readingsSingleUpdate ( $hash, "events", $msg, 1); - # } - - return; -} +##################################################################################################################### +# Get der Daten vom Gateway +# Hier für den normalen Update +# Es wird im Sub entschieden ob für alle Gateways oder für eine vorgegeben Gateway Serial +##################################################################################################################### sub vitoconnect_getResource { - my ($hash) = @_; - my $name = $hash->{NAME}; + my ($hash) = shift; # Übergabe-Parameter + my $name = $hash->{NAME}; # Device-Name my $access_token = $hash->{".access_token"}; - my $installation = $hash->{".installation"}; - my $gw = $hash->{".gw"}; - my $dev = AttrVal( $name, 'vitoconnect_device', 0 ); + my $installation = AttrVal( $name, 'vitoconnect_installationID', 0 ); + my $gw = AttrVal( $name, 'vitoconnect_serial', 0 ); + my $dev = AttrVal($name,'vitoconnect_device',0); - Log3 $name, 4, "$name - enter getResource"; - Log3 $name, 4, - "$name - access_token: " . substr( $access_token, 0, 20 ) . "..."; - Log3 $name, 4, "$name - installation: $installation"; - Log3 $name, 4, "$name - gw: $gw"; - if ( $access_token eq "" || $installation eq "" || $gw eq "" ) { + Log3($name,4,$name." - enter getResourceOnce"); + Log3($name,4,$name." - access_token: ".substr($access_token,0,20)."..."); + Log3($name,4,$name." - installation: ".$installation); + Log3($name,4,$name." - gw: ".$gw); + if ($access_token eq "" || $installation eq "" || $gw eq "") { # noch kein: Token, ID, GW vitoconnect_getCode($hash); return; } my $param = { - url => $apiURL - . "installations/$installation/gateways/$gw/devices/$dev/features", + url => $iotURL_V2 + ."installations/".$installation."/gateways/".$gw."/devices/".$dev."/features", hash => $hash, + gw => $gw, header => "Authorization: Bearer $access_token", timeout => $hash->{timeout}, sslargs => { SSL_verify_mode => 0 }, callback => \&vitoconnect_getResourceCallback }; - HttpUtils_NonblockingGet($param); + HttpUtils_NonblockingGet($param); # non-blocking aufrufen --> Antwort an: vitoconnect_getResourceCallback return; } -sub vitoconnect_getResourceCallback { - my ( $param, $err, $response_body ) = @_; - my $hash = $param->{hash}; - my $name = $hash->{NAME}; - if ( $err eq "" ) { - Log3 $name, 4, "$name - getResourceCallback went ok"; - Log3 $name, 5, "$name - Received response: " - . substr( $response_body, 0, 100 ) . "..."; - my $items = eval { decode_json($response_body) }; - if ($@) { - readingsSingleUpdate( $hash, "state", - "JSON error while request: $@", 1 ); - Log3 $name, 1, "$name - JSON error while request: $@"; - InternalTimer( gettimeofday() + $hash->{intervall}, - "vitoconnect_GetUpdate", $hash ); +##################################################################################################################### +# Verarbeiten der Daten vom Gateway und schreiben in Readings +# Entweder statisch gemapped oder über attribute mapping gemapped oder nur raw Werte +# Wenn gemapped wird wird für alle Treffer des Mappings kein raw Wert mehr aktualisiert +##################################################################################################################### +sub vitoconnect_getResourceCallback { + my ($param,$err,$response_body) = @_; # Übergabe-Parameter + my $hash = $param->{hash}; + my $name = $hash->{NAME}; + my $gw = AttrVal( $name, 'vitoconnect_serial', 0 ); + + Log(5,$name.", -getResourceCallback started"); + Log3($name,5,$name." getResourceCallback calles with gw:".$gw); + + if ($err eq "") { # kein Fehler aufgetreten + Log3($name,4,$name." - getResourceCallback went ok"); + Log3($name,5,$name." - Received response: ".substr($response_body,0,100)."..."); + my $items = eval {decode_json($response_body)}; + if ($@) { # Fehler beim JSON dekodieren + readingsSingleUpdate($hash,"state","JSON error while request: ".$@,1); # Reading 'state' + Log3($name,1,$name.", vitoconnect_getResourceCallback: JSON error while request: ".$@); + InternalTimer(gettimeofday() + $hash->{intervall},"vitoconnect_GetUpdate",$hash); return; } + + $err = vitoconnect_errorHandling($hash,$items); + if ($err ==1){ + return; + } - if ( !$items->{statusCode} eq "" ) { + if ($hash->{".logResponseOnce"} ) { + my $dir = path(AttrVal("global","logdir","log")); # Verzeichnis + my $file = $dir->child("resource_".$gw.".json"); # Dateiname + my $file_handle = $file->openw_utf8(); + #$file_handle->print(Dumper($items)); # Datei 'resource.json' schreiben + $file_handle->print(Dumper($response_body)); # Datei 'resource.json' schreiben + Log3($name,3,$name." Datei: ".$dir."/".$file." geschrieben"); + $hash->{".logResponseOnce"} = 0; + } + + $hash->{".response_$gw"} = $response_body; + + Log(5,$name.", translations count:".scalar keys %translations); + Log(5,$name.", RequestListMapping count:".scalar keys %$RequestListMapping); + + readingsBeginUpdate($hash); + foreach ( @{ $items->{data} } ) { + my $feature = $_; + my $properties = $feature->{properties}; + + + if (AttrVal( $name, 'vitoconnect_actions_active', 0 ) eq "1") { + # Write all commands + if (exists $feature->{commands}) { + foreach my $command (keys %{$feature->{commands}}) { + my $Reading = $feature->{feature}.".".$command; + my $Value = $feature->{commands}{$command}{uri}; + readingsBulkUpdate($hash,$Reading,$Value,1); + } + } + } + + + foreach my $key ( sort keys %$properties ) { + + + my $Reading = ""; + + if ( scalar keys %translations > 0) { + + # Use translation from attr + my @parts = split(/\./, $feature->{feature} . "." . $key); + foreach my $part (@parts) { + if ($part !~ /\d+/) { + $part = $translations{$part} // $part; # Übersetze den Teil oder behalte ihn bei + } + } + + $Reading = join('.', @parts); + + } + elsif ( scalar keys %$RequestListMapping > 0) { + # Use RequestListMapping from Attr + $Reading = + $RequestListMapping->{ $feature->{feature} . "." . $key }; + } + elsif (AttrVal( $name, 'vitoconnect_mapping_roger', 0 ) eq "1") { + # Use build in Mapping Roger (old way) + $Reading = $RequestListRoger->{ $feature->{feature} . "." . $key }; + } + else { + # Use build in Mapping SVN (old way) + $Reading = $RequestListSvn->{ $feature->{feature} . "." . $key }; + }; + + if ( !defined($Reading) || AttrVal( $name, 'vitoconnect_raw_readings', 0 ) eq "1" ) + { + $Reading = $feature->{feature} . "." . $key; + } + + if ( !defined($Reading) && AttrVal( $name, 'vitoconnect_disable_raw_readings', 0 ) eq "1" ) + { + next; + } + + my $Type = $properties->{$key}->{type}; + my $Value = $properties->{$key}->{value}; + my $comma_separated_string = ""; + if ( $Type eq "array" ) { + if ( defined($Value) ) { + if (ref($Value->[0]) eq 'HASH') { + foreach my $entry (@$Value) { + foreach my $hash_key (sort keys %$entry) { + if ($hash_key ne "audiences") { + my $hash_value = $entry->{$hash_key}; + if (ref($hash_value) eq 'ARRAY') { + $comma_separated_string .= join(", ", @$hash_value) . ", "; + } else { + $comma_separated_string .= $hash_value . ", "; + } + } + } + } + # Entferne das letzte Komma und Leerzeichen + $comma_separated_string =~ s/, $//; + readingsBulkUpdate($hash,$Reading,$comma_separated_string); + } + elsif (ref($Value) eq 'ARRAY') { + $comma_separated_string = ( join(",",@$Value) ); + readingsBulkUpdate($hash,$Reading,$comma_separated_string); + Log3($name,5,$name." - ".$Reading." ".$comma_separated_string." (".$Type.")"); + } + else { + Log3($name,4,$name." - Array Workaround for Property: ".$Reading); + } + } + } + elsif ($Type eq 'object') { + # Iteriere durch die Schlüssel des Hashes + foreach my $hash_key (sort keys %$Value) { + my $hash_value = $Value->{$hash_key}; + $comma_separated_string .= $hash_value . ", "; + } + # Entferne das letzte Komma und Leerzeichen + $comma_separated_string =~ s/, $//; + readingsBulkUpdate($hash,$Reading,$comma_separated_string); + } + elsif ( $Type eq "Schedule" ) { + my $Result = encode_json($Value); + readingsBulkUpdate($hash,$Reading,$Result); + Log3($name, 5, "$name - $Reading: $Result ($Type)"); + } + else { + readingsBulkUpdate($hash,$Reading,$Value); + Log3 $name, 5, "$name - $Reading: $Value ($Type)"; + #Log3 $name, 1, "$name - $Reading: $Value ($Type)"; + } + + # Store power readings as asSingleValue + if ($Reading =~ m/dayValueReadAt$/) { + Log(5,$name.", -call setpower $Reading"); + vitoconnect_getPowerLast ($hash,$name,$Reading); + } + } + } + + readingsBulkUpdate($hash,"state","last update: ".TimeNow().""); # Reading 'state' + readingsEndUpdate( $hash, 1 ); # Readings schreiben + } + else { + Log3($name,1,$name." - An error occured: ".$err); + } + + InternalTimer(gettimeofday() + $hash->{intervall},"vitoconnect_GetUpdate",$hash); + Log(5,$name.", -getResourceCallback ended"); + + + return; +} + + +##################################################################################################################### +# Implementierung power readings die nur sehr selten kommen in ein logbares reading füllen (asSingleValue) +##################################################################################################################### +sub vitoconnect_getPowerLast { + my ($hash, $name, $Reading) = @_; + + # entferne alles hinter dem letzten Punkt + $Reading =~ s/\.[^.]*$//; + + # Liste der Stromwerte + my @values = split(",", ReadingsVal($name,$Reading.".day","")); #(1.2, 76.7, 52.6, 40.9, 40.4, 30, 33.9, 75); + + # Zeitpunkt des ersten Wertes + my $timestamp = ReadingsVal($name,$Reading.".dayValueReadAt",""); #'2024-11-29T11:28:56.915Z'; + + if (!defined($timestamp)) { + return; + } + + # Datum extrahieren und in ein Time::Piece Objekt umwandeln + my $date = Time::Piece->strptime(substr($timestamp, 0, 10), '%Y-%m-%d'); + + # Anzahl der Sekunden in einem Tag + my $one_day = 24 * 60 * 60; + + # Hash für die Key-Value-Paare + my %data; + my $readingLastTimestamp = ReadingsTimestamp($name,$Reading.".day.asSingleValue","0000000000"); + #my $lastTS = "0000000000"; + #if ($readingLastTimestamp ne "") { + my $lastTS = time_str2num($readingLastTimestamp); + #} + Log(5,$name.", -setpower: readinglast: $readingLastTimestamp lastTS $lastTS"); + + # Werte den entsprechenden Tagen zuordnen, start mit 1, letzten Tag ausschließen weil unvollständig + for (my $i = $#values; $i >= 1; $i--) { + my $current_date = $date - ($one_day * $i); + Log3($name, 5, ", -setpower: date:$current_date value:$values[$i] ($i)"); + my $readingDate = $current_date->ymd . " 23:59:59"; + my $readingTS = time_str2num($readingDate); + Log(5,$name.", -setpower: date $readingDate lastdate $readingLastTimestamp"); + if ($readingTS > $lastTS) { + readingsBulkUpdate ($hash, $Reading.".day.asSingleValue", $values[$i], undef, $readingDate); + Log(4,$name.", -setpower: readingsBulkUpdate ($hash, $Reading.day.asSingleValue, $values[$i], undef, $readingDate"); + } + } + + return; +} + + +##################################################################################################################### +# Setzen von Daten +##################################################################################################################### +sub vitoconnect_action { + my ($hash,$feature,$data,$name,$opt,@args ) = @_; # Übergabe-Parameter + my $access_token = $hash->{".access_token"}; # Internal: .access_token + my $installation = AttrVal( $name, 'vitoconnect_installationID', 0 ); + my $gw = AttrVal( $name, 'vitoconnect_serial', 0 ); + my $dev = AttrVal($name,'vitoconnect_device',0); + + my $param = { + url => $iotURL_V2 + ."installations/".$installation."/gateways/".$gw."/" + ."devices/".$dev."/features/".$feature, + hash => $hash, + header => "Authorization: Bearer ".$access_token."\r\n" + . "Content-Type: application/json", + data => $data, + timeout => $hash->{timeout}, # Timeout von Internals = 15s + method => "POST", + sslargs => { SSL_verify_mode => 0 }, + }; + Log3($name,3,$name.", vitoconnect_action url=" .$param->{url}); + Log3($name,3,$name.", vitoconnect_action data=".$param->{data}); +# https://wiki.fhem.de/wiki/HttpUtils#HttpUtils_BlockingGet + (my $err,my $msg) = HttpUtils_BlockingGet($param); + my $decode_json = eval {decode_json($msg)}; + + Log3($name,3,$name.", vitoconnect_action call finished, err:" .$err); + my $Text = join(' ',@args); # Befehlsparameter in Text + if ( (defined($err) && $err ne "") || (defined($decode_json->{statusCode}) && $decode_json->{statusCode} ne "") ) { # Fehler bei Befehlsausführung + readingsSingleUpdate($hash,"Aktion_Status","Fehler: ".$opt." ".$Text,1); # Reading 'Aktion_Status' setzen + Log3($name,1,$name.",vitoconnect_action: set ".$name." ".$opt." ".@args.", Fehler bei Befehlsausfuehrung: ".$err." :: ".$msg); + } + else { # Befehl korrekt ausgeführt + readingsSingleUpdate($hash,"Aktion_Status","OK: ".$opt." ".$Text,1); # Reading 'Aktion_Status' setzen + #Log3($name,1,$name.",vitoconnect_action: set name:".$name." opt:".$opt." text:".$Text.", korrekt ausgefuehrt: ".$err." :: ".$msg); # TODO: Wieder weg machen $err + Log3($name,3,$name.",vitoconnect_action: set name:".$name." opt:".$opt." text:".$Text.", korrekt ausgefuehrt"); + + # Spezial Readings update + if ($opt =~ /(.*)\.deactivate/) { + $opt = $1 . ".active"; + $Text = "0"; + } elsif ($opt =~ /(.*)\.activate/) { + $opt = $1 . ".active"; + $Text = "1"; + } + readingsSingleUpdate($hash,$opt,$Text,1); # Reading updaten + + # Spezial Readings update, activate mit temperatur siehe brenner Vitoladens300C + if ($feature =~ /(.*)\.deactivate/) { + # funktioniert da deactivate ohne temperatur gesendet wird + } elsif ($feature =~ /(.*)\/commands\/activate/) { + $opt = $1 . ".active"; + $Text = "1"; + } + readingsSingleUpdate($hash,$opt,$Text,1); # Reading updaten + + + Log3($name,4,$name.",vitoconnect_action: set feature:".$feature." data:".$data.", korrekt ausgefuehrt"); #4 + } + return; +} + + +##################################################################################################################### +# Errors bearbeiten +##################################################################################################################### +sub vitoconnect_errorHandling { + my ($hash,$items) = @_; + my $name = $hash->{NAME}; + + #Log3 $name, 1, "$name - errorHandling StatusCode: $items->{statusCode} "; + + if (!$items->{statusCode} eq "") { Log3 $name, 4, "$name - statusCode: $items->{statusCode} " . "errorType: $items->{errorType} " @@ -1962,37 +3709,34 @@ sub vitoconnect_getResourceCallback { 1 ); if ( $items->{statusCode} eq "401" ) { - # EXPIRED TOKEN - vitoconnect_getRefresh($hash); - return; + vitoconnect_getRefresh($hash); # neuen Access-Token anfragen + return(1); } elsif ( $items->{statusCode} eq "404" ) { - # DEVICE_NOT_FOUND + readingsSingleUpdate($hash,"state","Device not found: Optolink prüfen!",1); Log3 $name, 1, "$name - Device not found: Optolink prüfen!"; - InternalTimer( gettimeofday() + $hash->{intervall}, - "vitoconnect_GetUpdate", $hash ); - return; + InternalTimer(gettimeofday() + $hash->{intervall},"vitoconnect_GetUpdate",$hash); + return(1); } elsif ( $items->{statusCode} eq "429" ) { - # RATE_LIMIT_EXCEEDED + readingsSingleUpdate($hash,"state","Anzahl der möglichen API Calls in überschritten!",1); Log3 $name, 1, "$name - Anzahl der möglichen API Calls in überschritten!"; - InternalTimer( gettimeofday() + $hash->{intervall}, - "vitoconnect_GetUpdate", $hash ); - return; + InternalTimer(gettimeofday() + $hash->{intervall},"vitoconnect_GetUpdate",$hash); + return(1); } elsif ( $items->{statusCode} eq "502" ) { - + readingsSingleUpdate($hash,"state","temporärer API Fehler",1); # DEVICE_COMMUNICATION_ERROR error: Bad Gateway Log3 $name, 1, "$name - temporärer API Fehler"; - InternalTimer( gettimeofday() + $hash->{intervall}, - "vitoconnect_GetUpdate", $hash ); - return; + InternalTimer(gettimeofday() + $hash->{intervall},"vitoconnect_GetUpdate",$hash); + return(1); } else { + readingsSingleUpdate($hash,"state","unbekannter Fehler, bitte den Entwickler informieren!",1); Log3 $name, 1, "$name - unbekannter Fehler: " . "Bitte den Entwickler informieren!"; Log3 $name, 1, @@ -2000,106 +3744,22 @@ sub vitoconnect_getResourceCallback { . "errorType: $items->{errorType} " . "message: $items->{message} " . "error: $items->{error}"; - InternalTimer( gettimeofday() + $hash->{intervall}, - "vitoconnect_GetUpdate", $hash ); - return; + InternalTimer(gettimeofday() + $hash->{intervall},"vitoconnect_GetUpdate",$hash); + return(1); } } +}; - if ( $hash->{".logResponseOnce"} ) { - my $dir = path( AttrVal( "global", "logdir", "log" ) ); - my $file = $dir->child("resource.json"); - my $file_handle = $file->openw_utf8(); - $file_handle->print( Dumper($items) ); - } - - readingsBeginUpdate($hash); - foreach ( @{ $items->{data} } ) { - my $feature = $_; - my $properties = $feature->{properties}; - foreach my $key ( keys %$properties ) { - my $Reading = - $RequestList->{ $feature->{feature} . "." . $key }; - if ( !defined($Reading) - || AttrVal( $name, 'vitoconnect_raw_readings', 0 ) eq "1" ) - { - $Reading = $feature->{feature} . "." . $key; - } - my $Type = $properties->{$key}->{type}; - my $Value = $properties->{$key}->{value}; - if ( $Type eq "array" ) { - if ( defined($Value) ) { - if ( ref($Value) eq 'ARRAY' ) { - my $Array = ( join( ",", @$Value ) ); - readingsBulkUpdate( $hash, $Reading, $Array ); - Log3 $name, 5, "$name - $Reading $Array ($Type)"; - } - else { - Log3 $name, 4, - "$name - Array Workaround for Property: $Reading"; - } - } - } - elsif ( $Type eq "Schedule" ) { - my $Result = encode_json($Value); - readingsBulkUpdate( $hash, $Reading, $Result ); - Log3 $name, 5, "$name - $Reading: $Result ($Type)"; - } - else { - readingsBulkUpdate( $hash, $Reading, $Value ); - Log3 $name, 5, "$name - $Reading: $Value ($Type)"; - } - } - } - readingsBulkUpdate( $hash, "state", "last update: " . TimeNow() . "" ); - readingsEndUpdate( $hash, 1 ); - } - else { - Log3 $name, 1, "$name - An error occured: $err"; - } - InternalTimer( gettimeofday() + $hash->{intervall}, - "vitoconnect_GetUpdate", $hash ); - return; -} - -sub vitoconnect_action { - my ( $hash, $feature, $data, $name, $opt, @args ) = @_; - my $access_token = $hash->{".access_token"}; - my $installation = $hash->{".installation"}; - my $gw = $hash->{".gw"}; - my $dev = AttrVal( $name, 'vitoconnect_device', 0 ); - my $param = { - url => $apiURLBase - . "installations/$installation/gateways/$gw/" - . "devices/$dev/features/$feature", - hash => $hash, - header => "Authorization: Bearer $access_token\r\n" - . "Content-Type: application/json", - data => $data, - timeout => $hash->{timeout}, - method => "POST", - sslargs => { SSL_verify_mode => 0 }, - }; - Log3 $name, 4, "$name - url=$param->{url}"; - Log3 $name, 4, "$name - data=$param->{data}"; - ( my $err, my $msg ) = HttpUtils_BlockingGet($param); - my $decode_json = eval { decode_json($msg) }; - - if ( $err ne "" || $decode_json->{statusCode} ne "" ) { - Log3 $name, 1, "$name - set $name $opt @args: Fehler während der " - . "Befehlsausführung: $err :: $msg"; - } - else { Log3 $name, 3, "$name - set $name $opt @args"; } - return; -} +##################################################################################################################### +# Werte verschlüsselt speichern +##################################################################################################################### sub vitoconnect_StoreKeyValue { -################################################### # checks and stores obfuscated keys like passwords # based on / copied from FRITZBOX_storePassword my ( $hash, $kName, $value ) = @_; - my $index = $hash->{TYPE} . "_" . $hash->{NAME} . "_" . $kName; - my $key = getUniqueId() . $index; + my $index = $hash->{TYPE}."_".$hash->{NAME}."_".$kName; + my $key = getUniqueId().$index; my $enc = ""; if ( eval "use Digest::MD5;1" ) { @@ -2111,237 +3771,609 @@ sub vitoconnect_StoreKeyValue { $enc .= sprintf( "%.2x", ord($char) ^ ord($encode) ); $key = $encode . $key; } - my $err = setKeyValue( $index, $enc ); - return "error while saving the value - $err" if ( defined($err) ); + my $err = setKeyValue( $index, $enc ); # Die Funktion setKeyValue() speichert die Daten $value unter dem Schlüssel $key ab. + return "error while saving the value - ".$err if ( defined($err) ); # Fehler return; } + +##################################################################################################################### +# verschlüsselte Werte auslesen +##################################################################################################################### sub vitoconnect_ReadKeyValue { -##################################################### + # reads obfuscated value - my ( $hash, $kName ) = @_; + my ($hash,$kName) = @_; # Übergabe-Parameter my $name = $hash->{NAME}; - my $index = $hash->{TYPE} . "_" . $hash->{NAME} . "_" . $kName; - my $key = getUniqueId() . $index; + my $index = $hash->{TYPE}."_".$hash->{NAME}."_".$kName; + my $key = getUniqueId().$index; my ( $value, $err ); - Log3 $name, 5, - "$name - ReadKeyValue tries to read value for $kName from file"; - ( $err, $value ) = getKeyValue($index); + Log3($name,5,$name." - ReadKeyValue tries to read value for ".$kName." from file"); + ($err,$value ) = getKeyValue($index); # Die Funktion getKeyValue() gibt Daten, welche zuvor per setKeyValue() gespeichert wurden, zurück. - if ( defined($err) ) { - Log3 $name, 1, - "$name - ReadKeyValue is unable to read value from file: $err"; + if ( defined($err) ) { # im Fehlerfall + Log3($name,1,$name." - ReadKeyValue is unable to read value from file: ".$err); return; } - if ( defined($value) ) { + if ( defined($value) ) { if ( eval "use Digest::MD5;1" ) { $key = Digest::MD5::md5_hex( unpack "H*", $key ); $key .= Digest::MD5::md5_hex($key); } my $dec = ''; - for my $char ( map { pack( 'C', hex($_) ) } ( $value =~ /(..)/g ) ) { + for my $char ( map { pack( 'C', hex($_) ) } ( $value =~ /(..)/g ) ) { my $decode = chop($key); $dec .= chr( ord($char) ^ ord($decode) ); $key = $decode . $key; } - return $dec; + return $dec; # Rückgabe dekodierten Wert } - else { - Log3 $name, 1, "$name - ReadKeyValue could not find key $kName in file"; + else { # Fehler: + Log3($name,1,$name." - ReadKeyValue could not find key ".$kName." in file"); return; } return; } + +##################################################################################################################### +# verschlüsselte Werte löschen +##################################################################################################################### +sub DeleteKeyValue { + my ($hash,$kName) = @_; # Übergabe-Parameter + my $name = $hash->{NAME}; + + Log3( $name, 5,$name." - called function Delete()" ); + + my $index = $hash->{TYPE}."_".$hash->{NAME}."_".$kName; + setKeyValue( $index, undef ); + + return; +} + 1; + =pod =item device =item summary support for Viessmann API =item summary_DE Unterstützung für die Viessmann API =begin html - +

vitoconnect

    vitoconnect implements a device for the Viessmann API - Vitoconnect100 - based on investigation of - thetrueavatar
    + Vitoconnect100 or E3 One Base + based on the investigation of + thetrueavatar.
    - You need the user and password from the ViCare App account.
    - Attention: This module is limited to one 'installation' per account. If you have two or more heaters use one viessmann account and device for each heater.
    - - For details see: FHEM Wiki (german)

    - - vitoconnect needs the following libraries: -
      -
    • Path::Tiny
    • -
    • JSON
    • -
    • DateTime
    • -
    - - Use sudo apt install libtypes-path-tiny-perl libjson-perl libdatetime-perl or - install the libraries via cpan. - Otherwise you will get an error message "cannot load module vitoconnect". - -

    - + You need the user and password from the ViCare App account.
    + Additionally also an apiKey, see set apiKey.
    + + For details, see: FHEM Wiki (German)

    + + vitoconnect requires the following libraries: +
      +
    • Path::Tiny
    • +
    • JSON
    • +
    • JSON:XS
    • +
    • DateTime
    • +
    + + Use sudo apt install libtypes-path-tiny-perl libjson-perl libdatetime-perl or + install the libraries via CPAN. + Otherwise, you will get an error message: "cannot load module vitoconnect". + +

    + Define
      define <name> vitoconnect <user> <password> <interval>
      - It is a good idea to use a fake password here an set the correct one later because it is - readable in the detail view of the device + It is a good idea to use a fake password here and set the correct one later because it is + readable in the detail view of the device.

      Example:
      define vitoconnect vitoconnect user@mail.xx fakePassword 60
      - set vitoconnect password correctPassword 60 + set vitoconnect password correctPassword + set vitoconnect apiKey Client-ID

      -

    - + Set
      -
    • update
      - update readings immeadiatlely
    • -
    • clearReadings
      - clear all readings immeadiatlely
    • -
    • password passwd
      - store password in key store
    • -
    • logResponseOnce
      - dumps the json response of Viessmann server to entities.json, - gw.json, actions.json in FHEM log directory
    • - -
    • HK1-Heizkurve-Niveau shift
      - set shift of heating curve
    • -
    • HK1-Heizkurve-Steigung slope
      - set slope of heating curve
    • - -
    • HK1-Urlaub_Start start
      - set holiday start time
      - start has to look like this: 2019-02-02
    • -
    • HK1-Urlaub_Ende end
      - set holiday end time
      - end has to look like this: 2019-02-16
    • -
    • HK1-Urlaub_unschedule
      - remove holiday start and end time
    • - -
    • HK1-Zeitsteuerung_Heizung schedule
      - sets the heating schedule in JSON format
      - e.g. {"mon":[],"tue":[],"wed":[],"thu":[],"fri":[],"sat":[],"sun":[]} is completly off - and {"mon":[{"mode":"on","start":"00:00","end":"24:00","position":0}], - "tue":[{"mode":"on","start":"00:00","end":"24:00","position":0}], - "wed":[{"mode":"on","start":"00:00","end":"24:00","position":0}], - "thu":[{"mode":"on","start":"00:00","end":"24:00","position":0}], - "fri":[{"mode":"on","start":"00:00","end":"24:00","position":0}], - "sat":[{"mode":"on","start":"00:00","end":"24:00","position":0}], - "sun":[{"mode":"on","start":"00:00","end":"24:00","position":0}]} is on 24/7
    • - -
    • HK1-Betriebsart standby,dhw,dhwAndHeating,forcedReduced,forcedNormal
      - sets HK1-Betriebsart to standby,dhw,dhwAndHeating,forcedReduced or forcedNormal
    • - -
    • HK1-Solltemperatur_comfort_aktiv activate,deactivate
      - activate/deactivate comfort temperature
    • -
    • HK1-Solltemperatur_comfort targetTemperature
      - set comfort target temperatur
    • -
    • HK1-Solltemperatur_eco_aktiv activate,deactivate
      - activate/deactivate eco temperature
    • - -
    • HK1-Solltemperatur_normal targetTemperature
      - sets the normale target temperature where targetTemperature is an - integer between 3 and 37
    • -
    • HK1-Solltemperatur_reduziert targetTemperature
      - sets the reduced target temperature where targetTemperature is an - integer between 3 and 37
    • - -
    • HK1-Name name
      - sets the name of the circuit
    • - -
    • WW-einmaliges_Aufladen activate,deactivate
      - activate or deactivate one time charge for hot water
    • - -
    • WW-Zirkulationspumpe_Zeitplan schedule
      - sets the schedule in JSON format for hot water circulation pump
    • -
    • WW-Zeitplan schedule
      - sets the schedule in JSON format for hot water
    • - -
    • WW-Haupttemperatur targetTemperature
      - targetTemperature is an integer between 10 and 60
      - sets hot water main temperature to targetTemperature
    • -
    • WW-Solltemperatur targetTemperature
      - targetTemperature is an integer between 10 and 60
      - sets hot water temperature to targetTemperature
    • - -
    • Urlaub_Start start
      - set holiday start time
      - start has to look like this: 2019-02-02
    • -
    • Urlaub_Ende end
      - set holiday end time
      - end has to look like this: 2019-02-16
    • -
    • Urlaub_unschedule
      - remove holiday start and end time
    • - + +
    • update
      + Update readings immediately.
    • + +
    • selectDevice
      + Has to be used if you have more than one Viessmann Gateway/Device. You have to choose one Viessmann Device per FHEM Device.
      + You will be notified in the FHEM device state that you have to execute the set, and the Viessmann devices will be prefilled.
      + Selecting one Viessmann device and executing the set will fill the attributes vitoconnect_serial and vitoconnect_installationId.
      + If you have only one Viessmann device, this will be done automatically for you.
      + You should save the change after initialization or set. +
    • + +
    • clearReadings
      + Clear all readings immediately.
    • + +
    • password passwd
      + Store password in the key store.
    • + +
    • logResponseOnce
      + Dumps the JSON response of the Viessmann server to entities.json, + gw.json, and actions.json in the FHEM log directory. + If you have more than one gateway, the gateway serial is attached to the filenames.
    • + +
    • apiKey
      + You need to create an API Key under https://developer.viessmann.com/. + Create an account, add a new client (disable Google reCAPTCHA, Redirect URI = http://localhost:4200/). + Copy the Client ID here as apiKey.
    • +
    • Setters for your device will be available depending on the mapping method you choose with the help of the attributes vitoconnect_raw_readings or vitoconnect_mapping_roger.
      + New setters are used if vitoconnect_raw_readings = 1. + The default is the static mapping of the old SVN version. + For this, the following setters are available:
    • +
    • HKn_Heizkurve_Niveau shift
      + Set shift of heating curve for HKn.
    • +
    • HKn_Heizkurve_Steigung slope
      + Set slope of heating curve for HKn.
    • +
    • HKn_Urlaub_Start_Zeit start
      + Set holiday start time for HKn.
      + start has to look like this: 2019-02-02.
    • +
    • HKn_Urlaub_Ende_Zeit end
      + Set holiday end time for HKn.
      + end has to look like this: 2019-02-16.
    • +
    • HKn_Urlaub_stop
      + Remove holiday start and end time for HKn.
    • +
    • HKn_Zeitsteuerung_Heizung schedule
      + Sets the heating schedule for HKn in JSON format.
      + Example: {"mon":[],"tue":[],"wed":[],"thu":[],"fri":[],"sat":[],"sun":[]} is completely off, + and {"mon":[{"mode":"on","start":"00:00","end":"24:00","position":0}], + "tue":[{"mode":"on","start":"00:00","end":"24:00","position":0}], + "wed":[{"mode":"on","start":"00:00","end":"24:00","position":0}], + "thu":[{"mode":"on","start":"00:00","end":"24:00","position":0}], + "fri":[{"mode":"on","start":"00:00","end":"24:00","position":0}], + "sat":[{"mode":"on","start":"00:00","end":"24:00","position":0}], + "sun":[{"mode":"on","start":"00:00","end":"24:00","position":0}]} is on 24/7.
    • +
    • HKn_Betriebsart heating,standby
      + Sets HKn_Betriebsart to heating or standby.
    • +
    • WW_Betriebsart balanced,off
      + Sets WW_Betriebsart to balanced or off.
    • +
    • HKn_Soll_Temp_comfort_aktiv activate,deactivate
      + Activate/deactivate comfort temperature for HKn.
    • +
    • HKn_Soll_Temp_comfort targetTemperature
      + Set comfort target temperature for HKn.
    • +
    • HKn_Soll_Temp_eco_aktiv activate,deactivate
      + Activate/deactivate eco temperature for HKn.
    • +
    • HKn_Soll_Temp_normal targetTemperature
      + Sets the normal target temperature for HKn, where targetTemperature is an + integer between 3 and 37.
    • +
    • HKn_Soll_Temp_reduziert targetTemperature
      + Sets the reduced target temperature for HKn, where targetTemperature is an + integer between 3 and 37.
    • +
    • HKn_Name name
      + Sets the name of the circuit for HKn.
    • +
    • WW_einmaliges_Aufladen activate,deactivate
      + Activate or deactivate one-time charge for hot water.
    • +
    • WW_Zirkulationspumpe_Zeitplan schedule
      + Sets the schedule in JSON format for the hot water circulation pump.
    • +
    • WW_Zeitplan schedule
      + Sets the schedule in JSON format for hot water.
    • +
    • WW_Solltemperatur targetTemperature
      + targetTemperature is an integer between 10 and 60.
      + Sets hot water temperature to targetTemperature.
    • +
    • Urlaub_Start_Zeit start
      + Set holiday start time.
      + start has to look like this: 2019-02-02.
    • +
    • Urlaub_Ende_Zeit end
      + Set holiday end time.
      + end has to look like this: 2019-02-16.
    • +
    • Urlaub_stop
      + Remove holiday start and end time.
    • +

Get
    - nothing to get here + Nothing to get here.

- - Attributes -
    - attr <name> <attribute> <value> -

    - See commandref#attr for more info about - the attr command. -

    - Attributes: -
      -
    • disable:
      - stop communication with Viessmann server -
    • -
    • verbose:
      - set the verbosity level -
    • -
    • vitoconnect_raw_readings:
      - create readings with plain JSON names like 'heating.circuits.0.heating.curve.slope' - instead of german identifiers -
    • -
    • vitoconnect_gw_readings:
      - create readings from the gateway -
    • - -
    • vitoconnect_actions_active:
      - create readings for actions e.g. 'heating.circuits.0.heating.curve.setCurve' -
    • -
    -
- - - Readings + +Attributes +
    + attr <name> <attribute> <value>

    - vitoconnect sets one reading for every value delivered by - the API (depends on the type and the settings of your heater and the version of the API!). - Already known values will be mapped to clear names. Unknown values will added with their JSON path - (e.g. "heating.burner.modulation.value"). - Please report new readings to the module maintainer. A description of the known reading - could be found here (german) - + See commandref#attr for more info about the attr command. +

    + Attributes: +
      + +
    • disable:
      + Stop communication with the Viessmann server. +
    • + +
    • verbose:
      + Set the verbosity level. +
    • + +
    • vitoconnect_raw_readings:
      + Create readings with plain JSON names like heating.circuits.0.heating.curve.slope instead of German identifiers (old mapping), mapping attribute, or translation attribute.
      + When using raw readings, setters will be created dynamically matching the raw readings (new).
      + I recommend this setting since you get everything as dynamically as possible from the API.
      + You can use stateFormat or userReadings to display your important readings with a readable name.
      + If vitoconnect_raw_readings is set, no mapping will be used. +
    • + +
    • vitoconnect_disable_raw_readings:
      + This setting will disable the additional generation of raw readings.
      + This means you will only see the readings that are explicitly mapped in your chosen mapping.
      + This setting will not be active if you also choose vitoconnect_raw_readings = 1. +
    • + +
    • vitoconnect_gw_readings:
      + Create readings from the gateway, including information if you have more than one gateway. +
    • + +
    • vitoconnect_actions_active:
      + Create readings for actions, e.g., heating.circuits.0.heating.curve.setCurve.setURI. +
    • + +
    • vitoconnect_mappings:
      + Define your own mapping of key-value pairs instead of using the built-in ones. The format has to be:
      + mapping
      + { 'device.serial.value' => 'device_serial',
      + 'heating.boiler.sensors.temperature.main.status' => 'status',
      + 'heating.boiler.sensors.temperature.main.value' => 'haupt_temperatur'}

      + Mapping will be preferred over the old mapping. +
    • + +
    • vitoconnect_translations:
      + Define your own translation; it will translate every word part by part. The format has to be:
      + translation
      + { 'device' => 'gerät',
      + 'messages' => 'nachrichten',
      + 'errors' => 'fehler'}

      + Translation will be preferred over mapping and old mapping. +
    • + +
    • vitoconnect_mapping_roger:
      + Use the mapping from Roger from 8. November (https://forum.fhem.de/index.php?msg=1292441) instead of the SVN mapping. +
    • + +
    • vitoconnect_serial:
      + This handling will now take place during the initialization of the FHEM device.
      + You will be notified that you have to execute set <name> selectDevice <serial>.
      + The possible serials will be prefilled.
      + You do not need to set this attribute manually.
      + Defines the serial of the Viessmann device to be used.
      + If there is only one Viessmann device, you do not have to care about it.
      +
    • + +
    • vitoconnect_installationID:
      + This handling will now take place during the initialization of the FHEM device.
      + You will be notified that you have to execute set <name> selectDevice <serial>.
      + The possible serials will be prefilled.
      + You do not need to set this attribute manually.
      + Defines the installationID of the Viessmann device to be used.
      + If there is only one Viessmann device, you do not have to care about it.
      +
    • + +
    • vitoconnect_timeout:
      + Sets a timeout for the API call. +
    • + +
    • vitoconnect_device:
      + You can define the device 0 (default) or 1. I cannot test this because I have only one device. +
    • +
=end html - -=cut +=begin html_DE + + +

vitoconnect

+
    + vitoconnect implementiert ein Gerät für die Viessmann API + Vitoconnect100 oder E3 One Base, + basierend auf der Untersuchung von + thetrueavatar
    + + Es werden Benutzername und Passwort des ViCare App-Kontos benötigt.
    + Zusätzlich auch eine Client-ID, siehe set apiKey.
    + + Weitere Details sind im FHEM Wiki (deutsch) zu finden.

    + + Für die Nutzung werden die folgenden Bibliotheken benötigt: +
      +
    • Path::Tiny
    • +
    • JSON
    • +
    • JSON::XS
    • +
    • DateTime
    • +
    + + Die Bibliotheken können mit dem Befehl sudo apt install libtypes-path-tiny-perl libjson-perl libdatetime-perl installiert werden oder über cpan. Andernfalls tritt eine Fehlermeldung "cannot load module vitoconnect" auf. + +

    + + Define +
      + define <name> vitoconnect <user> <password> <interval>
      + Es wird empfohlen, zunächst ein falsches Passwort zu verwenden und dieses später zu ändern, da es in der Detailansicht des Geräts sichtbar ist. +

      + Beispiel:
      + define vitoconnect vitoconnect user@mail.xx fakePassword 60
      + set vitoconnect password correctPassword 60 + set vitoconnect apiKey Client-ID +

      +
    +
    + + + Set
    +
      + +
    • update
      + Liest sofort die aktuellen Werte aus.
    • + +
    • selectDevice
      + Wird benötigt, wenn mehr als ein Viessmann Gateway/Device vorhanden ist. Ein Viessmann Gerät muss für jedes FHEM Gerät ausgewählt werden.
      + Der Set-Befehl muss ausgeführt werden, nachdem die Viessmann Geräte im Gerätestatus vorgefüllt sind.
      + Bei Auswahl eines Viessmann Geräts und Ausführung des Set-Befehls werden die Attribute vitoconnect_serial und vitoconnect_installationId gefüllt.
      + Bei nur einem Viessmann Gerät erfolgt dies automatisch.
      + Es wird empfohlen, die Änderungen nach der Initialisierung oder dem Set zu speichern. +
    • + +
    • clearReadings
      + Löscht sofort alle Werte.
    • + +
    • password passwd
      + Speichert das Passwort im Schlüsselbund.
    • + +
    • logResponseOnce
      + Speichert die JSON-Antwort des Viessmann-Servers in den Dateien entities.json, gw.json und actions.json im FHEM-Log-Verzeichnis. + Wenn mehrere Gateways vorhanden sind, wird die Seriennummer des Gateways an die Dateinamen angehängt.
    • + +
    • apiKey
      + Ein API-Schlüssel muss unter https://developer.viessmann.com/ erstellt werden. + Dazu ein Konto anlegen, einen neuen Client hinzufügen (Google reCAPTCHA deaktivieren, Redirect URI = http://localhost:4200/). + Die Client-ID muss als apiKey hier eingefügt werden.
    • +
    • Die Setter für das Gerät hängen von der gewählten Mappingmethode ab, die durch die Attribute vitoconnect_raw_readings oder vitoconnect_mapping_roger gesteuert wird.
      + Neue Setter werden verwendet, wenn vitoconnect_raw_readings = 1 gesetzt ist. + Standardmäßig wird das statische Mapping der alten SVN-Version verwendet. + Die folgenden Setter sind verfügbar: +
    • +
    • HKn_Heizkurve_Niveau shift
      + Setzt die Verschiebung der Heizkurve für HKn.
    • +
    • HKn_Heizkurve_Steigung slope
      + Setzt die Steigung der Heizkurve für HKn.
    • +
    • HKn_Urlaub_Start_Zeit start
      + Setzt die Urlaubsstartzeit für HKn.
      + Start muss im Format: 2019-02-02 angegeben werden.
    • +
    • HKn_Urlaub_Ende_Zeit end
      + Setzt die Urlaubsendzeit für HKn.
      + Ende muss im Format: 2019-02-16 angegeben werden.
    • +
    • HKn_Urlaub_stop
      + Entfernt die Urlaubsstart- und Endzeit für HKn.
    • +
    • HKn_Zeitsteuerung_Heizung schedule
      + Setzt den Heizplan für HKn im JSON-Format.
      + Beispiel: {"mon":[],"tue":[],"wed":[],"thu":[],"fri":[],"sat":[],"sun":[]} für keinen Betrieb und {"mon":[{"mode":"on","start":"00:00","end":"24:00","position":0}],...} für 24/7 Betrieb.
    • +
    • HKn_Betriebsart heating,standby
      + Setzt den Betriebsmodus für HKn auf heizen oder standby.
    • +
    • WW_Betriebsart balanced,off
      + Setzt den Betriebsmodus für Warmwasser auf ausgeglichen oder aus.
    • +
    • HKn_Soll_Temp_comfort_aktiv activate,deactivate
      + Aktiviert/deaktiviert die Komforttemperatur für HKn.
    • +
    • HKn_Soll_Temp_comfort targetTemperature
      + Setzt die Komfortzieltemperatur für HKn.
    • +
    • HKn_Soll_Temp_eco_aktiv activate,deactivate
      + Aktiviert/deaktiviert die Ökotemperatur für HKn.
    • +
    • HKn_Soll_Temp_normal targetTemperature
      + Setzt die normale Zieltemperatur für HKn (zwischen 3 und 37 Grad Celsius).
    • +
    • HKn_Soll_Temp_reduziert targetTemperature
      + Setzt die reduzierte Zieltemperatur für HKn (zwischen 3 und 37 Grad Celsius).
    • +
    • HKn_Name name
      + Setzt den Namen des Kreislaufs für HKn.
    • +
    • WW_einmaliges_Aufladen activate,deactivate
      + Aktiviert oder deaktiviert einmaliges Aufladen für Warmwasser.
    • +
    • WW_Zirkulationspumpe_Zeitplan schedule
      + Setzt den Zeitplan im JSON-Format für die Warmwasserzirkulationspumpe.
    • +
    • WW_Zeitplan schedule
      + Setzt den Zeitplan im JSON-Format für Warmwasser.
    • +
    • WW_Solltemperatur targetTemperature
      + Setzt die Warmwassertemperatur (zwischen 10 und 60 Grad Celsius) auf targetTemperature.
    • +
    • Urlaub_Start_Zeit start
      + Setzt die Urlaubsstartzeit.
      + Start muss im Format: 2019-02-02 angegeben werden.
    • +
    • Urlaub_Ende_Zeit end
      + Setzt die Urlaubsendzeit.
      + Ende muss im Format: 2019-02-16 angegeben werden.
    • +
    • Urlaub_stop
      + Entfernt die Urlaubsstart- und Endzeit.
    • +
    +
+
+ + Get
+
    + Keine Daten zum Abrufen verfügbar. +
+
+ + +Attributes +
    + attr <name> <attribute> <value> +

    + Weitere Informationen zum attr-Befehl sind in der commandref#attr zu finden. +

    + Attribute: +
      + +
    • disable:
      + Stoppt die Kommunikation mit dem Viessmann-Server. +
    • + +
    • verbose:
      + Setzt das Verbositätslevel. +
    • + +
    • vitoconnect_raw_readings:
      + Erstellt Readings mit einfachen JSON-Namen wie 'heating.circuits.0.heating.curve.slope' anstelle von deutschen Bezeichnern (altes Mapping), Mapping-Attributen oder Übersetzungen.
      + Wenn raw Readings verwendet werden, werden die Setter dynamisch erstellt, die den raw Readings entsprechen.
      + Diese Einstellung wird empfohlen, um die Daten so dynamisch wie möglich von der API zu erhalten.
      + stateFormat oder userReadings können verwendet werden, um wichtige Readings mit einem lesbaren Namen anzuzeigen.
      + Wenn vitoconnect_raw_readings gesetzt ist, wird kein Mapping verwendet. +
    • + +
    • vitoconnect_disable_raw_readings:
      + Deaktiviert die zusätzliche Generierung von raw Readings.
      + Es werden nur die Messwerte angezeigt, die im gewählten Mapping explizit zugeordnet sind.
      + Diese Einstellung wird nicht aktiv, wenn vitoconnect_raw_readings = 1 gesetzt ist. +
    • + +
    • vitoconnect_gw_readings:
      + Erstellt ein Reading vom Gateway, einschließlich Informationen, wenn mehrere Gateways vorhanden sind. +
    • + +
    • vitoconnect_actions_active:
      + Erstellt Readings für Aktionen, z.B. 'heating.circuits.0.heating.curve.setCurve.setURI'. +
    • + +
    • vitoconnect_mappings:
      + Definiert eigene Zuordnungen von Schlüssel-Wert-Paaren anstelle der eingebauten Zuordnungen. Das Format muss wie folgt sein:
      + mapping
      + { 'device.serial.value' => 'device_serial',
      + 'heating.boiler.sensors.temperature.main.status' => 'status',
      + 'heating.boiler.sensors.temperature.main.value' => 'haupt_temperatur'}
      + Die eigene Zuordnung hat Vorrang vor der alten Zuordnung. +
    • + +
    • vitoconnect_translations:
      + Definiert eigene Übersetzungen für Wörter, die dann Teil für Teil übersetzt werden. Das Format muss wie folgt sein:
      + translation
      + { 'device' => 'gerät',
      + 'messages' => 'nachrichten',
      + 'errors' => 'fehler'}
      + Die eigene Übersetzung hat Vorrang vor der Zuordnung und der alten Zuordnung. +
    • + +
    • vitoconnect_mapping_roger:
      + Verwendet das Mapping von Roger vom 8. November (https://forum.fhem.de/index.php?msg=1292441) anstelle der SVN-Zuordnung. +
    • + +
    • vitoconnect_serial:
      + Dieses Attribut wird bei der Initialisierung des FHEM-Geräts gesetzt.
      + Der Befehl set selectDevice muss ausgeführt werden, wenn mehrere Seriennummern verfügbar sind.
      + Dieses Attribut muss nicht manuell gesetzt werden, wenn nur ein Viessmann Gerät vorhanden ist. +
    • + +
    • vitoconnect_installationID:
      + Dieses Attribut wird bei der Initialisierung des FHEM-Geräts gesetzt.
      + Der Befehl set selectDevice muss ausgeführt werden, wenn mehrere Seriennummern verfügbar sind.
      + Dieses Attribut muss nicht manuell gesetzt werden, wenn nur ein Viessmann Gerät vorhanden ist. +
    • + +
    • vitoconnect_timeout:
      + Setzt ein Timeout für den API-Aufruf. +
    • + +
    • vitoconnect_device:
      + Es kann zwischen den Geräten 0 (Standard) oder 1 gewählt werden. Diese Funktion konnte nicht getestet werden, da nur ein Gerät verfügbar ist. +
    • +
    +
+ +=end html_DE + +=for :application/json;q=META.json 98_vitoconnect.pm +{ + "abstract": "Using the viessmann API to read and set data", + "x_lang": { + "de": { + "abstract": "Benutzt die Viessmann API zum lesen und setzen von daten" + } + }, + "keywords": [ + "inverter", + "photovoltaik", + "electricity", + "heating", + "burner", + "heatpump", + "gas", + "oil" + ], + "version": "v1.1.1", + "release_status": "stable", + "author": [ + "Stefan Runge " + ], + "x_fhem_maintainer": [ + "Stefanru" + ], + "x_fhem_maintainer_github": [ + "stefanru1" + ], + "prereqs": { + "runtime": { + "requires": { + "FHEM": 5.00918799, + "perl": 5.014, + "POSIX": 0, + "GPUtils": 0, + "Encode": 0, + "Blocking": 0, + "Color": 0, + "utf8": 0, + "HttpUtils": 0, + "JSON": 4.020, + "FHEM::SynoModules::SMUtils": 1.0270, + "Time::HiRes": 0, + "MIME::Base64": 0, + "Math::Trig": 0, + "List::Util": 0, + "Storable": 0 + }, + "recommends": { + "FHEM::Meta": 0, + "FHEM::Utility::CTZ": 1.00, + "DateTime": 0, + "DateTime::Format::Strptime": 0, + "AI::DecisionTree": 0, + "Data::Dumper": 0 + }, + "suggests": { + } + } + }, + "resources": { + "x_wiki": { + "web": "https://wiki.fhem.de/wiki/Vitoconnect", + "title": "vitoconnect" + }, + "repository": { + "x_dev": { + "type": "svn", + "url": "https://svn.fhem.de/trac/browser/trunk/fhem/FHEM/", + "web": "https://svn.fhem.de/trac/browser/trunk/fhem/FHEM/98_vitoconnect.pm", + "x_branch": "dev", + "x_filepath": "fhem/contrib/", + "x_raw": "https://svn.fhem.de/trac/browser/trunk/fhem/FHEM/98_vitoconnect.pm" + } + } + } +} +=end :application/json;q=META.json =cut