PHP Sonos (Klasse zum Ansteuern einzelner Player)

Hi Martin,
ich kann mir das Verhalten aktuell nicht erklären. Ich schaue mal ob ich am Wochenende zum Nachstellen komme.
Ich arbeite allerdings aktuell auch mit einer älteren IP-Symcon Version, vielleicht ist mir das Verhalten deshalb nicht aufgefallen.
Viele Grüße, Benjamin

Moin zusammen,
ich habe mal zwei Fragen zum Einbinden von Sonos.
Zunächst: Ich verwende IPS im Augenblick nicht, um Sonos zu steuern, sondern nur, um den Status meiner Zone Player abzufragen und anzuzeigen.

Ich habe verstanden, dass die Cover URL bei mp3, die man aus Sonos auslesen kann, auf die jeweilige mp3 Datei verweist. Die kann man dann über die hier geposteten Skripte einlesen und als jpg im Media Ordner abspeichern. Das klappt bei mir auch.
FRAGE: Nun spielt ein Radiosender auf einem Zone Player- da wird einem in der Sonos App ja als Cover das Senderlogo angezeigt. Wie kann ich denn auf dieses Bild in IPS zugreifen? Die Cover URL ist dann leer :frowning:

Wenn ich mit dem in diesem Post veröffentlichten Skript von Benjamin über die IP Adresse eines Zone Players Cover, Title, Album etc. des aktuell gespielten Titels auslese, klappt alles- solange der Zone Player nicht gruppiert ist.
FRAGE: Wie kann ich dieselben Infos von diesem Player auslesen, wenn er gruppiert ist?

Vll. kann das ja jmd. aus dem Ärmel schütteln- wäre klasse!

LG Marcus

Hallo Marcus,

ich kann dir leider nichts direktes zu dem PHP-Modul sagen, aber was grundsätzliches zu Sonos zum Thema:
Die Informationen trägt immer der Gruppenmaster, der ja auch für die Steuerung innerhalb der Gruppe verwendet werden muss.
Dieser enthält wie üblich alle Titel und Coverinformationen. Die anderen Mitglieder enthalten nur die Information, von welchem Player sie den Musik-Strom erhalten.
So werden ja auch Gruppen gebildet: Man sagt einem Player, er soll einen anderen als Quelle verwenden (bzw. beim auflösen wird ihm gesagt, dass er wieder seine eigene Liste oder eine andere Quelle abspielen soll).

Ich weiss jetzt nicht, wie das Modul die URL für die Coverinformation abfragt. Ich habe ein anderes Modul auf Basis von Perl entwickelt, wo ich diese Informationen per UPnP-Callback geliefert bekomme, und dort wird mir unabhängig vom gespielten Stream/File die Cover-URL korrekt übermittelt. Das einzig doofe ist der Typ dieses Covers. Bei Radiostationen ist dieser immer ein GIF (Bei Sootify z.B. immer ein JPG), obwohl man es nicht abfragen kann, bzw. keine ordentliche Information dazu bekommt.

Vielleicht hilft dir das ein wenig…

Grüße
Reiner

Hallo Reiner,
vielen Dank- das macht mich schon ein wenig schlauer.
Das mit den Radiocovers habe ich jetzt etwas unelegant gelöst- aber für mich ausreichend. Ich höre ja nicht hunderte von Sendern- also habe ich von allen relevanten Sendern die Cover als JPG auf meiner Platte gespeichert, lese über IPS den Namen des Radiosenders aus und binde dann das entsprechende Cover ein. Sollte es das Cover nicht geben, wird ein „leeres Cover“ mit zwei Noten verwendet. Mir reicht das so.

LG, Marcus

Hallo,

habe mir mal den sleeptimer in die PHPSonos.inc.php eingebaut.

 /**
 * Sleeptimer in Minuten (0-59)
 *
 */  

	public function Sleeptimer($sleeptimer)
	{
$content='POST /MediaRenderer/AVTransport/Control HTTP/1.1
CONNECTION: close
HOST: '.$this->address.':1400
CONTENT-LENGTH: 331
CONTENT-TYPE: text/xml; charset="utf-8"
SOAPACTION: "urn:schemas-upnp-org:service:AVTransport:1#ConfigureSleepTimer"

<s:Envelope s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"><s:Body><u:ConfigureSleepTimer xmlns:u="urn:schemas-upnp-org:service:AVTransport:1"><InstanceID>0</InstanceID><NewSleepTimerDuration>0:'.$sleeptimer.':0</NewSleepTimerDuration></u:ConfigureSleepTimer></s:Body></s:Envelope>';

		$this->sendPacket($content);
	}

