# $Id$
##############################################################################
#
# 71_YAMAHA_BD.pm
# An FHEM Perl module for controlling Yamaha Blu-Ray players
# via network connection. As the interface is standardized
# within all Yamaha Blue-Ray players, this module should work
# with any player which has an ethernet or wlan connection.
#
# Copyright by Markus Bloch
# e-mail: Notausstieg0309@googlemail.com
#
# This file is part of fhem.
#
# Fhem is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# Fhem is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with fhem. If not, see .
#
##############################################################################
package main;
use strict;
use warnings;
use Time::HiRes qw(gettimeofday);
use HttpUtils;
sub YAMAHA_BD_Get($@);
sub YAMAHA_BD_Define($$);
sub YAMAHA_BD_GetStatus($;$);
sub YAMAHA_BD_Attr(@);
sub YAMAHA_BD_ResetTimer($;$);
sub YAMAHA_BD_Undefine($$);
###################################
sub
YAMAHA_BD_Initialize($)
{
my ($hash) = @_;
$hash->{GetFn} = "YAMAHA_BD_Get";
$hash->{SetFn} = "YAMAHA_BD_Set";
$hash->{DefFn} = "YAMAHA_BD_Define";
$hash->{AttrFn} = "YAMAHA_BD_Attr";
$hash->{UndefFn} = "YAMAHA_BD_Undefine";
$hash->{AttrList} = "do_not_notify:0,1 disable:0,1 request-timeout:1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20 model ".
$readingFnAttributes;
$data{RC_layout}{YAMAHA_BluRay} = "YAMAHA_BD_RClayout";
$data{RC_makenotify}{YAMAHA_BD} = "YAMAHA_BD_RCmakenotify";
}
###################################
sub
YAMAHA_BD_Define($$)
{
my ($hash, $def) = @_;
my @a = split("[ \t][ \t]*", $def);
my $name = $hash->{NAME};
if(! @a >= 3)
{
my $msg = "wrong syntax: define YAMAHA_BD [] []";
Log 2, $msg;
return $msg;
}
my $address = $a[2];
$hash->{helper}{ADDRESS} = $address;
# if an update interval was given which is greater than zero, use it.
if(defined($a[3]) and $a[3] > 0)
{
$hash->{helper}{OFF_INTERVAL}=$a[3];
}
else
{
$hash->{helper}{OFF_INTERVAL}=30;
}
# if a second update interval is given, use this as ON_INTERVAL, otherwise use OFF_INTERVAL instead.
if(defined($a[4]) and $a[4] > 0)
{
$hash->{helper}{ON_INTERVAL}=$a[4];
}
else
{
$hash->{helper}{ON_INTERVAL}=$hash->{helper}{OFF_INTERVAL};
}
$hash->{helper}{CMD_QUEUE} = ();
delete($hash->{helper}{HTTP_CONNECTION}) if(exists($hash->{helper}{HTTP_CONNECTION}));
# start the status update timer
$hash->{helper}{DISABLED} = 0 unless(exists($hash->{helper}{DISABLED}));
YAMAHA_BD_ResetTimer($hash, 2);
return undef;
}
###################################
sub
YAMAHA_BD_Get($@)
{
my ($hash, @a) = @_;
my $what;
my $return;
return "argument is missing" if(int(@a) != 2);
$what = $a[1];
if(exists($hash->{READINGS}{$what}))
{
YAMAHA_BD_GetStatus($hash, 1);
if(defined($hash->{READINGS}{$what}))
{
return $hash->{READINGS}{$what}{VAL};
}
else
{
return "no such reading: $what";
}
}
else
{
$return = "unknown argument $what, choose one of";
foreach my $reading (keys %{$hash->{READINGS}})
{
$return .= " $reading:noArg";
}
return $return;
}
}
###################################
sub
YAMAHA_BD_Set($@)
{
my ($hash, @a) = @_;
my $name = $hash->{NAME};
my $address = $hash->{helper}{ADDRESS};
my $result = "";
return "No Argument given" if(!defined($a[1]));
my $what = $a[1];
my $usage = "Unknown argument $what, choose one of on:noArg ".
"off:noArg ".
"statusRequest:noArg ".
"tray:open,close ".
"remoteControl:power,eject,up,down,left,right,return,enter,OSDonScreen,OSDstatus,topMenu,popupMenu,red,green,blue,yellow,0,1,2,3,4,5,6,7,8,9,setup,home,clear,program,search,repeat,repeat-AB,subtitle,angle,audio,pictureInPicture,secondVideo,secondAudio".(exists($hash->{MODEL}) && $hash->{MODEL} eq "BD-S673" ? ",netflix" : "")." ".
"fast:forward,reverse ".
"slow:forward,reverse ".
"skip:forward,reverse ".
"play:noArg pause:noArg ".
"stop:noArg ".
"trickPlay:normal,repeatChapter,repeatTitle,repeatFolder,repeat-AB,randomChapter,randomTitle,randomAll,shuffleChapter,shuffleTitle,shuffleAll,setApoint";
if($what eq "on")
{
YAMAHA_BD_SendCommand($hash, "On","on",undef);
}
elsif($what eq "off")
{
YAMAHA_BD_SendCommand($hash, "Network Standby","off",undef);
}
elsif($what eq "remoteControl")
{
if($a[2] eq "up")
{
YAMAHA_BD_SendCommand($hash, "Up","remoteControl","up");
}
elsif($a[2] eq "down")
{
YAMAHA_BD_SendCommand($hash, "Down","remoteControl","down");
}
elsif($a[2] eq "left")
{
YAMAHA_BD_SendCommand($hash, "Left","remoteControl","left");
}
elsif($a[2] eq "right")
{
YAMAHA_BD_SendCommand($hash, "Right","remoteControl","right");
}
elsif($a[2] eq "enter")
{
YAMAHA_BD_SendCommand($hash, "Enter","remoteControl","enter");
}
elsif($a[2] eq "return")
{
YAMAHA_BD_SendCommand($hash, "Return","remoteControl","return");
}
elsif($a[2] eq "OSDonScreen")
{
YAMAHA_BD_SendCommand($hash,"OnScreen","remoteControl","OSDonScreen");
}
elsif($a[2] eq "OSDstatus")
{
YAMAHA_BD_SendCommand($hash,"Status","remoteControl","OSDstatus");
}
elsif($a[2] eq "topMenu")
{
YAMAHA_BD_SendCommand($hash,"","remoteControl","topMenu");
}
elsif($a[2] eq "popupMenu")
{
YAMAHA_BD_SendCommand($hash,"","remoteControl","popupMenu");
}
elsif($a[2] eq "red")
{
YAMAHA_BD_SendCommand($hash,"RED","remoteControl","red");
}
elsif($a[2] eq "green")
{
YAMAHA_BD_SendCommand($hash,"GREEN","remoteControl","green");
}
elsif($a[2] eq "blue")
{
YAMAHA_BD_SendCommand($hash,"BLUE","remoteControl","blue");
}
elsif($a[2] eq "yellow")
{
YAMAHA_BD_SendCommand($hash,"YELLOW","remoteControl","yellow");
}
elsif($a[2] eq "0")
{
YAMAHA_BD_SendCommand($hash,"0","remoteControl","0");
}
elsif($a[2] eq "1")
{
YAMAHA_BD_SendCommand($hash,"1","remoteControl","1");
}
elsif($a[2] eq "2")
{
YAMAHA_BD_SendCommand($hash,"2","remoteControl","2");
}
elsif($a[2] eq "3")
{
YAMAHA_BD_SendCommand($hash,"3","remoteControl","3");
}
elsif($a[2] eq "4")
{
YAMAHA_BD_SendCommand($hash,"4","remoteControl","4");
}
elsif($a[2] eq "5")
{
YAMAHA_BD_SendCommand($hash,"5","remoteControl","5");
}
elsif($a[2] eq "6")
{
YAMAHA_BD_SendCommand($hash,"6","remoteControl","6");
}
elsif($a[2] eq "7")
{
YAMAHA_BD_SendCommand($hash,"7","remoteControl","7");
}
elsif($a[2] eq "8")
{
YAMAHA_BD_SendCommand($hash,"8","remoteControl","8");
}
elsif($a[2] eq "9")
{
YAMAHA_BD_SendCommand($hash,"9","remoteControl","9");
}
elsif($a[2] eq "setup")
{
YAMAHA_BD_SendCommand($hash,"SETUP","remoteControl","setup");
}
elsif($a[2] eq "home")
{
YAMAHA_BD_SendCommand($hash,"HOME","remoteControl","home");
}
elsif($a[2] eq "clear")
{
YAMAHA_BD_SendCommand($hash,"CLEAR","remoteControl","clear");
}
elsif($a[2] eq "program")
{
YAMAHA_BD_SendCommand($hash,"PROGRAM","remoteControl","program");
}
elsif($a[2] eq "search")
{
YAMAHA_BD_SendCommand($hash,"7C9E","remoteControl","search");
}
elsif($a[2] eq "repeat")
{
YAMAHA_BD_SendCommand($hash,"7CA3","remoteControl","repeat");
}
elsif($a[2] eq "repeat-AB")
{
YAMAHA_BD_SendCommand($hash,"7CA4","remoteControl","repeat-AB");
}
elsif($a[2] eq "subtitle")
{
YAMAHA_BD_SendCommand($hash,"SUBTITLE","remoteControl","subtitle");
}
elsif($a[2] eq "angle")
{
YAMAHA_BD_SendCommand($hash,"ANGLE","remoteControl","angle");
}
elsif($a[2] eq "audio")
{
YAMAHA_BD_SendCommand($hash,"AUDIO","remoteControl","audio");
}
elsif($a[2] eq "pictureInPicture")
{
YAMAHA_BD_SendCommand($hash,"PinP","remoteControl","pictureInPicture");
}
elsif($a[2] eq "secondVideo")
{
YAMAHA_BD_SendCommand($hash,"2nd Video","remoteControl","secondVideo");
}
elsif($a[2] eq "secondAudio")
{
YAMAHA_BD_SendCommand($hash,"2nd Audio","remoteControl","secondAudio");
}
elsif($a[2] eq "power")
{
YAMAHA_BD_SendCommand($hash,"7C80","remoteControl","power");
}
elsif($a[2] eq "eject")
{
YAMAHA_BD_SendCommand($hash,"7C81","remoteControl","eject");
}
elsif($a[2] eq "netflix")
{
YAMAHA_BD_SendCommand($hash,"7CFA","remoteControl","netflix");
}
else
{
return $usage;
}
}
elsif($what eq "trickPlay")
{
if($a[2] eq "normal")
{
YAMAHA_BD_SendCommand($hash,"Normal","trickPlay","normal");
}
elsif($a[2] eq "repeatChapter")
{
YAMAHA_BD_SendCommand($hash,"Repeat Chapter/Track/File","trickPlay","repeatChapter");
}
elsif($a[2] eq "repeatTitle")
{
YAMAHA_BD_SendCommand($hash,"Repeat Title","trickPlay","repeatTitle");
}
elsif($a[2] eq "repeatFolder")
{
YAMAHA_BD_SendCommand($hash,"Repeat Folder","trickPlay","repeatFolder");
}
elsif($a[2] eq "randomChapter")
{
YAMAHA_BD_SendCommand($hash,"Random Chapter/Track/File","trickPlay","randomChapter");
}
elsif($a[2] eq "randomTitle")
{
YAMAHA_BD_SendCommand($hash,"Random title","trickPlay","randomTitle");
}
elsif($a[2] eq "randomAll")
{
YAMAHA_BD_SendCommand($hash,"Random All","trickPlay","randomAll");
}
elsif($a[2] eq "shuffleChapter")
{
YAMAHA_BD_SendCommand($hash,"Shuffle Chapter/Track/File","trickPlay","shuffleChapter");
}
elsif($a[2] eq "shuffleTitle")
{
YAMAHA_BD_SendCommand($hash,"Shuffle title","trickPlay","shuffleTitle");
}
elsif($a[2] eq "shuffleAll")
{
YAMAHA_BD_SendCommand($hash,"Shuffle All","trickPlay","shuffleAll");
}
elsif($a[2] eq "setApoint")
{
YAMAHA_BD_SendCommand($hash,"SetA point","trickPlay","setApoint");
}
elsif($a[2] eq "repeat-AB")
{
YAMAHA_BD_SendCommand($hash,"A-B Repeat","trickPlay","ABrepeat");
}
else
{
return $usage;
}
}
elsif($what eq "tray")
{
if($a[2] eq "open")
{
YAMAHA_BD_SendCommand($hash, "Open","tray","open");
}
elsif($a[2] eq "close")
{
YAMAHA_BD_SendCommand($hash, "Close","tray","close");
}
}
elsif($what eq "skip")
{
if($a[2] eq "forward")
{
YAMAHA_BD_SendCommand($hash, "Fwd","skip","forward");
}
elsif($a[2] eq "reverse")
{
YAMAHA_BD_SendCommand($hash, "Rev","skip","reverse");
}
}
elsif($what eq "fast")
{
if($a[2] eq "forward")
{
YAMAHA_BD_SendCommand($hash, "Fwd","fast","forward");
}
elsif($a[2] eq "reverse")
{
YAMAHA_BD_SendCommand($hash, "Rev","fast","reverse");
}
}
elsif($what eq "slow")
{
if($a[2] eq "forward")
{
YAMAHA_BD_SendCommand($hash, "Fwd","slow","forward");
}
elsif($a[2] eq "reverse")
{
YAMAHA_BD_SendCommand($hash, "Rev","slow","reverse");
}
}
elsif($what eq "play")
{
YAMAHA_BD_SendCommand($hash, "Play","play", undef);
}
elsif($what eq "pause")
{
YAMAHA_BD_SendCommand($hash, "Pause","pause", undef);
}
elsif($what eq "stop")
{
YAMAHA_BD_SendCommand($hash, "Stop", "play",undef);
}
elsif($what eq "statusRequest")
{
YAMAHA_BD_GetStatus($hash, 1);
}
else
{
return $usage;
}
return undef;
}
###################################
sub
YAMAHA_BD_Attr(@)
{
my @a = @_;
my $hash = $defs{$a[1]};
if($a[0] eq "set" && $a[2] eq "disable")
{
if($a[3] eq "0")
{
$hash->{helper}{DISABLED} = 0;
YAMAHA_BD_GetStatus($hash, 1);
}
elsif($a[3] eq "1")
{
$hash->{helper}{DISABLED} = 1;
}
}
elsif($a[0] eq "del" && $a[2] eq "disable")
{
$hash->{helper}{DISABLED} = 0;
YAMAHA_BD_GetStatus($hash, 1);
}
# Start/Stop Timer according to new disabled-Value
YAMAHA_BD_ResetTimer($hash);
return undef;
}
###################################
sub
YAMAHA_BD_Undefine($$)
{
my($hash, $name) = @_;
# Stop the internal GetStatus-Loop and exit
RemoveInternalTimer($hash);
return undef;
}
#############################################################################################################
#
# Begin of helper functions
#
############################################################################################################
###################################
# get status of player
sub
YAMAHA_BD_GetStatus($;$)
{
my ($hash, $local) = @_;
my $name = $hash->{NAME};
my $power;
my $return;
$local = 0 unless(defined($local));
return "" if(!defined($hash->{helper}{ADDRESS}) or !defined($hash->{helper}{ON_INTERVAL}) or !defined($hash->{helper}{OFF_INTERVAL}));
# get the model informations if no informations are available
if((not defined($hash->{MODEL})) or (not defined($hash->{FIRMWARE})))
{
YAMAHA_BD_SendCommand($hash, "GetParam", "statusRequest","systemConfig");
}
Log3 $name, 4, "YAMAHA_BD ($name) - Requesting system status";
YAMAHA_BD_SendCommand($hash, "GetParam", "statusRequest","systemStatus");
Log3 $name, 4, "YAMAHA_BD ($name) - Requesting input info";
YAMAHA_BD_SendCommand($hash, "GetParam", "statusRequest","inputInfo");
Log3 $name, 4, "YAMAHA_BD ($name) - Requesting power state";
YAMAHA_BD_SendCommand($hash, "GetParam", "statusRequest","powerStatus");
Log3 $name, 4, "YAMAHA_BD ($name) - Requesting playing info";
YAMAHA_BD_SendCommand($hash, "GetParam", "statusRequest","playInfo");
Log3 $name, 4, "YAMAHA_BD ($name) - Requesting trickPlay info";
YAMAHA_BD_SendCommand($hash, "GetParam", "statusRequest","trickPlayInfo");
# Reset timer if this is not a local run
YAMAHA_BD_ResetTimer($hash) unless($local == 1);
}
#############################
# pushes new command to cmd queue
sub
YAMAHA_BD_SendCommand($$$$)
{
my ($hash, $data,$cmd,$arg) = @_;
my $name = $hash->{NAME};
my $response;
Log3 $name, 4, "YAMAHA_BD ($name) - append to queue \"$cmd".(defined($arg) ? " ".$arg : "")."\": $data";
# In case any URL changes must be made, this part is separated in this function".
my $param = {
data => "".$data,
cmd => $cmd,
arg => $arg,
};
push @{$hash->{helper}{CMD_QUEUE}}, $param;
YAMAHA_BD_HandleCmdQueue($hash);
}
#############################
# starts http requests from cmd queue
sub
YAMAHA_BD_HandleCmdQueue($)
{
my ($hash) = @_;
my $name = $hash->{NAME};
my $address = $hash->{helper}{ADDRESS};
if(not($hash->{helper}{RUNNING_REQUEST}) and @{$hash->{helper}{CMD_QUEUE}})
{
my $params = {
url => "http://".$address.":50100/YamahaRemoteControl/ctrl",
timeout => AttrVal($name, "request-timeout", 4),
noshutdown => 1,
keepalive => 0,
httpversion => "1.0",
loglevel => ($hash->{helper}{AVAILABLE} ? undef : 5),
hash => $hash,
callback => \&YAMAHA_BD_ParseResponse
};
my $request = pop @{$hash->{helper}{CMD_QUEUE}};
map {$hash->{helper}{HTTP_CONNECTION}{$_} = $params->{$_}} keys %{$params};
map {$hash->{helper}{HTTP_CONNECTION}{$_} = $request->{$_}} keys %{$request};
$hash->{helper}{RUNNING_REQUEST} = 1;
Log3 $name, 4, "YAMAHA_BD ($name) - send command \"$request->{cmd}".(defined($request->{arg}) ? " ".$request->{arg} : "")."\": $request->{data}";
HttpUtils_NonblockingGet($hash->{helper}{HTTP_CONNECTION});
}
}
#############################
# parses all HTTP response and creates events/readings
sub
YAMAHA_BD_ParseResponse($$$)
{
my ( $param, $err, $data ) = @_;
my $hash = $param->{hash};
my $name = $hash->{NAME};
my $cmd = $param->{cmd};
my $arg = $param->{arg};
$data = "" unless(defined($data));
$err = "" unless(defined($err));
$hash->{helper}{RUNNING_REQUEST} = 0;
delete($hash->{helper}{HTTP_CONNECTION}) unless($param->{keepalive});
if($err ne "")
{
Log3 $name, 4, "YAMAHA_BD ($name) - error while executing \"$cmd".(defined($arg) ? " ".$arg : "")."\": $err";
if((not exists($hash->{helper}{AVAILABLE})) or (exists($hash->{helper}{AVAILABLE}) and $hash->{helper}{AVAILABLE} eq 1))
{
Log3 $name, 3, "YAMAHA_BD ($name) - could not execute command on device $name. Please turn on your device in case of deactivated network standby or check for correct hostaddress: $err";
readingsSingleUpdate($hash, "presence", "absent", 1);
readingsSingleUpdate($hash, "state", "absent", 1);
}
}
elsif($data ne "")
{
Log3 $name, 5, "YAMAHA_BD ($name) - got HTTP response for \"$cmd".(defined($arg) ? " ".$arg : "")."\": $data";
if (defined($hash->{helper}{AVAILABLE}) and $hash->{helper}{AVAILABLE} == 0)
{
Log3 $name, 3, "YAMAHA_BD: device $name reappeared";
readingsSingleUpdate($hash, "presence", "present", 1);
}
readingsBeginUpdate($hash);
if(not $data =~ /RC="0"/)
{
# if the returncode isn't 0, than the command was not successful
Log3 $name, 3, "YAMAHA_BD ($name) - Could not execute \"$cmd".(defined($arg) ? " ".$arg : "")."\"";
}
if($cmd eq "statusRequest" and $arg eq "systemStatus")
{
if($data =~ /(.+?)<\/Error_Info>/)
{
readingsBulkUpdate($hash, "error", lc($1));
}
}
elsif($cmd eq "statusRequest" and $arg eq "systemConfig")
{
if($data =~ /(.+?)<\/Model_Name>.*(.+?)<\/Version>/)
{
$hash->{MODEL} = $1;
$hash->{FIRMWARE} = $2;
$hash->{MODEL} =~ s/\s*YAMAHA\s*//g;
$attr{$name}{"model"} = $hash->{MODEL};
}
}
elsif($cmd eq "statusRequest" and $arg eq "powerStatus")
{
if($data =~ /(.+?)<\/Power>/)
{
my $power = $1;
if($power eq "Standby" or $power eq "Network Standby")
{
$power = "off";
}
readingsBulkUpdate($hash, "power", lc($power));
readingsBulkUpdate($hash, "state", lc($power));
}
}
elsif($cmd eq "on")
{
if($data =~ /RC="0"/ and $data =~ /<\/Power>/)
{
# As the player startup takes about 5 seconds, the status will be already set, if the return code of the command is 0.
readingsBulkUpdate($hash, "power", "on");
readingsBulkUpdate($hash, "state","on");
}
else
{
Log3 $name, 3, "YAMAHA_BD ($name) - Could not set power to on";
}
}
elsif($cmd eq "statusRequest" and $arg eq "trickPlayInfo")
{
if($data =~ /(.+?)<\/Trick_Play>/)
{
readingsBulkUpdate($hash, "trickPlay", $1);
}
}
elsif($cmd eq "statusRequest" and $arg eq "inputInfo")
{
if($data =~ /(.+?)<\/Status><\/Input_Info/)
{
readingsBulkUpdate($hash, "input", $1);
}
elsif($data =~ /(.+?)<\/Input_Info/)
{
readingsBulkUpdate($hash, "input", $1);
}
}
elsif($cmd eq "statusRequest" and $arg eq "playInfo")
{
if($data =~ /(.+?)<\/Status>/)
{
readingsBulkUpdate($hash, "playStatus", lc($1));
}
if($data =~ /.*?(.+?)<\/Chapter>.*?<\/Contents>/)
{
readingsBulkUpdate($hash, "currentChapter", $1);
}
if($data =~ /.*?