Alcohol/Sanitizer dispenser code

There have been a couple of posts where people have had difficulty writing code for an alcohol dispenser. The usual sorts of issue - basically, not understanding state and timer driven code. So, this is a description of one way to do it.

A simple dispenser will have three states, where "state" means "what I am doing right now". And it will have events that move it from state to state - either external (the IR sensor) or internal (a timed interval).

To start with, the dispenser will be in an "idle" or "ready" state, doing nothing.

When the sensor detects a request for sanitizer, the dispenser moves into "dispensing" state.

When there is a 4-second timeout, or when the hands are removed, the dispenser should move into a "resetting" state - not dispensing any more sanitizer until a (say) 2-second timeout has occurred.

After the 2-second resetting timeout, the dispenser should go back to "ready".

The code to do this will look a little like this (this is psudeocode, not real working Arduino code):

enum DispenserState { READY, DISPENSING, RESETTING } dispenserState = READY;

long dispenserMs; // the time mark at the start of the current state

const long DISPENSE_MS = 4000;
const long RESET_MS = 2000;

void init() {
  startReadyState();
}

void loop() {
  switch(dispenserState) {
    case READY: handleReadyState(); break;
    case DISPENSING: handleDispensingState(); break;
    case RESETTING: handleResettingState(); break;
  }
}

void startReadyState() {
  turn the 'ready' LED on;
  dispenserState = READY;
}

void handleReadyState() {
    if(the sensor is sensing a hand) {
      startDispensingState();
    }
}

void startDispensingState() {
  turn the 'ready' LED off;
  turn on the dispensing motor;
  dispenserMs = millis();
  dispenserState = DISPENSING;
}

void handleDispensingState() {
  if(the sensor is not sensing a hand) {
    startResettingState();
  }
  else if(millis() - dispenserMs  >= DISPENSE_MS) {
    startResettingState();
  }
  else {
    // do nothing, continue to dispense
  }
}

void startResettingState() {
  turn off the dispensing motor;
  dispenserMs = millis();
  dispenserState = RESETTING;

}

void handleResettingState() {
  if(millis() - dispenserMs  >= RESET_MS) {
    startReadyState();
  }
  else {
    // do nothing, continue to wait for timeout
  }
}

Now - there's more to add, here. You may want to add hysteresis to the hand sensor, the hand sensor should wait for (say) 100ms before it says "yep, the hand has been inserted/removed". One way to do this is to add more states to the dispenser, but a better way would be to treat "hand sensor" as a state machine in its own right, with it's own 100ms timer.

enum DispenserState { READY, DISPENSING, RESETTING } dispenserState = READY;

long dispenserMs; // the time mark at the start of the current state

const long DISPENSE_MS = 4000;
const long RESET_MS = 2000;

enum SensorState { OFF, TURNING_ON, ON, TURNING_OFF} sensorState = OFF;

long sensorChangeMs;
const long SENSOR_TIMEOUT_MS = 100;

void init() {
  startReadyState();
}

void loop() {
  handleSensorState();

  switch(dispenserState) {
    case READY: handleReadyState(); break;
    case DISPENSING: handleDispensingState(); break;
    case RESETTING: handleResettingState(); break;
  }
}

void handleSensorState() {
  boolean handSensed = /* read the sensor, whatever that might involve */;

  switch(sensorState) {
    case OFF: 
      if(handSensed) { 
        sensorMs = millis();
        sensorState = TURNING_ON;
      }
      break;
    case TURNING_ON:
      if(!handSensed) { 
        sensorState = OFF;
      }
      else if(millis() - sensorMs>= SENSOR_TIMEOUT_MS) {
        sensorState = ON;
      }
      break;
    case ON:
      if(!handSensed) { 
        sensorMs= millis();
        sensorState = TURNING_ON;
      }
      break;
    case TURNING_OF:
      if(handSensed) { 
        sensorState = ON;
      }
      else if(millis() - sensorMs>= SENSOR_TIMEOUT_MS) {
        sensorState = OFF;
      }
      break;
  }
}

void startReadyState() {
  turn the 'ready' LED on;
  dispenserState = READY;
}

void handleReadyState() {
    if(sensorState == ON) {
      startDispensingState();
    }
}

void startDispensingState() {
  turn the 'ready' LED off;
  turn on the dispensing motor;
  dispenserMs = millis();
  dispenserState = DISPENSING;
}

void handleDispensingState() {
  if(sensorState == OFF) {
    startResettingState();
  }
  else if(millis() - dispenserMs  >= DISPENSE_MS) {
    startResettingState();
  }
  else {
    // do nothing, continue to dispense
  }

}

void startResettingState() {
  turn off the dispensing motor;
  dispenserMs = millis();
  dispenserState = RESETTING;

}

void handleResettingState() {
  if(millis() - dispenserMs  >= RESET_MS) {
    startReadyState();
  }
  else {
    // do nothing, continue to wait for timeout
  }
}

The code style is a bit inconsistent, but meh. Either way is fine. You can jam everything in the main loop, or you can seperate absolutely everyting into separate functions. It's a matter of tase.

Personally, I like to use C++ classes to encapsulate things, and there's info on how to do that in my sig. But it's not necessary.

This, naturally, is just one way to do it. You might want to add a rule "if the hand is removed but reinserted during the 4-second dispensing timout, then resume dispensing but only up to that original four second limit (don't restart the four second timer)". I shall leave it as an exercise for the reader as to how to implement that, and how to resolve the ambiguity in this specification (the first step is spotting what that ambiguity might be).