2022-03-14 20:11:07 -06:00
|
|
|
// Copyright (c) FIRST and other WPILib contributors.
|
|
|
|
|
// Open Source Software; you can modify and/or share it under the terms of
|
|
|
|
|
// the WPILib BSD license file in the root directory of this project.
|
|
|
|
|
|
|
|
|
|
package frc4388.robot.subsystems;
|
|
|
|
|
|
|
|
|
|
import java.util.HashMap;
|
|
|
|
|
|
2022-03-18 12:05:29 -06:00
|
|
|
import com.ctre.phoenix.motorcontrol.LimitSwitchNormal;
|
|
|
|
|
import com.ctre.phoenix.motorcontrol.LimitSwitchSource;
|
2022-03-14 20:11:07 -06:00
|
|
|
import com.ctre.phoenix.motorcontrol.NeutralMode;
|
2022-03-16 14:41:27 -06:00
|
|
|
import com.ctre.phoenix.motorcontrol.TalonFXControlMode;
|
2022-03-14 20:11:07 -06:00
|
|
|
import com.ctre.phoenix.motorcontrol.can.TalonFX;
|
|
|
|
|
import com.ctre.phoenix.motorcontrol.can.WPI_TalonFX;
|
|
|
|
|
import com.ctre.phoenix.sensors.WPI_Pigeon2;
|
|
|
|
|
|
2022-03-16 14:41:27 -06:00
|
|
|
import org.opencv.core.Point;
|
|
|
|
|
|
2022-03-14 20:11:07 -06:00
|
|
|
import edu.wpi.first.wpilibj.motorcontrol.Talon;
|
|
|
|
|
import edu.wpi.first.wpilibj2.command.SubsystemBase;
|
2022-03-16 14:41:27 -06:00
|
|
|
import frc4388.robot.Constants;
|
2022-03-14 20:11:07 -06:00
|
|
|
import frc4388.robot.Constants.ClimberConstants;
|
|
|
|
|
|
|
|
|
|
public class ClimberRewrite extends SubsystemBase {
|
2022-03-16 14:41:27 -06:00
|
|
|
private static double[][] shoulderFeedMap;
|
|
|
|
|
private static double[][] elbowFeedMap;
|
2022-03-14 20:11:07 -06:00
|
|
|
|
|
|
|
|
public static void configureFeedMaps() {
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private WPI_TalonFX m_shoulder;
|
|
|
|
|
private WPI_TalonFX m_elbow;
|
|
|
|
|
private WPI_Pigeon2 m_gyro;
|
|
|
|
|
|
2022-03-16 14:41:27 -06:00
|
|
|
private Point tPoint;
|
2022-03-14 20:11:07 -06:00
|
|
|
|
|
|
|
|
private boolean groundRelative;
|
|
|
|
|
|
|
|
|
|
/** Creates a new ClimberRewrite. */
|
2022-03-16 14:41:27 -06:00
|
|
|
public ClimberRewrite(WPI_TalonFX shoulder, WPI_TalonFX elbow, WPI_Pigeon2 gyro, boolean _groundRelative) {
|
2022-03-14 20:11:07 -06:00
|
|
|
m_shoulder = shoulder;
|
|
|
|
|
m_shoulder.configFactoryDefault();
|
|
|
|
|
m_shoulder.setNeutralMode(NeutralMode.Brake);
|
|
|
|
|
|
|
|
|
|
m_elbow = elbow;
|
|
|
|
|
m_elbow.configFactoryDefault();
|
|
|
|
|
m_elbow.setNeutralMode(NeutralMode.Brake);
|
|
|
|
|
|
2022-03-18 12:05:29 -06:00
|
|
|
// setClimberGains();
|
2022-03-14 20:11:07 -06:00
|
|
|
|
2022-03-16 14:41:27 -06:00
|
|
|
// shoulderStartPosition = m_shoulder.getSelectedSensorPosition();
|
|
|
|
|
// elbowStartPosition = m_elbow.getSelectedSensorPosition();
|
|
|
|
|
m_shoulder.setSelectedSensorPosition(((ClimberConstants.SHOULDER_RESTING_ANGLE * (Constants.TICKS_PER_ROTATION_FX/2.d)) / Math.PI) * ClimberConstants.SHOULDER_GB_RATIO);
|
|
|
|
|
m_elbow.setSelectedSensorPosition(((ClimberConstants.ELBOW_RESTING_ANGLE * (Constants.TICKS_PER_ROTATION_FX/2.d)) / Math.PI) * ClimberConstants.SHOULDER_GB_RATIO);
|
2022-03-14 20:11:07 -06:00
|
|
|
|
|
|
|
|
m_elbow.configForwardSoftLimitThreshold(ClimberConstants.ELBOW_SOFT_LIMIT_FORWARD);
|
|
|
|
|
m_elbow.configForwardSoftLimitEnable(false);
|
|
|
|
|
m_elbow.configReverseSoftLimitThreshold(ClimberConstants.ELBOW_SOFT_LIMIT_REVERSE);
|
|
|
|
|
m_elbow.configReverseSoftLimitEnable(false);
|
|
|
|
|
|
|
|
|
|
m_shoulder.configForwardSoftLimitThreshold(ClimberConstants.SHOULDER_SOFT_LIMIT_FORWARD);
|
|
|
|
|
m_shoulder.configForwardSoftLimitEnable(false);
|
|
|
|
|
m_shoulder.configReverseSoftLimitThreshold(ClimberConstants.SHOULDER_SOFT_LIMIT_REVERSE);
|
|
|
|
|
m_shoulder.configReverseSoftLimitEnable(false);
|
|
|
|
|
|
2022-03-18 12:05:29 -06:00
|
|
|
m_shoulder.configForwardLimitSwitchSource(LimitSwitchSource.FeedbackConnector, LimitSwitchNormal.NormallyOpen);
|
|
|
|
|
m_elbow.configReverseLimitSwitchSource(LimitSwitchSource.FeedbackConnector, LimitSwitchNormal.NormallyOpen);
|
|
|
|
|
m_shoulder.configReverseLimitSwitchSource(LimitSwitchSource.FeedbackConnector, LimitSwitchNormal.NormallyOpen);
|
|
|
|
|
|
|
|
|
|
m_shoulder.overrideLimitSwitchesEnable(true);
|
|
|
|
|
m_elbow.overrideLimitSwitchesEnable(true);
|
|
|
|
|
|
2022-03-16 14:41:27 -06:00
|
|
|
tPoint = new Point();
|
|
|
|
|
|
2022-03-14 20:11:07 -06:00
|
|
|
if(groundRelative)
|
|
|
|
|
m_gyro = gyro;
|
|
|
|
|
|
2022-03-16 14:41:27 -06:00
|
|
|
groundRelative = _groundRelative;
|
2022-03-14 20:11:07 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void setClimberGains() {
|
|
|
|
|
m_shoulder.selectProfileSlot(ClimberConstants.SHOULDER_SLOT_IDX, ClimberConstants.SHOULDER_PID_LOOP_IDX);
|
|
|
|
|
m_shoulder.config_kF(ClimberConstants.SHOULDER_SLOT_IDX, ClimberConstants.SHOULDER_GAINS.kF, ClimberConstants.CLIMBER_TIMEOUT_MS);
|
|
|
|
|
m_shoulder.config_kP(ClimberConstants.SHOULDER_SLOT_IDX, ClimberConstants.SHOULDER_GAINS.kP, ClimberConstants.CLIMBER_TIMEOUT_MS);
|
|
|
|
|
m_shoulder.config_kI(ClimberConstants.SHOULDER_SLOT_IDX, ClimberConstants.SHOULDER_GAINS.kI, ClimberConstants.CLIMBER_TIMEOUT_MS);
|
|
|
|
|
m_shoulder.config_kD(ClimberConstants.SHOULDER_SLOT_IDX, ClimberConstants.SHOULDER_GAINS.kD, ClimberConstants.CLIMBER_TIMEOUT_MS);
|
|
|
|
|
|
|
|
|
|
m_elbow.selectProfileSlot(ClimberConstants.ELBOW_SLOT_IDX, ClimberConstants.ELBOW_PID_LOOP_IDX);
|
|
|
|
|
m_elbow.config_kF(ClimberConstants.ELBOW_SLOT_IDX, ClimberConstants.ELBOW_GAINS.kF, ClimberConstants.CLIMBER_TIMEOUT_MS);
|
|
|
|
|
m_elbow.config_kP(ClimberConstants.ELBOW_SLOT_IDX, ClimberConstants.ELBOW_GAINS.kP, ClimberConstants.CLIMBER_TIMEOUT_MS);
|
|
|
|
|
m_elbow.config_kI(ClimberConstants.ELBOW_SLOT_IDX, ClimberConstants.ELBOW_GAINS.kI, ClimberConstants.CLIMBER_TIMEOUT_MS);
|
|
|
|
|
m_elbow.config_kD(ClimberConstants.ELBOW_SLOT_IDX, ClimberConstants.ELBOW_GAINS.kD, ClimberConstants.CLIMBER_TIMEOUT_MS);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void setClimberFeedForward(double shoulderF, double elbowF) {
|
|
|
|
|
m_shoulder.selectProfileSlot(ClimberConstants.SHOULDER_SLOT_IDX, ClimberConstants.SHOULDER_PID_LOOP_IDX);
|
|
|
|
|
m_shoulder.config_kF(ClimberConstants.SHOULDER_SLOT_IDX, shoulderF, ClimberConstants.CLIMBER_TIMEOUT_MS);
|
|
|
|
|
|
|
|
|
|
m_elbow.selectProfileSlot(ClimberConstants.ELBOW_SLOT_IDX, ClimberConstants.ELBOW_PID_LOOP_IDX);
|
|
|
|
|
m_elbow.config_kF(ClimberConstants.ELBOW_SLOT_IDX, elbowF, ClimberConstants.CLIMBER_TIMEOUT_MS);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void compensateFeedForArmAngles() {
|
2022-03-16 14:41:27 -06:00
|
|
|
double shoulderPosition = m_shoulder.getSelectedSensorPosition();
|
|
|
|
|
double elbowPosition = m_elbow.getSelectedSensorPosition();
|
|
|
|
|
|
|
|
|
|
double shoulderFeed = 0;
|
|
|
|
|
double elbowFeed = 0;
|
|
|
|
|
|
|
|
|
|
for(int i = 1; i < shoulderFeedMap.length; i++) {
|
|
|
|
|
if(shoulderFeedMap[i][0] > shoulderPosition) {
|
|
|
|
|
double percentDifference = (shoulderPosition - shoulderFeedMap[i-1][0]) / (shoulderFeedMap[i][0] - shoulderFeedMap[i-1][0]);
|
|
|
|
|
double feedDifference = shoulderFeedMap[i][1] - shoulderFeedMap[i-1][1];
|
|
|
|
|
shoulderFeed = (percentDifference * feedDifference) + shoulderFeedMap[i-1][1];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for(int i = 1; i < elbowFeedMap.length; i++) {
|
|
|
|
|
if(elbowFeedMap[i][0] > elbowPosition) {
|
|
|
|
|
double percentDifference = (elbowPosition - elbowFeedMap[i-1][0]) / (elbowFeedMap[i][0] - elbowFeedMap[i-1][0]);
|
|
|
|
|
double feedDifference = elbowFeedMap[i][1] - elbowFeedMap[i-1][1];
|
|
|
|
|
elbowFeed = (percentDifference * feedDifference) + elbowFeedMap[i-1][1];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setClimberFeedForward(shoulderFeed, elbowFeed);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void setMotors(double shoulderOutput, double elbowOutput) {
|
|
|
|
|
m_shoulder.set(shoulderOutput);
|
|
|
|
|
m_elbow.set(elbowOutput);
|
|
|
|
|
}
|
|
|
|
|
|
2022-03-16 15:46:39 -06:00
|
|
|
public double[] getJointAngles() {
|
|
|
|
|
return new double[] {
|
|
|
|
|
(m_shoulder.getSelectedSensorPosition() * Math.PI) / (Constants.TICKS_PER_ROTATION_FX/2.d) / ClimberConstants.SHOULDER_GB_RATIO,
|
|
|
|
|
(m_elbow.getSelectedSensorPosition() * Math.PI) / (Constants.TICKS_PER_ROTATION_FX/2.d) / ClimberConstants.ELBOW_GB_RATIO
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2022-03-16 14:41:27 -06:00
|
|
|
public void setJointAngles(double[] angles) {
|
|
|
|
|
System.out.println(angles);
|
|
|
|
|
setJointAngles(angles[0], angles[1]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void setJointAngles(double shoulderAngle, double elbowAngle) {
|
|
|
|
|
shoulderAngle = shoulderAngle < ClimberConstants.SHOULDER_RESTING_ANGLE ? ClimberConstants.SHOULDER_RESTING_ANGLE : shoulderAngle;
|
|
|
|
|
elbowAngle = elbowAngle < ClimberConstants.ELBOW_RESTING_ANGLE ? ClimberConstants.ELBOW_RESTING_ANGLE : elbowAngle;
|
|
|
|
|
|
|
|
|
|
shoulderAngle = shoulderAngle > ClimberConstants.SHOULDER_MAX_ANGLE ? ClimberConstants.SHOULDER_MAX_ANGLE : shoulderAngle;
|
|
|
|
|
elbowAngle = elbowAngle > ClimberConstants.ELBOW_MAX_ANGLE ? ClimberConstants.ELBOW_MAX_ANGLE : elbowAngle;
|
|
|
|
|
|
|
|
|
|
// Convert radians to ticks
|
|
|
|
|
System.out.println("angles: " + shoulderAngle + ", " + elbowAngle);
|
|
|
|
|
|
|
|
|
|
double shoulderPosition = (shoulderAngle * (Constants.TICKS_PER_ROTATION_FX/2.d)) / Math.PI;
|
|
|
|
|
double elbowPosition = (elbowAngle * (Constants.TICKS_PER_ROTATION_FX/2.d)) / Math.PI;
|
2022-03-14 20:11:07 -06:00
|
|
|
|
2022-03-16 14:41:27 -06:00
|
|
|
shoulderPosition *= ClimberConstants.SHOULDER_GB_RATIO;
|
|
|
|
|
elbowPosition *= ClimberConstants.ELBOW_GB_RATIO;
|
|
|
|
|
|
|
|
|
|
// shoulderPosition += m_shoulderOffset;
|
|
|
|
|
// elbowPosition += m_elbowOffset;
|
|
|
|
|
|
|
|
|
|
m_shoulder.set(TalonFXControlMode.Position, shoulderPosition);
|
|
|
|
|
m_elbow.set(TalonFXControlMode.Position, elbowPosition);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void controlWithInput(double xInput, double yInput) {
|
2022-03-16 15:46:39 -06:00
|
|
|
tPoint.x += xInput * ClimberConstants.MOVE_SPEED * .02;
|
|
|
|
|
tPoint.y += yInput * ClimberConstants.MOVE_SPEED * .02;
|
2022-03-14 20:11:07 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void periodic() {
|
2022-03-18 12:21:47 -06:00
|
|
|
// double[] jointAngles = getTargetJointAngles(tPoint, 0.d);
|
|
|
|
|
// setJointAngles(jointAngles);
|
2022-03-16 14:41:27 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Gets joint angles for climber arm
|
|
|
|
|
* {@code targetPoint.x} and {@code targetPoint.y} are set in the xy plane of the climber, accounting for the
|
|
|
|
|
* rotation of the robot. Both parameters should be in cm.
|
|
|
|
|
* Does not set the motors automatically
|
|
|
|
|
* <p><p>
|
|
|
|
|
* IK resource: https://devforum.roblox.com/t/2-joint-2-limb-inverse-kinematics/252399
|
|
|
|
|
*
|
|
|
|
|
* @param targetPoint Target xy point for the climber arm to go to
|
|
|
|
|
* @param tiltAngle The tilt of the robot
|
2022-03-16 15:46:39 -06:00
|
|
|
* @return [shoulderAngle, elbowAngle] in radians */
|
|
|
|
|
public static double[] getTargetJointAngles(Point targetPoint, double tiltAngle) {
|
2022-03-16 14:41:27 -06:00
|
|
|
double [] angles = new double[2];
|
|
|
|
|
|
|
|
|
|
double mag = Math.hypot(targetPoint.x, targetPoint.y);
|
|
|
|
|
if(mag >= ClimberConstants.MAX_ARM_LENGTH) {
|
|
|
|
|
targetPoint.x = (targetPoint.x / mag) * ClimberConstants.MAX_ARM_LENGTH;
|
|
|
|
|
targetPoint.y = (targetPoint.y / mag) * ClimberConstants.MAX_ARM_LENGTH;
|
|
|
|
|
mag = ClimberConstants.MAX_ARM_LENGTH;
|
|
|
|
|
} else if(mag < ClimberConstants.MIN_ARM_LENGTH && mag != 0) {
|
|
|
|
|
targetPoint.x = (targetPoint.x / mag) * ClimberConstants.MIN_ARM_LENGTH;
|
|
|
|
|
targetPoint.y = (targetPoint.y / mag) * ClimberConstants.MIN_ARM_LENGTH;
|
|
|
|
|
mag = ClimberConstants.MIN_ARM_LENGTH;
|
|
|
|
|
} else if(mag < ClimberConstants.MIN_ARM_LENGTH) {
|
|
|
|
|
targetPoint.x = ClimberConstants.MIN_ARM_LENGTH;
|
|
|
|
|
targetPoint.y = 0.d;
|
|
|
|
|
mag = ClimberConstants.MIN_ARM_LENGTH;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// The angle to the target point
|
|
|
|
|
double theta;
|
|
|
|
|
if(targetPoint.x == 0.d) {
|
|
|
|
|
theta = Math.PI/2.d; // TODO rename variable
|
|
|
|
|
} else {
|
|
|
|
|
theta = Math.atan(targetPoint.y / targetPoint.x);
|
|
|
|
|
}
|
|
|
|
|
theta += tiltAngle;
|
|
|
|
|
|
|
|
|
|
if(targetPoint.x < 0.d)
|
|
|
|
|
theta += Math.PI;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Correct target position for tilt
|
|
|
|
|
targetPoint.x = Math.cos(theta) * mag;
|
|
|
|
|
targetPoint.y = Math.sin(theta) * mag;
|
|
|
|
|
|
|
|
|
|
// Law and Order: Cosines edition
|
|
|
|
|
double shoulderAngle;
|
|
|
|
|
if(mag == 0) {
|
|
|
|
|
shoulderAngle = 0;
|
|
|
|
|
} else {
|
|
|
|
|
shoulderAngle = Math.acos((Math.pow(ClimberConstants.LOWER_ARM_LENGTH, 2) + Math.pow(mag, 2) - Math.pow(ClimberConstants.UPPER_ARM_LENGTH, 2)) /
|
|
|
|
|
(2.d * ClimberConstants.LOWER_ARM_LENGTH * mag));
|
|
|
|
|
}
|
|
|
|
|
shoulderAngle = theta - shoulderAngle;
|
|
|
|
|
|
|
|
|
|
double elbowAngle = Math.acos((Math.pow(ClimberConstants.LOWER_ARM_LENGTH, 2) + Math.pow(ClimberConstants.UPPER_ARM_LENGTH, 2) - Math.pow(mag, 2)) /
|
|
|
|
|
(2.d * ClimberConstants.LOWER_ARM_LENGTH * ClimberConstants.UPPER_ARM_LENGTH));
|
|
|
|
|
//elbowAngle = Math.PI - elbowAngle;
|
|
|
|
|
// System.out.println("sa1: " + shoulderAngle);
|
|
|
|
|
// System.out.println("ea1: " + elbowAngle);
|
|
|
|
|
|
|
|
|
|
// If the climber is resting on the robot base, rotate the upper arm in the direction of the target
|
|
|
|
|
if(shoulderAngle <= ClimberConstants.SHOULDER_RESTING_ANGLE) {
|
|
|
|
|
shoulderAngle = ClimberConstants.SHOULDER_RESTING_ANGLE;
|
|
|
|
|
double xDiff = targetPoint.x - ClimberConstants.LOWER_ARM_LENGTH;
|
|
|
|
|
// System.out.println("xDiff: " + xDiff);
|
|
|
|
|
|
|
|
|
|
elbowAngle = Math.atan(-targetPoint.y / xDiff);
|
|
|
|
|
// System.out.println("ea2: " + elbowAngle);
|
|
|
|
|
if(elbowAngle < 0) {
|
|
|
|
|
elbowAngle = Math.PI - Math.abs(elbowAngle);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(elbowAngle < ClimberConstants.ELBOW_RESTING_ANGLE)
|
|
|
|
|
elbowAngle = ClimberConstants.ELBOW_RESTING_ANGLE;
|
|
|
|
|
// System.out.println("ea3: " + elbowAngle);
|
|
|
|
|
|
|
|
|
|
// Deal with negative wraparound
|
|
|
|
|
// if(xDiff >= 0.d)
|
|
|
|
|
// elbowAngle += Math.PI;
|
|
|
|
|
// System.out.println("ea4: " + elbowAngle);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
angles[0] = shoulderAngle;
|
|
|
|
|
angles[1] = elbowAngle;
|
|
|
|
|
return angles;
|
2022-03-14 20:11:07 -06:00
|
|
|
}
|
2022-03-16 14:49:53 -06:00
|
|
|
|
2022-03-16 15:46:39 -06:00
|
|
|
public static Point getClimberPosition(double shoulderAngle, double elbowAngle) {
|
|
|
|
|
Point climberPoint = new Point(0, 0);
|
|
|
|
|
|
|
|
|
|
climberPoint.x += Math.sin(shoulderAngle);
|
|
|
|
|
climberPoint.y += Math.cos(shoulderAngle);
|
|
|
|
|
|
|
|
|
|
climberPoint.x -= Math.sin(elbowAngle - shoulderAngle);
|
|
|
|
|
climberPoint.y += Math.cos(elbowAngle - shoulderAngle);
|
|
|
|
|
|
|
|
|
|
return climberPoint;
|
2022-03-16 14:49:53 -06:00
|
|
|
}
|
|
|
|
|
|
2022-03-16 15:46:39 -06:00
|
|
|
public static Point getClimberPosition(double[] jointAngles) {
|
|
|
|
|
return getClimberPosition(jointAngles[0], jointAngles[1]);
|
|
|
|
|
}
|
2022-03-14 20:11:07 -06:00
|
|
|
}
|