Nutze ich gerne über Bewegungselder vom LCN,

lg Thomas

Hallo,

da ich verschiedene Player in unterschiedlichen Räumen ansteuere, nutze ich viel die Gruppenbildungsfunktion („addmember“). Leider funktioniert die Gruppenauflösungsfunktion („removemember“) nicht, obwohl sie in der PHPSonos.inc.php (soweit ich dies als absoluter Laie sehe) enthalten zu sein scheint.

Darüber hinaus wäre es prima, wenn die Funktion „stopall“ aufgenommen werden könnte, dann könnte man die Widergabe aller Player bei Verlassen des Hauses stoppen und nicht lediglich pausieren.

Leider bekomme ich dies nicht selbst hin :confused: - könntet Ihr die Themen mit aufnehmen oder hat jemand bereits einen Vorschlag zur entsprechenden Erweiterung der PHPSonos.inc.php?

Vielen Dank und lG

Piet

Hi Piet,

ich kann dir jetzt keine fertige Lösung präsentieren, aber Hinweise:
Du musst an dem Player, den du aus der Gruppe raushaben möchtest, den Befehl „BecomeCoordinatorOfStandaloneGroup“ mit dem Parameter „0“ am Service „AVTransport“ ausführen. Damit wird dieser eine eigene Gruppe, und wird sauber aus der anderen Gruppe entfernt…

Zu dem StopAll: Es gibt an den Sonosplayern keine solche direkte Anweisung, bedeutet, dass du selber eine Schleife über alle Player machen musst, und an jedem den Befehl „Stop“ aufrufen musst.

Grüße
Reiner

Hallo Reiner,

vielen Dank für den nett gemeinten Hinweis (removemember) - anfangen kann ich damit leider nicht viel.

Da ich den stopall-Befehl in anderem Zusammenhang (Fhem-Modul) gesehen habe, bin ich davon ausgegangen, dass es eine solche Anweisung allgemein für Sonos-Player gibt, die die Schleife entbehrlich machen würde.

VG

Piet

Hi Piet,

ich bin der Autor des Fhem-Moduls, und dort habe ich auch nur eine Schleife implementiert :slight_smile:

Zum RemoveMember:
Du brauchst doch nur z.B. den bestehenden Aufruf von RemoveMember dahingehend anzupassen:

	public function RemoveMember() {
		$header='POST /MediaRenderer/AVTransport/Control HTTP/1.1
SOAPACTION: "urn:schemas-upnp-org:service:AVTransport:1#BecomeCoordinatorOfStandaloneGroup"
CONTENT-TYPE: text/xml; charset="utf-8"
HOST: '.$this->address.':1400';
$xml='<?xml version="1.0" encoding="utf-8"?>
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><s:Body>
<u:BecomeCoordinatorOfStandaloneGroup xmlns:u="urn:schemas-upnp-org:service:AVTransport:1"><InstanceID>0</InstanceID></u:BecomeCoordinatorOfStandaloneGroup>
</s:Body>
</s:Envelope>';
$content=$header . '
Content-Length: '. strlen($xml) .'

'. $xml;

		return $this->sendPacket($content);
	}
Ungetestet, da ich das System nicht habe...

Diesen Befehl musst du an den Player senden, den du aus der Gruppe entfernen möchtest (der danach also seine eigene Gruppe bilden soll).

Grüße
Reiner

Hallo Reiner,

vielen Dank schon einmal für Deine Hilfe! Das sollte hoffentlich selbst ich hinbekommen, ich probiere es am WE mal aus - einschließlich der stopall-Schleife :wink:

VG

Piet

Hi Reiner,

da habe ich den Mund wohl etwas voll genommen, ich bräuchte doch noch einmal Deine Hilfe:

Was habe ich in Folgendem zu ändern (dies bezieht sich auf den removemember-Befehl aus der PHPSonos.inc.php - ich möchte den Player über den Browser ansprechen können - http://192.168.xxx.xxx/sonos2.php?zonen=bad&action=removemember - und das Folgende ist aus der sonos2.php):

case 'removemember':

			if(isset($_GET['member'])) {
			
				if($debug ==1) {
				echo "zonen: " . $zonen . "<br>";
				echo "member: " . $_GET['member'] . "<br>";
				}
				
				# wenn Member gleich Zone ist 

				
				if ($zonen == $_GET['member']) {
				die("Der Master kann sich selber nicht entfernen.");
				}
				
				
				# nachschauen ob die Zone überhaupt verbunden ist
				$master = zonenmaster($_GET['member']);
				
				if (isset($master)) {
				#	$RemoveMember = $sonos->RemoveMember(getRINCON($sonoszone[$_GET['member']])); # Member Rincon
					$sonos = new PHPSonos($sonoszone[$_GET['member']]); //Slave Sonos ZP IPAddress
					$sonos->SetAVTransportURI("");
				}
				else {
					die("Der Player ist nicht mir dem Master verbunden.");
				}

		  }
		   else  {
			die('Die Zone konnte nicht gefunden werden.');
		  }
		  break;

Vielen Dank im Voraus und lG

Piet

Hi Piet,

das sollte viel einfacher gehen, der neue Befehl ist stabiler, als die alte Implementierung, und kann auch einen Player „entfernen“, der in gar keiner Gruppe ist. Da passiert dann nix mit dem Gruppenstatus…

Also, etwas in der Art:

case 'removemember':
	if(isset($_GET['member'])) {
		$sonos = new PHPSonos($sonoszone[$_GET['member']]);
		$sonos->RemoveMember();
	} else  {
		die('Die Member-Zone konnte nicht gefunden werden.');
	}
break;

Auch hier: ungetestet…

Die URL braucht dann nicht mehr die „Zone“ zu enthalten, aber natürlich den „member“, der entfernt werden soll. In deinem Beispiel fehlt dieser Parameter komischerweise…

Aber nicht vergessen, die neue Version von „RemoveMember“ zu verwenden, oder z.B. als „RemoveMemberNew“ anzulegen und entsprechend zu verwenden :slight_smile:

Grüße
Reiner

Hey Reiner,

das musste ich jetzt noch ausprobieren - und es funktioniert perfekt! Vielen Dank für Deine Hilfe, da sind schon eine Menge Nerven draufgegangen :wink: !!!

Viele Grüße (mir fällt sicher noch etwas ein, was für einen offensichtlichen Profi offenbar kein Problem ist - nein, sorry, Dich mit solchen Laien-Problemen belästigt zu haben…)

Schönes WE

Piet

Hi Piet,

schön, das geht…
Ich helfe immer gerne, nur ist dies hier nicht meine „Kernkompetenz“, weswegen ich mich hier immer etwas zurückhalte :slight_smile:

Grüße
Reiner

Hi zusammen,

ich versuche mich gerade darin Ansagen über Sonos laufen zu lassen. Leider habe ich immer wieder das Problem das die Ansagen entweder stark verzögert ( Netzwerkverbindung i.o Latenz 1 ms ) oder aber auch doppelt abgespielt werden.

Das ganze mache ich mit diesem Script :


include('C:\IP-Symcon\scripts\IPSLibrary\app\modules\IPSSonos\PHPSonos.inc.php');
function TTS_Play_Sonos($Player_IP,$ansagetext,$ton)
{

$wav = array
(
   "chimes"   => "x-file-cifs://SMARTHOMESERVER/media/Sound/chimes.mp3"
);

// Sonos Player
$sonos                         = new PHPSonos($Player_IP);        //Sonos ZP IPAdresse

//Einlesen und runterrampen der Lautstärke
$volume                         = $sonos->GetVolume();                 // gibt die Lautstärke als Integer zurück

//Runterrampen der Lautstärke
while ($sonos->GetVolume()>=6)
{
    $sonos->SetVolume($sonos->GetVolume() -5);
    IPS_Sleep(500);
}

//Speichern der Aktuellen Informationen
$oldpi = $sonos->GetPositionInfo();
$oldmi = $sonos->GetMediaInfo();
$radio=(strpos($oldmi['CurrentURI'],"x-sonosapi-stream:")>0)===false;
$oldti = $sonos->GetTransportInfo();

//Abspielen der gewählten Ton's
$sonos->SetVolume(50);
$sonos->SetAVTransportURI($wav[$ton]);
$sonos->Play();

//Abfragen ob der Player noch spielt
IPS_Sleep(1000);
while ($sonos->GetTransportInfo()==1)
{
    IPS_Sleep(200); //Alle 200ms wird abgefragt
}

//Generieren der Sprachausgabe
TTS_GenerateFile(51074 /*[- CORE -\Kommuniaktion\Text To Speech]*/, $ansagetext,IPS_GetKernelDir()."media/Sprache1.wav",39);
IPS_Sleep(75);

//Abspielen der Sprachausgabe
$sonos->SetVolume(20);
$sonos->SetAVTransportURI("x-file-cifs://SMARTHOMESERVER/media/Sprache1.wav");
$sonos->Play();


}

Was könnte das sein ? Zerbreche mir schon seit Tagen den Kopf :frowning:

Hallo Flobo,

ich hab die Function in einem Script und rufe dieses aus einem anderen nur noch mit Übergabe der Parameter auf.

Function-Script:

<?
include("PHPSonos.inc.php");

function TTS_Play_Sonos($Player_IP,$ansagetext,$ton)
{
/*=============================================================
TTS_Play_Sonos - Martin Heinzel - 09.07.2013
Version: 0.1

Beschreibung:
Diese Funktion ersetzt die Sprachausgabe von "Ferengie-Master"
für die Sprachausgabe auf Soundkarten. Zur Sprachausgabe wird
ein Sonosplayer genutzt.

Parameter:
$Player_IP           ->          Die IP-Adresse des Sonosplayers
$ansagetext          ->          Der Ansagetext der ausgegeben werden soll
$ton                 ->          Der Ton/Melodie die vor dem Ansagetext abgespielt werden soll


Änderungen
----------

tt.mm.jjjj von Version x.x -> y.y

Beschreibung:

================================================================*/

// Variablen Deklaration --------------------------------------

// Variablen
//--------------
$wav = array
(
   "alarm01"        => "x-file-cifs://laptop01/media/Sounds/Alarm_01.mp3",
   "gemecker"        => "x-file-cifs://laptop01/media/Sounds/gemecker.mp3",
   "sheep"            => "x-file-cifs://laptop01/media/Sounds/sheep.mp3",
   "donkey"            => "x-file-cifs://laptop01/media/Sounds/donkey.mp3"
);

//-------------------------------------------------------------


//--------------------------- Main ----------------------------
// Sonos Player
$sonos                         = new PHPSonos($Player_IP);        //Sonos ZP IPAdresse

//Einlesen und runterrampen der Lautstärke
$volume                         = $sonos->GetVolume();                 // gibt die Lautstärke als Integer zurück

//Runterrampen der Lautstärke
while ($sonos->GetVolume()>=6)
{
    $sonos->SetVolume($sonos->GetVolume() -5);
    IPS_Sleep(500);
}

//Speichern der Aktuellen Informationen
$oldpi = $sonos->GetPositionInfo();
$oldmi = $sonos->GetMediaInfo();
$radio=(strpos($oldmi['CurrentURI'],"x-sonosapi-stream:")>0)===false;
$oldti = $sonos->GetTransportInfo();

//Abspielen der gewählten Ton's
$sonos->SetVolume(12);
$sonos->SetAVTransportURI($wav[$ton]);
$sonos->Play();

//Abfragen ob der Player noch spielt
IPS_Sleep(1000);
while ($sonos->GetTransportInfo()==1)
{
    IPS_Sleep(200); //Alle 200ms wird abgefragt
}

//Generieren der Sprachausgabe
TTS_GenerateFile(29148 /*[Logisch\Alarme\Ausgaben\TTS\TTS]*/ , $ansagetext,IPS_GetKernelDir()."/media/Sprachausgaben/gartenwasser.wav",39);
IPS_Sleep(75);

//Abspielen der Sprachausgabe
$sonos->SetVolume(12);
$sonos->SetAVTransportURI("x-file-cifs://laptop01/media/Sprachausgaben/gartenwasser.wav");
$sonos->Play();


//Abfragen ob der Player noch spielt
IPS_Sleep(1000);
while ($sonos->GetTransportInfo()==1)
{
    IPS_Sleep(200); //Alle 200ms wird abgefragt
}

//Player wieder Starten
if ($radio)
{
    $sonos->SetRadio($oldmi['CurrentURI']);
}
else
{
    $sonos->SetAVTransportURI($oldmi['CurrentURI'],$oldmi['CurrentURIMetaData']);
}

try
{
    // Seek TRack_Nr
   $sonos->Seek("TRACK_NR",$oldpi['Track']);
   // Seek REl_time
   $sonos->Seek("REL_TIME",$oldpi['RelTime']);
}
catch (Exception $e)
{

}
if ($oldti==1) $sonos->Play();
$sonos->SetVolume($volume);

//------------------------- Main ENDE --------------------------
}
?>


Aufrufendes Script:

<?

include('15409.ips.php'); // function ansage auf sonos
$Player_IP = $sonosKeller;
$ansagetext = 'Das Gartenwasser ist noch offen';
$ton = 'gemecker';

//function TTS_Play_Sonos($Player_IP,$ansagetext,$ton)
/*=============================================================
TTS_Play_Sonos - Martin Heinzel - 09.07.2013
Version: 0.1

Beschreibung:
Diese Funktion ersetzt die Sprachausgabe von "Ferengie-Master"
für die Sprachausgabe auf Soundkarten. Zur Sprachausgabe wird
ein Sonosplayer genutzt.

Parameter:
$Player_IP           ->          Die IP-Adresse des Sonosplayers
$ansagetext          ->          Der Ansagetext der ausgegeben werden soll
$ton                 ->          Der Ton/Melodie die vor dem Ansagetext abgespielt werden soll
*/

TTS_Play_Sonos($Player_IP,$ansagetext,$ton)

?>

Hallo Zusammen

Ich möchte gerne den Group-Coordinator eines angesprochenen Sonos herausfinden.

An die RINCON komme ich ja wie folgt recht einfach:

$PositionInfo = $sonos->GetPositionInfo();
$coordinatorRincon = substr(strstr($PositionInfo['TrackURI'],"x-rincon:"),9);

Wie komme ich den von von der RINCON auf die IP-Adresse des Players?

Danke schon mal für euer HIlfe!

Auch keiner ne Idee?

ich hatte mit mal eine function geschrieben, kann dir evtl. helfen

//******************************************************************************
// SONOS_Gruppe_tabelle()
// $tabelle['Büro'][0] = ID
// $tabelle['Büro'][1] = IP
// $tabelle['Büro'][2] = Coord oder Member
// $tabelle['Büro'][3] = Coord-Name
//******************************************************************************

function SONOS_Gruppe_tabelle()
{
	// Gruppen holen
	$zonen	= br_sonos_GetSONOS_Zones();
	$gruppen = br_sonos_GetSONOS_Groups();
	$master	= array_keys($gruppen);

	// eigene Tabelle aufbereiten
	foreach ($master as $index)
	{
		$member	= array_keys($gruppen [$index] ['Member']);
		$coord   = $zonen[($gruppen[$index]['Coordinator'])]['CurrentZoneName'];
		$tabelle[$zonen[$index]['CurrentZoneName']] = array ($index, $zonen[$index]['IP'], 'Coord', $coord);
   	foreach ($member as $index)
		{
			$tabelle[$zonen[$index]['CurrentZoneName']] = array ($index, $zonen[$index]['IP'], 'Member', $coord);
		}
	}
	//print_r ($master);
	//print_r ($member);
	return $tabelle;
}

Gruß, Peter

Moin Leute,
sorry, aber komme schon wieder nicht weiter, finde die Einbindung ist ein bischen komisch beschrieben… Habe einfach die PHPSonos.inc.php Datei in den Ordner Scripts im IPS Verzeichnis kopiert, mehr muss ich doch nicht machen oder? Keine IO Instanzen etc. anlegen? Weil wenn ich nun dieses Script

include("PHPSonos.inc.php");
$sonos = new PHPSonos("192.168.1.43"); //Sonos ZP IPAdresse
$sonos->SetMute(true);

einfüge (IP passt) passiert rein garnichts…

Gruß