Sonos Steuerung mit ESP8266 (Wemos D1 mini)

Ich besitze seit einiger Zeit 2 Sonos Play 1. Es ist wirklich sehr praktisch so unkompliziert in einem oder mehreren Räumen die selbe oder unterschiedliche Musik zu hören. Da ich mich sehr für Smart Home Technologien interessiere, habe ich gelesen, dass die Sonos Lautsprecher auch in ein Smart Home integriert werden können. KNX Sonos Komponenten, die dies später im Eigenheim realisieren könnten, sind jedoch recht teuer und können teilweise sogar nur das: Sonos Boxen steuern.

Das muss auch selber funktionieren, habe ich mir gedacht. Nach ein wenig Recherche wurde mir klar, dass das ganze per UPNP Kommandos funktioniert. Da bei UPNP jeder Hersteller sein eigenes Süppchen zu kochen scheint, funktioniert das nur mit ein bisschen Bastelei. Zuerst bin ich auf einen Node.JS Webserver von jishi (Link) gestoßen. Der kann quasi alles. Sehr interessant sind die Funktionen Say (Sprachausgabe eines Textes auf einem Speaker), SayAll (auf allen Speakern), ClipAll (Dateiwiedergabe auf allen Speakern, Clip (auf einem Speaker). Dafür würde ich aber einen Node.JS Server benötigen, der immer läuft. Habe ich aber nicht…

Dann kam mir die Idee:
Es muss doch irgendwie möglich sein, diese UPNP-Kommandos mit meinem Wemos D1 mini (Arduino kompatibler Microcontroller mit WLAN) zu erzeugen. Genauer gesagt wollte ich schließlich, dass mir jeden morgen beim Frühstücken vor der Arbeit automatisch zu einer bestimmten Zeit die aktuelle Außentemperatur und die zu erwartende Höchsttemperatur des Tages über eine der Sonos Play 1 mitgeteilt wird.

Folgende Komponenten/Hilfsmittel habe ich benutzt:

Der Microcontroller ruft alle 1,5 Minuten ein PHP-Script auf meinem Webserver auf, welches zurückgibt ob die richtige Uhrzeit zur Audioausgabe erreicht ist. Der Microcontroller hat keine RTC-Funktionalität, daher der Umweg. Wenn die Zeit erreicht ist, wird ein weiteres PHP-Script aufgerufen, welches die MP3-Erstellung durchführt und die Länge der Datei in Sekunden zurückgibt. Anschließend wird der aktuelle Status (aktuelle Wiedergabe gespeichert) und der Sonos Box die MP3 zur Wiedergabe gegeben. Nach der Länge + 1 Sekunde wird die vorherige Wiedergabe auf der Box wiederhergestellt. Es funktioniert! 🙂

So sieht das Arduino-Script aus.

#include <ESP8266WiFi.h>
#include <SPI.h>
#include <Event.h>
#include <Timer.h>
#include <Ethernet.h>
#include <SonosUPnP.h>
#include <MicroXPath.h>


#define SERIAL_DATA_THRESHOLD_MS 500
#define SERIAL_ERROR_TIMEOUT "E: Serial"
#define ETHERNET_ERROR_DHCP "E: DHCP"
#define ETHERNET_ERROR_CONNECT "E: Connect"
const char* WIFI_SSID = "***";
const char* WIFI_PW = "***";
const int LED_PIN = LED_BUILTIN;

int playMode;
byte volume;
int playState;
byte source;
TrackInfo trackInfo;
MediaInfo mediaInfo;
char trackuri[255] = "";
char trackmeta[1024] = "";
char curi[255] = "";
char curimeta[1024] = "";
long startmillis = 0;

Timer t;
void ethConnectError();

WiFiClient g_ethClient;
SonosUPnP g_sonos = SonosUPnP(g_ethClient, ethConnectError);

byte g_mac[] = {0x54, 0x48, 0x4F, 0x4D, 0x41, 0x53};
IPAddress g_ethernetStaticIP(192, 168, 178, 37);

// Kitchen
IPAddress g_sonosKitchenIP(192, 168, 178, 23);
const char g_sonosKitchenID[] = "000111111111";

void setup() {
  // put your setup code here, to run once:
  pinMode(LED_PIN, OUTPUT);
  
  WiFi.mode(WIFI_STA);
  WiFi.begin(WIFI_SSID, WIFI_PW);

  Serial.begin(115200);
  Serial.println();
  Serial.print("Connecting to " + String(WIFI_SSID));

  while (WiFi.status() != WL_CONNECTED) {
    digitalWrite(LED_PIN, HIGH);
    delay(200);
    digitalWrite(LED_PIN, LOW);
    delay(200);
    Serial.print(".");
  }
  digitalWrite(LED_PIN, LOW);
  Serial.println("Connected!");

  checkTime();
  //1,5 minutes
  t.every(90000, checkTime);
  // 3 days
  t.after(3 * 24 * 60 * 60 * 1000, restart);
}

void ethConnectError()
{
  Serial.println(ETHERNET_ERROR_CONNECT);
}

void loop() {
  t.update();
  delay(200);
}

void weather(IPAddress ip) {
  saveState(ip);
  if (playState != SONOS_STATE_PLAYING) return;
  WiFiClient client;
  if (!client.connect("***", 80)) {
      Serial.println("No server connection");
      return;
  }
  Serial.println("Recreating mp3-File");
  client.print("GET /sonos/create.php");
  client.println(" HTTP/1.1");
  client.println("Host: " + String("***"));
  client.println("Connection: close");
  client.println();
  unsigned long timeout = millis();
  String buffer;
  while (client.available() == 0) {
    if (millis() - timeout > 5000) {
      Serial.println(">>> Client Timeout !");
      client.stop();
      return;
    }
    yield();
  }
  while (client.available() != 0) {
    char c = client.read();
    
    buffer += c;
  }
  
  int duration = buffer.substring(buffer.lastIndexOf(":") + 1).toInt();
  Serial.print("duration:");
  Serial.println(duration);
  Serial.println("File recreated");
  g_sonos.playHttp(ip, "http://***/sonos/test.mp3");
  Serial.println("Play command sent");
  delay(duration*1000 + 1000);
  restoreState(ip);
}

void saveState(IPAddress ip) {
  Serial.println("--- SAVING STATE ---");
  playState = g_sonos.getState(ip);
  Serial.print("PlayState: ");
  Serial.println(playState);
  playMode = g_sonos.getPlayMode(ip);
  Serial.print("PlayMode: ");
  Serial.println(playMode);

  g_sonos.setPlayMode(ip, SONOS_PLAY_MODE_NORMAL);
  
  volume = g_sonos.getVolume(ip);
  Serial.print("Volume: ");
  Serial.println(volume);
  trackInfo = g_sonos.getTrackInfo(ip, trackuri, sizeof(trackuri), trackmeta, sizeof(trackmeta));
  Serial.print("TrackUri: ");
  Serial.println(trackInfo.trackuri);
  Serial.print("TrackNr: ");
  Serial.println(trackInfo.number);
  Serial.print("Position: ");
  Serial.println(trackInfo.position);
  mediaInfo = g_sonos.getMediaInfo(ip, curi, sizeof(curi), curimeta, sizeof(curimeta));
  Serial.print("CurrentUri: ");
  Serial.println(mediaInfo.uri);
  Serial.print("CurrentUriMeta: ");
  Serial.println(mediaInfo.uriMeta);
  source = g_sonos.getSource(ip);
  Serial.print("Source: ");
  Serial.println(source);
  Serial.println("--- STATE SAVED ---");
}

void restoreState(IPAddress ip) {
  Serial.println("--- RESTORING STATE ---");
  g_sonos.setPlayMode(ip, SONOS_PLAY_MODE_NORMAL);
  Serial.print("CurrentURI sent: ");
  Serial.println(mediaInfo.uri);
  Serial.print("CurrentUriMeta: ");
  Serial.println(mediaInfo.uriMeta);
  g_sonos.setAVTransportURI(ip, "", mediaInfo.uri, mediaInfo.uriMeta);
  
  g_sonos.setVolume(ip, volume);
  Serial.print("Volume: ");
  Serial.println(volume);
  Serial.print("TrackURI: ");
  Serial.println(trackInfo.trackuri);

  if (source != 3) {
    Serial.print("TrackNr: ");
    Serial.println(trackInfo.number);
    Serial.print("Position: ");
    Serial.println(trackInfo.position);
    g_sonos.seekTrack(ip, trackInfo.number);
    g_sonos.seekTime(ip, trackInfo.position / 60 / 60, trackInfo.position / 60, trackInfo.position);
  }
 
 
  Serial.print("Source: ");
  Serial.println(source);
  Serial.print("PlayMode: ");
  Serial.println(playMode);
  g_sonos.setPlayMode(ip, playMode);
  switch (playState) {
    case SONOS_STATE_PLAYING:
      g_sonos.play(ip);
      break;
    case SONOS_STATE_PAUSED:
      g_sonos.pause(ip);
      break;
    case SONOS_STATE_STOPPED:
      g_sonos.stop(ip);
      break;
  }
  Serial.println("--- STATE RESTORED ---");
}

bool isTriggerTime() {
  Serial.println("--- isTriggerTime ---");
  WiFiClient client;
  if (!client.connect("***", 80)) {
      Serial.println("No server connection");
      return 0;
  }
  
  client.println("GET /sonos/isTriggerTime.php HTTP/1.1");
  client.println("CONNECTION: close");
  //client.println("cache-control: no-cache");
  //client.println("Accept: */*");
  client.println("Host: ***");

  //client.println("content-length: 266");
  client.println();
  
  unsigned long timeout = millis();
  String buffer;
  while (client.available() == 0) {
    if (millis() - timeout > 5000) {
      Serial.println(">>> Client Timeout !");
      client.stop();
      return 0;
    }
    yield();
  }
  while (client.available() != 0) {
    char c = client.read();
    delay(1); 
    buffer += c;
  }
  Serial.println(buffer);
  return buffer.lastIndexOf("$") > 0;
}

void checkTime() {
  Serial.println("--- CHECKING TIME ---");
  digitalWrite(LED_PIN, HIGH);
  delay(200);
  digitalWrite(LED_PIN, LOW);
  delay(200);
  digitalWrite(LED_PIN, HIGH);
  delay(200);
  digitalWrite(LED_PIN, LOW);
  delay(200);
  if (isTriggerTime()) {
    weather(g_sonosKitchenIP);
  }
}

void restart() {
  ESP.restart();
}


bool isCommand(const char *command, byte b1, byte b2)
{
  return *command == b1 && *++command == b2;
}

So die verwendete angepasste SonosUPNP-Klasse:

/************************************************************************/
/* Sonos UPnP, an UPnP based read/write remote control library, v1.1.   */
/*                                                                      */
/* This library is free software: you can redistribute it and/or modify */
/* it under the terms of the GNU General Public License as published by */
/* the Free Software Foundation, either version 3 of the License, or    */
/* (at your option) any later version.                                  */
/*                                                                      */
/* This library is distributed in the hope that it will be useful, but  */
/* WITHOUT ANY WARRANTY; without even the implied warranty of           */
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU     */
/* General Public License for more details.                             */
/*                                                                      */
/* You should have received a copy of the GNU General Public License    */
/* along with this library. If not, see <http://www.gnu.org/licenses/>. */
/*                                                                      */
/* Written by Thomas Mittet (code@lookout.no) January 2015.             */
/************************************************************************/

#ifndef SonosUPnP_h
#define SonosUPnP_h

//#define SONOS_WRITE_ONLY_MODE

#include "Arduino.h"
//#include "avr/pgmspace.h"
#include "pgmspace.h"
#ifndef SONOS_WRITE_ONLY_MODE
#include "MicroXPath_P.h"
#endif
#include <ESP8266WiFi.h>

// HTTP:
#define HTTP_VERSION " HTTP/1.1\n"
#define HEADER_HOST "Host: %d.%d.%d.%d:%d\n"
#define HEADER_CONTENT_TYPE "Content-Type: text/xml; charset=\"utf-8\"\n"
#define HEADER_CONTENT_LENGTH "Content-Length: %d\n"
#define HEADER_SOAP_ACTION "SOAPAction: \"urn:"
#define HEADER_SOAP_ACTION_END "\"\n"
#define HEADER_CONNECTION "Connection: close\n"

// SOAP tag data:
#define SOAP_ENVELOPE_START "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
#define SOAP_ENVELOPE_END "</s:Envelope>"
#define SOAP_BODY_START "<s:Body>"
#define SOAP_BODY_END "</s:Body>"
#define SOAP_TAG_START "<%s>"
#define SOAP_TAG_END "</%s>"
#define SOAP_TAG_LEN 5
#define SOAP_TAG_ENVELOPE "s:Envelope"
#define SOAP_TAG_BODY "s:Body"

// UPnP config:
#define UPNP_PORT 1400
#define UPNP_MULTICAST_IP (byte[]) {239,255,255,250}
#define UPNP_MULTICAST_PORT 1900
#define UPNP_MULTICAST_TIMEOUT_S 2
#define UPNP_RESPONSE_TIMEOUT_MS 3000

// UPnP tag data:
#define SOAP_ACTION_START_TAG_START "<u:"
#define SOAP_ACTION_START_TAG_NS " xmlns:u=\"urn:"
#define SOAP_ACTION_START_TAG_END "\">"
#define SOAP_ACTION_END_TAG_START "</u:"
#define SOAP_ACTION_END_TAG_END ">"
#define SOAP_ACTION_TAG_LEN 24

// UPnP service data:
#define UPNP_URN_SCHEMA "schemas-upnp-org:service:"
#define UPNP_AV_TRANSPORT 1
#define UPNP_AV_TRANSPORT_SERVICE "AVTransport:1"
#define UPNP_AV_TRANSPORT_ENDPOINT "/MediaRenderer/AVTransport/Control"
#define UPNP_RENDERING_CONTROL 2
#define UPNP_RENDERING_CONTROL_SERVICE "RenderingControl:1"
#define UPNP_RENDERING_CONTROL_ENDPOINT "/MediaRenderer/RenderingControl/Control"
#define UPNP_DEVICE_PROPERTIES 3
#define UPNP_DEVICE_PROPERTIES_SERVICE "DeviceProperties:1"
#define UPNP_DEVICE_PROPERTIES_ENDPOINT "/DeviceProperties/Control"

// Sonos speaker state control:
/*
<u:Play>
  <InstanceID>0</InstanceID>
  <Speed>1</Speed>
</u:Play>
<u:Seek>
  <InstanceID>0</InstanceID>
  <Unit>REL_TIME</Unit>
  <Target>0:01:02</Target>
</u:Seek>
*/
#define SONOS_TAG_PLAY "Play"
#define SONOS_SOURCE_RINCON_TEMPLATE "RINCON_%s0%d%s"
#define SONOS_TAG_SPEED "Speed"
#define SONOS_TAG_STOP "Stop"
#define SONOS_TAG_PAUSE "Pause"
#define SONOS_TAG_PREVIOUS "Previous"
#define SONOS_TAG_NEXT "Next"
#define SONOS_DIRECTION_BACKWARD 0
#define SONOS_DIRECTION_FORWARD 1
#define SONOS_INSTANCE_ID_0_TAG "<InstanceID>0</InstanceID>"

#define SONOS_TAG_SEEK "Seek"
#define SONOS_TAG_TARGET "Target"
#define SONOS_SEEK_MODE_TAG_START "<Unit>"
#define SONOS_SEEK_MODE_TAG_END "</Unit>"
#define SONOS_SEEK_MODE_TRACK_NR "TRACK_NR"
#define SONOS_SEEK_MODE_REL_TIME "REL_TIME"
#define SONOS_TIME_FORMAT_TEMPLATE "%d:%02d:%02d"

#define SONOS_TAG_SET_AV_TRANSPORT_URI "SetAVTransportURI"
#define SONOS_TAG_CURRENT_URI "CurrentURI"
#define SONOS_URI_META_LIGHT_START "<CurrentURIMetaData>"
#define SONOS_URI_META_LIGHT_END "</CurrentURIMetaData>"
#define SONOS_RADIO_META_FULL_START "<CurrentURIMetaData><DIDL-Lite xmlns:dc=&quot;http://purl.org/dc/elements/1.1/&quot; xmlns:upnp=&quot;urn:schemas-upnp-org:metadata-1-0/upnp/&quot; xmlns:r=&quot;urn:schemas-rinconnetworks-com:metadata-1-0/&quot; xmlns=&quot;urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/&quot;><item id=&quot;R:0/0/46&quot; parentID=&quot;R:0/0&quot; restricted=&quot;true&quot;><dc:title>"
#define SONOS_RADIO_META_FULL_END "</dc:title><upnp:class>object.item.audioItem.audioBroadcast</upnp:class><desc id=&quot;cdudn&quot; nameSpace=&quot;urn:schemas-rinconnetworks-com:metadata-1-0/&quot;>SA_RINCON65031_</desc></item></DIDL-Lite></CurrentURIMetaData>"

#define SONOS_TAG_BECOME_COORDINATOR_OF_STANDALONE_GROUP "BecomeCoordinatorOfStandaloneGroup"

#define SONOS_TAG_SET_LED_STATE "SetLEDState"
#define SONOS_TAG_DESIRED_LED_STATE "DesiredLEDState"

// Playlist & Queue
/*
<u:AddURIToQueueResponse xmlns:u="urn:schemas-upnp-org:service:AVTransport:1">
  <FirstTrackNumberEnqueued>21</FirstTrackNumberEnqueued>
  <NumTracksAdded>36</NumTracksAdded>
  <NewQueueLength>56</NewQueueLength>
</u:AddURIToQueueResponse>
*/
#define SONOS_TAG_ADD_URI_TO_QUEUE "AddURIToQueue"
#define SONOS_TAG_ENQUEUED_URI "EnqueuedURI"
#define SONOS_SAVED_QUEUES "file:///jffs/settings/savedqueues.rsq#%d"
#define SONOS_TAG_REMOVE_ALL_TRACKS_FROM_QUEUE "RemoveAllTracksFromQueue"
#define SONOS_PLAYLIST_META_LIGHT_START "<EnqueuedURIMetaData></EnqueuedURIMetaData><DesiredFirstTrackNumberEnqueued>"
#define SONOS_PLAYLIST_META_LIGHT_END "0</DesiredFirstTrackNumberEnqueued><EnqueueAsNext>1</EnqueueAsNext>"
//#define SONOS_PLAYLIST_META_FULL_START "<EnqueuedURIMetaData><DIDL-Lite xmlns:dc=&quot;http://purl.org/dc/elements/1.1/&quot; xmlns:upnp=&quot;urn:schemas-upnp-org:metadata-1-0/upnp/&quot; xmlns:r=&quot;urn:schemas-rinconnetworks-com:metadata-1-0/&quot; xmlns=&quot;urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/&quot;><item id=&quot;SQ:0&quot; parentID=&quot;SQ:&quot; restricted=&quot;true&quot;><dc:title>"
//#define SONOS_PLAYLIST_META_FULL_END "</dc:title><upnp:class>object.container.playlistContainer</upnp:class><desc id=&quot;cdudn&quot; nameSpace=&quot;urn:schemas-rinconnetworks-com:metadata-1-0/&quot;>RINCON_AssociatedZPUDN</desc></item></DIDL-Lite></EnqueuedURIMetaData><DesiredFirstTrackNumberEnqueued>0</DesiredFirstTrackNumberEnqueued><EnqueueAsNext>1</EnqueueAsNext>"

// Track & source:
/*
<u:GetPositionInfoResponse>
  <Track>1</Track>
  <TrackDuration>0:03:21</TrackDuration>
  <TrackMetaData>[Meta data in DIDL-Lite]</TrackMetaData>
  <TrackURI></TrackURI>
  <RelTime>0:01:23</RelTime>
  <AbsTime>NOT_IMPLEMENTED</AbsTime>
  <RelCount>2147483647</RelCount>
  <AbsCount>2147483647</AbsCount>
</u:GetPositionInfoResponse>
*/
#define SONOS_TAG_GET_POSITION_INFO "GetPositionInfo"
#define SONOS_TAG_GET_POSITION_INFO_RESPONSE "u:GetPositionInfoResponse"
#define SONOS_TAG_TRACK "Track"
#define SONOS_TAG_TRACK_DURATION "TrackDuration"
#define SONOS_TAG_TRACK_URI "TrackURI"
#define SONOS_TAG_TRACK_META_DATA "TrackMetaData"
#define SONOS_TAG_REL_TIME "RelTime"
#define SONOS_SOURCE_UNKNOWN 0
#define SONOS_SOURCE_FILE 1
#define SONOS_SOURCE_HTTP 2
#define SONOS_SOURCE_RADIO 3
#define SONOS_SOURCE_LINEIN 4
#define SONOS_SOURCE_MASTER 5
#define SONOS_SOURCE_FILE_SCHEME "x-file-cifs:"
#define SONOS_SOURCE_HTTP_SCHEME "x-sonos-http:"
#define SONOS_SOURCE_RADIO_SCHEME "x-rincon-mp3radio:"
#define SONOS_SOURCE_RADIO_AAC_SCHEME "aac:"
#define SONOS_SOURCE_LINEIN_SCHEME "x-rincon-stream:"
#define SONOS_SOURCE_MASTER_SCHEME "x-rincon:"
#define SONOS_SOURCE_QUEUE_SCHEME "x-rincon-queue:"


/*
<u:GetMediaInfoResponse xmlns:u="urn:schemas-upnp-org:service:AVTransport:1">
<NrTracks>1</NrTracks>
<MediaDuration>NOT_IMPLEMENTED</MediaDuration>
<CurrentURI></CurrentURI>
<CurrentURIMetaData></CurrentURIMetaData>
<NextURI></NextURI>
<NextURIMetaData></NextURIMetaData>
<PlayMedium>NETWORK</PlayMedium>
<RecordMedium>NOT_IMPLEMENTED</RecordMedium>
<WriteStatus>NOT_IMPLEMENTED</WriteStatus>
</u:GetMediaInfoResponse>
*/
#define SONOS_TAG_GET_MEDIA_INFO "GetMediaInfo"
#define SONOS_TAG_GET_MEDIA_INFO_RESPONSE "u:GetMediaInfoResponse"
#define SONOS_TAG_NUMBER_TRACKS "NrTracks"
#define SONOS_TAG_URI "CurrentURI"
#define SONOS_TAG_URI_META "CurrentURIMetaData"

// Volume, bass & treble:
/*
<u:GetVolume>
  <InstanceID>0</InstanceID>
  <Channel>Master</Channel>
</u:GetVolume>
<u:GetVolumeResponse>
  <CurrentVolume>[0-100]</CurrentVolume>
</u:GetVolumeResponse>
*/
#define SONOS_TAG_CHANNEL "Channel"
#define SONOS_CHANNEL_MASTER "Master"
//#define SONOS_CHANNEL_LEFT "LF"
//#define SONOS_CHANNEL_RIGHT "RF"
#define SONOS_TAG_GET_MUTE "GetMute"
#define SONOS_TAG_GET_MUTE_RESPONSE "u:GetMuteResponse"
#define SONOS_TAG_CURRENT_MUTE "CurrentMute"
#define SONOS_TAG_GET_VOLUME "GetVolume"
#define SONOS_TAG_GET_VOLUME_RESPONSE "u:GetVolumeResponse"
#define SONOS_TAG_CURRENT_VOLUME "CurrentVolume"
#define SONOS_TAG_GET_OUTPUT_FIXED "GetOutputFixed"
#define SONOS_TAG_GET_FIXED_RESPONSE "u:GetOutputFixedResponse"
#define SONOS_TAG_CURRENT_FIXED "CurrentFixed"
#define SONOS_TAG_GET_BASS "GetBass"
#define SONOS_TAG_GET_BASS_RESPONSE "u:GetBassResponse"
#define SONOS_TAG_CURRENT_BASS "CurrentBass"
#define SONOS_TAG_GET_TREBLE "GetTreble"
#define SONOS_TAG_GET_TREBLE_RESPONSE "u:GetTrebleResponse"
#define SONOS_TAG_CURRENT_TREBLE "CurrentTreble"
#define SONOS_TAG_GET_LOUDNESS "GetLoudness"
#define SONOS_TAG_GET_LOUDNESS_RESPONSE "u:GetLoudnessResponse"
#define SONOS_TAG_CURRENT_LOUDNESS "CurrentLoudness"

// Set volume, bass & treble:
/*
<u:SetVolume>
  <InstanceID>0</InstanceID>
  <Channel>Master</Channel>
  <DesiredVolume>[0-100]</DesiredVolume>
</u:SetVolume>
*/
#define SONOS_TAG_SET_MUTE "SetMute"
#define SONOS_TAG_DESIRED_MUTE "DesiredMute"
#define SONOS_TAG_SET_VOLUME "SetVolume"
#define SONOS_TAG_DESIRED_VOLUME "DesiredVolume"
#define SONOS_TAG_SET_BASS "SetBass"
#define SONOS_TAG_DESIRED_BASS "DesiredBass"
#define SONOS_TAG_SET_TREBLE "SetTreble"
#define SONOS_TAG_DESIRED_TREBLE "DesiredTreble"
#define SONOS_TAG_SET_LOUDNESS "SetLoudness"
#define SONOS_TAG_DESIRED_LOUDNESS "DesiredLoudness"
#define SONOS_CHANNEL_TAG_START "<Channel>"
#define SONOS_CHANNEL_TAG_END "</Channel>"

// Play Mode:
/*
<u:GetTransportSettingsResponse>
  <PlayMode>[NORMAL/REPEAT_ALL/SHUFFLE/SHUFFLE_NOREPEAT]</PlayMode>
  <RecQualityMode>NOT_IMPLEMENTED</RecQualityMode>
</u:GetTransportSettingsResponse>
*/
#define SONOS_TAG_GET_TRANSPORT_SETTINGS "GetTransportSettings"
#define SONOS_TAG_GET_TRANSPORT_SETTINGS_RESPONSE "u:GetTransportSettingsResponse"
#define SONOS_TAG_PLAY_MODE "PlayMode"
#define SONOS_PLAY_MODE_NORMAL B00
#define SONOS_PLAY_MODE_NORMAL_VALUE "NORMAL"
#define SONOS_PLAY_MODE_REPEAT B01
#define SONOS_PLAY_MODE_REPEAT_VALUE "REPEAT_ALL"
#define SONOS_PLAY_MODE_SHUFFLE B10
#define SONOS_PLAY_MODE_SHUFFLE_VALUE "SHUFFLE_NOREPEAT"
#define SONOS_PLAY_MODE_SHUFFLE_REPEAT B11
#define SONOS_PLAY_MODE_SHUFFLE_REPEAT_VALUE "SHUFFLE"
// Set Play Mode:
#define SONOS_TAG_SET_PLAY_MODE "SetPlayMode"
#define SONOS_TAG_NEW_PLAY_MODE "NewPlayMode"

// State:
/*
<u:GetTransportInfoResponse>
  <CurrentTransportState>[PLAYING/PAUSED_PLAYBACK/STOPPED]</CurrentTransportState>
  <CurrentTransportStatus>[OK/ERROR]</CurrentTransportStatus>
  <CurrentSpeed>1</CurrentSpeed>
</u:GetTransportInfoResponse>
*/
#define SONOS_TAG_GET_TRANSPORT_INFO "GetTransportInfo"
#define SONOS_TAG_GET_TRANSPORT_INFO_RESPONSE "u:GetTransportInfoResponse"
#define SONOS_TAG_CURRENT_TRANSPORT_STATE "CurrentTransportState"
#define SONOS_STATE_PLAYING 1
#define SONOS_STATE_PLAYING_VALUE "PLAYING"
#define SONOS_STATE_PAUSED 2
#define SONOS_STATE_PAUSED_VALUE "PAUSED_PLAYBACK"
#define SONOS_STATE_STOPPED 3
#define SONOS_STATE_STOPPED_VALUE "STOPPED"

struct TrackInfo
{
  uint16_t number;
  uint32_t duration;
  uint32_t position;
  char *trackuri;
  char *trackmeta;
};

struct MediaInfo
{
  uint16_t numberTracks;
  char *uri;
  char *uriMeta;
};

class SonosUPnP
{

  public:

    SonosUPnP(WiFiClient client, void (*ethernetErrCallback)(void));

    void setAVTransportURI(IPAddress speakerIP, const char *scheme, const char *address);
	void setAVTransportURI(IPAddress speakerIP, const char *scheme, const char *address, const char *metaValue);
    void seekTrack(IPAddress speakerIP, uint16_t index);
    void seekTime(IPAddress speakerIP, uint8_t hour, uint8_t minute, uint8_t second);
    void setPlayMode(IPAddress speakerIP, uint8_t playMode);
    void play(IPAddress speakerIP);
    void playFile(IPAddress speakerIP, const char *path);
    void playHttp(IPAddress speakerIP, const char *address);
    void playRadio(IPAddress speakerIP, const char *address, const char *title);
    void playLineIn(IPAddress speakerIP, const char *speakerID);
    void playQueue(IPAddress speakerIP, const char *speakerID);
    void playConnectToMaster(IPAddress speakerIP, const char *masterSpeakerID);
    void disconnectFromMaster(IPAddress speakerIP);
    void stop(IPAddress speakerIP);
    void pause(IPAddress speakerIP);
    void skip(IPAddress speakerIP, uint8_t direction);
    void setMute(IPAddress speakerIP, bool state);
    void setVolume(IPAddress speakerIP, uint8_t volume);
    void setVolume(IPAddress speakerIP, uint8_t volume, const char *channel);
    void setBass(IPAddress speakerIP, int8_t bass);
    void setTreble(IPAddress speakerIP, int8_t treble);
    void setLoudness(IPAddress speakerIP, bool state);
    void setStatusLight(IPAddress speakerIP, bool state);
    void addPlaylistToQueue(IPAddress speakerIP, uint16_t playlistIndex);
    void addTrackToQueue(IPAddress speakerIP, const char *scheme, const char *address);
    void removeAllTracksFromQueue(IPAddress speakerIP);
    
    #ifndef SONOS_WRITE_ONLY_MODE
    
    void setRepeat(IPAddress speakerIP, bool repeat);
    void setShuffle(IPAddress speakerIP, bool shuffle);
    void toggleRepeat(IPAddress speakerIP);
    void toggleShuffle(IPAddress speakerIP);
    void togglePause(IPAddress speakerIP);
    void toggleMute(IPAddress speakerIP);
    void toggleLoudness(IPAddress speakerIP);
    uint8_t getState(IPAddress speakerIP);
    uint8_t getPlayMode(IPAddress speakerIP);
    bool getRepeat(IPAddress speakerIP);
    bool getShuffle(IPAddress speakerIP);
    TrackInfo getTrackInfo(IPAddress speakerIP, char *trackUriBuffer, size_t trackUriBufferSize, char *trackMetaBuffer, size_t trackMetaBufferSize);
	MediaInfo getMediaInfo(IPAddress speakerIP, char *uriBuffer, size_t uriBufferSize, char *curimetaBuffer, size_t curimetaBufferSize);
    uint16_t getTrackNumber(IPAddress speakerIP);
    void getTrackURI(IPAddress speakerIP, char *resultBuffer, size_t resultBufferSize);
    uint8_t getSource(IPAddress speakerIP);
    uint8_t getSourceFromURI(const char *uri);
    uint32_t getTrackDurationInSeconds(IPAddress speakerIP);
    uint32_t getTrackPositionInSeconds(IPAddress speakerIP);
    uint16_t getTrackPositionPerMille(IPAddress speakerIP);
    bool getMute(IPAddress speakerIP);
    uint8_t getVolume(IPAddress speakerIP);
    uint8_t getVolume(IPAddress speakerIP, const char *channel);
    bool getOutputFixed(IPAddress speakerIP);
    int8_t getBass(IPAddress speakerIP);
    int8_t getTreble(IPAddress speakerIP);
    bool getLoudness(IPAddress speakerIP);
    
    #endif

  private:

    WiFiClient ethClient;

	void setAVTransportURI(IPAddress speakerIP, const char *scheme, const char *address, PGM_P metaStart_P, PGM_P metaEnd_P, const char *metaValue);
    void (*ethernetErrCallback)(void);
    void seek(IPAddress speakerIP, const char *mode, const char *data);
    void upnpSet(IPAddress ip, uint8_t upnpMessageType, PGM_P action_P);
    void upnpSet(IPAddress ip, uint8_t upnpMessageType, PGM_P action_P, const char *field, const char *value);
    void upnpSet(IPAddress ip, uint8_t upnpMessageType, PGM_P action_P, const char *field, const char *valueA, const char *valueB, PGM_P extraStart_P, PGM_P extraEnd_P, const char *extraValue);
    bool upnpPost(IPAddress ip, uint8_t upnpMessageType, PGM_P action_P, const char *field, const char *valueA, const char *valueB, PGM_P extraStart_P, PGM_P extraEnd_P, const char *extraValue);
    const char *getUpnpService(uint8_t upnpMessageType);
    const char *getUpnpEndpoint(uint8_t upnpMessageType);
    void ethClient_write(const char *data);
    void ethClient_write_P(PGM_P data_P, char *buffer, size_t bufferSize);
    void ethClient_stop();

    #ifndef SONOS_WRITE_ONLY_MODE

    MicroXPath_P xPath;
    void ethClient_xPath(PGM_P *path, uint8_t pathSize, char *resultBuffer, size_t resultBufferSize);
    void upnpGetString(IPAddress speakerIP, uint8_t upnpMessageType, PGM_P action_P, const char *field, const char *value, PGM_P *path, uint8_t pathSize, char *resultBuffer, size_t resultBufferSize);
    uint32_t getTimeInSeconds(const char *time);
    uint32_t uiPow(uint16_t base, uint16_t exp);
    uint8_t convertState(const char *input);
    uint8_t convertPlayMode(const char *input);

    #endif
};

#endif

//#ifdef ESP8266  //has no strlcpy_P

/* size_t ICACHE_FLASH_ATTR strlcpy_P(char* dest, const char* src, size_t size) {
    const char* read = src;
    char* write = dest;

*/

/************************************************************************/
/* Sonos UPnP, an UPnP based read/write remote control library, v1.1.   */
/*                                                                      */
/* This library is free software: you can redistribute it and/or modify */
/* it under the terms of the GNU General Public License as published by */
/* the Free Software Foundation, either version 3 of the License, or    */
/* (at your option) any later version.                                  */
/*                                                                      */
/* This library is distributed in the hope that it will be useful, but  */
/* WITHOUT ANY WARRANTY; without even the implied warranty of           */
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU     */
/* General Public License for more details.                             */
/*                                                                      */
/* You should have received a copy of the GNU General Public License    */
/* along with this library. If not, see <http://www.gnu.org/licenses/>. */
/*                                                                      */
/* Written by Thomas Mittet (code@lookout.no) January 2015.             */
/************************************************************************/

#include "SonosUPnP.h"

const char p_HttpVersion[] PROGMEM = HTTP_VERSION;
const char p_HeaderHost[] PROGMEM = HEADER_HOST;
const char p_HeaderContentType[] PROGMEM = HEADER_CONTENT_TYPE;
const char p_HeaderContentLength[] PROGMEM = HEADER_CONTENT_LENGTH;
const char p_HeaderSoapAction[] PROGMEM = HEADER_SOAP_ACTION;
const char p_HeaderConnection[] PROGMEM = HEADER_CONNECTION;

const char p_SoapEnvelopeStart[] PROGMEM = SOAP_ENVELOPE_START;
const char p_SoapEnvelopeEnd[] PROGMEM = SOAP_ENVELOPE_END;
const char p_SoapBodyStart[] PROGMEM = SOAP_BODY_START;
const char p_SoapBodyEnd[] PROGMEM = SOAP_BODY_END;
const char p_SoapEnvelope[] PROGMEM = SOAP_TAG_ENVELOPE;
const char p_SoapBody[] PROGMEM = SOAP_TAG_BODY;

const char p_UpnpUrnSchema[] PROGMEM = UPNP_URN_SCHEMA;
const char p_UpnpAvTransportService[] PROGMEM = UPNP_AV_TRANSPORT_SERVICE;
const char p_UpnpAvTransportEndpoint[] PROGMEM = UPNP_AV_TRANSPORT_ENDPOINT;
const char p_UpnpRenderingControlService[] PROGMEM = UPNP_RENDERING_CONTROL_SERVICE;
const char p_UpnpRenderingControlEndpoint[] PROGMEM = UPNP_RENDERING_CONTROL_ENDPOINT;
const char p_UpnpDevicePropertiesService[] PROGMEM = UPNP_DEVICE_PROPERTIES_SERVICE;
const char p_UpnpDevicePropertiesEndpoint[] PROGMEM = UPNP_DEVICE_PROPERTIES_ENDPOINT;

const char p_Play[] PROGMEM = SONOS_TAG_PLAY;
const char p_SourceRinconTemplate[] PROGMEM = SONOS_SOURCE_RINCON_TEMPLATE;
const char p_Stop[] PROGMEM = SONOS_TAG_STOP;
const char p_Pause[] PROGMEM = SONOS_TAG_PAUSE;
const char p_Previous[] PROGMEM = SONOS_TAG_PREVIOUS;
const char p_Next[] PROGMEM = SONOS_TAG_NEXT;
const char p_InstenceId0Tag[] PROGMEM = SONOS_INSTANCE_ID_0_TAG;
const char p_Seek[] PROGMEM = SONOS_TAG_SEEK;
const char p_SeekModeTagStart[] PROGMEM = SONOS_SEEK_MODE_TAG_START;
const char p_SeekModeTagEnd[] PROGMEM = SONOS_SEEK_MODE_TAG_END;
const char p_TimeFormatTemplate[] PROGMEM = SONOS_TIME_FORMAT_TEMPLATE;
const char p_SetAVTransportURI[] PROGMEM = SONOS_TAG_SET_AV_TRANSPORT_URI;
const char p_UriMetaLightStart[] PROGMEM = SONOS_URI_META_LIGHT_START;
const char p_UriMetaLightEnd[] PROGMEM = SONOS_URI_META_LIGHT_END;
const char p_RadioMetaFullStart[] PROGMEM = SONOS_RADIO_META_FULL_START;
const char p_RadioMetaFullEnd[] PROGMEM = SONOS_RADIO_META_FULL_END;
const char p_BecomeCoordinatorOfStandaloneGroup[] PROGMEM = SONOS_TAG_BECOME_COORDINATOR_OF_STANDALONE_GROUP;
const char p_SetLEDState[] PROGMEM = SONOS_TAG_SET_LED_STATE;

const char p_AddURIToQueue[] PROGMEM = SONOS_TAG_ADD_URI_TO_QUEUE;
const char p_SavedQueues[] PROGMEM = SONOS_SAVED_QUEUES;
const char p_RemoveAllTracksFromQueue[] PROGMEM = SONOS_TAG_REMOVE_ALL_TRACKS_FROM_QUEUE;
const char p_PlaylistMetaLightStart[] PROGMEM = SONOS_PLAYLIST_META_LIGHT_START;
const char p_PlaylistMetaLightEnd[] PROGMEM = SONOS_PLAYLIST_META_LIGHT_END;

const char p_GetPositionInfoA[] PROGMEM = SONOS_TAG_GET_POSITION_INFO;
const char p_GetPositionInfoR[] PROGMEM = SONOS_TAG_GET_POSITION_INFO_RESPONSE;
const char p_Track[] PROGMEM = SONOS_TAG_TRACK;
const char p_TrackDuration[] PROGMEM = SONOS_TAG_TRACK_DURATION;
const char p_TrackURI[] PROGMEM = SONOS_TAG_TRACK_URI;
const char p_TrackMetaData[] PROGMEM = SONOS_TAG_TRACK_META_DATA;
const char p_RelTime[] PROGMEM = SONOS_TAG_REL_TIME;

const char p_GetMediaInfoA[] PROGMEM = SONOS_TAG_GET_MEDIA_INFO;
const char p_GetMediaInfoR[] PROGMEM = SONOS_TAG_GET_MEDIA_INFO_RESPONSE;
const char p_NumberTracks[] PROGMEM = SONOS_TAG_NUMBER_TRACKS;
const char p_URI[] PROGMEM = SONOS_TAG_URI;
const char p_URIMeta[] PROGMEM = SONOS_TAG_URI_META;

const char p_GetMuteA[] PROGMEM = SONOS_TAG_GET_MUTE;
const char p_GetMuteR[] PROGMEM = SONOS_TAG_GET_MUTE_RESPONSE;
const char p_CurrentMute[] PROGMEM = SONOS_TAG_CURRENT_MUTE;
const char p_GetVolumeA[] PROGMEM = SONOS_TAG_GET_VOLUME;
const char p_GetVolumeR[] PROGMEM = SONOS_TAG_GET_VOLUME_RESPONSE;
const char p_CurrentVolume[] PROGMEM = SONOS_TAG_CURRENT_VOLUME;
const char p_GetOutputFixedA[] PROGMEM = SONOS_TAG_GET_OUTPUT_FIXED;
const char p_GetOutputFixedR[] PROGMEM = SONOS_TAG_GET_FIXED_RESPONSE;
const char p_CurrentFixed[] PROGMEM = SONOS_TAG_CURRENT_FIXED;
const char p_GetBassA[] PROGMEM = SONOS_TAG_GET_BASS;
const char p_GetBassR[] PROGMEM = SONOS_TAG_GET_BASS_RESPONSE;
const char p_CurrentBass[] PROGMEM = SONOS_TAG_CURRENT_BASS;
const char p_GetTrebleA[] PROGMEM = SONOS_TAG_GET_TREBLE;
const char p_GetTrebleR[] PROGMEM = SONOS_TAG_GET_TREBLE_RESPONSE;
const char p_CurrentTreble[] PROGMEM = SONOS_TAG_CURRENT_TREBLE;
const char p_GetLoudnessA[] PROGMEM = SONOS_TAG_GET_LOUDNESS;
const char p_GetLoudnessR[] PROGMEM = SONOS_TAG_GET_LOUDNESS_RESPONSE;
const char p_CurrentLoudness[] PROGMEM = SONOS_TAG_CURRENT_LOUDNESS;

const char p_SetMute[] PROGMEM = SONOS_TAG_SET_MUTE;
const char p_SetVolume[] PROGMEM = SONOS_TAG_SET_VOLUME;
const char p_SetBass[] PROGMEM = SONOS_TAG_SET_BASS;
const char p_SetTreble[] PROGMEM = SONOS_TAG_SET_TREBLE;
const char p_SetLoudness[] PROGMEM = SONOS_TAG_SET_LOUDNESS;
const char p_ChannelTagStart[] PROGMEM = SONOS_CHANNEL_TAG_START;
const char p_ChannelTagEnd[] PROGMEM = SONOS_CHANNEL_TAG_END;

const char p_GetTransportSettingsA[] PROGMEM = SONOS_TAG_GET_TRANSPORT_SETTINGS;
const char p_GetTransportSettingsR[] PROGMEM = SONOS_TAG_GET_TRANSPORT_SETTINGS_RESPONSE;
const char p_PlayMode[] PROGMEM = SONOS_TAG_PLAY_MODE;
const char p_SetPlayMode[] PROGMEM = SONOS_TAG_SET_PLAY_MODE;

const char p_GetTransportInfoA[] PROGMEM = SONOS_TAG_GET_TRANSPORT_INFO;
const char p_GetTransportInfoR[] PROGMEM = SONOS_TAG_GET_TRANSPORT_INFO_RESPONSE;
const char p_CurrentTransportState[] PROGMEM = SONOS_TAG_CURRENT_TRANSPORT_STATE;

SonosUPnP::SonosUPnP(WiFiClient client, void (*ethernetErrCallback)(void))
{
  #ifndef SONOS_WRITE_ONLY_MODE
  this->xPath = MicroXPath_P();
  #endif
  this->ethClient = client;
  this->ethernetErrCallback = ethernetErrCallback;
}


void SonosUPnP::setAVTransportURI(IPAddress speakerIP, const char *scheme, const char *address)
{
  setAVTransportURI(speakerIP, scheme, address, p_UriMetaLightStart, p_UriMetaLightEnd, "");
}

void SonosUPnP::setAVTransportURI(IPAddress speakerIP, const char *scheme, const char *address, const char *metaValue)
{
	setAVTransportURI(speakerIP, scheme, address, p_UriMetaLightStart, p_UriMetaLightEnd, metaValue);
}

void SonosUPnP::seekTrack(IPAddress speakerIP, uint16_t index)
{
  char indexChar[6];
  itoa(index, indexChar, 10);
  seek(speakerIP, SONOS_SEEK_MODE_TRACK_NR, indexChar);
}

void SonosUPnP::seekTime(IPAddress speakerIP, uint8_t hour, uint8_t minute, uint8_t second)
{
  char time[11];
  sprintf_P(time, p_TimeFormatTemplate, hour, minute, second);
  seek(speakerIP, SONOS_SEEK_MODE_REL_TIME, time);
}

void SonosUPnP::setPlayMode(IPAddress speakerIP, uint8_t playMode)
{
  const char *playModeValue;
  switch (playMode)
  {
    case SONOS_PLAY_MODE_REPEAT:
      playModeValue = SONOS_PLAY_MODE_REPEAT_VALUE;
      break;
    case SONOS_PLAY_MODE_SHUFFLE_REPEAT:
      playModeValue = SONOS_PLAY_MODE_SHUFFLE_REPEAT_VALUE;
      break;
    case SONOS_PLAY_MODE_SHUFFLE:
      playModeValue = SONOS_PLAY_MODE_SHUFFLE_VALUE;
      break;
    default:
      playModeValue = SONOS_PLAY_MODE_NORMAL_VALUE;
      break;
  }
  upnpSet(speakerIP, UPNP_AV_TRANSPORT, p_SetPlayMode, SONOS_TAG_NEW_PLAY_MODE, playModeValue);
}

void SonosUPnP::play(IPAddress speakerIP)
{
	Serial.println("sending 'play' to " + speakerIP.toString());
  upnpSet(speakerIP, UPNP_AV_TRANSPORT, p_Play, SONOS_TAG_SPEED, "1");
}

void SonosUPnP::playFile(IPAddress speakerIP, const char *path)
{
  setAVTransportURI(speakerIP, SONOS_SOURCE_FILE_SCHEME, path);
  play(speakerIP);
}

void SonosUPnP::playHttp(IPAddress speakerIP, const char *address)
{
  // "x-sonos-http:" does not work for me etAVTransportURI(speakerIP, SONOS_SOURCE_HTTP_SCHEME, address);
  setAVTransportURI(speakerIP, "", address);
  play(speakerIP);
  play(speakerIP);
}

void SonosUPnP::playRadio(IPAddress speakerIP, const char *address, const char *title)
{
  setAVTransportURI(speakerIP, SONOS_SOURCE_RADIO_SCHEME, address, p_RadioMetaFullStart, p_RadioMetaFullEnd, title);
  play(speakerIP);
}

void SonosUPnP::playLineIn(IPAddress speakerIP, const char *speakerID)
{
  char address[30];
  sprintf_P(address, p_SourceRinconTemplate, speakerID, UPNP_PORT, "");
  setAVTransportURI(speakerIP, SONOS_SOURCE_LINEIN_SCHEME, address);
  play(speakerIP);
}

void SonosUPnP::playQueue(IPAddress speakerIP, const char *speakerID)
{
  char address[30];
  sprintf_P(address, p_SourceRinconTemplate, speakerID, UPNP_PORT, "#0");
  setAVTransportURI(speakerIP, SONOS_SOURCE_QUEUE_SCHEME, address);
  play(speakerIP);
}

void SonosUPnP::playConnectToMaster(IPAddress speakerIP, const char *masterSpeakerID)
{
  char address[30];
  sprintf_P(address, p_SourceRinconTemplate, masterSpeakerID, UPNP_PORT, "");
  setAVTransportURI(speakerIP, SONOS_SOURCE_MASTER_SCHEME, address);
}

void SonosUPnP::disconnectFromMaster(IPAddress speakerIP)
{
  upnpSet(speakerIP, UPNP_AV_TRANSPORT, p_BecomeCoordinatorOfStandaloneGroup);
}

void SonosUPnP::stop(IPAddress speakerIP)
{
  upnpSet(speakerIP, UPNP_AV_TRANSPORT, p_Stop);
}

void SonosUPnP::pause(IPAddress speakerIP)
{
  upnpSet(speakerIP, UPNP_AV_TRANSPORT, p_Pause);
}

void SonosUPnP::skip(IPAddress speakerIP, uint8_t direction)
{
  upnpSet(
    speakerIP, UPNP_AV_TRANSPORT, direction == SONOS_DIRECTION_FORWARD ? p_Next : p_Previous);
}

void SonosUPnP::setMute(IPAddress speakerIP, bool state)
{
  upnpSet(
    speakerIP, UPNP_RENDERING_CONTROL, p_SetMute,
    SONOS_TAG_DESIRED_MUTE, state ? "1" : "0", "", p_ChannelTagStart, p_ChannelTagEnd, SONOS_CHANNEL_MASTER);
}

void SonosUPnP::setVolume(IPAddress speakerIP, uint8_t volume)
{
  setVolume(speakerIP, volume, SONOS_CHANNEL_MASTER);
}

void SonosUPnP::setVolume(IPAddress speakerIP, uint8_t volume, const char *channel)
{
  if (volume > 100) volume = 100;
  char volumeChar[4];
  itoa(volume, volumeChar, 10);
  upnpSet(
    speakerIP, UPNP_RENDERING_CONTROL, p_SetVolume,
    SONOS_TAG_DESIRED_VOLUME, volumeChar, "", p_ChannelTagStart, p_ChannelTagEnd, channel);
}

void SonosUPnP::setBass(IPAddress speakerIP, int8_t bass)
{
  bass = constrain(bass, -10, 10);
  char bassChar[4];
  itoa(bass, bassChar, 10);
  upnpSet(
    speakerIP, UPNP_RENDERING_CONTROL, p_SetBass,
    SONOS_TAG_DESIRED_BASS, bassChar);
}

void SonosUPnP::setTreble(IPAddress speakerIP, int8_t treble)
{
  treble = constrain(treble, -10, 10);
  char trebleChar[4];
  itoa(treble, trebleChar, 10);
  upnpSet(
    speakerIP, UPNP_RENDERING_CONTROL, p_SetTreble,
    SONOS_TAG_DESIRED_TREBLE, trebleChar);
}

void SonosUPnP::setLoudness(IPAddress speakerIP, bool state)
{
  upnpSet(
    speakerIP, UPNP_RENDERING_CONTROL, p_SetLoudness,
    SONOS_TAG_DESIRED_LOUDNESS, state ? "1" : "0", "", p_ChannelTagStart, p_ChannelTagEnd, SONOS_CHANNEL_MASTER);
}

void SonosUPnP::setStatusLight(IPAddress speakerIP, bool state)
{
  upnpSet(
    speakerIP, UPNP_DEVICE_PROPERTIES, p_SetLEDState,
    SONOS_TAG_DESIRED_LED_STATE, state ? "On" : "Off");
}

void SonosUPnP::addPlaylistToQueue(IPAddress speakerIP, uint16_t playlistIndex)
{
  char path[45];
  sprintf_P(path, p_SavedQueues, playlistIndex);
  addTrackToQueue(speakerIP, "", path);
}

void SonosUPnP::addTrackToQueue(IPAddress speakerIP, const char *scheme, const char *address)
{
  upnpSet(
    speakerIP, UPNP_AV_TRANSPORT, p_AddURIToQueue,
    SONOS_TAG_ENQUEUED_URI, scheme, address, p_PlaylistMetaLightStart, p_PlaylistMetaLightEnd, "");
}

void SonosUPnP::removeAllTracksFromQueue(IPAddress speakerIP)
{
  upnpSet(speakerIP, UPNP_AV_TRANSPORT, p_RemoveAllTracksFromQueue);
}


#ifndef SONOS_WRITE_ONLY_MODE

void SonosUPnP::setRepeat(IPAddress speakerIP, bool repeat)
{
  bool current = getRepeat(speakerIP);
  if (repeat != current)
  {
    setPlayMode(speakerIP, current ^ SONOS_PLAY_MODE_REPEAT);
  }
}

void SonosUPnP::setShuffle(IPAddress speakerIP, bool shuffle)
{
  bool current = getShuffle(speakerIP);
  if (shuffle != current)
  {
    setPlayMode(speakerIP, current ^ SONOS_PLAY_MODE_SHUFFLE);
  }
}

void SonosUPnP::toggleRepeat(IPAddress speakerIP)
{
  setPlayMode(speakerIP, getPlayMode(speakerIP) ^ SONOS_PLAY_MODE_REPEAT);
}

void SonosUPnP::toggleShuffle(IPAddress speakerIP)
{
  setPlayMode(speakerIP, getPlayMode(speakerIP) ^ SONOS_PLAY_MODE_SHUFFLE);
}

void SonosUPnP::togglePause(IPAddress speakerIP)
{
  uint8_t state = getState(speakerIP);
  if (state == SONOS_STATE_PLAYING)
  {
    pause(speakerIP);
  }
  else if (state == SONOS_STATE_PAUSED)
  {
    play(speakerIP);
  }
}

void SonosUPnP::toggleMute(IPAddress speakerIP)
{
  setMute(speakerIP, !getMute(speakerIP));
}

void SonosUPnP::toggleLoudness(IPAddress speakerIP)
{
  setLoudness(speakerIP, !getLoudness(speakerIP));
}

uint8_t SonosUPnP::getState(IPAddress speakerIP)
{
  PGM_P path[] = { p_SoapEnvelope, p_SoapBody, p_GetTransportInfoR, p_CurrentTransportState };
  //             { p_SoapEnvelope, p_SoapBody, p_GetTransportInfoR, p_CurrentSpeed };
  char result[sizeof(SONOS_STATE_PAUSED_VALUE)] = "";
  upnpGetString(speakerIP, UPNP_AV_TRANSPORT, p_GetTransportInfoA, "", "", path, 4, result, sizeof(result));
  return convertState(result);
}

uint8_t SonosUPnP::getPlayMode(IPAddress speakerIP)
{
  PGM_P path[] = { p_SoapEnvelope, p_SoapBody, p_GetTransportSettingsR, p_PlayMode };
  char result[sizeof(SONOS_PLAY_MODE_SHUFFLE_VALUE)] = "";
  upnpGetString(speakerIP, UPNP_AV_TRANSPORT, p_GetTransportSettingsA, "", "", path, 4, result, sizeof(result));
  return convertPlayMode(result);
}

bool SonosUPnP::getRepeat(IPAddress speakerIP)
{
  return getPlayMode(speakerIP) & SONOS_PLAY_MODE_REPEAT;
}

bool SonosUPnP::getShuffle(IPAddress speakerIP)
{
  return getPlayMode(speakerIP) & SONOS_PLAY_MODE_SHUFFLE;
}

TrackInfo SonosUPnP::getTrackInfo(IPAddress speakerIP, char *trackUriBuffer, size_t trackUriBufferSize, char *trackMetaBuffer, size_t trackMetaBufferSize)
{
  TrackInfo trackInfo;
  if (upnpPost(speakerIP, UPNP_AV_TRANSPORT, p_GetPositionInfoA, "", "", "", 0, 0, ""))
  {
    xPath.reset();
    char infoBuffer[20] = "";
    // Track number
    PGM_P npath[] = { p_SoapEnvelope, p_SoapBody, p_GetPositionInfoR, p_Track };
    ethClient_xPath(npath, 4, infoBuffer, sizeof(infoBuffer));
    trackInfo.number = atoi(infoBuffer);
	// Track URI
	PGM_P upath[] = { p_SoapEnvelope, p_SoapBody, p_GetPositionInfoR, p_TrackURI };
	ethClient_xPath(upath, 4, trackUriBuffer, trackUriBufferSize);
	trackInfo.trackuri = trackUriBuffer;
    // Track duration
    PGM_P dpath[] = { p_SoapEnvelope, p_SoapBody, p_GetPositionInfoR, p_TrackDuration };
    ethClient_xPath(dpath, 4, infoBuffer, sizeof(infoBuffer));
    trackInfo.duration = getTimeInSeconds(infoBuffer);
	// Track MetaData
	PGM_P mpath[] = { p_SoapEnvelope, p_SoapBody, p_GetPositionInfoR, p_TrackMetaData };
	ethClient_xPath(mpath, 4, trackMetaBuffer, trackMetaBufferSize);
	trackInfo.trackmeta = trackMetaBuffer;
    // Track position
    PGM_P ppath[] = { p_SoapEnvelope, p_SoapBody, p_GetPositionInfoR, p_RelTime };
    ethClient_xPath(ppath, 4, infoBuffer, sizeof(infoBuffer));
    trackInfo.position = getTimeInSeconds(infoBuffer);
  }
  ethClient_stop();
  return trackInfo;
}

MediaInfo SonosUPnP::getMediaInfo(IPAddress speakerIP, char *curiBuffer, size_t curiBufferSize, char *curimetaBuffer, size_t curimetaBufferSize)
{
	MediaInfo mediaInfo;
	if (upnpPost(speakerIP, UPNP_AV_TRANSPORT, p_GetMediaInfoA, "", "", "", 0, 0, ""))
	{
		xPath.reset();
		char trackBuffer[20] = "";
		// Track number
		PGM_P npath[] = { p_SoapEnvelope, p_SoapBody, p_GetMediaInfoR, p_NumberTracks };
		ethClient_xPath(npath, 4, trackBuffer, sizeof(trackBuffer));
		mediaInfo.numberTracks = atoi(trackBuffer);
		// Current URI
		PGM_P upath[] = { p_SoapEnvelope, p_SoapBody, p_GetMediaInfoR, p_URI };
		ethClient_xPath(upath, 4, curiBuffer, curiBufferSize);
		mediaInfo.uri = curiBuffer;
		// CurrentURIMeta
		PGM_P mpath[] = { p_SoapEnvelope, p_SoapBody, p_GetMediaInfoR, p_URIMeta };
		ethClient_xPath(mpath, 4, curimetaBuffer, curimetaBufferSize);
		mediaInfo.uriMeta = curimetaBuffer;
	}
	ethClient_stop();
	return mediaInfo;
}

uint16_t SonosUPnP::getTrackNumber(IPAddress speakerIP)
{
  PGM_P path[] = { p_SoapEnvelope, p_SoapBody, p_GetPositionInfoR, p_Track };
  char result[6] = "0";
  upnpGetString(speakerIP, UPNP_AV_TRANSPORT, p_GetPositionInfoA, "", "", path, 4, result, sizeof(result));
  return atoi(result);
}

void SonosUPnP::getTrackURI(IPAddress speakerIP, char *resultBuffer, size_t resultBufferSize)
{
  PGM_P path[] = { p_SoapEnvelope, p_SoapBody, p_GetPositionInfoR, p_TrackURI };
  upnpGetString(speakerIP, UPNP_AV_TRANSPORT, p_GetPositionInfoA, "", "", path, 4, resultBuffer, resultBufferSize);
}

uint8_t SonosUPnP::getSource(IPAddress speakerIP)
{
  char uri[25] = "";
  getTrackURI(speakerIP, uri, sizeof(uri));
  return getSourceFromURI(uri);
}

uint8_t SonosUPnP::getSourceFromURI(const char *uri)
{
  if (!strncmp(SONOS_SOURCE_FILE_SCHEME, uri, sizeof(SONOS_SOURCE_FILE_SCHEME) - 1))
  {
    return SONOS_SOURCE_FILE;
  }
  if (!strncmp(SONOS_SOURCE_HTTP_SCHEME, uri, sizeof(SONOS_SOURCE_HTTP_SCHEME) - 1))
  {
    return SONOS_SOURCE_HTTP;
  }
  if (!strncmp(SONOS_SOURCE_RADIO_SCHEME, uri, sizeof(SONOS_SOURCE_RADIO_SCHEME) - 1))
  {
    return SONOS_SOURCE_RADIO;
  }
  if (!strncmp(SONOS_SOURCE_RADIO_AAC_SCHEME, uri, sizeof(SONOS_SOURCE_RADIO_AAC_SCHEME) - 1))
  {
    return SONOS_SOURCE_RADIO;
  }
  if (!strncmp(SONOS_SOURCE_MASTER_SCHEME, uri, sizeof(SONOS_SOURCE_MASTER_SCHEME) - 1))
  {
    return SONOS_SOURCE_MASTER;
  }
  if (!strncmp(SONOS_SOURCE_LINEIN_SCHEME, uri, sizeof(SONOS_SOURCE_LINEIN_SCHEME) - 1))
  {
    return SONOS_SOURCE_LINEIN;
  }
  return SONOS_SOURCE_UNKNOWN;
}

uint32_t SonosUPnP::getTrackDurationInSeconds(IPAddress speakerIP)
{
  PGM_P path[] = { p_SoapEnvelope, p_SoapBody, p_GetPositionInfoR, p_TrackDuration };
  char result[20] = "";
  upnpGetString(speakerIP, UPNP_AV_TRANSPORT, p_GetPositionInfoA, "", "", path, 4, result, sizeof(result));
  return getTimeInSeconds(result);
}

uint32_t SonosUPnP::getTrackPositionInSeconds(IPAddress speakerIP)
{
  PGM_P path[] = { p_SoapEnvelope, p_SoapBody, p_GetPositionInfoR, p_RelTime };
  char result[20] = "";
  upnpGetString(speakerIP, UPNP_AV_TRANSPORT, p_GetPositionInfoA, "", "", path, 4, result, sizeof(result));
  return getTimeInSeconds(result);
}

uint16_t SonosUPnP::getTrackPositionPerMille(IPAddress speakerIP)
{
  uint16_t perMille = 0;
  if (upnpPost(speakerIP, UPNP_AV_TRANSPORT, p_GetPositionInfoA, "", "", "", 0, 0, ""))
  {
    char result[20];
    xPath.reset();
    PGM_P dpath[] = { p_SoapEnvelope, p_SoapBody, p_GetPositionInfoR, p_TrackDuration };
    ethClient_xPath(dpath, 4, result, sizeof(result));
    uint32_t duration = getTimeInSeconds(result);
    PGM_P ppath[] = { p_SoapEnvelope, p_SoapBody, p_GetPositionInfoR, p_RelTime };
    ethClient_xPath(ppath, 4, result, sizeof(result));
    uint32_t position = getTimeInSeconds(result);
    if (duration && position)
    {
      perMille = (position * 1000) / duration;
    }
  }
  ethClient_stop();
  return perMille;
}

bool SonosUPnP::getMute(IPAddress speakerIP)
{
  PGM_P path[] = { p_SoapEnvelope, p_SoapBody, p_GetMuteR, p_CurrentMute };
  char result[3] = "0";
  upnpGetString(
    speakerIP, UPNP_RENDERING_CONTROL, p_GetMuteA,
    SONOS_TAG_CHANNEL, SONOS_CHANNEL_MASTER, path, 4, result, sizeof(result));
  return strcmp(result, "1") == 0;
}

uint8_t SonosUPnP::getVolume(IPAddress speakerIP)
{
  getVolume(speakerIP, SONOS_CHANNEL_MASTER);
}

uint8_t SonosUPnP::getVolume(IPAddress speakerIP, const char *channel)
{
  PGM_P path[] = { p_SoapEnvelope, p_SoapBody, p_GetVolumeR, p_CurrentVolume };
  char result[5] = "0";
  upnpGetString(
    speakerIP, UPNP_RENDERING_CONTROL, p_GetVolumeA,
    SONOS_TAG_CHANNEL, channel, path, 4, result, sizeof(result));
  return constrain(atoi(result), 0, 100);
}

bool SonosUPnP::getOutputFixed(IPAddress speakerIP)
{
  PGM_P path[] = { p_SoapEnvelope, p_SoapBody, p_GetOutputFixedR, p_CurrentFixed };
  char result[3] = "0";
  upnpGetString(speakerIP, UPNP_RENDERING_CONTROL, p_GetOutputFixedA, "", "", path, 4, result, sizeof(result));
  return strcmp(result, "1") == 0;
}

int8_t SonosUPnP::getBass(IPAddress speakerIP)
{
  PGM_P path[] = { p_SoapEnvelope, p_SoapBody, p_GetBassR, p_CurrentBass };
  char result[5] = "0";
  upnpGetString(
    speakerIP, UPNP_RENDERING_CONTROL, p_GetBassA,
    SONOS_TAG_CHANNEL, SONOS_CHANNEL_MASTER, path, 4, result, sizeof(result));
  return constrain(atoi(result), -10, 10);
}

int8_t SonosUPnP::getTreble(IPAddress speakerIP)
{
  PGM_P path[] = { p_SoapEnvelope, p_SoapBody, p_GetTrebleR, p_CurrentTreble };
  char result[5] = "0";
  upnpGetString(
    speakerIP, UPNP_RENDERING_CONTROL, p_GetTrebleA,
    SONOS_TAG_CHANNEL, SONOS_CHANNEL_MASTER, path, 4, result, sizeof(result));
  return constrain(atoi(result), -10, 10);
}

bool SonosUPnP::getLoudness(IPAddress speakerIP)
{
  PGM_P path[] = { p_SoapEnvelope, p_SoapBody, p_GetLoudnessR, p_CurrentLoudness };
  char result[3] = "0";
  upnpGetString(
    speakerIP, UPNP_RENDERING_CONTROL, p_GetLoudnessA,
    SONOS_TAG_CHANNEL, SONOS_CHANNEL_MASTER, path, 4, result, sizeof(result));
  return strcmp(result, "1") == 0;
}

#endif


void SonosUPnP::seek(IPAddress speakerIP, const char *mode, const char *data)
{
  upnpSet(
    speakerIP, UPNP_AV_TRANSPORT, p_Seek,
    SONOS_TAG_TARGET, data, "", p_SeekModeTagStart, p_SeekModeTagEnd, mode);
}

void SonosUPnP::setAVTransportURI(IPAddress speakerIP, const char *scheme, const char *address, PGM_P metaStart_P, PGM_P metaEnd_P, const char *metaValue)
{
  // Info to show in player, in DIDL format, can be added as META data
  upnpSet(
    speakerIP, UPNP_AV_TRANSPORT, p_SetAVTransportURI,
    SONOS_TAG_CURRENT_URI, scheme, address, metaStart_P, metaEnd_P, metaValue);
}

void SonosUPnP::upnpSet(IPAddress ip, uint8_t upnpMessageType, PGM_P action_P)
{
  upnpSet(ip, upnpMessageType, action_P, "", "");
}

void SonosUPnP::upnpSet(IPAddress ip, uint8_t upnpMessageType, PGM_P action_P, const char *field, const char *value)
{
  upnpSet(ip, upnpMessageType, action_P, field, value, "", 0, 0, "");
}

void SonosUPnP::upnpSet(IPAddress ip, uint8_t upnpMessageType, PGM_P action_P, const char *field, const char *valueA, const char *valueB, PGM_P extraStart_P, PGM_P extraEnd_P, const char *extraValue)
{
  upnpPost(ip, upnpMessageType, action_P, field, valueA, valueB, extraStart_P, extraEnd_P, extraValue);
  ethClient_stop();
}

bool SonosUPnP::upnpPost(IPAddress ip, uint8_t upnpMessageType, PGM_P action_P, const char *field, const char *valueA, const char *valueB, PGM_P extraStart_P, PGM_P extraEnd_P, const char *extraValue)
{
  if (!ethClient.connect(ip, UPNP_PORT)) 
 {
  Serial.println("we did'nt got a connection");
  return false;
  }
  //Serial.print("UPNPPOST CURRENTURIMETA:");
  //Serial.println(extraValue);
  // Get UPnP service name
  PGM_P upnpService = getUpnpService(upnpMessageType);

  // Get HTTP content/body length
  uint16_t contentLength =
    sizeof(SOAP_ENVELOPE_START) - 1 +
    sizeof(SOAP_BODY_START) - 1 +
    SOAP_ACTION_TAG_LEN +
    (strlen_P(action_P) * 2) +
    sizeof(UPNP_URN_SCHEMA) - 1 +
    strlen_P(upnpService) +
    sizeof(SONOS_INSTANCE_ID_0_TAG) - 1 +
    sizeof(SOAP_BODY_END) - 1 +
    sizeof(SOAP_ENVELOPE_END) - 1;

  // Get length of field
  uint8_t fieldLength = strlen(field);
  if (fieldLength)
  {
    contentLength +=
      SOAP_TAG_LEN +
      (fieldLength * 2) +
      strlen(valueA) +
      strlen(valueB);
  }

  // Get length of extra field data (e.g. meta data fields)
  if (extraStart_P)
  {
    contentLength +=
      strlen_P(extraStart_P) +
      strlen(extraValue) +
      strlen_P(extraEnd_P);
  }

  char buffer[1800];

  // Write HTTP start
  ethClient_write("POST ");
  ethClient_write_P(getUpnpEndpoint(upnpMessageType), buffer, sizeof(buffer));
  ethClient_write_P(p_HttpVersion, buffer, sizeof(buffer));

  // Write HTTP header
  sprintf_P(buffer, p_HeaderHost, ip[0], ip[1], ip[2], ip[3], UPNP_PORT); // 29 bytes max
  ethClient_write(buffer);
  ethClient_write_P(p_HeaderContentType, buffer, sizeof(buffer));
  sprintf_P(buffer, p_HeaderContentLength, contentLength); // 23 bytes max
  ethClient_write(buffer);
  ethClient_write_P(p_HeaderSoapAction, buffer, sizeof(buffer));
  ethClient_write_P(p_UpnpUrnSchema, buffer, sizeof(buffer));
  ethClient_write_P(upnpService, buffer, sizeof(buffer));
  ethClient_write("#");
  ethClient_write_P(action_P, buffer, sizeof(buffer));
  ethClient_write(HEADER_SOAP_ACTION_END);
  ethClient_write_P(p_HeaderConnection, buffer, sizeof(buffer));
  ethClient_write("\n");

  // Write HTTP body
  ethClient_write_P(p_SoapEnvelopeStart, buffer, sizeof(buffer));
  ethClient_write_P(p_SoapBodyStart, buffer, sizeof(buffer));
  ethClient_write(SOAP_ACTION_START_TAG_START);
  ethClient_write_P(action_P, buffer, sizeof(buffer));
  ethClient_write(SOAP_ACTION_START_TAG_NS);
  ethClient_write_P(p_UpnpUrnSchema, buffer, sizeof(buffer));
  ethClient_write_P(upnpService, buffer, sizeof(buffer));
  ethClient_write(SOAP_ACTION_START_TAG_END);
  ethClient_write_P(p_InstenceId0Tag, buffer, sizeof(buffer));
  if (fieldLength)
  {
    sprintf(buffer, SOAP_TAG_START, field); // 18 bytes
    ethClient_write(buffer);
	if (strlen(valueA) > 0) { ethClient_write(valueA); }
    ethClient_write(valueB);
    sprintf(buffer, SOAP_TAG_END, field); // 19 bytes
    ethClient_write(buffer);
  }
  if (extraStart_P)
  {
    ethClient_write_P(extraStart_P, buffer, sizeof(buffer)); // 390 bytes
    ethClient_write(extraValue);
    ethClient_write_P(extraEnd_P, buffer, sizeof(buffer)); // 271 bytes
  }
  ethClient_write(SOAP_ACTION_END_TAG_START);
  ethClient_write_P(action_P, buffer, sizeof(buffer)); // 35 bytes
  ethClient_write(SOAP_ACTION_END_TAG_END);
  ethClient_write_P(p_SoapBodyEnd, buffer, sizeof(buffer)); // 10 bytes
  ethClient_write_P(p_SoapEnvelopeEnd, buffer, sizeof(buffer)); // 14 bytes

  uint32_t start = millis();
  while (!ethClient.available())
  {
    if (millis() > (start + UPNP_RESPONSE_TIMEOUT_MS))
    {
      if (ethernetErrCallback) ethernetErrCallback();
      return false;
    }
  }
  return true;
}

PGM_P SonosUPnP::getUpnpService(uint8_t upnpMessageType)
{
  switch (upnpMessageType)
  {
    case UPNP_AV_TRANSPORT: return p_UpnpAvTransportService;
    case UPNP_RENDERING_CONTROL: return p_UpnpRenderingControlService;
    case UPNP_DEVICE_PROPERTIES: return p_UpnpDevicePropertiesService;
  }
}

PGM_P SonosUPnP::getUpnpEndpoint(uint8_t upnpMessageType)
{
  switch (upnpMessageType)
  {
    case UPNP_AV_TRANSPORT: return p_UpnpAvTransportEndpoint;
    case UPNP_RENDERING_CONTROL: return p_UpnpRenderingControlEndpoint;
    case UPNP_DEVICE_PROPERTIES: return p_UpnpDevicePropertiesEndpoint;
  }
}

void SonosUPnP::ethClient_write(const char *data)
{
  //Serial.println(data);
  ethClient.print(data);
}


//ToDo ESP8266 brings its own write_P, we better use this one
void SonosUPnP::ethClient_write_P(PGM_P data_P, char *buffer, size_t bufferSize)
{
  uint16_t dataLen = strlen_P(data_P);
  uint16_t dataPos = 0;
  while (dataLen > dataPos)
  {	
	//  *((char *)mempcpy(dst, src, n)) = '\0'; 
   //https://en.wikibooks.org/wiki/C_Programming/C_Reference/nonstandard/strlcpy
   //memcpy_P(buffer, data_P + dataPos, bufferSize); 
   strncpy_P(buffer, data_P + dataPos, bufferSize);
    //strlcpy_P(buffer, data_P + dataPos, bufferSize);
    //Serial.println(buffer);
    ethClient.print(buffer);
    dataPos += bufferSize - 1;
  }
}

void SonosUPnP::ethClient_stop()
{
  if (ethClient)
  {
    while (ethClient.available()) ethClient.read();
    ethClient.stop();
  }
}


#ifndef SONOS_WRITE_ONLY_MODE

void SonosUPnP::ethClient_xPath(PGM_P *path, uint8_t pathSize, char *resultBuffer, size_t resultBufferSize)
{
  xPath.setPath(path, pathSize);
  //while (ethClient.available() && !xPath.getValue(ethClient.read(), resultBuffer, resultBufferSize));
  while (true) {
	  if (!ethClient.available() || xPath.getValue(ethClient.read(), resultBuffer, resultBufferSize)) {
		  break;
	  }
	  if (resultBufferSize > 20) delay(1);
  }
}

void SonosUPnP::upnpGetString(IPAddress speakerIP, uint8_t upnpMessageType, PGM_P action_P, const char *field, const char *value, PGM_P *path, uint8_t pathSize, char *resultBuffer, size_t resultBufferSize)
{
  if (upnpPost(speakerIP, upnpMessageType, action_P, field, value, "", 0, 0, ""))
  {
    xPath.reset();
    ethClient_xPath(path, pathSize, resultBuffer, resultBufferSize);
  }
  ethClient_stop();
}

uint32_t SonosUPnP::getTimeInSeconds(const char *time)
{
  uint8_t len = strlen(time);
  uint32_t seconds = 0;
  uint8_t dPower = 0;
  uint8_t tPower = 0;
  for (int8_t i = len; i > 0; i--)
  {
    char character = time[i - 1];
    if (character == ':')
    {
      dPower = 0;
      tPower++;
    }
    else if(character >= '0' && character <= '9')
    {
      seconds += (character - '0') * uiPow(10, dPower) * uiPow(60, tPower);
      dPower++;
    }
  }  
  return seconds;
}

uint32_t SonosUPnP::uiPow(uint16_t base, uint16_t exponent)
{
  int result = 1;
  while (exponent)
  {
    if (exponent & 1) result *= base;
    exponent >>= 1;
    base *= base;
  }
  return result;
}

uint8_t SonosUPnP::convertState(const char *input)
{
  if (strcmp(input, SONOS_STATE_PLAYING_VALUE) == 0) return SONOS_STATE_PLAYING;
  if (strcmp(input, SONOS_STATE_PAUSED_VALUE) == 0)  return SONOS_STATE_PAUSED;
  return SONOS_STATE_STOPPED;
}

uint8_t SonosUPnP::convertPlayMode(const char *input)
{
  if (strcmp(input, SONOS_PLAY_MODE_NORMAL_VALUE) == 0)         return SONOS_PLAY_MODE_NORMAL;
  if (strcmp(input, SONOS_PLAY_MODE_REPEAT_VALUE) == 0)         return SONOS_PLAY_MODE_REPEAT;
  if (strcmp(input, SONOS_PLAY_MODE_SHUFFLE_REPEAT_VALUE) == 0) return SONOS_PLAY_MODE_SHUFFLE_REPEAT;
  if (strcmp(input, SONOS_PLAY_MODE_SHUFFLE_VALUE) == 0)        return SONOS_PLAY_MODE_SHUFFLE;
  return SONOS_PLAY_MODE_NORMAL;
}

#endif

Und hier die PHP-Scripte:

<?php
$date = Date("H:i");
$dateparts = explode(":", $date);

if ($dateparts[0] == 05 && $dateparts[1] >= 49 && $dateparts[1] <= 50) {
	echo "$";
}

?>
<?php

$currentTempUrl = "https://opendata.dwd.de/weather/weather_reports/poi/10502-BEOB.csv";
$maxTempUrl = "https://opendata.dwd.de/weather/local_forecasts/poi/10502-MOSMIX.csv"; // Nörvenich
//$maxTempUrl = "https://opendata.dwd.de/weather/local_forecasts/poi/H718_-MOSMIX.csv"; // Jülich

function getCurrentTemp() {
	global $currentTempUrl;
	$currentTemp = null;
	settype($currentTemp, "float");
	$handle = fopen($currentTempUrl, 'r');
	if ($handle) {
		fgets($handle);
		fgets($handle);
		fgets($handle);
		$line = fgets($handle);
		$cols = explode(";", $line);
		$currentTemp = $cols[9];
		fclose($handle);
	} else {
		$currentTemp = "Fehler";
	}
	return $currentTemp;
}

function getMaxTemp() {
	global $maxTempUrl;
	$maxTemp = -400;
	$todayfound = false;
	$handle = fopen($maxTempUrl, 'r');
	if ($handle) {
		fgets($handle);
		fgets($handle);
		fgets($handle);
		while (true) {
			$line = fgets($handle);
			$cols = explode(";", $line);
			if ($cols[0] == date("d.m.y")) {
				$todayfound = true;
				$t = trim($cols[2]);
				
				if ($t * 10 > $maxTemp * 10){
					$maxTemp = $t;
				}
			} else {
				if ($todayfound) break; 
			}
		}
		fclose($handle);
	} else {
		$maxTemp = "Fehler";
	} 
	return $maxTemp;
}

?>
<?php
require("mp3file.class.php");
require("weather.php");

$currentTemp = null;
$maxTemp = null;
settype($currentTemp, "float");
settype($maxTemp, "float");
$currentTemp = getCurrentTemp();
$maxTemp = getMaxTemp();

$text = "Die Außentemperatur beträgt zur Zeit ".$currentTemp."° und erreicht heute ein Maximum von ".$maxTemp."°. ";

echo $text;
$testurl = "http://translate.google.com/translate_tts?client=tw-ob&tl=de&ie=utf-8&q=";

file_put_contents("test.mp3", fopen($testurl.str_replace(" ", "%20",$text), 'r'));
$mp3file = new MP3File("test.mp3");
echo "duration:".$mp3file->getDurationEstimate();
?>

Ich arbeite gerade noch daran, dass mir zusätzlich die bisherige Verspätung meiner Zugverbindung mitgeteilt wird. Die Funktion ist aber noch in Arbeit und muss noch weiter getestet werden.

Categories: Arduino, PHP