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.