// Fan cart driver version 2.0
// Eric Ayars
// 11/12/10

#define MOTORPWM 11
#define MOTORDIR 12
#define POWERINDICATOR 13
#define MODESWITCH 2
#define SPEEDSWITCH 5
#define TIMESWITCH 7
#define MAGNETSWITCH 8
#define MODELIGHT 3
#define SPEEDLIGHT 4
#define TIMELIGHT 6
#define DEBOUNCE 10
#define FLASHTIME 150

byte UserInput = 0;
boolean ProgramState = false;
long int CurrentTime;
byte Speed = 2; // Ranges from 1 to 3 (0 not used).
byte PowerLevel[] = {0, 130, 200, 255};
byte Mode = 1;  // Ranges from 1 to 3.
byte Time = 2;  // Ranges from 1 to 4 (0 not used).
int TimeLevel[] = {0, 1000, 2000, 3000, 4000};
boolean MotorState = false;
boolean MotorDirection = false;
byte LastMagnetState = HIGH;

byte ButtonDown() {
    // Returns 3-bit indicator of which buttons are currently down.
    // bit 0 - Mode
    // bit 1 - Speed
    // bit 2 - Time
    byte result = 0;    // Default case -- nothing is pressed

    if (digitalRead(MODESWITCH) == LOW) {
        // Mode button is down
        result = result | B0001;
    }

    if (digitalRead(SPEEDSWITCH) == LOW) {
        // Speed button is down
        result = result | B0010;
    }

    if (digitalRead(TIMESWITCH) == LOW) {
        // Time button is down
        result = result | B0100;
    }

    delay(DEBOUNCE);
    return result;
}
    
boolean MagnetSensed() {
    boolean state;
    byte CurrentState = digitalRead(MAGNETSWITCH);
    if ((LastMagnetState == HIGH) && (CurrentState == LOW)) {
        // Cart has just passed a magnet
        state = true;
        delay(DEBOUNCE);
    } else {
        state = false;
    }
    LastMagnetState = CurrentState;
    return state;
}

void AnnounceReady() {
    digitalWrite(MODELIGHT, HIGH);
    digitalWrite(SPEEDLIGHT, HIGH);
    digitalWrite(TIMELIGHT, HIGH);
}

void ShowStatus(byte Light, byte Blinks) {
    // Blinks a light some number of times.
    digitalWrite(Light, LOW);
    delay(FLASHTIME);
    for (byte j=0; j<Blinks; j++) {
        digitalWrite(Light, HIGH);
        delay(FLASHTIME);
        digitalWrite(Light, LOW);
        delay(FLASHTIME);
    }
    delay(FLASHTIME);
    delay(FLASHTIME);
}

void MotorOn() {
    // turns motor on in direction set by global MotorDirection.
    digitalWrite(POWERINDICATOR, HIGH);
    digitalWrite(MOTORDIR, MotorDirection);
    if (MotorDirection) {
        // set pwm appropriately for backward spin
        analogWrite(MOTORPWM, 255-PowerLevel[Speed]);
    } else {
        // set pwm for forward spin
        analogWrite(MOTORPWM, PowerLevel[Speed]);
    }
    MotorState = true;
}

void MotorOff() {
    // turns motor off.
    digitalWrite(MOTORDIR, LOW);
    digitalWrite(MOTORPWM, LOW);
    digitalWrite(POWERINDICATOR, LOW);
    MotorState = false;
}

void ProgramMode(byte ProgramThis) {
    // Sets mode, speed, time...
    boolean State = false;
    boolean Exit = false;
    
    if (ProgramThis & B0001) {  // Program the Mode.
        // Turn mode light off and the other two lights on.
        digitalWrite(MODELIGHT, LOW);
        digitalWrite(SPEEDLIGHT, HIGH);
        digitalWrite(TIMELIGHT, HIGH);
        
        while (!Exit) {
            delay(DEBOUNCE);
            if (ButtonDown() == 1) {
                // Mode button has been pressed. Increment mode.
                Mode += 1;
                if (Mode > 3) {
                    Mode = 1;
                }
                ShowStatus(MODELIGHT, Mode);
            }
            UserInput = ButtonDown();
            if (UserInput > 1) {
                Exit = true;
            }
        }
    }

    if (ProgramThis & B0010) {  // Program the Speed.
        // Turn speed light off and the other two lights on.
        digitalWrite(MODELIGHT, HIGH);
        digitalWrite(SPEEDLIGHT, LOW);
        digitalWrite(TIMELIGHT, HIGH);
        
        while (!Exit) {
            delay(DEBOUNCE);
            if (ButtonDown() == 2) {
                // Speed button has been pressed. Increment Speed.
                Speed += 1;
                if (Speed > 3) {
                    Speed = 1;
                }
                ShowStatus(SPEEDLIGHT, Speed);
            }
            UserInput = ButtonDown();
            if ((UserInput & B0001) || (UserInput & B0100)) {
                Exit = true;
            }
        }
    }

    if (ProgramThis & B0100) {  // Program the Time.
        // Turn Time light off and the other two lights on.
        digitalWrite(MODELIGHT, HIGH);
        digitalWrite(SPEEDLIGHT, HIGH);
        digitalWrite(TIMELIGHT, LOW);
        
        while (!Exit) {
            delay(DEBOUNCE);
            if (ButtonDown() == 4) {
                // Time button has been pressed. Increment Time.
                Time += 1;
                if (Time > 4) {
                    Time = 1;
                }
                ShowStatus(TIMELIGHT, Time);
            }
            UserInput = ButtonDown();
            if ((UserInput & B0001) || (UserInput & B0010)) {
                Exit = true;
            }
        }
    }
}

