Peek-A-Boo Bot (Puppet Pet)

This project successfully proves to have scripted and reactive behavior as it can entice users to interact with it, be hugged/shake hands and sense the approach of those seeking to play peek-a-boo with it.

There are multiple possible behavioral schedules that can be programmed based on a variety of user-interactions. The basic inputs and outputs include it’s passive “teasing” peeking when it senses no movement closer than 20 cm. Once there is an approach of less than 20 cm, it plays peek-a-boo, moving  its arms and eyes to a certain degree in addition to its OLED display blinking. There is also a capacitive touch sensor embedded on the front of the hands, making it reactive to a touch, hug, or handshake as it uncovers the eyes, chuckles with its OLED eyes, and proceeds to cover the eyes once again. This is a more gentle reaction. The user then recedes after playtime and the Puppet Pet waits for another friend to play peek-a-boo with, indicated by a touch on the hand.

Video:

Solidworks Files:

 Demo 4 CAD

Code

Full repository can be found on GitHub.

Behavior 1:

#include "Schedule.h"
#include "HAL.h"

/* Functional Primitives:
 * NOTE: Left and Right Refer to the Viewer's Left and Right:
 * SENSING:
 * dist() - returns the distance from the observer in cm
 * touched() - returns true while the robot is being touched
 *
 *
 * BEHAVIOUR:
 * blink()                - blinks both eyes once (quick down and up).
 * chuckle()              - chuckles slightly by inverting the eyes three times and moving eye stalks up and down.
 * togglePeek()           - moves hands slightly out of the way of the eyes on first call, on second call it covers them up
 *
 * coverEyes(int time)    - moves both hands over the eyes, taking %time% seconds to do so
 * uncoverEyes(int time)  - uncovers its eyes, taking %time% seconds to do so
 *
 * moveStalks(int percent) - moves stalks to %percent% of the way from the bottom of their swing where 0% is their lowest position and 100% is their highest
 * moveStalkLeft(int percent) - moves left stalk to %percent% of the way from the bottom of its swing where 0% is its lowest position and 100% is its highest
 * moveStalkRight(int percent) - moves right stalk to %percent% of the way from the bottom of its swing where 0% is its lowest position and 100% is its highest
 *
 * moveHands(int percent) - moves hands to %percent% of the way from the bottom of their swing where 0% is their lowest position and 100% is their highest
 * moveHandLeft(int percent) - moves left hand to %percent% of the way from the bottom of its swing where 0% is its lowest position and 100% is its highest
 * moveHandRight(int percent) - moves right hand to %percent% of the way from the bottom of its swing where 0% is its lowest position and 100% is its highest
 */

 /* EXAMPLE OF ALL TIMING OPTIONS (put these in setup NOT loop)
 ** avoid calling variables directly from inside these functions unless they are global variables **

 sch->EVERY(500)->DO(blink()); // Will call #blink every 500ms
 sch->EVERY_WHILE(750, dist() < 10)->DO(togglePeek()); // Will peek / unpeek Every 750ms while Dist is < 10cm sch->IN(2500)->DO(doThisOnce()); // Will call #doThisOnce one time in 2.5s

 sch->WHILE(dist() < 10)->DO(swing_arms()); // Will call #swing_arms (not a real function) as often as possible as long as dist() < 10. sch->WHEN(dist() > 10)->DO(someOtherThing()); // Will call #backup every time dist goes from <=10 to >10.
 sch->WHEN(touched())->DO(uncoverEyes()); // Will uncover eyes when touched goes from false to true (so, when touched)

 // Other sometimes more efficient notation:
 sch->EVERY(250)->do_(blink); // if you're just calling a void function with no arguments, it's more effective to just use the lowercase #do_
 sch->EVERY(100)->DO(x++); // x or other variables accessed must be a global variables
 */

#define RESTING_EYE_LEVEL 37

void wakeUp(){
  blink(750);
  **(Robot.eyes_open) = true;
  moveEyeLidsTo(RESTING_EYE_LEVEL);
  moveStalks(100);
  **(Robot.awake) = true;
} // #wakeUp

