Help with automatic shut off

Hello, I need help with a sketch. I want to read an LDR and turn on a Relay when the LDR’s output goes from >=800 to <=100.

I want the Relay to stay energized for 10 seconds, then I want it to turn off until the 800 to 100 LDR levels event happens again.

int LDR = A5;
const int Relay = 8;
int LDRValue = 0;   // variable to store the value coming from the LDR
int prev_LDRValue = 0;   // variable to store the previous state of the LDR
int RELAYstate = 0;

unsigned long previousMillis = 0;        // will store last time Relay was updated
long OnTime = 10000;           // milliseconds of on-time

void setup() {
  Serial.begin(9600);
  pinMode (Relay, OUTPUT);
}

void loop() {

  // check to see if it's time to change the state of the LED
  unsigned long currentMillis = millis();


  int LDRValue = analogRead(LDR);
  Serial.println(LDRValue);

  if (prev_LDRValue >= 800 && LDRValue < 100) {
    RELAYstate = HIGH;
    prev_LDRValue = LDRValue;
  }
  else {
    RELAYstate = LOW;
  }


  if ((RELAYstate == HIGH) && (currentMillis - previousMillis >= OnTime))
  {
    RELAYstate = LOW;  // Turn Relay OFF
    previousMillis = currentMillis;  // Remember the time
    
  }

  digitalWrite(Relay, RELAYstate);
  
}

You haven’t told us what your program actually does.

My guess is that this

  if (prev_LDRValue >= 800 && LDRValue < 100) {
    RELAYstate = HIGH;
    prev_LDRValue = LDRValue;
  }
  else {
    RELAYstate = LOW;
  }

does not work as you think because it will set RELAYstate LOW when you don’t want it to.

I think it may be a solution simply to remove the ELSE part altogether and leave it to the timeout to turn the relay off.

If that does not work then I think you need another variable to keep track of whether the relay is ON and waiting for the timeout to expire in which case the ELSE would be something like

else if (timeoutRunning == false) {
   RELAYstate = LOW;
}

But try the simple solution first.

If none of this makes sense then tell us exactly what the program does and what you want it to do that is different.

…R

So, your sketch can be in one of three states.

1 - the ldr level is out of range
2 - the ldr level is in range, and your relay is activated
2 - the ldr level is in range, but you have already done the relay thing.

Your sketch has three events that is interested in:
1 - the ldr comes into range
2 - the ldr goes out of range
3 - the relay has been on for 10 seconds

enum State { IDLE, TEN_SECONDS, ACTIVATION_DONE } state = IDLE;
uint32_t activation_time_ms;

void loop() {
  int reading = analogRead(A0);

  switch(state) {
  case IDLE:
    if(reading <= 100) {
      digitalWrite(relay_pin, HIGH);
      activation_time_ms = millis();
      state = TEN_SECONDS;
    }
    break;

  case TEN_SECONDS:
    if(millis - activation_time_ms >= 10000) {
      digitalWrite(relay_pin, LOW);
      state = ACTIVATION_DONE;
    }
    break;

  case ACTIVATION_DONE:
    if(reading >= 800) {
      state = IDLE;
    }
    break;
  }
}

Thanks for the help, guys. Here’s what I have now.

Robin2, I commented out the else portion as you suggested and nothing changed. I didn’t implement your 2nd suggestion yet.

PaulMurray I rewrote my code to include your suggestions (and I probably have something in the wrong place) but it now triggers the LED (stand-in for the relay for testing) for just a flicker and then back off again.

Here is in more detail what I need the circuit to do:

Mounted next to a table lamp, the only light in the room. When the table lamp is turned off at night, the room goes completely dark. I want the LDR circuit to sense that the room has just gone from full bright to full dark (hence the >800 to <100 range.)

I want it to energize the relay for 10 seconds, so that the relay can power a LED strip to act as a nightlight so the person who turned off the table lamp has light to see their way out of the room and after 10 seconds the LED strip turns back off, and the LDR circuit does nothing until the full light to full dark scenario happens again.

Here’s PaulMurray’s code mixed in with mine that results in the LED (relay proxy) flickering once when the LDR goes from >800 to <100.

int LDR = A5;
const int Relay = 8;
int reading = 0;


enum State { IDLE, TEN_SECONDS, ACTIVATION_DONE } state = IDLE;
uint32_t activation_time_ms;


void setup() {
 Serial.begin(9600);
 pinMode (Relay, OUTPUT);
}


