Control of 4ch relay via webpage for garage door opener and lights

Hi folks, thanks for your interest.

I am currently trying to use a Uno board with W5100 ethernet control a bank of 4 relays channels, via a hosted webpage.

The webpage is very basic, and works perfectly. I don't need any help with this.

The 3 buttons have three functions, as follows:

Door Toggle - momentarily closes Relay 1 to toggle the door opener

  • turns on Relay 2 until a timer expires (currently 5 seconds for testing)
    Inside light - turns on Relay 2, which runs a strip of LEDs inside the garage, until a timer expires
    Outside light - turns on Relay 3, which runs a strip of LEDs outside the garage, until a timer expires

The basic code is there and it works fine, if I use the Delay command to drive the relays. But I don't want to use Delay, because it means that I can't press other buttons while the Delay is active. So I am trying to use millis instead, and it's not working!

I'm getting some weird behaviour - the timers clearly aren't working as required.
Relay 1 works as expected (uses Delay)
Relay 2 turns on when the webpage button is pressed, but never switches off again
Relay 3 seems to turn on really quickly and off again - but the code for both buttons is the same?

Can anyone see what's wrong with this code? I have spent hours reading about millis and syntax, I am reading the outputs from the serial monitor and I just don't seem to be able to nail down what's going wrong.

//3 button GET server code to control relays 
//open serial monitor to see what the arduino receives
//use the \ slash to escape the " in the html
//for use with W5100 based ethernet shields

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

// assign relay codes to pins
#define RELAY1  6                        
#define RELAY2  7                        
#define RELAY3  8                        
#define RELAY4  9

unsigned long timerIn = 0;    // long variable to store Timer 1 value
unsigned long timerOut = 0;    // long variable to Store Timer 2 value

byte mac[] = { 
  0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED }; //physical mac address
byte ip[] = { 
  192, 168, 1, 150 }; // ip in lan
byte gateway[] = { 
  192, 168, 1, 1 }; // internet access via router
byte subnet[] = { 
  255, 255, 255, 0 }; //subnet mask
EthernetServer server(80); //server port

String readString;

//////////////////////

void setup()
{
  // set up relay pins
  pinMode(RELAY1, OUTPUT);     // Set relay pins to output       
  pinMode(RELAY2, OUTPUT);     // Repeat for relays 2 to 4
  pinMode(RELAY3, OUTPUT);
  pinMode(RELAY4, OUTPUT);

  // initialise relays:  note HIGH = relay off, LOW = relay on
  digitalWrite(RELAY1, HIGH); // Set initial position of relay 1 output
  pinMode(RELAY1, OUTPUT);    // Write position to digital pin (prevents accidental toggle on startup)
  digitalWrite(RELAY2, HIGH); // Repeat for relays 2 to 4
  pinMode(RELAY2, OUTPUT);
  digitalWrite(RELAY3, HIGH);
  pinMode(RELAY3, OUTPUT);
  digitalWrite(RELAY4, HIGH);
  pinMode(RELAY4, OUTPUT);

  Ethernet.begin(mac, ip, gateway, subnet);    //start Ethernet
  server.begin();
  Serial.begin(9600);                          //enable serial data print
  Serial.println("Initialisation complete");   //check that setup has completed (in serial monitor)
}

