Welcome to Hackster!
Hackster is a community dedicated to learning hardware, from beginner to pro. Join us, it's free!
Waldemar Sakalus
Published © LGPL

Sonos, Alexa, Denon Ultimate Integration

Automating my Sonos: Connect with Denon AVR-3805 receiver through Alexa and a serial controller.

IntermediateFull instructions provided4 hours3,169
Sonos, Alexa, Denon Ultimate Integration

Things used in this project

Hardware components

Photon
Particle Photon
×1
optocoupler 4n35
×1
680ohm, 220ohm, 10kohm resistors
×1
LED (generic)
LED (generic)
×1

Software apps and online services

AWS Lambda
Amazon Web Services AWS Lambda

Story

Read more

Schematics

RS-232 to Photon

12V trigger via optocoupler

Code

Lambda code

JavaScript
var https = require('https');
var radioControllerId = "SAKA_Denon_12072017";
var radioZ3ControllerId = "SAKA_Denon_Z3_12282017"; //bedroom
var radioZMControllerId = "SAKA_Denon_MZ_12282017"; //living
var particleServer = "api.particle.io";
var particlePath = "/v1/devices/";
var particleId = "put your particle device id";
var powerOn = "PWON";
var powerOff = "PWSTANDBY";
var volumeUp = "MVUP";
var volumeDown = "MVDOWN";
var setVolumeToXXLevel = "MV"; //append in back volume level MVxxx
var muteEnable = "MUON";
var muteDisable = "MUOFF";
//zone 3 commands below
var z3powerOn = "Z1ON";
var z3powerOff = "Z1OFF";
var z3volumeUp = "Z1UP";
var z3volumeDown = "Z1DOWN";
var z3setVolumeToXXLevel = "Z1"; //append in back volume level Z1xxx
var z3muteEnable = "Z1ON";
var z3muteDisable = "Z1OFF";
//main zone
var ZMpowerOn = "ZMON";
var ZMpowerOff = "ZMOFF";

exports.handler = function(event, context) {
    log('Input', event);
    switch (event.directive.header.namespace) {
    
    /**
    * The namespace of "Discovery" indicates a request is being made to the lambda for
    * discovering all appliances associated with the customer's appliance cloud account.
    * can use the accessToken that is made available as part of the payload to determine
    * the customer.
    */
    case 'Alexa.Discovery':
        handleDiscovery(event, context);
        break;
    case 'Alexa.PowerController':
        handleControl(event, context);
        break;

    case 'Alexa.Speaker':
        handleControl(event, context);
        break;

    case 'Alexa.InputController':
        handleControl(event, context);
        break;

    case 'Alexa.ChannelController':
        handleControl(event, context);
        break;
        
    case 'Alexa.PlaybackController':
        handleControl(event, context);
        break;

    default:
        log('Err', 'No supported namespace: ' + event.directive.header.namespace);
        context.fail('Something went wrong');
        break;
    }
};
/**
 * This method is invoked when we receive a "Discovery" message from Alexa Connected Home Skill.
 * We are expected to respond back with a list of appliances that we have discovered for a given
 * customer. 
 */
