##############################################################################
#
#     $Id: 71_PHILIPS_AUDIO.pm 11329 2016-04-27 18:00:43Z ra666ack $
#
#     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 Radoslaw Watroba
#     (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 model max-device-presets max-device-favorites request-timeout:1,2,3,4,5 ".$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};
  
  
  # Check for Presets availability
  if(not defined($hash->{READINGS}{"totalInetRadioPresets"}))
  {
	 # Hierarchichal navigation through the contents mandatory
    $hash->{helper}{cmdStep} = 1;
    PHILIPS_AUDIO_SendCommand($hash, "/index", "", "getInetRadioPresets", "noArg");
    # Wait 10 seconds for next http request due to slow command processing of the streamium devices.
    PHILIPS_AUDIO_ResetTimer($hash, 10);
    return;
  }

  # Check for Favorites availability
  if(not defined($hash->{READINGS}{"totalInetRadioFavorites"}))
  {
   # Hierarchichal navigation through the contents mandatory
    $hash->{helper}{cmdStep} = 1;
    PHILIPS_AUDIO_SendCommand($hash, "/index", "", "getInetRadioFavorites", "noArg");
    # Wait 10 seconds for next http request due to slow command processing of the streamium devices.
    PHILIPS_AUDIO_ResetTimer($hash, 10);
    return;
  }

  PHILIPS_AUDIO_SendCommand($hash, "/nowplay", "","nowplay", "noArg");
  PHILIPS_AUDIO_ResetTimer($hash) unless($local == 1);
  
  return;
}

###################################
sub PHILIPS_AUDIO_Get
{
  my ($hash, @a) = @_;
  my $what;
  my $return;
  
  my $name = $hash->{NAME};
  
  my $address = $hash->{IP_ADDRESS};
  $hash->{IP_ADDRESS} = $address;
  
  return "argument is missing" if(int(@a) != 2);
  
  if(not defined($hash->{MODEL}))
  {
    return "Please provide the model information as argument.";    
  }  

  $what = $a[1];
  
  if(exists($hash->{READINGS}{$what}))
  {
    if(defined($hash->{READINGS}{$what}))
    {
      return $hash->{READINGS}{$what}{VAL};
    }
    else
    {
      return "no such reading: $what";
    }
  }
  else
  {
    $return = "unknown argument $what, choose one of";
    
    foreach my $reading (keys %{$hash->{READINGS}})
    {
      $return .= " $reading:noArg";
    }
  
    return $return;
  }
}

