/*******************************************************************************
Titre : OpenCR
Date : 6 février 2019
Auteur : Maxime Desmarais-Laporte
Descritpion : 

Specifications :
Baud for motors : 57600 b/s
Adress for motors : 11 and 12 and 13
*******************************************************************************/
#include <DynamixelWorkbench.h>
#include "functions.h"
#include "models.h"

#if defined(__OPENCM904__)
  #define DEVICE_NAME "3"
#elif defined(__OPENCR__)
  #define DEVICE_NAME ""
#endif   

#define STRING_BUF_NUM 64
#define MINTICK 0
#define MAXTICK 1048575
const int ACCEPTABLE_RANGE[3] = { 2, 2, 3 };

// 0 = not reverse, 1 = reverse
uint8_t X_REVERSE = 0;
uint8_t Y_REVERSE = 1;
uint8_t Z_REVERSE = 0;

const String HOMING_OFFSET = "Homing_Offset";
const String OPERATING_MODE = "Operating_Mode";
const String PRESENT_POSITION = "Present_Position";
const String GOAL_POSITION = "Goal_Position";

String cmd[STRING_BUF_NUM];

DynamixelWorkbench dxl_wb;
uint8_t get_id[16];
uint8_t scan_cnt = 0;
uint8_t ping_cnt = 0;

const char *NULL_POINTER = NULL;

bool isEmegencyState = false;

// Motors Propertys :
uint8_t idX = 11;
uint8_t idY = 12;
uint8_t idZ = 13;

// Mecanicals propertys:
const float pouleyPitch = 2;  //mm
const int nbTheets = 20;
const int resMotorTick = 4096;  //Ticks per revolution

int32_t tickFromMm = resMotorTick/(pouleyPitch*nbTheets);

// Limit Switch propertys
const int xSwitchPin = 8;
const int ySwitchPin = 9;
const int zSwitchPin = 10;
const int emergencySwitchPin = 2; // intrupt pin

// Homing variables
bool homing = false;
bool homingX = false;
bool homingY = false;
bool homingZ = false;
int homingState = 0;

const int homeOffsetX = /*28*/50*tickFromMm;
const int homeOffsetY = /*19.5*/50*tickFromMm;
const int homeOffsetZ = 2.5*tickFromMm;

// Debounce timer variables
bool isFalling = false;
long debounceTimeFalling = 250;
long lastTimeXFalling = 0;
long lastTimeYFalling = 0;
long lastTimeZFalling = 0;

bool isRising = false;
long debounceTimeRising = 250;
long lastTimeXRising = 0;
long lastTimeYRising = 0;
long lastTimeZRising = 0;

bool isXSwitchPress = false;
bool isYSwitchPress = false;
bool isZSwitchPress = false;

// eStop
bool ledX = false;
bool ledY = false;
bool ledZ = false;

// Moving variables
bool isMoving = false;
bool currentMoveDone = false;
bool hasFailed = false;
int currentMove = 0;
int nbsMovements = 0;
int32_t currentPosition = 0;
int32_t movingSpeed[] = { 75, 75 };
MovingCommand commands[3];

// Initialisation :
void setup() 
{
  // Initialisation des pins :
  pinMode(xSwitchPin, INPUT_PULLUP);
  pinMode(ySwitchPin, INPUT_PULLUP);
  pinMode(zSwitchPin, INPUT_PULLUP);
  pinMode(emergencySwitchPin, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(emergencySwitchPin), LimiteSwitch, CHANGE);
  
  Serial.begin(57600);

  //Motor Initialisation Section :
  Begin((uint32_t)57600);
  Ping(idX);
  Ping(idY);
  Ping(idZ);
  Scan();
  
  delay(1000);
  
  Torque_off(idX);
  Torque_off(idY);
  Torque_off(idZ);
  
  Write(idX, 4, OPERATING_MODE);
  Write(idY, 4, OPERATING_MODE);
  Write(idZ, 4, OPERATING_MODE);

  Write(idX, 0, HOMING_OFFSET);
  Write(idY, 0, HOMING_OFFSET);
  Write(idZ, 0, HOMING_OFFSET);

  dxl_wb.writeRegister(idX, 10, 1, &X_REVERSE, &NULL_POINTER);
  dxl_wb.writeRegister(idY, 10, 1, &Y_REVERSE, &NULL_POINTER);
  dxl_wb.writeRegister(idZ, 10, 1, &Z_REVERSE, &NULL_POINTER);

  Write(idX, movingSpeed[0], "Profile_Velocity");
  Write(idY, movingSpeed[1], "Profile_Velocity");

  Torque_on(idX);
  Torque_on(idY);
  Torque_on(idZ);

  resetMovingVariables();
}

