# $Id$
##############################################################################
#
# 70_BRAVIA.pm
# An FHEM Perl module for controlling Sony Televisons
# via network connection. Supported are models with release date starting from 2011.
#
# Copyright by Ulf von Mersewsky
# e-mail: umersewsky 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 .
#
##############################################################################
#
# 05.03.2020 Sandro Gertz: add "requestReboot"
#
##############################################################################
package FHEM::BRAVIA;
use strict;
use warnings;
use POSIX;
use GPUtils qw(:all); # wird für den Import der FHEM Funktionen aus der fhem.pl benötigt
use Data::Dumper;
use Time::HiRes qw(gettimeofday);
use Time::Local;
use Encode;
use JSON qw(decode_json);
use MIME::Base64;
use XML::Simple qw(:strict);
use IO::Socket;
require HttpUtils;
## Import der FHEM Funktionen
BEGIN {
GP_Import(qw(
AttrVal
createUniqueId
fhem
fhemTimeLocal
InternalTimer
InternalVal
Log3
readingFnAttributes
ReadingsAge
readingsBeginUpdate
readingsBulkUpdate
readingsBulkUpdateIfChanged
readingsDelete
readingsSingleUpdate
readingsEndUpdate
ReadingsNum
ReadingsTimestamp
ReadingsVal
RemoveInternalTimer
))
};
GP_Export(
qw(
Initialize
)
);
###################################
sub Initialize {
my ($hash) = @_;
Log3($hash, 5, "BRAVIA_Initialize: Entering");
$hash->{GetFn} = \&Get;
$hash->{SetFn} = \&Set;
$hash->{DefFn} = \&Define;
$hash->{UndefFn} = \&Undefine;
$hash->{AttrList} =
"disable:0,1 "
. "macaddr:textField "
. "channelsMax:textField "
. "wolBroadcast:textField "
. $readingFnAttributes;
$::data{RC_layout}{BRAVIA_SVG} = \&RClayout_SVG;
$::data{RC_layout}{BRAVIA} = \&RClayout;
$::data{RC_makenotify}{BRAVIA} = \&RCmakenotify;
return;
}
###################################
sub Define {
my ( $hash, $def ) = @_;
my @a = split( "[ \t][ \t]*", $def );
my $name = $hash->{NAME};
Log3($name, 5, "BRAVIA $name: called function Define()");
if ( int(@a) < 3 ) {
my $msg =
"Wrong syntax: define BRAVIA []";
Log3($name, 4, $msg);
return $msg;
}
$hash->{TYPE} = "BRAVIA";
my $address = $a[2];
$hash->{helper}{ADDRESS} = $address;
# use interval of 45 sec if not defined
my $interval = $a[3] || 45;
$hash->{INTERVAL} = $interval;
# number of channels read from channellist, maximum 50
my $channelCount = 50;
$hash->{CHANNELCOUNT} = $channelCount;
$hash->{helper}{PORT} = {
'IRCC' => "80",
'SERVICE' => "80",
'UPNP' => "52323",
};
$hash->{helper}{HEADER} = 'X-CERS-DEVICE-ID: fhem_remote';
unless ( defined( AttrVal( $name, "webCmd", undef ) ) ) {
$::attr{$name}{webCmd} = 'volume:channelUp:channelDown';
}
unless ( defined( AttrVal( $name, "devStateIcon", undef ) ) ) {
$::attr{$name}{devStateIcon} =
'on:rc_GREEN:off off:rc_YELLOW:on absent:rc_STOP:on';
}
unless ( defined( AttrVal( $name, "icon", undef ) ) ) {
$::attr{$name}{icon} = 'it_television';
}
# start the status update timer
RemoveInternalTimer($hash);
InternalTimer( gettimeofday() + 2, \&GetStatus, $hash, 1 );
return;
}
###################################
sub Undefine {
my ( $hash, $arg ) = @_;
my $name = $hash->{NAME};
Log3($name, 5, "BRAVIA $name: called function Undefine()");
# Stop the internal GetStatus-Loop and exit
RemoveInternalTimer($hash);
return;
}
#####################################
sub GetStatus {
my ( $hash, $update ) = @_;
my $name = $hash->{NAME};
my $interval = $hash->{INTERVAL};
Log3($name, 5, "BRAVIA $name: called function GetStatus()");
RemoveInternalTimer($hash);
InternalTimer( gettimeofday() + $interval, \&GetStatus, $hash, 0 );
return if ( AttrVal($name, "disable", 0) == 1 );
# check device availability
if (!$update) {
SendCommand( $hash, "getStatus", "xml" )
if (ReadingsVal($name, "requestFormat", "xml") eq "xml");
SendCommand( $hash, "getStatus", "json" )
if (ReadingsVal($name, "requestFormat", "json") eq "json");
}
return;
}
###################################
sub Get {
my ( $hash, @a ) = @_;
my $name = $hash->{NAME};
my $what;
Log3($name, 5, "BRAVIA $name: called function Get()");
return "argument is missing" if ( int(@a) < 2 );
$what = $a[1];
if ( $what =~ /^(power|presence|input|channel|volume|mute)$/xms ) {
my $value = ReadingsVal($name, $what, "");
if ($value ne "") {
return $value;
}
else {
return "no such reading: $what";
}
}
else {
return
"Unknown argument $what, choose one of power:noArg presence:noArg input:noArg channel:noArg volume:noArg mute:noArg";
}
}
###################################
sub Set {
my ( $hash, @a ) = @_;
my $name = $hash->{NAME};
my $power = ReadingsVal($name, "power", "");
my $presence = ReadingsVal($name, "presence", "");
my $channel = ReadingsVal($name, "channel", "");
my $channelId = ReadingsVal($name, "channelId", "");
my $channels = "";
my $inputs = "";
my $apps = "";
my $mutes = "toggle";
if ( ReadingsVal($name, "input", "") ne "-" ) {
$hash->{helper}{lastInput} = ReadingsVal($name, "input", "");
} elsif ( !defined( $hash->{helper}{lastInput} ) ) {
$hash->{helper}{lastInput} = "";
}
my $input = $hash->{helper}{lastInput};
Log3($name, 5, "BRAVIA $name: called function Set()");
return "No Argument given" if ( !defined( $a[1] ) );
# Input handling
my @inputs;
if ( defined( $hash->{helper}{device}{inputPreset} )
&& ref( $hash->{helper}{device}{inputPreset} ) eq "HASH" ) {
@inputs = keys %{ $hash->{helper}{device}{inputPreset} };
}
@inputs = sort(@inputs);
$inputs = join(",", @inputs);
# load channel list
my @channels;
if ( defined( $hash->{helper}{device}{channelPreset} )
&& ref( $hash->{helper}{device}{channelPreset} ) eq "HASH" )
{
my $count = 0;
my @keys = keys %{ $hash->{helper}{device}{channelPreset} };
@keys = sort(@keys);
my $maxChannels = (@keys < AttrVal($name, "channelsMax", 50) ? @keys : AttrVal($name, "channelsMax", 50));
for (my $i = 0; $i < $maxChannels; $i++) {
my $preset = $keys[$i];
if ( $hash->{helper}{device}{channelPreset}{$preset}{name}
&& $hash->{helper}{device}{channelPreset}{$preset}{name} ne ""
&& $hash->{helper}{device}{channelPreset}{$preset}{name} ne "-"
&& $hash->{helper}{device}{channelPreset}{$preset}{id} ne "-" ) {
push(
@channels,
$hash->{helper}{device}{channelPreset}{$preset}{id}.":".$hash->{helper}{device}{channelPreset}{$preset}{name});
}
}
}
if ( $channel ne "" && $channel ne "-" && $channelId ne "-" ) {
my $currentChannel = $channelId . ":" . $channel;
my @matches = grep {"/".$currentChannel."/"} @channels ;
push( @channels, $currentChannel ) if ( ( scalar @matches ) eq "0" );
}
@channels = sort(@channels);
$channels = join(",", @channels);
$mutes .= ",on,off";
#$mutes .= ",off" if ( defined( $hash->{READINGS}{generation}{VAL} ) and $hash->{READINGS}{generation}{VAL} ne "1.0" );
# App handling
my @apps;
if ( defined( $hash->{helper}{device}{appPreset} )
&& ref( $hash->{helper}{device}{appPreset} ) eq "HASH" ) {
@apps = keys %{ $hash->{helper}{device}{appPreset} };
}
@apps = sort(@apps);
$apps = join(",", @apps);
my $usage = "Unknown argument " . $a[1] . ", choose one of";
$usage .= " requestFormat:json,xml register";
$usage .= ":noArg"
if (ReadingsVal($name, "requestFormat", "") eq "xml");
$usage .= " statusRequest:noArg toggle:noArg on:noArg off:noArg tvpause:noarg play:noArg pause:noArg stop:noArg record:noArg upnp:on,off volume:slider,1,1,100 volumeUp:noArg volumeDown:noArg channelUp:noArg channelDown:noArg remoteControl";
$usage .= " mute:" . $mutes;
$usage .= " input:" . $inputs if ( $inputs ne "" );
$usage .= " channel:$channels" if ( $channels ne "" );
$usage .= " openUrl application:" . $apps if ( $apps ne "" );
$usage .= " text" if (ReadingsVal($name, "requestFormat", "") eq "json");
$usage .= " requestReboot:noArg " if (ReadingsVal($name, "requestFormat", "") eq "json");
my $cmd = '';
# statusRequest
if ( lc( $a[1] ) eq "statusrequest" ) {
Log3($name, 2, "BRAVIA set $name " . $a[1]);
delete $hash->{helper}{device}
if ( defined( $hash->{helper}{device} ) );
GetStatus($hash);
}
# toggle
elsif ( $a[1] eq "toggle" ) {
Log3($name, 2, "BRAVIA set $name " . $a[1]);
if ( $power eq "off" ) {
return Set( $hash, $name, "on" );
}
else {
return Set( $hash, $name, "off" );
}
}
# on
elsif ( $a[1] eq "on" ) {
Log3($name, 2, "BRAVIA set $name " . $a[1]);
if ( $power eq "off" ) {
readingsSingleUpdate($hash, "state", "set_on", 1);
my $macAddr = AttrVal( $name, "macaddr", "" );
if ($macAddr eq "") {
$macAddr = ReadingsVal( $name, "macAddr", "");
}
if ( $macAddr ne "" && $macAddr ne "-" &&
($presence eq "absent" ||
ReadingsVal($name, "generation", "") eq "1.0.5" ||
ReadingsVal($name, "generation", "") eq "2.5.0") ) {
wake( $name, $macAddr );
return "wake-up command sent";
} else {
$cmd = "POWER";
SendCommand( $hash, "ircc", $cmd );
}
}
}
# off
elsif ( $a[1] eq "off" ) {
Log3($name, 2, "BRAVIA set $name " . $a[1]);
if ( $presence eq "present" ) {
readingsSingleUpdate($hash, "state", "set_off", 1);
if ( ReadingsVal($name, "generation", "") ne "1.0" ) {
$cmd = "STANDBY";
} else {
$cmd = "POWER";
}
SendCommand( $hash, "ircc", $cmd );
} else {
return "Device needs to be reachable to toggle standby mode.";
}
}
# volume
elsif ( $a[1] eq 'volume' ) {
Log3($name, 2, "BRAVIA set $name " . $a[1] . " " . $a[2]);
return 'No argument given' if ( !defined( $a[2] ) );
my $vol = $a[2];
if ( $presence eq 'present' ) {
if ( $vol =~ m/^\d+$/xms && $vol >= 1 && $vol <= 100 ) {
$cmd = $vol;
}
else {
return
'Argument does not seem to be a valid integer between 1 and 100';
}
SendCommand( $hash, 'setAudioVolume', $cmd );
readingsBeginUpdate($hash);
readingsBulkUpdateIfChanged($hash, 'volume', $a[2]);
readingsEndUpdate($hash, 1);
}
else {
return 'Device needs to be ON to adjust volume.';
}
}
# volumeUp/volumeDown
elsif ( lc( $a[1] ) =~ /^(volumeup|volumedown)$/xms ) {
Log3($name, 2, "BRAVIA set $name " . $a[1]);
if ( $presence eq "present" ) {
if ( lc( $a[1] ) eq "volumeup" ) {
$cmd = "VOLUP";
}
else {
$cmd = "VOLDOWN";
}
SendCommand( $hash, "ircc", $cmd );
}
else {
return "Device needs to be ON to adjust volume.";
}
}
# mute
elsif ( $a[1] eq 'mute' ) {
if ( defined( $a[2] ) ) {
Log3($name, 2, "BRAVIA set $name " . $a[1] . " " . $a[2]);
}
else {
Log3($name, 2, "BRAVIA set $name " . $a[1]);
}
if ( $presence eq 'present' ) {
if ( !defined( $a[2] ) || $a[2] eq 'toggle' ) {
SendCommand( $hash, 'ircc', 'MUTE' );
readingsSingleUpdate( $hash, 'mute', (ReadingsVal($name, 'mute', '') eq 'on' ? 'off' : 'on'), 1 );
}
elsif ( $a[2] eq 'on' || $a[2] eq 'off' ) {
SendCommand( $hash, 'setAudioMute', ($a[2] eq 'on' ? 'true' : 'false') );
readingsBeginUpdate($hash);
readingsBulkUpdateIfChanged($hash, 'mute', $a[2]);
readingsEndUpdate($hash, 1);
}
else {
return 'Unknown argument ' . $a[2];
}
}
else {
return 'Device needs to be ON to mute/unmute audio.';
}
}
# remoteControl
elsif ( lc( $a[1] ) eq "remotecontrol" ) {
Log3($name, 2, "BRAVIA set $name " . $a[1] . " " . $a[2]);
if ( !defined( $a[2] ) ) {
my $commandKeys = "";
for (sort keys %{GetRemotecontrolCommand("GetRemotecontrolCommands")}) {
$commandKeys = $commandKeys . " " . $_;
}
return "No argument given, choose one of" . $commandKeys;
}
$cmd = uc( $a[2] );
if ( $cmd eq "WOL" ) {
my $macAddr = AttrVal( $name, "macaddr", "" );
$macAddr = ReadingsVal( $name, "macAddr", "") if ($macAddr eq "");
wake( $name, $macAddr ) if ( $macAddr ne "" && $macAddr ne "-" );
}
elsif ( $presence eq "present" ) {
if ( $cmd eq "MUTE" ) {
Set( $hash, $name, "mute" );
}
elsif ( $cmd eq "CHANUP" ) {
Set( $hash, $name, "channelUp" );
}
elsif ( $cmd eq "CHANDOWN" ) {
Set( $hash, $name, "channelDown" );
}
elsif ( $cmd ne "" ) {
SendCommand( $hash, "ircc", $cmd );
}
else {
my $commandKeys = "";
for (sort keys %{GetRemotecontrolCommand("GetRemotecontrolCommands")}) {
$commandKeys = $commandKeys . " " . $_;
}
return
"Unknown argument "
. $a[2]
. ", choose one of"
. $commandKeys;
}
}
else {
return "Device needs to be reachable to be controlled remotely.";
}
}
# channel
elsif ( $a[1] eq "channel" ) {
if (defined($a[2]) && $presence eq "present" && $power ne "on" ) {
Log3($name, 4, "BRAVIA $name: indirect switching request to ON");
Set( $hash, $name, "on" );
}
shift(@a); shift(@a);
my $channelStr = join("#", @a);
Log3($name, 2, "BRAVIA set $name channel " . $channelStr);
return
"No argument given, choose one of channel presetNumber channelName "
if ( !defined( $channelStr ) );
if ( $presence eq "present" ) {
my $channelName = $channelStr;
if ( defined($hash->{helper}{device}{channelPreset}) && $channelName =~ /^(\d+).*$/xms ) {
if ( defined($hash->{helper}{device}{channelPreset}{$1}{uri}) ) {
SendCommand( $hash, "setPlayContent", $hash->{helper}{device}{channelPreset}{$1}{uri} );
return;
}
}
if ( $channelName =~ /^(\d)(\d?)(\d?)(\d?).*$/xms ) {
my @successor = ();
push(@successor, ["ircc", $2]) if (defined($2));
push(@successor, ["ircc", $3]) if (defined($3));
push(@successor, ["ircc", $4]) if (defined($4));
SendCommand( $hash, "ircc", $1, undef, @successor );
return;
}
return "Argument " . $channelName . " is not a valid channel name";
}
else {
return
"Device needs to be reachable to switch to a specific channel.";
}
}
# channelUp/channelDown
elsif ( lc( $a[1] ) =~ /^(channelup|channeldown)$/xms ) {
Log3($name, 2, "BRAVIA set $name " . $a[1]);
if ( $presence eq "present" ) {
if ( lc( $a[1] ) eq "channelup" ) {
$cmd = "CHANUP";
}
else {
$cmd = "CHANDOWN";
}
SendCommand( $hash, "ircc", $cmd );
}
else {
return "Device needs to be ON to switch channel.";
}
}
# input
elsif ( $a[1] eq "input" ) {
if (defined($a[2]) && $presence eq "present" && $power ne "on" ) {
Log3($name, 4, "BRAVIA $name: indirect switching request to ON");
Set( $hash, $name, "on" );
}
return "No 2nd argument given" if ( !defined( $a[2] ) );
shift(@a); shift(@a);
my $inputStr = join("#", @a);
Log3($name, 2, "BRAVIA set $name input $inputStr");
# Resolve input uri
my $input_uri;
if ( defined( $hash->{helper}{device}{inputPreset}{ $inputStr } ) ) {
$input_uri = $hash->{helper}{device}{inputPreset}{ $inputStr }{uri};
} else {
return "Unknown source input '" . $inputStr . "' on that device.";
}
if ( $presence eq "present" ) {
SendCommand( $hash, "setPlayContent", $input_uri );
if ( ReadingsVal($name, "input", "") ne $inputStr ) {
readingsSingleUpdate( $hash, "input", $inputStr, 1 );
}
}
else {
return "Device needs to be reachable to switch input.";
}
}
# application
elsif ( $a[1] eq "application" or $a[1] eq "app") {
if (defined($a[2]) && $presence eq "present" && $power ne "on" ) {
Log3($name, 4, "BRAVIA $name: indirect switching request to ON");
Set( $hash, $name, "on" );
}
return "No 2nd argument given" if ( !defined( $a[2] ) );
shift(@a); shift(@a);
my $appStr;
# Resolve app uri + data
my $app_name;
my $app_uri;
my $app_data;
while (@a) {
my $arg = shift(@a);
if (defined($appStr)) {
$appStr .= "#";
$appStr .= $arg;
} else {
$appStr = $arg;
}
if ( defined( $hash->{helper}{device}{appPreset}{ $appStr } ) ) {
$app_name = $appStr;
$app_uri = $hash->{helper}{device}{appPreset}{ $appStr }{uri};
$app_data = join(" ", @a);
}
}
return "Unknown app '" . $appStr . "' on that device." unless defined($app_uri);
if ( $presence eq "present" ) {
Log3($name, 2, "BRAVIA set $name " . $app_name . ($app_data ? " " . $app_data : ""));
SendCommand( $hash, "setActiveApp", $app_uri, $app_data );
}
else {
return "Device needs to be reachable to start an app.";
}
}
# openUrl
elsif ( $a[1] eq "openUrl") {
if (defined($a[2]) && $presence eq "present" && $power ne "on" ) {
Log3($name, 4, "BRAVIA $name: indirect switching request to ON");
Set( $hash, $name, "on" );
}
return "No 2nd argument given" if ( !defined( $a[2] ) );
if ( $presence eq "present" ) {
Log3($name, 2, "BRAVIA set $name " . $a[1] . " " . $a[2]);
my $url = lc($a[2]);
if ($url !~ /^https?:\/\/.*/xms) {
$url = "http://$url";
}
$url =~ s/([\x2F \x3A])/sprintf("%%%02X",ord($1))/egxms;
$url = "localapp://webappruntime?url=$url";
Log3($name, 2, "BRAVIA set $name " . $a[1] . " " . $url);
SendCommand( $hash, "setActiveApp", $url );
}
else {
return "Device needs to be reachable to start an app.";
}
}
# tvpause
elsif ( $a[1] eq "tvpause" ) {
Log3($name, 2, "BRAVIA set $name " . $a[1]);
if ( $power eq "on" ) {
SendCommand( $hash, "ircc", "TVPAUSE" );
}
else {
return "Device needs to be ON to pause tv.";
}
}
# pause
elsif ( $a[1] eq "pause" ) {
Log3($name, 2, "BRAVIA set $name " . $a[1]);
if ( $power eq "on" ) {
SendCommand( $hash, "ircc", "PAUSE" );
}
else {
return "Device needs to be ON to pause video.";
}
}
# play
elsif ( $a[1] eq "play" ) {
Log3($name, 2, "BRAVIA set $name " . $a[1]);
if ( $power eq "on" ) {
SendCommand( $hash, "ircc", "PLAY" );
}
else {
return "Device needs to be ON to play video.";
}
}
# stop
elsif ( $a[1] eq "stop" ) {
Log3($name, 2, "BRAVIA set $name " . $a[1]);
if ( $power eq "on" ) {
SendCommand( $hash, "ircc", "STOP" );
}
else {
return "Device needs to be ON to stop video.";
}
}
# record
elsif ( $a[1] eq "record" ) {
Log3($name, 2, "BRAVIA set $name " . $a[1]);
if ( $power eq "on" ) {
SendCommand( $hash, "ircc", "RECORD" );
}
else {
return "Device needs to be ON to start instant recording.";
}
}
# register
elsif ( $a[1] eq "register" ) {
if (defined($a[2])) {
Log3($name, 2, "BRAVIA set $name " . $a[1] . " " . $a[2]);
SendCommand( $hash, "register", $a[2] );
} else {
Log3($name, 2, "BRAVIA set $name " . $a[1]);
SendCommand( $hash, "register" );
}
}
# requestFormat
elsif ( $a[1] eq "requestFormat" ) {
return "No 2nd argument given" if ( !defined( $a[2] ) );
Log3($name, 2, "BRAVIA set $name " . $a[1] . " " . $a[2]);
readingsSingleUpdate( $hash, "requestFormat", $a[2], 1 )
if ( ReadingsVal($name, "requestFormat", "") ne $a[2] );
}
# upnp
elsif ( $a[1] eq "upnp" ) {
return "No 2nd argument given" if ( !defined( $a[2] ) );
Log3($name, 2, "BRAVIA set $name " . $a[1] . " " . $a[2]);
readingsSingleUpdate( $hash, "upnp", $a[2], 1 )
if ( ReadingsVal($name, "upnp", "") ne $a[2] );
}
# reboot
elsif ($a[1] eq "requestReboot") {
Log3($name, 2, "BRAVIA set $name " . $a[1]);
SendCommand( $hash, "requestReboot" );
}
# text
elsif ( $a[1] eq "text" ) {
return "No 2nd argument given" if ( !defined( $a[2] ) );
shift(@a); shift(@a);
my $text = join(" ", @a);
Log3($name, 2, "BRAVIA set $name text $text");
SendCommand( $hash, "text", $text );
}
# return usage hint
else {
return $usage;
}
return;
}
############################################################################################################
#
# Begin of helper functions
#
############################################################################################################
###################################
sub SendCommand {
my ( $hash, $service, $cmd, $param, @successor ) = @_;
my $name = $hash->{NAME};
my $address = $hash->{helper}{ADDRESS};
my $port = $hash->{helper}{PORT};
my $header = $hash->{helper}{HEADER};
my $timestamp = gettimeofday();
my $data;
my $timeout;
Log3($name, 5, "BRAVIA $name: called function SendCommand()");
my $URL;
my $return;
my $requestFormat = ReadingsVal($name, "requestFormat", "");
if ($service ne "register" && $service ne "getStatus") {
return if CheckRegistration($hash, $service, $cmd, $param, @successor);
}
if (!CheckServiceAvailable($hash, $service, $cmd, $param, @successor)) {
return;
}
if ( !defined($cmd) ) {
Log3($name, 4, "BRAVIA $name: REQ $service");
}
else {
Log3($name, 4, "BRAVIA $name: REQ $service/" . ::urlDecode($cmd));
}
LogSuccessors($hash, @successor);
$URL = "http://" . $address . ":";
$header .= "\r\nCookie: auth=".ReadingsVal($name, "authCookie", "")
if (ReadingsVal($name, "authCookie", "") ne "");
if ($service eq "ircc") {
$URL .= $port->{IRCC};
$URL .= "/sony"
if ($requestFormat eq "json");
$URL .= "/IRCC";
$header .= "\r\nSoapaction: \"urn:schemas-sony-com:service:IRCC:1#X_SendIRCC\"";
$header .= "\r\nContent-Type: text/xml; charset=UTF-8";
$cmd = GetRemotecontrolCommand($cmd);
$data = GetIrccRequest($cmd);
} elsif ($service eq "upnp") {
my $value;
if ($cmd =~ m/^(.+):(\d+)$/xms) {
$cmd = $1;
$value = $2;
}
$URL .= $port->{UPNP};
$URL .= "/upnp/control/RenderingControl";
$header .= "\r\nSoapaction: \"urn:schemas-upnp-org:service:RenderingControl:1#";
$header .= ucfirst($cmd);
$header .= "\"";
$header .= "\r\nContent-Type: text/xml";
$data = GetUpnpRequest($cmd, $value);
} elsif ($service eq "register") {
my $id = "Fhem Remote";
my $device = "fhem_remote";
$URL .= $port->{SERVICE};
if ($requestFormat eq "json") {
my $uuid = ReadingsVal($name, "registrationUUID", "");
if (defined($cmd) && $uuid ne "") {
if ($cmd ne "renew") {
$header = "Authorization: Basic ";
$header .= encode_base64(":".$cmd,"");
}
} else {
undef $header;
$uuid = createUniqueId();
readingsSingleUpdate($hash, "registrationUUID", $uuid, 1);
}
$URL .= "/sony/accessControl";
$data = "{\"method\":\"actRegister\",\"params\":[{";
$data .= "\"clientid\":\"".$id.":".$uuid."\",";
$data .= "\"nickname\":\"".$id." (".$device.")\",";
$data .= "\"level\":\"private\"},";
$data .= "[{\"value\":\"yes\",\"function\":\"WOL\"}]],\"id\":8,\"version\":\"1.0\"}";
} else {
$URL .= "/cers/api/register?name=".::urlEncode($id)."®istrAtionType=initial&deviceId=".$device;
}
} elsif ($service eq "getStatus") {
$URL .= $port->{SERVICE};
if ($cmd eq "xml") {
$URL .= "/cers/api/" . $service;
} else {
$URL .= "/sony/system";
$data = "{\"method\":\"getPowerStatus\",\"params\":[],\"id\":1,\"version\":\"1.0\"}";
}
} elsif ($service eq "getContentInformation") {
$URL .= $port->{SERVICE};
if ($requestFormat eq "json") {
$URL .= "/sony/avContent";
$data = "{\"method\":\"getPlayingContentInfo\",\"params\":[],\"id\":1,\"version\":\"1.0\"}";
} else {
$URL .= "/cersEx/api/" . $service;
}
} elsif ($service eq "getContentCount") {
$URL .= $port->{SERVICE};
if ($requestFormat eq "json") {
$URL .= "/sony/avContent";
$data = "{\"method\":\"getContentCount\",\"params\":[{\"source\":\"" . $cmd . "\",\"type\":\"\"}],\"id\":1,\"version\":\"1.0\"}";
}
} elsif ($service eq "getContentList") {
$URL .= $port->{SERVICE};
if ($requestFormat eq "json") {
my $source = $cmd;
my $index = 0;
if ($cmd =~ /^(.*)\|(\d+)$/xms){
$source = $1;
$index = $2;
}
$URL .= "/sony/avContent";
$data = "{\"method\":\"getContentList\",\"params\":[{\"source\":\"".$source."\",\"type\":\"\",\"cnt\":".InternalVal($name, "CHANNELCOUNT", 50).",\"stIdx\":".$index."}],\"id\":1,\"version\":\"1.0\"}";
}
} elsif ($service eq "getSchemeList") {
$URL .= $port->{SERVICE};
if ($requestFormat eq "json") {
$URL .= "/sony/avContent";
$data = "{\"id\":2,\"method\":\"getSchemeList\",\"version\":\"1.0\",\"params\":[]}";
}
} elsif ($service eq "getSourceList") {
$URL .= $port->{SERVICE};
if ($requestFormat eq "json") {
$URL .= "/sony/avContent";
$data = "{\"id\":2,\"method\":\"getSourceList\",\"version\":\"1.0\",\"params\":[{\"scheme\":\"".$cmd."\"}]}";
}
} elsif ($service eq "getCurrentExternalInputsStatus") {
$URL .= $port->{SERVICE};
if ($requestFormat eq "json") {
$URL .= "/sony/avContent";
$data = "{\"id\":2,\"method\":\"getCurrentExternalInputsStatus\",\"version\":\"1.0\",\"params\":[]}";
}
} elsif ($service eq "setPlayContent") {
$URL .= $port->{SERVICE};
if ($requestFormat eq "json") {
$URL .= "/sony/avContent";
$data = "{\"id\":2,\"method\":\"setPlayContent\",\"version\":\"1.0\",\"params\":[{\"uri\":\"".$cmd."\"}]}";
}
} elsif ($service eq "setPlayTvContent") {
$URL .= $port->{SERVICE};
if ($requestFormat eq "json") {
$URL .= "/sony/avContent";
$data = "{\"id\":2,\"method\":\"setPlayTvContent\",\"version\":\"1.0\",\"params\":[{\"channel\":\"".$cmd."\"}]}";
}
} elsif ($service eq "getScheduleList") {
$URL .= $port->{SERVICE};
if ($requestFormat eq "json") {
$URL .= "/sony/recording";
$data = "{\"method\":\"getScheduleList\",\"params\":[{\"cnt\":100,\"stIdx\":0}],\"id\":1,\"version\":\"1.0\"}";
} else {
$URL .= "/cersEx/api/" . $service;
}
} elsif ($service eq "getApplicationList") {
$URL .= $port->{SERVICE};
if ($requestFormat eq "json") {
$URL .= "/sony/appControl";
$data = "{\"id\":2,\"method\":\"getApplicationList\",\"version\":\"1.0\",\"params\":[]}";
}
} elsif ($service eq "setActiveApp") {
$URL .= $port->{SERVICE};
if ($requestFormat eq "json") {
$URL .= "/sony/appControl";
$data = "{\"id\":2,\"method\":\"setActiveApp\",\"version\":\"1.0\",\"params\":[{\"uri\":\"".$cmd."\"";
$data .= ",\"data\":\"".$param."\"" if (defined($param));
$data .= "}]}";
}
} elsif ($service eq "getVolumeInformation") {
$URL .= $port->{SERVICE};
if ($requestFormat eq "json") {
$URL .= "/sony/audio";
$data = "{\"id\":2,\"method\":\"getVolumeInformation\",\"version\":\"1.0\",\"params\":[]}";
}
} elsif ($service eq "setAudioVolume") {
$URL .= $port->{SERVICE};
if ($requestFormat eq "json") {
$URL .= "/sony/audio";
$data = "{\"id\":2,\"method\":\"setAudioVolume\",\"version\":\"1.0\",\"params\":[{\"volume\":\"".$cmd."\",\"target\":\"speaker\"}]}";
}
} elsif ($service eq "setAudioMute") {
$URL .= $port->{SERVICE};
if ($requestFormat eq "json") {
$URL .= "/sony/audio";
$data = "{\"id\":2,\"method\":\"setAudioMute\",\"version\":\"1.0\",\"params\":[{\"status\":".$cmd."}]}";
}
} elsif ($service eq "getSupportedApiInfo") {
$URL .= $port->{SERVICE};
if ($requestFormat eq "json") {
$URL .= "/sony/guide";
$data = "{\"id\":2,\"method\":\"getSupportedApiInfo\",\"version\":\"1.0\",\"params\":[{\"services\":[\"audio\"]}]}";
}
} elsif ($service eq "text") {
$URL .= $port->{SERVICE};
if ($requestFormat eq "json") {
$URL .= "/sony/appControl";
$data = "{\"id\":2,\"method\":\"setTextForm\",\"version\":\"1.0\",\"params\":[\"".$cmd."\"]}";
}
} else {
$URL .= $port->{SERVICE};
if ($requestFormat eq "json") {
$URL .= "/sony/system";
$data = "{\"method\":\"".$service."\",\"params\":[],\"id\":1,\"version\":\"1.0\"}";
} else {
$URL .= "/cers";
if ($service =~ /^Mute.*$/xms) {
$URL .= "/command/".$service;
} else {
$URL .= "/api/" . $service;
}
}
}
$timeout = AttrVal($name, "timeout", 0);
if ($timeout !~ /^\d+$/xms or $timeout == 0) {
if ( $service eq "getStatus" ) {
$timeout = 10;
} else {
$timeout = 30;
}
}
# send request via HTTP-POST method
Log3($name, 5, "BRAVIA $name: POST " . $URL . " (" . ::urlDecode($data) . ")")
if ( defined($data) );
Log3($name, 5, "BRAVIA $name: GET " . $URL)
if ( !defined($data) );
Log3($name, 5, "BRAVIA $name: header " . $header)
if ( defined($header) );
::HttpUtils_NonblockingGet(
{
url => $URL,
timeout => $timeout,
noshutdown => 1,
header => $header,
data => $data,
hash => $hash,
service => $service,
cmd => $cmd,
successor => \@successor,
timestamp => $timestamp,
callback => \&ReceiveCommand,
}
);
return;
}
###################################
sub ReceiveCommand {
my ( $param, $err, $data ) = @_;
my $hash = $param->{hash};
my $name = $hash->{NAME};
my $service = $param->{service};
my $cmd = $param->{cmd};
my @successor = @{$param->{successor}};
my $newstate;
my $rc = ( $param->{buf} ) ? $param->{buf} : $param;
my $return;
Log3($name, 5, "BRAVIA $name: called function ReceiveCommand() rc: $rc err: $err data: $data ");
# device not reachable
if ($err) {
if ( !defined($cmd) || ref($cmd) eq "HASH" || $cmd eq "" ) {
Log3($name, 4, "BRAVIA $name: RCV TIMEOUT $service");
}
else {
Log3($name, 4, "BRAVIA $name: RCV TIMEOUT $service/" . ::urlDecode($cmd));
}
# device is not reachable or
# does not even support master command for status
if ( $service eq "getStatus" ) {
ClearContentInformation($hash);
$newstate = "absent";
if (
( !defined( $hash->{helper}{AVAILABLE} ) )
or ( defined( $hash->{helper}{AVAILABLE} )
and $hash->{helper}{AVAILABLE} == 1 )
)
{
$hash->{helper}{AVAILABLE} = 0;
readingsSingleUpdate( $hash, "presence", "absent", 1 );
}
}
# stop pulling for current interval
# upnp service might not run at all, e.g. on KDL-65W857C
if ($service ne "upnp") {
Log3($name, 4, "BRAVIA $name: drop successors");
LogSuccessors($hash, @successor);
return;
}
}
# data received
elsif ($data) {
if (
( !defined( $hash->{helper}{AVAILABLE} ) )
or ( defined( $hash->{helper}{AVAILABLE} )
and $hash->{helper}{AVAILABLE} == 0 )
)
{
$hash->{helper}{AVAILABLE} = 1;
readingsSingleUpdate( $hash, "presence", "present", 1 );
}
if ( !defined($cmd) ) {
Log3($name, 4, "BRAVIA $name: RCV $service");
}
else {
Log3($name, 4, "BRAVIA $name: RCV $service/" . ::urlDecode($cmd));
}
LogSuccessors($hash, @successor);
if ( $data ne "" ) {
if ( $data =~ /^<\?xml/xms ) {
my $parser = XML::Simple->new(
NormaliseSpace => 2,
KeepRoot => 0,
ForceArray => 0,
SuppressEmpty => 1
);
if ( !defined($cmd) || ref($cmd) eq "HASH" || $cmd eq "" ) {
Log3($name, 4, "BRAVIA $name: RES $service - $data");
}
else {
Log3($name, 4, "BRAVIA $name: RES $service/" . ::urlDecode($cmd) . " - $data");
}
readingsSingleUpdate( $hash, "requestFormat", "xml", 1 )
if ( $service eq "getStatus" && ReadingsVal($name , "requestFormat", "") eq "" );
$return = $parser->XMLin( encode_utf8($data), KeyAttr => [ ] );
}
elsif ( $data =~ /^\{/xms || $data =~ /^\[/xms ) {
if ( !defined($cmd) || ref($cmd) eq "HASH" || $cmd eq "" ) {
Log3($name, 4, "BRAVIA $name: RES $service - $data");
}
else {
Log3($name, 4, "BRAVIA $name: RES $service/" . ::urlDecode($cmd) . " - $data");
}
readingsSingleUpdate( $hash, "requestFormat", "json", 1 )
if ( $service eq "getStatus" && ReadingsVal($name , "requestFormat", "") eq "" );
$return = decode_json( encode_utf8($data) );
}
elsif ( $data eq "not foundnot found" ) {
if ( !defined($cmd) || ref($cmd) eq "HASH" || $cmd eq "" ) {
Log3($name, 4, "BRAVIA $name: RES $service - not found");
}
else {
Log3($name, 4, "BRAVIA $name: RES $service/" . ::urlDecode($cmd) . " - not found");
}
$return = "not found";
}
elsif ( $data =~ /^ 60) )
{
readingsBulkUpdate( $hash, "state", $newstate );
}
# Set BRAVIA online-only readings to "-"
# in case box is not reachable
if ( $newstate eq "absent"
|| $newstate eq "undefined" )
{
foreach ( 'input', ) {
if ( ReadingsVal($name, $_, "-") ne "-" ) {
readingsBulkUpdate( $hash, $_, "-" );
}
}
}
readingsEndUpdate( $hash, 1 );
}
if (@successor) {
my @nextCmd = @{shift(@successor)};
my $cmdLength = @nextCmd;
my $cmdService = $nextCmd[0];
my $cmdCmd;
my $cmdParam;
$cmdCmd = $nextCmd[1] if ($cmdLength > 1);
$cmdParam = $nextCmd[2] if ($cmdLength > 2);
SendCommand($hash, $cmdService, $cmdCmd, $cmdParam, @successor);
}
return;
}
###################################
sub wake {
my ( $name, $mac_addr ) = @_;
my $address = AttrVal($name, 'wolBroadcast', '255.255.255.255');
my $port = 9;
my $sock = IO::Socket::INET->new( Proto => 'udp' )
or die "socket : $!\n";
die "Can't create WOL socket\n" if ( !$sock );
my $ip_addr = inet_aton($address);
my $sock_addr = sockaddr_in( $port, $ip_addr );
$mac_addr =~ s/://gxms;
my $packet =
pack( 'C6H*', 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, $mac_addr x 16 );
setsockopt( $sock, SOL_SOCKET, SO_BROADCAST, 1 )
or die "setsockopt : $!\n";
Log3($name, 4, "BRAVIA $name: Waking up by sending Wake-On-Lan magic packet to $mac_addr");
send( $sock, $packet, 0, $sock_addr ) or die "send : $!\n";
close($sock);
return;
}
###################################
# process return data
sub ProcessCommandData {
my ($param,$return,$successor) = @_;
my $hash = $param->{hash};
my $name = $hash->{NAME};
my $service = $param->{service};
my $cmd = $param->{cmd};
my $header = $param->{httpheader};
my $newstate;
# ircc
if ( $service eq "ircc" ) {
# nothing to do
}
# upnp
elsif ( $service eq "upnp" ) {
if ( ref($return) eq "HASH" ) {
if ( $cmd eq "getVolume" ) {
my $volume = $return->{"s:Body"}{"u:GetVolumeResponse"}{CurrentVolume};
if ( defined( $volume ) ) {
readingsSingleUpdate( $hash, "volume", $volume, 1 )
if (ReadingsVal($name, "volume", "-1") ne $volume);
}
} elsif ( $cmd eq "getMute" ) {
my $mute = $return->{"s:Body"}{"u:GetMuteResponse"}{CurrentMute} eq "0" ? "off" : "on";
if ( defined( $mute ) ) {
readingsSingleUpdate( $hash, "mute", $mute, 1 )
if (ReadingsVal($name, "mute", "-1") ne $mute);
}
}
}
}
# getStatus
elsif ( $service eq "getStatus" ) {
my $input = "-";
my $setInput;
my %statusKeys;
foreach ( keys %{ $hash->{READINGS} } ) {
$statusKeys{$_} = 1 if ( $_ =~ /^s_.*/xms && ReadingsVal($name, $_, "") ne "-" );
}
readingsBeginUpdate($hash);
if ( ref($return) eq "HASH" ) {
if ( ref($return->{status}{statusItem}) eq "ARRAY" ) {
foreach ( @{ $return->{status}{statusItem} } ) {
if ( $_->{field} eq "source" ) {
$input = $_->{value};
$setInput = "true";
} else {
readingsBulkUpdateIfChanged( $hash, "s_".$_->{field}, $_->{value} );
}
delete $statusKeys{"s_".$_->{field}};
}
} elsif (defined($return->{status}{statusItem}{field})) {
my $field = "s_".$return->{status}{statusItem}{field};
if ( defined($field) && $field ne "" ) {
if ( $field eq "s_source" ) {
$input = $return->{status}{statusItem}{value};
$setInput = "true";
} else {
readingsBulkUpdateIfChanged( $hash, $field, $return->{status}{statusItem}{value} );
}
delete $statusKeys{$field};
}
}
}
readingsBulkUpdateIfChanged( $hash, "input", $input ) if ( defined($setInput) );
#remove outdated content information - replaces by "-"
foreach ( keys %statusKeys ) {
readingsBulkUpdate( $hash, $_, "-" );
}
readingsEndUpdate( $hash, 1 );
# check for valid status
if (ref $return eq ref {} && ref($return->{error}) eq "ARRAY" && $return->{error}[0] eq "404") {
ClearContentInformation($hash);
return "off";
}
# fetch other info
# read system information if not existing
push(@$successor, ["getSystemInformation"])
if ( ReadingsVal($name, "name", "0") eq "0" || ReadingsVal($name, "model", "0") eq "0" );
push(@$successor, ["getSupportedApiInfo"])
if ( ReadingsVal( $name, "requestFormat", "" ) eq "json" &&
(!defined($hash->{helper}{services}) || scalar($hash->{helper}{services}) == 0) );
# read content information
if ( ReadingsVal($name, "generation", "1.0") ne "1.0" ) {
if (ref $return eq ref {} && ref($return->{result}) eq "ARRAY" && $return->{result}[0]{status} ne "active") {
# current status is not active, don't need to fetch content information
ClearContentInformation($hash);
$newstate = "off";
} else {
push(@$successor, ["getContentInformation"]);
}
} elsif (ref $return eq ref {}) {
if (ref($return->{result}) eq "ARRAY") {
$newstate = ( $return->{result}[0]{status} eq "active" ? "on" : $return->{result}[0]{status} );
} else {
$newstate = ( $return->{status}{name} eq "viewing" ? "on" : $return->{status}{name} );
}
# get current system settings
if ($newstate eq 'on') {
push(@$successor, ['getVolumeInformation']);
}
}
}
# getSystemInformation
elsif ( $service eq "getSystemInformation" ) {
if ( ref($return) eq "HASH" ) {
readingsBeginUpdate($hash);
if (ref($return->{result}) eq "ARRAY") {
my $sysInfo = $return->{result}[0];
readingsBulkUpdate( $hash, "name", $sysInfo->{name} );
readingsBulkUpdate( $hash, "generation", $sysInfo->{generation} );
readingsBulkUpdate( $hash, "area", $sysInfo->{area} );
readingsBulkUpdate( $hash, "language", $sysInfo->{language} );
readingsBulkUpdate( $hash, "country", $sysInfo->{region} );
readingsBulkUpdate( $hash, "model", $sysInfo->{model} );
readingsBulkUpdate( $hash, "macAddr", $sysInfo->{macAddr} );
} else {
readingsBulkUpdate( $hash, "name", $return->{name} );
readingsBulkUpdate( $hash, "generation", $return->{generation} );
readingsBulkUpdate( $hash, "area", $return->{area} );
readingsBulkUpdate( $hash, "language", $return->{language} );
readingsBulkUpdate( $hash, "country", $return->{country} );
readingsBulkUpdate( $hash, "model", $return->{modelName} );
}
readingsEndUpdate( $hash, 1 );
}
}
# getSupportedApiInfo
elsif ( $service eq "getSupportedApiInfo" ) {
if ( ref($return) eq "HASH" && ref($return->{result}) eq "ARRAY") {
my $elements = $return->{result}[0];
if (ref($elements) eq "ARRAY") {
$hash->{helper}{services} = ();
foreach my $srvName (@$elements) {
push(@{$hash->{helper}{services}}, $srvName->{service});
}
}
}
}
# getContentInformation
elsif ( $service eq "getContentInformation" ) {
my %contentKeys;
my $channelName = "-";
my $channelNo = "-";
my $channelUri;
my $currentTitle = "-";
my $currentMedia = "-";
foreach ( keys %{ $hash->{READINGS} } ) {
$contentKeys{$_} = 1
if ( $_ =~ /^ci_.*/xms and ReadingsVal($name, $_, "") ne "-" );
}
if ( ref($return) eq "HASH" ) {
$newstate = "on";
if ( defined($return->{infoItem}) ) {
# xml
if ( ref($return->{infoItem}) eq "ARRAY" ) {
readingsBeginUpdate($hash);
foreach ( @{ $return->{infoItem} } ) {
if ( $_->{field} eq "displayNumber" ) {
$channelNo = $_->{value};
} elsif ( $_->{field} eq "inputType" ) {
$currentMedia = $_->{value};
} elsif ( $_->{field} eq "serviceName" ) {
$channelName = $_->{value};
} elsif ( $_->{field} eq "title" ) {
$currentTitle = $_->{value};
} else {
readingsBulkUpdateIfChanged( $hash, "ci_".$_->{field}, $_->{value} );
delete $contentKeys{"ci_".$_->{field}};
}
}
readingsEndUpdate( $hash, 1 );
} else {
my $field = "ci_".$return->{infoItem}->{field};
my $value = $return->{infoItem}->{value};
readingsSingleUpdate( $hash, $field, $value, 1 )
if ( ReadingsVal($name, $field, "") ne $value );
delete $contentKeys{$field};
}
} else {
# json
if ( ref($return->{result}[0]) eq "HASH" ) {
my $uri;
my $input;
readingsBeginUpdate($hash);
foreach ( keys %{$return->{result}[0]} ) {
if ( $_ eq "dispNum" ) {
$channelNo = $return->{result}[0]{$_};
} elsif ( $_ eq "programMediaType" ) {
$currentMedia = $return->{result}[0]{$_};
} elsif ( $_ eq "title" ) {
$channelName = $return->{result}[0]{$_};
} elsif ( $_ eq "programTitle" ) {
$currentTitle = $return->{result}[0]{$_};
} elsif ( $_ eq "source" ) {
$input = $return->{result}[0]{$_};
} elsif ( $_ eq "uri" ) {
$uri = $return->{result}[0]{$_};
# set TV input uri to last tv-norm (tv:dvbt, tv:dvbs)
$hash->{helper}{device}{inputPreset}{TV}{uri} = $return->{result}[0]{$_}
if (defined($hash->{helper}{device}{inputPreset}) && $return->{result}[0]{$_} =~ /tv:.*/xms);
} else {
readingsBulkUpdateIfChanged( $hash, "ci_".$_, $return->{result}[0]{$_} );
delete $contentKeys{"ci_".$_};
}
}
if ($uri) {
$channelUri = $uri;
readingsBulkUpdateIfChanged($hash, "uri", $uri);
foreach ( keys %{$hash->{helper}{device}{inputPreset}} ) {
if ($hash->{helper}{device}{inputPreset}{$_}{uri} eq $uri) {
$input = $_;
$input =~ s/\#/ /gxms;
last;
}
}
}
readingsBulkUpdateIfChanged($hash, "input", $input) if ($input);
#reset application
readingsBulkUpdate( $hash, "application", "-" ) if (ReadingsVal($name, "application", "-") ne "-");
readingsEndUpdate( $hash, 1 );
} elsif ( ref($return->{error}) eq "ARRAY" && $return->{error}[0] eq "7" && $return->{error}[1] eq "Illegal State" ) {
#could be timeshift mode or app mode
push(@$successor, ["getScheduleList"]);
FetchPresets($hash, $successor);
return;
}
}
} else {
if (ReadingsVal($name, "input", "") eq "Others" || ReadingsVal($name, "input", "") eq "Broadcast" ) {
$newstate = "off";
} else {
$newstate = "on";
}
}
readingsBeginUpdate($hash);
readingsBulkUpdateIfChanged( $hash, "channel", $channelName );
readingsBulkUpdateIfChanged( $hash, "channelId", $channelNo );
readingsBulkUpdateIfChanged( $hash, "currentTitle", $currentTitle );
readingsBulkUpdateIfChanged( $hash, "currentMedia", $currentMedia );
#remove outdated content information - replaces by "-"
foreach ( keys %contentKeys ) {
readingsBulkUpdate( $hash, $_, "-" );
}
readingsEndUpdate( $hash, 1 );
if ($channelName ne "-" && $channelNo ne "-") {
# push(@$successor, ["getContentList", ReadingsVal($name, "input", "")])
# if (ReadingsVal($name, "requestFormat", "") eq "json"
# && (!defined($hash->{helper}{device}{channelPreset}) || ReadingsVal($name, "state", "") ne "on"));
$hash->{helper}{device}{channelPreset}{ $channelNo }{id} = $channelNo;
$hash->{helper}{device}{channelPreset}{ $channelNo }{name} = GetNormalizedName($channelName);
$hash->{helper}{device}{channelPreset}{ $channelNo }{uri} = $channelUri;
}
# get current system settings
if ($newstate eq 'on') {
push(@$successor, ['getVolumeInformation']);
}
FetchPresets($hash, $successor) if ($newstate eq "on");
}
# getScheduleList
elsif ( $service eq "getScheduleList" ) {
my %contentKeys;
my $channelName = "-";
my $currentTitle = "-";
my $currentMedia = "-";
foreach ( keys %{ $hash->{READINGS} } ) {
$contentKeys{$_} = 1
if ( $_ =~ /^ci_.*/xms and ReadingsVal($name, $_, "") ne "-" );
}
readingsBeginUpdate($hash);
if ( ref($return) eq "HASH" ) {
if (ref($return->{result}) eq "ARRAY") {
$newstate = "on";
foreach ( @{ $return->{result} } ) {
foreach ( @{ $_ } ) {
if ($_->{recordingStatus} eq "recording") {
foreach my $key ( keys %{ $_ }) {
if ( $key eq "type" ) {
$currentMedia = $_->{$key};
readingsBulkUpdateIfChanged( $hash, "input", $_->{$key} );
} elsif ( $key eq "channelName" ) {
$channelName = $_->{$key};
} elsif ( $key eq "title" ) {
$currentTitle = $_->{$key};
} else {
readingsBulkUpdateIfChanged( $hash, "ci_".$key, $_->{$key} );
delete $contentKeys{"ci_".$key};
}
}
}
}
}
}
}
readingsBulkUpdateIfChanged( $hash, "channel", $channelName );
readingsBulkUpdateIfChanged( $hash, "currentTitle", $currentTitle );
readingsBulkUpdateIfChanged( $hash, "currentMedia", $currentMedia );
#remove outdated content information - replaces by "-"
foreach ( keys %contentKeys ) {
readingsBulkUpdate( $hash, $_, "-" );
}
readingsEndUpdate( $hash, 1 );
# get current system settings
if (ReadingsVal($name, "upnp", "on") eq "on") {
push(@$successor, ["upnp", "getVolume"]);
push(@$successor, ["upnp", "getMute"]);
}
}
# getContentList
elsif ( $service eq "getContentList" ) {
my $channelIndex = 0;
if ( ref($return) eq "HASH" ) {
if (ref($return->{result}) eq "ARRAY") {
foreach ( @{ $return->{result} } ) {
foreach ( @{ $_ } ) {
my $channelNo;
my $channelName;
my $channelUri;
foreach my $key ( keys %{ $_ }) {
if ( $key eq "dispNum" ) {
$channelNo = $_->{$key};
} elsif ( $key eq "title" ) {
$channelName = GetNormalizedName($_->{$key});
} elsif ( $key eq "index" ) {
$channelIndex = $_->{$key};
} elsif ( $key eq "uri" ) {
$channelUri = $_->{$key};
}
}
$hash->{helper}{device}{channelPreset}{ $channelNo }{id} = $channelNo;
$hash->{helper}{device}{channelPreset}{ $channelNo }{name} = $channelName;
$hash->{helper}{device}{channelPreset}{ $channelNo }{uri} = $channelUri;
}
}
}
}
# increment index, because it starts with 0
if (++$channelIndex % InternalVal($name, "CHANNELCOUNT", 50) == 0) {
# try next junk of channels
my $source = $cmd;
if ($cmd =~ /^(.*)\|(\d+)$/xms){
$source = $1;
}
push(@$successor, ["getContentList", $source."|".$channelIndex]);
}
}
# getSchemeList
elsif ( $service eq "getSchemeList" ) {
if ( ref($return) eq "HASH" ) {
if (ref($return->{result}) eq "ARRAY") {
foreach ( @{ $return->{result} } ) {
foreach ( @{ $_ } ) {
my $scheme = undef;
foreach my $key ( keys %{ $_ }) {
if ( $key eq "scheme" ) {
$scheme = $_->{$key};
}
}
if (defined($scheme)) {
if ($scheme eq "extInput") {
push(@$successor, ["getCurrentExternalInputsStatus"]);
} elsif ($scheme eq "tv") {
push(@$successor, ["getSourceList", $scheme]);
}
}
}
}
}
}
}
# getSourceList
elsif ( $service eq "getSourceList" ) {
if ( ref($return) eq "HASH" ) {
if (ref($return->{result}) eq "ARRAY") {
foreach ( @{ $return->{result} } ) {
foreach ( @{ $_ } ) {
my $source = undef;
foreach my $key ( keys %{ $_ }) {
if ( $key eq "source" ) {
$source = $_->{$key};
}
}
if (defined($source) and $source =~ /tv:dvb(.)/xms) {
my $dvbName = GetNormalizedName("TV / DVB-".uc($1));
$hash->{helper}{device}{inputPreset}{$dvbName}{uri} = $source;
push(@$successor, ["getContentList", $source]);
}
}
}
}
}
}
# getCurrentExternalInputsStatus
elsif ( $service eq "getCurrentExternalInputsStatus" ) {
if ( ref($return) eq "HASH" ) {
if (ref($return->{result}) eq "ARRAY") {
foreach ( @{ $return->{result} } ) {
foreach ( @{ $_ } ) {
my $inputName;
my $inputLabel;
my $inputUri;
foreach my $key ( keys %{ $_ }) {
if ( $key eq "uri" ) {
$inputUri = $_->{$key};
} elsif ( $key eq "title" ) {
$inputName = GetNormalizedName($_->{$key});
} elsif ( $key eq "label" ) {
$inputLabel = GetNormalizedName($_->{$key});
}
}
$hash->{helper}{device}{inputPreset}{$inputName}{uri} = $inputUri;
}
}
my $tvUri = ReadingsVal($name, "uri", "tv");
$tvUri = "tv" if ($tvUri !~ /tv.*/xms);
$hash->{helper}{device}{inputPreset}{TV}{uri} = $tvUri;
}
}
}
# getApplicationList
elsif ( $service eq "getApplicationList" ) {
if ( ref($return) eq "HASH" ) {
if (ref($return->{result}) eq "ARRAY") {
foreach ( @{ $return->{result} } ) {
foreach ( @{ $_ } ) {
my $appName;
my $appUri;
foreach my $key ( keys %{ $_ }) {
if ( $key eq "uri" ) {
$appUri = $_->{$key};
} elsif ( $key eq "title" ) {
$appName = GetNormalizedName($_->{$key});
}
}
$hash->{helper}{device}{appPreset}{$appName}{uri} = $appUri;
}
}
}
}
}
# setPlayContent
elsif ( $service eq "setPlayContent" ) {
# nothing to do
}
# setActiveApp
elsif ( $service eq "setActiveApp" ) {
my $appName;
foreach ( keys %{$hash->{helper}{device}{appPreset}} ) {
if ($hash->{helper}{device}{appPreset}{$_}{uri} eq $cmd) {
$appName = $_;
$appName =~ s/\#/ /gxms;
last;
}
}
readingsSingleUpdate( $hash, "application", $appName, 1 ) if ($appName);
}
# getVolumeInformation
elsif ( $service eq 'getVolumeInformation') {
if ( ref($return) eq 'HASH' && ref($return->{result}) eq 'ARRAY') {
my $elements = $return->{result}[0];
if (ref($elements) eq 'ARRAY') {
my %speaker = ();
foreach my $target (@$elements) {
if (ref($target) eq 'HASH') {
%speaker = %$target if ($target->{'target'} eq 'speaker');
last;
}
}
%speaker = %{@$elements[0]} if (!%speaker && $elements > 0);
if (%speaker) {
readingsBeginUpdate($hash);
readingsBulkUpdateIfChanged( $hash, 'volume', $speaker{volume} );
readingsBulkUpdateIfChanged( $hash, 'mute', ($speaker{mute} ? "on" : "off") );
readingsEndUpdate( $hash, 1 );
}
}
}
}
# setAudioVolume
elsif ( $service eq 'setAudioVolume') {
# nothing to do
}
# setAudioMute
elsif ( $service eq 'setAudioMute') {
# nothing to do
}
# text
elsif ( $service eq "text" ) {
# nothing to do
}
# register
elsif ( $service eq "register" ) {
# check for error
if (ref $return eq ref {} && ref($return->{error}) eq "ARRAY" && $return->{error}[0] eq "401") {
# drop successors for current interval
Log3($name, 4, "BRAVIA $name: drop successors");
LogSuccessors($hash, @$successor);
@$successor = ();
} else {
readingsBeginUpdate($hash);
if ( $header =~ /auth=([A-Za-z0-9]+)/xms ) {
readingsBulkUpdate( $hash, "authCookie", $1 );
}
if ( $header =~ /expires=([^;]+)/ixms ) {
readingsBulkUpdate( $hash, "authExpires", $1 );
}
if ( $header =~ /max-age=(\d+)/ixms ) {
readingsBulkUpdateIfChanged( $hash, "authMaxAge", $1 );
}
readingsEndUpdate( $hash, 1 );
}
}
# all other command results
else {
Log3($name, 2, "BRAVIA $name: ERROR: method to handle response of $service not implemented");
}
return $newstate;
}
#####################################
sub ClearContentInformation {
my ($hash) = @_;
my $name = $hash->{NAME};
readingsBeginUpdate($hash);
#remove outdated content information - replaces by "-"
foreach ( keys %{ $hash->{READINGS} } ) {
readingsBulkUpdateIfChanged($hash, $_, "-") if ( $_ =~ /^ci_.*/xms );
}
readingsBulkUpdateIfChanged( $hash, "channel", "-" );
readingsBulkUpdateIfChanged( $hash, "channelId", "-" );
readingsBulkUpdateIfChanged( $hash, "currentTitle", "-" );
readingsBulkUpdateIfChanged( $hash, "currentMedia", "-" );
readingsBulkUpdateIfChanged( $hash, "input", "-" );
readingsBulkUpdateIfChanged( $hash, "uri", "-" );
readingsEndUpdate( $hash, 1 );
return;
}
sub FetchPresets {
my ($hash,$successor) = @_;
my $name = $hash->{NAME};
if ( ReadingsVal( $name, "requestFormat", "" ) eq "json" ) {
# load input
push(@$successor, ["getSchemeList"])
if ( ReadingsVal( $name, "state", "" ) ne "on"
|| !defined( $hash->{helper}{device}{inputPreset} )
|| scalar( keys %{ $hash->{helper}{device}{inputPreset} } ) == 0 );
# load app
push(@$successor, ["getApplicationList"])
if ( ReadingsVal( $name, "state", "" ) ne "on"
|| !defined( $hash->{helper}{device}{appPreset} )
|| scalar( keys %{ $hash->{helper}{device}{appPreset} } ) == 0 );
}
return;
}
sub LogSuccessors {
my ($hash,@successor) = @_;
my $name = $hash->{NAME};
my $msg = "BRAVIA $name: successors";
my @succ_item;
for (my $i = 0; $i < @successor; $i++) {
@succ_item = @{$successor[$i]};
$msg .= " $i: ";
$msg .= join(",", map { defined($_) ? $_ : '' } @succ_item);
}
Log3($name, 4, $msg) if (@successor > 0);
return;
}
#####################################
# Callback from 95_remotecontrol for command makenotify.
sub RCmakenotify {
my ( $nam, $ndev ) = @_;
my $nname = "notify_$nam";
fhem( "define $nname notify $nam set $ndev remoteControl " . '$EVENT', 1 );
Log3(undef, 2, "[remotecontrol:BRAVIA] Notify created: $nname");
return "Notify created by BRAVIA: $nname";
}
#####################################
# RC layouts
# Sony TV with SVG
sub RClayout_SVG {
my @row;
$row[0] = "SOURCE:rc_AV.svg,:rc_BLANK.svg,:rc_BLANK.svg,POWER:rc_POWER.svg";
$row[1] = "TVPAUSE:rc_TVstop.svg,ASPECT,MODE3D,TRACKID";
$row[2] = "PREVIOUS:rc_PREVIOUS.svg,REWIND:rc_REW.svg,FORWARD:rc_FF.svg,NEXT:rc_NEXT.svg";
$row[3] = "REC:rc_REC.svg,PLAY:rc_PLAY.svg,PAUSE:rc_PAUSE.svg,STOP:rc_STOP.svg";
$row[4] = "RED:rc_RED.svg,GREEN:rc_GREEN.svg,YELLOW:rc_YELLOW.svg,BLUE:rc_BLUE.svg";
$row[5] = ":rc_BLANK.svg,:rc_BLANK.svg,:rc_BLANK.svg";
$row[6] = "HELP:rc_HELP.svg,SEN,SYNCMENU";
$row[7] = "GUIDE:rc_MENU.svg,UP:rc_UP.svg,INFO:rc_INFO.svg";
$row[8] = "LEFT:rc_LEFT.svg,OK:rc_OK.svg,RIGHT:rc_RIGHT.svg";
$row[9] = "RETURN:rc_BACK.svg,DOWN:rc_DOWN.svg,OPTIONS:rc_OPTIONS.svg,HOMEtxt";
$row[10] = ":rc_BLANK.svg,:rc_BLANK.svg,:rc_BLANK.svg";
$row[11] = "DIGITAL,EXIT:rc_EXIT.svg,TV:rc_TV.svg";
$row[12] = "1:rc_1.svg,2:rc_2.svg,3:rc_3.svg";
$row[13] = "4:rc_4.svg,5:rc_5.svg,6:rc_6.svg";
$row[14] = "7:rc_7.svg,8:rc_8.svg,9:rc_9.svg";
$row[15] = "TEXT:rc_TEXT.svg,0:rc_0.svg,SUBTITLE";
$row[16] = ":rc_BLANK.svg,:rc_BLANK.svg,:rc_BLANK.svg";
$row[17] = "MUTE:rc_MUTE.svg,VOLUP:rc_VOLPLUS.svg,CHANNELUP:rc_UP.svg,AUDIO:rc_AUDIO.svg";
$row[18] = ":rc_BLANK.svg,VOLDOWN:rc_VOLMINUS.svg,CHANNELDOWN:rc_DOWN.svg";
$row[19] = "attr rc_iconpath icons";
$row[20] = "attr rc_iconprefix rc_";
return @row;
}
# Sony TV with PNG
sub RClayout {
my @row;
$row[0] = "SOURCE,:blank,:blank,POWER:POWEROFF";
$row[1] = "TVPAUSE:TVstop,ASPECT,MODE3D,TRACKID";
$row[2] = "PREVIOUS,REWIND,FORWARD:FF,NEXT";
$row[3] = "REC,PLAY,PAUSE,STOP";
$row[4] = "RED,GREEN,YELLOW,BLUE";
$row[5] = ":blank,:blank,:blank";
$row[6] = "HELP,SEN,SYNCMENU";
$row[7] = "GUIDE,UP,INFO";
$row[8] = "LEFT,OK,RIGHT";
$row[9] = "RETURN,DOWN,OPTIONS:SUBMENU,HOMEtxt";
$row[10] = ":blank,:blank,:blank";
$row[11] = "DIGITAL,EXIT,TV";
$row[12] = "1,2,3";
$row[13] = "4,5,6";
$row[14] = "7,8,9";
$row[15] = "TEXT,0,SUBTITLE";
$row[16] = ":blank,:blank,:blank";
$row[17] = "MUTE,VOLUP:VOLUP2,CHANNELUP:CHUP2,AUDIO";
$row[18] = ":blank,VOLDOWN:VOLDOWN2,CHANNELDOWN:CHDOWN2";
$row[19] = "attr rc_iconpath icons/remotecontrol";
$row[20] = "attr rc_iconprefix black_btn_";
return @row;
}
###################################
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
# 755
#
#
#
# 755
#
# 755
#
#
#
#
# 755
# 755
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
# 755
# 755
# 755
#
#
# 755
# 755
# 755
# 755
# 755
# 755
# 755
# 755
sub GetRemotecontrolCommand {
my ($command) = @_;
my $commands = {
'POWER' => "AAAAAQAAAAEAAAAVAw==",
'STANDBY' => "AAAAAQAAAAEAAAAvAw==",
'EXIT' => "AAAAAQAAAAEAAABjAw==",
'RED' => "AAAAAgAAAJcAAAAlAw==",
'GREEN' => "AAAAAgAAAJcAAAAmAw==",
'YELLOW' => "AAAAAgAAAJcAAAAnAw==",
'BLUE' => "AAAAAgAAAJcAAAAkAw==",
'HOME' => "AAAAAQAAAAEAAABgAw==",
'VOLUP' => "AAAAAQAAAAEAAAASAw==",
'VOLUMEUP' => "AAAAAQAAAAEAAAASAw==",
'VOLDOWN' => "AAAAAQAAAAEAAAATAw==",
'VOLUMEDOWN' => "AAAAAQAAAAEAAAATAw==",
'MUTE' => "AAAAAQAAAAEAAAAUAw==",
'OPTIONS' => "AAAAAgAAAJcAAAA2Aw==",
'DOT' => "AAAAAgAAAJcAAAAdAw==",
'0' => "AAAAAQAAAAEAAAAJAw==",
'1' => "AAAAAQAAAAEAAAAAAw==",
'2' => "AAAAAQAAAAEAAAABAw==",
'3' => "AAAAAQAAAAEAAAACAw==",
'4' => "AAAAAQAAAAEAAAADAw==",
'5' => "AAAAAQAAAAEAAAAEAw==",
'6' => "AAAAAQAAAAEAAAAFAw==",
'7' => "AAAAAQAAAAEAAAAGAw==",
'8' => "AAAAAQAAAAEAAAAHAw==",
'9' => "AAAAAQAAAAEAAAAIAw==",
'GUIDE' => "AAAAAQAAAAEAAAAOAw==",
'INFO' => "AAAAAQAAAAEAAAA6Aw==",
'UP' => "AAAAAQAAAAEAAAB0Aw==",
'DOWN' => "AAAAAQAAAAEAAAB1Aw==",
'LEFT' => "AAAAAQAAAAEAAAA0Aw==",
'RIGHT' => "AAAAAQAAAAEAAAAzAw==",
'OK' => "AAAAAQAAAAEAAABlAw==",
'RETURN' => "AAAAAgAAAJcAAAAjAw==",
'NEXT' => "AAAAAgAAAJcAAAA9Aw==",
'PREVIOUS' => "AAAAAgAAAJcAAAA8Aw==",
'TV' => "AAAAAgAAABoAAABXAw==",
'TVPAUSE' => "AAAAAgAAABoAAABnAw==",
'MODE3D' => "AAAAAgAAAHcAAABNAw==",
'TEXT' => "AAAAAQAAAAEAAAA/Aw==",
'SUBTITLE' => "AAAAAgAAAJcAAAAoAw==",
'CHANUP' => "AAAAAQAAAAEAAAAQAw==",
'CHANNELUP' => "AAAAAQAAAAEAAAAQAw==",
'CHANDOWN' => "AAAAAQAAAAEAAAARAw==",
'CHANNELDOWN' => "AAAAAQAAAAEAAAARAw==",
'SOURCE' => "AAAAAQAAAAEAAAAlAw==",
'PLAY' => "AAAAAgAAAJcAAAAaAw==",
'PAUSE' => "AAAAAgAAAJcAAAAZAw==",
'FORWARD' => "AAAAAgAAAJcAAAAcAw==",
'STOP' => "AAAAAgAAAJcAAAAYAw==",
'REWIND' => "AAAAAgAAAJcAAAAbAw==",
'RECORD' => "AAAAAgAAAJcAAAAgAw==",
'ASPECT' => "AAAAAQAAAAEAAAA6Aw==",
'HELP' => "AAAAAgAAABoAAAB7Aw==",
'DIGITAL' => "AAAAAgAAABoAAAA7Aw==",
'TRACKID' => "AAAAAgAAABoAAAB+Aw==",
'AUDIO' => "AAAAAQAAAAEAAAAXAw==",
'SEN' => "AAAAAgAAABoAAAB9Aw==",
'SYNCMENU' => "AAAAAgAAABoAAABYAw==",
'SCENESELECT' => "AAAAAgAAABoAAAB4Aw==",
'NETFLIX' => "AAAAAgAAABoAAAB8Aw==",
'PICTUREMODE' => "AAAAAQAAAAEAAABkAw==",
};
if ( defined( $commands->{$command} ) ) {
return $commands->{$command};
}
elsif ( $command eq "GetRemotecontrolCommands" ) {
return $commands;
}
else {
# return command itself if not mapped
return $command;
}
}
sub GetModelYear {
my ($command) = @_;
my $commands = {
'1.0' => "2011",
'1.1' => "2012",
'1.0.4' => "2013",
'1.0.5' => "2013", #KDL42-W655A
'2.4.0' => "2014",
'2.5.0' => "2014", #KD-49X8505B
'3.8.0' => "2016", #KD-55XD8505
'3.9.0' => "201x", #KD-55X8505C,KD-55XD8505
};
if (defined( $commands->{$command})) {
return $commands->{$command};
} else {
return "";
}
}
sub GetIrccRequest {
my ($cmd) = @_;
my $data = "";
$data .= "";
$data .= "";
$data .= "";
$data .= "" . $cmd . "";
$data .= "";
$data .= "";
$data .= "";
return $data;
}
sub GetUpnpRequest {
my ($cmd,$value) = @_;
my $data = "";
$data .= "";
$data .= "";
if ($cmd eq "getVolume") {
$data .= "";
$data .= "0";
$data .= "Master";
$data .= "";
} elsif ($cmd eq "setVolume") {
$data .= "";
$data .= "0";
$data .= "Master";
$data .= "";
$data .= $value;
$data .= "";
$data .= "";
} elsif ($cmd eq "getMute") {
$data .= "";
$data .= "0";
$data .= "Master";
$data .= "";
} elsif ($cmd eq "setMute") {
$data .= "";
$data .= "0";
$data .= "Master";
$data .= "";
$data .= $value;
$data .= "";
$data .= "";
}
$data .= "";
$data .= "";
return $data;
}
sub CheckRegistration {
my ( $hash, $service, $cmd, $param, @successor ) = @_;
my $name = $hash->{NAME};
my $authCookie = ReadingsVal($name, "authCookie", "");
my $authCookieTS = ReadingsTimestamp($name, "authCookie", "");
if ($authCookie ne "" and
$authCookieTS =~ m/^(\d{4})-(\d{2})-(\d{2})\ ([0-2]\d):([0-5]\d):([0-5]\d)$/xms) {
my $time = fhemTimeLocal($6, $5, $4, $3, $2 - 1, $1 - 1900);
# max age defaults to 14 days
my $maxAge = ReadingsNum($name, "authMaxAge", 1209600);
# renew registration after half period of validity
if ($time + $maxAge/2 < time()) {
Log3($name, 3, "BRAVIA $name: renew registration");
my @nextCmd = ($service, $cmd, $param);
unshift(@successor, [$service, $cmd, $param]);
my @succ_item;
my $msg = " successor:";
for (my $i = 0; $i < @successor; $i++) {
@succ_item = @{$successor[$i]};
$msg .= " $i: ";
$msg .= join(",", map { defined($_) ? $_ : '' } @succ_item);
}
Log3($name, 4, "BRAVIA $name: created".$msg);
SendCommand( $hash, "register", "renew", undef, @successor );
return 1;
} else {
Log3($name, 5, "BRAVIA $name: registration valid until $authCookieTS");
}
} else {
Log3($name, 4, "BRAVIA $name: authCookie not valid '$authCookie $authCookieTS'");
}
return;
}
sub CheckServiceAvailable {
my ($hash, $service, $cmd, $param, @successor) = @_;
my $name = $hash->{NAME};
if (!defined($hash->{helper}{services}) || join(',', @{$hash->{helper}{services}}) !~ /audio/xms) {
if ($service eq 'getVolumeInformation' && ReadingsVal($name, 'upnp', 'on') eq 'on') {
unshift(@successor, ['upnp', 'getMute']);
SendCommand($hash, 'upnp', 'getVolume', undef, @successor);
return;
} elsif ($service eq 'setAudioVolume') {
SendCommand($hash, 'upnp', "setVolume:$cmd", undef, @successor);
return;
} elsif ($service eq 'setAudioMute') {
SendCommand($hash, 'upnp', 'setMute:'.($cmd eq 'false' ? '0' : '1'), undef, @successor);
return;
}
}
return 1;
}
sub GetNormalizedName {
my ( $name ) = @_;
$name =~ s/^\s+//xms;
$name =~ s/\s+$//xms;
$name =~ s/\s/#/gxms;
$name =~ s/,/./gxms;
return $name;
}
1;
=pod
=item summary controls a Sony TV device of series starting from 2011 via LAN
=begin html
BRAVIA
This module controls a Sony TV device over ethernet. Devices of series starting from 2011 are supported.
Define
define <name> BRAVIA <ip-or-hostname> [<poll-interval>]
With definition of a BRAVIA device an internal task will be scheduled.
This task pulls frequently the status and other information from the TV.
The intervall can be defined in seconds by an optional parameter <poll-intervall>.
The default value is 45 seconds.
After definition of a device using this module it has to be registered as a remote control
(set register
).
As long as readings are not among the usual AV readings they are clustered:
s_* | : status |
ci_* | : content information |
The module contains predefined layouts for remotecontrol using PNG and SVG.
Set
set <name> <option> <value>
Options:
- application
List of applications.
Applications are available with models from 2013 and newer.
- channel
List of all known channels. The module collects all visited channels.
Channels can be loaded automtically with models from 2013 and newer.
(number of channels, see channelsMax).
- channelDown
Switches a channel back.
- channelUp
Switches a channel forward.
- input
List of input channels.
Imputs are available with models from 2013 and newer.
- mute
Set mute if Upnp is activated.
- off
Switches TV to off. State of device will have been set to "set_off" for 60 seconds or until off-status is pulled from TV.
- on
Switches TV to on, with models from 2013 using WOL. State of device will have been set to "set_on" for 60 seconds or until on-status is pulled from TV.
- openUrl
Opens an URL on the screen.
This Feature is available on models from 2013 and newer.
- pause
Pauses a playing of a recording, of an internal App, etc.
- play
Starts playing of a recording, of an internal App, etc.
- record
Starts recording of current content.
- register
One-time registration of Fhem as remote control in the TV.
With requestFormat = "xml" registration works without parameter.
With requestFormat = "json" registration has to be executed twice.
The register option offers an additional input field:
- Call with empty input. A PIN for registration has to be shown on the TV.
- Insert PIN into input field and register again.
- requestFormat
"xml" for xml based communication (models from 2011 and 2012)
"json" for communication with models from 2013 and newer
- requestReboot
Reboots the TV immediately.
This Feature is available on models from 2013 and newer.
- remoteControl
Sends command directly to TV.
- statusRequest
Retrieves current status information from TV.
- stop
Stops recording, playing of an internal App, etc.
- text
Includes the given text into an input field on display.
- toggle
Toggles power status of TV.
- tvpause
Activates Timeshift mode.
- upnp
Activates Upnp service used to control volume.
- volume
Straight setting of volume. Upnp service has to be activated.
- volumeDown
Decreases volume.
- volumeUp
Increases volume.
Attributes
attr <name> <attribute> <value>
Attributes:
- channelsMax
Maximum amount of channels to be displayed, default is 50.
- macaddr
Enables power on of TV using Wake-On-Lan.
- wolBroadcast
Broadcast address for Wake-On-Lan magic packets, default is 255.255.255.255.
=end html
=item summary_DE steuert Sony TVs der BRAVIA-Serien ab dem Modelljahr 2011, via LAN-Verbindung
=begin html_DE
BRAVIA
Diese Module dient zur Steuerung von Sony TVs der BRAVIA-Serien beginnend mit dem Modelljahr 2011.
Define
define <name> BRAVIA <ip-or-hostname> [<poll-interval>]
Bei der Definition eines BRAVIA Gerätes wird ein interner Task eingeplant,
der regelmäßig den Status des TV prüft und weitere Informationen abruft.
Das Intervall des Tasks kann durch den optionalen Parameter <poll-intervall> in Sekunden gesetzt werden.
Ansonsten wird der Task mit 45 Sekunden als Intervall definiert.
Nach der Definition eines Gerätes muss dieses einmalig im TV als Fernbedienung
registriert werden (set register
).
Soweit die Readings nicht den allgemeinen AV Readings entsprechen, sind sie gruppiert:
s_* | : Status |
ci_* | : Inhaltsinfo |
Das Modul enthält vorgefertigte Layouts für remotecontrol mit PNG und SVG.
Set
set <name> <option> <value>
Optionen:
- application
Liste der Anwendungen.
Anwendungen sind ab Modelljahr 2013 verfügbar.
- channel
Liste aller bekannten Kanäle. Das Modul merkt sich alle aufgerufenen Kanäle.
Ab Modelljahr 2013 werden die Kanäle automatisch geladen
(Anzahl siehe channelsMax).
- channelDown
Einen Kanal zurück schalten.
- channelUp
Einen Kanal weiter schalten.
- input
Liste der Eingänge.
Eingänge sind ab Modelljahr 2013 verfügbar.
- mute
Direkte Stummschaltung erfolgt nur per aktiviertem Upnp.
- off
Schaltet den TV aus. Der State des Gerätes wird auf "set_off" gesetzt. Dieser Wert wird nach 60 Sekunden wieder überschrieben oder sobald der TV entsprechend "off" meldet.
- on
Einschalten des TV, ab Modelljahr 2013 per WOL. Der State des Gerätes wird auf "set_on" gesetzt. Dieser Wert wird nach 60 Sekunden wieder überschrieben oder sobald der TV entsprechend "on" meldet.
- openUrl
Öffnet eine URL auf dem Bildschirm.
Diese Funktion ist ab Modelljahr 2013 verfügbar.
- pause
Pausiert die Wiedergabe einer Aufnahme, einer internen App, etc.
- play
Startet die Wiedergabe einer Aufnahme, einer internen App, etc.
- record
Startet die Aufnahme des aktuellen Inhalts.
- register
Einmalige Registrierung von FHEM als Fernbedienung im TV.
Bei requestFormat = "xml" erfolgt die Registrierung ohne Parameter.
Bei requestFormat = "json" ist die Registrierung zweistufig.
Beim Aufruf des Setter gibt es ein Eingabefeld:
- Aufruf mit leerem Eingabefeld. Auf dem TV sollte eine PIN zur Registrierung erscheinen.
- PIN im Eingabefeld eintragen und Registrierung noch mal ausführen
- requestFormat
"xml" für xml-basierte Kommunikation 2011er/2012er Geräte
"json" für die Kommunikation seit der 2013er Generation
- requestReboot
Startet den TV direkt neu.
Diese Funktion ist ab Modelljahr 2013 verfügbar.
- remoteControl
Direktes Senden von Kommandos an den TV.
- statusRequest
Ruft die aktuellen Statusinformationen vom TV ab.
- stop
Stoppt die Wiedergabe einer Aufnahme, einer internen App, etc.
- text
Überträgt den eingegebenen Text in ein Textfeld der Anzeige.
- toggle
Wechselt den Einschaltstatus des TV.
- tvpause
Aktiviert den Timeshift-Modus.
- upnp
Aktiviert Upnp zum Abfragen und Einstellen der Lautstärke.
- volume
Direktes Setzen der Lautstärke erfolgt nur per aktiviertem Upnp.
- volumeDown
Verringert die Lautstärke.
- volumeUp
Erhöht die Lautstärke.
Attributes
attr <name> <attribute> <value>
Attribute:
- channelsMax
Maximale Anzahl der im FHEMWEB angezeigten Kanäle. Der Standartwert ist 50.
- macaddr
Ermöglicht das Einschalten des TV per Wake-On-Lan.
- wolBroadcast
Broadcast-Adresse für die Wake-On-Lan Magic Packets. Der Standartwert ist 255.255.255.255.
=end html_DE
=cut