###################################
sub PHILIPS_AUDIO_Set
{
  my ($hash, @a) = @_;
  my $name = $hash->{NAME};
  my $port = $hash->{PORT};
  
  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 $index;
  
  # Provide number of selectable presets according to the given attribute
  my $inetRadioPreset = "inetRadioPreset:";
  for ($index = 1; $index < AttrVal($name, "max-device-presets", "24"); $index++)
  {
	 $inetRadioPreset .= $index . ",";
  }
  $inetRadioPreset .= $index . " ";
  
  # Provide number of selectable favorites according to the given attribute
  my $inetRadioFavorite = "inetRadioFavorite:";
  for ($index = 1; $index < AttrVal($name, "max-device-favorites", "24"); $index++)
  {
	 $inetRadioFavorite .= $index . ",";
  }
  $inetRadioFavorite .= $index . " ";

  my $usage;
  
  my $model = $hash->{MODEL};

  $usage = "Unknown argument $what, choose one of ".
           "volumeStraight:slider,0,1,64 ".
           "volume:slider,0,1,100 ".
           #"volumeUp:noArg ".
           #"volumeDown:noArg ".
           "standbyButton:noArg ".
           "unmute:noArg ".
           "next:noArg ".
           "previous:noArg ".
           "play_pause:noArg ".
           "stop:noArg ".
           "shuffle:on,off ".
           "aux:noArg ".
           ((uc($model) eq "AW9000") ? "mp3link:noArg " : ""). # Input implemented in AW9000 only
           ((uc($model) eq "AW9000") ? "coaxial:noArg " : ""). # Input implemented in AW9000 only
           ((uc($model) eq "AW9000") ? "optical:noArg " : ""). # Input implemented in AW9000 only
           #"input:aux,internetRadio,mediaLibrary,onlineServices ".
           $inetRadioPreset.
           $inetRadioFavorite.
           "statusRequest:noArg ".
           "getInetRadioPresets:noArg ".
           "getInetRadioFavorites:noArg ".
           ((exists($hash->{helper}{PRESETS}))? "inetRadioPresetByName:".($hash->{helper}{PRESETS})." ":"").
           ((exists($hash->{helper}{FAVORITES}))? "inetRadioFavoriteByName:".($hash->{helper}{FAVORITES})." ":"").
           #"addToFavourites:noArg ".
           #"removeFromFavourites:noArg ".
           "repeat:single,all,off ".
           #"home:noArg ".
           "mute:noArg ";         
    
  Log3 $name, 5, "PHILIPS_AUDIO ($name) - set ".join(" ", @a);
    
  if($what eq "standbyButton")
  {
    PHILIPS_AUDIO_SendCommand($hash, "/CTRL\$STANDBY", "",$what, "noArg");
  }
  elsif($what eq "aux")
  {
    PHILIPS_AUDIO_SendCommand($hash, "/aux", "",$what, $a[2]);
  }
  elsif($what eq "mp3link")
  {
    PHILIPS_AUDIO_SendCommand($hash, "/mp3link", "",$what, $a[2]);
  }
  elsif($what eq "coaxial")
  {
    PHILIPS_AUDIO_SendCommand($hash, "/digin_coaxial", "",$what, $a[2]);
  }
  elsif($what eq "optical")
  {
    PHILIPS_AUDIO_SendCommand($hash, "/digin_optical", "",$what, $a[2]);
  }
  elsif($what eq "home")
  {
    PHILIPS_AUDIO_SendCommand($hash, "/index", "",$what, $a[2]);
  }
  elsif($what  eq "mediaLibrary")
  {
    PHILIPS_AUDIO_SendCommand($hash, "/nav\$02\$01\$001\$0", "",$what, $a[2]);
  }
  elsif($what  eq "internetRadio")
  {
    PHILIPS_AUDIO_SendCommand($hash, "/nav\$03\$01\$001\$0", "",$what, $a[2]);
  }
  elsif($what  eq "onlineServices")
  {
    PHILIPS_AUDIO_SendCommand($hash, "/nav\$09\$01\$001\$0", "",$what, $a[2]);
  }  
  elsif($what eq "shuffle")
  {
    if($a[2]  eq "on")
    {
      PHILIPS_AUDIO_SendCommand($hash, "/MODE\$SHUFFLE_ON", "",$what, $a[2]);
    }
    elsif($a[2]  eq "off")
    {
      PHILIPS_AUDIO_SendCommand($hash, "/MODE\$SHUFFLE_OFF", "",$what, $a[2]);
    }
    else
    {
      return $usage;
    }
  }
  elsif($what eq "repeat")
  {
    if($a[2]  eq "single")
    {
      PHILIPS_AUDIO_SendCommand($hash, "/MODE\$REPEAT_SINGLE", "",$what, $a[2]);
    }
    elsif($a[2]  eq "all")
    {
      PHILIPS_AUDIO_SendCommand($hash, "/MODE\$REPEAT_ALL", "",$what, $a[2]);
    }
    elsif($a[2]  eq "off")
    {
      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 "addToFavourites")
  {
    PHILIPS_AUDIO_SendCommand($hash, "/CTRL\$ADD2FAV", "",$what, "noArg");
  }
  elsif($what eq "removeFromFavourites")
  {
    PHILIPS_AUDIO_SendCommand($hash, "/CTRL\$REMFAV", "",$what, "noArg");
  }
  elsif($what eq "mute")
  {
    PHILIPS_AUDIO_SendCommand($hash, "/VOLUME\$MUTE", "",$what, "noArg");
  }
  elsif($what eq "unmute")
  {
    PHILIPS_AUDIO_SendCommand($hash, "/VOLUME\$UNMUTE", "",$what, "noArg");
  }
  elsif($what eq "next")
  {
    PHILIPS_AUDIO_SendCommand($hash, "/CTRL\$NEXT", "",$what, "noArg");
  }
  elsif($what eq "previous")
  {
    PHILIPS_AUDIO_SendCommand($hash, "/CTRL\$PREV", "",$what, "noArg");
  }
  elsif($what eq "play_pause")
  {
    PHILIPS_AUDIO_SendCommand($hash, "/CTRL\$PLAY_PAUSE", "",$what, "noArg");
  }
  elsif($what eq "stop")
  {
    PHILIPS_AUDIO_SendCommand($hash, "/CTRL\$STOP", "",$what, "noArg");
  }
  elsif($what eq "inetRadioPreset")
  {
    # Hierarchichal navigation through the contents mandatory
    $hash->{helper}{cmdStep} = 1;
    $hash->{helper}{inetRadioPreset} = $a[2];
    PHILIPS_AUDIO_SendCommand($hash, "/index", "", $what, $a[2]);    
  }
  elsif($what eq "inetRadioPresetByName")
  {
    # Hierarchichal navigation through the contents mandatory
    $hash->{helper}{cmdStep} = 1;
    my ($presetNumber, $presetName) = split(/:/, $a[2], 2);
    $hash->{helper}{inetRadioPresetNumber} = $presetNumber;
    PHILIPS_AUDIO_SendCommand($hash, "/index", "", $what, $a[2]);            
  }  
  elsif($what eq "inetRadioFavorite")
  {
    # Hierarchichal navigation through the contents mandatory
    $hash->{helper}{cmdStep} = 1;
    $hash->{helper}{inetRadioFavorite} = $a[2];
    PHILIPS_AUDIO_SendCommand($hash, "/index", "", $what, $a[2]);
  }
  elsif($what eq "inetRadioFavoriteByName")
  {
    # Hierarchichal navigation through the contents mandatory
    $hash->{helper}{cmdStep} = 1;
    my ($favoriteNumber, $favoriteName) = split(/:/, $a[2], 2);
    $hash->{helper}{inetRadioFavoriteNumber} = $favoriteNumber;
    PHILIPS_AUDIO_SendCommand($hash, "/index", "", $what, $a[2]);
  }
  elsif($what eq "volumeStraight")
  {
    if($a[2] >= 0 and $a[2] <= 64)
    {
      $hash->{helper}{targetVolume} = $a[2];
      PHILIPS_AUDIO_SendCommand($hash, "/VOLUME\$VAL\$".$a[2], "",$what, $a[2]);
    }
    else
    {
      return "volumeStraight must be in the range 0...64.";
    }
  }
  elsif($what eq "volume")
  {
    if($a[2] >= 0 and $a[2] <= 100)
    {
      $hash->{helper}{targetVolume} = PHILIPS_AUDIO_volume_rel2abs($hash, $a[2]);
      PHILIPS_AUDIO_SendCommand($hash, "/VOLUME\$VAL\$".$a[2], "",$what, $a[2]);
    }
    else
    {
      return "volumeStraight must be in the range 0...100.";
    }
  }
  elsif($what eq "nowplay")
  {
    PHILIPS_AUDIO_SendCommand($hash, "/nowplay", "",$what, "noArg");
  }
  elsif($what eq "getInetRadioPresets")
  {
    
    delete($hash->{helper}{PRESETS});

    # Hierarchichal navigation through the contents mandatory
    $hash->{helper}{cmdStep} = 1;
    PHILIPS_AUDIO_SendCommand($hash, "/index", "", $what, "noArg");    
  }
  elsif($what eq "getInetRadioFavorites")
  {
    # Hierarchichal navigation through the contents mandatory
    $hash->{helper}{cmdStep} = 1;
    PHILIPS_AUDIO_SendCommand($hash, "/index", "", $what, "noArg");
  }
  else  
  {
    return $usage;
  }

  PHILIPS_AUDIO_ResetTimer($hash);  
  
  return;
}

#############################
sub PHILIPS_AUDIO_Define
{
    my ($hash, $def) = @_;
    my @a = split("[ \t][ \t]*", $def);
    my $name = $hash->{NAME};
    
    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 was given which is greater than zero, use it.
    if(defined($a[4]) and $a[4] > 0)
    {
      $hash->{helper}{OFF_INTERVAL} = $a[4];
      # Minimum interval 3 sec
      if($hash->{helper}{OFF_INTERVAL} < 3)
      {
        $hash->{helper}{OFF_INTERVAL} = 3;
      }
    }
    else
    {
      $hash->{helper}{OFF_INTERVAL} = 30;
    }
      
    if(defined($a[5]) and $a[5] > 0)
    {
      $hash->{helper}{ON_INTERVAL} = $a[5];
      # Minimum interval 3 sec
      if($hash->{helper}{ON_INTERVAL} < 3)
      {
        $hash->{helper}{ON_INTERVAL} = 3;
      }
    }
    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 @a = @_;
  my $hash = $defs{$a[1]};

  if($a[0] eq "set" && $a[2] eq "disable")
  {
    if($a[3] eq "0")
    {
      $hash->{helper}{DISABLED} = 0;
      PHILIPS_AUDIO_GetStatus($hash, 1);
    }
    elsif($a[3] eq "1")
    {
      $hash->{helper}{DISABLED} = 1;
    }
  }
  elsif($a[0] eq "del" && $a[2] eq "disable")
  {
    $hash->{helper}{DISABLED} = 0;
    PHILIPS_AUDIO_GetStatus($hash, 1);
  }

  # 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);
  return;
}

############################################################################################################
#
#   Begin of helper functions
#
############################################################################################################

#############################
# sends a command to the receiver via HTTP
sub PHILIPS_AUDIO_SendCommand
{
  my ($hash,$url,$data,$cmd,$arg) = @_;
  my $name    = $hash->{NAME};
  my $address = $hash->{IP_ADDRESS};
  my $port    = $hash->{PORT};

  Log3 $name, 5, "PHILIPS_AUDIO ($name) - execute nonblocking \"$cmd".(defined($arg) ? " ".(split("\\|", $arg))[0] : "")."\" on $name: $data";
    
  HttpUtils_NonblockingGet
  ({
    url        => "http://".$address.":".$port."".$url,
    timeout    => AttrVal($name, "request-timeout", 30),
    noshutdown => 1,
    data       => $data,
    loglevel   => ($hash->{helper}{AVAILABLE} ? undef : 5),
    hash       => $hash,
    cmd        => $cmd,
    arg        => $arg,
    callback   => \&PHILIPS_AUDIO_ParseResponse                        
  });  
  return;  
}

#############################
# parses the receiver response
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] : "")."\"";
    }
    
     #Log3 $name, 5, "Error = $err";
     #Log3 $name, 5, "Data = $data";
    
    if($err ne "")
    {
      Log3 $name, 5, "PHILIPS_AUDIO ($name) - could not execute command \"$cmd".(defined($arg) ? " ".(split("\\|", $arg))[0] : "")."\": $err";

      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 command on device $name. Please turn on your device in case of deactivated network standby or check for correct hostaddress.";
        readingsSingleUpdate($hash, "presence", "absent", 1);
        readingsSingleUpdate($hash, "state", "absent", 1);
        $hash->{STATE} = "absent";
      }  

      $hash->{helper}{AVAILABLE} = 0;
    }
    elsif($data ne "")
    {
      Log3 $name, 5, "PHILIPS_AUDIO ($name) - got response for \"$cmd".(defined($arg) ? " ".(split("\\|", $arg))[0] : "")."\": $data";

      if (defined($hash->{helper}{AVAILABLE}) and $hash->{helper}{AVAILABLE} eq 0)
      {
        Log3 $name, 3, "PHILIPS_AUDIO ($name) - device $name reappeared";
        readingsSingleUpdate($hash, "presence", "present", 1);        
      }     
      
      $hash->{helper}{AVAILABLE} = 1;
           
      readingsBeginUpdate($hash);
      
      readingsBulkUpdate($hash, "power", "on");
      readingsBulkUpdate($hash, "state","on");
      $hash->{STATE} = "on";
      
      if($cmd eq "standbyButton")
      {
        if($data =~ /SUCCESS/)
        {
          #readingsBulkUpdate($hash, "power", "on");
          #readingsBulkUpdate($hash, "state","on");          
        }        
      }      
      elsif($cmd eq "mute")
      {
        if($data =~ /SUCCESS/)
        {
          readingsBulkUpdate($hash, "mute", "on");
        }
      }
      elsif($cmd eq "unmute")
      {
        if($data =~ /SUCCESS/)
        {
          readingsBulkUpdate($hash, "mute", "off");
        }
      }
      elsif($cmd eq "removeFromFavourites")
      {
        # evtl. for future use
      }
      elsif($cmd eq "addToFavourites")
      {
        # evtl. for future use
      }
      elsif($cmd eq "inetRadioPreset")
      {        
        # 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", "", "inetRadioPreset", $hash->{helper}{inetRadioPreset});
        }
        elsif($hash->{helper}{cmdStep} == 2)
        {
          $hash->{helper}{cmdStep} = 3;
          # Presets
          PHILIPS_AUDIO_SendCommand($hash, "/nav\$03\$02\$001\$0", "","inetRadioPreset", $hash->{helper}{inetRadioPreset});
        }
        elsif($hash->{helper}{cmdStep} == 3)
        {
          $hash->{helper}{cmdStep} = 4;
          # Preset select
          PHILIPS_AUDIO_SendCommand($hash, "/nav\$03\$03\$".sprintf("%03d", $hash->{helper}{inetRadioPreset})."\$1", "","inetRadioPreset", $hash->{helper}{inetRadioPreset});
        }               
      }
      elsif($cmd eq "inetRadioPresetByName")
      {        
        # 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", "", "inetRadioPresetByName", $hash->{helper}{inetRadioPresetNumber});
        }
        elsif($hash->{helper}{cmdStep} == 2)
        {
          $hash->{helper}{cmdStep} = 3;
          # Presets
          PHILIPS_AUDIO_SendCommand($hash, "/nav\$03\$02\$001\$0", "","inetRadioPresetByName", $hash->{helper}{inetRadioPresetNumber});
        }
        elsif($hash->{helper}{cmdStep} == 3)
        {
          $hash->{helper}{cmdStep} = 4;
          # Preset select
          PHILIPS_AUDIO_SendCommand($hash, "/nav\$03\$03\$". sprintf("%03d", $hash->{helper}{inetRadioPresetNumber}) . "\$1", "", "inetRadioPresetByName", $hash->{helper}{inetRadioPresetNumber});
        }               
      }
      elsif($cmd eq "inetRadioFavorite")
      {
      # This command must be processed hierarchicaly through the navigation path
        if($hash->{helper}{cmdStep} == 1)
        {
          $hash->{helper}{cmdStep} = 2;
          # Internet radio favorite
          PHILIPS_AUDIO_SendCommand($hash, "/nav\$03\$01\$001\$0", "", "inetRadioFavorite", $hash->{helper}{inetRadioFavorite});
        }
        elsif($hash->{helper}{cmdStep} == 2)
        {
          $hash->{helper}{cmdStep} = 3;
          # Favorite Presets
          PHILIPS_AUDIO_SendCommand($hash, "/nav\$03\$02\$002\$0", "","inetRadioFavorite", $hash->{helper}{inetRadioFavorite});
        }
        elsif($hash->{helper}{cmdStep} == 3)
        {
          $hash->{helper}{cmdStep} = 4;
          # Favorite Preset select
          PHILIPS_AUDIO_SendCommand($hash, "/nav\$03\$03\$".sprintf("%03d", $hash->{helper}{inetRadioFavorite})."\$1", "","inetRadioFavorite", $hash->{helper}{inetRadioFavorite});
        }
      }
      elsif($cmd eq "inetRadioFavoriteByName")
      {        
        # 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", "", "inetRadioFavoriteByName", $hash->{helper}{inetRadioFavoriteNumber});
        }
        elsif($hash->{helper}{cmdStep} == 2)
        {
          $hash->{helper}{cmdStep} = 3;
          # Favorites
          PHILIPS_AUDIO_SendCommand($hash, "/nav\$03\$02\$002\$0", "","inetRadioFavoriteByName", $hash->{helper}{inetRadioFavoriteNumber});
        }
        elsif($hash->{helper}{cmdStep} == 3)
        {
          $hash->{helper}{cmdStep} = 4;
          PHILIPS_AUDIO_SendCommand($hash, "/nav\$03\$03\$". sprintf("%03d", $hash->{helper}{inetRadioFavoriteNumber}) . "\$1", "", "inetRadioFavoriteByName", $hash->{helper}{inetRadioFavoriteNumber});
        }               
      }
      elsif($cmd eq "play_pause")
      {
        if($data =~ /SUCCESS/)
        {
          #readingsBulkUpdate($hash, "play_pause", "on");
          #readingsBulkUpdate($hash, "stop", "off");
        }
      }
      elsif($cmd eq "stop")
      {
        if($data =~ /STOP/)
        {
          readingsBulkUpdate($hash, "playing", "no");          
        }
      }      
      elsif($cmd eq "volume" or $cmd eq "volumeStraight")
      {        
        if($data =~ /SUCCESS/)
        {
          readingsBulkUpdate($hash, "volumeStraight", $hash->{helper}{targetVolume});
          my $targetVolume = $hash->{helper}{targetVolume};
          readingsBulkUpdate($hash, "volume", PHILIPS_AUDIO_volume_abs2rel($hash, $targetVolume));          
        }
      }      
      elsif($cmd eq "nowplay")
      {        
        if($data =~ /'title':'\\'(.+)\\''/)
        {
          readingsBulkUpdate($hash, "title", PHILIPS_AUDIO_STREAMIUMNP2txt($1));
        }
        else
        {
          readingsBulkUpdate($hash, "title", "");
        }
        
        if($data =~ /'title':'(.+)'/)
        {
          readingsBulkUpdate($hash, "title", PHILIPS_AUDIO_STREAMIUMNP2txt($1));
        }
        else
        {
          readingsBulkUpdate($hash, "title", "");
        }
        
        if($data =~ /'subTitle':'(.+)'/)
        {
          readingsBulkUpdate($hash, "subtitle", $1);
        }
        else
        {
          readingsBulkUpdate($hash, "subtitle", "");
        }
        
        if($data =~ /'albumArt':'(.+)'/)
        {
          readingsBulkUpdate($hash, "albumArt", $1);
        }
        else
        {
          readingsBulkUpdate($hash, "albumArt", "");
        }
        
        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");  
        }
        
        if($data =~ /'elapsetime':(.+),/)
        {
          readingsBulkUpdate($hash, "elapseTime", strftime("\%H:\%M:\%S", gmtime($1)));
        }
        else
        {
          readingsBulkUpdate($hash, "elapseTime", "");
        }
        if($data =~ /'totaltime':(.+),/)
        {
          # Playing radio delivers that total time
          if($1 eq "65535")
          {
            readingsBulkUpdate($hash, "totalTime", "infinite");
          }
          else
          {
            readingsBulkUpdate($hash, "totalTime", strftime("\%H:\%M:\%S", gmtime($1)));
          }          
        }
        else
        {
          readingsBulkUpdate($hash, "totalTime", "");
        }
        
        if($data =~ /'muteStatus':(.+),/)
        {
          if($1 == 1)
          {
            readingsBulkUpdate($hash, "mute", "on");
          }
          else
          {
            readingsBulkUpdate($hash, "mute", "off");
          }
        }
        
        # typo in the (buggy) Streamium firmware...
        if($data =~ /'playStaus':(.+),/)
        {
          if($1 == 1)
          {
            readingsBulkUpdate($hash, "playing", "yes");
          }        
          else
          {
            readingsBulkUpdate($hash, "playing", "no");
          }
        }
        else
        {
          readingsBulkUpdate($hash, "playing", "no");
        }
      }
      
      # Eventual future UPNP implementation. Requires IO::Socket::Multicast non-standard module.      
      elsif ($cmd eq "getModel")
      {
        if($data =~ /<friendlyName>(.+)<\/friendlyName>/)
        {
          $hash->{FRIENDLY_NAME} = $1;
        }        
        if($data =~ /<UDN>(.+)<\/UDN>/)
        {
          $hash->{UNIQUE_DEVICE_NAME} = uc($1);
        }
        
        my $modelName   = "";
        my $modelNumber = "";
        
        if($data =~ /<modelName>(.+)<\/modelName>/)
        {
          $modelName = $1;
        }       
        if($data =~ /<modelNumber>(.+)<\/modelNumber>/)
        {
          $modelNumber = $1;
        }
        # Combine both strings
        if(($modelName ne "") and ($modelNumber ne ""))
        {
          $hash->{MODEL} = $modelName . $modelNumber;
        }
        
        if($data =~ /<serialNumber>(.+)<\/serialNumber>/)
        {
          $hash->{SERIAL_NUMBER} = uc($1);
        }
        if($data =~ /<modelDescription>(.+)<\/modelDescription>/)
        {
          $hash->{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 $i = 1;
          
          while ($data =~ /<url>(.+?)<\/url>/g)
          {
            $hash->{"NP_ICON_$i"} = "http://".$address.":".$arg."/".$1;            
            $i++;
          }
        }      
      }
      elsif ($cmd eq "getInetRadioPresets")
      {
        # 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", "","getInetRadioPresets", "noArg");
        }
        elsif($hash->{helper}{cmdStep} == 2)
        {
          $hash->{helper}{cmdStep} = 3;
          # Presets
          PHILIPS_AUDIO_SendCommand($hash, "/nav\$03\$02\$001\$0", "","getInetRadioPresets", "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;
            readingsBulkUpdate($hash, "totalInetRadioPresets", "0");
          }
          else
          {
            if ($data =~ /'nextrequrl':'(.+?)',/)
            {
              $nextreqURL = $1;
              #Log3 $name, 5, "NextreqURL: $nextreqURL";
            }

            if ($data =~ /'totalListCount':(.+),/)
            {
              $hash->{helper}{TOTALINETRADIOPRESETS} = $1;
              readingsBulkUpdate($hash, "totalInetRadioPresets", $1);
              #Log3 $name, 5, "ListedItems: $listedItems";
            }
            
            #if ($data =~ /'totalitems':(.+),/)
            #{
            #  $listedItems = $1;
            #} 
            
            $data =~ s/\R//g;        # Remove new lines

            for(;;)
            {            
              if($data =~ /{'title':'(.+?)',/g)
              {            
                $presetName = $1;
                #Log3 $name, 5, "PresetName: $presetName";
                
                if($data =~ /'id':(.+?),/g)
                {
                  $presetID = $1;
                  #Log3 $name, 5, "PresetIDLoop: $presetID"; 
                }
                if ($presetID ne "" and $presetName ne "")
                {
                  readingsBulkUpdate($hash, sprintf("inetRadioPreset%02d", $presetID), $presetName);
                }
              }
              else
              {
                last;
              }                                            
            }

            #Log3 $name, 5, "PresetIDNachLoop: $presetID"; 

            if($presetID < ($hash->{helper}{TOTALINETRADIOPRESETS})) # Maximum listed items = 8. Get the next items by sending the nextreqURL
            {
              PHILIPS_AUDIO_SendCommand($hash, $nextreqURL, "","getInetRadioPresets", "noArg"); 
            }
            else
            {
              $hash->{helper}{cmdStep} = 4;
              if (exists($hash->{helper}{PRESETS}))
              {
                $hash->{helper}{PRESETS} = ""; # Reset the pull-down menue
              }
              PHILIPS_AUDIO_updatePresets($hash);
              #my $debug = $hash->{helper}{PRESETS};
              #Log3 $name, 5, "Presets: $debug";
            }
          }
        }  
      }
      elsif ($cmd eq "getInetRadioFavorites")
      { 
        # 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", "","getInetRadioFavorites", "noArg");
        }
        elsif($hash->{helper}{cmdStep} == 2)
        {
          $hash->{helper}{cmdStep} = 3;
          # Favorites
          PHILIPS_AUDIO_SendCommand($hash, "/nav\$03\$02\$002\$0", "","getInetRadioFavorites", "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;
            readingsBulkUpdate($hash, "totalInetRadioFavorites", "0");
          }
          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;
              readingsBulkUpdate($hash, "totalInetRadioFavorites", $1);
              #Log3 $name, 5, "ListedItems: $listedItems";
            }
            
            #if ($data =~ /'totalitems':(.+),/)
            #{
            #  $listedItems = $1;
            #} 
            
            $data =~ s/\R//g;        # Remove new lines

            for(;;)
            {            
              if($data =~ /{'title':'(.+?)',/g)
              {            
                $favoriteName = $1;
                #Log3 $name, 5, "FavoriteName: $favoriteName";
                
                if($data =~ /'id':(.+?),/g)
                {
                  $favoriteID = $1;
                  #Log3 $name, 5, "FavoriteIDLoop: $favoriteID"; 
                }
                if ($favoriteID ne "" and $favoriteName ne "")
                {
                  readingsBulkUpdate($hash, sprintf("inetRadioFavorite%02d", $favoriteID), $favoriteName);
                }
              }
              else
              {
                last;
              }                                            
            }

            #Log3 $name, 5, "FavoriteIDNachLoop: $favoriteID"; 

            if($favoriteID < ($hash->{helper}{TOTALINETRADIOFAVORITES})) # Maximum listed items = 8. Get the next items by sending the nextreqURL
            {
              PHILIPS_AUDIO_SendCommand($hash, $nextreqURL, "","getInetRadioFavorites", "noArg"); 
            }
            else
            {
              $hash->{helper}{cmdStep} = 4;
              if (exists($hash->{helper}{FAVORITES}))
              {
                $hash->{helper}{FAVORITES} = ""; # Reset the pull-down menue
              }
              PHILIPS_AUDIO_updateFavorites($hash);
              #my $debug = $hash->{helper}{FAVORITES};
              #Log3 $name, 5, "Favorites: $debug";
            }
          }  
        }        
      }

      readingsEndUpdate($hash, 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(int($absolute * 100) / int(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);

  if($hash->{helper}{DISABLED} == 0)
  {
    if(defined($interval))
    {
      InternalTimer(gettimeofday()+$interval, "PHILIPS_AUDIO_GetStatus", $hash, 0);
    }
    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, 0);
    }
    else
    {
      InternalTimer(gettimeofday() + $hash->{helper}{OFF_INTERVAL}, "PHILIPS_AUDIO_GetStatus", $hash, 0);
    }
  }  
  return;
}

