#include <MPU6050_light.h>
#include <Wire.h>
#include <Servo.h>

// ARM LENGTH CONSTANTS
#define SHOULDER_LENGTH 5 // shoulder/short length (screw to screw)
#define FOREARM_LENGTH 8 // long length + 1/2 gripper (screw to screw)

// GRIPPER POSITION CONSTRAINTS
#define MAX_RADIUS sqrt(SHOULDER_LENGTH * SHOULDER_LENGTH + FOREARM_LENGTH * FOREARM_LENGTH)
#define MIN_X FOREARM_LENGTH-SHOULDER_LENGTH
#define MAX_X MAX_RADIUS
#define MIN_Y FOREARM_LENGTH
#define MAX_Y MAX_RADIUS

// MOVEMENT THRESHOLD AND SCALE
#define MOVEMENT_SCALE 50
#define ANGLE_THRESHOLD 35
#define CART_SPEED 20

// PIN DEFINITIONS
#define SLIDER_POT A3
#define RIGHT_BUTTON 2
#define CART_SERVO 5
#define LEFT_BUTTON 4
#define SHOULDER_SERVO 9
#define ELBOW_SERVO 10
#define GRIPPER_SERVO 11
#define RESET_BUTTON 12
#define CALIBRATING_LED 13

// OBJECTS
MPU6050 mpu(Wire);
Servo shoulderServo;
Servo elbowServo;
Servo gripperServo;
Servo cartServo;

// GLOBAL VARIABLES
float gripperXPos = (MIN_X+MAX_X)/2;
float gripperYPos = (MIN_Y+MAX_Y)/2;
float angX = 0;
float angY = 0;
float angZ = 0;
float shoulderAngle;
float elbowAngle;
int gripperRot = 0;
int slidePos = 0;
int cartSpeed = 0;
bool goRight = false;
bool goLeft = false;

unsigned long timer = 0;

void setup() {
  // GENERAL SETUP (pinModes and attachments)
  //Serial.begin(9600);
  pinMode(CALIBRATING_LED, OUTPUT);
  pinMode(RESET_BUTTON, INPUT_PULLUP);
  pinMode(RIGHT_BUTTON, INPUT_PULLUP);
  pinMode(LEFT_BUTTON, INPUT_PULLUP);
  pinMode(SLIDER_POT, INPUT);
  shoulderServo.attach(SHOULDER_SERVO);
  elbowServo.attach(ELBOW_SERVO);
  gripperServo.attach(GRIPPER_SERVO);

  // CART SETUP
  // Write before attach to define starting state (stopped)
  // 0 - turn one way, 180 - other way, 90 - stop
  cartServo.write(90);
  cartServo.attach(CART_SERVO);
  
  // SENSOR SETUP
  Wire.begin();
  mpu.begin();
  calibrate();
}

void loop() {
  checkReset();
  readSensors();
  determineMovement();
  controlArm();
  controlGripper();
  controlCart();
  //printData();
}

// CALL ON CALIBRATE METHOD FROM MPU LIBRARY
void calibrate() {
  digitalWrite(CALIBRATING_LED, HIGH);
  mpu.calcGyroOffsets();
  digitalWrite(CALIBRATING_LED, LOW);
}

// CALL ON CALIBRATE IF RESET BUTTON PRESSED
void checkReset() {
  if(!digitalRead(RESET_BUTTON)) {
    cartServo.write(90);
    gripperXPos = (MIN_X+MAX_X)/2;
    gripperYPos = (MIN_Y+MAX_Y)/2;
    cartSpeed = 0; // 0 for stopped, CART_SPEED for right, -CART_SPEED for left
    calibrate();
  }
}

// READ ANGLES FROM MPU6050, ADJUST SIGN DEPENDING ON MPU ORIENTATION
// READ SLIDE POTENTIOMETER FOR GRIPPER
// READ BUTTONS TO DETERMINE WHERE TO MOVE CART
void readSensors() {
  mpu.update();
  angX = mpu.getAngleX();
  angY = mpu.getAngleY();
  angZ = -mpu.getAngleZ();

  slidePos = analogRead(SLIDER_POT);
  goRight = !digitalRead(RIGHT_BUTTON);
  goLeft = !digitalRead(LEFT_BUTTON);
}

