# $Id$ ############################################################################## # # 70_ONKYO_AVR_PULL.pm # An FHEM Perl module for controlling ONKYO A/V receivers # via network connection using a cyclic pull mechanism. # # +++ THIS VERSION IS _NOT_ UNDER ACTIVE DEVELOPMENT +++ # +++ ============================================== +++ # +++ For the benefit to get rid of pulling and getting +++ # +++ real-time updates, use the official version +++ # +++ ../FHEM/70_ONKYO_AVR.pm instead. +++ # # Copyright by Julian Pawlowski # e-mail: julian.pawlowski at gmail.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 ONKYOdb; use IO::Socket; use IO::Handle; use IO::Select; use XML::Simple; use Time::HiRes qw(usleep); use Symbol qw; use Data::Dumper; no if $] >= 5.017011, warnings => 'experimental::smartmatch'; sub ONKYO_AVR_PULL_Set($@); sub ONKYO_AVR_PULL_Get($@); sub ONKYO_AVR_PULL_GetStatus($;$); sub ONKYO_AVR_PULL_Define($$); sub ONKYO_AVR_PULL_Undefine($$); ######################### # Forward declaration for remotecontrol module sub ONKYO_AVR_PULL_RClayout_TV(); sub ONKYO_AVR_PULL_RCmakenotify($$); ################################### sub ONKYO_AVR_PULL_Initialize($) { my ($hash) = @_; Log3 $hash, 5, "ONKYO_AVR_PULL_Initialize: Entering"; $hash->{GetFn} = "ONKYO_AVR_PULL_Get"; $hash->{SetFn} = "ONKYO_AVR_PULL_Set"; $hash->{DefFn} = "ONKYO_AVR_PULL_Define"; $hash->{UndefFn} = "ONKYO_AVR_PULL_Undefine"; $hash->{AttrList} = "volumeSteps:1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20 inputs disable:0,1 model wakeupCmd:textField " . $readingFnAttributes; # $data{RC_layout}{ONKYO_AVR_PULL_SVG} = "ONKYO_AVR_PULL_RClayout_SVG"; # $data{RC_layout}{ONKYO_AVR_PULL} = "ONKYO_AVR_PULL_RClayout"; $data{RC_makenotify}{ONKYO_AVR_PULL} = "ONKYO_AVR_PULL_RCmakenotify"; } ##################################### sub ONKYO_AVR_PULL_GetStatus($;$) { my ( $hash, $local ) = @_; my $name = $hash->{NAME}; my $interval = $hash->{INTERVAL}; my $zone = $hash->{ZONE}; my $protocol = $hash->{READINGS}{deviceyear}{VAL}; my $state = ''; my $reading; my $states; Log3 $name, 5, "ONKYO_AVR_PULL $name: called function ONKYO_AVR_PULL_GetStatus()"; $local = 0 unless ( defined($local) ); if ( defined( $attr{$name}{disable} ) && $attr{$name}{disable} eq "1" ) { return $hash->{STATE}; } InternalTimer( gettimeofday() + $interval, "ONKYO_AVR_PULL_GetStatus", $hash, 1 ) unless ( $local == 1 ); # cache XML device information # # get device information if not available from helper if ( !defined( $hash->{helper}{receiver} ) && $protocol ne "pre2013" && $hash->{READINGS}{presence}{VAL} ne "absent" ) { my $xml = ONKYO_AVR_PULL_SendCommand( $hash, "net-receiver-information", "query" ); if ( defined($xml) && $xml =~ /^<\?xml/ ) { my $xml_parser = XML::Simple->new( NormaliseSpace => 2, KeepRoot => 0, ForceArray => 0, SuppressEmpty => 1 ); $hash->{helper}{receiver} = $xml_parser->XMLin($xml); # Safe input names my $inputs; foreach my $input ( sort keys %{ $hash->{helper}{receiver}{device}{selectorlist}{selector} } ) { if ( $input ne "" ) { my $id = uc( $hash->{helper}{receiver}{device}{selectorlist} {selector}{$input}{id} ); $input =~ s/\s/_/g; $hash->{helper}{receiver}{input}{$id} = $input; $inputs .= $input . ":"; } } if ( !defined( $attr{$name}{inputs} ) ) { $inputs = substr( $inputs, 0, -1 ); $attr{$name}{inputs} = $inputs; } readingsBeginUpdate($hash); # Brand $reading = "brand"; if ( defined( $hash->{helper}{receiver}{device}{$reading} ) && ( !defined( $hash->{READINGS}{$reading}{VAL} ) || $hash->{READINGS}{$reading}{VAL} ne $hash->{helper}{receiver}{device}{$reading} ) ) { readingsBulkUpdate( $hash, $reading, $hash->{helper}{receiver}{device}{$reading} ); } # Model $reading = "model"; if ( defined( $hash->{helper}{receiver}{device}{$reading} ) && ( !defined( $hash->{READINGS}{$reading}{VAL} ) || $hash->{READINGS}{$reading}{VAL} ne $hash->{helper}{receiver}{device}{$reading} ) ) { readingsBulkUpdate( $hash, $reading, $hash->{helper}{receiver}{device}{$reading} ); if ( !exists( $attr{$name}{model} ) || $attr{$name}{model} ne $hash->{helper}{receiver}{device}{$reading} ) { $attr{$name}{model} = $hash->{helper}{receiver}{device}{$reading}; } } # Firmware version $reading = "firmwareversion"; if ( defined( $hash->{helper}{receiver}{device}{$reading} ) && ( !defined( $hash->{READINGS}{$reading}{VAL} ) || $hash->{READINGS}{$reading}{VAL} ne $hash->{helper}{receiver}{device}{$reading} ) ) { readingsBulkUpdate( $hash, $reading, $hash->{helper}{receiver}{device}{$reading} ); } # device_id $reading = "deviceid"; if ( defined( $hash->{helper}{receiver}{device}{id} ) && ( !defined( $hash->{READINGS}{$reading}{VAL} ) || $hash->{READINGS}{$reading}{VAL} ne $hash->{helper}{receiver}{device}{id} ) ) { readingsBulkUpdate( $hash, $reading, $hash->{helper}{receiver}{device}{id} ); } # device_year $reading = "deviceyear"; if ( defined( $hash->{helper}{receiver}{device}{year} ) && ( !defined( $hash->{READINGS}{$reading}{VAL} ) || $hash->{READINGS}{$reading}{VAL} ne $hash->{helper}{receiver}{device}{year} ) ) { readingsBulkUpdate( $hash, $reading, $hash->{helper}{receiver}{device}{year} ); } readingsEndUpdate( $hash, 1 ); } elsif ( $hash->{READINGS}{presence}{VAL} ne "absent" ) { Log3 $name, 3, "ONKYO_AVR_PULL $name: net-receiver-information command unsupported, this must be a pre2013 device! Implicit fallback to protocol version pre2013."; readingsBeginUpdate($hash); readingsBulkUpdate( $hash, "deviceyear", "pre2013" ); readingsEndUpdate( $hash, 1 ); unless ( exists( $attr{$name}{model} ) ) { $attr{$name}{model} = "pre2013"; } } # Input alias handling # if ( defined( $attr{$name}{inputs} ) ) { my @inputs = split( ':', $attr{$name}{inputs} ); if (@inputs) { foreach (@inputs) { if (m/[^,\s]+(,[^,\s]+)+/) { my @input_names = split( ',', $_ ); $input_names[1] =~ s/\s/_/g; $hash->{helper}{receiver}{input_aliases} { $input_names[0] } = $input_names[1]; $hash->{helper}{receiver}{input_names} { $input_names[1] } = $input_names[0]; } } } } } # Read powerstate # my $powerstate = ONKYO_AVR_PULL_SendCommand( $hash, "power", "query" ); $state = "off"; if ( defined($powerstate) ) { if ( $powerstate eq "on" ) { $state = "on"; # Read other state information $states->{mute} = ONKYO_AVR_PULL_SendCommand( $hash, "mute", "query" ); $states->{volume} = ONKYO_AVR_PULL_SendCommand( $hash, "volume", "query" ); $states->{sleep} = ONKYO_AVR_PULL_SendCommand( $hash, "sleep", "query" ) if ( $zone eq "main" ); $states->{input} = ONKYO_AVR_PULL_SendCommand( $hash, "input", "query" ); $states->{video} = ONKYO_AVR_PULL_SendCommand( $hash, "video-information", "query" ) if ( $zone eq "main" ); $states->{audio} = ONKYO_AVR_PULL_SendCommand( $hash, "audio-information", "query" ) if ( $zone eq "main" ); } } else { $state = "absent"; } readingsBeginUpdate($hash); # Set reading for power # my $readingPower = "off"; if ( $state eq "on" ) { $readingPower = "on"; } if ( !defined( $hash->{READINGS}{power}{VAL} ) || $hash->{READINGS}{power}{VAL} ne $readingPower ) { readingsBulkUpdate( $hash, "power", $readingPower ); } # Set reading for state # if ( !defined( $hash->{READINGS}{state}{VAL} ) || $hash->{READINGS}{state}{VAL} ne $state ) { readingsBulkUpdate( $hash, "state", $state ); } # Set general readings for all zones # foreach ( "mute", "volume", "input" ) { if ( defined( $states->{$_} ) && $states->{$_} ne "" ) { if ( !defined( $hash->{READINGS}{$_}{VAL} ) || $hash->{READINGS}{$_}{VAL} ne $states->{$_} ) { readingsBulkUpdate( $hash, $_, $states->{$_} ); } } else { if ( !defined( $hash->{READINGS}{$_}{VAL} ) || $hash->{READINGS}{$_}{VAL} ne "-" ) { readingsBulkUpdate( $hash, $_, "-" ); } } } # Process for main zone only # if ( $zone eq "main" ) { # Set reading for sleep # foreach ("sleep") { if ( defined( $states->{$_} ) && $states->{$_} ne "" ) { if ( !defined( $hash->{READINGS}{$_}{VAL} ) || $hash->{READINGS}{$_}{VAL} ne $states->{$_} ) { readingsBulkUpdate( $hash, $_, $states->{$_} ); } } else { if ( !defined( $hash->{READINGS}{$_}{VAL} ) || $hash->{READINGS}{$_}{VAL} ne "-" ) { readingsBulkUpdate( $hash, $_, "-" ); } } } # Set readings for audio # if ( defined( $states->{audio} ) ) { my @audio_split = split( /,/, $states->{audio} ); if ( scalar(@audio_split) >= 6 ) { # Audio-in sampling rate my ($audin_srate) = split /[:\s]+/, $audio_split[2], 2; # Audio-in channels my ($audin_ch) = split /[:\s]+/, $audio_split[3], 2; # Audio-out channels my ($audout_ch) = split /[:\s]+/, $audio_split[5], 2; if ( !defined( $hash->{READINGS}{audin_src}{VAL} ) || $hash->{READINGS}{audin_src}{VAL} ne $audio_split[0] ) { readingsBulkUpdate( $hash, "audin_src", $audio_split[0] ); } if ( !defined( $hash->{READINGS}{audin_enc}{VAL} ) || $hash->{READINGS}{audin_enc}{VAL} ne $audio_split[1] ) { readingsBulkUpdate( $hash, "audin_enc", $audio_split[1] ); } if ( !defined( $hash->{READINGS}{audin_srate}{VAL} ) || ( defined($audin_srate) && $hash->{READINGS}{audin_srate}{VAL} ne $audin_srate ) ) { readingsBulkUpdate( $hash, "audin_srate", $audin_srate ); } if ( !defined( $hash->{READINGS}{audin_ch}{VAL} ) || ( defined($audin_ch) && $hash->{READINGS}{audin_ch}{VAL} ne $audin_ch ) ) { readingsBulkUpdate( $hash, "audin_ch", $audin_ch ); } if ( !defined( $hash->{READINGS}{audout_mode}{VAL} ) || $hash->{READINGS}{audout_mode}{VAL} ne $audio_split[4] ) { readingsBulkUpdate( $hash, "audout_mode", $audio_split[4] ); } if ( !defined( $hash->{READINGS}{audout_ch}{VAL} ) || ( defined($audout_ch) && $hash->{READINGS}{audout_ch}{VAL} ne $audout_ch ) ) { readingsBulkUpdate( $hash, "audout_ch", $audout_ch ); } } else { foreach ( "audin_src", "audin_enc", "audin_srate", "audin_ch", "audout_ch", "audout_mode", ) { if ( !defined( $hash->{READINGS}{$_}{VAL} ) || $hash->{READINGS}{$_}{VAL} ne "-" ) { readingsBulkUpdate( $hash, $_, "-" ); } } } } else { foreach ( "audin_src", "audin_enc", "audin_srate", "audin_ch", "audout_ch", "audout_mode", ) { if ( !defined( $hash->{READINGS}{$_}{VAL} ) || $hash->{READINGS}{$_}{VAL} ne "-" ) { readingsBulkUpdate( $hash, $_, "-" ); } } } # Set readings for video # if ( defined( $states->{video} ) ) { my @video_split = split( /,/, $states->{video} ); if ( scalar(@video_split) >= 9 ) { # Video-in resolution my @vidin_res_string = split( / +/, $video_split[1] ); my $vidin_res; if ( defined( $vidin_res_string[0] ) && defined( $vidin_res_string[2] ) && defined( $vidin_res_string[3] ) && uc( $vidin_res_string[0] ) ne "UNKNOWN" && uc( $vidin_res_string[2] ) ne "UNKNOWN" && uc( $vidin_res_string[3] ) ne "UNKNOWN" ) { $vidin_res = $vidin_res_string[0] . "x" . $vidin_res_string[2] . $vidin_res_string[3]; } else { $vidin_res = ""; } # Video-out resolution my @vidout_res_string = split( / +/, $video_split[5] ); my $vidout_res; if ( defined( $vidout_res_string[0] ) && defined( $vidout_res_string[2] ) && defined( $vidout_res_string[3] ) && uc( $vidout_res_string[0] ) ne "UNKNOWN" && uc( $vidout_res_string[2] ) ne "UNKNOWN" && uc( $vidout_res_string[3] ) ne "UNKNOWN" ) { $vidout_res = $vidout_res_string[0] . "x" . $vidout_res_string[2] . $vidout_res_string[3]; } else { $vidout_res = ""; } # Video-in color depth my ($vidin_cdepth) = split( /[:\s]+/, $video_split[3], 2 ) || ""; # Video-out color depth my ($vidout_cdepth) = split( /[:\s]+/, $video_split[7], 2 ) || ""; if ( !defined( $hash->{READINGS}{vidin_src}{VAL} ) || $hash->{READINGS}{vidin_src}{VAL} ne $video_split[0] ) { readingsBulkUpdate( $hash, "vidin_src", $video_split[0] ); } if ( !defined( $hash->{READINGS}{vidin_res}{VAL} ) || $hash->{READINGS}{vidin_res}{VAL} ne $vidin_res ) { readingsBulkUpdate( $hash, "vidin_res", $vidin_res ); } if ( !defined( $hash->{READINGS}{vidin_cspace}{VAL} ) || $hash->{READINGS}{vidin_cspace}{VAL} ne lc( $video_split[2] ) ) { readingsBulkUpdate( $hash, "vidin_cspace", lc( $video_split[2] ) ); } if ( !defined( $hash->{READINGS}{vidin_cdepth}{VAL} ) || $hash->{READINGS}{vidin_cdepth}{VAL} ne $vidin_cdepth ) { readingsBulkUpdate( $hash, "vidin_cdepth", $vidin_cdepth ); } if ( !defined( $hash->{READINGS}{vidout_dst}{VAL} ) || $hash->{READINGS}{vidout_dst}{VAL} ne $video_split[4] ) { readingsBulkUpdate( $hash, "vidout_dst", $video_split[4] ); } if ( !defined( $hash->{READINGS}{vidout_res}{VAL} ) || $hash->{READINGS}{vidout_res}{VAL} ne $vidout_res ) { readingsBulkUpdate( $hash, "vidout_res", $vidout_res ); } if ( !defined( $hash->{READINGS}{vidout_cspace}{VAL} ) || $hash->{READINGS}{vidout_cspace}{VAL} ne lc( $video_split[6] ) ) { readingsBulkUpdate( $hash, "vidout_cspace", lc( $video_split[6] ) ); } if ( !defined( $hash->{READINGS}{vidout_cdepth}{VAL} ) || $hash->{READINGS}{vidout_cdepth}{VAL} ne $vidout_cdepth ) { readingsBulkUpdate( $hash, "vidout_cdepth", $vidout_cdepth ); } if ( !defined( $hash->{READINGS}{vidout_mode}{VAL} ) || $hash->{READINGS}{vidout_mode}{VAL} ne lc( $video_split[8] ) ) { readingsBulkUpdate( $hash, "vidout_mode", lc( $video_split[8] ) ); } } else { foreach ( "vidin_src", "vidin_res", "vidin_cspace", "vidin_cdepth", "vidout_dst", "vidout_res", "vidout_cspace", "vidout_cdepth", "vidout_mode", ) { if ( !defined( $hash->{READINGS}{$_}{VAL} ) || $hash->{READINGS}{$_}{VAL} ne "-" ) { readingsBulkUpdate( $hash, $_, "-" ); } } } } else { foreach ( "vidin_src", "vidin_res", "vidin_cspace", "vidin_cdepth", "vidout_dst", "vidout_res", "vidout_cspace", "vidout_cdepth", "vidout_mode", ) { if ( !defined( $hash->{READINGS}{$_}{VAL} ) || $hash->{READINGS}{$_}{VAL} ne "-" ) { readingsBulkUpdate( $hash, $_, "-" ); } } } } readingsEndUpdate( $hash, 1 ); Log3 $name, 4, "ONKYO_AVR_PULL $name: " . $hash->{STATE}; return $hash->{STATE}; } ################################### sub ONKYO_AVR_PULL_Get($@) { my ( $hash, @a ) = @_; my $name = $hash->{NAME}; my $what; Log3 $name, 5, "ONKYO_AVR_PULL $name: called function ONKYO_AVR_PULL_Get()"; return "argument is missing" if ( int(@a) < 2 ); $what = $a[1]; if ( $what =~ /^(power|input|volume|mute|sleep)$/ ) { if ( defined( $hash->{READINGS}{$what} ) ) { return $hash->{READINGS}{$what}{VAL}; } else { return "no such reading: $what"; } } else { return "Unknown argument $what, choose one of power:noArg input:noArg volume:noArg mute:noArg sleep:noArg "; } } ################################### sub ONKYO_AVR_PULL_Set($@) { my ( $hash, @a ) = @_; my $name = $hash->{NAME}; my $interval = $hash->{INTERVAL}; my $zone = $hash->{ZONE}; my $state = $hash->{STATE}; my $return; my $reading; my $inputs_txt = ""; Log3 $name, 5, "ONKYO_AVR_PULL $name: called function ONKYO_AVR_PULL_Set()"; return "No argument given to ONKYO_AVR_PULL_Set" if ( !defined( $a[1] ) ); # depending on current FHEMWEB instance's allowedCommands, # restrict set commands if there is "set-user" in it my $adminMode = 1; my $FWallowedCommands = 0; $FWallowedCommands = AttrVal( $FW_wname, "allowedCommands", 0 ) if ( defined($FW_wname) ); if ( $FWallowedCommands && $FWallowedCommands =~ m/\bset-user\b/ ) { $adminMode = 0; return "Forbidden command: set " . $a[1] if ( lc( $a[1] ) eq "statusrequest" || lc( $a[1] ) eq "remotecontrol" ); } # Input alias handling if ( defined( $attr{$name}{inputs} ) && $attr{$name}{inputs} ne "" ) { my @inputs = split( ':', $attr{$name}{inputs} ); $inputs_txt = "-," if ( $state ne "on" ); if (@inputs) { foreach (@inputs) { if (m/[^,\s]+(,[^,\s]+)+/) { my @input_names = split( ',', $_ ); $inputs_txt .= $input_names[1] . ","; $input_names[1] =~ s/\s/_/g; $hash->{helper}{receiver}{input_aliases}{ $input_names[0] } = $input_names[1]; $hash->{helper}{receiver}{input_names}{ $input_names[1] } = $input_names[0]; } else { $inputs_txt .= $_ . ","; } } } $inputs_txt =~ s/\s/_/g; $inputs_txt = substr( $inputs_txt, 0, -1 ); } # if we could read the actual available inputs from the receiver, use them elsif (defined( $hash->{helper}{receiver} ) && ref( $hash->{helper}{receiver} ) eq "HASH" && defined( $hash->{helper}{receiver}{device}{selectorlist}{count} ) && $hash->{helper}{receiver}{device}{selectorlist}{count} > 0 ) { $inputs_txt = "-," if ( $state ne "on" ); foreach my $input ( sort keys %{ $hash->{helper}{receiver}{device}{selectorlist}{selector} } ) { if ( $hash->{helper}{receiver}{device}{selectorlist}{selector} {$input}{value} eq "1" && $hash->{helper}{receiver}{device}{selectorlist}{selector} {$input}{id} !~ /(80)/ ) { $inputs_txt .= $input . ","; } } $inputs_txt =~ s/\s/_/g; $inputs_txt = substr( $inputs_txt, 0, -1 ); } # use general list of possible inputs else { # Find out valid inputs my $inputs = ONKYOdb::ONKYO_GetRemotecontrolValue( "1", ONKYOdb::ONKYO_GetRemotecontrolCommand( "1", "input" ) ); foreach my $input ( sort keys %{$inputs} ) { $inputs_txt .= $input . "," if ( !( $input =~ /^(07|08|09|up|down|query)$/ ) ); } $inputs_txt = substr( $inputs_txt, 0, -1 ); } my $usage = "Unknown argument '" . $a[1] . "', choose one of toggle:noArg on:noArg off:noArg volume:slider,0,1,100 volumeUp:noArg volumeDown:noArg input:" . $inputs_txt; $usage .= " sleep:off,5,10,15,30,60,90" if ( $zone eq "main" ); $usage .= " mute:off,on" if ( $state eq "on" ); $usage .= " mute:,-" if ( $state ne "on" ); if ($adminMode) { $usage .= " statusRequest:noArg"; $usage .= " remoteControl:noArg"; } my $cmd = ''; my $result; # Stop the internal GetStatus-Loop to avoid # parallel/conflicting requests to device RemoveInternalTimer($hash) if ( $a[1] ne "?" ); readingsBeginUpdate($hash); # statusRequest if ( lc( $a[1] ) eq "statusrequest" ) { Log3 $name, 3, "ONKYO_AVR_PULL set $name " . $a[1]; delete $hash->{helper}{receiver} if ( $state ne "absent" ); ONKYO_AVR_PULL_GetStatus( $hash, 1 ) if ( !defined( $a[2] ) ); } # toggle elsif ( lc( $a[1] ) eq "toggle" ) { Log3 $name, 3, "ONKYO_AVR_PULL set $name " . $a[1]; if ( $hash->{READINGS}{power}{VAL} eq "off" ) { $return = ONKYO_AVR_PULL_Set( $hash, $name, "on" ); } else { $return = ONKYO_AVR_PULL_Set( $hash, $name, "off" ); } } # on elsif ( lc( $a[1] ) eq "on" ) { if ( $hash->{READINGS}{state}{VAL} eq "absent" ) { Log3 $name, 3, "ONKYO_AVR_PULL set $name " . $a[1] . " (wakeup)"; my $wakeupCmd = AttrVal( $name, "wakeupCmd", "" ); if ( $wakeupCmd ne "" ) { $wakeupCmd =~ s/\$DEVICE/$name/g; if ( $wakeupCmd =~ s/^[ \t]*\{|\}[ \t]*$//g ) { Log3 $name, 4, "ONKYO_AVR_PULL executing wake-up command (Perl): $wakeupCmd"; $result = eval $wakeupCmd; } else { Log3 $name, 4, "ONKYO_AVR_PULL executing wake-up command (fhem): $wakeupCmd"; $result = fhem $wakeupCmd; } } else { $return = "Device is offline and cannot be controlled at that stage."; } } else { Log3 $name, 3, "ONKYO_AVR_PULL set $name " . $a[1]; $result = ONKYO_AVR_PULL_SendCommand( $hash, "power", "on" ); if ( defined($result) ) { if ( !defined( $hash->{READINGS}{power}{VAL} ) || $hash->{READINGS}{power}{VAL} ne $result ) { readingsBulkUpdate( $hash, "power", $result ); } if ( !defined( $hash->{READINGS}{state}{VAL} ) || $hash->{READINGS}{state}{VAL} ne $result ) { readingsBulkUpdate( $hash, "state", $result ); } } $interval = 2; } } # off elsif ( lc( $a[1] ) eq "off" ) { Log3 $name, 3, "ONKYO_AVR_PULL set $name " . $a[1]; if ( $hash->{READINGS}{state}{VAL} eq "absent" ) { $return = "Device is offline and cannot be controlled at that stage."; } else { $result = ONKYO_AVR_PULL_SendCommand( $hash, "power", "off" ); if ( defined($result) ) { if ( !defined( $hash->{READINGS}{power}{VAL} ) || $hash->{READINGS}{power}{VAL} ne $result ) { readingsBulkUpdate( $hash, "power", $result ); } if ( !defined( $hash->{READINGS}{state}{VAL} ) || $hash->{READINGS}{state}{VAL} ne $result ) { readingsBulkUpdate( $hash, "state", $result ); } } $interval = 2; } } # sleep elsif ( lc( $a[1] ) eq "sleep" && $zone eq "main" ) { if ( !defined( $a[2] ) ) { $return = "No argument given, choose one of minutes off"; } else { Log3 $name, 3, "ONKYO_AVR_PULL set $name " . $a[1] . " " . $a[2]; if ( $hash->{READINGS}{state}{VAL} eq "absent" ) { $return = "Device is offline and cannot be controlled at that stage."; } else { my $_ = $a[2]; if ( $_ eq "off" ) { $result = ONKYO_AVR_PULL_SendCommand( $hash, "sleep", "off" ); } elsif ( m/^\d+$/ && $_ > 0 && $_ <= 90 ) { $result = ONKYO_AVR_PULL_SendCommand( $hash, "sleep", ONKYO_AVR_PULL_dec2hex($_) ); } else { $return = "Argument does not seem to be a valid integer between 0 and 90"; } if ( defined($result) ) { if ( !defined( $hash->{READINGS}{sleep}{VAL} ) || $hash->{READINGS}{sleep}{VAL} ne $result ) { readingsBulkUpdate( $hash, "sleep", $result ); } } } } } # mute elsif ( lc( $a[1] ) eq "mute" || lc( $a[1] ) eq "mutet" ) { if ( defined( $a[2] ) ) { Log3 $name, 3, "ONKYO_AVR_PULL set $name " . $a[1] . " " . $a[2]; } else { Log3 $name, 3, "ONKYO_AVR_PULL set $name " . $a[1]; } if ( $hash->{READINGS}{state}{VAL} eq "on" ) { if ( !defined( $a[2] ) || $a[2] eq "toggle" ) { $result = ONKYO_AVR_PULL_SendCommand( $hash, "mute", "toggle" ); } elsif ( lc( $a[2] ) eq "off" ) { $result = ONKYO_AVR_PULL_SendCommand( $hash, "mute", "off" ); } elsif ( lc( $a[2] ) eq "on" ) { $result = ONKYO_AVR_PULL_SendCommand( $hash, "mute", "on" ); } else { $return = "Argument does not seem to be one of on off toogle"; } if ( defined($result) ) { if ( !defined( $hash->{READINGS}{mute}{VAL} ) || $hash->{READINGS}{mute}{VAL} ne $result ) { readingsBulkUpdate( $hash, "mute", $result ); } } } else { $return = "Device needs to be ON to mute/unmute audio."; } } # volume elsif ( lc( $a[1] ) eq "volume" ) { if ( !defined( $a[2] ) ) { $return = "No argument given"; } else { Log3 $name, 3, "ONKYO_AVR_PULL set $name " . $a[1] . " " . $a[2]; if ( $hash->{READINGS}{state}{VAL} eq "on" ) { my $_ = $a[2]; if ( m/^\d+$/ && $_ >= 0 && $_ <= 100 ) { $result = ONKYO_AVR_PULL_SendCommand( $hash, "volume", ONKYO_AVR_PULL_dec2hex($_) ); if ( defined($result) ) { if ( !defined( $hash->{READINGS}{volume}{VAL} ) || $hash->{READINGS}{volume}{VAL} ne $result ) { readingsBulkUpdate( $hash, "volume", $result ); } if ( !defined( $hash->{READINGS}{mute}{VAL} ) || $hash->{READINGS}{mute}{VAL} eq "on" ) { readingsBulkUpdate( $hash, "mute", "off" ) } } } else { $return = "Argument does not seem to be a valid integer between 0 and 100"; } } else { $return = "Device needs to be ON to adjust volume."; } } } # volumeUp/volumeDown elsif ( lc( $a[1] ) =~ /^(volumeup|volumedown)$/ ) { Log3 $name, 3, "ONKYO_AVR_PULL set $name " . $a[1]; if ( $hash->{READINGS}{state}{VAL} eq "on" ) { if ( lc( $a[1] ) eq "volumeup" ) { $result = ONKYO_AVR_PULL_SendCommand( $hash, "volume", "level-up" ); } else { $result = ONKYO_AVR_PULL_SendCommand( $hash, "volume", "level-down" ); } if ( defined($result) ) { if ( !defined( $hash->{READINGS}{volume}{VAL} ) || $hash->{READINGS}{volume}{VAL} ne $result ) { readingsBulkUpdate( $hash, "volume", $result ); } } } else { $return = "Device needs to be ON to adjust volume."; } } # input elsif ( lc( $a[1] ) eq "input" ) { if ( !defined( $a[2] ) ) { $return = "No input given"; } else { Log3 $name, 3, "ONKYO_AVR_PULL set $name " . $a[1] . " " . $a[2]; if ( $hash->{READINGS}{power}{VAL} eq "off" ) { $return = ONKYO_AVR_PULL_Set( $hash, $name, "on" ); } if ( $hash->{READINGS}{state}{VAL} eq "on" ) { $result = ONKYO_AVR_PULL_SendCommand( $hash, "input", $a[2] ); if ( defined($result) ) { if ( !defined( $hash->{READINGS}{input}{VAL} ) || $hash->{READINGS}{input}{VAL} ne $a[2] ) { readingsBulkUpdate( $hash, "input", $a[2] ); } } } else { $return = "Device needs to be ON to change input."; } $interval = 2; } } # remoteControl elsif ( lc( $a[1] ) eq "remotecontrol" ) { # Reading commands for zone from HASH table my $commands = ONKYOdb::ONKYO_GetRemotecontrolCommand($zone); # Output help for commands if ( !defined( $a[2] ) || $a[2] eq "help" ) { # Get all commands for zone my $commands_details = ONKYOdb::ONKYO_GetRemotecontrolCommandDetails($zone); my $valid_commands = "Usage: \n\nValid commands in zone '$zone':\n\n\n" . "COMMAND\t\t\tDESCRIPTION\n\n"; # For each valid command foreach my $command ( sort keys %{$commands} ) { my $command_raw = $commands->{$command}; # add command including description if found if ( defined( $commands_details->{$command_raw}{description} ) ) { $valid_commands .= $command . "\t\t\t" . $commands_details->{$command_raw}{description} . "\n"; } # add command only else { $valid_commands .= $command . "\n"; } } $valid_commands .= "\nTry ' help' to find out well known values.\n\n\n"; $return = $valid_commands; } else { # return if command cannot be found in HASH table if ( !defined( $commands->{ $a[2] } ) ) { $return = "Invalid command: " . $a[2]; } else { # Reading values for command from HASH table my $values = ONKYOdb::ONKYO_GetRemotecontrolValue( $zone, $commands->{ $a[2] } ); # Output help for values if ( !defined( $a[3] ) || $a[3] eq "help" ) { # Get all details for command my $command_details = ONKYOdb::ONKYO_GetRemotecontrolCommandDetails( $zone, $commands->{ $a[2] } ); my $valid_values = "Usage: " . $a[2] . " \n\nWell known values:\n\n\n" . "VALUE\t\t\tDESCRIPTION\n\n"; # For each valid value foreach my $value ( sort keys %{$values} ) { # add value including description if found if ( defined( $command_details->{description} ) ) { $valid_values .= $value . "\t\t\t" . $command_details->{description} . "\n"; } # add value only else { $valid_values .= $value . "\n"; } } $valid_values .= "\n\n\n"; $return = $valid_values; } # normal processing else { Log3 $name, 3, "ONKYO_AVR_PULL set $name " . $a[1] . " " . $a[2] . " " . $a[3]; if ( $hash->{READINGS}{state}{VAL} ne "absent" ) { # special power toogle handling if ( $a[2] eq "power" && $a[3] eq "toggle" ) { $result = ONKYO_AVR_PULL_Set( $hash, $name, "toggle" ); } # normal processing else { $result = ONKYO_AVR_PULL_SendCommand( $hash, $a[2], $a[3] ); } if ( !defined($result) ) { $return = "ERROR: command '" . $a[2] . " " . $a[3] . "' was NOT successful."; } elsif ( $a[3] eq "query" ) { $return = $result; } } else { $return = "Device needs to be reachable to be controlled remotely."; } } } } } # return usage hint else { $return = $usage; } readingsEndUpdate( $hash, 1 ); # Re-start internal timer InternalTimer( gettimeofday() + $interval, "ONKYO_AVR_PULL_GetStatus", $hash, 1 ) if ( $a[1] ne "?" ); # return result return $return; } ################################### sub ONKYO_AVR_PULL_Define($$) { my ( $hash, $def ) = @_; my @a = split( "[ \t][ \t]*", $def ); my $name = $hash->{NAME}; Log3 $name, 5, "ONKYO_AVR_PULL $name: called function ONKYO_AVR_PULL_Define()"; if ( int(@a) < 3 ) { my $msg = "Wrong syntax: define ONKYO_AVR_PULL [] [] []"; Log3 $name, 4, $msg; return $msg; } $hash->{TYPE} = "ONKYO_AVR_PULL"; my $address = $a[2]; $hash->{helper}{ADDRESS} = $address; # use fixed port 60128 my $port = 60128; $hash->{helper}{PORT} = $port; # used zone to control my $zone = "1"; if (defined($a[4]) && $a[4] =~ /^[a-zA-Z]*([2-4])$/) { $zone = $1; } $hash->{ZONE} = $zone; my $interval; if ( $zone eq "1" ) { # use interval of 75sec for main zone if not defined $interval = $a[5] || 75; } else { # use interval of 90sec for other zones if not defined $interval = $a[5] || 90; } $hash->{INTERVAL} = $interval; # protocol version my $protocol = $a[3] || 2013; if ( !( $protocol =~ /^(2013|pre2013)$/ ) ) { return "Invalid protocol, choose one of 2013 pre2013"; } readingsSingleUpdate( $hash, "deviceyear", $protocol, 1 ); if ( $protocol eq "pre2013" && ( !exists( $attr{$name}{model} ) || $attr{$name}{model} ne $protocol ) ) { $attr{$name}{model} = $protocol; } # check values if ( !( $zone =~ /^(main|[a-zA-Z]*[1-4]|dock)$/ ) ) { return "Invalid zone, choose one of main zone2 zone3 zone4 dock"; } # set default settings on first define if ($init_done) { $attr{$name}{webCmd} = 'volume:mute:input'; $attr{$name}{devStateIcon} = 'on:rc_GREEN:off off:rc_STOP:on absent:rc_RED'; } $hash->{helper}{receiver} = undef; unless ( exists( $hash->{helper}{AVAILABLE} ) and ( $hash->{helper}{AVAILABLE} == 0 ) ) { $hash->{helper}{AVAILABLE} = 1; readingsSingleUpdate( $hash, "presence", "present", 1 ); } # start the status update timer RemoveInternalTimer($hash); InternalTimer( gettimeofday() + 2, "ONKYO_AVR_PULL_GetStatus", $hash, 0 ); return undef; } ############################################################################################################ # # Begin of helper functions # ############################################################################################################ ################################### sub ONKYO_AVR_PULL_SendCommand($$$) { my ( $hash, $cmd, $value ) = @_; my $name = $hash->{NAME}; my $address = $hash->{helper}{ADDRESS}; my $port = $hash->{helper}{PORT}; my $protocol = $hash->{READINGS}{deviceyear}{VAL}; my $zone = $hash->{ZONE}; my $timeout = 3; my $response; my $response_code; my $return; Log3 $name, 5, "ONKYO_AVR_PULL $name: called function ONKYO_AVR_PULL_SendCommand()"; # Input alias handling if ( $cmd eq "input" ) { # Resolve input alias to correct name if ( defined( $hash->{helper}{receiver}{input_names}{$value} ) ) { $value = $hash->{helper}{receiver}{input_names}{$value}; } # Resolve device specific input alias $value =~ s/_/ /g; if ( defined( $hash->{helper}{receiver}{device}{selectorlist} {selector}{$value}{id} ) ) { $value = uc( $hash->{helper}{receiver}{device}{selectorlist} {selector}{$value}{id} ); } } # Resolve command and value to ISCP raw command my $cmd_raw = ONKYOdb::ONKYO_GetRemotecontrolCommand( $zone, $cmd ); my $value_raw = ONKYOdb::ONKYO_GetRemotecontrolValue( $zone, $cmd_raw, $value ); my $request_code = substr( $cmd_raw, 0, 3 ); if ( !defined($cmd_raw) ) { Log3 $name, 4, "ONKYO_AVR_PULL $name($zone): command '$cmd' is not available within zone '$zone' or command is invalid"; return undef; } if ( !defined($value_raw) ) { Log3 $name, 4, "ONKYO_AVR_PULL $name($zone): $cmd - Warning, value '$value' not found in HASH table, will be sent to receiver 'as is'"; $value_raw = $value; } Log3 $name, 4, "ONKYO_AVR_PULL $name($zone): $cmd -> $value ($cmd_raw$value_raw)"; my $filehandle = IO::Socket::INET->new( PeerAddr => $address, PeerPort => $port, Proto => 'tcp', Timeout => $timeout, ); if ( defined($filehandle) && $cmd_raw ne "" && $value_raw ne "" ) { my $str = ONKYO_AVR_PULL_Pack( $cmd_raw . $value_raw, $protocol ); Log3 $name, 5, "ONKYO_AVR_PULL $name($zone): $address:$port snd " . ONKYO_AVR_PULL_hexdump($str); syswrite $filehandle, $str, length $str; my $start_time = time(); my $readon = 1; do { my $bytes = ONKYO_AVR_PULL_sysreadline( $filehandle, 1, $protocol ); my $line = ONKYO_AVR_PULL_read( $hash, \$bytes ) if ( defined($bytes) && $bytes ne "" ); $response_code = substr( $line, 0, 3 ) if defined($line); if ( defined($response_code) && $response_code eq $request_code ) { $response->{$response_code} = $line; $readon = 0; } elsif ( defined($response_code) ) { $response->{$response_code} = $line; } $readon = 0 if time() > ( $start_time + $timeout ); } while ($readon); # Close socket connections $filehandle->close(); } readingsBeginUpdate($hash); unless ( defined($response) ) { if ( defined( $hash->{helper}{AVAILABLE} ) and $hash->{helper}{AVAILABLE} eq 1 ) { Log3 $name, 3, "ONKYO_AVR_PULL device $name is unavailable"; readingsBulkUpdate( $hash, "presence", "absent" ); } $hash->{helper}{AVAILABLE} = 0; } else { if ( defined( $hash->{helper}{AVAILABLE} ) and $hash->{helper}{AVAILABLE} eq 0 ) { Log3 $name, 3, "ONKYO_AVR_PULL device $name is available"; readingsBulkUpdate( $hash, "presence", "present" ); } $hash->{helper}{AVAILABLE} = 1; # Search for expected answer if ( defined( $response->{$request_code} ) ) { my $_ = substr( $response->{$request_code}, 3 ); # Decode return value # my $values = ONKYOdb::ONKYO_GetRemotecontrolCommandDetails( $zone, $request_code ); # Decode through device information if ( $cmd eq "input" && defined( $hash->{helper}{receiver} ) && ref( $hash->{helper}{receiver} ) eq "HASH" && defined( $hash->{helper}{receiver}{input}{$_} ) ) { Log3 $name, 4, "ONKYO_AVR_PULL $name($zone): $cmd_raw$value_raw return value '$_' converted through device information to '" . $hash->{helper}{receiver}{input}{$_} . "'"; $return = $hash->{helper}{receiver}{input}{$_}; } # Decode through HASH table elsif ( defined( $values->{values}{"$_"}{name} ) ) { if ( ref( $values->{values}{"$_"}{name} ) eq "ARRAY" ) { Log3 $name, 4, "ONKYO_AVR_PULL $name($zone): $cmd_raw$value_raw return value '$_' converted through ARRAY from HASH table to '" . $values->{values}{"$_"}{name}[0] . "'"; $return = $values->{values}{"$_"}{name}[0]; } else { Log3 $name, 4, "ONKYO_AVR_PULL $name($zone): $cmd_raw$value_raw return value '$_' converted through VALUE from HASH table to '" . $values->{values}{"$_"}{name} . "'"; $return = $values->{values}{"$_"}{name}; } } # return as decimal elsif ( m/^[0-9A-Fa-f][0-9A-Fa-f]$/ && $request_code =~ /^(MVL|SLP)$/ ) { Log3 $name, 4, "ONKYO_AVR_PULL $name($zone): $cmd_raw$value_raw return value '$_' converted from HEX to DEC "; $return = ONKYO_AVR_PULL_hex2dec($_); } # just return the original return value if there is # no decoding function elsif ( lc($_) ne "n/a" ) { Log3 $name, 4, "ONKYO_AVR_PULL $name($zone): $cmd_raw$value_raw unconverted return of value '$_'"; $return = $_; } # Log if the command is not supported by the device elsif ( $value_raw ne "QSTN" ) { Log3 $name, 3, "ONKYO_AVR_PULL $name($zone): command $cmd -> $value ($cmd_raw$value_raw) not supported by device"; } } else { Log3 $name, 4, "ONKYO_AVR_PULL $name($zone): No valid response for command '$cmd_raw' during request session of $timeout seconds"; } # Input alias handling if ( $cmd eq "input" && defined($return) && defined( $hash->{helper}{receiver}{input_aliases}{$return} ) ) { Log3 $name, 4, "ONKYO_AVR_PULL $name($zone): $cmd_raw$value_raw aliasing '$return' to '" . $hash->{helper}{receiver}{input_aliases}{$return} . "'"; $return = $hash->{helper}{receiver}{input_aliases}{$return}; } # clear hash to free memory %{$response} = (); return $return; } readingsEndUpdate( $hash, 1 ); return undef; } ################################### sub ONKYO_AVR_PULL_sysreadline($;$$) { my ( $handle, $timeout, $protocol ) = @_; $handle = qualify_to_ref( $handle, caller() ); my $infinitely_patient = ( @_ == 1 || $timeout < 0 ); my $start_time = time(); my $selector = IO::Select->new(); $selector->add($handle); my $line = ""; SLEEP: until ( ONKYO_AVR_PULL_at_eol( $line, $protocol ) ) { unless ($infinitely_patient) { return $line if time() > ( $start_time + $timeout ); } # sleep only 1 second before checking again next SLEEP unless $selector->can_read(1.0); INPUT_READY: while ( $selector->can_read(0.0) ) { my $was_blocking = $handle->blocking(0); CHAR: while ( sysread( $handle, my $nextbyte, 1 ) ) { $line .= $nextbyte; last CHAR if $nextbyte eq "\n"; } $handle->blocking($was_blocking); # if incomplete line, keep trying next SLEEP unless ONKYO_AVR_PULL_at_eol( $line, $protocol ); last INPUT_READY; } } return $line; } ################################### sub ONKYO_AVR_PULL_at_eol($;$) { if ( $_[0] =~ /\r\n\z/ || $_[0] =~ /\r\z/ ) { return 1; } else { return 0; } } ################################### sub ONKYO_AVR_PULL_Undefine($$) { my ( $hash, $arg ) = @_; my $name = $hash->{NAME}; Log3 $name, 5, "ONKYO_AVR_PULL $name: called function ONKYO_AVR_PULL_Undefine()"; # Stop the internal GetStatus-Loop and exit RemoveInternalTimer($hash); return undef; } ################################### sub ONKYO_AVR_PULL_read($$) { my ( $hash, $rbuf ) = @_; my $name = $hash->{NAME}; my $address = $hash->{helper}{ADDRESS}; my $port = $hash->{helper}{PORT}; my $zone = $hash->{ZONE}; return unless ($$rbuf); Log3 $name, 5, "ONKYO_AVR_PULL $name($zone): $address:$port rcv " . ONKYO_AVR_PULL_hexdump($$rbuf); my $length = length $$rbuf; return unless ( $length >= 16 ); my ( $magic, $header_size, $data_size, $version, $res1, $res2, $res3 ) = unpack 'a4 N N C4', $$rbuf; Log3 $name, 5, "ONKYO_AVR_PULL $name: Unexpected magic: expected 'ISCP', got '$magic'" and return unless ( $magic eq 'ISCP' ); return unless ( $length >= $header_size + $data_size ); substr $$rbuf, 0, $header_size, ''; Log3 $name, 5, "ONKYO_AVR_PULL $name: Unexpected version: expected '0x01', got '0x%02x' " . $version unless ( $version == 0x01 ); Log3 $name, 5, "ONKYO_AVR_PULL $name: Unexpected header size: expected '0x10', got '0x%02x' " . $header_size unless ( $header_size == 0x10 ); my $body = substr $$rbuf, 0, $data_size, ''; my $sd = substr $body, 0, 2, ''; $body =~ s/([\032\r\n]|[\032\r]|[\r\n]|[\r])+$//; Log3 $name, 5, "ONKYO_AVR_PULL $name: Unexpected start/destination: expected '!1', got '$sd'" unless ( $sd eq '!1' ); return $body; } ################################### sub ONKYO_AVR_PULL_Pack($;$) { my ( $d, $protocol ) = @_; # ------------------ # < 2013 (produced by TX-NR515) # ------------------ # # EXAMPLE REQUEST FOR PWRQSTN # 4953 4350 0000 0010 0000 000a 0100 0000 ISCP............ # 2131 5057 5251 5354 4e0d !1PWRQSTN. # # EXAMPLE REPLY FOR PWRQSTN # 4953 4350 0000 0010 0000 000a 0100 0000 ISCP............ # 2131 5057 5230 311a 0d0a !1PWR01... # # ------------------ # 2013+ (produced by TX-NR626) # ------------------ # # EXAMPLE REQUEST FOR PWRQSTN # 4953 4350 0000 0010 0000 000b 0100 0000 ISCP............ # 2131 5057 5251 5354 4e0d 0a !1PWRQSTN.. # # EXAMPLE REPLY FOR PWRQSTN # 4953 4350 0000 0010 0000 000a 0100 0000 ISCP............ # 2131 5057 5230 311a 0d0a !1PWR01... # # add start character and destination unit type 1=receiver $d = '!1' . $d; # If protocol is defined as pre-2013 use EOF code for older models if ( defined($protocol) && $protocol eq "pre2013" ) { # = 0x0d $d .= "\r"; } # otherwise use EOF code for newer models else { # = 0x0d0a $d .= "\r\n"; } pack( "a* N N N a*", 'ISCP', 0x10, ( length $d ), 0x01000000, $d ); } ################################### sub ONKYO_AVR_PULL_hexdump { my $s = shift; my $r = unpack 'H*', $s; $s =~ s/[^ -~]/./g; $r . ' ' . $s; } ################################### sub ONKYO_AVR_PULL_hex2dec($) { my ($hex) = @_; return unpack( 's', pack 's', hex($hex) ); } ################################### sub ONKYO_AVR_PULL_dec2hex($) { my ($dec) = @_; my $hex = uc( sprintf( "%x", $dec ) ); return "0" . $hex if ( length($hex) eq 1 ); return $hex; } ##################################### # Callback from 95_remotecontrol for command makenotify. sub ONKYO_AVR_PULL_RCmakenotify($$) { my ( $name, $ndev ) = @_; my $nname = "notify_$name"; fhem( "define $nname notify $name set $ndev remoteControl " . '$EVENT', 1 ); Log3 undef, 2, "[remotecontrol:ONKYO_AVR_PULL] Notify created: $nname"; return "Notify created by ENIGMA2: $nname"; } ##################################### # RC layouts sub ONKYO_AVR_PULL_RClayout_SVG() { my @row; $row[0] = ":rc_BLANK.svg,:rc_BLANK.svg,power toggle:rc_POWER.svg"; $row[1] = ":rc_BLANK.svg,:rc_BLANK.svg,:rc_BLANK.svg"; $row[2] = "1:rc_1.svg,2:rc_2.svg,3:rc_3.svg"; $row[3] = "4:rc_4.svg,5:rc_5.svg,6:rc_6.svg"; $row[4] = "7:rc_7.svg,8:rc_8.svg,9:rc_9.svg"; $row[5] = ":rc_BLANK.svg,0:rc_0.svg,:rc_BLANK.svg"; $row[6] = ":rc_BLANK.svg,:rc_BLANK.svg,:rc_BLANK.svg"; $row[7] = "VOLUMEUP:rc_VOLPLUS.svg,MUTE:rc_MUTE.svg,CHANNELUP:rc_UP.svg"; $row[8] = "VOLUMEDOWN:rc_VOLMINUS.svg,EXIT:rc_EXIT.svg,CHANNELDOWN:rc_DOWN.svg"; $row[9] = ":rc_BLANK.svg,:rc_BLANK.svg,:rc_BLANK.svg"; $row[10] = "INFO:rc_INFO.svg,UP:rc_UP.svg,MENU:rc_MENU.svg"; $row[11] = "LEFT:rc_LEFT.svg,OK:rc_OK.svg,RIGHT:rc_RIGHT.svg"; $row[12] = "AUDIO:rc_AUDIO.svg,DOWN:rc_DOWN.svg,VIDEO:rc_VIDEO.svg"; $row[13] = ":rc_BLANK.svg,EXIT:rc_EXIT.svg,:rc_BLANK.svg"; $row[14] = "RED:rc_REWred.svg,GREEN:rc_PLAYgreen.svg,YELLOW:rc_PAUSEyellow.svg,BLUE:rc_FFblue.svg"; $row[15] = "TV:rc_TVstop.svg,RADIO:rc_RADIOred.svg,TEXT:rc_TEXT.svg,HELP:rc_HELP.svg"; $row[16] = "attr rc_iconpath icons/remotecontrol"; $row[17] = "attr rc_iconprefix black_btn_"; return @row; } sub ONKYO_AVR_PULL_RClayout() { my @row; $row[0] = ":blank,:blank,power toggle:POWEROFF"; $row[1] = ":blank,:blank,:blank"; $row[2] = "1,2,3"; $row[3] = "4,5,6"; $row[4] = "7,8,9"; $row[5] = ":blank,0:0,:blank"; $row[6] = ":blank,:blank,:blank"; $row[7] = "VOLUMEUP:VOLUP,MUTE,CHANNELUP:CHUP2"; $row[8] = "VOLUMEDOWN:VOLDOWN,EXIT,CHANNELDOWN:CHDOWN2"; $row[9] = ":blank,:blank,:blank"; $row[10] = "INFO,UP,MENU"; $row[11] = "LEFT,OK,RIGHT"; $row[12] = "AUDIO,DOWN,VIDEO"; $row[13] = ":blank,:blank,:blank"; $row[14] = "RED:REWINDred,GREEN:PLAYgreen,YELLOW:PAUSEyellow,BLUE:FFblue"; $row[15] = "TV:TVstop,RADIO:RADIOred,TEXT,HELP"; $row[16] = "attr rc_iconpath icons/remotecontrol"; $row[17] = "attr rc_iconprefix black_btn_"; return @row; } 1; =pod =item device =begin html