#############################
# convert all HTML entities into UTF-8 equivalent
sub PHILIPS_AUDIO_STREAMIUMNP2txt
{
  my ($string) = @_;

  $string =~ s/\\'//g;
  return $string;
}

#############################
# Updates the pull-down menue
sub PHILIPS_AUDIO_updatePresets
{
  my ($hash) = @_;
  my $name = $hash->{NAME};

  if((not defined($hash->{helper}{PRESETS}) or length($hash->{helper}{PRESETS}) == 0))
  {
    my $presetName;
    
    for(my $i = 1; $i < (AttrVal($name, "max-device-presets", "24") + 1); $i++)
    {
      if(exists($hash->{READINGS}{sprintf("inetRadioPreset%02d", $i)}))
      {
        if($i != 1)
        {
          $presetName = ",";
        }
        $presetName .= sprintf("%02d", $i) . ":";
        $presetName .= $hash->{READINGS}{sprintf("inetRadioPreset%02d", $i)}{VAL};
        $presetName =~ s/ /_/g; # Replace blank by underscore
        $hash->{helper}{PRESETS} .= $presetName;      
      }
      else
      {
        last;
      }      
    }    
  }
  else
  {
    $hash->{helper}{PRESETS} = "";  
  }

  return;
}

#############################
# Updates the pull-down menue
sub PHILIPS_AUDIO_updateFavorites
{
  my ($hash) = @_;
  my $name = $hash->{NAME};

  if((not defined($hash->{helper}{FAVORITES}) or length($hash->{helper}{FAVORITES}) == 0))
  {
    my $favoriteName;
    
    for(my $i = 1; $i < (AttrVal($name, "max-device-favorites", "24") + 1); $i++)
    {
      if(exists($hash->{READINGS}{sprintf("inetRadioFavorite%02d", $i)}))
      {
        if($i != 1)
        {
          $favoriteName = ",";
        }
        $favoriteName .= sprintf("%02d", $i) . ":";
        $favoriteName .= $hash->{READINGS}{sprintf("inetRadioFavorite%02d" , $i)}{VAL};
        $favoriteName =~ s/ /_/g; # Replace blank by underscore
        $hash->{helper}{FAVORITES} .= $favoriteName;               
      }
      else
      {
        last;
      }
    }
  }
}

