// --------------------------------------------
//    M. Wobisch   March 2019
//    Arduino code for Marble Run
//
//  the code changes the timing of the marble stairs randomly 
// --------------------------------------------
//
// works with 
//  - Arduino Pro Mini (requires FTDI adapter for programming) 
// or
//  - Arduino Nano
//
// --------------------------------------------------
// connections:
//  - the servo pins are connected to +5V, GND, and to Arduino pin 9
//  - the micro switch is connected between GND and Arduino pin 10
// ------------------------------------------------
// the constants maxAngle and minAngle have to be set corresponding 
// to the maximum and minimum angles of the servo
//



// --- add servo library and create servo object to control a servo
#include <Servo.h>
Servo liftServo;

const byte pinSwitch = 10;  // micro switch to detect when
                            //top position is reached

// --- the min/max angles were calibrated
const int maxAngle = 154;   // angle for lift in bottom position (158 is safe, but 154 is good enough)
const int minAngle = 20;    // angle for lift in top position
   
void setup() {  
  liftServo.attach(9);       // attaches the servo on pin 9 to the servo object
  liftServo.write(maxAngle-1); // init to initial angle (maxAngle = lift down)

  // --- LED flashing - to see if board resets during operation
  pinMode(13, OUTPUT);
  for (byte i = 1; i<4 ; i++) { 
    digitalWrite(13, HIGH);   // turn the LED on (HIGH is the voltage level)
    delay(100);              // wait 
    digitalWrite(13, LOW);    // turn the LED off by making the voltage LOW
    delay(350);              // wait 
  }
    
  pinMode(pinSwitch, INPUT_PULLUP); // init pin for micro switch

  // --- initialize random number generator with reading from open analog input
  randomSeed(analogRead(A0));
  delay(1200);
 }




void loop() {
  static byte rotationMode = 99;    // 99: cycle through all modes
                                    // 0-n: specify mode (not used)
  rotateServo(rotationMode);        // rotate servo
}



// --- rotate the servo to next position
void rotateServo(byte iMode) {
  static unsigned long previousTime = 0;
  const byte nMode = 5;      // number of modes
  static byte prevMode = 0;

  // - timing 
  const unsigned int timePerStep[nMode] = {    8,   12,    10,    13,   11} ;
  const unsigned int timeWait[nMode] =    {  600, 1100,   800,  1200,  900};
  const unsigned int timeLongWaitMin[nMode] = { 9000, 10000, 11000, 12000, 13000};
  const unsigned int timeLongWaitMax[nMode] = {20000, 20000, 25000, 19000, 21000};
  const byte modeStepsMin[nMode] = { 3,  10,  8,  11,  6};
  const byte modeStepsMax[nMode] = { 7,  16, 10,  17, 13};

  static unsigned int timeExtra =  0;  // add to timePerStep for smaller angles

  static byte Mode = 0;    // initial mode - when starting
  static byte modeSteps = 11;   // No. of steps for first mode - when starting
                                // set =11 to make sure one marble makes it to the top  
  static unsigned int timeLongWait = 1000;

  static byte stepCount = 0;
  static byte wait = 0;        // =1 indicates wait between change of direction
  static byte longWait = 0;    // =1 indicates a long wait in progress
  
  const byte stepSize = 1;
  static int currentAngle = maxAngle;   // the value to which the servo was  initialized in setup()
  static int increment = -stepSize; // negative when starting at max

  // --- for specifying a mode (not yet fully implemented ...)
  if (iMode != 99) Mode = iMode;

  // --- make a long break between different modes - set next mode - set No. steps
  if (longWait == 1 && ( (millis()-previousTime) > timeLongWait || digitalRead(pinSwitch) == LOW) ) {
    longWait = 0; 
    previousTime = millis();
    Mode++;       // <<< maybe later randomize - store lastMode - require =! lastMode
    if (Mode >= nMode) Mode = 0;
    while (Mode == prevMode) Mode = random(nMode);    // randomly - avoid repeating
    prevMode = Mode;
    modeSteps = random(modeStepsMin[Mode],modeStepsMax[Mode]);
    digitalWrite(13, LOW);  // turn off LED13
  }

  // --- make a break between different steps (in up and down positions)
  if (wait == 1 && (millis()-previousTime) > timeWait[Mode]) {
    wait = 0; 
    previousTime = millis();
  }

  // --- the individual steps --- need to modify for switch - separately check max(angle) / min(switch) criteria
  if (longWait == 0 && wait == 0 && (millis()-previousTime) > (timePerStep[Mode] + timeExtra)) {
    previousTime = millis();                   // - save time 
    currentAngle = currentAngle + increment;   // - increment angle
    if (currentAngle < 70) {                   // - for smaller angles: add a little time per step
      timeExtra = 5;      
    } else if (currentAngle < 82) {
      timeExtra = 3;
    } else {
      timeExtra = 0;
    }
    liftServo.write(currentAngle);             // - set servo
    if (currentAngle >= maxAngle || currentAngle <= minAngle || (digitalRead(pinSwitch) == LOW && increment < 0)) {   // new code: switch
      increment = -increment;                  // - change direction
      wait = 1;                                // - signal: make a pause
    }
    
    if (currentAngle == maxAngle) stepCount++; // one step is finished - count No steps
    
    if (stepCount == modeSteps) {              // long break after mode is finished
      stepCount = 0;
      longWait = 1;
      digitalWrite(13, HIGH);  // turn on LED13
      timeLongWait = random(timeLongWaitMin[Mode],timeLongWaitMax[Mode]);
    }
   
  }
  
}

