Sonos Modul

Hi Thorsten,

Ich bin derzeit noch stiller Mitleser im SonosModul-Thread, da ich derzeit die IPS4.0 noch nicht nutzen kann.
Was ich so mitbekommen habe, müssen die Radiosender in eine Config irgendwo in einer „KernDatei“ geschrieben werden. Wäre es denkbar, wie bei IPSSonos, eine „RadiostationSync“ - Funktion einzubauen, welche die Radiostationen von der Sonos Box synchroniesiert? In etwa so:

		/**
		 * @public
		 *
		 * Liefert ein IPSSonosRoom Objekt für eine Raum Nummer, sind keine Räume vorhanden
		 * liefert die Funktion false.
		 *
		 * @param integer $roomId Nummer des Raumes (1-4).
		 * @return IPSSonos_Room IPSSonos Room Object
		 */
		private function Sync_Radiostations() {

			// Delete old entries		
			$varprofile = IPS_GetVariableProfile('IPSSonos_Radiostations');

			foreach ($varprofile["Associations"] as $Variable => $Line) {
					$value = $Line["Value"];
					IPS_SetVariableProfileAssociation('IPSSonos_Radiostations', $value, null, null, null);
			};

			//Get new Playlists from Sonos Server
			$list_key	= 0;
			$sonos 			= new PHPSonos($this->IPAddr); 
			$radiostations_lists 	= $sonos->Browse("R:0/0","c"); 
			if ($radiostations_lists != null) {
				foreach ($radiostations_lists as $radiostation_key => $ls_radiostation) {
					$Radiostation_Title 	= utf8_decode($ls_radiostation['title']);
					IPS_SetVariableProfileAssociation('IPSSonos_Radiostations', $list_key, $Radiostation_Title, null, null);
					$list_key 		= $list_key + 1;
				}
				$this->LogInf('Radiostations erfolgreich synchronisiert, '.$list_key.' Einträge vorhanden.');
				return true;
			}	
			else {	
				$this->LogErr('Fehler beim Synchronisieren der Sonsos Radiostations!');	
				return false;
			}
		}

Finde das Sonos Modul jetzt schon sehr interessant und freue mich es zu installieren wenn ich auf IPS4.0 umgestiegen bin. Klasse Arbeit! :wink:

Viele Grüße
Roman

Hallo Thorsten,

habe das Sonos Modul im Einsatz, tolle arbeit,

Habe noch eine Bitte oder besser eine Frage, besteht die Möglichkeit im nächsten Update
einen Regler „Balance“ einzubinden auch mit der Auswahl pro Instance wie Bass und Treble.
Bei mir ist ein Connect Amp im Einsatz in zwei Räumen, rechter Kanal und linker Kanal in
jeweils einem anderen Raum.
Wäre schön wenn man das auch mit einem Script trennen bzw einstellen könnte.

Glück Auf D.Voss

Hallo Thorsten,

jetzt versuche ich mit folgendem Skript zu arbeiten:

$Sonos_Dateiordner = "//sonos/";
$Sonos_SMBordner = "//192.168.178.20/public/";
$TTS_Text = GetValue(28070 /*[Sonos\zu sprechender Text]*/);

$SonosTTS_Dateiname = "SonosBY_GoogleTTS.mp3";
        $SonosTTS_DateiPfad = $Sonos_Dateiordner.$SonosTTS_Dateiname;
        $Sonos_SMBPfadUndDateiname = $Sonos_SMBordner.$SonosTTS_Dateiname;
        $TTS_Text_UTF8 = urlencode(utf8_encode($TTS_Text));
        if (strlen($TTS_Text_UTF8) > 100) IPS_LogMessage("Sonos-TTS", "TTS Text darf maximal 100 Zeichen lang sein");
        $GoogleTTSmp3 = @file_get_contents('http://translate.google.de/translate_tts?tl=de&ie=UTF-8&q='.$TTS_Text_UTF8."&client=t");
        if ((strpos($http_response_header[0], "200") !== true)) {
            file_put_contents($SonosTTS_DateiPfad, $GoogleTTSmp3);
        }
        IPS_Sleep(500);
        SNS_PlayFiles(39668 /*[Sonos\Sonos]*/, Array("//192.168.178.20/public/SonosBY_GoogleTTS.mp3"));
        print_r ($http_response_header);

Leider bekomme ich folgende Meldung:

Array
(
[0] => HTTP/1.0 503 Service Unavailable
[1] => Date: Sun, 25 Oct 2015 15:20:46 GMT
[2] => Pragma: no-cache
[3] => Expires: Fri, 01 Jan 1990 00:00:00 GMT
[4] => Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
[5] => Content-Type: text/html
[6] => Server: HTTP server (unknown)
[7] => Content-Length: 1948
[8] => X-XSS-Protection: 1; mode=block
[9] => X-Frame-Options: SAMEORIGIN

Das sieht für mich so aus als wäre der Google Service nicht erreichbar.

Kannst Du mir sagen wie der Befehl für Amazon aussehen müsste?

Gruß

Axel

Hi Axel!

Du solltest weniger durcheinander machen und mein SonosBY nicht mit diesem Module hier von Kugelberg/Paresy verwechseln und vermischen :wink:

Die URL der Google API lautet:

http://translate.google.de/translate_tts?tl=de&ie=UTF-8&q=Text&client=t

Und alle Infos zu Amazon/Ivona findest du hier:
TTS mit Amazon / Ivona statt Google Translate

Grüße,
Chris

Hallo,

ich nicht, aber Titus: TTS mit Amazon / Ivona statt Google Translate

Gruß,
Thorsten

Hi,

geht jetzt.

Ich würde mir dann als nächstes den Sleep Timer vornehmen.

Gruß,
Thorsten

Danke,

bin noch in der Testphase und versuche mich mit 4.0 (Raspi).
Begeistert bin ich von den Modulen, wie jetzt mit dem Balance
Regler.

  1. In Module update durchführen
  2. In die entsprechende Instance wechseln und siehe da der Regler taucht auf.
  3. Jetzt nur noch anticken, bestätigen

und schon taucht der Regler im Webfront auf, das war es - das hier schreiben hat länger gedauert.

Vielen Dank Thorsten :slight_smile: :slight_smile:

Hallo Thorsten,

da ich mit Google nicht weitergekommen bin, habe ich jetzt das Skript von Titus probiert.


///////////////////////////////////////////////////////////////////////////////////////
//   IVONA_TTS             ////////////////////////////////////////////////////////////
//    by Titus 15.10.2015  ////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////

class IVONA_TTS
{
    static  $utc_tz;
    const   ACCESS_KEY = 'AAA'; // HIER DEINEN ACCESS KEY EINTRAGEN!!
    const   SECRET_KEY = 'BBB'; // HIER DEINEN SECRET KEY EINTRAGEN!!
    
    function save_mp3($text, $filename, $language="de-DE", $voice="Marlene", $rate="medium", $volume="loud") {
         $payload['Input'] = array();
            $payload['Input']['Data'] = utf8_encode($text);//{"Input":{"Data":"Hello world"}}'
            $payload['Parameters']['Rate'] = $rate;
            $payload['Parameters']['Volume'] = $volume;
            $payload['Voice']['Name'] = $voice;
            $payload['Voice']['Language'] = $language;
            
            $payload = json_encode($payload); 
            $mp3 = $this->get_mp3($payload);
            file_put_contents($filename, $mp3);
     }

    function get_mp3( $payload )
    {
        if( !self::$utc_tz ) {
            self::$utc_tz = new \DateTimeZone( 'UTC' );
          }
        $datestamp  = new \DateTime( "now", self::$utc_tz );
        $longdate   = $datestamp->format( "Ymd\\THis\\Z");
        $shortdate  = $datestamp->format( "Ymd" );
        // establish the signing key
        {
            $ksecret    = 'AWS4' . self::SECRET_KEY;
            $kdate      = hash_hmac( 'sha256', $shortdate, $ksecret, true );
            $kregion    = hash_hmac( 'sha256', 'eu-west-1', $kdate, true );
            $kservice   = hash_hmac( 'sha256', 'tts', $kregion, true );
            $ksigning   = hash_hmac( 'sha256', 'aws4_request', $kservice, true );
        }
        // command parameters
        $params     = array(
            'host'          => 'tts.eu-west-1.ivonacloud.com',
            'content-type'  => 'application/json',
            'x-amz-content-sha256' => hash( 'sha256', $payload ),
            'x-amz-date'    => $longdate,
        );
        $canonical_request  = $this->createCanonicalRequest( $params, $payload );
        $signed_request     = hash( 'sha256', $canonical_request );
        $sign_string        = "AWS4-HMAC-SHA256
{$longdate}
$shortdate/eu-west-1/tts/aws4_request
" . $signed_request;
        $signature          = hash_hmac( 'sha256', $sign_string, $ksigning );
        $params['Authorization'] = "AWS4-HMAC-SHA256 Credential=" . self::ACCESS_KEY . "/$shortdate/eu-west-1/tts/aws4_request, " .
                                   "SignedHeaders=content-type;host;x-amz-content-sha256;x-amz-date, " .
                                   "Signature=$signature";
          $params['content-length'] = strlen( $payload ) ;
        /*
         * Execute Crafted Request
         */
        $url    = "https://tts.eu-west-1.ivonacloud.com/CreateSpeech";
        $ch     = curl_init();
        $curl_headers = array();
        foreach( $params as $p => $k )
            $curl_headers[] = $p . ": " . $k;
        curl_setopt($ch, CURLOPT_URL,$url);
        curl_setopt($ch, CURLOPT_POST,1);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
        curl_setopt($ch, CURLOPT_HTTPHEADER, $curl_headers);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER,1);
        curl_setopt($ch, CURLOPT_TCP_NODELAY, true);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false );
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false );
        // debug opts
        {
            curl_setopt($ch, CURLOPT_VERBOSE, true);
            $verbose = fopen('php://temp', 'rw+');
            curl_setopt($ch, CURLOPT_STDERR, $verbose);
            $result = curl_exec($ch); // raw result
            rewind($verbose);
            $verboseLog = stream_get_contents($verbose);
            #echo "Verbose information:
<pre>", htmlspecialchars($verboseLog), "</pre>
";
        }
        return $result;
    }
    private function createCanonicalRequest( Array $params, $payload )
    {
        $canonical_request      = array();
        $canonical_request[]    = 'POST';
        $canonical_request[]    = '/CreateSpeech';
        $canonical_request[]    = '';
        $can_headers            = array(
          'host' => 'tts.eu-west-1.ivonacloud.com'
        );
        foreach( $params as $k => $v )
            $can_headers[ strtolower( $k ) ] = trim( $v );
        uksort( $can_headers, 'strcmp' );
        foreach ( $can_headers as $k => $v )
            $canonical_request[] = $k . ':' . $v;
        $canonical_request[] = '';
        $canonical_request[] = implode( ';', array_keys( $can_headers ) );
        $canonical_request[] = hash( 'sha256', $payload );
        $canonical_request = implode( "
", $canonical_request );
        return $canonical_request;
    }
}