void setup(){
  Serial.begin(9600);

  initHAL();
  moveStalks(0);
  moveHands(0);
  eyeLids(100); // Eyes Start Closed (call this before the scheduler turns on)

  sch->WHEN(!**(Robot.awake) && personPresent())->do_(wakeUp);

  sch
    ->WHEN( **(Robot.awake) )
    ->do_([](){
      Serial.println("I'm Awake.");
      chuckle();
      moveEyeLidsTo(RESTING_EYE_LEVEL);
      sch->IN(1200)->do_([](){
        coverEyes();
        moveStalks(0);
      });
    });

  sch->EVERY_WHILE( 1500, **(Robot.eyes_covered) )->do_(togglePeek);

  sch
    ->WHEN( **(Robot.eyes_covered) && personPresent() )
    ->do_([](){
      uncoverEyes();
      chuckle();
      chuckle();
      moveEyeLidsTo(RESTING_EYE_LEVEL);
      moveStalks(100);
    });

  sch
    ->WHEN(touched())
    ->do_([](){
      coverEyes();
      delay(500);
      moveStalks(0);
    });
} // #setup

void loop(){
  sch->loop();
} // #loop

 

Behavior 2:

#include "Schedule.h"
#include "HAL.h"

/* Functional Primitives:
 * NOTE: Left and Right Refer to the Viewer's Left and Right:
 * SENSING:
 * dist() - returns the distance from the observer in cm
 * touched() - returns true while the robot is being touched
 *
 *
 * BEHAVIOUR:
 * blink()                - blinks both eyes once (quick down and up).
 * chuckle()              - chuckles slightly by inverting the eyes three times and moving eye stalks up and down.
 * togglePeek()           - moves hands slightly out of the way of the eyes on first call, on second call it covers them up
 *
 * coverEyes(int time)    - moves both hands over the eyes, taking %time% seconds to do so
 * uncoverEyes(int time)  - uncovers its eyes, taking %time% seconds to do so
 *
 * moveStalks(int percent) - moves stalks to %percent% of the way from the bottom of their swing where 0% is their lowest position and 100% is their highest
 * moveStalkLeft(int percent) - moves left stalk to %percent% of the way from the bottom of its swing where 0% is its lowest position and 100% is its highest
 * moveStalkRight(int percent) - moves right stalk to %percent% of the way from the bottom of its swing where 0% is its lowest position and 100% is its highest
 *
 * moveHands(int percent) - moves hands to %percent% of the way from the bottom of their swing where 0% is their lowest position and 100% is their highest
 * moveHandLeft(int percent) - moves left hand to %percent% of the way from the bottom of its swing where 0% is its lowest position and 100% is its highest
 * moveHandRight(int percent) - moves right hand to %percent% of the way from the bottom of its swing where 0% is its lowest position and 100% is its highest
 */

 /* EXAMPLE OF ALL TIMING OPTIONS (put these in setup NOT loop)
 ** avoid calling variables directly from inside these functions unless they are global variables **

 sch->EVERY(500)->DO(blink()); // Will call #blink every 500ms
 sch->EVERY_WHILE(750, dist() < 10)->DO(togglePeek()); // Will peek / unpeek Every 750ms while Dist is < 10cm sch->IN(2500)->DO(doThisOnce()); // Will call #doThisOnce one time in 2.5s

 sch->WHILE(dist() < 10)->DO(swing_arms()); // Will call #swing_arms (not a real function) as often as possible as long as dist() < 10. sch->WHEN(dist() > 10)->DO(someOtherThing()); // Will call #backup every time dist goes from <=10 to >10.
 sch->WHEN(touched())->DO(uncoverEyes()); // Will uncover eyes when touched goes from false to true (so, when touched)

 // Other sometimes more efficient notation:
 sch->EVERY(250)->do_(blink); // if you're just calling a void function with no arguments, it's more effective to just use the lowercase #do_
 sch->EVERY(100)->DO(x++); // x or other variables accessed must be a global variables
 */

void setup(){
  initHAL();
  moveStalks(0);
  moveHands(0);
  eyeLids(100); // Eyes Start Closed (call this before the scheduler turns on)
  moveEyeLidsTo(40);

  sch->EVERY(1000)->DO(blink());

  sch->EVERY_WHILE(2000, dist() > 20)->DO(togglePeek());

  sch->WHILE(dist() < 20)->DO(moveHands(100));
  sch->EVERY_WHILE(700, dist() < 20)->DO(moveStalkLeft(100));
  sch->EVERY_WHILE(1000, dist() < 20)->DO(moveStalkRight(100));

  sch->WHEN(touched())->DO(uncoverEyes(); chuckle(); coverEyes(););

} // #setup

void loop(){
  sch->loop();
} // #loop

 

Backend:

HAL (Hardware Abstraction Layer):

#ifndef HAL_H
#define HAL_H
#include <Wire.h>
#include <CapacitiveSensor.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Servo.h>
#include <HCSR04.h>
#include "Schedule.h"

Schedule* sch = new Schedule();

// Ultrasound Sensing Pins:
#define P_ECHO 12
#define P_TRIG 11
UltraSonicDistanceSensor sonar(P_TRIG, P_ECHO);

// Capacitive Sensor on Hands:
#define CAP_PUSH A0
#define CAP_SENS A1
// Threshold Value for Detecting a Touch:
#define CAP_THRESH 20
CapacitiveSensor capsens = CapacitiveSensor(CAP_PUSH,CAP_SENS);


// Servo Motor Pins:
#define P_LEFT_STALK 3
#define P_RIGHT_STALK 10
#define P_LEFT_HAND 6
#define P_RIGHT_HAND 4
Servo S_LEFT_STALK, S_RIGHT_STALK, S_LEFT_HAND, S_RIGHT_HAND;

#define OLED_RESET 4
Adafruit_SSD1306 display(OLED_RESET);

#if (SSD1306_LCDHEIGHT != 32)
#error("Height incorrect, please fix Adafruit_SSD1306.h!");
#endif

// STATE VARIABLES:
struct RobotType{
  ActionState awake = new_ActionState(false);
  ActionState eyes_open = new_ActionState(false);
  ActionState eyes_covered = new_ActionState(false);
} Robot;

// OPERATIONS VARIABLES:
int currentEyePercent = 100;

// EMOTION PRIMITIVES:
// Chuckles slightly by inverting the eyes three times and moving eye stalks up and down.
void chuckle();
// Opens/Close the Eyes by Drawing them at the Given Percent Closed where 0 is fully open and 1 is fully closed
void eyeLids(int);
// Moves the Eye Lid Smoothly and Quickly to the Given Eye Level.
void moveEyeLidsTo(int);
// Blinks Both Eyes by Lowering and Raising the Eye Level (Lids) Quickly. Returns Eye Lids to their initial state.
void blink();
// Blinks Both Eyes by Lowering and Raising the Eye Level (Lids) taking the Given Time to Complete. Returns Eye Lids to their initial state.
void blink(int);

// MOTION PRIMITIVES:
// Moves stalks to %percent% of the way from the bottom of their swing where 0% is their lowest position and 100% is their highest
void moveStalks(int);
// Moves left stalk to %percent% of the way from the bottom of its swing where 0% is its lowest position and 100% is its highest
void moveStalkLeft(int);
// Moves right stalk to %percent% of the way from the bottom of its swing where 0% is its lowest position and 100% is its highest
void moveStalkRight(int);
// Moves hands to %percent% of the way from the bottom of their swing where 0% is their lowest position and 100% is their highest
void moveHands(int percent);
// Moves left hand to %percent% of the way from the bottom of its swing where 0% is its lowest position and 100% is its highest
void moveHandLeft(int percent);
// Moves right hand to %percent% of the way from the bottom of its swing where 0% is its lowest position and 100% is its highest
void moveHandRight(int percent);

// Moves both hands over the eyes.
void coverEyes();
// Uncovers its eyes.
void uncoverEyes();

// SENSING PRIMITIVES:
// Returns the distance to the nearest object in front of the robot based on ultrasound.
float dist();
// Returns Whether the Robot is Currently Being Touched on its Hands.
bool touched();
// Determines if a person is actually present (and not noise);
bool personPresent();

// HELPER FUNCTIONS:
// Performs a Basic Blink/Squint by Inverting the Screen then Uninverting Shortly Later:
bool** invertBlink();
// Commands the Given Servo to the Given Percent of its Range from minAng to maxAng
void commandServo(Servo, int, int, int);


void initHAL(){
  S_LEFT_STALK.attach(P_LEFT_STALK);
  S_RIGHT_STALK.attach(P_RIGHT_STALK);
  S_LEFT_HAND.attach(P_LEFT_HAND);
  S_RIGHT_HAND.attach(P_RIGHT_HAND);
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);  // initialize with the I2C addr 0x3C (for the 128x32)
  display.invertDisplay(false);
  display.clearDisplay();   // clears the screen and buffer
  display.drawRect(0,0,display.width(),display.height(),BLACK);
  display.display();
} // #initHAL

// Blinks Both Eyes by Lowering and Raising the Eye Level (Lids) Quickly. Returns Eye Lids to their initial state.
void blink(){ blink(0); }
// Blinks Both Eyes by Lowering and Raising the Eye Level (Lids) taking the Given Time to Complete. Returns Eye Lids to their initial state.
void blink(int t){
  static const char step = 5;
  int initState = currentEyePercent;
  int waitTime = step * t / 200;

  int i;
  for(i=initState; i<100; i+=step){ // Close Eyes First delay(waitTime); eyeLids(i); } for(i=100; i>0; i-=step){ // Then Open
    delay(waitTime);
    eyeLids(i);
  }
  for(i=0; i<=initState; i+=step){ // Return to Initial State delay(waitTime); eyeLids(i); } } // #blink bool** invertBlink(){ display.invertDisplay(true); delay(250); display.invertDisplay(false); delay(250); return new_ActionState(true); // Used to spawn an event - returns for compatibility } // #invertBlink // Chuckles slightly by inverting the eyes three times and moving eye stalks up and down. void chuckle(){ invertBlink(); moveStalks(80); invertBlink(); moveStalks(20); } // #chuckle // Opens/Close the Eyes by Drawing them at the Given Percent Closed where 0 is fully open and 1 is fully closed. #define LID_COLOR WHITE // Color of the eye-lid void eyeLids(int percent){ /* "Eye Levels" are lines drawn parallel to the line which runs diagonally across the display from lower-left to upper-right corner of the display. The value of the "lvl" is determined by the height at which the level must intersect the left edge of the display. */ static const int w2h = display.width() / display.height(); // used to determine top edge intersection point #ifdef ROTATE_DISPLAY_180 static const int open_lvl = 2*display.height(); static const int closed_lvl = 0; #else static const int open_lvl = 0; static const int closed_lvl = 2*display.height(); #endif int curr_lvl = open_lvl + (closed_lvl-open_lvl)*currentEyePercent/100.0; int targ_lvl = open_lvl + (closed_lvl-open_lvl)*percent/100.0; // Determine whether lid-color lines need to be added or removed: unsigned char color = (currentEyePercent > percent) ? LID_COLOR : !LID_COLOR;
  // Change Eye-Level
  char dir = abs(targ_lvl-curr_lvl)/(targ_lvl-curr_lvl);
  while(curr_lvl != targ_lvl){
    curr_lvl += dir;
    display.drawLine(0,curr_lvl, w2h*curr_lvl,0, color);
  }

  display.display(); // this is slow, call infrequently
  currentEyePercent = percent;
} // #eyeLids

// Moves the Eye Lid Smoothly and Quickly to the Given Eye Level.
void moveEyeLidsTo(int targ_percent){
  // Change Eye-Level
  static const int step = 10;
  char dir = abs(targ_percent-currentEyePercent)/(targ_percent-currentEyePercent);
  int curr_target = currentEyePercent;
  while(dir * (targ_percent-currentEyePercent) > 0){
    curr_target += dir*step;
    eyeLids(curr_target);
  }
} // #moveEyeLidsTo

// Commands the Given Servo to the Given Percent of its Range from minAng to maxAng
void commandServo(Servo S, int minAng, int maxAng, int percent){
  S.write(minAng + (maxAng-minAng) * constrain(percent,0,100) / 100.0);
} // #commandServo
// Moves left stalk to %percent% of the way from the bottom of its swing where 0% is its lowest position and 100% is its highest
void moveStalkLeft(int percent){ commandServo(S_LEFT_STALK, 90, 200, percent); }
// Moves right stalk to %percent% of the way from the bottom of its swing where 0% is its lowest position and 100% is its highest
void moveStalkRight(int percent){ commandServo(S_RIGHT_STALK, 180, 100, percent); }
// Moves stalks to %percent% of the way from the bottom of their swing where 0% is their lowest position and 100% is their highest
void moveStalks(int percent){ moveStalkLeft(percent); moveStalkRight(percent); }

// Moves left hand to %percent% of the way from the bottom of its swing where 0% is its lowest position and 100% is its highest
void moveHandLeft(int percent){ commandServo(S_LEFT_HAND, 0, 80, percent); }
// Moves right hand to %percent% of the way from the bottom of its swing where 0% is its lowest position and 100% is its highest
void moveHandRight(int percent){ commandServo(S_RIGHT_HAND, 180, 105, percent); }
// Moves hands to %percent% of the way from the bottom of their swing where 0% is their lowest position and 100% is their highest
void moveHands(int percent){ moveHandLeft(percent); moveHandRight(percent); }

// Moves both hands over the eyes.
void coverEyes(){
  moveHands(100);
  **(Robot.eyes_covered) = true;
} // #coverEyes
// Uncovers its eyes.
void uncoverEyes(){
  moveHands(0);
  **(Robot.eyes_covered) = false;
} // #uncoverEyes

// Moves hands slightly out of the way of the eyes on first call, on second call it covers them up
void togglePeek(){
  static bool peeking = false;
  if(!peeking){
    moveHands(71);
  } else{
    moveHands(100);
  }
  peeking = !peeking;
} // #togglePeek

// Returns the distance to the nearest object in front of the robot based on ultrasound.
float dist(){
  return sonar.measureDistanceCm();
} // #dist

// Returns Whether the Robot is Currently Being Touched on its Hands.
bool touched(){
  return capsens.capacitiveSensor(30) > 20;
} // #touched

// Determines if a person is actually present (and not noise)
bool personPresent(){
  static const unsigned int threshold = 25;
  static unsigned int oldest_value = -1;
  static unsigned long t_last_check = 0;
  static unsigned int last_value = -1;

  bool present = false;
  if(millis() - t_last_check > 125){
    t_last_check = millis();
    present = dist() < threshold && last_value < threshold && oldest_value < threshold;
    oldest_value = last_value;
    last_value = dist();
  }
  return present;
}
#endif // HAL_H

 

Scheduler:

/* Schedule.h
 * Intuitive Scheduling Utility that Allows for Complex Time and Condition Based
 * Behaviors to be Constructed out of Simple, Legible Event-Based Primitives.
 * (admittedly, this has a bit of a ways to go in terms of memory efficiency -
 * badly needs a ring buffer. (especially bad now that state persistence has
 * been added))
 * KNOWN BUGS / PROBLEMS:
 *  - Semi-Required memory leak on the %done% state of Actions. Need to have
 * some way of determining whether / how long other functions will need access to
 * this information after the Action has been deleted. NOTE: Until this is fixed,
 * the ability to create unbounded series of SingleTimedEvents with #in_ is
 * gone. Keep total number of events known and bounded.
 * Author: Connor W. Colombo, 9/21/2018
 * Version: 0.1.4
 * License: MIT
 */
#ifndef SCHEDULE_H
#define SCHEDULE_H
#include <StandardCplusplus.h>
#include <vector>
/* Example Usage (only call these once, likely in setup):
 ** avoid calling variables directly from inside these functions unless they are global variables **

 void setup(){
 // Basic Call:
 sch->EVERY(500)->DO(blink()); // Will call #blink every 500ms
 sch->EVERY_WHILE(750, dist < 10)->DO(togglePeek()); // Will peek / unpeek every 750ms while dist is < 10cm sch->IN(2500)->DO(doThisOnce()); // Will call #doThisOnce one time in 2.5s

 sch->NOW->DO(sortOfUrgent()); // Will call #sortOfUrgent as soon as possible without blocking other events (useful in comm. interrupts for longer behavior)

 sch->WHILE(dist < 10)->DO(swing_arms()); // Will call #swing_arms as often as possible as long as dist < 10. sch->WHEN(dist > 10)->DO(someOtherThing()); // Will call #someOtherThing every time dist goes from <=10 to >10.
 sch->WHEN(touched())->DO(uncoverEyes()); // Will uncover eyes when touched goes from false to true (so, when touched)

 // Other more efficient notation for simple function calls:
 sch->EVERY(250)->do_(blink); // if you're just calling a void function with no arguments, it's more effective to just use the lowercase #do_
 // Note:
 sch->EVERY(100)->DO(x++); // x or other variables accessed directly must be a global variables (not local scope)

 // Or Save Events to be Registered to Later:
 Event* FREQ_100Hz = schedule->EVERY(10);
 Event* TOO_CLOSE = schedule->WHEN(dist < 10); // ... somewhere else in code: TOO_CLOSE->DO(tone(BUZZER, 1000, 25));
 TOO_CLOSE->SIGNUP(tone(BUZZER, 1000, 25));

 // Additionally, events which setup other events (using nested actions) return
 // a double pointer to a bool which indicates when all sub-events have been
 // executed at least once.
 // Note: bool** beepboopd must be global.
 beepboopd = sch->IN(3100)->DO_LONG( *(sch->IN(1000)->DO( plt("***BEEP***BOOP***"); )); );
 sch->WHEN(**beepboopd)->DO( plt("## BOP ##"); );
 }
 */

/* NB: Some functionality must be assigned in macros b/c lambdas with captures
 can't be converted to function pointers. */
// More Legible Shorthand for "do_" syntax:
#define DO(x) do_([](){x;})
/* Shorthand for Calling a Function which Takes a Long Time to Complete after it
 Returns (has its own event calls) and returns a double pointer of a boolean which
 indicates when it is done. */
#define DO_LONG(x) \
do_(new NestingAction([](Action* action){ \
delete action->done; \
action->done = x; \
}));
// More Legible Shorthand for "do_" syntax:
#define SIGNUP(x) signup([](){x;})
// More Legible Shorthand for "while_" syntax:
#define WHILE(x) while_([](){return (x);})
// More Legible Shorthand for "when" syntax
#define WHEN(x) when([](){return (x);})
// Syntax to Normalize All-Caps Syntax used by Conditionals:
#define EVERY(x) every(x)
// More Legible Shorthand for "everyWhile" syntax:
#define EVERY_WHILE(x,y) everyWhile(x, [](){return (y);})
// Syntax to Normalize All-Caps Syntax used by Conditionals:
#define IN(x) in_(x)
// Shorthand Syntax for Performing a Task as Soon as Possible:
#define NOW in_(0)

typedef bool** ActionState;
#define new_ActionState(b) new bool*(new bool(b));

/*
 * Container for Action which are called in events and their respective metadata.
 */
class Action{ // Abstract Container for Use in Arrays of Pointers
public:
    bool* done = new bool(false);

    virtual ~Action(){
        //delete done; // <- Leave the done state variable behind //done = nullptr; } // dtor virtual void call() = 0; /* Tells Whether this Action and its Required Actions are Complete. Returns the dereferrenced state of member %done% */ bool isDone(){ return *(this->done);
    } // #isDone
}; // class Action
/*
 * Most basic form of an Action which takes a void-void function which has no
 * dependencies and thus is considered to be done executing once the function
 * returns (ie. doesn't generate any Events).
 */
class BasicAction : public Action{
public:
    // Type of Function to be Called which Consumes the Stored Data:
    typedef void (*function) ();

    BasicAction(function f) : oncall{f} {};

    void call(){
        oncall();
        *(this->done) = true;
    }
private:
    // Function to be Executed when this Action is Called:
    function oncall;
}; // class BasicAction
/*
 * Most basic form of an Action which takes a void-Action* function which has
 * dependencies / triggers other events and is expected to set this Action's
 * done value to true once all of its sub-functions are complete.
 */
class NestingAction : public Action{
public:
    // Type of Function to be Called which Consumes the Stored Data:
    typedef void (*function) (Action*);

    NestingAction(function f) : oncall{f} {};

    void call(){
        oncall(this);
    }
private:
    // Function to be Executed when this Action is Called:
    function oncall;
}; // class NestingAction
/*
 * An Action (ie function) to be Performed by being Called when an Event
 * Triggers and Must Receive some Piece(s) of Stored Data of type T to Execute.
 * The contained function is considered to have no dependencies and thus be
 * done executing once the function returns (ie. doesn't generate any Events).
 */
template <typename T>
class DataAction : public Action{
public:
    // Type of Function to be Called which Consumes the Stored Data:
    typedef void (*function) (T);
    // Stored Data to be Given to the Function:
    T data;

    DataAction(function f, T d) :  data{d}, oncall{f} {};

    // Calls this Action by Passing the Stored Data to #oncall and Calling It.
    void call(){
        oncall(data);
        *(this->done) = true;
    }
private:
    // Function to be Executed when this Action is Called:
    function oncall;
}; // Class: DataAction
/*
 * An Action (ie function) to be Performed by being Called when an Event
 * Triggers and Must Receive some Piece(s) of Stored Data of type T to Execute.
 * The contained function has dependencies / triggers other events and is
 * expected to set this Action's done value to true once all of its s
 * sub-functions are complete.
 */
template <typename T>
class NestingDataAction : public Action{
public:
    // Type of Function to be Called which Consumes the Stored Data:
    typedef void (*function) (T, Action*);
    // Stored Data to be Given to the Function:
    T data;

    NestingDataAction(function f, T d) : data{d}, oncall{f} {};

    // Calls this Action by Passing the Stored Data to #oncall and Calling It.
    void call(){
        oncall(this);
    }
private:
    // Function to be Executed when this Action is Called:
    function oncall;
}; // Class: NestingDataAction

/*
 * Basic Event Class which Triggers only when Called Directly.
 */
class Event{
public:
    // Basic void-void function which can signup for the event:
    typedef void (*RegisteredFunction) ();
    const bool runs_once; // Indentifies whether this event only happens once.

    Event() : runs_once{false} {};

    virtual ~Event(){
        /*for(
            std::vector<Action*>::iterator it = this->registry.begin();
            it != this->registry.end();
            ++it
            ){
            delete (*it);
        }
         this->registry.clear(); // TODO: Need to come up with way to make Action::done itself stick around*/
    } // dtor

    /*
     * Request this Event to Execute ASAP.
     * NOTE: Calls happen IN ADDITION to any event-specific timings or conditions. */
    void call(){
        this->calledButNotRun = true;
    } // #call

    /*
     * Executes this Event if it Should Execute either Because it's been Called or
     * Should Self-Trigger.
     * Returns Whether the Event was Executed.
     */
    bool tryExecute(){
        if(this->shouldTrigger() || this->calledButNotRun){ // Call #shouldTrigger first
            this->execute();
            this->calledButNotRun = false;
            return 1;
        }

        return 0;
    } // #tryExecute

    /* Test if this Event Should Self-Trigger*/
    virtual bool shouldTrigger(){
        return 0; // Basic Events only Trigger when Explicitly Called
    } // #shouldTrigger

    /* Add the Given Function to the %registry% as a BasicAction to be Executed
     Every Time the Event is Triggered. Returns a double pointer of the done variable of the Action created. */
    bool** signup(RegisteredFunction fcn){
        Action* a = new BasicAction(fcn);
        this->registry.push_back(a);
        return &(a->done);
    } // #signup

    /* Add the Given Action to the %registry% to be Executed Every Time the Event
     is Triggered. Returns a double pointer of the done variable of the Action. */
    bool** signup(Action* a){
        this->registry.push_back(a);
        return &(a->done);
    } // #signup

    // Alias for Signing Up for the Event
    bool** do_(RegisteredFunction fcn){ return signup(fcn); }
    bool** do_(Action* a){ return signup(a); }

    // Calls All Functions Registered to this Event
    void execute(){
        if(!this->ran || !this->runs_once){
            // Do this ^ check instead of deleting self b/c pointer might be accessed later if in list.
            for(std::vector<Action*>::size_type i = 0; i != this->registry.size(); i++) {
                this->registry[i]->call();
            }
            this->ran = true;
        }
    } // #execute

protected:
    Event(bool ro) : runs_once{ro} {};
    std::vector<Action*> registry;
    bool ran = false; // Whether this function has been run before (ever).
    bool calledButNotRun = false; // Whether this Event has been Called Recently but Not Yet Executed
}; // Class: Event

/* Event which Triggers Anytime #shouldTrigger is called and its condition is True*/
class ConditionalEvent : public Event{
public:
    typedef bool (*EventCondition) ();

    EventCondition condition; // Function that Triggers the Event if it's Ready to be Triggered

    ConditionalEvent(EventCondition t) : condition{t} {}; // Constructor

    virtual ~ConditionalEvent(){
        delete& condition;
    } // Destructor

    /*
     * Triggers this Event if its %condition% Allows It.
     * Returns Whether the Event was Triggered.
     */
    virtual bool shouldTrigger(){
        if(this->condition()){
            return 1;
        }
        return 0;
    } // #shouldTrigger
};

/*
 * Event Class which Triggers when its EventCondition is True When #shouldTrigger
 * is Called and was False the Last time it was Called.
 */
class TransitionEvent : public ConditionalEvent{
public:
    TransitionEvent(EventCondition t) : ConditionalEvent(t) {}; // Constructor

    bool shouldTrigger(){
        bool curr_state = this->condition();

        if(curr_state && !this->last_state){
            this->last_state = curr_state;
            return 1;
        }

        this->last_state = curr_state;
        return 0;
    } // #shouldTrigger

protected:
    bool last_state = false;
};

/*
 * Event which Triggers as Close to its Specified Interval after its Previous
 * Execution as Possible
 */
class TimedEvent : public Event{
public:
    unsigned long interval; // Interval between Executions

    TimedEvent(unsigned long i) : interval{i} {
        this->timer = i;
        this->last_time = millis();
    }; // Constructor

    ~TimedEvent(){ } // Destructor

    /*
     * Triggers this Event if its %condition% Allows It.
     * Returns Whether the Event was Triggered.
     */
    bool shouldTrigger(){
        unsigned long now = millis();
        this->timer -= now - last_time;
        this->last_time = now;

        if(this->timer < 0){ this->timer += this->interval; // Keeps execution freq. as close to interval as possible
            return 1;
        }

        return 0;
    }  // #shouldTrigger

protected:
    unsigned long last_time;
    long timer;
    TimedEvent(bool runs_once_, unsigned long i) : Event(runs_once_), interval{i} {
        this->timer = i;
        this->last_time = millis();
    };
};

/* An Event which Triggers Once After a Set Period of Time */
class SingleTimedEvent : public TimedEvent{
public:
    SingleTimedEvent(unsigned long i) : TimedEvent(true, i) {}; // Constructor
};

/* An Event which Triggers at a Certain Frequency so Long as a Given Condition is True */
class ConditionalTimedEvent : public TimedEvent{
public:
    typedef bool (*EventCondition) ();

    EventCondition condition; // Function that Triggers the Event if it's Ready to be Triggered

    ConditionalTimedEvent(unsigned long i, EventCondition t) : TimedEvent(i), condition(t){};

    virtual ~ConditionalTimedEvent(){
        delete& condition;
    } // Destructor

    /*
     * Triggers this Event if its %condition% Allows It.
     * Returns Whether the Event was Triggered.
     */
    bool shouldTrigger(){
        unsigned long now = millis();
        this->timer -= now - last_time;
        this->last_time = now;

        bool curr_state = this->condition();

        // Everytime Condition Becomes True, Restart Timer
        if(curr_state && !this->last_state){
            timer = this->interval;
        }

        this->last_state = curr_state;

        if(curr_state && this->timer < 0){ this->timer += this->interval; // Keeps execution freq. as close to interval as possible
            return 1;
        }

        return 0;
    }  // #shouldTrigger

protected:
    bool last_state = false;
};

class Schedule{
public:
    std::vector<Event*> events;

    /* Create an Event to be Triggered as Long as the Given Condition is True */
    ConditionalEvent* while_( bool (*condition)() ){
        ConditionalEvent* e = new ConditionalEvent(condition);
        this->events.push_back(e);
        return e;
    } // #while_

    /* Create an Event to be Triggered Once for Every Time the Given Condition
     Changes from false to true: */
    TransitionEvent* when( bool (*condition)() ){
        TransitionEvent* e = new TransitionEvent(condition);
        this->events.push_back(e);
        return e;
    } // #when

    /* Create an Event that will be Triggered Every %interval% Milliseconds */
    TimedEvent* every(const unsigned long interval){
        TimedEvent* e = new TimedEvent(interval);
        this->events.push_back(e);
        return e;
    } // #every

    /* Create an Event that will be Triggered Once in %t% Milliseconds */
    SingleTimedEvent* in_(const unsigned long t){
        SingleTimedEvent* e = new SingleTimedEvent(t);
        this->events.push_back(e);
        return e;
    } // #in_

    /*
     * Create an Event that will be Triggered Every %interval% Milliseconds While
     * a Given Condition is True, starting %interval% Milliseconds AFTER the
     * Condition Becomes True.
     */
    ConditionalTimedEvent* everyWhile(const unsigned long interval, bool (*condition)()){
        ConditionalTimedEvent* e = new ConditionalTimedEvent(interval, condition);
        this->events.push_back(e);
        return e;
    } // #everyWhile

    // Function to be Executed on Every Main Loop (as fast as possible)
    void loop(){
        // Iteration has to account for the fact that elements are intentionally
        // deleted from the vector in the loop and potentially added at any call
        // of #Event::tryExecute
        std::vector<Event*>::size_type size = this->events.size();
        std::vector<Event*>::size_type i = 0;
        while(i < size){ if( this->events[i]->tryExecute() && this->events[i]->runs_once ){
                // Delete Event if it's been Executed and Only Runs Once
                delete this->events[i]; // Delete the Event
                this->events.erase(this->events.begin() + i); // Remove the addr from the vector
                size--; // As far as we know, the vector is now smaller
            } else{
                ++i; // Increment iterator normally
            }
        }
    } // #loop
}; // Class: Schedule
#endif // SCHEDULE_H