# ############################################################################## # $Id$ # # FHEM Module for Squeezebox Players # # ############################################################################## # # used to interact with Squeezebox Player # # ############################################################################## # # Written by bugster_de # # Contributions from: Siggi85, Oliv06, ChrisD, Markus M., Matthew, KernSani # # ############################################################################## # # This is absolutley open source. Please feel free to use just as you # like. Please note, that no warranty is given and no liability # granted # # ############################################################################## # # we have the following readings # state on or off # # ############################################################################## # # we have the following attributes # timer the time frequency how often we check # volumeStep the volume delta when sending the up or down command # timeout the timeout in seconds for the TCP connection # # ############################################################################## # we have the following internals (all UPPERCASE) # PLAYERIP the IP adress of the player in the network # PLAYERID the unique identifier of the player. Mostly the MAC # SERVER based on the IP and the port as given # IP the IP of the server # PLAYERNAME the name of the Player # CONNECTION the connection status to the server # CANPOWEROFF is the player supporting power off commands # MODEL the model of the player # DISPLAYTYPE what sort of display is there, if any # # ############################################################################## # based on 98_SB_PLAYER.pm beta 0043 CD/MM/Matthew # ############################################################################## package main; use strict; use warnings; use IO::Socket; use URI::Escape; use Encode qw(decode encode); # include this for the self-calling timer we use later on use Time::HiRes qw(gettimeofday); use constant { true => 1, false => 0 }; # the list of favorites # CD 0010 moved to $hash->{helper}{SB_PLAYER_Favs}, fixes problem on module reload #my %SB_PLAYER_Favs; # the list of sync masters # CD 0010 moved to $hash->{helper}{SB_PLAYER_SyncMasters}, fixes problem on module reload #my %SB_PLAYER_SyncMasters; # the list of Server side playlists # CD 0010 moved to $hash->{helper}{SB_PLAYER_Playlists}, fixes problem on module reload #my %SB_PLAYER_Playlists; # used for $hash->{helper}{ttsstate} use constant TTS_IDLE => 0; use constant TTS_TEXT2SPEECH_BUSY => 4; use constant TTS_TEXT2SPEECH_ACTIVE => 6; use constant TTS_WAITFORPOWERON => 10; use constant TTS_SAVE => 20; use constant TTS_UNSYNC => 30; use constant TTS_SETVOLUME => 40; use constant TTS_LOADPLAYLIST => 50; use constant TTS_DELAY => 55; use constant TTS_WAITFORPLAY => 60; use constant TTS_PLAYING => 70; use constant TTS_STOP => 80; use constant TTS_RESTORE => 90; use constant TTS_SYNC => 100; use constant TTS_SYNCGROUPACTIVE => 1000; use constant TTS_EXT_TEXT2SPEECH_BUSY => 2004; use constant TTS_EXT_TEXT2SPEECH_ACTIVE => 2006; my %ttsstates = ( 0 =>'idle', 4 =>'Text2Speech busy, waiting', 6 =>'Text2Speech active', 10 =>'wait for power on', 20 =>'save state', 30 =>'unsync player', 40 =>'set volume', 50 =>'load playlist', 55 =>'delay', 60 =>'wait for play', 70 =>'playing', 80 =>'stopped', 90 =>'restore state', 100 =>'sync', 1000=>'active', 2004=>'Text2Speech busy, waiting', 2006=>'Text2Speech active'); my $SB_PLAYER_hasDataDumper = 1; # CD 0036 # ---------------------------------------------------------------------------- # Initialisation routine called upon start-up of FHEM # ---------------------------------------------------------------------------- sub SB_PLAYER_Initialize( $ ) { my ($hash) = @_; # the commands we provide to FHEM # installs the respecitive call-backs for FHEM. The call back in quotes # must be realised as a sub later on in the file $hash->{DefFn} = "SB_PLAYER_Define"; $hash->{UndefFn} = "SB_PLAYER_Undef"; $hash->{ShutdownFn} = "SB_PLAYER_Shutdown"; $hash->{SetFn} = "SB_PLAYER_Set"; $hash->{GetFn} = "SB_PLAYER_Get"; # for the two step approach $hash->{Match} = "^SB_PLAYER:"; $hash->{ParseFn} = "SB_PLAYER_Parse"; # CD 0007 $hash->{AttrFn} = "SB_PLAYER_Attr"; # CD 0032 $hash->{NotifyFn} = "SB_PLAYER_Notify"; # the attributes we have. Space separated list of attribute values in # the form name:default1,default2 $hash->{AttrList} = "IODev ignore:1,0 do_not_notify:1,0 "; $hash->{AttrList} .= "volumeStep volumeLimit "; $hash->{AttrList} .= "ttslanguage:de,en,fr ttslink "; $hash->{AttrList} .= "donotnotify:true,false "; $hash->{AttrList} .= "idismac:true,false "; $hash->{AttrList} .= "serverautoon:true,false "; $hash->{AttrList} .= "fadeinsecs "; $hash->{AttrList} .= "amplifier:on,play "; $hash->{AttrList} .= "coverartheight:50,100,200 "; $hash->{AttrList} .= "coverartwidth:50,100,200 "; # CD 0028 $hash->{AttrList} .= "ttsVolume "; $hash->{AttrList} .= "ttsOptions "; # CD 0030 $hash->{AttrList} .= "ttsDelay "; # CD 0032 $hash->{AttrList} .= "ttsPrefix "; # DJAlex 665 # CD 0033 $hash->{AttrList} .= "ttsMP3FileDir "; # CD 0007 $hash->{AttrList} .= "syncVolume "; $hash->{AttrList} .= "amplifierDelayOff "; # CD 0012 $hash->{AttrList} .= "updateReadingsOnSet:true,false "; # CD 0017 $hash->{AttrList} .= "statusRequestInterval "; # CD 0037 $hash->{AttrList} .= $readingFnAttributes; # CD 0036 aus 37_sonosBookmarker eval "use Data::Dumper"; $SB_PLAYER_hasDataDumper = 0 if($@); } # CD 0007 start # ---------------------------------------------------------------------------- # Attr functions # ---------------------------------------------------------------------------- sub SB_PLAYER_Attr( @ ) { my $cmd = shift( @_ ); my $name = shift( @_ ); my @args = @_; my $hash = $defs{$name}; Log( 4, "SB_PLAYER_Attr($name): called with @args" ); if( $args[ 0 ] eq "syncVolume" ) { if( $cmd eq "set" ) { if (defined($args[1])) { if($args[1] eq "1") { IOWrite( $hash, "$hash->{PLAYERMAC} playerpref syncVolume 1\n" ); } else { IOWrite( $hash, "$hash->{PLAYERMAC} playerpref syncVolume 0\n" ); } } else { IOWrite( $hash, "$hash->{PLAYERMAC} playerpref syncVolume ?\n" ); } } else { } } # CD 0012 start - bei Änderung des Attributes Zustand überprüfen elsif( $args[ 0 ] eq "amplifier" ) { RemoveInternalTimer( "DelayAmplifier:$name"); InternalTimer( gettimeofday() + 0.01, "SB_PLAYER_tcb_DelayAmplifier", # CD 0014 Name geändert "DelayAmplifier:$name", 0 ); } # CD 0028 elsif( $args[ 0 ] eq "ttsVolume" ) { if( $cmd eq "set" ) { if (defined($args[1])) { return "invalid value for ttsVolume" if(($args[1] < 0)||($args[1] > 100)); $hash->{helper}{ttsVolume}=$args[1]; } else { return "invalid value for ttsVolume"; } } else { delete($hash->{helper}{ttsVolume}) if(defined($hash->{helper}{ttsVolume})); } } elsif( $args[ 0 ] eq "ttsOptions" ) { if( $cmd eq "set" ) { if (defined($args[1])) { my @options=split(',',$args[1]); delete($hash->{helper}{ttsOptions}) if(defined($hash->{helper}{ttsOptions})); for my $opt (@options) { $hash->{helper}{ttsOptions}{debug}=1 if($opt=~ m/debug/); $hash->{helper}{ttsOptions}{debugsaverestore}=1 if($opt=~ m/debugsaverestore/); # CD 0029 $hash->{helper}{ttsOptions}{unsync}=1 if($opt=~ m/unsync/); $hash->{helper}{ttsOptions}{nosaverestore}=1 if($opt=~ m/nosaverestore/); $hash->{helper}{ttsOptions}{forcegroupon}=1 if($opt=~ m/forcegroupon/); $hash->{helper}{ttsOptions}{internalsave}=1 if($opt=~ m/internalsave/); # CD 0029 $hash->{helper}{ttsOptions}{ignorevolumelimit}=1 if($opt=~ m/ignorevolumelimit/); # CD 0031 } } else { return "invalid value for ttsOptions"; } } else { delete($hash->{helper}{ttsOptions}) if(defined($hash->{helper}{ttsOptions})); } } # CD 0030 elsif( $args[ 0 ] eq "ttsDelay" ) { if( $cmd eq "set" ) { if (defined($args[1])) { my @options=split(',',$args[1]); $hash->{helper}{ttsDelay}{PowerIsOn}=$options[0]; if(defined($options[1])) { $hash->{helper}{ttsDelay}{PowerIsOff}=$options[1]; } else { $hash->{helper}{ttsDelay}{PowerIsOff}=$options[0]; } } else { return "invalid value for ttsDelay"; } } else { delete($hash->{helper}{ttsDelay}) if(defined($hash->{helper}{ttsDelay})); } } # CD 0037 elsif( $args[ 0 ] eq "statusRequestInterval" ) { RemoveInternalTimer( $hash ); if( $cmd eq "set" ) { if (defined($args[1])) { InternalTimer( gettimeofday() + $args[1], "SB_PLAYER_GetStatus", $hash, 0 ) if $args[1]>0; } else { return "missing value for statusRequestInterval"; } } else { InternalTimer( gettimeofday() + 5, "SB_PLAYER_GetStatus", $hash, 0 ); } } return; # CD 0012 } # CD 0007 end # ---------------------------------------------------------------------------- # Definition of a module instance # called when defining an element via fhem.cfg # ---------------------------------------------------------------------------- sub SB_PLAYER_Define( $$ ) { my ( $hash, $def ) = @_; my $name = $hash->{NAME}; my @a = split("[ \t][ \t]*", $def); # do we have the right number of arguments? if( ( @a < 3 ) || ( @a > 5 ) ) { Log3( $hash, 1, "SB_PLAYER_Define: falsche Anzahl an Argumenten" ); return( "wrong syntax: define SB_PLAYER " . " " ); } # remove the name and our type # my $name = shift( @a ); shift( @a ); # name shift( @a ); # type # needed for manual creation of the Player; autocreate checks in ParseFn if( SB_PLAYER_IsValidMAC( $a[ 0] ) == 1 ) { # the MAC adress is valid $hash->{PLAYERMAC} = lc($a[ 0 ]); # CD 0026 lc added } else { my $msg = "SB_PLAYER_Define: playerid ist keine MAC Adresse " . "im Format xx:xx:xx:xx:xx:xx oder xx-xx-xx-xx-xx-xx"; Log3( $hash, 1, $msg ); return( $msg ); } # shift the MAC away shift( @a ); $hash->{AMPLIFIER} = "none"; $hash->{COVERARTLINK} = "none"; foreach( @a ) { if( $_ =~ /^(ampl:)(.*)/ ) { $hash->{AMPLIFIER} = $2; next; } elsif( $_ =~ /^(coverart:)(.*)/ ) { $hash->{COVERARTLINK} = $2; next; } else { next; } } Log3( $hash, 5, "SB_PLAYER_Define successfully called" ); # remove the : from the ID my @idbuf = split( ":", $hash->{PLAYERMAC} ); my $uniqueid = join( "", @idbuf ); # our unique id $hash->{FHEMUID} = $uniqueid; # do the alarms fade in #$hash->{ALARMSFADEIN} = "?"; # CD 0016 deaktiviert, -> Reading # the number of alarms of the player $hash->{helper}{ALARMSCOUNT} = 0; # CD 0016 ALARMSCOUNT nach {helper} verschoben # for the two step approach $modules{SB_PLAYER}{defptr}{$uniqueid} = $hash; AssignIoPort( $hash ); # preset the internals # can the player power off $hash->{CANPOWEROFF} = "?"; # graphical or textual display $hash->{DISPLAYTYPE} = "?"; # which model do we see? $hash->{MODEL} = "?"; # what's the ip adress of the player $hash->{PLAYERIP} = "?"; # the name of the player as assigned by the server $hash->{PLAYERNAME} = "?"; # the last alarm we did set $hash->{LASTALARM} = 1; # the reference to the favorites list $hash->{FAVREF} = " "; # the command for selecting a favorite $hash->{FAVSET} = "favorites"; # the entry in the global hash table $hash->{FAVSTR} = "not,yet,defined "; # the selected favorites $hash->{FAVSELECT} = "not"; # last received answer from the server $hash->{LASTANSWER} = "none"; # for sync group (multi-room) $hash->{SYNCMASTER} = "?"; $hash->{SYNCGROUP} = "?"; $hash->{SYNCED} = "?"; # seconds until sleeping $hash->{WILLSLEEPIN} = "?"; # the list of potential sync masters $hash->{SYNCMASTERS} = "not,yet,defined"; # is currently playing a remote stream $hash->{ISREMOTESTREAM} = "?"; # the server side playlists $hash->{SERVERPLAYLISTS} = "not,yet,defined"; # the URL to the artwork $hash->{ARTWORKURL} = "?"; $hash->{COVERARTURL} = "?"; $hash->{COVERID} = "?"; # the IP and Port of the Server $hash->{SBSERVER} = "?"; # preset the attributes # volume delta settings if( !defined( $attr{$name}{volumeStep} ) ) { $attr{$name}{volumeStep} = 10; } # Upper limit for volume setting if( !defined( $attr{$name}{volumeLimit} ) ) { $attr{$name}{volumeLimit} = 100; } # how many secs for fade in when going from stop to play if( !defined( $attr{$name}{fadeinsecs} ) ) { $attr{$name}{fadeinsecs} = 10; } # do not create FHEM notifies (true=no notifies) if( !defined( $attr{$name}{donotnotify} ) ) { $attr{$name}{donotnotify} = "true"; } # is the ID the MAC adress if( !defined( $attr{$name}{idismac} ) ) { $attr{$name}{idismac} = "true"; } # the language for text2speech if( !defined( $attr{$name}{ttslanguage} ) ) { $attr{$name}{ttslanguage} = "de"; } # link to the text2speech engine if( !defined( $attr{$name}{ttslink} ) ) { $attr{$name}{ttslink} = "http://translate.google.com" . "/translate_tts?ie=UTF-8"; } # turn on the server when player is used if( !defined( $attr{$name}{serverautoon} ) ) { $attr{$name}{serverautoon} = "true"; } # amplifier on/off when play/pause or on/off if( !defined( $attr{$name}{amplifier} ) ) { $attr{$name}{amplifier} = "play"; } # height and width of the cover art for the URL if( !defined( $attr{$name}{coverartwidth} ) ) { $attr{$name}{coverartwidth} = 50; } if( !defined( $attr{$name}{coverartheight} ) ) { $attr{$name}{coverartheight} = 50; } # Preset our readings if undefined my $tn = TimeNow(); # according to development guidelines of FHEM AV Module if( !defined( $hash->{READINGS}{presence}{VAL} ) ) { $hash->{READINGS}{presence}{VAL} = "?"; $hash->{READINGS}{presence}{TIME} = $tn; } # according to development guidelines of FHEM AV Module if( !defined( $hash->{READINGS}{power}{VAL} ) ) { $hash->{READINGS}{power}{VAL} = "?"; $hash->{READINGS}{power}{TIME} = $tn; } # the last unkown command if( !defined( $hash->{READINGS}{lastunkowncmd}{VAL} ) ) { $hash->{READINGS}{lastunkowncmd}{VAL} = "none"; $hash->{READINGS}{lastunkowncmd}{TIME} = $tn; } # the last unkown IR command if( !defined( $hash->{READINGS}{lastir}{VAL} ) ) { $hash->{READINGS}{lastir}{VAL} = "?"; $hash->{READINGS}{lastir}{TIME} = $tn; } # the id of the alarm we create # CD 0015 deaktiviert # if( !defined( $hash->{READINGS}{alarmid1}{VAL} ) ) { # $hash->{READINGS}{alarmid1}{VAL} = "none"; # $hash->{READINGS}{alarmid1}{TIME} = $tn; # } # if( !defined( $hash->{READINGS}{alarmid2}{VAL} ) ) { # $hash->{READINGS}{alarmid2}{VAL} = "none"; # $hash->{READINGS}{alarmid2}{TIME} = $tn; # } # values according to standard if( !defined( $hash->{READINGS}{playStatus}{VAL} ) ) { $hash->{READINGS}{playStatus}{VAL} = "?"; $hash->{READINGS}{playStatus}{TIME} = $tn; } if( !defined( $hash->{READINGS}{currentArtist}{VAL} ) ) { $hash->{READINGS}{currentArtist}{VAL} = "?"; $hash->{READINGS}{currentArtist}{TIME} = $tn; } if( !defined( $hash->{READINGS}{currentAlbum}{VAL} ) ) { $hash->{READINGS}{currentAlbum}{VAL} = "?"; $hash->{READINGS}{currentAlbum}{TIME} = $tn; } if( !defined( $hash->{READINGS}{currentTitle}{VAL} ) ) { $hash->{READINGS}{currentTitle}{VAL} = "?"; $hash->{READINGS}{currentTitle}{TIME} = $tn; } if( !defined( $hash->{READINGS}{favorites}{VAL} ) ) { $hash->{READINGS}{favorites}{VAL} = "not"; $hash->{READINGS}{favorites}{TIME} = $tn; } if( !defined( $hash->{READINGS}{playlists}{VAL} ) ) { $hash->{READINGS}{playlists}{VAL} = "not"; $hash->{READINGS}{playlists}{TIME} = $tn; } # for the FHEM AV Development Guidelinses # we use this to store the currently playing ID to later on return to if( !defined( $hash->{READINGS}{currentMedia}{VAL} ) ) { $hash->{READINGS}{currentMedia}{VAL} = "?"; $hash->{READINGS}{currentMedia}{TIME} = $tn; } if( !defined( $hash->{READINGS}{currentPlaylistName}{VAL} ) ) { $hash->{READINGS}{currentPlaylistName}{VAL} = "?"; $hash->{READINGS}{currentPlaylistName}{TIME} = $tn; } if( !defined( $hash->{READINGS}{currentPlaylistUrl}{VAL} ) ) { $hash->{READINGS}{currentPlaylistUrl}{VAL} = "?"; $hash->{READINGS}{currentPlaylistUrl}{TIME} = $tn; } if( !defined( $hash->{READINGS}{volume}{VAL} ) ) { $hash->{READINGS}{volume}{VAL} = 0; $hash->{READINGS}{volume}{TIME} = $tn; } if( !defined( $hash->{READINGS}{volumeStraight}{VAL} ) ) { $hash->{READINGS}{volumeStraight}{VAL} = "?"; $hash->{READINGS}{volumeStraight}{TIME} = $tn; } if( !defined( $hash->{READINGS}{connected}{VAL} ) ) { $hash->{READINGS}{connected}{VAL} = "?"; $hash->{READINGS}{connected}{TIME} = $tn; } if( !defined( $hash->{READINGS}{signalstrength}{VAL} ) ) { $hash->{READINGS}{signalstrength}{VAL} = "?"; $hash->{READINGS}{currentTitle}{TIME} = $tn; } if( !defined( $hash->{READINGS}{shuffle}{VAL} ) ) { $hash->{READINGS}{shuffle}{VAL} = "?"; $hash->{READINGS}{currentTitle}{TIME} = $tn; } if( !defined( $hash->{READINGS}{repeat}{VAL} ) ) { $hash->{READINGS}{repeat}{VAL} = "?"; $hash->{READINGS}{currentTitle}{TIME} = $tn; } if( !defined( $hash->{READINGS}{state}{VAL} ) ) { $hash->{READINGS}{state}{VAL} = "?"; $hash->{READINGS}{state}{TIME} = $tn; } $hash->{helper}{ttsstate}=TTS_IDLE; # CD 0028 SB_PLAYER_LoadPlayerStates($hash) if($SB_PLAYER_hasDataDumper==1); # CD 0036 $hash->{helper}{playerStatusOK}=0; # CD 0042 $hash->{helper}{playerStatusOKCounter}=0; # CD 0042 # do and update of the status InternalTimer( gettimeofday() + 10, "SB_PLAYER_GetStatus", $hash, 0 ); return( undef ); } # CD 0002 start sub SB_PLAYER_tcb_QueryCoverArt($) { # CD 0014 Name geändert my($in ) = shift; my(undef,$name) = split(':',$in); my $hash = $defs{$name}; #Log 0,"delayed cover art query"; IOWrite( $hash, "$hash->{PLAYERMAC} status - 1 tags:Kcu\n" ); # CD 0030 u added to tags # CD 0005 query cover art for synced players if ($hash->{PLAYERMAC} eq $hash->{SYNCMASTER}) { if (defined($hash->{SYNCGROUP}) && ($hash->{SYNCGROUP} ne '?') && ($hash->{SYNCMASTER} ne 'none')) { # CD 0018 none hinzugefügt my @pl=split(",",$hash->{SYNCGROUP}); foreach (@pl) { IOWrite( $hash, "$_ status - 1 tags:Kcu\n" ); # CD 0039 u hinzugefügt } } } } # CD 0002 end # CD 0014 start sub SB_PLAYER_tcb_DeleteRecallPause($) { my($in ) = shift; my(undef,$name) = split(':',$in); my $hash = $defs{$name}; delete($hash->{helper}{recallPause}); } sub SB_PLAYER_QueryElapsedTime($) { my ($hash) = @_; if(!defined($hash->{helper}{lastTimeQuery})||($hash->{helper}{lastTimeQuery}{helper}{lastTimeQuery}, now: ".gettimeofday(); $hash->{helper}{lastTimeQuery}=gettimeofday(); IOWrite( $hash, "$hash->{PLAYERMAC} time ?\n" ); } } # CD 0014 end # CD 0028 start sub SB_PLAYER_tcb_TTSRestore( $ ) { my($in ) = shift; my(undef,$name) = split(':',$in); my $hash = $defs{$name}; # CD 0033 start if(defined($hash->{helper}{ttsqueue})) { SB_PLAYER_SetTTSState($hash,TTS_LOADPLAYLIST,0,0); SB_PLAYER_LoadTalk($hash); } else { # CD 0033 end if(!defined($hash->{helper}{ttsOptions}{nosaverestore})) { SB_PLAYER_SetTTSState($hash,TTS_RESTORE,0,0); SB_PLAYER_Recall( $hash, "xxTTSxx del" ); } else { SB_PLAYER_SetTTSState($hash,TTS_IDLE,0,1); } } } # CD 0028 end # ---------------------------------------------------------------------------- # called from the global dispatch if new data is available # ---------------------------------------------------------------------------- sub SB_PLAYER_Parse( $$ ) { my ( $iohash, $msg ) = @_; # we expect the data to be in the following format # xxxxxxxxxxxx cmd1 cmd2 cmd3 ... # where xxxxxxxxxxxx is derived from xx:xx:xx:xx:xx:xx # that needs to be done by the server Log3( $iohash, 5, "SB_PLAYER_Parse: called with $msg" ); # storing the last in an array is necessery, for tagged responses my ( $modtype, $id, @data ) = split(":", $msg, 3 ); Log3( $iohash, 5, "SB_PLAYER_Parse: type:$modtype, ID:$id CMD:@data" ); if( $modtype ne "SB_PLAYER" ) { # funny stuff happens at the disptach function Log3( $iohash, 5, "SB_PLAYER_Parse: wrong type given." ); } # let's see what we got. Split the data at the space # necessery, for tagged responses my @args = split( " ", join( " ", @data ) ); my $cmd = shift( @args ); my $hash = $modules{SB_PLAYER}{defptr}{$id}; if( !$hash ) { Log3( undef, 3, "SB_PLAYER Unknown device with ID $id, " . "please define it"); # do the autocreate; derive the unique id (MAC adress) my @playermac = ( $id =~ m/.{2}/g ); my $idbuf = join( ":", @playermac ); Log3( undef, 3, "SB_PLAYER Dervived the following MAC $idbuf " ); if( SB_PLAYER_IsValidMAC( $idbuf ) == 1 ) { # the MAC Adress is valid Log3( undef, 3, "SB_PLAYER_Parse: the unknown ID $id is a valid " . "MAC Adress" ); # this line supports autocreate return( "UNDEFINED SB_PLAYER_$id SB_PLAYER $idbuf" ); } else { # the MAC adress is not valid Log3( undef, 3, "SB_PLAYER_Parse: the unknown ID $id is NOT " . "a valid MAC Adress" ); return( undef ); } } # so the data is for us my $name = $hash->{NAME}; #return "" if(IsIgnored($name)); Log3( $hash, 5, "SB_PLAYER_Parse: $name CMD:$cmd ARGS:@args..." ); # what ever we have received, signal it $hash->{LASTANSWER} = "$cmd @args"; $hash->{helper}{ttsstate}=TTS_IDLE if(!defined($hash->{helper}{ttsstate})); # CD 0028 # signal the update to FHEM readingsBeginUpdate( $hash ); if( $cmd eq "mixer" ) { if( $args[ 0 ] eq "volume" ) { # update the volume if ($args[ 1 ] eq "?") { # it is a request } else { # CD 0040 start if( ( index( $args[ 1 ], "+" ) != -1 ) || ( index( $args[ 1 ], "-" ) != -1 ) ) { # that was a relative value. We do nothing and fire an update IOWrite( $hash, "$hash->{PLAYERMAC} mixer volume ?\n" ); } else { # CD 0040 end SB_PLAYER_UpdateVolumeReadings( $hash, $args[ 1 ], true ); # CD 0007 start if((defined($hash->{helper}{setSyncVolume}) && ($hash->{helper}{setSyncVolume} != $args[ 1 ]))|| (!defined($hash->{helper}{setSyncVolume}))) { SB_PLAYER_SetSyncedVolume($hash,$args[ 1 ]); } delete $hash->{helper}{setSyncVolume}; # CD 0007 end } } } } elsif( $cmd eq "remote" ) { if( defined( $args[ 0 ] ) ) { $hash->{ISREMOTESTREAM} = "$args[ 0 ]"; } else { $hash->{ISREMOTESTREAM} = "0"; } } elsif( $cmd eq "play" ) { if(!defined($hash->{helper}{recallPause})) { # CD 0014 readingsBulkUpdate( $hash, "playStatus", "playing" ); SB_PLAYER_Amplifier( $hash ); } # CD 0014 } elsif( $cmd eq "stop" ) { readingsBulkUpdate( $hash, "playStatus", "stopped" ); SB_PLAYER_Amplifier( $hash ); } elsif( $cmd eq "pause" ) { if((defined($args[ 0 ])) && ( $args[ 0 ] eq "0" )) { # CD 0028 check if $args[0] exists readingsBulkUpdate( $hash, "playStatus", "playing" ); SB_PLAYER_Amplifier( $hash ); } else { readingsBulkUpdate( $hash, "playStatus", "paused" ); SB_PLAYER_Amplifier( $hash ); } } elsif( $cmd eq "mode" ) { my $updateSyncedPlayers=0; # CD 0039 # alittle more complex to fulfill FHEM Development guidelines Log3( $hash, 5, "SB_PLAYER_Parse($name): mode:$cmd args:$args[0]" ); if( $args[ 0 ] eq "play" ) { # CD 0014 start if(defined($hash->{helper}{recallPause})) { IOWrite( $hash, "$hash->{PLAYERMAC} pause 1\n" ); } else { # CD 0014 end readingsBulkUpdate( $hash, "playStatus", "playing" ); SB_PLAYER_Amplifier( $hash ); SB_PLAYER_QueryElapsedTime( $hash ); # CD 0014 } # CD 0014 # CD 0029 start if(defined($hash->{helper}{ttsOptions}{logplay})) { Log3( $hash, 0, "SB_PLAYER_Parse: $name: mode play"); delete($hash->{helper}{ttsOptions}{logplay}); } # CD 0029 # CD 0028 start if($hash->{helper}{ttsstate}==TTS_WAITFORPLAY) { SB_PLAYER_SetTTSState($hash,TTS_PLAYING,1,0); } if(($hash->{helper}{ttsstate}==TTS_SYNCGROUPACTIVE) && ($hash->{SYNCMASTER} eq $hash->{PLAYERMAC})) { IOWrite( $hash, $hash->{helper}{ttsMaster} . " fhemrelay ttsplaying\n" ); } # CD 0028 end $updateSyncedPlayers=1; # CD 0039 gesyncte Player aktualisieren } elsif( $args[ 0 ] eq "stop" ) { # CD 0028 start if($hash->{helper}{ttsstate}==TTS_PLAYING) { SB_PLAYER_TTSStopped($hash); } # wenn tts auf Slave aktiv ist schickt der LMS den Stop nur an den Master if(($hash->{helper}{ttsstate}==TTS_SYNCGROUPACTIVE) && ($hash->{SYNCMASTER} eq $hash->{PLAYERMAC})) { if (defined($hash->{SYNCGROUP}) && ($hash->{SYNCGROUP} ne '?') && ($hash->{SYNCMASTER} ne 'none')) { my @pl=split(",",$hash->{SYNCGROUP}); foreach (@pl) { if ($hash->{PLAYERMAC} ne $_) { IOWrite( $hash, "$_ fhemrelay ttsstopped\n" ); } } } } # CD 0028 end readingsBulkUpdate( $hash, "playStatus", "stopped" ); SB_PLAYER_Amplifier( $hash ); $updateSyncedPlayers=1; # CD 0039 gesyncte Player aktualisieren } elsif( $args[ 0 ] eq "pause" ) { readingsBulkUpdate( $hash, "playStatus", "paused" ); SB_PLAYER_Amplifier( $hash ); $updateSyncedPlayers=1; # CD 0039 gesyncte Player aktualisieren } else { readingsBulkUpdate( $hash, "playStatus", $args[ 0 ] ); } # CD 0039 gesyncte Player aktualisieren if($updateSyncedPlayers==1) { if ($hash->{PLAYERMAC} eq $hash->{SYNCMASTER}) { if (defined($hash->{SYNCGROUP}) && ($hash->{SYNCGROUP} ne '?') && ($hash->{SYNCMASTER} ne 'none')) { my @pl=split(",",$hash->{SYNCGROUP}); foreach (@pl) { if ($hash->{PLAYERMAC} ne $_) { IOWrite( $hash, "$_ mode ?\n" ); } } } } } # CD 0039 end } elsif( $cmd eq "newmetadata" ) { # the song has changed, but we are easy and just ask the player # sending the requests causes endless loop #IOWrite( $hash, "$hash->{PLAYERMAC} artist ?\n" ); #IOWrite( $hash, "$hash->{PLAYERMAC} album ?\n" ); #IOWrite( $hash, "$hash->{PLAYERMAC} title ?\n" ); IOWrite( $hash, "$hash->{PLAYERMAC} remote ?\n" ); #IOWrite( $hash, "$hash->{PLAYERMAC} status 0 500 tags:Kc\n" ); #SB_PLAYER_CoverArt( $hash ); # CD 0026 deaktiviert } elsif( $cmd eq "playlist" ) { my $queryMode=1; # CD 0014 if( $args[ 0 ] eq "newsong" ) { # the song has changed, but we are easy and just ask the player IOWrite( $hash, "$hash->{PLAYERMAC} artist ?\n" ); IOWrite( $hash, "$hash->{PLAYERMAC} album ?\n" ); IOWrite( $hash, "$hash->{PLAYERMAC} title ?\n" ); # CD 0007 get playlist name IOWrite( $hash, "$hash->{PLAYERMAC} playlist name ?\n" ); # CD 0014 get duration and index IOWrite( $hash, "$hash->{PLAYERMAC} duration ?\n" ); IOWrite( $hash, "$hash->{PLAYERMAC} playlist index ?\n" ); IOWrite( $hash, "$hash->{PLAYERMAC} time ?\n" ); # CD 0002 Coverart anfordern, todo: Zeit variabel $hash->{helper}{CoverOk}=0; # CD 0026 added # CD 0027 changed # CD 0025 bei lokalen Playlisten schneller abfragen if( $hash->{ISREMOTESTREAM} eq "0" ) { InternalTimer( gettimeofday() + 3, "SB_PLAYER_tcb_QueryCoverArt", "QueryCoverArt:$name", 0 ); } else { InternalTimer( gettimeofday() + 10, "SB_PLAYER_tcb_QueryCoverArt", # CD 0014 Name geändert "QueryCoverArt:$name", # CD 0014 Name geändert 0 ); } # CD 0002 zu früh, CoverArt ist noch nicht verfügbar # SB_PLAYER_CoverArt( $hash ); # CD 0000 start - sync players in same group if ($hash->{PLAYERMAC} eq $hash->{SYNCMASTER}) { if (defined($hash->{SYNCGROUP}) && ($hash->{SYNCGROUP} ne '?') && ($hash->{SYNCMASTER} ne 'none')) { # CD 0018 none hinzugefügt my @pl=split(",",$hash->{SYNCGROUP}); foreach (@pl) { #Log 0,"SB_Player to sync: $_"; IOWrite( $hash, "$_ artist ?\n" ); IOWrite( $hash, "$_ album ?\n" ); IOWrite( $hash, "$_ title ?\n" ); # CD 0010 IOWrite( $hash, "$_ playlist name ?\n" ); # CD 0014 IOWrite( $hash, "$_ duration ?\n" ); IOWrite( $hash, "$_ playlist index ?\n" ); } } } # CD 0000 end # CD 0014 start if(defined($hash->{helper}{recallPause})) { IOWrite( $hash, "$hash->{PLAYERMAC} pause 1\n" ); RemoveInternalTimer( "recallPause:$name"); InternalTimer( gettimeofday() + 0.5, "SB_PLAYER_tcb_DeleteRecallPause", "recallPause:$name", 0 ); } # CD 0014 end # the id is in the last return. ID not reported for radio stations # so this will go wrong for e.g. Bayern 3 # if( $args[ $#args ] =~ /(^[0-9]{1,3})/g ) { # readingsBulkUpdate( $hash, "currentMedia", $1 ); # } } elsif( $args[ 0 ] eq "cant_open" ) { #TODO: needs to be handled # CD 0033 TTS abbrechen bei Fehler if($hash->{helper}{ttsstate}==TTS_WAITFORPLAY) { SB_PLAYER_TTSStopped($hash); } } elsif( $args[ 0 ] eq "open" ) { readingsBulkUpdate( $hash, "currentMedia", "$args[1]" ); SB_PLAYER_Amplifier( $hash ); SB_PLAYER_GetStatus( $hash ); # CD 0014 # $args[ 2 ] =~ /^(file:)(.*)/g; # if( defined( $2 ) ) { #readingsBulkUpdate( $hash, "currentMedia", $2 ); # } } elsif( $args[ 0 ] eq "repeat" ) { if( $args[ 1 ] eq "0" ) { readingsBulkUpdate( $hash, "repeat", "off" ); } elsif( $args[ 1 ] eq "1") { readingsBulkUpdate( $hash, "repeat", "one" ); } elsif( $args[ 1 ] eq "2") { readingsBulkUpdate( $hash, "repeat", "all" ); } else { readingsBulkUpdate( $hash, "repeat", "?" ); } # CD 0039 Änderung am Master, gesyncte Player aktualisieren if ($hash->{PLAYERMAC} eq $hash->{SYNCMASTER}) { if (defined($hash->{SYNCGROUP}) && ($hash->{SYNCGROUP} ne '?') && ($hash->{SYNCMASTER} ne 'none')) { my @pl=split(",",$hash->{SYNCGROUP}); foreach (@pl) { IOWrite( $hash, "$_ playlist repeat ?\n" ); } } } # CD 0039 end } elsif( $args[ 0 ] eq "shuffle" ) { if( $args[ 1 ] eq "0" ) { readingsBulkUpdate( $hash, "shuffle", "off" ); } elsif( $args[ 1 ] eq "1") { readingsBulkUpdate( $hash, "shuffle", "song" ); } elsif( $args[ 1 ] eq "2") { readingsBulkUpdate( $hash, "shuffle", "album" ); } else { readingsBulkUpdate( $hash, "shuffle", "?" ); } # CD 0039 Änderung am Master, gesyncte Player aktualisieren if ($hash->{PLAYERMAC} eq $hash->{SYNCMASTER}) { if (defined($hash->{SYNCGROUP}) && ($hash->{SYNCGROUP} ne '?') && ($hash->{SYNCMASTER} ne 'none')) { my @pl=split(",",$hash->{SYNCGROUP}); foreach (@pl) { IOWrite( $hash, "$_ playlist shuffle ?\n" ); } } } # CD 0039 end SB_PLAYER_GetStatus( $hash ); # CD 0014 } elsif( $args[ 0 ] eq "name" ) { # CD 0014 start $queryMode=0; if(!defined($args[ 1 ])) { readingsBulkUpdate( $hash, "currentPlaylistName","-"); readingsBulkUpdate( $hash, "playlists","-"); #$hash->{FAVSELECT} = '-'; # CD 0021 deaktiviert #readingsBulkUpdate( $hash, "$hash->{FAVSET}", '-' ); # CD 0021 deaktiviert } # CD 0014 end if(defined($args[ 1 ]) && ($args[ 1 ] ne '?')) { # CD 0009 check empty name - 0011 ignore '?' shift( @args ); readingsBulkUpdate( $hash, "currentPlaylistName", join( " ", @args ) ); my $pn=SB_SERVER_FavoritesName2UID(join( " ", @args )); # CD 0021 verschoben, decode hinzugefügt # CD 0023 decode entfernt # CD 0008 update playlists reading readingsBulkUpdate( $hash, "playlists", $pn); # CD 0021 $pn verwenden wegen Dropdown # join( "_", @args ) ); # CD 0021 deaktiviert # CD 0007 start - check if playlist == fav, 0014 removed debug info if( defined($hash->{helper}{SB_PLAYER_Favs}{$pn}) && defined($hash->{helper}{SB_PLAYER_Favs}{$pn}{ID})) { # CD 0011 check if defined($hash->{helper}{SB_PLAYER_Favs}{$pn}) $hash->{FAVSELECT} = $pn; readingsBulkUpdate( $hash, "$hash->{FAVSET}", "$pn" ); } else { $hash->{FAVSELECT} = '-'; # CD 0014 readingsBulkUpdate( $hash, "$hash->{FAVSET}", '-' ); # CD 0014 } # CD 0007 end } # CD 0009 start # CD 0021 start, update favorites if url matches } elsif( $args[ 0 ] eq "play" ) { if(defined($args[ 1 ])) { $args[ 1 ]=~s/\\/\//g; $hash->{FAVSELECT}="-"; foreach my $e ( keys %{$hash->{helper}{SB_PLAYER_Favs}} ) { if($args[ 1 ] eq $hash->{helper}{SB_PLAYER_Favs}{$e}{URL}) { $hash->{FAVSELECT} = $e; last; } } readingsBulkUpdate( $hash, "$hash->{FAVSET}", "$hash->{FAVSELECT}" ); # CD 0022 send to synced players # CD 0023 fixed if( $hash->{SYNCED} eq "yes") { if (defined($hash->{SYNCGROUP}) && ($hash->{SYNCGROUP} ne '?') && ($hash->{SYNCMASTER} ne 'none')) { my @pl=split(",",$hash->{SYNCGROUP}.",".$hash->{SYNCMASTER}); foreach (@pl) { if ($hash->{PLAYERMAC} ne $_) { IOWrite( $hash, "$_ fhemrelay favorites $hash->{FAVSELECT}\n" ); } } } } } # CD 0021 end } elsif( $args[ 0 ] eq "clear" ) { readingsBulkUpdate( $hash, "currentPlaylistName", "none" ); readingsBulkUpdate( $hash, "playlists", "none" ); # CD 0009 end SB_PLAYER_GetStatus( $hash ); # CD 0014 } elsif( $args[ 0 ] eq "url" ) { shift( @args ); readingsBulkUpdate( $hash, "currentPlaylistUrl", join( " ", @args ) ); } elsif( $args[ 0 ] eq "stop" ) { readingsBulkUpdate( $hash, "playStatus", "stopped" ); # CD 0012 'power off' durch 'playStatus stopped' ersetzt SB_PLAYER_Amplifier( $hash ); # CD 0014 start } elsif( $args[ 0 ] eq "index" ) { readingsBulkUpdate( $hash, "playlistCurrentTrack", $args[ 1 ]+1 ); $queryMode=0; } elsif( $args[ 0 ] eq "addtracks" ) { $queryMode=0; SB_PLAYER_GetStatus( $hash ); } elsif( $args[ 0 ] eq "delete" ) { $queryMode=0; #IOWrite( $hash, "$hash->{PLAYERMAC} alarm playlists 0 200\n" ); # CD 0016 get available elements for alarms # CD 0026 deaktiviert SB_PLAYER_GetStatus( $hash ); } elsif( $args[ 0 ] eq "load_done" ) { if($hash->{helper}{ttsstate}==TTS_PLAYING) { #IOWrite( $hash, "$hash->{PLAYERMAC} playlist index +0\n"); #IOWrite( $hash, "$hash->{PLAYERMAC} play\n" ); } if($hash->{helper}{ttsstate}==TTS_LOADPLAYLIST) { # CD 0030 start if(SB_PLAYER_GetTTSDelay($hash)>0) { RemoveInternalTimer( "TTSDelay:$name"); InternalTimer( gettimeofday() + SB_PLAYER_GetTTSDelay($hash), "SB_PLAYER_tcb_TTSDelay", "TTSDelay:$name", 0 ); SB_PLAYER_SetTTSState($hash,TTS_DELAY,1,0); } else { # CD 0030 end SB_PLAYER_SetTTSState($hash,TTS_WAITFORPLAY,1,0); IOWrite( $hash, "$hash->{PLAYERMAC} play\n" ); # CD 0038 Timeout hinzugefügt RemoveInternalTimer( "TimeoutTTSWaitForPlay:$name"); InternalTimer( gettimeofday() + 10.00, "SB_PLAYER_tcb_TimeoutTTSWaitForPlay", "TimeoutTTSWaitForPlay:$name", 0 ); # CD 0038 end } } # CD 0029 start if(defined($hash->{helper}{ttsOptions}{logloaddone})) { Log3( $hash, 0, "SB_PLAYER_Parse: $name: load_done"); delete($hash->{helper}{ttsOptions}{logloaddone}); } # CD 0029 end if(defined($hash->{helper}{recallPending})) { delete($hash->{helper}{recallPending}); SB_PLAYER_SetTTSState($hash,TTS_IDLE,1,1); IOWrite( $hash, "$hash->{PLAYERMAC} play 300\n" ); IOWrite( $hash, "$hash->{PLAYERMAC} time $hash->{helper}{savedPlayerState}{elapsedTime}\n" ); } } elsif( $args[ 0 ] eq "loadtracks" ) { if(defined($hash->{helper}{recallPending})) { delete($hash->{helper}{recallPending}); SB_PLAYER_SetTTSState($hash,TTS_IDLE,1,1); IOWrite( $hash, "$hash->{PLAYERMAC} play 300\n" ); IOWrite( $hash, "$hash->{PLAYERMAC} time $hash->{helper}{savedPlayerState}{elapsedTime}\n" ); } # CD 0014 end } else { } # check if this caused going to play, as not send automatically if(!defined($hash->{helper}{lastModeQuery})||($hash->{helper}{lastModeQuery} < gettimeofday()-0.05)) { # CD 0014 überflüssige Abfragen begrenzen IOWrite( $hash, "$hash->{PLAYERMAC} mode ?\n" ) if(!(defined($hash->{helper}{recallPending})||defined($hash->{helper}{recallPause})||($queryMode==0))); # CD 0014 if(... hinzugefügt $hash->{helper}{lastModeQuery} = gettimeofday(); # CD 0014 } # CD 0014 } elsif( $cmd eq "playlistcontrol" ) { #playlistcontrol cmd:load artist_id:22 count:4 } elsif( $cmd eq "connected" ) { readingsBulkUpdate( $hash, "connected", $args[ 0 ] ); readingsBulkUpdate( $hash, "presence", "present" ); } elsif( $cmd eq "name" ) { $hash->{PLAYERNAME} = join( " ", @args ); } elsif( $cmd eq "title" ) { readingsBulkUpdate( $hash, "currentTitle", join( " ", @args ) ); } elsif( $cmd eq "artist" ) { readingsBulkUpdate( $hash, "currentArtist", join( " ", @args ) ); } elsif( $cmd eq "album" ) { readingsBulkUpdate( $hash, "currentAlbum", join( " ", @args ) ); } elsif( $cmd eq "player" ) { if( $args[ 0 ] eq "model" ) { $hash->{MODEL} = $args[ 1 ]; } elsif( $args[ 0 ] eq "canpoweroff" ) { $hash->{CANPOWEROFF} = $args[ 1 ]; } elsif( $args[ 0 ] eq "ip" ) { $hash->{PLAYERIP} = "$args[ 1 ]"; if( defined( $args[ 2 ] ) ) { $hash->{PLAYERIP} .= ":$args[ 2 ]"; } } else { } } elsif( $cmd eq "power" ) { if( !( @args ) ) { # no arguments were send with the Power command # potentially this is a power toggle : should only happen # when called with SB CLI } elsif( $args[ 0 ] eq "1" ) { readingsBulkUpdate( $hash, "state", "on" ); readingsBulkUpdate( $hash, "power", "on" ); SB_PLAYER_Amplifier( $hash ); } elsif( $args[ 0 ] eq "0" ) { #readingsBulkUpdate( $hash, "presence", "absent" ); # CD 0013 deaktiviert, power sagt nichts über presence readingsBulkUpdate( $hash, "state", "off" ); readingsBulkUpdate( $hash, "power", "off" ); SB_PLAYER_Amplifier( $hash ); } else { # should be "?" normally } } elsif( $cmd eq "displaytype" ) { $hash->{DISPLAYTYPE} = $args[ 0 ]; } elsif( $cmd eq "signalstrength" ) { if( $args[ 0 ] eq "0" ) { readingsBulkUpdate( $hash, "signalstrength", "wired" ); } else { readingsBulkUpdate( $hash, "signalstrength", "$args[ 0 ]" ); } } elsif( $cmd eq "alarm" ) { if( $args[ 0 ] eq "sound" ) { # fired when an alarm goes off } elsif( $args[ 0 ] eq "end" ) { # fired when an alarm ends } elsif( $args[ 0 ] eq "snooze" ) { # fired when an alarm is snoozed by the user } elsif( $args[ 0 ] eq "snooze_end" ) { # fired when an alarm comes back from snooze } elsif( $args[ 0 ] eq "add" ) { # fired when an alarm has been added. # this setup goes wrong, when an alarm is defined manually # the last entry in the array shall contain th id my $idstr = $args[ $#args ]; if( $idstr =~ /^(id:)([0-9a-zA-Z\.]+)/g ) { #readingsBulkUpdate( $hash, "alarmid$hash->{LASTALARM}", $2 ); # CD 0015 deaktiviert } else { } #IOWrite( $hash, "$hash->{PLAYERMAC} alarm playlists 0 200\n" ) if (!defined($hash->{helper}{alarmPlaylists})); # CD 0015 get available elements for alarms CD 0016 nur wenn nicht vorhanden abfragen # CD 0026 wird über Server verteilt IOWrite( $hash, "$hash->{PLAYERMAC} alarms 0 200 tags:all filter:all\n" ); # CD 0015 update alarm list } elsif( $args[ 0 ] eq "_cmd" ) { #IOWrite( $hash, "$hash->{PLAYERMAC} alarm playlists 0 200\n" ); # CD 0015 get available elements for alarms CD 0016 deaktiviert, nicht nötig IOWrite( $hash, "$hash->{PLAYERMAC} alarms 0 200 tags:all filter:all\n" ); # CD 0015 filter added } elsif( $args[ 0 ] eq "update" ) { #IOWrite( $hash, "$hash->{PLAYERMAC} alarm playlists 0 200\n" ) if (!defined($hash->{helper}{alarmPlaylists})); # CD 0015 get available elements for alarms CD 0016 nur wenn nicht vorhanden abfragen # CD 0026 wird über Server verteilt IOWrite( $hash, "$hash->{PLAYERMAC} alarms 0 200 tags:all filter:all\n" ); # CD 0015 filter added } elsif( $args[ 0 ] eq "delete" ) { if(!defined($hash->{helper}{deleteAllAlarms})) { # CD 0015 do not query while deleting all alarms IOWrite( $hash, "$hash->{PLAYERMAC} alarms 0 200 tags:all filter:all\n" ); # CD 0015 filter added } # CD 0015 start # verfügbare Elemente für Alarme, zwischenspeichern für Anzeige # CD 0026 deaktiviert, kommt über Broadcast vom Server #} elsif( $args[ 0 ] eq "playlists" ) { # delete($hash->{helper}{alarmPlaylists}) if (defined($hash->{helper}{alarmPlaylists})); # my @r=split("category:",join(" ",@args)); # foreach my $a (@r){ # my $i1=index($a," title:"); # my $i2=index($a," url:"); # my $i3=index($a," singleton:"); # if (($i1!=-1)&&($i2!=-1)&&($i3!=-1)) { # my $url=substr($a,$i2+5,$i3-$i2-5); # $url=substr($a,$i1+7,$i2-$i1-7) if ($url eq ""); # my $pn=SB_SERVER_FavoritesName2UID(decode('utf-8',$url)); # CD 0021 decode hinzugefügt # $hash->{helper}{alarmPlaylists}{$pn}{category}=substr($a,0,$i1); # $hash->{helper}{alarmPlaylists}{$pn}{title}=substr($a,$i1+7,$i2-$i1-7); # $hash->{helper}{alarmPlaylists}{$pn}{url}=$url; # } # } # CD 0015 } else { } } elsif( $cmd eq "alarms" ) { delete($hash->{helper}{deleteAllAlarms}) if(defined($hash->{helper}{deleteAllAlarms})); # CD 0015 SB_PLAYER_ParseAlarms( $hash, @args ); } elsif( $cmd eq "showbriefly" ) { # to be ignored, we get two hashes } elsif( ($cmd eq "unknownir" ) || ($cmd eq "ir" ) ) { readingsBulkUpdate( $hash, "lastir", $args[ 0 ] ); } elsif( $cmd eq "status" ) { SB_PLAYER_ParsePlayerStatus( $hash, \@args ); } elsif( $cmd eq "client" ) { if( $args[ 0 ] eq "new" ) { # not to be handled here, should lead to a new FHEM Player } elsif( $args[ 0 ] eq "disconnect" ) { readingsBulkUpdate( $hash, "presence", "absent" ); readingsBulkUpdate( $hash, "state", "off" ); readingsBulkUpdate( $hash, "power", "off" ); SB_PLAYER_Amplifier( $hash ); # CD 0031 wenn Player während TTS verschwindet Zustand zurücksetzen if(($hash->{helper}{ttsstate}>TTS_IDLE)&&($hash->{helper}{ttsstate}{helper}{savedPlayerState}{power}="off" if(defined($hash->{helper}{savedPlayerState})); SB_PLAYER_SetTTSState($hash,TTS_STOP,1,0); RemoveInternalTimer( "TTSRestore:$name"); InternalTimer( gettimeofday() + 0.01, "SB_PLAYER_tcb_TTSRestore", "TTSRestore:$name", 0 ); } # CD 0031 end } elsif( $args[ 0 ] eq "reconnect" ) { IOWrite( $hash, "$hash->{PLAYERMAC} status 0 500 tags:Kcu\n" ); # CD 0030 u added to tags } else { } } elsif( $cmd eq "prefset" ) { if( $args[ 0 ] eq "server" ) { if( $args[ 1 ] eq "currentSong" ) { # readingsBulkUpdate( $hash, "currentMedia", $args[ 2 ] ); # CD 0014 deaktiviert } elsif( $args[ 1 ] eq "volume" ) { SB_PLAYER_UpdateVolumeReadings( $hash, $args[ 2 ], true ); # CD 0000 start - handle 'prefset power' message for synced players } elsif( $args[ 1 ] eq "power" ) { if( $args[ 2 ] eq "1" ) { #Log 0,"$name power on"; readingsBulkUpdate( $hash, "state", "on" ); readingsBulkUpdate( $hash, "power", "on" ); SB_PLAYER_Amplifier( $hash ); # CD 0038 start if($hash->{helper}{ttsstate}==TTS_WAITFORPOWERON) { # CD 0042 readingsBulkUpdate abwarten RemoveInternalTimer( "TTSStartAfterPowerOn:$name"); InternalTimer( gettimeofday() + 0.01, "SB_PLAYER_tcb_TTSStartAfterPowerOn", "TTSStartAfterPowerOn:$name", 0 ); } # CD 0038 end # CD 0030 send play only after power is on if(defined($hash->{helper}{playAfterPowerOn})) { IOWrite( $hash, "$hash->{PLAYERMAC} play ".$hash->{helper}{playAfterPowerOn}."\n" ); delete($hash->{helper}{playAfterPowerOn}); } # CD 0030 end } elsif( $args[ 2 ] eq "0" ) { #Log 0,"$name power off"; #readingsBulkUpdate( $hash, "presence", "absent" ); # CD 0013 deaktiviert, power sagt nichts über presence readingsBulkUpdate( $hash, "state", "off" ); readingsBulkUpdate( $hash, "power", "off" ); SB_PLAYER_Amplifier( $hash ); delete($hash->{helper}{playAfterPowerOn}) if(defined($hash->{helper}{playAfterPowerOn})); # CD 0030 # CD 0031 wenn Player während TTS ausgeschaltet wird nicht wieder einschalten if(($hash->{helper}{ttsstate}>TTS_IDLE)&&($hash->{helper}{ttsstate}{helper}{savedPlayerState}{power}="off" if(defined($hash->{helper}{savedPlayerState})); SB_PLAYER_SetTTSState($hash,TTS_STOP,1,0); RemoveInternalTimer( "TTSRestore:$name"); InternalTimer( gettimeofday() + 0.01, "SB_PLAYER_tcb_TTSRestore", "TTSRestore:$name", 0 ); } # CD 0031 end } # CD 0000 end # CD 0010 start prefset server mute } elsif( $args[ 1 ] eq "mute" ) { SB_PLAYER_SetSyncedVolume($hash, -1) if ($args[ 2 ] == 1); IOWrite( $hash, "$hash->{PLAYERMAC} mixer volume ?\n" ) if ($args[ 2 ] == 0); # CD 0010 end # CD 0016 start } elsif( $args[ 1 ] eq "alarmTimeoutSeconds" ) { readingsBulkUpdate( $hash, "alarmsTimeout", $args[ 2 ]/60 ); } elsif( $args[ 1 ] eq "alarmSnoozeSeconds" ) { readingsBulkUpdate( $hash, "alarmsSnooze", $args[ 2 ]/60 ); } elsif( $args[ 1 ] eq "alarmDefaultVolume" ) { readingsBulkUpdate( $hash, "alarmsDefaultVolume", $args[ 2 ]/60 ); } elsif( $args[ 1 ] eq "alarmfadeseconds" ) { if($args[ 2 ] eq "1") { readingsBulkUpdate( $hash, "alarmsFadeIn", "on" ); } else { readingsBulkUpdate( $hash, "alarmsFadeIn", "off" ); } # CD 0016 end # CD 0018 start } elsif( $args[ 1 ] eq "syncgroupid" ) { IOWrite( $hash, "$hash->{PLAYERMAC} status 0 500 tags:Kcu\n" ); # CD 0030 u added to tags # CD 0018 end # CD 0039 für gesyncte Player bei Änderung am Slave erhält der Master eine prefset Meldung, an alle Slaves weitergeben } elsif( $args[ 1 ] eq "repeat" ) { if( $args[ 2 ] eq "0" ) { readingsBulkUpdate( $hash, "repeat", "off" ); } elsif( $args[ 2 ] eq "1") { readingsBulkUpdate( $hash, "repeat", "one" ); } elsif( $args[ 2 ] eq "2") { readingsBulkUpdate( $hash, "repeat", "all" ); } else { readingsBulkUpdate( $hash, "repeat", "?" ); } if ($hash->{PLAYERMAC} eq $hash->{SYNCMASTER}) { if (defined($hash->{SYNCGROUP}) && ($hash->{SYNCGROUP} ne '?') && ($hash->{SYNCMASTER} ne 'none')) { my @pl=split(",",$hash->{SYNCGROUP}); if(@pl>1) { foreach (@pl) { IOWrite( $hash, "$_ playlist repeat ?\n" ); } } } } } elsif( $args[ 1 ] eq "shuffle" ) { if( $args[ 2 ] eq "0" ) { readingsBulkUpdate( $hash, "shuffle", "off" ); } elsif( $args[ 2 ] eq "1") { readingsBulkUpdate( $hash, "shuffle", "song" ); } elsif( $args[ 2 ] eq "2") { readingsBulkUpdate( $hash, "shuffle", "album" ); } else { readingsBulkUpdate( $hash, "shuffle", "?" ); } if ($hash->{PLAYERMAC} eq $hash->{SYNCMASTER}) { if (defined($hash->{SYNCGROUP}) && ($hash->{SYNCGROUP} ne '?') && ($hash->{SYNCMASTER} ne 'none')) { my @pl=split(",",$hash->{SYNCGROUP}); if(@pl>1) { foreach (@pl) { IOWrite( $hash, "$_ playlist shuffle ?\n" ); } } } } SB_PLAYER_GetStatus( $hash ); # CD 0039 end } } else { readingsBulkUpdate( $hash, "lastunkowncmd", $cmd . " " . join( " ", @args ) ); } # CD 0007 start } elsif( $cmd eq "playerpref" ) { if( $args[ 0 ] eq "syncVolume" ) { if (defined($args[1])) { $hash->{SYNCVOLUME}=$args[1]; my $sva=AttrVal($hash->{NAME}, "syncVolume", undef); # force attribute if (defined($sva)) { IOWrite( $hash, "$hash->{PLAYERMAC} playerpref syncVolume 0\n" ) if(($sva ne "1") && ($args[1] ne "0")); IOWrite( $hash, "$hash->{PLAYERMAC} playerpref syncVolume 1\n" ) if(($sva eq "1") && ($args[1] ne "1")); } } } # CD 0007 end # CD 0016 start, von MM übernommen, Namen Readings geändert elsif( $args[ 0 ] eq "alarmsEnabled" ) { if (defined($args[1])) { if( $args[1] eq "1" ) { readingsBulkUpdate( $hash, "alarmsEnabled", "on" ); # CD 0016 Internal durch Reading ersetzt # CD 0017 'yes' durch 'on' ersetzt } else { readingsBulkUpdate( $hash, "alarmsEnabled", "off" ); # CD 0016 Internal durch Reading ersetzt # CD 0017 'no' durch 'off' ersetzt } } } elsif( $args[ 0 ] eq "alarmDefaultVolume" ) { if (defined($args[1]) && ($args[1] ne "?")) { # CD 0016 Rückmeldung auf Anfrage ignorieren #$hash->{ALARMSVOLUME} = $args[1]; # CD 0016 nicht benötigt readingsBulkUpdate( $hash, "alarmsDefaultVolume", $args[ 1 ] ); } } elsif( $args[ 0 ] eq "alarmTimeoutSeconds" ) { if (defined($args[1]) && ($args[1] ne "?")) { # CD 0016 Rückmeldung auf Anfrage ignorieren #$hash->{ALARMSTIMEOUT} = $args[1]/60 . " min"; # CD 0016 nicht benötigt readingsBulkUpdate( $hash, "alarmsTimeout", $args[ 1 ]/60 ); } } elsif( $args[ 0 ] eq "alarmSnoozeSeconds" ) { if (defined($args[1]) && ($args[1] ne "?")) { # CD 0016 Rückmeldung auf Anfrage ignorieren #$hash->{ALARMSSNOOZE} = $args[1]/60 . " min"; # CD 0016 nicht benötigt readingsBulkUpdate( $hash, "alarmsSnooze", $args[ 1 ]/60 ); } } # CD 0016 end # CD 0014 start } elsif( $cmd eq "duration" ) { readingsBulkUpdate( $hash, "duration", $args[ 0 ] ); } elsif( $cmd eq "time" ) { $hash->{helper}{elapsedTime}{VAL}=$args[ 0 ]; $hash->{helper}{elapsedTime}{TS}=gettimeofday(); delete($hash->{helper}{saveLocked}) if (($hash->{helper}{ttsstate}==TTS_IDLE) && defined($hash->{helper}{saveLocked})); } elsif( $cmd eq "playlist_tracks" ) { readingsBulkUpdate( $hash, "playlistTracks", $args[ 0 ] ); # CD 0014 end # CD 0018 sync Meldungen auswerten, alle anderen Player abfragen } elsif( $cmd eq "sync" ) { foreach my $e ( keys %{$hash->{helper}{SB_PLAYER_SyncMasters}} ) { IOWrite( $hash, $hash->{helper}{SB_PLAYER_SyncMasters}{$e}{MAC}." status 0 500 tags:Kcu\n" ); # CD 0039 u hinzugefügt } # CD 0018 # CD 0022 fhemrelay ist keine Meldung des LMS sondern eine Info die von einem anderen Player über 98_SB_PLAYER kommt } elsif( $cmd eq "fhemrelay" ) { if (defined($args[0])) { # CD 0022 Favoriten vom Sync-Master übernehmen if ($args[0] eq "favorites") { if (defined($args[1])) { $hash->{FAVSELECT} = $args[1]; readingsBulkUpdate( $hash, "$hash->{FAVSET}", "$hash->{FAVSELECT}" ); } } # CD 0028 tts aktiv elsif ($args[0] eq "ttsactive") { $hash->{helper}{ttsMaster}=$args[1]; Log3( $hash, defined($hash->{helper}{ttsOptions}{debug})?0:6, "SB_PLAYER_Parse: $name: fhemrelay ttsactive ".$hash->{helper}{ttsMaster} ); SB_PLAYER_SetTTSState($hash,TTS_SYNCGROUPACTIVE,1,0); # CD 0031 Lautstärke setzen if(!defined($hash->{SYNCVOLUME}) || ($hash->{SYNCVOLUME}==0)) { if(defined($hash->{helper}{ttsVolume})) { $hash->{helper}{ttsRestoreVolumeAfterStop}=ReadingsVal($name,"volumeStraight","?"); my $vol=$hash->{helper}{ttsVolume}; $vol=AttrVal( $name, "volumeLimit", 100 ) if(( $hash->{helper}{ttsVolume} > AttrVal( $name, "volumeLimit", 100 ) )&&!defined($hash->{helper}{ttsOptions}{ignorevolumelimit})); IOWrite( $hash, "$hash->{PLAYERMAC} mixer volume ".$vol."\n" ); } } # CD 0031 end } elsif ($args[0] eq "ttsstopped") { Log3( $hash, defined($hash->{helper}{ttsOptions}{debug})?0:6, "SB_PLAYER_Parse: $name: fhemrelay ttsstopped" ); if($hash->{helper}{ttsstate}==TTS_PLAYING) { # CD 0034 delay ttsstopped RemoveInternalTimer( "TTSStopped:$name"); InternalTimer( gettimeofday() + 0.01, "SB_PLAYER_tcb_TTSStopped", "TTSRestore:$name", 0 ); } } elsif ($args[0] eq "ttsplaying") { Log3( $hash, defined($hash->{helper}{ttsOptions}{debug})?0:6, "SB_PLAYER_Parse: $name: fhemrelay ttsplaying" ); if($hash->{helper}{ttsstate}==TTS_WAITFORPLAY) { SB_PLAYER_SetTTSState($hash,TTS_PLAYING,1,0); } } elsif ($args[0] eq "ttsidle") { Log3( $hash, defined($hash->{helper}{ttsOptions}{debug})?0:6, "SB_PLAYER_Parse: $name: fhemrelay ttsidle" ); SB_PLAYER_SetTTSState($hash,TTS_IDLE,1,0); # CD 0030 start if(defined($hash->{helper}{ttspoweroffafterstop})) { IOWrite( $hash, "$hash->{PLAYERMAC} power 0\n" ); delete($hash->{helper}{ttspoweroffafterstop}); } # CD 0030 end # CD 0031 Lautstärke zurücksetzen if(defined($hash->{helper}{ttsRestoreVolumeAfterStop})) { IOWrite( $hash, "$hash->{PLAYERMAC} mixer volume ".($hash->{helper}{ttsRestoreVolumeAfterStop})."\n" ); delete($hash->{helper}{ttsRestoreVolumeAfterStop}); } # CD 0031 end } elsif ($args[0] eq "ttsadd") { Log3( $hash, defined($hash->{helper}{ttsOptions}{debug})?0:6, "SB_PLAYER_Parse: $name: fhemrelay ttsadd $args[1]" ); push(@{$hash->{helper}{ttsqueue}},$args[1]); } # CD 0030 start elsif ($args[0] eq "ttsforcegroupon") { Log3( $hash, defined($hash->{helper}{ttsOptions}{debug})?0:6, "SB_PLAYER_Parse: $name: fhemrelay ttsforcegroupon" ); if( $hash->{CANPOWEROFF} ne "0" ) { IOWrite( $hash, "$hash->{PLAYERMAC} power 1\n" ); $hash->{helper}{ttspoweroffafterstop}=1; } } # CD 0030 end } # CD 0022 end } elsif( $cmd eq "NONE" ) { # we shall never end up here, as cmd=NONE is used by the server for # autocreate } else { # unkown command, we push it to the last command thingy readingsBulkUpdate( $hash, "lastunkowncmd", $cmd . " " . join( " ", @args ) ); } # and signal the end of the readings update if( AttrVal( $name, "donotnotify", "false" ) eq "true" ) { readingsEndUpdate( $hash, 0 ); } else { readingsEndUpdate( $hash, 1 ); } Log3( $hash, 5, "SB_PLAYER_Parse: $name: leaving" ); return( $name ); } # CD 0030 # ---------------------------------------------------------------------------- # delay TTS # ---------------------------------------------------------------------------- sub SB_PLAYER_tcb_TTSDelay( $ ) { my($in ) = shift; my(undef,$name) = split(':',$in); my $hash = $defs{$name}; SB_PLAYER_SetTTSState($hash,TTS_WAITFORPLAY,0,0); IOWrite( $hash, "$hash->{PLAYERMAC} play\n" ); # CD 0038 Timeout hinzugefügt RemoveInternalTimer( "TimeoutTTSWaitForPlay:$name"); InternalTimer( gettimeofday() + 10.00, "SB_PLAYER_tcb_TimeoutTTSWaitForPlay", "TimeoutTTSWaitForPlay:$name", 0 ); # CD 0038 end } # CD 0030 # CD 0038 # ---------------------------------------------------------------------------- # TTS 'wait for play' timeout # ---------------------------------------------------------------------------- sub SB_PLAYER_tcb_TimeoutTTSWaitForPlay( $ ) { my($in ) = shift; my(undef,$name) = split(':',$in); my $hash = $defs{$name}; if($hash->{helper}{ttsstate}==TTS_WAITFORPLAY) { readingsBeginUpdate( $hash ); SB_PLAYER_TTSStopped($hash); if( AttrVal( $name, "donotnotify", "false" ) eq "true" ) { readingsEndUpdate( $hash, 0 ); } else { readingsEndUpdate( $hash, 1 ); } } } # ---------------------------------------------------------------------------- # TTS 'wait for power on' timeout # ---------------------------------------------------------------------------- sub SB_PLAYER_tcb_TimeoutTTSWaitForPowerOn( $ ) { my($in ) = shift; my(undef,$name) = split(':',$in); my $hash = $defs{$name}; if($hash->{helper}{ttsstate}==TTS_WAITFORPOWERON) { SB_PLAYER_SetTTSState($hash,TTS_IDLE,0,0); } } # CD 0038 # CD 0034 # ---------------------------------------------------------------------------- # delay ttsstopped # ---------------------------------------------------------------------------- sub SB_PLAYER_tcb_TTSStopped( $ ) { my($in ) = shift; my(undef,$name) = split(':',$in); my $hash = $defs{$name}; readingsBeginUpdate( $hash ); SB_PLAYER_TTSStopped($hash); if( AttrVal( $name, "donotnotify", "false" ) eq "true" ) { readingsEndUpdate( $hash, 0 ); } else { readingsEndUpdate( $hash, 1 ); } } # CD 0034 end # CD 0042 # ---------------------------------------------------------------------------- # start tts after power on, wait if startup is not complete # ---------------------------------------------------------------------------- sub SB_PLAYER_tcb_TTSStartAfterPowerOn( $ ) { my($in ) = shift; my(undef,$name) = split(':',$in); my $hash = $defs{$name}; if(($hash->{helper}{playerStatusOK}==1)||($hash->{helper}{playerStatusOKCounter}>10)) { SB_PLAYER_PrepareTalk($hash); SB_PLAYER_LoadTalk($hash); } else { InternalTimer( gettimeofday() + 1, "SB_PLAYER_tcb_TTSStartAfterPowerOn", "TTSStartAfterPowerOn:$name", 0 ); $hash->{helper}{playerStatusOKCounter}++; } } # CD 0042 end # ---------------------------------------------------------------------------- # called when talk is stopped, check if there are queued elements # ---------------------------------------------------------------------------- sub SB_PLAYER_TTSStopped($) { # readingsBulkUpdate muss aktiv sein my ($hash) = @_; my $name = $hash->{NAME}; if(defined($hash->{helper}{ttsqueue})) { SB_PLAYER_SetTTSState($hash,TTS_LOADPLAYLIST,1,0); SB_PLAYER_LoadTalk($hash); # CD 0033 } else { SB_PLAYER_SetTTSState($hash,TTS_STOP,1,0); RemoveInternalTimer( "TTSRestore:$name"); InternalTimer( gettimeofday() + 0.01, "SB_PLAYER_tcb_TTSRestore", "TTSRestore:$name", 0 ); } } # ---------------------------------------------------------------------------- # Undefinition of an SB_PLAYER # called when undefining (delete) and element # ---------------------------------------------------------------------------- sub SB_PLAYER_Undef( $$$ ) { my ($hash, $arg) = @_; Log3( $hash, 5, "SB_PLAYER_Undef: called" ); RemoveInternalTimer( $hash ); # to be reviewed if that works. # check for uc() # what is $hash->{DEF}? delete $modules{SB_PLAYER}{defptr}{uc($hash->{DEF})}; return( undef ); } # ---------------------------------------------------------------------------- # Shutdown function - called before fhem shuts down # ---------------------------------------------------------------------------- sub SB_PLAYER_Shutdown( $$ ) { my ($hash, $dev) = @_; RemoveInternalTimer( $hash ); Log3( $hash, 5, "SB_PLAYER_Shutdown: called" ); return( undef ); } # ---------------------------------------------------------------------------- # Get of a module # called upon get cmd, arg1, arg2, .... # ---------------------------------------------------------------------------- sub SB_PLAYER_Get( $@ ) { my ($hash, @a) = @_; my $name = $hash->{NAME}; Log3( $hash, 4, "SB_PLAYER_Get: called with @a" ); if( @a < 2 ) { my $msg = "SB_PLAYER_Get: $name: wrong number of arguments"; Log3( $hash, 5, $msg ); return( $msg ); } #my $name = shift( @a ); shift( @a ); # name my $cmd = shift( @a ); if( $cmd eq "?" ) { my $res = "Unknown argument ?, choose one of " . "volume " . $hash->{FAVSET} . " savedStates "; return( $res ); } elsif( ( $cmd eq "volume" ) || ( $cmd eq "volumeStraight" ) ) { return( scalar( ReadingsVal( "$name", "volumeStraight", 25 ) ) ); } elsif( $cmd eq $hash->{FAVSET} ) { return( "$hash->{FAVSELECT}" ); # CD 0036 start } elsif( $cmd eq 'savedStates' ) { my $out=""; if (defined($hash->{helper}{savedPlayerState})) { foreach my $pl ( keys %{$hash->{helper}{savedPlayerState}} ) { $out.=$pl."\n" unless ($pl=~/xxTTSxx/); } } return( $out ); # CD 0036 end } else { my $msg = "SB_PLAYER_Get: $name: unkown argument"; Log3( $hash, 5, $msg ); return( $msg ); } return( undef ); } # CD 0030 start # ---------------------------------------------------------------------------- # Calculate delay for TTS # ---------------------------------------------------------------------------- sub SB_PLAYER_GetTTSDelay( $ ) { my ( $hash ) = @_; my $name = $hash->{NAME}; # todo synced players if(defined($hash->{helper}{ttsDelay})) { if(ReadingsVal($name,"power","x") eq "on") { return $hash->{helper}{ttsDelay}{PowerIsOn} } else { return $hash->{helper}{ttsDelay}{PowerIsOff} } } else { return 0; } } # CD 0030 end # CD 0033 start # ---------------------------------------------------------------------------- # called after Text2Speech has finished, start talk # ---------------------------------------------------------------------------- sub SB_PLAYER_tcb_StartT2STalk( $ ) { my($in ) = shift; my(undef,$name) = split(':',$in); my $hash = $defs{$name}; if($hash->{helper}{ttsstate}==TTS_TEXT2SPEECH_ACTIVE) { # talk ist nicht aktiv SB_PLAYER_PrepareTalk($hash); } SB_PLAYER_LoadTalk($hash); } # CD 0033 end # ---------------------------------------------------------------------------- # the Notify function # ---------------------------------------------------------------------------- sub SB_PLAYER_Notify( $$ ) { my ( $hash, $dev_hash ) = @_; my $name = $hash->{NAME}; # own name / hash my $devName = $dev_hash->{NAME}; # Device that created the events if ($dev_hash->{NAME} eq "global" && grep (m/^INITIALIZED$|^REREADCFG$/,@{$dev_hash->{CHANGED}})){ } # CD 0036 start if( grep(m/^SAVE$/, @{$dev_hash->{CHANGED}}) ) { SB_PLAYER_SavePlayerStates($hash) if($SB_PLAYER_hasDataDumper==1); } # CD 0036 end # CD 0033 start if(defined($hash->{helper}{text2speech}{name}) && ($hash->{helper}{text2speech}{name} eq $devName)) { $hash->{helper}{ttsExtstate}=TTS_IDLE if(!defined($hash->{helper}{ttsExtstate})); if(($hash->{helper}{ttsstate}==TTS_TEXT2SPEECH_ACTIVE)||($hash->{helper}{ttsExtstate}==TTS_EXT_TEXT2SPEECH_ACTIVE)) { foreach my $line (@{$dev_hash->{CHANGED}}) { my @args=split(' ',$line); if ($args[0] eq $name) { if($args[1] eq "ttsadd") { push(@{$hash->{helper}{ttsqueue}},$hash->{helper}{text2speech}{pathPrefix}.$args[2]); } elsif($args[1] eq 'ttsdone') { RemoveInternalTimer( "StartTalk:$name"); InternalTimer( gettimeofday() + 0.01, "SB_PLAYER_tcb_StartT2STalk", "StartTalk:$name", 0 ); } } } } elsif (($hash->{helper}{ttsstate}==TTS_TEXT2SPEECH_BUSY)||($hash->{helper}{ttsExtstate}==TTS_EXT_TEXT2SPEECH_BUSY)) { # versuchen Text2Speech zu belegen if(defined($dev_hash->{helper}{SB_PLAYER}) || (defined($dev_hash->{helper}{Text2Speech}) && @{$dev_hash->{helper}{Text2Speech}} > 0)) { # zu spät, weiter warten } else { $dev_hash->{helper}{SB_PLAYER}=$name; if($hash->{helper}{ttsstate}==TTS_TEXT2SPEECH_BUSY) { SB_PLAYER_SetTTSState($hash,TTS_TEXT2SPEECH_ACTIVE,0,0); } else { $hash->{helper}{ttsExtstate}=TTS_EXT_TEXT2SPEECH_ACTIVE; } fhem("set $devName tts ".($hash->{helper}{text2speech}{text})); delete($hash->{helper}{text2speech}{text}); } } } # CD 0033 end } # ---------------------------------------------------------------------------- # Set of a module # called upon set cmd, arg1, arg2, .... # ---------------------------------------------------------------------------- sub SB_PLAYER_Set( $@ ) { my ( $hash, $name, $cmd, @arg ) = @_; #my $name = $hash->{NAME}; Log3( $hash, 5, "SB_PLAYER_Set: called with $cmd"); # check if we have received a command if( !defined( $cmd ) ) { my $msg = "$name: set needs at least one parameter"; Log3( $hash, 3, $msg ); return( $msg ); } # now parse the commands if( $cmd eq "?" ) { # this one should give us a drop down list my $res = "Unknown argument ?, choose one of " . "on off stop:noArg play:noArg pause:noArg " . "save " . # CD 0014 # CD 0036 removed :noArg "volume:slider,0,1,100 " . "volumeStraight:slider,0,1,100 " . "volumeUp:noArg volumeDown:noArg " . "mute:noArg repeat:off,one,all show statusRequest:noArg " . "shuffle:off,on,song,album next:noArg prev:noArg playlist sleep " . # CD 0017 song und album hinzugefügt "allalarms:enable,disable,statusRequest,delete,add " . # CD 0015 alarm1 alarm2 entfernt "alarmsSnooze:slider,0,1,30 alarmsTimeout:slider,0,5,90 alarmsDefaultVolume:slider,0,1,100 alarmsFadeIn:on,off alarmsEnabled:on,off " . # CD 0016, von MM übernommen, Namen geändert "cliraw talk sayText " . # CD 0014 sayText hinzugefügt "unsync:noArg " . "resetTTS:noArg "; # CD 0028 hinzugefügt # add the favorites $res .= $hash->{FAVSET} . ":-," . $hash->{FAVSTR} . " "; # CD 0014 '-' hinzugefügt # add the syncmasters $res .= "sync:" . $hash->{SYNCMASTERS} . " "; # add the playlists $res .= "playlists:-," . $hash->{SERVERPLAYLISTS} . " "; # CD 0014 '-' hinzugefügt # add player saved lists # CD 0036 my $out=""; if (defined($hash->{helper}{savedPlayerState})) { foreach my $pl ( keys %{$hash->{helper}{savedPlayerState}} ) { $out.=$pl."," unless ($pl=~/xxTTSxx/); } $out=~s/,$//; } $res .= "recall:$out "; # CD 0016 start {ALARMSCOUNT} verschieben nach reload if (defined($hash->{ALARMSCOUNT})) { $hash->{helper}{ALARMSCOUNT}=$hash->{ALARMSCOUNT}; delete($hash->{ALARMSCOUNT}); } # CD 0016 end # CD 0015 - add the alarms if (defined($hash->{helper}{ALARMSCOUNT})&&($hash->{helper}{ALARMSCOUNT}>0)) { # CD 0016 ALARMSCOUNT nach {helper} verschoben for(my $i=1;$i<=$hash->{helper}{ALARMSCOUNT};$i++) { # CD 0016 ALARMSCOUNT nach {helper} verschoben $res .="alarm$i "; } } return( $res ); } # CD 0038 Befehle ignorieren wenn Player nicht vorhanden ist # CD 0040 wieder deaktiviert # if(ReadingsVal($name,"presence","x") ne "present") { # return "$name: player is not available"; # } # CD 0038 end my $updateReadingsOnSet=AttrVal($name, "updateReadingsOnSet", false); # CD 0017 my $donotnotify=AttrVal($name, "donotnotify", "true"); # CD 0017 # CD 0028 added " # as we have some other command, we need to turn on the server #if( AttrVal( $name, "serverautoon", "true" ) eq "true" ) { #SB_PLAYER_ServerTurnOn( $hash ); #} if( ( $cmd eq "Stop" ) || ( $cmd eq "STOP" ) || ( $cmd eq "stop" ) ) { IOWrite( $hash, "$hash->{PLAYERMAC} stop\n" ); } elsif( ( $cmd eq "Play" ) || ( $cmd eq "PLAY" ) || ( $cmd eq "play" ) ) { my $secbuf = AttrVal( $name, "fadeinsecs", 10 ); # CD 0030 wait until power on if(ReadingsVal($name,"power","x") eq "on") { IOWrite( $hash, "$hash->{PLAYERMAC} play $secbuf\n" ); } else { $hash->{helper}{playAfterPowerOn}=$secbuf; IOWrite( $hash, "$hash->{PLAYERMAC} power 1\n" ); } # CD 0030 end } elsif( ( $cmd eq "Pause" ) || ( $cmd eq "PAUSE" ) || ( $cmd eq "pause" ) ) { my $secbuf = AttrVal( $name, "fadeinsecs", 10 ); if( @arg == 1 ) { if( $arg[ 0 ] eq "1" ) { # pause the player IOWrite( $hash, "$hash->{PLAYERMAC} pause 1 $secbuf\n" ); } else { # unpause the player IOWrite( $hash, "$hash->{PLAYERMAC} pause 0 $secbuf\n" ); } } else { IOWrite( $hash, "$hash->{PLAYERMAC} pause $secbuf\n" ); } } elsif( ( $cmd eq "next" ) || ( $cmd eq "NEXT" ) || ( $cmd eq "Next" ) || ( $cmd eq "channelUp" ) || ( $cmd eq "CHANNELUP" ) ) { IOWrite( $hash, "$hash->{PLAYERMAC} playlist jump %2B1\n" ); } elsif( ( $cmd eq "prev" ) || ( $cmd eq "PREV" ) || ( $cmd eq "Prev" ) || ( $cmd eq "channelDown" ) || ( $cmd eq "CHANNELDOWN" ) ) { IOWrite( $hash, "$hash->{PLAYERMAC} playlist jump %2D1\n" ); } elsif( ( $cmd eq "volume" ) || ( $cmd eq "VOLUME" ) || ( $cmd eq "Volume" ) ||( $cmd eq "volumeStraight" ) ) { if(( @arg != 1 )&&( @arg != 2 )) { my $msg = "SB_PLAYER_Set: no arguments for Vol given."; Log3( $hash, 3, $msg ); return( $msg ); } # set the volume to the desired level. Needs to be 0..100 # no error checking here, as the server does this if( $arg[ 0 ] <= AttrVal( $name, "volumeLimit", 100 ) ) { IOWrite( $hash, "$hash->{PLAYERMAC} mixer volume $arg[ 0 ]\n" ); # CD 0007 SB_PLAYER_SetSyncedVolume($hash,$arg[0]) if (!defined($arg[1])); } else { IOWrite( $hash, "$hash->{PLAYERMAC} mixer volume " . AttrVal( $name, "volumeLimit", 50 ) . "\n" ); # CD 0007 SB_PLAYER_SetSyncedVolume($hash,AttrVal( $name, "volumeLimit", 50 )) if (!defined($arg[1])); } } elsif( $cmd eq $hash->{FAVSET} ) { if ($arg[0] ne '-') { # CD 0014 if( defined( $hash->{helper}{SB_PLAYER_Favs}{$arg[0]}{ID} ) ) { my $fid = $hash->{helper}{SB_PLAYER_Favs}{$arg[0]}{ID}; IOWrite( $hash, "$hash->{PLAYERMAC} favorites playlist " . "play item_id:$fid\n" ); $hash->{FAVSELECT} = $arg[ 0 ]; readingsSingleUpdate( $hash, "$hash->{FAVSET}", "$arg[ 0 ]", 1 ); # SB_PLAYER_GetStatus( $hash ); # CD 0021 deaktiviert, zu früh } } # CD 0014 } elsif( ( $cmd eq "volumeUp" ) || ( $cmd eq "VOLUMEUP" ) || ( $cmd eq "VolumeUp" ) ) { # increase volume if( ( ReadingsVal( $name, "volumeStraight", 50 ) + AttrVal( $name, "volumeStep", 10 ) ) <= AttrVal( $name, "volumeLimit", 100 ) ) { my $volstr = sprintf( "+%02d", AttrVal( $name, "volumeStep", 10 ) ); IOWrite( $hash, "$hash->{PLAYERMAC} mixer volume $volstr\n" ); # CD 0007 SB_PLAYER_SetSyncedVolume($hash,$volstr); } else { IOWrite( $hash, "$hash->{PLAYERMAC} mixer volume " . AttrVal( $name, "volumeLimit", 50 ) . "\n" ); # CD 0007 SB_PLAYER_SetSyncedVolume($hash,AttrVal( $name, "volumeLimit", 50 )); } } elsif( ( $cmd eq "volumeDown" ) || ( $cmd eq "VOLUMEDOWN" ) || ( $cmd eq "VolumeDown" ) ) { my $volstr = sprintf( "-%02d", AttrVal( $name, "volumeStep", 10 ) ); IOWrite( $hash, "$hash->{PLAYERMAC} mixer volume $volstr\n" ); # CD 0007 SB_PLAYER_SetSyncedVolume($hash,$volstr); } elsif( ( $cmd eq "mute" ) || ( $cmd eq "MUTE" ) || ( $cmd eq "Mute" ) ) { IOWrite( $hash, "$hash->{PLAYERMAC} mixer muting toggle\n" ); } elsif( $cmd eq "on" ) { if( $hash->{CANPOWEROFF} eq "0" ) { IOWrite( $hash, "$hash->{PLAYERMAC} play\n" ); } else { IOWrite( $hash, "$hash->{PLAYERMAC} power 1\n" ); } } elsif( $cmd eq "off" ) { # off command to go here if( $hash->{CANPOWEROFF} eq "0" ) { IOWrite( $hash, "$hash->{PLAYERMAC} stop\n" ); } else { IOWrite( $hash, "$hash->{PLAYERMAC} power 0\n" ); } } elsif( ( $cmd eq "repeat" ) || ( $cmd eq "REPEAT" ) || ( $cmd eq "Repeat" ) ) { if( @arg != 1 ) { my $msg = "SB_PLAYER_Set: no arguments for repeat given."; Log3( $hash, 3, $msg ); return( $msg ); } if( $arg[ 0 ] eq "off" ) { IOWrite( $hash, "$hash->{PLAYERMAC} playlist repeat 0\n" ); readingsSingleUpdate( $hash, "repeat", "off", $donotnotify ) if($updateReadingsOnSet); # CD 0017 } elsif( $arg[ 0 ] eq "one" ) { IOWrite( $hash, "$hash->{PLAYERMAC} playlist repeat 1\n" ); readingsSingleUpdate( $hash, "repeat", "one", $donotnotify ) if($updateReadingsOnSet); # CD 0017 } elsif( $arg[ 0 ] eq "all" ) { IOWrite( $hash, "$hash->{PLAYERMAC} playlist repeat 2\n" ); readingsSingleUpdate( $hash, "repeat", "all", $donotnotify ) if($updateReadingsOnSet); # CD 0017 } else { my $msg = "SB_PLAYER_Set: unknown argument for repeat given."; Log3( $hash, 3, $msg ); return( $msg ); } } elsif( ( $cmd eq "shuffle" ) || ( $cmd eq "SHUFFLE" ) || ( $cmd eq "Shuffle" ) ) { if( @arg != 1 ) { my $msg = "SB_PLAYER_Set: no arguments for shuffle given."; Log3( $hash, 3, $msg ); return( $msg ); } if( $arg[ 0 ] eq "off" ) { IOWrite( $hash, "$hash->{PLAYERMAC} playlist shuffle 0\n" ); readingsSingleUpdate( $hash, "shuffle", "off", $donotnotify ) if($updateReadingsOnSet); # CD 0017 } elsif(( $arg[ 0 ] eq "on" ) || ($arg[ 0 ] eq "song" )) { # CD 0017 'song' hinzugefügt IOWrite( $hash, "$hash->{PLAYERMAC} playlist shuffle 1\n" ); readingsSingleUpdate( $hash, "shuffle", "song", $donotnotify ) if($updateReadingsOnSet); # CD 0017 # CD 0017 start } elsif( $arg[ 0 ] eq "album" ) { IOWrite( $hash, "$hash->{PLAYERMAC} playlist shuffle 2\n" ); readingsSingleUpdate( $hash, "shuffle", "album", $donotnotify ) if($updateReadingsOnSet); # CD 0017 end } else { my $msg = "SB_PLAYER_Set: unknown argument for shuffle given."; Log3( $hash, 3, $msg ); return( $msg ); } } elsif( ( $cmd eq "show" ) || ( $cmd eq "SHOW" ) || ( $cmd eq "Show" ) ) { # set show line1:text line2:text duration:ss my $v = join( " ", @arg ); my @buf = split( "line1:", $v ); @buf = split( "line2:", $buf[ 1 ] ); my $line1 = uri_escape( $buf[ 0 ] ); @buf = split( "duration:", $buf[ 1 ] ); my $line2 = uri_escape( $buf[ 0 ] ); my $duration = $buf[ 1 ]; my $cmdstr = "$hash->{PLAYERMAC} display $line1 $line2 $duration\n"; IOWrite( $hash, $cmdstr ); } elsif( ( $cmd eq "talk" ) || ( $cmd eq "TALK" ) || ( $cmd eq "talk" ) || ( lc($cmd) eq "saytext" ) ) { # CD 0014 hinzugefügt $hash->{helper}{ttsstate}=TTS_IDLE if(!defined($hash->{helper}{ttsstate})); $hash->{helper}{ttsExtstate}=TTS_IDLE if(!defined($hash->{helper}{ttsExtstate})); # CD 0032 - Text2Speech verwenden ? # CD 0033 - überarbeitet my $useText2Speech=0; my $errMsg; if(AttrVal( $name, "ttslink", "none" )=~m/^Text2Speech/) { my @extTTS=split(":",AttrVal( $name, "ttslink", "none" )); # Device überhaupt verwendbar ? if(defined($extTTS[1]) && defined($defs{$extTTS[1]})) { my $ttshash=$defs{$extTTS[1]}; if(defined($ttshash->{TYPE}) && ($ttshash->{TYPE} eq 'Text2SpeechSB')) { if(defined($ttshash->{ALSADEVICE}) && ($ttshash->{ALSADEVICE} eq 'SB_PLAYER')) { if (AttrVal($hash->{NAME}, "TTS_Ressource", "Google") eq "Google") { $useText2Speech=1; $hash->{helper}{text2speech}{name}=$extTTS[1]; $hash->{helper}{text2speech}{pathPrefix}=join(':',@extTTS[2..$#extTTS]) if defined($extTTS[2]); # Zustand Text2Speech ? if(defined($ttshash->{helper}{SB_PLAYER}) || (defined($ttshash->{helper}{Text2Speech}) && @{$ttshash->{helper}{Text2Speech}} > 0)) { # Text2Speech besetzt, warten if($hash->{helper}{ttsstate}==TTS_IDLE) { SB_PLAYER_SetTTSState($hash,TTS_TEXT2SPEECH_BUSY,0,0); } else { $hash->{helper}{ttsExtstate}=TTS_EXT_TEXT2SPEECH_BUSY if($hash->{helper}{ttsExtstate}==TTS_IDLE); } if(defined($hash->{helper}{text2speech}{text})) { $hash->{helper}{text2speech}{text}.=" " . join( " ", @arg ); } else { $hash->{helper}{text2speech}{text}=join( " ", @arg ); } return; } else { # Text2Speech belegen $ttshash->{helper}{SB_PLAYER}=$name; if($hash->{helper}{ttsstate}==TTS_IDLE) { SB_PLAYER_SetTTSState($hash,TTS_TEXT2SPEECH_ACTIVE,0,0); } else { $hash->{helper}{ttsExtstate}=TTS_EXT_TEXT2SPEECH_ACTIVE; } fhem("set $extTTS[1] tts ".join( " ", @arg )); return; } } else { $errMsg = "SB_PLAYER_Set: ".$extTTS[1].": attribute TTS_Ressource must be set to Google"; } } else { $errMsg = "SB_PLAYER_Set: ".$extTTS[1].": Text2Speech uses unsupported ALSADEVICE"; } } else { $errMsg = "SB_PLAYER_Set: ".$extTTS[1].": unsupported Text2Speech device"; } } else { $errMsg = "SB_PLAYER_Set: invalid Text2Speech device"; } } if(defined($errMsg)) { Log3( $hash, 1, $errMsg ); return( $errMsg ); } # CD 0028 start - komplett überarbeitet # prepare text my $ttstext=join( " ", @arg ); $ttstext = AttrVal( $name, "ttsPrefix", "" )." ".$ttstext; # CD 0032 my %Sonderzeichen = ("ä" => "ae", "Ä" => "Ae", "ü" => "ue", "Ü" => "Ue", "ö" => "oe", "Ö" => "Oe", "ß" => "ss", "é" => "e", "è" => "e", "ë" => "e", "à" => "a", "ç" => "c" ); my $Sonderzeichenkeys = join ("|", keys(%Sonderzeichen)); if (length($ttstext)==0) { my $msg = "SB_PLAYER_Set: no text passed for synthesis."; Log3( $hash, 3, $msg ); return( $msg ); } $ttstext .= "." unless ($ttstext =~ m/^.+[.,?!:;]$/); my @textlines; my $tl=''; # CD 0033 Unterstützung für Dateien und URLs hinzugefügt, teilweise aus 00_SONOS übernommen my $targetSpeakMP3FileDir = AttrVal( $name, "ttsMP3FileDir", "" ); # CD 0033 my $filename; # CD 0038 if (length($ttstext)>0) { my @words=split(' ',$ttstext); for my $w (@words) { # CD 0033 Datei ?, teilweise aus 00_SONOS übernommen if ($w =~ m/\|(.*)\|/) { push(@textlines,$tl) if($tl ne ''); $tl=''; $filename = $1; $filename = $targetSpeakMP3FileDir.'/'.$filename if ($filename !~ m/^(\/|[a-z]:)/i); $filename = $filename.'.mp3' if ($filename !~ m/\.mp3$/i); push(@textlines, '|'.$filename.'|'); $filename=undef; # CD 0038 Leerzeichen in Dateinamen zulassen } elsif ($w =~ m/^\|(.*)/) { $filename = $1; } elsif (($w =~ m/(.*)\|/) && defined($filename)) { $filename .= " ".$1; push(@textlines,$tl) if($tl ne ''); $tl=''; $filename = $targetSpeakMP3FileDir.'/'.$filename if ($filename !~ m/^(\/|[a-z]:)/i); $filename = $filename.'.mp3' if ($filename !~ m/\.mp3$/i); push(@textlines, '|'.$filename.'|'); $filename=undef; # CD 0038 } else { $w =~ s/[\\|*~<>^\n\(\)\[\]\{\}[:cntrl:]]/ /g; $w =~ s/\s+/ /g; $w =~ s/^\s|\s$//g; $w =~ s/($Sonderzeichenkeys)/$Sonderzeichen{$1}/g; # CD 0032 end if((length($tl)+length($w)+1)<100) { $tl.=' ' if(length($tl)>0); $tl.=$w; } else { push(@textlines,$tl); $tl=$w; } } } } push(@textlines,$tl) if($tl ne ''); if($hash->{helper}{ttsstate}==TTS_IDLE) { # talk ist nicht aktiv # CD 0038 Player zuerst einschalten if(ReadingsVal($name,"power","x") ne "on") { SB_PLAYER_SetTTSState($hash,TTS_WAITFORPOWERON,0,0); RemoveInternalTimer( "TimeoutTTSWaitForPowerOn:$name"); if($hash->{helper}{playerStatusOK}==1) { # CD 0042 IOWrite( $hash, "$hash->{PLAYERMAC} power 1\n" ); InternalTimer( gettimeofday() + 5.00, "SB_PLAYER_tcb_TimeoutTTSWaitForPowerOn", "TimeoutTTSWaitForPowerOn:$name", 0 ); $hash->{helper}{ttsPowerWasOff}=1; # CD 0042 start } else { InternalTimer( gettimeofday() + 10.00, "SB_PLAYER_tcb_TimeoutTTSWaitForPowerOn", "TimeoutTTSWaitForPowerOn:$name", 0 ); } # CD 0042 end } else { # CD 0038 end SB_PLAYER_PrepareTalk($hash); } } else { } for my $outstr (@textlines) { if ($outstr =~ m/\|(.*)\|/) { # CD 0033 Log3($hash, defined($hash->{helper}{ttsOptions}{debug})?0:6,"SB_PLAYER_Set: $name: add to ttsqueue: $1"); # CD 0036 push(@{$hash->{helper}{ttsqueue}},uri_escape(decode('utf-8',$1))); # CD 0033 # CD 0038 uri_escape(decode... hinzugefügt } else { $outstr =~ s/\s/+/g; $outstr = uri_escape( $outstr ); $outstr = AttrVal( $name, "ttslink", "none" ) . "&tl=" . AttrVal( $name, "ttslanguage", "de" ) . "&q=". $outstr; Log3($hash, defined($hash->{helper}{ttsOptions}{debug})?0:6,"SB_PLAYER_Set: $name: add to ttsqueue: $outstr"); # CD 0036 push(@{$hash->{helper}{ttsqueue}},$outstr); } } SB_PLAYER_LoadTalk($hash) if($hash->{helper}{ttsstate}==TTS_LOADPLAYLIST); # CD 0033 # CD 0038 auf TTS_LOADPLAYLIST prüfen # CD 0028 end } elsif( ( $cmd eq "playlist" ) || ( $cmd eq "PLAYLIST" ) || ( $cmd eq "Playlist" ) ) { #if( ( @arg != 2 ) && ( @arg != 3 ) ) { # CD 0014 deaktiviert if( @arg < 2) { # CD 0014 my $msg = "SB_PLAYER_Set: no arguments for Playlist given."; Log3( $hash, 3, $msg ); return( $msg ); } # CD 0014 start if (@arg>1) { my $outstr = uri_escape(decode('utf-8',join( " ", @arg[1..$#arg]))); # CD 0017 Log3( $hash, 5, "SB_PLAYER_Set($name): playlists command = $arg[ 0 ] param = $outstr" ); if( $arg[ 0 ] eq "track" ) { IOWrite( $hash, "$hash->{PLAYERMAC} playlist loadtracks " . "track.titlesearch:$outstr\n" ); } elsif( $arg[ 0 ] eq "album" ) { IOWrite( $hash, "$hash->{PLAYERMAC} playlist loadtracks " . "album.titlesearch:$outstr\n" ); } elsif( $arg[ 0 ] eq "artist" ) { IOWrite( $hash, "$hash->{PLAYERMAC} playlist loadtracks " . "contributor.namesearch:$outstr\n" ); # CD 0014 'titlesearch' durch 'namesearch' ersetzt } elsif( $arg[ 0 ] eq "play" ) { IOWrite( $hash, "$hash->{PLAYERMAC} playlist play $outstr\n" ); # CD 0038 start } elsif( $arg[ 0 ] eq "add" ) { IOWrite( $hash, "$hash->{PLAYERMAC} playlist add $outstr\n" ); } elsif( $arg[ 0 ] eq "insert" ) { IOWrite( $hash, "$hash->{PLAYERMAC} playlist insert $outstr\n" ); # CD 0038 end } elsif( $arg[ 0 ] eq "year" ) { IOWrite( $hash, "$hash->{PLAYERMAC} playlist loadtracks " . "track.year:$outstr\n" ); } elsif( $arg[ 0 ] eq "genre" ) { IOWrite( $hash, "$hash->{PLAYERMAC} playlist loadtracks " . "genre.namesearch:$outstr\n" ); #} elsif( $arg[ 0 ] eq "comment" ) { # CD 0014 funktioniert nicht # IOWrite( $hash, "$hash->{PLAYERMAC} playlist loadtracks " . # "comments.value:$outstr\n" ); # CD 0038 loadalbum wieder hergestellt und überarbeitet } else { my $genre='*'; my $album='*'; my $artist='*'; my $t=0; foreach( @arg ) { if( $_ =~ /^(genre:)(.*)/ ) { $genre=$2; $t=1; } elsif ( $_ =~ /^(album:)(.*)/ ) { $album=$2; $t=2; } elsif ( $_ =~ /^(artist:)(.*)/ ) { $artist=$2; $t=3; } else { $genre.=" ".$_ if($t==1); $album.=" ".$_ if($t==2); $artist.=" ".$_ if($t==3); } } if( $t>0 ) { $genre=uri_escape(decode('utf-8',$genre)) if($genre ne '*'); $album=uri_escape(decode('utf-8',$album)) if($album ne '*'); $artist=uri_escape(decode('utf-8',$artist)) if($artist ne '*'); IOWrite( $hash, "$hash->{PLAYERMAC} playlist loadalbum " . "$genre $artist $album\n" ); } # CD 0038 end } # CD 0014 end } else { # what the f... we checked beforehand } } elsif( $cmd eq "allalarms" ) { if( $arg[ 0 ] eq "enable" ) { IOWrite( $hash, "$hash->{PLAYERMAC} playerpref alarmsEnabled 1\n" ); # MM 0016 readingsSingleUpdate( $hash, "alarmsEnabled", "on", $donotnotify ) if($updateReadingsOnSet); # CD 0017 } elsif( $arg[ 0 ] eq "disable" ) { IOWrite( $hash, "$hash->{PLAYERMAC} playerpref alarmsEnabled 0\n" ); # MM 0016 readingsSingleUpdate( $hash, "alarmsEnabled", "off", $donotnotify ) if($updateReadingsOnSet); # CD 0017 } elsif( $arg[ 0 ] eq "statusRequest" ) { IOWrite( $hash, "$hash->{PLAYERMAC} alarms 0 200 tags:all filter:all\n" ); # CD 0015 filter added # CD 0016 start IOWrite( $hash, "$hash->{PLAYERMAC} playerpref alarmsEnabled ?\n" ); IOWrite( $hash, "$hash->{PLAYERMAC} playerpref alarmDefaultVolume ?\n" ); IOWrite( $hash, "$hash->{PLAYERMAC} playerpref alarmTimeoutSeconds ?\n" ); IOWrite( $hash, "$hash->{PLAYERMAC} playerpref alarmSnoozeSeconds ?\n" ); # CD 0016 end # CD 0015 start } elsif( $arg[ 0 ] eq "delete" ) { $hash->{helper}{deleteAllAlarms}=1; for(my $i=1;$i<=$hash->{helper}{ALARMSCOUNT};$i++) { # CD 0016 ALARMSCOUNT nach {helper} verschoben IOWrite( $hash, "$hash->{PLAYERMAC} alarm delete id:". ReadingsVal($name,"alarm".$i."_id","0"). "\n" ); } IOWrite( $hash, "$hash->{PLAYERMAC} alarms 0 200 tags:all filter:all\n" ); # CD 0015 filter added } elsif( $arg[ 0 ] eq "add" ) { $arg[ 0 ]="set"; SB_PLAYER_Alarm( $hash, 0, @arg ); # CD 0015 end } else { } # CD 0016 start, von MM übernommen, Namen geändert } elsif( index( $cmd, "alarms" ) != -1 ) { if($cmd eq "alarmsSnooze") { IOWrite( $hash, "$hash->{PLAYERMAC} playerpref alarmSnoozeSeconds ". $arg[0]*60 ."\n" ); readingsSingleUpdate( $hash, "alarmsSnooze", $arg[ 0 ], $donotnotify ) if($updateReadingsOnSet); # CD 0017 } elsif($cmd eq "alarmsTimeout") { IOWrite( $hash, "$hash->{PLAYERMAC} playerpref alarmTimeoutSeconds ". $arg[0]*60 ."\n" ); readingsSingleUpdate( $hash, "alarmsTimeout", $arg[ 0 ], $donotnotify ) if($updateReadingsOnSet); # CD 0017 } elsif($cmd eq "alarmsDefaultVolume") { IOWrite( $hash, "$hash->{PLAYERMAC} playerpref alarmDefaultVolume ". $arg[0] ."\n" ); readingsSingleUpdate( $hash, "alarmsDefaultVolume", $arg[ 0 ], $donotnotify ) if($updateReadingsOnSet); # CD 0017 } elsif($cmd eq "alarmsFadeIn") { if($arg[0] eq 'on') { IOWrite( $hash, "$hash->{PLAYERMAC} playerpref alarmfadeseconds 1\n" ); readingsSingleUpdate( $hash, "alarmsFadeIn", "on", $donotnotify ) if($updateReadingsOnSet); # CD 0017 } else { IOWrite( $hash, "$hash->{PLAYERMAC} playerpref alarmfadeseconds 0\n" ); readingsSingleUpdate( $hash, "alarmsFadeIn", "off", $donotnotify ) if($updateReadingsOnSet); # CD 0017 } } elsif($cmd eq "alarmsEnabled") { if( $arg[ 0 ] eq "on" ) { IOWrite( $hash, "$hash->{PLAYERMAC} playerpref alarmsEnabled 1\n" ); readingsSingleUpdate( $hash, "alarmsEnabled", "on", $donotnotify ) if($updateReadingsOnSet); # CD 0017 } else { IOWrite( $hash, "$hash->{PLAYERMAC} playerpref alarmsEnabled 0\n" ); readingsSingleUpdate( $hash, "alarmsEnabled", "off", $donotnotify ) if($updateReadingsOnSet); # CD 0017 } } # CD 0016 } elsif( index( $cmd, "alarm" ) != -1 ) { my $alarmno = int( substr( $cmd, 5 ) ) + 0; Log3( $hash, 5, "SB_PLAYER_Set: $name: alarmid:$alarmno" ); return( SB_PLAYER_Alarm( $hash, $alarmno, @arg ) ); } elsif( ( $cmd eq "sleep" ) || ( $cmd eq "SLEEP" ) || ( $cmd eq "Sleep" ) ) { # split the time string up my @buf = split( ":", $arg[ 0 ] ); if( scalar( @buf ) != 3 ) { my $msg = "SB_PLAYER_Set: please use hh:mm:ss for sleep time."; Log3( $hash, 3, $msg ); return( $msg ); } my $secs = ( $buf[ 0 ] * 3600 ) + ( $buf[ 1 ] * 60 ) + $buf[ 2 ]; IOWrite( $hash, "$hash->{PLAYERMAC} sleep $secs\n" ); return( undef ); } elsif( ( $cmd eq "cliraw" ) || ( $cmd eq "CLIRAW" ) || ( $cmd eq "Cliraw" ) ) { # write raw messages to the CLI interface per player my $v = join( " ", @arg ); Log3( $hash, 5, "SB_PLAYER_Set: cliraw: $v " ); IOWrite( $hash, "$hash->{PLAYERMAC} $v\n" ); return( undef ); # CD 0014 start } elsif( ( $cmd eq "save" ) || ( $cmd eq "SAVE" ) ) { if(defined($arg[0])) { SB_PLAYER_Save($hash, $arg[0]); } else { SB_PLAYER_Save($hash, ""); } } elsif( ( $cmd eq "recall" ) || ( $cmd eq "RECALL" ) ) { if(defined($arg[0])) { SB_PLAYER_Recall($hash, join(" ", @arg)); } else { SB_PLAYER_Recall($hash, ""); } # CD 0014 end } elsif( $cmd eq "statusRequest" ) { RemoveInternalTimer( $hash ); SB_PLAYER_GetStatus( $hash ); } elsif( $cmd eq "sync" ) { # CD 0018 wenn der Player bereits in einer Gruppe ist und 'new' ist vorhanden, wird der Player zuerst aus der Gruppe entfernt if(( @arg == 2) && ($arg[1] eq "new") && ($hash->{SYNCED} eq 'yes')) { IOWrite( $hash, "$hash->{PLAYERMAC} sync -\n" ); # CD 0028 start if($hash->{helper}{ttsstate}==TTS_SYNCGROUPACTIVE) { SB_PLAYER_SetTTSState($hash,TTS_IDLE,1,0); } # CD 0028 end } # CD 0018 end # CD 0018 Synchronisation mehrerer Player if(( @arg == 1 ) || ( @arg == 2)) { my $msg; my $dev; my @dvs=(); my $doGetStatus=0; @dvs=split(",",$arg[0]); foreach (@dvs) { my $dev=$_; my $mac; # CD 0018 end if( defined( $hash->{helper}{SB_PLAYER_SyncMasters}{$dev}{MAC} ) ) { $mac=$hash->{helper}{SB_PLAYER_SyncMasters}{$dev}{MAC}; } else { # CD 0038 Player nicht gefunden, testen ob Name zu einem FHEM-Gerät passt if(defined($defs{$dev}) && defined($defs{$dev}{TYPE}) && ($defs{$dev}{TYPE} eq 'SB_PLAYER') && defined($defs{$dev}{PLAYERMAC})) { $mac=$defs{$dev}{PLAYERMAC}; } else { # CD 0038 end my $msg = "SB_PLAYER_Set: no MAC for player ".$dev."."; Log3( $hash, 3, $msg ); #return( $msg ); # CD 0018 wenn keine MAC vorhanden weitermachen } } if(defined($mac)) { # CD 0038 wenn asSlave angegeben ist, wird der erste Player zur Gruppe des 2. hinzugefügt if((@arg == 2) && ($arg[1] eq "asSlave")) { IOWrite( $hash, "$mac sync " . "$hash->{PLAYERMAC}\n" ); } else { # CD 0038 end IOWrite( $hash, "$hash->{PLAYERMAC} sync " . "$mac\n" ); } $doGetStatus=1; } } # CD 0018 SB_PLAYER_GetStatus( $hash ) if($doGetStatus==1); } # CD 0018 end } elsif( $cmd eq "unsync" ) { IOWrite( $hash, "$hash->{PLAYERMAC} sync -\n" ); SB_PLAYER_GetStatus( $hash ); # CD 0028 start if($hash->{helper}{ttsstate}==TTS_SYNCGROUPACTIVE) { SB_PLAYER_SetTTSState($hash,TTS_IDLE,1,0); } # CD 0028 end } elsif( $cmd eq "playlists" ) { if( @arg == 1 ) { my $msg; if( defined( $hash->{helper}{SB_PLAYER_Playlists}{$arg[0]}{ID} ) ) { $msg = "$hash->{PLAYERMAC} playlistcontrol cmd:load " . "playlist_id:$hash->{helper}{SB_PLAYER_Playlists}{$arg[0]}{ID}"; Log3( $hash, 5, "SB_PLAYER_Set($name): playlists command = " . $msg . " ........ with $arg[0]" ); IOWrite( $hash, $msg . "\n" ); readingsSingleUpdate( $hash, "playlists", "$arg[ 0 ]", 1 ); SB_PLAYER_GetStatus( $hash ); } else { $msg = "SB_PLAYER_Set: no name for playlist defined."; Log3( $hash, 3, $msg ); return( $msg ); } } else { my $msg = "SB_PLAYER_Set: no arguments for playlists given."; Log3( $hash, 3, $msg ); return( $msg ); } } elsif( $cmd eq "resetTTS" ) { SB_PLAYER_SetTTSState($hash,TTS_IDLE,0,1); } else { my $msg = "SB_PLAYER_Set: unsupported command given"; Log3( $hash, 3, $msg ); return( $msg ); } return( undef ); } # CD 0033 hinzugefügt # ---------------------------------------------------------------------------- # add talk segments to playlist # ---------------------------------------------------------------------------- sub SB_PLAYER_LoadTalk($) { # gespeicherte Elemente in Playlist einfügen my ( $hash ) = @_; my $name = $hash->{NAME}; if(defined($hash->{helper}{ttsqueue})) { if(($hash->{helper}{ttsstate}==TTS_LOADPLAYLIST)||($hash->{helper}{ttsstate}==TTS_SYNCGROUPACTIVE)) { IOWrite( $hash, "$hash->{PLAYERMAC} playlist clear\n" ) if($hash->{helper}{ttsstate}==TTS_LOADPLAYLIST); for (@{$hash->{helper}{ttsqueue}}) { if($hash->{helper}{ttsstate}==TTS_LOADPLAYLIST) { # ich bin Master und talk ist nicht aktiv IOWrite( $hash, "$hash->{PLAYERMAC} playlist add " . $_ . "\n" ); } else { # talk ist aktiv und ein anderer Player ist Master IOWrite( $hash, $hash->{helper}{ttsMaster}." fhemrelay ttsadd ".$_."\n" ); } } delete($hash->{helper}{ttsqueue}); if($hash->{helper}{ttsstate}!=TTS_SYNCGROUPACTIVE) { # andere Player in Gruppe informieren if (defined($hash->{SYNCGROUP}) && ($hash->{SYNCGROUP} ne '?') && ($hash->{SYNCMASTER} ne 'none')) { my @pl=split(",",$hash->{SYNCGROUP}.",".$hash->{SYNCMASTER}); foreach (@pl) { if ($hash->{PLAYERMAC} ne $_) { IOWrite( $hash, "$_ fhemrelay ttsactive ".$hash->{PLAYERMAC}."\n" ); IOWrite( $hash, "$_ fhemrelay ttsforcegroupon\n" ) if(defined($hash->{helper}{ttsOptions}{forcegroupon})); # CD 0030 } } } } } else { # talk ist aktiv und ich bin Master # warten bis stop } } } # CD 0033 hinzugefügt # ---------------------------------------------------------------------------- # prepare player for talk # ---------------------------------------------------------------------------- sub SB_PLAYER_PrepareTalk($) { # kein readingsBulkUpdate # aktuellen Stand abspeichern, playlist löschen, Lautstärke setzen my ( $hash ) = @_; my $name = $hash->{NAME}; # talk ist nicht aktiv if(!defined($hash->{helper}{ttsOptions}{nosaverestore})) { SB_PLAYER_SetTTSState($hash,TTS_SAVE,0,0); SB_PLAYER_Save( $hash, "xxTTSxx" ) if(!defined($hash->{helper}{saveLocked})); } $hash->{helper}{saveLocked}=1; IOWrite( $hash, "$hash->{PLAYERMAC} playlist repeat 0\n" ); IOWrite( $hash, "$hash->{PLAYERMAC} playlist clear\n" ); if(defined($hash->{helper}{ttsVolume})) { SB_PLAYER_SetTTSState($hash,TTS_SETVOLUME,0,0); my $vol=$hash->{helper}{ttsVolume}; $vol=AttrVal( $name, "volumeLimit", 100 ) if(( $hash->{helper}{ttsVolume} > AttrVal( $name, "volumeLimit", 100 ) )&&!defined($hash->{helper}{ttsOptions}{ignorevolumelimit})); # CD 0031 IOWrite( $hash, "$hash->{PLAYERMAC} mixer volume ".$vol."\n" ); SB_PLAYER_SetSyncedVolume($hash,$hash->{helper}{ttsVolume}); } SB_PLAYER_SetTTSState($hash,TTS_LOADPLAYLIST,0,0); } # CD 0014 start # ---------------------------------------------------------------------------- # recall player state # # CD 0036 added $statename # ---------------------------------------------------------------------------- sub SB_PLAYER_Recall($$) { my ( $hash, $arg ) = @_; # CD 0036 my $name = $hash->{NAME}; # CD 0036 start my $del=0; my $delonly=0; my $forceoff=0; my $forceon=0; my $forceplay=0; my $forcestop=0; my $statename; my @args=split " ",$arg; if(defined($args[0])) { $statename=$args[0]; } else { $statename='default'; } # Optionen auswerten for my $opt (@args) { $del=1 if($opt=~ m/^del$/); $delonly=1 if($opt=~ m/^delonly$/); $forceoff=1 if($opt=~ m/^off$/); $forceon=1 if(($opt=~ m/^on$/)||($opt=~ m/^play$/)); $forceplay=1 if($opt=~ m/^play$/); $forcestop=1 if($opt=~ m/^stop$/); } $forceoff=0 if($forceon==1); $forcestop=0 if($forceplay==1); # CD 0036 end # wurde überhaupt etwas gespeichert ? if(defined($hash->{helper}{savedPlayerState}) && defined($hash->{helper}{savedPlayerState}{$statename})) { if($delonly==0) { # CD 0029 start if(($hash->{helper}{ttsstate}!=TTS_IDLE) && defined($hash->{helper}{ttsOptions}{debugsaverestore})) { Log3( $hash, 0, "SB_PLAYER_Recall: $name: restoring..."); $hash->{helper}{ttsOptions}{logloaddone}=1; $hash->{helper}{ttsOptions}{logplay}=1; } # CD 0029 end IOWrite( $hash, "$hash->{PLAYERMAC} playlist shuffle 0\n"); if (defined($hash->{helper}{savedPlayerState}{$statename}{playlistIds})) { # wegen Shuffle Playlist neu erzeugen # CD 0030 start IOWrite( $hash, "$hash->{PLAYERMAC} playlist clear\n"); my @playlistIds=split(',',$hash->{helper}{savedPlayerState}{$statename}{playlistIds}); for my $id (@playlistIds) { if($id>=0) { IOWrite( $hash, "$hash->{PLAYERMAC} playlistcontrol cmd:add track_id:".$id."\n"); } else { if(defined($hash->{helper}{savedPlayerState}{$statename}{playlistUrls}) && defined($hash->{helper}{savedPlayerState}{$statename}{playlistUrls}{$id})) { IOWrite( $hash, "$hash->{PLAYERMAC} playlist add ".$hash->{helper}{savedPlayerState}{$statename}{playlistUrls}{$id}."\n"); } else { Log3( $hash, 2, "SB_PLAYER_Recall: $name: no url found for id ".$id); } } } IOWrite( $hash, "$hash->{PLAYERMAC} playlist index ".$hash->{helper}{savedPlayerState}{$statename}{playlistCurrentTrack}."\n"); # CD 0030 end } else { # auf dem Server gespeicherte Playlist fortsetzen if( $hash->{helper}{savedPlayerState}{$statename}{playStatus} eq "playing" ) { IOWrite( $hash, "$hash->{PLAYERMAC} playlist resume fhem_$hash->{NAME}_$statename\n" ); } else { IOWrite( $hash, "$hash->{PLAYERMAC} playlist resume fhem_$hash->{NAME}_$statename noplay:1\n" ); } } if ($hash->{helper}{savedPlayerState}{$statename}{volumeStraight} ne '?') { IOWrite( $hash, "$hash->{PLAYERMAC} mixer volume $hash->{helper}{savedPlayerState}{$statename}{volumeStraight}\n" ); SB_PLAYER_SetSyncedVolume($hash,$hash->{helper}{savedPlayerState}{$statename}{volumeStraight}); } if ($hash->{helper}{savedPlayerState}{$statename}{repeat} ne ReadingsVal($name,"repeat","?")) { if( $hash->{helper}{savedPlayerState}{$statename}{repeat} eq "off" ) { IOWrite( $hash, "$hash->{PLAYERMAC} playlist repeat 0\n" ); } elsif( $hash->{helper}{savedPlayerState}{$statename}{repeat} eq "one" ) { IOWrite( $hash, "$hash->{PLAYERMAC} playlist repeat 1\n" ); } elsif( $hash->{helper}{savedPlayerState}{$statename}{repeat} eq "all" ) { IOWrite( $hash, "$hash->{PLAYERMAC} playlist repeat 2\n" ); } } # CD 0028 start if ((($hash->{helper}{savedPlayerState}{$statename}{power} eq "off") && ($forceon!=1))||($forceoff==1)) { # CD 0036 added $forceon and $forceoff IOWrite( $hash, "$hash->{PLAYERMAC} power 0\n" ); if($hash->{helper}{ttsstate}==TTS_RESTORE) { SB_PLAYER_SetTTSState($hash,TTS_IDLE,0,1); } } else { # CD 0028 end if ((($hash->{helper}{savedPlayerState}{$statename}{playStatus} eq "stopped" ) && ($forceplay!=1))||($forcestop==1)) { # CD 0036 added $forceplay and $forcestop IOWrite( $hash, "$hash->{PLAYERMAC} stop\n" ); # CD 0028 start if($hash->{helper}{ttsstate}==TTS_RESTORE) { SB_PLAYER_SetTTSState($hash,TTS_IDLE,0,1); } # CD 0028 end } elsif(( $hash->{helper}{savedPlayerState}{$statename}{playStatus} eq "playing" )||($forceplay==1)) { # CD 0036 added $forceplay my $secbuf = AttrVal( $name, "fadeinsecs", 10 ); IOWrite( $hash, "$hash->{PLAYERMAC} play $secbuf\n" ); IOWrite( $hash, "$hash->{PLAYERMAC} time $hash->{helper}{savedPlayerState}{$statename}{elapsedTime}\n" ) if(defined($hash->{helper}{savedPlayerState}{$statename}{elapsedTime})); # CD 0028 start if($hash->{helper}{ttsstate}==TTS_RESTORE) { SB_PLAYER_SetTTSState($hash,TTS_IDLE,0,1); } # CD 0028 end } elsif( $hash->{helper}{savedPlayerState}{$statename}{playStatus} eq "paused" ) { # paused kann nicht aus stop erreicht werden -> Playlist starten und dann pausieren $hash->{helper}{recallPause}=1; $hash->{helper}{recallPending}=1; } } # CD 0028 restore names readingsSingleUpdate( $hash,"playlists", $hash->{helper}{savedPlayerState}{$statename}{playlist},(AttrVal($name, "donotnotify", "true") eq "true")?0:1) if(defined($hash->{helper}{savedPlayerState}{$statename}{playlist})); readingsSingleUpdate( $hash,"favorites", $hash->{helper}{savedPlayerState}{$statename}{favorite},(AttrVal($name, "donotnotify", "true") eq "true")?0:1) if(defined($hash->{helper}{savedPlayerState}{$statename}{favorite})); } delete($hash->{helper}{savedPlayerState}{$statename}) if(($del==1)||($delonly==1)); } } sub SB_PLAYER_SetTTSState($$$$) { my ( $hash, $state, $bulk, $broadcast ) = @_; my $name = $hash->{NAME}; return if($state eq $hash->{helper}{ttsstate}); $hash->{helper}{ttsstate}=$state; Log3( $hash, defined($hash->{helper}{ttsOptions}{debug})?0:6, "SB_PLAYER_SetTTSState: $name: ttsstate: ".$ttsstates{$hash->{helper}{ttsstate}} ); if($bulk==1) { readingsBulkUpdate( $hash,"talkStatus", $ttsstates{$hash->{helper}{ttsstate}} ); } else { readingsSingleUpdate( $hash,"talkStatus", $ttsstates{$hash->{helper}{ttsstate}},(AttrVal($name, "donotnotify", "true") eq "true")?0:1); } if($broadcast==1) { if($state==TTS_IDLE) { if (defined($hash->{SYNCGROUP}) && ($hash->{SYNCGROUP} ne '?') && ($hash->{SYNCMASTER} ne 'none')) { my @pl=split(",",$hash->{SYNCGROUP}.",".$hash->{SYNCMASTER}); foreach (@pl) { if ($hash->{PLAYERMAC} ne $_) { IOWrite( $hash, "$_ fhemrelay ttsidle\n" ); } } } } } delete($hash->{helper}{ttsqueue}) if(defined($hash->{helper}{ttsqueue}) && ($state==TTS_IDLE)); delete($hash->{helper}{ttsPowerWasOff}) if (defined($hash->{helper}{ttsPowerWasOff}) && ($state==TTS_IDLE)); } # ---------------------------------------------------------------------------- # save player state # # CD 0036 added $statename # ---------------------------------------------------------------------------- sub SB_PLAYER_Save($$) { my ( $hash, $statename ) = @_; my $name = $hash->{NAME}; $statename='default' unless defined($statename); delete($hash->{helper}{savedPlayerState}{$statename}) if(defined($hash->{helper}{savedPlayerState}) && defined($hash->{helper}{savedPlayerState}{$statename})); SB_PLAYER_EstimateElapsedTime($hash); $hash->{helper}{savedPlayerState}{$statename}{power}=ReadingsVal($name,"power","on"); # CD 0028 $hash->{helper}{savedPlayerState}{$statename}{power}="off" if(($statename eq "xxTTSxx") && defined($hash->{helper}{ttsPowerWasOff})); # CD 0038 $hash->{helper}{savedPlayerState}{$statename}{SYNCGROUP}=$hash->{SYNCGROUP} if(defined($hash->{SYNCGROUP})); # CD 0028 $hash->{helper}{savedPlayerState}{$statename}{SYNCMASTER}=$hash->{SYNCMASTER} if(defined($hash->{SYNCMASTER})); # CD 0028 $hash->{helper}{savedPlayerState}{$statename}{elapsedTime}=$hash->{helper}{elapsedTime}{VAL}; $hash->{helper}{savedPlayerState}{$statename}{playlistCurrentTrack}=ReadingsVal($name,"playlistCurrentTrack",1)-1; $hash->{helper}{savedPlayerState}{$statename}{playStatus}=ReadingsVal($name,"playStatus","?"); $hash->{helper}{savedPlayerState}{$statename}{repeat}=ReadingsVal($name,"repeat","?"); $hash->{helper}{savedPlayerState}{$statename}{volumeStraight}=ReadingsVal($name,"volumeStraight","?"); $hash->{helper}{savedPlayerState}{$statename}{playlist}=ReadingsVal($name,"playlists","-"); $hash->{helper}{savedPlayerState}{$statename}{favorite}=ReadingsVal($name,"favorites","-"); # CD 0029 start delete($hash->{helper}{ttsOptions}{logloaddone}) if(defined($hash->{helper}{ttsOptions}{logloaddone})); delete($hash->{helper}{ttsOptions}{logplay}) if(defined($hash->{helper}{ttsOptions}{logplay})); # CD 0029 end # CD 0036 immer intern speichern $hash->{helper}{savedPlayerState}{$statename}{playlistIds}=$hash->{helper}{playlistIds}; $hash->{helper}{savedPlayerState}{$statename}{playlistUrls}=$hash->{helper}{playlistUrls}; if(($hash->{helper}{ttsstate}!=TTS_IDLE) && defined($hash->{helper}{ttsOptions}{debugsaverestore})) { Log3( $hash, 0, "SB_PLAYER_Save: $name: saving {helper}{playlistIds}: ".$hash->{helper}{playlistIds}) if(defined($hash->{helper}{playlistIds})); } # CD 0036 # CD 0036, alte Version, deaktivert # --------------------------------- if(1==0) { # nur 1 Track -> playlist save verwenden if((ReadingsVal($name,"playlistTracks",1)<=1)&&(!defined($hash->{helper}{ttsOptions}{internalsave}))) { IOWrite( $hash, "$hash->{PLAYERMAC} playlist save fhem_$hash->{NAME}_$statename silent:1\n" ); # CD 0029 start if(($hash->{helper}{ttsstate}!=TTS_IDLE) && defined($hash->{helper}{ttsOptions}{debugsaverestore})) { Log3( $hash, 0, "SB_PLAYER_Save: $name: 1 track in playlist, using playlist save"); } # CD 0029 end } else { # mehr als 1 Track, auf shuffle prüfen (playlist resume funktioniert nicht richtig wenn shuffle nicht auf off steht) # bei negativen Ids (Remote-Streams) und shuffle geht die vorherige Reihenfolge verloren, kein Workaround bekannt # es werden maximal 500 Ids gespeichert, bei mehr als 500 Einträgen in der Playlists geht die vorherige Reihenfolge verloren (zu ändern) if (( (ReadingsVal($name,"shuffle","?") eq "off") || (!defined($hash->{helper}{playlistIds})) || ($hash->{helper}{playlistIds}=~ /-/) || (ReadingsVal($name,"playlistTracks",0)>500)) && !defined($hash->{helper}{ttsOptions}{internalsave})) { IOWrite( $hash, "$hash->{PLAYERMAC} playlist save fhem_$hash->{NAME}_$statename silent:1\n" ); # CD 0029 start if(($hash->{helper}{ttsstate}!=TTS_IDLE) && defined($hash->{helper}{ttsOptions}{debugsaverestore})) { Log3( $hash, 0, "SB_PLAYER_Save: $name: multiple tracks in playlist, using playlist save"); } # CD 0029 end } else { $hash->{helper}{savedPlayerState}{$statename}{playlistIds}=$hash->{helper}{playlistIds}; $hash->{helper}{savedPlayerState}{$statename}{playlistUrls}=$hash->{helper}{playlistUrls}; if(($hash->{helper}{ttsstate}!=TTS_IDLE) && defined($hash->{helper}{ttsOptions}{debugsaverestore})) { # CD 0029 start if(defined($hash->{helper}{ttsOptions}{internalsave})) { Log3( $hash, 0, "SB_PLAYER_Save: $name: forcing {helper}{playlistIds}: ".$hash->{helper}{playlistIds}) if(defined($hash->{helper}{playlistIds})); # CD 0033 if added #Log3( $hash, 0, "SB_PLAYER_Save: $name: warning - negative playlist ids cannot be restored") if ($hash->{helper}{playlistIds}=~ /-/); } else { Log3( $hash, 0, "SB_PLAYER_Save: $name: multiple tracks in playlist, shuffle active, using {helper}{playlistIds} (".$hash->{helper}{playlistIds}.")"); } # CD 0029 end } } } } # --------------------------------- # CD 0029 start if(($hash->{helper}{ttsstate}!=TTS_IDLE) && defined($hash->{helper}{ttsOptions}{debugsaverestore})) { Log3( $hash, 0, "SB_PLAYER_Save: $name: power ".$hash->{helper}{savedPlayerState}{$statename}{power} ); Log3( $hash, 0, "SB_PLAYER_Save: $name: elapsedTime ".$hash->{helper}{savedPlayerState}{$statename}{elapsedTime} ) if (defined($hash->{helper}{savedPlayerState}{$statename}{elapsedTime} )); Log3( $hash, 0, "SB_PLAYER_Save: $name: playlistCurrentTrack ".$hash->{helper}{savedPlayerState}{$statename}{playlistCurrentTrack} ); Log3( $hash, 0, "SB_PLAYER_Save: $name: playStatus ".$hash->{helper}{savedPlayerState}{$statename}{playStatus} ); Log3( $hash, 0, "SB_PLAYER_Save: $name: repeat ".$hash->{helper}{savedPlayerState}{$statename}{repeat} ); Log3( $hash, 0, "SB_PLAYER_Save: $name: volumeStraight ".$hash->{helper}{savedPlayerState}{$statename}{volumeStraight} ); Log3( $hash, 0, "SB_PLAYER_Save: $name: playlist ".$hash->{helper}{savedPlayerState}{$statename}{playlist} ); Log3( $hash, 0, "SB_PLAYER_Save: $name: favorite ".$hash->{helper}{savedPlayerState}{$statename}{favorite} ); } # CD 0029 end } # CD 0014 end # ---------------------------------------------------------------------------- # set Alarms of the Player # ---------------------------------------------------------------------------- # CD 0015 angepasst für größere Anzahl an Alarmen sub SB_PLAYER_Alarm( $$@ ) { my ( $hash, $n, @arg ) = @_; my $name = $hash->{NAME}; # CD 0015 deaktiviert #if( ( $n != 1 ) && ( $n != 2 ) ) { # Log3( $hash, 1, "SB_PLAYER_Alarm: $name: wrong ID given. Must be 1|2" ); # return; #} my $id = ReadingsVal( "$name", "alarm".$n."_id", "none" ); # CD 0015 angepasst my $updateReadingsOnSet=AttrVal($name, "updateReadingsOnSet", false); # CD 0017 my $donotnotify=AttrVal($name, "donotnotify", true); # CD 0017 Log3( $hash, 5, "SB_PLAYER_Alarm: $name: ID:$id, N:$n" ); my $cmdstr = ""; if( $arg[ 0 ] eq "set" ) { # set alarm set 0..6 hh:mm:ss playlist if( ( @arg != 4 ) && ( @arg != 3 ) ) { my $msg = "SB_PLAYER_Set: not enough arguments for alarm given."; Log3( $hash, 3, $msg ); return( $msg ); } if( $id ne "none" ) { IOWrite( $hash, "$hash->{PLAYERMAC} alarm delete id:$id\n" ); # CD 0020 'id' fehlt # readingsSingleUpdate( $hash, "alarmid$n", "none", 0 ); # CD 0015 deaktiviert } my $dow = SB_PLAYER_CheckWeekdays($arg[ 1 ]); # CD 0016 hinzugefügt # split the time string up my @buf = split( ":", $arg[ 2 ] ); $buf[ 2 ] = 0 if( scalar( @buf ) == 2 ); # CD 0016, von MM übernommen, geändert if( scalar( @buf ) != 3 ) { my $msg = "SB_PLAYER_Set: please use hh:mm:ss for alarm time."; Log3( $hash, 3, $msg ); return( $msg ); } my $secs = ( $buf[ 0 ] * 3600 ) + ( $buf[ 1 ] * 60 ) + $buf[ 2 ]; $cmdstr = "$hash->{PLAYERMAC} alarm add dow:$dow repeat:0 enabled:1"; if( defined( $arg[ 3 ] ) ) { # CD 0015 start my $url=join( " ", @arg[3..$#arg]); if (defined($hash->{helper}{alarmPlaylists})) { foreach my $e ( keys %{$hash->{helper}{alarmPlaylists}} ) { if($url eq $hash->{helper}{alarmPlaylists}{$e}{title}) { $url=$hash->{helper}{alarmPlaylists}{$e}{url}; last; } } } # CD 0015 end # CD 0034 überprüfen ob gültige url, wenn nicht, versuchen file:// anzuhängen if($url !~ /:\/\//) { $url='/' . $url if ($url =~ /^[a-zA-Z]:\\/); # für Windows $url=~ s/\\/\//g; $url='file://' . $url; } # CD 0034 end $cmdstr .= " url:" . uri_escape(decode('utf-8',$url)); # CD 0015 uri_escape und join hinzugefügt # CD 0020 decode hinzugefügt # CD 0034 playlist: in url: geändert } $cmdstr .= " time:$secs\n"; IOWrite( $hash, $cmdstr ); $hash->{LASTALARM} = $n; } elsif(( $arg[ 0 ] eq "enable" )||( $arg[ 0 ] eq "on" )) { # CD 0015 'on' hinzugefügt if( $id ne "none" ) { $cmdstr = "$hash->{PLAYERMAC} alarm update id:$id "; $cmdstr .= "enabled:1\n"; IOWrite( $hash, $cmdstr ); readingsSingleUpdate( $hash, "alarm".$n."_state", "on", $donotnotify ) if($updateReadingsOnSet); # CD 0017 } } elsif(( $arg[ 0 ] eq "disable" )||( $arg[ 0 ] eq "off" )) { # CD 0015 'off' hinzugefügt if( $id ne "none" ) { $cmdstr = "$hash->{PLAYERMAC} alarm update id:$id "; $cmdstr .= "enabled:0\n"; IOWrite( $hash, $cmdstr ); readingsSingleUpdate( $hash, "alarm".$n."_state", "off", $donotnotify ) if($updateReadingsOnSet); # CD 0017 } } elsif( $arg[ 0 ] eq "volume" ) { if( $id ne "none" ) { $cmdstr = "$hash->{PLAYERMAC} alarm update id:$id "; $cmdstr .= "volume:" . $arg[ 1 ] . "\n"; IOWrite( $hash, $cmdstr ); readingsSingleUpdate( $hash, "alarm".$n."_volume", $arg[ 1 ], $donotnotify ) if($updateReadingsOnSet); # CD 0017 } # CD 0015 start } elsif( $arg[ 0 ] eq "sound" ) { if( $id ne "none" ) { if( defined($arg[ 1 ]) ) { $cmdstr = "$hash->{PLAYERMAC} alarm update id:$id "; my $url=join( " ", @arg[1..$#arg]); readingsSingleUpdate( $hash, "alarm".$n."_sound", $url, $donotnotify ) if($updateReadingsOnSet); # CD 0017 if (defined($hash->{helper}{alarmPlaylists})) { foreach my $e ( keys %{$hash->{helper}{alarmPlaylists}} ) { if($url eq $hash->{helper}{alarmPlaylists}{$e}{title}) { $url=$hash->{helper}{alarmPlaylists}{$e}{url}; last; } } } # CD 0034 überprüfen ob gültige url, wenn nicht, versuchen file:// anzuhängen if($url !~ /:\/\//) { $url='/' . $url if ($url =~ /^[a-zA-Z]:\\/); # für Windows $url=~ s/\\/\//g; $url='file://' . $url; } # CD 0034 end $cmdstr .= "url:" . uri_escape(decode('utf-8',$url)); # CD 0017 decode hinzugefügt # CD 0034 playlist: in url: geändert IOWrite( $hash, $cmdstr . "\n" ); # CD 0017 reaktiviert # CD 0034 \n hinzugefügt } else { my $msg = "SB_PLAYER_Set: alarm, no value for sound."; Log3( $hash, 3, $msg ); return( $msg ); } } } elsif( $arg[ 0 ] eq "repeat" ) { if( $id ne "none" ) { if( defined($arg[ 1 ]) ) { $cmdstr = "$hash->{PLAYERMAC} alarm update id:$id "; if(($arg[ 1 ] eq "1")||($arg[ 1 ] eq "on")||($arg[ 1 ] eq "yes")) { $cmdstr .= "repeat:1\n"; readingsSingleUpdate( $hash, "alarm".$n."_repeat", "on", $donotnotify ) if($updateReadingsOnSet); # CD 0017 } else { $cmdstr .= "repeat:0\n"; readingsSingleUpdate( $hash, "alarm".$n."_repeat", "off", $donotnotify ) if($updateReadingsOnSet); # CD 0017 } IOWrite( $hash, $cmdstr ); } else { my $msg = "SB_PLAYER_Set: alarm, no value for repeat."; Log3( $hash, 3, $msg ); return( $msg ); } } } elsif( $arg[ 0 ] eq "wdays" ) { if( $id ne "none" ) { if( defined($arg[ 1 ]) ) { $cmdstr = "$hash->{PLAYERMAC} alarm update id:$id "; my $dow=SB_PLAYER_CheckWeekdays(join( "", @arg[1..$#arg])); # CD 0017 $cmdstr .= "dow:" . $dow . "\n"; # CD 0016 SB_PLAYER_CheckWeekdays verwenden IOWrite( $hash, $cmdstr ); # CD 0017 start if($updateReadingsOnSet) { my $rdaystr=""; if ($dow ne "") { $rdaystr = "Mo" if( index( $dow, "1" ) != -1 ); $rdaystr .= " Tu" if( index( $dow, "2" ) != -1 ); $rdaystr .= " We" if( index( $dow, "3" ) != -1 ); $rdaystr .= " Th" if( index( $dow, "4" ) != -1 ); $rdaystr .= " Fr" if( index( $dow, "5" ) != -1 ); $rdaystr .= " Sa" if( index( $dow, "6" ) != -1 ); $rdaystr .= " Su" if( index( $dow, "0" ) != -1 ); } else { $rdaystr = "none"; } $rdaystr =~ s/^\s+|\s+$//g; readingsSingleUpdate( $hash, "alarm".$n."_wdays", $rdaystr, $donotnotify ); } # CD 0017 end } else { my $msg = "SB_PLAYER_Set: no weekdays specified for alarm."; Log3( $hash, 3, $msg ); return( $msg ); } } } elsif( $arg[ 0 ] eq "time" ) { if( $id ne "none" ) { # split the time string up if( !defined($arg[ 1 ]) ) { my $msg = "SB_PLAYER_Set: no alarm time given."; Log3( $hash, 3, $msg ); return( $msg ); } my @buf = split( ":", $arg[ 1 ] ); $buf[ 2 ] = 0 if( scalar( @buf ) == 2 ); # CD 0016, von MM übernommen, geändert if( scalar( @buf ) != 3 ) { my $msg = "SB_PLAYER_Set: please use hh:mm:ss for alarm time."; Log3( $hash, 3, $msg ); return( $msg ); } my $secs = ( $buf[ 0 ] * 3600 ) + ( $buf[ 1 ] * 60 ) + $buf[ 2 ]; $cmdstr = "$hash->{PLAYERMAC} alarm update id:$id "; $cmdstr .= "time:" . $secs . "\n"; IOWrite( $hash, $cmdstr ); # CD 0017 start if($updateReadingsOnSet) { my $buf = sprintf( "%02d:%02d:%02d", int( scalar( $secs ) / 3600 ), int( ( $secs % 3600 ) / 60 ), int( $secs % 60 ) ); readingsSingleUpdate( $hash, "alarm".$n."_time", $buf, $donotnotify ); } # CD 0017 end } # CD 0015 end } elsif( $arg[ 0 ] eq "delete" ) { if( $id ne "none" ) { $cmdstr = "$hash->{PLAYERMAC} alarm delete id:$id\n"; IOWrite( $hash, $cmdstr ); # readingsSingleUpdate( $hash, "alarmid$n", "none", 1 ); # CD 0015 deaktiviert } } else { my $msg = "SB_PLAYER_Set: unkown argument ".$arg[ 0 ]." for alarm given."; Log3( $hash, 3, $msg ); return( $msg ); } return( undef ); } # CD 0016, neu, von MM übernommen # ---------------------------------------------------------------------------- # Check weekdays string # ---------------------------------------------------------------------------- sub SB_PLAYER_CheckWeekdays( $ ) { my ($wdayargs) = @_; my $weekdays = ''; if(index($wdayargs,"Mo") != -1 || index($wdayargs,"1") != -1) { $weekdays.='1,'; } if(index($wdayargs,"Tu") != -1 || index($wdayargs,"Di") != -1 || index($wdayargs,"2") != -1) { $weekdays.='2,'; } if(index($wdayargs,"We") != -1 || index($wdayargs,"Mi") != -1 || index($wdayargs,"3") != -1) { $weekdays.='3,'; } if(index($wdayargs,"Th") != -1 || index($wdayargs,"Do") != -1 || index($wdayargs,"4") != -1) { $weekdays.='4,'; } if(index($wdayargs,"Fr") != -1 || index($wdayargs,"5") != -1) { $weekdays.='5,'; } if(index($wdayargs,"Sa") != -1 || index($wdayargs,"6") != -1) { $weekdays.='6,'; } if(index($wdayargs,"Su") != -1 || index($wdayargs,"So") != -1 || index($wdayargs,"0") != -1) { $weekdays.='0'; } if(index($wdayargs,"all") != -1 || index($wdayargs,"daily") != -1 || index($wdayargs,"7") != -1) { $weekdays='0,1,2,3,4,5,6'; } if(index($wdayargs,"none") != -1) # || index($wdayargs,"once") != -1) # CD 0016 once funktioniert so nicht, muss über repeat gemacht werden { $weekdays='7'; } $weekdays=~ s/,$//; # CD 0019 letztes , entfernen return $weekdays; } # ---------------------------------------------------------------------------- # Status update - just internal use and invoked by the timer # ---------------------------------------------------------------------------- sub SB_PLAYER_GetStatus( $ ) { my ($hash) = @_; my $name = $hash->{NAME}; my $strbuf = ""; Log3( $hash, 5, "SB_PLAYER_GetStatus: called" ); # CD 0014 start - Anzahl Anfragen begrenzen if(!defined($hash->{helper}{lastGetStatus})||($hash->{helper}{lastGetStatus}{helper}{lastGetStatus}, now: ".gettimeofday(); $hash->{helper}{lastGetStatus}=gettimeofday(); # we fire the respective questions and parse the answers in parse IOWrite( $hash, "$hash->{PLAYERMAC} artist ?\n" ); IOWrite( $hash, "$hash->{PLAYERMAC} album ?\n" ); IOWrite( $hash, "$hash->{PLAYERMAC} title ?\n" ); IOWrite( $hash, "$hash->{PLAYERMAC} playlist url ?\n" ); IOWrite( $hash, "$hash->{PLAYERMAC} remote ?\n" ); IOWrite( $hash, "$hash->{PLAYERMAC} status 0 500 tags:Kcu\n" ); # CD 0030 u added to tags #IOWrite( $hash, "$hash->{PLAYERMAC} alarm playlists 0 200\n" ) if (!defined($hash->{helper}{alarmPlaylists})); # CD 0016 get available elements for alarms before querying the alarms # CD 0026 wird über Server verteilt IOWrite( $hash, "$hash->{PLAYERMAC} alarms 0 200 tags:all filter:all\n" ); # CD 0015 filter added # MM 0016 start IOWrite( $hash, "$hash->{PLAYERMAC} playerpref alarmsEnabled ?\n" ); IOWrite( $hash, "$hash->{PLAYERMAC} playerpref alarmDefaultVolume ?\n" ); IOWrite( $hash, "$hash->{PLAYERMAC} playerpref alarmTimeoutSeconds ?\n" ); IOWrite( $hash, "$hash->{PLAYERMAC} playerpref alarmSnoozeSeconds ?\n" ); # MM 0016 end # CD 0007 IOWrite( $hash, "$hash->{PLAYERMAC} playerpref syncVolume ?\n" ); # CD 0009 IOWrite( $hash, "$hash->{PLAYERMAC} playlist name ?\n" ); # CD 0014 IOWrite( $hash, "$hash->{PLAYERMAC} duration ?\n" ); SB_PLAYER_QueryElapsedTime($hash); } # CD 0014 end # the other values below are provided by our server. we don't # need to ask again if( $hash->{PLAYERIP} eq "?" ) { # the server doesn't care about us IOWrite( $hash, "$hash->{PLAYERMAC} player ip ?\n" ); } if( $hash->{MODEL} eq "?" ) { IOWrite( $hash, "$hash->{PLAYERMAC} player model ?\n" ); } if( $hash->{CANPOWEROFF} eq "?" ) { IOWrite( $hash, "$hash->{PLAYERMAC} player canpoweroff ?\n" ); } if( $hash->{PLAYERNAME} eq "?" ) { IOWrite( $hash, "$hash->{PLAYERMAC} name ?\n" ); } if( ReadingsVal( $name, "state", "?" ) eq "?" ) { IOWrite( $hash, "$hash->{PLAYERMAC} power ?\n" ); } if( ReadingsVal( $name, "connected", "?" ) eq "?" ) { IOWrite( $hash, "$hash->{PLAYERMAC} connected ?\n" ); } # do and update of the status RemoveInternalTimer( $hash ); # CD 0014 my $iv=AttrVal($name, "statusRequestInterval", 300); # CD 0037 InternalTimer( gettimeofday() + $iv, "SB_PLAYER_GetStatus", $hash, 0 ) if $iv>0; Log3( $hash, 5, "SB_PLAYER_GetStatus: leaving" ); return( ); } # ---------------------------------------------------------------------------- # called from the IODev for Broadcastmessages # ---------------------------------------------------------------------------- sub SB_PLAYER_RecBroadcast( $$@ ) { my ( $hash, $cmd, $msg, $bin ) = @_; my $name = $hash->{NAME}; Log3( $hash, 5, "SB_PLAYER_Broadcast($name): called with $msg" ); # let's see what we got. Split the data at the space my @args = split( " ", $msg ); if( $cmd eq "SERVER" ) { # a message from the server if( $args[ 0 ] eq "OFF" ) { # the server is off, so are we RemoveInternalTimer( $hash ); readingsSingleUpdate( $hash, "state", "off", 1 ); readingsSingleUpdate( $hash, "power", "off", 1 ); SB_PLAYER_Amplifier( $hash ); } elsif( $args[ 0 ] eq "ON" ) { # the server is back #readingsSingleUpdate( $hash, "state", "on", 1 ); # CD 0011 ob der Player eingeschaltet ist, ist hier noch nicht bekannt, SB_PLAYER_GetStatus abwarten #readingsSingleUpdate( $hash, "power", "on", 1 ); # CD 0011 ob der Player eingeschaltet ist, ist hier noch nicht bekannt, SB_PLAYER_GetStatus abwarten # do and update of the status RemoveInternalTimer( $hash ); # CD 0016 InternalTimer( gettimeofday() + 10, "SB_PLAYER_GetStatus", $hash, 0 ); } elsif( $args[ 0 ] eq "IP" ) { $hash->{SBSERVER} = $args[ 1 ]; } else { # unkown broadcast message } } elsif( $cmd eq "FAVORITES" ) { if( $args[ 0 ] eq "ADD" ) { # format: ADD IODEVname ID shortentry $hash->{helper}{SB_PLAYER_Favs}{$args[3]}{ID} = $args[ 2 ]; $hash->{helper}{SB_PLAYER_Favs}{$args[3]}{URL} = $args[ 4 ]; # CD 0021 hinzugefügt if( $hash->{FAVSTR} eq "" ) { $hash->{FAVSTR} = $args[ 3 ]; # CD Test für Leerzeichen join(" ",@args[ 4..$#args ]); } else { $hash->{FAVSTR} .= "," . $args[ 3 ]; # CD Test für Leerzeichen join(" ",@args[ 4..$#args ]); } # CD 0016 start, provisorisch um alarmPlaylists zu aktualisieren, TODO: muss von 97_SB_SERVER kommen RemoveInternalTimer( $hash ); # CD 0016 InternalTimer( gettimeofday() + 3, "SB_PLAYER_GetStatus", $hash, 0 ); #end } elsif( $args[ 0 ] eq "FLUSH" ) { undef( %{$hash->{helper}{SB_PLAYER_Favs}} ); $hash->{FAVSTR} = ""; delete($hash->{helper}{alarmPlaylists}) if (defined($hash->{helper}{alarmPlaylists})); # CD 0016 } else { } } elsif( $cmd eq "SYNCMASTER" ) { if( $args[ 0 ] eq "ADD" ) { if( $args[ 1 ] ne $hash->{PLAYERNAME} ) { $hash->{helper}{SB_PLAYER_SyncMasters}{$args[1]}{MAC} = $args[ 2 ]; if( $hash->{SYNCMASTERS} eq "" ) { $hash->{SYNCMASTERS} = $args[ 1 ]; } else { $hash->{SYNCMASTERS} .= "," . $args[ 1 ]; } } } elsif( $args[ 0 ] eq "FLUSH" ) { undef( %{$hash->{helper}{SB_PLAYER_SyncMasters}} ); $hash->{SYNCMASTERS} = ""; } else { } } elsif( $cmd eq "PLAYLISTS" ) { if( $args[ 0 ] eq "ADD" ) { # CD 0014 Playlists mit fhem_* ignorieren if($args[3]=~/^fhem_.*/) { # CD 0036 start if($args[3]=~/$name/) { my $plname=$args[1]; # kein Name ? if($args[1]=~/^fhem_$name$/) { $plname='default'; } else { $plname=~s/^fhem_$name//; $plname=~s/^_//; } Log3( $hash, 4, "SB_PLAYER_RecbroadCast($name): - adding - PLAYLISTS ADD " . "name:$args[1] id:$args[2] uid:$plname" ); $hash->{helper}{myPlaylists}{$plname}{ID} = $args[ 2 ]; $hash->{helper}{myPlaylists}{$plname}{NAME} = $args[ 1 ]; } else { # CD 0036 end Log3( $hash, 5, "SB_PLAYER_RecbroadCast($name): - ignoring - PLAYLISTS ADD " . "name:$args[1] id:$args[2] uid:$args[3]" ); } } else { # CD 0014 end Log3( $hash, 5, "SB_PLAYER_RecbroadCast($name): PLAYLISTS ADD " . "name:$args[1] id:$args[2] uid:$args[3]" ); $hash->{helper}{SB_PLAYER_Playlists}{$args[3]}{ID} = $args[ 2 ]; $hash->{helper}{SB_PLAYER_Playlists}{$args[3]}{NAME} = $args[ 1 ]; if( $hash->{SERVERPLAYLISTS} eq "" ) { $hash->{SERVERPLAYLISTS} = $args[ 3 ]; } else { $hash->{SERVERPLAYLISTS} .= "," . $args[ 3 ]; } } # CD 0014 # CD 0016 start, provisorisch um alarmPlaylists zu aktualisieren, TODO: muss von 97_SB_SERVER kommen RemoveInternalTimer( $hash ); # CD 0016 InternalTimer( gettimeofday() + 3, "SB_PLAYER_GetStatus", $hash, 0 ); #end } elsif( $args[ 0 ] eq "FLUSH" ) { undef( %{$hash->{helper}{SB_PLAYER_Playlists}} ); undef( %{$hash->{helper}{myPlaylists}} ); # CD 0036 $hash->{SERVERPLAYLISTS} = ""; delete($hash->{helper}{alarmPlaylists}) if (defined($hash->{helper}{alarmPlaylists})); # CD 0016 } else { } # CD 0026 start } elsif( $cmd eq "ALARMPLAYLISTS" ) { if( $args[ 0 ] eq "ADD" ) { $hash->{helper}{alarmPlaylists}{$args[ 1 ]}{$args[ 2 ]}=join( " ", @args[3..$#args]); } elsif( $args[ 0 ] eq "FLUSH" ) { delete($hash->{helper}{alarmPlaylists}) if (defined($hash->{helper}{alarmPlaylists})); } # CD 0026 end } else { } } # ---------------------------------------------------------------------------- # parse the return on the alarms status # ---------------------------------------------------------------------------- # wird von SB_PLAYER_Parse aufgerufen, readingsBeginUpdate ist aktiv sub SB_PLAYER_ParseAlarms( $@ ) { my ( $hash, @data ) = @_; my $name = $hash->{NAME}; # CD 0016 start {ALARMSCOUNT} verschieben nach reload if (defined($hash->{ALARMSCOUNT})) { $hash->{helper}{ALARMSCOUNT}=$hash->{ALARMSCOUNT}; delete($hash->{ALARMSCOUNT}); } # CD 0016 my $lastAlarmCount=$hash->{helper}{ALARMSCOUNT}; # CD 0016 ALARMSCOUNT nach {helper} verschoben if( $data[ 0 ] =~ /^([0-9])*/ ) { shift( @data ); } if( $data[ 0 ] =~ /^([0-9])*/ ) { shift( @data ); } fhem( "deletereading $name alarmid.*" ); # CD 0015 alte readings entfernen my $alarmcounter=0; # CD 0015 foreach( @data ) { if( $_ =~ /^(id:)(\S{8})/ ) { # id is 8 non-white-space characters # example: id:0ac7f3a2 $alarmcounter+=1; # CD 0015 readingsBulkUpdate( $hash, "alarm".$alarmcounter."_id", $2 ); # CD 0015 next; } elsif( $_ =~ /^(dow:)([0-9,]*)/ ) { # C 0016 + durch * ersetzt, für dow: ohne Tage # example: dow:1,2,4,5,6 my $rdaystr=""; # CD 0015 if ($2 ne "") { # CD 0016 if( index( $2, "1" ) != -1 ) { $rdaystr = "Mo"; # CD 0015 } if( index( $2, "2" ) != -1 ) { $rdaystr .= " Tu"; # CD 0015 } if( index( $2, "3" ) != -1 ) { $rdaystr .= " We"; # CD 0015 } if( index( $2, "4" ) != -1 ) { $rdaystr .= " Th"; # CD 0015 } if( index( $2, "5" ) != -1 ) { $rdaystr .= " Fr"; # CD 0015 } if( index( $2, "6" ) != -1 ) { $rdaystr .= " Sa"; # CD 0015 } if( index( $2, "0" ) != -1 ) { $rdaystr .= " Su"; # CD 0015 } } else { # CD 0016 $rdaystr = "none"; # CD 0016 } # CD 0016 $rdaystr =~ s/^\s+|\s+$//g; # CD 0015 readingsBulkUpdate( $hash, "alarm".$alarmcounter."_wdays", $rdaystr ); # CD 0015 next; } elsif( $_ =~ /^(enabled:)([0|1])/ ) { # example: enabled:1 if( $2 eq "1" ) { readingsBulkUpdate( $hash, "alarm".$alarmcounter."_state", "on" ); # CD 0015 } else { readingsBulkUpdate( $hash, "alarm".$alarmcounter."_state", "off" ); # CD 0015 } next; } elsif( $_ =~ /^(repeat:)([0|1])/ ) { # example: repeat:1 if( $2 eq "1" ) { readingsBulkUpdate( $hash, "alarm".$alarmcounter."_repeat", "yes" ); # CD 0015 } else { readingsBulkUpdate( $hash, "alarm".$alarmcounter."_repeat", "no" ); # CD 0015 } next; } elsif( $_ =~ /^(time:)([0-9]+)/ ) { # example: time:25200 my $buf = sprintf( "%02d:%02d:%02d", int( scalar( $2 ) / 3600 ), int( ( $2 % 3600 ) / 60 ), int( $2 % 60 ) ); readingsBulkUpdate( $hash, "alarm".$alarmcounter."_time", $buf ); # CD 0015 next; } elsif( $_ =~ /^(volume:)(\d{1,2})/ ) { # example: volume:50 readingsBulkUpdate( $hash, "alarm".$alarmcounter."_volume", $2 ); # CD 0015 next; } elsif( $_ =~ /^(url:)(\S+)/ ) { # CD 0015 start my $pn=SB_SERVER_FavoritesName2UID(uri_unescape($2)); if(defined($hash->{helper}{alarmPlaylists}) && defined($hash->{helper}{alarmPlaylists}{$pn})) { readingsBulkUpdate( $hash, "alarm".$alarmcounter."_sound", $hash->{helper}{alarmPlaylists}{$pn}{title} ); } else { readingsBulkUpdate( $hash, "alarm".$alarmcounter."_sound", $2 ); } # CD 0015 end next; # CD 0016 start } elsif( $_ =~ /^(filter:)(\S+)/ ) { next; # CD 0016 end # MM 0016 start } elsif( $_ =~ /^(tags:)(\S+)/ ) { next; } elsif( $_ =~ /^(fade:)([0|1])/ ) { # example: fade:1 if( $2 eq "1" ) { readingsBulkUpdate( $hash, "alarmsFadeIn", "on" ); # CD 0016 von MM übernommen, Namen geändert } else { readingsBulkUpdate( $hash, "alarmsFadeIn", "off" ); # CD 0016 von MM übernommen, Namen geändert } next; } elsif( $_ =~ /^(count:)([0-9]+)/ ) { $hash->{helper}{ALARMSCOUNT} = $2; # CD 0016 ALARMSCOUNT nach {helper} verschoben next; } else { Log3( $hash, 1, "SB_PLAYER_Alarms($name): Unknown data ($_)"); next; } # MM 0016 end } # CD 0015 nicht mehr vorhandene Alarme löschen if ($lastAlarmCount>$hash->{helper}{ALARMSCOUNT}) { # CD 0016 ALARMSCOUNT nach {helper} verschoben for(my $i=$hash->{helper}{ALARMSCOUNT}+1;$i<=$lastAlarmCount;$i++) { # CD 0016 ALARMSCOUNT nach {helper} verschoben fhem( "deletereading $name alarm".$i."_.*" ); } } } # ---------------------------------------------------------------------------- # used for checking, if the string contains a valid MAC adress # ---------------------------------------------------------------------------- sub SB_PLAYER_IsValidMAC( $ ) { my $instr = shift( @_ ); my $d = "[0-9A-Fa-f]"; my $dd = "$d$d"; if( $instr =~ /($dd([:-])$dd(\2$dd){4})/og ) { if( $instr =~ /^(00[:-]){5}(00)$/) { # CD 0032 00:00:00:00:00:00 is not a valid MAC return( 0 ); } else { return( 1 ); } } else { return( 0 ); } } # ---------------------------------------------------------------------------- # used to turn on our server # ---------------------------------------------------------------------------- sub SB_PLAYER_ServerTurnOn( $ ) { my ( $hash ) = @_; my $name = $hash->{NAME}; my $servername; Log3( $hash, 5, "SB_PLAYER_ServerTurnOn($name): please implement me" ); return; fhem( "set $servername on" ); } # ---------------------------------------------------------------------------- # used to turn on a connected amplifier # ---------------------------------------------------------------------------- # CD 0012 start sub SB_PLAYER_tcb_DelayAmplifier( $ ) { # CD 0014 Name geändert my($in ) = shift; my(undef,$name) = split(':',$in); my $hash = $defs{$name}; #Log 0,"SB_PLAYER_DelayAmplifier"; $hash->{helper}{AMPLIFIERDELAYOFF}=1; SB_PLAYER_Amplifier($hash); } # CD 0012 end sub SB_PLAYER_Amplifier( $ ) { my ( $hash ) = @_; my $name = $hash->{NAME}; if( ( $hash->{AMPLIFIER} eq "none" ) || ( !defined( $defs{$hash->{AMPLIFIER}} ) ) ) { # amplifier not specified delete($hash->{helper}{AMPLIFIERDELAYOFF}) if defined($hash->{helper}{AMPLIFIERDELAYOFF}); # CD 0012 return; } my $setvalue = "off"; Log3( $hash, 4, "SB_PLAYER_Amplifier($name): called" ); if( AttrVal( $name, "amplifier", "play" ) eq "play" ) { my $thestatus = ReadingsVal( $name, "playStatus", "pause" ); Log3( $hash, 5, "SB_PLAYER_Amplifier($name): with mode play " . "and status:$thestatus" ); if( ( $thestatus eq "playing" ) || ( $thestatus eq "paused" ) ) { $setvalue = "on"; } elsif( $thestatus eq "stopped" ) { $setvalue = "off"; } else { $setvalue = "off"; } } elsif( AttrVal( $name, "amplifier", "on" ) eq "on" ) { my $thestatus = ReadingsVal( $name, "power", "off" ); Log3( $hash, 5, "SB_PLAYER_Amplifier($name): with mode on " . "and status:$thestatus" ); if( $thestatus eq "on" ) { $setvalue = "on"; } else { $setvalue = "off"; } } else { Log3( $hash, 1, "SB_PLAYER_Amplifier($name): ATTR amplifier " . "set to wrong value [on|play]" ); return; } my $actualState = ReadingsVal( "$hash->{AMPLIFIER}", "state", "off" ); Log3( $hash, 5, "SB_PLAYER_Amplifier($name): actual:$actualState " . "and set:$setvalue" ); if ( $actualState ne $setvalue) { # CD 0012 start - Abschalten über Attribut verzögern, generell verzögern damit set-Event funktioniert my $delayAmp=($setvalue eq "off")?AttrVal( $name, "amplifierDelayOff", 0 ):0.1; $delayAmp=0.01 if($delayAmp==0); if (!defined($hash->{helper}{AMPLIFIERDELAYOFF})) { Log3( $hash, 5, 'SB_PLAYER_Amplifier($name): delaying amplifier on/off' ); RemoveInternalTimer( "DelayAmplifier:$name"); InternalTimer( gettimeofday() + $delayAmp, "SB_PLAYER_tcb_DelayAmplifier", # CD 0014 Name geändert "DelayAmplifier:$name", 0 ); return; } # CD 0012 end fhem( "set $hash->{AMPLIFIER} $setvalue" ); Log3( $hash, 5, "SB_PLAYER_Amplifier($name): amplifier changed to " . $setvalue ); } else { Log3( $hash, 5, "SB_PLAYER_Amplifier($name): no amplifier " . "state change" ); } delete($hash->{helper}{AMPLIFIERDELAYOFF}) if (defined($hash->{helper}{AMPLIFIERDELAYOFF})); return; } # ---------------------------------------------------------------------------- # update the coverart image # ---------------------------------------------------------------------------- sub SB_PLAYER_CoverArt( $ ) { my ( $hash ) = @_; my $name = $hash->{NAME}; # return if (defined($hash->{helper}{CoverOk}) && ($hash->{helper}{CoverOk} == 1) && ( $hash->{ISREMOTESTREAM} eq "0" )); # CD 0026 added # CD 0027 removed # CD 0003 fix missing server if(!defined($hash->{SBSERVER})||($hash->{SBSERVER} eq '?')) { if ((defined($hash->{IODev})) && (defined($hash->{IODev}->{IP}))) { $hash->{SBSERVER}=$hash->{IODev}->{IP} . ":" . AttrVal( $hash->{IODev}, "httpport", "9000" ); } } # CD 0003 end my $lastCoverartUrl=$hash->{COVERARTURL}; # CD 0013 # compile the link to the album cover if(( $hash->{ISREMOTESTREAM} eq "0" ) || ($hash->{ARTWORKURL} =~ /imageproxy%2F/)) { # CD 0026 LMS 7.8/7.9 $hash->{COVERARTURL} = "http://" . $hash->{SBSERVER} . "/music/" . "current/cover_" . AttrVal( $name, "coverartheight", 50 ) . "x" . AttrVal( $name, "coverartwidth", 50 ) . ".jpg?player=$hash->{PLAYERMAC}&x=".int(rand(100000)); # CD 0025 added rand() to force browser refresh $hash->{helper}{CoverOk}=1; # CD 0026 added } elsif( $hash->{ISREMOTESTREAM} eq "1" ) { # CD 0017 Abfrage || ( $hash->{ISREMOTESTREAM} == 1 ) entfernt # CD 0011 überprüfen ob überhaupt eine URL vorhanden ist if($hash->{ARTWORKURL} ne "?") { # CD 0034 Abfrage für Spotify und LMS < 7.8, ungetest, #674, KernSani # CD 0035 Code von KernSani übernommen, #676 if ($hash->{ARTWORKURL} =~ /spotifyimage%2Fspotify/) { my $cover = "cover.jpg"; my $coverArtWithSize = "cover_".AttrVal( $name, "coverartheight", 50 )."x".AttrVal( $name, "coverartwidth", 50 )."_o.jpg"; $hash->{ARTWORKURL} =~ s/$cover/$coverArtWithSize/g; $hash->{COVERARTURL} = "http://" . $hash->{SBSERVER} . "/" . uri_unescape($hash->{ARTWORKURL}); } else { $hash->{COVERARTURL} = "http://www.mysqueezebox.com/public/" . "imageproxy?u=" . $hash->{ARTWORKURL} . "&h=" . AttrVal( $name, "coverartheight", 50 ) . "&w=". AttrVal( $name, "coverartwidth", 50 ); } } else { $hash->{COVERARTURL} = "http://" . $hash->{SBSERVER} . "/music/" . $hash->{COVERID} . "/cover_" . AttrVal( $name, "coverartheight", 50 ) . "x" . AttrVal( $name, "coverartwidth", 50 ) . ".jpg"; } # CD 0011 Ende } else { $hash->{COVERARTURL} = "http://" . $hash->{SBSERVER} . "/music/" . $hash->{COVERID} . "/cover_" . AttrVal( $name, "coverartheight", 50 ) . # CD 0011 -160206228 durch $hash->{COVERID} ersetzt "x" . AttrVal( $name, "coverartwidth", 50 ) . ".jpg"; } # CD 0004, url as reading readingsBulkUpdate( $hash, "coverarturl", $hash->{COVERARTURL}); if( ( $hash->{COVERARTLINK} eq "none" ) || ( !defined( $defs{$hash->{COVERARTLINK}} ) ) || ( $hash->{COVERARTURL} eq "?" ) ) { # weblink not specified return; } else { if ($lastCoverartUrl ne $hash->{COVERARTURL}) { # CD 0013 nur bei Änderung aktualisieren fhem( "modify " . $hash->{COVERARTLINK} . " image " . $hash->{COVERARTURL} ); } # CD 0013 } } # ---------------------------------------------------------------------------- # Handle the return for a playerstatus query # ---------------------------------------------------------------------------- sub SB_PLAYER_ParsePlayerStatus( $$ ) { my( $hash, $dataptr ) = @_; my $name = $hash->{NAME}; my $leftover = ""; my $cur = ""; my $playlistIds = ""; # CD 0014 my $refreshIds=0; # CD 0014 # typically the start index being a number if( $dataptr->[ 0 ] =~ /^([0-9])*/ ) { shift( @{$dataptr} ); } else { Log3( $hash, 5, "SB_PLAYER_ParsePlayerStatus($name): entry is " . "not the start number" ); return; } # typically the max index being a number if( $dataptr->[ 0 ] =~ /^([0-9])*/ ) { if($dataptr->[ 0 ]>1) { $refreshIds=1; # CD 0014 delete($hash->{helper}{playlistUrls}) if(defined($hash->{helper}{playlistUrls})); # CD 0030 } shift( @{$dataptr} ); } else { Log3( $hash, 5, "SB_PLAYER_ParsePlayerStatus($name): entry is " . "not the end number" ); return; } my $datastr = join( " ", @{$dataptr} ); # replace funny stuff # CD 0006 all keywords with spaces must be converted here $datastr =~ s/mixer volume/mixervolume/g; # CD 0006 replaced mixertreble with mixer treble $datastr =~ s/mixer treble/mixertreble/g; $datastr =~ s/mixer bass/mixerbass/g; $datastr =~ s/mixer pitch/mixerpitch/g; $datastr =~ s/playlist repeat/playlistrepeat/g; $datastr =~ s/playlist shuffle/playlistshuffle/g; $datastr =~ s/playlist index/playlistindex/g; # CD 0003 $datastr =~ s/playlist mode/playlistmode/g; Log3( $hash, 5, "SB_PLAYER_ParsePlayerStatus($name): data to parse: " . $datastr ); my @data1 = split( " ", $datastr ); # the rest of the array should now have the data, we're interested in # CD 0006 - deaktiviert, SB_PLAYER_ParsePlayerStatus kann nur von SB_PLAYER_Parse aufgerufen werden, dort ist readingsBeginUpdate aber bereits aktiv #readingsBeginUpdate( $hash ); # set default values for stuff not always send $hash->{SYNCMASTER} = "none"; $hash->{SYNCGROUP} = "none"; $hash->{SYNCMASTERPN} = "none"; # CD 0018 $hash->{SYNCGROUPPN} = "none"; # CD 0018 $hash->{SYNCED} = "no"; $hash->{COVERID} = "?"; $hash->{ARTWORKURL} = "?"; $hash->{ISREMOTESTREAM} = "0"; # needed for scanning the MAC Adress my $d = "[0-9A-Fa-f]"; my $dd = "$d$d"; # needed for scanning the IP adress my $e = "[0-9]"; my $ee = "$e$e"; # CD 0003 start, fix handling of spaces my @data2; my $last_d=""; # loop through the results foreach( @data1 ) { if( index( $_, ":" ) < 2 ) { $last_d = $last_d . " " . $_; next; } if( $last_d ne "" ) { push @data2,$last_d; } $last_d=$_; } if( $last_d ne "" ) { push @data2,$last_d; } # CD 0003 end my $lastId=0; # CD 0030 # loop through the results foreach( @data2 ) { my $cur=$_; if( $cur =~ /^(player_connected:)([0-9]*)/ ) { if( $2 == "1" ) { readingsBulkUpdate( $hash, "connected", $2 ); readingsBulkUpdate( $hash, "presence", "present" ); } else { readingsBulkUpdate( $hash, "connected", $3 ); readingsBulkUpdate( $hash, "presence", "absent" ); } next; } elsif( $cur =~ /^(player_ip:)(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d{3,5})/ ) { if( $hash->{PLAYERIP} ne "?" ) { $hash->{PLAYERIP} = $2; } next; } elsif( $cur =~ /^(player_name:)(.*)/ ) { if( $hash->{PLAYERNAME} ne "?" ) { $hash->{PLAYERNAME} = $2; } next; } elsif( $cur =~ /^(power:)([0-9\.]*)/ ) { if( $2 eq "1" ) { readingsBulkUpdate( $hash, "state", "on" ); # CD 0041 hinzugefügt readingsBulkUpdate( $hash, "power", "on" ); SB_PLAYER_Amplifier( $hash ); # CD 0042 start if($hash->{helper}{ttsstate}==TTS_WAITFORPOWERON) { RemoveInternalTimer( "TTSStartAfterPowerOn:$name"); InternalTimer( gettimeofday() + 0.01, "SB_PLAYER_tcb_TTSStartAfterPowerOn", "TTSStartAfterPowerOn:$name", 0 ); } # CD 0042 end } else { # CD 0042 start if(($hash->{helper}{playerStatusOK}==0) && ($hash->{helper}{ttsstate}==TTS_WAITFORPOWERON)) { $hash->{helper}{ttsPowerWasOff}=1; IOWrite( $hash, "$hash->{PLAYERMAC} power 1\n" ); } # CD 0042 end readingsBulkUpdate( $hash, "state", "off" ); # CD 0041 hinzugefügt readingsBulkUpdate( $hash, "power", "off" ); SB_PLAYER_Amplifier( $hash ); } next; } elsif( $cur =~ /^(signalstrength:)([0-9\.]*)/ ) { if( $2 eq "0" ) { readingsBulkUpdate( $hash, "signalstrength", "wired" ); } else { readingsBulkUpdate( $hash, "signalstrength", "$2" ); } next; } elsif( $cur =~ /^(mode:)(.*)/ ) { if( $2 eq "play" ) { readingsBulkUpdate( $hash, "playStatus", "playing" ); SB_PLAYER_Amplifier( $hash ); } elsif( $2 eq "stop" ) { readingsBulkUpdate( $hash, "playStatus", "stopped" ); SB_PLAYER_Amplifier( $hash ); } elsif( $2 eq "pause" ) { readingsBulkUpdate( $hash, "playStatus", "paused" ); SB_PLAYER_Amplifier( $hash ); } else { # unkown } next; } elsif( $cur =~ /^(sync_master:)($dd[:|-]$dd[:|-]$dd[:|-]$dd[:|-]$dd[:|-]$dd)/ ) { $hash->{SYNCMASTER} = $2; $hash->{SYNCED} = "yes"; $hash->{SYNCMASTERPN} = SB_PLAYER_MACToPlayername($hash,$2); # CD 0018 next; } elsif( $cur =~ /^(sync_slaves:)(.*)/ ) { $hash->{SYNCGROUP} = $2; # CD 0018 start my @macs=split(",",$hash->{SYNCGROUP}); my $syncgroup; foreach ( @macs ) { my $mac=$_; my $dev=SB_PLAYER_MACToPlayername($hash,$mac); $syncgroup.="," if(defined($syncgroup)); if(defined($dev)) { $syncgroup.=$dev; } else { if($mac eq $hash->{PLAYERMAC}) { $syncgroup.=$name; } else { $syncgroup.=$mac; } } } $hash->{SYNCGROUPPN} = $syncgroup; # CD 0018 end readingsBulkUpdate( $hash, "synced", "$hash->{SYNCMASTERPN},$hash->{SYNCGROUPPN}" ); # Matthew 0019 hinzugefügt next; } elsif( $cur =~ /^(will_sleep_in:)([0-9\.]*)/ ) { $hash->{WILLSLEEPIN} = "$2 secs"; next; } elsif( $cur =~ /^(mixervolume:)(.*)/ ) { if( ( index( $2, "+" ) != -1 ) || ( index( $2, "-" ) != -1 ) ) { # that was a relative value. We do nothing and fire an update IOWrite( $hash, "$hash->{PLAYERMAC} mixer volume ?\n" ); } else { SB_PLAYER_UpdateVolumeReadings( $hash, $2, true ); # CD 0007 start if((defined($hash->{helper}{setSyncVolume}) && ($hash->{helper}{setSyncVolume} != $2))|| (!defined($hash->{helper}{setSyncVolume}))) { SB_PLAYER_SetSyncedVolume($hash,$2); } delete $hash->{helper}{setSyncVolume}; # CD 0007 end } next; } elsif( $cur =~ /^(playlistshuffle:)(.*)/ ) { if( $2 eq "0" ) { readingsBulkUpdate( $hash, "shuffle", "off" ); } elsif( $2 eq "1") { readingsBulkUpdate( $hash, "shuffle", "song" ); } elsif( $2 eq "2") { readingsBulkUpdate( $hash, "shuffle", "album" ); } else { readingsBulkUpdate( $hash, "shuffle", "?" ); } next; } elsif( $cur =~ /^(playlistrepeat:)(.*)/ ) { if( $2 eq "0" ) { readingsBulkUpdate( $hash, "repeat", "off" ); } elsif( $2 eq "1") { readingsBulkUpdate( $hash, "repeat", "one" ); } elsif( $2 eq "2") { readingsBulkUpdate( $hash, "repeat", "all" ); } else { readingsBulkUpdate( $hash, "repeat", "?" ); } next; } elsif( $cur =~ /^(playlistname:)(.*)/ ) { readingsBulkUpdate( $hash, "currentPlaylistName", $2 ); next; } elsif( $cur =~ /^(artwork_url:)(.*)/ ) { $hash->{ARTWORKURL} = uri_escape( $2 ); #Log 0,"Update Artwork: ".$hash->{ARTWORKURL}; #SB_PLAYER_CoverArt( $hash ); next; } elsif( $cur =~ /^(coverid:)(.*)/ ) { $hash->{COVERID} = $2; next; } elsif( $cur =~ /^(remote:)(.*)/ ) { $hash->{ISREMOTESTREAM} = $2; next; # CD 0014 start } elsif( $cur =~ /^(duration:)(.*)/ ) { readingsBulkUpdate( $hash, "duration", $2 ); next; } elsif( $cur =~ /^(time:)(.*)/ ) { $hash->{helper}{elapsedTime}{VAL}=$2; $hash->{helper}{elapsedTime}{TS}=gettimeofday(); delete($hash->{helper}{saveLocked}) if (($hash->{helper}{ttsstate}==TTS_IDLE) && defined($hash->{helper}{saveLocked})); next; } elsif( $cur =~ /^(playlist_tracks:)(.*)/ ) { readingsBulkUpdate( $hash, "playlistTracks", $2 ); next; } elsif( $cur =~ /^(playlist_cur_index:)(.*)/ ) { readingsBulkUpdate( $hash, "playlistCurrentTrack", $2+1 ); next; } elsif( $cur =~ /^(id:)(.*)/ ) { if($refreshIds==1) { if($playlistIds) { $playlistIds=$playlistIds.",$2"; } else { $playlistIds=$2; } $hash->{helper}{playlistIds}=$playlistIds; } $lastId=$2; # CD 0030 next; # CD 0030 start } elsif( $cur =~ /^(url:)(.*)/ ) { if($refreshIds==1) { if ($lastId<0) { $hash->{helper}{playlistUrls}{$lastId}=$2; } } next; # CD 0030 end # CD 0014 end } else { next; } } $hash->{helper}{playerStatusOK}=1; # CD 0042 # Matthew 0019 start if( $hash->{SYNCED} ne "yes") { readingsBulkUpdate( $hash, "synced", "none" ); } # Matthew 0019 end # CD 0003 moved before readingsEndUpdate # update the cover art SB_PLAYER_CoverArt( $hash ); # CD 0006 - deaktiviert, SB_PLAYER_ParsePlayerStatus kann nur von SB_PLAYER_Parse aufgerufen werden, dort ist readingsBeginUpdate aber bereits aktiv #readingsEndUpdate( $hash, 1 ); } # CD 0018 start # ---------------------------------------------------------------------------- # convert MAC to playername # ---------------------------------------------------------------------------- sub SB_PLAYER_MACToPlayername( $$ ) { my( $hash, $mac ) = @_; my $name = $hash->{NAME}; return $hash->{PLAYERNAME} if($hash->{PLAYERMAC} eq $mac); my $dev; foreach my $e ( keys %{$hash->{helper}{SB_PLAYER_SyncMasters}} ) { if($mac eq $hash->{helper}{SB_PLAYER_SyncMasters}{$e}{MAC}) { $dev=$e; last; } } return $dev; } # CD 0018 end # CD 0014 start # ---------------------------------------------------------------------------- # estimate elapsed time # ---------------------------------------------------------------------------- sub SB_PLAYER_EstimateElapsedTime( $ ) { my( $hash ) = @_; my $name = $hash->{NAME}; my $d=ReadingsVal($name,"duration",0); # nur wenn duration>0 if(($d ne '?')&&($d>0)) { # CD 0033 check for '?' # wenn {helper}{elapsedTime} bekannt ist als Basis verwenden if((defined($hash->{helper}{elapsedTime}))&&($hash->{helper}{elapsedTime}{VAL}>0)) { $hash->{helper}{elapsedTime}{VAL}=$hash->{helper}{elapsedTime}{VAL}+(gettimeofday()-$hash->{helper}{elapsedTime}{TS}); $hash->{helper}{elapsedTime}{TS}=gettimeofday(); } else { my $dTS=time_str2num(ReadingsTimestamp($name,"duration",0)); my $n=gettimeofday(); if(($n-$dTS)<=$d) { $hash->{helper}{elapsedTime}{VAL}=gettimeofday()-$dTS; $hash->{helper}{elapsedTime}{TS}=gettimeofday(); } else { $hash->{helper}{elapsedTime}{VAL}=$d; $hash->{helper}{elapsedTime}{TS}=gettimeofday(); } } } else { delete($hash->{helper}{elapsedTime}) if(defined($hash->{helper}{elapsedTime})); } } # CD 0014 end # ---------------------------------------------------------------------------- # update the volume readings # ---------------------------------------------------------------------------- sub SB_PLAYER_UpdateVolumeReadings( $$$ ) { my( $hash, $vol, $bulk ) = @_; my $name = $hash->{NAME}; $vol = int($vol); # MM 0016, Fix wegen AirPlay-Plugin if( $bulk == true ) { readingsBulkUpdate( $hash, "volumeStraight", $vol ); if( $vol > 0 ) { readingsBulkUpdate( $hash, "volume", $vol ); } else { readingsBulkUpdate( $hash, "volume", "muted" ); } } else { readingsSingleUpdate( $hash, "volumeStraight", $vol, 0 ); if( $vol > 0 ) { readingsSingleUpdate( $hash, "volume", $vol, 0 ); } else { readingsSingleUpdate( $hash, "volume", "muted", 0 ); } } return; } # ---------------------------------------------------------------------------- # set volume of synced players # ---------------------------------------------------------------------------- sub SB_PLAYER_SetSyncedVolume( $$ ) { my( $hash, $vol ) = @_; my $name = $hash->{NAME}; return if (!defined($hash->{SYNCED}) || ($hash->{SYNCED} ne "yes")); my $sva=AttrVal($name, "syncVolume", undef); my $t=$hash->{SYNCGROUP}.",".$hash->{SYNCMASTER}; $vol = int($vol); # MM 0016, Fix wegen AirPlay-Plugin $hash->{helper}{setSyncVolume}=$vol; if(defined($sva) && ($sva ne "0") && ($sva ne "1")) { my @pl=split(",",$t); my @chlds=devspec2array("TYPE=SB_PLAYER"); foreach (@pl) { if (($_ ne "?") && ($_ ne $hash->{PLAYERMAC})) { my $mac=$_; foreach(@chlds) { my $chash=$defs{$_}; if(defined($chash) && defined($chash->{PLAYERMAC}) && ($chash->{PLAYERMAC} eq $mac)) { my $sva2=AttrVal($chash->{NAME}, "syncVolume", undef); if (defined($sva2) && ($sva eq $sva2)) { if ($vol>0) { # CD 0010 if(ReadingsVal($chash->{NAME}, "volumeStraight", $vol)!=$vol) { #Log 0,$chash->{NAME}." setting volume to ".$vol." (from ".$hash->{NAME}.")"; $chash->{helper}{setSyncVolume}=$vol; fhem "set ".$chash->{NAME}." volumeStraight ".$vol." x"; } # CD 0010 start } else { if(ReadingsVal($chash->{NAME}, "volume", "x") ne "muted") { #Log 0,$chash->{NAME}." muting (from ".$hash->{NAME}.")"; IOWrite( $chash, "$chash->{PLAYERMAC} mixer muting 1\n" ); } # CD 0010 end } } } } } } } return; } # ---------------------------------------------------------------------------- # load/save player state # # CD 0036 # ---------------------------------------------------------------------------- sub SB_PLAYER_PlayerStatefileName($) { my( $name ) = @_; my $statefile = $attr{global}{statefile}; $statefile = substr $statefile,0,rindex($statefile,'/')+1; return $statefile ."sbps_$name.dd.save"; } sub SB_PLAYER_SavePlayerStates($) { my( $hash ) = @_; my $name = $hash->{NAME}; return "No saved playlists found" unless(defined($hash->{helper}{savedPlayerState})); return "No statefile specified" if(!$attr{global}{statefile}); my $statefile = SB_PLAYER_PlayerStatefileName($name); if(open(FH, ">$statefile")) { my $t = localtime; print FH "#$t\n"; my $dumper = Data::Dumper->new([]); $dumper->Terse(1); $dumper->Values([$hash->{helper}{savedPlayerState}]); print FH $dumper->Dump; close(FH); } else { my $msg = "SB_PLAYER_SavePlayerStates: Cannot open $statefile: $!"; Log3 $hash, 1, $msg; } return undef; } sub SB_PLAYER_LoadPlayerStates($) { my ($hash) = @_; my $name = $hash->{NAME}; return "No statefile specified" if(!$attr{global}{statefile}); my $statefile = SB_PLAYER_PlayerStatefileName($name); if(open(FH, "<$statefile")) { my $encoded; while (my $line = ) { chomp $line; next if($line =~ m/^#.*$/); $encoded .= $line; } close(FH); return if( !defined($encoded) ); my $decoded = eval $encoded; $hash->{helper}{savedPlayerState} = $decoded; } else { my $msg = "SB_PLAYER_LoadPlayerStates: no playlists file found"; Log3 undef, 4, $msg; } return undef; } # ############################################################################## # No PERL code beyond this line # ############################################################################## 1; =pod =begin html