Leider kommt es zu folgender Fehlermeldung:

Fatal error: Call to undefined function hash_hmac() in /usr/share/symcon/scripts/IVONA_TTS.ips.php on line 37

Titus meint das PHP-Hash-Modul würde wohl auf dem Raspberry fehlen.

Auf meinem Raspberry läuft die PHP Version 5.5.26. Angeblich soll diese Version das Hash-Modul bereits beinhalten.

Weist Du wie ich es vielleicht sonst installieren kann?

Gruß

Axel

Moin,

da hat er auch recht.

Allerdings liegt es nicht an dem PHP auf dem Raspberry, sondern an dem was IP-Symcon mitbringt:
Auf dem OS:
os.png
Im IPS:
IPS.png

IPS liefert auf dem Raspberry (bei Windows kann ich es nicht sagen) eine eigene PHP Version mit. Ich glaube aus Performance Gründen.

Und da ist kein hash_hmac() enthalten.
Ich werde mal im „Test Bereich“ nachfragen. Wenn ich es richtig verstanden haben kann man es irgendwie hinzufügen, ohne es mit ins PHP reinzukompilieren.

Das gehört aber nicht in diesen Thread…

Gruß,
Thorsten

Hallo,

ich habe mittlerweile mit paresy kommuniziert, und er wird es beim nächsten php update aufnehmen. Das wird aber noch ein wenig dauern.
Bis dahin habe ich mir einen extrem häßlichen Workaround ausgedacht:( :


<?

class IVONA_TTS
{
    static  $utc_tz;
    const   ACCESS_KEY = 'AAA';
    const   SECRET_KEY = 'BBB';

    function save_mp3($text, $filename, $language="de-DE", $voice="Marlene", $rate="medium", $volume="loud") {
         $payload['Input'] = array();
            $payload['Input']['Data'] = utf8_encode($text);
            $payload['Parameters']['Rate'] = $rate;
            $payload['Parameters']['Volume'] = $volume;
            $payload['Voice']['Name'] = $voice;
            $payload['Voice']['Language'] = $language;

            $payload = json_encode($payload);
            $mp3 = $this->get_mp3($payload);
            file_put_contents($filename, $mp3);
     }

    function get_mp3( $payload )
    {
        if( !self::$utc_tz ) {
            self::$utc_tz = new \DateTimeZone( 'UTC' );
          }

        $datestamp                = new \DateTime( "now", self::$utc_tz );
        $longdate                 = $datestamp->format( "Ymd\\THis\\Z");
        $shortdate                = $datestamp->format( "Ymd" );
        $ksecret                  = 'AWS4' . self::SECRET_KEY;

 		  $hash_command             = "php -r \"print(hash( 'sha256', '".str_replace('"','\"',$payload)."', false ));\"";
        $params                   = array( 'host'                 => 'tts.eu-west-1.ivonacloud.com',
                                           'content-type'         => 'application/json',
                                           'x-amz-content-sha256' => exec($hash_command),
                                           'x-amz-date'           => $longdate );
        $canonical_request        = $this->createCanonicalRequest( $params, $payload );
		  $hash_command             = "php -r \"print(hash( 'sha256', '".$canonical_request."', false ));\"";
        $signed_request           = exec($hash_command);
        $sign_string              = "AWS4-HMAC-SHA256
{$longdate}
$shortdate/eu-west-1/tts/aws4_request
" . $signed_request;
        $signature_command        = "php -r \"print(hash_hmac( 'sha256', '".$sign_string."', hash_hmac( 'sha256', 'aws4_request', hash_hmac( 'sha256', 'tts', hash_hmac( 'sha256', 'eu-west-1', hash_hmac( 'sha256', '".$shortdate."', '".$ksecret."', true ) , true ) , true ), true ) ) );\"";
        $signature                = exec($signature_command);;
        $params['Authorization']  = "AWS4-HMAC-SHA256 Credential=" . self::ACCESS_KEY . "/$shortdate/eu-west-1/tts/aws4_request, " .
                                    "SignedHeaders=content-type;host;x-amz-content-sha256;x-amz-date, " .
                                    "Signature=$signature";
        $params['content-length'] = strlen( $payload ) ;
        /*
         * Execute Crafted Request
         */
        $url    = "https://tts.eu-west-1.ivonacloud.com/CreateSpeech";
        $ch     = curl_init();
        $curl_headers = array();
        foreach( $params as $p => $k )
            $curl_headers[] = $p . ": " . $k;
        curl_setopt($ch, CURLOPT_URL,$url);
        curl_setopt($ch, CURLOPT_POST,1);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
        curl_setopt($ch, CURLOPT_HTTPHEADER, $curl_headers);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER,1);
        curl_setopt($ch, CURLOPT_TCP_NODELAY, true);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false );
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false );
        // debug opts
        {
            curl_setopt($ch, CURLOPT_VERBOSE, true);
            $verbose = fopen('php://temp', 'rw+');
            curl_setopt($ch, CURLOPT_STDERR, $verbose);
            $result = curl_exec($ch); // raw result
            rewind($verbose);
            $verboseLog = stream_get_contents($verbose);
            #echo "Verbose information:
<pre>", htmlspecialchars($verboseLog), "</pre>
";
        }
        return $result;
    }
    private function createCanonicalRequest( Array $params, $payload )
    {
        $canonical_request      = array();
        $canonical_request[]    = 'POST';
        $canonical_request[]    = '/CreateSpeech';
        $canonical_request[]    = '';
        $can_headers            = array(
          'host' => 'tts.eu-west-1.ivonacloud.com'
        );
        foreach( $params as $k => $v )
            $can_headers[ strtolower( $k ) ] = trim( $v );
        uksort( $can_headers, 'strcmp' );
        foreach ( $can_headers as $k => $v )
            $canonical_request[] = $k . ':' . $v;
        $canonical_request[] = '';
        $canonical_request[] = implode( ';', array_keys( $can_headers ) );
		  $hash_command        = "php -r \"print(hash( 'sha256', '".str_replace('"','\"',$payload)."', false ));\"";
        $canonical_request[] = exec($hash_command);
        $canonical_request = implode( "
", $canonical_request );
        return $canonical_request;
    }
}

$tts = new IVONA_TTS();
$tts->save_mp3("So geht es!", "/tmp/test.mp3");

?>

Vorraussetzung ist, dass php auf dem Raspberry installiert ist.
Das hier ist der relevante Teil:


$hash_command        = "php -r \"print(hash( 'sha256', '".str_replace('"','\"',$payload)."', false ));\"";
$canonical_request[] = exec($hash_command);

Es wird die hash() Funktion vom lokalen php aufgerufen.
Sobald hash in IPS verfügbar sein wird, sollte man es aber dringend wieder umbauen.

Ich habe auch schon bei Titus nachgefragt, und werde bald (in ein paar Tagen) diesen TTS Service als Modul zur Verfügung stellen…

Gruß,
Thorsten

Hallo Thorsten,

vielen Dank für Deinen tollen Einsatz.:slight_smile:

Leider bekomme ich bei der Ausführung von folgendem Skript immer noch einige Fehlermeldungen:

SonosBY_TTS_Alle("Das ist ein Test");

Notice: Undefined offset: 1 in /usr/share/symcon/scripts/SonosBY.ips.php on line 694

Notice: Undefined offset: 1 in /usr/share/symcon/scripts/SonosBY.ips.php on line 694

Notice: Undefined offset: 1 in /usr/share/symcon/scripts/SonosBY.ips.php on line 694

Notice: Undefined offset: 1 in /usr/share/symcon/scripts/SonosBY.ips.php on line 694