function handleDiscovery(event, context) {
    var payload = {
        "endpoints":[
            {
            "endpointId": radioControllerId,
            "friendlyName": "Receiver",
            "description": "Denon AVR-3805 Alexa Echo controller",
            "manufacturerName": "SAKA",
            "displayCategories": ["OTHER"],
            "cookie": {
                "deviceId" : particleId
                },
            "capabilities": [
            {
                "type": "AlexaInterface",
                "interface": "Alexa.Speaker",
                "version": "1.0",
                "properties":{ 
                    "supported": [
                        {
                            "name": "volume"
                        },
                        {
                            "name": "muted" 
                        }
                    ]
                }
            },
            {
                "type": "AlexaInterface",
                "interface": "Alexa.PowerController",
                "version": "1.0",
                "properties": {
                    "supported" : [
                        {
                            "name": "powerState"
                        }
                    ]
                }
            },
            {
              "type": "AlexaInterface",
              "interface": "Alexa.PlaybackController",
              "version": "1.0",
              "supportedOperations" : ["Play", "Pause", "Stop"]
            }
        ]},
        {
            "endpointId": radioZ3ControllerId,
            "friendlyName": "Bedroom Receiver",
            "description": "Denon AVR-3805 Alexa Echo controller, zone 3",
            "manufacturerName": "SAKA",
            "displayCategories": ["OTHER"],
            "cookie": {
                "deviceId" : particleId
                },
            "capabilities": [
            {
                "type": "AlexaInterface",
                "interface": "Alexa.Speaker",
                "version": "1.0",
                "properties":{ 
                    "supported": [
                        {
                            "name": "volume"
                        },
                        {
                            "name": "muted" 
                        }
                    ]
                }
            },
            {
                "type": "AlexaInterface",
                "interface": "Alexa.PowerController",
                "version": "1.0",
                "properties": {
                    "supported" : [
                        {
                            "name": "powerState"
                        }
                    ]
                }
            }
        ]},
        {
            "endpointId": radioZMControllerId,
            "friendlyName": "Living Receiver",
            "description": "Denon AVR-3805 Alexa Echo controller, main zone living room",
            "manufacturerName": "SAKA",
            "displayCategories": ["OTHER"],
            "cookie": {
                "deviceId" : particleId
                },
            "capabilities": [
            {
                "type": "AlexaInterface",
                "interface": "Alexa.PowerController",
                "version": "1.0",
                "properties": {
                    "supported" : [
                        {
                            "name": "powerState"
                        }
                    ]
                }
            }
        ]}
    ]};

    /**
    * Craft the final response back to Alexa Connected Home Skill. This will include all the 
    * discoverd appliances.
    */
    var header = event.directive.header;
    header.name = "Discover.Response";
    context.succeed({ event: {
    header: header, payload: payload
    } });
    //log('Discovery', result);
}
/**
 * Control events are processed here.
 * This is called when Alexa requests an action (IE turn off appliance).
 */