ONKYO_AVR_PULL

    Define
      define <name> ONKYO_AVR_PULL <ip-address-or-hostname> [<protocol-version>] [<zone>] [<poll-interval>]

      This module controls ONKYO A/V receivers via network connection.

      Defining an ONKYO device will schedule an internal task (interval can be set with optional parameter <poll-interval> in seconds, if not set, the value is 75 seconds), which periodically reads the status of the device and triggers notify/filelog commands.

      Example:
        define avr ONKYO_AVR_PULL 192.168.0.10

        # With explicit protocol version 2013 and later
        define avr ONKYO_AVR_PULL 192.168.0.10 2013

        # With protocol version prior 2013
        define avr ONKYO_AVR_PULL 192.168.0.10 pre2013

        # With zone2
        define avr ONKYO_AVR_PULL 192.168.0.10 pre2013 zone2

        # With custom interval of 60 seconds
        define avr ONKYO_AVR_PULL 192.168.0.10 pre2013 main 60

        # With zone2 and custom interval of 60 seconds
        define avr ONKYO_AVR_PULL 192.168.0.10 pre2013 zone2 60


    Set
      set <name> <command> [<parameter>]

      Currently, the following commands are defined (may vary depending on zone).
      • on   -   powers on the device
      • off   -   turns the device in standby mode
      • sleep 1..90,off   -   sets auto-turnoff after X minutes
      • toggle   -   switch between on and off
      • volume 0...100   -   set the volume level in percentage
      • volumeUp   -   increases the volume level
      • volumeDown   -   decreases the volume level
      • mute on,off   -   controls volume mute
      • input   -   switches between inputs
      • statusRequest   -   requests the current status of the device
      • remoteControl   -   sends remote control commands; see remoteControl help
        Note: If you would like to restrict access to admin set-commands (-> statusRequest, remoteControl) you may set your FHEMWEB instance's attribute allowedCommands like 'set,set-user'. The string 'set-user' will ensure only non-admin set-commands can be executed when accessing FHEM using this FHEMWEB instance.


    Get
      get <name> <what>

      Currently, the following commands are defined (may vary depending on zone):

        power
        input
        volume
        mute
        sleep


    Generated Readings/Events (may vary depending on zone):
    • input - Shows currently used input; part of FHEM-4-AV-Devices compatibility
    • mute - Reports the mute status of the device (can be "on" or "off")
    • power - Reports the power status of the device (can be "on" or "off")
    • presence - Reports the presence status of the receiver (can be "absent" or "present"). In case of an absent device, control is not possible.
    • sleep - Reports current sleep state (can be "off" or shows timer in minutes)
    • state - Reports current power state and an absence of the device (can be "on", "off" or "absent")
    • volume - Reports current volume level of the receiver in percentage values (between 0 and 100 %)
=end html =begin html_DE

ONKYO_AVR_PULL

    Eine deutsche Version der Dokumentation ist derzeit nicht vorhanden. Die englische Version ist hier zu finden:
=end html_DE =cut