diff --git a/fhem/contrib/97_SB_SERVER.pm b/fhem/contrib/97_SB_SERVER.pm index c88a33c83..edbab02ef 100644 --- a/fhem/contrib/97_SB_SERVER.pm +++ b/fhem/contrib/97_SB_SERVER.pm @@ -11,12 +11,12 @@ # # Written by bugster_de # -# Contributions from: Siggi85, Oliv06, ChrisD +# Contributions from: Siggi85, Oliv06, ChrisD, Eberhard # # ############################################################################ # # This is absolutley open source. Please feel free to use just as you -# like. Please note, that no warranty is given and no liability +# like. Please note, that no warranty is given and no liability # granted # # ############################################################################ @@ -24,7 +24,7 @@ # we have the following readings # power on|off # version the version of the SB Server -# serversecure is the CLI port protected with a passowrd? +# serversecure is the CLI port protected with a password? # # ############################################################################ # @@ -38,8 +38,7 @@ # CLIPORT the port for the CLI interface of the server # # ############################################################################ -# based on 97_SB_SERVER.pm 9811 beta 0023 CD -# ############################################################################ + package main; use strict; use warnings; @@ -49,6 +48,7 @@ use URI::Escape; # include for using the perl ping command use Net::Ping; use Encode qw(decode encode); # CD 0009 hinzugefügt +#use Text::Unidecode; no if $] >= 5.017011, warnings => 'experimental::smartmatch'; @@ -59,12 +59,19 @@ my $favsetstring = "favorites: "; # this is the buffer for commands, we queue up when server is power=off my %SB_SERVER_CmdStack; +my @SB_SERVER_AL_PLS; +my @SB_SERVER_FAVS; +my @SB_SERVER_PLS; +my @SB_SERVER_SM; + # include this for the self-calling timer we use later on use Time::HiRes qw(gettimeofday time); use constant { true => 1, false => 0 }; use constant { TRUE => 1, FALSE => 0 }; -use constant SB_SERVER_VERSION => '0023'; +use constant SB_SERVER_VERSION => '0037'; + +my $SB_SERVER_hasDataDumper = 1; # CD 0024 # ---------------------------------------------------------------------------- # Initialisation routine called upon start-up of FHEM @@ -97,26 +104,55 @@ sub SB_SERVER_Initialize( $ ) { $hash->{AttrList} .= "doalivecheck:true,false "; $hash->{AttrList} .= "maxcmdstack "; $hash->{AttrList} .= "httpport "; + $hash->{AttrList} .= "enablePlugins "; $hash->{AttrList} .= "ignoredIPs ignoredMACs internalPingProtocol:icmp,tcp,udp,syn,stream,none "; # CD 0021 none hinzugefügt $hash->{AttrList} .= $readingFnAttributes; + # CD 0024 + eval "use Data::Dumper"; + $SB_SERVER_hasDataDumper = 0 if($@); } +# CD 0032 start +sub SB_SERVER_SetAttrList( $ ) { + my ($hash) = @_; + + my $attrList; + $attrList = "alivetimer maxfavorites "; + $attrList .= "doalivecheck:true,false "; + $attrList .= "maxcmdstack "; + $attrList .= "httpport "; + $attrList .= "ignoredIPs ignoredMACs internalPingProtocol:icmp,tcp,udp,syn,stream,none "; # CD 0021 none hinzugefügt + my $applist="enablePlugins"; + if (defined($hash->{helper}{apps})) { + $applist.=":multiple-strict"; + foreach my $app ( sort keys %{$hash->{helper}{apps}} ) { + $applist.=",$app"; + } + } + $attrList .= "$applist "; + + $attrList .= $readingFnAttributes; + + $modules{$defs{$hash->{NAME}}{TYPE}}{AttrList}=$attrList; +} +# CD 0032 end + # ---------------------------------------------------------------------------- # called when defining a module # ---------------------------------------------------------------------------- sub SB_SERVER_Define( $$ ) { my ($hash, $def ) = @_; - + #my $name = $hash->{NAME}; Log3( $hash, 4, "SB_SERVER_Define: called" ); # first of all close existing connections DevIo_CloseDev( $hash ); - + my @a = split("[ \t][ \t]*", $def); - + # do we have the right number of arguments? if( ( @a < 3 ) || ( @a > 7 ) ) { Log3( $hash, 3, "SB_SERVER_Define: falsche Anzahl an Argumenten" ); @@ -137,34 +173,54 @@ sub SB_SERVER_Define( $$ ) { $hash->{RCCNAME} = "none"; $hash->{USERNAME} = "?"; $hash->{PASSWORD} = "?"; + + my ($user,$password); + my @newDef; + # parse the user spec foreach( @a ) { if( $_ =~ /^(RCC:)(.*)/ ) { $hash->{RCCNAME} = $2; + push @newDef,$_; next; } elsif( $_ =~ /^(WOL:)(.*)/ ) { $hash->{WOLNAME} = $2; + push @newDef,$_; next; } elsif( $_ =~ /^(PRESENCE:)(.*)/ ) { # CD 0007 $hash->{PRESENCENAME} = $2; # CD 0007 + push @newDef,$_; next; # CD 0007 } elsif( $_ =~ /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}):(\d{3,5})/ ) { $hash->{IP} = $1; $hash->{CLIPORT} = $2; + push @newDef,$_; next; } elsif( $_ =~ /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/ ) { $hash->{IP} = $1; $hash->{CLIPORT} = 9090; + push @newDef,$_; next; } elsif( $_ =~ /^(USER:)(.*)/ ) { - $hash->{USERNAME} = $2; + $user=$2 if($2 ne 'yes'); + $hash->{USERNAME} = 'yes'; + push @newDef,'USER:yes'; } elsif( $_ =~ /^(PASSWORD:)(.*)/ ) { - $hash->{PASSWORD} = $2; + $password=$2 if($2 ne 'yes'); + $hash->{PASSWORD} = 'yes'; + push @newDef,'PASSWORD:yes'; } else { + push @newDef,$_; next; } } + # CD 0031 + if(defined($user) && defined($password)) { + SB_SERVER_storePassword($hash,$user,$password); + $hash->{DEF} = join(' ',@newDef); + } + $hash->{LASTANSWER} = "none"; # used for alive checking of the CLI interface @@ -201,98 +257,98 @@ sub SB_SERVER_Define( $$ ) { # server on / off if( !defined( $hash->{READINGS}{power}{VAL} ) ) { $hash->{READINGS}{power}{VAL} = "?"; - $hash->{READINGS}{power}{TIME} = $tn; + $hash->{READINGS}{power}{TIME} = $tn; } # the server version if( !defined( $hash->{READINGS}{serverversion}{VAL} ) ) { $hash->{READINGS}{serverversion}{VAL} = "?"; - $hash->{READINGS}{serverversion}{TIME} = $tn; + $hash->{READINGS}{serverversion}{TIME} = $tn; } # is the CLI port secured with password? if( !defined( $hash->{READINGS}{serversecure}{VAL} ) ) { $hash->{READINGS}{serversecure}{VAL} = "?"; - $hash->{READINGS}{serversecure}{TIME} = $tn; + $hash->{READINGS}{serversecure}{TIME} = $tn; } # the maximum number of favorites on the server if( !defined( $hash->{READINGS}{favoritestotal}{VAL} ) ) { $hash->{READINGS}{favoritestotal}{VAL} = 0; - $hash->{READINGS}{favoritestotal}{TIME} = $tn; + $hash->{READINGS}{favoritestotal}{TIME} = $tn; } # is a scan in progress if( !defined( $hash->{READINGS}{scanning}{VAL} ) ) { $hash->{READINGS}{scanning}{VAL} = "?"; - $hash->{READINGS}{scanning}{TIME} = $tn; + $hash->{READINGS}{scanning}{TIME} = $tn; } # the scan in progress if( !defined( $hash->{READINGS}{scandb}{VAL} ) ) { $hash->{READINGS}{scandb}{VAL} = "?"; - $hash->{READINGS}{scandb}{TIME} = $tn; + $hash->{READINGS}{scandb}{TIME} = $tn; } # the scan already completed if( !defined( $hash->{READINGS}{scanprogressdone}{VAL} ) ) { $hash->{READINGS}{scanprogressdone}{VAL} = "?"; - $hash->{READINGS}{scanprogressdone}{TIME} = $tn; + $hash->{READINGS}{scanprogressdone}{TIME} = $tn; } # the scan already completed if( !defined( $hash->{READINGS}{scanprogresstotal}{VAL} ) ) { $hash->{READINGS}{scanprogresstotal}{VAL} = "?"; - $hash->{READINGS}{scanprogresstotal}{TIME} = $tn; + $hash->{READINGS}{scanprogresstotal}{TIME} = $tn; } # did the last scan fail if( !defined( $hash->{READINGS}{scanlastfailed}{VAL} ) ) { $hash->{READINGS}{scanlastfailed}{VAL} = "?"; - $hash->{READINGS}{scanlastfailed}{TIME} = $tn; + $hash->{READINGS}{scanlastfailed}{TIME} = $tn; } # number of players connected to us if( !defined( $hash->{READINGS}{players}{VAL} ) ) { $hash->{READINGS}{players}{VAL} = "?"; - $hash->{READINGS}{players}{TIME} = $tn; + $hash->{READINGS}{players}{TIME} = $tn; } # number of players connected to mysqueezebox if( !defined( $hash->{READINGS}{players_mysb}{VAL} ) ) { $hash->{READINGS}{players_mysb}{VAL} = "?"; - $hash->{READINGS}{players_mysb}{TIME} = $tn; + $hash->{READINGS}{players_mysb}{TIME} = $tn; } # number of players connected to other servers in our network if( !defined( $hash->{READINGS}{players_other}{VAL} ) ) { $hash->{READINGS}{players_other}{VAL} = "?"; - $hash->{READINGS}{players_other}{TIME} = $tn; + $hash->{READINGS}{players_other}{TIME} = $tn; } # number of albums in the database if( !defined( $hash->{READINGS}{db_albums}{VAL} ) ) { $hash->{READINGS}{db_albums}{VAL} = "?"; - $hash->{READINGS}{db_albums}{TIME} = $tn; + $hash->{READINGS}{db_albums}{TIME} = $tn; } # number of artists in the database if( !defined( $hash->{READINGS}{db_artists}{VAL} ) ) { $hash->{READINGS}{db_artists}{VAL} = "?"; - $hash->{READINGS}{db_artists}{TIME} = $tn; + $hash->{READINGS}{db_artists}{TIME} = $tn; } # number of songs in the database if( !defined( $hash->{READINGS}{db_songs}{VAL} ) ) { $hash->{READINGS}{db_songs}{VAL} = "?"; - $hash->{READINGS}{db_songs}{TIME} = $tn; + $hash->{READINGS}{db_songs}{TIME} = $tn; } # number of genres in the database if( !defined( $hash->{READINGS}{db_genres}{VAL} ) ) { $hash->{READINGS}{db_genres}{VAL} = "?"; - $hash->{READINGS}{db_genres}{TIME} = $tn; + $hash->{READINGS}{db_genres}{TIME} = $tn; } # initialize the command stack @@ -306,10 +362,17 @@ sub SB_SERVER_Define( $$ ) { $hash->{helper}{pingCounter}=0; # CD 0004 $hash->{helper}{lastPRESENCEstate}='?'; # CD 0023 - + # CD 0009 set module version, needed for reload $hash->{helper}{SB_SERVER_VERSION}=SB_SERVER_VERSION; - + + if (!defined($hash->{OLDDEF})) { # CD 0024 + SB_SERVER_LoadSyncGroups($hash) if($SB_SERVER_hasDataDumper==1); + SB_SERVER_LoadServerStates($hash) if($SB_SERVER_hasDataDumper==1); + SB_SERVER_FixSyncGroupNames($hash); # CD 0027 + SB_SERVER_UpdateSgReadings($hash); # CD 0027 + } + # open the IO device my $ret; @@ -328,9 +391,9 @@ sub SB_SERVER_Define( $$ ) { # do and update of the status # CD disabled - #InternalTimer( gettimeofday() + 10, - # "SB_SERVER_Alive", - # $hash, + #InternalTimer( gettimeofday() + 10, + # "SB_SERVER_Alive", + # $hash, # 0 ); Log3( $hash, 4, "SB_SERVER_Define: leaving" ); @@ -345,29 +408,29 @@ sub SB_SERVER_Define( $$ ) { sub SB_SERVER_Undef( $$ ) { my ($hash, $arg) = @_; my $name = $hash->{NAME}; - + Log3( $hash, 4, "SB_SERVER_Undef: called" ); - + # no idea what this is for. Copied from 10_TCM.pm # presumably to notify the clients, that the server is gone foreach my $d (sort keys %defs) { - if( ( defined( $defs{$d} ) ) && + if( ( defined( $defs{$d} ) ) && ( defined( $defs{$d}{IODev} ) ) && ( $defs{$d}{IODev} == $hash ) ) { delete $defs{$d}{IODev}; } } - + # terminate the CLI session DevIo_SimpleWrite( $hash, "listen 0\n", 0 ); DevIo_SimpleWrite( $hash, "exit\n", 0 ); # close the device - DevIo_CloseDev( $hash ); - + DevIo_CloseDev( $hash ); + # remove all timers we created RemoveInternalTimer( $hash ); - + return( undef ); } @@ -376,7 +439,7 @@ sub SB_SERVER_Undef( $$ ) { # ---------------------------------------------------------------------------- sub SB_SERVER_Shutdown( $$ ) { my ($hash, $dev) = @_; - + Log3( $hash, 4, "SB_SERVER_Shutdown: called" ); # terminate the CLI session @@ -384,7 +447,7 @@ sub SB_SERVER_Shutdown( $$ ) { DevIo_SimpleWrite( $hash, "exit\n", 0 ); # close the device - DevIo_CloseDev( $hash ); + DevIo_CloseDev( $hash ); # remove all timers we created RemoveInternalTimer( $hash ); @@ -405,7 +468,7 @@ sub SB_SERVER_Ready( $ ) { # check for bad/missing password if (defined($hash->{helper}{SB_SERVER_LMS_Status})) { if (time()-($hash->{helper}{SB_SERVER_LMS_Status})<2) { - if( ( $hash->{USERNAME} ne "?" ) && + if( ( $hash->{USERNAME} ne "?" ) && ( $hash->{PASSWORD} ne "?" ) ) { $hash->{LASTANSWER}='invalid username or password ?'; Log( 1, "SB_SERVER($name): invalid username or password ?" ); @@ -417,7 +480,7 @@ sub SB_SERVER_Ready( $ ) { } delete($hash->{helper}{SB_SERVER_LMS_Status}); } - + # we need to re-open the device if( $hash->{STATE} eq "disconnected" ) { if( ( ReadingsVal( $name, "power", "on" ) eq "on" ) || @@ -426,7 +489,7 @@ sub SB_SERVER_Ready( $ ) { # clean up first RemoveInternalTimer( $hash ); readingsSingleUpdate( $hash, "power", "off", 1 ); - + $hash->{CLICONNECTION} = "off"; # CD 0007 # and signal to our clients @@ -464,12 +527,12 @@ sub SB_SERVER_Ready( $ ) { } # ---------------------------------------------------------------------------- -# Get functions +# Get functions # ---------------------------------------------------------------------------- sub SB_SERVER_Get( $@ ) { my ($hash, @a) = @_; my $name = $hash->{NAME}; - + Log3( $hash, 4, "SB_SERVER_Get: called" ); if( @a != 2 ) { @@ -481,7 +544,7 @@ sub SB_SERVER_Get( $@ ) { # ---------------------------------------------------------------------------- -# Attr functions +# Attr functions # ---------------------------------------------------------------------------- sub SB_SERVER_Attr( @ ) { my $cmd = shift( @_ ); @@ -491,24 +554,53 @@ sub SB_SERVER_Attr( @ ) { Log( 4, "SB_SERVER_Attr($name): called with @args" ); - if( $cmd eq "set" ) { - if( $args[ 0 ] eq "alivetimer" ) { + if( $args[ 0 ] eq "alivetimer" ) { + if( $cmd eq "set" ) { # CD 0021 start RemoveInternalTimer( "SB_SERVER_Alive:$name"); InternalTimer( gettimeofday() + $args[ 1 ], "SB_SERVER_tcb_Alive", - "SB_SERVER_Alive:$name", + "SB_SERVER_Alive:$name", 0 ); # CD 0021 end } + } elsif( $args[ 0 ] eq "httpport" ) { # CD 0015 bei Änderung des Ports diesen an Clients schicken - if( $args[ 0 ] eq "httpport" ) { - SB_SERVER_Broadcast( $hash, "SERVER", + if( $cmd eq "set" ) { + SB_SERVER_Broadcast( $hash, "SERVER", "IP " . $hash->{IP} . ":" . $args[ 1 ] ); } + } elsif( $args[ 0 ] eq "enablePlugins" ) { + # CD 0070 bei Änderung Status abfragen + if( $cmd eq "set" ) { + if($init_done>0) { + if(defined($hash->{helper}{apps})) { + my @enabledApps=split(',',$args[ 1 ]); + foreach my $app (@enabledApps) { + if(defined($hash->{helper}{apps}{$app})) { + DevIo_SimpleWrite( $hash, ($hash->{helper}{apps}{$app}{cmd})." items 0 200\n", 0 ); + } + } + } + } + } else { + if(defined($hash->{helper}{apps})) { + my @enabledApps=split(',',AttrVal($name,'enablePlugins','')); + foreach my $app (@enabledApps) { + if(defined($hash->{helper}{apps}{$app})) { + SB_SERVER_Broadcast( $hash, "PLAYLISTS", "FLUSH ".($hash->{helper}{apps}{$app}{cmd}), undef ); + SB_SERVER_Broadcast( $hash, "FAVORITES", "FLUSH ".($hash->{helper}{apps}{$app}{cmd}), undef ); + } + } + } + + delete($hash->{helper}{apps}) if(defined($hash->{helper}{apps})); + delete($hash->{helper}{appcmd}) if(defined($hash->{helper}{appcmd})); + DevIo_SimpleWrite( $hash, "apps 0 200\n", 0 ); + } } - return; # 0032 betateilchen/mahowi + return; # 0033 betateilchen/mahowi } @@ -529,65 +621,509 @@ sub SB_SERVER_Set( $@ ) { my $cmd = shift( @a ); if( $cmd eq "?" ) { - # this one should give us a drop down list - my $res = "Unknown argument ?, choose one of " . - "on renew:noArg abort:noArg cliraw statusRequest:noArg "; - $res .= "rescan:full,playlists "; - #$res .= "addToFHEMUpdate:noArg removeFromFHEMUpdate:noArg"; # CD 0019 - - return( $res ); + # this one should give us a drop down list + my $res = "Unknown argument ?, choose one of " . + "on renew:noArg abort:noArg cliraw statusRequest:noArg "; + $res .= "rescan:full,playlists "; + #$res .= "addToFHEMUpdate:noArg removeFromFHEMUpdate:noArg "; # CD 0019 + $res .= "syncGroup "; # CD 0024 + $res .= "save "; # CD 0025 + #$res .= "getData "; # CD 0030 + my $out=""; + if (defined($hash->{helper}{savedServerStates})) { + foreach my $pl ( keys %{$hash->{helper}{savedServerStates}} ) { + $out.=$pl."," unless ($pl=~/xxxSgTalkxxx/); # CD 0027 xxxSgTalkxxx hinzugefügt + } + $out=~s/,$//; + } + $res .= "recall:$out "; + return( $res ); } elsif( $cmd eq "on" ) { if( ReadingsVal( $name, "power", "off" ) eq "off" ) { - # the server is off, try to reactivate it - if( $hash->{WOLNAME} ne "none" ) { - fhem( "set $hash->{WOLNAME} on" ); - $hash->{helper}{WOLFastReconnectUntil}=time()+120; # CD 0007 - $hash->{helper}{WOLFastReconnectNext}=time()+30; # CD 0007 - } - if( $hash->{RCCNAME} ne "none" ) { - fhem( "set $hash->{RCCNAME} on" ); - } + # the server is off, try to reactivate it + if( $hash->{WOLNAME} ne "none" ) { + fhem( "set $hash->{WOLNAME} on" ); + $hash->{helper}{WOLFastReconnectUntil}=time()+120; # CD 0007 + $hash->{helper}{WOLFastReconnectNext}=time()+30; # CD 0007 + } + if( $hash->{RCCNAME} ne "none" ) { + fhem( "set $hash->{RCCNAME} on" ); + } } } elsif( $cmd eq "renew" ) { - Log3( $hash, 5, "SB_SERVER_Set: renew" ); - DevIo_SimpleWrite( $hash, "listen 1\n", 0 ); - + Log3( $hash, 5, "SB_SERVER_Set: renew" ); + DevIo_SimpleWrite( $hash, "listen 1\n", 0 ); } elsif( $cmd eq "abort" ) { - DevIo_SimpleWrite( $hash, "listen 0\n", 0 ); - + DevIo_SimpleWrite( $hash, "listen 0\n", 0 ); } elsif( $cmd eq "statusRequest" ) { - Log3( $hash, 5, "SB_SERVER_Set: statusRequest" ); - DevIo_SimpleWrite( $hash, "version ?\n", 0 ); - DevIo_SimpleWrite( $hash, "serverstatus 0 200\n", 0 ); - DevIo_SimpleWrite( $hash, "favorites items 0 " . - AttrVal( $name, "maxfavorites", 100 ) . " want_url:1\n", # CD 0009 url mit abfragen - 0 ); - DevIo_SimpleWrite( $hash, "playlists 0 200\n", 0 ); - DevIo_SimpleWrite( $hash, "alarm playlists 0 300\n", 0 ); # CD 0011 - + Log3( $hash, 5, "SB_SERVER_Set: statusRequest" ); + DevIo_SimpleWrite( $hash, "version ?\n", 0 ); + DevIo_SimpleWrite( $hash, "serverstatus 0 200\n", 0 ); + DevIo_SimpleWrite( $hash, "favorites items 0 " . + AttrVal( $name, "maxfavorites", 100 ) . " want_url:1\n", # CD 0009 url mit abfragen + 0 ); + DevIo_SimpleWrite( $hash, "playlists 0 200\n", 0 ); + DevIo_SimpleWrite( $hash, "alarm playlists 0 300\n", 0 ); # CD 0011 + # CD 0032 start + DevIo_SimpleWrite( $hash, "apps 0 200\n", 0 ); + if(defined($hash->{helper}{apps})) { + my @enabledApps=split(',',AttrVal($name,'enablePlugins','')); + foreach my $app (@enabledApps) { + if(defined($hash->{helper}{apps}{$app})) { + DevIo_SimpleWrite( $hash, ($hash->{helper}{apps}{$app}{cmd})." items 0 200\n", 0 ); + } + } + } + # CD 0032 end } elsif( $cmd eq "cliraw" ) { # write raw messages to the CLI interface per player my $v = join( " ", @a ); - $v .= "\n"; - Log3( $hash, 5, "SB_SERVER_Set: cliraw: $v " ); + $v .= "\n"; + Log3( $hash, 5, "SB_SERVER_Set: cliraw: $v " ); DevIo_SimpleWrite( $hash, $v, 0 ); # CD 0016 IOWrite in DevIo_SimpleWrite geändert } elsif( $cmd eq "rescan" ) { DevIo_SimpleWrite( $hash, $cmd . " " . $a[ 0 ] . "\n", 0 ); # CD 0016 IOWrite in DevIo_SimpleWrite geändert # CD 0018 start - #} elsif( $cmd eq "addToFHEMUpdate" ) { - # fhem("update add https://raw.githubusercontent.com/ChrisD70/FHEM-Modules/master/autoupdate/sb/controls_squeezebox.txt"); - #} elsif( $cmd eq "removeFromFHEMUpdate" ) { - # fhem("update delete https://raw.githubusercontent.com/ChrisD70/FHEM-Modules/master/autoupdate/sb/controls_squeezebox.txt"); + } elsif( $cmd eq "addToFHEMUpdate" ) { + fhem("update add https://raw.githubusercontent.com/ChrisD70/FHEM-Modules/master/autoupdate/sb/controls_squeezebox.txt"); + } elsif( $cmd eq "removeFromFHEMUpdate" ) { + fhem("update delete https://raw.githubusercontent.com/ChrisD70/FHEM-Modules/master/autoupdate/sb/controls_squeezebox.txt"); # CD 0018 end + # CD 0024 start + } elsif( $cmd eq "syncGroup" ) { + return( "at least one parameter is needed" ) if( @a < 1 ); + + my $updateReadings=0; + + my $subcmd=shift( @a ); + + if($subcmd eq 'addp') { + return( "not enough parameters" ) if( @a < 2 ); + return( "too many parameters" ) if( @a > 2 ); # CD 0027 + my $players=$a[0]; + my $statename=$a[1]; + $statename =~ s/[,;:]/_/g; # CD 0027 Sonderzeichen ersetzen + + SB_SERVER_BuildPlayerList($hash) if(!defined($hash->{helper}{players})); + + if(!defined($hash->{helper}{syncGroups}{$statename})) { + $hash->{helper}{syncGroups}{$statename}{0}{fhemname}=''; + $hash->{helper}{syncGroups}{$statename}{0}{lmsname}=''; + $hash->{helper}{syncGroups}{$statename}{0}{mac}=''; + $hash->{helper}{syncGroups}{$statename}{0}{c}=1; + $updateReadings=1; + } + + for my $pl (split(",",$players)) { + my $found=0; + + foreach my $e ( keys %{$hash->{helper}{syncGroups}{$statename}} ) { + if($e ne '') { + if(defined($hash->{helper}{syncGroups}{$statename}{$e})) { + if(($pl eq $hash->{helper}{syncGroups}{$statename}{$e}{fhemname}) + or ($pl eq $hash->{helper}{syncGroups}{$statename}{$e}{lmsname})) { + $found=1; + last; + } + } + } + } + + if($found==0) { + if(defined($hash->{helper}{players}) && defined($hash->{helper}{players}{$pl})) { + my $c=$hash->{helper}{syncGroups}{$statename}{0}{c}; + + $hash->{helper}{syncGroups}{$statename}{$c}{fhemname}=$hash->{helper}{players}{$pl}{fhemname}; + $hash->{helper}{syncGroups}{$statename}{$c}{lmsname}=$hash->{helper}{players}{$pl}{lmsname}; + $hash->{helper}{syncGroups}{$statename}{$c}{mac}=$hash->{helper}{players}{$pl}{mac}; + + if($hash->{helper}{syncGroups}{$statename}{0}{fhemname} eq '') { + $hash->{helper}{syncGroups}{$statename}{0}{fhemname}=$hash->{helper}{players}{$pl}{fhemname}; + $hash->{helper}{syncGroups}{$statename}{0}{lmsname}=$hash->{helper}{players}{$pl}{lmsname}; + $hash->{helper}{syncGroups}{$statename}{0}{mac}=$hash->{helper}{players}{$pl}{mac}; + } + $hash->{helper}{syncGroups}{$statename}{0}{c}+=1; + $updateReadings=1; # CD 0028 + } + } + } + } elsif($subcmd eq 'removep') { + return( "not enough parameters" ) if( @a < 2 ); + return( "too many parameters" ) if( @a > 2 ); # CD 0027 + my $players=$a[0]; + my $statename=$a[1]; + $statename =~ s/[,;:]/_/g; # CD 0027 Sonderzeichen ersetzen + + if((!defined($hash->{helper}{syncGroups}))||(!defined($hash->{helper}{syncGroups}{$statename}))) { + return( "sync group $statename not found" ); + } + + SB_SERVER_BuildPlayerList($hash) if(!defined($hash->{helper}{players})); + + for my $pl (split(",",$players)) { + foreach my $e ( keys %{$hash->{helper}{syncGroups}{$statename}} ) { + if($e ne '') { + if(defined($hash->{helper}{syncGroups}{$statename}{$e})) { + if(($pl eq $hash->{helper}{syncGroups}{$statename}{$e}{fhemname}) + or ($pl eq $hash->{helper}{syncGroups}{$statename}{$e}{lmsname})) { + + if($e eq '0') { + # master ? + $hash->{helper}{syncGroups}{$statename}{0}{fhemname}=''; + $hash->{helper}{syncGroups}{$statename}{0}{lmsname}=''; + $hash->{helper}{syncGroups}{$statename}{0}{mac}=''; + } else { + delete($hash->{helper}{syncGroups}{$statename}{$e}); + } + $updateReadings=1; # CD 0028 + } + } + } + } + } + # neuen Master suchen ? + if($hash->{helper}{syncGroups}{$statename}{0}{fhemname} eq '') { + foreach my $e ( keys %{$hash->{helper}{syncGroups}{$statename}} ) { + if(($e ne '')&&($e ne '0')) { + $hash->{helper}{syncGroups}{$statename}{0}{fhemname}=$hash->{helper}{syncGroups}{$statename}{$e}{fhemname}; + $hash->{helper}{syncGroups}{$statename}{0}{lmsname}=$hash->{helper}{syncGroups}{$statename}{$e}{lmsname}; + $hash->{helper}{syncGroups}{$statename}{0}{mac}=$hash->{helper}{syncGroups}{$statename}{$e}{mac}; + last; + } + } + } + } elsif($subcmd eq 'masterp') { + return( "not enough parameters" ) if( @a < 2 ); + return( "too many parameters" ) if( @a > 2 ); # CD 0027 + my $pl=$a[0]; + my $statename=$a[1]; + $statename =~ s/[,;:]/_/g; # CD 0027 Sonderzeichen ersetzen + + if((!defined($hash->{helper}{syncGroups}))||(!defined($hash->{helper}{syncGroups}{$statename}))) { + return( "sync group $statename not found" ); + } + + foreach my $e ( keys %{$hash->{helper}{syncGroups}{$statename}} ) { + if(($e ne '')&&($e ne '0')) { + if(defined($hash->{helper}{syncGroups}{$statename}{$e})) { + if(($pl eq $hash->{helper}{syncGroups}{$statename}{$e}{fhemname}) + or ($pl eq $hash->{helper}{syncGroups}{$statename}{$e}{lmsname})) { + + $hash->{helper}{syncGroups}{$statename}{0}{fhemname}=$hash->{helper}{syncGroups}{$statename}{$e}{fhemname}; + $hash->{helper}{syncGroups}{$statename}{0}{lmsname}=$hash->{helper}{syncGroups}{$statename}{$e}{lmsname}; + $hash->{helper}{syncGroups}{$statename}{0}{mac}=$hash->{helper}{syncGroups}{$statename}{$e}{mac}; + + $updateReadings=1; + } + } + } + } + } elsif($subcmd eq 'load') { + my $poweron=0; + + if($a[0] eq 'poweron') { + $poweron=1; + shift(@a); + } + + return( "not enough parameters" ) if( @a == 0 ); + + my $statename=$a[0]; + $statename =~ s/[,;:]/_/g; # CD 0027 Sonderzeichen ersetzen + + SB_SERVER_LoadSyncGroup($hash, $statename, $poweron); # CD 0027 + } elsif($subcmd eq 'delete') { + my $statename=$a[0]; + $statename =~ s/[,;:]/_/g; # CD 0027 Sonderzeichen ersetzen + + delete($hash->{helper}{syncGroups}{$statename}) if(defined($hash->{helper}{syncGroups}{$statename})); + delete($defs{$name}{READINGS}{"sg$statename"}) if(defined($defs{$name}{READINGS}{"sg$statename"})); # CD 0027 + $updateReadings=1; + # CD 0027 start + } elsif($subcmd eq 'deleteall') { + foreach my $e ( keys %{$hash->{helper}{syncGroups}} ) { + if($e ne '') { + if(defined($hash->{helper}{syncGroups}{$e})) { + delete($hash->{helper}{syncGroups}{$e}); + delete($defs{$name}{READINGS}{"sg$e"}) if(defined($defs{$name}{READINGS}{"sg$e"})); + $updateReadings=1; + } + } + } + } elsif($subcmd eq 'talk') { + return "talk already in progress" if(defined($hash->{helper}{sgTalkActivePlayer})); + + my $poweron=0; + + if($a[0] eq 'poweron') { + $poweron=1; + shift(@a); + } + + return( "not enough parameters" ) if( @a < 2 ); + + my $statename=shift(@a); + $statename =~ s/[,;:]/_/g; + + if((!defined($hash->{helper}{syncGroups}))||(!defined($hash->{helper}{syncGroups}{$statename}))) { + return( "sync group $statename not found" ); + } + + # Zustand speichern, Gruppe laden, talk verzögert an Player absetzen + SB_SERVER_Save($hash, 'xxxSgTalkxxx'); + SB_SERVER_LoadSyncGroup($hash, $statename, $poweron); + $hash->{helper}{sgTalkPlayers}=ReadingsVal($name,"sg$statename","-"); + if($hash->{helper}{sgTalkPlayers} eq '-') { + Log3( $hash, 2, "SB_SERVER_Set($name): sgtalk: no players found for group $statename"); + return "no players found"; + } + $hash->{helper}{sgTalkActivePlayer}='waiting for power on'; + $hash->{helper}{sgTalkData}=join(' ', @a); + $hash->{helper}{sgTalkTimeoutPowerOn}=time()+3; + $hash->{helper}{sgTalkGroup}=$statename; # CD 0031 + RemoveInternalTimer( "StartTalk:$name"); + InternalTimer( gettimeofday() + 0.01, + "SB_SERVER_tcb_StartTalk", + "StartTalk:$name", + 0 ); + } elsif($subcmd eq 'resettts') { + SB_SERVER_Recall($hash,'xxxSgTalkxxx del'); + delete $hash->{helper}{sgTalkActivePlayer}; + } elsif($subcmd eq 'fixnames') { + SB_SERVER_FixSyncGroupNames($hash); + SB_SERVER_UpdateSgReadings($hash); + # CD 0027 end + # CD 0031 start + } elsif($subcmd eq 'volume') { + return( "not enough parameters" ) if( @a != 2 ); + + my $statename=$a[0]; + $statename =~ s/[,;:]/_/g; # CD 0027 Sonderzeichen ersetzen + + my $vol=$a[1]; + + if (SB_SERVER_isSyncGroupActive($hash,$statename)==1) { + foreach my $e ( keys %{$hash->{helper}{syncGroups}{$statename}} ) { + if(($e ne '')&&($e ne '0')) { + if(defined($hash->{helper}{syncGroups}{$statename}{$e})) { + fhem("set ".$hash->{helper}{syncGroups}{$statename}{$e}{fhemname}." volume ".$vol." nosync"); + } + } + } + } else { + return "Sync group $statename not active"; + } + # CD 0031 end + # CD 0031 nur zu Testzwecken + } elsif($subcmd eq 'isActive') { + return( "not enough parameters" ) if( @a == 0 ); + + my $statename=$a[0]; + $statename =~ s/[,;:]/_/g; # CD 0027 Sonderzeichen ersetzen + + return SB_SERVER_isSyncGroupActive($hash, $statename); + } else { + return( "unknown command $subcmd" ); + } + # CD 0031 end + # CD 0025 start + if($updateReadings==1) { + SB_SERVER_UpdateSgReadings($hash); + } + # CD 0025 end + # CD 0024 end + # CD 0025 start + } elsif( $cmd eq "save" ) { + if(defined($a[0])) { + SB_SERVER_Save($hash, $a[0]); + } else { + SB_SERVER_Save($hash, ""); + } + } elsif( $cmd eq "recall" ) { + if(defined($a[0])) { + SB_SERVER_Recall($hash, $a[0]); + } else { + SB_SERVER_Recall($hash, ""); + } + # CD 0025 end + # CD 0030 start + } elsif( $cmd eq "getData" ) { + return( "not enough parameters" ) if( @a < 3 ); + $hash->{helper}{getData}{$a[2]}{format}=$a[0]; + $hash->{helper}{getData}{$a[2]}{reading}=$a[1]; + + if($a[2] eq 'artists') { + DevIo_SimpleWrite( $hash, "artists 0 5000\n", 0 ); + } + # CD 0030 end } else { ; } - + return( undef ); } +# CD 0027 start +sub SB_SERVER_tcb_StartTalk($) { + my($in ) = shift; + my(undef,$name) = split(':',$in); + my $hash = $defs{$name}; + + return unless defined($hash->{helper}{sgTalkActivePlayer}); + + # alle gesynced ? + if (SB_SERVER_isSyncGroupActive($hash,$hash->{helper}{sgTalkGroup})==1) { + # eingeschalteten Player suchen + my @pls=split(',',$hash->{helper}{sgTalkPlayers}); + foreach my $pl (@pls) { + if(ReadingsVal($pl,'power','0') eq 'on') { + $hash->{helper}{sgTalkActivePlayer}=$pl; + my $phash = $defs{$pl}; # CD 0029 + $phash->{helper}{sgTalkActive}=1; # CD 0029 + fhem "set $pl talk " . $hash->{helper}{sgTalkData}; + last; + } + } + } + + # keinen eingeschalteten Player gefunden + if($hash->{helper}{sgTalkTimeoutPowerOn}{helper}{sgTalkActivePlayer}; + } else { + # warten... + if($hash->{helper}{sgTalkActivePlayer} eq 'waiting for power on') { + RemoveInternalTimer( "StartTalk:$name"); + InternalTimer( gettimeofday() + 0.2, + "SB_SERVER_tcb_StartTalk", + "StartTalk:$name", + 0 ); + } + } +} + +# CD 0031 start +sub SB_SERVER_isSyncGroupActive($$) { + my ($hash,$statename) = @_; + my $name = $hash->{NAME}; + + if((!defined($hash->{helper}{syncGroups}))||(!defined($hash->{helper}{syncGroups}{$statename}))) { + return 0; + } + + my $master=$hash->{helper}{syncGroups}{$statename}{0}{lmsname}; + my $isActive=1; + + foreach my $e ( keys %{$hash->{helper}{syncGroups}{$statename}} ) { + if(($e ne '')&&($e ne '0')) { + if(defined($hash->{helper}{syncGroups}{$statename}{$e})) { + $isActive=0 if (InternalVal($hash->{helper}{syncGroups}{$statename}{$e}{fhemname},'SYNCMASTERPN','?') ne $master) + } + } + } + return $isActive; +} +# CD 0031 end + +sub SB_SERVER_LoadSyncGroup($$$) { + my ($hash,$statename,$poweron) = @_; + my $name = $hash->{NAME}; + + Log3( $hash, 3, "SB_SERVER_LoadSyncGroup($name): load: $statename, poweron: $poweron"); + + if((!defined($hash->{helper}{syncGroups}))||(!defined($hash->{helper}{syncGroups}{$statename}))) { + return( "sync group $statename not found" ); + } + + # unsync all + foreach my $e ( keys %{$hash->{helper}{syncGroups}{$statename}} ) { + if(($e ne '')&&($e ne '0')) { + if(defined($hash->{helper}{syncGroups}{$statename}{$e})) { + SB_SERVER_Write( $hash, $hash->{helper}{syncGroups}{$statename}{$e}{mac}." sync -\n", "" ); + } + } + } + + # sync with new master + foreach my $e ( keys %{$hash->{helper}{syncGroups}{$statename}} ) { + if(($e ne '')&&($e ne '0')) { + if(defined($hash->{helper}{syncGroups}{$statename}{$e})) { + if($hash->{helper}{syncGroups}{$statename}{$e}{mac} ne $hash->{helper}{syncGroups}{$statename}{0}{mac}) { + SB_SERVER_Write( $hash, $hash->{helper}{syncGroups}{$statename}{0}{mac}." sync ".$hash->{helper}{syncGroups}{$statename}{$e}{mac}."\n", "" ); + } + SB_SERVER_Write( $hash, $hash->{helper}{syncGroups}{$statename}{$e}{mac}." power 1\n", "" ) if($poweron==1); + } + } + } +} + +sub SB_SERVER_FixSyncGroupNames($) { + my ($hash) = @_; + my $name = $hash->{NAME}; + + foreach my $e ( keys %{$hash->{helper}{syncGroups}} ) { + if($e ne '') { + if(defined($hash->{helper}{syncGroups}{$e})) { + if($e =~ /[,;:\s]/) { + my $n=$e; + $n =~ s/[,;:\s]/_/g; + $hash->{helper}{syncGroups}{$n}=$hash->{helper}{syncGroups}{$e}; + delete $hash->{helper}{syncGroups}{$e}; + } + } + } + } +} + +sub SB_SERVER_UpdateSgReadings($) { + my ($hash) = @_; + my $name = $hash->{NAME}; + + my $sg=''; + + readingsBeginUpdate( $hash ); + + foreach my $e ( keys %{$hash->{helper}{syncGroups}} ) { + if($e ne '') { + if(defined($hash->{helper}{syncGroups}{$e})) { + $sg.="$e,"; + + my $sgd=''; + + foreach my $p ( keys %{$hash->{helper}{syncGroups}{$e}} ) { + if($p ne '') { + if(defined($hash->{helper}{syncGroups}{$e}{$p})) { + if(($hash->{helper}{syncGroups}{$e}{$p}{fhemname} ne $hash->{helper}{syncGroups}{$e}{0}{fhemname})||($p eq '0')) { # CD 0029 + $sgd.=$hash->{helper}{syncGroups}{$e}{$p}{fhemname} . ","; + } + } + } + } + $sgd =~ s/,$//; + + if($sgd eq '') { + delete($defs{$name}{READINGS}{"sg$e"}) if(defined($defs{$name}{READINGS}{"sg$e"})); + } else { + if(ReadingsVal($name,"sg$e","x") ne $sgd) { + readingsBulkUpdate( $hash, "sg$e", $sgd ); + } + } + } + } + } + $sg =~ s/,$//; + if($sg eq '') { + delete($defs{$name}{READINGS}{"syncGroups"}) if(defined($defs{$name}{READINGS}{"syncGroups"})); + } else { + readingsBulkUpdate( $hash, "syncGroups", $sg ); + } + readingsEndUpdate( $hash, 1 ); +} +# CD 0027 end # ---------------------------------------------------------------------------- # Read @@ -598,7 +1134,7 @@ sub SB_SERVER_Read( $ ) { my $name = $hash->{NAME}; #my $start = time; # CD 0019 - + Log3( $hash, 4, "SB_SERVER_Read($name): called" ); Log3( $hash, 5, "+++++++++++++++++++++++++++++++++++++++++++++++++++++" ); Log3( $hash, 5, "New Squeezebox Server Read cycle starts here" ); @@ -620,7 +1156,7 @@ sub SB_SERVER_Read( $ ) { $out = SB_SERVER_CMDStackPop( $hash ); if( $out ne "empty" ) { DevIo_SimpleWrite( $hash, $out , 0 ); - } + } } } #Log3( $hash, 5, "SB_SERVER_Read($name): please implement the " . # CD 0009 Meldung deaktiviert @@ -637,13 +1173,13 @@ sub SB_SERVER_Read( $ ) { # CD 0021 start - Server lebt noch, alivetimer neu starten RemoveInternalTimer( "SB_SERVER_Alive:$name"); - InternalTimer( gettimeofday() + + InternalTimer( gettimeofday() + AttrVal( $name, "alivetimer", 10 ), "SB_SERVER_tcb_Alive", - "SB_SERVER_Alive:$name", + "SB_SERVER_Alive:$name", 0 ); # CD 0021 end - + #my $t2 = time; # CD 0020 # if we have received multiline commands, they are split by \n @@ -671,7 +1207,7 @@ sub SB_SERVER_Read( $ ) { my $lastchar = substr( $_, -1); SB_SERVER_DispatchCommandLine( $hash, $_ ); # CD 0020 start - #if((time-$t31)>0.3) { + #if((time-$t31)>0.2) { # Log3($hash,0,"SB_SERVER_Read($name), time:".int((time-$t31)*1000)." cmd: ".$_); #} # CD 0020 end @@ -687,7 +1223,7 @@ sub SB_SERVER_Read( $ ) { $hash->{helper}{SB_SERVER_VERSION}=SB_SERVER_VERSION; DevIo_SimpleWrite( $hash, "version ?\n", 0 ); DevIo_SimpleWrite( $hash, "serverstatus 0 200\n", 0 ); - DevIo_SimpleWrite( $hash, "favorites items 0 " . + DevIo_SimpleWrite( $hash, "favorites items 0 " . AttrVal( $name, "maxfavorites", 100 ) . " want_url:1\n", # CD 0009 url mit abfragen 0 ); DevIo_SimpleWrite( $hash, "playlists 0 200\n", 0 ); @@ -697,17 +1233,30 @@ sub SB_SERVER_Read( $ ) { Log3( $hash, 5, "+++++++++++++++++++++++++++++++++++++++++++++++++++++" ); Log3( $hash, 5, "Squeezebox Server Read cycle ends here" ); Log3( $hash, 5, "+++++++++++++++++++++++++++++++++++++++++++++++++++++" ); - + # CD 0019 start #my $end = time; #if (($end - $start)>1) { # Log3( $hash, 0, "SB_SERVER_Read($name), times: ".int(($t1 - $start)*1000)." ".int(($t2 - $t1)*1000)." ".int(($t3 - $t2)*1000)." ".int(($t4 - $t3)*1000)." ".int(($end - $start)*1000)." nCmds: ".$#cmds ); #} # CD 0019 end - + return( undef ); } +# CD 0027 start +sub SB_SERVER_tcb_RecallAfterTalk($) { + my($in ) = shift; + my(undef,$name) = split(':',$in); + my $hash = $defs{$name}; + + return unless defined($hash->{helper}{sgTalkActivePlayer}); + + SB_SERVER_Recall($hash,'xxxSgTalkxxx del'); + + delete $hash->{helper}{sgTalkActivePlayer}; +} +# CD 0027 end # ---------------------------------------------------------------------------- # called by the clients to send data @@ -728,16 +1277,34 @@ sub SB_SERVER_Write( $$$ ) { # CD 0012 fhemrelay Meldungen nicht an den LMS schicken sondern direkt an Dispatch übergeben if($fn =~ m/fhemrelay/) { - SB_SERVER_DispatchCommandLine( $hash, $fn ); + # CD 0027 start + if ($fn =~ m/ttsdone/) { + my @a=split(' ',$fn); + + # sg talk auf Player aktiv ? + if(defined($hash->{helper}{sgTalkActivePlayer})) { + if($a[0] eq $hash->{helper}{sgTalkActivePlayer}) { + # recall auslösen + RemoveInternalTimer( "RecallAfterTalk:$name"); + InternalTimer( gettimeofday() + 0.01, + "SB_SERVER_tcb_RecallAfterTalk", + "RecallAfterTalk:$name", + 0 ); + } + } + } else { + # CD 0027 end + SB_SERVER_DispatchCommandLine( $hash, $fn ); + } return( undef ); } - + if( ReadingsVal( $name, "serversecure", "0" ) eq "1" ) { if( ( $hash->{USERNAME} ne "?" ) && ( $hash->{PASSWORD} ne "?" ) ) { # we need to send username and password first } else { - my $retmsg = "SB_SERVER_Write: Server needs username and " . - "password but you did not specify those. No sending"; + my $retmsg = "SB_SERVER_Write: Server needs username and " . + "password but you did not specify those. No sending"; Log3( $hash, 1, $retmsg ); return( $retmsg ); } @@ -767,7 +1334,7 @@ sub SB_SERVER_DoInit( $ ) { if( !$hash->{TCPDev} ) { Log3( $hash, 2, "SB_SERVER_DoInit: no TCPDev available?" ); # CD 0009 level 5->2 - DevIo_CloseDev( $hash ); + DevIo_CloseDev( $hash ); } Log3( $hash, 3, "SB_SERVER_DoInit($name): STATE: " . $hash->{STATE} . " power: ". ReadingsVal( $name, "power", "X" )); # CD 0009 level 2 -> 3 @@ -784,7 +1351,7 @@ sub SB_SERVER_DoInit( $ ) { # and signal to our clients SB_SERVER_Broadcast( $hash, "SERVER", "OFF" ); - SB_SERVER_Broadcast( $hash, "SERVER", + SB_SERVER_Broadcast( $hash, "SERVER", "IP " . $hash->{IP} . ":" . AttrVal( $name, "httpport", "9000" ) ); } @@ -804,8 +1371,8 @@ sub SB_SERVER_DoInit( $ ) { delete($hash->{helper}{WOLFastReconnectNext}); } $hash->{helper}{pingCounter}=0; # CD 0007 - - SB_SERVER_Broadcast( $hash, "SERVER", + + SB_SERVER_Broadcast( $hash, "SERVER", "IP " . $hash->{IP} . ":" . AttrVal( $name, "httpport", "9000" ) ); $hash->{helper}{doBroadcast}=1; # CD 0007 @@ -820,21 +1387,21 @@ sub SB_SERVER_DoInit( $ ) { # start the alive checking mechanism # CD 0020 SB_SERVER_tcb_Alive verwenden RemoveInternalTimer( "SB_SERVER_Alive:$name"); - InternalTimer( gettimeofday() + + InternalTimer( gettimeofday() + AttrVal( $name, "alivetimer", 10 ), "SB_SERVER_tcb_Alive", - "SB_SERVER_Alive:$name", + "SB_SERVER_Alive:$name", 0 ); return( 0 ); } else { - Log3( $hash, 2, "SB_SERVER_DoInit: doalivecheck has " . + Log3( $hash, 2, "SB_SERVER_DoInit: doalivecheck has " . "wrong value" ); return( 1 ); } - + } - + } else { # what the f... Log3( $hash, 2, "SB_SERVER_DoInit: unclear status reported" ); @@ -846,6 +1413,222 @@ sub SB_SERVER_DoInit( $ ) { return( 1 ); } +# CD 0032 start +# ---------------------------------------------------------------------------- +# Parse return of app items query +# ---------------------------------------------------------------------------- +sub SB_SERVER_ParseAppResponse( $$ ) { + my ( $hash, $buf ) = @_; + my $name = $hash->{NAME}; + my $appresponse=0; + + if($buf=~m/items/) { + my @data=split(' ',$buf); + #Log 0,$buf; + if(defined($hash->{helper}{appcmd}) && defined($hash->{helper}{appcmd}{$data[1]})) { + my $appcmd=$data[1]; + my $appname=$hash->{helper}{appcmd}{$appcmd}{name}; + my $nameactive=0; + my $save=0; + + if($buf=~m/item_id:/) { + # app subitems ... + my $id="0"; + my $pname=""; + my $dest=0; + my $broadcastPlaylists=0; + my $broadcastFavorites=0; + + foreach (@data) { + if( $_ =~ /^(item_id:)(.*)/ ) { + if(defined($hash->{helper}{appcmd}{$appcmd}{playlistsId}) && ($hash->{helper}{appcmd}{$appcmd}{playlistsId} eq $2)) { + $dest=1; + } + if(defined($hash->{helper}{appcmd}{$appcmd}{favoritesId}) && ($hash->{helper}{appcmd}{$appcmd}{favoritesId} eq $2)) { + $dest=2; + } + } elsif( $_ =~ /^(id:)(.*)/ ) { + # new entry + if($save==1) { + if($dest==1) { + if(defined($hash->{helper}{appcmd}{$appcmd}{playlists}) && defined($hash->{helper}{appcmd}{$appcmd}{playlists}{$id})) { + $broadcastPlaylists=1 if($hash->{helper}{appcmd}{$appcmd}{playlists}{$id}{name} ne $pname); + } else { + $broadcastPlaylists=1; + } + $hash->{helper}{appcmd}{$appcmd}{playlists}{$id}{name}=$pname; + } + if($dest==2) { + # Bug in squeezecloud ? Element 3.2 funktioniert nicht + if($appcmd eq 'squeezecloud') { + if($id-int($id)>0.19) { + $id+=0.1; + } + } + + if(defined($hash->{helper}{appcmd}{$appcmd}{favorites}) && defined($hash->{helper}{appcmd}{$appcmd}{favorites}{$id})) { + $broadcastFavorites=1 if($hash->{helper}{appcmd}{$appcmd}{favorites}{$id}{name} ne $pname); + } else { + $broadcastFavorites=1; + } + $hash->{helper}{appcmd}{$appcmd}{favorites}{$id}{name}=$pname; + } + $save=0; + } + $id=$2; + $nameactive=0; + next; + } elsif( $_ =~ /^(name:)(.*)/ ) { + $pname=$2; + $save=1; + $nameactive=1; + next; + } elsif( $_ =~ /^(type:)(.*)/ ) { + $nameactive=0; + } else { + $pname.=" $_" if($nameactive==1); + } + } + if($save==1) { + if($dest==1) { + if(defined($hash->{helper}{appcmd}{$appcmd}{playlists}) && defined($hash->{helper}{appcmd}{$appcmd}{playlists}{$id})) { + $broadcastPlaylists=1 if($hash->{helper}{appcmd}{$appcmd}{playlists}{$id}{name} ne $pname); + } else { + $broadcastPlaylists=1; + } + $hash->{helper}{appcmd}{$appcmd}{playlists}{$id}{name}=$pname; + } + if($dest==2) { + # Bug in squeezecloud ? Element 3.2 funktioniert nicht + if($appcmd eq 'squeezecloud') { + if($id-int($id)>0.19) { + $id+=0.1; + } + } + + if(defined($hash->{helper}{appcmd}{$appcmd}{favorites}) && defined($hash->{helper}{appcmd}{$appcmd}{favorites}{$id})) { + $broadcastFavorites=1 if($hash->{helper}{appcmd}{$appcmd}{favorites}{$id}{name} ne $pname); + } else { + $broadcastFavorites=1; + } + $hash->{helper}{appcmd}{$appcmd}{favorites}{$id}{name}=$pname; + } + } + if($broadcastPlaylists==1) { + SB_SERVER_Broadcast( $hash, "PLAYLISTS", "FLUSH $appcmd", undef ); + RemoveInternalTimer( "SB_SERVER_tcb_SendPlaylists:$name"); + foreach my $pl ( keys %{$hash->{helper}{appcmd}{$appcmd}{playlists}} ) { + my $plname=$hash->{helper}{appcmd}{$appcmd}{playlists}{$pl}{name}; + $plname=~s/ /_/g; + $plname=~s/[^[:ascii:]]//g; + #$plname=unidecode($plname); + my $uniquename = SB_SERVER_FavoritesName2UID( $plname ); + push @SB_SERVER_PLS, "ADD $plname $pl $uniquename $appcmd"; + } + if(scalar(@SB_SERVER_PLS)>0) { + InternalTimer( gettimeofday() + 0.01, + "SB_SERVER_tcb_SendPlaylists", + "SB_SERVER_tcb_SendPlaylists:$name", + 0 ); + } + } + if($broadcastFavorites==1) { + SB_SERVER_Broadcast( $hash, "FAVORITES", "FLUSH $appcmd", undef ); + RemoveInternalTimer( "SB_SERVER_tcb_SendFavorites:$name"); + foreach my $pl ( keys %{$hash->{helper}{appcmd}{$appcmd}{favorites}} ) { + my $plname=$hash->{helper}{appcmd}{$appcmd}{favorites}{$pl}{name}; + $plname=~s/ /_/g; + $plname=~s/[^[:ascii:]]//g; + #$plname=unidecode($plname); + my $uniquename = SB_SERVER_FavoritesName2UID( $plname ); + push @SB_SERVER_FAVS, "ADD $name $pl $uniquename url $appcmd $plname"; + } + if(scalar(@SB_SERVER_FAVS)>0) { + InternalTimer( gettimeofday() + 0.01, + "SB_SERVER_tcb_SendFavorites", + "SB_SERVER_tcb_SendFavorites:$name", + 0 ); + } + } + } else { + # app items ... + my $id=0; + my $iname=""; + my $type=""; + my $isaudio=0; + my $hasitems=0; + + foreach (@data) { + if( $_ =~ /^(id:)(.*)/ ) { + # new entry + if($save==1) { + $hash->{helper}{appcmd}{$appcmd}{items}{$id}{name}=$iname; + $hash->{helper}{appcmd}{$appcmd}{items}{$id}{type}=$type; + $hash->{helper}{appcmd}{$appcmd}{items}{$id}{isaudio}=$isaudio; + $hash->{helper}{appcmd}{$appcmd}{items}{$id}{hasitems}=$hasitems; + $hash->{helper}{appcmd}{$appcmd}{playlistsId}=$id if($iname eq 'Playlists'); + $hash->{helper}{appcmd}{$appcmd}{playlistsId}=$id if($iname eq 'Wiedergabelisten'); # CD 0035 + $hash->{helper}{appcmd}{$appcmd}{playlistsId}=$id if($iname eq 'Listes de lecture'); # CD 0036 + $hash->{helper}{appcmd}{$appcmd}{favoritesId}=$id if($iname eq 'Likes'); + $hash->{helper}{appcmd}{$appcmd}{favoritesId}=$id if($iname eq 'Favorites'); + $save=0; + } + $id=$2; + $nameactive=0; + next; + } elsif( $_ =~ /^(type:)(.*)/ ) { + $type=$2; + $save=1; + $nameactive=0; + next; + } elsif( $_ =~ /^(isaudio:)(.*)/ ) { + $isaudio=$2; + $save=1; + $nameactive=0; + next; + } elsif( $_ =~ /^(hasitems:)(.*)/ ) { + $hasitems=$2; + $save=1; + $nameactive=0; + next; + } elsif( $_ =~ /^(name:)(.*)/ ) { + $iname=$2; + $save=1; + $nameactive=1; + next; + } elsif( $_ =~ /^(count:)(.*)/ ) { + if($2==0) { + delete $hash->{helper}{appcmd}{$appcmd}{items} if defined($hash->{helper}{appcmd}{$appcmd}{items}); + $save=0; + Log3( $hash, 2, "SB_SERVER_ParseAppResponse($name): no valid data for $appname: $buf" ); + last; + } + } else { + $iname.=" $_" if($nameactive==1); + } + } + if($save==1) { + $hash->{helper}{appcmd}{$appcmd}{items}{$id}{name}=$iname; + $hash->{helper}{appcmd}{$appcmd}{items}{$id}{type}=$type; + $hash->{helper}{appcmd}{$appcmd}{items}{$id}{isaudio}=$isaudio; + $hash->{helper}{appcmd}{$appcmd}{items}{$id}{hasitems}=$hasitems; + $hash->{helper}{appcmd}{$appcmd}{playlistsId}=$id if($iname eq 'Playlists'); + $hash->{helper}{appcmd}{$appcmd}{favoritesId}=$id if($iname eq 'Likes'); + $hash->{helper}{appcmd}{$appcmd}{favoritesId}=$id if($iname eq 'Favorites'); + } + if(defined($hash->{helper}{appcmd}{$appcmd}{playlistsId})) { + DevIo_SimpleWrite( $hash, "$appcmd items 0 200 item_id:".($hash->{helper}{appcmd}{$appcmd}{playlistsId})."\n", 0 ); + } + if(defined($hash->{helper}{appcmd}{$appcmd}{favoritesId})) { + DevIo_SimpleWrite( $hash, "$appcmd items 0 200 item_id:".($hash->{helper}{appcmd}{$appcmd}{favoritesId})." want_url:1\n", 0 ); + } + } + $appresponse=1; + } + } + return $appresponse; +} +# CD 0032 end # ---------------------------------------------------------------------------- # Dispatch every single line of commands @@ -860,25 +1643,28 @@ sub SB_SERVER_DispatchCommandLine( $$ ) { my $indx = index( $buf, " " ); my $id1 = substr( $buf, 0, $indx ); - # is the first return value a player ID? + # is the first return value a player ID? # Player ID is MAC adress, hence : included my @id = split( ":", $id1 ); if( @id > 1 ) { - # we have received a return for a dedicated player + # CD 0032 start + # check for app response + if(SB_SERVER_ParseAppResponse($hash,$buf)==0) { + # we have received a return for a dedicated player - # create the fhem specific unique id - my $playerid = join( "", @id ); - Log3( $hash, 5, "SB_SERVER_DispatchCommandLine: fhem-id: $playerid" ); - - # create the commands - my $cmds = substr( $buf, $indx + 1 ); - Log3( $hash, 5, "SB_SERVER__DispatchCommandLine: commands: $cmds" ); - Dispatch( $hash, "SB_PLAYER:$playerid:$cmds", undef ); + # create the fhem specific unique id + my $playerid = join( "", @id ); + Log3( $hash, 5, "SB_SERVER_DispatchCommandLine: fhem-id: $playerid" ); + # create the commands + my $cmds = substr( $buf, $indx + 1 ); + Log3( $hash, 5, "SB_SERVER__DispatchCommandLine: commands: $cmds" ); + Dispatch( $hash, "SB_PLAYER:$playerid:$cmds", undef ); + } } else { - # that is a server specific command - SB_SERVER_ParseCmds( $hash, $buf ); + # that is a server specific command + SB_SERVER_ParseCmds( $hash, $buf ); } return( undef ); @@ -903,13 +1689,13 @@ sub SB_SERVER_ParseCmds( $$ ) { # CD 0007 start if (defined($hash->{helper}{doBroadcast})) { SB_SERVER_Broadcast( $hash, "SERVER", "ON" ); - SB_SERVER_Broadcast( $hash, "SERVER", + SB_SERVER_Broadcast( $hash, "SERVER", "IP " . $hash->{IP} . ":" . AttrVal( $name, "httpport", "9000" ) ); delete ($hash->{helper}{doBroadcast}); } # CD 0007 end - + if( $cmd eq "version" ) { readingsSingleUpdate( $hash, "serverversion", $args[ 1 ], 0 ); @@ -918,7 +1704,7 @@ sub SB_SERVER_ParseCmds( $$ ) { readingsSingleUpdate( $hash, "power", "on", 1 ); # signal our players SB_SERVER_Broadcast( $hash, "SERVER", "ON" ); - SB_SERVER_Broadcast( $hash, "SERVER", + SB_SERVER_Broadcast( $hash, "SERVER", "IP " . $hash->{IP} . ":" . AttrVal( $name, "httpport", "9000" ) ); } @@ -929,14 +1715,20 @@ sub SB_SERVER_ParseCmds( $$ ) { if( $args[ 1 ] eq "1" ) { # username and password is required # CD 0007 zu spät, login muss als erstes gesendet werden, andernfalls bricht der Server die Verbindung sofort ab - if( ( $hash->{USERNAME} ne "?" ) && + if( ( $hash->{USERNAME} ne "?" ) && ( $hash->{PASSWORD} ne "?" ) ) { - DevIo_SimpleWrite( $hash, "login " . - $hash->{USERNAME} . " " . - $hash->{PASSWORD} . "\n", - 0 ); + my ($user,$password)=SB_SERVER_readPassword($hash); # CD 0031 + if(defined($user)) { + DevIo_SimpleWrite( $hash, "login " . + $user . " " . + $password . "\n", + 0 ); + } else { + Log3( $hash, 3, "SB_SERVER_ParseCmds($name): login " . + "required but no username and password specified" ); + } } else { - Log3( $hash, 3, "SB_SERVER_ParseCmds($name): login " . + Log3( $hash, 3, "SB_SERVER_ParseCmds($name): login " . "required but no username and password specified" ); } # next step is to wait for the answer of the LMS server @@ -944,18 +1736,18 @@ sub SB_SERVER_ParseCmds( $$ ) { # no username password required, go ahead directly #SB_SERVER_LMS_Status( $hash ); } else { - Log3( $hash, 3, "SB_SERVER_ParseCmds($name): unkown " . + Log3( $hash, 3, "SB_SERVER_ParseCmds($name): unkown " . "result for authorize received. Should be 0 or 1" ); - } + } } } elsif( $cmd eq "login" ) { - if( ( $args[ 1 ] eq $hash->{USERNAME} ) && + if( ( $args[ 1 ] eq $hash->{USERNAME} ) && ( $args[ 2 ] eq "******" ) ) { # login has been succesful, go ahead SB_SERVER_LMS_Status( $hash ); } - + } elsif( $cmd eq "fhemalivecheck" ) { $hash->{ALIVECHECK} = "received"; @@ -965,14 +1757,14 @@ sub SB_SERVER_ParseCmds( $$ ) { if( $args[ 0 ] eq "changed" ) { Log3( $hash, 4, "SB_SERVER_ParseCmds($name): favorites changed" ); # we need to trigger the favorites update here - DevIo_SimpleWrite( $hash, "favorites items 0 " . - AttrVal( $name, "maxfavorites", 100 ) . + DevIo_SimpleWrite( $hash, "favorites items 0 " . + AttrVal( $name, "maxfavorites", 100 ) . " want_url:1\n", 0 ); # CD 0009 url mit abfragen DevIo_SimpleWrite( $hash, "alarm playlists 0 300\n", 0 ); # CD 0011 } elsif( $args[ 0 ] eq "items" ) { Log3( $hash, 4, "SB_SERVER_ParseCmds($name): favorites items" ); # the response to our query of the favorites - SB_SERVER_FavoritesParse( $hash, join( " ", @args ) ); + SB_SERVER_FavoritesParse( $hash, join( " ", @args ) ); } else { } @@ -1001,8 +1793,121 @@ sub SB_SERVER_ParseCmds( $$ ) { } elsif( $cmd eq "rescan" ) { if( $args[0] eq "done" ) { DevIo_SimpleWrite( $hash, "serverstatus 0 200\n", 0 ); + # CD 0036 start - refresh favorites and playlists after rescan + DevIo_SimpleWrite( $hash, "favorites items 0 " . + AttrVal( $name, "maxfavorites", 100 ) . " want_url:1\n", + 0 ); + DevIo_SimpleWrite( $hash, "playlists 0 200\n", 0 ); + DevIo_SimpleWrite( $hash, "alarm playlists 0 300\n", 0 ); + # CD 0036 end } # CD 0016 end + # CD 0030 start + } elsif( $cmd eq "artists" ) { + if(defined($hash->{helper}{getData}) && defined($hash->{helper}{getData}{artists})) { + my ($dev,$reading)=split(':',$hash->{helper}{getData}{artists}{reading}); + if ($hash->{helper}{getData}{artists}{format} eq 'raw') { + if(defined($defs{$dev})) { + readingsSingleUpdate( $defs{$dev}, $reading, $instr, 1 ); + } + } else { + my $artistname=""; + my $jout="["; + my $lout=""; + my $iout=""; + my $artistid=0; + + foreach( @args ) { + if( $_ =~ /^(artist:)(.*)/ ) { + $artistname=$2; + next; + } elsif( $_ =~ /^(id:)([0-9]*)/ ) { + # start new entry + if($artistname ne "") { + $artistname=~s/\"/\\\"/g; + $jout.="{\"Artist\":\"".$artistname."\",\"Id\":\"".$artistid."\"},"; + $lout.="\"".$artistname."\":"; + $iout.=$artistid.":"; + } + $artistid=$2; + next; + } elsif( $_ =~ /^(count:)([0-9]*)/ ) { + next; + } elsif( $artistname ne "" ) { + $artistname=~s/\"/\\\"/g; + $artistname.=" ".$_; + next; + } + } + if($artistname ne "") { + $jout.="{\"Artist\":\"".$artistname."\"}"; + $lout.="\"".$artistname."\""; + $iout.=$artistid; + } + $jout.="]"; + if(defined($defs{$dev})) { + readingsSingleUpdate( $defs{$dev}, $reading, $jout, 1 ) if ($hash->{helper}{getData}{artists}{format} eq 'json'); + readingsSingleUpdate( $defs{$dev}, $reading, $lout, 1 ) if ($hash->{helper}{getData}{artists}{format} eq 'delimited'); + readingsSingleUpdate( $defs{$dev}, $reading."_index", $iout, 1 ) if ($hash->{helper}{getData}{artists}{format} eq 'delimited'); + } + } + delete $hash->{helper}{getData}{artists}; + } + # CD 0030 end + # CD 0032 start + } elsif( $cmd eq "apps" ) { + my $save=0; + my $appcmd=""; + my $appname=""; + my $scansubs=0; + + # 1. Mal ? + $scansubs=1 unless (defined($hash->{helper}{apps})); + + delete($hash->{helper}{apps}) if(defined($hash->{helper}{apps})); + delete($hash->{helper}{appcmd}) if(defined($hash->{helper}{appcmd})); + + foreach( @args ) { + if( $_ =~ /^(icon:)(.*)/ ) { + # new entry + if($save==1) { + $hash->{helper}{apps}{$appname}{cmd}=$appcmd; + $hash->{helper}{appcmd}{$appcmd}{name}=$appname; + $save=0; + } + next; + } elsif( $_ =~ /^(cmd:)(.*)/ ) { + $appcmd=$2; + $save=1; + next; + } elsif( $_ =~ /^(name:)(.*)/ ) { + $appname=$2; + $appname=~s/\./_/g; + $appname=~s/-/_/g; + $save=1; + next; + } elsif( $_ =~ /^(type:)(.*)/ ) { + $save=0 if($2 ne 'xmlbrowser'); + next; + } + } + if($save==1) { + $hash->{helper}{apps}{$appname}{cmd}=$appcmd; + $hash->{helper}{appcmd}{$appcmd}{name}=$appname; + } + SB_SERVER_SetAttrList($hash); + + if(defined($hash->{helper}{apps})&&($scansubs==1)) { + my @enabledApps=split(',',AttrVal($name,'enablePlugins','')); + foreach my $app (@enabledApps) { + if(defined($hash->{helper}{apps}{$app})) { + DevIo_SimpleWrite( $hash, ($hash->{helper}{apps}{$app}{cmd})." items 0 200\n", 0 ); + } + } + } + # CD 0032 end + + # CD 0032 end } else { # unkown } @@ -1079,7 +1984,7 @@ sub SB_SERVER_Alive( $ ) { # check via ping my $p; # CD 0017 eval hinzugefügt, Absturz auf FritzBox, bei Fehler annehmen dass Host verfügbar ist, internalPingProtocol hinzugefügt - + eval { $p = Net::Ping->new( $ipp ); }; if($@) { Log3( $hash,1,"SB_SERVER_Alive($name): internal ping failed with $@"); @@ -1139,8 +2044,8 @@ sub SB_SERVER_Alive( $ ) { # close the device # CD 0007 use DevIo_Disconnected instead of DevIo_CloseDev - #DevIo_CloseDev( $hash ); - DevIo_Disconnected( $hash ); + #DevIo_CloseDev( $hash ); + DevIo_Disconnected( $hash ); $hash->{helper}{pingCounter}=9999; # CD 0007 # CD 0000 start - exit infinite loop after socket has been closed @@ -1149,7 +2054,7 @@ sub SB_SERVER_Alive( $ ) { # CD 0005 line above does not work (on Linux), fix: # CD 0006 DevIo_setStates requires v7099 of DevIo.pm, replaced with SB_SERVER_setStates SB_SERVER_setStates($hash, "disconnected"); - + readingsSingleUpdate( $hash, "power", "off", 1 ); # test: clear stack ? $SB_SERVER_CmdStack{$name}{last_n} = 0; @@ -1166,7 +2071,7 @@ sub SB_SERVER_Alive( $ ) { #SB_SERVER_Broadcast( $hash, "SERVER", "ON" ); # CD 0007 disabled, wait for SB_SERVER_LMS_Status SB_SERVER_LMS_Status( $hash ); } - + $hash->{CLICONNECTION} = "on"; # just send something to the SB-Server. It will echo it @@ -1191,8 +2096,8 @@ sub SB_SERVER_Alive( $ ) { # close the device # CD 0007 use DevIo_Disconnected instead of DevIo_CloseDev - #DevIo_CloseDev( $hash ); - DevIo_Disconnected( $hash ); + #DevIo_CloseDev( $hash ); + DevIo_Disconnected( $hash ); $hash->{helper}{pingCounter}=9999; # CD 0007 # CD 0004 set STATE, needed for reconnect $hash->{STATE}="disconnected"; @@ -1204,16 +2109,16 @@ sub SB_SERVER_Alive( $ ) { } } else { # we shouldn't end up here - Log3( $hash, 5, "SB_SERVER_Alive($name): funny server status " . + Log3( $hash, 5, "SB_SERVER_Alive($name): funny server status " . "received. Ping=" . $pingstatus . " RCC=" . $rccstatus ); } # do an update of the status # CD 0020 SB_SERVER_tcb_Alive verwenden RemoveInternalTimer( "SB_SERVER_Alive:$name"); - InternalTimer( $nexttime, + InternalTimer( $nexttime, "SB_SERVER_tcb_Alive", - "SB_SERVER_Alive:$name", + "SB_SERVER_Alive:$name", 0 ); } @@ -1233,29 +2138,27 @@ sub SB_SERVER_Broadcast( $$@ ) { } foreach my $mydev ( keys %defs ) { - # the hash to the IODev as defined at the client - if( defined( $defs{$mydev}{IODev} ) ) { - $iodevhash = $defs{$mydev}{IODev}; - } else { - $iodevhash = undef; - } + # the hash to the IODev as defined at the client + if( defined( $defs{$mydev}{IODev} ) ) { + $iodevhash = $defs{$mydev}{IODev}; + } else { + $iodevhash = undef; + } - if( defined( $iodevhash ) ) { - if( ( defined( $defs{$mydev}{TYPE} ) ) && - ( defined( $iodevhash->{NAME} ) ) ){ + if( defined( $iodevhash ) ) { + if( ( defined( $defs{$mydev}{TYPE} ) ) && + ( $defs{$mydev}{TYPE} eq "SB_PLAYER" )){ # CD 0029 umsortiert + if( ( defined( $iodevhash->{NAME} ) ) && # CD 0029 umsortiert + ( $iodevhash->{NAME} eq $name ) ) { + # we found a valid entry + my $clienthash = $defs{$mydev}; + my $namebuf = $clienthash->{NAME}; - if( ( $defs{$mydev}{TYPE} eq "SB_PLAYER" ) && - ( $iodevhash->{NAME} eq $name ) ) { - # we found a valid entry - my $clienthash = $defs{$mydev}; - my $namebuf = $clienthash->{NAME}; - - SB_PLAYER_RecBroadcast( $clienthash, $cmd, $msg, $bin ); - } - } - } + SB_PLAYER_RecBroadcast( $clienthash, $cmd, $msg, $bin ); + } + } + } } - return; } @@ -1265,11 +2168,10 @@ sub SB_SERVER_Broadcast( $$@ ) { # ---------------------------------------------------------------------------- sub SB_SERVER_ParseServerStatus( $$ ) { my( $hash, $dataptr ) = @_; - my $name = $hash->{NAME}; Log3( $hash, 4, "SB_SERVER_ParseServerStatus($name): called " ); - + # typically the start index being a number if( $dataptr->[ 0 ] =~ /^([0-9])*/ ) { shift( @{$dataptr} ); @@ -1325,10 +2227,12 @@ sub SB_SERVER_ParseServerStatus( $$ ) { my $e = "[0-9]"; my $ee = "$e$e"; + my $nameactive=0; + foreach( @data1 ) { if( $_ =~ /^(lastscan:)([0-9]*)/ ) { # we found the lastscan entry - my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) = + my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) = localtime( $2 ); $year = $year + 1900; readingsBulkUpdate( $hash, "scan_last", "$mday-".($mon+1)."-$year " . # CD 0016 Monat korrigiert @@ -1379,70 +2283,100 @@ sub SB_SERVER_ParseServerStatus( $$ ) { $players{$id}{MAC} = $2; $currentplayerid = $id; } + $nameactive=0; # CD 0030 next; } elsif( $_ =~ /^(name:)(.*)/ ) { if( $currentplayerid ne "none" ) { $players{$currentplayerid}{name} = $2; + $nameactive=1; # CD 0030 } next; } elsif( $_ =~ /^(displaytype:)(.*)/ ) { if( $currentplayerid ne "none" ) { $players{$currentplayerid}{displaytype} = $2; } + $nameactive=0; # CD 0030 next; } elsif( $_ =~ /^(model:)(.*)/ ) { if( $currentplayerid ne "none" ) { $players{$currentplayerid}{model} = $2; } + $nameactive=0; # CD 0030 next; } elsif( $_ =~ /^(power:)([0|1])/ ) { if( $currentplayerid ne "none" ) { $players{$currentplayerid}{power} = $2; } + $nameactive=0; # CD 0030 next; } elsif( $_ =~ /^(canpoweroff:)([0|1])/ ) { if( $currentplayerid ne "none" ) { $players{$currentplayerid}{canpoweroff} = $2; } + $nameactive=0; # CD 0030 next; } elsif( $_ =~ /^(connected:)([0|1])/ ) { if( $currentplayerid ne "none" ) { $players{$currentplayerid}{connected} = $2; } + $nameactive=0; # CD 0030 next; } elsif( $_ =~ /^(isplayer:)([0|1])/ ) { if( $currentplayerid ne "none" ) { $players{$currentplayerid}{isplayer} = $2; } + $nameactive=0; # CD 0030 next; } elsif( $_ =~ /^(ip:)(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d{3,5})/ ) { if( $currentplayerid ne "none" ) { $players{$currentplayerid}{IP} = $2; } + $nameactive=0; # CD 0030 next; } elsif( $_ =~ /^(seq_no:)(.*)/ ) { # just to take care of the keyword + $nameactive=0; # CD 0030 next; # CD 0017 start } elsif( $_ =~ /^(isplaying:)(.*)/ ) { # just to take care of the keyword + $nameactive=0; # CD 0030 next; } elsif( $_ =~ /^(snplayercount:)(.*)/ ) { # just to take care of the keyword + $nameactive=0; # CD 0030 next; } elsif( $_ =~ /^(otherplayercount:)(.*)/ ) { # just to take care of the keyword + $nameactive=0; # CD 0030 next; } elsif( $_ =~ /^(server:)(.*)/ ) { # just to take care of the keyword + $nameactive=0; # CD 0030 next; } elsif( $_ =~ /^(serverurl:)(.*)/ ) { # just to take care of the keyword + $nameactive=0; # CD 0030 next; # CD 0017 end + # CD 0030 firmware und modelname + } elsif( $_ =~ /^(modelname:)(.*)/ ) { + if( $currentplayerid ne "none" ) { + $players{$currentplayerid}{model} = $2; + } + $nameactive=0; + next; + } elsif( $_ =~ /^(firmware:)(.*)/ ) { + # just to take care of the keyword + $nameactive=0; + next; + } elsif( $_ =~ /:/ ) { + $nameactive=0; + # CD 0030 Ende } else { # no keyword found, so let us assume it is part of the player name - if( $currentplayerid ne "none" ) { + # CD 0030 aber nur wenn 'name' noch aktiv ist + if(( $currentplayerid ne "none" )&&($nameactive==1)) { $players{$currentplayerid}{name} .= $_; } @@ -1453,104 +2387,153 @@ sub SB_SERVER_ParseServerStatus( $$ ) { my @ignoredIPs=split(',',AttrVal($name,'ignoredIPs','')); # CD 0017 my @ignoredMACs=split(',',AttrVal($name,'ignoredMACs','')); # CD 0017 - + foreach my $player ( keys %players ) { - if( defined( $players{$player}{isplayer} ) ) { - if( $players{$player}{isplayer} eq "0" ) { - Log3( $hash, 1, "not a player" ); - next; - } - } + my $playerdata; # CD 0029 - # CD 0017 check ignored IPs - if( defined( $players{$player}{IP} ) ) { - my @ip=split(':',$players{$player}{IP}); - if ($ip[0] ~~ @ignoredIPs) { - $players{$player}{ignore}=1; + if( defined( $players{$player}{isplayer} ) ) { + if( $players{$player}{isplayer} eq "0" ) { + Log3( $hash, 1, "not a player" ); + next; + } + } + + # CD 0017 check ignored IPs + if( defined( $players{$player}{IP} ) ) { + my @ip=split(':',$players{$player}{IP}); + if ($ip[0] ~~ @ignoredIPs) { + $players{$player}{ignore}=1; + next; + } + } + + # CD 0017 check ignored MACs + if( defined( $players{$player}{MAC} ) ) { + if ($players{$player}{MAC} ~~ @ignoredMACs) { + $players{$player}{ignore}=1; + next; + } + } + + # if the player is not yet known, it will be created + if( defined( $players{$player}{ID} ) ) { + Dispatch( $hash, "SB_PLAYER:$players{$player}{ID}:NONE", undef ); + } else { + Log3( $hash, 1, "not defined" ); next; } - } - - # CD 0017 check ignored MACs - if( defined( $players{$player}{MAC} ) ) { - if ($players{$player}{MAC} ~~ @ignoredMACs) { - $players{$player}{ignore}=1; - next; + + if( defined( $players{$player}{name} ) ) { + Dispatch( $hash, "SB_PLAYER:$players{$player}{ID}:" . + "name $players{$player}{name}", undef ); } - } - # if the player is not yet known, it will be created - if( defined( $players{$player}{ID} ) ) { - Dispatch( $hash, "SB_PLAYER:$players{$player}{ID}:NONE", undef ); - } else { - Log3( $hash, 1, "not defined" ); - next; - } + # CD 0029 start + if( defined( $players{$player}{model} ) ) { + $playerdata=$players{$player}{model}." "; + } else { + $playerdata="unknown "; + } - if( defined( $players{$player}{name} ) ) { - Dispatch( $hash, "SB_PLAYER:$players{$player}{ID}:" . - "name $players{$player}{name}", undef ); - } + if( defined( $players{$player}{canpoweroff} ) ) { + $playerdata.=$players{$player}{canpoweroff}." "; + } else { + $playerdata.="unknown "; + } - if( defined( $players{$player}{IP} ) ) { - Dispatch( $hash, "SB_PLAYER:$players{$player}{ID}:" . - "player ip $players{$player}{IP}", undef ); - } + if( defined( $players{$player}{displaytype} ) ) { + $playerdata.=$players{$player}{displaytype}." "; + } else { + $playerdata.="unknown "; + } - if( defined( $players{$player}{model} ) ) { - Dispatch( $hash, "SB_PLAYER:$players{$player}{ID}:" . - "player model $players{$player}{model}", undef ); - } + if( defined( $players{$player}{connected} ) ) { + $playerdata.=$players{$player}{connected}." "; + } else { + $playerdata.="unknown"; + } - if( defined( $players{$player}{canpoweroff} ) ) { - Dispatch( $hash, "SB_PLAYER:$players{$player}{ID}:" . - "player canpoweroff $players{$player}{canpoweroff}", - undef ); - } + if( defined( $players{$player}{IP} ) ) { + $playerdata.=$players{$player}{IP}; + } else { + $playerdata.="unknown"; + } + Dispatch( $hash, "SB_PLAYER:$players{$player}{ID}:" . + "playerdata $playerdata", undef ); - if( defined( $players{$player}{power} ) ) { - Dispatch( $hash, "SB_PLAYER:$players{$player}{ID}:" . - "power $players{$player}{power}", undef ); - } + # CD 0029 end - if( defined( $players{$player}{connected} ) ) { - Dispatch( $hash, "SB_PLAYER:$players{$player}{ID}:" . - "connected $players{$player}{connected}", undef ); - } - - if( defined( $players{$player}{displaytype} ) ) { - Dispatch( $hash, "SB_PLAYER:$players{$player}{ID}:" . - "displaytype $players{$player}{displaytype}", undef ); - } + if( defined( $players{$player}{power} ) ) { + Dispatch( $hash, "SB_PLAYER:$players{$player}{ID}:" . + "power $players{$player}{power}", undef ); + } } # the list for the sync masters # make all client create e new sync master list - SB_SERVER_Broadcast( $hash, "SYNCMASTER", - "FLUSH dont care", undef ); + SB_SERVER_Broadcast( $hash, "SYNCMASTER", + "FLUSH all", undef ); # now send the list for the sync masters + @SB_SERVER_SM=(); foreach my $player ( keys %players ) { next if defined($players{$player}{ignore}); my $uniqueid = join( "", split( ":", $players{$player}{MAC} ) ); Log3( $hash, 1, "SB_SERVER_ParseServerStatus($name): player has no name") unless defined($players{$player}{name}); Log3( $hash, 1, "SB_SERVER_ParseServerStatus($name): player has no MAC") unless defined($players{$player}{MAC}); - SB_SERVER_Broadcast( $hash, "SYNCMASTER", - "ADD $players{$player}{name} " . - "$players{$player}{MAC} $uniqueid", undef ); + push @SB_SERVER_SM, "ADD $players{$player}{name} " . + "$players{$player}{MAC} $uniqueid" # CD 0029 aufteilen wenn Hardware zu schwach } + # CD 0029 start + if(scalar(@SB_SERVER_SM)>0) { + RemoveInternalTimer( "SB_SERVER_tcb_SendSyncMasters:$name"); + InternalTimer( gettimeofday() + 0.01, + "SB_SERVER_tcb_SendSyncMasters", + "SB_SERVER_tcb_SendSyncMasters:$name", + 0 ); + } + # CD 0029 end + SB_SERVER_BuildPlayerList($hash); # CD 0024 return; } +# CD 0029 start +# für schwache Hardware Übertragung aufteilen +sub SB_SERVER_tcb_SendSyncMasters( $ ) { + my($in ) = shift; + my(undef,$name) = split(':',$in); + my $hash = $defs{$name}; + + RemoveInternalTimer( "SB_SERVER_tcb_SendSyncMasters:$name"); + + my $a; + my $t=time(); + + do { + $a=pop @SB_SERVER_SM; + if (defined($a)) { + SB_SERVER_Broadcast( $hash, "SYNCMASTER", $a, undef ); + } + } while ((time()<$t+0.05) && defined($a)); + + if(scalar(@SB_SERVER_SM)>0) { + Log 0,"SB_SERVER_tcb_SendSyncMasters: ".scalar(@SB_SERVER_SM)." entries remaining"; + InternalTimer( gettimeofday() + 0.05, + "SB_SERVER_tcb_SendSyncMasters", + "SB_SERVER_tcb_SendSyncMasters:$name", + 0 ); + } +} +# CD 0029 end # ---------------------------------------------------------------------------- # Parse the return values of the favorites items # ---------------------------------------------------------------------------- sub SB_SERVER_FavoritesParse( $$ ) { my ( $hash, $str ) = @_; - + my $name = $hash->{NAME}; Log3( $hash, 5, "SB_SERVER_FavoritesParse($name): called" ); @@ -1568,8 +2551,8 @@ sub SB_SERVER_FavoritesParse( $$ ) { # typically 'items' if( $data[ 0 ] =~ /^(items)*/ ) { my $notneeded = shift( @data ); - } - + } + # typically the start index being a number if( $data[ 0 ] =~ /^([0-9])*/ ) { my $notneeded = shift( @data ); @@ -1581,7 +2564,7 @@ sub SB_SERVER_FavoritesParse( $$ ) { $maxwanted = int( shift( @data ) ); } - # find the maximum number of favorites. That is typically at the + # find the maximum number of favorites. That is typically at the # end of the server response. So check there first my $totals = 0; my $lastdata = $data[ $#data ]; @@ -1600,7 +2583,7 @@ sub SB_SERVER_FavoritesParse( $$ ) { } else { $i++; } - + # delete the element from the list if( $delneeded == true ) { splice( @data, $i, 1 ); @@ -1633,6 +2616,8 @@ sub SB_SERVER_FavoritesParse( $$ ) { my $isplaylist = false; my $url = "?"; # CD 0009 hinzugefügt + my $cnt=0; + foreach ( @data ) { #Log 0,$_; if( $_ =~ /^(id:|ID:)([A-Za-z0-9\.]*)/ ) { @@ -1641,6 +2626,7 @@ sub SB_SERVER_FavoritesParse( $$ ) { if( $firstone == false ) { if(( $hasitemsbuf == false )||($isplaylist == true)) { # derive our hash entry + $namebuf="noname_".$cnt++ if($namebuf=~/^\s*$/); # CD 0037 my $entryuid = SB_SERVER_FavoritesName2UID( $namebuf ); # CD 0009 decode hinzugefügt # CD 0010 decode wieder entfernt $favorites{$name}{$entryuid} = { ID => $idbuf, @@ -1653,7 +2639,7 @@ sub SB_SERVER_FavoritesParse( $$ ) { $isplaylist = false; } else { # that is a folder we found, but we don't handle that - } + } } $firstone = false; @@ -1671,7 +2657,7 @@ sub SB_SERVER_FavoritesParse( $$ ) { } } elsif( $_ =~ /^(hasitems:)([0|1]?)/ ) { - if( int( $2 ) == 0 ) { + if( int( $2 ) == 0 ) { $hasitemsbuf = false; } else { $hasitemsbuf = true; @@ -1691,7 +2677,7 @@ sub SB_SERVER_FavoritesParse( $$ ) { } elsif( $_ =~ /^(name:)(.*)/ ) { # CD 0009 hinzugefügt $namebuf = $2; $namestarted = true; - + # CD 0009 start } elsif( $_ =~ /^(url:)(.*)/ ) { $url = $2; @@ -1709,6 +2695,7 @@ sub SB_SERVER_FavoritesParse( $$ ) { if( ( $namebuf ne "" ) && ( $idbuf ne "" ) ) { if(( $hasitemsbuf == false )||($isplaylist == true)) { # CD 0003 replaced ** my $entryuid = join( "", split( " ", $namebuf ) ); ** with: + $namebuf="noname_".$cnt++ if($namebuf=~/^\s*$/); # CD 0037 my $entryuid = SB_SERVER_FavoritesName2UID( $namebuf ); # CD 0009 decode hinzugefügt # CD 0010 decode wieder entfernt $favorites{$name}{$entryuid} = { ID => $idbuf, @@ -1720,24 +2707,61 @@ sub SB_SERVER_FavoritesParse( $$ ) { } # make all client create e new favorites list - SB_SERVER_Broadcast( $hash, "FAVORITES", - "FLUSH dont care", undef ); + SB_SERVER_Broadcast( $hash, "FAVORITES", + "FLUSH all", undef ); # find all the names and broadcast to our clients $favsetstring = "favorites:"; + @SB_SERVER_FAVS=(); # CD 0029 foreach my $titi ( keys %{$favorites{$name}} ) { - Log3( $hash, 5, "SB_SERVER_ParseFavorites($name): " . - "ID:" . $favorites{$name}{$titi}{ID} . - " Name:" . $favorites{$name}{$titi}{Name} . " $titi" ); - $favsetstring .= "$titi,"; - SB_SERVER_Broadcast( $hash, "FAVORITES", - "ADD $name $favorites{$name}{$titi}{ID} " . - "$titi $favorites{$name}{$titi}{URL} $favorites{$name}{$titi}{Name}", undef ); # CD 0009 URL an Player schicken + Log3( $hash, 5, "SB_SERVER_ParseFavorites($name): " . + "ID:" . $favorites{$name}{$titi}{ID} . + " Name:" . $favorites{$name}{$titi}{Name} . " $titi" ); + $favsetstring .= "$titi,"; + push @SB_SERVER_FAVS, "ADD $name $favorites{$name}{$titi}{ID} " . + "$titi $favorites{$name}{$titi}{URL} LMS $favorites{$name}{$titi}{Name}"; # CD 0009 URL an Player schicken # CD 0029 aufteilen wenn Hardware zu schwach } + # CD 0029 start + if(scalar(@SB_SERVER_FAVS)>0) { + RemoveInternalTimer( "SB_SERVER_tcb_SendFavorites:$name"); + InternalTimer( gettimeofday() + 0.01, + "SB_SERVER_tcb_SendFavorites", + "SB_SERVER_tcb_SendFavorites:$name", + 0 ); + } + # CD 0029 end #chop( $favsetstring ); #$favsetstring .= " "; } +# CD 0029 start +# für schwache Hardware Übertragung aufteilen +sub SB_SERVER_tcb_SendFavorites( $ ) { + my($in ) = shift; + my(undef,$name) = split(':',$in); + my $hash = $defs{$name}; + + RemoveInternalTimer( "SB_SERVER_tcb_SendFavorites:$name"); + + my $a; + my $t=time(); + + do { + $a=pop @SB_SERVER_FAVS; + if (defined($a)) { + SB_SERVER_Broadcast( $hash, "FAVORITES", $a, undef ); # CD 0009 URL an Player schicken + } + } while ((time()<$t+0.05) && defined($a)); + + if(scalar(@SB_SERVER_FAVS)>0) { + #Log 0,"SB_SERVER_tcb_SendFavorites: ".scalar(@SB_SERVER_FAVS)." entries remaining"; + InternalTimer( gettimeofday() + 0.05, + "SB_SERVER_tcb_SendFavorites", + "SB_SERVER_tcb_SendFavorites:$name", + 0 ); + } +} +# CD 0029 end # ---------------------------------------------------------------------------- # generate a UID for the hash entry from the name @@ -1753,12 +2777,21 @@ sub SB_SERVER_FavoritesName2UID( $ ) { "é" => "e", "è" => "e", "ë" => "e", "à" => "a", "ç" => "c" ); my $Sonderzeichenkeys = join ("|", keys(%Sonderzeichen)); $namestr =~ s/($Sonderzeichenkeys)/$Sonderzeichen{$1}/g; +# $namestr =~ s/($Sonderzeichenkeys)/$Sonderzeichen{$1}||''/g; # CD 0009 + # CD 0034 start + my $rc=eval + { + require Text::Unaccent; + $namestr=Text::Unaccent::unac_string('UTF8', $namestr); + }; + # CD 0034 end + # this defines the regexp. Please add new stuff with the seperator | # CD 0003 changed öÜ to ö|Ü - my $tobereplaced = '[Ä|ä|Ö|ö|Ü|ü|\[|\]|\{|\}|\(|\)|\\\\|,|:|\?|' . # CD 0011 ,:? hinzugefügt - '\/|\'|\.|\"|\^|°|\$|\||%|@|&|\+]'; # CD 0009 + hinzugefügt + my $tobereplaced = '[Ä|ä|Ö|ö|Ü|ü|\[|\]|\{|\}|\(|\)|\\\\|,|:|\?|;|' . # CD 0011 ,:? hinzugefügt # CD 0035 ; hinzugefügt + '\/|\'|\.|\"|\^|°|\$|\||%|@|*|#|&|\+]'; # CD 0009 + hinzugefügt # CD 0070 * und # hinzugefügt $namestr =~ s/$tobereplaced//g; @@ -1774,7 +2807,7 @@ sub SB_SERVER_CMDStackPush( $$ ) { my $name = $hash->{NAME}; my $n = $SB_SERVER_CmdStack{$name}{last_n}; - + $n=0 if(!defined($n)); # CD 0007 if( $n > AttrVal( $name, "maxcmdstack", 200 ) ) { @@ -1790,9 +2823,9 @@ sub SB_SERVER_CMDStackPush( $$ ) { $SB_SERVER_CmdStack{$name}{last_n} = $n; $SB_SERVER_CmdStack{$name}{first_n} = $n if (!defined($SB_SERVER_CmdStack{$name}{first_n})); # CD 0007 - + # update overall number of entries - $SB_SERVER_CmdStack{$name}{cnt} = $SB_SERVER_CmdStack{$name}{last_n} - + $SB_SERVER_CmdStack{$name}{cnt} = $SB_SERVER_CmdStack{$name}{last_n} - $SB_SERVER_CmdStack{$name}{first_n} + 1; $hash->{CMDSTACK}=$SB_SERVER_CmdStack{$name}{cnt}; # CD 0007 } @@ -1802,13 +2835,13 @@ sub SB_SERVER_CMDStackPush( $$ ) { # ---------------------------------------------------------------------------- sub SB_SERVER_CMDStackPop( $ ) { my ( $hash ) = @_; - + my $name = $hash->{NAME}; - + my $n = $SB_SERVER_CmdStack{$name}{first_n}; $n=0 if(!defined($n)); # CD 0007 - + my $res = ""; # return the first element of the list if( defined( $SB_SERVER_CmdStack{$name}{$n} ) ) { @@ -1819,15 +2852,15 @@ sub SB_SERVER_CMDStackPop( $ ) { } # and now remove the first element - + delete( $SB_SERVER_CmdStack{$name}{$n} ); - + $n = $n + 1; - + if ( $n <= $SB_SERVER_CmdStack{$name}{last_n} ) { # CD 0000 changed first_n to last_n $SB_SERVER_CmdStack{$name}{first_n} = $n; # update overall number of entries - $SB_SERVER_CmdStack{$name}{cnt} = $SB_SERVER_CmdStack{$name}{last_n} - + $SB_SERVER_CmdStack{$name}{cnt} = $SB_SERVER_CmdStack{$name}{last_n} - $SB_SERVER_CmdStack{$name}{first_n} + 1; } else { # end of list reached @@ -1836,7 +2869,7 @@ sub SB_SERVER_CMDStackPop( $ ) { $SB_SERVER_CmdStack{$name}{cnt} = 0; } $hash->{CMDSTACK}=$SB_SERVER_CmdStack{$name}{cnt}; # CD 0007 - + return( $res ); } @@ -1847,41 +2880,75 @@ sub SB_SERVER_CMDStackPop( $ ) { # ---------------------------------------------------------------------------- sub SB_SERVER_ParseServerAlarmPlaylists( $$ ) { my( $hash, $dataptr ) = @_; - + my $name = $hash->{NAME}; Log3( $hash, 4, "SB_SERVER_ParseServerAlarmPlaylists($name): called" ); # force all clients to delete alarm playlists - SB_SERVER_Broadcast( $hash, "ALARMPLAYLISTS", - "FLUSH dont care", undef ); + SB_SERVER_Broadcast( $hash, "ALARMPLAYLISTS", + "FLUSH all", undef ); - my @r=split("category:",join(" ",@{$dataptr})); - 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)); - SB_SERVER_Broadcast( $hash, "ALARMPLAYLISTS", - "ADD $pn category ".substr($a,0,$i1), undef ); - SB_SERVER_Broadcast( $hash, "ALARMPLAYLISTS", - "ADD $pn title ".substr($a,$i1+7,$i2-$i1-7), undef ); - SB_SERVER_Broadcast( $hash, "ALARMPLAYLISTS", - "ADD $pn url $url", undef ); - } + @SB_SERVER_AL_PLS=split("category:",join(" ",@{$dataptr})); + # CD 0029 Übertragung an Player aufteilen + if(scalar(@SB_SERVER_AL_PLS)>0) { + RemoveInternalTimer( "SB_SERVER_tcb_SendAlarmPlaylists:$name"); + InternalTimer( gettimeofday() + 0.01, + "SB_SERVER_tcb_SendAlarmPlaylists", + "SB_SERVER_tcb_SendAlarmPlaylists:$name", + 0 ); } } # CD 0011 end +# CD 0029 start +# für schwache Hardware Übertragung aufteilen +sub SB_SERVER_tcb_SendAlarmPlaylists( $ ) { + my($in ) = shift; + my(undef,$name) = split(':',$in); + my $hash = $defs{$name}; + + RemoveInternalTimer( "SB_SERVER_tcb_SendAlarmPlaylists:$name"); + + my $a; + my $t=time(); + + do { + $a=pop @SB_SERVER_AL_PLS; + if (defined($a)) { + 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)); + SB_SERVER_Broadcast( $hash, "ALARMPLAYLISTS", + "ADD $pn category ".substr($a,0,$i1), undef ); + SB_SERVER_Broadcast( $hash, "ALARMPLAYLISTS", + "ADD $pn title ".substr($a,$i1+7,$i2-$i1-7), undef ); + SB_SERVER_Broadcast( $hash, "ALARMPLAYLISTS", + "ADD $pn url $url", undef ); + } + } + } while ((time()<$t+0.05) && defined($a)); + + if(scalar(@SB_SERVER_AL_PLS)>0) { + #Log 0,"SB_SERVER_tcb_SendAlarmPlaylists: ".scalar(@SB_SERVER_AL_PLS)." entries remaining"; + InternalTimer( gettimeofday() + 0.05, + "SB_SERVER_tcb_SendAlarmPlaylists", + "SB_SERVER_tcb_SendAlarmPlaylists:$name", + 0 ); + } +} +# CD 0029 end + # ---------------------------------------------------------------------------- # parse the list of known Playlists # ---------------------------------------------------------------------------- sub SB_SERVER_ParseServerPlaylists( $$ ) { my( $hash, $dataptr ) = @_; - + my $name = $hash->{NAME}; Log3( $hash, 4, "SB_SERVER_ParseServerPlaylists($name): called" ); @@ -1889,7 +2956,7 @@ sub SB_SERVER_ParseServerPlaylists( $$ ) { my $namebuf = ""; my $uniquename = ""; my $idbuf = -1; - + # typically the start index being a number if( $dataptr->[ 0 ] =~ /^([0-9])*/ ) { shift( @{$dataptr} ); @@ -1914,46 +2981,86 @@ sub SB_SERVER_ParseServerPlaylists( $$ ) { $datastr ); # make all client create a new favorites list - SB_SERVER_Broadcast( $hash, "PLAYLISTS", - "FLUSH dont care", undef ); + SB_SERVER_Broadcast( $hash, "PLAYLISTS", + "FLUSH all", undef ); my @data1 = split( " ", $datastr ); - foreach( @data1 ) { - if( $_ =~ /^(id:)(.*)/ ) { - Log3( $hash, 5, "SB_SERVER_ParseServerPlaylists($name): " . - "id:$idbuf name:$namebuf " ); - if( $idbuf != -1 ) { - $uniquename = SB_SERVER_FavoritesName2UID( $namebuf ); # CD 0009 decode hinzugefügt # CD 0010 decode wieder entfernt - SB_SERVER_Broadcast( $hash, "PLAYLISTS", - "ADD $namebuf $idbuf $uniquename", undef ); - } - $idbuf = $2; - $namebuf = ""; - $uniquename = ""; - next; - } elsif( $_ =~ /^(playlist:)(.*)/ ) { - $namebuf = $2; - next; - } elsif( $_ =~ /^(count:)([0-9]*)/ ) { - # the last entry of the return - Log3( $hash, 5, "SB_SERVER_ParseServerPlaylists($name): " . - "id:$idbuf name:$namebuf " ); - if( $idbuf != -1 ) { - $uniquename = SB_SERVER_FavoritesName2UID( $namebuf ); # CD 0009 decode hinzugefügt # CD 0010 decode wieder entfernt - SB_SERVER_Broadcast( $hash, "PLAYLISTS", - "ADD $namebuf $idbuf $uniquename", undef ); - } - - } else { - $namebuf .= "_" . $_; - next; - } - } + @SB_SERVER_PLS=(); + my $cnt=1; # CD 0037 + + foreach( @data1 ) { + if( $_ =~ /^(id:)(.*)/ ) { + Log3( $hash, 5, "SB_SERVER_ParseServerPlaylists($name): " . + "id:$idbuf name:$namebuf " ); + if( $idbuf != -1 ) { + $namebuf="noname_".$cnt++ if($namebuf=~/^\s*$/); # CD 0037 + $uniquename = SB_SERVER_FavoritesName2UID( $namebuf ); # CD 0009 decode hinzugefügt # CD 0010 decode wieder entfernt + push @SB_SERVER_PLS, "ADD $namebuf $idbuf $uniquename LMS"; # CD 0029 + } + $idbuf = $2; + $namebuf = ""; + $uniquename = ""; + next; + } elsif( $_ =~ /^(playlist:)(.*)/ ) { + $namebuf = $2; + next; + } elsif( $_ =~ /^(count:)([0-9]*)/ ) { + # the last entry of the return + Log3( $hash, 5, "SB_SERVER_ParseServerPlaylists($name): " . + "id:$idbuf name:$namebuf " ); + if( $idbuf != -1 ) { + $namebuf="noname_".$cnt++ if($namebuf=~/^\s*$/); # CD 0037 + $uniquename = SB_SERVER_FavoritesName2UID( $namebuf ); # CD 0009 decode hinzugefügt # CD 0010 decode wieder entfernt + push @SB_SERVER_PLS, "ADD $namebuf $idbuf $uniquename LMS"; # CD 0029 + } + + } else { + $namebuf .= "_" . $_; + next; + } + } + # CD 0029 start + if(scalar(@SB_SERVER_PLS)>0) { + InternalTimer( gettimeofday() + 0.01, + "SB_SERVER_tcb_SendPlaylists", + "SB_SERVER_tcb_SendPlaylists:$name", + 0 ); + } + # CD 0029 end return; } +# CD 0029 start +# für schwache Hardware Übertragung aufteilen +sub SB_SERVER_tcb_SendPlaylists( $ ) { + my($in ) = shift; + my(undef,$name) = split(':',$in); + my $hash = $defs{$name}; + + RemoveInternalTimer( "SB_SERVER_tcb_SendPlaylists:$name"); + + my $a; + my $t=time(); + + do { + $a=pop @SB_SERVER_PLS; + if (defined($a)) { + SB_SERVER_Broadcast( $hash, "PLAYLISTS", $a, undef ); + } + } while ((time()<$t+0.05) && defined($a)); + + if(scalar(@SB_SERVER_PLS)>0) { + #Log 0,"SB_SERVER_tcb_SendPlaylists: ".scalar(@SB_SERVER_PLS)." entries remaining"; + InternalTimer( gettimeofday() + 0.05, + "SB_SERVER_tcb_SendPlaylists", + "SB_SERVER_tcb_SendPlaylists:$name", + 0 ); + } +} +# CD 0029 end + # CD 0008 start sub SB_SERVER_CheckConnection($) { my($in ) = shift; @@ -1963,10 +3070,10 @@ sub SB_SERVER_CheckConnection($) { Log3( $hash, 3, "SB_SERVER_CheckConnection($name): STATE: " . $hash->{STATE} . " power: ". ReadingsVal( $name, "power", "X" )); # CD 0009 level 2->3 if(ReadingsVal( $name, "power", "X" ) ne "on") { Log3( $hash, 3, "SB_SERVER_CheckConnection($name): forcing power on"); # CD 0009 level 2->3 - + $hash->{helper}{pingCounter}=0; - - SB_SERVER_Broadcast( $hash, "SERVER", + + SB_SERVER_Broadcast( $hash, "SERVER", "IP " . $hash->{IP} . ":" . AttrVal( $name, "httpport", "9000" ) ); $hash->{helper}{doBroadcast}=1; @@ -1978,15 +3085,15 @@ sub SB_SERVER_CheckConnection($) { # start the alive checking mechanism # CD 0020 SB_SERVER_tcb_Alive verwenden RemoveInternalTimer( "SB_SERVER_Alive:$name"); - InternalTimer( gettimeofday() + + InternalTimer( gettimeofday() + AttrVal( $name, "alivetimer", 10 ), "SB_SERVER_tcb_Alive", - "SB_SERVER_Alive:$name", + "SB_SERVER_Alive:$name", 0 ); } } RemoveInternalTimer( "CheckConnection:$name"); -} +} # CD 0008 end # ---------------------------------------------------------------------------- @@ -2002,9 +3109,16 @@ sub SB_SERVER_Notify( $$ ) { DevIo_OpenDev($hash, 0, "SB_SERVER_DoInit" ); } # CD end - #Log3( $hash, 4, "SB_SERVER_Notify($name): called" . + #Log3( $hash, 4, "SB_SERVER_Notify($name): called" . # "Own:" . $name . " Device:" . $devName ); + # CD 0024 start + if( grep(m/^SAVE$|^SHUTDOWN$/, @{$dev_hash->{CHANGED}}) ) { # CD 0043 auch bei SHUTDOWN speichern + SB_SERVER_SaveSyncGroups($hash) if($SB_SERVER_hasDataDumper==1); + SB_SERVER_SaveServerStates($hash) if($SB_SERVER_hasDataDumper==1); + } + # CD 0024 end + # CD 0008 start if($devName eq $name ) { if (grep (m/^DISCONNECTED$/,@{$dev_hash->{CHANGED}})) { @@ -2013,8 +3127,8 @@ sub SB_SERVER_Notify( $$ ) { } if (grep (m/^CONNECTED$/,@{$dev_hash->{CHANGED}})) { Log3( $hash, 3, "SB_SERVER_Notify($name): CONNECTED - STATE: " . $hash->{STATE} . " power: ". ReadingsVal( $name, "power", "X" )); # CD 0009 level 2->3 - InternalTimer( gettimeofday() + 2, - "SB_SERVER_CheckConnection", + InternalTimer( gettimeofday() + 2, + "SB_SERVER_CheckConnection", "CheckConnection:$name", 0 ); } @@ -2026,14 +3140,14 @@ sub SB_SERVER_Notify( $$ ) { RemoveInternalTimer( $hash ); # CD 0020 SB_SERVER_tcb_Alive verwenden RemoveInternalTimer( "SB_SERVER_Alive:$name"); - InternalTimer( gettimeofday() + 10, + InternalTimer( gettimeofday() + 10, "SB_SERVER_tcb_Alive", - "SB_SERVER_Alive:$name", + "SB_SERVER_Alive:$name", 0 ); # CD 0007 use DevIo_Disconnected instead of DevIo_CloseDev - #DevIo_CloseDev( $hash ); - DevIo_Disconnected( $hash ); + #DevIo_CloseDev( $hash ); + DevIo_Disconnected( $hash ); $hash->{helper}{pingCounter}=9999; # CD 0007 $hash->{CLICONNECTION} = "off"; # CD 0007 # CD 0005 set state after DevIo_CloseDev @@ -2044,9 +3158,9 @@ sub SB_SERVER_Notify( $$ ) { # do an update of the status, but SB CLI must come up # CD 0020 SB_SERVER_tcb_Alive verwenden RemoveInternalTimer( "SB_SERVER_Alive:$name"); - InternalTimer( gettimeofday() + 20, + InternalTimer( gettimeofday() + 20, "SB_SERVER_tcb_Alive", - "SB_SERVER_Alive:$name", + "SB_SERVER_Alive:$name", 0 ); } else { return( undef ); @@ -2069,9 +3183,9 @@ sub SB_SERVER_Notify( $$ ) { # do an update of the status, but SB CLI must come up # CD 0020 SB_SERVER_tcb_Alive verwenden RemoveInternalTimer( "SB_SERVER_Alive:$name"); - InternalTimer( gettimeofday() + 10, + InternalTimer( gettimeofday() + 10, "SB_SERVER_tcb_Alive", - "SB_SERVER_Alive:$name", + "SB_SERVER_Alive:$name", 0 ); return( "" ); } else { @@ -2092,14 +3206,20 @@ sub SB_SERVER_LMS_Status( $ ) { # CD 0007 login muss als erstes gesendet werden $hash->{helper}{SB_SERVER_LMS_Status}=time(); - if( ( $hash->{USERNAME} ne "?" ) && + if( ( $hash->{USERNAME} ne "?" ) && ( $hash->{PASSWORD} ne "?" ) ) { - DevIo_SimpleWrite( $hash, "login " . - $hash->{USERNAME} . " " . - $hash->{PASSWORD} . "\n", - 0 ); + my ($user,$password)=SB_SERVER_readPassword($hash); # CD 0031 + if(defined($user)) { + DevIo_SimpleWrite( $hash, "login " . + $user . " " . + $password . "\n", + 0 ); + } else { + Log3( $hash, 3, "SB_SERVER_LMS_Status($name): login " . + "required but no username and password specified" ); + } } - + # subscribe us DevIo_SimpleWrite( $hash, "listen 1\n", 0 ); @@ -2107,10 +3227,21 @@ sub SB_SERVER_LMS_Status( $ ) { DevIo_SimpleWrite( $hash, "pref authorize ?\n", 0 ); DevIo_SimpleWrite( $hash, "version ?\n", 0 ); DevIo_SimpleWrite( $hash, "serverstatus 0 200\n", 0 ); - DevIo_SimpleWrite( $hash, "favorites items 0 " . + DevIo_SimpleWrite( $hash, "favorites items 0 " . AttrVal( $name, "maxfavorites", 100 ) . " want_url:1\n", 0 ); # CD 0009 url mit abfragen DevIo_SimpleWrite( $hash, "playlists 0 200\n", 0 ); DevIo_SimpleWrite( $hash, "alarm playlists 0 300\n", 0 ); # CD 0011 + DevIo_SimpleWrite( $hash, "apps 0 200\n", 0 ); # CD 0029 + # CD 0032 start + if(defined($hash->{helper}{apps})) { + my @enabledApps=split(',',AttrVal($name,'enablePlugins','')); + foreach my $app (@enabledApps) { + if(defined($hash->{helper}{apps}{$app})) { + DevIo_SimpleWrite( $hash, ($hash->{helper}{apps}{$app}{cmd})." items 0 200\n", 0 ); + } + } + } + # CD 0032 end return( true ); } @@ -2127,6 +3258,388 @@ sub SB_SERVER_setStates($$) } # CD 0006 end +# ---------------------------------------------------------------------------- +# load/save sync groups +# +# CD 0024 +# ---------------------------------------------------------------------------- +sub SB_SERVER_StatefileName($$) +{ + my( $name,$prefix ) = @_; + + my $statefile = $attr{global}{statefile}; + $statefile = substr $statefile,0,rindex($statefile,'/')+1; + return $statefile . $prefix . "_$name.dd.save"; +} + +sub SB_SERVER_SaveSyncGroups($) +{ + my( $hash ) = @_; + my $name = $hash->{NAME}; + + return "No saved syncgroups found" unless(defined($hash->{helper}{syncGroups})); + return "No statefile specified" if(!$attr{global}{statefile}); + my $statefile = SB_SERVER_StatefileName($name,'sbsg'); + + if(open(FH, ">$statefile")) { + my $t = localtime; + print FH "#$t\n"; + + my $dumper = Data::Dumper->new([]); + $dumper->Terse(1); + + $dumper->Values([$hash->{helper}{syncGroups}]); + print FH $dumper->Dump; + + close(FH); + } else { + + my $msg = "SB_SERVER_SaveSyncGroups: Cannot open $statefile: $!"; + Log3 $hash, 1, $msg; + } + + return undef; +} + +sub SB_SERVER_LoadSyncGroups($) +{ + my ($hash) = @_; + my $name = $hash->{NAME}; + + return "No statefile specified" if(!$attr{global}{statefile}); + my $statefile = SB_SERVER_StatefileName($name,'sbsg'); + + 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}{syncGroups} = $decoded; + } else { + my $msg = "SB_SERVER_LoadSyncGroups: no syncgroups file found"; + Log3 undef, 4, $msg; + } + return undef; +} + +sub SB_SERVER_BuildPlayerList($) +{ + my ($hash) = @_; + my $name = $hash->{NAME}; + + delete( $hash->{helper}{players} ) if (defined($hash->{helper}{players})); + + # build player list + foreach my $mydev ( keys %defs ) { + my $iodevhash; + + if( defined( $defs{$mydev}{IODev} ) ) { + $iodevhash = $defs{$mydev}{IODev}; + if( ( defined( $defs{$mydev}{TYPE} ) ) && + ( $defs{$mydev}{TYPE} eq "SB_PLAYER" )){ # CD 0029 umsortiert + if( ( defined( $iodevhash->{NAME} ) ) && # CD 0029 umsortiert + ( $iodevhash->{NAME} eq $name ) ) { + # we found a valid entry + my $chash = $defs{$mydev}; + my $fn = $chash->{NAME}; + my $ln = $chash->{PLAYERNAME}; + + if($ln ne '?') { + if(!defined($hash->{helper}{players}{$fn})) { + $hash->{helper}{players}{$fn}{fhemname}=$fn; + $hash->{helper}{players}{$fn}{lmsname}=$ln; + $hash->{helper}{players}{$fn}{mac}=$chash->{PLAYERMAC}; + $hash->{helper}{players}{$fn}{type}='FHEM'; + } + if(!defined($hash->{helper}{players}{$ln})) { + $hash->{helper}{players}{$ln}{fhemname}=$fn; + $hash->{helper}{players}{$ln}{lmsname}=$ln; + $hash->{helper}{players}{$ln}{mac}=$chash->{PLAYERMAC}; + $hash->{helper}{players}{$ln}{type}='LMS'; + } + } + } + } + } + } +} + +# ---------------------------------------------------------------------------- +# load/save server state +# +# CD 0025 +# ---------------------------------------------------------------------------- + +sub SB_SERVER_SaveServerStates($) +{ + my( $hash ) = @_; + my $name = $hash->{NAME}; + + return "No server states found" unless(defined($hash->{helper}{savedServerStates})); + return "No statefile specified" if(!$attr{global}{statefile}); + my $statefile = SB_SERVER_StatefileName($name,'sbst'); + + if(open(FH, ">$statefile")) { + my $t = localtime; + print FH "#$t\n"; + + my $dumper = Data::Dumper->new([]); + $dumper->Terse(1); + + $dumper->Values([$hash->{helper}{savedServerStates}]); + print FH $dumper->Dump; + + close(FH); + } else { + + my $msg = "SB_SERVER_SaveServerState: Cannot open $statefile: $!"; + Log3 $hash, 1, $msg; + } + + return undef; +} + +sub SB_SERVER_LoadServerStates($) +{ + my ($hash) = @_; + my $name = $hash->{NAME}; + + return "No statefile specified" if(!$attr{global}{statefile}); + my $statefile = SB_SERVER_StatefileName($name,'sbst'); + + 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}{savedServerStates} = $decoded; + } else { + my $msg = "SB_SERVER_LoadServerState: no server state file found"; + Log3 undef, 4, $msg; + } + return undef; +} + +sub SB_SERVER_Save($$) { + my ( $hash, $statename ) = @_; + my $name = $hash->{NAME}; + + $statename='default' unless defined($statename); + + Log3( $hash, 3, "SB_SERVER_Save($name): name: $statename"); + + delete($hash->{helper}{savedServerStates}{$statename}) if(defined($hash->{helper}{savedServerStates}) && defined($hash->{helper}{savedServerStates}{$statename})); + + SB_SERVER_BuildPlayerList($hash) if(!defined($hash->{helper}{players})); + + foreach my $e ( keys %{$hash->{helper}{players}} ) { + if($e ne '') { + if(defined($hash->{helper}{players}{$e})) { + if($hash->{helper}{players}{$e}{type} eq 'FHEM') { + if( defined( $defs{$e} ) ) { + my $phash = $defs{$e}; + + $hash->{helper}{savedServerStates}{$statename}{players}{$e}{mac}=$hash->{helper}{players}{$e}{mac}; + $hash->{helper}{savedServerStates}{$statename}{players}{$e}{power}=ReadingsVal($e,"power","off"); + $hash->{helper}{savedServerStates}{$statename}{players}{$e}{volume}=ReadingsVal($e,"volume","0"); + + my $sm=InternalVal($e,"SYNCMASTER","none"); + $hash->{helper}{savedServerStates}{$statename}{players}{$e}{syncMaster}=$sm; + if(($sm eq 'none') || ($sm eq $hash->{helper}{players}{$e}{mac})) { + SB_PLAYER_Save($phash,"xxx_sss_".$statename); + } + } + } + } + } + } +} + +sub SB_SERVER_Recall($$) { + my ( $hash, $arg ) = @_; # CD 0036 + my $name = $hash->{NAME}; + + my $del=0; + my $delonly=0; + + my $statename; + my @args=split " ",$arg; + + if(defined($args[0])) { + $statename=$args[0]; + } else { + $statename='default'; + } + + Log3( $hash, 3, "SB_SERVER_Recall($name): name: $statename"); + + # Optionen auswerten + for my $opt (@args) { + $del=1 if($opt=~ m/^del$/); + $delonly=1 if($opt=~ m/^delonly$/); + } + + if(defined($hash->{helper}{savedServerStates}) && defined($hash->{helper}{savedServerStates}{$statename})) { + if($delonly==0) { + # unsync all & set power + foreach my $e ( keys %{$hash->{helper}{savedServerStates}{$statename}{players}} ) { + if($e ne '') { + if(defined($hash->{helper}{savedServerStates}{$statename}{players}{$e})) { + my $mac=$hash->{helper}{savedServerStates}{$statename}{players}{$e}{mac}; + SB_SERVER_Write( $hash, $mac." sync -\n", "" ); + if($hash->{helper}{savedServerStates}{$statename}{players}{$e}{power} eq 'on') { + SB_SERVER_Write( $hash, $mac." power 1\n", "" ); + } else { + SB_SERVER_Write( $hash, $mac." power 0\n", "" ); + } + } + } + } + + # sync slaves & set volume + foreach my $e ( keys %{$hash->{helper}{savedServerStates}{$statename}{players}} ) { + if($e ne '') { + if(defined($hash->{helper}{savedServerStates}{$statename}{players}{$e})) { + my $mac=$hash->{helper}{savedServerStates}{$statename}{players}{$e}{mac}; + if (($hash->{helper}{savedServerStates}{$statename}{players}{$e}{syncMaster} ne 'none') + && ($hash->{helper}{savedServerStates}{$statename}{players}{$e}{syncMaster} ne $mac)) { + SB_SERVER_Write( $hash, $hash->{helper}{savedServerStates}{$statename}{players}{$e}{syncMaster}." sync $mac\n", "" ); + SB_SERVER_Write( $hash, "$mac mixer volume ". $hash->{helper}{savedServerStates}{$statename}{players}{$e}{volume}. "\n", "" ); + } + } + } + } + + # reload player states + foreach my $e ( keys %{$hash->{helper}{savedServerStates}{$statename}{players}} ) { + if($e ne '') { + if(defined($hash->{helper}{savedServerStates}{$statename}{players}{$e})) { + my $mac=$hash->{helper}{savedServerStates}{$statename}{players}{$e}{mac}; + if (($hash->{helper}{savedServerStates}{$statename}{players}{$e}{syncMaster} eq 'none') + || ($hash->{helper}{savedServerStates}{$statename}{players}{$e}{syncMaster} eq $mac)) { # CD 0026 ne durch eq ersetzt + if( defined( $defs{$e} ) ) { + my $phash = $defs{$e}; + + SB_PLAYER_Recall($phash,"xxx_sss_".$statename); + } + } + } + } + } + } + if(($del==1)||($delonly==1)) { + # delete + foreach my $e ( keys %{$hash->{helper}{savedServerStates}{$statename}{players}} ) { + if($e ne '') { + if(defined($hash->{helper}{savedServerStates}{$statename}{players}{$e})) { + if( defined( $defs{$e} ) ) { + my $phash = $defs{$e}; + + SB_PLAYER_Recall($phash,"xxx_sss_".$statename." delonly"); + } + } + } + } + delete($hash->{helper}{savedServerStates}{$statename}); + } + } +} + +# CD 0031 User und Passwort speichern/lesen, aus 72_FRITZBOX +sub SB_SERVER_storePassword($$$) +{ + my ($hash, $user, $password) = @_; + + my $index = $hash->{TYPE}."_".$hash->{NAME}."_passwd"; + my $key = getUniqueId().$index; + + my $enc_pwd = ""; + + if(eval "use Digest::MD5;1") + { + $key = Digest::MD5::md5_hex(unpack "H*", $key); + $key .= Digest::MD5::md5_hex($key); + } + + for my $char (split //, $password) + { + my $encode=chop($key); + $enc_pwd.=sprintf("%.2x",ord($char)^ord($encode)); + $key=$encode.$key; + } + + my $err = setKeyValue($hash->{TYPE}."_".$hash->{NAME}."_user", $user); + return "error while saving the user - $err" if(defined($err)); + + $err = setKeyValue($index, $enc_pwd); + return "error while saving the password - $err" if(defined($err)); + + return "user and password successfully saved"; +} + +sub SB_SERVER_readPassword($) +{ + my ($hash) = @_; + my $name = $hash->{NAME}; + + my $index = $hash->{TYPE}."_".$hash->{NAME}."_passwd"; + my $key = getUniqueId().$index; + + my ($user, $password, $err); + + ($err, $password) = getKeyValue($index); + + if ( defined($err) ) { + Log3 $hash, 2, "SB_SERVER_readPassword($name): unable to read SB_SERVER password: $err"; + return undef; + } + + my $dec_pwd = ''; + + if ( defined($password) ) { + if ( eval "use Digest::MD5;1" ) { + $key = Digest::MD5::md5_hex(unpack "H*", $key); + $key .= Digest::MD5::md5_hex($key); + } + + for my $char (map { pack('C', hex($_)) } ($password =~ /(..)/g)) { + my $decode=chop($key); + $dec_pwd.=chr(ord($char)^ord($decode)); + $key=$decode.$key; + } + } else { + Log3 $hash, 2, "SB_SERVER_readPassword($name): No password found"; + return undef; + } + + ($err, $user) = getKeyValue($hash->{TYPE}."_".$hash->{NAME}."_user"); + + if ( defined($err) ) { + Log3 $hash, 2, "SB_SERVER_readPassword($name): unable to read SB_SERVER user: $err"; + return undef; + } + if ( defined($user) ) { + return ($user,$dec_pwd) + } else { + Log3 $hash, 2, "SB_SERVER_readPassword($name): No user found"; + return undef; + } +} +# CD 0031 end # ############################################################################ # No PERL code beyond this line @@ -2134,9 +3647,9 @@ sub SB_SERVER_setStates($$) 1; =pod -=item device +=item device =item summary connect to a Logitech Media Server (LMS) -=item summary_DE Anbindung an Logitech Media Server (LMS) +=item summary_DE Anbindung an Logitech Media Server (LMS) =begin html @@ -2150,10 +3663,10 @@ sub SB_SERVER_setStates($$) This module allows you in combination with the module SB_PLAYER to control a Logitech Media Server (LMS) and connected Squeezebox Media Players.

