fhem-mirror/FHEM/71_DENON_AVR_ZONE.pm
2020-10-26 08:52:21 +00:00

1442 lines
38 KiB
Perl

###############################################################################
# 71_DENON_AVR_ZONE
#
# 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 <http://www.gnu.org/licenses/>.
#
###############################################################################
#
# DENON_AVR_ZONE maintained by Martin Gutenbrunner
# original credits to raman and the community (see Forum thread)
#
# This module enables FHEM to interact with Denon and Marantz audio devices.
#
# Discussed in FHEM Forum: https://forum.fhem.de/index.php/topic,58452.300.html
#
# $Id$
package main;
use strict;
use warnings;
use Time::HiRes qw(gettimeofday);
sub DENON_AVR_ZONE_Get($$$);
sub DENON_AVR_ZONE_Set($$$);
sub DENON_AVR_ZONE_Attr($@);
sub DENON_AVR_ZONE_Define($$$);
sub DENON_AVR_ZONE_Undefine($$);
# Database and call-functions
######################################################################
my $DENON_db_zone = {
'UP' => 'up',
'DOWN' => 'down',
'ON' => 'on',
'OFF' => 'off',
'PHONO' => 'Phono',
'CD' => 'CD',
'TUNER' => 'Tuner',
'DVD' => 'DVD',
'BD' => 'Blu-Ray',
'TV' => 'TV',
'SAT/CBL' => 'Cbl/Sat',
'MPLAY' => 'Mediaplayer',
'GAME' => 'Game',
'HDRADIO' => 'HDRadio',
'NET' => 'OnlineMusic',
'SPOTIFY' => 'Spotify',
'LASTFM' => 'LastFM',
'FLICKR' => 'Flickr',
'IRADIO' => 'iRadio',
'SERVER' => 'Server',
'FAVORITES' => 'Favorites',
'PANDORA' => 'Pandora',
'SIRIUSXM' => 'SiriusXM',
'AUX1' => 'Aux1',
'AUX2' => 'Aux2',
'AUX3' => 'Aux3',
'AUX4' => 'Aux4',
'AUX5' => 'Aux5',
'AUX6' => 'Aux6',
'AUX7' => 'Aux7',
'BT' => 'Bluetooth',
'USB/IPOD' => 'Usb/iPod',
'USB' => 'Usb_play',
'IPD' => 'iPod_play',
'IRP' => 'iRadio_play',
'FVP' => 'Favorites_play',
'QUICK1' => 'Quick1',
'QUICK2' => 'Quick2',
'QUICK3' => 'Quick2',
'QUICK4' => 'Quick4',
'QUICK5' => 'Quick5',
'SMART1' => 'Smart1',
'SMART2' => 'Smart2',
'SMART3' => 'Smart3',
'SMART4' => 'Smart4',
'SMART5' => 'Smart5',
'SI' => {
'Phono' => 'PHONO',
'CD' => 'CD',
'Tuner' => 'TUNER',
'DVD' => 'DVD',
'Blu-Ray' => 'BD',
'TV' => 'TV',
'Cbl/Sat' => 'SAT/CBL',
'Mediaplayer' => 'MPLAY',
'Game' => 'GAME',
'HDRadio' => 'HDRADIO',
'OnlineMusic' => 'NET',
'Spotify' => 'SPOTIFY',
'LastFM' => 'LASTFM',
'Flickr' => 'FLICKR',
'iRadio' => 'IRADIO',
'Server' => 'SERVER',
'Favorites' => 'FAVORITES',
'Pandora' => 'PANDORA',
'SiriusXM' => 'SIRIUSXM',
'Aux1' => 'AUX1',
'Aux2' => 'AUX2',
'Aux3' => 'AUX3',
'Aux4' => 'AUX4',
'Aux5' => 'AUX5',
'Aux6' => 'AUX6',
'Aux7' => 'AUX7',
'Bluetooth' => 'BT',
'Usb/iPod' => 'USB/IPOD',
'Usb_play' => 'USB',
'iPod_play' => 'IPD',
'iRadio_play' => 'IRP',
'Favorites_play' => 'FVP',
'Source' => 'SOURCE',
# 'Status' => '?',
},
'MU' => {
'on' => 'ON',
'off' => 'OFF',
'status' => '?',
},
'CS' => {
'stereo' => 'ST',
'mono' => 'MONO',
'status' => '?',
},
'CV' => {
'FL' => 'FrontLeft',
'FR' => 'FrontRight',
},
'HPF' => {
'on' => 'ON',
'off' => 'OFF',
'status' => '?',
},
'PS' => {
'BAS' => 'bass',
'TRE' => 'treble',
},
'HDA' => {
'HDA' => 'HDMI_out',
'THR' => 'through',
'PCM' => 'pcm',
},
'SLP' => {
'off' => 'OFF',
'10min' => '010',
'15min' => '015',
'30min' => '030',
'40min' => '040',
'50min' => '050',
'60min' => '060',
'70min' => '070',
'80min' => '080',
'90min' => '090',
'100min' => '100',
'110min' => '110',
'120min' => '120',
'status' => '?',
},
'STBY' => {
'2h' => '2H',
'4h' => '4H',
'8h' => '8H',
'off' => 'OFF',
'status' => '?',
},
'SWITCH' => {
"on" => "off",
"off" => "on",
},
'REMOTE' => { #Remote - all commands:
'up' => 'UP',
'down' => 'DOWN',
},
'SOD' => { # used inputs: USE = aviable / DEL = not aviable
'DVD' => 'USE',
'BD' => 'USE',
'TV' => 'USE',
'SAT/CBL' => 'USE',
'SAT' => 'DEL',
'MPLAY' => 'USE',
'BT' => 'USE',
'GAME' => 'USE',
'HDRADIO' => 'DEL',
'AUX1' => 'USE',
'AUX2' => 'USE',
'AUX3' => 'DEL',
'AUX4' => 'DEL',
'AUX5' => 'DEL',
'AUX6' => 'DEL',
'AUX7' => 'DEL',
'AUXA' => 'DEL',
'AUXB' => 'DEL',
'AUXC' => 'DEL',
'AUXD' => 'DEL',
'CD' => 'USE',
'PHONO' => 'USE',
'TUNER' => 'USE',
'FAVORITES' => 'USE',
'IRADIO' => 'USE',
'SIRIUSXM' => 'DEL',
'PANDORA' => 'DEL',
'SERVER' => 'USE',
'FLICKR' => 'USE',
'NET' => 'USE',
'LASTFM' => 'DEL',
'USB/IPOD' => 'USE',
'USB' => 'USE',
'IPD' => 'USE',
'IRP' => 'USE',
'FVP' => 'USE',
'SOURCE' => 'USE',
},
};
sub
DENON_ZONE_GetValue($;$;$) {
my ( $status, $com, $inf) = @_;
my $command = (defined($com) ? $com : "na");
my $info = (defined($inf) ? $inf : "na");
if ( $command eq "na" && $info eq "na"
&& defined( $DENON_db_zone->{$status} ) )
{
my $value = eval { $DENON_db_zone->{$status} };
$value = $@ ? "unknown" : $value;
return $value;
}
elsif ( defined($DENON_db_zone->{$status}{$command} ) && $info eq "na" ) {
my $value = eval { $DENON_db_zone->{$status}{$command} };
$value = $@ ? "unknown" : $value;
return $value;
}
elsif ( defined($DENON_db_zone->{$status}{$command}{$info}) ) {
my $value = eval { $DENON_db_zone->{$status}{$command}{$info} };
$value = $@ ? "unknown" : $value;
return $value;
}
else {
return "unknown";
}
}
sub
DENON_ZONE_GetKey($$;$) {
my ( $status, $command, $info) = @_;
if ( defined($status) && defined($command) && !defined($info))
{
my @keys = keys %{$DENON_db_zone->{$status}};
my @values = values %{$DENON_db_zone->{$status}};
while (@keys) {
my $fhemCommand = pop(@keys);
my $denonCommand = pop(@values);
if ($command eq $denonCommand)
{
return $fhemCommand;
}
}
}
if ( defined($status) && defined($command) && defined($info))
{
my @keys = keys %{$DENON_db_zone->{$status}{$command}};
my @values = values %{$DENON_db_zone->{$status}{$command}};
while (@keys) {
my $fhemCommand = pop(@keys);
my $denonCommand = pop(@values);
if ($info eq $denonCommand)
{
return $fhemCommand;
}
}
}
else {
return undef;
}
}
###################################
sub
DENON_AVR_ZONE_Initialize($)
{
my ($hash) = @_;
$hash->{Match} = ".+";
$hash->{GetFn} = "DENON_AVR_ZONE_Get";
$hash->{SetFn} = "DENON_AVR_ZONE_Set";
$hash->{DefFn} = "DENON_AVR_ZONE_Define";
$hash->{UndefFn} = "DENON_AVR_ZONE_Undefine";
$hash->{ParseFn} = "DENON_AVR_ZONE_Parse";
$hash->{AttrFn} = "DENON_AVR_ZONE_Attr";
$hash->{AttrList} = "IODev brand:Denon,Marantz do_not_notify:1,0 disable:0,1 connectionCheck:off,30,45,60,75,90,105,120,240,300 timeout:1,2,3,4,5 unit:off,on ".$readingFnAttributes;
$data{RC_makenotify}{DENON_AVR_ZONE} = "DENON_AVR_ZONE_RCmakenotify";
$data{RC_layout}{DENON_AVR_ZONE_RC} = "DENON_AVR_ZONE_RClayout";
$hash->{parseParams} = 1;
}
#############################
sub
DENON_AVR_ZONE_Define($$$)
{
my ($hash, $a, $h) = @_;
my $name = $hash->{NAME};
return "Usage: define <name> DENON_AVR_ZONE <zone> <iodev> ... wrong paramter count: ".int(@$a) if(int(@$a) != 4);
AssignIoPort($hash, @$a[3]);
my $IOhash = $hash->{IODev};
my $IOname = $IOhash->{NAME};
my $zone;
if(!defined($IOhash) && !defined($IOname)) {
my $err= "DENON_AVR_ZONE $name error: no I/O device.";
Log3 $hash, 1, $err;
return $err;
}
if ( !defined( @$a[2] ) ) {
$zone = "2";
}
elsif ( @$a[2] eq "2" || @$a[2] eq "3" || @$a[2] eq "4") {
$zone = @$a[2];
}
else {
return @$a[2] . " is not a valid Zone number";
}
if ( defined($modules{DENON_AVR_ZONE}{defptr}{$IOname}{$zone}) )
{
return "Zone already defined in " . $modules{DENON_AVR_ZONE}{defptr}{$IOname}{$zone}{NAME};
}
if ( !defined($IOhash) ) {
return "No matching I/O device found, please define a DENON_AVR device first";
}
elsif ( !defined( $IOhash->{TYPE} ) || !defined( $IOhash->{NAME} ) ) {
return "IODev does not seem to be existing";
}
elsif ( $IOhash->{TYPE} ne "DENON_AVR" ) {
return "IODev is not of type DENON_AVR";
}
else {
$hash->{ZONE} = $zone;
$modules{DENON_AVR_ZONE}{defptr}{$IOname}{$zone} = $hash;
}
# set default attributes
unless ( exists( $attr{$name}{webCmd} ) ) {
$attr{$name}{webCmd} = 'volume:muteT:input';
}
unless ( exists( $attr{$name}{cmdIcon} ) ) {
$attr{$name}{cmdIcon} = 'muteT:rc_MUTE';
}
unless ( exists( $attr{$name}{devStateIcon} ) ) {
$attr{$name}{devStateIcon} = 'on:rc_GREEN:off off:rc_STOP:on absent:rc_RED muted:rc_MUTE@green:muteT';
}
unless (exists($attr{$name}{stateFormat})){
$attr{$name}{stateFormat} = 'stateAV';
}
return undef;
}
#####################################
sub DENON_AVR_ZONE_Undefine($$) {
my ( $hash, $name ) = @_;
my $zone = $hash->{ZONE};
my $IOhash = $hash->{IODev};
my $IOname = $IOhash->{NAME};
Log3 $name, 5,
"DENON_AVR_ZONE $name: called function DENON_AVR_ZONE_Undefine()";
delete $modules{DENON_AVR_ZONE}{defptr}{$IOname}{$zone}
if ( defined( $modules{DENON_AVR_ZONE}{defptr}{$IOname}{$zone} ) );
DENON_AVR_ZONE_RCdelete($name);
# Disconnect from device
DevIo_CloseDev($hash);
# Stop the internal GetStatus-Loop and exit
RemoveInternalTimer($hash);
return undef;
}
#####################################
sub
DENON_AVR_ZONE_Parse(@)
{
my ($IOhash, $msg) = @_; # IOhash points to the DENON_AVR, not to the DENON_AVR_ZONE
my @matches;
my $name = $IOhash->{NAME};
foreach my $d (keys %defs) {
my $hash = $defs{$d};
my $state = ReadingsVal( $name, "power", "off" );
if($hash->{TYPE} eq "DENON_AVR_ZONE" && $hash->{IODev} eq $IOhash) {
my $return = "unknown";
my $zone = 2;
my $zonehash = undef;
my $zonename = undef;
if ( defined($modules{DENON_AVR_ZONE}{defptr}{$name}{2}) && $msg =~ /^Z2/ )
{
$zone = 2;
$zonehash = $modules{DENON_AVR_ZONE}{defptr}{$name}{2};
$zonename = $zonehash->{NAME};
}
elsif ( defined($modules{DENON_AVR_ZONE}{defptr}{$name}{3}) && $msg =~ /^Z3/ )
{
$zone = 3;
$zonehash = $modules{DENON_AVR_ZONE}{defptr}{$name}{3};
$zonename = $zonehash->{NAME};
}
elsif ( defined($modules{DENON_AVR_ZONE}{defptr}{$name}{4}) && $msg =~ /^Z4/ )
{
$zone = 4;
$zonehash = $modules{DENON_AVR_ZONE}{defptr}{$name}{4};
$zonename = $zonehash->{NAME};
}
my $dezibel = AttrVal($zonename, "unit", "off") eq "on" ? " dB" : "";
my $percent = AttrVal($zonename, "unit", "off") eq "on" ? " %" : "";
Log3 $zonename, 5, "DENON_AVR_ZONE $zone: $name <dq($msg)>";
push @matches, $d;
if ($msg =~ /^(Z2|Z3|Z4)(.+)/) {
my $zone = $1;
my $arg = $2;
readingsBeginUpdate($zonehash);
if ($arg eq "ON" || $arg eq "OFF") {
readingsBulkUpdate($zonehash, "power", lc($arg));
readingsBulkUpdate($zonehash, "state", lc($arg));
readingsBulkUpdate($zonehash, "presence", "present");
readingsBulkUpdate($zonehash, "stateAV", DENON_AVR_ZONE_GetStateAV($zonehash));
$return = lc($arg);
}
elsif ($arg =~ /^CS(.+)/) {
Log3 $zonename, 0, "DENON_AVR_ZONE $zone: $name <$1>";
my $status = DENON_ZONE_GetKey("CS", $1);
readingsBulkUpdate($zonehash, "channelSetting", $status);
$return = "channelSetting " . $status;
}
elsif ($arg =~ /^CV([A-Z]+) (.+)/) {
Log3 $zonename, 0, "DENON_AVR_ZONE $zone: $name <$1>";
my $channel = DENON_ZONE_GetValue("CV", $1);
my $volume = $2;
if (length($volume) == 2)
{
$volume = $volume."0";
}
readingsBulkUpdate($zonehash, "level".$channel, ($volume / 10 - 50).$dezibel);
$return = "level".$channel . " " . ($volume / 10 - 50).$dezibel;
}
elsif ($arg =~ /^FAVORITE(.+)/)
{
readingsBulkUpdate($zonehash, "favorite", $1);
$return = "favorite " . $1;
}
elsif ($arg =~ /^MU(.+)/) {
readingsBulkUpdate($zonehash, "mute", lc($1));
readingsBulkUpdate($zonehash, "stateAV", DENON_AVR_ZONE_GetStateAV($zonehash));
$return = "mute " . lc($1);
}
elsif ($arg =~ /^HDA(.+)/) {
my $status = DENON_ZONE_GetValue('HDA', $1);
readingsBulkUpdate($zonehash, "digitalOut", $status);
$return = "digitalOut " . $status;
}
elsif ($arg =~ /^HPF(.+)/) {
readingsBulkUpdate($zonehash, "highPassFilter", lc($1));
$return = "highPassFilter " . lc($1);
}
elsif ($arg =~/^PS(.+)/)
{
my $parameter = $1;
if($parameter =~ /^([A-Z]{3}) (.+)/)
{
my $status = DENON_ZONE_GetValue('PS', $1);
my $volume = $2;
if (length($volume) == 2)
{
$volume = $volume."0";
}
$volume = ($volume / 10 - 50).$dezibel;
readingsBulkUpdate($zonehash, $status, $volume);
$return = $status . " " . $volume;
}
}
elsif ($arg =~ /^QUICK(.+)/) {
readingsBulkUpdate($zonehash, "quickselect", $1);
$return = "quickselect " . $1;
}
elsif ($arg =~ /^SMART(.+)/) {
readingsBulkUpdate($zonehash, "smartselect", $1);
$return = "smartselect " . $1;
}
elsif ($arg =~ /^SLP(.+)/) {
readingsBulkUpdate($zonehash, "sleep", $1."min");
$return = "sleep " . $1."min";
}
elsif ($arg =~ /^STBY(.+)/) {
my $status = DENON_ZONE_GetKey("STBY", $1);
readingsBulkUpdate($zonehash, "autoStandby", $status);
$return = "autoStandby " . $status;
}
elsif ($arg =~ /(^[0-9]+)/){
my $volume = $1;
if (length($volume) == 2)
{
$volume = $volume."0";
}
readingsBulkUpdate($zonehash, "volumeStraight", ($volume / 10 - 80).$percent);
readingsBulkUpdate($zonehash, "volume", ($volume / 10).$dezibel);
$return = "volume/volumeStraight ".($volume / 10)."/".($volume / 10 - 80);
}
else{
if ($arg eq 'SOURCE'){
my $status = ReadingsVal($name, "input", "Cbl/Sat");
readingsBulkUpdate($zonehash, "input", $status);
$return = "input " . $status;
}
else{
my $status = DENON_ZONE_GetValue($arg);
readingsBulkUpdate($zonehash, "input", $status);
$return = "input " . $status;
}
}
readingsEndUpdate($zonehash, 1);
}
elsif ( $msg =~ /^power (.+)/ && defined($zonehash) ) {
readingsBeginUpdate($zonehash);
readingsBulkUpdate($zonehash, "power", $1);
readingsEndUpdate($zonehash, 1);
$return = $1;
}
elsif ( $msg =~ /^presence (.+)/ && defined($zonehash) ) {
readingsBeginUpdate($zonehash);
readingsBulkUpdate($zonehash, "presence", $1);
readingsEndUpdate($zonehash, 1);
$return = "presence " . $1;
}
Log3 $zonename, 4, "DENON_AVR_ZONE $zone: parsing <$msg> to <$return>";
return @matches if (@matches);
return "UNDEFINED DENON_AVR_ZONE";
}
}
}
#####################################
sub
DENON_AVR_ZONE_Get($$$)
{
my ( $hash, $a, $h ) = @_;
my $arg;
my $name = $hash->{NAME};
my $zone = $hash->{ZONE};
return "argument is missing" if (int(@$a) != 2);
if (@$a[1] =~ /^(power|volumeStraight|volume|channelSetting|highPassFilter|mute|input|remotecontrol|autoStandby|sleep|zone|zoneStatusRequest)$/)
{
if (@$a[1] eq "remotecontrol")
{
return DENON_AVR_ZONE_RCmake($name);
}
elsif (@$a[1] eq "zoneStatusRequest")
{
DENON_AVR_ZONE_Command_Write($hash, "?", "power");
return "StatusRequest zone $zone finished!";
}
elsif(defined(ReadingsVal( $name, @$a[1], "" )))
{
return ReadingsVal( $name, @$a[1], "" );
}
else
{
return "No such reading: @$a[1]";
}
}
else
{
return "Unknown argument @$a[1], choose one of power volumeStraight volume autoStandby channelSetting highPassFilter mute input mute remotecontrol sleep zone zoneStatusRequest";
}
}
#####################################
sub
DENON_AVR_ZONE_Set($$$)
{
my ( $hash, $a, $h ) = @_;
my $IOhash = $hash->{IODev};
my $name = $hash->{NAME};
my $zone = $hash->{ZONE};
my $state = ReadingsVal( $name, "power", "off" );
my $presence = ReadingsVal( $name, "presence", "absent" );
my $return;
my $select = "quick";
my @channel = ();
my @preset = (01..56);
my @inputs = ();
#my @usedInputs = ();
my @remoteControl = ();
my $dezibel = AttrVal($name, "unit", "off") eq "on" ? " dB" : "";
foreach my $key (sort(keys %{$DENON_db_zone->{'REMOTE'}})) {
push(@remoteControl, $key);
}
if ( exists( $attr{$IOhash->{NAME}}{inputs} ) )
{
@inputs = split(/,/,$attr{$IOhash->{NAME}}{inputs});
}
else
{
foreach my $key (sort(keys %{$DENON_db_zone->{'SI'}})) {
my $device = $DENON_db_zone->{'SI'}{$key};
if ( defined($DENON_db_zone->{'SOD'}{$device}))
{
if ($DENON_db_zone->{'SOD'}{$device} eq 'USE')
{
push(@inputs, $key);
}
#push(@usedInputs, $key);
}
}
}
if(AttrVal($name, "brand", "Denon") eq "Marantz")
{
$select = "smart";
}
foreach my $key (sort(keys %{$DENON_db_zone->{'CV'}})) {
push(@channel, $DENON_db_zone->{'CV'}{$key}."_up");
push(@channel, $DENON_db_zone->{'CV'}{$key}."_down");
}
my $usage = "Unknown argument @$a[1], choose one of on off toggle volumeDown volumeUp volumeStraight:slider,-80,1,18 volume:slider,0,1,98 mute:on,off,toggle muteT sleep:off,10min,15min,20min,30min,40min,50min,60min,70min,80min,90min,100min,110min,120min autoStandby:off,2h,4h,8h channelSetting:stereo,mono highPassFilter:on,off " .
"favorite:1,2,3,4" . " " .
$select . "select:1,2,3,4,5 " .
"input:" . join(",", @inputs) . " " .
# "usedInputs:multiple-strict," . join(",", @usedInputs) . " " .
"bass:slider,-10,1,10 treble:slider,-10,1,10 " .
"channelVolume:" . join(",", @channel) . " " .
"remoteControl:" . join(",", @remoteControl) . " " .
"rawCommand";
if (@$a[1] eq "?")
{
$return = "?";
return $usage;
}
readingsBeginUpdate($hash);
if (@$a[1] =~ /^(on|off)$/)
{
$return = DENON_AVR_ZONE_Command_SetPower($hash, @$a[1]);
}
elsif (@$a[1] eq "bass")
{
my $volume = @$a[2] + 50;
$return = DENON_AVR_ZONE_Command_Write($hash, "PSBAS ".$volume, "bass", @$a[2].$dezibel);
}
elsif (@$a[1] eq "treble")
{
my $volume = @$a[2] + 50;
$return = DENON_AVR_ZONE_Command_Write($hash, "PSTRE ".$volume, "treble", @$a[2].$dezibel);
}
elsif (@$a[1] eq "channelSetting")
{
my $favorite = @$a[2];
my $channel = DENON_ZONE_GetValue('CS', @$a[2]);
$return = DENON_AVR_ZONE_Command_Write($hash, "CS".$channel, "channelSetting", @$a[2]);
}
elsif (@$a[1] eq "channelVolume")
{
my $channel = "";
my $command = @$a[2];
my $volume = "";
if($command =~ /^(.+)_(up|down)/)
{
$channel = DENON_ZONE_GetKey("CV", $1);
$channel = $channel." ".uc($2);
#my $state = ReadingsVal( $name, $channel, "0" );
#$volume = uc($2);
$volume = "query_CV?";
}
else
{
$channel = DENON_ZONE_GetKey("CV", $command);
$volume = @$a[3] + 50;
if ($volume % 1 == 0)
{
$volume = 40 if($volume < 40);
$volume = 60 if($volume > 60);
$volume = sprintf ('%02d', $volume);
$channel = $channel." ".$volume;
$volume = @$a[3].$dezibel;
}
elsif ($volume % 1 == 0.5)
{
$volume = 40.5 if($volume < 40.5);
$volume = 59.5 if($volume > 59.5);
$volume = sprintf ('%03d', ($volume * 10));
$channel = $channel." ".$volume;
$volume = @$a[3].$dezibel;
}
else
{
return undef;
}
}
$return = DENON_AVR_ZONE_Command_Write($hash, "CV".$channel, "channelVolume", $volume);
}
elsif (@$a[1] eq "favorite")
{
my $favorite = @$a[2];
$return = DENON_AVR_ZONE_Command_Write($hash, "FAVORITE".$favorite, "favorite", @$a[2]);
}
elsif (@$a[1] eq "highPassFilter")
{
my $favorite = @$a[2];
my $channel = DENON_ZONE_GetValue('HPF', @$a[2]);
$return = DENON_AVR_ZONE_Command_Write($hash, "HPF".$channel, "highPassFilter", @$a[2]);
}
elsif (@$a[1] eq "input")
{
my $input = DENON_ZONE_GetValue('SI', @$a[2]);
$return = DENON_AVR_ZONE_Command_SetInput($hash, $input, @$a[2]);
}
elsif (@$a[1] eq "mute" || @$a[1] eq "muteT")
{
my $mute = @$a[2];
if ($mute eq "toggle" || @$a[1] eq "muteT")
{
my $newMuteState = DENON_ZONE_GetValue('SWITCH', ReadingsVal( $name, "mute", "off"));
$return = DENON_AVR_ZONE_Command_SetMute($hash, $newMuteState);
}
else
{
$return = DENON_AVR_ZONE_Command_SetMute($hash, $mute);
}
}
elsif (@$a[1] eq "quickselect")
{
my $msg = "QUICK".@$a[2];
$return = DENON_AVR_ZONE_Command_Write($hash, $msg, "quickselect", @$a[2]);
}
elsif (@$a[1] eq "smartselect")
{
my $msg = "SMART".@$a[2];
$return = DENON_AVR_ZONE_Command_Write($hash, $msg, "smartselect", @$a[2]);
}
elsif (@$a[1] eq "sleep")
{
my $msg = DENON_ZONE_GetValue('SLP', @$a[2]);
$return = DENON_AVR_ZONE_Command_Write($hash, "SLP".$msg, "sleep", @$a[2]);
}
elsif (@$a[1] eq "autoStandby")
{
my $msg = DENON_ZONE_GetValue('STBY',@$a[2]);
$return = DENON_AVR_ZONE_Command_Write($hash, "STBY".$msg, "autoStandby", @$a[2]);
}
elsif (@$a[1] eq "toggle")
{
my $newPowerState = DENON_ZONE_GetValue('SWITCH', ReadingsVal( $name, "state", "on"));
$return =DENON_AVR_ZONE_Command_SetPower($hash, $newPowerState);
}
elsif (@$a[1] eq "volumeStraight")
{
my $volume = @$a[2];
$return = DENON_AVR_ZONE_Command_SetVolume($hash, $volume + 80);
}
elsif (@$a[1] eq "volume")
{
my $volume = @$a[2];
$return = DENON_AVR_ZONE_Command_SetVolume($hash, $volume);
}
elsif (@$a[1] eq "volumeDown")
{
my $msg = "DOWN";
my $oldVolume = ReadingsVal( $name, "volume", "0" );
my $volume = @$a[2];
if(@$a[2])
{
$volume = $oldVolume - $volume;
$return = DENON_AVR_ZONE_Command_SetVolume($hash, $volume);
}
else
{
readingsBulkUpdate($hash, "volumeStraight", $oldVolume - 81);
readingsBulkUpdate($hash, "volume", $oldVolume - 1);
$return = DENON_AVR_ZONE_Command_Write($hash, $msg, "volumeDown");
}
}
elsif (@$a[1] eq "volumeUp")
{
my $msg = "UP";
my $oldVolume = ReadingsVal( $name, "volume", "0" );
my $volume = @$a[2];
if(@$a[2])
{
$volume = $oldVolume + $volume;
$return = DENON_AVR_ZONE_Command_SetVolume($hash, $volume);
}
else
{
readingsBulkUpdate($hash, "volumeStraight", $oldVolume - 79);
readingsBulkUpdate($hash, "volume", $oldVolume + 1);
$return = DENON_AVR_ZONE_Command_Write($hash, $msg, "volumeUp");
}
}
elsif (@$a[1] eq "rawCommand")
{
my $msg = @$a[2];
$msg = @$a[2]." ".@$a[3] if defined @$a[3];
$msg = $msg." ".@$a[4] if defined @$a[4];
$return = DENON_AVR_ZONE_Command_Write($hash, $msg, "rawCommand");
}
elsif (@$a[1] eq "remoteControl")
{
if(@$a[2] =~ /^in_(.+)/) #inputs
{
my $input = DENON_ZONE_GetValue('SI', $1);
$return = DENON_AVR_ZONE_Command_SetInput($hash, $input, @$a[2]);
}
if(@$a[2] =~ /^(up|down)$/) #volume
{
$return = DENON_AVR_ZONE_Command_Write($hash, uc($1), "volume");
}
else
{
fhem("set $name @$a[2]");
}
}
elsif (@$a[1] eq "usedInputs")
{
DENON_AVR_ZONE_SetUsedInputs($hash, @$a[2]);
$return = "";
}
else
{
$return = $usage;
}
readingsEndUpdate( $hash, 1 );
# return result
return $return;
}
#####################################
sub
DENON_AVR_ZONE_Attr($@)
{
my @a = @_;
my $hash= $defs{$a[1]};
return undef;
}
#####################################
sub
DENON_AVR_ZONE_Command_Write($$$;$)
{
my ($hash, $msg, $call, $state) = @_;
my $name = $hash->{NAME};
my $zone = $hash->{ZONE};
Log3 $name, 5, "DENON_AVR_ZONE $name: zone $zone called Write";
IOWrite($hash, "Z".$zone."".$msg, "Z".$zone." ".$call);
if(defined($state))
{
if($state =~ /^query_(.+)/)
{
IOWrite($hash, "Z".$zone."".$1, "Z".$zone." query");
}
else
{
readingsBulkUpdate($hash, $call, $state);
}
}
return undef;
}
#####################################
sub
DENON_AVR_ZONE_GetStateAV($) {
my ($hash) = @_;
my $name = $hash->{NAME};
if ( ReadingsVal( $name, "presence", "absent" ) eq "absent" ) {
return "absent";
}
elsif ( ReadingsVal( $name, "power", "off" ) eq "off" ) {
return "off";
}
elsif ( ReadingsVal( $name, "mute", "off" ) eq "on" ) {
return "muted";
}
else {
return ReadingsVal( $name, "power", "off" );
}
}
#####################################
sub
DENON_AVR_ZONE_Command_SetInput($$$)
{
my ($hash, $input, $friendlyName) = @_;
my $name = $hash->{NAME};
my $zone = $hash->{ZONE};
Log3 $name, 5, "DENON_AVR_ZONE $name: zone $zone called SetInput.";
IOWrite($hash, "Z".$zone."".$input, "Z".$zone." input");
readingsBulkUpdate($hash, "input", $friendlyName);
return undef;
}
#####################################
sub
DENON_AVR_ZONE_Command_SetMute($$)
{
my ($hash, $mute) = @_;
my $name = $hash->{NAME};
my $zone = $hash->{ZONE};
Log3 $name, 5, "DENON_AVR_ZONE $name: zone $zone called SetMute.";
return "mute can only used when device is powered on" if (ReadingsVal( $name, "state", "off") eq "off");
my $status = DENON_ZONE_GetValue('MU', lc($mute));
IOWrite($hash, "Z".$zone."MU".$status, "Z".$zone." mute");
readingsBulkUpdate($hash, "mute", $mute);
readingsBulkUpdate($hash, "stateAV", DENON_AVR_ZONE_GetStateAV($hash));
return undef;
}
#####################################
sub
DENON_AVR_ZONE_Command_SetPower($$)
{
my ($hash, $power) = @_;
my $name = $hash->{NAME};
my $zone = $hash->{ZONE};
Log3 $name, 5, "DENON_AVR_ZONE $name: zone $zone called SetPower";
IOWrite($hash, "Z".$zone."".uc($power), "Z".$zone." power");
readingsBulkUpdate($hash, "power", lc($power));
readingsBulkUpdate($hash, "state", lc($power));
readingsBulkUpdate($hash, "stateAV", DENON_AVR_ZONE_GetStateAV($hash));
return undef;
}
#####################################
sub
DENON_AVR_ZONE_Command_SetVolume($$)
{
my ($hash, $volume) = @_;
my $name = $hash->{NAME};
my $zone = $hash->{ZONE};
my $dezibel = AttrVal($name, "unit", "off") eq "on" ? " dB" : "";
my $percent = AttrVal($name, "unit", "off") eq "on" ? " %" : "";
Log3 $name, 5, "DENON_AVR_ZONE $name: zone $zone called SetVolume.";
if(ReadingsVal( $name, "state", "off") eq "off")
{
return "Volume can only set when device is powered on!";
}
else
{
if (length($volume) == 1)
{
$volume = "0".$volume;
}
IOWrite($hash, "Z".$zone."".$volume, "Z".$zone." volume");
readingsBulkUpdate($hash, "volumeStraight", ($volume - 80).$percent);
readingsBulkUpdate($hash, "volume", $volume.$dezibel);
}
return undef;
}
#####################################
sub
DENON_AVR_ZONE_SetUsedInputs($$) {
my ($hash, $usedInputs) = @_;
my $name = $hash->{NAME};
my @inputs = split(/,/,$usedInputs);
my @denonInputs = ();
foreach (@inputs)
{
if(exists $DENON_db_zone->{'SI'}{$_})
{
push(@denonInputs, $_);
}
}
$attr{$name}{inputs} = join(",", @denonInputs);
}
#####################################
sub
DENON_AVR_ZONE_RCmakenotify($$) {
my ( $name, $ndev ) = @_;
my $nname = "notify_$name";
fhem( "define $nname notify $name set $ndev remoteControl " . '$EVENT', 1 );
Log3 $name, 3, "DENON_AVR_ZONE $name: create notify for remoteControl.";
return "Notify created by DENON_AVR_ZONE $nname";
}
#####################################
sub
DENON_AVR_ZONE_RCmake($) {
my ( $name ) = @_;
my $device = $name."_RC";
if(!defined($defs{$device}))
{
fhem("define $device remotecontrol");
fhem("sleep 1;set $device layout DENON_AVR_ZONE_RC");
my $notify = "notify_$name";
if(!defined($defs{$notify}))
{
fhem("sleep 1;set $device makenotify $name");
}
}
Log3 $name, 3, "DENON_AVR $name: create remoteControl.";
return "Remotecontrol created by DENON_AVR $name";
}
#####################################
sub
DENON_AVR_ZONE_RCdelete($) {
my ( $name ) = @_;
my $device = $name."_RC";
if(defined($defs{$device}))
{
fhem("delete $device");
my $notify = "notify_".$device;
if(defined($defs{$notify}))
{
fhem("sleep 1;delete $notify");
}
}
Log3 undef, 3, "DENON_AVR $name: delete remoteControl.";
return "Remotecontrol deleted by DENON_AVR_ZONE: $name";
}
#####################################
sub DENON_AVR_ZONE_RClayout() {
my @row;
$row[0] = "volumeUp:VOLUP,:blank,in_Cbl/Sat:CBLSAT,in_Blu-Ray:BR,in_DVD:DVD,in_CD:CD,:blank,play:PLAY,:blank,toggle:POWEROFF3";
$row[1] = "muteT:MUTE,:blank,in_Mediaplayer:MEDIAPLAYER,in_iRadio:IRADIO,in_OnlineMusic:ONLINEMUSIC,in_Usb/iPod:IPODUSB,:blank,pause:PAUSE";
$row[2] = "volumeDown:VOLDOWN,:blank,in_Bluetooth:BT,in_Favorites:FAV,in_Aux1:AUX1,in_Phono:PHONO,:blank,stop:STOP";
$row[3] = "attr rc_iconpath icons/remotecontrol";
$row[4] = "attr rc_iconprefix black_btn_";
return @row;
}
1;
=pod
=item device
=item summary control for DENON (Marantz) AV receivers zone
=item summary_DE Zonen-Steuerung von DENON (Marantz) AV Receivern
=begin html
<p>
<a name="DENON_AVR_ZONE" id="DENON_AVR_ZONE"></a>
</p>
<h3>
DENON_AVR_ZONE
</h3>
<ul>
<a name="DENON_AVR_ZONEdefine" id="DENON_AVR_ZONEdefine"></a> <b>Define</b>
<ul>
<code>define &lt;name&gt; DENON_AVR_ZONE &lt;zonenumber&gt;</code><br>
<br>
This module controls DENON A/V receivers zones.<br>
<br>
<br>
Example:<br>
<br>
<ul>
<code>
define avr_zone2 DENON_AVR_ZONE 2<br>
<br>
define avr_zone2 DENON_AVR_ZONE 3<br>
<br>
</code>
</ul>
</ul><br>
<br>
<a name="DENON_AVR_ZONEset" id="DENON_AVR_ZONEset"></a> <b>Set</b>
<ul>
<code>set &lt;name&gt; &lt;command&gt; [&lt;parameter&gt;]</code><br>
<br>
Currently, the following commands are defined:<br>
<ul>
<li>
<b>autoStandby</b> &nbsp;&nbsp;-&nbsp;&nbsp; set auto standby time
</li>
<li>
<b>favorite</b> &nbsp;&nbsp;-&nbsp;&nbsp; switches between favorite (only older models)
</li>
<li>
<b>input</b> &nbsp;&nbsp;-&nbsp;&nbsp; switches between inputs
</li>
<li>
<b>mute</b> on,off &nbsp;&nbsp;-&nbsp;&nbsp; controls volume mute
</li>
<li>
<b>muteT</b> &nbsp;&nbsp;-&nbsp;&nbsp; toggle mute state
</li>
<li>
<b>off</b> &nbsp;&nbsp;-&nbsp;&nbsp; turns the device in standby mode
</li>
<li>
<b>on</b> &nbsp;&nbsp;-&nbsp;&nbsp; powers on the device
</li>
<li>
<b>quickselect</b> &nbsp;&nbsp;-&nbsp;&nbsp; switches between quick select modes (1-5, only new models)
</li>
<li>
<b>rawCommand</b> &nbsp;&nbsp;-&nbsp;&nbsp; send raw command to AV receiver
</li>
<li>
<b>remote</b> &nbsp;&nbsp;-&nbsp;&nbsp; remote commands (play, stop, pause,...)
</li>
<li>
<b>surroundMode</b> &nbsp;&nbsp;-&nbsp;&nbsp; set surround mode
</li>
<li>
<b>toggle</b> &nbsp;&nbsp;-&nbsp;&nbsp; switch between on and off
</li>
<li>
<b>volume</b> 0...98 &nbsp;&nbsp;-&nbsp;&nbsp; set the volume level in percentage
</li>
<li>
<b>volumeStraight</b> -80...18 &nbsp;&nbsp;-&nbsp;&nbsp; set the volume level in dB
</li>
<li>
<b>volumeUp</b> &nbsp;&nbsp;-&nbsp;&nbsp; increases the volume level
</li>
<li>
<b>volumeDown</b> &nbsp;&nbsp;-&nbsp;&nbsp; decreases the volume level
</li>
</ul>
</ul>
<br>
<a name="DENON_AVR_ZONEget" id="DENON_AVR_ZONEget"></a> <b>Get</b>
<ul>
<code>get &lt;name&gt; &lt;what&gt;</code><br>
<br>
Currently, the following commands are defined:<br>
<ul>
<li>
<b>remoteControl</b> &nbsp;&nbsp;-&nbsp;&nbsp; autocreate remote ccontrol
</li>
<li>
<b>some readings</b> &nbsp;&nbsp;-&nbsp;&nbsp; see list below
</li>
</ul>
</ul><br>
<br>
<b>Generated Readings/Events:</b><br>
<br>
<ul>
<li>
<b>autoStandby</b> &nbsp;&nbsp;-&nbsp;&nbsp; auto standby state
</li>
<li>
<b>input</b> &nbsp;&nbsp;-&nbsp;&nbsp; selected input
</li>
<li>
<b>mute</b> &nbsp;&nbsp;-&nbsp;&nbsp; mute state
</li>
<li>
<b>power</b> &nbsp;&nbsp;-&nbsp;&nbsp; power state
</li>
<li>
<b>state</b> &nbsp;&nbsp;-&nbsp;&nbsp; state of AV reciever (on,off,disconnected)
</li>
<li>
<b>stateAV</b> &nbsp;&nbsp;-&nbsp;&nbsp; state of AV reciever (on,off,absent,mute)
</li>
<li>
<b>treble</b> &nbsp;&nbsp;-&nbsp;&nbsp; treble level in dB
</li>
<li>
<b>videoSelect</b> &nbsp;&nbsp;-&nbsp;&nbsp; actual video select mode
</li>
<li>
<b>volume</b> &nbsp;&nbsp;-&nbsp;&nbsp; actual volume
</li>
<li>
<b>volumeMax</b> &nbsp;&nbsp;-&nbsp;&nbsp; actual maximum volume
</li>
<li>
<b>volumeStraight</b> &nbsp;&nbsp;-&nbsp;&nbsp; actual volume straight
</li>
</ul>
<br>
<b>Attributes</b><br>
<br>
<ul>
<li>
<b>IODev</b> &nbsp;&nbsp;-&nbsp;&nbsp; Input/Output Device
</li>
</ul>
</ul>
=end html
=begin html_DE
<p>
<a name="DENON_AVR_ZONE" id="DENON_AVR_ZONE"></a>
</p>
<h3>
DENON_AVR_ZONE
</h3>
<ul>
<a name="DENON_AVR_ZONEdefine" id="DENON_AVR_ZONEdefine"></a> <b>Define</b>
<ul>
<code>define &lt;name&gt; DENON_AVR_ZONE &lt;zonename[:PORT]&gt;</code><br>
<br>
Dieses Modul steuert DENON A/V Receiver &uuml;ber das Netzwerk.<br>
<br>
<br>
Beispiele:<br>
<br>
<ul>
<code>
define avr_zone2 DENON_AVR_ZONE 2<br>
<br>
define avr_zone3 DENON_AVR_ZONE 3<br>
<br>
</code>
</ul>
</ul><br>
<br>
<a name="DENON_AVR_ZONEset" id="DENON_AVR_ZONEset"></a> <b>Set</b>
<ul>
<code>set &lt;name&gt; &lt;command&gt; [&lt;parameter&gt;]</code><br>
<br>
Momentan sind folgende Befehle verf&uuml;gbar:<br>
<ul>
<li>
<b>autoStandby</b> &nbsp;&nbsp;-&nbsp;&nbsp; Zeit für den Auto-Standby setzen
</li>
<li>
<b>favorite</b> &nbsp;&nbsp;-&nbsp;&nbsp; zur Auswahl der Favoriten (nur alte Modelle)
</li>
<li>
<b>input</b> &nbsp;&nbsp;-&nbsp;&nbsp; zur Auswahl der Eing&auml;nge
</li>
<li>
<b>mute</b> an,aus &nbsp;&nbsp;-&nbsp;&nbsp; AV-Receiver laut/stumm schalten
</li>
<li>
<b>muteT</b> &nbsp;&nbsp;-&nbsp;&nbsp; zwischen laut und stumm wechseln
</li>
<li>
<b>off</b> &nbsp;&nbsp;-&nbsp;&nbsp; Standby AV-Receiver
</li>
<li>
<b>on</b> &nbsp;&nbsp;-&nbsp;&nbsp; AV-Receiver anschalten
</li>
<li>
<b>quickselect</b> &nbsp;&nbsp;-&nbsp;&nbsp; zur Auswahl der &quot;Quick-Select&quot; Modi (1-5, nur neue Modelle)
</li>
<li>
<b>rawCommand</b> &nbsp;&nbsp;-&nbsp;&nbsp; schickt ein &quot;raw command&quot; zum AV-Receiver
</li>
<li>
<b>remote</b> &nbsp;&nbsp;-&nbsp;&nbsp; Fernbedienungsbefehle (play, stop, pause,...)
</li>
<li>
<b>surroundMode</b> &nbsp;&nbsp;-&nbsp;&nbsp; zur Auswahl der Surround-Modi
</li>
<li>
<b>toggle</b> &nbsp;&nbsp;-&nbsp;&nbsp; AV-Receiver an/aus
</li>
<li>
<b>volume</b> 0...98 &nbsp;&nbsp;-&nbsp;&nbsp; Lautst&auml;rke in Prozent
</li>
<li>
<b>volumeStraight</b> -80...18 &nbsp;&nbsp;-&nbsp;&nbsp; absolute Lautst&auml;rke in dB
</li>
<li>
<b>volumeUp</b> &nbsp;&nbsp;-&nbsp;&nbsp; erhöht Lautst&auml;rke
</li>
<li>
<b>volumeDown</b> &nbsp;&nbsp;-&nbsp;&nbsp; erniedrigt Lautst&auml;rke
</li>
</ul>
</ul><br>
<br>
<a name="DENON_AVR_ZONEget" id="DENON_AVR_ZONEget"></a> <b>Get</b>
<ul>
<code>get &lt;name&gt; &lt;what&gt;</code><br>
<br>
Momentan sind folgende Befehle verf&uuml;gbar:<br>
<br>
<ul>
<li>
<b>remoteControl</b> &nbsp;&nbsp;-&nbsp;&nbsp; Fernbedienung automatisch erzeugen
</li>
<li>
<b>diverse Readings</b> &nbsp;&nbsp;-&nbsp;&nbsp; siehe Liste unten
</li>
</ul>
</ul><br>
<br>
<b>Erzeugte Readings/Events:</b><br>
<br>
<ul>
<li>
<b>autoStandby</b> &nbsp;&nbsp;-&nbsp;&nbsp; Standbyzustand des AV-Recievers
</li>
<li>
<b>bass</b> &nbsp;&nbsp;-&nbsp;&nbsp; Bass-Level in dB
</li>
<li>
<b>display</b> &nbsp;&nbsp;-&nbsp;&nbsp; Dim-Status des Displays
</li>
<li>
<b>input</b> &nbsp;&nbsp;-&nbsp;&nbsp; gew&auml;hlte Eingangsquelle
</li>
<li>
<b>levelFrontLeft</b> &nbsp;&nbsp;-&nbsp;&nbsp; Pegel des linken Frontlautsprechers in dB
</li>
<li>
<b>levelFrontRight</b> &nbsp;&nbsp;-&nbsp;&nbsp; Pegel des rechten Frontlautsprechers in dB
</li>
<li>
<b>mute</b> &nbsp;&nbsp;-&nbsp;&nbsp; Status der Stummschaltung
</li>
<li>
<b>power</b> &nbsp;&nbsp;-&nbsp;&nbsp; Einschaltzustand des AV-Recievers
</li>
<li>
<b>sound</b> &nbsp;&nbsp;-&nbsp;&nbsp; aktueller Sound-Modus
</li>
<li>
<b>state</b> &nbsp;&nbsp;-&nbsp;&nbsp; Status des AV-Recievers (on,off,disconnected)
</li>
<li>
<b>stateAV</b> &nbsp;&nbsp;-&nbsp;&nbsp; stateAV-Status des AV-Recievers (on,off,mute,absent)
</li>
<li>
<b>toneControl</b> &nbsp;&nbsp;-&nbsp;&nbsp; Status der Klangkontrolle
</li>
<li>
<b>treble</b> &nbsp;&nbsp;-&nbsp;&nbsp; H&ouml;hen-Level in dB
</li>
<li>
<b>videoSelect</b> &nbsp;&nbsp;-&nbsp;&nbsp; gew&auml;hlter Videoselect-Modus
</li>
<li>
<b>volume</b> &nbsp;&nbsp;-&nbsp;&nbsp; aktuelle Lautst&auml;rke in Prozent
</li>
<li>
<b>volumeMax</b> &nbsp;&nbsp;-&nbsp;&nbsp; maximale Lautst&auml;rke in Prozent
</li>
<li>
<b>volumeStraight</b> &nbsp;&nbsp;-&nbsp;&nbsp; aktuelle absolute Lautst&auml;rke in dB
</li>
</ul>
<br>
<b>Attribute</b><br>
<br>
<ul>
<li>
<b>IODev</b> &nbsp;&nbsp;-&nbsp;&nbsp; Input/Output Device
</li>
</ul>
</ul>
=end html_DE
=cut