Warning: file_put_contents(//192.168.178.20/public/SonosBY_AmazonIvonaTTS.mp3): failed to open stream: Datei oder Verzeichnis nicht gefunden in /usr/share/symcon/scripts/IVONA_TTS.ips.php on line 19

Notice: Undefined offset: 1 in /usr/share/symcon/scripts/SonosBY.ips.php on line 694

Notice: Undefined offset: 1 in /usr/share/symcon/scripts/SonosBY.ips.php on line 694

Notice: Undefined offset: 1 in /usr/share/symcon/scripts/SonosBY.ips.php on line 365

Notice: Undefined offset: 2 in /usr/share/symcon/scripts/SonosBY.ips.php on line 366

Mein SonosBY.ips.php Skript lautet wie folgt:


///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//   SonosBY v1.6   ///////////////////////////////////////////////////////////////////////////////////////////////////////////
//    by Bayaro     ///////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// https://www.symcon.de/forum/threads/28615-SonosBY-%28Sonos-Gruppen-erstellen-Gruppen-aufl%C3%B6sen-Text-to-Speech-%29 //////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
//
// Auflistungen aller aktuell verfügbaren Funktionen in diesem Skript:
//
// SonosBY_GruppeErstellen()                   << Erstellt aus allen eingetragenen Sonos Playern eine Gruppe
// SonosBY_GruppeErstellen_Auswahl($Raumliste) << Erstellt aus den aufgelisteten Räumen eine Gruppe (1. Raum = Master)   ...("Wohnzimmer,Flur,Kueche")
// SonosBY_GruppeAufloesen()                    << Löst die (alle) Gruppe(n) auf und setzt alle Player einzeln (nur wenn nichts abgespielt wird)
// $result = SonosBY_AltenZustandLesen()        << Gibt den Zustand aller Player zurück (Playing/Track/TrackPosition/Volume) - wird sich nur während der Skriptlaufzeit gemerkt
// SonosBY_AltenZustandSetzen($result)          << Setzt den vorher gespeicherten Zustand der Player wieder (nur wenn der alte Zustand während gleicher Skriptlaufzeit gelesen wurde)
// SonosBY_SetAVTransportRincon($SonosGroupAR_NrDesPlayer, $MasterRincon, $URI)      << Setzt bei einem Player die Rincon vom Gruppen-Master, damit werden beide zu einer Gruppe und weitere Player spielen das ab, was der Gruppen-Master abspielt
// SonosBY_SetAVTransportFile($SonosGroupAR_NrDesPlayer, $Sonos_SMBPfadUndDateiname) << Spielt beim angegebenen Player eine Datei vom freigegebenen SMB-Pfad ab (z.B. "//192.168.1.50/sonos-sounds/datei.wav")
// SonosBY_SetPlaylistTrack($SonosGroupAR_NrDesPlayer, $TrackNr)         << Wählt bei einem Player eine TrackNr in der Playlist aus  ...(1, 5);   < wählt Player 1, Track 5
// SonosBY_SetTrackPosition($SonosGroupAR_NrDesPlayer, $TrackPosition)   << Setzt bei einem Player die TrackPosition  ...(2, "0:01:20");  < wählt bei bei Player 2, TrackPosition 1 Minute 20 Sekunden
// $result = SonosBY_GetTrackDurationSekunden($SonosGroupAR_NrDesPlayer) << Gibt die Dauer des aktuellen Tracks eines Players in Sekunden zurück
// $result = SonosBY_GetTransportStatus($SonosGroupAR_NrDesPlayer)       << Gibt von einem Player den Status von Transport zurück (PLAYING/STOPPED/PAUSED_PLAYBACK)
// $result = SonosBY_GetTrackPositionInfo($SonosGroupAR_NrDesPlayer)     << Gibt von einem Player ein Array mit Infos zurück (RelTime=TrackPosition, Track=TrackNr, TrackDuration=TrackLänge)
// $result = SonosBY_GetSonosInfos($SonosGroupAR_NrDesPlayer)            << Gibt Infos zum Sonos Player zurück (SoftwareVersion Build (z.B. 29.6-99010), SoftwareVersion auf Player (z.B. 6.0), HardwareVersion)
// SonosBY_StartResume_Playing($SonosGroupAR_NrDesPlayer)  << Setzt den Transport Status eines Players auf PLAY und spielt das was in seiner Queue ist ab
// SonosBY_Stop_Playing($SonosGroupAR_NrDesPlayer)            << Setzt den Transport Status eines Players auf STOP
// SonosBY_Pause_Playing($SonosGroupAR_NrDesPlayer)            << Setzt den Transport Status eines Players auf PAUSE
// $result = SonosBY_GetVolume($SonosGroupAR_NrDesPlayer)  << Liest die aktuell eingestellte Lautstärke eines Player aus
// SonosBY_SetVolume($SonosGroupAR_NrDesPlayer, $Volume)   << Setzt die Lautstärke eines Players auf einen bestimmten Wert (0-99)
// SonosBY_SetGroupVolume($Volume)                         << Ändert die Lautstärke der gruppierten Player (Relativ zur aktuellen Lautstärke der einzelnen Player)
// SonosBY_SetGroupMute(1)                                 << Setzt MUTE für die Gruppe (1 = Mute an // 0 = Mute aus)
// SonosBY_TTS($SonosGroupAR_NrDesPlayer, $TTS_Text)       << Spielt am gewählten Player einen Text als Ton ab (Instanz-ID eures "Text to Speech" angeben // oder FALSE für Google API)
// SonosBY_TTS_Alle($Text)                                 << Merkt sich den Zustand aller Player, erstellt eine Gruppe, spielt den Text ab, löst die Gruppe auf, stellt alte Zustände wieder her
// SonosBY_TTS_Auswahl($RaumlisteDerPlayer, $Text)         << Merkt sich den Zustand der gewählten Player, erstellt eine Gruppe, spielt den Text ab, löst die Gruppe auf, stellt alte Zustände wieder her
// SonosBY_DateiAbspielen_Alle($Dateiname)                 << Spielt in allen Räumen eine Datei ab (Datei muss im freigegebenen Ordner liegen)
// SonosBY_DateiAbspielen_Auswahl($Raumliste, $Dateiname)  << Spielt in bestimmten Räumen eine Datei ab (Datei muss im freigegebenen Ordner liegen)


/* BEISPIELE /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

>> Beispiel1 (aus allen eingetragenen Playern eine Gruppe erstellen):
SonosBY_GruppeErstellen();

>> Beispiel2 (Gruppe(n) auflösen - alle Player einzeln):
SonosBY_GruppeAufloesen();

>> Beispiel3 (Alte Zustände (Playing/Track/TrackPosition/Volume) merken, Gruppe erstellen, Sprachausgabe abspielen, Gruppe auflösen, alte Zustände wiederherstellen):
$AlterZustandAR = SonosBY_AltenZustandLesen();
SonosBY_GruppeErstellen();
SonosBY_TTS(0, "Ich bin ein Test der Sprachausgabe");
SonosBY_GruppeAufloesen();
SonosBY_AltenZustandSetzen($AlterZustandAR);

>> Beispiel4 (kurze Version von Beispiel3, macht aber genau das Gleiche)
SonosBY_TTS_Alle("Ich bin ein Test");

>> Beispiel5 (Sprachausgabe auf einem Sonos Player abspielen - inkl. merken und setzen der alten Zustände):
SonosBY_TTS_Auswahl("Badezimmer","Das ist ein Test");

>> Beispiel6 (Sprachausgabe auf bestimmten Sonos Playern (in Gruppe) abspielen - inkl. merken und setzen der alten Zustände):
SonosBY_TTS_Auswahl("Badezimmer,Wohnzimmer", "Das ist ein Test);   // Spielt nur auf den Playern "Badezimmer" und "Wohnzimmer" den Text ab, aber nicht im "Flur" (ID 0) // Master ist dann der 1. genannte Raum/Player

>> Beispiel7 (Eine Datei in bestimmten Räumen abspielen - inkl. merken und setzen der alten Zustände)
SonosBY_DateiAbspielen_Alle("Waschmaschine_ist_fertig.wav");

>> Beispiel8 (Bestimmte Räume zu einer Gruppe zusammenfügen):
SonosBY_GruppeErstellen_Auswahl("Wohnzimmer,Badezimmer");  // Erstellt eine Gruppe mit beiden Räumen, Master ist Wohnzimmer
SonosBY_GruppeErstellen_Auswahl("Flur,Badezimmer");        // Erstellt eine Gruppe mit beiden Räumen, Master ist Flur


// KONFIGURATION //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// */
//
// Gruppen-Master (für alle Player in einer Gruppe) als 1. bei $SonosGroupAR[0] eintragen, alle
// anderen Sonos Player danach.
//
// Wenn ihr jetzt in der Gruppe irgendwelche Musik/Radio/Sprachausgaben abspielen wollt, dann
// müsst ihr das immer nur auf dem Player machen, welchen ihr als Gruppen-Master verwendet!
// Ich habe als Master einen Player genommen, welchen ich so gut wie nie im Einsatz habe, weil
// sollte auf dem Master gerade etwas abgespielt werden, dann hört man das kurzzeitig auf der
// Lautstärke in der das TTS erfolgt und gegebenenfalls auf weiteren Playern! Klingt dann
// bei Sprachausgaben etwas "unschön". Deshalb wählt als Master am Besten einen Player, der
// so gut wie nie (oder nie) in Verwendung ist. Auch wird beim Gruppen-Master der alte Zustand
// nicht wiederhergestellt! Gilt auch für den Master, wenn ihr nur bestimmte Player zu
// einer Gruppe zusammenfasst!
//
// Das Skript prüft nicht, ob bereits Gruppen existieren und stellt diese auch nicht
// wieder her! Er nimmt sich einfach alle Player, steckt sie in eine Gruppe und beim
// Gruppe auflösen werden alle Player wieder einzeln gesetzt.
//
//
// Je nach Länge der Sprachausgaben/Musikdateien, kann es notwendig sein die max_execution_time zu erhöhen (Skript bricht dann wegen Timeout ab)
ini_set("max_execution_time", 90);
//
// Syntax: $SonosGroupAR[0] = array("Raumname","IP-Adresse","RINCON","Lautstärke des Players im Gruppenbetrieb");
// Beispiel für 3 Sonos Player // [0] = Gruppen-Master
$SonosGroupAR[0] = array("Sonos","192.168.178.40","RINCON_B8E937E427AE01400","30");
//$SonosGroupAR[1] = array("Badezimmer","192.168.1.12","RINCON_B1111111111111111","35");
//$SonosGroupAR[2] = array("Wohnzimmer","192.168.1.13","RINCON_B2222222222222222","30");

$Sonos_Dateiordner = "/sonos/";  // Das hier eingetragene Verzeichnis muss für Sonos freigegeben werden
$Sonos_SMBordner = "//192.168.178.20/public/";            // Hier den SMB-Pfad zur Netzwerkfreigabe eintragen ("//IPS-Server-IP/ORDNERNAME/")
$TTS_InstanzID = TRUE;                  // ID eurer "Text to Speech" Instanz (ID = IPS TTS, FALSE = Google TTS, TRUE = Amazon/Ivona TTS)
// Mit der Text to Speech API von Google sind max. 100 Zeichen möglich! Wer mehr Zeichen will > https://www.symcon.de/forum/threads/25562-IPSSonos?p=243647#post243647
// Mit der Amazon/Ivona API sind max. 200 Zeichen möglich und ihr müsst diese Class einbauen > https://www.symcon.de/forum/threads/29015-TTS-mit-Amazon-Ivona-statt-Google-Translate
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

include 'IVONA_TTS.ips.php';

/****** AB HIER NICHTS MEHR ÄNDERN ****************************************************************/

function SonosBY_GruppeErstellen() {
    Global $SonosGroupAR;
   $AnzahlPlayer = count($SonosGroupAR);

    for ($i=0; $i < $AnzahlPlayer; $i++) {
        SonosBY_SetVolume($i, (int)$SonosGroupAR[$i][3]);

       if ($i == 0) {
            $MasterRincon = $SonosGroupAR[0][2];
        }
        else {
            SonosBY_SetAVTransportRincon($i, 0, "x-rincon:".$MasterRincon);
        }
    }
}


function SonosBY_GruppeErstellen_Auswahl($Raumliste) {
    Global $SonosGroupAR;
    if(strpos($Raumliste,",")!==false){
       $RaumlisteAR = explode(",", $Raumliste);
    }
    else {
       $RaumlisteAR[] = $Raumliste;
    }
    $i=0;
    foreach ($RaumlisteAR as $Raumname) {
        $ResultVal = SearchArray($Raumname, 0, $SonosGroupAR);
        $SonosGroupARx[$i] = array($SonosGroupAR[$ResultVal][0],$SonosGroupAR[$ResultVal][1],$SonosGroupAR[$ResultVal][2],$SonosGroupAR[$ResultVal][3]);
        $i++;
    }
    $SonosGroupAR = $SonosGroupARx;
   $AnzahlPlayer = count($SonosGroupAR);

    for ($i=0; $i < $AnzahlPlayer; $i++) {
        SonosBY_SetVolume($i, (int)$SonosGroupAR[$i][3]);

       if ($i == 0) {
            $MasterRincon = $SonosGroupAR[0][2];
        }
        else {
            SonosBY_SetAVTransportRincon($i, 0, "x-rincon:".$MasterRincon);
        }
    }
}


function SonosBY_GruppeAufloesen() {
    Global $SonosGroupAR;
   $AnzahlPlayer = count($SonosGroupAR);

   IPS_Sleep(2000);
    $SonosInfos = SonosBY_GetSonosInfos(0);  //     Bis Sonos einen Fix hat für >> Player in einer Gruppe haben IMMER den Status PLAYING
    if ($SonosInfos["DisplaySoftwareVersion"] < 5.5) {
       $TransportStatus = SonosBY_GetTransportStatus(0);
        while ($TransportStatus == "PLAYING") {
           $TransportStatus = SonosBY_GetTransportStatus(0);
            IPS_Sleep(100);
        }
    }
    else {
       $AbspieldauerSekunden = SonosBY_GetTrackDurationSekunden(0);
       $SleepWaitTime = ($AbspieldauerSekunden * 1000) + 3000;
        IPS_Sleep($SleepWaitTime);
    }

    for ($i=1; $i < $AnzahlPlayer; $i++) {
        SonosBY_SetAVTransportRincon($i, $i, "x-rincon:".$SonosGroupAR[$i][2]);
    }
}



function SonosBY_AltenZustandLesen() {
   Global $SonosGroupAR;
   $AnzahlPlayer = count($SonosGroupAR);
    for ($i=0; $i < $AnzahlPlayer; $i++) {
        $StatuslisteAR[0][] = SonosBY_GetTransportStatus($i);
        $StatuslisteAR[1][] = SonosBY_GetVolume($i);
        $StatuslisteAR[2][] = SonosBY_GetTrackPositionInfo($i);
    }
    return $StatuslisteAR;
}


function SonosBY_AltenZustandSetzen($StatuslisteAR) {
   Global $SonosGroupAR;
   $AnzahlPlayer = count($SonosGroupAR);
    for ($i=0; $i < $AnzahlPlayer; $i++) {
        SonosBY_SetVolume($i, $StatuslisteAR[1][$i]);
        if ($StatuslisteAR[2][$i]["TrackDuration"] != "0:00:00") {
           if ($StatuslisteAR[2][$i]["TrackNr"] != "") {
                SonosBY_SetPlaylistTrack($i, $StatuslisteAR[2][$i]["TrackNr"]);
            }
            if ($StatuslisteAR[2][$i]["RelTime"] != "") {
              SonosBY_SetTrackPosition($i, $StatuslisteAR[2][$i]["RelTime"]);
          }
      }
        if ($StatuslisteAR[0][$i] == "PLAYING") {
            SonosBY_StartResume_Playing($i);
        }
    }
}


function SonosBY_SetAVTransportRincon($i, $MasterPlayerID, $AVURI) {
Global $SonosGroupAR;
$content='POST /MediaRenderer/AVTransport/Control HTTP/1.1
HOST: '.$SonosGroupAR[$i][1].':1400
SOAPACTION: "urn:schemas-upnp-org:service:AVTransport:1#SetAVTransportURI"
CONTENT-TYPE: text/xml; charset="utf-8"
Content-Length: 477

<?xml version="1.0" encoding="utf-8"?>
<s:Envelope s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
   <s:Body>
      <u:SetAVTransportURI xmlns:u="urn:schemas-upnp-org:service:AVTransport:1">
         <InstanceID>0</InstanceID>
         <CurrentURI>x-rincon:'.$SonosGroupAR[$MasterPlayerID][2].'</CurrentURI>
         <CurrentURIMetaData>x</CurrentURIMetaData>
      </u:SetAVTransportURI>
   </s:Body>
</s:Envelope>';
$result = SonosBY_SendSOAP($SonosGroupAR[$i][1], $content);
return $result;
}


function SonosBY_SetAVTransportFile($i, $Sonos_SMBPfadUndDateiname) {
Global $SonosGroupAR;
$SonosInfos = SonosBY_GetSonosInfos(0);
if ($SonosInfos["DisplaySoftwareVersion"] < 5.5) {
    $contentlength = 477;
}
else {
    $contentlength = 456 + strlen($Sonos_SMBPfadUndDateiname);
}
$content='POST /MediaRenderer/AVTransport/Control HTTP/1.1
HOST: '.$SonosGroupAR[$i][1].':1400
SOAPACTION: "urn:schemas-upnp-org:service:AVTransport:1#SetAVTransportURI"
CONTENT-TYPE: text/xml; charset="utf-8"
Content-Length: '.$contentlength.'

<?xml version="1.0" encoding="utf-8"?>
<s:Envelope s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
   <s:Body>
      <u:SetAVTransportURI xmlns:u="urn:schemas-upnp-org:service:AVTransport:1">
         <InstanceID>0</InstanceID>
         <CurrentURI>x-file-cifs:'.$Sonos_SMBPfadUndDateiname.'</CurrentURI>
         <CurrentURIMetaData>x</CurrentURIMetaData>
      </u:SetAVTransportURI>
   </s:Body>
</s:Envelope>';
$result = SonosBY_SendSOAP($SonosGroupAR[$i][1], $content);
return $result;
}


function SonosBY_SetPlaylistTrack($i, $TrackNr) {
Global $SonosGroupAR;
$content='POST /MediaRenderer/AVTransport/Control HTTP/1.1
HOST: '.$SonosGroupAR[$i][1].':1400
SOAPACTION: "urn:schemas-upnp-org:service:AVTransport:1#Seek"
CONTENT-TYPE: text/xml; charset="utf-8"
Content-Length: 390

<?xml version="1.0" encoding="utf-8"?>
<s:Envelope s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
   <s:Body>
      <u:Seek xmlns:u="urn:schemas-upnp-org:service:AVTransport:1">
         <InstanceID>0</InstanceID>
         <Unit>TRACK_NR</Unit>
         <Target>'.$TrackNr.'</Target>
      </u:Seek>
   </s:Body>
</s:Envelope>';
$result = SonosBY_SendSOAP($SonosGroupAR[$i][1], $content);
return $result;
}


function SonosBY_SetTrackPosition($i, $TrackPosition) {
Global $SonosGroupAR;
$content='POST /MediaRenderer/AVTransport/Control HTTP/1.1
HOST: '.$SonosGroupAR[$i][1].':1400
SOAPACTION: "urn:schemas-upnp-org:service:AVTransport:1#Seek"
CONTENT-TYPE: text/xml; charset="utf-8"
Content-Length: 396

<?xml version="1.0" encoding="utf-8"?>
<s:Envelope s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
   <s:Body>
      <u:Seek xmlns:u="urn:schemas-upnp-org:service:AVTransport:1">
         <InstanceID>0</InstanceID>
         <Unit>REL_TIME</Unit>
         <Target>'.$TrackPosition.'</Target>
      </u:Seek>
   </s:Body>
</s:Envelope>';
$result = SonosBY_SendSOAP($SonosGroupAR[$i][1], $content);
return $result;
}


function SonosBY_GetTransportStatus($i) {
Global $SonosGroupAR;
$content='POST /MediaRenderer/AVTransport/Control HTTP/1.1
HOST: '.$SonosGroupAR[$i][1].':1400
SOAPACTION: "urn:schemas-upnp-org:service:AVTransport:1#GetTransportInfo"
CONTENT-TYPE: text/xml; charset="utf-8"
Content-Length: 353

<?xml version="1.0" encoding="utf-8"?>
<s:Envelope s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
   <s:Body>
      <u:GetTransportInfo xmlns:u="urn:schemas-upnp-org:service:AVTransport:1">
         <InstanceID>0</InstanceID>
      </u:GetTransportInfo>
   </s:Body>
</s:Envelope>';
$resultx = SonosBY_SendSOAP($SonosGroupAR[$i][1], $content);
$result = XMLFilter($resultx, "CurrentTransportState");
return $result;
}


function SonosBY_GetTrackPositionInfo($i) {
Global $SonosGroupAR;
$content='POST /MediaRenderer/AVTransport/Control HTTP/1.1
HOST: '.$SonosGroupAR[$i][1].':1400
SOAPACTION: "urn:schemas-upnp-org:service:AVTransport:1#GetPositionInfo"
CONTENT-TYPE: text/xml; charset="utf-8"
Content-Length: 351

<?xml version="1.0" encoding="utf-8"?>
<s:Envelope s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
   <s:Body>
      <u:GetPositionInfo xmlns:u="urn:schemas-upnp-org:service:AVTransport:1">
         <InstanceID>0</InstanceID>
      </u:GetPositionInfo>
   </s:Body>
</s:Envelope>';
$resultTrackInfo = SonosBY_SendSOAP($SonosGroupAR[$i][1], $content);
$result["RelTime"] = XMLFilter($resultTrackInfo, "RelTime");
$result["TrackNr"] = @XMLFilter($resultTrackInfo, "Track");
$result["TrackDuration"] = XMLFilter($resultTrackInfo, "TrackDuration");
return $result;
}


function SonosBY_GetTrackDurationSekunden($i) {
    $TrackPositionInfoX = SonosBY_GetTrackPositionInfo($i);
    $DurationAR = explode(":", $TrackPositionInfoX['TrackDuration']);
    $DurationSTD = (int)$DurationAR[0];
    $DurationMIN = (int)$DurationAR[1];
    $DurationSEK = (int)$DurationAR[2];
    $DurationSekGesamt = $DurationSEK + ($DurationMIN * 60) + ($DurationSTD * 3600);
    return $DurationSekGesamt;
}


function SonosBY_GetSonosInfos($i) {
Global $SonosGroupAR;
$content='POST /DeviceProperties/Control HTTP/1.1
HOST: '.$SonosGroupAR[$i][1].':1400
SOAPACTION: "urn:schemas-upnp-org:service:DeviceProperties:1#GetZoneInfo"
CONTENT-TYPE: text/xml; charset="utf-8"
Content-Length: 289

<?xml version="1.0" encoding="utf-8"?>
<s:Envelope s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
   <s:Body>
      <u:GetZoneInfo xmlns:u="urn:schemas-upnp-org:service:DeviceProperties:1" />
   </s:Body>
</s:Envelope>';
$resultSonosInfos = SonosBY_SendSOAP($SonosGroupAR[$i][1], $content);
$result["SoftwareVersion"] = XMLFilter($resultSonosInfos, "SoftwareVersion");
$result["DisplaySoftwareVersion"] = @XMLFilter($resultSonosInfos, "DisplaySoftwareVersion");
$result["HardwareVersion"] = XMLFilter($resultSonosInfos, "HardwareVersion");
return $result;
}


function SonosBY_StartResume_Playing($i) {
Global $SonosGroupAR;
$content='POST /MediaRenderer/AVTransport/Control HTTP/1.1
HOST: '.$SonosGroupAR[$i][1].':1400
SOAPACTION: "urn:schemas-upnp-org:service:AVTransport:1#Play"
CONTENT-TYPE: text/xml; charset="utf-8"
Content-Length: 356

<?xml version="1.0" encoding="utf-8"?>
<s:Envelope s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
   <s:Body>
      <u:Play xmlns:u="urn:schemas-upnp-org:service:AVTransport:1">
         <InstanceID>0</InstanceID>
         <Speed>1</Speed>
      </u:Play>
   </s:Body>
</s:Envelope>';
$result = SonosBY_SendSOAP($SonosGroupAR[$i][1], $content);
return $result;
}


function SonosBY_Stop_Playing($i) {
Global $SonosGroupAR;
$content='POST /MediaRenderer/AVTransport/Control HTTP/1.1
HOST: '.$SonosGroupAR[$i][1].':1400
SOAPACTION: "urn:schemas-upnp-org:service:AVTransport:1#Stop"
CONTENT-TYPE: text/xml; charset="utf-8"
Content-Length: 329

<?xml version="1.0" encoding="utf-8"?>
<s:Envelope s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
   <s:Body>
      <u:Stop xmlns:u="urn:schemas-upnp-org:service:AVTransport:1">
         <InstanceID>0</InstanceID>
      </u:Stop>
   </s:Body>
</s:Envelope>';
$result = SonosBY_SendSOAP($SonosGroupAR[$i][1], $content);
return $result;
}


function SonosBY_Pause_Playing($i) {
Global $SonosGroupAR;
$content='POST /MediaRenderer/AVTransport/Control HTTP/1.1
HOST: '.$SonosGroupAR[$i][1].':1400
SOAPACTION: "urn:schemas-upnp-org:service:AVTransport:1#Pause"
CONTENT-TYPE: text/xml; charset="utf-8"
Content-Length: 331

<?xml version="1.0" encoding="utf-8"?>
<s:Envelope s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
   <s:Body>
      <u:Pause xmlns:u="urn:schemas-upnp-org:service:AVTransport:1">
         <InstanceID>0</InstanceID>
      </u:Pause>
   </s:Body>
</s:Envelope>';
$result = SonosBY_SendSOAP($SonosGroupAR[$i][1], $content);
return $result;
}


function SonosBY_GetVolume($i) {
Global $SonosGroupAR;
$content='POST /MediaRenderer/RenderingControl/Control HTTP/1.1
HOST: '.$SonosGroupAR[$i][1].':1400
SOAPACTION: "urn:schemas-upnp-org:service:RenderingControl:1#GetVolume"
CONTENT-TYPE: text/xml; charset="utf-8"
Content-Length: 380

<?xml version="1.0" encoding="utf-8"?>
<s:Envelope s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
   <s:Body>
      <u:GetVolume xmlns:u="urn:schemas-upnp-org:service:RenderingControl:1">
         <InstanceID>0</InstanceID>
         <Channel>Master</Channel>
      </u:GetVolume>
   </s:Body>
</s:Envelope>';
$resultx = SonosBY_SendSOAP($SonosGroupAR[$i][1], $content);
$result = XMLFilter($resultx, "CurrentVolume");
return $result;
}


function SonosBY_SetVolume($i, $Volume) {
Global $SonosGroupAR;
if (strlen($Volume) > 1) {
    $contentlength = 424;
}
else {
   $contentlength = 423;
}

$content='POST /MediaRenderer/RenderingControl/Control HTTP/1.1
HOST: '.$SonosGroupAR[$i][1].':1400
SOAPACTION: "urn:schemas-upnp-org:service:RenderingControl:1#SetVolume"
CONTENT-TYPE: text/xml; charset="utf-8"
Content-Length: '.$contentlength.'

<?xml version="1.0" encoding="utf-8"?>
<s:Envelope s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
   <s:Body>
      <u:SetVolume xmlns:u="urn:schemas-upnp-org:service:RenderingControl:1">
         <InstanceID>0</InstanceID>
         <Channel>Master</Channel>
         <DesiredVolume>'.$Volume.'</DesiredVolume>
      </u:SetVolume>
   </s:Body>
</s:Envelope>';
$result = SonosBY_SendSOAP($SonosGroupAR[$i][1], $content);
return $result;
}


function SonosBY_SetGroupVolume($GroupVolume) {
Global $SonosGroupAR;
if (strlen($GroupVolume) > 1) {
    $contentlength = 403;
}
else {
   $contentlength = 402;
}

$content='POST /MediaRenderer/GroupRenderingControl/Control HTTP/1.1
HOST: '.$SonosGroupAR[0][1].':1400
SOAPACTION: "urn:schemas-upnp-org:service:GroupRenderingControl:1#SetGroupVolume"
CONTENT-TYPE: text/xml; charset="utf-8"
Content-Length: '.$contentlength.'

<?xml version="1.0" encoding="utf-8"?>
<s:Envelope s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
   <s:Body>
      <u:SetGroupVolume xmlns:u="urn:schemas-upnp-org:service:GroupRenderingControl:1">
         <InstanceID>0</InstanceID>
         <DesiredVolume>'.$GroupVolume.'</DesiredVolume>
      </u:SetGroupVolume>
   </s:Body>
</s:Envelope>';
$result = SonosBY_SendSOAP($SonosGroupAR[0][1], $content);
return $result;
}


function SonosBY_SetGroupMute($MuteEinsNull) {
Global $SonosGroupAR;
$content='POST /MediaRenderer/GroupRenderingControl/Control HTTP/1.1
HOST: '.$SonosGroupAR[0][1].':1400
SOAPACTION: "urn:schemas-upnp-org:service:GroupRenderingControl:1#SetGroupMute"
CONTENT-TYPE: text/xml; charset="utf-8"
Content-Length: 394

<?xml version="1.0" encoding="utf-8"?>
<s:Envelope s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
   <s:Body>
      <u:SetGroupMute xmlns:u="urn:schemas-upnp-org:service:GroupRenderingControl:1">
         <InstanceID>0</InstanceID>
         <DesiredMute>'.$MuteEinsNull.'</DesiredMute>
      </u:SetGroupMute>
   </s:Body>
</s:Envelope>';
$result = SonosBY_SendSOAP($SonosGroupAR[0][1], $content);
return $result;
}


function SonosBY_TTS($i, $TTS_Text) {
    Global $Sonos_Dateiordner;
    Global $Sonos_SMBordner;
    Global $TTS_InstanzID;
    if ($TTS_InstanzID === FALSE) {
       $SonosTTS_Dateiname = "SonosBY_GoogleTTS.mp3";
        $SonosTTS_DateiPfad = $Sonos_Dateiordner.$SonosTTS_Dateiname;
        $Sonos_SMBPfadUndDateiname = $Sonos_SMBordner.$SonosTTS_Dateiname;
        $TTS_Text_UTF8 = urlencode(utf8_encode($TTS_Text));
        if (strlen($TTS_Text_UTF8) > 100) {
            IPS_LogMessage("SonosBY-TTS", "Google TTS Text darf maximal 100 Zeichen lang sein!");
            return;
        }
        $GoogleTTSmp3 = @file_get_contents('http://translate.google.de/translate_tts?tl=de&ie=UTF-8&q='.$TTS_Text_UTF8."&client=t");
        if ((strpos($http_response_header[0], "200") != false)) {
            file_put_contents($SonosTTS_DateiPfad, $GoogleTTSmp3);
        }
        IPS_Sleep(500);
        SonosBY_SetAVTransportFile($i, $Sonos_SMBPfadUndDateiname);
        SonosBY_StartResume_Playing($i);
    }
    elseif ($TTS_InstanzID === TRUE) {
       $SonosTTS_Dateiname = "SonosBY_AmazonIvonaTTS.mp3";
        $SonosTTS_DateiPfad = $Sonos_Dateiordner.$SonosTTS_Dateiname;
        $Sonos_SMBPfadUndDateiname = $Sonos_SMBordner.$SonosTTS_Dateiname;
        $TTS_Text_UTF8 = urlencode(utf8_encode($TTS_Text));
        if (strlen($TTS_Text_UTF8) > 200) {
            IPS_LogMessage("SonosBY-TTS", "Amazon/Ivona TTS Text darf maximal 200 Zeichen lang sein!");
            return;
        }
        if (class_exists("IVONA_TTS")) {
          $Ivona = new IVONA_TTS();
            $Ivona->save_mp3($TTS_Text_UTF8, $Sonos_SMBPfadUndDateiname);
        }
        IPS_Sleep(500);
        SonosBY_SetAVTransportFile($i, $Sonos_SMBPfadUndDateiname);
        SonosBY_StartResume_Playing($i);
    }
    else {
        $SonosTTS_Dateiname = "SonosBY_TTS.wav";
        $SonosTTS_DateiPfad = $Sonos_Dateiordner.$SonosTTS_Dateiname;
        $Sonos_SMBPfadUndDateiname = $Sonos_SMBordner.$SonosTTS_Dateiname;
        TTS_GenerateFile((int)$TTS_InstanzID, $TTS_Text, $SonosTTS_DateiPfad, 39);
        IPS_Sleep(500);
        SonosBY_SetAVTransportFile($i, $Sonos_SMBPfadUndDateiname);
        SonosBY_StartResume_Playing($i);
    }
}


function SonosBY_TTS_Alle($Text) {
    $AlterZustandAR = SonosBY_AltenZustandLesen();
    SonosBY_GruppeErstellen();
    SonosBY_TTS(0, $Text);
    SonosBY_GruppeAufloesen();
    SonosBY_AltenZustandSetzen($AlterZustandAR);
}


function SonosBY_TTS_Auswahl($Raumliste, $Text) {
    Global $SonosGroupAR;
    if(strpos($Raumliste,",")!==false){
       $RaumlisteAR = explode(",", $Raumliste);
    }
    else {
       $RaumlisteAR[] = $Raumliste;
    }
    $i=0;
    foreach ($RaumlisteAR as $Raumname) {
        $ResultVal = SearchArray($Raumname, 0, $SonosGroupAR);
        $SonosGroupARx[$i] = array($SonosGroupAR[$ResultVal][0],$SonosGroupAR[$ResultVal][1],$SonosGroupAR[$ResultVal][2],$SonosGroupAR[$ResultVal][3]);
        $i++;
    }
    $SonosGroupAR = $SonosGroupARx;
    $AlterZustandAR = SonosBY_AltenZustandLesen();
    SonosBY_GruppeErstellen();
    SonosBY_TTS(0, $Text);
    SonosBY_GruppeAufloesen();
    SonosBY_AltenZustandSetzen($AlterZustandAR);
}


function SonosBY_DateiAbspielen_Alle($Dateiname) {
    Global $SonosGroupAR;
    Global $Sonos_SMBordner;
    $Sonos_SMBPfadUndDateiname = $Sonos_SMBordner.$Dateiname;
    $AlterZustandAR = SonosBY_AltenZustandLesen();
    SonosBY_GruppeErstellen();
    SonosBY_SetAVTransportFile(0, $Sonos_SMBPfadUndDateiname);
    SonosBY_StartResume_Playing(0);
    SonosBY_GruppeAufloesen();
    SonosBY_AltenZustandSetzen($AlterZustandAR);
}


function SonosBY_DateiAbspielen_Auswahl($Raumliste, $Dateiname) {
    Global $SonosGroupAR;
    Global $Sonos_SMBordner;
    if(strpos($Raumliste,",")!==false){
       $RaumlisteAR = explode(",", $Raumliste);
    }
    else {
       $RaumlisteAR[] = $Raumliste;
    }
    $i=0;
    foreach ($RaumlisteAR as $Raumname) {
        $ResultVal = SearchArray($Raumname, 0, $SonosGroupAR);
        $SonosGroupARx[$i] = array($SonosGroupAR[$ResultVal][0],$SonosGroupAR[$ResultVal][1],$SonosGroupAR[$ResultVal][2],$SonosGroupAR[$ResultVal][3]);
        $i++;
    }
    $SonosGroupAR = $SonosGroupARx;
    $Sonos_SMBPfadUndDateiname = $Sonos_SMBordner.$Dateiname;
    $AlterZustandAR = SonosBY_AltenZustandLesen();
    SonosBY_GruppeErstellen();
    SonosBY_SetAVTransportFile(0, $Sonos_SMBPfadUndDateiname);
    SonosBY_StartResume_Playing(0);
    SonosBY_GruppeAufloesen();
    SonosBY_AltenZustandSetzen($AlterZustandAR);
}


function SonosBY_SendSOAP($IPaddress, $content) {
    $fp = fsockopen($IPaddress, 1400);
   fputs ($fp, $content);
   $result = stream_get_contents($fp, -1);
   fclose($fp);
   return $result;
}


function XMLFilter($Var,$SuchString) {
    preg_match('/\<'.$SuchString.'\>(.+)\<\/'.$SuchString.'\>/',$Var,$result);
    return $result[1];
}


function SearchArray($value, $key, $array) {
   foreach ($array as $k => $val) {
       if ($val[$key] == $value) {
            return $k;
        }
   }
    return NULL;
}

Hast Du eine Idee was ich noch ändern muß?

Gruß

Axel

Hallo Alex,

nun ja, da steht die eine oder andere Fehlermeldung, der Du nachgehen könntest.

Aber das gehört nicht in dieses Thread, da SonosBY nichts mit diesem Sonos Modul zu tun hat.
Daher möchte ich die Diskussion an dieser Stelle abbrechen.

Ich hoffe Du verstehst das.

Gruß,
Thorsten

Hallo,

ich habe das Ivona Modul fertig: Ivona TTS Modul

Kann man direkt an Sonos übergeben lassen:


SNS_PlayFiles(31596 /*[Sonos_neu\Küche]*/ , array(IVNTTS_saveMP3(55186 /*[Ivona]*/,"Geht das so?"),
                                                                                                       IVNTTS_saveMP3(55186 /*[Ivona]*/,"Jepp!")) );

Gruß,
Thorsten

Hallo zusammen,

ich habe gerade die Verszion 6.0 (Build 31322220a) auf meine Boxen installiert.
Alle funktionen gehen noch.

Beim testen habe ich aber festgestellt, das SNS_SetGroupVolume(); noch nie gehen konnte…:eek:
Jetzt tut auch dies,:rolleyes:

Viel Spaß,
Thorsten

Hallo Thorsten,
ich habe mir heute auch das Sonos-Modul installiert. Läuft super - vielen Dank für die klasse Arbeit.

Noch eine Idee/Vorschlag: Ich möchte das Modul u.a. verwenden, um bei bestimmten Events (z.B. durch Bewegungsmelder) ein MP3-File mit Hundegebell abzuspielen. Ideal wäre es, wenn man dem Aufruf "SNS_PlayFiles() einen neuen Volumen-Wert mitgeben könnte, so dass eine gerade leise gespielte Musik durch ein deutlich lauteres Hundegebell unterbrochen wird, und die Musik danach leise weiterspielt. Dies wäre auch bei Durchsagen interessant. Auch ein automatisches FadeIn/Out der Lautstärke wäre prima :).

Lässt sich die aktuell eingestellte Lautstärke eigentlich abfragen? (ein „SNS_GetVolume“ gibt es ja scheinbar nicht)

Viele Grüße
Peter

Lässt sich die aktuell eingestellte Lautstärke eigentlich abfragen? (ein „SNS_GetVolume“ gibt es ja scheinbar nicht)

Ja, aktuelle Lautstärke lässt sich abfragen.
Habe ich in einem meiner Module drin,(TS_SonosAlarm)
Git in IPS eintragen : git://icy.my-router.de/Ts_Module
und probieren.
Doku gibt es nicht, sollte aber klar sein, wenn die Eingabemaske auf ist.
Achtung, geht nur bei NAS Dateien, die kein Kennwort brauchen, um an die MP3 zu kommen !

Hallo,

Danke, das hört man gerne.

Angeregt durch Toms Modul hatte ich dies schon in Erwägung gezogen.
Allerdings habe ich noch „Probleme“ damit.

  1. Ein Inhaltlichens. Möchte ich lieber einen Fixwert (also „volume = 34“) für die Ansage, oder lieber ein Anpassungswert (also „volume = volume_jetzt + 10“). Da bin ich mir noch nicht einig

  2. Da ich ein PHP neuling bin ( ausser diesem Modul lediglich kleine scripte für meine IPS installtion, die ich erst seit Januar habe), verwirren mich optionale Funktionsparameter…
    Daher schaffe ich es nicht einen optionalen Volume Parameter zu definieren…
    Vielleicht muss ich mal jemanden fragen, der sich auskennt :rolleyes:

Als Workaround könnte man auch ein


SNS_ChangeGroupVolume(31596, 10);
SNS_PlayFiles(31596 /*[Sonos_neu\Küche]*/ , array(IVNTTS_saveMP3(55186 /*[Ivona]*/,"Wau, wau!") ) );
SNS_ChangeGroupVolume(31596, -10);

machen, was allerdings vor der Ansage gaaanz kurz lauter macht.

auch würde gehen:


// Dateien vor dem Pausieren erzeugen, um die Pause zu minimieren
$text=IVNTTS_saveMP3(55186 /*[Ivona]*/,"Wau, wau!");

SNS_Pause(31596);
SNS_ChangeGroupVolume(31596, 10);
SNS_PlayFiles(31596 /*[Sonos_neu\Küche]*/ , array($text) );
SNS_ChangeGroupVolume(31596, -10);
SNS_Play(31596);

Was anderes würde die Funktion dann auch nicht machen…
Es wäre nur komplizierter, da sich die Funktion darum kümmern müsste, ob es sich um Gruppen oder einzel Boxen handelt und dann die Lautstärke dementsprechend regeln.

Die Lautstärke wird zusammen mit anderen Paramters periodisch abgefragt, und die die Volume Variable der Instanz geschrieben. Reicht das nicht?

GetValueInteger(IPS_GetVariableIDByName("Volume",31596 /*[Sonos_neu\Küche]*/));

Ich denke, dass es keinen Sinn macht, an dieser Stelle an ein anderes Modul zu verweisen.
Es ist dann einfach zu komliziert das ganze zu konbinieren…

Gruß,
Thorsten

Stimmt, es gibt ja eine Variable „Volume“ mit dem aktuellen Wert. Ich hab’s jetzt so gemacht:

$old_volume = GetValue(42499 /*[Erdgeschoss\Wohnzimmer\Sonos\Volume]*/);
SNS_Pause(56568);
SNS_SetVolume(56568 /*[Erdgeschoss\Wohnzimmer\Sonos]*/,50);
SNS_PlayFiles(56568 /*[Erdgeschoss\Wohnzimmer\Sonos]*/, Array( "//192.168.1.7/SonosSoundfiles/dog1.mp3"));
SNS_SetVolume(56568 /*[Erdgeschoss\Wohnzimmer\Sonos]*/,$old_volume);
SNS_Play(56568);

Vielen Dank :slight_smile:
Gruß
Peter

Hi,

nur ein kleiner Verbesserungsvorschlag, damit nicht plötzlich die Musik angeht:
Nur Pause und Play, wenn wenn vorher auch was lief:


$old_volume = GetValue(42499 /*[Erdgeschoss\Wohnzimmer\Sonos\Volume]*/);
$old_status = GetValueInteger(IPS_GetVariableIDByName("Status",56568 /*[Erdgeschoss\Wohnzimmer\Sonos]*/));

// only if sonos was playing
if( $old_status === 1 )
  SNS_Pause(56568);

SNS_SetVolume(56568 /*[Erdgeschoss\Wohnzimmer\Sonos]*/,50);
SNS_PlayFiles(56568 /*[Erdgeschoss\Wohnzimmer\Sonos]*/, Array( "//192.168.1.7/SonosSoundfiles/dog1.mp3"));
SNS_SetVolume(56568 /*[Erdgeschoss\Wohnzimmer\Sonos]*/,$old_volume);

// only if sonos was playing
if( $old_status === 1 )
  SNS_Play(56568);

Gruß,
Thorsten

Hi Thorsten,

vielen DANK für Deine Module IVONA und SONOS.

Bisher konnte ich nur einen „Fehler“ finden, mich irritiert, dass Du auch auf der V6 von Sonos bist und ihn nicht hast…

Selbst bei diesem Beispiel:

SNS_PlayFiles(31596 /*[Sonos_neu\Küche]*/ , array(IVNTTS_saveMP3(55186 /*[Ivona]*/,"Wau, wau!") ) );

kommt ein Fehler - wie hier im Thread schon beschrieben. Das File wird aber korrekt erzeugt und uach nach 15Min wieder gelöscht.

Fatal error: Uncaught exception ‚Exception‘ with message ‚Error sending command: HTTP/1.1 500 Internal Server Error CONTENT-LENGTH: 347 CONTENT-TYPE: text/xml; charset=„utf-8“ EXT: Server: Linux UPnP/1.0 Sonos/31.3-22220 (ZP120) Connection: close s:ClientUPnPError‘ in C:\IP-Symcon\modules\SymconSonos\Sonos\sonos.php:650 Stack trace: #0 C:\IP-Symcon\modules\SymconSonos\Sonos\sonos.php(370): PHPSonos->sendPacket(‚POST /MediaRend…‘) #1 C:\IP-Symcon\modules\SymconSonos\Sonos\module.php(428): PHPSonos->Play() #2 C:\IP-Symcon\scripts__generated.inc.php(56): Sonos->PlayFiles(Array) #3 C:\IP-Symcon\scripts\27874.ips.php(3): SNS_PlayFiles(57587, Array) #4 {main} thrown in C:\IP-Symcon\modules\SymconSonos\Sonos\sonos.php on line 650
Abort Processing during Fatal-Error: Uncaught exception ‚Exception‘ with message ‚Error sending command: HTTP/1.1 500 Internal Server Error CONTENT-LENGTH: 347 CONTENT-TYPE: text/xml; charset=„utf-8“ EXT: Server: Linux UPnP/1.0 Sonos/31.3-22220 (ZP120) Connection: close s:ClientUPnPError‘ in C:\IP-Symcon\modules\SymconSonos\Sonos\sonos.php:650 Stack trace: #0 C:\IP-Symcon\modules\SymconSonos\Sonos\sonos.php(370): PHPSonos->sendPacket(‚POST /MediaRend…‘) #1 C:\IP-Symcon\modules\SymconSonos\Sonos\module.php(428): PHPSonos->Play() #2 C:\IP-Symcon\scripts__generated.inc.php(56): Sonos->PlayFiles(Array) #3 C:\IP-Symcon\scripts\27874.ips.php(3): SNS_PlayFiles(57587, Array) #4 {main} thrown Error in Script C:\IP-Symcon\modules\SymconSonos\Sonos\sonos.php on Line 650

Update: Dieser Fehler tritt bei eine Play1 nicht auf, da verhält sich alles wie gewünscht. Fehler ist nur bei Verstärker (ZP120?) vorhanden.

DANK im Voraus
herbertf