Hardware components | ||||||
![]() |
| × | 1 | |||
| × | 1 | ||||
| × | 1 | ||||
![]() |
| × | 1 | |||
Software apps and online services | ||||||
![]() |
|
I'm streaming music or radio station through Sonos: Connect, which is connected to a Denon AV-3805 amplifier, that's driving multiple zones (speakers in the living room and bedroom); additionally the amplifier is also used to power the TV sound. This project is an ultimate integration, using Alexa and smart music detection to power and steer the Denon amplifier.
The convenience of using Sonos and its application is hampered by the necessity of steering the amplifier. I could leave it always on, but that drives power consumption, heats the amplifiers, and anyway does not solve completely the issue as I would still need to use a remote to change the input between TV, streaming music (Sonos) and occasionally CD. A partial solution is to use the Logitech Harmony universal remote. I can program the right sequence, select input, etc. It works well in many situations and this project does not remove the Harmony integration, just supplants it.
With this integration, I'm able to achieve scenarios as:
- When I switch on the music/radio in the Sonos app, the amplifier is automatically switched on and set to the right input (and conversely, when the music is off the amplifier turns off automatically)
- I can ask Alexa to switch on the receiver (and whatever was last playing, e.g. radio station, is turned on), raise/ lower the volume, independently switch zones (so I can listen just in e.g. bedroom, while living is muted)
This project draws heavily from some previous ones I did, especially:
- serial controller https://www.hackster.io/saka/alexa-voice-control-for-almost-any-amplifier-tv-cd-dvd-87fff1
- sonos controller https://www.hackster.io/saka/sonos-controller-with-wemo-switch-and-alexa-echo-speech-16a794
Detailed description on how to build a RS-232 controller can be found here serial controller https://www.hackster.io/saka/alexa-voice-control-for-almost-any-amplifier-tv-cd-dvd-87fff1
Additionally, this project adds an ability to read a 12V trigger from the amplifier and additional LED to signal states:
The serial interface commands are specific to an equipment, so can differ even for Denon. Please download yours; my model is AVR-3805, I found the instructions easily on the web. Having said that, while the syntax might differ, the commands should be similar not only for Denon but other brands. Should be straightforward to adapt to your device.
Again, step-by-step instructions can be found here: https://www.hackster.io/saka/alexa-voice-control-for-almost-any-amplifier-tv-cd-dvd-87fff1
Posted the code, just remember you need to put your Particle Photon ID.
Particle Photon firmwarePosted the Photon firmware. Few comments and explanations below.
How the Sonos integration is done?
Sonos is using SOAP APIs. You can actually easily check what's being sent by downloading Device Spy; here some detailed instructions: http://blog.travelmarx.com/2010/06/exploring-sonos-via-upnp.html.
My project is basically sending an inquiry to Sonos every 1s, based on the response I can detect whether there has been any change (music starting playing, stopped, etc). Then using SOAP commands I can start/ stop Sonos.
Here is the specific code:
Can I read serial responses?
Denon is not only allowing to send serial commands, but as well communicates back. This is very useful if you want to check the amplifier status. In my project I'm using this to check the current master volume. While Denon allows a volume up/ down command, it goes in 1 volume step, which is a very small change. Typically, when I say to "Alexa, volume down on receiver", I want a noticeable change in volume. One way would be to repeat the command few times (stupid), or implement a software that sends multiple commands "volume down" (MVDOWN) to the amplifier. Instead, I opted to keep record of the master volume (the amplifier is probed every 1 min for the volume) and just send one command that raises/ lowers 4 steps the volume, relative to existing one.
Check Photon documentation for more details. The SerialEvent1() function is triggered when a transmission from the serial port is detected.
Can I ask Alexa to play specific station?
This functionality is implemented already with the Alexa-Sonos integration. I did only a small twist, as I have a favorite radio station. I'm defaulting to that one each time I ask Alexa to turn on the receiver; this is very handy when e.g. power was down and Sonos was restarted, I don't have to go to the app or ask Alexa to select the station.
If you want to change to your favorite station select it in the Sonos app so it is active and in Device Spy invoke the GetMediaInfo in the AVTransport service.
Then copy the "Current URI" and "CurrentURIMetaData" to the setRadioStation() function.
Please note that you have to add "\" before each "sign in the string."
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);
}
// 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&flags=8224&sn=0</CurrentURI>
<CurrentURIMetaData><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/"><item id="-1" parentID="-1" restricted="true"><dc:title>PR3 Trójka</dc:title><upnp:class>object.item.audioItem.audioBroadcast</upnp:class><desc id="cdudn" nameSpace="urn:schemas-rinconnetworks-com:metadata-1-0/">SA_RINCON65031_</desc></item></DIDL-Lite></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&flags=8224&sn=0</CurrentURI>";
contentSOAP += "<CurrentURIMetaData><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/\"><item id=\"-1\" parentID=\"-1\" restricted=\"true\"><dc:title>PR3 Trójka</dc:title><upnp:class>object.item.audioItem.audioBroadcast</upnp:class><desc id=\"cdudn\" nameSpace=\"urn:schemas-rinconnetworks-com:metadata-1-0/\">SA_RINCON65031_</desc></item></DIDL-Lite></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);
}
Comments
Please log in or sign up to comment.