Timed relay with countdown LCD display

Arduino UNO with a i2c LCD display.

This timed relay is for a UV-C disinfection chamber that uses 20 light tubes (30W 36in TUV T8 Germicidal Fluorescent) with interlocking doors to prevent entry while the system is running. The Arduino only controls the start/stop functions. It works as it should but I would like to improve it.

I got great help on this forum to accomplish this project, then added a few things here and there and so far works as it should.

The blinking LED is an indicator that the relay is on during the countdown, also the LCD states "RUNNING"

Now, what I have been unable to do is to have a countdown for the 20 minutes, and to print it here instead of the word running.

 lcd.setCursor(7,1);
     lcd.print("RUNNING   ");

I have looked all over and came up with a few half working solutions but the closest I got I was able to display the countdown at the press of the start button but such countdown was already started, it would start once the device was powered on, for example if I waited one minute to press the button the count will start at 19:00

#include <LiquidCrystal_I2C.h>
#include <OneButton.h>

LiquidCrystal_I2C lcd(0x27, 16, 2);

const byte buttonPin = 2;  // SIGNAL FROM MOMENTARY START BUTTON
const byte resetPin = 4;   // SIGNAL TO RESET COUNTDOWN, MOMENTARY PUSHBUTTON
const byte relayPin = 12;  // 5 VOLT RELAY TO SWITCH LOAD ON
const byte ledPin = 13;    //  BLINKING LED TO INDICATE PROCESS 
OneButton startButton(buttonPin);
OneButton resetButton(resetPin);
int ledState = LOW;
unsigned long previousMillis = 0;
const long interval = 350;                 // INTERVAL TO TOGGLE THE LED ON AND OFF
const unsigned long duration = 1200000ul;  // 20 MINUTES IS THE SET TIME TO TURN THE LOAD OFF AFTER HITTING START BUTTON
unsigned long chrono;                      // RECORD TIME TO COMPARE MILLIS FUNCTION
bool relayIsOn = false;


void relayOff() {
  lcd.setCursor(7,1);
  lcd.print("STAND BY");
  digitalWrite(ledPin, LOW);
  digitalWrite(relayPin, LOW);
  relayIsOn = false;
}

void relayOn() {
  digitalWrite(relayPin, HIGH);
  chrono = millis();
  relayIsOn = true;
}

void start() {

  if (!relayIsOn) relayOn();
}

void stop() {
  relayOff();
}



void setup() {

  lcd.init();
  lcd.backlight();
  pinMode(ledPin, OUTPUT);
  pinMode(relayPin, OUTPUT);
  relayOff();
  lcd.setCursor(1,0);
  lcd.print("RIVER BEND UV ROOM");
  lcd.setCursor(7,1);
  lcd.print("STAND BY");
  startButton.attachClick(start);
  resetButton.attachClick(stop);
}


void loop() {

  startButton.tick();
  resetButton.tick();

  if (relayIsOn && (millis() - chrono >= duration)) relayOff();

  unsigned long currentMillis = millis();

  if (relayIsOn && (currentMillis - previousMillis >= interval)) {
     lcd.setCursor(7,1);
     lcd.print("RUNNING   ");
    previousMillis = currentMillis;

    // if the LED is off turn it on and vice-versa:
    if (ledState == LOW) {
      ledState = HIGH;
    } else {
      ledState = LOW;
    }

    
    digitalWrite(ledPin, ledState);
    
  }

}

Thanks in advance

detail the required sequence of events and what is not working

In connection with reply #3, you mentioned multiple versions but didn't say which one you posted.

This sketch:

#include <LiquidCrystal_I2C.h>
#include <OneButton.h>