// CALCULATE XY-COORDINATES BASED ON CALCULATED ANGLES
// MAP SLIDER POTENTIOMETER RAW VALUE
void determineMovement() {
  gripperXPos = constrain(gripperXPos + floor(angX / ANGLE_THRESHOLD) / MOVEMENT_SCALE, MIN_X, MAX_X);
  gripperYPos = constrain(gripperYPos + floor(angY / ANGLE_THRESHOLD) / MOVEMENT_SCALE, MIN_Y, MAX_Y);

  gripperRot = constrain(map(slidePos, 100, 750, 0, 120), 0, 120);
}

// SET CART SPEED BASED ON MOVEMENT BUTTON PRESSED
void controlCart() {
  if(goRight) {
    cartSpeed = 90 + CART_SPEED;
    cartServo.write(cartSpeed);
  }
  else if(goLeft) {
    cartSpeed = 90 - CART_SPEED;
    cartServo.write(cartSpeed);
  }
  else {
    cartSpeed = 90;
    cartServo.write(cartSpeed);
  }
  delay(10);
  return;
}

// WRITE THE CALCULATED ANGLE TO THE GRIPPER SERVO
void controlGripper() {
  gripperServo.write(gripperRot);
  delay(10);
  return;
}

// PRINT CURRENT EXPECTED CART SPEED
void printData() {
  if((millis()-timer)>10) // print data every 10ms
  {
    Serial.print("cart speed: ");
    Serial.print(cartSpeed);
    Serial.println();
    timer = millis();
  }
}

// CALCULATE THE INVERSE KINEMATICS BASED ON XY-COORDINATES AND WHICH SERVO
float inverseKinematics(float x, float y, byte servo_num) {
  double cb = (-sq(SHOULDER_LENGTH) - sq(FOREARM_LENGTH) + sq(x) + sq(y)) / (2 * (SHOULDER_LENGTH) * (FOREARM_LENGTH));
  double sbin = 1 - (sq(cb));
  double sb = sqrt(sbin);
  if (servo_num == SHOULDER_SERVO) {
    long double ca = ((x*SHOULDER_LENGTH)+(x*FOREARM_LENGTH*cb)+(y*FOREARM_LENGTH*sb))/(sq(x)+sq(y));
    double sain = 1 - (sq(ca));
    double sa = sqrt(sain);
    return (atan2(sa,ca)) * (180/PI);
  }
  else if (servo_num == ELBOW_SERVO) { 
    return (atan2(sb,cb)) * (180/PI);
  }
}

// MOVE SERVO POSITIONS TO CALCULATED ANGLES
void controlArm() {
  shoulderAngle = constrain(180-2*(inverseKinematics(gripperXPos, gripperYPos, SHOULDER_SERVO)),0,180);
  elbowAngle = 180-constrain(inverseKinematics(gripperXPos, gripperYPos, ELBOW_SERVO),0,180);
  shoulderServo.write(shoulderAngle);
  elbowServo.write(elbowAngle);
  return;
}
uno:A5.2
uno:A4.2
uno:AREF
uno:GND.1
uno:13
uno:12
uno:11
uno:10
uno:9
uno:8
uno:7
uno:6
uno:5
uno:4
uno:3
uno:2
uno:1
uno:0
uno:IOREF
uno:RESET
uno:3.3V
uno:5V
uno:GND.2
uno:GND.3
uno:VIN
uno:A0
uno:A1
uno:A2
uno:A3
uno:A4
uno:A5
servo1:GND
servo1:V+
servo1:PWM
servo2:GND
servo2:V+
servo2:PWM
servo3:GND
servo3:V+
servo3:PWM
servo4:GND
servo4:V+
servo4:PWM
btn1:1.l
btn1:2.l
btn1:1.r
btn1:2.r
btn2:1.l
btn2:2.l
btn2:1.r
btn2:2.r
btn3:1.l
btn3:2.l
btn3:1.r
btn3:2.r
pot1:VCC
pot1:SIG
pot1:GND
imu1:INT
imu1:AD0
imu1:XCL
imu1:XDA
imu1:SDA
imu1:SCL
imu1:GND
imu1:VCC