 
 class ProgramStep {
     uint8_t motorDirection = BRAKEGND;
+    
+    // derived upon construction
+  protected:
     uint8_t motorSpeed = 0;
-    long durationMillis = 0;
-    long startTime = 0;
-    
-    // derived upon construction
     uint8_t desiredStateIna;
     uint8_t desiredStateInb;
     
   public:
-    ProgramStep(uint8_t motorDirection, uint8_t motorSpeed, long durationMillis) {
+    /**
+     * If durationMillis == 0, this program step will only report false
+     * from isDurationComplete() when  isSignalActive is true.
+     */
+    ProgramStep(uint8_t motorDirection, uint8_t motorSpeed) {
         this->motorDirection = motorDirection;
         this->motorSpeed = motorSpeed;
-        this->durationMillis = durationMillis;
         if (motorDirection == FORWARD || motorDirection == BRAKEVCC)
             desiredStateIna = HIGH;
         else

    See details in the comments for the startMotor() function below.

 Note: This was adapted (a little bit) from the example code for the SparkFun
    (2-chip, 2-motor) Monster Moto Shield.
 Date: 2016/10/10
 Author: R Thomas
 

        else
            desiredStateInb = LOW;
    }
    
    virtual boolean activate(MotorState *motorState) = 0;
    
    virtual boolean isDone(void) = 0;
    
};

/*
 * Base Signal class includes support for subclasses to extend the time
 * they report active by a number of milliseconds set in the extraTimeDuration
 * member variable (by calling setExtraTimeDuration()).  This provides a way
 * to "soften" the reaction time when a signal is detected, which is sometimes
 * called "coasting".  Subclasses should call startExtraTime immediately after
 * detecting a signal condition to reset the extra-time clock, and thereafter, 
 * delay reporting isSignalActive as false until isExtraTimeDone() returns true.
 */
class Signal {
    long extraTimeDuration = 0;
    long extraTimeStartTime = 0;
    protected:
    void startExtraTime() {
        extraTimeStartTime = millis();
    }
    boolean isDuringExtraTime() {
        return (millis() <= (extraTimeStartTime + extraTimeDuration));
    }

  public:  
    void setExtraTimeDuration(uint8_t extraTimeDuration) {
        this->extraTimeDuration = extraTimeDuration;
    }
    virtual boolean isSignalActive() = 0;
    
};

class ProgramStepWithDuration: public ProgramStep {
    long durationMillis = 0;
    long startTime = 0;
    Signal *skipSignal = 0;
  public:
    ProgramStepWithDuration(uint8_t motorDirection, uint8_t motorSpeed, long durationMillis) : ProgramStep(motorDirection, motorSpeed) {
        this->durationMillis = durationMillis;
    }
    
    ProgramStepWithDuration(uint8_t motorDirection, uint8_t motorSpeed, long durationMillis, Signal *skipSignal) : ProgramStepWithDuration(motorDirection, motorSpeed, durationMillis) {
        this->skipSignal = skipSignal;
    }
    
    boolean isDone() {
        /*
         * If this object was constructed with a Signal object, any time that signal
         * reports that it is "on", this step immediately reports that it is done.
         */
        if (skipSignal && skipSignal->isSignalActive()) {
            return true;
        }
        long endTime = startTime + durationMillis;
        return (millis() >= endTime);
    }

    boolean activate(MotorState *motorState) {
        /*
         * If this object was constructed with a Signal object, any time that signal
         * reports that it is "on", this step never gets activated.
         */
        if (skipSignal && skipSignal->isSignalActive()) {
            return false;
        }
        startTime = millis();  // reset the clock
        motorState->setDesiredState(desiredStateIna, desiredStateInb, motorSpeed);
        return true;
    }
    
};

class SignalSimulatedWithTimer : public Signal {
    long startTime;
  public:
    boolean isSignalActive() {
        return (millis() > (startTime + 3000));
    }
    
    void startTimer() {
        startTime = millis();
    }
};

class SignalInputPin : public Signal {
    /*
     * The pin from which the digital state is read and compared with the signalOnValue
     */
    uint8_t digitalPinNumber;
    /* Th signalOnValue variable holds HIGH or LOW, depending upon which of those represents the "signal-on" state
     * The default is LOW, which assumes the pin is initialized with INPUT_PULLUP (i.e. open == HIGH == "off")
     * If this object is constructed with a custom signalOnValue (which would be superfluous unless it is HIGH),
     *     then isSignalActive() will return "true":
     *         1) UNLESS the pin is connected to ground,
     *         or
     *         2) IF the pin is initialized as a normal INPUT (without using the built in PULLUP resistor).
     * 
     * Note: If a digital pin is used without the internal pullup, it should use
     *    an external pull-down resistor (~10K)
     */   
    uint8_t signalOnValue = LOW;
    boolean wasActiveOnPreviousCall = false;
  public:
    SignalInputPin(uint8_t digitalPinNumber) : Signal() {
        this->digitalPinNumber = digitalPinNumber;
    }
    SignalInputPin(uint8_t digitalPinNumber, uint8_t signalOnValue) : SignalInputPin(digitalPinNumber) {
        this->signalOnValue = signalOnValue;
    }
    /*
     * Implemented to report true when the current digital state of the pin
     * matches the value of signalOnValue.
     */
    boolean isSignalActive() {
        uint8_t currentPinStatus = digitalRead(digitalPinNumber);
        if (wasActiveOnPreviousCall && !currentPinStatus) {
            wasActiveOnPreviousCall = false;
            startExtraTime();
        }
        if (currentPinStatus) {
            wasActiveOnPreviousCall = true;
        }
        return (isDuringExtraTime() || currentPinStatus == signalOnValue);
    }
    
};


/*
 * This implementation of Signal reads logic pins which, together, indicate that
 * that the park function has been triggered and that the motor is not yet
 * in the park position.
 * 
 * A program step using this Signal would continue at its speed and
 * direction until the motor is parked, so it should be followed by
 * a step that does whatever should follow parking the motor.
 *     Example 1: Follow with a step that remains stopped while just the trigger pin is still "on".
 *     Example 2: Continue with a sequence of motions meant to begin at the park position.
 * 
 * This is designed to be used with windshield wiper motors
 * that have an internal switch that connects two contacts
 * when the crank-arm has reached a particular spot.
 *
 * Reminder: Be sure the input to the parkStatus digital pin is
 * properly isolated and noise filtered if it is anywhere near
 * the brushes or other electrical noise in the motor.
 */
class SignalParkMotorFeedback : public Signal {
    uint8_t triggerDigitalPinNumber;
    uint8_t parkStatusDigitalPinNumber;
    /*
     * Note: The pins should be initialized as INPUT_PULLUP (open == "off" == HIGH)
     * unless the default "on" values are overridden upon object construction.
     */
    uint8_t triggerDigitalPinOn = LOW;
    uint8_t parkStatusDigitalPinOn = LOW;
  public:
    SignalParkMotorFeedback(uint8_t triggerDigitalPinNumber, uint8_t parkStatusDigitalPinNumber) : Signal() {
        this->triggerDigitalPinNumber = triggerDigitalPinNumber;
        this->parkStatusDigitalPinNumber = parkStatusDigitalPinNumber;
    }
    SignalParkMotorFeedback(uint8_t triggerDigitalPinNumber, uint8_t parkStatusDigitalPinNumber, uint8_t triggerDigitalPinOn, uint8_t parkStatusDigitalPinOn)
            : SignalParkMotorFeedback(triggerDigitalPinNumber, parkStatusDigitalPinNumber) {
        this->triggerDigitalPinOn = triggerDigitalPinOn;
        this->parkStatusDigitalPinOn = parkStatusDigitalPinOn;
    }
    /*
     * This Signal reports that it is "active" as long as the triggering pin
     * is "on" and the park position pin is still "off" (i.e. not parked yet.)
     */
    boolean isSignalActive() {
        return (digitalRead(triggerDigitalPinNumber) == triggerDigitalPinOn 
            && digitalRead(parkStatusDigitalPinNumber) != parkStatusDigitalPinOn);
    }
    
};

/**
 * Note: This can be used as a "pause until signal is not active"
 * by simply never calling activate, or it may follow another
 * signal step with the same signal but different motor settings.
 */
class ProgramStepWithSignal: public ProgramStep {
  protected:
    Signal *signalSource = 0;

  public:
    ProgramStepWithSignal(uint8_t motorDirection, uint8_t motorSpeed, Signal *signalSource) : ProgramStep(motorDirection, motorSpeed) {
        this->signalSource = signalSource;
    }
    
    /*
     * Returns the false if isSignalActive() returns true.
     * In other words, the program step is _not_ done yet
     * as long as the signal _is_ active.
     */
    boolean isDone() {
        return (signalSource->isSignalActive() != true);
    }
    
    boolean activate(MotorState *motorState) {
        if (signalSource && signalSource->isSignalActive()) {
            motorState->setDesiredState(desiredStateIna, desiredStateInb, motorSpeed);
        }
        // Otherwise don't do anything
    }
};

class Program {
    uint8_t currentProgramStepIndex = 0;
    uint8_t programStepCount = 0;
    ProgramStep *programSteps[MAXIMUM_PROGRAM_STEPS];
        
  public:
