Friday, May 22, 2015

Arduino speaker controller

I've had this project in mind for a while, and finally got around to it. I wanted a way to turn on or off the various speakers around the house. In addition to the front room, where the main stereo is, we have speakers in the dining room, hot tub area, and back patio. Previously, I could turn on the amp, pick a playlist from Kodi, and make it play all from my phone, but if I was outside on the patio, I had to go back in the house and push a button to turn on the speakers. No more! Now I have a simple web app and an Arduino controller to turn on the speakers.

 I got a lot of inspiration and ideas for this project from http://www.33snowflakes.com/blog/arduino-android-speaker-selector/. There is even a link to a nice looking Android app to control the speaker relays, but I couldn't get it to work, so I just did a little web app that runs on Arduino to control the speakers. I got just about all the parts from Amazon:


Here is the wiring to control the relay board. The brown wire on the left connects to the 9V output on the Arduino to power the relays themselves. The black wire on the right is the ground, and is common to the board and the relays. The white wire connects to the 5V output on the Arduino to power the relay circuit board.

A close up of the connections on the relay board. Again, the brown wire on the right is connected to the 9V output on the Arduino and powers the relays themselves, while the white wire is connected to the 5V output on the Arduino to power the relay circuit board. The other wires are connected to pins 2 through 9 on the Arduino to actually trigger the relays.


Here are the connections on the Arduino side. You can see the 5V white wire, the black ground wire, and the 9V brown wire. The other wires are connected to pins 2 though 9 to control the 8 relays.

Just a big picture of the completed wiring between the Arduino and the relay board

A shot of everything in the box. My dremel skills aren't the best, but this will be in a cabinet and essentially hidden from view, so it's okay. There are cut outs for the ethernet, USB, and power connections.

The speakers will connect here. It might have been better if I'd found similar connectors that were all black. For this use, each speaker gets two connectors. The negative wire is cut, then one of the cut ends goes in the top row of connectors and the other cut end connects to the corresponding connector in the bottom row. The relay then completes the circuit. These are installed right to left, so the right most connector is controlled by relay 1, and the left most connector is controlled by relay 8. I did it this way so it would be easier to wire into my stereo cabinet. Note that this piece is one of about 6 that make all the speakers work. 

A shot of the inside. It looks messy, but it's pretty straightforward.


The code is a simple web server. The IP address is hard-coded to 192.168.2.251 so it's easy to bookmark in my phone's browser. The web page it produces looks like this, both on my phone and on my desktop browser:


Pretty plain, but it does the right thing.




/*
Simple web server to serve a form to turn on or off digital pins. In this case,
the pins are connected to relays to turn speakers on or off. This uses pins
2 through 9 in pairs, so 2 and 3 control the hot tub speakers, 4 and 5 control
the dining room speakers, and 6 and 7 control the portico speakers. 8 and 9 are
available in case I ever find a need to connect one more pair of speakers.
*/

#include <SPI.h>
#include <Ethernet.h>
#include <string.h>

// MAC address can be anything that is unique within the local network.
byte mac[] = {0x00, 0x1E, 0x2A, 0x77, 0x24, 0x02 };

// Some unused IP address on the local network.
byte ip[] = {192, 168, 2, 251 }; 

// web server, nothing fancy, just port 80 for http
EthernetServer server( 80 ); 

#define RELAY_ON 0
#define RELAY_OFF 1


// true, just show speaker status or false, actually change the speaker states
boolean showStatus = false;

// which zones are on or off, initially, all are off
boolean zone1 = false;
boolean zone2 = false;
boolean zone3 = false;
boolean zone4 = false;

// pin definition, one pin per speaker, 
// so the left speaker in zone 1 is pin 2, etc
static const int zone1L = 2;
static const int zone1R = 3;
static const int zone2L = 4;
static const int zone2R = 5;
static const int zone3L = 6;
static const int zone3R = 7;
static const int zone4L = 8;
static const int zone4R = 9;