void loop(){

  // Create a client connection
  EthernetClient client = server.available();
  if (client) {
    while (client.connected()) {
      if (client.available()) {
        char c = client.read();

        //read char by char HTTP request
        if (readString.length() < 100) {

          //store characters to string
          readString += c;
          //Serial.print(c);
        }

        //if HTTP request has ended
        if (c == '\n') {

          //Webpage code (title and buttons)
          Serial.println(readString); //print to serial monitor for debuging

            client.println("HTTP/1.1 200 OK"); //send new page
          client.println("Content-Type: text/html");
          client.println();

          client.println("<HTML>");
          client.println("<HEAD>");
          client.println("<meta name='apple-mobile-web-app-capable' content='yes' />");
          client.println("<meta name='apple-mobile-web-app-status-bar-style' content='black-translucent' />");
          client.println("<link rel='stylesheet' type='text/css' href='http://homeautocss.net84.net/a.css' />");
          client.println("<TITLE>Arduino Controller</TITLE>");
          client.println("</HEAD>");
          client.println("<BODY>");
          client.println("<H1>Arduino Controller</H1>");
          client.println("<hr />");
          client.println("
");

          client.println("<a href=\"/?buttonleft\"\">Door Toggle</a>");
          client.println("<a href=\"/?buttoncentre\"\">Inside</a>");
          client.println("<a href=\"/?buttonright\"\">Outside</a>
");        

          client.println("</BODY>");
          client.println("</HTML>");

          delay(1);
          //stopping client
          client.stop();

          //Door Toggle button
          if (readString.indexOf("?buttonleft") >0)    //checks for 'left' button press - used to open garage door via momentary grounding
          {
            digitalWrite(RELAY1,LOW);                  // Turns on Relay 1 to toggle door
            Serial.println("Left button pressed");     // serial monitor output confirmation
            digitalWrite(RELAY2,LOW);                  // Turns on Relay 2 to toggle inside light
            timerIn = millis();                        // Sets timerIn to current millis to start timer
            Serial.print("Indoor light timer = ");  
            Serial.println(timerIn);                   // serial monitor output confirmation  

              delay(500);                              // Wait 0.5 seconds for momentary grounding on Relay 1
            digitalWrite(RELAY1,HIGH);                 // Turns Relay 1 Off - door toggle operation complete
            Serial.println("Door toggle complete");    // serial monitor output confirmation
                                                       // note Relay 2 needs to be switched off using timerIn routine
            readString="";                             //clear string for next check
          }                         
 
          //Inside Light button operation
          else if (readString.indexOf("?buttoncentre") >0) //checks for 'centre' button press - used to turn on inside light for preset period
          {
            digitalWrite(RELAY2,LOW);                  // Turns on Relay 2
            Serial.println("Centre button pressed");   // serial monitor output confirmation
            timerOut = millis();                       // Sets timerOut to current millis to start timer
            Serial.print("Indoor light timer = ");  
            Serial.println(timerIn);                   // serial monitor output confirmation  
                                                       // note Relay 2 needs to be switched off using timerIn routine
            readString="";                             //clear string for next check
          }                               

          //Outside Light button operation
          else if (readString.indexOf("?buttonright") >0)  //checks for 'right' button press
          {
            digitalWrite(RELAY3,LOW);                  // Turns on Relay 3
            Serial.println("Right button toggled");    // serial monitor output confirmation

            timerOut = millis();                       // Sets timerOut to current millis
            Serial.print("Outdoor light timer = ");  
            Serial.println(timerIn);                   // serial monitor output confirmation  
            // note Relay 3 can also be switched off using timerOut routine
            readString="";                              //clear string for next check 
          }   

          // Relays switched off if timers have expired
          else if (millis() >= timerIn + 3000)
          {
            digitalWrite(RELAY2,HIGH);                 // Turns Relay 2 Off
            Serial.println("Inside timer expired");   // confirm toggle 
          }

          else if (millis() >= timerOut + 3000)
          {
            digitalWrite(RELAY3,HIGH);                 // Turns Relay 3 Off
            Serial.println("Outside timer expired");   // confirm toggle               
          } 

          else{  
            readString="";     //Clearing string for next read - probably not required, but left here just in case
          }
        }
      }
    }
  }
}

All help greatly appreciated.

Hello

The relay switch of operation should not be in the if (client) test, because, once the client is disconnected, your code does not enter the if (client) test and so doesn't switch of your relays

I would do the switch of operation after the close bracket of the if (client) test :
If (timeIn != 0) {
If (tmierIn > millis()....) {
Digital write low
Serial print your message
TimerIn = 0 //so you will not enter in this test while timerIn is not set again
}
}

Sorry for my English and writing format ...

Thanks for your advice. I have reformatted the timer code to read as follows:

        // Relay timer operations
        if (timerIn !=0);                                   // check if Inside timer has been activated 
            {          
            if (millis() >= timerIn + 5000);                // check if timer has expired
                 {
                  digitalWrite(RELAY2,HIGH);                // Turns Relay 2 Off
                  Serial.println("Inside timer expired");   // confirm timer operation
                  timerIn = 0;                              // remove timer flag
                 }
            }

       if (timerOut !=0);                                    // check if Outside timer has been activated 
            {          
            if (millis() >= timerOut + 5000);                // check if timer has expired
                 {
                  digitalWrite(RELAY3,HIGH);                 // Turns Relay 3 Off
                  Serial.println("Outside timer expired");   // confirm timer operation
                  timerOut = 0;                              // remove timer flag
                 }

I know the timers are being used now, because the expiry notices are appearing on the serial monitor.

But the program still doesn't work. The relays only get switched on for a few milliseconds after I press the Inside or Outside buttons - basically, the time it takes for the program to reach the timer part. Here's an example the serial monitor output:

Outside button pressed
Outdoor light timer = 0
Inside timer expired
Outside timer expired

There are 2 things wrong with this:
a) It would appear that the current value of millis() is not being assigned to the timerIn or timerOut variables. Why?
b) The If statements shown above should prevent the timer operations from running unless the timerIn or timerOut variables are NOT zero. But they are running anyway, or the monitor wouldn't see their output. How?

Any advice?

I quickly check your code ...

when you check the state of buttoncentre and buttonleft (ie for the lights) you set the timerOut value but you print the timerIn value ...

the the switch off of the relay should be outside the if (client) test ... is it ?

  } //end of if(client) 
  
  // Relay timer operations (switch off)
  if (timerIn !=0);                                   // check if Inside timer has been activated 
        {          
        if (millis() >= timerIn + 5000);                // check if timer has expired
             {
              digitalWrite(RELAY2,HIGH);                // Turns Relay 2 Off
              Serial.println("Inside timer expired");   // confirm timer operation
              timerIn = 0;                              // remove timer flag
             }
        }
   if (timerOut !=0);                                    // check if Outside timer has been activated 
        {          
        if (millis() >= timerOut + 5000);                // check if timer has expired
             {
              digitalWrite(RELAY3,HIGH);                 // Turns Relay 3 Off
              Serial.println("Outside timer expired");   // confirm timer operation
              timerOut = 0;                              // remove timer flag
             }
        }
    
} //end of loop

