SONOS: Neue Features und Fehlerbehebungen

git-svn-id: https://svn.fhem.de/fhem/trunk/fhem@22400 2b470e98-0d58-463d-a4d8-8e2adae1ed80
This commit is contained in:
Reinerlein 2020-07-14 17:54:36 +00:00
parent 834f26b41d
commit 1fc7eaca51
3 changed files with 134 additions and 78 deletions

View File

@ -1,6 +1,6 @@
########################################################################################
#
# SONOS.pm (c) by Reiner Leins, June 2020
# SONOS.pm (c) by Reiner Leins, July 2020
# rleins at lmsoft dot de
#
# $Id$
@ -51,6 +51,14 @@
# Changelog (last 4 entries only, see Wiki for complete changelog)
#
# SVN-History:
# 14.07.2020
# Sonos Arc (S19) zu den Playern mit Optischer Eingangswahl hinzugefügt.
# Der Gruppenname wird jetzt mit Umlauten belassen, und es werden Räume mit den Zonennamen angelegt.
# ControlPoint: Logausgaben für Such-Antworten erweitert.
# Bei der Erkennung, ob ein SubProzess läuft, gab es noch einen Fehler bei der Überprüfung der Serverantwort.
# Bei einem Fehler in der IsAlive-Prozedur wurde u.U. die Sendeverarbeitung nicht wieder freigegeben. Dafür wurden jetzt zusätzliche Logausgaben eingebaut.
# Neuer Setter "PlayT", um zwischen Abspielen und Pausieren umzuschalten.
# Bei der Playerbutton-Leiste wurden Play und Pause auf einen Button reduziert und der Mute-Button wird bzgl. des Zustands farbig eingefärbt (Mute-An: Dunkelrot, Mute-Aus: Standardfarbe)
# 30.06.2020
# Sonos Playbase und Beam (S11 und S14) zu den Playern mit Optischer Eingangswahl hinzugefügt.
# Bei "setCurrentTrackPosition" kann man nun auch Sekunden angeben (auch mit Vorzeichen-Prefix und/oder Prozent-Suffix).
@ -76,11 +84,6 @@
# 15.04.2018
# Streams über Alexa (z.B. Sonos One) werden nun korrekt als Radiostreams dargestellt
# Es werden nun auch Updateinformationen und die interne Softwareversionsnummer gesucht und als Reading gesetzt: "softwareRevisionAvailable", "softwareRevisionInternal" und "softwareRevisionInternalAvailable"
# 24.03.2018
# Einige Log-Ausgaben haben bei undefinierten Default-Übergaben Fehlermeldungen verursacht.
# Bei einigen Positionsabfragen an die Player wurden Sonderfälle (wie NOT_IMPLEMENTED) nicht berücksichtigt.
# Slider-Wertebereich für Bass und Treble auf den Bereich -10..10 korrigiert.
# Es gibt jetzt ein Reading "IsZoneBridge", das 0 oder 1 sein kann. Danach wird jetzt auch entschieden, ob eine Playersteuerung dargestellt wird, oder nicht.
#
########################################################################################
#
@ -1855,8 +1858,8 @@ sub SONOS_StartClientProcessIfNeccessary($) {
# Antwort vom Client weglesen...
my $answer;
$socket->recv($answer, 5000);
SONOS_Log $hash, 5, 'Antwort vom SubProzess: '.$answer;
if ($answer eq 'This is UPnP-Server listening for commands\r\n') {
SONOS_Log $hash->{UDN}, 5, 'Antwort vom SubProzess: '.$answer;
if ($answer eq "This is UPnP-Server listening for commands\r\n") {
$socket->send("Test\r\n", 0);
# Hiermit wird eine etwaig bestehende Thread-Struktur beendet und diese Verbindung selbst geschlossen...
@ -2005,7 +2008,7 @@ sub SONOS_IsSubprocessAliveChecker() {
}
# Wenn die letzte Antwort zu lange her ist, dann den SubProzess neustarten...
if (($lastProcessAnswer != 0) && ($lastProcessAnswer < time() - 5 - (4 * $hash->{INTERVAL}))) {
if (($lastProcessAnswer != 0) && ($lastProcessAnswer < time() - 5 - (400000 * $hash->{INTERVAL}))) {
# Verbindung beenden, damit der SubProzess die Chance hat neu initialisiert zu werden...
SONOS_Log $hash->{UDN}, 2, 'LastProcessAnswer way too old (Lastanswer: '.$lastProcessAnswer.' ~ '.SONOS_GetTimeString($lastProcessAnswer).')... try to restart the process and connection...';
@ -2845,12 +2848,13 @@ sub SONOS_Discover_DoQueue($) {
my $roomName = $SONOS_DevicePropertiesProxy{$udn}->GetZoneAttributes()->getValue('CurrentZoneName');
# Gruppennamen ermitteln
my $groupName = decode('UTF-8', $roomName);
eval {
use utf8;
$groupName =~ s/([äöüÄÖÜß])/SONOS_UmlautConvert($1)/eg; # Hier erstmal Umlaute 'schön' machen, damit dafür nicht '_' verwendet werden...
$groupName =~ s/[^a-zA-Z0-9_ äöüÄÖÜß]//g;
};
$groupName =~ s/[^a-zA-Z0-9]/_/g;
$groupName = encode('UTF-8', SONOS_Trim($groupName));
my $iconPath = decode_entities($1) if ($SONOS_UPnPDevice{$udn}->descriptionDocument() =~ m/<iconList>.*?<icon>.*?<id>0<\/id>.*?<url>(.*?)<\/url>.*?<\/icon>.*?<\/iconList>/sim);
$iconPath =~ s/.*\/(.*)/icoSONOSPLAYER_$1/i;
@ -4647,7 +4651,7 @@ sub SONOS_MakeCoverURL($$) {
$resURL = SONOS_getSpotifyCoverURL($1);
} elsif ($resURL =~ m/^x-sonosapi-stream:(.+?)\?/i) {
my $resURLtemp = SONOS_GetRadioMediaMetadata($udn, $1);
SONOS_Log undef, 5, 'Stream-Cover-Ermittlung 1 erfolgreich: '.$resURLtemp;
SONOS_Log undef, 5, 'Stream-Cover-Ermittlung 1 erfolgreich: '.((defined($resURLtemp)) ? $resURLtemp : 'undef');
eval {
my $result = SONOS_ReadURL($resURLtemp);
@ -4658,7 +4662,7 @@ sub SONOS_MakeCoverURL($$) {
if ($@) {
SONOS_Log $udn, 2, 'Beim Laden der Ressourcen vom Player ist ein Fehler aufgetreten: '.$@;
}
SONOS_Log undef, 5, 'Stream-Cover-Ermittlung 2 erfolgreich: '.$resURLtemp;
SONOS_Log undef, 5, 'Stream-Cover-Ermittlung 2 erfolgreich: '.((defined($resURLtemp)) ? $resURLtemp : 'undef');
$resURL = $resURLtemp;
} elsif (($resURL =~ m/x-rincon-playlist:.*?#(.*)/i) || ($resURL =~ m/savedqueues.rsq(#\d+)/i)) {
@ -5844,6 +5848,7 @@ sub SONOS_Discover_Callback($$$) {
# Variablen initialisieren
my $roomName = '';
my $saveRoomName = '';
my $groupName = '';
my $modelNumber = '';
my $displayVersion = '';
my $internalVersion = '';
@ -5861,7 +5866,14 @@ sub SONOS_Discover_Callback($$$) {
$saveRoomName =~ s/[^a-zA-Z0-9_ ]//g;
$saveRoomName = SONOS_Trim($saveRoomName);
$saveRoomName =~ s/ /_/g;
my $groupName = $saveRoomName;
# Gruppennamen ermitteln
$groupName = decode('UTF-8', $roomName);
eval {
use utf8;
$groupName =~ s/[^a-zA-Z0-9_ äöüÄÖÜß]//g;
};
$groupName = encode('UTF-8', SONOS_Trim($groupName));
# Modelnumber ermitteln
$modelNumber = decode_entities($1) if ($descriptionDocument =~ m/<modelNumber>(.*?)<\/modelNumber>/im);
@ -6029,6 +6041,10 @@ sub SONOS_Discover_Callback($$$) {
# ...and his attributes
for my $elem (SONOS_GetDefineStringlist('SONOSPLAYER_Attributes', $SONOS_Client_Data{SonosDeviceName}, undef, $master, $name, $roomName, $aliasSuffix, $groupName, $iconPath, $isZoneBridge)) {
SONOS_Client_Notifier($elem);
if ($elem =~ m/CommandAttr:.+? (.+?) (.+)/i) {
SONOS_Client_Data_Refresh('', $udn, $1, $2);
}
}
# Setting Internal-Data
@ -6347,7 +6363,7 @@ sub SONOS_GetDefineStringlist($$$$$$$$$$) {
if (lc($devicetype) eq 'sonosplayer') {
push(@defs, 'CommandDefine:'.$name.' SONOSPLAYER '.$udn);
} elsif (lc($devicetype) eq 'sonosplayer_attributes') {
push(@defs, 'CommandAttr:'.$name.' room '.$sonosDeviceName);
push(@defs, 'CommandAttr:'.$name.' room '.$sonosDeviceName.','.$groupName);
push(@defs, 'CommandAttr:'.$name.' alias '.$roomName.$aliasSuffix);
push(@defs, 'CommandAttr:'.$name.' group '.$groupName);
push(@defs, 'CommandAttr:'.$name.' icon '.$iconPath);
@ -6361,16 +6377,15 @@ sub SONOS_GetDefineStringlist($$$$$$$$$$) {
push(@defs, 'CommandAttr:'.$name.' generateInfoSummarize2 <TransportState/><InfoSummarize1 prefix=" => "/>');
push(@defs, 'CommandAttr:'.$name.' generateInfoSummarize3 <Volume prefix="Lautstärke: "/><Mute instead=" ~ Kein Ton" ifempty=" ~ Ton An" emptyval="0"/> ~ Balance: <Balance ifempty="Mitte" emptyval="0"/><HeadphoneConnected instead=" ~ Kopfhörer aktiv" ifempty=" ~ Kein Kopfhörer" emptyval="0"/>');
push(@defs, 'CommandAttr:'.$name.' generateVolumeSlider 1');
push(@defs, 'CommandAttr:'.$name.' generateVolumeEvent 1');
push(@defs, 'CommandAttr:'.$name.' getAlarms 1');
push(@defs, 'CommandAttr:'.$name.' minVolume 0');
#push(@defs, 'CommandAttr:'.$name.' stateVariable Presence');
push(@defs, 'CommandAttr:'.$name.' stateFormat presence ~ currentTrackPositionSimulatedPercent% (currentTrackPositionSimulated / currentTrackDuration)');
push(@defs, 'CommandAttr:'.$name.' getTitleInfoFromMaster 1');
push(@defs, 'CommandAttr:'.$name.' simulateCurrentTrackPosition 1');
push(@defs, 'CommandAttr:'.$name.' webCmd Volume');
push(@defs, 'CommandAttr:'.$name.' verbose '.SONOS_Client_Data_Retreive('undef', 'attr', 'verbose', 0)) if (SONOS_Client_Data_Retreive('undef', 'attr', 'verbose', undef));
#push(@defs, 'CommandAttr:'.$name.' webCmd Play:Pause:Previous:Next:VolumeD:VolumeU:MuteT');
} else {
push(@defs, 'CommandAttr:'.$name.' stateFormat presence');
}
@ -6497,56 +6512,68 @@ sub SONOS_AnalyzeZoneGroupTopology($$) {
sub SONOS_IsAlive($) {
my ($udn) = @_;
SONOS_Log $udn, 4, "IsAlive-Event UDN=$udn";
SONOS_Log $udn, 4, "╓── IsAlive-Event-Anfang UDN=$udn";
my $result = 1;
my $doDeleteProxyObjects = 0;
$SONOS_Client_SendQueue_Suspend = 1;
my $location = SONOS_Client_Data_Retreive($udn, 'reading', 'location', '');
if ($location) {
SONOS_Log $udn, 5, "Location: $location";
my ($host, $port) = ($1, $2) if ($location =~ m/http:\/\/(.*?):(.*?)\//);
my $pingType = $SONOS_Client_Data{pingType};
return 1 if (lc($pingType) eq 'none');
if (SONOS_isInList($pingType, @SONOS_PINGTYPELIST)) {
SONOS_Log $udn, 5, "PingType: $pingType";
} else {
SONOS_Log $udn, 1, "Wrong pingType given for '$udn': '$pingType'. Choose one of '".join(', ', @SONOS_PINGTYPELIST)."'";
$pingType = $SONOS_DEFAULTPINGTYPE;
}
my $ping = Net::Ping->new($pingType, 1);
$ping->source_verify(0); # Es ist egal, von welcher Schnittstelle des Zielsystems die Antwort kommt
$ping->port_number($port) if ((lc($pingType) eq 'tcp') || (lc($pingType) eq 'syn')); # Wenn TCP oder SYN verwendet werden soll, dann auf HTTP-Port des Location-Documents (Standard: 1400) des Player verbinden
$ping->ping($host, $SONOS_DEFAULTCOVERLOADTIMEOUT) if (lc($pingType) eq 'syn');
if (((lc($pingType) eq 'syn') && $ping->ack($host)) ||
((lc($pingType) ne 'syn') && $ping->ping($host, $SONOS_DEFAULTCOVERLOADTIMEOUT))) {
# Alive
SONOS_Log $udn, 4, "$host is alive";
$result = 1;
eval {
my $location = SONOS_Client_Data_Retreive($udn, 'reading', 'location', '');
if ($location) {
SONOS_Log $udn, 5, "║ Location: $location";
my ($host, $port) = ($1, $2) if ($location =~ m/http:\/\/(.*?):(.*?)\//);
# IsAlive-Negativ-Counter zurücksetzen
$SONOS_Thread_IsAlive_Counter{$host} = 0;
} else {
# Not Alive
$SONOS_Thread_IsAlive_Counter{$host}++;
if ($SONOS_Thread_IsAlive_Counter{$host} > $SONOS_Thread_IsAlive_Counter_MaxMerci) {
SONOS_Log $udn, 3, "$host is REALLY NOT alive (out of merci maxlevel '".$SONOS_Thread_IsAlive_Counter_MaxMerci.'\')';
$result = 0;
SONOS_Client_Data_Refresh('ReadingsSingleUpdateIfChanged', $udn, 'presence', 'disappeared');
SONOS_Client_Data_Refresh('ReadingsSingleUpdateIfChanged', $udn, 'state', 'disappeared');
SONOS_Client_Data_Refresh('ReadingsSingleUpdateIfChanged', $udn, 'transportState', 'STOPPED');
$doDeleteProxyObjects = 1;
my $pingType = $SONOS_Client_Data{pingType};
return 1 if (lc($pingType) eq 'none');
if (SONOS_isInList($pingType, @SONOS_PINGTYPELIST)) {
SONOS_Log $udn, 5, "║ PingType: $pingType";
} else {
SONOS_Log $udn, 3, "$host is NOT alive, but in merci level ".$SONOS_Thread_IsAlive_Counter{$host}.'/'.$SONOS_Thread_IsAlive_Counter_MaxMerci.'.';
SONOS_Log $udn, 1, "║ Wrong pingType given for '$udn': '$pingType'. Choose one of '".join(', ', @SONOS_PINGTYPELIST)."'";
$pingType = $SONOS_DEFAULTPINGTYPE;
}
my $ping = Net::Ping->new($pingType, $SONOS_DEFAULTCOVERLOADTIMEOUT);
SONOS_Log $udn, 5, '║ IsAlive: After Ping->new';
$ping->source_verify(0); # Es ist egal, von welcher Schnittstelle des Zielsystems die Antwort kommt
SONOS_Log $udn, 5, '║ IsAlive: After Ping->source_verify';
$ping->service_check(1);
SONOS_Log $udn, 5, '║ IsAlive: After Ping->service_check';
$ping->port_number($port) if ((lc($pingType) eq 'tcp') || (lc($pingType) eq 'syn')); # Wenn TCP oder SYN verwendet werden soll, dann auf HTTP-Port des Location-Documents (Standard: 1400) des Player verbinden
SONOS_Log $udn, 5, '║ IsAlive: After Ping->port_number';
$ping->ping($host, $SONOS_DEFAULTCOVERLOADTIMEOUT) if (lc($pingType) eq 'syn');
SONOS_Log $udn, 5, '║ IsAlive: After Ping->ping(1) for syn';
if (((lc($pingType) eq 'syn') && $ping->ack($host)) ||
((lc($pingType) ne 'syn') && $ping->ping($host, $SONOS_DEFAULTCOVERLOADTIMEOUT))) {
# Alive
SONOS_Log $udn, 4, "║ $host is alive";
$result = 1;
# IsAlive-Negativ-Counter zurücksetzen
$SONOS_Thread_IsAlive_Counter{$host} = 0;
} else {
# Not Alive
$SONOS_Thread_IsAlive_Counter{$host}++;
if ($SONOS_Thread_IsAlive_Counter{$host} > $SONOS_Thread_IsAlive_Counter_MaxMerci) {
SONOS_Log $udn, 3, "║ $host is REALLY NOT alive (out of merci maxlevel '$SONOS_Thread_IsAlive_Counter_MaxMerci')";
$result = 0;
SONOS_Client_Data_Refresh('ReadingsSingleUpdateIfChanged', $udn, 'presence', 'disappeared');
SONOS_Client_Data_Refresh('ReadingsSingleUpdateIfChanged', $udn, 'state', 'disappeared');
SONOS_Client_Data_Refresh('ReadingsSingleUpdateIfChanged', $udn, 'transportState', 'STOPPED');
$doDeleteProxyObjects = 1;
} else {
SONOS_Log $udn, 3, "║ $host is NOT alive, but in merci level ".$SONOS_Thread_IsAlive_Counter{$host}.'/'.$SONOS_Thread_IsAlive_Counter_MaxMerci.'.';
}
}
$ping->close();
SONOS_Log $udn, 5, '║ IsAlive: After Ping->close';
}
$ping->close();
};
if ($@) {
SONOS_Log undef, 3, '║ Bei der Player-IsAlive-Prüfung ist ein Fehler aufgetreten: '.$@;
}
$SONOS_Client_SendQueue_Suspend = 0;
@ -6562,6 +6589,8 @@ sub SONOS_IsAlive($) {
$SONOS_Client_ReceiveQueue->enqueue(\%data);
}
SONOS_Log $udn, 4, "╙── IsAlive-Event-Ende UDN=$udn";
return $result;
}
@ -9796,7 +9825,7 @@ sub SONOS_Shutdown ($$) {
} else {
DevIo_SimpleWrite($hash, "disconnect\n", 2);
}
$hash->{TCPDev}->sockopt(SO_LINGER, pack("ii", 1, 0));
$hash->{TCPDev}->sockopt(SO_LINGER, pack("ii", 1, 0)) if (defined($hash->{TCPDev}));
DevIo_CloseDev($hash);
select(undef, undef, undef, 2);
@ -9958,9 +9987,13 @@ sub SONOS_Log($$$) {
if ($SONOS_Client_LogfileName eq '-') {
print "$tim $level: SONOS".threads->tid().": $text\n";
} else {
open(my $fh, '>>', $SONOS_Client_LogfileName);
print $fh "$tim $level: SONOS".threads->tid().": $text\n";
close $fh;
eval {
use utf8;
open(my $fh, '>>', $SONOS_Client_LogfileName);
print $fh "$tim $level: SONOS".threads->tid().": $text\n";
close $fh;
}
}
}
} else {
@ -10217,10 +10250,10 @@ sub SONOS_Client_Data_Retreive($$$$;$) {
# Anfrage zulässig, also ausliefern...
if (defined($SONOS_Client_Data{Buffer}->{$udnBuffer}) && defined($SONOS_Client_Data{Buffer}->{$udnBuffer}->{$name})) {
SONOS_Log undef, 4, "SONOS_Client_Data_Retreive($udnBuffer, $reading, $name, ".((defined($default)) ? $default : 'undef').") -> ".$SONOS_Client_Data{Buffer}->{$udnBuffer}->{$name} if (!$nologging);
SONOS_Log undef, 5, "SONOS_Client_Data_Retreive($udnBuffer, $reading, $name, ".((defined($default)) ? $default : 'undef').") -> ".$SONOS_Client_Data{Buffer}->{$udnBuffer}->{$name} if (!$nologging);
return $SONOS_Client_Data{Buffer}->{$udnBuffer}->{$name};
} else {
SONOS_Log undef, 4, "SONOS_Client_Data_Retreive($udnBuffer, $reading, $name, ".((defined($default)) ? $default : 'undef').") -> DEFAULT" if (!$nologging);
SONOS_Log undef, 5, "SONOS_Client_Data_Retreive($udnBuffer, $reading, $name, ".((defined($default)) ? $default : 'undef').") -> DEFAULT" if (!$nologging);
return $default;
}
}

View File

@ -1,6 +1,6 @@
########################################################################################
#
# SONOSPLAYER.pm (c) by Reiner Leins, March 2018
# SONOSPLAYER.pm (c) by Reiner Leins, July 2020
# rleins at lmsoft dot de
#
# $Id$
@ -69,7 +69,7 @@ use vars qw{%modules %defs};
# Variable Definitions
########################################################################################
my @possibleRoomIcons = qw(bathroom library office foyer dining tvroom hallway garage garden guestroom den bedroom kitchen portable media family pool masterbedroom playroom patio living);
my @SONOSPLAYER_opticalInputDeviceTypes = qw(S9 S11 S14);
my @SONOSPLAYER_opticalInputDeviceTypes = qw(S9 S11 S14 S19);
my %gets = (
'CurrentTrackPosition' => '',
@ -91,6 +91,7 @@ my %gets = (
my %sets = (
'Play' => '',
'PlayT' => '',
'Pause' => '',
'Stop' => '',
'Next' => '',
@ -267,24 +268,24 @@ sub SONOSPLAYER_Detail($$$;$) {
$html .= SONOS_getCoverTitleRG($d);
$html .= '</div>';
# Close Inform-Div
$html .= '</div>';
# Control-Buttons
if (!AttrVal($d, 'suppressControlButtons', 0) && ($withRC)) {
$html .= '<div class="rc_body" style="border: 1px solid gray; border-radius: 10px; padding: 5px;">';
$html .= '<table style="text-align: center;"><tr>';
$html .= '<td><a onclick="FW_cmd(\'?XHR=1&amp;cmd.dummy=set '.$d.' Previous\')">'.FW_makeImage('rc_PREVIOUS.svg', 'Previous', 'rc-button').'</a></td>
<td><a style="padding-left: 10px;" onclick="FW_cmd(\'?XHR=1&amp;cmd.dummy=set '.$d.' Play\')">'.FW_makeImage('rc_PLAY.svg', 'Play', 'rc-button').'</a></td>
<td><a onclick="FW_cmd(\'?XHR=1&amp;cmd.dummy=set '.$d.' Pause\')">'.FW_makeImage('rc_PAUSE.svg', 'Pause', 'rc-button').'</a></td>
<td><a style="padding-left: 10px;" onclick="FW_cmd(\'?XHR=1&amp;cmd.dummy=set '.$d.' Next\')">'.FW_makeImage('rc_NEXT.svg', 'Next', 'rc-button').'</a></td>
$html .= '<td><a onclick="FW_cmd(\'?XHR=1&amp;cmd.dummy=set '.$d.' Previous\')">'.FW_makeImage('rc_PREVIOUS.svg', 'Previous', 'rc-button').'</a></td>';
$html .= '<td><a style="padding-left: 10px;" onclick="FW_cmd(\'?XHR=1&amp;cmd.dummy=set '.$d.' Play\')">'.FW_makeImage('rc_PLAY.svg', 'Play', 'rc-button').'</a></td>' if (ReadingsVal($d, 'transportState', 'PLAYING') ne 'PLAYING');
$html .= '<td><a style="padding-left: 10px;" onclick="FW_cmd(\'?XHR=1&amp;cmd.dummy=set '.$d.' Pause\')">'.FW_makeImage('rc_PAUSE.svg', 'Pause', 'rc-button').'</a></td>' if (ReadingsVal($d, 'transportState', 'PLAYING') eq 'PLAYING');
$html .= '<td><a style="padding-left: 10px;" onclick="FW_cmd(\'?XHR=1&amp;cmd.dummy=set '.$d.' Next\')">'.FW_makeImage('rc_NEXT.svg', 'Next', 'rc-button').'</a></td>
<td><a style="padding-left: 20px;" onclick="FW_cmd(\'?XHR=1&amp;cmd.dummy=set '.$d.' VolumeD\')">'.FW_makeImage('rc_VOLDOWN.svg', 'VolDown', 'rc-button').'</a></td>
<td><a onclick="FW_cmd(\'?XHR=1&amp;cmd.dummy=set '.$d.' MuteT\')">'.FW_makeImage('rc_MUTE.svg', 'Mute', 'rc-button').'</a></td>
<td><a onclick="FW_cmd(\'?XHR=1&amp;cmd.dummy=set '.$d.' MuteT\')">'.FW_makeImage('rc_MUTE'.((ReadingsVal($d, 'Mute', '0') ne '0') ? '@8B0000' : ''), 'Mute', 'rc-button').'</a></td>
<td><a onclick="FW_cmd(\'?XHR=1&amp;cmd.dummy=set '.$d.' VolumeU\')">'.FW_makeImage('rc_VOLUP.svg', 'VolUp', 'rc-button').'</a></td>';
$html .= '</tr></table>';
$html .= '</div>';
}
# Close Inform-Div
$html .= '</div>';
# Close
$html .= '</html>';
@ -361,7 +362,7 @@ sub SONOSPLAYER_Notify($$) {
next if(!defined($event));
# Wenn ein CoverTitle-Trigger gesendet werden muss...
if ($event =~ m/^(currentAlbumArtURL|currentTrackProviderIconRoundURL|currentTrackDuration|currentTrack|numberOfTracks|currentTitle|currentArtist|currentAlbum|nextAlbumArtURL|nextTrackProviderIconRoundURL|nextTitle|nextArtist|nextAlbum|currentSender|currentSenderInfo|currentSenderCurrent|transportState):/is) {
if ($event =~ m/^(currentAlbumArtURL|currentTrackProviderIconRoundURL|currentTrackDuration|currentTrack|numberOfTracks|currentTitle|currentArtist|currentAlbum|nextAlbumArtURL|nextTrackProviderIconRoundURL|nextTitle|nextArtist|nextAlbum|currentSender|currentSenderInfo|currentSenderCurrent|transportState|Mute):/is) {
SONOSPLAYER_Log $hash->{NAME}, 5, 'Notify-CoverTitle: '.$event;
$triggerCoverTitle = 1;
}
@ -398,7 +399,7 @@ sub SONOSPLAYER_Notify($$) {
sub SONOSPLAYER_TriggerCoverTitleLater($) {
my ($hash) = @_;
my $html = SONOSPLAYER_Detail('', $hash->{NAME}, '', 0);
my $html = SONOSPLAYER_Detail('', $hash->{NAME}, '', 1);
DoTrigger($hash->{NAME}, 'display_covertitle: '.$html, 1);
return undef;
@ -785,6 +786,15 @@ sub SONOSPLAYER_Set($@) {
$udn = $hash->{UDN};
SONOS_DoWork($udn, 'play');
} elsif (lc($key) eq 'playt') {
$hash = SONOSPLAYER_GetRealTargetPlayerHash($hash);
$udn = $hash->{UDN};
if (ReadingsVal($hash->{NAME}, 'transportState', '') eq 'PLAYING') {
SONOS_DoWork($udn, 'pause');
} else {
SONOS_DoWork($udn, 'play');
}
} elsif (lc($key) eq 'stop') {
$hash = SONOSPLAYER_GetRealTargetPlayerHash($hash);
$udn = $hash->{UDN};
@ -1392,6 +1402,9 @@ sub SONOSPLAYER_Log($$$) {
<li><a name="SONOSPLAYERPlay">
<b><code>Play</code></b></a>
<br /> Starts playing</li>
<li><a name="SONOSPLAYERPlayT">
<b><code>PlayT</code></b></a>
<br /> Starts playing, if player is currently stopped, pauses playing otherwise</li>
<li><a name="SONOSPLAYERPlayURI">
<b><code>PlayURI &lt;songURI&gt; [Volume]</code></b></a>
<br />Plays the given MP3-File with the optional given volume.</li>
@ -1776,6 +1789,9 @@ Here an event is defined, where in time of 2 seconds the Mute-Button has to be p
<li><a name="SONOSPLAYERPlay">
<b><code>Play</code></b></a>
<br /> Startet die Wiedergabe</li>
<li><a name="SONOSPLAYERPlayT">
<b><code>PlayT</code></b></a>
<br /> Startet die Wiedergabe, wenn gerade nichts abgespielt wird und pausiert sonst.</li>
<li><a name="SONOSPLAYERPlayURI">
<b><code>PlayURI &lt;songURI&gt; [Volume]</code></b></a>
<br /> Spielt die angegebene MP3-Datei ab. Dabei kann eine Lautstärke optional mit angegeben werden.</li>

View File

@ -35,6 +35,7 @@ use IO::Select;
use HTTP::Daemon;
use HTTP::Headers;
use LWP::UserAgent;
use Time::HiRes qw(usleep gettimeofday);
use UPnP::Common;
use vars qw($VERSION @ISA);
@ -457,13 +458,19 @@ sub _receiveSearchResponse {
# Bad header
return;
}
my ($seconds, $microseconds) = gettimeofday();
my @t = localtime($seconds);
my $tim = sprintf("%04d.%02d.%02d %02d:%02d:%02d.%03d", $t[5]+1900,$t[4]+1,$t[3], $t[2],$t[1],$t[0], $microseconds / 1000);
print $tim.' 5: ControlPoint: Receive Search-Response: "'.$buf.'"'."\n" if ($LogLevel >= 5);
# Basic check to see if the response is actually for a search
my $found = 0;
foreach my $searchkey (keys %{$self->{_activeSearches}}) {
my $search = $self->{_activeSearches}->{$searchkey};
if ($search->{_type} && $buf =~ $search->{_type}) {
print 'xxxx.xx.xx xx:xx:xx 5: ControlPoint: Accepted Search-Response: "'.$buf.'"'."\n" if ($LogLevel >= 5);
print "$tim 5: ControlPoint: Accept Search-Response...\n" if ($LogLevel >= 5);
$found = 1;
last;
}
@ -481,7 +488,7 @@ sub _receiveSearchResponse {
}
if (! $found) {
print 'xxxx.xx.xx xx:xx:xx 5: ControlPoint: Unknown Search-Response: "'.$buf.'"'."\n" if ($LogLevel >= 5);
print "$tim 5: ControlPoint: Unknown Search-Response...\n" if ($LogLevel >= 5);
return;
}