mirror of
https://github.com/fhem/fhem-mirror.git
synced 2025-02-01 19:30:31 +00:00
e88c7419ae
git-svn-id: https://svn.fhem.de/fhem/trunk@18728 2b470e98-0d58-463d-a4d8-8e2adae1ed80
2683 lines
94 KiB
Perl
Executable File
2683 lines
94 KiB
Perl
Executable File
# $Id$
|
|
##############################################################################
|
|
#
|
|
# 71_PHILIPS_AUDIO.pm
|
|
#
|
|
# An FHEM Perl module for controlling Philips Audio Equipment connected to
|
|
# local network such as MCi, Streamium and Fidelio devices.
|
|
# The module provides basic functionality accessible through the port 8889
|
|
# of the device: (http://<device_ip>:8889/index)
|
|
# e.g. AW9000, NP3500, NP3700, NP3900
|
|
#
|
|
# Copyright by ra666ack
|
|
# (e-mail: ra666ack at g**glemail d*t c*m)
|
|
#
|
|
# 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 <http://www.gnu.org/licenses/>.
|
|
#
|
|
##############################################################################
|
|
|
|
package main;
|
|
|
|
use strict;
|
|
use warnings;
|
|
use Time::HiRes qw(gettimeofday sleep);
|
|
use Time::Piece;
|
|
use POSIX qw{strftime};
|
|
use HttpUtils;
|
|
|
|
sub PHILIPS_AUDIO_Initialize
|
|
{
|
|
my ($hash) = @_;
|
|
|
|
$hash->{DefFn} = "PHILIPS_AUDIO_Define";
|
|
$hash->{GetFn} = "PHILIPS_AUDIO_Get";
|
|
$hash->{SetFn} = "PHILIPS_AUDIO_Set";
|
|
$hash->{AttrFn} = "PHILIPS_AUDIO_Attr";
|
|
$hash->{UndefFn} = "PHILIPS_AUDIO_Undefine";
|
|
|
|
$hash->{AttrList} = "do_not_notify:0,1 ".
|
|
"disable:0,1 ".
|
|
"autoGetPresets:0,1 ".
|
|
"autoGetFavorites:0,1 ".
|
|
"httpBufferTimeout ".
|
|
"maxListItems ".
|
|
"model ".
|
|
"playerBrowsingTimeout ".
|
|
"requestTimeout ".
|
|
"$readingFnAttributes";
|
|
return;
|
|
}
|
|
|
|
sub PHILIPS_AUDIO_GetStatus
|
|
{
|
|
my ($hash, $local) = @_;
|
|
my $name = $hash->{NAME};
|
|
my $power;
|
|
|
|
$local = 0 unless(defined($local));
|
|
|
|
return if((!defined($hash->{IP_ADDRESS})) or (!defined($hash->{helper}{OFF_INTERVAL})) or (!defined($hash->{helper}{ON_INTERVAL})));
|
|
my $device = $hash->{IP_ADDRESS};
|
|
|
|
# First run
|
|
$hash->{helper}{networkRequest} = "idle" if (not defined($hash->{helper}{networkRequest}));
|
|
|
|
# Try to get additional info from the device.
|
|
# Only if device already implemented.
|
|
# Otherwise possible timeout due to wrong ports and device description links
|
|
if(not defined($hash->{helper}{dInfo}{UUID}))
|
|
{
|
|
if (grep {$_ eq $hash->{MODEL}} @{$hash->{helper}{DevDescImplementedModels}})
|
|
{
|
|
PHILIPS_AUDIO_getMediaRendererDesc($hash);
|
|
}
|
|
}
|
|
|
|
if (not defined($hash->{helper}{playerState}))
|
|
{
|
|
# First run. Go to index.
|
|
$hash->{helper}{playerState} = "home";
|
|
readingsSingleUpdate($hash, "playerState", "home", 1);
|
|
PHILIPS_AUDIO_SendCommand($hash, "/index", "","home", "noArg");
|
|
PHILIPS_AUDIO_ResetTimer($hash); # getStatus
|
|
return;
|
|
}
|
|
|
|
if($hash->{helper}{playerState} eq "home")
|
|
{
|
|
# Check if device playing. Might by activated by the remote control or app.
|
|
# If not, the device returns 'NOTHING';
|
|
PHILIPS_AUDIO_SendCommand($hash, "/nowplay", "","nowplay", "noArg");
|
|
|
|
# Heartbeat
|
|
#PHILIPS_AUDIO_SendCommand($hash, "/HOMESTATUS", "","homestatus", "noArg");
|
|
|
|
readingsBeginUpdate($hash);
|
|
readingsBulkUpdate($hash, "input", "-");
|
|
readingsBulkUpdate($hash, "playerState", "home");
|
|
readingsBulkUpdate($hash, "playerPlaying", "no");
|
|
readingsEndUpdate($hash, 1);
|
|
}
|
|
elsif($hash->{helper}{playerState} eq "browsing")
|
|
{
|
|
# Do nothing and check for inactivity duration
|
|
|
|
$hash->{helper}{playerBrowsingTimeout} = 0 if(not defined ($hash->{helper}{playerBrowsingTimeout}));
|
|
|
|
$hash->{helper}{playerBrowsingTimeout} += $hash->{helper}{ON_INTERVAL};
|
|
|
|
# reset browsing state after 3 minutes inactivity in order to update the readings automatically again
|
|
if($hash->{helper}{playerBrowsingTimeout} >= int(AttrVal($name, "playerBrowsingTimeout", 180)))
|
|
{
|
|
$hash->{helper}{playerState} = "home";
|
|
readingsBeginUpdate($hash);
|
|
readingsBulkUpdate($hash, "input", "-");
|
|
readingsBulkUpdate($hash, "playerState", "home");
|
|
readingsBulkUpdate($hash, "playerPlaying", "no");
|
|
readingsEndUpdate($hash, 1);
|
|
$hash->{helper}{playerBrowsingTimeout} = 0;
|
|
}
|
|
}
|
|
elsif($hash->{helper}{playerState} eq "playing")
|
|
{
|
|
PHILIPS_AUDIO_SendCommand($hash, "/ELAPSE", "","elapse", "noArg");
|
|
}
|
|
|
|
# Check for Presets availability
|
|
if
|
|
(
|
|
(not defined($hash->{READINGS}{"totalPresets"})) and
|
|
(ReadingsVal($name, "presence", "no") eq "present") and
|
|
(AttrVal($name, "autoGetPresets", "0") eq "1")
|
|
)
|
|
{
|
|
readingsSingleUpdate($hash, "Reading_presets" , "May take some time...", 1);
|
|
# Hierarchichal navigation through the contents mandatory
|
|
$hash->{helper}{cmdStep} = 1;
|
|
PHILIPS_AUDIO_SendCommand($hash, "/index", "", "getPresets", "noArg") if((ReadingsVal($name, "playerListStatus", "ready") eq "ready") and (ReadingsVal($name, "readingPresets", "no") eq "no"));
|
|
PHILIPS_AUDIO_ResetTimer($hash, 10); # Scan takes approx 8 sec.
|
|
return;
|
|
}
|
|
|
|
# Check for Favorites availability
|
|
if
|
|
(
|
|
(not defined($hash->{READINGS}{"totalFavorites"})) and
|
|
(ReadingsVal($name, "presence", "no") eq "present") and
|
|
(AttrVal($name, "autoGetFavorites", "0") eq "1")
|
|
)
|
|
{
|
|
readingsSingleUpdate($hash, "Reading_favorites" , "May take some time...", 1);
|
|
# Hierarchichal navigation through the contents mandatory
|
|
$hash->{helper}{cmdStep} = 1;
|
|
PHILIPS_AUDIO_SendCommand($hash, "/index", "", "getFavorites", "noArg") if((ReadingsVal($name, "playerListStatus", "ready") eq "ready") and (ReadingsVal($name, "readingFavorites", "no") eq "no"));
|
|
PHILIPS_AUDIO_ResetTimer($hash, 10); # Scan takes approx 8 sec.
|
|
return;
|
|
}
|
|
|
|
PHILIPS_AUDIO_ResetTimer($hash) if(not ($local == 1)); # getStatus
|
|
return;
|
|
}
|
|
|
|
sub PHILIPS_AUDIO_Get
|
|
{
|
|
my ($hash, @a) = @_;
|
|
my $what = $a[1];
|
|
my $return;
|
|
|
|
my $name = $hash->{NAME};
|
|
|
|
my $address = $hash->{IP_ADDRESS};
|
|
$hash->{IP_ADDRESS} = $address;
|
|
|
|
return "Argument missing." if(int(@a) < 2);
|
|
|
|
if($what eq "reading")
|
|
{
|
|
if(exists($hash->{READINGS}{$a[2]}))
|
|
{
|
|
if(defined($hash->{READINGS}{$a[2]}))
|
|
{
|
|
return $hash->{READINGS}{$a[2]}{VAL};
|
|
}
|
|
else
|
|
{
|
|
return "No such reading: $what";
|
|
}
|
|
}
|
|
}
|
|
elsif($what eq "deviceInfo")
|
|
{
|
|
return "Device info:\n\n".
|
|
join("\n", map {sprintf("%-17s: %-17s", $_, $hash->{helper}{dInfo}{$_})} sort keys %{$hash->{helper}{dInfo}});
|
|
}
|
|
else
|
|
{
|
|
$return = "Unknown argument $what, choose one of"
|
|
." deviceInfo:noArg"
|
|
." reading:".(join(",",(sort keys %{$hash->{READINGS}})));
|
|
|
|
return $return;
|
|
}
|
|
}
|
|
|
|
sub PHILIPS_AUDIO_Set
|
|
{
|
|
my ($hash, @a) = @_;
|
|
my $name = $hash->{NAME};
|
|
my $port = $hash->{PORT};
|
|
my $address = $hash->{IP_ADDRESS};
|
|
|
|
if(not defined($hash->{MODEL}))
|
|
{
|
|
return "Please provide the model information as argument.";
|
|
}
|
|
|
|
return "No Argument given" if(!defined($a[1]));
|
|
|
|
my $what = $a[1];
|
|
my $usage = "";
|
|
|
|
my $model = $hash->{MODEL};
|
|
|
|
$hash->{helper}{dInfo}{MODEL} = $model;
|
|
$hash->{helper}{dInfo}{NAME} = $name;
|
|
$hash->{helper}{dInfo}{PORT} = $port;
|
|
$hash->{helper}{dInfo}{IP_ADDRESS} = $address;
|
|
|
|
$usage = "Unknown argument $what, choose one of".
|
|
" volumeStraight:slider,0,1,64 ".
|
|
" volume:slider,0,1,100 ".
|
|
" volumeUp:noArg ".
|
|
" volumeDown:noArg ".
|
|
" standbyButton:noArg ".
|
|
" player:next,prev,play-pause,stop ".
|
|
" shuffle:on,off ".
|
|
" input:---,".
|
|
|
|
((uc($model) eq "AW9000") ? "analogAux," : ""). # Input implemented in AW9000 only
|
|
((uc($model) eq "AW9000") ? "digital1Coaxial," : ""). # Input implemented in AW9000 only
|
|
((uc($model) eq "AW9000") ? "digital2Optical," : ""). # Input implemented in AW9000 only
|
|
|
|
# Input not implemented in the AW9000. Only as DLNA renderer
|
|
((uc($model) ne "AW9000") ? "mediaLibrary," : "").
|
|
|
|
"internetRadio,onlineServices,mp3Link ". # Available in all devices
|
|
#" statusRequest:noArg".
|
|
" getPresets:noArg".
|
|
" getFavorites:noArg".
|
|
" getMediaRendererDesc:noArg".
|
|
" favoriteAdd:noArg".
|
|
" favoriteRemove:noArg".
|
|
" repeat:single,all,off".
|
|
#" home:noArg".
|
|
" mute:on,off ";
|
|
|
|
my @favoriteList;
|
|
my @favoriteNumber;
|
|
foreach my $readings (keys % {$hash->{READINGS}})
|
|
{
|
|
push @favoriteList,$1."_".substr($hash->{READINGS}{$readings}{VAL}, 0, 25) if($readings =~ m/^.inetRadioFavorite_(..)/);
|
|
push @favoriteNumber, $1 if($readings =~ m/^.inetRadioFavorite_(..)/);
|
|
}
|
|
|
|
(s/\*/\[asterisk\]/g) for @favoriteList; # '*' not shown correctly
|
|
(s/#/\[hash\]/g) for @favoriteList; # '#' not shown correctly
|
|
(s/[\\]//g) for @favoriteList;
|
|
(s/[ :;,']/_/g) for @favoriteList; # Replace not allowed characters
|
|
|
|
my @presetList;
|
|
my @presetNumber;
|
|
foreach my $readings (keys % {$hash->{READINGS}})
|
|
{
|
|
push @presetList, $1."_".substr($hash->{READINGS}{$readings}{VAL}, 0, 25) if($readings =~ m/^.inetRadioPreset_(..)/);
|
|
push @presetNumber, $1 if($readings =~ m/^.inetRadioPreset_(..)/);
|
|
}
|
|
|
|
(s/\*/\[asterisk\]/g) for @presetList; # '*' not shown correctly
|
|
(s/#/\[hash\]/g) for @presetList; # '#' not shown correctly
|
|
(s/[\\]//g) for @presetList; # Replace \
|
|
(s/[ :;,']/_/g) for @presetList; # Replace not allowed characters
|
|
|
|
$usage .= "selectFavorite:" .join(",",("---",(sort @favoriteList))) . " ";
|
|
$usage .= "selectPreset:" .join(",",("---",(sort @presetList))) . " ";
|
|
$usage .= "selectPresetByNumber:" .join(",",("---",(sort @presetNumber))) . " ";
|
|
$usage .= "selectFavoriteByNumber:".join(",",("---",(sort @favoriteNumber))) . " ";
|
|
# Direct stream selection if any
|
|
|
|
my @selectStream;
|
|
|
|
for(my $lvl = 1; $lvl < int(ReadingsVal($name, ".listDepthLevel", "1") - 1); $lvl++)
|
|
{
|
|
my $listLevelName = ReadingsVal($name, ".lvl_".$lvl."_name", "");
|
|
push @selectStream, "lvl_".$lvl."_".$listLevelName;
|
|
}
|
|
|
|
foreach my $readings (keys % {$hash->{READINGS}})
|
|
{
|
|
push @selectStream,$1."_".substr($hash->{READINGS}{$readings}{VAL}, 0, 25) if($readings =~ m/^listItem_(...)/);
|
|
}
|
|
|
|
@selectStream = sort map{s/\*/\[asterisk\]/g;$_;} grep/._..*$/, @selectStream; # Replace *
|
|
@selectStream = sort map{s/#/\[hash\]/g;$_;} grep/._..*$/, @selectStream; # Replace #
|
|
@selectStream = sort map{s/[\\]//g;$_;} grep/._..*$/, @selectStream; # Replace not allowed characters
|
|
@selectStream = sort map{s/[ :;,']/_/g;$_;} grep/._..*$/, @selectStream; # Replace not allowed characters
|
|
|
|
$usage .= "selectStream:".join(",",("---",(sort @selectStream))) . " ";
|
|
|
|
Log3 $name, 5, "PHILIPS_AUDIO ($name) - set ".join(" ", @a);
|
|
|
|
# External Command. Not from buffer timer.
|
|
$hash->{helper}{fromSendCommandBuffer} = 0;
|
|
|
|
if($what =~ /input|selectStream|selectPreset|selectFavorite/)
|
|
{
|
|
# GetStatus while manual operation causes the device to stuck. Supress.
|
|
# Change device state to "browsing" in order to suppress automatic update
|
|
$hash->{helper}{playerState} = "browsing";
|
|
$hash->{helper}{manualOperation} = 1;
|
|
readingsBeginUpdate($hash);
|
|
readingsBulkUpdate($hash, "playerState", "browsing");
|
|
readingsBulkUpdate($hash, ".manualOperation", "yes", 1);
|
|
readingsEndUpdate($hash, 1);
|
|
# Reset browsing timeout
|
|
$hash->{helper}{playerBrowsingTimeout} = 0;
|
|
}
|
|
|
|
if($what eq "standbyButton")
|
|
{
|
|
readingsSingleUpdate($hash, "input", "-", 1);
|
|
PHILIPS_AUDIO_SendCommand($hash, "/CTRL\$STANDBY", "",$what, "noArg");
|
|
}
|
|
elsif($what eq "getMediaRendererDesc")
|
|
{
|
|
PHILIPS_AUDIO_getMediaRendererDesc($hash);
|
|
}
|
|
elsif($what eq "input")
|
|
{
|
|
if ($a[2] =~ /analogAux|mp3Link|digital1Coaxial|digital2Optical/)
|
|
{
|
|
# Delete List related readings
|
|
delete $hash->{READINGS}{$_} foreach (grep /list/, keys %{$hash->{READINGS}});
|
|
# Delete player related readings
|
|
delete $hash->{READINGS}{$_} foreach (grep /player/, keys %{$hash->{READINGS}});
|
|
}
|
|
|
|
if($a[2] eq "analogAux")
|
|
{
|
|
readingsSingleUpdate($hash, "input", "Aux-in (analog)", 1);
|
|
PHILIPS_AUDIO_SendCommand($hash, "/aux", "",$what, $a[2]);
|
|
}
|
|
elsif($a[2] eq "mp3Link")
|
|
{
|
|
|
|
$hash->{helper}{playerState} = "home";
|
|
readingsBeginUpdate($hash);
|
|
readingsBulkUpdate($hash, "input", "MP3-Link");
|
|
readingsBulkUpdate($hash, "playerState", "home");
|
|
readingsEndUpdate($hash, 1);
|
|
|
|
if(uc($model) eq "AW9000")
|
|
{
|
|
PHILIPS_AUDIO_SendCommand($hash, "/mp3link", "", $what, $a[2]);
|
|
}
|
|
else
|
|
{
|
|
PHILIPS_AUDIO_SendCommand($hash, "/aux", "", $what, $a[2]);
|
|
}
|
|
}
|
|
elsif($a[2] eq "digital1Coaxial")
|
|
{
|
|
$hash->{helper}{playerState} = "home";
|
|
readingsBeginUpdate($hash);
|
|
readingsBulkUpdate($hash, "input", "Digital-in 1 (coaxial)");
|
|
readingsBulkUpdate($hash, "playerState", "home");
|
|
readingsEndUpdate($hash, 1);
|
|
|
|
PHILIPS_AUDIO_SendCommand($hash, "/digin_coaxial", "",$what, $a[2]);
|
|
|
|
}
|
|
elsif($a[2] eq "digital2Optical")
|
|
{
|
|
$hash->{helper}{playerState} = "home";
|
|
readingsBeginUpdate($hash);
|
|
readingsBulkUpdate($hash, "input", "Digital-in 2 (optical)");
|
|
readingsBulkUpdate($hash, "playerState", "home");
|
|
readingsEndUpdate($hash, 1);
|
|
|
|
PHILIPS_AUDIO_SendCommand($hash, "/digin_optical", "",$what, $a[2]);
|
|
}
|
|
elsif($a[2] eq "mediaLibrary")
|
|
{
|
|
readingsSingleUpdate($hash, "playerListStatus", "busy", 1);
|
|
#readingsSingleUpdate($hash, "input", "Media Library", 1);
|
|
PHILIPS_AUDIO_SendCommand($hash, "/nav\$02\$01\$001\$0", "", $what, $a[2]);
|
|
}
|
|
elsif($a[2] eq "internetRadio")
|
|
{
|
|
readingsSingleUpdate($hash, "playerListStatus", "busy", 1);
|
|
#readingsSingleUpdate($hash, "input", "Internet Radio", 1);
|
|
PHILIPS_AUDIO_SendCommand($hash, "/nav\$03\$01\$001\$0", "",$what, $a[2]);
|
|
}
|
|
elsif($a[2] eq "onlineServices")
|
|
{
|
|
readingsSingleUpdate($hash, "playerListStatus", "busy", 1);
|
|
#readingsSingleUpdate($hash, "input", "Online Services", 1);
|
|
PHILIPS_AUDIO_SendCommand($hash, "/nav\$09\$01\$001\$0", "", $what, $a[2]);
|
|
}
|
|
else
|
|
{
|
|
return $usage;
|
|
}
|
|
}
|
|
elsif($what eq "home")
|
|
{
|
|
readingsSingleUpdate($hash, "playerListStatus", "busy", 1);
|
|
$hash->{header}{httpHeaderRefer} = "Upgrade-Insecure-Requests: 1\r\n";
|
|
PHILIPS_AUDIO_SendCommand($hash, "/index", "",$what, "noArg");
|
|
}
|
|
elsif($what eq "shuffle")
|
|
{
|
|
if($a[2] eq "on")
|
|
{
|
|
readingsSingleUpdate($hash, "playerShuffle", "on", 1);
|
|
PHILIPS_AUDIO_SendCommand($hash, "/MODE\$SHUFFLE_ON", "",$what, $a[2]);
|
|
}
|
|
elsif($a[2] eq "off")
|
|
{
|
|
readingsSingleUpdate($hash, "playerShuffle", "off", 1);
|
|
PHILIPS_AUDIO_SendCommand($hash, "/MODE\$SHUFFLE_OFF", "",$what, $a[2]);
|
|
}
|
|
else
|
|
{
|
|
return $usage;
|
|
}
|
|
}
|
|
elsif($what eq "repeat")
|
|
{
|
|
if($a[2] eq "single")
|
|
{
|
|
readingsSingleUpdate($hash, "playerRepeat", "single", 1);
|
|
PHILIPS_AUDIO_SendCommand($hash, "/MODE\$REPEAT_SINGLE", "",$what, $a[2]);
|
|
}
|
|
elsif($a[2] eq "all")
|
|
{
|
|
readingsSingleUpdate($hash, "playerRepeat", "all", 1);
|
|
PHILIPS_AUDIO_SendCommand($hash, "/MODE\$REPEAT_ALL", "",$what, $a[2]);
|
|
}
|
|
elsif($a[2] eq "off")
|
|
{
|
|
readingsSingleUpdate($hash, "playerRepeat", "off", 1);
|
|
PHILIPS_AUDIO_SendCommand($hash, "/MODE\$REPEAT_OFF", "",$what, $a[2]);
|
|
}
|
|
else
|
|
{
|
|
return $usage;
|
|
}
|
|
}
|
|
elsif($what eq "statusRequest")
|
|
{
|
|
PHILIPS_AUDIO_SendCommand($hash, "/nowplay", "","nowplay", "noArg");
|
|
}
|
|
elsif($what eq "favoriteAdd")
|
|
{
|
|
PHILIPS_AUDIO_SendCommand($hash, "/CTRL\$ADD2FAV", "",$what, "noArg");
|
|
}
|
|
elsif($what eq "favoriteRemove")
|
|
{
|
|
PHILIPS_AUDIO_SendCommand($hash, "/CTRL\$REMFAV", "",$what, "noArg");
|
|
}
|
|
elsif($what eq "mute")
|
|
{
|
|
if($a[2] eq "on")
|
|
{
|
|
readingsSingleUpdate($hash, "mute", "on", 1);
|
|
PHILIPS_AUDIO_SendCommand($hash, "/VOLUME\$MUTE", "",$what, "noArg");
|
|
}
|
|
elsif($a[2] eq "off")
|
|
{
|
|
readingsSingleUpdate($hash, "mute", "off", 1);
|
|
PHILIPS_AUDIO_SendCommand($hash, "/VOLUME\$UNMUTE", "",$what, "noArg");
|
|
}
|
|
else
|
|
{
|
|
return $usage;
|
|
}
|
|
}
|
|
elsif($what eq "player")
|
|
{
|
|
if($a[2] eq "next")
|
|
{
|
|
PHILIPS_AUDIO_SendCommand($hash, "/CTRL\$NEXT", "",$what, "noArg");
|
|
}
|
|
elsif($a[2] eq "prev")
|
|
{
|
|
PHILIPS_AUDIO_SendCommand($hash, "/CTRL\$PREV", "",$what, "noArg");
|
|
}
|
|
elsif($a[2] eq "play-pause")
|
|
{
|
|
PHILIPS_AUDIO_SendCommand($hash, "/CTRL\$PLAY_PAUSE", "",$what, "noArg");
|
|
}
|
|
elsif($a[2] eq "stop")
|
|
{
|
|
PHILIPS_AUDIO_SendCommand($hash, "/CTRL\$STOP", "",$what, "noArg");
|
|
}
|
|
else
|
|
{
|
|
return $usage;
|
|
}
|
|
}
|
|
elsif($what =~ m/^(selectPreset|selectPresetByNumber)/)
|
|
{
|
|
if($a[2] ne "---")
|
|
{
|
|
# Hierarchichal navigation through the contents mandatory
|
|
$hash->{helper}{cmdStep} = 1;
|
|
|
|
my $presetNumber = substr($a[2], 0, 2); # Get 2 first digits
|
|
|
|
if($a[2] =~ m/empty/ or ReadingsVal($name, ".inetRadioPreset_$presetNumber","") eq "empty")
|
|
{
|
|
# Do nothing
|
|
}
|
|
else
|
|
{
|
|
$presetNumber =~ s/^0+//g; # Remove leading '0'
|
|
$hash->{helper}{inetRadioPreset} = $presetNumber;
|
|
readingsSingleUpdate($hash, "input", "Internet Radio", 1);
|
|
PHILIPS_AUDIO_SendCommand($hash, "/index", "", $what, $a[2]);
|
|
}
|
|
}
|
|
}
|
|
elsif($what =~ m/^(selectFavorite|selectFavoriteByNumber)/)
|
|
{
|
|
if($a[2] ne "---")
|
|
{
|
|
# Hierarchichal navigation through the contents mandatory
|
|
$hash->{helper}{cmdStep} = 1;
|
|
|
|
my $favoriteNumber = substr($a[2], 0, 2); # Get 2 first digits
|
|
|
|
if($a[2] =~ m/empty/ or ReadingsVal($name, ".inetRadioFavorite_$favoriteNumber","") eq "empty")
|
|
{
|
|
# Do nothing
|
|
}
|
|
else
|
|
{
|
|
$favoriteNumber =~ s/^0+//g; # Remove leading '0'
|
|
$hash->{helper}{inetRadioFavorite} = $favoriteNumber;
|
|
#readingsSingleUpdate($hash, "input", "Internet Radio", 1);
|
|
PHILIPS_AUDIO_SendCommand($hash, "/index", "", $what, $a[2]);
|
|
}
|
|
}
|
|
}
|
|
elsif($what eq "volumeStraight")
|
|
{
|
|
if(($a[2] < 0) || ($a[2] > 64))
|
|
{
|
|
return "volumeStraight must be in the range 0...64.";
|
|
}
|
|
else
|
|
{
|
|
$hash->{helper}{targetVolume} = int($a[2]);
|
|
readingsBeginUpdate($hash);
|
|
readingsBulkUpdate($hash, "volumeStraight", $hash->{helper}{targetVolume});
|
|
readingsBulkUpdate($hash, "volume", PHILIPS_AUDIO_volume_abs2rel($hash, $hash->{helper}{targetVolume}));
|
|
readingsEndUpdate($hash, 1);
|
|
PHILIPS_AUDIO_SendCommand($hash, "/VOLUME\$VAL\$".$a[2], "",$what, $a[2]);
|
|
}
|
|
}
|
|
elsif($what eq "volumeUp")
|
|
{
|
|
my $targetVolume = int(int(ReadingsVal($name, "volumeStraight", "0")) + 1);
|
|
|
|
if($targetVolume > 64)
|
|
{
|
|
$targetVolume = 64;
|
|
}
|
|
|
|
$hash->{helper}{targetVolume} = $targetVolume;
|
|
|
|
readingsBeginUpdate($hash);
|
|
readingsBulkUpdate($hash, "volumeStraight", $hash->{helper}{targetVolume});
|
|
readingsBulkUpdate($hash, "volume", PHILIPS_AUDIO_volume_abs2rel($hash, $hash->{helper}{targetVolume}));
|
|
readingsEndUpdate($hash, 1);
|
|
|
|
PHILIPS_AUDIO_SendCommand($hash, "/VOLUME\$VAL\$" . $hash->{helper}{targetVolume}, "",$what, $hash->{helper}{targetVolume});
|
|
}
|
|
elsif($what eq "volumeDown")
|
|
{
|
|
my $targetVolume = int(int(ReadingsVal($name, "volumeStraight", "0")) - 1);
|
|
|
|
if($targetVolume < 0)
|
|
{
|
|
$targetVolume = 0;
|
|
}
|
|
|
|
$hash->{helper}{targetVolume} = $targetVolume;
|
|
|
|
readingsBeginUpdate($hash);
|
|
readingsBulkUpdate($hash, "volumeStraight", $hash->{helper}{targetVolume});
|
|
readingsBulkUpdate($hash, "volume", PHILIPS_AUDIO_volume_abs2rel($hash, $hash->{helper}{targetVolume}));
|
|
readingsEndUpdate($hash, 1);
|
|
|
|
PHILIPS_AUDIO_SendCommand($hash, "/VOLUME\$VAL\$" . $hash->{helper}{targetVolume}, "",$what, $hash->{helper}{targetVolume});
|
|
}
|
|
elsif($what eq "volume")
|
|
{
|
|
if(($a[2] < 0) || ($a[2] > 100))
|
|
{
|
|
return "volumeStraight must be in the range 0...100.";
|
|
}
|
|
else
|
|
{
|
|
$hash->{helper}{targetVolume} = PHILIPS_AUDIO_volume_rel2abs($hash, $a[2]);
|
|
|
|
readingsBeginUpdate($hash);
|
|
readingsBulkUpdate($hash, "volumeStraight", $hash->{helper}{targetVolume});
|
|
readingsBulkUpdate($hash, "volume", $a[2]);
|
|
readingsEndUpdate($hash, 1);
|
|
|
|
PHILIPS_AUDIO_SendCommand($hash, "/VOLUME\$VAL\$".$hash->{helper}{targetVolume}, "",$what, $hash->{helper}{targetVolume});
|
|
}
|
|
}
|
|
elsif($what eq "nowplay")
|
|
{
|
|
PHILIPS_AUDIO_SendCommand($hash, "/nowplay", "",$what, "noArg");
|
|
}
|
|
elsif($what eq "homestatus")
|
|
{
|
|
$hash->{helper}{httpHeaderRefer} = "http://$hash->{IP_ADDRESS}:$hash->{PORT}/index\r\n";
|
|
PHILIPS_AUDIO_SendCommand($hash, "/HOMESTATUS", "",$what, "noArg");
|
|
}
|
|
elsif($what eq "getPresets")
|
|
{
|
|
readingsBeginUpdate($hash);
|
|
readingsBulkUpdate($hash, "playerListStatus", "busy");
|
|
readingsBulkUpdate($hash, "readingPresets", "yes");
|
|
readingsEndUpdate($hash, 1);
|
|
|
|
# Delete old redings
|
|
delete $hash->{READINGS}{$_} foreach (grep /.inetRadioPreset_..$/, keys %{$hash->{READINGS}});
|
|
|
|
# Hierarchichal navigation through the contents mandatory
|
|
$hash->{helper}{cmdStep} = 1;
|
|
PHILIPS_AUDIO_SendCommand($hash, "/index", "", $what, "noArg");
|
|
}
|
|
elsif($what eq "getFavorites")
|
|
{
|
|
readingsBeginUpdate($hash);
|
|
readingsBulkUpdate($hash, "playerListStatus", "busy");
|
|
readingsBulkUpdate($hash, "readingFavorites", "yes");
|
|
readingsEndUpdate($hash, 1);
|
|
|
|
# Delete old redings
|
|
delete $hash->{READINGS}{$_} foreach (grep /.inetRadioFavorite_..$/, keys %{$hash->{READINGS}});
|
|
|
|
# Hierarchichal navigation through the contents mandatory
|
|
$hash->{helper}{cmdStep} = 1;
|
|
PHILIPS_AUDIO_SendCommand($hash, "/index", "", $what, "noArg");
|
|
}
|
|
elsif($what eq "selectStream")
|
|
{
|
|
|
|
# The player list selection has been designed for a GUI/touchscreen
|
|
# Virtually scrolling down to last item and choosing the first one
|
|
# arises an error. Needs to navigate back to the corresponding page
|
|
# consisting of 8 items per page
|
|
|
|
readingsSingleUpdate($hash, "playerListStatus", "busy", 1);
|
|
|
|
if($a[2] =~ /lvl_(.+?)/)
|
|
{
|
|
my $desiredLevel = $1;
|
|
my $currentUrl = $hash->{helper}{currentNavUrl};
|
|
my $newUrl = "";
|
|
my $currentLevel = "";
|
|
my $newLevel = "";
|
|
|
|
if($currentUrl =~ /\/nav\$(.*)\$(.*)\$(.*)\$(.*)/) # e.g. /nav$02$XX$001$0
|
|
{
|
|
$currentLevel = int($2);
|
|
$newLevel = sprintf("%02d", $currentLevel - ($currentLevel - $desiredLevel) + 1);
|
|
$newUrl = "/nav\$$1\$$newLevel\$$3\$0";
|
|
}
|
|
|
|
PHILIPS_AUDIO_SendCommand($hash, $newUrl, "", $what, $a[2]);
|
|
}
|
|
elsif($a[2] ne "---")
|
|
{
|
|
my $targetNr = substr($a[2], 0, 3);
|
|
my $currentUrl = $hash->{helper}{currentNavUrl};
|
|
|
|
# Remark TODO...
|
|
|
|
if($a[2] =~ /(\d{3})_(.+?)_(.*)/)
|
|
{
|
|
my $currentListLevel = ReadingsVal($name, ".listDepthLevel", "");
|
|
readingsSingleUpdate($hash, ".lvl_".$currentListLevel."_name", "$2_$3", 1);
|
|
|
|
if(int(ReadingsVal($name, "playerListTotalCount", "8")) > 8)
|
|
{
|
|
if($currentUrl =~ /\/nav\$(.*)\$(.*)\$(.*)\$(.*)/)
|
|
{
|
|
# Virtually scroll back to the first line
|
|
|
|
my $scrollUrl = "\/nav\$$1\$$2\$001\$$4";
|
|
|
|
PHILIPS_AUDIO_SendCommand($hash, $scrollUrl, "", "selectStream", "scroll");
|
|
|
|
# int(($targetNr / 8) + 0.5) -> Round up
|
|
for(my $i = 1; $i < (int(($targetNr / 8) + 0.5) + 1); $i += 8) # Max 8 items in the actual list
|
|
{
|
|
my $scrollUrl = "\/nav\$$1\$$2\$".sprintf("%003s", ($i + 8))."\$$4";
|
|
|
|
PHILIPS_AUDIO_SendCommand($hash, $scrollUrl, "", "selectStream", "scroll");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
PHILIPS_AUDIO_SendCommand($hash, ReadingsVal($name, ".listItemTarget_$targetNr", ""), "", $what, $a[2]);
|
|
}
|
|
else
|
|
{
|
|
return $usage;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return $usage;
|
|
}
|
|
|
|
PHILIPS_AUDIO_ResetTimer($hash); # Reset timer for the next loop
|
|
|
|
return;
|
|
}
|
|
|
|
sub PHILIPS_AUDIO_Define
|
|
{
|
|
my ($hash, $def) = @_;
|
|
my @a = split("[ \t][ \t]*", $def);
|
|
my $name = $hash->{NAME};
|
|
|
|
# Implemented devices for reading Device Description
|
|
@{$hash->{helper}{DevDescImplementedModels}} = qw( AW9000 NP3500 NP3700 NP3900 );
|
|
|
|
$hash->{helper}{sendCommandBuffer} = [];
|
|
|
|
delete $hash->{READINGS}{$_} foreach (grep /player/, keys %{$hash->{READINGS}});
|
|
|
|
if(! @a >= 4)
|
|
{
|
|
my $msg = "Wrong syntax: define <name> PHILIPS_AUDIO <model> <ip-or-hostname> [<ON-statusinterval>] [<OFF-statusinterval>] ";
|
|
Log3 $name, 2, $msg;
|
|
return $msg;
|
|
}
|
|
|
|
if(defined($a[2]))
|
|
{
|
|
$hash->{MODEL} = uc($a[2]);
|
|
|
|
# Used by 'fheminfo' command for statistics
|
|
$attr{$name}{"model"} = $hash->{MODEL};
|
|
}
|
|
|
|
$hash->{IP_ADDRESS} = $a[3];
|
|
$hash->{PORT} = 8889;
|
|
|
|
# if an update interval >= 5 use it.
|
|
if(defined($a[4]) and $a[4] > 0)
|
|
{
|
|
$hash->{helper}{OFF_INTERVAL} = $a[4];
|
|
# Minimum interval 5 sec
|
|
if($hash->{helper}{OFF_INTERVAL} < 5)
|
|
{
|
|
$hash->{helper}{OFF_INTERVAL} = 5;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
$hash->{helper}{OFF_INTERVAL} = 30;
|
|
}
|
|
|
|
if(defined($a[5]) and $a[5] > 0)
|
|
{
|
|
$hash->{helper}{ON_INTERVAL} = $a[5];
|
|
# Minimum interval 5 sec
|
|
if($hash->{helper}{ON_INTERVAL} < 5)
|
|
{
|
|
$hash->{helper}{ON_INTERVAL} = 5;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
$hash->{helper}{ON_INTERVAL} = $hash->{helper}{OFF_INTERVAL};
|
|
}
|
|
|
|
unless(exists($hash->{helper}{AVAILABLE}) and ($hash->{helper}{AVAILABLE} == 0))
|
|
{
|
|
$hash->{helper}{AVAILABLE} = 1;
|
|
readingsSingleUpdate($hash, "presence", "present", 1);
|
|
}
|
|
|
|
# start the status update timer
|
|
$hash->{helper}{DISABLED} = 0 unless(exists($hash->{helper}{DISABLED}));
|
|
|
|
PHILIPS_AUDIO_ResetTimer($hash, 0);
|
|
|
|
return;
|
|
}
|
|
|
|
sub PHILIPS_AUDIO_Attr
|
|
{
|
|
my ($cmd, $name, $attrName, $attrVal) = @_;
|
|
my $hash = $defs{$name};
|
|
|
|
if($attrName eq "disable")
|
|
{
|
|
if ($cmd eq "set")
|
|
{
|
|
$hash->{helper}{DISABLED} = $attrVal;
|
|
if ($attrVal eq "0")
|
|
{
|
|
PHILIPS_AUDIO_GetStatus($hash, 1);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
$hash->{helper}{DISABLED} = 0;
|
|
PHILIPS_AUDIO_GetStatus($hash, 1);
|
|
}
|
|
}
|
|
elsif($attrName eq "maxListItems" && defined($attrVal))
|
|
{
|
|
if ($cmd eq "set" && (($attrVal < 8) || ($attrVal > 999)))
|
|
{
|
|
return "$attrName must be between 8 and 999";
|
|
}
|
|
}
|
|
elsif($attrName eq "httpBufferTimeout" && defined($attrVal))
|
|
{
|
|
if ($cmd eq "set" && (($attrVal < 10) || ($attrVal > 15)))
|
|
{
|
|
return "$attrName must be between 10 and 15";
|
|
}
|
|
}
|
|
elsif($attrName eq "requestTimeout" && defined($attrVal))
|
|
{
|
|
if ($cmd eq "set" && (($attrVal < 10) || ($attrVal > 15)))
|
|
{
|
|
return "$attrName must be between 10 and 15";
|
|
}
|
|
}
|
|
elsif($attrName eq "autoGetPresets" && defined($attrVal))
|
|
{
|
|
if ($cmd eq "set" && (($attrVal < 0) || ($attrVal > 1)))
|
|
{
|
|
return "$attrName must be between 0 or 1";
|
|
}
|
|
}
|
|
elsif($attrName eq "autoGetFavorites" && defined($attrVal))
|
|
{
|
|
if ($cmd eq "set" && (($attrVal < 0) || ($attrVal > 1)))
|
|
{
|
|
return "$attrName must be between 0 or 1";
|
|
}
|
|
}
|
|
elsif($attrName eq "playerBrowsingTimeout" && defined($attrVal))
|
|
{
|
|
if ($cmd eq "set" && (($attrVal < 60) || ($attrVal > 600)))
|
|
{
|
|
return "$attrName must be between 60 or 600";
|
|
}
|
|
}
|
|
|
|
# Start/Stop Timer according to new disabled-Value
|
|
PHILIPS_AUDIO_ResetTimer($hash);
|
|
|
|
return;
|
|
}
|
|
|
|
sub PHILIPS_AUDIO_Undefine
|
|
{
|
|
my($hash, $name) = @_;
|
|
|
|
# Stop the internal GetStatus-Loop and exit
|
|
RemoveInternalTimer($hash, "PHILIPS_AUDIO_GetStatus");
|
|
return;
|
|
}
|
|
|
|
sub PHILIPS_AUDIO_SendCommandBuffer
|
|
{
|
|
# Function called by an internal timer in case device busy
|
|
my ($hash) = @_;
|
|
|
|
my $firstCommand = "";
|
|
my ($url, $data, $cmd, $arg) = ("", "", "", "");
|
|
|
|
|
|
# Get first command from buffer
|
|
if
|
|
(
|
|
@{$hash->{helper}{sendCommandBuffer}}
|
|
)
|
|
{
|
|
$firstCommand = shift(@{$hash->{helper}{sendCommandBuffer}});
|
|
|
|
$url = $firstCommand->{url};
|
|
$data = $firstCommand->{data};
|
|
$cmd = $firstCommand->{cmd};
|
|
$arg = $firstCommand->{arg};
|
|
}
|
|
|
|
# Only send in case buffer not empty
|
|
if(($url ne "") and ($cmd ne "") and ($arg ne "")) # $data may be empty
|
|
{
|
|
|
|
$hash->{helper}{comeFromSendBuffer} = 1;
|
|
|
|
PHILIPS_AUDIO_SendCommand(
|
|
$hash,
|
|
$url,
|
|
$data,
|
|
$cmd,
|
|
$arg
|
|
);
|
|
|
|
|
|
# Check if buffer empty
|
|
if
|
|
(
|
|
(@{$hash->{helper}{sendCommandBuffer}})
|
|
)
|
|
{
|
|
# Come back
|
|
# -> try again after 1 sec delay and process buffer.
|
|
RemoveInternalTimer($hash, "PHILIPS_AUDIO_SendCommandBuffer");
|
|
InternalTimer(gettimeofday() + 1, "PHILIPS_AUDIO_SendCommandBuffer", $hash);
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
# Do nothing.
|
|
# Reset flag in case buffer empty
|
|
$hash->{helper}{comeFromSendBuffer} = 0;
|
|
PHILIPS_AUDIO_ResetTimer($hash);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
sub PHILIPS_AUDIO_SendCommand
|
|
{
|
|
my ($hash, $url, $data, $cmd, $arg) = @_;
|
|
my $name = $hash->{NAME};
|
|
my $address = $hash->{IP_ADDRESS};
|
|
my $port = $hash->{PORT};
|
|
|
|
$hash->{helper}{networkRequest} = "idle" if(not defined($hash->{helper}{networkRequest})); # First run
|
|
$hash->{helper}{comeFromSendBuffer} = 0 if(not defined($hash->{helper}{comeFromSendBuffer})); # First run
|
|
|
|
# buffer command and wait a second in case device busy
|
|
if ($hash->{helper}{networkRequest} eq "busy")
|
|
{
|
|
|
|
my $httpBufferData =
|
|
{
|
|
url => $url,
|
|
data => $data,
|
|
cmd => $cmd,
|
|
arg => $arg
|
|
};
|
|
|
|
# Append to buffer only if not coming from itself
|
|
# Or put to first position for resending
|
|
|
|
# add remove start end
|
|
# push X X
|
|
# pop X X
|
|
# unshift X X
|
|
# shift X X
|
|
|
|
if($hash->{helper}{comeFromSendBuffer} == 0)
|
|
{
|
|
push @{$hash->{helper}{sendCommandBuffer}}, $httpBufferData;
|
|
}
|
|
else
|
|
{
|
|
unshift @{$hash->{helper}{sendCommandBuffer}}, $httpBufferData;
|
|
}
|
|
|
|
if($hash->{helper}{timeoutCounter} >= AttrVal($name, "httpBufferTimeout", 10))
|
|
{
|
|
# X seconds timeout. Something went wrong. Clear buffer. Release "busy"
|
|
|
|
# Delete all remaining commands
|
|
splice(@{$hash->{helper}{sendCommandBuffer}});
|
|
|
|
$hash->{helper}{timeoutCounter} = 0;
|
|
$hash->{helper}{networkRequest} = "idle";
|
|
$hash->{helper}{comeFromSendBuffer} = 0;
|
|
|
|
readingsBeginUpdate($hash);
|
|
readingsBulkUpdate ($hash, "networkRequest", "idle");
|
|
readingsBulkUpdate ($hash, "networkError", "buffer timed out");
|
|
readingsEndUpdate ($hash, 1);
|
|
|
|
Log3 $name, 3, "PHILIPS_AUDIO ($name) - HTTP buffer timeout. Please, check your network connection, ip-address etc. Device switched off?";
|
|
|
|
PHILIPS_AUDIO_ResetTimer($hash);
|
|
|
|
return;
|
|
}
|
|
|
|
# -> try again after 1 sec delay and process buffer.
|
|
RemoveInternalTimer($hash, "PHILIPS_AUDIO_SendCommandBuffer");
|
|
InternalTimer(gettimeofday() + 1, "PHILIPS_AUDIO_SendCommandBuffer", $hash);
|
|
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
if($url =~ /\/nav(.*)/)
|
|
{
|
|
$hash->{helper}{currentNavUrl} = $url;
|
|
}
|
|
else
|
|
{
|
|
$hash->{helper}{currentUrl} = $url;
|
|
}
|
|
|
|
if(ReadingsVal($name, "presence", "absent") ne "absent")
|
|
{
|
|
$hash->{helper}{networkRequest} = "busy";
|
|
readingsSingleUpdate($hash, "networkRequest", "busy", 1);
|
|
}
|
|
|
|
PHILIPS_AUDIO_ResetTimer($hash); # getStatus
|
|
}
|
|
|
|
$hash->{helper}{timeoutCounter} = 0;
|
|
|
|
Log3 $name, 5, "PHILIPS_AUDIO ($name) - Executing nonblocking \"$cmd".(defined($arg) ? " ".(split("\\|", $arg))[0] : "")."\" on $name: $data";
|
|
|
|
# Reset flag if successfully sent
|
|
$hash->{helper}{comeFromSendBuffer} = 0;
|
|
|
|
HttpUtils_NonblockingGet
|
|
({
|
|
url => "http://".$address.":".$port."".$url,
|
|
timeout => AttrVal($name, "requestTimeout", 10),
|
|
noshutdown => 1,
|
|
data => $data,
|
|
loglevel => ($hash->{helper}{AVAILABLE} ? undef : 5),
|
|
hash => $hash,
|
|
cmd => $cmd,
|
|
arg => $arg,
|
|
method => "GET",
|
|
httpversion => "1.1",
|
|
keepalive => 1, # Philips app always uses keep-alive
|
|
header => $hash->{helper}{httpHeaderRefer},
|
|
callback => \&PHILIPS_AUDIO_ParseResponse
|
|
});
|
|
|
|
return;
|
|
}
|
|
|
|
sub PHILIPS_AUDIO_ParseResponse
|
|
{
|
|
my ($param, $err, $data ) = @_;
|
|
|
|
my $hash = $param->{hash};
|
|
my $name = $hash ->{NAME};
|
|
my $cmd = $param->{cmd};
|
|
my $arg = $param->{arg};
|
|
|
|
if(exists($param->{code}))
|
|
{
|
|
Log3 $name, 5, "PHILIPS_AUDIO ($name) - received HTTP code ".$param->{code}." for command \"$cmd".(defined($arg) ? " ".(split("\\|", $arg))[0] : "")."\"";
|
|
}
|
|
|
|
if($err ne "")
|
|
{
|
|
readingsBeginUpdate($hash);
|
|
|
|
Log3 $name, 5, "PHILIPS_AUDIO ($name) - Could not execute command \"$cmd".(defined($arg) ? " ".(split("\\|", $arg))[0] : "")."\": $err";
|
|
|
|
# Release the busy flag
|
|
$hash->{helper}{networkRequest} = "idle";
|
|
readingsBulkUpdate($hash, "networkRequest", "idle");
|
|
|
|
if((not exists($hash->{helper}{AVAILABLE})) or (exists($hash->{helper}{AVAILABLE}) and $hash->{helper}{AVAILABLE} == 1))
|
|
{
|
|
Log3 $name, 3, "PHILIPS_AUDIO ($name) - Could not execute HTTP request. Please, check your network connection, ip-address etc. Device switched off? ($err)";
|
|
readingsBulkUpdate($hash, "networkError", "$err");
|
|
readingsBulkUpdate($hash, "presence", "absent");
|
|
readingsBulkUpdate($hash, "state", "absent");
|
|
$hash->{STATE} = "absent";
|
|
}
|
|
|
|
$hash->{helper}{AVAILABLE} = 0;
|
|
$hash->{helper}{timeoutCounter} = 0;
|
|
$hash->{helper}{comeFromSendBuffer} = 0;
|
|
|
|
# Close HTTP connection
|
|
HttpUtils_Close($param);
|
|
|
|
# Force "home" state
|
|
$hash->{helper}{playerState} = "home";
|
|
readingsBulkUpdate($hash, "playerState", "home");
|
|
readingsBulkUpdate($hash, "playerListStatus", "ready");
|
|
|
|
readingsEndUpdate($hash, 1);
|
|
|
|
# Device firware "somehow buggy".
|
|
# Try to reanimate the device after timeout.
|
|
# Go to /index
|
|
PHILIPS_AUDIO_SendCommand($hash, "/index", "","home", "noArg");
|
|
|
|
PHILIPS_AUDIO_ResetTimer($hash);
|
|
return;
|
|
}
|
|
elsif($data ne "")
|
|
{
|
|
|
|
Log3 $name, 5, "PHILIPS_AUDIO ($name) - got response for \"$cmd".(defined($arg) ? " ".(split("\\|", $arg))[0] : "")."\": $data";
|
|
|
|
delete $hash->{READINGS}{networkError};
|
|
|
|
readingsBeginUpdate($hash);
|
|
|
|
if (defined($hash->{helper}{AVAILABLE}) and $hash->{helper}{AVAILABLE} eq 0)
|
|
{
|
|
Log3 $name, 3, "PHILIPS_AUDIO ($name) - device $name reappeared";
|
|
readingsBulkUpdate($hash, "presence", "present");
|
|
}
|
|
|
|
$hash->{helper}{AVAILABLE} = 1;
|
|
|
|
readingsBulkUpdate($hash, "power", "on");
|
|
readingsBulkUpdate($hash, "state","on");
|
|
|
|
$hash->{STATE} = "on";
|
|
|
|
readingsEndUpdate($hash, 1);
|
|
|
|
if($cmd eq "standbyButton")
|
|
{
|
|
if($data =~ /SUCCESS/)
|
|
{
|
|
#readingsBulkUpdate($hash, "power", "on");
|
|
#readingsBulkUpdate($hash, "state","on");
|
|
}
|
|
|
|
}
|
|
elsif($cmd eq "home")
|
|
{
|
|
if($data =~ /'devicename'/) # Device responded correctly
|
|
{
|
|
$hash->{helper}{playerState} = "home";
|
|
|
|
readingsBeginUpdate($hash);
|
|
readingsBulkUpdate ($hash, "playerState", "home");
|
|
readingsBulkUpdate ($hash, "playerListStatus", "ready");
|
|
readingsEndUpdate ($hash, 1);
|
|
}
|
|
}
|
|
elsif($cmd eq "mute")
|
|
{
|
|
if($data =~ /SUCCESS/)
|
|
{
|
|
readingsSingleUpdate($hash, "mute", "on", 1);
|
|
}
|
|
}
|
|
elsif($cmd eq "unmute")
|
|
{
|
|
if($data =~ /SUCCESS/)
|
|
{
|
|
readingsSingleUpdate($hash, "mute", "off", 1);
|
|
}
|
|
}
|
|
elsif($cmd eq "favoriteRemove")
|
|
{
|
|
# replace \n by ""
|
|
$data =~ s/\n//g;
|
|
|
|
if($data =~ /{'command':'MESSAGE','value':'(.+)'}/)
|
|
{
|
|
# Do nothing
|
|
}
|
|
}
|
|
elsif($cmd eq "favoriteAdd")
|
|
{
|
|
# replace \n by ""
|
|
$data =~ s/\n//g;
|
|
|
|
if($data =~ /{'command':'MESSAGE','value':'(.+)'}/)
|
|
{
|
|
# Do nothing
|
|
}
|
|
}
|
|
elsif($cmd =~ m/^(selectPreset|inetRadioPreset)/)
|
|
{
|
|
# This command must be processed hierarchicaly through the navigation path
|
|
if($hash->{helper}{cmdStep} == 1)
|
|
{
|
|
$hash->{helper}{cmdStep} = 2;
|
|
|
|
# External Command. Not from buffer timer.
|
|
$hash->{helper}{fromSendCommandBuffer} = 0;
|
|
# Internet radio
|
|
PHILIPS_AUDIO_SendCommand($hash, "/nav\$03\$01\$001\$0", "", "selectPreset", $hash->{helper}{inetRadioPreset});
|
|
}
|
|
elsif($hash->{helper}{cmdStep} == 2)
|
|
{
|
|
$hash->{helper}{cmdStep} = 3;
|
|
|
|
# External Command. Not from buffer timer.
|
|
$hash->{helper}{fromSendCommandBuffer} = 0;
|
|
# Presets
|
|
PHILIPS_AUDIO_SendCommand($hash, "/nav\$03\$02\$001\$0", "","selectPreset", $hash->{helper}{inetRadioPreset});
|
|
}
|
|
elsif($hash->{helper}{cmdStep} == 3)
|
|
{
|
|
$hash->{helper}{cmdStep} = 4;
|
|
|
|
# External Command. Not from buffer timer.
|
|
$hash->{helper}{fromSendCommandBuffer} = 0;
|
|
|
|
# Preset select
|
|
PHILIPS_AUDIO_SendCommand($hash, "/nav\$03\$03\$".sprintf("%03d", $hash->{helper}{inetRadioPreset})."\$1", "","selectPreset", $hash->{helper}{inetRadioPreset});
|
|
|
|
$hash->{helper}{playerState} = "playing";
|
|
readingsSingleUpdate($hash, "playerState", "playing", 1);
|
|
}
|
|
|
|
}
|
|
elsif($cmd =~ m/^(selectFavorite|inetRadioFavorite)/)
|
|
{
|
|
# This command must be processed hierarchicaly through the navigation path
|
|
if($hash->{helper}{cmdStep} == 1)
|
|
{
|
|
$hash->{helper}{cmdStep} = 2;
|
|
# Internet radio favorite# External Command. Not from buffer timer.
|
|
$hash->{helper}{fromSendCommandBuffer} = 0;
|
|
PHILIPS_AUDIO_SendCommand($hash, "/nav\$03\$01\$001\$0", "", "selectFavorite", $hash->{helper}{inetRadioFavorite});
|
|
}
|
|
elsif($hash->{helper}{cmdStep} == 2)
|
|
{
|
|
$hash->{helper}{cmdStep} = 3;
|
|
|
|
# External Command. Not from buffer timer.
|
|
$hash->{helper}{fromSendCommandBuffer} = 0;
|
|
|
|
# Favorite Presets
|
|
PHILIPS_AUDIO_SendCommand($hash, "/nav\$03\$02\$002\$0", "","selectFavorite", $hash->{helper}{inetRadioFavorite});
|
|
}
|
|
elsif($hash->{helper}{cmdStep} == 3)
|
|
{
|
|
$hash->{helper}{cmdStep} = 4;
|
|
|
|
# External Command. Not from buffer timer.
|
|
$hash->{helper}{fromSendCommandBuffer} = 0;
|
|
|
|
# Favorite Preset select
|
|
PHILIPS_AUDIO_SendCommand($hash, "/nav\$03\$03\$".sprintf("%03d", $hash->{helper}{inetRadioFavorite})."\$1", "","selectFavorite", $hash->{helper}{inetRadioFavorite});
|
|
|
|
$hash->{helper}{playerState} = "playing";
|
|
readingsSingleUpdate($hash, "playerState", "playing", 1);
|
|
}
|
|
}
|
|
elsif($cmd eq "play_pause")
|
|
{
|
|
if($data =~ /SUCCESS/)
|
|
{
|
|
if(ReadingsVal($name, "playerPlaying", "") eq "no")
|
|
{
|
|
readingsSingleUpdate($hash, "playerPlaying", "yes", 1);
|
|
}
|
|
else
|
|
{
|
|
delete $hash->{READINGS}{$_} foreach (grep /player/, keys %{$hash->{READINGS}});
|
|
|
|
readingsBeginUpdate($hash);
|
|
readingsBulkUpdate($hash, "playerPlaying", "no");
|
|
readingsBulkUpdate($hash, "input", "-");
|
|
readingsEndUpdate($hash, 1);
|
|
}
|
|
}
|
|
}
|
|
elsif($cmd eq "stop")
|
|
{
|
|
if($data =~ /STOP/)
|
|
{
|
|
delete $hash->{READINGS}{$_} foreach (grep /player/, keys %{$hash->{READINGS}});
|
|
|
|
readingsBeginUpdate($hash);
|
|
readingsBulkUpdate($hash, "playerPlaying", "no");
|
|
readingsBulkUpdate($hash, "input", "-");
|
|
readingsEndUpdate($hash, 1);
|
|
}
|
|
}
|
|
elsif($cmd =~ m/^(volumeStraight|volumeUp|volumeDown)/)
|
|
{
|
|
if($data =~ /SUCCESS/)
|
|
{
|
|
my $targetVolume = $hash->{helper}{targetVolume};
|
|
|
|
readingsBeginUpdate($hash);
|
|
readingsBulkUpdate($hash, "volumeStraight", $hash->{helper}{targetVolume});
|
|
readingsBulkUpdate($hash, "volume", PHILIPS_AUDIO_volume_abs2rel($hash, $targetVolume));
|
|
readingsEndUpdate($hash, 1);
|
|
}
|
|
}
|
|
elsif($cmd eq "elapse")
|
|
{
|
|
if($data =~ /'command':'(.+)',/)
|
|
{
|
|
if($1 eq "NOWPLAY")
|
|
{
|
|
# New player status information available
|
|
readingsSingleUpdate($hash, "playerPlaying", "yes", 1);
|
|
PHILIPS_AUDIO_SendCommand($hash, "/nowplay", "","nowplay", "noArg");
|
|
}
|
|
elsif($1 eq "ELAPSE")
|
|
{
|
|
readingsBeginUpdate($hash);
|
|
readingsBulkUpdate($hash, "playerPlaying", "yes");
|
|
|
|
if($data =~ /'value':(.+),/)
|
|
{
|
|
# Sometimes the device does not refresh the ELAPSE -> NOWPLAY request
|
|
# Showing the current stream position.
|
|
# Check for a new song.
|
|
|
|
$hash->{helper}{elapseValueOld} = "0" if(not defined($hash->{helper}{elapseValueOld}));
|
|
|
|
if (int($1) < int($hash->{helper}{elapseValueOld})) # New song
|
|
{
|
|
PHILIPS_AUDIO_SendCommand($hash, "/nowplay", "","nowplay", "noArg");
|
|
}
|
|
|
|
$hash->{helper}{elapseValueOld} = $1;
|
|
|
|
if(int($1) < 3600)
|
|
{
|
|
readingsBulkUpdate($hash, "playerPlayTime", strftime("%M:\%S", gmtime($1)));
|
|
}
|
|
else
|
|
{
|
|
readingsBulkUpdate($hash, "playerPlayTime", strftime("\%H:\%M:\%S", gmtime($1)));
|
|
}
|
|
}
|
|
if($data =~ /'mute':(.+),/)
|
|
{
|
|
if(int($1) == 1)
|
|
{
|
|
readingsBulkUpdate($hash, "mute", "on");
|
|
}
|
|
else
|
|
{
|
|
readingsBulkUpdate($hash, "mute", "off");
|
|
}
|
|
}
|
|
if($data =~ /'shuffle':(.+),/)
|
|
{
|
|
if(int($1) == 1)
|
|
{
|
|
readingsBulkUpdate($hash, "playerShuffle", "on");
|
|
}
|
|
else
|
|
{
|
|
readingsBulkUpdate($hash, "playerShuffle", "off");
|
|
}
|
|
}
|
|
if($data =~ /'repeat':(.+),/)
|
|
{
|
|
if(int($1) == 0)
|
|
{
|
|
readingsBulkUpdate($hash, "playerRepeat", "off");
|
|
}
|
|
elsif(int($1) == 1)
|
|
{
|
|
readingsBulkUpdate($hash, "playerRepeat", "single");
|
|
}
|
|
elsif(int($1) == 2)
|
|
{
|
|
readingsBulkUpdate($hash, "playerRepeat", "all");
|
|
}
|
|
}
|
|
if($data =~ /'rating':(.+),/)
|
|
{
|
|
readingsBulkUpdate($hash, "playerStreamRating", $1);
|
|
}
|
|
if($data =~ /'favstatus':(.+)}/)
|
|
{
|
|
if(int($1) == 1)
|
|
{
|
|
readingsBulkUpdate($hash, "playerStreamFavorite", "yes");
|
|
}
|
|
else
|
|
{
|
|
readingsBulkUpdate($hash, "playerStreamFavorite", "no");
|
|
}
|
|
}
|
|
if($data =~ /'volume':(.+),/)
|
|
{
|
|
readingsBulkUpdate($hash, "volumeStraight", $1);
|
|
readingsBulkUpdate($hash, "volume", PHILIPS_AUDIO_volume_abs2rel($hash, $1));
|
|
}
|
|
|
|
readingsEndUpdate($hash, 1);
|
|
|
|
if($data =~ /'play':(.+),/)
|
|
{
|
|
if(int($1) == 1)
|
|
{
|
|
$hash->{helper}{playerState} = "playing";
|
|
readingsBeginUpdate($hash);
|
|
readingsBulkUpdate ($hash, "playerPlaying", "yes");
|
|
readingsBulkUpdate ($hash, "playerState", "playing");
|
|
readingsEndUpdate ($hash, 1);
|
|
}
|
|
else
|
|
{
|
|
delete $hash->{READINGS}{$_} foreach (grep /player/, keys %{$hash->{READINGS}});
|
|
|
|
readingsBeginUpdate($hash);
|
|
readingsBulkUpdate ($hash, "playerPlaying", "no");
|
|
readingsBulkUpdate ($hash, "playerState", "home");
|
|
readingsEndUpdate ($hash, 1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
elsif($cmd eq "nowplay")
|
|
{
|
|
if($data =~ /'command':'(.+)',/)
|
|
{
|
|
if($1 eq "NOTHING")
|
|
{
|
|
delete $hash->{READINGS}{$_} foreach (grep /player/, keys %{$hash->{READINGS}});
|
|
|
|
$hash->{helper}{playerState} = "home";
|
|
|
|
readingsBeginUpdate($hash);
|
|
readingsBulkUpdate ($hash, "playerPlaying", "no");
|
|
readingsBulkUpdate ($hash, "playerState", "home");
|
|
readingsEndUpdate ($hash, 1);
|
|
}
|
|
}
|
|
elsif($data =~ /window.location = \"\/index\"/)
|
|
{
|
|
delete $hash->{READINGS}{$_} foreach (grep /player/, keys %{$hash->{READINGS}});
|
|
|
|
$hash->{helper}{playerState} = "home";
|
|
|
|
readingsBeginUpdate($hash);
|
|
readingsBulkUpdate ($hash, "playerPlaying", "no");
|
|
readingsBulkUpdate ($hash, "playerState", "home");
|
|
readingsEndUpdate ($hash, 1);
|
|
}
|
|
else
|
|
{
|
|
$hash->{helper}{playerState} = "playing";
|
|
|
|
readingsBeginUpdate($hash);
|
|
readingsBulkUpdate ($hash, "playerPlaying", "yes");
|
|
readingsBulkUpdate ($hash, "playerState", "playing");
|
|
readingsEndUpdate ($hash, 1);
|
|
}
|
|
|
|
if($data =~ /'devicename':'(.*?)'/)
|
|
{
|
|
if(not defined ($hash->{FRIENDLY_NAME}))
|
|
{
|
|
$hash->{FRIENDLY_NAME} = $1;
|
|
$hash->{helper}{dInfo}{FRIENDLY_NAME} = $1;
|
|
}
|
|
}
|
|
|
|
readingsBeginUpdate($hash);
|
|
|
|
if ($data =~ /'defaultAlbumArt':'res\/Internet_radio.jpg'/)
|
|
{
|
|
readingsBulkUpdate($hash, "input", "Internet Radio");
|
|
}
|
|
|
|
if($data =~ /'defaultAlbumArt':'res\/Media_library.jpg'/)
|
|
{
|
|
readingsBulkUpdate($hash, "input", "Media Library");
|
|
}
|
|
|
|
if($data =~ /'defaultAlbumArt':'res\/Home_AUX_nowplaying.jpg'/)
|
|
{
|
|
if($hash->{MODEL} eq "AW9000")
|
|
{
|
|
if($data =~ />Aux-in</)
|
|
{
|
|
readingsBulkUpdate($hash, "input", "Aux-in (analog)");
|
|
}
|
|
elsif($data =~ />Digital-in 1(.+)</)
|
|
{
|
|
readingsBulkUpdate($hash, "input", "Digital-in 1 (coaxial)");
|
|
}
|
|
elsif($data =~ />Digital-in 2(.+)</)
|
|
{
|
|
readingsBulkUpdate($hash, "input", "Digital-in 2 (optical)");
|
|
}
|
|
elsif($data =~ />MP3-Link</)
|
|
{
|
|
readingsBulkUpdate($hash, "input", "MP3-Link");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
readingsBulkUpdate($hash, "input", "MP3-Link");
|
|
}
|
|
}
|
|
|
|
if($data =~ /'defaultAlbumArt':'res\/spot_Nowplaying_AA.png'/)
|
|
{
|
|
readingsBulkUpdate($hash, "input", "Spotify");
|
|
}
|
|
|
|
if($data =~ /'defaultAlbumArt':'res\/Home_OnlineServices.png'/)
|
|
{
|
|
readingsBulkUpdate($hash, "input", "Online Services");
|
|
}
|
|
|
|
readingsEndUpdate($hash, 1);
|
|
|
|
if($data =~ /'title':'\\'(.+)\\''/)
|
|
{
|
|
if((ReadingsVal($name, "input", "") eq "Media Library") or (ReadingsVal($name, "input", "") eq "Spotify"))
|
|
{
|
|
readingsSingleUpdate($hash, "playerTitle", PHILIPS_AUDIO_html2txt($1), 1);
|
|
delete $hash->{READINGS}{playerRadioStationInfo};
|
|
}
|
|
elsif(ReadingsVal($name, "input", "") eq "Internet Radio")
|
|
{
|
|
readingsSingleUpdate($hash, "playerRadioStationInfo", PHILIPS_AUDIO_html2txt($1), 1);
|
|
delete $hash->{READINGS}{playerTitle};
|
|
}
|
|
}
|
|
elsif($data =~ /'title':'(.+)'/)
|
|
{
|
|
if((ReadingsVal($name, "input", "") eq "Media Library") or (ReadingsVal($name, "input", "") eq "Spotify"))
|
|
{
|
|
readingsSingleUpdate($hash, "playerTitle", PHILIPS_AUDIO_html2txt($1), 1);
|
|
delete $hash->{READINGS}{playerRadioStationInfo};
|
|
}
|
|
elsif(ReadingsVal($name, "input", "") eq "Internet Radio")
|
|
{
|
|
readingsSingleUpdate($hash, "playerRadioStationInfo", PHILIPS_AUDIO_html2txt($1), 1);
|
|
delete $hash->{READINGS}{playerTitle};
|
|
}
|
|
}
|
|
else
|
|
{
|
|
delete $hash->{READINGS}{playerTitle};
|
|
delete $hash->{READINGS}{playerRadioStationInfo};
|
|
}
|
|
|
|
if($data =~ /'subTitle':'(.+)'/)
|
|
{
|
|
if((ReadingsVal($name, "input", "") eq "Media Library") or (ReadingsVal($name, "input", "") eq "Spotify"))
|
|
{
|
|
readingsSingleUpdate($hash, "playerAlbum", PHILIPS_AUDIO_html2txt($1), 1);
|
|
delete $hash->{READINGS}{playerRadioStation};
|
|
}
|
|
else
|
|
{
|
|
readingsSingleUpdate($hash, "playerRadioStation", PHILIPS_AUDIO_html2txt($1), 1);
|
|
delete $hash->{READINGS}{playerAlbum};
|
|
}
|
|
}
|
|
else
|
|
{
|
|
delete $hash->{READINGS}{playerRadioStation};
|
|
delete $hash->{READINGS}{playerAlbum};
|
|
}
|
|
|
|
if($data =~ /'albumArt':'(.+)'/)
|
|
{
|
|
readingsSingleUpdate($hash, "playerAlbumArt", $1, 1);
|
|
}
|
|
else
|
|
{
|
|
delete $hash->{READINGS}{playerAlbumArt};
|
|
}
|
|
|
|
readingsBeginUpdate($hash);
|
|
|
|
if($data =~ /'volume':(.+),/)
|
|
{
|
|
readingsBulkUpdate($hash, "volumeStraight", $1);
|
|
readingsBulkUpdate($hash, "volume", PHILIPS_AUDIO_volume_abs2rel($hash, $1));
|
|
}
|
|
else
|
|
{
|
|
readingsBulkUpdate($hash, "volumeStraight", "0");
|
|
readingsBulkUpdate($hash, "volume", "0");
|
|
}
|
|
|
|
readingsEndUpdate($hash, 1);
|
|
|
|
if($data =~ /'elapsetime':(.+),/)
|
|
{
|
|
if(int($1) < 3600)
|
|
{
|
|
readingsSingleUpdate($hash, "playerPlayTime", strftime("%M:\%S", gmtime($1)), 1);
|
|
}
|
|
else
|
|
{
|
|
readingsSingleUpdate($hash, "playerPlayTime", strftime("\%H:\%M:\%S", gmtime($1)), 1);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
delete $hash->{READINGS}{playerPlayTime};
|
|
}
|
|
|
|
if($data =~ /'totaltime':(.+),/)
|
|
{
|
|
# Playing radio delivers that total time
|
|
if($1 eq "65535")
|
|
{
|
|
delete $hash->{READINGS}{playerTotalPlayTime};
|
|
}
|
|
elsif(int($1) < 3600)
|
|
{
|
|
readingsSingleUpdate($hash, "playerTotalPlayTime", strftime("%M:\%S", gmtime($1)), 1);
|
|
}
|
|
else
|
|
{
|
|
readingsSingleUpdate($hash, "playerTotalPlayTime", strftime("\%H:\%M:\%S", gmtime($1)), 1);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
delete $hash->{READINGS}{playerTotalPlayTime};
|
|
}
|
|
|
|
if($data =~ /'muteStatus':(.+),/)
|
|
{
|
|
if($1 == 1)
|
|
{
|
|
readingsSingleUpdate($hash, "mute", "on", 1);
|
|
}
|
|
else
|
|
{
|
|
readingsSingleUpdate($hash, "mute", "off", 1);
|
|
}
|
|
}
|
|
|
|
# typo in the (buggy) Streamium firmware...
|
|
if($data =~ /'playStaus':(.+),/)
|
|
{
|
|
if($1 == 1)
|
|
{
|
|
readingsSingleUpdate($hash, "playerPlaying", "yes", 1);
|
|
}
|
|
else
|
|
{
|
|
delete $hash->{READINGS}{$_} foreach (grep /player/, keys %{$hash->{READINGS}});
|
|
readingsSingleUpdate($hash, "playerPlaying", "no", 1);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
delete $hash->{READINGS}{$_} foreach (grep /player/, keys %{$hash->{READINGS}});
|
|
readingsSingleUpdate($hash, "playerPlaying", "no", 1);
|
|
}
|
|
}
|
|
elsif($cmd eq "homestatus")
|
|
{
|
|
# Homestatus answers with
|
|
# {'command':'NOTHING',\n'value':0}
|
|
|
|
# Do nothing ?
|
|
}
|
|
elsif ($cmd eq "getMediaRendererDesc")
|
|
{
|
|
if($data =~ /<manufacturer>(.+)<\/manufacturer>/)
|
|
{
|
|
$hash->{helper}{dInfo}{MANUFACTURER} = $1;
|
|
}
|
|
|
|
if($data =~ /<manufacturerURL>(.+)<\/manufacturerURL>/)
|
|
{
|
|
$hash->{helper}{dInfo}{MANUFACTURER_URL} = $1;
|
|
}
|
|
|
|
if($data =~ /<manufacturerURL>(.+)<\/manufacturerURL>/)
|
|
{
|
|
$hash->{helper}{dInfo}{MANUFACTURER_URL} = $1;
|
|
}
|
|
|
|
if($data =~ /<presentationURL>(.+)<\/presentationURL>/)
|
|
{
|
|
$hash->{helper}{dInfo}{PRESENTATION_URL} = $1;
|
|
}
|
|
|
|
if($data =~ /<deviceType>(.+)<\/deviceType>/)
|
|
{
|
|
$hash->{helper}{dInfo}{UPNP_DEVICE_TYPE} = $1;
|
|
}
|
|
|
|
if($data =~ /<friendlyName>(.+)<\/friendlyName>/)
|
|
{
|
|
$hash->{helper}{dInfo}{FRIENDLY_NAME} = $1;
|
|
}
|
|
if($data =~ /<UDN>(.+)<\/UDN>/)
|
|
{
|
|
my $uuid = uc($1);
|
|
$uuid =~ s/UUID://g;
|
|
$hash->{helper}{dInfo}{UUID} = $uuid;
|
|
}
|
|
|
|
if($data =~ /<UPC>(.+)<\/UPC>/)
|
|
{
|
|
$hash->{helper}{dInfo}{UPC} = $1;
|
|
}
|
|
|
|
if($data =~ /<modelName>(.+)<\/modelName>/)
|
|
{
|
|
$hash->{helper}{dInfo}{MODEL_NAME} = $1;
|
|
}
|
|
if($data =~ /<modelNumber>(.+)<\/modelNumber>/)
|
|
{
|
|
$hash->{helper}{dInfo}{MODEL_NUMBER} = $1;
|
|
}
|
|
|
|
if($data =~ /<serialNumber>(.+)<\/serialNumber>/)
|
|
{
|
|
$hash->{helper}{dInfo}{SERIAL_NUMBER} = uc($1);
|
|
}
|
|
if($data =~ /<modelDescription>(.+)<\/modelDescription>/)
|
|
{
|
|
$hash->{helper}{dInfo}{MODEL_DESCRIPTION} = $1;
|
|
}
|
|
# Replace \n, \r, \t from the string for XML parsing
|
|
|
|
# replace \n by ""
|
|
$data =~ s/\n//g;
|
|
|
|
# replace \t by ""
|
|
$data =~ s/\t//g;
|
|
|
|
# replace \r by ""
|
|
$data =~ s/\r//g;
|
|
|
|
if($data =~ /<iconList>(.+?)<\/iconList>/)
|
|
{
|
|
my $address = $hash->{IP_ADDRESS};
|
|
my $port = "";
|
|
|
|
if (uc($hash->{MODEL}) eq "NP3700")
|
|
{
|
|
$port = 7123;
|
|
}
|
|
elsif
|
|
(
|
|
(uc($hash->{MODEL}) eq "NP3500") or
|
|
(uc($hash->{MODEL}) eq "NP3900") or
|
|
(uc($hash->{MODEL}) eq "AW9000")
|
|
)
|
|
{
|
|
$port = 49153;
|
|
}
|
|
|
|
my $i = 1;
|
|
|
|
while ($data =~ /<url>(.+?)<\/url>/g)
|
|
{
|
|
# May have several urls according to the UPNP/DLNA standard
|
|
$hash->{helper}{dInfo}{"DEVICE_ICON_$i"} = "http://$address:$port/$1";
|
|
$i++;
|
|
}
|
|
}
|
|
}
|
|
elsif ($cmd eq "getPresets")
|
|
{
|
|
$hash->{helper}{TOTALINETRADIOPRESETS} = 0 if(not defined($hash->{helper}{TOTALINETRADIOPRESETS}));
|
|
|
|
# This command must be processed hierarchicaly through the navigation path
|
|
if($hash->{helper}{cmdStep} == 1)
|
|
{
|
|
$hash->{helper}{cmdStep} = 2;
|
|
# Internet radio
|
|
PHILIPS_AUDIO_SendCommand($hash, "/nav\$03\$01\$001\$0", "","getPresets", "noArg");
|
|
}
|
|
elsif($hash->{helper}{cmdStep} == 2)
|
|
{
|
|
$hash->{helper}{cmdStep} = 3;
|
|
# Presets
|
|
PHILIPS_AUDIO_SendCommand($hash, "/nav\$03\$02\$001\$0", "","getPresets", "noArg");
|
|
}
|
|
elsif($hash->{helper}{cmdStep} == 3)
|
|
{
|
|
my $listedItems; # Visible Items in the display. Max 8
|
|
my $nextreqURL;
|
|
my $i;
|
|
my $presetID = 0;
|
|
my $presetName;
|
|
|
|
# Parse first 8 Presets
|
|
if($data =~ /<title>Error<\/title>/)
|
|
{
|
|
# In case on presets defined the player returns an Error
|
|
# Do nothing
|
|
$hash->{helper}{TOTALINETRADIOPRESETS} = 0;
|
|
readingsSingleUpdate($hash, "totalPresets", "0", 1);
|
|
delete $hash->{READINGS}{Reading_presets};
|
|
}
|
|
else
|
|
{
|
|
if ($data =~ /'nextrequrl':'(.+?)',/)
|
|
{
|
|
$nextreqURL = $1;
|
|
#Log3 $name, 5, "NextreqURL: $nextreqURL";
|
|
}
|
|
|
|
if ($data =~ /'totalListCount':(.+),/)
|
|
{
|
|
$hash->{helper}{TOTALINETRADIOPRESETS} = $1;
|
|
readingsSingleUpdate($hash, "totalPresets", $1, 1);
|
|
#Log3 $name, 5, "ListedItems: $listedItems";
|
|
}
|
|
|
|
$data =~ s/\R//g; # Remove new lines
|
|
|
|
readingsBeginUpdate($hash);
|
|
|
|
while ($data =~ /{'title':'(.+?)',/g)
|
|
{
|
|
|
|
$presetName = $1;
|
|
|
|
if($data =~ /'id':(.+?),/g)
|
|
{
|
|
$presetID = $1;
|
|
}
|
|
if ($presetID ne "" and $presetName ne "")
|
|
{
|
|
readingsBulkUpdate($hash, sprintf(".inetRadioPreset_%02d", $presetID), $presetName);
|
|
}
|
|
}
|
|
readingsEndUpdate($hash, 1);
|
|
|
|
if($presetID < ($hash->{helper}{TOTALINETRADIOPRESETS})) # Maximum listed items = 8. Get the next items by sending the nextreqURL
|
|
{
|
|
# External Command. Not from buffer timer.
|
|
$hash->{helper}{fromSendCommandBuffer} = 0;
|
|
PHILIPS_AUDIO_SendCommand($hash, $nextreqURL, "","getPresets", "noArg");
|
|
}
|
|
else
|
|
{
|
|
readingsSingleUpdate($hash, "playerListStatus", "ready", 1);
|
|
delete $hash->{READINGS}{Reading_presets};
|
|
delete $hash->{READINGS}{readingPresets};
|
|
$hash->{helper}{cmdStep} = 4;
|
|
# Finished
|
|
}
|
|
}
|
|
}
|
|
}
|
|
elsif ($cmd eq "getFavorites")
|
|
{
|
|
$hash->{helper}{TOTALINETRADIOFAVORITES} = 0 if(not defined($hash->{helper}{TOTALINETRADIOFAVORITES}));
|
|
|
|
# This command must be processed hierarchicaly through the navigation path
|
|
if($hash->{helper}{cmdStep} == 1)
|
|
{
|
|
$hash->{helper}{cmdStep} = 2;
|
|
# Internet radio
|
|
PHILIPS_AUDIO_SendCommand($hash, "/nav\$03\$01\$001\$0", "","getFavorites", "noArg");
|
|
}
|
|
elsif($hash->{helper}{cmdStep} == 2)
|
|
{
|
|
$hash->{helper}{cmdStep} = 3;
|
|
# Favorites
|
|
PHILIPS_AUDIO_SendCommand($hash, "/nav\$03\$02\$002\$0", "","getFavorites", "noArg");
|
|
}
|
|
elsif($hash->{helper}{cmdStep} == 3)
|
|
{
|
|
if($data =~ /<title>Error<\/title>/)
|
|
{
|
|
# In case on presets defined the player returns an Error
|
|
# Do nothing
|
|
$hash->{helper}{TOTALINETRADIOFAVORITES} = 0;
|
|
readingsSingleUpdate($hash, "totalFavorites", "0", 1);
|
|
delete $hash->{READINGS}{Reading_favorites};
|
|
}
|
|
else
|
|
{
|
|
my $listedItems; # Visible Items in the display. Max 8
|
|
my $nextreqURL;
|
|
my $i;
|
|
my $favoriteID = 0;
|
|
my $favoriteName;
|
|
|
|
# Parse first 8 Presets
|
|
|
|
if ($data =~ /'nextrequrl':'(.+?)',/)
|
|
{
|
|
$nextreqURL = $1;
|
|
#Log3 $name, 5, "NextreqURL: $nextreqURL";
|
|
}
|
|
|
|
if ($data =~ /'totalListCount':(.+),/)
|
|
{
|
|
$hash->{helper}{TOTALINETRADIOFAVORITES} = $1;
|
|
readingsSingleUpdate($hash, "totalFavorites", $1, 1);
|
|
}
|
|
|
|
$data =~ s/\R//g; # Remove new lines
|
|
|
|
readingsBeginUpdate($hash);
|
|
|
|
while($data =~ /{'title':'(.+?)',/g)
|
|
{
|
|
$favoriteName = $1;
|
|
|
|
if($data =~ /'id':(.+?),/g)
|
|
{
|
|
$favoriteID = $1;
|
|
}
|
|
if ($favoriteID ne "" and $favoriteName ne "")
|
|
{
|
|
readingsBulkUpdate($hash, sprintf(".inetRadioFavorite_%02d", $favoriteID), $favoriteName);
|
|
}
|
|
}
|
|
|
|
readingsEndUpdate($hash, 1);
|
|
|
|
#Log3 $name, 5, "FavoriteIDNachLoop: $favoriteID";
|
|
|
|
if($favoriteID < ($hash->{helper}{TOTALINETRADIOFAVORITES})) # Maximum listed items = 8. Get the next items by sending the nextreqURL
|
|
{
|
|
# External Command. Not from buffer timer.
|
|
$hash->{helper}{fromSendCommandBuffer} = 0;
|
|
|
|
PHILIPS_AUDIO_SendCommand($hash, $nextreqURL, "","getFavorites", "noArg");
|
|
}
|
|
else
|
|
{
|
|
readingsSingleUpdate($hash, "playerListStatus", "ready", 1);
|
|
delete $hash->{READINGS}{Reading_favorites};
|
|
delete $hash->{READINGS}{readingFavorites};
|
|
$hash->{helper}{cmdStep} = 4;
|
|
# Finished
|
|
}
|
|
}
|
|
}
|
|
}
|
|
elsif($cmd =~ /input|selectStream/)
|
|
{
|
|
$data =~ s/\R//g; # Remove new lines for regex
|
|
|
|
if($arg eq "---")
|
|
{
|
|
# Do nothing
|
|
}
|
|
elsif($arg =~ /internetRadio|onlineServices|mediaLibrary|(\d{3})_(.+?)_|lvl_(.+?)/) # 000_[c|i]_******
|
|
{
|
|
# don't update menu content if playable item was chosen
|
|
if (defined $1)
|
|
{
|
|
if ($2 eq "i")
|
|
{
|
|
$hash->{helper}{networkRequest} = "idle";
|
|
$hash->{helper}{playerState} = "playing";
|
|
# Stream selected. Manual operation finished.
|
|
$hash->{helper}{manualOperation} = 0;
|
|
|
|
readingsBeginUpdate($hash);
|
|
readingsBulkUpdate ($hash, "networkRequest", "idle");
|
|
readingsBulkUpdate ($hash, "playerListStatus", "ready");
|
|
readingsBulkUpdate ($hash, "playerState", "playing");
|
|
readingsBulkUpdate ($hash, ".manualOperation", "no");
|
|
readingsEndUpdate ($hash, 1);
|
|
|
|
PHILIPS_AUDIO_SendCommand($hash, "/nowplay", "","nowplay", "noArg");
|
|
|
|
# Stream selected trigger getStatus
|
|
PHILIPS_AUDIO_ResetTimer($hash); # getStatus
|
|
return;
|
|
}
|
|
}
|
|
|
|
if ($data =~ /{'command':'NOTHING','value':0}|<title>Error<\/title>/)
|
|
{
|
|
my $errorMessage = "Player responded with unspecified error";
|
|
|
|
if($data =~ /alert\(\'(.*?)\'\);|\'command\':\'(.*?)\'/)
|
|
{
|
|
$errorMessage = $1;
|
|
}
|
|
# Delete old readings
|
|
delete $hash->{READINGS}{$_} foreach (grep /listItem_...$/, keys %{$hash->{READINGS}});
|
|
delete $hash->{READINGS}{$_} foreach (grep /.listItemTarget_...$/, keys %{$hash->{READINGS}});
|
|
|
|
$hash->{helper}{networkRequest} = "idle";
|
|
$hash->{helper}{manualOperation} = 0;
|
|
|
|
readingsBeginUpdate($hash);
|
|
readingsBulkUpdate ($hash, "networkRequest", "idle");
|
|
readingsBulkUpdate ($hash, "listItem_001", "$errorMessage");
|
|
readingsBulkUpdate ($hash, ".listItemTarget_001", "$hash->{helper}{currentNavUrl}");
|
|
readingsBulkUpdate ($hash, "playerListStatus", "ready");
|
|
readingsBulkUpdate ($hash, ".manualOperation", "no");
|
|
readingsEndUpdate ($hash, 1);
|
|
|
|
return "Player response: $errorMessage.";
|
|
}
|
|
|
|
my $listDepthLevel = 0;
|
|
my $playerListTotalCount = 0;
|
|
my $listId = 0;
|
|
my $listItems = "";
|
|
|
|
readingsBeginUpdate($hash);
|
|
|
|
if($data =~ /var listdetails = \{(.*)\};/)
|
|
{
|
|
my $listdetails_temp = $1;
|
|
my $listdetails = $listdetails_temp;
|
|
|
|
if($listdetails =~ /'totalitems':(.*?),/)
|
|
{
|
|
readingsBulkUpdate($hash, ".listTotalItems", "$1");
|
|
}
|
|
|
|
$listdetails = $listdetails_temp;
|
|
|
|
if($listdetails =~ /'totalListCount':(.*?),/)
|
|
{
|
|
$playerListTotalCount = $1;
|
|
$hash->{helper}{playerListTotalCount} = $1;
|
|
readingsBulkUpdate($hash, "playerListTotalCount", "$1");
|
|
}
|
|
|
|
$listdetails = $listdetails_temp;
|
|
|
|
if($listdetails =~ /'depthlevel':(.*?),/)
|
|
{
|
|
$listDepthLevel = $1;
|
|
readingsBulkUpdate($hash, ".listDepthLevel", "$1");
|
|
}
|
|
}
|
|
|
|
if($data =~ /var listItem = \{(.*)\};/)
|
|
{
|
|
my $listItems_temp = $1;
|
|
$listItems = $listItems_temp;
|
|
|
|
if($listItems =~ /'nextrequrl':'(.*?)'/)
|
|
{
|
|
$hash->{helper}{nextUrl} = $1;
|
|
readingsBulkUpdate($hash, ".listNextUrl", "$1");
|
|
}
|
|
|
|
$listItems = $listItems_temp;
|
|
|
|
if($listItems =~ /'prevUrl':'(.*?)',/)
|
|
{
|
|
if($1 ne "")
|
|
{
|
|
$hash->{helper}{prevUrl} = $1;
|
|
readingsBulkUpdate($hash, ".listPrevUrl", "$1");
|
|
}
|
|
else
|
|
{
|
|
$hash->{helper}{prevUrl} = "-";
|
|
readingsBulkUpdate($hash, ".listPrevUrl", "-");
|
|
}
|
|
}
|
|
|
|
readingsEndUpdate($hash, 1);
|
|
|
|
$listItems = $listItems_temp;
|
|
|
|
if($listItems =~ /'items': \[(.*)\]/)
|
|
{
|
|
|
|
# Delete old readings
|
|
delete $hash->{READINGS}{$_} foreach (grep /listItem_...$/, keys %{$hash->{READINGS}});
|
|
delete $hash->{READINGS}{$_} foreach (grep /.listItemTarget_...$/, keys %{$hash->{READINGS}});
|
|
|
|
# Predefine all listItem readings with "reading..."
|
|
|
|
readingsBeginUpdate($hash);
|
|
|
|
for(my $i = 1; ($i < int($hash->{helper}{playerListTotalCount}) + 1) and ($i < AttrVal($name, "maxListItems", 100) + 1); $i++)
|
|
{
|
|
readingsBulkUpdate($hash, "listItem_".sprintf("%03s", $i), "reading...");
|
|
}
|
|
|
|
my $items = $1;
|
|
|
|
while ($items =~ /\{(.*?)\}/g)
|
|
{
|
|
my $item = $1;
|
|
my $title = "";
|
|
|
|
if($item =~ /'title':'(.*?)',/)
|
|
{
|
|
$title = PHILIPS_AUDIO_html2txt($1);
|
|
}
|
|
|
|
if($item =~ /'id':(.*?),/)
|
|
{
|
|
$listId = $1;
|
|
}
|
|
|
|
my $itemTarget = "";
|
|
if($item =~ /'target':'(.*?)'/)
|
|
{
|
|
$itemTarget = $1;
|
|
readingsBulkUpdate($hash, ".listItemTarget_".sprintf("%03s", int($listId)), $1);
|
|
}
|
|
if(substr($itemTarget, -1) eq "1")
|
|
{
|
|
$title = "i_" . $title; # item
|
|
}
|
|
else
|
|
{
|
|
$title = "c_" . $title; # container
|
|
}
|
|
readingsBulkUpdate($hash, "listItem_".sprintf("%03s", int($listId)), $title);
|
|
}
|
|
|
|
readingsEndUpdate($hash, 1);
|
|
|
|
}
|
|
}
|
|
if($listId < $hash->{helper}{playerListTotalCount})
|
|
{
|
|
# External Command. Not from buffer timer.
|
|
$hash->{helper}{fromSendCommandBuffer} = 0;
|
|
PHILIPS_AUDIO_SendCommand($hash, "$hash->{helper}{nextUrl}", "", "selectStream", "list");
|
|
}
|
|
else
|
|
{
|
|
$hash->{helper}{networkRequest} = "idle";
|
|
|
|
readingsBeginUpdate($hash);
|
|
readingsBulkUpdate ($hash, "networkRequest", "idle");
|
|
readingsBulkUpdate ($hash, "playerListStatus", "ready");
|
|
readingsEndUpdate ($hash, 1);
|
|
|
|
}
|
|
}
|
|
elsif($arg =~ /list/)
|
|
{
|
|
if ($data =~ /{'command':'NOTHING','value':0}|<title>Error<\/title>/)
|
|
{
|
|
$hash->{helper}{networkRequest} = "idle";
|
|
readingsSingleUpdate($hash, "networkRequest", "idle", 1);
|
|
Log3 $name, 3, "PHILIPS_AUDIO ($name) - Player response: Media Library change not successful.";
|
|
return "Player response: Media Library change not successful.";
|
|
}
|
|
|
|
$hash->{helper}{networkRequest} = "busy";
|
|
|
|
readingsBeginUpdate($hash);
|
|
readingsBulkUpdate($hash, "networkRequest", "busy");
|
|
|
|
$data =~ s/\R//g; # Remove new lines for regex
|
|
|
|
my $listDepthLevel = 0;
|
|
my $playerListTotalCount = 0;
|
|
my $listId = 0;
|
|
my $listItems = "";
|
|
|
|
$listItems = $data;
|
|
|
|
if($listItems =~ /'nextrequrl':'(.*?)'/)
|
|
{
|
|
$hash->{helper}{nextUrl} = $1;
|
|
readingsBulkUpdate($hash, ".listNextUrl", "$1");
|
|
}
|
|
|
|
$listItems = $data;
|
|
|
|
if($listItems =~ /'prevUrl':'(.*?)',/)
|
|
{
|
|
if($1 ne "")
|
|
{
|
|
$hash->{helper}{prevUrl} = $1;
|
|
readingsBulkUpdate($hash, ".listPrevUrl", "$1");
|
|
}
|
|
else
|
|
{
|
|
$hash->{helper}{prevUrl} = "-";
|
|
readingsBulkUpdate($hash, ".listPrevUrl", "-");
|
|
}
|
|
}
|
|
|
|
$listItems = $data;
|
|
|
|
if($listItems =~ /'items': \[(.*)\]/)
|
|
{
|
|
my $items = $1;
|
|
|
|
while ($items =~ /\{(.*?)\}/g)
|
|
{
|
|
my $item_temp = $1;
|
|
my $item = $item_temp;
|
|
|
|
my $title = "";
|
|
if($item =~ /'title':'(.*?)',/)
|
|
{
|
|
$title = PHILIPS_AUDIO_html2txt($1);
|
|
}
|
|
$item = $item_temp;
|
|
if($item =~ /'id':(.*?),/)
|
|
{
|
|
$listId = $1;
|
|
}
|
|
|
|
$item = $item_temp;
|
|
my $itemTarget = "";
|
|
if($item =~ /'target':'(.*?)'/)
|
|
{
|
|
$itemTarget = $1;
|
|
readingsBulkUpdate($hash, ".listItemTarget_".sprintf("%03s", int($listId)), $1);
|
|
}
|
|
|
|
if(substr($itemTarget, -1) eq "1")
|
|
{
|
|
$title = "i_" . $title; # item
|
|
}
|
|
else
|
|
{
|
|
$title = "c_" . $title; # container
|
|
}
|
|
readingsBulkUpdate($hash, "listItem_".sprintf("%03s", int($listId)), $title);
|
|
|
|
last if($listId eq AttrVal($name, "maxListItems", 100));
|
|
}
|
|
}
|
|
|
|
if(($listId < $hash->{helper}{playerListTotalCount}) && ($listId < AttrVal($name, "maxListItems", 100)))
|
|
{
|
|
# External Command. Not from buffer timer.
|
|
$hash->{helper}{fromSendCommandBuffer} = 0;
|
|
|
|
PHILIPS_AUDIO_SendCommand($hash, "$hash->{helper}{nextUrl}", "", "selectStream", "list");
|
|
}
|
|
else
|
|
{
|
|
$hash->{helper}{networkRequest} = "idle";
|
|
readingsBulkUpdate($hash, "networkRequest", "idle");
|
|
readingsBulkUpdate($hash, "playerListStatus", "ready");
|
|
}
|
|
|
|
readingsEndUpdate($hash, 1);
|
|
}
|
|
|
|
}
|
|
|
|
$hash->{helper}{networkRequest} = "idle";
|
|
readingsSingleUpdate($hash, "networkRequest", "idle", 1);
|
|
}
|
|
return;
|
|
}
|
|
|
|
#############################
|
|
# converts straight volume in percentage volume (volumestraightmin .. volumestraightmax => 0 .. 100%)
|
|
sub PHILIPS_AUDIO_volume_rel2abs
|
|
{
|
|
my ($hash, $percentage) = @_;
|
|
|
|
return int($percentage * 64 / 100);
|
|
}
|
|
|
|
#############################
|
|
# converts relative volume to "straight" volume (0 .. 100% => volumestraightmin .. volumestraightmax)
|
|
sub PHILIPS_AUDIO_volume_abs2rel
|
|
{
|
|
my ($hash, $absolute) = @_;
|
|
|
|
return int($absolute * 100 / 64);
|
|
}
|
|
|
|
#############################
|
|
# Restarts the internal status request timer according to the given interval or current receiver state
|
|
sub PHILIPS_AUDIO_ResetTimer
|
|
{
|
|
my ($hash, $interval) = @_;
|
|
|
|
RemoveInternalTimer($hash, "PHILIPS_AUDIO_GetStatus");
|
|
|
|
if($hash->{helper}{DISABLED} == 0)
|
|
{
|
|
if(defined($interval))
|
|
{
|
|
InternalTimer(gettimeofday() + $interval, "PHILIPS_AUDIO_GetStatus", $hash);
|
|
}
|
|
elsif((exists($hash->{READINGS}{presence}{VAL}) and $hash->{READINGS}{presence}{VAL} eq "present") and (exists($hash->{READINGS}{power}{VAL}) and $hash->{READINGS}{power}{VAL} eq "on"))
|
|
{
|
|
InternalTimer(gettimeofday() + $hash->{helper}{ON_INTERVAL}, "PHILIPS_AUDIO_GetStatus", $hash);
|
|
}
|
|
else
|
|
{
|
|
InternalTimer(gettimeofday() + $hash->{helper}{OFF_INTERVAL}, "PHILIPS_AUDIO_GetStatus", $hash);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
#############################
|
|
# convert all HTML entities into UTF-8 equivalent
|
|
|
|
sub PHILIPS_AUDIO_html2txt
|
|
{
|
|
my ($string) = @_;
|
|
|
|
$string =~ s/(&amp;quot;|&quot;|"|\\")/\"/g;
|
|
$string =~ s/(&amp;|&)/&/g;
|
|
$string =~ s/ / /g;
|
|
$string =~ s/('|\\'|\')/'/g;
|
|
$string =~ s/(\xe4|ä)/ä/g;
|
|
$string =~ s/(\xc4|Ä)/Ä/g;
|
|
$string =~ s/(\xf6|ö)/ö/g;
|
|
$string =~ s/(\xd6|Ö)/Ö/g;
|
|
$string =~ s/(\xfc|ü)/ü/g;
|
|
$string =~ s/(\xdc|Ü)/Ü/g;
|
|
$string =~ s/(\xdf|ß)/ß/g;
|
|
|
|
return $string;
|
|
}
|
|
|
|
sub PHILIPS_AUDIO_getMediaRendererDesc
|
|
{
|
|
# queries the addition model descriptions
|
|
my ($hash) = @_;
|
|
|
|
my $name = $hash->{NAME};
|
|
|
|
Log3 $name, 5, "PHILIPS_AUDIO ($name) - execute nonblocking \"MediaRendererDesc\"";
|
|
|
|
my $url = "";
|
|
my $port = "";
|
|
|
|
if(uc($hash->{MODEL}) eq "NP3700")
|
|
{
|
|
$url = "http://$hash->{IP_ADDRESS}:7123/DeviceDescription.xml";
|
|
}
|
|
elsif
|
|
(
|
|
(uc($hash->{MODEL}) eq "NP3500") or
|
|
(uc($hash->{MODEL}) eq "NP3900") or
|
|
(uc($hash->{MODEL}) eq "AW9000")
|
|
)
|
|
{
|
|
$url = "http://$hash->{IP_ADDRESS}:49153/nmrDescription.xml";
|
|
}
|
|
else
|
|
{
|
|
return "Unknown Device.";
|
|
}
|
|
|
|
HttpUtils_NonblockingGet({
|
|
url => $url,
|
|
timeout => AttrVal($name, "requestTimeout", 10),
|
|
noshutdown => 1,
|
|
data => "",
|
|
loglevel => ($hash->{helper}{AVAILABLE} ? undef : 5),
|
|
hash => $hash,
|
|
cmd => "getMediaRendererDesc",
|
|
arg => "noArg",
|
|
callback => \&PHILIPS_AUDIO_ParseResponse
|
|
});
|
|
return;
|
|
}
|
|
|
|
1;
|
|
|
|
=pod
|
|
=item device
|
|
=item summary controls a Philips Streamium Network Player in a local network
|
|
=item summary_DE steuert einen Philips Streamium Netzwerkplayer im lokalen Netzwerk
|
|
=begin html
|
|
|
|
<a name="PHILIPS_AUDIO"></a>
|
|
<h3>PHILIPS_AUDIO</h3>
|
|
<ul>
|
|
<a name="PHILIPS_AUDIOdefine"></a>
|
|
<b>Define</b>
|
|
<br><br>
|
|
<ul>
|
|
<code>
|
|
define <name> PHILIPS_AUDIO <device model> <ip-address> [<status_interval>]<br><br>
|
|
define <name> PHILIPS_AUDIO <device model> [<off_status_interval>] [<on_status_interval>]
|
|
</code>
|
|
<br><br>
|
|
This module controls a Philips Audio Player e.g. MCi, Streamium or Fidelio and potentially any other device controlled by the "myRemote" app.<br>
|
|
You might also check, opening the following URL in the browser: http://[ip number of your device]:8889/index
|
|
<br><br>
|
|
(Tested on: AW9000, NP3500, NP3700 and NP3900)
|
|
<br><br>
|
|
Example:<br><br>
|
|
<ul>
|
|
<code>
|
|
define player PHILIPS_AUDIO NP3900 192.168.0.15<br><br>
|
|
# With custom status interval of 60 seconds<br>
|
|
define PHAUDIO_player PHILIPS_AUDIO NP3900 192.168.0.15 <b>60</b><br><br>
|
|
# With custom "off"-interval of 60 seconds and "on"-interval of 10 seconds<br>
|
|
define PHAUDIO_player PHILIPS_AUDIO NP3900 192.168.0.15 <b>60 10</b><br>
|
|
</code>
|
|
<br>
|
|
<i>Note: Due to slow command processing by the player itself the minimum interval is <b>limited to 5 seconds</b>. More frequent polling might cause device freezes.</i>
|
|
</ul>
|
|
</ul>
|
|
<br>
|
|
<a name="PHILIPS_AUDIOset"></a>
|
|
<b>Set</b>
|
|
<ul>
|
|
<code>
|
|
set <name> <command> [<parameter>]
|
|
</code><br><br>
|
|
<i>Note: Commands and parameters are case sensitive.</i><br>
|
|
<ul><br>
|
|
<li><b>favoriteAdd</b> – Adds currently played Internet Radio stream to favorites</li>
|
|
<li><b>favoriteRemove</b> – Removes currently played Internet Radio stream from favorites</li>
|
|
<li><b>getFavorites</b> – Reads stored favorites from the device (may take some time...)</li>
|
|
<li><b>getMediaRendererDesc</b> – Reads device specific information (stored in the deviceInfo reading)</li>
|
|
<li><b>getPresets</b> – Reads stored presets from the device (may take some time...)</li>
|
|
<li><b>input</b> – Selects the following input</li>
|
|
<ul>
|
|
<li><b>analogAux</b> – Selects the analog AUX input (AW9000 only)</li>
|
|
<li><b>digital1Coaxial</b> – Selects the digital coaxial input (AW9000 only)</li>
|
|
<li><b>digital2Optical</b> – Selects the digital optical input (AW9000 only)</li>
|
|
<li><b>internetRadio</b> – Selects the Internet Radio input</li>
|
|
<li><b>mediaLibrary</b> – Selects the Media Library input (UPnP/DLNA server) (not available on AW9000)</li>
|
|
<li><b>mp3Link</b> – Selects the analog MP3 Link input (not available on AW9000)</li>
|
|
<li><b>onlineServices</b> – Selects the Online Services input</li>
|
|
</ul>
|
|
<li><b>mute [ on | off ]</b> – Mutes/unmutes the device</li>
|
|
<li><b>player</b> – Player related commands</li>
|
|
<ul>
|
|
<li><b>next</b> – Selects next audio stream</li>
|
|
<li><b>prev</b> – Selects previous audio stream</li>
|
|
<li><b>play-pause</b> – Plays/pauses the current audio stream</li>
|
|
<li><b>stop</b> – Stops the current audio stream</li>
|
|
</ul>
|
|
<li><b>repeat [ single | all | off]</b> – Selects the repeate mode</li>
|
|
<li><b>selectFavorite [ name ]</b> – Selects a favorite. Empty if no favorites found. (see also getFavorites)</li>
|
|
<li><b>selectFavoriteByNumber [ number ]</b> – Selects a favorite by its number. Empty if no favorites found. (see also getFavorites)</li>
|
|
<li><b>selectPreset [ name ]</b> – Selects a preset. Empty if no presets found. (see also getPresets)</li>
|
|
<li><b>selectPresetByNumber [ number ]</b> – Selects a preset by its number. Empty if no presets found. (see also getPresets)</li>
|
|
<li><b>selectStream [ name ]</b> – Context-sensitive. Selects a stream depending on the current input and player list content. A 'c'-prefix represents a 'container' (directory). An 'i'-prefix represents an 'item' (audio stream).</li>
|
|
<li><b>shuffle [ on | off ]</b> – Sets the shuffle mode</li>
|
|
<li><b>standbyButton</b> – Emulates the standby button. Toggles between standby and power on</li>
|
|
<li><b>volume</b> – Sets the relative volume 0...100%</li>
|
|
<li><b>volumeDown</b> – Sets the device specific volume by one decrement</li>
|
|
<li><b>volumeStraight</b> – Sets the device specific absolute volume 0...64</li>
|
|
<li><b>volumeUp</b> – Sets the device specific volume by one increment</li>
|
|
</ul>
|
|
</ul>
|
|
<br>
|
|
<a name="PHILIPS_AUDIOget"></a>
|
|
<b>Get</b>
|
|
<ul>
|
|
<code>
|
|
get <name> <reading> <reading name>
|
|
</code>
|
|
<ul>
|
|
<br>
|
|
<li><b>deviceInfo</b> – Returns device specific information</li>
|
|
<li><b>reading</b></li>
|
|
<ul>
|
|
<li><b>input</b> – Returns current input or '-' if not playing</li>
|
|
<li><b>listItem_xxx</b> – Returns player list item (limited to 999 entries)</li>
|
|
<li><b>networkError</b> – Shows an occured current network error</li>
|
|
<li><b>networkRequest</b> – Shows current network activity (idle/busy)</li>
|
|
<li><b>power</b> – Returns power status (on/off)</li>
|
|
<li><b>playerAlbum</b> – Returns the album name of played stream</li>
|
|
<li><b>playerAlbumArt</b> – Returns the album art of played audio stream</li>
|
|
<li><b>playerListStatus</b> – Returns current player list status (busy/ready)</li>
|
|
<li><b>playerListTotalCount</b> – Returns number of player list entries</li>
|
|
<li><b>playerPlayTime</b> – Returns audio stream duration</li>
|
|
<li><b>playerPlaying</b> – Returns current player playing state (yes/no)</li>
|
|
<li><b>playerRadioStation</b> – Returns the name of played radio station</li>
|
|
<li><b>playerRadioStationInfo</b> – Returns additional info of the played radio station</li>
|
|
<li><b>playerRepeat</b> – Returns current repeat mode (off/single/all)</li>
|
|
<li><b>playerShuffle</b> – Returns current shuffle mode (on/off)</li>
|
|
<li><b>playerState</b> – Returns current player state (home/browsing/playing)</li>
|
|
<li><b>playerStreamFavorite</b> – Shows if audio stream is a favorite (yes/no)</li>
|
|
<li><b>playerStreamRating</b> – Shows rating of the audio stream</li>
|
|
<li><b>playerTitle</b> – Returns audio stream's title</li>
|
|
<li><b>playerTotalTime</b> – Shows audio stream's total time</li>
|
|
<li><b>presence</b> – Returns peresence status (present/absent)</li>
|
|
<li><b>state</b> – Returns current state (on/off)</li>
|
|
<li><b>totalFavorites</b> – Returns total number of stored favorites (see getFavorites)</li>
|
|
<li><b>totalPresets</b> – Returns total number of stored presets (see getPresets)</li>
|
|
<li><b>volume</b> – Returns current relative volume (0...100%)</li>
|
|
<li><b>volumeStraight</b> – Returns current device absolute volume (0...64)</li>
|
|
</ul>
|
|
</ul>
|
|
<br>
|
|
</ul>
|
|
<a name="PHILIPS_AUDIOattr"></a>
|
|
<b>Attributes</b><br><br>
|
|
<ul>
|
|
<ul>
|
|
<li><b>autoGetFavorites</b> – Automatically read favorites from device if none available (default off)</li>
|
|
<li><b>autoGetPresets</b> – Automatically read presets from device if none available (default off)</li>
|
|
<li><b>do_not_notify</b></li>
|
|
<li><b>httpBufferTimeout</b> – Optional attribute defing the internal http buffer timeount (default 10)</li>
|
|
<li><b>maxListItems</b> – Defines max. number of player list items (default 100)</li>
|
|
<li><b>playerBrowsingTimeout</b> – Defines the inactivity timeout for browsing. After that timeout the player returns to the state 'home' in which the readings are updated automaically again. (default 180 seconds)</li>
|
|
<li><b>readingFnAttributes</b></li>
|
|
<li><b>requestTimeout</b> – Optional attribute defining the http response timeout (default 4 seconds)</li>
|
|
</ul>
|
|
</ul>
|
|
<br>
|
|
</ul>
|
|
|
|
=end html
|
|
|
|
=begin html_DE
|
|
|
|
<a name="PHILIPS_AUDIO"></a>
|
|
<h3>PHILIPS_AUDIO</h3>
|
|
<ul>
|
|
<a name="PHILIPS_AUDIOdefine"></a>
|
|
<b>Define</b><br><br>
|
|
<ul>
|
|
<code>
|
|
define <name> PHILIPS_AUDIO <device model> <ip-address> [<status_interval>]<br><br>
|
|
define <name> PHILIPS_AUDIO <device model> [<off_status_interval>] [<on_status_interval>]
|
|
</code>
|
|
<br><br>
|
|
Mit Hilfe dieses Moduls lassen sich Philips Audio Netzwerk Player wie z.B. MCi, Streamium oder Fidelio im lokalen Netzwerk steuern.<br>
|
|
Geräte, die über die myRemote App oder einen internen HTTP Server am Port 8889 sollten theoretisch ebenfalls bedient werden können.<br>
|
|
(http://[ip Nummer des Gerätes]:8889/index)<br>
|
|
<br>
|
|
(Getestet mit: AW9000, NP3500, NP3700 und NP3900)
|
|
<br><br>
|
|
Beispiel:<br>
|
|
<ul><br>
|
|
<code>
|
|
define PHAUDIO_player PHILIPS_AUDIO NP3900 192.168.0.15
|
|
<br>
|
|
<br>
|
|
# 60 Sekunden Intervall<br>
|
|
define PHAUDIO_player PHILIPS_AUDIO NP3900 192.168.0.15 <b>60</b>
|
|
<br>
|
|
<br>
|
|
# 60 Sekunden Intervall für "off" und 10 Sekunden für "on"<br>
|
|
define PHAUDIO_player PHILIPS_AUDIO NP3900 192.168.0.15 <b>60 10</b>
|
|
</code>
|
|
<br><br>
|
|
<i>Bemerkung: Aufgrund der relativ langsamen Verarbeitung von Befehlen durch den Player selbst wurde das minimale Intervall <b>auf 5 Sekunden limitiert</b>. Dadurch sollten potentielle Gerätefreezes reduziert werden.</i>
|
|
</ul>
|
|
</ul><br>
|
|
<a name="PHILIPS_AUDIOset"></a>
|
|
<b>Set</b>
|
|
<ul>
|
|
<code>
|
|
set <name> <command> [<parameter>]
|
|
</code>
|
|
<br><br>
|
|
<i>Bemerkung: Befehle und Parameter sind case-sensitive</i><br>
|
|
<ul><br>
|
|
<li><b>favoriteAdd</b> – Fügt den aktuellen Audiostream zu Favoriten hinzu</li>
|
|
<li><b>favoriteRemove</b> – Löscht den aktuellen Audiostream aus den Favoriten</li>
|
|
<li><b>getFavorites</b> – Liest aus die gespeicherten Favoriten aus dem Gerät (kann einige Zeit dauern...)</li>
|
|
<li><b>getMediaRendererDesc</b> – Liest aus Gerätspezifische Informationen aus (siehe auch deviceInfo reading)</li>
|
|
<li><b>getPresets</b> – Liest aus die gespeicherten Presets aus dem Gerät (kann einige Zeit dauern...)</li>
|
|
<li><b>input</b> – Schaltet auf den folgenden Eingang</li>
|
|
<ul>
|
|
<li><b>analogAux</b> – AUX input (nur AW9000)</li>
|
|
<li><b>digital1Coaxial</b> – digital coaxial input (nur AW9000)</li>
|
|
<li><b>digital2Optical</b> – digital optical input (nur AW9000)</li>
|
|
<li><b>internetRadio</b> – Internet Radio</li>
|
|
<li><b>mediaLibrary</b> – Media Library (UPnP/DLNA server) (nicht verfügbar beim AW9000)</li>
|
|
<li><b>mp3Link</b> – Analoger MP3 Link (nicht verfügbar beim AW9000)</li>
|
|
<li><b>onlineServices</b> – Online Services</li>
|
|
</ul>
|
|
<li><b>mute [ on | off ]</b> – Stummschaltung (an/aus)</li>
|
|
<li><b>player</b> – Player-Befehle</li>
|
|
<ul>
|
|
<li><b>next</b> – Nächstee Audiostream</li>
|
|
<li><b>prev</b> – Letzter Audiostream</li>
|
|
<li><b>play-pause</b> – Play/pause des aktuellen Audiostreams</li>
|
|
<li><b>stop</b> – Stoppt das Abspielen des aktuellen Audiostreams</li>
|
|
</ul>
|
|
<li><b>repeat [ single | all | off]</b> – Stellt den repeat mode ein</li>
|
|
<li><b>selectFavorite [ name ]</b> – Wählt einen Favoriten. Leer falls keine Favoriten vorhanden (s. getFavorites)</li>
|
|
<li><b>selectFavoriteByNumber [ number ]</b> – Wählt einen Favoriten anhand seiner Speichernummer. Leer falls keine Favoriten vorhanden (s. getFavorites)</li>
|
|
<li><b>selectPreset [ name ]</b> – Wählt einen Preset. Leer falls keine Presets vorhanden (s. getPresets)</li>
|
|
<li><b>selectPresetByNumber [ number ]</b> – Wählt einen Preset anhand seiner Speichernummer. Leer falls keine Presets vorhanden (see also getPresets)</li>
|
|
<li><b>selectStream [ name ]</b> – Context-sensitive. Wählt einen Audiostream. Hängt vom aktuellen Inhalt der Playerlist ab. Ein 'c'-Präfix repräsentiert einen 'Container' (Directory). ein 'i'-Präfix repräsentiert ein 'Item' (audio stream).</li>
|
|
<li><b>shuffle [ on | off ]</b> – Wählt den gewünschten Shuffle Modus</li>
|
|
<li><b>standbyButton</b> – Emuliert den standby-Knopf. Toggelt zwischen standby und power on</li>
|
|
<li><b>volume</b> – Setzt die relative Lautstärke 0...100%</li>
|
|
<li><b>volumeDown</b> – Setzt die Lautstärke um ein Dekrement herunter</li>
|
|
<li><b>volumeStraight</b> – Setzt die devicespezifische Lautstärke 0...64</li>
|
|
<li><b>volumeUp</b> – Setzt die Lautstärke um ein Inkrement herauf</li>
|
|
</ul>
|
|
</ul>
|
|
<br>
|
|
<a name="PHILIPS_AUDIOget"></a>
|
|
<b>Get</b>
|
|
<ul>
|
|
<code>
|
|
get <name> <reading> <reading name>
|
|
</code>
|
|
<ul>
|
|
<br>
|
|
<li><b>deviceInfo</b> – Liefert devicespezifische Information</li>
|
|
<li><b>reading</b></li>
|
|
<ul>
|
|
<li><b>input</b> – Liefert den aktuellen Eingang oder '-' falls kein Audiostream aktiv</li>
|
|
<li><b>listItem_xxx</b> – Liefert Einträge der Playerliste (limitiert auf 999 Einträge)</li>
|
|
<li><b>networkError</b> – Liefert einen potentiellen Netzwerkfehler</li>
|
|
<li><b>networkRequest</b> – Liefert die aktuelle Netzwerkaktivität (idle/busy)</li>
|
|
<li><b>power</b> – Liefert den Power-Status (on/off)</li>
|
|
<li><b>playerAlbum</b> – Liefert den Albumnamen des aktiven Audiostreams</li>
|
|
<li><b>playerAlbumArt</b> – Liefert die Albumart des aktiven Audiostreams</li>
|
|
<li><b>playerListStatus</b> – Liefert den aktuellen Zusatand der Playlist (busy/ready)</li>
|
|
<li><b>playerListTotalCount</b> – Liefert die Anzahl der Playlisteinträge</li>
|
|
<li><b>playerPlayTime</b> – Liefert die aktuell Audiostreamspieldauer</li>
|
|
<li><b>playerPlaying</b> – Zeigt an, ob Audiostream abgespielt wird (yes/no)</li>
|
|
<li><b>playerRadioStation</b> – Liefert den Stationsnamen des Radiosenders</li>
|
|
<li><b>playerRadioStationInfo</b> – Liefert zusätzliche Informationen des Radiosenders</li>
|
|
<li><b>playerRepeat</b> – Zeigt den Reapeat Mode an (off/single/all)</li>
|
|
<li><b>playerShuffle</b> – Zeigt den aktuellen Shuffle mode an (on/off)</li>
|
|
<li><b>playerState</b> – Zeigt den Playerzustand an (home/browsing/playing)</li>
|
|
<li><b>playerStreamFavorite</b> – Zeigt an, ob aktueller Audiostream ein Favorit ist (yes/no)</li>
|
|
<li><b>playerStreamRating</b> – Zeigt das rating des Audiostreams</li>
|
|
<li><b>playerTitle</b> – Zeigt den Titel des Audiostreams an</li>
|
|
<li><b>playerTotalTime</b> – Zeigt die Audiostreamdauer an</li>
|
|
<li><b>presence</b> – Liefert den presence status (present/absent)</li>
|
|
<li><b>state</b> – Lifert den aktuellen Gerätestatus (on/off)</li>
|
|
<li><b>totalFavorites</b> – Liefert die Anzahl gepseicherter Favoriten (s. getFavorites)</li>
|
|
<li><b>totalPresets</b> – Liefert die Anzahl gepseicherter Presets (see getPresets)</li>
|
|
<li><b>volume</b> – Liefert die relative Lutstärke (0...100%)</li>
|
|
<li><b>volumeStraight</b> – Liefert die devicespezifische Lautstärke (0...64)</li>
|
|
</ul>
|
|
</ul>
|
|
<br>
|
|
</ul>
|
|
<a name="PHILIPS_AUDIOattr"></a>
|
|
<b>Attribute</b><br><br>
|
|
<ul>
|
|
<ul>
|
|
<li><b>autoGetFavorites</b> – Automatisches Auslesen der Favoriten beim Modulstart falls keine vorhanden (default off)</li>
|
|
<li><b>autoGetPresets</b> – Automatisches Auslesen der Presets beim Modulstart falls keine vorhanden (default off)</li>
|
|
<li><b>do_not_notify</b></li>
|
|
<li><b>httpBufferTimeout</b> – Optionalles Attribut für den internen http buffer timeount (default 10 Sekunden)</li>
|
|
<li><b>maxListItems</b> – Definiert die max. Anzahl der anzuzeigenden Playerlisteinträge (default 100)</li>
|
|
<li><b>playerBrowsingTimeout</b> – Definiert den Inaktivitäts-Timeout beim Browsen der Playerlist. Nach diesem Timeout fällt das Modul in den "home"-State zurück. Die Playerreadings werden wieder aktualisiert (default 180 Sekunden)</li>
|
|
<li><b>readingFnAttributes</b></li>
|
|
<li><b>requestTimeout</b> – Optionalles Attribut für http responses (default 4 Sekunden)</li>
|
|
</ul>
|
|
</ul>
|
|
</ul>
|
|
|
|
=end html_DE
|
|
|
|
=cut
|