# $Id: 71_PHILIPS_AUDIO.pm 18728 2019-02-25 08:25:16Z betateilchen $
##############################################################################
#
# 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;amp;quot;|&amp;quot;|&quot;|\\")/\"/g;
  $string =~ s/(&amp;amp;|&amp;)/&/g;  
  $string =~ s/&nbsp;/ /g;
  $string =~ s/(&apos;|\\'|\')/'/g;
  $string =~ s/(\xe4|&auml;)/ä/g;
  $string =~ s/(\xc4|&Auml;)/Ä/g;
  $string =~ s/(\xf6|&ouml;)/ö/g;
  $string =~ s/(\xd6|&Ouml;)/Ö/g;
  $string =~ s/(\xfc|&uuml;)/ü/g;
  $string =~ s/(\xdc|&Uuml;)/Ü/g;
  $string =~ s/(\xdf|&szlig;)/ß/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 &lt;name&gt; PHILIPS_AUDIO &lt;device model&gt; &lt;ip-address&gt; [&lt;status_interval&gt;]<br><br>
      define &lt;name&gt; PHILIPS_AUDIO &lt;device model&gt; [&lt;off_status_interval&gt;] [&lt;on_status_interval&gt;]
    </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 &lt;name&gt; &lt;command&gt; [&lt;parameter&gt;]
    </code><br><br>
    <i>Note: Commands and parameters are case sensitive.</i><br>
      <ul><br>
      <li><b>favoriteAdd</b> &ndash; Adds currently played Internet Radio stream to favorites</li>
      <li><b>favoriteRemove</b> &ndash; Removes currently played Internet Radio stream from favorites</li>
      <li><b>getFavorites</b> &ndash; Reads stored favorites from the device (may take some time...)</li>
      <li><b>getMediaRendererDesc</b> &ndash; Reads device specific information (stored in the deviceInfo reading)</li>
      <li><b>getPresets</b> &ndash; Reads stored presets from the device (may take some time...)</li>
      <li><b>input</b> &ndash; Selects the following input</li>
      <ul>
      <li><b>analogAux</b> &ndash; Selects the analog AUX input (AW9000 only)</li>
      <li><b>digital1Coaxial</b> &ndash; Selects the digital coaxial input (AW9000 only)</li>
      <li><b>digital2Optical</b> &ndash; Selects the digital optical input (AW9000 only)</li>      
      <li><b>internetRadio</b> &ndash; Selects the Internet Radio input</li>
      <li><b>mediaLibrary</b> &ndash; Selects the Media Library input (UPnP/DLNA server) (not available on AW9000)</li>
      <li><b>mp3Link</b> &ndash; Selects the analog MP3 Link input (not available on AW9000)</li>
      <li><b>onlineServices</b> &ndash; Selects the Online Services input</li>
      </ul>
      <li><b>mute [ on | off ]</b> &ndash; Mutes/unmutes the device</li>
      <li><b>player</b> &ndash; Player related commands</li>
      <ul>
      <li><b>next</b> &ndash; Selects next audio stream</li>
      <li><b>prev</b> &ndash; Selects previous audio stream</li>
      <li><b>play-pause</b> &ndash; Plays/pauses the current audio stream</li>
      <li><b>stop</b> &ndash; Stops the current audio stream</li>
      </ul>
      <li><b>repeat [ single | all | off]</b> &ndash; Selects the repeate mode</li>
      <li><b>selectFavorite [ name ]</b> &ndash; Selects a favorite. Empty if no favorites found. (see also getFavorites)</li>
      <li><b>selectFavoriteByNumber [ number ]</b> &ndash; Selects a favorite by its number. Empty if no favorites found. (see also getFavorites)</li>
      <li><b>selectPreset [ name ]</b> &ndash; Selects a preset. Empty if no presets found. (see also getPresets)</li>
      <li><b>selectPresetByNumber [ number ]</b> &ndash; Selects a preset by its number. Empty if no presets found. (see also getPresets)</li>
      <li><b>selectStream [ name ]</b> &ndash; 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> &ndash; Sets the shuffle mode</li>
      <li><b>standbyButton</b> &ndash; Emulates the standby button. Toggles between standby and power on</li>
      <li><b>volume</b> &ndash; Sets the relative volume 0...100%</li>
      <li><b>volumeDown</b> &ndash; Sets the device specific volume by one decrement</li>
      <li><b>volumeStraight</b> &ndash; Sets the device specific absolute volume 0...64</li>
      <li><b>volumeUp</b> &ndash; Sets the device specific volume by one increment</li>      
    </ul>
  </ul>
  <br>
  <a name="PHILIPS_AUDIOget"></a>
  <b>Get</b>
  <ul>
    <code>
      get &lt;name&gt; &lt;reading&gt &lt;reading name&gt;
    </code>
    <ul>
      <br>
      <li><b>deviceInfo</b> &ndash; Returns device specific information</li>
      <li><b>reading</b></li>
      <ul>
        <li><b>input</b> &ndash; Returns current input or '-' if not playing</li>
        <li><b>listItem_xxx</b> &ndash; Returns player list item (limited to 999 entries)</li>
        <li><b>networkError</b> &ndash; Shows an occured current network error</li>
        <li><b>networkRequest</b> &ndash; Shows current network activity (idle/busy)</li>
        <li><b>power</b> &ndash; Returns power status (on/off)</li>
        <li><b>playerAlbum</b> &ndash; Returns the album name of played stream</li>
        <li><b>playerAlbumArt</b> &ndash; Returns the album art of played audio stream</li>
        <li><b>playerListStatus</b> &ndash; Returns current player list status (busy/ready)</li>
        <li><b>playerListTotalCount</b> &ndash; Returns number of player list entries</li>
        <li><b>playerPlayTime</b> &ndash; Returns audio stream duration</li>
        <li><b>playerPlaying</b> &ndash; Returns current player playing state (yes/no)</li>
        <li><b>playerRadioStation</b> &ndash; Returns the name of played radio station</li>
        <li><b>playerRadioStationInfo</b> &ndash; Returns additional info of the played radio station</li>
        <li><b>playerRepeat</b> &ndash; Returns current repeat mode (off/single/all)</li>
        <li><b>playerShuffle</b> &ndash; Returns current shuffle mode (on/off)</li>
        <li><b>playerState</b> &ndash; Returns current player state (home/browsing/playing)</li>
        <li><b>playerStreamFavorite</b> &ndash; Shows if audio stream is a favorite (yes/no)</li>
        <li><b>playerStreamRating</b> &ndash; Shows rating of the audio stream</li>
        <li><b>playerTitle</b> &ndash; Returns audio stream's title</li>
        <li><b>playerTotalTime</b> &ndash; Shows audio stream's total time</li>
        <li><b>presence</b> &ndash; Returns peresence status (present/absent)</li>
        <li><b>state</b> &ndash; Returns current state (on/off)</li>
        <li><b>totalFavorites</b> &ndash; Returns total number of stored favorites (see getFavorites)</li>
        <li><b>totalPresets</b> &ndash; Returns total number of stored presets (see getPresets)</li>
        <li><b>volume</b> &ndash; Returns current relative volume (0...100%)</li>
        <li><b>volumeStraight</b> &ndash; 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> &ndash; Automatically read favorites from device if none available (default off)</li>
      <li><b>autoGetPresets</b> &ndash; Automatically read presets from device if none available (default off)</li>
      <li><b>do_not_notify</b></li>
      <li><b>httpBufferTimeout</b> &ndash; Optional attribute defing the internal http buffer timeount (default 10)</li>
      <li><b>maxListItems</b> &ndash; Defines max. number of player list items (default 100)</li>
      <li><b>playerBrowsingTimeout</b> &ndash; 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> &ndash; 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 &lt;name&gt; PHILIPS_AUDIO &lt;device model&gt; &lt;ip-address&gt; [&lt;status_interval&gt;]<br><br>
      define &lt;name&gt; PHILIPS_AUDIO &lt;device model&gt; [&lt;off_status_interval&gt;] [&lt;on_status_interval&gt;]
    </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&auml;te, die &uuml;ber die myRemote App oder einen internen HTTP Server am Port 8889 sollten theoretisch ebenfalls bedient werden k&ouml;nnen.<br>
    (http://[ip Nummer des Ger&auml;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&uuml;r "off" und 10 Sekunden f&uuml;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&auml;tefreezes reduziert werden.</i>
    </ul>   
  </ul><br>  
  <a name="PHILIPS_AUDIOset"></a>
  <b>Set</b>
  <ul>
    <code>
      set &lt;name&gt; &lt;command&gt; [&lt;parameter&gt;]
    </code>
    <br><br>
    <i>Bemerkung: Befehle und Parameter sind case-sensitive</i><br>
    <ul><br>
      <li><b>favoriteAdd</b> &ndash; F&uuml;gt den aktuellen Audiostream zu Favoriten hinzu</li>
      <li><b>favoriteRemove</b> &ndash; L&ouml;scht den aktuellen Audiostream aus den Favoriten</li>
      <li><b>getFavorites</b> &ndash; Liest aus die gespeicherten Favoriten aus dem Ger&auml;t (kann einige Zeit dauern...)</li>
      <li><b>getMediaRendererDesc</b> &ndash; Liest aus Ger&auml;tspezifische Informationen aus (siehe auch deviceInfo reading)</li>
      <li><b>getPresets</b> &ndash; Liest aus die gespeicherten Presets aus dem Ger&auml;t (kann einige Zeit dauern...)</li>
      <li><b>input</b> &ndash; Schaltet auf den folgenden Eingang</li>
      <ul>
      <li><b>analogAux</b> &ndash; AUX input (nur AW9000)</li>
      <li><b>digital1Coaxial</b> &ndash; digital coaxial input (nur AW9000)</li>
      <li><b>digital2Optical</b> &ndash; digital optical input (nur AW9000)</li>      
      <li><b>internetRadio</b> &ndash; Internet Radio</li>
      <li><b>mediaLibrary</b> &ndash; Media Library (UPnP/DLNA server) (nicht verf&uuml;gbar beim AW9000)</li>
      <li><b>mp3Link</b> &ndash; Analoger MP3 Link (nicht verf&uuml;gbar beim AW9000)</li>
      <li><b>onlineServices</b> &ndash; Online Services</li>
      </ul>
      <li><b>mute [ on | off ]</b> &ndash; Stummschaltung (an/aus)</li>
      <li><b>player</b> &ndash; Player-Befehle</li>
      <ul>
      <li><b>next</b> &ndash; N&auml;chstee Audiostream</li>
      <li><b>prev</b> &ndash; Letzter Audiostream</li>
      <li><b>play-pause</b> &ndash; Play/pause des aktuellen Audiostreams</li>
      <li><b>stop</b> &ndash; Stoppt das Abspielen des aktuellen Audiostreams</li>
      </ul>
      <li><b>repeat [ single | all | off]</b> &ndash; Stellt den repeat mode ein</li>
      <li><b>selectFavorite [ name ]</b> &ndash; W&auml;hlt einen Favoriten. Leer falls keine Favoriten vorhanden (s. getFavorites)</li>
      <li><b>selectFavoriteByNumber [ number ]</b> &ndash; W&auml;hlt einen Favoriten anhand seiner Speichernummer. Leer falls keine Favoriten vorhanden (s. getFavorites)</li>
      <li><b>selectPreset [ name ]</b> &ndash; W&auml;hlt einen Preset. Leer falls keine Presets vorhanden (s. getPresets)</li>
      <li><b>selectPresetByNumber [ number ]</b> &ndash; W&auml;hlt einen Preset anhand seiner Speichernummer. Leer falls keine Presets vorhanden (see also getPresets)</li>
      <li><b>selectStream [ name ]</b> &ndash; Context-sensitive. W&auml;hlt einen Audiostream. H&auml;ngt vom aktuellen Inhalt der Playerlist ab. Ein 'c'-Pr&auml;fix repr&auml;sentiert einen 'Container' (Directory). ein 'i'-Pr&auml;fix repr&auml;sentiert ein 'Item' (audio stream).</li>
      <li><b>shuffle [ on | off ]</b> &ndash; W&auml;hlt den gew&uuml;nschten Shuffle Modus</li>
      <li><b>standbyButton</b> &ndash; Emuliert den standby-Knopf. Toggelt zwischen standby und power on</li>
      <li><b>volume</b> &ndash; Setzt die relative Lautst&auml;rke 0...100%</li>
      <li><b>volumeDown</b> &ndash; Setzt die Lautst&auml;rke um ein Dekrement herunter</li>
      <li><b>volumeStraight</b> &ndash; Setzt die devicespezifische Lautst&auml;rke 0...64</li>
      <li><b>volumeUp</b> &ndash; Setzt die Lautst&auml;rke um ein Inkrement herauf</li>      
    </ul>
  </ul>
  <br>
  <a name="PHILIPS_AUDIOget"></a>
  <b>Get</b>        
  <ul>
    <code>
      get &lt;name&gt; &lt;reading&gt &lt;reading name&gt;
    </code>
    <ul>
      <br>
      <li><b>deviceInfo</b> &ndash; Liefert devicespezifische Information</li>
      <li><b>reading</b></li>
      <ul>
        <li><b>input</b> &ndash; Liefert den aktuellen Eingang oder '-' falls kein Audiostream aktiv</li>
        <li><b>listItem_xxx</b> &ndash; Liefert Eintr&auml;ge der Playerliste (limitiert auf 999 Eintr&auml;ge)</li>
        <li><b>networkError</b> &ndash; Liefert einen potentiellen Netzwerkfehler</li>
        <li><b>networkRequest</b> &ndash; Liefert die aktuelle Netzwerkaktivit&auml;t (idle/busy)</li>
        <li><b>power</b> &ndash; Liefert den Power-Status (on/off)</li>
        <li><b>playerAlbum</b> &ndash; Liefert den Albumnamen des aktiven Audiostreams</li>
        <li><b>playerAlbumArt</b> &ndash; Liefert die Albumart des aktiven Audiostreams</li>
        <li><b>playerListStatus</b> &ndash; Liefert den aktuellen Zusatand der Playlist (busy/ready)</li>
        <li><b>playerListTotalCount</b> &ndash; Liefert die Anzahl der Playlisteintr&auml;ge</li>
        <li><b>playerPlayTime</b> &ndash; Liefert die aktuell Audiostreamspieldauer</li>
        <li><b>playerPlaying</b> &ndash; Zeigt an, ob Audiostream abgespielt wird (yes/no)</li>
        <li><b>playerRadioStation</b> &ndash; Liefert den Stationsnamen des Radiosenders</li>
        <li><b>playerRadioStationInfo</b> &ndash; Liefert zus&auml;tzliche Informationen des Radiosenders</li>
        <li><b>playerRepeat</b> &ndash; Zeigt den Reapeat Mode an (off/single/all)</li>
        <li><b>playerShuffle</b> &ndash; Zeigt den aktuellen Shuffle mode an (on/off)</li>
        <li><b>playerState</b> &ndash; Zeigt den Playerzustand an (home/browsing/playing)</li>
        <li><b>playerStreamFavorite</b> &ndash; Zeigt an, ob aktueller Audiostream ein Favorit ist (yes/no)</li>
        <li><b>playerStreamRating</b> &ndash; Zeigt das rating des Audiostreams</li>
        <li><b>playerTitle</b> &ndash; Zeigt den Titel des Audiostreams an</li>
        <li><b>playerTotalTime</b> &ndash; Zeigt die Audiostreamdauer an</li>
        <li><b>presence</b> &ndash; Liefert den presence status (present/absent)</li>
        <li><b>state</b> &ndash; Lifert den aktuellen Ger&auml;testatus (on/off)</li>
        <li><b>totalFavorites</b> &ndash; Liefert die Anzahl gepseicherter Favoriten (s. getFavorites)</li>
        <li><b>totalPresets</b> &ndash; Liefert die Anzahl gepseicherter Presets (see getPresets)</li>
        <li><b>volume</b> &ndash; Liefert die relative Lutst&auml;rke (0...100%)</li>
        <li><b>volumeStraight</b> &ndash; Liefert die devicespezifische Lautst&auml;rke (0...64)</li>    
      </ul>
    </ul>
    <br>
  </ul>
  <a name="PHILIPS_AUDIOattr"></a>
    <b>Attribute</b><br><br>
    <ul>
      <ul>  
      <li><b>autoGetFavorites</b> &ndash; Automatisches Auslesen der Favoriten beim Modulstart falls keine vorhanden (default off)</li>
      <li><b>autoGetPresets</b> &ndash; Automatisches Auslesen der Presets beim Modulstart falls keine vorhanden (default off)</li>
      <li><b>do_not_notify</b></li>
      <li><b>httpBufferTimeout</b> &ndash; Optionalles Attribut f&uuml;r den internen http buffer timeount (default 10 Sekunden)</li>
      <li><b>maxListItems</b> &ndash; Definiert die max. Anzahl der anzuzeigenden Playerlisteintr&auml;ge (default 100)</li>
      <li><b>playerBrowsingTimeout</b> &ndash; Definiert den Inaktivit&auml;ts-Timeout beim Browsen der Playerlist. Nach diesem Timeout f&auml;llt das Modul in den "home"-State zur&uuml;ck. Die Playerreadings werden wieder aktualisiert (default 180 Sekunden)</li>
      <li><b>readingFnAttributes</b></li>
      <li><b>requestTimeout</b> &ndash; Optionalles Attribut f&uuml;r http responses (default 4 Sekunden)</li>      
      </ul>
    </ul>    
</ul>

=end html_DE

=cut