void DoMode(byte Mode) {
    // This routine runs the desired mode until a button is pressed.

    AnnounceReady();
    MotorDirection = false; // false is forward, true is backwards.

    while (!ButtonDown()) {
        // No button has been pressed, so keep going.

        // Mode 1
        //
        // In mode 1, the fan turns on and off on alternating magnets.
        if (Mode==1) {
            MotorDirection = false;
            if (MagnetSensed()) {
                if (MotorState) {
                    // Motor is currently on. Turn it off.
                    MotorOff();
                } else {
                    // Motor is currently off. Turn it on.
                    MotorOn();
                }
            }
        }

        // Mode 2
        //
        // In mode 2, the fan turns on at the first magnet, then turns off
        // a set time later.
        if (Mode==2) {
            MotorDirection = false;
            if (MagnetSensed()) {
                // Turn the motor on...
                MotorOn();
                // Wait a given time...
                delay(TimeLevel[Time]);
                // And turn the motor off.
                MotorOff();
            }
        }

        // Mode 3
        //
        // In mode 3, the motor turns on when it first sees a magnet, then 
        // reverses each time it passes a magnet.
        if (Mode==3) {
            if (MagnetSensed()) {
                // Reverse the motor...
                MotorDirection = !MotorDirection;
                // And turn it on in the new direction.
                MotorOn();
            }
        }
    }

    // Button has been pressed, so...
    MotorDirection = false;
    MotorOff();
}

void setup() {

    // Set appropriate pins to output.
    pinMode(POWERINDICATOR, OUTPUT);
    pinMode(MODELIGHT, OUTPUT);
    pinMode(SPEEDLIGHT, OUTPUT);
    pinMode(TIMELIGHT, OUTPUT);
    pinMode(MOTORPWM, OUTPUT);
    pinMode(MOTORDIR, OUTPUT);

    // Set appropriate pins to input, with pull-up resistors as needed.
    pinMode(MODESWITCH, INPUT);
    digitalWrite(MODESWITCH, HIGH);
    pinMode(SPEEDSWITCH, INPUT);
    digitalWrite(SPEEDSWITCH, HIGH);
    pinMode(TIMESWITCH, INPUT);
    digitalWrite(TIMESWITCH, HIGH);
    pinMode(MAGNETSWITCH, INPUT);
    digitalWrite(MAGNETSWITCH, HIGH);   // This one may not need the 
                                        // pull-up resistor, but...
    
    // Turn off motor just in case.
    MotorOff();

    // Indicate Status.
    ShowStatus(MODELIGHT, Mode);
    ShowStatus(SPEEDLIGHT, Speed);
    ShowStatus(TIMELIGHT, Time);

    // Indicate ready-to-go
    AnnounceReady();
}

void loop() {
    
    // The main loop checks for user input, passes any input to
    // the "ProgramMode" routine, then runs "DoMode". That's it!

    // Check to see whether the user wants anything
    UserInput = ButtonDown();
    CurrentTime = millis(); // Check what time it is

    while (ButtonDown()) {
        // Wait until button is up to decide what to do.
        delay(10);
    }
    if (millis()-CurrentTime > 2000) {
        // Button was held for more than 2 seconds, so program the device.
        ProgramMode(UserInput);
    } else {
        if (UserInput & B0001) {
            // The mode button was pressed.
            ShowStatus(MODELIGHT, Mode);
        } 
        if (UserInput & B0010) {
            // The speed button was pressed.
            ShowStatus(SPEEDLIGHT, Speed);
        }
        if (UserInput & B0100) {
            // The Time button was pressed.
            ShowStatus(TIMELIGHT, Time);
        }
    }

    DoMode(Mode);
}