// Main Program
void loop() 
{
  if(isFalling)
  {
    if(digitalRead(xSwitchPin))
    {
      if(millis() - lastTimeXFalling > debounceTimeFalling)
      {
        lastTimeXFalling = millis();
        isFalling = false;
        isXSwitchPress = true;

        if(homingX)
        {
          homingX = false;
          OffsetAxe(idX, homeOffsetX);
          Torque_off(idX);
          Write(idX, 4, OPERATING_MODE);
          Torque_on(idX);
          
          currentMove = 0;
          nbsMovements = 1;
          commands[currentMove]._motorId = idX;
          commands[currentMove]._goalPosition = 0;
          isMoving = true;
          Write(commands[currentMove]._motorId, commands[currentMove]._goalPosition, GOAL_POSITION); 
        }
        else
        {
          isEmegencyState = true;
          ledX = true;
        }
      }
    }
    else
    {
      lastTimeXFalling = millis();
      isXSwitchPress = false;
    }

    if(digitalRead(ySwitchPin))
    {
      if(millis() - lastTimeYFalling > debounceTimeFalling)
      {
        lastTimeYFalling = millis();
        isFalling = false;
        isYSwitchPress = true;

        if(homingY)
        {
          homingY = false;
          OffsetAxe(idY, homeOffsetY);
          Torque_off(idY);
          Write(idY, 4, OPERATING_MODE);
          Torque_on(idY);
          
          currentMove = 0;
          nbsMovements = 1;
          commands[currentMove]._motorId = idY;
          commands[currentMove]._goalPosition = 0;
          isMoving = true;
          Write(commands[currentMove]._motorId, commands[currentMove]._goalPosition, GOAL_POSITION);
        }
        else
        {
          isEmegencyState = true;
          ledY = true;
        }
      }
    }
    else
    {
      lastTimeYFalling = millis();
      isYSwitchPress = false;
    }

    if(digitalRead(zSwitchPin))
    {
      if(millis() - lastTimeZFalling > debounceTimeFalling)
      {
        lastTimeZFalling = millis();
        isFalling = false;
        isZSwitchPress = true;
        
        if(homingZ)
        {
          homingZ = false;
          OffsetAxe(idZ, homeOffsetZ);
          Torque_off(idZ);
          Write(idZ, 4, OPERATING_MODE);
          Torque_on(idZ);

          currentMove = 0;
          nbsMovements = 1;
          commands[currentMove]._motorId = idZ;
          commands[currentMove]._goalPosition = 0;
          isMoving = true;          
          Write(commands[currentMove]._motorId, commands[currentMove]._goalPosition, GOAL_POSITION);
        }
        else
          {
          isEmegencyState = true;
          ledZ = true;
        }
      }
    }
    else
    {
      lastTimeZFalling = millis();
      isZSwitchPress = false;
    }

    if(!digitalRead(xSwitchPin) && !digitalRead(ySwitchPin) && !digitalRead(zSwitchPin))
      isFalling = false;
  }

  if(isRising)
  {
    if(!digitalRead(xSwitchPin))
    {
      if(millis() - lastTimeXRising > debounceTimeRising)
      {
        lastTimeXRising = millis();
        isRising = false;
        isXSwitchPress = false;
      }
    }
    else
      lastTimeXRising = millis();

    if(!digitalRead(ySwitchPin))
    {
      if(millis() - lastTimeYRising > debounceTimeRising)
      {
        lastTimeYRising = millis();
        isRising = false;
        isYSwitchPress = false;
      }
    }
    else
      lastTimeYRising = millis();

    if(!digitalRead(zSwitchPin))
    {
      if(millis() - lastTimeZRising > debounceTimeRising)
      {
        lastTimeZRising = millis();
        isRising = false;
        isZSwitchPress = false;
      }
    }
    else
      lastTimeZRising = millis();

    if(digitalRead(xSwitchPin) && digitalRead(ySwitchPin) && digitalRead(zSwitchPin))
    {
      isRising = false;
    }
  }

  if(isMoving)
  {
    currentPosition = Read(commands[currentMove]._motorId, PRESENT_POSITION);
    if(isInRange(currentPosition, commands[currentMove]._goalPosition, commands[currentMove]._motorId))
    {
      currentMoveDone = true;
    }
  }

  if(isMoving && currentMoveDone)
  {
    if(currentMove+1 >= nbsMovements)
    {
      if(homing)
      {
        if(commands[currentMove]._motorId == idZ)
        {
          homingY = true;
          HomingAxis(idY, -100);
          homingState++;
        }
        else if(commands[currentMove]._motorId == idY)
        {
          homingX = true;
          HomingAxis(idX, -100);
          homingState++;
        }
        else if(commands[currentMove]._motorId == idX)
        {
          homingState++;
          homing = false;
          Serial.println(homingState == 3 ? "1" : "-1");
          homingState = 0;

          Write(idX, movingSpeed[0], "Profile_Velocity");
          Write(idY, movingSpeed[1], "Profile_Velocity");
        }
        resetMovingVariables();
      }
      else
      {
        resetMovingVariables();
        Serial.println("1");
      }
    }
    else
    {
      currentMoveDone = false;
      currentMove++;
      Write(commands[currentMove]._motorId, commands[currentMove]._goalPosition, GOAL_POSITION);
    }
  }
  
  if(isEmegencyState)
    setEmergency();
  
  if (!homing && !isMoving && Serial.available())
  {
    String read_string = Serial.readStringUntil('\n');
    if(!isEmegencyState)
    {
      String words[] = {"", "", ""};
      
      int start = 0;
      int wordIndex = 0;
      int statut[3] = {0, 0, 0};
      
      for(int i = 1; i < read_string.length(); i++)
      {
        if(read_string.charAt(i) == ' ')
        {
          words[wordIndex++] = read_string.substring(start, i);
          start = i+1;
        }
      }
      words[wordIndex] = read_string.substring(start, read_string.length());
      nbsMovements = wordIndex;
      Serial.println("2");

      if(words[0] == "G0")
      {
        int i;
        for(i = 1; i < wordIndex+1; i++)
        {
            if(words[i].length() > 1)
            {
                float value = words[i].substring(1, words[i].length()).toFloat();
                uint8_t idMotor = getIdFromChar((char)words[i].charAt(0));
                if(idMotor == -1)
                {
                  Serial.println("-1");
                  break;
                }
                else
                {
                  commands[i-1]._motorId = idMotor;
                  commands[i-1]._goalPosition = value*tickFromMm;
                }
            }
            else
            {
              Serial.println("-1");
              break;
            }
        }
        
        if(i == wordIndex+1)
        {
          isMoving = true;
          currentMove = 0;
          Write(commands[currentMove]._motorId, commands[currentMove]._goalPosition, GOAL_POSITION);
        }
      }
      
      else if(words[0] == "G28")
      {
        homing = true;
        homingZ = true;
        HomingAxis(idZ, -50);
      }
      else if(words[0] == "M18"){
          TorqueOffAll();
          Serial.println("1");
      }
      else if(words[0] == "M112"){
          setEmergency();

          Led(idX, 1);
          Led(idY, 1);
          Led(idZ, 1);

          Serial.println("1");
      }
      else if(words[0] == "M1")
      {
        TorqueOffAll();
        
        Write(idX, -Read(idX, PRESENT_POSITION), HOMING_OFFSET);
        Write(idY, -Read(idY, PRESENT_POSITION), HOMING_OFFSET);
        Write(idZ, -Read(idZ, PRESENT_POSITION), HOMING_OFFSET);
        
        TorqueOnAll();
        Serial.println("1");
      }
      else if(words[0] == "G90")
      {
        Serial.println("1");
      }
      else
      { 
        Serial.println("-1");
      }
    }
    else
    {
      Serial.println("-2");
    }
  }
}