- + Attention: The [:cliserverport] parameter is optional. You just need to configure it if you changed it on the LMS. - The default TCP port is 9090.

+ The default TCP port is 9090.

Optional
  • <[RCC]>: You can define a FHEM RCC Device, if you want to wake it up when you set the SB_SERVER on.
  • @@ -2177,7 +3690,34 @@ sub SB_SERVER_setStates($$)
  • renew - Renews the connection to the server
  • rescan - Starts the scan of the music library of the server
  • statusRequest - Update of readings from server and configured players
  • -
+
  • save [<name>] - Save all players state
  • +
  • recall [<name>] [options] - recall all players state
    Options:
  • +
      +
    • del - delete saved state after restore
    • +
    • delonly - delete saved state without restoring
    • +
    + +

    + The command syncGroup can be used to manage group templates for synchronizing players. Each template + contains a list of players and can be activated on demand. + Possible subcommands:

    +
      +
    • addp <playerName[,playerName...]> <template> - Add the specified player(s) to the template. + if the template doesn't exist, it is created automatically and the first player will be group master.
    • +
    • removep <playerName[,playerName...]> <template> - Remove the specified player(s) from the + template. If one of the players was group master, the first remaining player will become new group master.
    • +
    • masterp <playerName> <template> - Change the group master.
    • +
    • load [poweron] <template> - Activate the template. The players of the template are unsynced from their + groups and added to a new group. With the optional keyword poweron, FHEM tries to power on all the players.
    • +
    • delete <template> - Delete the template.
    • +
    • deleteall - Delete all templates.
    • +
    • talk <template> <text> - Save the state of all the players, activate the template and output <text> using + the configured TTS provider. Restore the state of all the players after the end of the TTS output. + With the optional keyword poweron, FHEM tries to power on all the players.
    • +
    • resetTTS - Reset TTS output.
    • +
    • volume <template> <n> - Set the volume to <n> if the template is active.
    • +
    • volume <template> +|-<n> - Increase or decrease the volume by the given value if the template is active.
    • +

    @@ -2189,6 +3729,8 @@ sub SB_SERVER_setStates($$) - and running.
  • doalivecheck <true|false>
    Switches the LMS-monitoring on or off.
  • +
  • enablePlugins <plugin1[,pluginX]>
    + Adds the playlists and favorites (if available) of the specified LMS-plugins.
  • httpport <port>
    Normally the http-port is set to 9000. If this ist NOT the case, you have to enter here the new port-number. You can check the port-number of the LMS within its setup under Setup – Network – Web Server Port Number.
  • @@ -2196,6 +3738,8 @@ sub SB_SERVER_setStates($$)
    With this attribute you can define IP-addresses of players which will to be ignored by the server, e.g. "192.168.0.11,192.168.0.37"
  • ignoredMACs <MAC-Address[,MAC-Address]>
    With this attribute you can define MAC-addresses of players which will to be ignored by the server, e.g. "00:11:22:33:44:55,ff:ee:dd:cc:bb:aa"
  • +
  • internalPingProtocol icmp|tcp|udp|syn|stream|none
    + Specifies the protocol for the internal ping, default is tcp.
  • maxcmdstack <quantity>
    By default the stack ist set up to 200. If the connection to the LMS is lost, up to <quantity> commands are buffered. After the link is reconnected, commands, that are not older than five minutes, @@ -2220,11 +3764,11 @@ sub SB_SERVER_setStates($$) Diese Modul ermöglicht es - zusammen mit dem Modul SB_PLAYER - einen Logitech Media Server (LMS) und die angeschlossenen Squeezebox Media Player zu steuern.

    - + Achtung: Die Angabe des Parameters [:cliserverport] ist optional und nur dann erforderlich, wenn die Portnummer im LMS vom Standardwert (TCP Port 9090) abweichend eingetragen wurde.

    - + Optionen
    • <[RCC]>: Hier kann ein FHEM RCC Device angegeben werden mit dem der Server aufgeweckt und eingeschaltet werden kann.
    • @@ -2248,7 +3792,38 @@ sub SB_SERVER_setStates($$)
    • renew - Erneuert die Verbindung zum Server.
    • rescan - Startet einen Scan der Musikbibliothek für alle im Server angegebenen Verzeichnisse.
    • statusRequest - Aktualisiert die Readings von Server und konfigurierten Playern.
    • -
    +
  • save [<name>] - Speichert den Zustand aller Player unter dem Namen <name> ab.
  • +
  • recall [<name>] [options] - Ruft den Zustand aller Player auf.
    Optionen:
  • +
      +
    • del - Löscht nach dem Restore den gespeicherten Status
    • +
    • delonly - Löscht den gespeicherten Status ohne vorherigem Restore
    • +
    + +

    + Der Befehl syncGroup dient zur Verwaltung von Gruppenvorlagen für die Synchronisierung der Player. + Darüber können Gruppen von Playern angelegt werden die sich bei Bedarf aktivieren lassen. Die Vorlagen + werden bei SAVE und SHUTDOWN von FHEM abgespeichert und beim Start von FHEM geladen. + Folgende Unterbefehle sind definiert:

    +
      +
    • addp <playerName[,playerName...]> <Vorlage> - Fügt die angegebenen Player zur Vorlage hinzu, wenn die + Vorlage noch nicht existiert wird sie angelegt und der erste Player wird Master.
    • +
    • removep <playerName[,playerName...]> <Vorlage> - Entfernt die angegebenen Player aus der Vorlage, falls + einer Master war wird der 1. verbleibende Player der Vorlage zum neuen Master.
    • +
    • masterp <playerName> <Vorlage> - Legt den angegebenen Player als Master fest.
    • +
    • load [poweron] <Vorlage> - Aktiviert die angegebene Vorlage, die betroffenen Player werden entsynchronisiert + und zu einer neuen Gruppe zusammengefügt. Wenn zusätzlich poweron angegeben wird, werden die Player + eingeschaltet.
    • +
    • delete <Vorlage> - Löscht die angegebene Vorlage.
    • +
    • deleteall - Löscht alle Gruppenvorlagen.
    • +
    • talk <Vorlage> <Text> - Speichert den Zustand aller Player ab, aktiviert die angegebene Vorlage und spielt + den Text über den beim Player konfigurierten TTS-Dienst ab. Nach Ende der Durchsage wird der vorherige Zustand der Player wieder + hergestellt. Wenn zusätzlich poweron angegeben wird, wird versucht die Player einzuschalten.
    • +
    • resetTTS - TTS zurücksetzen, kann nötig sein wenn die Ausgabe hängt.
    • +
    • volume <Vorlage> <n> - Stellt die Lautstärke auf einen Wert <n> ein. Dabei muss <n> + eine Zahl zwischen 0 und 100 sein. Der Befehl wird nur ausgeführt wenn die Vorlage aktiv ist.
    • +
    • volume <Vorlage> +|-<n> - Erhöht oder vermindert die Lautstärke um den Wert, der durch +|-<n> + vorgegeben wird. Dabei muss <n> eine Zahl zwischen 0 und 100 sein. Der Befehl wird nur ausgeführt wenn die Vorlage aktiv ist.
    • +

    @@ -2260,6 +3835,8 @@ sub SB_SERVER_setStates($$) keine Hänger) - und ob der LMS noch läuft.
  • doalivecheck <true|false>
    Überwachung des LMS ein- oder auschalten.
  • +
  • enablePlugins <plugin1[,pluginX]>
    + Bindet die Wiedergabelisten und Favoriten (soweit vorhanden) von LMS-Plugins (z.B. Spotify) ein.
  • httpport <port>
    Im Normalfall ist der http-Port auf 9000 eingestellt. Sollte dies NICHT der Fall sein muss hier die geänderte Portnummer eingetragen werden. Zur Überprüfung kann im Server unter Einstellungen – Erweitert –Netzwerk @@ -2268,6 +3845,8 @@ sub SB_SERVER_setStates($$)
    Mit diesem Attribut kann die automatische Erkennung dedizierter Geräte durch die Angabe derer IP-Adressen unterdrückt werden, z.B. "192.168.0.11,192.168.0.37"
  • ignoredMACs <MAC-Adresse>[,MAC-Adresse]
    Mit diesem Attribut kann die automatische Erkennung dedizierter Geräte durch die Angabe derer MAC-Adressen unterdrückt werden, z.B. "00:11:22:33:44:55,ff:ee:dd:cc:bb:aa"
  • +
  • internalPingProtocol icmp|tcp|udp|syn|stream|none
    + Legt fest welches Protokoll für den internen Ping verwendet wird. Wenn das Attribut nicht definiert ist, wird tcp verwendet.
  • maxcmdstack <Anzahl>
    Default ist der Stack auf eine Größe von 200 eingestellt. Wenn die Verbindung zum LMS unterbrochen ist, werden bis zu <Anzahl> Befehle zwischengespeichert. Nach dem Verbindungsaufbau werden die Befehle,