# $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_ResetTimer($hash) unless($local == 1);
}
###################################
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 " : "")."sleep:off,30min,60min,90min,120min,last 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)
{
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 "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-Vx75 series use a different tag name to access the remoteControl commands
my $control_tag = (exists($hash->{MODEL}) and $hash->{MODEL} =~ /RX-V\d75/ ? "Cursor_Control" : "List_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 "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};
YAMAHA_AVR_getInputs($hash);
}
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,$blocking) = @_;
my $name = $hash->{NAME};
my $address = $hash->{helper}{ADDRESS};
if(not defined($blocking) and $cmd ne "statusRequest" and $hash->{helper}{AVAILABLE} == 1)
{
$blocking = 1;
}
else
{
$blocking = 0;
}
# 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
};
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,
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};
if(exists($param->{code}))
{
Log3 $name, 5, "YAMAHA_AVR ($name) - received HTTP code ".$param->{code}." for command \"$cmd".(defined($arg) ? " ".(split("\\|", $arg))[0] : "")."\"";
}
if($err ne "")
{
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"/)
{
# 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] : "")."\"";
}
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 "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 =~ /.*?