void loop() {
 int reading = analogRead(A5);
 Serial.println(reading);

 switch (state) {
   case IDLE:
     if (reading <= 100) {
       digitalWrite(Relay, HIGH);
       activation_time_ms = millis();
       state = TEN_SECONDS;
     }
     break;

   case TEN_SECONDS:
     if (millis - activation_time_ms >= 10000) {
       digitalWrite(Relay, LOW);
       state = ACTIVATION_DONE;
     }
     break;

   case ACTIVATION_DONE:
     if (reading >= 800) {
       state = IDLE;
     }
     break;
 }
}

if (millis - activation_time_ms >= 10000) {

try

if (millis() - activation_time_ms >= 10000L) {

instead.

just remember that the led may be backwards as paul may have taken into account that most arduino relays are backwards e.g LOW is ON.

I added the "L" and the LED still just flickers. I know the LED is working OK, though, because if I just add digitalWrite(Relay, HIGH); into the loop on its own, the LED lights and remains lit.

clarkdv:
I added the “L” and the LED still just flickers. I know the LED is working OK, though, because if I just add digitalWrite(Relay, HIGH); into the loop on its own, the LED lights and remains lit.

so you only added the L not the other change.

Ah, I missed the (), I just added them and it WORKS!!!

Just for my own education, what does the L do? It seems to work both with and without it.

Thanks!!!

L forces the compiler to assign the number to a long rather than a int.

If you over flow a int it may change to a different number. To be honest it probably should be assigned as UL so it can never go minus.

Its not needed at the moment but in the future when you decided that 10 seconds is to short it avoids problems

To be honest I could not see the mistake in millis so I copyed your program and added a bunch of serial prints. That proved that the timer was not operating as it was designed. So I added a sum to see what the timer was doing which resulted in a compiler error. The error pointed out that millis could not be used that way as it should have been millis().

Maybe you should spend some time getting use to serial printing.

I have a Serial.println(reading); in there so I can monitor the LDR.

As for future mods, I will change the On time of the relay to 30 seconds before I install it. I just went with 10 for testing (and even dropped that down to 3 seconds to make testing quicker.)

This code will run forever once I deploy the device. Will I run into any overflow (or whatever it's called) issues after weeks or months with the code as it is now?

Ah, I missed the (), I just added them and it WORKS!!!

Doh! A typo on my part. Without the parenthesis, instead of invoking the millis() function and getting the current time, the code treats ‘millis’ as a pointer to function - a memory address. It then subtracts ‘activation time’ from this constant value.

It’s not good enough that it works (of course it bloody works: I wrote it!), you need to understand why it works. Think about what ‘state’ does as the light level fluctuates up and down. What happens should the light level hit 800 while the relay is on? What should happen? Or do we not care, because it’s pretty unlikely.

Arguably, on this forum we shouldn’t be just supplying the answers like this, but this pattern for code isn’t something that a relative newbie is just going to invent de novo. Hopefully, however, you can take it and run with it to do other things.

I did a similar thing for a daylight sensor, but I was concerned about passing cars. So I added another check “the light level must be above 800 AND it must remain above 800 for at least 5 seconds”. Try adding that to this sketch. Hint: you will need another state.

This code will run forever once I deploy the device. Will I run into any overflow (or whatever it’s called) issues after weeks or months with the code as it is now?

The idiom we use everywhere for timing: millis()-startTimeMs <= delayTimeMs is the way it is because it addresses that problem. To understand how it works, have a look at what twos complement subtraction does near rollover, when startTimeMs is (say) 0xFFFFFFF9 and delayTime is 10ms (0x0A).

To put it another way: an unsigned long int is not really an integer - it is an integer modulo 2^32.

millis() startTime milis()-startTime
0xFFFFFF9 0xFFFFFF9 0x00000000
0xFFFFFFA 0xFFFFFF9 0x00000001
0xFFFFFFB 0xFFFFFF9 0x00000002
0xFFFFFFC 0xFFFFFF9 0x00000003
0xFFFFFFD 0xFFFFFF9 0x00000004
0xFFFFFFE 0xFFFFFF9 0x00000005
0xFFFFFFF 0xFFFFFF9 0x00000006
0x00000000 0xFFFFFF9 0x00000007
0x00000001 0xFFFFFF9 0x00000008
0x00000002 0xFFFFFF9 0x00000009
0x00000004 0xFFFFFF9 0x0000000A

Much thanks to Paul and gpop and Robin. My device does exactly what I want and I did learn a lot about states and can't wait to re-write a really really long bit of code I have working now as a thermostat using states instead of a buttload of if statements! LOL

I've added all your suggestions and code to my notes and will use it again and again.

clarkdv:
Much thanks to Paul and gpop and Robin. My device does exactly what I want and I did learn a lot about states and can’t wait to re-write a really really long bit of code I have working now as a thermostat using states instead of a buttload of if statements!

That is exactly what we oldtimers love to hear.

If you want to stretch your brain a little further, have a look at my page ArduinoTheOOWay.