/* 
Polisseur
Copyright (C) 2019 Arofarn

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

#include <RotaryEncoder.h>
#include "SevSeg.h"
#include <EveryTimer.h>
#include <AccelStepper.h>

// PIN USAGE
#define SEVSEG_DIGIT_1    16
#define SEVSEG_DIGIT_2    17
#define SEVSEG_DIGIT_3    3
#define SEVSEG_DIGIT_4    4
#define SEVSEG_COLON      2
#define SEVSEG_SEGMENT_A  8
#define SEVSEG_SEGMENT_B  14
#define SEVSEG_SEGMENT_C  6
#define SEVSEG_SEGMENT_D  A1
#define SEVSEG_SEGMENT_E  23
#define SEVSEG_SEGMENT_F  7
#define SEVSEG_SEGMENT_G  5
#define SEVSEG_SEGMENT_H  22

#define ROT_BUTT    11
#define ROT_ENC_1   12
#define ROT_ENC_2   13

#define STEPPER_STEP              10
#define STEPPER_DIRECTION         0
#define STEPPER_ENABLE            A5


//SETTINGS
#define SEVSEG_REFRESH            250   // Refresh rate in ms
#define SEVSEG_BRIGHTNESS         100  // 100%, full brightness
#define STEPPER_MAX_SPEED         5000
#define STEPPER_MIN_SPEED         0
#define STEPPER_ACCELERATION      30.0
#define STEP_MOTOR_STEPS_PER_TURN 200
#define STEPPER_MICROSTEP         8

SevSeg sevseg; //Instantiate a seven segment controller object

// Setup a RoraryEncoder for pins 12 and 13:
RotaryEncoder encoder(ROT_ENC_1, ROT_ENC_2);

AccelStepper stepper(1, STEPPER_STEP, STEPPER_DIRECTION);

EveryTimer cntdwn;
EveryTimer calc_dspl;

//Global variables

byte numDigits = 4;
byte digitPins[] = {SEVSEG_DIGIT_1, SEVSEG_DIGIT_2,
                    SEVSEG_DIGIT_3, SEVSEG_DIGIT_4};
byte segmentPins[] = {SEVSEG_SEGMENT_A, SEVSEG_SEGMENT_B, 
                      SEVSEG_SEGMENT_C, SEVSEG_SEGMENT_D,
                      SEVSEG_SEGMENT_E, SEVSEG_SEGMENT_F,
                      SEVSEG_SEGMENT_G, SEVSEG_SEGMENT_H};
bool resistorsOnSegments = false; // 'false' means resistors are on digit pins
byte hardwareConfig = COMMON_ANODE; // See README.md for options
bool updateWithDelays = false; // Default. Recommended
bool leadingZeros = false; // Use 'true' if you'd like to keep the leading zeros
long refresh_timer = 0;

long countdown = 0;
byte hours = 0;
byte minutes = 0;
byte seconds = 0;
int current_rpm = 0;
int new_rpm = 0;
static bool currBut = true;
byte mode = 0;

void countdown_callback() {
  if (current_rpm != 0) {   // if speed = 0 => pause countdown
    countdown--;
    if (countdown < 0) {
      countdown = 0;
    }
    recalculate_HMS();
    //Serial.println(countdown);
  }
  else {
    Serial.println("Pause");
  }
}

void calculate_display_callback() {
  // Update data to be displayed
  if (mode == 0) {          // mode 0 = display speed
    digitalWrite(SEVSEG_COLON, false);  
    sevseg.setNumber(current_rpm);
  } 
  else if (mode == 1) {     // mode 1 = display countdown
    //Blink colon each seconds
    if (countdown % 2) {
      digitalWrite(SEVSEG_COLON, true);
    }
    else {
      digitalWrite(SEVSEG_COLON, false);
    }
    //Display HH:MM or MM:SS if there is less than 1h left
    if (hours) {
      sevseg.setNumber(100*hours+minutes);
    } else {
      sevseg.setNumber(100*minutes+seconds);
    }
  }
}

void update_encoder() {
  static long currVal = 0;
  int multiplier = 10;
  
  encoder.tick();

  long newVal = encoder.getPosition();
  int dir = encoder.getDirection();
 
  if (currVal != newVal) {
    currVal = newVal;
    if (mode == 0) {    // Mode 0 : speed setting
      if (current_rpm >= 500) {
        multiplier = 100;   // default : 1 encoder step = +/- 10 step/min
                            // if speed > 500 : encoder step = +/- 100 step/min
      }
      new_rpm += multiplier * dir;
      if (new_rpm < STEPPER_MIN_SPEED) {
        new_rpm = STEPPER_MIN_SPEED;
      }
      if (new_rpm > STEPPER_MAX_SPEED) {
        new_rpm = STEPPER_MAX_SPEED;
      }
    }
    if (mode == 1) {    //mode 1 : countdown setting
      multiplier = 60;  // default : 1 encoder step = +/- 1min
                        // if countdown > 1h : encoder step = +/- 1h
      if (countdown >= 3600) {
        multiplier = 600;
      }
      countdown += multiplier * dir;
      if (countdown < 0) {
        countdown = 0;
      }
      
      // Max: 86400s = 1 day
      if (countdown >= 86400) {
        countdown = 86399;
      }
      recalculate_HMS();
    }
    Serial.print("RPM: ");
    Serial.print(new_rpm);
    Serial.print(" | Countdown: ");
    Serial.println(countdown);
  }

  //Check button for mode change
  bool newBut = digitalRead(ROT_BUTT);
  if (newBut != currBut) {
    currBut = newBut;
    if (currBut) {
      mode++;
    }
    
    if (mode == 2) {
      mode = 0;
    }
    else if (mode == 0) {
      encoder.setPosition(current_rpm);
    }
    else if (mode == 1) {
      encoder.setPosition(countdown);
    }
    Serial.print("Mode :");
    Serial.println(mode);
  }
}

void update_stepper() {

  //Update speend setting and ENABLE pin if new speed value
  if (new_rpm != current_rpm) {
    current_rpm = new_rpm;
    stepper.setSpeed(current_rpm);
    // Enable/Disable motor
    if (current_rpm == 0) {
      stepper.disableOutputs();
      Serial.println("Stepper : Off");
    }
    else {
      stepper.enableOutputs();
      Serial.println("Stepper : On");
    }
  }
  //if Countdown = 0 => pause (no move but keep speed setting)
  if (countdown != 0 and current_rpm != 0) {
    //Serial.println("Run motor !");
    stepper.runSpeed();
  }
}

void recalculate_HMS() {
  minutes = (countdown / 60) % 60;
      if (countdown >= 3600) {
        hours = countdown / 3600;  
      }
      else {
        hours = 0;
        seconds = countdown % 60;
      }
}

void setup()
{  
  Serial.begin(115200);

  pinMode(ROT_BUTT, INPUT_PULLUP);
  pinMode(SEVSEG_COLON, OUTPUT);
  
  sevseg.begin(hardwareConfig, numDigits, digitPins, segmentPins, resistorsOnSegments, updateWithDelays, leadingZeros);
  sevseg.setBrightness(SEVSEG_BRIGHTNESS);

  stepper.setEnablePin(STEPPER_ENABLE);
  stepper.disableOutputs();
  stepper.setMaxSpeed(STEPPER_MAX_SPEED);
  stepper.setAcceleration(STEPPER_ACCELERATION);
  stepper.setSpeed(0);  

  cntdwn.Every(1000, countdown_callback);
  calc_dspl.Every(SEVSEG_REFRESH, calculate_display_callback);
}

void loop() {
  update_encoder();

  //Make the motor move
  update_stepper();

  //Update countdown
  cntdwn.Update();

  //refresh the 7-segment display
  calc_dspl.Update();
  sevseg.refreshDisplay();
}