uint8_t getIdFromChar(char letter)
{
    if(letter == 'X')
        return idX;
    else if(letter == 'Y')
        return idY;
    else if(letter == 'Z')
        return idZ;
    else
        return -1;
}

bool isInRange(int goal, int curPos, uint8_t id)
{
  return (abs(goal - curPos) <= ACCEPTABLE_RANGE[id-idX]);
}

void resetMovingVariables()
{
  isMoving = false;
  currentMoveDone = false;
  currentMove = 0;
  nbsMovements = 0;
  currentPosition = 0;
  hasFailed = false;

  for(int i = 0; i < 3; i++)
  {
    commands[i]._motorId = i+idX;
    commands[i]._goalPosition = Read(i+idX, PRESENT_POSITION);
  }
}

void HomingAxis(uint8_t id, int speed)
{
  Torque_off(id);
  Write(id, 0, HOMING_OFFSET);
  Torque_on(id);
  Wheel(id, speed);
}

void OffsetAxe(uint8_t id, int offset){
  int32_t posPresent = Read(id, PRESENT_POSITION);
  int32_t homePosition = - posPresent - offset;
  Torque_off(id);

  if(id == idX && X_REVERSE)
    homePosition *= -1;
  else if(id == idY && Y_REVERSE)
    homePosition *= -1;
  else if(id == idZ && Z_REVERSE)
    homePosition *= -1;
  
  Write(id, homePosition, HOMING_OFFSET);
  Torque_on(id);
}