function handleControl(event, context) {
    var namespace = event.directive.header.namespace;
    var messageId = event.directive.header.messageId;
    var correlationToken = event.directive.header.correlationToken;
    var accessToken = event.directive.endpoint.scope.token;
    var endpointId = event.directive.endpoint.endpointId;
    var deviceId = event.directive.endpoint.cookie.deviceId;
    var name = "";
    var value = "";
    var UTCDate = new Date();
    var jsonDate = JSON.stringify(UTCDate);
    var strDate = JSON.parse(jsonDate); 
    var funcName = "DenonSerial";
    var denonSerialCommand = "";
    if ((namespace === "Alexa.PowerController") || 
        (namespace === "Alexa.Speaker") ||
        (namespace === "Alexa.PlaybackController")){

        var param = "";
        var index = "0";
        var executeParticle = true;
        if (namespace === "Alexa.PowerController") {
            if(event.directive.header.name == "TurnOn"){
                if (endpointId == radioControllerId) denonSerialCommand = powerOn;
                else if (endpointId == radioZ3ControllerId) denonSerialCommand = z3powerOn;
                else if (endpointId == radioZMControllerId) denonSerialCommand = ZMpowerOn;
                name = "powerState";
                value = "ON";
            }
            else if(event.directive.header.name == "TurnOff"){
                if (endpointId == radioControllerId) denonSerialCommand = powerOff;
                else if (endpointId == radioZ3ControllerId) denonSerialCommand = z3powerOff;
                else if (endpointId == radioZMControllerId) denonSerialCommand = ZMpowerOff;
                name = "powerState";
                value = "OFF";
            }
        }
        else if (namespace === "Alexa.Speaker") {
            if(event.directive.header.name == "SetVolume"){
                value = event.directive.payload.volume;
                if (endpointId == radioControllerId) denonSerialCommand = setVolumeToXXLevel + pad(value, 2);
                else if (endpointId == radioZ3ControllerId) denonSerialCommand = z3setVolumeToXXLevel + pad(value, 2);
                name = "volume";
            }
            else if(event.directive.header.name == "SetMute"){
                var mute = event.directive.payload.mute;
                if (mute) {
                    if (endpointId == radioControllerId) denonSerialCommand = muteEnable;
                    else if (endpointId == radioZ3ControllerId) denonSerialCommand = z3muteEnable;
                    value = true;}
                else if (!mute) {
                    if (endpointId == radioControllerId) denonSerialCommand = muteDisable;
                    else if (endpointId == radioZ3ControllerId) denonSerialCommand = z3muteDisable;
                    value = false;}
                name = "muted";
            }
            else if(event.directive.header.name == "AdjustVolume"){
                var volume = event.directive.payload.volume;
                if (volume >= 0) {
                    if (endpointId == radioControllerId) denonSerialCommand = volumeUp;
                    else if (endpointId == radioZ3ControllerId) denonSerialCommand = z3volumeUp;
                    value = 10;}
                else if (volume << 0) {
                    if (endpointId == radioControllerId) denonSerialCommand = volumeDown;
                    else if (endpointId == radioZ3ControllerId) denonSerialCommand = z3volumeDown;
                    value = -10;}
                name = "volume";
            }
        }

        
        else if (namespace === "Alexa.PlaybackController") {
            denonSerialCommand = event.directive.header.name;
            name = denonSerialCommand;
            funcName = "Sonos";
        }

        if(deviceId == particleId){
            index = "0";
        }
        param = denonSerialCommand;

        var options = {
            hostname: particleServer,
            port: 443,
            path: particlePath + deviceId + "/" + funcName,
            method: 'POST',
            headers: {
                'Content-Type': 'application/x-www-form-urlencoded'
            }
        };

        log(options);

        var data = "access_token=" + accessToken + "&" + "args=" + param;

        log(data);
        var serverError = function (e) {
            log('Error', e.message);
            context.fail(generateControlError(event, 'ENDPOINT_UNREACHABLE', 'Unable to connect to server'));
        };
        if (executeParticle) {
            var callback = function(response) {
                var str = '';
                response.on('data', function(chunk) {
                    str += chunk.toString('utf-8');
                });

                response.on('end', function() {
                log('Return Value');
                log(str);

                var result = {
                    "context": {
                        "properties": [ {
                        "namespace": namespace,
                            "name": name,
                            "value": value,
                            "timeOfSample": strDate,
                            "uncertaintyInMilliseconds": 500
                            } ]
                        },
                        "event": {
                            "header": {
                                "namespace": "Alexa",
                                "name": "Response",
                                "payloadVersion": "3",
                                "messageId": messageId,
                                "correlationToken": correlationToken
                            },
                            "endpoint": {
                                "scope": {
                                    "type": "BearerToken",
                                    "token": accessToken
                                },
                            "endpointId": endpointId
                            },
                            "payload": {}
                            }
                        };
                    context.succeed(result);
                });

                response.on('error', serverError);
            };

            var req = https.request(options, callback);

            req.on('error', serverError);

            req.write(data);
            req.end();
        }
    }
}
/**
 * Utility functions.
 */
function log(title, msg) {
    console.log(title + ": " + msg);
}
function generateControlError(event, code, description) {
    var messageId = event.directive.header.messageId;
    var correlationToken = event.directive.header.correlationToken;
    var accessToken = event.directive.endpoint.scope.token;
    var endpointId = event.directive.endpoint.endpointId;
    var response = {
        "header": {
            "namespace": "Alexa",
            "name": "ErrorResponse",
            "messageId": messageId,
            "correlationToken": correlationToken,
            "payloadVersion": "3"
            },
            "endpoint":{
                "scope":{
                    "type":"BearerToken",
                    "token":accessToken
                },
                "endpointId":endpointId
            },
            "payload": {
                "type": code,
                "message": description
            }
        };
    var result = { event : {response}};
    return result;
}
//Function to format the volume with leading zeros
function pad(num, size) {
    var s = "000" + num;
    return s.substr(s.length-size);
}