SB_PLAYER

    Define
      define <name> SB_PLAYER <player_mac_adress> [<ampl>] [<coverart>]

      This module allows you to control Squeezebox Media Players connected with a defined Logitech Media Server. An SB_SERVER device is needed to work.
      Normally you don't need to define your SB_PLAYERS because autocreate will do that if enabled.

      • <player_mac_adress>: Mac adress of the player found in the LMS.

      Optional

      • <[ampl]>: You can define an FHEM Device to command when an on or off event is received. With the attribute amplifier you can specify whether to command the selected FHEM Device on on|off or play|stop.
      • <[coverart]>: You can define an FHEM weblink. The player will update the weblink with the current coverart. Useful for putting coverarts in the floorplan.


    Set
      set <name> <command> [<parameter>]

      This module supports the following commands:
      SB_Player related commands:

      • play - starts the playback (might only work if previously paused).
      • pause [<0|1>] - toggles between play and pause. With parameter 0 it unpauses and with 1 it pauses the player, it doesn't matter which state it had before
      • stop - stops the playback
      • next|channelUp - jumps to the next track
      • prev|channelDown - jumps to the previous track or the beginning of the current track.
      • mute - toggles between muted and unmuted
      • volume <n> - sets the volume to <n>. <n> must be a number between 0 and 100
      • volumeStraight <n> - same as volume
      • volumeDown <n> - volume down
      • volumeUp <n> - volume up
      • on - sets the player on if possible. Otherwise it does play
      • off - sets the player off if possible. Otherwise it does stop
      • shuffle on|off|song|album - Enables/Disables shuffle mode
      • repeat one|all|off - Sets the repeat mode
      • sleep <n> - Sets the player off in <n> seconds and fade the player volume down
      • favorites <favorit> - Empties the current playlist and starts the selected playlist. Favorites are selectable through a dropdown list
      • talk|sayText <text> - Saves the current playlist, speaks the selected text with google TTS and resumes saved playlist
      • playlist track|album|artist|genre|year <x> - Empties the current playlist and starts the track, album or artist <x>
      • playlist genre:<genre> artist:<artist> album:<album> - Empties the current playlist and starts the track which will match the search. You can use * as wildcard for everything.

      • Example:
        set myplayer playlist genre:* artist:Whigfield album:*

      • playlist play <filename|playlistname> - Empties the current playlist and starts the track or playlist
      • playlist add <filename|playlistname> - Adds the specified file or playlist at the end of the current playlist
      • playlist insert <filename|playlistname> - Inserts the specified file or playlist after the current song in the current playlist.
      • statusRequest - Update of all readings
      • sync <playerName[,playerName...]> [new|asSlave] - Syncs with other players for multiroom function. Other players are selectable through a dropdown list. The shown player is the master. Options:
        • new - creates a new group with the selected players instead of adding to existing group
        • asSlave - adds player as slave to a player/group

        Examples:
        set playerA sync playerB    adds playerB to playerA's group
        set playerA sync playerB,playerC,playerD    adds playerB, C and D to playerA's group
        set playerA sync playerB new    creates a new group with playerA and B, playerA is master
        set playerA sync playerB asSlave    adds playerA to playerB's group

      • unsync - Unsyncs the player from multiroom group
      • playlists - Empties the current playlist and starts the selected playlist. Playlists are selectable through a dropdown list
      • cliraw <command> - Sends the <command> to the LMS CLI for selected player
      • save [name] - Saves the current player state
      • recall [name] [options] - Recalls a saved player state, options:
        • del - delete saved state after restore
        • delonly - delete saved state without restoring
        • off - ignore saved power setting, turn player off after restore
        • on - ignore saved power setting, turn player on after restore
        • play - ignore saved play state, start playing after restore
        • stop - ignore saved play state, stop player after restore
      • show <line1> <line2> <duration> - displays text on the player
        • line1 - Text for first line
        • line2 - Text for second line
        • duration - Duration for apperance in seconds

      Alarms
        You can define up to 2 alarms. set sbradio alarm1 set <weekday> <time>
      • <weekday> - Number of weekday. The week starts with Sunday and is 0
      • <time> - Timeformat HH:MM[:SS]
      • Example:
        set sbradio alarm1 set 5 12:23:17
        set sbradio alarm2 set 4 17:18:00
      • alarm<1|2> delete - Delete alarm
      • alarm<1|2> volume <n> - Set volume for alarm to <n>
      • alarm<1|2> <enable|disable> - Enable or disable alarm
      • allalarms <enable|disable> - Enable or disable all alarms


    Generated Readings
    • READING - READING DESCRIPTIONS
    • /* CHECK TODO


    Attributes
    • IODev
      The name of the SB_SERVER device to which this player is connected.

    • donotnotify
      Disables all events from the device. Must be explicitly set to false to enable events.

    • volumeLimit
      Sets the volume limit of the player between 0 and 100. 100 means the function is disabled.

    • amplifier
      Defines how a configured amplifier will be controlled. If set to on, the amplifier will be turned on and off with the player. If set to play the amplifier will be turned on on play and off on stop.

    • amplifierDelayOff
      Sets the delay in seconds before turning the amplifier off after the player has stopped or been turned off.

    • updateReadingsOnSet
      If set to true most readings are immediately updated when a set command is executed without waiting for the reply from the server.

    • statusRequestInterval
      Defines the interval in seconds for the automatic status request. Default: 300

    • ttsDelay
      Delay in seconds before starting text to speech playback. If two values, separated by comma, are given, the first is used if the player is on, the second if the player is off.

    • ttsMP3FileDir
      The directory which should be used as a default for text-embedded MP3-Files.

    • ttsPrefix
      Text prepended to every text to speech output

    • ttsVolume
      Volume for text to speech, if not set the current volume will be used. If the attribute ttsoptions contains ignorevolumelimit the defined volume limit will be ignoerd for text to speech

=end html =cut