void LimiteSwitch(){
  if(!digitalRead(emergencySwitchPin))
    isRising = true;
  else
    isFalling = true;
}

void setEmergency()
{
  isMoving = false;
  TorqueOffAll();
  Led(idX, ledX);
  Led(idY, ledY); 
  Led(idZ, ledZ);
}

void changeMode(uint8_t id)
{
  Write(id, 4, OPERATING_MODE);
  //Write(id, movingSpeed[id-idX], "Profile_Velocity");
}


//########################
// Low Levels functions :
//########################

void Begin(uint32_t baud){
  if (cmd[1] == '\0')
  cmd[1] = String("57600");
  
  dxl_wb.init(DEVICE_NAME, baud);
}

void Ping(int identification){
  get_id[ping_cnt] = identification;
  uint16_t model_number = 0;
  dxl_wb.ping(get_id[ping_cnt], &model_number, &NULL_POINTER); 
}

void Scan(){
  uint8_t range = 253;
  dxl_wb.scan(get_id, &scan_cnt, range);  
}

void Joint(uint8_t id, int32_t goal){
  dxl_wb.jointMode(id, 0, 0, &NULL_POINTER);
  dxl_wb.goalPosition(id, goal, &NULL_POINTER);
}

void Wheel(uint8_t id, int32_t goal){
  dxl_wb.wheelMode(id, 0, &NULL_POINTER);
  dxl_wb.goalVelocity(id, goal, &NULL_POINTER);
}

void Torque_on(uint8_t id){
  dxl_wb.torqueOn(id, &NULL_POINTER);
}

void Torque_off(uint8_t id){
  dxl_wb.torqueOff(id, &NULL_POINTER);
}

void Write(uint8_t id, uint32_t value, String commande){
  dxl_wb.writeRegister(id, commande.c_str(), value, &NULL_POINTER);
}

int32_t Read(uint8_t id, String commande){
  int32_t data = 0;
  dxl_wb.readRegister(id, commande.c_str(), &data, &NULL_POINTER);
  return data;
}

void Led(uint8_t id, bool state){
  Write(id, (uint32_t)state, "LED");
}

void TorqueOffAll(){
      Torque_off(idX);
      Torque_off(idY);
      Torque_off(idZ);
}

void TorqueOnAll(){
      Torque_on(idX);
      Torque_on(idY);
      Torque_on(idZ);
}