Particle Photon firmware

Arduino
// Design by Waldemar Sakalus - SAKA
// ver 12282017
// App to monitor Sonos status, integrated with Alexa and IFTTT


TCPClient client; //for IFTTT

TCPClient sonos;
byte sonosIP[] = { 192, 168, 1, 111 }; //put your Sonos_IP_address
String sonosResponse;

//IFTTT key
const String keyWaldi = "your IFTTT id";

unsigned long smartDelay = millis();
unsigned long smartCheck = millis();
unsigned long resetTimer = millis();
const unsigned long smartDelayTime = 1000; //1s probing time, how often Sonos is checked whether playing/ stopped
int builtInLED = 7;
String sonosTransportState = "";
String previousState;
byte ledStatus = LOW;

//byte playSonosCheck = LOW;
//byte stopSonosCheck = LOW;

int sonosAmp(String command);

int ampOpto = D2;
int ledOpto = D3;

String masterVolume = "80";
unsigned long volumeDelay = millis();
const unsigned long volumeDelayTime = 60000; //1min probing time, how often current volume on amplifier is checked

void setup()
{
    pinMode(builtInLED, OUTPUT);
    pinMode(ampOpto, INPUT);
    pinMode(ledOpto, OUTPUT);
    checkAmp();
    getSonosStatus();
    sonosTransportState = tryExtractString(readSonosResponse(),"<CurrentTransportState>","</CurrentTransportState>");
    previousState = sonosTransportState;
    
    //Serial1.blockOnOverrun(false);
    Serial1.begin(9600, SERIAL_8N1);
    Particle.function("DenonSerial", controlAmplifier);
    Particle.function("Sonos", controlSonos);
    Particle.variable("MasterVolume", masterVolume);
    
    Serial.begin(9600);
    Serial.println("Connected");
    //Particle.function("Sonos", sonosAmp);
}

void loop() {
    byte ampRead;
    checkAmp();
    getSonosStatus();
    sonosTransportState = tryExtractString(readSonosResponse(),"<CurrentTransportState>","</CurrentTransportState>");
    if (previousState != sonosTransportState) {
        Serial.println(sonosTransportState);
        if (sonosTransportState == "PLAYING") {
            digitalWrite (builtInLED , HIGH );
            delay(2000);
            ampRead = checkAmp();
            if (ampRead == LOW) makerIFTTT(keyWaldi, "SonosOn");
            ledStatus = HIGH;
        }
        else if (sonosTransportState == "PAUSED_PLAYBACK" || sonosTransportState == "STOPPED") {
            digitalWrite (builtInLED , LOW );
            delay(3000); //that's how long it gets for the amplifier to switch up
            ampRead = checkAmp();
            if (ampRead == HIGH ) makerIFTTT(keyWaldi, "SonosOff");
            ledStatus = LOW;
        }
        previousState = sonosTransportState;
    }
    if (volumeDelay < millis()) {
        Serial1.println("MV?"); //check volume
        Serial1.flush();
        volumeDelay = millis() + volumeDelayTime;
    }
    
    sendHeartbeat();
    /*
    if (playSonosCheck == HIGH) {
        playSonos();
        //sonosResponse = readSonosResponse();
        //Serial.println(tryExtractString(sonosResponse,"HTTP/1.1 ","CONTENT-LENGTH:"));
        //Serial.println("playSonosCheck");
        playSonosCheck = LOW;
    }
    if (stopSonosCheck == HIGH) {
        stopSonos();
        //sonosResponse = readSonosResponse();
        //Serial.print(tryExtractString(sonosResponse,"HTTP/1.1 ","CONTENT-LENGTH:"));
        //Serial.println("stopSonosCheck");
        stopSonosCheck = LOW;
    }
    */
    delay(smartDelayTime);
    
    // Reset after 7h of operation
    // ==================================
    if (millis() - resetTimer > 25200000) {
        System.reset();
    }
}

