# $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