// set up pins, initially all speakers in all zones are off
void setupPins() {
    digitalWrite(zone1L, RELAY_OFF);
    digitalWrite(zone1R, RELAY_OFF);
    digitalWrite(zone2L, RELAY_OFF);
    digitalWrite(zone2R, RELAY_OFF);
    digitalWrite(zone3L, RELAY_OFF);
    digitalWrite(zone3R, RELAY_OFF);
    digitalWrite(zone4L, RELAY_OFF);
    digitalWrite(zone4R, RELAY_OFF);

    pinMode( zone1L, OUTPUT );
    pinMode( zone1R, OUTPUT );
    pinMode( zone2L, OUTPUT );
    pinMode( zone2R, OUTPUT );
    pinMode( zone3L, OUTPUT );
    pinMode( zone3R, OUTPUT );
    pinMode( zone4L, OUTPUT );
    pinMode( zone4R, OUTPUT );
    
    delay(4000);
}

void setup() {
    Ethernet.begin( mac, ip );
    server.begin();

    setupPins();
}

// set up buffer for reading web requests
static const int bufferMax = 128;
int bufferSize;
char buffer[ bufferMax ];

void loop() {
    EthernetClient client = server.available();
    if ( client ) {
        waitForRequest( client );
        handleRequest();
        client.stop();
    }
}

void waitForRequest( EthernetClient client ) 
{
    bufferSize = 0;

    while ( client.connected() ) {
        if ( client.available() ) {
            char c = client.read();
            if ( c == '\n' ) {
                break;
            }
            else {
                if ( bufferSize < bufferMax ) {
                    buffer[ bufferSize++ ] = c;
                }
                else {
                    break;
                }
            }
        }
    }
}

void handleRequest() {
    // Received buffer contains a standard HTTP GET line, something like
    // "GET /?X=X&Y=Y HTTP/1.1". 
    // Could have up to 4 parameters, 1=on&2=on&3=on&4=on,
    // one for each set of speakers. All that is necessary here is to
    // extract the query string and check for each of the zone numbers.
    String request = String(buffer);
    int firstSpace = request.indexOf(" ");   // right after GET
    int lastSpace = request.indexOf(" ", firstSpace + 1); // just after the query string
    request = request.substring(firstSpace, lastSpace);
    
    showStatus = request.indexOf("?") == -1;
    zone1 = request.indexOf("1") > -1;
    zone2 = request.indexOf("2") > -1;
    zone3 = request.indexOf("3") > -1;
    zone4 = request.indexOf("4") > -1;

    if (!showStatus) {
        setSpeakerState();
    }
    sendPage();
}

void setSpeakerState() {
    digitalWrite(zone1L, zone1 ? RELAY_ON : RELAY_OFF);
    digitalWrite(zone1R, zone1 ? RELAY_ON : RELAY_OFF);
    digitalWrite(zone2L, zone2 ? RELAY_ON : RELAY_OFF);
    digitalWrite(zone2R, zone2 ? RELAY_ON : RELAY_OFF);
    digitalWrite(zone3L, zone3 ? RELAY_ON : RELAY_OFF);
    digitalWrite(zone3R, zone3 ? RELAY_ON : RELAY_OFF);
    digitalWrite(zone4L, zone4 ? RELAY_ON : RELAY_OFF);
    digitalWrite(zone4R, zone4 ? RELAY_ON : RELAY_OFF);
}

void sendPage() {
    printHead();
    printForm();
    printTail();
}

// the 'bootstrap' css makes the page look good in desktop browsers and on phones
void printHead() {
    server.print("<html>\n<head>\n<title>Speaker Control</title>\n");
    server.print("<meta name='viewport' content='width=device-width, initial-scale=1'>\n");
    server.print("<link rel='stylesheet' href='http://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css'>\n");
    server.print("</head>\n<body>\n"); 
}

