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 (request.indexOf("1=on") > -1) {
      zone1 = true;
    }
    else if (request.indexOf("1=off") > -1) {
      zone1 = false;
    }
    if (request.indexOf("2=on") > -1) {
      zone2 = true;
    }
    else if (request.indexOf("2=off") > -1) {
      zone2 = false;
    }
    if (request.indexOf("3=on") > -1) {
      zone3 = true;
    }
    else if (request.indexOf("3=off") > -1) {
      zone3 = false;
    }
    if (request.indexOf("4=on") > -1) {
      zone4 = true;
    }
    else if (request.indexOf("4=off") > -1) {
      zone4 = false;
    }


    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"); 
}



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

Anonymous 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

Anonymous said...

Thanks a million! I will definitely give it a try :)

/Oleg

Mike V said...

Hi Dale,

First off, let me say what an amazing project. This is exactly what I was looking for to control my speakers. However I'm trying to modify your code to include up 6 zones but I've run into a roadblock.

For whatever reason the relays on pins 10, 11 and 12 are always on and won't turn off. I will be the first to admit I have no idea what I am doing as I'm just starting out on my road to coding so all I did was copy and paste your code and add in Zone 5 and 6. Then I went through adding in what I thought was the relevant additions but those three relays won't turn off.

Any idea what I might be doing wrong? I apologize if this is a basic question but I'm a little stumped. (I didn't post the code because I didn't want to take over your comment section.)

Thank you in advance for any help.

Mike.

Rodger said...

I do not see a link in your post to what you used for volume control. Can you control volume via WiFi? we

Dale Anson said...

Mike V - sorry for the really late response, maybe you've figured it out by now, but pins 10, 11, and 12 are for the serial interface and don't work like the other pins. To run more than 4 relays, you'll need a port expander. See the post I did about version 2 for some details.

Dale Anson said...

Rodger - I use Kodi to play music, and have the Yatse app on my phone to control Kodi, so I use Kodi via Yatse to control the volume. I've looked into adding volume controls to this speaker controller, but it is somewhat difficult and time is lacking.

Unknown said...

Dale,
Thank you for posting this project. I do have a question regarding an issue I am having connecting to my home network. I connect to the internet using a modem and EERO. EERO provides me the ability to manually set the IP Address and the MAC address. However, the MAC address in EERO is formatted as xx:xx:xx:xx:xx:xx. When I connect the speaker selector project to EERO using the Ethernet cable, the device does not show on EERO as being connected. I am able to put the IP Address in my phone and see the “Speaker Control Screen” but the when a speaker pair is selected on the screen, the relay does not function. I believe the MAC Address is not being recognized and that is why the device does not show on my EERO app as being connected and therefore the functionality fails.
Are you aware of anyone using this project with an EERO and if so did the code included in the project need to be altered in order accommodate the EERO MAC Address formatting

Thank you

Jim A.

Dale Anson said...

@Jim A. -- I would be surprised that it's problem with the MAC address. You can change it in the code and recompile just to make sure it's some number that's not already on your network. Did you change the IP address in the code? This version uses a hard-coded IP address, so if it's not one that is in range for your network, your EERO won't see it. (I'm not familiar with EERO, I assume it's router of some sort).

Unknown said...

Dale,
Thank you for your response. I am fairly certain my issue is not the IP address. I did change it in the code and entered the same IP address number when manually entering this device on my network using the EERO app. EERO does replace the router and is a mesh system. In my system I have 3 EERO access points which provides excellent access to all areas of my house with no signal speed or strength of signal degradation when using multiple devices. Each access point has 2 Ethernet ports and one of those ports on one of the three access points is what I have the w5100 Ethernet shield device connected to.
In my post I focused on the MAC address because in the code the MAC address was formatted with 6 sets of 4 digits each separated by a comma. In EERO all devices have MAC Addresses formatted with 6 sets of 2 digits each separated by a colon. I received a compiling error when I tried to use a unique 6 sets of 2 digits each separated by a colon in the code with the error indicating the use of the colon was causing the error. I am referring to the line in the code beginning with “ byte Mac [ ] = “. Since I can’t seem to match the MAC address in the code with the MAC address I need to manually enter into the EERO app because of the formatting difference, I have assumed the MAC address is the crux of my issue.
FYI:
Currently I have the same 6 sets of 2 digits in both the code and the MAC address line on EERO app device set up screen. The difference is in the EERO app set up screen the 6 sets of 2 digits are separated by a colon and in the code the 6 sets of 2 digits are separated by a comma. This approach does not result in a functioning display on my phone. When I enter the IP address I do get the phone to display the Speaker Control screen with the room names as I had coded them. The buttons on the phone do not activate the relays when pressed (highlighted). As I mentioned in my original post the device does not show in the EERO app on my list of connected devices, either as connected and active or connected and inactive device. This MAC address being used is unique to all other Mac addresses on my list of devices as shown in the EERO app.

Thank you again for the response. I reached out hoping either you or some reader may have tried to connect the speaker selector to an EERO network and had an answer I could try.

Once again I appreciate your posting this project. It is a perfect solution for my audio system. I will continue to see if I can find a solution.


Jim A

Dale Anson said...

Okay, I read up on EERO. It sounds a lot like Google Wifi. The MAC address isn't the problem, I'm certain of it. The 6 sets of 4 digits in the code for the MAC address are just hex values, so you can put the same values minus the "0x" in your EERO and use semi-colons there. I suspect the problem is actually the EEROs themselves causing the problem. If you can see the speaker control screen on your phone, then you are connecting to the arduino with a good IP address and the MAC address doesn't matter at that point. I've had no end of problems getting the Google Wifi to route correctly on my home network. These little mesh systems work well when it's all wifi, but seem to not do so well with wired networking and routing in general. If you have the time, patience, and a little more money, you could try using a wireless card like I did here: https://danson.grafidog.com/2017/05/speaker-controller-version-2-part-1.html