From ad83e0df0000d0ddc4c0450c3c83ff8bedc3581b Mon Sep 17 00:00:00 2001 From: choenig <> Date: Thu, 21 Oct 2021 11:53:36 +0000 Subject: [PATCH] 48_MieleAtHome: v1.2.0 - support set targetTemperature and mode git-svn-id: https://svn.fhem.de/fhem/trunk@25100 2b470e98-0d58-463d-a4d8-8e2adae1ed80 --- fhem/CHANGED | 1 + fhem/FHEM/48_MieleAtHome.pm | 234 +++++++++++++++++++++++++++++------- 2 files changed, 190 insertions(+), 45 deletions(-) diff --git a/fhem/CHANGED b/fhem/CHANGED index 11092bc10..81830bcd6 100644 --- a/fhem/CHANGED +++ b/fhem/CHANGED @@ -1,5 +1,6 @@ # Add changes at the top of the list. Keep it in ASCII, and 80-char wide. # Do not insert empty lines here, update check depends on it. + - feature: 48_MieleAtHome: support set targetTemperature and mode - bugfix: 50_Signalbot: Fixed issue with Babble and invite - bugfix: 73_ElectricityCalculator: use Data:Dumper inserted - bugfix: 73_GasCalculator: use Data:Dumper inserted diff --git a/fhem/FHEM/48_MieleAtHome.pm b/fhem/FHEM/48_MieleAtHome.pm index a8e356bc4..e1af1cbe4 100644 --- a/fhem/FHEM/48_MieleAtHome.pm +++ b/fhem/FHEM/48_MieleAtHome.pm @@ -35,10 +35,11 @@ use Encode qw(encode_utf8); use List::Util qw[min max]; use JSON; -my $version = "1.1.1"; +my $version = "1.2.0"; my $MAH_hasMimeBase64 = 1; +# DONE: use constant PROCESS_ACTIONS => { 0x01 => "start", # 1 START 0x02 => "stop", # 2 STOP @@ -49,11 +50,13 @@ use constant PROCESS_ACTIONS => { 0x07 => "stopSuperCooling", # 7 STOP SUPERCOOLING }; +# DONE: use constant LIGHT_ACTIONS => { 0x01 => "enable", # 1 Enable 0x02 => "disable", # 2 Disable }; +# DONE: use constant VENTILATION_STEPS => { 0x01 => "Step1", # 1 Step1 0x02 => "Step2", # 2 Step2 @@ -61,11 +64,20 @@ use constant VENTILATION_STEPS => { 0x04 => "Step4", # 4 Step4 }; +# DONE: +use constant MODE_ACTIONS => { + 0x00 => "normalOperationMode", # 0 Normal operation mode + 0x01 => "sabbathMode", # 1 Sabbath mode +}; + +# DONE: +# DONE: +# DONE: +# DONE: + # TODO: -# TODO: # TODO: # TODO: -# TODO: use constant COUNTRIES => { "Miele-Deutschland" => "de-DE", @@ -404,7 +416,7 @@ sub MAH_SetFn($$@) } elsif( $cmd eq 'startTime') { return "usage: startTime " if(@args != 1); - return MAH_setStartTime($hash, $args[0]) + return MAH_setStartTime($hash, $args[0]); } elsif( $cmd eq 'update' ) { return "use $cmd without arguments" if(@args != 0); @@ -413,11 +425,19 @@ sub MAH_SetFn($$@) } elsif( $cmd eq 'ventilationStep') { return "usage: ventilationStep " if(@args != 1); - return MAH_setVentilationStep($hash, $args[0]) + return MAH_setVentilationStep($hash, $args[0]); } elsif( $cmd eq 'light') { return "usage: light enable|disable" if(@args != 1); - return MAH_setLight($hash, $args[0]) + return MAH_setLight($hash, $args[0]); + } + elsif( $cmd eq 'mode') { + return "usage: mode " if(@args != 1); + return MAH_setMode($hash, $args[0]); + } + elsif( $cmd =~ /targetTemperature_zone([0-9]+)/) { + return "usage: targetTemperature_zone${1} " if(@args != 1); + return MAH_setTargetTemperature($hash, $1, $args[0]); } else { @@ -436,32 +456,49 @@ sub MAH_SetFn($$@) } } - # light actions - my $lightCmds = ""; - my @lightIds = split(/,/, ReadingsVal($name, "actions_light", "")); - foreach my $lightId (@lightIds) { - if (defined LIGHT_ACTIONS->{$lightId}) { - $lightCmds .= LIGHT_ACTIONS->{$lightId} . ","; - } - } - chop($lightCmds); # remove trailing ',' + # light + my $lightCmds = MAH_getAvailableCommands($hash, "actions_light", LIGHT_ACTIONS()); $list .= "light:${lightCmds} " if ($lightCmds ne ""); # ventilation steps - my $ventilationStepCmds = ""; - my @ventilationStepIds = split(/,/, ReadingsVal($name, "actions_ventilationStep", "")); - foreach my $ventilationStepId (@ventilationStepIds) { - if (defined VENTILATION_STEPS->{$ventilationStepId}) { - $ventilationStepCmds .= VENTILATION_STEPS->{$ventilationStepId} . ","; + my $ventilationStepCmds = MAH_getAvailableCommands($hash, "actions_ventilationStep", VENTILATION_STEPS()); + $list .= "ventilationStep:${ventilationStepCmds} " if ($ventilationStepCmds ne ""); + + # modes + my $modeCmds = MAH_getAvailableCommands($hash, "actions_modes", MODE_ACTIONS()); + $list .= "mode:${modeCmds} " if ($modeCmds ne ""); + + # target temperatures + my @availableTargetTemperatures = split(/ /, ReadingsVal($name, "actions_targetTemperature", "")); + foreach my $targetTemperature (@availableTargetTemperatures) { + if ($targetTemperature =~ /^([0-9]+)\[(-?[0-9]+),(-?[0-9]+)\]$/) { + $list .= "targetTemperature_zone$1:slider,$2,1,$3 " } } - chop($ventilationStepCmds); # remove trailing ',' - $list .= "ventilationStep:${ventilationStepCmds} " if ($ventilationStepCmds ne ""); return "Unknown argument $cmd, choose one of $list"; } } +#------------------------------------------------------------------------------------------------------ +# returns the strings of the commands from const ACTIONS that are currently available (via `actions_...`) +#------------------------------------------------------------------------------------------------------ +sub MAH_getAvailableCommands($$$) +{ + my ($hash, $action_reading, $ACTIONS) = @_; + my $name = $hash->{NAME}; + + my $cmds = ""; + my @ids = split(/,/, ReadingsVal($name, $action_reading, "")); + foreach my $id (@ids) { + if (defined $ACTIONS->{$id}) { + $cmds .= $ACTIONS->{$id} . ","; + } + } + chop($cmds); # remove trailing ',' + return $cmds; +} + #------------------------------------------------------------------------------------------------------ # SetFn #------------------------------------------------------------------------------------------------------ @@ -716,7 +753,7 @@ sub MAH_onOauthLoginReply($$$) } if ($code eq "") { - $code = scrapeGrantAccessPage($hash, $data); + $code = MAH_scrapeGrantAccessPage($hash, $data); if ($code ne "") { MAH_Log($hash, 5, "Bearer found in HTML"); } @@ -731,7 +768,7 @@ sub MAH_onOauthLoginReply($$$) MAH_doThirdpartyTokenRequest($hash, $code, ""); } -sub scrapeGrantAccessPage($$) +sub MAH_scrapeGrantAccessPage($$) { my ($hash, $data) = (@_); @@ -1036,9 +1073,25 @@ sub MAH_onGetDeviceIdentAndStateReply($$$) readingsBulkUpdate($hash, "signalFailure", $json->{state}->{signalFailure}); readingsBulkUpdate($hash, "signalInfo", $json->{state}->{signalInfo}); + # target temperature + my @targetTemperatures = MAH_decodeTemperature($hash, @{$json->{state}->{targetTemperature}}); + if (scalar(@targetTemperatures) == 1) { + readingsBulkUpdate($hash, "targetTemperature", ${targetTemperatures[0]}); + } else { + for (my $i = 0; $i < scalar(@targetTemperatures); $i++) { + readingsBulkUpdate($hash, "targetTemperature_zone".($i+1), ${targetTemperatures[$i]}); + } + } + # temperature - readingsBulkUpdate($hash, "targetTemperature", MAH_decodeTemperature($hash, @{$json->{state}->{targetTemperature}})); - readingsBulkUpdate($hash, "temperature", MAH_decodeTemperature($hash, @{$json->{state}->{temperature}})); + my @temperatures = MAH_decodeTemperature($hash, @{$json->{state}->{temperature}}); + if (scalar(@temperatures) == 1) { + readingsBulkUpdate($hash, "temperature", ${temperatures[0]}); + } else { + for (my $i = 0; $i < scalar(@temperatures); $i++) { + readingsBulkUpdate($hash, "temperature_zone".($i+1), ${temperatures[$i]}); + } + } #eta & state my ($eta, $etaHR) = MAH_calculateETA($json->{state}->{remainingTime}, @@ -1122,27 +1175,20 @@ sub MAH_onGetDeviceActionsReply($$$) return; } - # possible processAction out of - # 1 START - # 2 STOP - # 3 PAUSE - # 4 START SUPERFREEZING - # 5 STOP SUPERFREEZING - # 6 START SUPERCOOLING - # 7 STOP SUPERCOOLING - no strict "refs"; readingsBeginUpdate($hash); - readingsBulkUpdate($hash, "actions_processAction", join(",", @{$json->{processAction}})); - readingsBulkUpdate($hash, "actions_light", join(",", @{$json->{light}})); - readingsBulkUpdate($hash, "actions_startTime", join(",", @{$json->{startTime}})); - readingsBulkUpdate($hash, "actions_ventilationStep", join(",", @{$json->{ventilationStep}})); - readingsBulkUpdate($hash, "actions_programId", join(",", @{$json->{programId}})); - readingsBulkUpdate($hash, "actions_startTime", join(",", MAH_parseActionsStartTime($json->{startTime}))); - readingsBulkUpdate($hash, "actions_deviceName", $json->{deviceName}); - readingsBulkUpdate($hash, "actions_powerOn", defined($json->{powerOn}) ? $json->{powerOn} : "0"); - readingsBulkUpdate($hash, "actions_powerOff", defined($json->{powerOff}) ? $json->{powerOff} : "0"); + readingsBulkUpdate($hash, "actions_processAction", join(",", @{$json->{processAction}})); + readingsBulkUpdate($hash, "actions_light", join(",", @{$json->{light}})); + readingsBulkUpdate($hash, "actions_startTime", join(",", MAH_parseActionsStartTime($json->{startTime}))); + readingsBulkUpdate($hash, "actions_ventilationStep", join(",", @{$json->{ventilationStep}})); + readingsBulkUpdate($hash, "actions_programId", join(",", @{$json->{programId}})); + readingsBulkUpdate($hash, "actions_targetTemperature", MAH_parseActionsTargetTemperature($hash,$json->{targetTemperature})); + readingsBulkUpdate($hash, "actions_deviceName", $json->{deviceName}); + readingsBulkUpdate($hash, "actions_powerOff", defined($json->{powerOff}) ? $json->{powerOff} : "0"); + readingsBulkUpdate($hash, "actions_powerOn", defined($json->{powerOn}) ? $json->{powerOn} : "0"); + # readingsBulkUpdate($hash, "actions_colors", ); + readingsBulkUpdate($hash, "actions_modes", join(",", @{$json->{modes}})); readingsEndUpdate($hash, 1 ); use strict "refs"; @@ -1152,6 +1198,14 @@ sub MAH_onGetDeviceActionsReply($$$) #------------------------------------------------------------------------------------------------------ # format time from array +# "targetTemperature":[ +# {"value_raw":600,"value_localized":6.0,"unit":"Celsius"}, +# {"value_raw":-1800,"value_localized":-18.0,"unit":"Celsius"}, +# {"value_raw":-32768,"value_localized":null,"unit":"Celsius"}], +# "temperature":[ +# {"value_raw":593,"value_localized":5.93,"unit":"Celsius"}, +# {"value_raw":-1800,"value_localized":-18.0,"unit":"Celsius"}, +# {"value_raw":-32768,"value_localized":null,"unit":"Celsius"}], #------------------------------------------------------------------------------------------------------ sub MAH_decodeTemperature($@) { @@ -1165,7 +1219,7 @@ sub MAH_decodeTemperature($@) } } - return join(", ", @retval); + return @retval; } #------------------------------------------------------------------------------------------------------ @@ -1187,6 +1241,40 @@ sub MAH_parseActionsStartTime($) return "[?]"; } +#------------------------------------------------------------------------------------------------------ +# parse the target temperature from actions +# [ +# { +# "zone": 1, +# "min": 1, +# "max": 9 +# }, +# { +# "zone": 2, +# "min": -26, +# "max": -16 +# } +# ] +#------------------------------------------------------------------------------------------------------ +sub MAH_parseActionsTargetTemperature($$) +{ + my ($hash,$json) = @_; + + no strict "refs"; + + my $retval = ""; + my @zones = @{$json}; + for (my $i = 0; $i < scalar(@zones); $i++) { + my $zone = $zones[$i]; + $retval .= $zone->{zone} . "[" . $zone->{min} . "," . $zone->{max} . "] "; + } + + use strict "refs"; + + chop($retval); # remove trailing space + return $retval; +} + #------------------------------------------------------------------------------------------------------ # calculate the estimated time of arrival (as HH:MM and as human readable version) #------------------------------------------------------------------------------------------------------ @@ -1353,6 +1441,51 @@ sub MAH_setLight($$) return MAH_setAction($hash, "light", "${lightActionId}"); } +#------------------------------------------------------------------------------------------------------ +# MAH_setMode +#------------------------------------------------------------------------------------------------------ +sub MAH_setMode($$) +{ + my ($hash, $modeActionName) = @_; + my $name = $hash->{NAME}; + + my ($modeActionId) = grep{ MODE_ACTIONS->{$_} eq $modeActionName } keys %{MODE_ACTIONS()}; + if (!defined $modeActionId) { + return "invalid mode action: '${modeActionName}'"; + } + + my @availableModeActions = split(/,/, ReadingsVal($name, "actions_modes", "")); + if (! grep {$_ eq $modeActionId} @availableModeActions) { + return "'${modeActionName}' is currently not available"; + } + + return MAH_setAction($hash, "modes", "${modeActionId}"); +} + +#------------------------------------------------------------------------------------------------------ +# MAH_setTargetTemperature +#------------------------------------------------------------------------------------------------------ +sub MAH_setTargetTemperature($$$) +{ + my ($hash, $zone, $temp) = @_; + my $name = $hash->{NAME}; + + my @availableTargetTemperatures = split(/ /, ReadingsVal($name, "actions_targetTemperature", "")); + foreach my $targetTemperature (@availableTargetTemperatures) { + if ($targetTemperature =~ /^([0-9]+)\[(-?[0-9]+),(-?[0-9]+)\]$/) { + if ($1 eq $zone) { + if ($2 <= int($temp) && int($temp) <= $3) { + return MAH_setAction($hash, "targetTemperature", "[{\"zone\": $zone, \"value\": $temp}]"); + } else { + return "temperature for zone ${zone} out of range, must be between ${2} and ${3}"; + } + } + } + } + + return "zone ${zone} not setable"; +} + #------------------------------------------------------------------------------------------------------ # MAH_setVentilationStep #------------------------------------------------------------------------------------------------------ @@ -1829,6 +1962,9 @@ sub MAH_Log($$$) my $subroutine = ( split(':', $modAndSub) )[2]; my $name = ( ref($hash) eq "HASH" ) ? $hash->{NAME} : "MieleAtHome"; + # replace non-printable characters by "" + $logMessage =~ s/([\x{00}-\x{1f}\x{7f}-\x{ffffffff}])/'<'.unpack('(H2)',$1).'>'/ge; + Log3($hash, $logLevel, "${name} (MieleAtHome::${subroutine}:${line}) " . $logMessage); #Log3($hash, $logLevel, "${name} (MieleAtHome::${subroutine}:${line}) Stack was: " . MAH_getStacktrace()); } @@ -1955,6 +2091,10 @@ sub MAH_getStacktrace()
light [enable|disable]
enable/disable the light of your device. only available depending on the type and state of your appliance. +
  • +
    mode <mode>
    + set the mode of your applience. can be either sabbathMode or normalOperationMode. +
  • on
    power up your device. only available depending on the type and state of your appliance. @@ -1999,6 +2139,10 @@ sub MAH_getStacktrace()
    stopSuperCooling
    stop super cooling your device. only available depending on the type and state of your appliance.
  • +
  • +
    targetTemperature_zoneN <temp>
    + set the targetTemperature of zone N of your applience. The available temperature-range is determined by the Miele-API. +
  • update
    instantly update all readings.