int controlAmplifier(String command)
{   
    if (command != NULL) {
        if (command == "MVUP" || command == "MVDOWN") {
            int increment = 5;
            int vol = masterVolume.toInt();
            if (masterVolume.length() == 3){
                vol = (vol-5)/10;
            }
            if (command == "MVUP") {
                vol = vol + increment;
                if (vol >= 99 ) vol = 99;
            }
            else if (command == "MVDOWN") {
                vol = vol - increment;
                if (vol <= 0) vol = 0;
            }
            //Function to format the volume with leading zeros
            String s = "000" + String(vol);
            s = s.substring(s.length() - 2);
            masterVolume = s;
            String newCommand = "MV" + masterVolume;
            Serial1.println(newCommand);
        }
        else if (command == "PWON"){
            Serial1.println(command);
            setRadioStation();
            playSonos();            
            Serial1.println("SICD"); //select CD as input, that's where Sonos connected
            Serial1.println("MZON"); //switch on main zone, just in case it was off
        }
        else if (command == "PWSTANDBY"){
            Serial1.println(command);
            stopSonos();            
        }
        else {
            Serial1.println(command);
        }
        return 1;
    }
    else return -1;
}

void serialEvent1()
//Triggered when response received from the Denon amplifer
{       
        String denonResponse = "";
        while (Serial1.available() > 0) { 
            char c = Serial1.read();
            denonResponse += c;
        }
        if (denonResponse.startsWith("MV")) {
            masterVolume = "";
            for (int i=2; i<denonResponse.indexOf("\r"); i++){
                masterVolume += denonResponse.charAt(i);
            }
            Serial.println(masterVolume);
        }
}

int controlSonos(String command)
{
    if (command != NULL) {
        if (command == "Play") playSonos();
        else if (command == "Pause") stopSonos();
        else if (command == "Stop") stopSonos();
        return 1;
    }
    else return -1;
}

/*int sonosAmp(String command)
{
    String sonosResponse;
    if(command == "ON")
    {
        Serial.println("ON received");
        playSonosCheck = HIGH;
        //playSonos();
        //sonosResponse = readSonosResponse();
        //Serial.print(tryExtractString(sonosResponse,"HTTP/1.1 ","CONTENT-LENGTH:"));
    }
    else if(command == "OFF")
    {
        //Serial.println(command);
        Serial.println("OFF received");
        stopSonosCheck = HIGH;
        //stopSonos();
        //sonosResponse = readSonosResponse();
        //Serial.print(tryExtractString(sonosResponse,"HTTP/1.1 ","CONTENT-LENGTH:"));
    }

    if (sonosResponse != NULL) return 1;
    else return -1;
}
*/

String readSonosResponse() {
    unsigned long lastdata = millis();
    String sonosResponse = "";
    while (sonos.connected()  || (millis()-lastdata < 500)) { //500ms timeout
        if (sonos.available()) {
            char c = sonos.read();
            sonosResponse += c;
            lastdata = millis();
        }
    }
    sonos.flush();
    sonos.stop();
    return sonosResponse;
}