void printForm() {
   server.print("<div class='container'>\n");
   server.print("<h2>Speaker Control</h2><br/>\n");
   server.print("<form action='/' role='form'>\n");
   
   int pinState = digitalRead(zone4L);                                           
   server.print("<div class='checkbox'><label><input type='checkbox' name='4'");
   server.print(pinState == RELAY_ON ? " checked='checked'" : "");
   server.print(">Hot tub</label></div><br/>\n");

   pinState = digitalRead(zone3L);                                           
   server.print("<div class='checkbox'><label><input type='checkbox' name='3'");
   server.print(pinState == RELAY_ON ? " checked='checked'" : "");               
   server.print(">Dining room</label></div><br/>\n");                            
                                                                             
   pinState = digitalRead(zone2L);                                           
   server.print("<div class='checkbox'><label><input type='checkbox' name='2'");
   server.print(pinState == RELAY_ON ? " checked='checked'" : "");               
   server.print(">Portico</label></div><br/>\n");                        
                                                                             
   pinState = digitalRead(zone1L);
   server.print("<div class='checkbox'><label><input type='checkbox' name='1'");
   server.print(pinState == RELAY_ON ? " checked='checked'" : "");               
   server.print(">(Empty)</label></div><br/>\n");                            
                                                                             
   server.print("<br/>\n");
   server.print("<button type='submit' class='btn btn-default'>Save</button>\n");
   server.print("</form>\n");
   
   server.print("</div>\n");
}

void printTail() {
   server.print("</body>\n</html>\n"); 
}



10 comments:

John Buginas said...

Great project. Where does the audio from the amp enter the circuit?

I see four sets of output connectors, but no input connectors.


Also, do you use anything to protect the amp or speakers during switching?

Thank you.

John Buginas said...

Ah, I figured the speaker wiring out.

Do you need to use some sort of device to protect the speakers or amp during switching?

이동환 said...

Hi, Nice to meet you.

My name is Oscar, at WIZnet in Korea.

We have been searching some application references in which WIZnet solution is applied, and found your project “Arduino speaker controller“ using Ethernet Shield. In the Ethernet Shield WZnet’s W5100 chip is embedded. Your development looks very cool & smart.

Recently we opened WIZnet Museum (http://wiznetmuseum.com) site. This is a academic-purposed collection of open projects, tutorials, articles and etc from our global customers.

If you are O.K. we would like to introduce your projects in here. Hopefully, you will allow this.


Hopefully, keep contacting us for the friendship.

Thank you very much.

Dale Anson said...

John, I have one of these ahead of the speaker controller: http://www.amazon.com/gp/product/B002I9XKTE

It has circuit protection built it. Before I built the arduino box, I had to go push buttons on this box. Now I just leave all the buttons on all the time and let the arduino do the switching. I also have 4 of these downstream (after the arduino) to adjust volume on the individual speaker pairs.

Dale Anson said...

Oscar, that's fine with me.

Scotty Winter said...

Any chance you want to build another and sell it to me? :)

Dale Anson said...

@Scotty -- What's it worth to you? Drop me a line at danson at grafidog.com if you're serious.

Etara said...

Hi, i would like to know where or how do you connect your amplifier to your relay? I understand that the relay output and switch speakers but where is the "common" / main signal carrying the source? How to connect it?

Anonymous said...

Great job Dale!

One question however: you are using ISS4. Can I use SSVC4 instead of ISS4 as a feeder? I want to use this solution to route L/C/R channels between 2 sets of acoustics so I need to have a feeder with dual input (input 1 will be L/R and input 2 will be C).

I suppose SSVC4 uses some kind of more advanced circuitry because it has built-in volume control - but won't this circuitry malfunction when the output is 'blocked' by the relay? I.e. zone X output is set to 'On' at SSVC4 so the SSVC device 'assumes' that load is active but actually there is no load because the relay shuts the chain down - that's what I'm a little bit afraid of.

p.s. I'm in Europe, so all my speakers are 6 Ohm
/Oleg

Dale Anson said...

Hi Oleg,

I don't see any reason why your SSVC4 wouldn't work in place of the ISS4. I don't think it would make any assumptions, there is either a load or there isn't. It would be like having the switch on but there is no speaker connected to the other end, I can't imagine that would cause any problem, but then, I don't have SSVC4 to test with, so don't hold me to this :)

Dale