# $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 .
#
##############################################################################
package main;
use 5.012;
use strict;
use warnings;
use Data::Dumper;
use Time::HiRes qw(gettimeofday);
use Time::Local;
use HttpUtils;
use SetExtensions;
use Encode;
use JSON qw(decode_json);
use MIME::Base64;
use XML::Simple qw(:strict);
use IO::Socket;
sub BRAVIA_Set($@);
sub BRAVIA_Get($@);
sub BRAVIA_GetStatus($;$);
sub BRAVIA_Define($$);
sub BRAVIA_Undefine($$);
#########################
# Forward declaration for remotecontrol module
#sub BRAVIA_RClayout_TV();
#sub BRAVIA_RCmakenotify($$);
###################################
sub BRAVIA_Initialize($) {
my ($hash) = @_;
Log3 $hash, 5, "BRAVIA_Initialize: Entering";
$hash->{GetFn} = "BRAVIA_Get";
$hash->{SetFn} = "BRAVIA_Set";
$hash->{DefFn} = "BRAVIA_Define";
$hash->{UndefFn} = "BRAVIA_Undefine";
$hash->{AttrList} = "disable:0,1 macaddr:textField channelsMax:textField " . $readingFnAttributes;
$data{RC_layout}{BRAVIA_SVG} = "BRAVIA_RClayout_SVG";
$data{RC_layout}{BRAVIA} = "BRAVIA_RClayout";
$data{RC_makenotify}{BRAVIA} = "BRAVIA_RCmakenotify";
return;
}
#####################################
sub BRAVIA_GetStatus($;$) {
my ( $hash, $update ) = @_;
my $name = $hash->{NAME};
my $interval = $hash->{INTERVAL};
Log3 $name, 5, "BRAVIA $name: called function BRAVIA_GetStatus()";
RemoveInternalTimer($hash);
InternalTimer( gettimeofday() + $interval, "BRAVIA_GetStatus", $hash, 0 );
return if ( AttrVal($name, "disable", 0) == 1 );
# check device availability
if (!$update) {
BRAVIA_SendCommand( $hash, "getStatus", "xml" )
if (ReadingsVal($name, "requestFormat", "xml") eq "xml");
BRAVIA_SendCommand( $hash, "getStatus", "json" )
if (ReadingsVal($name, "requestFormat", "json") eq "json");
}
return;
}
###################################
sub BRAVIA_Get($@) {
my ( $hash, @a ) = @_;
my $name = $hash->{NAME};
my $what;
Log3 $name, 5, "BRAVIA $name: called function BRAVIA_Get()";
return "argument is missing" if ( int(@a) < 2 );
$what = $a[1];
if ( $what =~ /^(power|presence|input|channel|volume|mute)$/ ) {
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 BRAVIA_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 BRAVIA_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 .= " application:" . $apps if ( $apps ne "" );
$usage .= " text" if (ReadingsVal($name, "requestFormat", "") eq "json");
my $cmd = '';
my $result;
# statusRequest
if ( lc( $a[1] ) eq "statusrequest" ) {
Log3 $name, 2, "BRAVIA set $name " . $a[1];
delete $hash->{helper}{device}
if ( defined( $hash->{helper}{device} ) );
BRAVIA_GetStatus($hash);
}
# toggle
elsif ( $a[1] eq "toggle" ) {
Log3 $name, 2, "BRAVIA set $name " . $a[1];
if ( $power eq "off" ) {
return BRAVIA_Set( $hash, $name, "on" );
}
else {
return BRAVIA_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") ) {
$result = BRAVIA_wake( $name, $macAddr );
return "wake-up command sent";
} else {
$cmd = "POWER";
BRAVIA_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";
}
BRAVIA_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+$/ && $vol >= 1 && $vol <= 100 ) {
$cmd = 'setVolume:' . $vol;
}
else {
return
"Argument does not seem to be a valid integer between 1 and 100";
}
BRAVIA_SendCommand( $hash, "upnp", $cmd );
readingsSingleUpdate( $hash, "volume", $a[2], 1 )
if ( ReadingsVal($name, "volume", "") ne $a[2] );
}
else {
return "Device needs to be ON to adjust volume.";
}
}
# volumeUp/volumeDown
elsif ( lc( $a[1] ) =~ /^(volumeup|volumedown)$/ ) {
Log3 $name, 2, "BRAVIA set $name " . $a[1];
if ( $presence eq "present" ) {
if ( lc( $a[1] ) eq "volumeup" ) {
$cmd = "VOLUP";
}
else {
$cmd = "VOLDOWN";
}
BRAVIA_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" ) {
$result = BRAVIA_SendCommand( $hash, "ircc", "MUTE" );
readingsSingleUpdate( $hash, "mute", (ReadingsVal($name, "mute", "") eq "on" ? "off" : "on"), 1 );
}
elsif ( $a[2] eq "off" ) {
#$result = BRAVIA_SendCommand( $hash, "MuteOff" )
$result = BRAVIA_SendCommand( $hash, "upnp", "setMute:0" );
readingsSingleUpdate( $hash, "mute", $a[2], 1 )
if ( ReadingsVal($name, "mute", "") ne $a[2] );
}
elsif ( $a[2] eq "on" ) {
#$result = BRAVIA_SendCommand( $hash, "MuteOn" )
$result = BRAVIA_SendCommand( $hash, "upnp", "setMute:1" );
readingsSingleUpdate( $hash, "mute", $a[2], 1 )
if ( ReadingsVal($name, "mute", "") ne $a[2] );
}
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 ( $presence eq "present" ) {
if ( !defined( $a[2] ) ) {
my $commandKeys = "";
for (
sort keys %{
BRAVIA_GetRemotecontrolCommand(
"GetRemotecontrolCommands")
}
)
{
$commandKeys = $commandKeys . " " . $_;
}
return "No argument given, choose one of" . $commandKeys;
}
$cmd = uc( $a[2] );
if ( $cmd eq "MUTE" ) {
BRAVIA_Set( $hash, $name, "mute" );
}
elsif ( $cmd eq "CHANUP" ) {
BRAVIA_Set( $hash, $name, "channelUp" );
}
elsif ( $cmd eq "CHANDOWN" ) {
BRAVIA_Set( $hash, $name, "channelDown" );
}
elsif ( $cmd eq "WOL" ) {
$macAddr = AttrVal( $name, "macaddr", "" );
$macAddr = ReadingsVal( $name, "macAddr", "") if ($macAddr eq "");
BRAVIA_wake( $name, $macAddr ) if ( $macAddr ne "" && $macAddr ne "-" );
}
elsif ( $cmd ne "" ) {
BRAVIA_SendCommand( $hash, "ircc", $cmd );
}
else {
my $commandKeys = "";
for (
sort keys %{
BRAVIA_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";
BRAVIA_Set( $hash, $name, "on" );
}
shift(@a); shift(@a);
my $channelStr = join("#", @a);
Log3 $name, 2, "BRAVIA set $name " . $a[1] . " " . $channelStr;
return
"No argument given, choose one of channel presetNumber channelName "
if ( !defined( $channelStr ) );
if ( $presence eq "present" ) {
my $channelName = $channelStr;
if ( $channelName =~ /^(\d)(\d?)(\d?)(\d?):.*$/ ) {
BRAVIA_SendCommand( $hash, "ircc", $1, "blocking" );
BRAVIA_SendCommand( $hash, "ircc", $2, "blocking" ) if (defined($2));
BRAVIA_SendCommand( $hash, "ircc", $3, "blocking" ) if (defined($3));
BRAVIA_SendCommand( $hash, "ircc", $4, "blocking" ) if (defined($4));
} else {
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)$/ ) {
Log3 $name, 2, "BRAVIA set $name " . $a[1];
if ( $presence eq "present" ) {
if ( lc( $a[1] ) eq "channelup" ) {
$cmd = "CHANUP";
}
else {
$cmd = "CHANDOWN";
}
BRAVIA_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";
BRAVIA_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 " . $a[1] . " " . $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" ) {
BRAVIA_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";
BRAVIA_Set( $hash, $name, "on" );
}
return "No 2nd argument given" if ( !defined( $a[2] ) );
shift(@a); shift(@a);
my $appStr = join("#", @a);
Log3 $name, 2, "BRAVIA set $name " . $a[1] . " " . $appStr;
# Resolve app uri
my $app_uri;
if ( defined( $hash->{helper}{device}{appPreset}{ $appStr } ) ) {
$app_uri = $hash->{helper}{device}{appPreset}{ $appStr }{uri};
} else {
return "Unknown app '" . $appStr . "' on that device.";
}
if ( $presence eq "present" ) {
BRAVIA_SendCommand( $hash, "setActiveApp", $app_uri );
}
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" ) {
BRAVIA_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" ) {
BRAVIA_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" ) {
BRAVIA_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" ) {
BRAVIA_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" ) {
BRAVIA_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];
BRAVIA_SendCommand( $hash, "register", $a[2] );
} else {
Log3 $name, 2, "BRAVIA set $name " . $a[1];
BRAVIA_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] );
}
# 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";
BRAVIA_SendCommand( $hash, "text", $text );
}
# return usage hint
else {
return $usage;
}
return;
}
###################################
sub BRAVIA_Define($$) {
my ( $hash, $def ) = @_;
my @a = split( "[ \t][ \t]*", $def );
my $name = $hash->{NAME};
Log3 $name, 5, "BRAVIA $name: called function BRAVIA_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';
$hash->{name} = ReadingsVal($name, "name", "");
$hash->{model} = ReadingsVal($name, "model", "");
$hash->{generation} = ReadingsVal($name, "generation", "");
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, "BRAVIA_GetStatus", $hash, 1 );
return;
}
############################################################################################################
#
# Begin of helper functions
#
############################################################################################################
###################################
sub BRAVIA_SendCommand($$;$$) {
my ( $hash, $service, $cmd, $type ) = @_;
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 BRAVIA_SendCommand()";
my $URL;
my $response;
my $return;
my $requestFormat = ReadingsVal($name, "requestFormat", "");
BRAVIA_CheckRegistration($hash) if ($service ne "register" && $service ne "getStatus");
if ( !defined($cmd) ) {
Log3 $name, 4, "BRAVIA $name: REQ $service";
}
else {
Log3 $name, 4, "BRAVIA $name: REQ $service/" . urlDecode($cmd);
}
$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 = BRAVIA_GetRemotecontrolCommand($cmd);
$data = BRAVIA_GetIrccRequest($cmd);
} elsif ($service eq "upnp") {
my $value;
if ($cmd =~ m/^(.+):(\d+)$/) {
$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 = BRAVIA_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+)$/){
$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 "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."\"}]}";
}
} 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.*$/) {
$URL .= "/command/".$service;
} else {
$URL .= "/api/" . $service;
}
}
}
if ( defined( $attr{$name}{timeout} ) && $attr{$name}{timeout} =~ /^\d+$/ ) {
$timeout = $attr{$name}{timeout};
} elsif ( $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) );
if ( defined($type) && $type eq "blocking" ) {
my ($err, $data) = HttpUtils_BlockingGet(
{
url => $URL,
timeout => 4,
noshutdown => 1,
header => $header,
data => $data,
hash => $hash,
service => $service,
cmd => $cmd,
type => $type,
timestamp => $timestamp,
}
);
Log3 $name, 5, "BRAVIA $name: REQ $service received err: $err data: $data ";
sleep 1;
} else {
HttpUtils_NonblockingGet(
{
url => $URL,
timeout => $timeout,
noshutdown => 1,
header => $header,
data => $data,
hash => $hash,
service => $service,
cmd => $cmd,
type => $type,
timestamp => $timestamp,
callback => \&BRAVIA_ReceiveCommand,
}
);
}
return;
}
###################################
sub BRAVIA_ReceiveCommand($$$) {
my ( $param, $err, $data ) = @_;
my $hash = $param->{hash};
my $name = $hash->{NAME};
my $service = $param->{service};
my $cmd = $param->{cmd};
my $newstate;
my $rc = ( $param->{buf} ) ? $param->{buf} : $param;
my $return;
Log3 $name, 5, "BRAVIA $name: called function BRAVIA_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" ) {
BRAVIA_ClearContentInformation($hash);
$newstate = "absent";
if (
( !defined( $hash->{helper}{AVAILABLE} ) )
or ( defined( $hash->{helper}{AVAILABLE} )
and $hash->{helper}{AVAILABLE} eq 1 )
)
{
$hash->{helper}{AVAILABLE} = 0;
readingsSingleUpdate( $hash, "presence", "absent", 1 );
}
}
}
# data received
elsif ($data) {
if (
( !defined( $hash->{helper}{AVAILABLE} ) )
or ( defined( $hash->{helper}{AVAILABLE} )
and $hash->{helper}{AVAILABLE} eq 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);
}
if ( $data ne "" ) {
if ( $data =~ /^<\?xml/ ) {
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 =~ /^{/ || $data =~ /^\[/ ) {
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 );
}
return;
}
###################################
sub BRAVIA_Undefine($$) {
my ( $hash, $arg ) = @_;
my $name = $hash->{NAME};
Log3 $name, 5, "BRAVIA $name: called function BRAVIA_Undefine()";
# Stop the internal GetStatus-Loop and exit
RemoveInternalTimer($hash);
return;
}
###################################
sub BRAVIA_wake ($$) {
my ( $name, $mac_addr ) = @_;
my $address = '255.255.255.255';
my $port = 9;
my $sock = new IO::Socket::INET( Proto => 'udp' )
or die "socket : $!";
die "Can't create WOL socket" if ( !$sock );
my $ip_addr = inet_aton($address);
my $sock_addr = sockaddr_in( $port, $ip_addr );
$mac_addr =~ s/://g;
my $packet =
pack( 'C6H*', 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, $mac_addr x 16 );
setsockopt( $sock, SOL_SOCKET, SO_BROADCAST, 1 )
or die "setsockopt : $!";
Log3 $name, 4,
"BRAVIA $name: Waking up by sending Wake-On-Lan magic package to "
. $mac_addr;
send( $sock, $packet, 0, $sock_addr ) or die "send : $!";
close($sock);
return;
}
###################################
# process return data
sub BRAVIA_ProcessCommandData ($$) {
my ($param, $return) = @_;
my $hash = $param->{hash};
my $name = $hash->{NAME};
my $service = $param->{service};
my $cmd = $param->{cmd};
my $type = ( $param->{type} ) ? $param->{type} : "";
my $header = $param->{httpheader};
my $newstate;
# ircc
if ( $service eq "ircc" ) {
if ( ref($return) ne "HASH" && $return eq "ok" ) {
# toggle standby
if ( defined($type) && $type eq "off" ) {
$newstate = "off";
}
# toggle standby
elsif ( defined($type) && $type eq "on" ) {
$newstate = "on";
}
}
}
# 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_.*/ && 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 {
readingsBulkUpdate( $hash, "s_".$_->{field}, $_->{value} )
if (ReadingsVal($name, "s_".$_->{field}, "") ne $_->{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 {
readingsBulkUpdate( $hash, $field, $return->{status}{statusItem}{value} )
if (ReadingsVal($name, $field, "") ne $return->{status}{statusItem}{value} );
}
delete $statusKeys{$field};
}
}
}
readingsBulkUpdate( $hash, "input", $input )
if ( defined($setInput) and
(ReadingsVal($name, "input", "") ne $input) );
#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") {
BRAVIA_ClearContentInformation($hash);
return "off";
}
# fetch other info
# read system information if not existing
BRAVIA_SendCommand( $hash, "getSystemInformation" )
if ( ReadingsVal($name, "name", "0") eq "0" || ReadingsVal($name, "model", "0") eq "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
BRAVIA_ClearContentInformation($hash);
$newstate = "off";
} else {
BRAVIA_SendCommand( $hash, "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" && ReadingsVal($name, "upnp", "on") eq "on") {
BRAVIA_SendCommand( $hash, "upnp", "getVolume" );
BRAVIA_SendCommand( $hash, "upnp", "getMute" );
}
}
}
# 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} );
$hash->{name} = $sysInfo->{name};
$hash->{model} = $sysInfo->{model};
$hash->{generation} = $sysInfo->{generation};
} 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} );
$hash->{name} = $return->{name};
$hash->{model} = $return->{modelName};
$hash->{generation} = $return->{generation};
}
readingsEndUpdate( $hash, 1 );
}
}
# getContentInformation
elsif ( $service eq "getContentInformation" ) {
my %contentKeys;
my $channelName = "-";
my $channelNo = "-";
my $currentTitle = "-";
my $currentMedia = "-";
foreach ( keys %{ $hash->{READINGS} } ) {
$contentKeys{$_} = 1
if ( $_ =~ /^ci_.*/ 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 = BRAVIA_GetNormalizedName($_->{value});
} elsif ( $_->{field} eq "title" ) {
$currentTitle = $_->{value};
} else {
readingsBulkUpdate( $hash, "ci_".$_->{field}, $_->{value} )
if ( ReadingsVal($name, "ci_".$_->{field}, "") ne $_->{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" ) {
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 = BRAVIA_GetNormalizedName($return->{result}[0]{$_});
} elsif ( $_ eq "programTitle" ) {
$currentTitle = $return->{result}[0]{$_};
} elsif ( $_ eq "source" ) {
readingsBulkUpdate( $hash, "input", $return->{result}[0]{$_} )
if ( ReadingsVal($name, "input", "") ne $return->{result}[0]{$_} );
} elsif ( $_ eq "uri" ) {
readingsBulkUpdate( $hash, "uri", $return->{result}[0]{$_} )
if ( ReadingsVal($name, "uri", "") ne $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:.*/);
} else {
readingsBulkUpdate( $hash, "ci_".$_, $return->{result}[0]{$_} )
if ( ReadingsVal($name, "ci_".$_, "") ne $return->{result}[0]{$_} );
delete $contentKeys{"ci_".$_};
}
}
readingsEndUpdate( $hash, 1 );
} elsif ( ref($return->{error}) eq "ARRAY" && $return->{error}[0] eq "7" && $return->{error}[1] eq "Illegal State" ) {
#could be timeshift mode
BRAVIA_SendCommand( $hash, "getScheduleList" );
return;
}
}
} else {
if ( ReadingsVal($name, "input", "") eq "Others" || ReadingsVal($name, "input", "") eq "Broadcast" ) {
$newstate = "off";
} else {
$newstate = "on";
}
}
readingsBeginUpdate($hash);
readingsBulkUpdate( $hash, "channel", $channelName )
if ( ReadingsVal($name, "channel", "") ne $channelName );
readingsBulkUpdate( $hash, "channelId", $channelNo )
if ( ReadingsVal($name, "channelId", "") ne $channelNo );
readingsBulkUpdate( $hash, "currentTitle", $currentTitle )
if ( ReadingsVal($name, "currentTitle", "") ne $currentTitle );
readingsBulkUpdate( $hash, "currentMedia", $currentMedia )
if ( ReadingsVal($name, "currentMedia", "") ne $currentMedia );
#remove outdated content information - replaces by "-"
foreach ( keys %contentKeys ) {
readingsBulkUpdate( $hash, $_, "-" );
}
readingsEndUpdate( $hash, 1 );
if ($channelName ne "-" && $channelNo ne "-") {
BRAVIA_SendCommand( $hash, "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} = $channelName;
}
# get current system settings
if ($newstate eq "on" && ReadingsVal($name, "upnp", "on") eq "on") {
BRAVIA_SendCommand( $hash, "upnp", "getVolume" );
BRAVIA_SendCommand( $hash, "upnp", "getMute" );
}
# load input list if just switched on
if ($newstate eq "on"
&& (ReadingsVal($name, "state", "") ne "on" || !defined($hash->{helper}{device}{inputPreset}))
&& ReadingsVal($name, "requestFormat", "") eq "json") {
BRAVIA_SendCommand( $hash, "getCurrentExternalInputsStatus" );
}
# load app list if just switched on
if ($newstate eq "on"
&& (ReadingsVal($name, "state", "") ne "on" || !defined($hash->{helper}{device}{appPreset}))
&& ReadingsVal($name, "requestFormat", "") eq "json") {
BRAVIA_SendCommand( $hash, "getApplicationList" );
}
}
# getScheduleList
elsif ( $service eq "getScheduleList" ) {
my %contentKeys;
my $channelName = "-";
my $currentTitle = "-";
my $currentMedia = "-";
foreach ( keys %{ $hash->{READINGS} } ) {
$contentKeys{$_} = 1
if ( $_ =~ /^ci_.*/ 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") {
my $key;
foreach $key ( keys %{ $_ }) {
if ( $key eq "type" ) {
$currentMedia = $_->{$key};
readingsBulkUpdate( $hash, "input", $_->{$key} )
if ( ReadingsVal($name, "input", "") ne $_->{$key} );
} elsif ( $key eq "channelName" ) {
$channelName = BRAVIA_GetNormalizedName($_->{$key});
} elsif ( $key eq "title" ) {
$currentTitle = $_->{$key};
} else {
readingsBulkUpdate( $hash, "ci_".$key, $_->{$key} )
if ( ReadingsVal($name, "ci_".$key, "") ne $_->{$key} );
delete $contentKeys{"ci_".$key};
}
}
}
}
}
}
}
readingsBulkUpdate( $hash, "channel", $channelName )
if ( ReadingsVal($name, "channel", "") ne $channelName );
readingsBulkUpdate( $hash, "currentTitle", $currentTitle )
if ( ReadingsVal($name, "currentTitle", "") ne $currentTitle );
readingsBulkUpdate( $hash, "currentMedia", $currentMedia )
if ( ReadingsVal($name, "currentMedia", "") ne $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") {
BRAVIA_SendCommand( $hash, "upnp", "getVolume" );
BRAVIA_SendCommand( $hash, "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 $key;
foreach $key ( keys %{ $_ }) {
if ( $key eq "dispNum" ) {
$channelNo = $_->{$key};
} elsif ( $key eq "title" ) {
$channelName = BRAVIA_GetNormalizedName($_->{$key});
} elsif ( $key eq "index" ) {
$channelIndex = $_->{$key};
}
}
$hash->{helper}{device}{channelPreset}{ $channelNo }{id} = $channelNo;
$hash->{helper}{device}{channelPreset}{ $channelNo }{name} = $channelName;
}
}
}
}
# increment index, because it starts with 0
if (++$channelIndex % InternalVal($name, "CHANNELCOUNT", 50) == 0) {
# try next junk of channels
BRAVIA_SendCommand( $hash, "getContentList", ReadingsVal($name, "input", "")."|".$channelIndex );
}
}
# getCurrentExternalInputsStatus
elsif ( $service eq "getCurrentExternalInputsStatus" ) {
my $channelIndex = 0;
if ( ref($return) eq "HASH" ) {
if (ref($return->{result}) eq "ARRAY") {
foreach ( @{ $return->{result} } ) {
foreach ( @{ $_ } ) {
my $inputName;
my $inputUri;
my $key;
foreach $key ( keys %{ $_ }) {
if ( $key eq "uri" ) {
$inputUri = $_->{$key};
} elsif ( $key eq "title" ) {
$inputName = BRAVIA_GetNormalizedName($_->{$key});
}
}
$hash->{helper}{device}{inputPreset}{$inputName}{uri} = $inputUri;
}
}
my $tvUri = ReadingsVal($name, "uri", "tv");
$tvUri = "tv" if ($tvUri !~ /tv.*/);
$hash->{helper}{device}{inputPreset}{TV}{uri} = $tvUri;
}
}
}
# getApplicationList
elsif ( $service eq "getApplicationList" ) {
my $channelIndex = 0;
if ( ref($return) eq "HASH" ) {
if (ref($return->{result}) eq "ARRAY") {
foreach ( @{ $return->{result} } ) {
foreach ( @{ $_ } ) {
my $appName;
my $appUri;
my $key;
foreach $key ( keys %{ $_ }) {
if ( $key eq "uri" ) {
$appUri = $_->{$key};
} elsif ( $key eq "title" ) {
$appName = BRAVIA_GetNormalizedName($_->{$key});
}
}
$hash->{helper}{device}{appPreset}{$appName}{uri} = $appUri;
}
}
}
}
}
# setPlayContent
elsif ( $service eq "setPlayContent" ) {
# nothing to do
}
# setActiveApp
elsif ( $service eq "setActiveApp" ) {
# nothing to do
}
# text
elsif ( $service eq "text" ) {
# nothing to do
}
# register
elsif ( $service eq "register" ) {
readingsBeginUpdate($hash);
if ( $header =~ /auth=([A-Za-z0-9]+)/ ) {
readingsBulkUpdate( $hash, "authCookie", $1 );
}
if ( $header =~ /[Ee]xpires=([^;]+)/ ) {
readingsBulkUpdate( $hash, "authExpires", $1 );
}
if ( $header =~ /[Mm]ax-[Aa]ge=(\d+)/ ) {
readingsBulkUpdate( $hash, "authMaxAge", $1 ) if (ReadingsVal($name, "authMaxAge", 0) != $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 BRAVIA_ClearContentInformation ($) {
my ($hash) = @_;
my $name = $hash->{NAME};
readingsBeginUpdate($hash);
#remove outdated content information - replaces by "-"
foreach ( keys %{ $hash->{READINGS} } ) {
readingsBulkUpdate($hash, $_, "-")
if ( $_ =~ /^ci_.*/ and ReadingsVal($name, $_, "") ne "-" );
}
readingsBulkUpdate( $hash, "channel", "-" )
if ( ReadingsVal($name, "channel", "") ne "-" );
readingsBulkUpdate( $hash, "channelId", "-" )
if ( ReadingsVal($name, "channelId", "") ne "-" );
readingsBulkUpdate( $hash, "currentTitle", "-" )
if ( ReadingsVal($name, "currentTitle", "") ne "-" );
readingsBulkUpdate( $hash, "currentMedia", "-" )
if ( ReadingsVal($name, "currentMedia", "") ne "-" );
readingsBulkUpdate( $hash, "input", "-" )
if ( ReadingsVal($name, "input", "") ne "-" );
readingsEndUpdate( $hash, 1 );
}
#####################################
# Callback from 95_remotecontrol for command makenotify.
sub BRAVIA_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 BRAVIA_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 BRAVIA_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";
return @row;
}
###################################
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
# 755
#
#
#
# 755
#
# 755
#
#
#
#
# 755
# 755
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
# 755
# 755
# 755
#
#
# 755
# 755
# 755
# 755
# 755
# 755
# 755
# 755
sub BRAVIA_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 BRAVIA_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
};
if (defined( $commands->{$command})) {
return $commands->{$command};
} else {
return "";
}
}
sub BRAVIA_GetIrccRequest($) {
my ($cmd) = @_;
my $data = "";
$data .= "";
$data .= "";
$data .= "";
$data .= "" . $cmd . "";
$data .= "";
$data .= "";
$data .= "";
return $data;
}
sub BRAVIA_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 BRAVIA_CheckRegistration($) {
my ( $hash ) = @_;
my $name = $hash->{NAME};
if (ReadingsVal($name, "authCookie", "") ne "" and
ReadingsTimestamp($name, "authCookie", "") =~ m/^(\d{4})-(\d{2})-(\d{2}) ([0-2]\d):([0-5]\d):([0-5]\d)$/) {
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";
BRAVIA_SendCommand( $hash, "register", "renew" );
}
}
}
sub BRAVIA_GetNormalizedName($) {
my ( $name ) = @_;
$name =~ s/^\s+//;
$name =~ s/\s+$//;
$name =~ s/\s/#/g;
$name =~ s/,/./g;
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 modells from 2013 and newer.
- channel
List of all known channels. The module collects all visited channels.
Channels can be loaded automtically with modells 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 modells 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 modells 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.
- 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 (modells from 2011 and 2012)
"json" for communication with modells 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 WOL.
=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.
Anwenungen 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.
- 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
- 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 WOL.
=end html_DE
=cut