void getSonosStatus()
/*
POST http://Sonos_IP_address:1400/MediaRenderer/AVTransport/Control HTTP/1.1
HOST: Sonos_IP_address:1400
SOAPACTION: "urn:schemas-upnp-org:service:AVTransport:1#GetTransportInfo"
CONTENT-TYPE: text/xml; charset="utf-8"
Content-Length: 354

<?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>
*/
{
    if (sonos.connect(sonosIP, 1400))
    {
        String contentSOAP ="<?xml version=\"1.0\" encoding=\"utf-8\"?>";
        contentSOAP += "<s:Envelope s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\" xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\">";
        contentSOAP += "<s:Body>";
        contentSOAP += "<u:GetTransportInfo xmlns:u=\"urn:schemas-upnp-org:service:AVTransport:1\">";
        contentSOAP += "<InstanceID>0</InstanceID>";
        contentSOAP += "</u:GetTransportInfo>";
        contentSOAP += "</s:Body>";
        contentSOAP += "</s:Envelope>";
        
        String post = "POST http://";
        post += sonosText();
        post += ":1400/MediaRenderer/AVTransport/Control HTTP/1.1\r\n";
        post += "HOST: ";
        post += sonosText();
        post += ":1400\r\n";
        post += "SOAPACTION: \"urn:schemas-upnp-org:service:AVTransport:1#GetTransportInfo\"\r\n";
        post += "CONTENT-TYPE: text/xml; charset=\"utf-8\"\r\n";
        post += "Content-Length: ";
        post += contentSOAP.length();
        post += "\r\n\r\n";
        post += contentSOAP;
        post += "\r\n\r\n";

        //Serial.println(post);
        sonos.print(post);

        delay(10);
    }
    else Serial.println("connection failed");
}


void playSonos()
/*
POST http://Sonos_IP_address:1400/MediaRenderer/AVTransport/Control HTTP/1.1
HOST: Sonos_IP_address:1400
SOAPACTION: "urn:schemas-upnp-org:service:AVTransport:1#Play"
CONTENT-TYPE: text/xml; charset="utf-8"
Content-Length: 354

<?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>

<?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>
*/
{
    if (sonos.connect(sonosIP, 1400))
    {
        String contentSOAP ="<?xml version=\"1.0\" encoding=\"utf-8\"?>";
        contentSOAP += "<s:Envelope s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\" xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\">";
        contentSOAP += "<s:Body>";
        contentSOAP += "<u:Play xmlns:u=\"urn:schemas-upnp-org:service:AVTransport:1\">";
        contentSOAP += "<InstanceID>0</InstanceID>";
        contentSOAP += "<Speed>1</Speed>";
        contentSOAP += "</u:Play>";
        contentSOAP += "</s:Body>";
        contentSOAP += "</s:Envelope>";
        
        String post = "POST http://";
        post += sonosText();
        post += ":1400/MediaRenderer/AVTransport/Control HTTP/1.1\r\n";
        post += "HOST: ";
        post += sonosText();
        post += ":1400\r\n";
        post += "SOAPACTION: \"urn:schemas-upnp-org:service:AVTransport:1#Play\"\r\n";
        post += "CONTENT-TYPE: text/xml; charset=\"utf-8\"\r\n";
        post += "Content-Length: ";
        post += contentSOAP.length();
        post += "\r\n\r\n";
        post += contentSOAP;
        post += "\r\n\r\n";

        //Serial.println(post);
        sonos.print(post);

        delay(10);
    }
    else Serial.println("connection failed");
}

void stopSonos()
/*
POST http://Sonos_IP_address:1400/MediaRenderer/AVTransport/Control HTTP/1.1
HOST: Sonos_IP_address:1400
SOAPACTION: "urn:schemas-upnp-org:service:AVTransport:1#Stop"
CONTENT-TYPE: text/xml; charset="utf-8"
Content-Length: 354

<?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>
      <Speed>1</Speed>
      </u:Stop>
   </s:Body>
</s:Envelope>
*/
{
    if (sonos.connect(sonosIP, 1400))
    {
        String contentSOAP ="<?xml version=\"1.0\" encoding=\"utf-8\"?>";
        contentSOAP += "<s:Envelope s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\" xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\">";
        contentSOAP += "<s:Body>";
        contentSOAP += "<u:Stop xmlns:u=\"urn:schemas-upnp-org:service:AVTransport:1\">";
        contentSOAP += "<InstanceID>0</InstanceID>";
        contentSOAP += "<Speed>1</Speed>";
        contentSOAP += "</u:Stop>";
        contentSOAP += "</s:Body>";
        contentSOAP += "</s:Envelope>";
        
        String post = "POST http://";
        post += sonosText();
        post += ":1400/MediaRenderer/AVTransport/Control HTTP/1.1\r\n";
        post += "HOST: ";
        post += sonosText();
        post += ":1400\r\n";
        post += "SOAPACTION: \"urn:schemas-upnp-org:service:AVTransport:1#Stop\"\r\n";
        post += "CONTENT-TYPE: text/xml; charset=\"utf-8\"\r\n";
        post += "Content-Length: ";
        post += contentSOAP.length();
        post += "\r\n\r\n";
        post += contentSOAP;
        post += "\r\n\r\n";

        //Serial.println(post);
        sonos.print(post);

        delay(10);
    }
    else Serial.println("connection failed");
}

