Sonos trigger signal for B&O.

This small project is able to turn on and off a pair of Beolab Penta and one Beolab 2 subwoofer.
Depending on, if a specific Sonos Connect is play or not.

The setup can control eny B&O speakers connected using Powerlink MKII. And is easy to convert to a 12 volt amplifier trigger signal, if you need to use it with other amplifier types.

The develpment board used is a Feather 32u4 Basic Proto and a Ethernet FeatherWing from Adafruit.

The wireing is really easy with the B&O speakers. Because they only need between 2,5-5V for the amplifier to turn on.
And the Adafruit Feather 32u4 supply 3V from it´s output.

In this case I connected the output 2 from the controller to PIN 4 on powerlink connector and PIN 7 to the ground on the development board.

Here after, it´s just the two developmentbords that needs to be asambled. And thay can only be put together one way. So this is a no brainer...

Next step is to download the Arduino IDE for programming the micro controller and
follow the adafruit instuction to connect the bord. Copy and paste the code below and correct the IP adress for the Sonos Connect that you need to detect the paying status of.
I have used the ethernet connection on the back of the Sonos Connect to the micro controller becurese this serves as an extension of my network over WIFI.

/* sonosTrigSig is used for turning on amplefires when an specifig Sonos CONNECT is playing */

// Setup sektion*****************************************************************************

// Library needed.
  #include <SPI.h>       //enable SPI communication to ethernet shield
  #include <Ethernet.h>  //enable the ethernet commands some how..!?
  #include <stdio.h>
 
// Initialize the Ethernet client library
  EthernetClient client;

//Constants
  const PROGMEM String sonosConURL = "192.168.1.5";                          // IP adress for the Sonos connect of interest.
  const PROGMEM int ledPin = 13;                                            // LED connected to digital pin 13
  const PROGMEM int trigPin = 2;                                            // LED connected to digital pin 2


//Global Variabels:
  byte  macAdress[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };               // MAC address for ethernet shield.
  int trigSig;                                                              //
  unsigned long lastConnectionTime = 0;                                     // last time you connected to the server, in milliseconds
  unsigned long postingInterval = 10L * 1000L;                              // delay between updates, in milliseconds
  byte URLHOST[4];  
  String httpResponce;                                                      // contains the respone from the sonos.
  String requestPointer;

void setup() {

//only used in debugging mode.
  while (!Serial){; // wait for serial port to connect if debug is on!!!!!
      #define debugOn // used if debugging is needed.
      Serial.begin(9600);
      writeDebugToMon(F("sonosTrigSig debug started! Wellcome..."), false);
      if (millis() > 10000){
        break;  
      }
  }

// Configur board outputs
  pinMode(ledPin, OUTPUT);      // sets the digital pin as output
  pinMode(trigPin, OUTPUT);      // sets the digital pin as output
 
 
  dhcpRetry:
  writeDebugToMon(F("Initialize ethernet"), false);
     
  delay(1000); // give the ethernet module time to boot up:

  writeDebugToMon(F("Whaiting for IP adress from DHCP server"), false);
  // start the Ethernet connection:
  if (Ethernet.begin(macAdress) == 0) {
    writeDebugToMon(F("Failed to configure Ethernet using DHCP"), false);
    writeDebugToMon(F("Retrying DHCP connection"), false);
    goto dhcpRetry;
  }else{
    char ip[16];
    String stringTest;
    sprintf(ip, "Assined adrress: %d.%d.%d.%d", Ethernet.localIP()[0],Ethernet.localIP()[1],Ethernet.localIP()[2],Ethernet.localIP()[3]);
    stringTest = ip;
    writeDebugToMon(stringTest, false);
  }
  setURLHOST(sonosConURL);
}

