Skip to content

Instantly share code, notes, and snippets.

@fcostin
Last active September 1, 2020 12:28
Show Gist options
  • Save fcostin/851c1b4d1e3cb75ba972408151f185ba to your computer and use it in GitHub Desktop.
Save fcostin/851c1b4d1e3cb75ba972408151f185ba to your computer and use it in GitHub Desktop.
hypothetical refactor of self-opening trash can state machine
// 2020/09/01 - this code is placed in the public domain.
//
// This is a hypothetical refactor of state machine code from
// ivanilves's fun self-opening trash can project.
//
// Ref: https://github.com/ivanilves/arduino-sketches/blob/d0e965ebae1b98fa23b833fed2e2e28b21ef8863/basurito/basurito.ino
//
// The code below is incomplete, has not been tested and will not work! it's a sketch of an idea.
//
// Rough principles of this refactor:
// * define a finite set of explicit enum values for states, instead of using many bools
// * push all the state machine transition logic into pure function
//
// Details lost in translation:
// * controlling the LED
// There are 5 possible states.
// Note: there are two kinds of opening state,
// depending on if we are opening in response
// to button press (OPENING_MANUAL) or if we
// are opening because we decided to based on
// distance sensor (OPENING_AUTO).
typedef enum {
CLOSED,
OPENING_AUTO,
OPENING_MANUAL,
CLOSING,
OPEN
} state_t;
// inputs_t contains all inputs required to compute our successor state
typedef struct {
state_t state;
int distance;
int srv_pos;
bool button_pressed;
} inputs_t;
// outputs_t will hold the successor state we decided & any actions requested
typedef struct {
state_t state;
int srv_pos;
int delay;
int lowpower_delay;
} outputs_t;
// advance_state computes the successor state & any requested actions.
// Structured as a pure function.
outputs_t advance_state(inputs_t x) {
outputs_t succ;
succ.state = x.state;
succ.srv_pos = x.srv_pos;
succ.delay = 0;
succ.lowpower_delay = 0;
if ((x.state == CLOSED) && (x.distance >= MIN_DIST) && (x.distance <= MAX_DIST)) {
succ.state = OPENING_AUTO;
} else if ((x.state == CLOSED) && x.button_pressed) {
succ.state = OPENING_MANUAL;
} else if ((x.state == OPEN) && x.button_pressed) {
succ.state = CLOSING;
} else if (((x.state == OPENING_AUTO) || (x.state == OPENING_MANUAL)) && (x.srv_pos < SRV_OPEN)) {
succ.srv_pos = x.srv_pos + 1;
succ.delay = OPENING_DELAY;
} else if ((x.state == OPENING_MANUAL) && (x.srv_pos >= SRV_OPEN)) {
succ.state = OPEN;
succ.srv_pos = SRV_OPEN;
succ.delay = OPENING_DELAY;
} else if ((x.state == OPENING_AUTO) && (x.srv_pos >= SRV_OPEN)) {
succ.state = CLOSING;
succ.srv_pos = SRV_OPEN;
succ.delay = OPENING_DELAY;
succ.lowpower_delay = SLEEP_4S;
} else if ((x.state == CLOSING) && (x.srv_pos > SRV_CLOSED)) {
succ.srv_pos = x.srv_pos - 1;
succ.delay = CLOSING_DELAY;
} else if ((x.state == CLOSING) && (x.srv_pos <= SRV_CLOSED)) {
succ.state = CLOSED;
succ.srv_pos = SRV_CLOSED;
succ.delay = CLOSING_DELAY;
} else if ((x.state == CLOSED) || (x.state == OPEN)) {
succ.state = state;
succ.lowpower_delay = SLEEP_500MS;
}
return succ;
};
// Globals used to maintain state between ticks.
state_t current_state;
int srv_pos;
void setup() {
// ... put setup here ...
}
void loop() {
// Gather all inputs
inputs_t x;
x.state = current_state;
x.distance = calculateDistance(); // fixme: sensing every tick may be wasteful
x.srv_pos = srv_pos;
x.button_pressed = buttonPressed();
// Compute successor state
outputs_t succ;
succ = advance_state(x);
// Execute requested actions
if (succ.srv_pos != x.srv_pos) {
srv.write(succ.srv_pos);
srv_pos = succ.srv_pos;
}
current_state = succ.state;
if (succ.delay > 0) {
delay(succ.delay);
}
if (succ.lowpower_delay > 0) {
LowPower.powerDown(succ.lowpower_delay, ADC_OFF, BOD_OFF);
}
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment