# $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 Encode qw(decode encode);
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 ".
"disabledForIntervals ".
"requestTimeout:1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20 ".
"model ".
"volumeSteps:1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20 ".
"volumeMax ".
"volumeSmoothChange:0,1 ".
"volumeSmoothSteps:1,2,3,4,5,6,7,8,9,10 ".
$readingFnAttributes;
$hash->{AttrRenameMap} = { "request-timeout" => "requestTimeout",
"volume-smooth-change" => "volumeSmoothChange",
"volume-smooth-steps" => "volumeSmoothSteps"
};
}
#############################
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};
}
$hash->{helper}{CMD_QUEUE} = [];
delete($hash->{helper}{".HTTP_CONNECTION"}) if(exists($hash->{helper}{".HTTP_CONNECTION"}));
# 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);
}
unless(exists($hash->{helper}{AVAILABLE}) and ($hash->{helper}{AVAILABLE} == 0))
{
$hash->{helper}{AVAILABLE} = 1;
readingsSingleUpdate($hash, "presence", "present", 1);
}
# start the status update timer
YAMAHA_AVR_ResetTimer($hash,1);
return undef;
}
###################################
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);
}
# 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");
if($hash->{ACTIVE_ZONE} eq "mainzone" and (!exists($hash->{helper}{SUPPORT_PARTY_MODE}) or $hash->{helper}{SUPPORT_PARTY_MODE}))
{
YAMAHA_AVR_SendCommand($hash, "GetParam", "statusRequest", "partyModeStatus", {options => {can_fail => 1}});
}
elsif($hash->{ACTIVE_ZONE} ne "mainzone" and (!exists($hash->{helper}{SUPPORT_PARTY_MODE}) or $hash->{helper}{SUPPORT_PARTY_MODE}))
{
YAMAHA_AVR_SendCommand($hash, "GetParam", "statusRequest", "partyModeZones", {options => {can_fail => 1}});
}
if($hash->{ACTIVE_ZONE} eq "mainzone" and (!exists($hash->{helper}{SUPPORT_SURROUND_DECODER}) or $hash->{helper}{SUPPORT_SURROUND_DECODER}))
{
YAMAHA_AVR_SendCommand($hash, "<$zone>GetParam$zone>", "statusRequest", "surroundDecoder", {options => {can_fail => 1}});
}
if($hash->{ACTIVE_ZONE} eq "mainzone" and (!exists($hash->{helper}{SUPPORT_DISPLAY_BRIGHTNESS}) or $hash->{helper}{SUPPORT_DISPLAY_BRIGHTNESS}))
{
if(YAMAHA_AVR_isModel_DSP($hash))
{
YAMAHA_AVR_SendCommand($hash, "GetParam", "statusRequest", "displayBrightness", {options => {can_fail => 1}});
}
else
{
YAMAHA_AVR_SendCommand($hash, "GetParam", "statusRequest", "displayBrightness", {options => {can_fail => 1}});
}
}
if(!exists($hash->{helper}{SUPPORT_TONE_STATUS}) or (exists($hash->{helper}{SUPPORT_TONE_STATUS}) and exists($hash->{MODEL}) 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", {options => {can_fail => 1}});
YAMAHA_AVR_SendCommand($hash, "<$zone>GetParam$zone>", "statusRequest", "toneStatus", {options => {can_fail => 1}});
}
else
{
YAMAHA_AVR_SendCommand($hash, "<$zone>GetParam$zone>", "statusRequest", "toneStatus", {options => {can_fail => 1}});
YAMAHA_AVR_SendCommand($hash, "<$zone>GetParam$zone>", "statusRequest", "toneStatus", {options => {can_fail => 1}});
}
}
else
{
YAMAHA_AVR_SendCommand($hash, "<$zone>GetParam$zone>", "statusRequest", "toneStatus", {options => {can_fail => 1}});
YAMAHA_AVR_SendCommand($hash, "<$zone>GetParam$zone>", "statusRequest", "toneStatus", {options => {can_fail => 1}});
}
}
# check for FW update
if(defined($hash->{MODEL}))
{
if($hash->{MODEL} =~ /^RX-(?:A\d{1,2}10|V\d{1,2}71)$/) # RX-Vx71 / RX-Ax10 have different firmware status request
{
YAMAHA_AVR_SendCommand($hash, "GetParam", "statusRequest", "fwUpdate", {options => {can_fail => 1}});
}
elsif($hash->{MODEL} =~ /^RX-(?:A\d{1,2}20|V\d{1,2}73)$/) # RX-Vx73 / RX-Ax20 have different firmware status request
{
YAMAHA_AVR_SendCommand($hash, "GetParam", "statusRequest", "fwUpdate", {options => {can_fail => 1}});
}
}
# check hdmi output state, if supported
if($hash->{ACTIVE_ZONE} eq "mainzone" and $hash->{helper}{SUPPORT_HDMI_OUT})
{
YAMAHA_AVR_SendCommand($hash, "", "statusRequest", "hdmiOut1", {options => {can_fail => 1}});
YAMAHA_AVR_SendCommand($hash, "", "statusRequest", "hdmiOut2", {options => {can_fail => 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];
return ReadingsVal($hash->{NAME}, $what, "") if(defined(ReadingsVal($hash->{NAME}, $what, undef)));
$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) : "" ;
my $decoders_piped = defined($hash->{helper}{SURROUND_DECODERS}) ? YAMAHA_AVR_Param2Fhem(lc($hash->{helper}{SURROUND_DECODERS}), 0) : "" ;
my $decoders_comma = defined($hash->{helper}{SURROUND_DECODERS}) ? YAMAHA_AVR_Param2Fhem(lc($hash->{helper}{SURROUND_DECODERS}), 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 ".
(defined(ReadingsVal($name, "volume", undef)) ? "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}{SURROUND_DECODERS}) ? "surroundDecoder:".$decoders_comma." " : "").
($hash->{helper}{SUPPORT_DISPLAY_BRIGHTNESS} ? "displayBrightness:slider,-4,1,0 " : "").
(exists($hash->{helper}{DSP_MODES}) ? "dsp:".$dsp_modes_comma." " : "").
"enhancer:on,off ".
($hash->{helper}{SUPPORT_HDMI_OUT} ? "hdmiOut1:on,off hdmiOut2:on,off " : "")
:"").
(exists($hash->{helper}{CURRENT_INPUT_TAG}) ?
"navigateListMenu play:noArg pause:noArg stop:noArg skip:reverse,forward ".
"preset:1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40 ".
"presetUp:noArg presetDown:noArg ".
(($hash->{helper}{SUPPORT_SHUFFLE_REPEAT}) ? "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 " : "").
($hash->{helper}{SUPPORT_PARTY_MODE} ? "partyMode:on,off " : "").
($hash->{helper}{SUPPORT_EXTRA_BASS} ? "extraBass:off,auto " : "").
($hash->{helper}{SUPPORT_YPAO_VOLUME} ? "ypaoVolume:off,auto " : "").
($hash->{helper}{SUPPORT_DAB} ? "tunerFrequencyBand:FM,DAB " : "").
"tunerFrequency ".
"displayBrightness:slider,-4,1,0 ".
"statusRequest:noArg";
# number of seconds to wait after on/off was executed (DSP based: 3 sec, other models: 2 sec)
my $powerCmdDelay = (YAMAHA_AVR_isModel_DSP($hash) ? "3" : "2");
Log3 $name, 5, "YAMAHA_AVR ($name) - set ".join(" ", @a);
if($what eq "on")
{
YAMAHA_AVR_SendCommand($hash, "<$zone>On$zone>" ,$what, undef, {options => {wait_after_response => $powerCmdDelay}});
}
elsif($what eq "off")
{
YAMAHA_AVR_SendCommand($hash, "<$zone>Standby$zone>", $what, undef,{options => {wait_after_response => $powerCmdDelay}});
}
elsif($what eq "input")
{
if(defined($a[2]))
{
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]);
YAMAHA_AVR_SendCommand($hash, "<$zone>GetParam$zone>", "statusRequest", "basicStatus", {options => {no_playinfo => 1}});
}
else
{
return "invalid input: ".$a[2];
}
}
else
{
return $usage;
}
}
else
{
return "No inputs are avaible. Please try an statusUpdate.";
}
}
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" and defined($a[2]))
{
# Depending on the status response, use the short or long Volume command
my $volume_cmd = (YAMAHA_AVR_isModel_DSP($hash) ? "Vol" : "Volume");
if( $a[2] eq "on" or ($a[2] eq "toggle" and ReadingsVal($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($name, "mute", "off") eq "on"))
{
YAMAHA_AVR_SendCommand($hash, "<$zone><$volume_cmd>Off$volume_cmd>$zone>", $what, "off");
}
else
{
return $usage;
}
}
elsif($what =~ /^(volumeStraight|volume|volumeUp|volumeDown)$/)
{
my $target_volume;
if($what eq "volume" and defined($a[2]) and $a[2] =~ /^\d{1,3}(?:\.\d+)?$/ and $a[2] >= 0 && $a[2] <= 100)
{
$target_volume = YAMAHA_AVR_volume_rel2abs(int($a[2]));
}
elsif($what eq "volumeDown" and defined(ReadingsVal($name, "volume", undef)))
{
$target_volume = YAMAHA_AVR_volume_rel2abs(ReadingsVal($name, "volume", -45) - ((defined($a[2]) and $a[2] =~ /^\d+(?:\.\d+)?$/) ? int($a[2]) : AttrVal($hash->{NAME}, "volumeSteps",5)));
}
elsif($what eq "volumeUp" and defined(ReadingsVal($name, "volume", undef)))
{
$target_volume = YAMAHA_AVR_volume_rel2abs(ReadingsVal($name, "volume", -45) + ((defined($a[2]) and $a[2] =~ /^\d+(?:\.\d+)?$/) ? int($a[2]) : AttrVal($hash->{NAME}, "volumeSteps",5)));
}
elsif($what eq "volumeStraight" and defined($a[2]) and $a[2] =~ /^-?\d+(?:\.\d+)?$/)
{
$target_volume = $a[2];
}
else
{
return $usage;
}
if($target_volume > YAMAHA_AVR_volume_rel2abs(AttrVal($name, "volumeMax","100")))
{
$target_volume = YAMAHA_AVR_volume_rel2abs(AttrVal($name, "volumeMax","100"));
}
# 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);
# ensure $target_volume mod 0.5 == 0
$target_volume = int($target_volume / 0.5) * 0.5;
Log3 $name, 4, "YAMAHA_AVR ($name) - new target volume: $target_volume";
if(defined($target_volume))
{
# DSP based models use "Vol" instead of "Volume"
my $volume_cmd = (YAMAHA_AVR_isModel_DSP($hash) ? "Vol" : "Volume");
if(AttrVal($name, "volumeSmoothChange", "1") eq "1")
{
my $steps = AttrVal($name, "volumeSmoothSteps", 5);
my $diff = int(($target_volume - ReadingsVal($name, "volumeStraight", $target_volume)) / $steps / 0.5) * 0.5;
my $current_volume = ReadingsVal($name, "volumeStraight", undef);
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($current_volume) 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), {options => {volume_diff => $diff, volume_target => $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, {options => {volume_diff => $diff, volume_target => $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, {options => {volume_diff => 0, volume_target => $target_volume}});
}
}
}
elsif($what eq "bass" and defined($a[2]))
{
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,"bassCrossover","125") . "0Hz" . $bassVal*10 . "1dB$zone>", $what, $bassVal);
}
else
{
YAMAHA_AVR_SendCommand($hash, "<$zone>" . $bassVal*10 . "1dB$zone>", $what, $bassVal);
}
}
else
{
$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
{
# step range is 2 dB for non DSP based models. add/subtract 1 if modulus 2 != 0
$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);
}
}
}
elsif($what eq "treble" and defined($a[2]))
{
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,"trebleCrossover","35") . "1kHz" . $trebleVal*10 . "1dB$zone>", $what, $trebleVal);
}
else
{
YAMAHA_AVR_SendCommand($hash, "<$zone>" . $trebleVal*10 . "1dB$zone>", $what, $trebleVal);
}
}
else
{
$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
{
# step range is 2 dB for non DSP based models. add/subtract 1 if modulus 2 != 0
$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);
}
}
}
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)
{
if(YAMAHA_AVR_isModel_DSP($hash))
{
if($hash->{MODEL} eq "RX-V2065")
{
YAMAHA_AVR_SendCommand($hash, "<$zone>$command$zone>", $what, $a[2]);
}
else
{
YAMAHA_AVR_SendCommand($hash, "<$zone>$command$zone>", $what, $a[2]);
}
}
else
{
my $straight_command = ((defined($hash->{MODEL}) && $hash->{MODEL} =~ /^RX-(?:A\d{1,2}00|V\d{1,2}67)$/) ? "Off" : "");
YAMAHA_AVR_SendCommand($hash, "<$zone>$straight_command$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" and defined($a[2]))
{
if($a[2] eq "on")
{
if(YAMAHA_AVR_isModel_DSP($hash))
{
YAMAHA_AVR_SendCommand($hash, "<$zone>On$zone>", $what, $a[2]);
}
else
{
YAMAHA_AVR_SendCommand($hash, "<$zone>On$zone>", $what, $a[2]);
}
}
elsif($a[2] eq "off")
{
if(YAMAHA_AVR_isModel_DSP($hash))
{
YAMAHA_AVR_SendCommand($hash, "<$zone>Off$zone>", $what, $a[2]);
}
else
{
YAMAHA_AVR_SendCommand($hash, "<$zone>Off$zone>", $what, $a[2]);
}
}
else
{
return $usage;
}
}
elsif($what eq "3dCinemaDsp" and defined($a[2]))
{
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" and defined($a[2]))
{
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" and defined($a[2]))
{
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" and defined($a[2]))
{
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" and defined($a[2]))
{
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" and defined($a[2]))
{
# 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")
{
if($hash->{helper}{SUPPORT_DAB})
{
if(ReadingsVal($name, "tunerFrequencyBand", "FM") eq "DAB")
{
YAMAHA_AVR_SendCommand($hash,"Up", $what, $a[2]);
}
else
{
YAMAHA_AVR_SendCommand($hash,"Up", $what, $a[2]);
}
}
else
{
YAMAHA_AVR_SendCommand($hash,"Up", $what, $a[2]);
}
}
elsif($a[2] eq "tunerPresetDown")
{
if($hash->{helper}{SUPPORT_DAB})
{
if(ReadingsVal($name, "tunerFrequencyBand", "FM") eq "DAB")
{
YAMAHA_AVR_SendCommand($hash,"Down", $what, $a[2]);
}
else
{
YAMAHA_AVR_SendCommand($hash,"Down", $what, $a[2]);
}
}
else
{
YAMAHA_AVR_SendCommand($hash,"Down", $what, $a[2]);
}
}
else
{
return $usage;
}
}
elsif($what eq "play")
{
YAMAHA_AVR_SendCommand($hash,"<[CURRENT_INPUT_TAG]>Play[CURRENT_INPUT_TAG]>", $what, $a[2]);
}
elsif($what eq "stop")
{
YAMAHA_AVR_SendCommand($hash,"<[CURRENT_INPUT_TAG]>Stop[CURRENT_INPUT_TAG]>", $what, $a[2]);
}
elsif($what eq "pause")
{
YAMAHA_AVR_SendCommand($hash,"<[CURRENT_INPUT_TAG]>Pause[CURRENT_INPUT_TAG]>", $what, $a[2]);
}
elsif($what eq "navigateListMenu")
{
YAMAHA_AVR_SendCommand($hash, "<$zone>GetParam$zone>", "statusRequest", "basicStatus", {options => {no_playinfo => 1}});
YAMAHA_AVR_SendCommand($hash, "<[CURRENT_INPUT_TAG]>GetParam[CURRENT_INPUT_TAG]>", $what, join(" ", @a[2..$#a]), {options => {init => 1}});
}
elsif($what eq "preset" and $a[2] =~ /^\d+$/ and $a[2] >= 1 and $a[2] <= 40)
{
YAMAHA_AVR_SendCommand($hash, "<[CURRENT_INPUT_TAG]>".$a[2]."[CURRENT_INPUT_TAG]>", $what, $a[2], {options => {can_fail => 1, preset => $a[2]}});
}
elsif($what eq "presetUp")
{
YAMAHA_AVR_SendCommand($hash, "<[CURRENT_INPUT_TAG]>Up[CURRENT_INPUT_TAG]>", $what, $a[2], {options => {can_fail => 1, preset => "Up"}});
}
elsif($what eq "presetDown")
{
YAMAHA_AVR_SendCommand($hash, "<[CURRENT_INPUT_TAG]>Down[CURRENT_INPUT_TAG]>", $what, $a[2], {options => {can_fail => 1, preset => "Down"}});
}
elsif($what eq "skip" and defined($a[2]))
{
if($a[2] eq "forward")
{
YAMAHA_AVR_SendCommand($hash,"<[CURRENT_INPUT_TAG]>Skip Fwd[CURRENT_INPUT_TAG]>", $what, $a[2]);
}
elsif($a[2] eq "reverse")
{
YAMAHA_AVR_SendCommand($hash,"<[CURRENT_INPUT_TAG]>Skip Rev[CURRENT_INPUT_TAG]>", $what, $a[2]);
}
else
{
return $usage;
}
}
elsif($what eq "shuffle" and defined($a[2]))
{
if($a[2] eq "on")
{
YAMAHA_AVR_SendCommand($hash,"<[CURRENT_INPUT_TAG]>On[CURRENT_INPUT_TAG]>", $what, $a[2]);
}
elsif($a[2] eq "off")
{
YAMAHA_AVR_SendCommand($hash,"<[CURRENT_INPUT_TAG]>Off[CURRENT_INPUT_TAG]>", $what, $a[2]);
}
else
{
return $usage;
}
}
elsif($what eq "repeat" and defined($a[2]))
{
if($a[2] eq "one")
{
YAMAHA_AVR_SendCommand($hash,"<[CURRENT_INPUT_TAG]>One[CURRENT_INPUT_TAG]>", $what, $a[2]);
}
elsif($a[2] eq "off")
{
YAMAHA_AVR_SendCommand($hash,"<[CURRENT_INPUT_TAG]>Off[CURRENT_INPUT_TAG]>", $what, $a[2]);
}
elsif($a[2] eq "all")
{
YAMAHA_AVR_SendCommand($hash,"<[CURRENT_INPUT_TAG]>All[CURRENT_INPUT_TAG]>", $what, $a[2]);
}
else
{
return $usage;
}
}
elsif($what eq "partyMode" and defined($a[2]))
{
if($hash->{helper}{SUPPORT_PARTY_MODE} and $hash->{ACTIVE_ZONE} eq "mainzone")
{
if($a[2] eq "on")
{
YAMAHA_AVR_SendCommand($hash,"On", $what, $a[2]);
}
elsif($a[2] eq "off")
{
YAMAHA_AVR_SendCommand($hash,"Off", $what, $a[2]);
}
}
elsif($hash->{helper}{SUPPORT_PARTY_MODE} and $hash->{ACTIVE_ZONE} ne "mainzone")
{
if($a[2] eq "on")
{
YAMAHA_AVR_SendCommand($hash,"<$zone>Enable$zone>", $what, $a[2]);
}
elsif($a[2] eq "off")
{
YAMAHA_AVR_SendCommand($hash,"<$zone>Disable$zone>", $what, $a[2]);
}
}
}
elsif($what eq "tunerFrequency" and defined($a[2]))
{
if($a[2] =~ /^\d+(?:(?:\.|,)\d{1,2})?$/)
{
$a[2] =~ s/,/./;
if((defined($a[3]) and $a[3] eq "AM" )) # AM Band
{
if($hash->{helper}{SUPPORT_DAB})
{
return "no AM band supported by DAB based models";
}
else
{
YAMAHA_AVR_SendCommand($hash,"AM".$a[2]."0kHz", $what, $a[2], {options => {can_fail => 1}});
}
}
else # FM Band
{
if($hash->{helper}{SUPPORT_DAB})
{
YAMAHA_AVR_SendCommand($hash,"".($a[2] * 100)."2MHz", $what, $a[2], {options => {can_fail => 1}});
}
else
{
YAMAHA_AVR_SendCommand($hash,"FM".($a[2] * 100)."2MHz", $what, $a[2], {options => {can_fail => 1}});
}
}
}
else
{
return "invalid tuner frequency value: ".$a[2];
}
}
elsif($what eq "tunerFrequencyBand" and defined($a[2]))
{
if($a[2] eq "FM")
{
YAMAHA_AVR_SendCommand($hash,"FM", $what, $a[2]);
}
elsif($a[2] eq "DAB")
{
YAMAHA_AVR_SendCommand($hash,"DAB", $what, $a[2]);
}
else
{
return $usage;
}
}
elsif($what eq "surroundDecoder")
{
if(defined($a[2]))
{
if(not $decoders_piped eq "")
{
if($a[2] =~ /^($decoders_piped)$/)
{
my $command = YAMAHA_AVR_getParamName($hash, $a[2],$hash->{helper}{SURROUND_DECODERS});
if(defined($command) and length($command) > 0)
{
YAMAHA_AVR_SendCommand($hash, "<$zone>$command$zone>", $what, $a[2]);
}
else
{
return "invalid surround decoder: ".$a[2];
}
}
else
{
return $usage;
}
}
else
{
return "No surround decoders are avaible. Please try an statusUpdate.";
}
}
else
{
return (($decoders_piped eq "") ? "No surround decoders are available. Please try an statusUpdate." : "No surround decoder was given");
}
}
elsif($what eq "displayBrightness")
{
if($a[2] =~ /^-?\d+$/ and $a[2] >= -4 and $a[2] <= 0)
{
if(YAMAHA_AVR_isModel_DSP($hash))
{
YAMAHA_AVR_SendCommand($hash,"".$a[2]."0", $what, $a[2]);
}
else
{
YAMAHA_AVR_SendCommand($hash,"".$a[2]."", $what, $a[2]);
}
}
else
{
return "invalid tuner frequency value: ".$a[2];
}
}
elsif($what eq "ypaoVolume" and defined($a[2]))
{
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 "extraBass" and defined($a[2]))
{
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 "hdmiOut1" and defined($a[2]))
{
if($a[2] eq "on")
{
YAMAHA_AVR_SendCommand($hash, "", $what, $a[2]);
}
elsif($a[2] eq "off")
{
YAMAHA_AVR_SendCommand($hash, "", $what, $a[2]);
}
else
{
return $usage;
}
}
elsif($what eq "hdmiOut2" and defined($a[2]))
{
if($a[2] eq "on")
{
YAMAHA_AVR_SendCommand($hash, "", $what, $a[2]);
}
elsif($a[2] eq "off")
{
YAMAHA_AVR_SendCommand($hash, "", $what, $a[2]);
}
else
{
return $usage;
}
}
elsif($what eq "statusRequest")
{
YAMAHA_AVR_GetStatus($hash, 1);
}
else
{
return $usage;
}
}
##########################
sub
YAMAHA_AVR_Attr(@)
{
my ($cmd, $name, $attr, $val) = @_;
my $hash = $defs{$name};
return unless($hash);
if($attr eq "disable")
{
# Start/Stop Timer according to new disabled-Value
YAMAHA_AVR_ResetTimer($hash, 1);
}
if($cmd eq "set")
{
if($attr =~ /^(?:volumeMax|volumeSteps)$/)
{
if($val !~ /^\d+$/)
{
return "invalid attribute value for attribute $attr: $val";
}
if($attr eq "volumeMax" and ($val < 0 or $val > 100))
{
return "value is out of range (0-100) for attribute $attr: $val";
}
if($attr eq "volumeSteps" and ($val < 1))
{
return "value is out of range (1-*) for attribute $attr: $val";
}
}
}
return undef;
}
#############################
sub
YAMAHA_AVR_Undefine($$)
{
my($hash, $name) = @_;
# Stop all timers and exit
RemoveInternalTimer($hash);
if(exists($hash->{SYSTEM_ID}) and exists($hash->{ACTIVE_ZONE}) and exists($modules{YAMAHA_AVR}{defptr}{$hash->{SYSTEM_ID}}{$hash->{ACTIVE_ZONE}}))
{
delete($modules{YAMAHA_AVR}{defptr}{$hash->{SYSTEM_ID}}{$hash->{ACTIVE_ZONE}});
}
return undef;
}
############################################################################################################
#
# Begin of helper functions
#
############################################################################################################
#
# Structure of a request hash
# ===========================
#
# {
# data => XML data to send without prefix
# cmd => name of the command which is related to the request
# arg => optional argument related to the command and request
# original_hash => $hash of the originating definition. must be set, if a zone definition sends a request via the mainzones command queue.
# options => optional values, see following list of possibilities.
# }
#
# following option values can be used to control the execution of the command:
#
# {
# unless_in_queue => don't insert the command if an equivalent command already exists in the queue. (flag: 0,1 - default: 0)
# priority => integer value of priority. lower values will be executed before higher values in the appropriate order. (integer value - default value: 3)
# at_first => insert the command at the beginning of the queue, not at the end. (flag: 0,1 - default: 0)
# not_before => don't execute the command before the given Unix timestamp is reached (integer/float value)
# wait_after_response => wait the given number of seconds before processing further queue commands (integer/float value)
# can_fail => the request can return an error. If this flag is set, don't treat this as an communication error, ignore it instead. (flag: 0,1 - default: 0)
# no_playinfo => (only relevant for "statusRequest basicStatus") - don't retrieve extended playback information, after receiving a successful response (flag: 0,1 - default: 0)
# init => (only relevant for navigateListMenu) - marks the initial request to obtain the current menu level (flag: 0,1 - default: 0)
# last_layer => (only relevant for navigateListMenu) - the menu layer that was reached within the last request (integer value)
# item_selected => (only relevant for navigateListMenu) - is set, when the final item is going to be selected with the current request. (flag: 0,1 - default: 0)
# volume_target => (only relevant for volume) - the target volume, that should be reached by smoothing. (float value)
# volume_diff => (only relevant for volume) - the volume difference between each step to reach the target volume (float value)
# input_tag => (only relevant for "statusRequest playInfo") - contains the input tag name when requesting playInfo
# preset => (only relevant for preset/presetUp/presetDown) - contains the value for the preset to select (Up/Down/). If set and the receiver model contains DAB radio, the command will be changed to DAB specific.
# }
#
#############################
# sends a command to the receiver via HTTP
sub
YAMAHA_AVR_SendCommand($$$$;$)
{
my ($hash, $data,$cmd,$arg,$additional_args) = @_;
my $name = $hash->{NAME};
my $options;
$data = "".$data if($data);
# In case any URL changes must be made, this part is separated in this function".
my $param = {
data => $data,
cmd => $cmd,
arg => $arg
};
map {$param->{$_} = $additional_args->{$_}} keys %{$additional_args};
$options = $additional_args->{options} if(exists($additional_args->{options}));
my $device = $hash;
# if device is not mainzone and mainzone is defined via defptr
if(exists($hash->{SYSTEM_ID}) and exists($hash->{ACTIVE_ZONE}) and $hash->{ACTIVE_ZONE} ne "mainzone" and exists($modules{YAMAHA_AVR}{defptr}{$hash->{SYSTEM_ID}}{mainzone}))
{
$hash->{MAIN_ZONE} = $modules{YAMAHA_AVR}{defptr}{$hash->{SYSTEM_ID}}{mainzone}->{NAME};
# DSP based models only: use the http queue from mainzone to execute command
if(YAMAHA_AVR_isModel_DSP($hash))
{
$device = $modules{YAMAHA_AVR}{defptr}{$hash->{SYSTEM_ID}}{mainzone};
$param->{original_hash} = $hash;
}
}
else
{
delete($hash->{MAIN_ZONE}) if(exists($hash->{MAIN_ZONE}));
}
if($options->{unless_in_queue} and grep( ($_->{cmd} eq $cmd and ( (not(defined($arg) or defined($_->{arg}))) or $_->{arg} eq $arg)) ,@{$device->{helper}{CMD_QUEUE}}))
{
Log3 $name, 4, "YAMAHA_AVR ($name) - comand \"$cmd".(defined($arg) ? " ".$arg : "")."\" is already in queue, skip adding another one";
}
else
{
Log3 $name, 4, "YAMAHA_AVR ($name) - append to queue ".($options->{at_first} ? "(at first) ":"")."of device ".$device->{NAME}." \"$cmd".(defined($arg) ? " ".$arg : "")."\": $data";
if($options->{at_first})
{
unshift @{$device->{helper}{CMD_QUEUE}}, $param;
}
else
{
push @{$device->{helper}{CMD_QUEUE}}, $param;
}
}
YAMAHA_AVR_HandleCmdQueue($device);
return undef;
}
#############################
# starts http requests from cmd queue
sub
YAMAHA_AVR_HandleCmdQueue($)
{
my ($hash) = @_;
my $name = $hash->{NAME};
my $address = $hash->{helper}{ADDRESS};
if(not($hash->{helper}{RUNNING_REQUEST}) and @{$hash->{helper}{CMD_QUEUE}})
{
Log3 $name, 5, "YAMAHA_AVR ($name) - no commands currently running, but queue has pending commands. preparing new request";
my $params = {
url => "http://".$address."/YamahaRemoteControl/ctrl",
timeout => AttrVal($name, "requestTimeout", 10),
noshutdown => 1,
keepalive => 0,
httpversion => "1.1",
loglevel => ($hash->{helper}{AVAILABLE} ? undef : 5),
hash => $hash,
callback => \&YAMAHA_AVR_ParseResponse
};
my $request = YAMAHA_AVR_getNextRequestHash($hash);
unless(defined($request))
{
# still request in queue, but not mentioned to be executed now
Log3 $name, 5, "YAMAHA_AVR ($name) - still requests in queue, but no command shall be executed at the moment. Retry in 1 second.";
RemoveInternalTimer($hash, "YAMAHA_AVR_HandleCmdQueue");
InternalTimer(gettimeofday()+1,"YAMAHA_AVR_HandleCmdQueue", $hash);
return undef;
}
$request->{options}{priority} = 3 unless(exists($request->{options}{priority}));
delete($request->{data}) if(exists($request->{data}) and !$request->{data});
$request->{data}=~ s/\[CURRENT_INPUT_TAG\]/$hash->{helper}{CURRENT_INPUT_TAG}/g if(exists($request->{data}) and exists($hash->{helper}{CURRENT_INPUT_TAG}));
if($request->{options}{preset} and $hash->{helper}{SUPPORT_DAB} and $hash->{helper}{CURRENT_INPUT_TAG} eq "DAB")
{
if(ReadingsVal($name, "tunerFrequencyBand","") eq "DAB")
{
$request->{data} = "".$request->{options}{preset}."";
}
elsif(ReadingsVal($name, "tunerFrequencyBand","") eq "FM")
{
$request->{data} = "".$request->{options}{preset}."";
}
}
map {$hash->{helper}{".HTTP_CONNECTION"}{$_} = $params->{$_}} keys %{$params};
map {$hash->{helper}{".HTTP_CONNECTION"}{$_} = $request->{$_}} keys %{$request};
$hash->{helper}{RUNNING_REQUEST} = 1;
Log3 $name, 4, "YAMAHA_AVR ($name) - send command \"$request->{cmd}".(defined($request->{arg}) ? " ".$request->{arg} : "")."\"".(exists($request->{data}) ? ": ".$request->{data} : "");
HttpUtils_NonblockingGet($hash->{helper}{".HTTP_CONNECTION"});
}
$hash->{CMDs_pending} = @{$hash->{helper}{CMD_QUEUE}};
delete($hash->{CMDs_pending}) unless($hash->{CMDs_pending});
return undef;
}
#############################
# selects the next command from command queue that has to be executed (undef if no command has to be executed now)
sub YAMAHA_AVR_getNextRequestHash($)
{
my ($hash) = @_;
my $name = $hash->{NAME};
if(@{$hash->{helper}{CMD_QUEUE}})
{
my $last = $#{$hash->{helper}{CMD_QUEUE}};
my $next_item;
my $next_item_prio;
for my $item (0 .. $last)
{
my $param = $hash->{helper}{CMD_QUEUE}[$item];
if(defined($param))
{
my $cmd = (defined($param->{cmd}) ? $param->{cmd} : "");
my $arg = (defined($param->{arg}) ? $param->{arg} : "");
my $data = (defined($param->{data}) ? "1" : "0");
my $options = $param->{options};
my $opt_not_before = (exists($options->{not_before}) ? sprintf("%.2fs", ($options->{not_before} - gettimeofday())): "0");
my $opt_priority = (exists($options->{priority}) ? $options->{priority} : "-");
my $opt_at_first = (exists($options->{at_first}) ? $options->{at_first} : "0");
Log3 $name, 5, "YAMAHA_AVR ($name) - checking cmd queue item: $item (cmd: $cmd, arg: $arg, data: $data, priority: $opt_priority, at_first: $opt_at_first, not_before: $opt_not_before)";
if(exists($param->{data}))
{
if(defined($next_item) and ((defined($next_item_prio) and exists($options->{priority}) and $options->{priority} < $next_item_prio) or (defined($options->{priority}) and not defined($next_item_prio))))
{
# choose actual item if priority of previous selected item is higher or not set
$next_item = $item;
$next_item_prio = $options->{priority};
}
unless((exists($options->{not_before}) and $options->{not_before} > gettimeofday()) or (defined($next_item)))
{
$next_item = $item;
$next_item_prio = $options->{priority};
}
}
else # dummy command to delay the execution of further commands in queue
{
if(exists($options->{not_before}) and $options->{not_before} <= gettimeofday() and not(defined($next_item)))
{
# if not_before timestamp of dummy item is reached, delete it and continue processing for next command
Log3 $name, 5, "YAMAHA_AVR ($name) - item $item is a dummy cmd item with 'not_before' set which is already expired, delete it and recheck index $item again";
splice(@{$hash->{helper}{CMD_QUEUE}}, $item, 1);
redo;
}
elsif(exists($options->{not_before}) and not(defined($next_item) and defined($next_item_prio)))
{
Log3 $name, 5, "YAMAHA_AVR ($name) - we have to wait ".sprintf("%.2fs", ($options->{not_before} - gettimeofday()))." seconds before next item can be checked";
last;
}
}
}
}
if(defined($next_item))
{
if(exists($hash->{helper}{CMD_QUEUE}[$next_item]{options}{not_before}))
{
delete($hash->{helper}{CMD_QUEUE}[$next_item]{options}{not_before});
}
my $return = $hash->{helper}{CMD_QUEUE}[$next_item];
splice(@{$hash->{helper}{CMD_QUEUE}}, $next_item, 1);
$hash->{helper}{CMD_QUEUE} = () unless(defined($hash->{helper}{CMD_QUEUE}));
Log3 $name, 5, "YAMAHA_AVR ($name) - choosed item $next_item as next command";
return $return;
}
Log3 $name, 5, "YAMAHA_AVR ($name) - no suitable command item found";
return undef;
}
}
#############################
# parses the receiver response
sub
YAMAHA_AVR_ParseResponse($$$)
{
my ( $param, $err, $data ) = @_;
my $hash = $param->{hash};
my $queue_hash = $param->{hash};
my $cmd = $param->{cmd};
my $arg = $param->{arg};
my $options = $param->{options};
$data = "" unless(defined($data));
$err = "" unless(defined($err));
$hash->{helper}{RUNNING_REQUEST} = 0;
delete($hash->{helper}{".HTTP_CONNECTION"}) unless($param->{keepalive});
# if request is from an other definition (zone2, zone3, ...)
$hash = $param->{original_hash} if(exists($param->{original_hash}));
my $name = $hash->{NAME};
my $zone = YAMAHA_AVR_getParamName($hash, $hash->{ACTIVE_ZONE}, $hash->{helper}{ZONES});
if(exists($param->{code}))
{
Log3 $name, 4, "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")
{
$hash->{helper}{SUPPORT_SHUFFLE_REPEAT} = 0;
}
elsif($arg eq "toneStatus")
{
$hash->{helper}{SUPPORT_TONE_STATUS} = 0;
}
elsif($arg eq "partyModeStatus" or $arg eq "partyModeZones")
{
$hash->{helper}{SUPPORT_PARTY_MODE} = 0;
}
elsif($arg eq "surroundDecoder")
{
$hash->{helper}{SUPPORT_SURROUND_DECODER} = 0;
}
elsif($arg eq "displayBrightness")
{
$hash->{helper}{SUPPORT_DISPLAY_BRIGHTNESS} = 0;
}
elsif($arg eq "hdmiOut1" or $arg eq "hdmiOut2")
{
$hash->{helper}{SUPPORT_HDMI_OUT} = 0;
}
}
}
if($err ne "" and not $options->{can_fail})
{
Log3 $name, 4, "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;
}
if($data ne "")
{
Log3 $name, 4, "YAMAHA_AVR ($name) - got response for \"$cmd".(defined($arg) ? " ".(split("\\|", $arg))[0] : "")."\": $data";
# add a dummy queue entry to wait a specific time before next command starts
if($options->{wait_after_response})
{
Log3 $name, 5, "YAMAHA_AVR ($name) - next command for device ".$queue_hash->{NAME}." has to wait at least ".$options->{wait_after_response}." seconds before execution";
unshift @{$queue_hash->{helper}{CMD_QUEUE}}, {options=> { priority => 1, not_before => (gettimeofday()+$options->{wait_after_response})} };
}
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);
YAMAHA_AVR_SendCommand($hash, "<$zone>GetParam$zone>", "statusRequest", "basicStatus", {options => {at_first => 1}}) if(defined($zone));
}
$hash->{helper}{AVAILABLE} = 1;
if(not $data =~ / RC="0"/ and $data =~ / RC="(\d+)"/ and not $options->{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};
YAMAHA_AVR_SendCommand($hash,0,"statusRequest","retrieveDescXML", {
url => "http://".$hash->{helper}{ADDRESS}.$hash->{helper}{XML} ,
callback => \&YAMAHA_AVR_ParseXML,
options => {priority => 2}
});
}
elsif($arg eq "systemConfig")
{
if($data =~ /(.+?)<\/Model_Name>.*?(.+?)<\/System_ID>.*?.*?(.+?)<\/Main>.*?(.+?)<\/Sub>.*?<\/Version>/) # DSP based models
{
$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 "partyModeStatus")
{
if($hash->{ACTIVE_ZONE} eq "mainzone" and $data =~ /(.+?)<\/Mode>/)
{
$hash->{helper}{SUPPORT_PARTY_MODE} = 1;
readingsBulkUpdate($hash, "partyMode", lc($1));
}
else
{
$hash->{helper}{SUPPORT_PARTY_MODE} = 0;
}
}
elsif($arg eq "partyModeZones")
{
if($hash->{ACTIVE_ZONE} ne "mainzone" and $data =~ /.*?<$zone>(.+?)<\/$zone>.*?<\/Target_Zone>/)
{
$hash->{helper}{SUPPORT_PARTY_MODE} = 1;
if($1 eq "Enable")
{
readingsBulkUpdate($hash, "partyModeStatus", "on");
}
elsif($1 eq "Disable")
{
readingsBulkUpdate($hash, "partyModeStatus", "off");
}
}
else
{
$hash->{helper}{SUPPORT_PARTY_MODE} = 0;
}
}
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(defined($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(defined($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));
}
elsif($data =~ /(.+?)<\/Val>(.+?)<\/Exp>.+?<\/Unit><\/Lvl>(.+?)<\/Mute>.*?<\/Vol>/) # DSP based models
{
readingsBulkUpdate($hash, "volumeStraight", ($1 / 10 ** $2));
readingsBulkUpdate($hash, "volume", YAMAHA_AVR_volume_abs2rel(($1 / 10 ** $2)));
readingsBulkUpdate($hash, "mute", lc($3));
}
# (only available in zones other than mainzone) absolute or relative volume change to the mainzone
if($data =~ /.*?