//Program sektion****************************************************************************
void loop() {

  // if ten seconds have passed since your last connection,
  // then connect again and send data:
  if (trigSig == 0) {
    postingInterval = 1.2L * 1000L;
    digitalWrite(ledPin, LOW);   // sets the LED off
    digitalWrite(trigPin, LOW);   // turn speakers off
  }else{
    postingInterval = 10L * 1000L;
    digitalWrite(ledPin, HIGH);   // sets the LED on
    digitalWrite(trigPin, HIGH);   // turn speakers on
  }
 
// if script stock in an responce pointer we will retry after 30 sek
   if ((millis() - lastConnectionTime > (30L * 1000L)) and requestPointer != "") {
    houseKeeping();
    writeDebugToMon(F("requestPointer has been reset due to requst time out?"), false);
   }

  if (millis() - lastConnectionTime > postingInterval and requestPointer == "") {
    requstPlyingStatus();
  }
 
  // reaciving responce from Sonos
  while (client.available()) {
    char c = client.read();
    String d;
    d = c;
    httpResponce.concat(c);
    //writeDebugToMon(d, true);
   
    if (c == 32 or c == 62){
      if (requestPointer == "Requsting" and httpResponce.startsWith("IdleState"))  {
        requestPointer = "IdleStateNext";  
        Serial.println(httpResponce);
        httpResponce.remove(0, 16);    
      }else if (requestPointer == "IdleStateNext"){
        requestPointer = "IdleStateChecked";  
        if (httpResponce.startsWith("0")){
          writeDebugToMon("Sonos Playing!", false);
            trigSig = 1;
          }else{
            writeDebugToMon("Sonos not Playing?", false);
            trigSig = 0;
          }
      }else if(requestPointer == "IdleStateChecked" and httpResponce.endsWith("</s:Envelope>")){
        writeDebugToMon("Responce from Sonos done.", false);
        houseKeeping();
      }else {
        Serial.println(httpResponce);
        httpResponce.remove(0);
      }
    }
  }
}

//Function sektion***************************************************************************

//-- Convert string to byte for the ethernet to use.
void setURLHOST(String URLCordinatorString){
  for (int urlConvert = 0; urlConvert < 4; urlConvert++) {
    URLHOST[urlConvert] = URLCordinatorString.substring(0,URLCordinatorString.indexOf(".")).toInt();
    URLCordinatorString.remove(0,URLCordinatorString.indexOf(".") + 1);
  }
}

//-- The requeste for playing status:
void requstPlyingStatus(){
  if (client.connect(URLHOST, 1400)) {
    writeDebugToMon(F("Connected to Sonos, sending request for coordinator status:"), false);
    client.println(F("POST /ZoneGroupTopology/Control HTTP/1.1"));
    client.println(F("Connection: close" ));
    client.println(F("ACCEPT-ENCODING: gzip"));
    client.println("Host: "+sonosConURL+":1400");
    client.println(F("USER-AGENT: Linux UPnP/1.0 Sonos/29.5-90191 (WDCR:Microsoft Windows NT 6.1.7601 Service Pack 1)"));
    client.println(F("Content-Length: 289"));
    client.println(F("Content-Type: text/xml; charset=\"utf-8\""));
    client.println(F("Soapaction: \"urn:schemas-upnp-org:service:ZoneGroupTopology:1#GetZoneGroupState\""));
    client.println();
    client.println(F("<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\"><s:Body><u:GetZoneGroupState xmlns:u=\"urn:schemas-upnp-org:service:ZoneGroupTopology:1\"><InstanceID>0</InstanceID></u:GetZoneGroupState></s:Body></s:Envelope>"));
    writeDebugToMon(F("Request send, wating on responce"), false);
    requestPointer ="Requsting";
  }
  else {
    writeDebugToMon(F("Connecting attemt to Sonos failed"), false);
  }
  lastConnectionTime = millis();  
}


//-- House keeping
void houseKeeping(){
  client.stop();
  httpResponce.remove(0);
  requestPointer = "";
}

//-- Write to monitor if debug is on.
void writeDebugToMon(String text, bool responce){
  #ifdef debugOn
  if (responce){
    Serial.print(text);
  }else{
    Serial.println("");
    Serial.print(F("ADPS DEBUG --> "));
    Serial.println(text);
  }
  #endif
}