Adding a timer to a push button sketch

I am using this code from Robjax for a 4 button/relay controller for a sprinkler. I would like to have a fail-safe timer (say, 25 minutes) in case the button does not get pushed to turn off the relay. Or, in maybe a timer begins when the relay makes, and the button can be used to turn it off before it times out. Any help will be appreciated. I will be tinkering with it myself in the meantime.

/*

  • Arduino code to push-ON and push-OFF 4 relays

  • when button pushed, relay ON and stay ON

  • push again to make it OFF and stay OFF

  • this can be done with 4 push button to control 4 relays.

  • written by Ahmad Shamshiri for Robojax. com

  • on December 11, 2018 at 19:39 in Ajax, Ontario, Canada

  • This code is "AS IS" without warranty or liability. Free to be used as long as you keep this note intact.*

  • This code has been download from Robojax .com
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    GNU General Public License for more details.

*/
const int pushButton ={2,3,4,5};// define push button inputs
const int relayPin={11,10,9,8};// output pins where 4 relays will be connected
String relayNames ={"CH1", "CH2","CH3","CH4"};// Just put name for 4 relays
int pushed ={0,0,0,0};// status of each buttons
int relayStatus ={HIGH,HIGH,HIGH,HIGH};// initial status of relay

void setup() {
// Robojax.com 4-Relay-4-Push button 20181211
Serial.begin(9600);// initialize serial monitor
for(int i=0; i<4; i++)
{
pinMode(pushButton[i], INPUT_PULLUP);
pinMode(relayPin[i], OUTPUT);
digitalWrite(relayPin[i], HIGH);// initial relay status to be OFF
}

// Robojax . com 4-Relay-4-Push button 20181211
}

void loop() {
// Robojax.com 4-Relay-4-Push button 20181211

for(int i=0; i<4; i++)
{
int val = digitalRead(pushButton[i]);
if(val == HIGH && relayStatus[i] == LOW){

  pushed[i] = 1-pushed[i];
  delay(100);
}// if   

relayStatus[i] = val;

  if(pushed[i] == HIGH){
    Serial.print(relayNames[i]);
    Serial.println(" ON");
    digitalWrite(relayPin[i], LOW); 
   
  }else{
    Serial.print(relayNames[i]);
    Serial.println(" OFF");
    digitalWrite(relayPin[i], HIGH);

  }// else   

}// for
Serial.println("==");
delay(100);
// Robojax. com 4-Relay-4-Push button 20181211
}// loop end

This skeleton of a sketch can easily be modified to your needs.


//*************************************^****************************************
//  FileName.ino
//  LarryD
//  Version   YY/MM/DD     Comments
//  1.00      19/01/01     Running code
//
//
//*************************************^****************************************
 
 
//                                M A C R O S
//*************************************^****************************************
//                                         Resistor    LED
#define LEDon       HIGH           //pin----[220R]-----[>|]----GND
#define LEDoff      LOW
 
#define noCHANGE    -1             //there was no change in switch state

#define isPUSHED    LOW            //   ~50k                 normally open
#define isRELEASED  HIGH           //INPUT_PULLUP-----pin----[N.O. switch]----GND
 
#define enable      true
#define disable     false
 
//*************************************^****************************************
//                       C l a s s   m a k e T i m e r
//*************************************^****************************************
//
class makeTimer
{
#define MILLIS true
#define MICROS false
 
    //Note: since code takes time to execute, precise micro second timing is difficult.
 
