# $Id$
##############################################################################
#
# 71_YAMAHA_AVR.pm
# An FHEM Perl module for controlling Yamaha AV-Receivers
# via network connection. As the interface is standardized
# within all Yamaha AV-Receivers, this module should work
# with any receiver which has an ethernet or wlan connection.
#
# Copyright by Markus Bloch
# e-mail: Notausstieg0309@googlemail.com
#
# This file is part of fhem.
#
# Fhem is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# Fhem is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with fhem. If not, see .
#
##############################################################################
package main;
use strict;
use warnings;
use Time::HiRes qw(gettimeofday sleep);
use HttpUtils;
sub YAMAHA_AVR_Get($@);
sub YAMAHA_AVR_Define($$);
sub YAMAHA_AVR_GetStatus($;$);
sub YAMAHA_AVR_Attr(@);
sub YAMAHA_AVR_ResetTimer($;$);
sub YAMAHA_AVR_Undefine($$);
###################################
sub
YAMAHA_AVR_Initialize($)
{
my ($hash) = @_;
$hash->{GetFn} = "YAMAHA_AVR_Get";
$hash->{SetFn} = "YAMAHA_AVR_Set";
$hash->{DefFn} = "YAMAHA_AVR_Define";
$hash->{AttrFn} = "YAMAHA_AVR_Attr";
$hash->{UndefFn} = "YAMAHA_AVR_Undefine";
$hash->{AttrList} = "do_not_notify:0,1 disable:0,1 request-timeout:1,2,3,4,5 volumeSteps:1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20 model volume-smooth-change:0,1 volume-smooth-steps:1,2,3,4,5,6,7,8,9,10 ".
$readingFnAttributes;
}
###################################
sub
YAMAHA_AVR_GetStatus($;$)
{
my ($hash, $local) = @_;
my $name = $hash->{NAME};
my $power;
$local = 0 unless(defined($local));
return "" if(!defined($hash->{helper}{ADDRESS}) or !defined($hash->{helper}{OFF_INTERVAL}) or !defined($hash->{helper}{ON_INTERVAL}));
my $device = $hash->{helper}{ADDRESS};
# get the model informations and available zones if no informations are available
if(not defined($hash->{ACTIVE_ZONE}) or not defined($hash->{helper}{ZONES}) or not defined($hash->{MODEL}) or not defined($hash->{FIRMWARE}))
{
YAMAHA_AVR_getModel($hash);
YAMAHA_AVR_ResetTimer($hash) unless($local == 1);
return;
}
# get all available inputs and scenes if nothing is available
if((not defined($hash->{helper}{INPUTS}) or length($hash->{helper}{INPUTS}) == 0))
{
YAMAHA_AVR_getInputs($hash);
}
my $zone = YAMAHA_AVR_getParamName($hash, $hash->{ACTIVE_ZONE}, $hash->{helper}{ZONES});
if(not defined($zone))
{
YAMAHA_AVR_ResetTimer($hash) unless($local == 1);
return "No Zone available";
}
YAMAHA_AVR_SendCommand($hash, "<$zone>GetParam$zone>", "statusRequest", "basicStatus");
YAMAHA_AVR_SendCommand($hash, "GetParam", "statusRequest", "fwUpdate", 1);
if(!exists($hash->{helper}{SUPPORT_TONE_STATUS}) or (exists($hash->{helper}{SUPPORT_TONE_STATUS}) and $hash->{helper}{SUPPORT_TONE_STATUS}))
{
if (YAMAHA_AVR_isModel_DSP($hash))
{
if ($zone eq "Main_Zone")
{
YAMAHA_AVR_SendCommand($hash, "<$zone>GetParam$zone>", "statusRequest", "toneStatus", 1);
YAMAHA_AVR_SendCommand($hash, "<$zone>GetParam$zone>", "statusRequest", "toneStatus", 1);
}
else
{
YAMAHA_AVR_SendCommand($hash, "<$zone>GetParam$zone>", "statusRequest", "toneStatus", 1);
YAMAHA_AVR_SendCommand($hash, "<$zone>GetParam$zone>", "statusRequest", "toneStatus", 1);
}
}
else
{
YAMAHA_AVR_SendCommand($hash, "<$zone>GetParam$zone>", "statusRequest", "toneStatus", 1);
YAMAHA_AVR_SendCommand($hash, "<$zone>GetParam$zone>", "statusRequest", "toneStatus", 1);
}
}
YAMAHA_AVR_SendCommand($hash, "GetParam", "statusRequest", "fwUpdate", 1);
YAMAHA_AVR_ResetTimer($hash) unless($local == 1);
return undef;
}
###################################
sub
YAMAHA_AVR_Get($@)
{
my ($hash, @a) = @_;
my $what;
my $return;
return "argument is missing" if(int(@a) != 2);
$what = $a[1];
if(exists($hash->{READINGS}{$what}))
{
if(defined($hash->{READINGS}{$what}))
{
return $hash->{READINGS}{$what}{VAL};
}
else
{
return "no such reading: $what";
}
}
else
{
$return = "unknown argument $what, choose one of";
foreach my $reading (keys %{$hash->{READINGS}})
{
$return .= " $reading:noArg";
}
return $return;
}
}
###################################
sub
YAMAHA_AVR_Set($@)
{
my ($hash, @a) = @_;
my $name = $hash->{NAME};
my $address = $hash->{helper}{ADDRESS};
# get the model informations and available zones if no informations are available
if(not defined($hash->{ACTIVE_ZONE}) or not defined($hash->{helper}{ZONES}))
{
YAMAHA_AVR_getModel($hash);
}
# get all available inputs if nothing is available
if(not defined($hash->{helper}{INPUTS}) or length($hash->{helper}{INPUTS}) == 0)
{
YAMAHA_AVR_getInputs($hash);
}
my $zone = YAMAHA_AVR_getParamName($hash, $hash->{ACTIVE_ZONE}, $hash->{helper}{ZONES});
my $inputs_piped = defined($hash->{helper}{INPUTS}) ? YAMAHA_AVR_Param2Fhem(lc($hash->{helper}{INPUTS}), 0) : "" ;
my $inputs_comma = defined($hash->{helper}{INPUTS}) ? YAMAHA_AVR_Param2Fhem(lc($hash->{helper}{INPUTS}), 1) : "" ;
my $scenes_piped = defined($hash->{helper}{SCENES}) ? YAMAHA_AVR_Param2Fhem(lc($hash->{helper}{SCENES}), 0) : "" ;
my $scenes_comma = defined($hash->{helper}{SCENES}) ? YAMAHA_AVR_Param2Fhem(lc($hash->{helper}{SCENES}), 1) : "" ;
my $dsp_modes_piped = defined($hash->{helper}{DSP_MODES}) ? YAMAHA_AVR_Param2Fhem(lc($hash->{helper}{DSP_MODES}), 0) : "" ;
my $dsp_modes_comma = defined($hash->{helper}{DSP_MODES}) ? YAMAHA_AVR_Param2Fhem(lc($hash->{helper}{DSP_MODES}), 1) : "" ;
return "No Argument given" if(!defined($a[1]));
my $what = $a[1];
my $usage = "Unknown argument $what, choose one of ". "on:noArg ".
"off:noArg ".
"volumeStraight:slider,-80,1,16 ".
"volume:slider,0,1,100 ".
"volumeUp ".
"volumeDown ".
(exists($hash->{helper}{INPUTS}) ? "input:".$inputs_comma." " : "").
"mute:on,off,toggle ".
"remoteControl:setup,up,down,left,right,return,option,display,tunerPresetUp,tunerPresetDown,enter ".
(exists($hash->{helper}{SCENES}) ? "scene:".$scenes_comma." " : "").
((exists($hash->{ACTIVE_ZONE}) and $hash->{ACTIVE_ZONE} eq "mainzone") ? "straight:on,off 3dCinemaDsp:off,auto adaptiveDrc:off,auto ".
(exists($hash->{helper}{DIRECT_TAG}) ? "direct:on,off " : "").
(exists($hash->{helper}{DSP_MODES}) ? "dsp:".$dsp_modes_comma." " : "")."enhancer:on,off " : "").
(exists($hash->{helper}{CURRENT_INPUT_TAG}) ? "play:noArg pause:noArg stop:noArg skip:reverse,forward ".
(exists($hash->{helper}{PLAY_CONTROL}) ? "shuffle:on,off repeat:off,one,all " : "") : "").
"sleep:off,30min,60min,90min,120min,last ".
(($hash->{helper}{SUPPORT_TONE_STATUS} and exists($hash->{ACTIVE_ZONE}) and $hash->{ACTIVE_ZONE} eq "mainzone") ? "bass:slider,-6,0.5,6 treble:slider,-6,0.5,6 " : "").
(($hash->{helper}{SUPPORT_TONE_STATUS} and exists($hash->{ACTIVE_ZONE}) and ($hash->{ACTIVE_ZONE} ne "mainzone") and YAMAHA_AVR_isModel_DSP($hash)) ? "bass:slider,-10,1,10 treble:slider,-10,1,10 " : "").
(($hash->{helper}{SUPPORT_TONE_STATUS} and exists($hash->{ACTIVE_ZONE}) and ($hash->{ACTIVE_ZONE} ne "mainzone") and not YAMAHA_AVR_isModel_DSP($hash)) ? "bass:slider,-10,2,10 treble:slider,-10,2,10 " : "").
"statusRequest:noArg";
Log3 $name, 5, "YAMAHA_AVR ($name) - set ".join(" ", @a);
if($what eq "on")
{
YAMAHA_AVR_SendCommand($hash, "<$zone>On$zone>" ,$what,undef);
}
elsif($what eq "off")
{
YAMAHA_AVR_SendCommand($hash, "<$zone>Standby$zone>", $what, undef);
}
elsif($what eq "input")
{
if(defined($a[2]))
{
if($hash->{READINGS}{power}{VAL} eq "on")
{
if(not $inputs_piped eq "")
{
if($a[2] =~ /^($inputs_piped)$/)
{
my $command = YAMAHA_AVR_getParamName($hash, $a[2], $hash->{helper}{INPUTS});
if(defined($command) and length($command) > 0)
{
YAMAHA_AVR_SendCommand($hash, "<$zone>".$command."$zone>", $what, $a[2]);
}
else
{
return "invalid input: ".$a[2];
}
}
else
{
return $usage;
}
}
else
{
return "No inputs are avaible. Please try an statusUpdate.";
}
}
else
{
return "input can only be used when device is powered on";
}
}
else
{
return $inputs_piped eq "" ? "No inputs are available. Please try an statusUpdate." : "No input parameter was given";
}
}
elsif($what eq "scene")
{
if(defined($a[2]))
{
if(not $scenes_piped eq "")
{
if($a[2] =~ /^($scenes_piped)$/)
{
my $command = YAMAHA_AVR_getParamName($hash, $a[2], $hash->{helper}{SCENES});
if(defined($command) and length($command) > 0)
{
YAMAHA_AVR_SendCommand($hash, "<$zone>".$command."$zone>", $what, $a[2]);
}
else
{
return "invalid input: ".$a[2];
}
}
else
{
return $usage;
}
}
else
{
return "No scenes are avaible. Please try an statusUpdate.";
}
}
else
{
return $scenes_piped eq "" ? "No scenes are available. Please try an statusUpdate." : "No scene parameter was given";
}
}
elsif($what eq "mute")
{
if(defined($a[2]))
{
if($hash->{READINGS}{power}{VAL} eq "on")
{
# Depending on the status response, use the short or long Volume command
my $volume_cmd = (exists($hash->{helper}{USE_SHORT_VOL_CMD}) and $hash->{helper}{USE_SHORT_VOL_CMD} eq "1" ? "Vol" : "Volume");
if( $a[2] eq "on" or ($a[2] eq "toggle" and ReadingsVal($hash->{NAME}, "mute", "off") eq "off"))
{
YAMAHA_AVR_SendCommand($hash, "<$zone><$volume_cmd>On$volume_cmd>$zone>", $what, "on");
}
elsif($a[2] eq "off" or ($a[2] eq "toggle" and ReadingsVal($hash->{NAME}, "mute", "off") eq "on"))
{
YAMAHA_AVR_SendCommand($hash, "<$zone><$volume_cmd>Off$volume_cmd>$zone>", $what, "off");
}
else
{
return $usage;
}
}
else
{
return "mute can only used when device is powered on";
}
}
}
elsif($what =~ /^(volumeStraight|volume|volumeUp|volumeDown)$/)
{
my $target_volume;
if($what eq "volume" and $a[2] >= 0 && $a[2] <= 100)
{
$target_volume = YAMAHA_AVR_volume_rel2abs($a[2]);
}
elsif($what eq "volumeDown")
{
$target_volume = YAMAHA_AVR_volume_rel2abs($hash->{READINGS}{volume}{VAL} - ((defined($a[2]) and $a[2] =~ /^\d+$/) ? $a[2] : AttrVal($hash->{NAME}, "volumeSteps",5)));
}
elsif($what eq "volumeUp")
{
$target_volume = YAMAHA_AVR_volume_rel2abs($hash->{READINGS}{volume}{VAL} + ((defined($a[2]) and $a[2] =~ /^\d+$/) ? $a[2] : AttrVal($hash->{NAME}, "volumeSteps",5)));
}
else
{
$target_volume = $a[2];
}
# if lower than minimum (-80.5) or higher than max (16.5) set target volume to the corresponding boundary
$target_volume = -80.5 if(defined($target_volume) and $target_volume < -80.5);
$target_volume = 16.5 if(defined($target_volume) and $target_volume > 16.5);
Log3 $name, 4, "YAMAHA_AVR ($name) - new target volume: $target_volume";
if(defined($target_volume) )
{
if($hash->{READINGS}{power}{VAL} eq "on")
{
# Depending on the status response, use the short or long Volume command
my $volume_cmd = (exists($hash->{helper}{USE_SHORT_VOL_CMD}) and $hash->{helper}{USE_SHORT_VOL_CMD} eq "1" ? "Vol" : "Volume");
if(AttrVal($name, "volume-smooth-change", "0") eq "1")
{
my $steps = AttrVal($name, "volume-smooth-steps", 5);
my $diff = int(($target_volume - $hash->{READINGS}{volumeStraight}{VAL}) / $steps / 0.5) * 0.5;
my $current_volume = $hash->{READINGS}{volumeStraight}{VAL};
if($diff > 0)
{
Log3 $name, 4, "YAMAHA_AVR ($name) - use smooth volume change (with $steps steps of +$diff volume change to reach $target_volume)";
}
else
{
Log3 $name, 4, "YAMAHA_AVR ($name) - use smooth volume change (with $steps steps of $diff volume change to reach $target_volume)";
}
# Only if a volume reading exists and smoohing is really needed (step difference is not zero)
if(defined($hash->{READINGS}{volumeStraight}{VAL}) and $diff != 0 and not (defined($a[3]) and $a[3] eq "direct"))
{
Log3 $name, 4, "YAMAHA_AVR ($name) - set volume to ".($current_volume + $diff)." dB (target is $target_volume dB)";
YAMAHA_AVR_SendCommand($hash, "<$zone><$volume_cmd>".(($current_volume + $diff)*10)."1dB$volume_cmd>$zone>", "volume", ($current_volume + $diff)."|$diff|$target_volume" );
}
else
{
# Set the desired volume
Log3 $name, 4, "YAMAHA_AVR ($name) - set volume to ".$target_volume." dB";
YAMAHA_AVR_SendCommand($hash, "<$zone><$volume_cmd>".($target_volume*10)."1dB$volume_cmd>$zone>", "volume", "$target_volume|0|$target_volume");
}
}
else
{
# Set the desired volume
Log3 $name, 4, "YAMAHA_AVR ($name) - set volume to ".$target_volume." dB";
YAMAHA_AVR_SendCommand($hash, "<$zone><$volume_cmd>".($target_volume*10)."1dB$volume_cmd>$zone>", "volume", "$target_volume|0|$target_volume");
}
}
else
{
return "volume can only be used when device is powered on";
}
}
}
elsif($what eq "bass")
{
if(defined($a[2]))
{
if($hash->{READINGS}{power}{VAL} eq "on")
{
my $bassVal = $a[2];
if ((exists($hash->{ACTIVE_ZONE})) && ($hash->{ACTIVE_ZONE} eq "mainzone"))
{
$bassVal = int($a[2]) if not (($a[2] =~ /^\d$/ ) || ($a[2] =~ /\.5/) || ($a[2] =~ /\.0/));
$bassVal = -6 if($bassVal < -6);
$bassVal = 6 if($bassVal > 6);
if (YAMAHA_AVR_isModel_DSP($hash))
{
YAMAHA_AVR_SendCommand($hash, "<$zone>" . ReadingsVal($name,".bass_crossover","125") . "0Hz" . $bassVal*10 . "1dB$zone>", $what, $bassVal);
}
else
{
YAMAHA_AVR_SendCommand($hash, "<$zone>" . $bassVal*10 . "1dB$zone>", $what, $bassVal);
}
}
else # !main_zone
{
$bassVal = int($a[2]);
$bassVal = -10 if($bassVal < -10);
$bassVal = 10 if($bassVal > 10);
if (YAMAHA_AVR_isModel_DSP($hash))
{
YAMAHA_AVR_SendCommand($hash, "<$zone>" . $bassVal*10 . "1dB$zone>", $what, $bassVal);
}
else
{
$bassVal-- if (($bassVal % 2 != 0) && ($bassVal > 0));
$bassVal++ if (($bassVal % 2 != 0) && ($bassVal < 0));
YAMAHA_AVR_SendCommand($hash, "<$zone>" . $bassVal*10 . "1dB$zone>", $what, $bassVal);
}
}
}
else
{
return "bass can only used when device is powered on";
}
}
}
elsif($what eq "treble")
{
if(defined($a[2]))
{
if($hash->{READINGS}{power}{VAL} eq "on")
{
my $trebleVal = $a[2];
if ((exists($hash->{ACTIVE_ZONE})) && ($hash->{ACTIVE_ZONE} eq "mainzone"))
{
$trebleVal = int($a[2]) if not (($a[2] =~ /^\d$/ ) || ($a[2] =~ /\.5/) || ($a[2] =~ /\.0/));
$trebleVal = -6 if($trebleVal < -6);
$trebleVal = 6 if($trebleVal > 6);
if (YAMAHA_AVR_isModel_DSP($hash))
{
YAMAHA_AVR_SendCommand($hash, "<$zone>" . ReadingsVal($name,".treble_crossover","35") . "1kHz" . $trebleVal*10 . "1dB$zone>", $what, $trebleVal);
}
else
{
YAMAHA_AVR_SendCommand($hash, "<$zone>" . $trebleVal*10 . "1dB$zone>", $what, $trebleVal);
}
}
else # !main_zone
{
$trebleVal = int($trebleVal);
$trebleVal = -10 if($trebleVal < -10);
$trebleVal = 10 if($trebleVal > 10);
if (YAMAHA_AVR_isModel_DSP($hash))
{
YAMAHA_AVR_SendCommand($hash, "<$zone>" . $trebleVal*10 . "1dB$zone>", $what, $trebleVal);
}
else
{
$trebleVal-- if (($trebleVal % 2 != 0) && ($trebleVal > 0));
$trebleVal++ if (($trebleVal % 2 != 0) && ($trebleVal < 0));
YAMAHA_AVR_SendCommand($hash, "<$zone>" . $trebleVal*10 . "1dB$zone>", $what, $trebleVal);
}
}
}
else
{
return "treble can only used when device is powered on";
}
}
}
elsif($what eq "dsp")
{
if(defined($a[2]))
{
if(not $dsp_modes_piped eq "")
{
if($a[2] =~ /^($dsp_modes_piped)$/)
{
my $command = YAMAHA_AVR_getParamName($hash, $a[2],$hash->{helper}{DSP_MODES});
if(defined($command) and length($command) > 0)
{
YAMAHA_AVR_SendCommand($hash, "<$zone>$command$zone>", $what, $a[2]);
}
else
{
return "invalid dsp mode: ".$a[2];
}
}
else
{
return $usage;
}
}
else
{
return "No DSP presets are avaible. Please try an statusUpdate.";
}
}
else
{
return $dsp_modes_piped eq "" ? "No dsp presets are available. Please try an statusUpdate." : "No dsp preset was given";
}
}
elsif($what eq "straight")
{
if($a[2] eq "on")
{
YAMAHA_AVR_SendCommand($hash, "<$zone>On$zone>", $what, $a[2]);
}
elsif($a[2] eq "off")
{
YAMAHA_AVR_SendCommand($hash, "<$zone>Off$zone>", $what, $a[2]);
}
else
{
return $usage;
}
}
elsif($what eq "3dCinemaDsp")
{
if($a[2] eq "auto")
{
YAMAHA_AVR_SendCommand($hash, "<$zone><_3D_Cinema_DSP>Auto$zone>", "3dCinemaDsp", "auto");
}
elsif($a[2] eq "off")
{
YAMAHA_AVR_SendCommand($hash, "<$zone><_3D_Cinema_DSP>Off$zone>", "3dCinemaDsp", "off");
}
else
{
return $usage;
}
}
elsif($what eq "adaptiveDrc")
{
if($a[2] eq "auto")
{
YAMAHA_AVR_SendCommand($hash, "<$zone>Auto$zone>", $what, $a[2]);
}
elsif($a[2] eq "off")
{
YAMAHA_AVR_SendCommand($hash, "<$zone>Off$zone>", $what, $a[2]);
}
else
{
return $usage;
}
}
elsif($what eq "enhancer")
{
if($a[2] eq "on")
{
YAMAHA_AVR_SendCommand($hash, "<$zone>On$zone>", $what, $a[2]);
}
elsif($a[2] eq "off")
{
YAMAHA_AVR_SendCommand($hash, "<$zone>Off$zone>", $what, $a[2]);
}
else
{
return $usage;
}
}
elsif($what eq "direct" )
{
if(exists($hash->{helper}{DIRECT_TAG}))
{
if($a[2] eq "on")
{
YAMAHA_AVR_SendCommand($hash, "<$zone><".$hash->{helper}{DIRECT_TAG}.">On".$hash->{helper}{DIRECT_TAG}.">$zone>", $what, $a[2]);
}
elsif($a[2] eq "off")
{
YAMAHA_AVR_SendCommand($hash, "<$zone><".$hash->{helper}{DIRECT_TAG}.">Off".$hash->{helper}{DIRECT_TAG}.">$zone>", $what, $a[2]);
}
else
{
return $usage;
}
}
else
{
return "Unable to execute \"$what ".$a[2]."\" - please execute a statusUpdate first before you use this command";
}
}
elsif($what eq "sleep")
{
if($a[2] eq "off")
{
YAMAHA_AVR_SendCommand($hash, "<$zone>Off$zone>", $what, $a[2]);
}
elsif($a[2] eq "30min")
{
YAMAHA_AVR_SendCommand($hash, "<$zone>30 min$zone>", $what, $a[2]);
}
elsif($a[2] eq "60min")
{
YAMAHA_AVR_SendCommand($hash, "<$zone>60 min$zone>", $what, $a[2]);
}
elsif($a[2] eq "90min")
{
YAMAHA_AVR_SendCommand($hash, "<$zone>90 min$zone>", $what, $a[2]);
}
elsif($a[2] eq "120min")
{
YAMAHA_AVR_SendCommand($hash, "<$zone>120 min$zone>", $what, $a[2]);
}
elsif($a[2] eq "last")
{
YAMAHA_AVR_SendCommand($hash, "<$zone>Last$zone>", $what, $a[2]);
}
else
{
return $usage;
}
}
elsif($what eq "remoteControl")
{
# the RX-Vx71, RX-Vx73, RX-Ax10, RX-Ax20 series use a different tag name to access the remoteControl commands
my $control_tag = (exists($hash->{MODEL}) and $hash->{MODEL} =~ /^(RX-V\d{1,2}7(1|3)|RX-A\d{1,2}(1|2)0)$/ ? "List_Control" : "Cursor_Control");
if($a[2] eq "up")
{
YAMAHA_AVR_SendCommand($hash, "<$zone><$control_tag>Up$control_tag>$zone>", $what, $a[2]);
}
elsif($a[2] eq "down")
{
YAMAHA_AVR_SendCommand($hash, "<$zone><$control_tag>Down$control_tag>$zone>", $what, $a[2]);
}
elsif($a[2] eq "left")
{
YAMAHA_AVR_SendCommand($hash, "<$zone><$control_tag>Left$control_tag>$zone>", $what, $a[2]);
}
elsif($a[2] eq "right")
{
YAMAHA_AVR_SendCommand($hash, "<$zone><$control_tag>Right$control_tag>$zone>", $what, $a[2]);
}
elsif($a[2] eq "display")
{
YAMAHA_AVR_SendCommand($hash,"<$zone><$control_tag>Display$control_tag>$zone>", $what, $a[2]);
}
elsif($a[2] eq "return")
{
YAMAHA_AVR_SendCommand($hash,"<$zone><$control_tag>Return$control_tag>$zone>", $what, $a[2]);
}
elsif($a[2] eq "enter")
{
YAMAHA_AVR_SendCommand($hash,"<$zone><$control_tag>Sel$control_tag>$zone>", $what, $a[2]);
}
elsif($a[2] eq "setup")
{
YAMAHA_AVR_SendCommand($hash,"<$zone><$control_tag>On Screen$control_tag>$zone>", $what, $a[2]);
}
elsif($a[2] eq "option")
{
YAMAHA_AVR_SendCommand($hash,"<$zone><$control_tag>Option$control_tag>$zone>", $what, $a[2]);
}
elsif($a[2] eq "tunerPresetUp")
{
YAMAHA_AVR_SendCommand($hash,"Up", $what, $a[2]);
}
elsif($a[2] eq "tunerPresetDown")
{
YAMAHA_AVR_SendCommand($hash,"Down", $what, $a[2]);
}
else
{
return $usage;
}
}
elsif($what eq "play" and exists($hash->{helper}{CURRENT_INPUT_TAG}))
{
YAMAHA_AVR_SendCommand($hash,"<".$hash->{helper}{CURRENT_INPUT_TAG}.">Play".$hash->{helper}{CURRENT_INPUT_TAG}.">", $what, $a[2]);
}
elsif($what eq "stop" and exists($hash->{helper}{CURRENT_INPUT_TAG}))
{
YAMAHA_AVR_SendCommand($hash,"<".$hash->{helper}{CURRENT_INPUT_TAG}.">Stop".$hash->{helper}{CURRENT_INPUT_TAG}.">", $what, $a[2]);
}
elsif($what eq "pause" and exists($hash->{helper}{CURRENT_INPUT_TAG}))
{
YAMAHA_AVR_SendCommand($hash,"<".$hash->{helper}{CURRENT_INPUT_TAG}.">Pause".$hash->{helper}{CURRENT_INPUT_TAG}.">", $what, $a[2]);
}
elsif($what eq "skip")
{
if($a[2] eq "forward")
{
YAMAHA_AVR_SendCommand($hash,"<".$hash->{helper}{CURRENT_INPUT_TAG}.">Skip Fwd".$hash->{helper}{CURRENT_INPUT_TAG}.">", $what, $a[2]);
}
elsif($a[2] eq "reverse")
{
YAMAHA_AVR_SendCommand($hash,"<".$hash->{helper}{CURRENT_INPUT_TAG}.">Skip Rev".$hash->{helper}{CURRENT_INPUT_TAG}.">", $what, $a[2]);
}
else
{
return $usage;
}
}
elsif($what eq "shuffle")
{
if($a[2] eq "on")
{
YAMAHA_AVR_SendCommand($hash,"<".$hash->{helper}{CURRENT_INPUT_TAG}.">On".$hash->{helper}{CURRENT_INPUT_TAG}.">", $what, $a[2]);
}
elsif($a[2] eq "off")
{
YAMAHA_AVR_SendCommand($hash,"<".$hash->{helper}{CURRENT_INPUT_TAG}.">Off".$hash->{helper}{CURRENT_INPUT_TAG}.">", $what, $a[2]);
}
else
{
return $usage;
}
}
elsif($what eq "repeat")
{
if($a[2] eq "one")
{
YAMAHA_AVR_SendCommand($hash,"<".$hash->{helper}{CURRENT_INPUT_TAG}.">One".$hash->{helper}{CURRENT_INPUT_TAG}.">", $what, $a[2]);
}
elsif($a[2] eq "off")
{
YAMAHA_AVR_SendCommand($hash,"<".$hash->{helper}{CURRENT_INPUT_TAG}.">Off".$hash->{helper}{CURRENT_INPUT_TAG}.">", $what, $a[2]);
}
elsif($a[2] eq "all")
{
YAMAHA_AVR_SendCommand($hash,"<".$hash->{helper}{CURRENT_INPUT_TAG}.">All".$hash->{helper}{CURRENT_INPUT_TAG}.">", $what, $a[2]);
}
else
{
return $usage;
}
}
elsif($what eq "statusRequest")
{
YAMAHA_AVR_GetStatus($hash, 1);
}
else
{
return $usage;
}
}
#############################
sub
YAMAHA_AVR_Define($$)
{
my ($hash, $def) = @_;
my @a = split("[ \t][ \t]*", $def);
my $name = $hash->{NAME};
if(!@a >= 4)
{
my $msg = "wrong syntax: define YAMAHA_AVR [] [] [] ";
Log3 $name, 2, $msg;
return $msg;
}
my $address = $a[2];
$hash->{helper}{ADDRESS} = $address;
# if a zone was given, use it, otherwise use the mainzone
if(defined($a[3]))
{
$hash->{helper}{SELECTED_ZONE} = $a[3];
}
else
{
$hash->{helper}{SELECTED_ZONE} = "mainzone";
}
# if an update interval was given which is greater than zero, use it.
if(defined($a[4]) and $a[4] > 0)
{
$hash->{helper}{OFF_INTERVAL} = $a[4];
}
else
{
$hash->{helper}{OFF_INTERVAL} = 30;
}
if(defined($a[5]) and $a[5] > 0)
{
$hash->{helper}{ON_INTERVAL} = $a[5];
}
else
{
$hash->{helper}{ON_INTERVAL} = $hash->{helper}{OFF_INTERVAL};
}
# In case of a redefine, check the zone parameter if the specified zone exist, otherwise use the main zone
if(defined($hash->{helper}{ZONES}) and length($hash->{helper}{ZONES}) > 0)
{
if(defined(YAMAHA_AVR_getParamName($hash, lc $hash->{helper}{SELECTED_ZONE}, $hash->{helper}{ZONES})))
{
$hash->{ACTIVE_ZONE} = lc $hash->{helper}{SELECTED_ZONE};
}
else
{
Log3 $name, 2, "YAMAHA_AVR ($name) - selected zone >>".$hash->{helper}{SELECTED_ZONE}."<< is not available on device ".$hash->{NAME}.". Using Main Zone instead";
$hash->{ACTIVE_ZONE} = "mainzone";
}
YAMAHA_AVR_getInputs($hash);
}
# set the volume-smooth-change attribute only if it is not defined, so no user values will be overwritten
#
# own attribute values will be overwritten anyway when all attr-commands are executed from fhem.cfg
$attr{$name}{"volume-smooth-change"} = "1" unless(exists($attr{$name}{"volume-smooth-change"}));
unless(exists($hash->{helper}{AVAILABLE}) and ($hash->{helper}{AVAILABLE} == 0))
{
$hash->{helper}{AVAILABLE} = 1;
readingsSingleUpdate($hash, "presence", "present", 1);
}
# start the status update timer
$hash->{helper}{DISABLED} = 0 unless(exists($hash->{helper}{DISABLED}));
YAMAHA_AVR_ResetTimer($hash,0);
return undef;
}
##########################
sub
YAMAHA_AVR_Attr(@)
{
my @a = @_;
my $hash = $defs{$a[1]};
if($a[0] eq "set" && $a[2] eq "disable")
{
if($a[3] eq "0")
{
$hash->{helper}{DISABLED} = 0;
YAMAHA_AVR_GetStatus($hash, 1);
}
elsif($a[3] eq "1")
{
$hash->{helper}{DISABLED} = 1;
}
}
elsif($a[0] eq "del" && $a[2] eq "disable")
{
$hash->{helper}{DISABLED} = 0;
YAMAHA_AVR_GetStatus($hash, 1);
}
# Start/Stop Timer according to new disabled-Value
YAMAHA_AVR_ResetTimer($hash);
return undef;
}
#############################
sub
YAMAHA_AVR_Undefine($$)
{
my($hash, $name) = @_;
# Stop the internal GetStatus-Loop and exit
RemoveInternalTimer($hash);
return undef;
}
############################################################################################################
#
# Begin of helper functions
#
############################################################################################################
#############################
# sends a command to the receiver via HTTP
sub
YAMAHA_AVR_SendCommand($@)
{
my ($hash, $data,$cmd,$arg,$can_fail) = @_;
my $name = $hash->{NAME};
my $address = $hash->{helper}{ADDRESS};
my $blocking = 0;
if($cmd ne "statusRequest" and $hash->{helper}{AVAILABLE} == 1)
{
$blocking = 1;
}
# In case any URL changes must be made, this part is separated in this function".
if($blocking == 1)
{
Log3 $name, 5, "YAMAHA_AVR ($name) - execute blocking \"$cmd".(defined($arg) ? " ".(split("\\|", $arg))[0] : "")."\" on $name: $data";
my $param = {
url => "http://".$address."/YamahaRemoteControl/ctrl",
timeout => AttrVal($name, "request-timeout", 4),
noshutdown => 1,
data => "".$data,
loglevel => ($hash->{helper}{AVAILABLE} ? undef : 5),
hash => $hash,
cmd => $cmd,
arg => $arg,
can_fail => $can_fail
};
my ($err, $data) = HttpUtils_BlockingGet($param);
YAMAHA_AVR_ParseResponse($param, $err, $data);
}
else
{
Log3 $name, 5, "YAMAHA_AVR ($name) - execute nonblocking \"$cmd".(defined($arg) ? " ".(split("\\|", $arg))[0] : "")."\" on $name: $data";
HttpUtils_NonblockingGet({
url => "http://".$address."/YamahaRemoteControl/ctrl",
timeout => AttrVal($name, "request-timeout", 4),
noshutdown => 1,
data => "".$data,
loglevel => ($hash->{helper}{AVAILABLE} ? undef : 5),
hash => $hash,
cmd => $cmd,
arg => $arg,
can_fail => $can_fail,
callback => \&YAMAHA_AVR_ParseResponse
}
);
}
}
#############################
# parses the receiver response
sub
YAMAHA_AVR_ParseResponse ($$$)
{
my ( $param, $err, $data ) = @_;
my $hash = $param->{hash};
my $name = $hash->{NAME};
my $cmd = $param->{cmd};
my $arg = $param->{arg};
my $can_fail = $param->{can_fail};
if(exists($param->{code}))
{
Log3 $name, 5, "YAMAHA_AVR ($name) - received HTTP code ".$param->{code}." for command \"$cmd".(defined($arg) ? " ".(split("\\|", $arg))[0] : "")."\"";
if($cmd eq "statusRequest" and $param->{code} ne "200")
{
if($arg eq "playShuffle")
{
delete($hash->{helper}{PLAY_CONTROL}) if(exists($hash->{helper}{PLAY_CONTROL}));
}
elsif($arg eq "toneStatus")
{
$hash->{helper}{SUPPORT_TONE_STATUS} = 0;
}
}
}
if($err ne "" and not $can_fail)
{
Log3 $name, 5, "YAMAHA_AVR ($name) - could not execute command \"$cmd".(defined($arg) ? " ".(split("\\|", $arg))[0] : "")."\": $err";
if((not exists($hash->{helper}{AVAILABLE})) or (exists($hash->{helper}{AVAILABLE}) and $hash->{helper}{AVAILABLE} eq 1))
{
Log3 $name, 3, "YAMAHA_AVR ($name) - could not execute command on device $name. Please turn on your device in case of deactivated network standby or check for correct hostaddress.";
readingsSingleUpdate($hash, "presence", "absent", 1);
readingsSingleUpdate($hash, "state", "absent", 1);
}
$hash->{helper}{AVAILABLE} = 0;
}
elsif($data ne "")
{
Log3 $name, 5, "YAMAHA_AVR ($name) - got response for \"$cmd".(defined($arg) ? " ".(split("\\|", $arg))[0] : "")."\": $data";
if (defined($hash->{helper}{AVAILABLE}) and $hash->{helper}{AVAILABLE} eq 0)
{
Log3 $name, 3, "YAMAHA_AVR ($name) - device $name reappeared";
readingsSingleUpdate($hash, "presence", "present", 1);
}
$hash->{helper}{AVAILABLE} = 1;
if(not $data =~ / RC="0"/ and $data =~ / RC="(\d+)"/ and not $can_fail)
{
# if the returncode isn't 0, than the command was not successful
Log3 $name, 3, "YAMAHA_AVR ($name) - Could not execute \"$cmd".(defined($arg) ? " ".(split("\\|", $arg))[0] : "")."\": received return code $1";
}
readingsBeginUpdate($hash);
if($cmd eq "statusRequest")
{
if($arg eq "unitDescription")
{
if($data =~ /(.+?)<\/URL>/)
{
$hash->{helper}{XML} = $1;
}
else
{
$hash->{helper}{XML} = "/YamahaRemoteControl/desc.xml";
}
Log3 $name, 5, "YAMAHA_AVR ($name) - requesting unit description XML: http://".$hash->{helper}{ADDRESS}.$hash->{helper}{XML};
HttpUtils_NonblockingGet({
url => "http://".$hash->{helper}{ADDRESS}.$hash->{helper}{XML} ,
timeout => AttrVal($name, "request-timeout", 4),
noshutdown => 1,
loglevel => ($hash->{helper}{AVAILABLE} ? undef : 5),
hash => $hash,
callback => \&YAMAHA_AVR_ParseXML
}
);
}
elsif($arg eq "systemConfig")
{
if($data =~ /(.+?)<\/Model_Name>.*(.+?)<\/System_ID>.*.*(.+?)<\/Main>.*(.+?)<\/Sub>.*?<\/Version>/)
{
$hash->{MODEL} = $1;
$hash->{SYSTEM_ID} = $2;
$hash->{FIRMWARE} = $3." ".$4;
}
elsif($data =~ /(.+?)<\/Model_Name>.*(.+?)<\/System_ID>.*(.+?)<\/Version>/)
{
$hash->{MODEL} = $1;
$hash->{SYSTEM_ID} = $2;
$hash->{FIRMWARE} = $3;
}
$attr{$name}{"model"} = $hash->{MODEL};
}
elsif($arg eq "getInputs")
{
delete($hash->{helper}{INPUTS}) if(exists($hash->{helper}{INPUTS}));
while($data =~ /(.+?)<\/Param>/gc)
{
if(defined($hash->{helper}{INPUTS}) and length($hash->{helper}{INPUTS}) > 0)
{
$hash->{helper}{INPUTS} .= "|";
}
Log3 $name, 4, "YAMAHA_AVR ($name) - found input: $1";
$hash->{helper}{INPUTS} .= $1;
}
$hash->{helper}{INPUTS} = join("|", sort split("\\|", $hash->{helper}{INPUTS}));
}
elsif($arg eq "getScenes")
{
delete($hash->{helper}{SCENES}) if(exists($hash->{helper}{SCENES}));
# get all available scenes from response
while($data =~ /.*?(.+?)<\/Param>.*?(\w+)<\/RW>.*?<\/Item_\d+>/gc)
{
# check if the RW-value is "W" (means: writeable => can be set through FHEM)
if($2 eq "W")
{
if(defined($hash->{helper}{SCENES}) and length($hash->{helper}{SCENES}) > 0)
{
$hash->{helper}{SCENES} .= "|";
}
Log3 $name, 4, "YAMAHA_AVR ($name) - found scene: $1";
$hash->{helper}{SCENES} .= $1;
}
}
}
elsif($arg eq "toneStatus")
{
if(($data =~ /(.+?)<\/Val>.*?<\/Exp>.*?<\/Unit><\/Cross_Over>(.+?)<\/Val>.*?<\/Lvl><\/Bass><\/Speaker><\/Tone>/) or ($data =~ /(.+?)<\/Val>1<\/Exp>dB<\/Unit><\/Bass><\/Tone>/))
{
$hash->{helper}{SUPPORT_TONE_STATUS} = 1;
if ((exists($hash->{ACTIVE_ZONE})) && ($hash->{ACTIVE_ZONE} eq "mainzone"))
{
if($2)
{
readingsBulkUpdate($hash, "bass", int($2)/10);
readingsBulkUpdate($hash, "bassCrossover", lc($1));
}
else
{
readingsBulkUpdate($hash, "bass", int($1)/10);
}
}
else
{
readingsBulkUpdate($hash, "bass", int($1)/10);
}
}
elsif(($data =~ /(.+?)<\/Val>.*?<\/Exp>.*?<\/Unit><\/Cross_Over>(.+?)<\/Val>.*?<\/Lvl><\/Treble><\/Speaker><\/Tone>/) or ($data =~ /(.+?)<\/Val>1<\/Exp>dB<\/Unit><\/Treble><\/Tone>/))
{
$hash->{helper}{SUPPORT_TONE_STATUS} = 1;
if((exists($hash->{ACTIVE_ZONE})) && ($hash->{ACTIVE_ZONE} eq "mainzone"))
{
if($2)
{
readingsBulkUpdate($hash, "treble", int($2)/10);
readingsBulkUpdate($hash, "trebleCrossover", lc($1));
}
else
{
readingsBulkUpdate($hash, "treble", int($1)/10);
}
}
else
{
readingsBulkUpdate($hash, "treble", int($1)/10);
}
}
else
{
$hash->{helper}{SUPPORT_TONE_STATUS} = 0;
}
}
elsif($arg eq "basicStatus")
{
if($data =~ /(.+?)<\/Power>/)
{
my $power = $1;
if($power eq "Standby")
{
$power = "off";
}
readingsBulkUpdate($hash, "power", lc($power));
readingsBulkUpdate($hash, "state", lc($power));
}
# current volume and mute status
if($data =~ /(.+?)<\/Val>(.+?)<\/Exp>.+?<\/Unit><\/Lvl>(.+?)<\/Mute>.*?<\/Volume>/)
{
readingsBulkUpdate($hash, "volumeStraight", ($1 / 10 ** $2));
readingsBulkUpdate($hash, "volume", YAMAHA_AVR_volume_abs2rel(($1 / 10 ** $2)));
readingsBulkUpdate($hash, "mute", lc($3));
$hash->{helper}{USE_SHORT_VOL_CMD} = "0";
}
elsif($data =~ /(.+?)<\/Val>(.+?)<\/Exp>.+?<\/Unit><\/Lvl>(.+?)<\/Mute>.*?<\/Vol>/)
{
readingsBulkUpdate($hash, "volumeStraight", ($1 / 10 ** $2));
readingsBulkUpdate($hash, "volume", YAMAHA_AVR_volume_abs2rel(($1 / 10 ** $2)));
readingsBulkUpdate($hash, "mute", lc($3));
$hash->{helper}{USE_SHORT_VOL_CMD} = "1";
}
# (only available in zones other than mainzone) absolute or relative volume change to the mainzone
if($data =~ /.*?