  /////////////////////////////////////////////////////////////////
  // Time-lapse shutter release control based on an Arduino Nano //
  /////////////////////////////////////////////////////////////////

// A program/sketch to control the shutter of a Nikon DSLR to create a time-lapse video.
//
// The program is controlled through the Arduino serial monitor.
// The shutter release rate can be adjusted during the filming to adapt to varying 
// degrees of action taking place.
//
//
// Written in 2013 by Per Magnusson, Axotron, http://axotron.se/index_en.php
// v 0.2
//
//
// (c) 2013, Per Magnusson, Axotron, http://axotron.se/index_en.php
//
// License: 
// I make this code available in the hope that it might be educational and/or 
// inspirational. Feel free to use it, change it, include it in your own commercial 
// or non-commercial projects as you see fit. You do not have to attribute me, but 
// you are welcome to do so if you like. 
// I give no guarantee that it will be good for anything. 
// Below is some legalese I copied from a version of the BSD license:
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.


// Hardware description
const int focusPin = 2;
const int shutterPin = 3;
const int lampPin = 4;

// Constants
const unsigned int lampOnTime = 10; // Lamp on time after shutter release in ms
const unsigned long initialTimeLapseInterval = 500; // In ms
const unsigned int timeLapseDelta = 0; // Lengthening of interval for each frame, ms. 
// Set timeLapseDelta to 0 for constant interval.

// Global variables
int incomingByte;
byte timeLapseMode = 0;
unsigned long timeLapseInterval;
unsigned long nextTime;
unsigned long photoCounter = 0;

void setup() {
  // initialize serial communication:
  Serial.begin(9600);
  // initialize pins
  pinMode(focusPin, OUTPUT);
  pinMode(shutterPin, OUTPUT);
  pinMode(lampPin, OUTPUT);
  digitalWrite(focusPin, LOW);
  digitalWrite(shutterPin, LOW);
  digitalWrite(lampPin, LOW);
  // Initialize variables
  timeLapseInterval = initialTimeLapseInterval;
  printHelp();
}

void loop() {
  static unsigned long startTime = 0;
  
  // see if there is an incoming command on the serial port
  if (Serial.available() > 0) {
    // read the oldest byte in the serial buffer:
    incomingByte = Serial.read();
    Serial.write(incomingByte);
    if (incomingByte == 'p') {
      // Take a single picture
      expose();
      Serial.println("!");
    } else if (incomingByte == 'L') {
      // Lamp on
      digitalWrite(lampPin, HIGH);
      Serial.println("!");
    } else if (incomingByte == 'l') {
      // Lamp off
      digitalWrite(lampPin, LOW);
      Serial.println("!");
    } else if (incomingByte == 'F') {
      // Start time lapse
      Serial.println("!");
      Serial.print("Entering time lapse mode with interval ");
      Serial.print(timeLapseInterval);
      Serial.println(" ms.");
      timeLapseMode = 1;
      photoCounter = 0;
      startTime = millis();
      nextTime = startTime;
    } else if (incomingByte == 'f') {
      // End time lapse
      Serial.println("!");
      Serial.println("Exiting time lapse mode.");
      Serial.print(photoCounter);
      Serial.print(" pictures taken in ");
      Serial.print((millis()-startTime)/1000);
      Serial.println(" seconds.");
      timeLapseMode = 0;
    } else if (incomingByte == 's') {
      // Slower (longer interval)
      nextTime += timeLapseInterval>>2; // Make this longer time effective immediately
      timeLapseInterval += timeLapseInterval>>2;
      Serial.println("!");
      Serial.print("New rate: ");
      Serial.print(timeLapseInterval);
      Serial.println(" ms.");
    } else if (incomingByte == 'q') {
      // Quicker (shorter interval)
      if(timeLapseInterval > 350) {
        nextTime -= timeLapseInterval>>2; // Make this shorter time effective immediately
        timeLapseInterval -= timeLapseInterval>>2;
        Serial.println("!");
        Serial.print("New rate: ");
        Serial.print(timeLapseInterval);
        Serial.println(" ms.");
      } else {
        // We cannot go this fast
        Serial.println("?");
        Serial.println("Already at maximum rate.");
      }
    } else {
      // Unknown command, print help
      // First empty the serial buffer to not print the help message many times if 
      // there are more characters coming at once.
      delay(5); // Wait to see if there are more crap characters coming
      while (Serial.available() > 0) {
        while (Serial.available() > 0) {
          // Discard all characters that are coming in this burst in case 
          // there is a lot of crap coming
          Serial.read();
        }
        // an extra delay and check again for more characters
        delay(5); // Wait to see if there are more crap characters coming
      }
      printHelp();
    }      
  }

  // Handle the time lapse  
  if(timeLapseMode) {
    if(millis() >= nextTime) {
      expose();
      timeLapseInterval += timeLapseDelta;
      nextTime += timeLapseInterval;
      photoCounter += 1;
      Serial.print(photoCounter);
      Serial.print(" pictures taken in ");
      Serial.print((millis()-startTime)/1000);
      Serial.print(" seconds. Current interval is ");
      Serial.print(timeLapseInterval);
      Serial.println(" ms.");    
    }
  }
}

void expose() {
  digitalWrite(lampPin, HIGH);
  delay(1);
  digitalWrite(focusPin, HIGH);
  delay(300); // Seems like this has to be around 300 or more to make the D300 happy
  digitalWrite(shutterPin, HIGH);
  delay(50);
  digitalWrite(shutterPin, LOW);
  digitalWrite(focusPin, LOW);
  delay(lampOnTime); // The exposure time might be long, make sure this is longer
  digitalWrite(lampPin, LOW);
}

void printHelp() {
  Serial.println("\nCommands:");
  Serial.println("p - take a picture");
  Serial.println("L - turn lamp on");
  Serial.println("l - turn lamp off");
  Serial.print("F - start time lapse mode with ");
  Serial.print(initialTimeLapseInterval);
  Serial.println(" ms initial interval.");
  Serial.println("f - end time lapse mode");
  Serial.println("s - take pictures slower (increase period by ~25%)");
  Serial.println("q - take pictures quicker (decrease period by ~25%)");
}