LiquidCrystal_I2C lcd(0x27, 16, 2);
long hour = 00, minute = 00, second = 10;
long countdown_time = (hour*3600) + (minute * 60) + second;
const byte buttonPin = 2;  // SIGNAL FROM MOMENTARY START BUTTON
const byte resetPin = 4;   // SIGNAL TO RESET COUNTDOWN, MOMENTARY PUSHBUTTON
const byte relayPin = 12;  // 5 VOLT RELAY TO SWITCH LOAD ON
const byte ledPin = 13;    //  BLINKING LED TO INDICATE PROCESS
OneButton startButton(buttonPin);
OneButton resetButton(resetPin);
int ledState = LOW;
unsigned long previousMillis = 0;
const long interval = 350;                 // INTERVAL TO TOGGLE THE LED ON AND OFF
const unsigned long duration = 10000ul;  // 20 MINUTES IS THE SET TIME TO TURN THE LOAD OFF AFTER HITTING START BUTTON
unsigned long chrono;                      // RECORD TIME TO COMPARE MILLIS FUNCTION
bool relayIsOn = false;


void relayOff() {
  lcd.setCursor(6,1);
  lcd.print("STAND BY");
  digitalWrite(ledPin, LOW);
  digitalWrite(relayPin, LOW);
  relayIsOn = false;
}

void relayOn() {
  digitalWrite(relayPin, HIGH);
  chrono = millis();
  relayIsOn = true;
}

void start() {

  if (!relayIsOn) relayOn();
}

void stop() {
  relayOff();
}



void setup() {

  lcd.init();
  lcd.backlight();
  pinMode(ledPin, OUTPUT);
  pinMode(relayPin, OUTPUT);
  relayOff();
  lcd.setCursor(1,0);
  lcd.print("RIVER BEND UV ROOM");
  lcd.setCursor(6,1);
  lcd.print("STAND BY");
  startButton.attachClick(start);
  resetButton.attachClick(stop);
}


void loop() {

  startButton.tick();
  resetButton.tick();

  if (relayIsOn && (millis() - chrono >= duration)) relayOff();

  unsigned long currentMillis = millis();

  if (relayIsOn && (currentMillis - previousMillis >= interval)) {
     lcd.setCursor(6,1);
     lcd.print(" RUNNING   ");
    previousMillis = currentMillis;

    // if the LED is off turn it on and vice-versa:
    if (ledState == LOW) {
      ledState = HIGH;
    } else {
      ledState = LOW;
    }

    // set the LED with the ledState of the variable:
    digitalWrite(ledPin, ledState);
    
  }

 long countdowntime_seconds = countdown_time - (millis() / 1000);
  if (relayIsOn && (countdowntime_seconds >= 0)) {
    long countdown_hour = countdowntime_seconds / 3600;
    long countdown_minute = ((countdowntime_seconds / 60)%60);
    long countdown_sec = countdowntime_seconds % 60;
    lcd.setCursor(6,1);
    if (countdown_hour < 10) {
      lcd.print("0");
    }
    lcd.print(countdown_hour);
    lcd.print(":");
    if (countdown_minute < 10) {
      lcd.print("0");
    }
    lcd.print(countdown_minute);
    lcd.print(":");
    if (countdown_sec < 10) {
      lcd.print("0");
    }
    lcd.print(countdown_sec);
  }
  }

works but the countdown starts as soon as power is on, It only displays it after pressing start.
i am not sure if makes sense

Have you tested the buttons alone, independently from all the other logic, like a test sketch?

yes the prototype physically works, relay turns on, LCD reacts, but i am assuming my problem is on the use of millis()

That does not answer the question I asked you. While we're on that subject, how are the buttons wired?

Assumptions rarely lead to problem solutions. Determinations are what you need.

We want to know exactly what troubleshooting steps you've made, to see if everything has been covered, and also to avoid redundant suggestions.

buttons have a pullup resistor, momentary low gives signal to either start or reset pins

Okay, that's one of several answers... thanks. You didn't reply about the test sketches...

Yes, I am able to use the buttons on other sketches using digitalRead and triggering functions to print to serial or lcd

Where do you initialize 'countdown_time'? Only once, in the declaration. So it will always start from boot time.

But that is different, isn't it? What about when using the same button library?

using same button library

I think I catch a hint but I tried doing it on the loop:

void loop() {

  startButton.tick();
  resetButton.tick();

  if (relayIsOn && (millis() - chrono >= duration)) relayOff();

  unsigned long currentMillis = millis();

  if (relayIsOn && (currentMillis - previousMillis >= interval)) {
     lcd.setCursor(6,1);
     lcd.print(" RUNNING   ");
    previousMillis = currentMillis;

    // if the LED is off turn it on and vice-versa:
    if (ledState == LOW) {
      ledState = HIGH;
    } else {
      ledState = LOW;
    }

    // set the LED with the ledState of the variable:
    digitalWrite(ledPin, ledState);
    
  }
if (relayIsOn) {
long hour = 00, minute = 00, second = 59;
long countdown_time = (hour*3600) + (minute * 60) + second;
count = millis();
long countdowntime_seconds = countdown_time - (millis() / 1000);
  if (countdowntime_seconds >= 0) {
    long countdown_hour = countdowntime_seconds / 3600;
    long countdown_minute = ((countdowntime_seconds / 60)%60);
    long countdown_sec = countdowntime_seconds % 60;
    lcd.setCursor(6,1);
    if (countdown_hour < 10) {
      lcd.print("0");
    }
    lcd.print(countdown_hour);
    lcd.print(":");
    if (countdown_minute < 10) {
      lcd.print("0");
    }
    lcd.print(countdown_minute);
    lcd.print(":");
    if (countdown_sec < 10) {
      lcd.print("0");
    }
    lcd.print(countdown_sec);
  }
  }
}

with same results

You're initializing it to the same value repeatedly. Those values are constant, so you are really just saying:

if (relayIsOn) {
long second = 59;
long countdown_time = second;

As long as the condition is true, the time will be set to 59 each and every time through loop().

I suggest a big step back from the details and start to look at your logic top down.

Well, Thanks for all your help. Seems like we have a winner here, works fine, less complicated.

#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0x27, 16, 2);
uint8_t clock[8] = {0x0, 0xe, 0x15, 0x17, 0x11, 0xe, 0x0};
const int switch_on = 2, input_off_time = 3, output = 12, reset = 4;
int off_timer_start = 0, offtime = 20;


void setup() {
  pinMode(switch_on, INPUT);
  pinMode(reset, INPUT);
  pinMode(input_off_time, INPUT);
  pinMode(output, OUTPUT);
  lcd.init();
  lcd.backlight();
  lcd.setCursor(1, 0);
  lcd.print("RIVER BEND UV ROOM    ");
  lcd.setCursor(6, 1);
  lcd.print("OFF       ");
  lcd.createChar(2, clock);
  lcd.setCursor(0, 1);
  lcd.write(2);
  lcd.print((String)" " + offtime + " ");
}

void loop() {
  int x = ((millis() / 1000) - off_timer_start)/60;
  if (x <= offtime && digitalRead(output) == HIGH) {
    int m = ((offtime * 60) + off_timer_start - (millis() / 1000))/60;
    int s = ((offtime * 60) + off_timer_start - (millis() / 1000))%60;
    lcd.setCursor(10, 1);
    lcd.print((String)" " + m + ":" + s +" ");
  }
  if (digitalRead(switch_on) == LOW) {
    digitalWrite(output, HIGH);
    off_timer_start = millis() / 1000;
    lcd.setCursor(6, 1);
    lcd.print("ON        ");
  } else if (x >= offtime && digitalRead(output == HIGH)) {
    digitalWrite(output, LOW);
    lcd.setCursor(6, 1);
    lcd.print("OFF       ");
  }
  while (digitalRead(input_off_time) == HIGH) {
    if (offtime <= 29) { //change max time limit
      offtime += 1; //change increment by x
    } else {
      offtime = 0;
    }
    lcd.setCursor(0, 1);
    lcd.write(2);
    lcd.print((String)" " + offtime + " ");
    delay(100);// adjust speed of incrementing

  }
if ((digitalRead(output) == HIGH) && (digitalRead(reset) == HIGH)) {
    digitalWrite(output, LOW);
    lcd.setCursor(6, 1);
    lcd.print("OFF       ");

}

}