    //StartTime  = the time this "TIMER" was (re)started
    //Interval   = Interval/delay time we are looking for
    //Restart    = do we start "this TIMER" again and again
    //EnableFlag = is "this TIMER" enabled/allowed to be accessed
    //TimerType  = true/MILLIS = millis(), false/MICROS = micros()
    //****************************
    //For each TIMER object you need:
    //Example:
    //   makeTimer myTimer = //create a Timer named "myTimer"
    //   {
    //     0, 200UL, true, true, MILLIS  //StartTime, Interval, Restart, EnableFlag, TimerType (MILLIS/MICROS)
    //   };
    //
    //Each TIMER object is made up of 5 variables:
    //myTimer.StartTime, myTimer.Interval, myTimer.Restart, myTimer.EnableFlag and myTimer.TimerType
    //
    //You have the following class functions:
    //myTimer.CheckTime() and  myTimer.EnableTimer()  and  myTimer.DisableTimer()
    //****************************
 
  public:
    unsigned long StartTime;
    unsigned long Interval;
    bool          Restart;
    bool          EnableFlag;
    bool          TimerType;
 
    unsigned long currentTime;
 
    //                            m a k e t i m e r ( )
    //*************************************^****************************************
    //Constructor
    //
    makeTimer(unsigned long ST, unsigned long INT, bool RES, bool ENA, bool TT)
    {
      StartTime  = ST;
      Interval   = INT;
      Restart    = RES;
      EnableFlag = ENA;
      TimerType  = TT;
 
    } //END of makeTimer()
 
    //                           C h e c k T i m e r ( )
    //*************************************^****************************************
    //Delay time expired function "CheckTime()"
    //
    bool CheckTime()
    {
      //StartTime  = the time TIMER was (re)started
      //Interval   = interval/delay we are looking for
      //Restart    = do we restart TIMER automatically
      //EnableFlag = is TIMER enabled/allowed to be accessed
      //TimerType  = use ms or us timing, MILLIS/true = millis(), MICROS/false = micros()
 
      if (TimerType == MILLIS)
      {
        //using millis() for this TIMER
        currentTime = millis();
      }
 
      else
      {
        //using micros() for this TIMER
        currentTime = micros();
      }
 
      //is the TIMER enabled and has the TIMER expired?
      if (EnableFlag == true && currentTime - StartTime >= Interval)
        //Note: if delays of < 2ms are needed use micros() and adjust 'Interval' as needed
      {
        //should we restart the TIMER automatically?
        if (Restart)
        {
          //Restart the TIMER
          StartTime = currentTime;
          //optionally
          //StartTime = StartTime + Interval;
        }
 
        //this TIMER did expired
        return true;
 
      } //END of   if(EnableFlag == true && currentTime - StartTime >= Interval)
 
      //this TIMER did not expire or it is disabled
      return false;
 
    } //END of  CheckTime()
 
    //                          E n a b l e T i m e r ( )
    //*************************************^****************************************
    //Function to enable and initialize a TIMER, ex: myTimer.EnableTimer();
    //
    void EnableTimer()
    {
      EnableFlag = true;
 
      //initialize lastTime to current millis() or micros()
      if (TimerType == true)
      {
        StartTime = millis();
      }
 
      else
      {
        StartTime = micros();
      }
 
    } //END of  EnableTimer()
 
    //                         D i s a b l e T i m e r ( )
    //*************************************^****************************************
    //Function to disable a TIMER, ex: myTimer.DisableTimer();
    //
    void DisableTimer()
    {
      EnableFlag = false;
 
    } //END of  DisableTimer()
 
}; //END of  class makeTimer
 
 
//*************************************^****************************************
//                         Instantiate TIMER objects
//*************************************^****************************************
makeTimer heartbeatLED =         //create a millisecond TIMER 'heartbeatLED'
{
  0, 200UL, true, true, MILLIS   //StartTime, Interval, Restart, EnableFlag, TimerType (MILLIS/MICROS)
};
 
//*************
makeTimer checkMySwitches =      //create a millisecond TIMER 'checkMySwitches'
{
  0, 20UL, true, true, MILLIS    //StartTime, Interval, Restart, EnableFlag, TimerType (MILLIS/MICROS)
};
 
 
//*************************************^****************************************
//                    C l a s s   d e f i n e S w i t c h
//*************************************^****************************************
//
class defineSwitch
{
  public:
    byte          pin;           //physical pin being used for this switch
    byte          closedLevel;   //pin level when the switch is closed
   
    unsigned long switchMillis;  //how long the switch was held closed
    byte          lastState;     //last state the switch was in
 
 
    //                          d e f i n e S w i t c h ( )
    //*************************************^****************************************
    //Constructor
    //
    defineSwitch(byte _pin, byte _closedLevel)
    {
      pin          = _pin;
      closedLevel  = _closedLevel;
     
      //how long the switch was closed
      switchMillis = 0;
 
      //the level of the switch when released
      lastState    = 1 - closedLevel;
 
      pinMode(pin, INPUT_PULLUP);
 
    } //END of  defineSwitch()
 
    //                         C h a n g e I n S t a t e ( )
    //*************************************^****************************************
    //check to see if there is a change in switch state
    //
    char ChangeInState()
    {
      byte currentState = digitalRead(pin);
 
      if (lastState != currentState)
      {
        //update to the new state
        lastState = currentState;
 
        if (currentState == closedLevel)
        {
          //the time the switch was closed
          switchMillis = millis();
 
          //the switch was closed/pushed
          return closedLevel;
        }
 
        else
        {
          //calculate the time the switch was closed
          switchMillis = millis() - switchMillis;
 
          //then switch was opened/released
          return 1 - closedLevel;
        }
 
      } //END of  if (lastState != currentState)
 
      //there was no change in switch state
      return -1;
 
    } //END of ChangeInState()
 
    //                             P i n S t a t e ( )
    //*************************************^****************************************
    //check to see if there is a change in switch state
    //
    byte PinState()
    {
      byte state = digitalRead(pin);
 
      return state;
     
    } //END of  PinState()
 
}; //END of  class defineSwitch
 
 
//*************************************^****************************************
//                         Instantiate switch objects
//*************************************^****************************************
//                                ~50k                  normally open
//switch are wired as such     INPUT_PULLUP-----pin-----[N.O. switch]----GND
//
//                       pin#, closed level
defineSwitch SWITCH1     = {2, isPUSHED};
defineSwitch SWITCH2     = {3, isPUSHED};
 
 
//*************************************^****************************************
//                       G l o b a l   v a r i a b l e s
//*************************************^****************************************
 
// I N P U T S
 
 
// O U T P U T S
const byte PIN_13        = 13;
const byte PIN_12        = 12;
const byte PIN_11        = 11;
 
// S R A M
 
int counter;
 
 
//                                s e t u p ( )
//*************************************^****************************************
//
void setup()
{
  Serial.begin(9600);
 
  //***************************
  pinMode(PIN_13, OUTPUT);
  pinMode(PIN_12, OUTPUT);
  pinMode(PIN_11, OUTPUT);
 
} //END of  setup()
 
 
//                                l o o p ( )
//*************************************^****************************************
//
void loop()
{
  //****************************
  //is it time to toggle the heartbeat LED
  if (heartbeatLED.CheckTime())
  {
    //Toggle heartbeatLED
    //digitalWrite(heartLED, !digitalRead(heartLED)); //generic
    PINB = 0x20; // Toggles pin D13 UNO
 
    //Toggle pins specific to UNO
    //PINB=0x20; //13 PINB=0x10; //12  PINB=0x08; //11  PINB=0x04; //10  PINB=0x02; //9  PINB=0x01; //8
    //PIND=0x80; //7  PIND=0x40; //6   PIND=0x20; //5   PIND=0x10; //4   PIND=0x08; //3  PIND=0x04; //2
 
    //if you only want this section of code to happen once
    //uncomment the next line
    //heartbeatLED.DisableTimer();
 
  } //END of   if(heartbeatLED.CheckTime())
 
  //****************************
  //is it time to check the switches ?
  if (checkMySwitches.CheckTime())
  {
    checkSwiches();
 
  } //END of   if(checkMySwitches.CheckTime())
 
 
  //****************************
  //None blocking code goes here
  //****************************
 
} //END of  loop()
 
 
//                         c h e c k S w i t c h e s ( )
//*************************************^****************************************
//
void checkSwiches()
{
  //******************************************************        SWITCH1
  //3 possibilities: noCHANGE, isPUSHED or isRELEASED
  switch (SWITCH1.ChangeInState())
  {
    //******************
    case noCHANGE:
      //do nothing
 
      break;
 
    //******************
    case isPUSHED:
      //digitalWrite(12, !digitalRead(12)); //generic
      PINB = 0x10;  //Toggles pin D12,  specific to UNO
 
      counter++;
      Serial.println(counter);
 
      break;
 
    //******************
    case isRELEASED:
      //how long was the switch pushed for ?
      Serial.print("SWITCH1 was pressed for: ");
      Serial.println(SWITCH1.switchMillis);
 
      break;
 
  } //END of  switch/case
 
  //******************************************************        SWITCH2
  //3 possibilities: noCHANGE, isPUSHED or isRELEASED
  switch (SWITCH2.ChangeInState())
  {
    //******************
    case noCHANGE:
      //do nothing
 
      break;
 
    //******************
    case isPUSHED:
      //digitalWrite(11, !digitalRead(11)); //generic
      PINB = 0x08;  //Toggles pin D11,  specific to UNO
 
      counter--;
      if (counter < 0)
      {
        counter = 0;
      }
 
      Serial.println(counter);
 
      break;
 
    //******************
    case isRELEASED:
      //how long was the switch pushed for ?
      Serial.print("SWITCH2 was pressed for: ");
      Serial.println(SWITCH2.switchMillis);
 
      break;
 
  } //END of  switch/case
 
  //******************************************************        Other switches
 
} //END of checkSwitches()
 
 
 
//*************************************^****************************************
//                            E N D  O F  C O D E
//*************************************^****************************************

consider

/*
Arduino code to push-ON and push-OFF 4 relays
when button pushed, relay ON and stay ON
push again to make it OFF and stay OFF
this can be done with 4 push button to control 4 relays.
written by Ahmad Shamshiri for Robojax. com
on December 11, 2018 at 19:39 in Ajax, Ontario, Canada
This code is "AS IS" without warranty or liability. Free to be used as long as you keep this note intact.*
This code has been download from Robojax .com
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY;
without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
*/

#undef MyHW
#ifdef MyHW
const byte pushButton [] = { A1, A2, A3 };
const byte relayPin   [] = { 10, 11, 12 };
#else
const byte pushButton [] = {2,3,4,5};// define push button inputs
const byte relayPin [] = {11,10,9,8};// output pins where 4 relays will be connected
#endif

#define N_RELAY     sizeof(relayPin)

String relayNames  []  ={"CH1", "CH2","CH3","CH4"};// Just put name for 4 relays
int    pushed      [N_RELAY];
int    relayStatus [N_RELAY];

enum { OFF = HIGH, ON = LOW };

const unsigned long TimeOut = 10000;
unsigned long       msecOn [N_RELAY];
int                 tmrOn [N_RELAY];
unsigned long       msec;

void setup () {
    // Robojax.com 4-Relay-4-Push button 20181211
    Serial.begin (9600);// initialize serial monitor

    for (unsigned i=0; i< N_RELAY; i++)
    {
        pinMode (pushButton[i], INPUT_PULLUP);
        pushed [i] = digitalRead (pushButton [i]);

        pinMode (relayPin[i], OUTPUT);
        relayStatus [i] = OFF;
        digitalWrite (relayPin[i], relayStatus [i]);
    }
    // Robojax . com 4-Relay-4-Push button 20181211
}
void
relayTgl (
    int     idx,
    int     onOff )
{
    Serial.print (relayNames[idx]);

    if (ON == onOff)  {
        Serial.println (" ON");
        digitalWrite (relayPin[idx], ON);
        tmrOn  [idx] = idx + 1;
        msecOn [idx] = msec;
    }
    else  {
        Serial.println (" OFF");
        digitalWrite (relayPin[idx], OFF);
        tmrOn [idx] = 0;
    }
}

void loop () {
    msec = millis ();

    // Robojax.com 4-Relay-4-Push button 20181211
    for (unsigned i=0; i< N_RELAY; i++)
    {
        int val = digitalRead (pushButton[i]);
        if (pushed [i] != val)  {
            pushed[i] = val;
            delay (10);         // debounce

            if (LOW == val) {
                relayStatus[i] = OFF == relayStatus [i] ? ON : OFF;
                relayTgl (i, relayStatus [i]);
            }
        }// else

        if (tmrOn [i] && (msec - msecOn [i]) > TimeOut)
            relayTgl (tmrOn [i]-1, OFF);
    }// for

}// loop end
1 Like

Interesting code

I loaded this on my setup and works great however I have noticed that when the code is first loaded onto the arduino each button requires 2 presses to turn on the associated relay, once the relay is on it can be toggled on and off with one press however once the timer times out and the relay is off it then again requires 2 presses of the button to turn on the relay.

How would you fix this?

led toggles on first press when i test this

What about after it times out? do you need to press twice to turn it on again and start the timer again?

you're right. relay status was not updated after time-out

/*
Arduino code to push-ON and push-OFF 4 relays
when button pushed, relay ON and stay ON
push again to make it OFF and stay OFF
this can be done with 4 push button to control 4 relays.
written by Ahmad Shamshiri for Robojax. com
on December 11, 2018 at 19:39 in Ajax, Ontario, Canada
This code is "AS IS" without warranty or liability. Free to be used as long as you keep this note intact.*
This code has been download from Robojax .com
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY;
without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
*/
#define MyHW
#ifdef MyHW
const byte pushButton [] = { A1, A2, A3 };
const byte relayPin   [] = { 10, 11, 12 };
#else
const byte pushButton [] = {2,3,4,5};// define push button inputs
const byte relayPin [] = {11,10,9,8};// output pins where 4 relays will be connected
#endif
#define N_RELAY     sizeof(relayPin)
String relayNames  []  ={"CH1", "CH2","CH3","CH4"};// Just put name for 4 relays
int    pushed      [N_RELAY];
int    relayStatus [N_RELAY];
enum { OFF = HIGH, ON = LOW };
const unsigned long TimeOut = 10000;
unsigned long       msecOn [N_RELAY];
int                 tmrOn [N_RELAY];
unsigned long       msec;
void setup () {
    // Robojax.com 4-Relay-4-Push button 20181211
    Serial.begin (9600);// initialize serial monitor
    for (unsigned i=0; i< N_RELAY; i++)
    {
        pinMode (pushButton[i], INPUT_PULLUP);
        pushed [i] = digitalRead (pushButton [i]);
        pinMode (relayPin[i], OUTPUT);
        relayStatus [i] = OFF;
        digitalWrite (relayPin[i], relayStatus [i]);
    }
    // Robojax . com 4-Relay-4-Push button 20181211
}
void
relayTgl (
int     idx,
int     onOff )
{
    Serial.print (relayNames[idx]);
    if (ON == onOff)  {
        Serial.println (" ON");
        digitalWrite (relayPin[idx], ON);
        tmrOn  [idx] = idx + 1;
        msecOn [idx] = msec;
    }
    else  {
        Serial.println (" OFF");
        digitalWrite (relayPin[idx], OFF);
        tmrOn [idx] = 0;
    }
}
void loop () {
    msec = millis ();
    // Robojax.com 4-Relay-4-Push button 20181211
    for (unsigned i=0; i< N_RELAY; i++)
    {
        int val = digitalRead (pushButton[i]);
        if (pushed [i] != val)  {
            pushed[i] = val;
            delay (10);         // debounce
            if (LOW == val) {
                relayStatus[i] = OFF == relayStatus [i] ? ON : OFF;
                relayTgl (i, relayStatus [i]);
            }
        }

        if (tmrOn [i] && (msec - msecOn [i]) > TimeOut)  {
            relayStatus [tmrOn [i]-1] = OFF;
            relayTgl (tmrOn [i]-1,      OFF);
        }
    }// for
}// loop end