?

Sorry for the late response.

I couldn't decide how the multiple if statements related to each other, so I simply moved the timer section to the very start of the loop, before all the webpage stuff.

Unfortunately that didn't help at all - the monitor simply spewed out a never ending list of timer expiry notices. Which it should not have done, since the timerIn and timerOut values should be zero and should have failed the if test. So I am very confused.

I will post the whole code again in its current state so you can see how it looks now.

Thanks again for your help

Here's the complete code again.

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

// assign relay codes to pins
#define RELAY1  6                        
#define RELAY2  7                        
#define RELAY3  8                        
#define RELAY4  9

unsigned long timerIn = 0;    // long variable to store Timer 1 value
unsigned long timerOut = 0;   // long variable to Store Timer 2 value

byte mac[] = { 
  0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED }; //physical mac address
byte ip[] = { 
  192, 168, 1, 150 }; // ip in lan
byte gateway[] = { 
  192, 168, 1, 1 }; // internet access via router
byte subnet[] = { 
  255, 255, 255, 0 }; //subnet mask
EthernetServer server(80); //server port

String readString;

//////////////////////

void setup()
{
  // set up relay pins
  pinMode(RELAY1, OUTPUT);     // Set relay pins to output       
  pinMode(RELAY2, OUTPUT);     // Repeat for relays 2 to 4
  pinMode(RELAY3, OUTPUT);
  pinMode(RELAY4, OUTPUT);

  // initialise relays:  note HIGH = relay off, LOW = relay on
  digitalWrite(RELAY1, HIGH); // Set initial position of relay 1 output
  pinMode(RELAY1, OUTPUT);    // Write position to digital pin (prevents accidental toggle on startup)
  digitalWrite(RELAY2, HIGH); // Repeat for relays 2 to 4
  pinMode(RELAY2, OUTPUT);
  digitalWrite(RELAY3, HIGH);
  pinMode(RELAY3, OUTPUT);
  digitalWrite(RELAY4, HIGH);
  pinMode(RELAY4, OUTPUT);

  Ethernet.begin(mac, ip, gateway, subnet);    //start Ethernet
  server.begin();
  Serial.begin(9600);                          //enable serial data print
  Serial.println("Initialisation complete");   //check that setup has completed (in serial monitor)
}

void loop(){

  // Relay timer operations
   if (timerIn > 0 && millis() >= timerIn);   // check if Inside timer is active and expired
     {
     digitalWrite(RELAY2,HIGH);                 // Turns Relay 2 Off
     Serial.println("Inside timer expired");    // confirm timer operation
     timerIn = 0;                               // remove timer flag
     }


   if (timerOut > 0 && millis() >= timerOut);  // check if Outside timer is active and expired
    {
    digitalWrite(RELAY3,HIGH);                 // Turns Relay 3 Off
    Serial.println("Outside timer expired");   // confirm timer operation
    timerOut = 0;                              // remove timer flag
    }

 // Create a client connection
  EthernetClient client = server.available();
  if (client) {
    while (client.connected()) {
      if (client.available()) {
        char c = client.read();

        if (readString.length() < 100)         //read char by char HTTP request
        {
         readString += c;                      //store characters to string
        }

        //if HTTP request has ended
        if (c == '\n') {

          //Webpage code (title and buttons)
          Serial.println(readString); //print to serial monitor for debuging

          client.println("HTTP/1.1 200 OK"); //send new page
          client.println("Content-Type: text/html");
          client.println();

          client.println("<HTML>");
          client.println("<HEAD>");
          client.println("<meta name='apple-mobile-web-app-capable' content='yes' />");
          client.println("<meta name='apple-mobile-web-app-status-bar-style' content='black-translucent' />");
          client.println("<link rel='stylesheet' type='text/css' href='http://homeautocss.net84.net/a.css' />");
          client.println("<TITLE>Arduino Controller</TITLE>");
          client.println("</HEAD>");
          client.println("<BODY>");
          client.println("<H1>Arduino Controller</H1>");
          client.println("<hr />");
          client.println("
");

          client.println("<a href=\"/?buttonleft\"\">Door Toggle</a>");
          client.println("<a href=\"/?buttoncentre\"\">Inside</a>");
          client.println("<a href=\"/?buttonright\"\">Outside</a>
");        

          client.println("</BODY>");
          client.println("</HTML>");

          delay(1);
          //stopping client
          client.stop();

         //Door Toggle button
          if (readString.indexOf("?buttonleft") >0)        //checks for 'left' button press - used to open garage door via momentary grounding
          {
            Serial.println("Left button pressed");         // serial monitor output confirmation
            digitalWrite(RELAY1,LOW);                      // Turns on Relay 1 to toggle door
            digitalWrite(RELAY2,LOW);                      // Turns on Relay 2 for inside light
            timerIn = millis() + 5000;                     // Sets timerIn to current millis + duration
            Serial.print("Indoor light timer = ");  
            Serial.println(timerIn);                       // serial monitor output confirmation  
            delay(500);                                    // Wait 0.5 seconds for momentary grounding on Relay 1
            digitalWrite(RELAY1,HIGH);                     // Turns Relay 1 Off - door toggle operation complete
            Serial.println("Door toggle complete");        // serial monitor output confirmation
                                                           // note Relay 2 needs to be switched off using timerIn routine
            readString="";                                 //clear string for next check
          }                         
 
          //Inside Light button operation
          else if (readString.indexOf("?buttoncentre") >0) //checks for 'centre' button press - used to turn on inside light for preset period
          {
            Serial.println("Inside button pressed");       // serial monitor output confirmation
            digitalWrite(RELAY2,LOW);                      // Turns on Relay 2
            timerIn = millis() + 5000;                     // Sets timerOut to current millis + duration
            Serial.print("Indoor light timer = ");  
            Serial.println(timerIn);                       // serial monitor output confirmation  
                                                           // note Relay 2 needs to be switched off using timerIn routine
            readString="";                                 // clear string for next check
          }                               

          //Outside Light button operation
          else if (readString.indexOf("?buttonright") >0)  //checks for 'right' button press
          {
            Serial.println("Outside button pressed");      // serial monitor output confirmation
            digitalWrite(RELAY3,LOW);                      // Turns on Relay 3
            timerOut = millis() + 5000;                    // Sets timerOut to current millis + duration
            Serial.print("Outdoor light timer = ");  
            Serial.println(timerOut);                      // serial monitor output confirmation  
                                                           // note Relay 3 needs to be switched off using timerOut routine
            readString="";                                 // clear string for next check 
          }   
          
          else
          {  
          readString="";     //Clearing string for next read - probably not required, but left here just in case
          }      
        } 
      }
    }
  }
}

Changelog:

  • Moved the timer part of the code to the top, to be outside the client stuff
  • Changed timer if statement to look for timer values > 0 rather than !=0 (same thing really)
  • Assigned the duration of the timers at button press stage (eg. millis + 5000) rather than in timer expiry area
  • Various formatting to make it easier to read

Behavior with current code:
Relays only turn on momentarily (>1 second) when button is pressed
Serial monitor spits out a constant stream saying that timers have expired, even if no buttons are pressed

The question remains - how are the timer expiry routines being run, when the timer values should be zero? They are set at zero in setup, and without a button press the timer if statements should never be true.

A follow-up to say that the problem has been solved, and the code is now complete and working as intended. The biggest problem was simply that in moving code around, I'd mangled the brackets and had several of the else if statements inside each other. This made the code rather unpredictable.

For future reference, I have pasted the complete code below to assist in other similar projects.

I have 3 additional aspirations for this code:

  1. To have the status of the 2 relays (on or off) displayed on the webpage;
  2. To connect a Hall Effect sensor to the garage door, and have its status (closed or not) displayed on the webpage;
  3. To be able to press the light buttons again, before the timer expires, and have it switch off immediately.

I suspect that No. 3 is relatively easy, and just needs some additional "else if" statements.
The others are not so simple, so I'd welcome any advice.

Here's the code:

// 3 button GET server code to control relays 
// open serial monitor to see what the Arduino receives
// use the \ slash to escape the " in the html
// for use with W5100 based ethernet shields

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

#define RELAY1  6                        
#define RELAY2  7                        
#define RELAY3  8                        
#define RELAY4  9

// Create variable to store timer values
unsigned long timerIn = 0;    // u/s long variable to store Timer 1 value (inside)
unsigned long timerOut = 0;   // u/s long variable to Store Timer 2 value (outside)

byte mac[] = { 
  0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED }; //physical mac address
byte ip[] = { 
  192, 168, 1, 150 }; // ip in lan
byte gateway[] = { 
  192, 168, 1, 1 }; // internet access via router
byte subnet[] = { 
  255, 255, 255, 0 }; //subnet mask
EthernetServer server(80); //server port

String readString;

//////////////////////

void setup()
{
  pinMode(RELAY1, OUTPUT);     // Set 'codename' of relay output 1       
  pinMode(RELAY2, OUTPUT);     // Repeat for relays 2 to 4
  pinMode(RELAY3, OUTPUT);
  pinMode(RELAY4, OUTPUT);

  digitalWrite(RELAY1, HIGH); // Set initial OFF (high) position of relay 1 output
  pinMode(RELAY1, OUTPUT);    // Write OFF position to digital pin (prevents accidental toggle on startup)
  digitalWrite(RELAY2, HIGH); // Repeat for relays 2 to 4
  pinMode(RELAY2, OUTPUT);
  digitalWrite(RELAY3, HIGH);
  pinMode(RELAY3, OUTPUT);
  digitalWrite(RELAY4, HIGH);
  pinMode(RELAY4, OUTPUT);

  Ethernet.begin(mac, ip, gateway, subnet);    //start Ethernet
  server.begin();
  Serial.begin(9600);                          //enable serial data print
  Serial.println("Initialisation complete");   //check that setup has completed (in serial monitor)
}

void loop()
 
 {
  // Create a client connection
  EthernetClient client = server.available();
  if (client) {
    while (client.connected()) {
      if (client.available()) {
        char c = client.read();

        //read char by char HTTP request
        if (readString.length() < 100) {

          //store characters to string
          readString += c;
          //Serial.print(c);
        }

        //if HTTP request has ended
        if (c == '\n') {

          // Webpage code (title and buttons)
          Serial.println(readString); //print to serial monitor for debuging

          client.println("HTTP/1.1 200 OK"); //send new page
          client.println("Content-Type: text/html");
          client.println();

          client.println("<HTML>");
          client.println("<HEAD>");
          client.println("<meta name='apple-mobile-web-app-capable' content='yes' />");
          client.println("<meta name='apple-mobile-web-app-status-bar-style' content='black-translucent' />");
          client.println("<link rel='stylesheet' type='text/css' href='http://homeautocss.net84.net/a.css' />");
          client.println("<TITLE>Arduino Controller</TITLE>");
          client.println("</HEAD>");
          client.println("<BODY>");
          client.println("<H1>Arduino Controller</H1>");
          client.println("<hr />");
          client.println("
");

          client.println("<a href=\"/?buttonleft\"\">Door Toggle</a>");
          client.println("<a href=\"/?buttoncentre\"\">Inside</a>");
          client.println("<a href=\"/?buttonright\"\">Outside</a>
");        

          client.println("</BODY>");
          client.println("</HTML>");

          delay(1);
          //stopping client
          client.stop();

          //Code used to control relays
          if(readString.indexOf("?buttonleft") >0)      // Checks for 'left' button press - used to open garage door via momentary grounding
          {
            digitalWrite(RELAY1,LOW);                   // Turns on Relay 1
            delay(500);                                 // Wait half a second - provides the required momentary grounding
            digitalWrite(RELAY1,HIGH);                  // Turns Relay 1 off again
            digitalWrite(RELAY2, LOW);                  // Turns on Relay 2
            timerIn = millis();                         // Initialise inside timer
            Serial.println("Left button toggled");      // Output to monitor
            readString="";                              // Clear string for next check
          }                         

          else{
            if(readString.indexOf("?buttoncentre") >0)   // Checks for 'centre' button press - used to turn on inside light for preset period
            {
              digitalWrite(RELAY2,LOW);                  // Turns on Relay 2
              timerIn = millis();                        // Initialise inside timer
              Serial.println("Centre button toggled");   // Output to monitor
              readString="";                             // Clear string for next check
            }                            

          else{
            if(readString.indexOf("?buttonright") >0)  //checks for 'right' button press - used to turn on inside light for preset period
            {
              digitalWrite(RELAY3,LOW);                // Turns on Relay 3
              timerOut = millis();                     // Initialise outside timer
              Serial.println("Right button toggled");  // Output to monitor
              readString="";                           // Clear string for next check       
            } 
            }
  
            readString="";                               // Clearing string for next loop, if no tests are found to be true

         }
       }      
     }
   }
 }
  
// Timer delay for switching off relays 
  if (timerIn != 0 && millis() - timerIn > 1000 * 60 * 4)  // Checks for inside timer flag (not zero) and timer expiry (milliseconds x seconds x minutes since activation)  
  {
    digitalWrite(RELAY2, HIGH);                            // Turns off Relay 2
    Serial.println(millis() - timerIn);                    // Output to serial monitor
    Serial.println("Inside light off");                    
    timerIn = 0;                                           // Removes timerIn flag
  }

  if (timerOut != 0 && millis() - timerOut > 1000 * 60 * 4)// Checks for outside timer flag (not zero) and timer expiry (milliseconds x seconds x minutes since activation) 
  {
    digitalWrite(RELAY3, HIGH);                            // Turns off Relay 3
    Serial.println(millis() - timerOut);                   // Output to serial monitor
    Serial.println("Outside light off");
    timerOut = 0;                                          // Removes timerIn flag
  }
}

//Relay 4 not currently in use, although it's defined in the setup and can be easily added using a similar format to the above.

Will this work with a Yun and relay shield? I had tried the Uno with an ethernet shield but was unable to connect to the home network. The ethernet shield wouldn't take the IP address in the sketch. The Yun connects to the network via wired and wireless. Now I need a sketch that will allow the garage door to open via smartphone. Thank you for any advice or help.

I don't know much about the Yun, but the code should work fine.

Assuming that the Yun can just connect to a network and provide internet access to the Arduino side if the hardware, you'd simply need to modify the setup code to suit the pinout of your relay shield.

You'd also need to configure your network router to allow port forwarding to a specific port (hint: don't use 80 as in my code above) so that it's easy to access it from the internet.

That's about all of the help I can offer - good luck.