1;

=pod
=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 including a navigation server.<br>
    To check, open the following URL in the browser: http://[ip # of your device]:8889/index
    <br><br>
    (So far tested on: AW9000, NP3500, NP3700 and NP3900)  
    <br><br>
    Currently implemented features:
    <br><br>
    <ul>
      <li>Power on/off</li>
      <li>Internet Radio Preset Selection</li>
      <li>Input selection</li>
      <li>Volume +/-</li>
      <li>Mute on/off</li>
      <li>...</li>
    </ul>
    <br>
    Defining a PHILIPS_AUDIO device will schedule an internal task (interval can be set
    with optional parameters &lt;off_status_interval&gt; and &lt;on_status_interval&gt; in seconds.<br>
    &lt;off_status_interval&gt; is a parameter used in case the device is powered off or not available.<br>
    &lt;on_status_interval&gt; is a parameter used in case the device is powered on.<br>
    If both parameters are unset, a default value 30 (seconds) for both is used.<br>
    If &lt;off_status_interval&gt; is set only the same value is used for both parameters.<br>
    Due to a relatively low-performance of the devices the minimum interval is set to 3 seconds.
    <br>
    The internal task periodically reads the status of the Network Player (power state, volume and mute status etc.) and triggers notify/filelog commands.
    <br><br>    
    Example:<br><br>
    <ul><br>
      Add the following code into the <b>fhem.cfg</b> configuration file and restart fhem:<br><br>
      <code>
      define PHAUDIO_player PHILIPS_AUDIO NP3900 192.168.0.15<br>
      attr PHAUDIO_player webCmd input:volume:mute:inetRadioPreset<br><br>
      # With custom status interval of 60 seconds<br>
      define PHAUDIO_player PHILIPS_AUDIO NP3900 192.168.0.15 <b>60</b><br>
      attr PHAUDIO_player webCmd input:volume:mute:inetRadioPreset<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>
      attr PHAUDIO_player webCmd input:volume:mute:inetRadioPreset<br><br>
      </code>
    </ul>   
  </ul>
  <br><br>
  <a name="PHILIPS_AUDIOset"></a>
  <b>Set</b>
  <ul>
    <code>
      set &lt;name&gt; &lt;command&gt; [&lt;parameter&gt;]
    </code><br><br>
    Currently, the following commands are defined.<br>    
    <i>Note: Commands and parameters are case sensitive.</i><br>
      <ul><br><br>
      <u>Available commands:</u><br><br>
      <li><b>aux</b>&nbsp;&nbsp;-&nbsp;&nbsp; Switches to the AUX input (MP3 Link or similar).</li>
      <li><b>getInetRadioFavorites</b> &nbsp;&nbsp;-&nbsp;&nbsp; Reads the favorites. (May take some seconds...).</li>
      <li><b>getInetRadioPresets</b> &nbsp;&nbsp;-&nbsp;&nbsp; Reads the presets. (May take some seconds...).</li>
      <li><b>inetRadioFavorite</b> [1..24] &nbsp;&nbsp;-&nbsp;&nbsp; Selects an internet radio favorite (May take some seconds...).</li>
      <li><b>inetRadioPreset</b> [1..24] &nbsp;&nbsp;-&nbsp;&nbsp; Selects an internet radio preset (May take some seconds...).</li>
      <li><b>inetRadioFavoriteByName</b> [name] &nbsp;&nbsp;-&nbsp;&nbsp; Selects an internet radio favorite by name. (getInetRadioFavorites must be run previously for the command to become available.) (May take some seconds...).</li>
      <li><b>inetRadioPresetByName</b> [name] &nbsp;&nbsp;-&nbsp;&nbsp; Selects an internet radio preset by name. (getInetRadioPresets must be run previously for the command to become available.) (May take some seconds...).</li>
      <li><b>mute</b>&nbsp;&nbsp;-&nbsp;&nbsp; Mutes the device.</li>
      <li><b>unmute</b>&nbsp;&nbsp;-&nbsp;&nbsp; Unmutes the device.</li>
      <li><b>next</b> &nbsp;&nbsp;-&nbsp;&nbsp; Selects the next song, preset etc.</li>
      <li><b>play_pause</b> &nbsp;&nbsp;-&nbsp;&nbsp; Toggles PLAY/PAUSE.</li>
      <li><b>previous</b> &nbsp;&nbsp;-&nbsp;&nbsp; Selects the previous song, preset etc.</li>
      <li><b>repeat [single|all|off]</b> &nbsp;&nbsp;-&nbsp;&nbsp; Sets the repeat mode.</li>
      <li><b>shuffle [on|off]</b> &nbsp;&nbsp;-&nbsp;&nbsp; Sets the shuffle mode.</li>
      <li><b>standbyButton</b> &nbsp;&nbsp;-&nbsp;&nbsp; Toggles between standby and power on.</li>
      <li><b>statusRequest</b> &nbsp;&nbsp;-&nbsp;&nbsp; Updates the readings.</li>
      <li><b>stop</b> &nbsp;&nbsp;-&nbsp;&nbsp; Stops the player.</li>
      <li><b>volume</b> &nbsp;&nbsp;-&nbsp;&nbsp; Sets the relative volume 0...100%.</li>
      <li><b>volumeStraight</b> &nbsp;&nbsp;-&nbsp;&nbsp; Sets the absolute volume 0...64.</li>     
    </ul><br><br>

    A typical example is powering the device remotely and tuning the favourite radio station:<br><br>
    Add the following code into the <b>fhem.cfg</b> configuration file:<br><br><br>
    <ul>
      <code>
        define PHAUDIO_player PHILIPS_AUDIO NP3900 192.168.0.15 30 5<br>
        attr PHAUDIO_player webCmd volume:mute:inetRadioPreset
      </code>
    </ul><br><br>
    Add the following code into the <b>99_MyUtils.pm</b> file:<br><br>
    <ul>
      <code>
        sub startMyFavouriteRadioStation()<br>
        {<br>
          &nbsp;&nbsp;fhem "set PHAUDIO_player inetRadioPreset 1";<br>
          &nbsp;&nbsp;sleep 1;<br>
          &nbsp;&nbsp;fhem "set PHAUDIO_player volume 30";<br>
        }
      </code>
    </ul>
    <br><br>
    It's a good idea to insert a 'sleep' instruction between each fhem commands due to internal processing time of the player. Be patient when executing the commands...<br><br>

    Now the function can be called by typing the following line in the FHEM command line or by the notify-definitions:<br><br>
    <ul>
      <code>
        {startMyFavouriteRadioStation()}<br><br>
      </code>
    </ul>
  </ul>
  <a name="PHILIPS_AUDIOget"></a>
  <b>Get</b>
  <ul>
    <code>
      get &lt;name&gt; &lt;reading&gt;
    </code>
    <br><br>
    Currently, the 'get' command returns reading values only. For a specific list of possible values, see section <b>"Generated Readings"</b>.<br><br>
  </ul>
  <a name="PHILIPS_AUDIOattr"></a>
  <b>Attributes</b><br><br>
  <ul>
    <ul>  
      <li><b><a href="#do_not_notify">do_not_notify</a></b></li>
      <li><b><a href="#readingFnAttributes">readingFnAttributes</a></b></li>
      <li><b><a name="request-timeout">request-timeout</a></b></li>
      <br>Optional attribute change the response timeout in seconds for all queries to the receiver.
      <br>Possible values: 1...5 seconds. Default value is 4 seconds.<br><br>
      <li><b><a name="max-device-presets">max-device-presets</a></b></li>
      <br>Optional attribute defining maximum number of available preset slots in device's memory. Default value 24.<br><br>
      <li><b><a name="max-device-favorites">max-device-favorites</a></b></li>
      <br>Optional attribute defining maximum number of favorite slots in device's memory. Default value 24.<br><br>
      <li><b><a name="disable">disable</a></b></li>
      <br>Optional attribute to disable the internal cyclic status update of the receiver. Manual status updates via statusRequest command is still possible.
      <br>Possible values: 0 &rarr; perform cyclic status update, 1 &rarr; don't perform cyclic status updates.<br><br><br>
    </ul>
  </ul>
  <b>Readings</b><br><br>
  <ul>
    <ul>
      <li><b>albumArt</b> - Link to current album art or radio station.</li>
      <li><b>elapseTime</b> - Elapse time of the played audio.</li>
      <li><b>inetRadioFavorite[value]</b> - Name of the stored favorite.</li>
      <li><b>inetRadioPreset[value]</b> - Name of the stored preset.</li>
      <li><b>mute</b> - Reports the mute status (on|off).</li>
      <li><b>playing</b> - Reports the current playier status (yes|no).</li>
      <li><b>power</b> - Reports the current power status (on|absent).</li>
      <li><b>presence</b> - Reports the current presence (present|absent).</li>
      <li><b>state</b> - Reports the current state status (on|absent).</li>
      <li><b>subtitle</b> - Reports the current subtitle of played audio.</li>
      <li><b>totalInetRadioFavorites</b> - Number of stored favorites.</li>
      <li><b>totalInetRadioPresets</b> - Number of stored presets.</li>
      <li><b>title</b> - Reports the current title of played audio.</li>
      <li><b>totalTime</b> - Reports the total time of the played audio.</li>
      <li><b>volume</b> - Reports current relative volume (0..100).</li>
      <li><b>volumeStraight</b> - Reports current absolute volume (0..64).</li>      
    </ul>
  </ul><br>
  <b>Implementer's note</b><br><br>
    <ul>
    Trivial: In order to use that module the network player must be connected to the Ethernet.<br>
    There's no possibility to read back the current power on/standby status from the device. This fuctionality is missing in the server application.<br>
    </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 via Ethernet steuern.<br>
    Theoretisch sollten alle Ger&auml;te, die &uuml;ber einer implementierten HTTP Server am Port 8889 haben (http://[ip Nummer des Ger&auml;tes]:8889/index), bedient werden k&ouml;nnen.<br>
    <br>
    <br>
    (Getestet mit: AW9000, NP3500, NP3700 und NP3900)
    <br>
    <br>   
    Die aktuelle Implementierung erm&ouml;glicht u.a. den folgenden Funktionsumfang:<br><br>
    <ul>
      <li>Power on/off</li>
      <li>Internet Radio Preset Auswahl</li>
      <li>Input Auswahl</li>
      <li>Volume +/-</li>
      <li>Mute on/off</li>
      <li>...</li>
    </ul>
    <br>
    Eine PHILIPS_AUDIO Definition initiiert einen internen Task, der von FHEM zyklisch abgearbeitet wird.<br>
    Das Intervall (in Sekunden) kann f&uuml;r die Zust&auml;nde &lt;on_status_interval&gt; und &lt;off_status_interval&gt; optional gesetzt werden.<br>
    &lt;off_status_interval&gt; steht f&uuml;r das Intervall, wenn das Ger&auml;t ausgeschaltet/abwesend ist.<br>
    &lt;on_status_interval&gt; steht f&uuml;r das Intervall, wenn das Ger&auml;t eingeschaltet/verf&uuml;gbar ist.<br>
    Wenn keine Parameter angegeben wurden, wird ein Default-Wert von 30 Sekunden f&uuml;r beide gesetzt.<br>
    Wenn nur &lt;off_status_interval&gt; gesetzt wird, gilt dieser Wert f&uuml;r beide Zust&auml;nde (eingeschaltet/ausgeschaltet).<br>
    Der Task liest zyklisch grundlegende Parameter vom Player und triggert notify/filelog Befehle.<br>
    Aufgrund der recht schwachen Rechenleistung der Player wurde das minimale Intervall auf 3 Sekunden beschr&auml;nkt.<br><br>    
    Beispiel:<br><br>
    <ul><br>
      Definition in der <b>fhem.cfg</b> Konfigurationsdatei:<br><br>
      <code>
        define PHAUDIO_player PHILIPS_AUDIO NP3900 192.168.0.15<br>
        attr PHAUDIO_player webCmd input:volume:mute:inetRadioPreset<br><br>
        # 60 Sekunden Intervall<br>
        define PHAUDIO_player PHILIPS_AUDIO NP3900 192.168.0.15 <b>60</b><br>
        attr PHAUDIO_player webCmd input:volume:mute:inetRadioPreset<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><br>
        attr PHAUDIO_player webCmd input:volume:mute:inetRadioPreset
      </code>
    </ul>   
  </ul><br><br>
  <a name="PHILIPS_AUDIOset"></a>
  <b>Set</b>
  <ul>
    <code>
      set &lt;name&gt; &lt;command&gt; [&lt;parameter&gt;]
    </code>
    <br><br>
    Aktuell sind folgende Befehle implementiert:<br>
    <i>Bemerkung: Bitte bei den Befehlen und Parametern die Gro&szlig;- und Kleinschreibung beachten.</i><br>
    <ul><br><br>
      <li><b>aux</b>&nbsp;&nbsp;-&nbsp;&nbsp; Schaltet auf den AUX Eingang um (MP3 Link oder &auml;hnlich.).</li>
      <li><b>getInetRadioFavorites</b> &nbsp;&nbsp;-&nbsp;&nbsp; Liest die Favoriten. (Kann einige Sekunden dauern...).</li>
      <li><b>getInetRadioPresets</b> &nbsp;&nbsp;-&nbsp;&nbsp; Liest die Presets. (Kann einige Sekunden dauern...).</li>
      <li><b>inetRadioFavorite</b> [1..24] &nbsp;&nbsp;-&nbsp;&nbsp; W&auml;hlt den Internetradio-Lieblingssender (Das Umschalten kann einige Sekunden dauern...).</li>
      <li><b>inetRadioPreset</b> [1..24] &nbsp;&nbsp;-&nbsp;&nbsp; W&auml;hlt die Internetradio Voreinstellung (Das Umschalten kann einige Sekunden dauern...).</li>
      <li><b>inetRadioFavoriteByName</b> [name] &nbsp;&nbsp;-&nbsp;&nbsp; W&auml;hlt einen Internetradio Favoriten mit Hilfe seines Names. (getInetRadioFavorites muss zuvor ausgef&uuml;hrt werden, damit der Befehl verf&uuml;gbar wird. (Kann einige Sekunden dauern...).</li>
      <li><b>inetRadioPresetByName</b> [name] &nbsp;&nbsp;-&nbsp;&nbsp; W&auml;hlt eine Internetradio Voreinstellung mit Hilfe ihres Names. (getInetRadioPresets muss zuvor ausgef&uuml;hrt werden, damit der Befehl verf&uuml;gbar wird. (Kann einige Sekunden dauern...).</li>
      <li><b>mute</b>&nbsp;&nbsp;-&nbsp;&nbsp; Stummschaltung des Players.</li>
      <li><b>unmute</b>&nbsp;&nbsp;-&nbsp;&nbsp; Deaktivierung der Stummschaltung.</li>
      <li><b>next</b> &nbsp;&nbsp;-&nbsp;&nbsp; W&auml;hlt den n&auml;chten Titel, Voreinstellung etc.</li>
      <li><b>play_pause</b> &nbsp;&nbsp;-&nbsp;&nbsp; Schaltet um zwischen PLAY um PAUSE.</li>
      <li><b>previous</b> &nbsp;&nbsp;-&nbsp;&nbsp; W&auml;hlt den vorherigen Titel, Voreinstellung etc.</li>
      <li><b>repeat [single|all|off]</b> &nbsp;&nbsp;-&nbsp;&nbsp; Bestimmt den Wiederholungsmodus.</li>
      <li><b>shuffle [on|off]</b> &nbsp;&nbsp;-&nbsp;&nbsp; Bestimmt den Zufallswiedergabemodus.</li>
      <li><b>standbyButton</b> &nbsp;&nbsp;-&nbsp;&nbsp; Schaltet um zwischen Standby und Power on.</li>
      <li><b>statusRequest</b> &nbsp;&nbsp;-&nbsp;&nbsp; Readings Update.</li>
      <li><b>stop</b> &nbsp;&nbsp;-&nbsp;&nbsp; Stoppt den Player.</li>
      <li><b>volume</b> &nbsp;&nbsp;-&nbsp;&nbsp; Setzt die relative Lautst&auml;rke 0...100%.</li>
      <li><b>volumeStraight</b> &nbsp;&nbsp;-&nbsp;&nbsp; Setzt die absolute Lautst&auml;rke  0...64.</li>
    </ul><br><br>
      Ein typisches Beispiel ist das Einschalten des Ger&auml;tes und das Umschalten auf den Lieblingsradiosender:<br><br>
      Beispieldefinition in der <b>fhem.cfg</b> Konfigurationsdatei:<br><br><br>
      <ul>
        <code>
          define PHAUDIO_player PHILIPS_AUDIO NP3900 192.168.0.15 30 5<br>
          attr PHAUDIO_player webCmd input:volume:mute:inetRadioPreset
        </code>
      </ul><br><br>
      Folgender Code kann anschlie&szlig;end in die Datei <b>99_MyUtils.pm</b> eingebunden werden:<br><br>
      <ul>
        <code>
          sub startMyFavouriteRadioStation()<br>
          {<br>
            &nbsp;&nbsp;fhem "set PHAUDIO_player inetRadioPreset 1";<br>
            &nbsp;&nbsp;sleep 1;<br>
            &nbsp;&nbsp;fhem "set PHAUDIO_player volume 30";<br>}
        </code>
      </ul><br><br>
      <i>Bemerkung: Aufgrund der relativ langsamen Befehlsverarbeitung im  Player im Vergleich zur asynchronen Ethernet-Kommunikation, kann es vorkommen, dass veraltete Statusinformationen zur&uuml;ckgesendet werden.<br>
      Aus diesem Grund wird empfohlen, w&auml;hrend der Automatisierung zwischen den 'set' und 'get' Befehlen ein Delay einzubauen.</i><br><br>
      Die Funktion kann jetzt in der FHEM Befehlszeile eingegeben oder in die Notify-Definitionen eingebunden werden.<br><br>
      <ul>
        <code>
          {startMyFavouriteRadioStation()}<br><br>
        </code>
      </ul>
    </ul>
    <a name="PHILIPS_AUDIOget"></a>
    <b>Get</b>
    <code>
      get &lt;name&gt; &lt;reading&gt;
    </code><br><br>
    Aktuell liefert der Befehl 'get' ausschlie&szlig;lich Reading-Werte (s. Abschnitt <b>"Readings"</b>).<br><br>
  </ul>
  <a name="PHILIPS_AUDIOattr"></a>
  <ul>
    <b>Attribute</b><br><br>
    <ul>
      <ul>  
        <li><b><a href="#do_not_notify">do_not_notify</a></b></li>
        <li><b><a href="#readingFnAttributes">readingFnAttributes</a></b></li><br>
        <li><b><a name="request-timeout">request-timeout</a></b></li><br>
        Optionales Attribut, um das HTTP response timeout zu beeinflu&szlig;en.<br>
        M&ouml;gliche Werte: 1...5 Sekunden. Default Wert ist 4 Sekunden.<br><br>
        <li><b><a name="max-device-presets">max-device-presets</a></b></li>
        <br>Optionales Attribut zur Bestimmung der max. Anzahl von verf&uumlgbaren Preset-Speicherpl&aumltzen des Players. Default-Wert 24.<br><br>
        <li><b><a name="max-device-favorites">max-device-favorites</a></b></li>
        <br>Optionales Attribut zur Bestimmung der max. Anzahl von verf&uumlgbaren Favorite-Speicherpl&aumltzen des Players. Default-Wert 24.<br><br>
        <li><b><a name="disable">disable</a></b></li><br>
        Optionales Attribut zum Deaktivieren des internen zyklischen Timers zum Aktualisieren des NP-Status. Manuelles Update ist nach wie vor m&ouml;glich.<br>
        M&ouml;gliche Werte: 0 &rarr; Zyklisches Update aktiv., 1 &rarr; Zyklisches Update inaktiv.<br><br><br>
      </ul>
    </ul>
    <b>Readings</b><br><br>
    <ul>
      <ul>
        <li><b>albumArt</b> - Link zum aktuellen Album art oder Radiostation.</li>
        <li><b>elapseTime</b> - Aktuelle Zeit des abgespielten Audiost&uuml;ckes.</li>
        <li><b>inetRadioFavorite[Wert]</b> - Name des/der gespeicherten Favoriten.</li>
        <li><b>inetRadioPreset[Wert]</b> - Name des/der gespeicherten Presets.</li> 
        <li><b>mute</b> - Abfrage des Stummschaltungstatus (on|off).</li>
        <li><b>playing</b> - Abfrage des aktuelle Playierstatus (yes|no).</li>
        <li><b>power</b> - Abfrage des aktuellen Ger&auml;tezustands (on|absent).</li>
        <li><b>presence</b> - Abfrage der Ger&auml;teanwesenheit (present|absent).</li>
        <li><b>state</b> - Abfrage des aktuellen 'state'-Status (on|absent).</li>
        <li><b>subtitle</b> - Untertiltel des abgespielten Audiost&uuml;ckes.</li>
        <li><b>title</b> - Titel des abgespielten Audiost&uuml;ckes.</li>
        <li><b>totalInetRadioFavorites</b> - Anzahl der gespeicherten Favoriten.</li>
        <li><b>totalInetRadioPresets</b> - Anzahl der gespeicherten Presets.</li>
        <li><b>totalTime</b> Gesamtspieldauer des Audiost&uuml;ckes.</li>
        <li><b>volume</b> - Aktuelle relative Lautst&auml;rke (0..100).</li>
        <li><b>volumeStraight</b> - Aktuelle absolute Lautst&auml;rke (0..64).</li>  
      </ul>
    </ul><br>
  <b>Bemerkung des Entwicklers</b><br><br>
  <ul>
    Trivial: Um das Ger&auml;t fernbedienen zu k&ouml;nnen, muss es an das Ethernet-Netzwerk angeschlossen und erreichbar sein.<br>
    Es gibt keine M&ouml;glichkeit, den Zustand Power-on/Standby des Ger&auml;tes abzufragen. Diese Limitierung liegt auf Seiten des Ger&auml;tes.<br>
  </ul><br>
</ul>
=end html_DE
=cut