// Libraries
#include <SoftwareSerial.h>
#include <AccelStepper.h>

// Initialize the library
AccelStepper stepper(1, 15, 14);
SoftwareSerial BtSerial(3, 2); // Rx 2 / Tx 3

// Contantes
const int steps_for_mm = 41; // Nombre de pas pour déplacer l'appreil photo d'un millimètre [en pas] (400 pas = 1 tour)
const int delay_for_mm = 40; // Temps que met le système pour déplacer l'appreil photo d'un millimètre [en millisecondes]
const char mode_continue = 'M', mode_timelapse = 'm', mode_on = 'O', mode_off = 'o', set_speed = 's', set_intervale = 'd', set_distance = 'D', set_expo = 'e', set_direction_left = 'G', set_direction_right = 'g';

// Variables
char mode = 'c'; // c = continue / t = timelapse
int go = 0; // Démarrer le mouvement ou non

// vitesse en mode continue
int move_speed = 1000;
int move_speed_min = 100;
int move_speed_max = 3000;

// temps entre deux photos [en secondes]
int intervale = 10;      // 10 secondes par défaut
int intervale_min = 1;   // 1 seconde min
int intervale_max = 300; // 5 minutes max

// distance à parcourir entre deux photos [en millimètres]
int distance = 10;     // 1 centimètre par défaut
int distance_min = 1;  // 1 millimètre min
int distance_max = 50; // 5 centimètres max

// Temps d'exposition [en millisecondes]
int bulb_delay = 150;       // 150 millisecondes
int bulb_delay_min = 50;    // 50 millisecondes min
int bulb_delay_max = 10000; // 10 secondes max

int waiting = 0; //
int sens = 0; // sens de mouvement du chariot (0 => vers la gauche, 1 => vers la droite)
int next_pos = 0; // position suivante du moteur
int shotsNumber = 0; // nombre de photos prises

// Timers pour le calcul de l'intervale entre 2 photos
unsigned long previousMillis; 
unsigned long currentMillis;

// Pins
int camera_pin = 18; // PIN pour la commande de l'appareil photo
int sleep_pin = 16; // PIN pour mettre le BED en sommeil
int reset_pin = 17; // PIN pour redémarrer le BED

void setup()
{
  
  Serial.begin(9600);
  BtSerial.begin(9600);
  
  pinMode(camera_pin, OUTPUT);
  pinMode(sleep_pin, OUTPUT);
  pinMode(reset_pin, OUTPUT);
  
  digitalWrite(sleep_pin, LOW);
  
  // setup motor
  stepper.setMaxSpeed(3000);      // vitesse maxi du moteur (Max 3000)
  stepper.setSpeed(1500);         // vitesse par défaut
  stepper.setAcceleration(50000); // accélération du moteur (Max 100000)

}