void setRadioStation()
/*
POST /MediaRenderer/AVTransport/Control HTTP/1.1
HOST: 192.168.1.111:1400
SOAPACTION: "urn:schemas-upnp-org:service:AVTransport:1#SetAVTransportURI"
CONTENT-TYPE: text/xml ; charset="utf-8"
Content-Length: 1029

<?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-sonosapi-stream:s15984?sid=254&amp;flags=8224&amp;sn=0</CurrentURI>
         <CurrentURIMetaData>&lt;DIDL-Lite xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:upnp="urn:schemas-upnp-org:metadata-1-0/upnp/" xmlns:r="urn:schemas-rinconnetworks-com:metadata-1-0/" xmlns="urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/"&gt;&lt;item id="-1" parentID="-1" restricted="true"&gt;&lt;dc:title&gt;PR3 Trójka&lt;/dc:title&gt;&lt;upnp:class&gt;object.item.audioItem.audioBroadcast&lt;/upnp:class&gt;&lt;desc id="cdudn" nameSpace="urn:schemas-rinconnetworks-com:metadata-1-0/"&gt;SA_RINCON65031_&lt;/desc&gt;&lt;/item&gt;&lt;/DIDL-Lite&gt;</CurrentURIMetaData>
      </u:SetAVTransportURI>
   </s:Body>
</s:Envelope>
*/
{
    if (sonos.connect(sonosIP, 1400))
    {
        String contentSOAP ="<?xml version=\"1.0\" encoding=\"utf-8\"?>";
        contentSOAP += "<s:Envelope s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\" xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\">";
        contentSOAP += "<s:Body>";
        contentSOAP += "<u:SetAVTransportURI xmlns:u=\"urn:schemas-upnp-org:service:AVTransport:1\">";
        contentSOAP += "<InstanceID>0</InstanceID>";
        contentSOAP += "<CurrentURI>x-sonosapi-stream:s15984?sid=254&amp;flags=8224&amp;sn=0</CurrentURI>";
        contentSOAP += "<CurrentURIMetaData>&lt;DIDL-Lite xmlns:dc=\"http://purl.org/dc/elements/1.1/\" xmlns:upnp=\"urn:schemas-upnp-org:metadata-1-0/upnp/\" xmlns:r=\"urn:schemas-rinconnetworks-com:metadata-1-0/\" xmlns=\"urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/\"&gt;&lt;item id=\"-1\" parentID=\"-1\" restricted=\"true\"&gt;&lt;dc:title&gt;PR3 Trójka&lt;/dc:title&gt;&lt;upnp:class&gt;object.item.audioItem.audioBroadcast&lt;/upnp:class&gt;&lt;desc id=\"cdudn\" nameSpace=\"urn:schemas-rinconnetworks-com:metadata-1-0/\"&gt;SA_RINCON65031_&lt;/desc&gt;&lt;/item&gt;&lt;/DIDL-Lite&gt;</CurrentURIMetaData>";
        contentSOAP += "</u:SetAVTransportURI>";
        contentSOAP += "</s:Body>";
        contentSOAP += "</s:Envelope>";
        
        String post = "POST http://";
        post += sonosText();
        post += ":1400/MediaRenderer/AVTransport/Control HTTP/1.1\r\n";
        post += "HOST: ";
        post += sonosText();
        post += ":1400\r\n";
        post += "SOAPACTION: \"urn:schemas-upnp-org:service:AVTransport:1#SetAVTransportURI\"\r\n";
        post += "CONTENT-TYPE: text/xml; charset=\"utf-8\"\r\n";
        post += "Content-Length: ";
        post += contentSOAP.length();
        post += "\r\n\r\n";
        post += contentSOAP;
        post += "\r\n\r\n";

        //Serial.println(post);
        sonos.print(post);

        delay(10);
    }
    else Serial.println("connection failed");
}

void makerIFTTT (String keyIFTTT, String eventIFTTT){
   if (client.connect("maker.ifttt.com", 80))
    {
        String post = "GET /trigger/";
        post += eventIFTTT;
        post += "/with/key/";
        post += keyIFTTT;
        post += " HTTP/1.1\r\n";
        post += "Cache-Control: no-cache\r\n";
        post += "User-Agent: Spark/1.0\r\n";
        post += "Content-Length: 0\r\n";
        post += "Connection: close\r\n";
        post += "Host: maker.ifttt.com\r\n\r\n";
        client.print(post);

        delay(10);
    } 
    if (!client.connected()) client.stop();
}

void triggerIFTTT (String eventIFTTT, String keyIFTTT, String eventText)
{
   if (client.connect("maker.ifttt.com", 80))
    {
        String contentJSON = "\{\"value1\":\"";
        contentJSON += eventText;
        contentJSON += "\"\}";
        
        String post = "POST /trigger/";
        post += eventIFTTT;
        post += "/with/key/";
        post += keyIFTTT;
        post += " HTTP/1.1\r\n";
        post += "Cache-Control: no-cache\r\n";
        post += "User-Agent: Spark/1.0\r\n";
        post += "Connection: close\r\n";
        post += "Host: maker.ifttt.com\r\n";
        post += "Content-Type: application/json\r\n";
        post += "Content-Length: ";
        post += contentJSON.length();


        post += "\r\n\r\n";
        post += contentJSON;
        post += "\r\n\r\n";
        
        Serial.println(post);
        client.print(post);

        delay(10);
    }
    if (!client.connected()) client.stop();
}

String sonosText(){
    String addressIP = "";
    for (int i = 0; i < 4; i++) {
        addressIP += sonosIP[i];
        if (i < 3) {
            addressIP += ".";
        }
    }
    return addressIP;
}


void sendHeartbeat() {
    //send a short heartbeat, so visible through the built in LED that the device is not hanging
    byte tempLED = LOW;
    for (int i = 0; i < 5; i++) {
        digitalWrite(builtInLED, tempLED); // Write LED high/low
        tempLED = (tempLED == HIGH) ? LOW : HIGH;
        delay(30);
    }
    digitalWrite(builtInLED, ledStatus); //restore the LED status
}

byte checkAmp () {
    byte ampRead = digitalRead(ampOpto);
    if (ampRead) {
        digitalWrite(ledOpto, HIGH);
        return LOW;
    }
    else {
        digitalWrite(ledOpto, LOW);
        return HIGH;
    }
    //Serial.println(ampRead);
}

// Returns any text found between a start and end string inside 'str'
// example: startfooend  -> returns foo
// credit to an example on particle.io
String tryExtractString(String str, const char* start, const char* end) {
    if (str == NULL) {
        return "";
    }

    int idx = str.indexOf(start);
    if (idx < 0) {
        return "";
    }

    int endIdx = str.indexOf(end);
    if (endIdx < 0) {
        return "";
    }

    return str.substring(idx + strlen(start), endIdx);
}

Credits

Waldemar Sakalus
4 projects • 18 followers
Contact

Comments

Please log in or sign up to comment.