Changing state of a light (Uncle Bob's light switch problem) I watched Robert Martin's programming 101 video. I'll explain what problem he talks about in the video for those of you who haven't watched it.

Robert Martin is in his house and shows a light switch. If this switch goes up, a light is on. If switch goes down, the light is off. He wrote a code to handle it. This is simple but the situation is getting more complicated when he shows second, third and fourth light switch. All of them change state of the same light. Each of them is dependent on the others. He also shows how to handle it in a code.

Robert Martin explains that this code is not the best solution because there can be a situation when two people come to the same room and they want to turn the light on. He demonstrated that the code he has written doesn't work well because the light doesn't change its state. It's happening because a computer doesn't see simultaneous events. One of them occurs first. That's happening when two people want to turn on the light. It happens simultaneously only from human perspective. For a computer one person changes state of the light first so in fact the state of the light is changed twice. Fortunately Uncle Bob shows how to solve this problem. He measures time elapsed from the last light state change. If the next change is happening in 500 ms, he rejects it. Now when two people want to turn the light on the light is actually on.

Unfortunately implementing above solution breaks a case when one person quickly turns on and turns off the light. Because the second change is happening in 500 ms it is rejected. So it is not possible to quickly turn on and turn off the light. Uncle Bob didn't show how to solve this problem. He left it as a homework.

It took me long to figure it out how can I solve this but finally I've came to a working solution. I'm not sure though if my solution is good or maybe I only think it is good. Can you tell me if I solved the problem correctly or there is a better solution?

Uncle Bob's code looks like this:

void lightLogic() {
    boolean a = switchA.isUp();
    boolean b = switchB.isUp();
    boolean c = switchC.isUp();
    boolean d = switchD.isUp();
    int currentSwitchState = 0;

    int base=10;
    if (a) currentSwitchState += base*base*base;
    if (b) currentSwitchState += base*base;
    if (c) currentSwitchState += base;
    if (d) currentSwitchState += 1;

    int currentTime = millis();
    if (currentSwitchState != lastSwitchState) {
        if (currentTime - lastChangeTime &gt; 500) {
            light.state = !light.state;
            lastChangeTime = currentTime;
        }
    }

    lastSwitchState = currentSwitchState;


}

I introduced a new variable called penultimateSwitchState and I check if the current state of the light equals to penultimate state. If yes, I know that the same switch has been used to change light state so I accept that change:

void lightLogic() {
    boolean a = switchA.isUp();
    boolean b = switchB.isUp();
    boolean c = switchC.isUp();
    boolean d = switchD.isUp();
    int currentSwitchState = 0;

    int base=10;
    if (a) currentSwitchState += base*base*base;
    if (b) currentSwitchState += base*base;
    if (c) currentSwitchState += base;
    if (d) currentSwitchState += 1;

    int currentTime = millis();
    if (currentSwitchState != lastSwitchState) {
      if ((currentTime - lastChangeTime &gt; 500) || (penultimateSwitchState == currentSwitchState)) {
        light.state = !light.state;
        lastChangeTime = currentTime;
      }
      penultimateSwitchState = lastSwitchState;
    }

    lastSwitchState = currentSwitchState;
  }
}
Welcome to the world of programming where you never know exactly when you're done because the client doesn't know precisely what they want!

The business solution is to this problem is to first sit down with the client, write down a proposal about some precise interpretation of what should happen. Have the client agree with this interpretation and sign it. Then implement this exactly as proposed.

Then later on when the client tests your code they're either happy (yay, we're done!) or they are not. If they're not you write down what they want to change and make them pay for it again. Happy times! (for you at least, the client doesn't like paying again for something "you should have known" in the first place).

Extra note: sometimes a client asks for something that cannot work. They might only realise the contradiction after you explained what exactly happens and offer them 2 possible ways that achieve 1 of the 2 things they ask for. In such a case you let the client decide which of the 2 is more important.



To write working code we first need to know which exact result we want to get.
Here's the current problem more precisely:

1) If 2 (or more) different switches are flipped within a certain time (half a second) of each other, ignore one of them and change the state of the light.
2) If a switch is flipped twice in rapid succession the second flip should not be ignored.  

Question: If (1) and (2) happen within half a second, should the light change state?

My answer would be: If a switch is flipped once within half a second, the light should change state (compared to half a second earlier) irregardless of other switches.



Solution 1:

One possible solution is to modify the implementation of the switches themselves. If a switch is first in state off, and then flipped on, it will still say it's off for half a second. Only if it has remained on for half a second will it say it's turned on. This solves the problem of flipping the same switch twice in rappid succession ... with the downside that a light state will change at the earliest half a second after a switch is flipped. This is something the client has to agree on.

If the client agrees on that change, the lightLogic can remain exactly the same as in Bob's solution since that correctly handles the remaining requirement of ignoring multiple switches flipping "simultaniously".

This kind of solution is often used to prevent flickering in oversensitive keys. Consider a switch that has a bad contact. If switched halfway, it might rappidly say ON>OFF>ON>OFF even though you only flicked it once. Looking at a change from a small delay before makes sure that it will only fire once in such cases as intended.



Solution 2:

Alternatively if you require that the light switches on immediatly after a single switch is flipped, you'll have to do it differently.

Let's say the light is switched from off to on. For the next 500 milliseconds, under which condition should the light be switched back off? -> If ALL switches are back to what they were when the light was still off.

This looks similar to your idea, but only looking at the penultimate state isn't enough. If switch A is flipped, and then switch B is flipped 4 times, the light should still be ON because of switch A.

An idea would be like this (note not tested):

int currentTime = millis();
if (currentTime - lastChangeTime &lt;= 500) { // might be considered simultanious
    if(currentSwitchState == initialSwitchState) { //all switches back to initial state
        light.state = initialLightState;
        lastChangeTime = 0; //respond to any new flip after this as if it was new.
    } // else light stays as it is now
} else {
    if (currentSwitchState != previousSwitchState) { // start new case
        light.state = !light.state;
        lastChangeTime = currentTime;
        initialSwitchState = previousSwitchState;
    } // else nothing changed
}
previousSwitchState = currentSwitchState; //always update latest known state