Each time the loop finishes and starts, there are three things to worry about:
- light level threshhold
- timeout
- is the light currently on or off
This gives you 8 possible states, but there are really only three, because we are only interested in the states in which the previous execution of the loop (or the setup function) might have left things when it finished.
- light is off, and we are not counting down
- light is off, and we are counting down
- light is on, and we are not counting down
Let's call these three states "OFF, COUNTDOWN, ON". We define a variable outside the loop to "remember" the current state, and we'll also add the countdown start millis because we are going to need it later
enum state {
OFF, COUNTDOWN, ON
};
state current_state;
unsigned long countdownMs;
void setup() {
// put your setup code here, to run once:
current_state = OFF;
}
Now, each time we run through the loop, we do something (or nothing) based on the current state and what our sensors tell us - millis() in this respect is a sensor that tells us the time.
void loop() {
switch (current_state) {
case OFF:
break;
case COUNTDOWN:
break;
case ON:
break;
}
}
the question then becomes - what code belongs in those cases? Well, it will probably be something like:
void loop() {
switch (current_state) {
case OFF:
if (isLightAboveThreshhold()) {
// do nothing;
}
else {
countdownMs = millis();
current_state = COUNTDOWN;
}
break;
case COUNTDOWN:
if (isLightAboveThreshhold()) {
// don't need to perform any action to stop counting down
current_state = OFF;
}
else if (millis() - countdownMs > 5000) {
digitalWrite(LIGHT_PIN, HIGH);
current_state = ON;
}
else {
// do nothing
}
break;
case ON:
if (isLightAboveThreshhold()) {
digitalWrite(LIGHT_PIN, LOW);
current_state = OFF;
}
else {
// do nothing
} break;
}
}
boolean isLightAboveThreshhold() {
return analogRead(LIGHT_SENSOR) > LIGHT_THRESHOLD;
}
Now, you can refactor this many ways, but this style of programming - a state transition machine - works well with an arduino and is relatively easy to document and diagram. We have a variable named currentState, our loop is a single switch statement that handles all of the states, and for each state we have a chain of if-else-if statements that always has a final "else" clause and a break.
Each clause in the if-else chain has an action (or there's a comment saying "no action"), and as it's final act sets the current state to the new state (or there's a comment saying that the state is left as it is).
This means that we can read off the state transitions by looking at the main loop alone.
Conditions or actions that are complex or need to be done in more than one case (like checking the light sensor) get shoved out into functions. In this case, the "start timer" and "check timer" functionality might have been made into functions, even though they are only needed once.
- give your functions meaningful names
- whenever a variable stores a physical quantity, suffix it with a unit of measurement - ms, ohms, feet or meters.