void loop()
{

  if (BtSerial.available())
  {

    char ch = BtSerial.read();
  
    //Serial.write(ch);
    //Serial.write('\n');
   
    switch (ch)
    {
      
      // mode continue
      case mode_continue:
      {
        Serial.print("change mode for continue");
        mode = 'c';
        BtSerial.print("*IR255G255B255*");
        BtSerial.print("*iR0G0B0*");
        break;
      }

      // mode timelapse
      case mode_timelapse:
      {
        Serial.print("change mode for timelapse");
        mode = 't';
        BtSerial.print("*iR255G255B255*");
        BtSerial.print("*IR0G0B0*");
        break;
      }
      
      // mise en route
      case mode_on:
      {
        Serial.print("run");
        go = 1;
        bed_reset();
        BtSerial.print("*LR255G00B0*");
        digitalWrite(sleep_pin, HIGH);
        delay(5);
        break;
      }

      // arrêt
      case mode_off:
      {
        Serial.print("stop");
        go = 0;
        BtSerial.print("*LR0G0B0*");
        digitalWrite(sleep_pin, LOW);
        break;
      }

      // déplacement vers la gauche
      case set_direction_left:
      {
        if (sens)
        {
          Serial.print("change direction for left");
          sens = 0;
          stepper.setSpeed(move_speed);
          BtSerial.print("*PR0G0B255*");
          BtSerial.print("*pR0G0B0*");
        }
        break;
      }

      // déplacement vers la droite
      case set_direction_right:
      {
        if (!sens)
        {
          Serial.print("change direction for right");
          sens = 1;
          stepper.setSpeed(-move_speed);
          BtSerial.print("*pR0G0B255*");
          BtSerial.print("*PR0G0B0*");
        }
        break;
      }

      // réglage de la vitesse en mode continue
      case set_speed:
      {
        Serial.print("set speed to ");
        String data = get_btserial_data();
        int new_move_speed = data.toInt();
        move_speed = map(new_move_speed, 0, 100, move_speed_min, move_speed_max);
        stepper.setSpeed(move_speed);
        Serial.print(move_speed);
        BtSerial.print("*w" + String(move_speed) + '*');
        break;
      }

      // réglage du délais entre 2 photos
      case set_intervale:
      {
        Serial.print("set intervale to ");
        String data = get_btserial_data();
        int new_intervale = data.toInt();
        intervale = map(new_intervale, 0, 100, intervale_min, intervale_max);
        Serial.print(intervale);
        BtSerial.print("*x" + String(intervale) + '*');
        break;
      }

      // réglage de la distance entre 2 photos
      case set_distance:
      {
        Serial.print("set distance to ");
        String data = get_btserial_data();
        int new_distance = data.toInt();
        distance = map(new_distance, 0, 100, distance_min, distance_max);
        Serial.print(distance);
        BtSerial.print("*y" + String(distance) + '*');
        break;
      }

      // réglage du temps d'exposition
      case set_expo: 
      {
        Serial.print("set exposition duration to ");
        String data = get_btserial_data();
        int new_bulb_delay = data.toInt();
        bulb_delay = map(new_bulb_delay, 0, 100, bulb_delay_min, bulb_delay_max);
        Serial.print(bulb_delay);
        BtSerial.print("*z" + String(bulb_delay) + '*');
        break;
      }
    }
    
    Serial.write('\n');
    
  }

  // si dolly mise en route
  if (go == 1)
  {
    
    // si mode continue
    if (mode == 'c')
    {

       stepper.runSpeed();

    }

    // sinon si mode timelapse
    else if (mode == 't')
    {

      // calcul de la nouvelle position
      next_pos = distance * steps_for_mm;

      // sens de déplacement
      if (!sens) next_pos = -next_pos;

      // si on n'est pas dans le délais entre 2 photos
      if (!waiting) {

        // déplacement
        Serial.print("rotation");
        rotatation();

        // attente pour ne pas prendre la photo trop tôt
        delay(500);

        // prise de la photo
        Serial.print("shoot");
        shoot();

        // maintenant on attend la fin du délais entre 2 photos
        previousMillis = millis();
        waiting = 1;
      
      }

      // on est dans le délais entre 2 photos
      else
      {
        
        currentMillis = millis();

        // si le temps est écoulé, on recommence la boucle
        if (currentMillis - previousMillis >= (intervale * 1000 - 500)) {
          waiting = 0;
        }
      }
    }
  }
}

// lecteur des infos passées via le bluetooth
String get_btserial_data()
{
  
  String data = "";
  
  while (BtSerial.available())
  {
    delay(1);
    data += char(BtSerial.read());
  }
  
  return data;
  
}

// déplacement
int rotatation()
{
  
  digitalWrite(sleep_pin, HIGH);
  delay(5);
  
  stepper.moveTo(next_pos);
  while (stepper.distanceToGo() != 0) {
    stepper.run();
  }
  stepper.setCurrentPosition(0);
  
  digitalWrite(sleep_pin, LOW);
  
}

// prise d'une photo
int shoot()
{
  
  digitalWrite(camera_pin, HIGH);
  delay(bulb_delay);
  digitalWrite(camera_pin, LOW);
  shotsNumber++;
  BtSerial.print("*f" + String(shotsNumber) + '*');
  
}

// resert du BigEasyDriver
void bed_reset()
{
  
  // Reset du BED
  digitalWrite(reset_pin, LOW);
  delay(100);
  digitalWrite(reset_pin, HIGH);
  delay(100);
  
}