Hello
I have found a similar sketch in my sketch box.
Try it.

//BLOCK COMMENT
// https://forum.arduino.cc/t/adding-a-timer-to-a-push-button-sketch/879462/7
#define ProjectName "Adding a timer to a push button"
// CONSTANT DEFINITION
// you need to adapt these constants to your requirements
const byte relays[] {2, 3, 4, 5};
const byte buttons[] {A0, A1, A2, A3};
const unsigned long TimeOut[] {1000,2000,3000,4000};
// VARIABLE DECLARATION
enum {One, Two, Three, Four};
struct BLOCK {
  int name;
  byte relay;
  byte button;
  bool state;
  byte click;
  unsigned long stamp;
  unsigned long duration;
} blocks [] {
  {One, relays[One], buttons[One], 1, 0, 0, TimeOut[One]},
  {Two, relays[Two], buttons[Two], 1, 0, 0, TimeOut[Two]},
  {Three, relays[Three], buttons[Three], 1, 0, 0, TimeOut[Three]},
  {Four, relays[Four], buttons[Four], 1, 0, 0, TimeOut[Four]},
};
// FUNCTIONS
void setup() {
  Serial.begin(9600);
  Serial.println(F("."));
  Serial.print(F("File   : ")), Serial.println(__FILE__);
  Serial.print(F("Date   : ")), Serial.println(__DATE__);
  Serial.print(F("Project: ")), Serial.println(ProjectName);
  pinMode (LED_BUILTIN, OUTPUT);
  for (auto &relay : relays) pinMode(relay, OUTPUT), digitalWrite(relay,HIGH);
  for (auto button : buttons) pinMode(button, INPUT_PULLUP);
}
void loop () {
  unsigned long currentTime = millis();
  digitalWrite(LED_BUILTIN, (currentTime / 500) % 2);
  static unsigned long myMillis;
  const unsigned long myDuration {20};
  if (currentTime - myMillis >= myDuration) {
    myMillis = currentTime;
    for (auto &block : blocks) {
      bool stateNew = digitalRead(block.button);
      if (block.state != stateNew) {
        block.state = stateNew;
        if (!block.state) {
          block.click++;
          block.click = block.click % 2;
          digitalWrite(block.relay, !block.click);
          blocks[block.name].stamp = currentTime;
        }
      }
    }
  }
  for (auto &block : blocks) {
    if (currentTime - block.stamp >= block.duration && block.click) {
      block.click++;
      block.click = block.click % 2;
      digitalWrite(block.relay, !block.click);
    }
  }
}

Have fun and enjoy the weekend

What do these lines in the code do?

I tried your code and it works great thanks for sharing.

With my current setup when your code is loaded on my Leonardo all the relays are on and when i press a button they turn off and then on again after the timer finishes.

What part of your code determines the relays state? and how can I reverse it?

Hello and good moring
Take a view into o.m. sketch. I´ve made the modification wrt your relais board.
Have fun and enjoy the sunday

Thanks for that :+1:

If you like the sketch pretty much, make a donation to a local children's hospice.

selects code at compile time. change #undef to #define to "enable" the code under the #ifdef

Will do Thanks for the help

Hello Paul

I used your code, but cannot find the variable for the time? When I push the button the relay/light starts. Pushed again, the relay/light stops. But I cannot let it stop in a specific time.

For the detail, I'm just using one relay.

I found it in the mean time :grinning: there was something wrong in the connections.

hand over a pair of glasses