Merge branch 'develop'

This commit is contained in:
Keenan D. Buckley
2019-02-18 15:56:24 -07:00
85 changed files with 8613 additions and 0 deletions
Binary file not shown.
Binary file not shown.
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,2 @@
#Sat Jan 19 11:45:29 MST 2019
gradle.version=5.0
View File
+6
View File
@@ -0,0 +1,6 @@
{
"currentLanguage": "java",
"enableCppIntellisense": false,
"projectYear": "none",
"teamNumber": 4388
}
+62
View File
@@ -0,0 +1,62 @@
plugins {
id "java"
id "edu.wpi.first.GradleRIO" version "2019.2.1"
}
def ROBOT_MAIN_CLASS = "org.usfirst.frc4388.robot.Main"
// Define my targets (RoboRIO) and artifacts (deployable files)
// This is added by GradleRIO's backing project EmbeddedTools.
deploy {
targets {
roboRIO("roborio") {
// Team number is loaded either from the .wpilib/wpilib_preferences.json
// or from command line. If not found an exception will be thrown.
// You can use getTeamOrDefault(team) instead of getTeamNumber if you
// want to store a team number in this file.
team = frc.getTeamNumber()
}
}
artifacts {
frcJavaArtifact('frcJava') {
targets << "roborio"
// Debug can be overridden by command line, for use with VSCode
debug = frc.getDebugOrDefault(false)
}
// Built in artifact to deploy arbitrary files to the roboRIO.
fileTreeArtifact('frcStaticFileDeploy') {
// The directory below is the local directory to deploy
files = fileTree(dir: 'src/main/deploy')
// Deploy to RoboRIO target, into /home/lvuser/deploy
targets << "roborio"
directory = '/home/lvuser/deploy'
}
}
}
// Set this to true to enable desktop support.
def includeDesktopSupport = true
// Maven central needed for JUnit
repositories {
mavenCentral()
}
// Defining my dependencies. In this case, WPILib (+ friends), and vendor libraries.
// Also defines JUnit 4.
dependencies {
compile wpi.deps.wpilib()
compile wpi.deps.vendor.java()
nativeZip wpi.deps.vendor.jni(wpi.platforms.roborio)
nativeDesktopZip wpi.deps.vendor.jni(wpi.platforms.desktop)
testCompile 'junit:junit:4.12'
//compile pathfinder()
}
// Setting up my Jar File. In this case, adding all libraries into the main jar ('fat jar')
// in order to make them all available at runtime. Also adding the manifest so WPILib
// knows where to look for our Robot Class.
jar {
from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
manifest edu.wpi.first.gradlerio.GradleRIOPlugin.javaManifest(ROBOT_MAIN_CLASS)
}
@@ -0,0 +1,16 @@
package org.usfirst.frc4388.controller;
public interface IHandController {
public double getLeftXAxis();
public double getLeftYAxis();
public double getRightXAxis();
public double getRightYAxis();
public double getLeftTriggerAxis();
public double getRightTriggerAxis();
}
@@ -0,0 +1,72 @@
package org.usfirst.frc4388.robot;
/**
* A list of constants used by the rest of the robot code. This include physics
* constants as well as constants determined through calibrations.
*/
public class Constants {
public static double kLooperDt = 0.01;
public static double kDriveWheelDiameterInches = 6.04;
public static double kTrackLengthInches = 10;
public static double kTrackWidthInches = 26.5;
public static double kTrackEffectiveDiameter = (kTrackWidthInches * kTrackWidthInches + kTrackLengthInches * kTrackLengthInches) / kTrackWidthInches;
public static double kTrackScrubFactor = 0.75;
// Drive constants
public static double kDriveLowGearMaxSpeedInchesPerSec = 12.0 * 5.0;
public static double kDriveStraightBasicMaxSpeedInchesPerSec = 72.0;
public static double kDriveStraightBasicMinSpeedInchesPerSec = 5.0;
public static double kDriveStraightBasicYawErrorDivisor = 20.0; // steer parameter will be -yawError divided by this
public static double kDriveStraightBasicMaxSteerMagnitude = 0.8; // keep absolute value of steer parameter below this
public static double kDriveTurnBasicTankMotorOutput = 0.4;
public static double kDriveTurnBasicSingleMotorOutput = 0.15;
public static double kElevatorWheelDiameterInches = 1;
// Encoders
public static int kDriveEncoderTicksPerRev = 4096;
public static double kDriveEncoderTicksPerInch = (double)kDriveEncoderTicksPerRev / (kDriveWheelDiameterInches * Math.PI);
// Elevator
public static int kElevatorEncoderTickesPerRev = 256;
public static double kElevatorInchesOfTravelPerRev = 3.75;
public static double kElevatorEncoderTicksPerInch = 126.36;
public static double kElevatorBasicPercentOutputUp = -0.85;
public static double kElevatorBasicPercentOutputDown =.7;
// CONTROL LOOP GAINS
// PID gains for drive velocity loop (LOW GEAR)
// Units: error is 4096 counts/rev. Max output is +/- 1023 units.
public static double kDriveVelocityKp = 1.0;
public static double kDriveVelocityKi = 0.0;
public static double kDriveVelocityKd = 6.0;
public static double kDriveVelocityKf = 0.5;
public static int kDriveVelocityIZone = 0;
public static double kDriveVelocityRampRate = 0.0;
public static int kDriveVelocityAllowableError = 0;
// PID gains for drive base lock loop
// Units: error is 4096 counts/rev. Max output is +/- 1023 units.
public static double kDriveBaseLockKp = 0.5;
public static double kDriveBaseLockKi = 0;
public static double kDriveBaseLockKd = 0;
public static double kDriveBaseLockKf = 0;
public static int kDriveBaseLockIZone = 0;
public static double kDriveBaseLockRampRate = 0;
public static int kDriveBaseLockAllowableError = 10;
// PID gains for constant heading velocity control
// Units: Error is degrees. Output is inches/second difference to
// left/right.
public static double kDriveHeadingVelocityKp = 4.0; // 6.0;
public static double kDriveHeadingVelocityKi = 0.0;
public static double kDriveHeadingVelocityKd = 50.0;
// Path following constants
public static double kPathFollowingLookahead = 24.0; // inches
public static double kPathFollowingMaxVel = 120.0; // inches/sec
public static double kPathFollowingMaxAccel = 80.0; // inches/sec^2
}
@@ -0,0 +1,123 @@
package org.usfirst.frc4388.robot;
import buttons.XBoxTriggerButton;
import org.usfirst.frc4388.controller.IHandController;
import org.usfirst.frc4388.controller.XboxController;
import org.usfirst.frc4388.robot.commands.*;
import edu.wpi.first.wpilibj.smartdashboard.SmartDashboard;
import edu.wpi.first.wpilibj.GenericHID;
import edu.wpi.first.wpilibj.Joystick;
import edu.wpi.first.wpilibj.buttons.*;
import org.usfirst.frc4388.robot.subsystems.*;
import org.usfirst.frc4388.utility.MPSoftwarePIDController.MPSoftwareTurnType;
import org.usfirst.frc4388.robot.subsystems.Drive;
import org.usfirst.frc4388.utility.MPSoftwarePIDController.MPSoftwareTurnType;
import edu.wpi.first.wpilibj.buttons.Button;
import edu.wpi.first.wpilibj.buttons.InternalButton;
import edu.wpi.first.wpilibj.buttons.JoystickButton;
import edu.wpi.first.wpilibj.smartdashboard.SmartDashboard;
import jaci.pathfinder.Pathfinder;
public class OI
{
private static OI instance;
private XboxController m_driverXbox;
private XboxController m_operatorXbox;
private OI()
{
try
{
// Driver controller
m_driverXbox = new XboxController(RobotMap.DRIVER_JOYSTICK_1_USB_ID);
m_operatorXbox = new XboxController(RobotMap.OPERATOR_JOYSTICK_1_USB_ID);
XBoxTriggerButton CarriageIntake = new XBoxTriggerButton(m_operatorXbox, XBoxTriggerButton.LEFT_TRIGGER);
CarriageIntake.whenPressed(new SetIntakeSpeed(BallIntake.BALL_INTAKE_SPEED));
CarriageIntake.whenReleased(new SetIntakeSpeed(0.0));
XBoxTriggerButton CarriageEject = new XBoxTriggerButton(m_operatorXbox, XBoxTriggerButton.RIGHT_TRIGGER);
CarriageEject.whenPressed(new SetIntakeSpeed(BallIntake.BALL_EXTAKE_SPEED));
CarriageEject.whenReleased(new SetIntakeSpeed(0.0));
JoystickButton Expand = new JoystickButton(m_operatorXbox.getJoyStick(), XboxController.Y_BUTTON);
Expand.whenPressed(new WristPlacement(true));
JoystickButton Contract = new JoystickButton(m_operatorXbox.getJoyStick(), XboxController.A_BUTTON);
Contract.whenPressed(new WristPlacement(false));
JoystickButton liftBothIntake = new JoystickButton(m_operatorXbox.getJoyStick(), XboxController.X_BUTTON);
liftBothIntake.whenPressed(new HatchAndBallUp());
JoystickButton liftHatchIntake = new JoystickButton(m_operatorXbox.getJoyStick(), XboxController.RIGHT_BUMPER_BUTTON);
liftHatchIntake.whenPressed(new LiftHatchDropBall());
JoystickButton liftBallIntake = new JoystickButton(m_operatorXbox.getJoyStick(), XboxController.LEFT_BUMPER_BUTTON);
//liftBallIntake.whenPressed(new HatchFlip(false));
liftBallIntake.whenPressed(new LiftBallDropHatch());
JoystickButton climbUp = new JoystickButton(m_driverXbox.getJoyStick(), XboxController.RIGHT_TRIGGER_AXIS);
JoystickButton climbDown = new JoystickButton(m_driverXbox.getJoyStick(), XboxController.LEFT_TRIGGER_AXIS);
JoystickButton ratchetFlip = new JoystickButton(m_driverXbox.getJoyStick(), XboxController.Y_BUTTON);
ratchetFlip.whenPressed(new ratchetFlip(0.5));
ratchetFlip.whenReleased(new ratchetFlip(0));
JoystickButton shiftUp = new JoystickButton(m_driverXbox.getJoyStick(), XboxController.RIGHT_BUMPER_BUTTON);
shiftUp.whenPressed(new DriveSpeedShift(true));
//shiftUp.whenPressed(new LEDIndicators(true));
JoystickButton shiftDown = new JoystickButton(m_driverXbox.getJoyStick(), XboxController.LEFT_BUMPER_BUTTON);
shiftDown.whenPressed(new DriveSpeedShift(false));
// shiftDown.whenPressed(new LEDIndicators(false));
//Operator Xbox
/*
JoystickButton openIntake = new JoystickButton(m_operatorXbox.getJoyStick(), XboxController.LEFT_BUMPER_BUTTON);
openIntake.whenPressed(new IntakePosition(true));
JoystickButton CloseIntake = new JoystickButton(m_operatorXbox.getJoyStick(), XboxController.RIGHT_BUMPER_BUTTON);
CloseIntake.whenPressed(new IntakePosition(false));
*/
JoystickButton safteySwitch = new JoystickButton(m_operatorXbox.getJoyStick(), XboxController.START_BUTTON);
safteySwitch.whenPressed(new setClimberSafety(true));
safteySwitch.whenReleased(new setClimberSafety(false));
//SmartDashboard.putData("PRE GAME!!!!", new PreGame());
} catch (Exception e) {
System.err.println("An error occurred in the OI constructor");
}
}
public static OI getInstance() {
if(instance == null) {
instance = new OI();
}
return instance;
}
public IHandController getDriverController() {
return m_driverXbox;
}
public IHandController getOperatorController()
{
return m_operatorXbox;
}
public Joystick getOperatorJoystick()
{
return m_operatorXbox.getJoyStick();
}
}
@@ -0,0 +1,153 @@
package org.usfirst.frc4388.robot;
import edu.wpi.first.wpilibj.IterativeRobot;
import edu.wpi.first.wpilibj.CameraServer;
import edu.wpi.first.wpilibj.DriverStation;
//import edu.wpi.first.wpilibj.PowerDistributionPanel;
import edu.wpi.first.wpilibj.DriverStation.Alliance;
import edu.wpi.first.wpilibj.command.Command;
import edu.wpi.first.wpilibj.command.Scheduler;
import edu.wpi.first.wpilibj.command.Subsystem;
import edu.wpi.first.wpilibj.livewindow.LiveWindow;
import edu.wpi.first.wpilibj.smartdashboard.SendableChooser;
import edu.wpi.first.wpilibj.smartdashboard.SmartDashboard;
//import org.usfirst.frc4388.controller.Robot.OperationMode;
import org.usfirst.frc4388.robot.commands.*;
import org.usfirst.frc4388.robot.OI;
import org.usfirst.frc4388.robot.subsystems.*;
import org.usfirst.frc4388.utility.ControlLooper;
import org.usfirst.frc4388.robot.subsystems.Drive;
import org.usfirst.frc4388.robot.subsystems.Drive.DriveControlMode;;
public class Robot extends IterativeRobot
{
public static OI oi;
public static final Drive drive = new Drive();
public static final Arm arm = new Arm();
public static final Wrist wrist = new Wrist();
public static final BallIntake ballIntake = new BallIntake();
public static final Climber climber = new Climber();
public static final Pnumatics pnumatics = new Pnumatics();
public static final long periodMS = 10;
public static final ControlLooper controlLoop = new ControlLooper("Main control loop", periodMS);
public static enum OperationMode { TEST, COMPETITION };
public static OperationMode operationMode = OperationMode.TEST;
private SendableChooser<OperationMode> operationModeChooser;
public void robotInit()
{
//Printing game data to riolog
String gameData = DriverStation.getInstance().getGameSpecificMessage();
System.out.println(gameData);
CameraServer.getInstance().startAutomaticCapture();
CameraServer.getInstance().putVideo("res", 300, 220);
try {
oi = OI.getInstance();
controlLoop.addLoopable(drive);
controlLoop.addLoopable(arm);
controlLoop.addLoopable(wrist);
operationModeChooser = new SendableChooser<OperationMode>();
operationModeChooser.addDefault("Test", OperationMode.TEST);
operationModeChooser.addObject("Competition", OperationMode.COMPETITION);
SmartDashboard.putData("Operation Mode", operationModeChooser);
//ledLights.setAllLightsOn(false);
} catch (Exception e) {
System.err.println("An error occurred in robotInit()");
}
}
public void disabledInit(){
}
public void disabledPeriodic() {
Scheduler.getInstance().run();
updateStatus();
}
public void autonomousInit() {
updateChoosers();
controlLoop.start();
drive.endGyroCalibration();
drive.resetEncoders();
drive.resetGyro();
drive.setIsRed(getAlliance().equals(Alliance.Red));
}
public void autonomousPeriodic() {
Scheduler.getInstance().run();
updateStatus();
}
public void teleopInit() {
System.err.println("Beginning of teleopInit.");
drive.setToBrakeOnNeutral(false);
updateChoosers();
System.err.println("TeleopInit after UpdateChoosers");
controlLoop.start();
System.err.println("TeleopInit after controlLoop");
//drive.resetEncoders();
//System.err.println("TeleopInit after resetEncoders");
drive.endGyroCalibration();
System.err.println("TeleopInit after endGyroCalibrations");
updateStatus();
}
public void teleopPeriodic()
{
Scheduler.getInstance().run();
updateStatus();
}
public void testPeriodic() {
LiveWindow.run();
updateStatus();
}
private void updateChoosers() {
operationMode = (OperationMode)operationModeChooser.getSelected();
}
public Alliance getAlliance() {
return m_ds.getAlliance();
}
public void updateStatus() {
drive.updateStatus(operationMode);
}
}
@@ -0,0 +1,41 @@
package org.usfirst.frc4388.robot;
public class RobotMap {
// USB Port IDs
public static final int DRIVER_JOYSTICK_1_USB_ID = 0;
public static final int OPERATOR_JOYSTICK_1_USB_ID = 1;
// MOTORS
public static final int DRIVETRAIN_LEFT_MOTOR1_CAN_ID = 2;
public static final int DRIVETRAIN_LEFT_MOTOR2_CAN_ID = 3;
public static final int DRIVETRAIN_RIGHT_MOTOR1_CAN_ID = 4;
public static final int DRIVETRAIN_RIGHT_MOTOR2_CAN_ID = 5;
//Intake motors
public static final int INTAKE_BELT_DRIVE_CAN_ID = 8;
//Elevator Motors
public static final int ARM_MOTOR1_ID= 6;
public static final int ARM_MOTOR2_ID = 7;
public static final int WRIST_MOTOR_ID = 9;
//Climber Motors
public static final int CLIMBER_CAN_ID = 11;
public static final int CLIMBER_WHEEL1_ID = 12;
public static final int CLIMBER_WHEEL2_ID = 13;
// Pneumatics
public static final int DRIVETRAIN_SPEEDSHIFT_PCM_ID = 0;
public static final int DRIVETRAIN_SPEEDSHIFT2_PCM_ID = 1;
public static final int OPEN_INTAKE_PCM_ID = 4;
public static final int CLOSE_INTAKE_PCM_ID = 5;
}
@@ -0,0 +1,63 @@
package org.usfirst.frc4388.robot.commands;
import org.usfirst.frc4388.robot.Robot;
import org.usfirst.frc4388.robot.subsystems.Arm;
import edu.wpi.first.wpilibj.command.Command;
public class ArmAutoZero extends Command
{
private double MIN_ELEVATOR_POSITION_CHANGE = 0.05;
private double lastArmPosition;
private int encoderCount;
public ArmAutoZero(boolean interrutible) {
requires(Robot.arm);
setInterruptible(interrutible);
}
@Override
protected void initialize() {
lastArmPosition = Arm.MAX_POSITION_INCHES;
Robot.arm.setSpeed(Arm.AUTO_ZERO_SPEED);
encoderCount = 0;
// System.out.println("Auto zero initialize");
}
@Override
protected void execute() {
}
@Override
protected boolean isFinished() {
Robot.arm.setSpeed(Arm.AUTO_ZERO_SPEED);
double currentArmPosition = Robot.arm.getPositionInches();
double armPositionChange = lastArmPosition - currentArmPosition;
lastArmPosition = currentArmPosition;
boolean test = encoderCount > 2 && Math.abs(armPositionChange) < MIN_ELEVATOR_POSITION_CHANGE && Robot.arm.getAverageMotorCurrent() > Arm.AUTO_ZERO_MOTOR_CURRENT;
System.out.println("encoderCount = " + encoderCount + ", test = " + test + ", arm change = " + armPositionChange + ", current = " + Robot.arm.getAverageMotorCurrent());
if (Math.abs(armPositionChange) < MIN_ELEVATOR_POSITION_CHANGE) {
encoderCount++;
}
else {
encoderCount = 0;
}
return test;
}
@Override
protected void end() {
Robot.arm.setSpeed(0);
Robot.arm.resetZeroPosition(Arm.ZERO_POSITION_INCHES);
Robot.arm.setPositionPID(Arm.MIN_POSITION_INCHES);
// System.out.println("Arm Zeroed");
}
@Override
protected void interrupted() {
}
}
@@ -0,0 +1,50 @@
package org.usfirst.frc4388.robot.commands;
import org.usfirst.frc4388.robot.Robot;
import org.usfirst.frc4388.robot.subsystems.Arm.ArmControlMode;
import edu.wpi.first.wpilibj.command.Command;
/**
*
*/
public class ArmSetMode extends Command {
private ArmControlMode controlMode;
public ArmSetMode(ArmControlMode controlMode) {
this.controlMode = controlMode;
requires(Robot.arm);
}
// Called just before this Command runs the first time
protected void initialize() {
if (controlMode == ArmControlMode.JOYSTICK_PID) {
Robot.arm.setPositionPID(Robot.arm.getPositionInches());
}
else if (controlMode == ArmControlMode.JOYSTICK_MANUAL) {
Robot.arm.setSpeedJoystick(0);
}
else {
Robot.arm.setSpeed(0.0);
}
}
// Called repeatedly when this Command is scheduled to run
protected void execute() {
}
// Make this return true when this Command no longer needs to run execute()
protected boolean isFinished() {
return true;
}
// Called once after isFinished returns true
protected void end() {
}
// Called when another command which requires one or more of the same
// subsystems is scheduled to run
protected void interrupted() {
}
}
@@ -0,0 +1,55 @@
package org.usfirst.frc4388.robot.commands;
import org.usfirst.frc4388.robot.Robot;
import edu.wpi.first.wpilibj.command.Command;
/**
*
*/
public class ArmSetPositionMP extends Command {
private double targetPositionInches;
private boolean isAtTarget;
private static final double MIN_DELTA_TARGET = 0.3;
public ArmSetPositionMP(double targetPositionInches) {
this.targetPositionInches = targetPositionInches;
requires(Robot.arm);
}
// Called just before this Command runs the first time
protected void initialize() {
if (Math.abs(targetPositionInches - Robot.arm.getPositionInches()) < MIN_DELTA_TARGET) {
isAtTarget = true;
}
else {
isAtTarget = false;
Robot.arm.setPositionMP(targetPositionInches);
}
// System.out.println("Arm set MP initialized, target = " + targetPositionInches);
}
// Called repeatedly when this Command is scheduled to run
protected void execute() {
}
// Make this return true when this Command no longer needs to run execute()
protected boolean isFinished() {
return isAtTarget || Robot.arm.isFinished();
}
// Called once after isFinished returns true
protected void end() {
Robot.arm.setPositionPID(Robot.arm.getPositionInches());
// System.out.println("Arm set MP end");
}
// Called when another command which requires one or more of the same
// subsystems is scheduled to run
protected void interrupted() {
// System.out.println("ArmSetPositionMP interrupted");
Robot.arm.setFinished(true);
Robot.arm.setPositionPID(Robot.arm.getPositionInches());
}
}
@@ -0,0 +1,42 @@
package org.usfirst.frc4388.robot.commands;
import org.usfirst.frc4388.robot.Robot;
import org.usfirst.frc4388.robot.subsystems.Arm;
import edu.wpi.first.wpilibj.command.Command;
/**
*
*/
public class ArmSetPositionPID extends Command {
private double targetPositionInches;
public ArmSetPositionPID(double targetPositionInches) {
this.targetPositionInches = targetPositionInches;
requires(Robot.arm);
}
// Called just before this Command runs the first time
protected void initialize() {
Robot.arm.setPositionPID(targetPositionInches);
}
// Called repeatedly when this Command is scheduled to run
protected void execute() {
}
// Make this return true when this Command no longer needs to run execute()
protected boolean isFinished() {
return Math.abs(Robot.arm.getPositionInches() - this.targetPositionInches) < Arm.PID_ERROR_INCHES;
}
// Called once after isFinished returns true
protected void end() {
}
// Called when another command which requires one or more of the same
// subsystems is scheduled to run
protected void interrupted() {
}
}
@@ -0,0 +1,44 @@
package org.usfirst.frc4388.robot.commands;
import org.usfirst.frc4388.robot.Robot;
import org.usfirst.frc4388.robot.subsystems.*;
import edu.wpi.first.wpilibj.command.Command;
/**
*
*/
public class ArmSetSpeed extends Command {
private double RaiseSpeed;
// Constructor with speed
public ArmSetSpeed(double RaiseSpeed) {
this.RaiseSpeed = RaiseSpeed;
// requires(Robot.elevatorAuton);
}
// Called just before this Command runs the first time
protected void initialize() {
//Robot.elevatorAuton.setRaiseSpeed(RaiseSpeed);
}
// Called repeatedly when this Command is scheduled to run
protected void execute() {
}
// Make this return true when this Command no longer needs to run execute()
protected boolean isFinished() {
return true;
}
// Called once after isFinished returns true
protected void end() {
}
// Called when another command which requires one or more of the same
// subsystems is scheduled to run
protected void interrupted() {
}
}
@@ -0,0 +1,40 @@
package org.usfirst.frc4388.robot.commands;
import org.usfirst.frc4388.robot.Robot;
import edu.wpi.first.wpilibj.command.Command;
public class ArmSetZero extends Command
{
private double position;
public ArmSetZero(double position) {
this.position = position;
requires(Robot.arm);
}
@Override
protected void initialize() {
Robot.arm.resetZeroPosition(position);
}
@Override
protected void execute() {
}
@Override
protected boolean isFinished() {
return true;
}
@Override
protected void end() {
}
@Override
protected void interrupted() {
}
}
@@ -0,0 +1,40 @@
package org.usfirst.frc4388.robot.commands;
import org.usfirst.frc4388.robot.Robot;
import edu.wpi.first.wpilibj.command.Command;
public class DeployBallIntake extends Command
{
public boolean IsUp;
public DeployBallIntake(boolean IsUp) {
this.IsUp=IsUp;
requires(Robot.pnumatics);
}
@Override
protected void initialize() {
Robot.pnumatics.setBallIntake(IsUp);
}
@Override
protected void execute() {
}
@Override
protected boolean isFinished() {
return true;
}
@Override
protected void end() {
}
@Override
protected void interrupted() {
}
}
@@ -0,0 +1,151 @@
package org.usfirst.frc4388.robot.commands;
import org.usfirst.frc4388.robot.Constants;
import org.usfirst.frc4388.robot.Robot;
import org.usfirst.frc4388.robot.subsystems.Drive.DriveControlMode;
import org.usfirst.frc4388.utility.BHRMathUtils;
import org.usfirst.frc4388.utility.TalonSRXEncoder;
import org.usfirst.frc4388.utility.MPSoftwarePIDController.MPSoftwareTurnType;
import com.ctre.phoenix.motorcontrol.ControlMode;
import edu.wpi.first.wpilibj.command.Command;
public class DriveTurnBasic extends Command
{
private boolean m_useAbsolute; // if true, we don't reset the gyro before starting the turn
private double m_turnAngleDeg; // either absolute or relative to current heading, depending on useAbsolute
private double m_maxTurnRateDegPerSec;
private MPSoftwareTurnType m_turnType;
private boolean m_turningLeft;
private double m_targetGyroAngleDeg; // what we want the gyro to read when we're done
public DriveTurnBasic(boolean useAbsolute, double turnAngleDeg, double maxTurnRateDegPerSec, MPSoftwareTurnType turnType) {
requires(Robot.drive);
m_useAbsolute = useAbsolute;
m_turnAngleDeg = turnAngleDeg;
m_maxTurnRateDegPerSec = maxTurnRateDegPerSec;
m_turnType = turnType;
}
protected void initialize() {
double currentAngleDeg = Robot.drive.getGyroAngleDeg();
if (m_useAbsolute) {
m_targetGyroAngleDeg = m_turnAngleDeg;
} else {
m_targetGyroAngleDeg = currentAngleDeg + m_turnAngleDeg;
}
if ((m_turnType == MPSoftwareTurnType.RIGHT_SIDE_ONLY) || (m_turnType == MPSoftwareTurnType.RIGHT_ARC)) {
m_turningLeft = true; // gyro angle will be DEcreasing, so make sure we aim for an angle in that direction
while (m_targetGyroAngleDeg >= currentAngleDeg) {
m_targetGyroAngleDeg = m_targetGyroAngleDeg - 360.0;
}
} else if ((m_turnType == MPSoftwareTurnType.LEFT_SIDE_ONLY) || (m_turnType == MPSoftwareTurnType.LEFT_ARC)) {
m_turningLeft = false; // gyro angle will be INcreasing, so make sure we aim for an angle in that direction
while (m_targetGyroAngleDeg <= currentAngleDeg) {
m_targetGyroAngleDeg = m_targetGyroAngleDeg + 360.0;
}
} else { // MPSoftwareTurnType.TANK -- need to determine based on values passed
if (m_useAbsolute) { // this is the only case where we have to DECIDE which direction to turn
m_turnAngleDeg = BHRMathUtils.normalizeAngle0To360(m_turnAngleDeg);
m_targetGyroAngleDeg = BHRMathUtils.adjustAccumAngleToDesired(currentAngleDeg, m_turnAngleDeg);
m_turningLeft = (m_targetGyroAngleDeg < currentAngleDeg);
} else {
m_turningLeft = (m_turnAngleDeg < 0);
}
}
System.out.println("Turning " + (m_turningLeft?"left":"right") + " from " + currentAngleDeg + " to " + m_targetGyroAngleDeg + " degrees");
Robot.drive.setControlMode(DriveControlMode.RAW);
Robot.drive.resetEncoders();
}
protected void execute() {
double output = Constants.kDriveTurnBasicSingleMotorOutput;
if (m_turnType == MPSoftwareTurnType.TANK) {
output = Constants.kDriveTurnBasicTankMotorOutput;
if (m_turningLeft) {
Robot.drive.rawDriveLeftRight(-output, -output); // left backward, right forward
} else {
Robot.drive.rawDriveLeftRight(output, output); // left forward, right backward
}
// for (CANTalonEncoder motorController : motorControllers) {
// //motorController.set(output);
// motorController.set(ControlMode.PercentOutput, output);
// }
}
else if (m_turnType == MPSoftwareTurnType.LEFT_SIDE_ONLY) {
Robot.drive.rawDriveLeftRight(2.0 * output, 0.0); // left forward double speed
// for (CANTalonEncoder motorController : motorControllers) {
// if (motorController.isRight()) {
// //motorController.set(0);
// motorController.set(ControlMode.PercentOutput, 0);
// }
// else {
// //motorController.set(2.0 * output);
// motorController.set(ControlMode.PercentOutput, 2.0 * output);
// }
// }
}
else if (m_turnType == MPSoftwareTurnType.RIGHT_SIDE_ONLY) {
Robot.drive.rawDriveLeftRight(0.0, -2.0 * output); // right forward double speed
// for (CANTalonEncoder motorController : motorControllers) {
// if (motorController.isRight()) {
// //motorController.set(2.0 * output);
// motorController.set(ControlMode.PercentOutput, 2.0 * output);
// }
// else {
// //motorController.set(0);
// motorController.set(ControlMode.PercentOutput, 0);
// }
// }
}
else if (m_turnType == MPSoftwareTurnType.LEFT_ARC) {
Robot.drive.rawDriveLeftRight(2.0 * output, -output); // left fwd 2x, right fwd 1x
// for (CANTalonEncoder motorController : motorControllers) {
// if (motorController.isRight()) {
// //motorController.set(1.0 * output);
// motorController.set(ControlMode.PercentOutput, 1.0 * output);
// }
// else {
// //motorController.set(2.0 * output);
// motorController.set(ControlMode.PercentOutput, 2.0 * output);
// }
// }
}
else if (m_turnType == MPSoftwareTurnType.RIGHT_ARC) {
Robot.drive.rawDriveLeftRight(output, -2.0 * output); // left fwd 1x, right fwd 2x
// for (CANTalonEncoder motorController : motorControllers) {
// if (motorController.isRight()) {
// //motorController.set(2.0 * output);
// motorController.set(ControlMode.PercentOutput, 2.0 * output);
// }
// else {
// //motorController.set(1.0 * output);
// motorController.set(ControlMode.PercentOutput, 1.0 * output);
// }
// }
}
}
protected boolean isFinished() {
boolean finished;
double currentAngleDeg = Robot.drive.getGyroAngleDeg();
if (m_turningLeft) {
finished = currentAngleDeg <= m_targetGyroAngleDeg;
} else {
finished = currentAngleDeg >= m_targetGyroAngleDeg;
}
return finished;
}
protected void end() {
Robot.drive.rawDriveLeftRight(0.0, 0.0);
Robot.drive.setControlMode(DriveControlMode.JOYSTICK);
}
protected void interrupted() {
end();
}
}
@@ -0,0 +1,35 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2018 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
package org.usfirst.frc4388.robot.commands;
import edu.wpi.first.wpilibj.command.CommandGroup;
public class HatchAndBallUp extends CommandGroup {
/**
* Add your docs here.
*/
public HatchAndBallUp() {
addSequential(new HatchFlip(true));
addParallel(new DeployBallIntake(false));
// e.g. addSequential(new Command1());
// addSequential(new Command2());
// these will run in order.
// To run multiple commands at the same time,
// use addParallel()
// e.g. addParallel(new Command1());
// addSequential(new Command2());
// Command1 and Command2 will run in parallel.
// A command group will require all of the subsystems that each member
// would require.
// e.g. if Command1 requires chassis, and Command2 requires arm,
// a CommandGroup containing them would require both the chassis and the
// arm.
}
}
@@ -0,0 +1,40 @@
package org.usfirst.frc4388.robot.commands;
import org.usfirst.frc4388.robot.Robot;
import edu.wpi.first.wpilibj.command.Command;
public class HatchFlip extends Command
{
public boolean PickingUp;
public HatchFlip(boolean PickingUP) {
this.PickingUp=PickingUP;
requires(Robot.pnumatics);
}
@Override
protected void initialize() {
Robot.pnumatics.setHatchIntakeState(PickingUp);
}
@Override
protected void execute() {
}
@Override
protected boolean isFinished() {
return true;
}
@Override
protected void end() {
}
@Override
protected void interrupted() {
}
}
@@ -0,0 +1,36 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2018 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
package org.usfirst.frc4388.robot.commands;
import edu.wpi.first.wpilibj.command.CommandGroup;
public class LiftBallDropHatch extends CommandGroup {
/**
* Add your docs here.
*/
public LiftBallDropHatch() {
addSequential(new HatchFlip(false));
addParallel(new DeployBallIntake(false));
// Add Commands here:
// e.g. addSequential(new Command1());
// addSequential(new Command2());
// these will run in order.
// To run multiple commands at the same time,
// use addParallel()
// e.g. addParallel(new Command1());
// addSequential(new Command2());
// Command1 and Command2 will run in parallel.
// A command group will require all of the subsystems that each member
// would require.
// e.g. if Command1 requires chassis, and Command2 requires arm,
// a CommandGroup containing them would require both the chassis and the
// arm.
}
}
@@ -0,0 +1,36 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2018 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
package org.usfirst.frc4388.robot.commands;
import edu.wpi.first.wpilibj.command.CommandGroup;
public class LiftHatchDropBall extends CommandGroup {
/**
* Add your docs here.
*/
public LiftHatchDropBall() {
addSequential(new HatchFlip(true));
addParallel(new DeployBallIntake(true));
// Add Commands here:
// e.g. addSequential(new Command1());
// addSequential(new Command2());
// these will run in order.
// To run multiple commands at the same time,
// use addParallel()
// e.g. addParallel(new Command1());
// addSequential(new Command2());
// Command1 and Command2 will run in parallel.
// A command group will require all of the subsystems that each member
// would require.
// e.g. if Command1 requires chassis, and Command2 requires arm,
// a CommandGroup containing them would require both the chassis and the
// arm.
}
}
@@ -0,0 +1,44 @@
package org.usfirst.frc4388.robot.commands;
import org.usfirst.frc4388.robot.Robot;
import org.usfirst.frc4388.robot.subsystems.BallIntake;
import edu.wpi.first.wpilibj.command.Command;
/**
*
*/
public class SetIntakeSpeed extends Command {
private double WheelSpeed;
// Constructor with speed
public SetIntakeSpeed(double WheelSpeed) {
this.WheelSpeed = WheelSpeed;
requires(Robot.ballIntake);
}
// Called just before this Command runs the first time
protected void initialize() {
Robot.ballIntake.setWheelSpeed(WheelSpeed);
}
// Called repeatedly when this Command is scheduled to run
protected void execute() {
}
// Make this return true when this Command no longer needs to run execute()
protected boolean isFinished() {
return true;
}
// Called once after isFinished returns true
protected void end() {
}
// Called when another command which requires one or more of the same
// subsystems is scheduled to run
protected void interrupted() {
}
}
@@ -0,0 +1,40 @@
package org.usfirst.frc4388.robot.commands;
import org.usfirst.frc4388.robot.Robot;
import edu.wpi.first.wpilibj.command.Command;
public class WristPlacement extends Command
{
public boolean Forward;
public WristPlacement(boolean Forward) {
this.Forward=Forward;
requires(Robot.pnumatics);
}
@Override
protected void initialize() {
Robot.pnumatics.setWrist(Forward);
}
@Override
protected void execute() {
}
@Override
protected boolean isFinished() {
return true;
}
@Override
protected void end() {
}
@Override
protected void interrupted() {
}
}
@@ -0,0 +1,46 @@
package org.usfirst.frc4388.robot.commands;
import org.usfirst.frc4388.robot.Robot;
import org.usfirst.frc4388.robot.subsystems.Climber;
import org.usfirst.frc4388.robot.Constants;
import edu.wpi.first.wpilibj.command.Command;
public class ratchetFlip extends Command {
private double speed;
public ratchetFlip(double speed) {
requires(Robot.climber);
this.speed = speed;
}
// Called just before this Command runs the first time
@Override
protected void initialize() {
if (Climber.SPEED == 0){
Robot.climber.flipRatchet(speed);
}
}
// Called repeatedly when this Command is scheduled to run
@Override
protected void execute() {
}
// Make this return true when this Command no longer needs to run execute()
@Override
protected boolean isFinished() {
return true;
}
// Called once after isFinished returns true
@Override
protected void end() {
}
// Called when another command which requires one or more of the same
// subsystems is scheduled to run
@Override
protected void interrupted() {
}
}
@@ -0,0 +1,42 @@
package org.usfirst.frc4388.robot.commands;
import org.usfirst.frc4388.robot.Robot;
import org.usfirst.frc4388.robot.Constants;
import edu.wpi.first.wpilibj.command.Command;
public class setClimberSafety extends Command {
boolean safety;
public setClimberSafety(boolean safety) {
requires(Robot.climber);
this.safety = safety;
}
// Called just before this Command runs the first time
@Override
protected void initialize() {
Robot.climber.safetySwitch(safety);
}
// Called repeatedly when this Command is scheduled to run
@Override
protected void execute() {
}
// Make this return true when this Command no longer needs to run execute()
@Override
protected boolean isFinished() {
return true;
}
// Called once after isFinished returns true
@Override
protected void end() {
}
// Called when another command which requires one or more of the same
// subsystems is scheduled to run
@Override
protected void interrupted() {
}
}
@@ -0,0 +1,361 @@
package org.usfirst.frc4388.robot.subsystems;
import java.util.ArrayList;
import org.usfirst.frc4388.robot.Constants;
import org.usfirst.frc4388.robot.Robot;
import org.usfirst.frc4388.robot.RobotMap;
import org.usfirst.frc4388.utility.ControlLoopable;
import org.usfirst.frc4388.utility.Loop;
import org.usfirst.frc4388.utility.MPTalonPIDController;
import org.usfirst.frc4388.utility.PIDParams;
import org.usfirst.frc4388.utility.TalonSRXEncoder;
import org.usfirst.frc4388.utility.TalonSRXFactory;
import com.ctre.phoenix.motorcontrol.ControlMode;
import com.ctre.phoenix.motorcontrol.FeedbackDevice;
import com.ctre.phoenix.motorcontrol.can.TalonSRX;
import com.ctre.phoenix.motorcontrol.LimitSwitchSource;
import com.ctre.phoenix.motorcontrol.LimitSwitchNormal;
import com.ctre.phoenix.motorcontrol.RemoteLimitSwitchSource;
import com.ctre.phoenix.motorcontrol.NeutralMode;
import com.ctre.phoenix.motorcontrol.SensorCollection;
import com.ctre.phoenix.motorcontrol.can.WPI_TalonSRX;
import edu.wpi.first.wpilibj.Solenoid;
import edu.wpi.first.wpilibj.command.Subsystem;
import edu.wpi.first.wpilibj.smartdashboard.SmartDashboard;
import edu.wpi.first.wpilibj.Timer;
public class Arm extends Subsystem implements ControlLoopable
{
private static Arm instance;
public static enum ArmControlMode { MOTION_PROFILE, JOYSTICK_PID, JOYSTICK_MANUAL, MANUAL };
// One revolution of the 30T Drive 1.880" PD pulley = Pi * PD inches = 36/24 revs due to pulleys * 34/24 revs due to gears * 36/12 revs due mag encoder gear on ball shifter * 4096 ticks
public static final double ENCODER_TICKS_TO_INCHES = ((360/4096)/(3))-60;
private double periodMs;
private double lastControlLoopUpdatePeriod = 0.0;
private double lastControlLoopUpdateTimestamp = 0.0;
// Defined speeds
public static final double CLIMB_SPEED = -1.0;
public static final double TEST_SPEED_UP = 0.3;
public static final double TEST_SPEED_DOWN = -0.3;
public static final double AUTO_ZERO_SPEED = -0.3;
public static final double JOYSTICK_INCHES_PER_MS_HI = 0.75;
public static final double JOYSTICK_INCHES_PER_MS_LO = JOYSTICK_INCHES_PER_MS_HI/3.68 * 0.8;
// Defined positions
public static final double ZERO_POSITION_AUTON_FORWARD_INCHES = 8.0;
public static final double ZERO_POSITION_INCHES = -0.25;
public static final double NEAR_ZERO_POSITION_INCHES = 0.0;
public static final double MIN_POSITION_INCHES = 0.0;
public static final double MAX_POSITION_INCHES = 83.4;
public static final double AFTER_INTAKE_POSITION_INCHES = 4.0;
public static final double SWITCH_POSITION_INCHES = 24.0;
public static final double SWITCH_POSITION_HIGH_INCHES = 36.0; //Switch Position for First Cube APR
public static final double SCALE_LOW_POSITION_INCHES = 56.0;
public static final double SCALE_FIRST_CUBE_POSITION_INCHES = 78.0; //72.0
public static final double SCALE_SECOND_CUBE_POSITION_INCHES = 77.0;
public static final double SCALE_HIGH_POSITION_INCHES = MAX_POSITION_INCHES;
public static final double CLIMB_BAR_POSITION_INCHES = 70.0;
public static final double CLIMB_HIGH_POSITION_INCHES = 10.0;
public static final double CLIMB_ASSIST_POSITION_INCHES = 50.0;
// Motion profile max velocities and accel times
public static final double MP_MAX_VELOCITY_INCHES_PER_SEC = 60;
public static final double MP_T1 = 400; // Fast = 300
public static final double MP_T2 = 150; // Fast = 150
// Motor controllers
private ArrayList<TalonSRXEncoder> motorControllers = new ArrayList<TalonSRXEncoder>();
private TalonSRXEncoder motor1;
private TalonSRX motor2;
// PID controller and params
private MPTalonPIDController mpController;
public static int PID_SLOT = 0;
public static int MP_SLOT = 1;
private PIDParams mpPIDParams = new PIDParams(0.2, 0.0, 0.0, 0.0, 0.005, 0.0);
private PIDParams pidPIDParamsHiGear = new PIDParams(0.075, 0.0, 0.0, 0.0, 0.0, 0.0);
public static final double KF_UP = 0.005;
public static final double KF_DOWN = 0.0;
public static final double PID_ERROR_INCHES = 1.0;
LimitSwitchSource limitSwitchSource;
// Pneumatics
private Solenoid speedShift;
private ArmControlMode controlMode = ArmControlMode.JOYSTICK_MANUAL;
// Misc
public static final double AUTO_ZERO_MOTOR_CURRENT = 4.0;
private boolean isFinished;
private ArmControlMode armControlMode = ArmControlMode.JOYSTICK_MANUAL;
private double targetPositionInchesPID = 0;
private boolean firstMpPoint;
private double joystickInchesPerMs = JOYSTICK_INCHES_PER_MS_LO;
public Arm() {
try {
motor1 = TalonSRXFactory.createTalonEncoder(RobotMap.ARM_MOTOR1_ID, (ENCODER_TICKS_TO_INCHES), false, FeedbackDevice.QuadEncoder);
motor2 = TalonSRXFactory.createPermanentSlaveTalon(RobotMap.ARM_MOTOR2_ID, RobotMap.ARM_MOTOR1_ID);
motor1.setInverted(true);
motor2.setInverted(true);
// if (motor1.isSensorPresent(CANTalon.FeedbackDevice.QuadEncoder) != CANTalon.FeedbackDeviceStatus.FeedbackStatusPresent) {
// Driver.reportError("Could not detect elevator motor 1 encoder encoder!", false);
// }
motor1.configForwardLimitSwitchSource(limitSwitchSource, LimitSwitchNormal.NormallyOpen, 0);
motor1.configReverseLimitSwitchSource(limitSwitchSource, LimitSwitchNormal.NormallyOpen, 0);
motor1.setNeutralMode(NeutralMode.Brake);
motor2.setNeutralMode(NeutralMode.Brake);
motorControllers.add(motor1);
}
catch (Exception e) {
System.err.println("An error occurred in the Arm constructor");
}
}
@Override
public void initDefaultCommand() {
}
public void resetZeroPosition(double position) {
mpController.resetZeroPosition(position);
}
private synchronized void setArmControlMode(ArmControlMode controlMode) {
this.armControlMode = controlMode;
}
private synchronized ArmControlMode getArmControlMode() {
return this.armControlMode;
}
public void setSpeed(double speed) {
motor1.set(ControlMode.PercentOutput, speed);
setArmControlMode(ArmControlMode.MANUAL);
}
public void setSpeedJoystick(double speed) {
motor1.set(ControlMode.PercentOutput, speed);
setArmControlMode(ArmControlMode.JOYSTICK_MANUAL);
}
public void setPositionPID(double targetPositionInches) {
mpController.setPIDSlot(PID_SLOT);
updatePositionPID(targetPositionInches);
setArmControlMode(ArmControlMode.JOYSTICK_PID);
setFinished(false);
}
public void updatePositionPID(double targetPositionInches) {
targetPositionInchesPID = limitPosition(targetPositionInches);
double startPositionInches = motor1.getPositionWorld();
mpController.setTarget(targetPositionInchesPID, targetPositionInchesPID > startPositionInches ? KF_UP : KF_DOWN);
}
public void setPositionMP(double targetPositionInches) {
double startPositionInches = motor1.getPositionWorld();
mpController.setMPTarget(startPositionInches, limitPosition(targetPositionInches), MP_MAX_VELOCITY_INCHES_PER_SEC, MP_T1, MP_T2);
setFinished(false);
firstMpPoint = true;
setArmControlMode(ArmControlMode.MOTION_PROFILE);
}
private double limitPosition(double targetPosition) {
if (targetPosition < MIN_POSITION_INCHES) {
return MIN_POSITION_INCHES;
}
else if (targetPosition > MAX_POSITION_INCHES) {
return MAX_POSITION_INCHES;
}
return targetPosition;
}
@Override
public void setPeriodMs(long periodMs) {
mpController = new MPTalonPIDController(periodMs, motorControllers);
mpController.setPID(mpPIDParams, MP_SLOT);
mpController.setPID(pidPIDParamsHiGear, PID_SLOT);
mpController.setPIDSlot(PID_SLOT);
mpController.setPID(pidPIDParamsHiGear, PID_SLOT);
mpController.setPIDSlot(PID_SLOT);
this.periodMs = periodMs;
}
/*@Override
public void onStart(double timestamp) {
mpController = new MPTalonPIDController(periodMs, motorControllers);
mpController.setPID(mpPIDParams, MP_SLOT);
mpController.setPID(pidPIDParamsHiGear, PID_SLOT);
mpController.setPIDSlot(PID_SLOT);
mpController.setPID(pidPIDParamsHiGear, PID_SLOT);
mpController.setPIDSlot(PID_SLOT);
}
@Override
public void onStop(double timestamp) {
// TODO Auto-generated method stub
}
@Override
public void onLoop(double timestamp) {
synchronized (Arm.this) {
switch( getElevatorControlMode() ) {
case JOYSTICK_PID:
controlPidWithJoystick();
break;
case JOYSTICK_MANUAL:
controlManualWithJoystick();
break;
case MOTION_PROFILE:
if (!isFinished()) {
if (firstMpPoint) {
mpController.setPIDSlot(MP_SLOT);
firstMpPoint = false;
}
setFinished(mpController.controlLoopUpdate());
if (isFinished()) {
mpController.setPIDSlot(PID_SLOT);
}
}
break;
default:
break;
}
}
}
*/
public synchronized void controlLoopUpdate() {
// Measure *actual* update period
double currentTimestamp = Timer.getFPGATimestamp();
if (lastControlLoopUpdateTimestamp > 0.001) { // ie, if this is NOT the first time
lastControlLoopUpdatePeriod = currentTimestamp - lastControlLoopUpdateTimestamp;
}
lastControlLoopUpdateTimestamp = currentTimestamp;
// Do the update
if (controlMode == ArmControlMode.JOYSTICK_MANUAL) {
controlManualWithJoystick();
}
else if (!isFinished) {
if (controlMode == ArmControlMode.MOTION_PROFILE) {
isFinished = mpController.controlLoopUpdate(getPositionInches());
}
/*else if (controlMode == ArmControlMode.MP_PATH_VELOCITY) {
isFinished = mpPathVelocityController.controlLoopUpdate(getGyroAngleDeg());
}
else if (controlMode == ArmControlMode.ADAPTIVE_PURSUIT) {
updatePose();
isFinished = adaptivePursuitController.controlLoopUpdate(currentPose);
}*/
}
}
private void controlPidWithJoystick() {
double joystickPosition = -Robot.oi.getOperatorController().getLeftYAxis();
double deltaPosition = joystickPosition * joystickInchesPerMs;
targetPositionInchesPID = targetPositionInchesPID + deltaPosition;
updatePositionPID(targetPositionInchesPID);
}
private void ControlWithJoystickhold(){
double holdPosition = motor1.getPositionWorld();
updatePositionPID(holdPosition);
}
private void controlManualWithJoystick() {
double joyStickSpeed = -Robot.oi.getOperatorController().getLeftYAxis();
setSpeedJoystick(joyStickSpeed*.60);
}
/*
public void setShiftState(ElevatorSpeedShiftState state) {
joystickInchesPerMs = JOYSTICK_INCHES_PER_MS_HI;
speedShift.set(true);
mpController.setPID(pidPIDParamsHiGear, PID_SLOT);
}
else if(state == ElevatorSpeedShiftState.LO) {
joystickInchesPerMs = JOYSTICK_INCHES_PER_MS_LO;
speedShift.set(false);
mpController.setPID(pidPIDParamsLoGear, PID_SLOT);
}
}
public ElevatorSpeedShiftState getShiftState() {
return shiftState;
}
*/
public double getPositionInches() {
return motor1.getPositionWorld();
}
// public double getAverageMotorCurrent() {
// return (Robot.pdp.getCurrent(RobotMap.ELEVATOR_MOTOR_1_CAN_ID) + Robot.pdp.getCurrent(RobotMap.ELEVATOR_MOTOR_2_CAN_ID) + Robot.pdp.getCurrent(RobotMap.ELEVATOR_MOTOR_3_CAN_ID)) / 3;
// }
public double getAverageMotorCurrent() {
return (motor1.getOutputCurrent() + motor2.getOutputCurrent()) / 2;
}
public synchronized boolean isFinished() {
return isFinished;
}
public synchronized void setFinished(boolean isFinished) {
this.isFinished = isFinished;
}
public double getPeriodMs() {
return periodMs;
}
public void updateStatus(Robot.OperationMode operationMode) {
if (operationMode == Robot.OperationMode.TEST) {
try {
SmartDashboard.putNumber("Elevator Position Inches", motor1.getPositionWorld());
SmartDashboard.putNumber("Elevator Motor 1 Amps", motor1.getOutputCurrent());
SmartDashboard.putNumber("Elevator Motor 2 Amps", motor2.getOutputCurrent());
SmartDashboard.putNumber("Elevator Average Amps", getAverageMotorCurrent());
// SmartDashboard.putNumber("Elevator Motor 1 Amps PDP", Robot.pdp.getCurrent(RobotMap.ELEVATOR_MOTOR_1_CAN_ID));
// SmartDashboard.putNumber("Elevator Motor 2 Amps PDP", Robot.pdp.getCurrent(RobotMap.ELEVATOR_MOTOR_2_CAN_ID));
// SmartDashboard.putNumber("Elevator Motor 3 Amps PDP", Robot.pdp.getCurrent(RobotMap.ELEVATOR_MOTOR_3_CAN_ID));
SmartDashboard.putNumber("Elevator Target PID Position", targetPositionInchesPID);
}
catch (Exception e) {
}
}
}
public static Arm getInstance() {
if(instance == null) {
instance = new Arm();
}
return instance;
}
}
@@ -0,0 +1,60 @@
package org.usfirst.frc4388.robot.subsystems;
import org.usfirst.frc4388.robot.RobotMap;
import org.usfirst.frc4388.robot.commands.*;
import org.usfirst.frc4388.robot.subsystems.Drive.DriveControlMode;
import org.usfirst.frc4388.utility.TalonSRXEncoder;
import org.usfirst.frc4388.utility.Looper;
import edu.wpi.first.wpilibj.command.Subsystem;
import org.usfirst.frc4388.robot.OI;
import com.ctre.phoenix.motorcontrol.can.WPI_TalonSRX;
import com.ctre.phoenix.motorcontrol.ControlMode;
import com.ctre.phoenix.motorcontrol.FeedbackDevice;
/**
*
*/
public class BallIntake extends Subsystem {
private WPI_TalonSRX BallIntakeMain;
public static enum BallIntakeControlMode { JOYSTICK, MP_STRAIGHT, HOLD, MANUAL};
public static final double BALL_INTAKE_SPEED = -1.0;
public static final double BALL_EXTAKE_SPEED = 1.0;
public static final double CUBE_STOP_SPEED = 0;
/////^^^^^^^^^ replace this line with the modes we need
private boolean isFinished;
//private CarriageControlMode controlMode = CarriageControlMode.JOYSTICK;
public BallIntake() {
try {
BallIntakeMain = new WPI_TalonSRX(RobotMap.INTAKE_BELT_DRIVE_CAN_ID);
//\][carriageLeft.set(CurrentLimit, value);
}
catch (Exception e) {
System.err.println("An error occurred in the Ball Intake constructor");
}
}
public void setWheelSpeed(double speed) {
BallIntakeMain.set(-speed);
}
@Override
public void periodic() {
}
public void initDefaultCommand() {
}
}
@@ -0,0 +1,129 @@
package org.usfirst.frc4388.robot.subsystems;
import java.util.ArrayList;
import org.usfirst.frc4388.controller.XboxController;
import org.usfirst.frc4388.robot.Constants;
import org.usfirst.frc4388.robot.OI;
import org.usfirst.frc4388.robot.Robot;
import org.usfirst.frc4388.robot.RobotMap;
import org.usfirst.frc4388.robot.commands.*;
import org.usfirst.frc4388.robot.subsystems.Drive.DriveControlMode;
import org.usfirst.frc4388.utility.TalonSRXEncoder;
import org.usfirst.frc4388.utility.Looper;
import org.usfirst.frc4388.utility.PIDParams;
import org.usfirst.frc4388.utility.SoftwarePIDController;
import com.ctre.phoenix.motorcontrol.ControlMode;
import edu.wpi.first.wpilibj.DigitalInput;
import edu.wpi.first.wpilibj.Encoder;
import edu.wpi.first.wpilibj.PIDController;
import edu.wpi.first.wpilibj.SpeedControllerGroup;
import edu.wpi.first.wpilibj.command.Subsystem;
import edu.wpi.first.wpilibj.drive.DifferentialDrive;
import edu.wpi.first.wpilibj.smartdashboard.SmartDashboard;
import com.ctre.phoenix.motorcontrol.FeedbackDevice;
import com.ctre.phoenix.motorcontrol.LimitSwitchNormal;
import com.ctre.phoenix.motorcontrol.RemoteLimitSwitchSource;
import com.ctre.phoenix.motorcontrol.LimitSwitchSource;
import com.ctre.phoenix.motorcontrol.NeutralMode;
import com.ctre.phoenix.motorcontrol.SensorCollection;
import com.ctre.phoenix.motorcontrol.can.WPI_TalonSRX;
/**
*
*/
public class Climber extends Subsystem{
//Motors
private WPI_TalonSRX climberBack;
private WPI_TalonSRX climberFront;
private WPI_TalonSRX climberFront2;
private WPI_TalonSRX flipOutMotor;
//Frequency Control
static float BACK_FREQ = 1;
static float FRONT_FREQ = 1;
static float FREQ_RATIO = 0.2443744576F;
//Limit and Saftey vars
LimitSwitchSource limitSwitchSource;
SensorCollection isPressed;
boolean safetySwitch;
//Climb Trigger
double rightTriggerInput;
double leftTriggerInput;
public static double SPEED;
public Climber(){
try{
climberBack = new WPI_TalonSRX(RobotMap.CLIMBER_CAN_ID);
climberFront = new WPI_TalonSRX(RobotMap.CLIMBER_WHEEL1_ID);
climberFront2 = new WPI_TalonSRX(RobotMap.CLIMBER_WHEEL2_ID);
climberFront2.set(ControlMode.Follower, climberFront.getDeviceID());
climberBack.configForwardLimitSwitchSource(limitSwitchSource, LimitSwitchNormal.NormallyOpen, 0);
climberBack.configReverseLimitSwitchSource(limitSwitchSource, LimitSwitchNormal.NormallyOpen, 0);
climberBack.setNeutralMode(NeutralMode.Brake);
climberBack.setInverted(true);
climberFront.setNeutralMode(NeutralMode.Brake);
FRONT_FREQ = BACK_FREQ * FREQ_RATIO; // Sets the front motor speed to ~1/4 the back motor speed
}
catch (Exception e) {
System.err.println("The code broke before the guard did. An error occurred in the climbing constructor");
}
}
@Override
public void initDefaultCommand() {
}
@Override
public void periodic() {
rightTriggerInput = OI.getInstance().getDriverController().getRightTriggerAxis();
leftTriggerInput = OI.getInstance().getDriverController().getLeftTriggerAxis();
//SPEED = rightTriggerInput - leftTriggerInput;
// Put code here to be run every loop
if (safetySwitch) {
/*
if (isPressed.isRevLimitSwitchClosed() && speed < 0){ //If leg at min height, and the input would retract the leg
climberBack.set(0);
climberFront.set(0);
}
else if(isPressed.isFwdLimitSwitchClosed() && speed > 0){ //If leg at max height, and the input would extend the leg
climberBack.set(0);
climberFront.set(FRONT_FREQ * speed);
} *//*
if (SPEED < 0){
climberBack.set(0.3 * SPEED);
}
else { //If leg not at max height
climberBack.set(0.5 * SPEED);
climberFront.set(SPEED);
}*/
climberBack.set(-rightTriggerInput * 0.3);
climberFront.set(leftTriggerInput);
}
else {
climberBack.set(0);
}
}
public void safetySwitch(boolean safetySwitch){
this.safetySwitch = safetySwitch;
}
public void flipRatchet(double speed){
climberFront.set(speed);
}
}
@@ -0,0 +1,902 @@
package org.usfirst.frc4388.robot.subsystems;
import java.util.ArrayList;
import java.util.Set;
import org.usfirst.frc4388.robot.Constants;
import org.usfirst.frc4388.robot.OI;
import org.usfirst.frc4388.robot.Robot;
import org.usfirst.frc4388.robot.RobotMap;
import org.usfirst.frc4388.utility.control.AdaptivePurePursuitController;
import org.usfirst.frc4388.utility.BHRMathUtils;
//import org.usfirst.frc4388.utility.CANTalonEncoder;
import org.usfirst.frc4388.utility.ControlLoopable;
import org.usfirst.frc4388.utility.control.Kinematics;
import org.usfirst.frc4388.utility.MPSoftwarePIDController;
import org.usfirst.frc4388.utility.MPSoftwarePIDController.MPSoftwareTurnType;
import org.usfirst.frc4388.utility.MPTalonPIDController;
import org.usfirst.frc4388.utility.MPTalonPIDPathController;
import org.usfirst.frc4388.utility.MPTalonPIDPathVelocityController;
//import org.usfirst.frc4388.utility.SoftwarePIDPositionController;
import org.usfirst.frc4388.utility.MotionProfilePoint;
//import org.usfirst.frc4388.utility.MotionProfileBoxCar;
import org.usfirst.frc4388.utility.PIDParams;
//import org.usfirst.frc4388.utility.Path;
//import org.usfirst.frc4388.utility.PathGenerator;
import org.usfirst.frc4388.utility.math.RigidTransform2d;
import org.usfirst.frc4388.utility.math.Rotation2d;
import org.usfirst.frc4388.utility.SoftwarePIDController;
import org.usfirst.frc4388.utility.math.Translation2d;
import com.ctre.phoenix.motorcontrol.can.WPI_TalonSRX;
import com.ctre.phoenix.motorcontrol.ControlMode;
import com.ctre.phoenix.motorcontrol.FeedbackDevice;
//import com.ctre.PigeonImu;
//import com.ctre.PigeonImu.CalibrationMode;
import com.ctre.phoenix.motorcontrol.NeutralMode;
import com.kauailabs.navx.frc.AHRS;
import com.revrobotics.CANEncoder;
import com.revrobotics.CANPIDController;
import com.revrobotics.CANSparkMax;
import com.revrobotics.CANSparkMax.IdleMode;
import com.revrobotics.CANSparkMaxLowLevel.MotorType;
import edu.wpi.first.wpilibj.DoubleSolenoid;
//import edu.wpi.first.wpilibj.DigitalInput;
//import edu.wpi.first.wpilibj.DriverStation;
import edu.wpi.first.wpilibj.SPI;
import edu.wpi.first.wpilibj.Timer;
import edu.wpi.first.wpilibj.drive.DifferentialDrive;
//import edu.wpi.first.wpilibj.Solenoid;
//import edu.wpi.first.wpilibj.SpeedControllerGroup;
import edu.wpi.first.wpilibj.command.Subsystem;
import edu.wpi.first.wpilibj.smartdashboard.SmartDashboard;
/**
*
*/
public class Drive extends Subsystem implements ControlLoopable
{
public static enum DriveControlMode { JOYSTICK, MP_STRAIGHT, MP_TURN, PID_TURN, HOLD, MANUAL, CLIMB, MP_PATH, MP_PATH_VELOCITY, MOTION_MAGIC, ADAPTIVE_PURSUIT, MOVE_POSITION_MAX_SCALE, MOVE_POSITION_LOW_SCALE, MOVE_POSITION_SWITCH, MOVE_POSITION_LOWEST, STOP_MOTORS, RAW};
public static enum SpeedShiftState { HI, LO };
public static enum ClimberState { DEPLOYED, RETRACTED };
public static final double TRACK_WIDTH_INCHES = Constants.kTrackWidthInches;
public static final double ENCODER_TICKS_TO_INCHES = Constants.kDriveEncoderTicksPerInch;
public static final double CLIMB_SPEED = 0.45;
public static final double VOLTAGE_RAMP_RATE = 150; // Volts per second
public static final double OPEN_LOOP_RAMP_SECONDS = 12.0 / VOLTAGE_RAMP_RATE; // Seconds from neutral to full
public static final double CLOSED_LOOP_RAMP_SECONDS = 12.0 / VOLTAGE_RAMP_RATE; // Seconds from neutral to full
// Motion profile max velocities and accel times
public static final double MAX_TURN_RATE_DEG_PER_SEC = 320;
public static final double MP_AUTON_MAX_STRAIGHT_VELOCITY_INCHES_PER_SEC = 120; //72;
public static final double MP_AUTON_MAX_BOILER_STRAIGHT_VELOCITY_INCHES_PER_SEC = 200;
public static final double MP_AUTON_MAX_LO_GEAR_STRAIGHT_VELOCITY_INCHES_PER_SEC = 320;
public static final double MP_AUTON_MAX_HIGH_GEAR_STRAIGHT_VELOCITY_INCHES_PER_SEC = 400;
public static final double MP_AUTON_MAX_TURN_RATE_DEG_PER_SEC = 270;
public static final double MP_AUTON_MAX_BOILER_TURN_RATE_DEG_PER_SEC = 400;
public static final double MP_GEAR_DEPLOY_VELOCITY_INCHES_PER_SEC = 25;
public static final double MP_GEAR_DEPLOY_FASTER_VELOCITY_INCHES_PER_SEC = 80;
public static final double MP_STRAIGHT_T1 = 600;
public static final double MP_STRAIGHT_T2 = 300;
public static final double MP_TURN_T1 = 600;
public static final double MP_TURN_T2 = 300;
public static final double MP_MAX_TURN_T1 = 400;
public static final double MP_MAX_TURN_T2 = 200;
// Motor controllers
private ArrayList<CANPIDController> motorControllers = new ArrayList<CANPIDController>();
private CANSparkMax leftDrive1;
private CANSparkMax leftDrive2;
private CANSparkMax rightDrive1;
private CANSparkMax rightDrive2;
private CANEncoder encoderLeft;
private CANEncoder encoderRight;
private DifferentialDrive m_drive;
//PID encoder and motor
//private DigitalInput hopperSensorRed;
//private DigitalInput hopperSensorBlue;
private double climbSpeed;
private boolean isRed = true;
private double periodMs;
private double lastControlLoopUpdatePeriod = 0.0;
private double lastControlLoopUpdateTimestamp = 0.0;
// Pneumatics
//private Solenoid speedShift;
// Input devices
public static final int DRIVER_INPUT_JOYSTICK_ARCADE = 0;
public static final int DRIVER_INPUT_JOYSTICK_TANK = 1;
public static final int DRIVER_INPUT_JOYSTICK_CHEESY = 2;
public static final int DRIVER_INPUT_XBOX_CHEESY = 3;
public static final int DRIVER_INPUT_XBOX_ARCADE_LEFT = 4;
public static final int DRIVER_INPUT_XBOX_ARCADE_RIGHT = 5;
public static final int DRIVER_INPUT_WHEEL = 6;
public static final double STEER_NON_LINEARITY = 0.5;
public static final double MOVE_NON_LINEARITY = 1.0;
public static final double STICK_DEADBAND = 0.02;
private int m_moveNonLinear = 0;
private int m_steerNonLinear = -3;
private double m_moveScale = 1.0;
private double m_steerScale = 1.0;
private double m_moveInput = 0.0;
private double m_steerInput = 0.0;
private double m_moveOutput = 0.0;
private double m_steerOutput = 0.0;
private double m_moveTrim = 0.0;
private double m_steerTrim = 0.0;
private boolean isFinished;
private DriveControlMode controlMode = DriveControlMode.JOYSTICK;
private MPTalonPIDController mpStraightController;
private PIDParams mpStraightPIDParams = new PIDParams(.7, 0.0, 0.000000, .001, 0.001, .7); // 4 colsons
// private PIDParams mpStraightPIDParams = new PIDParams(0.1, 0, 0, 0.005, 0.03, 0.03); // 4 omni
private PIDParams mpHoldPIDParams = new PIDParams(1, 0, 0, 0.0, 0.0, 0.0);
private MPSoftwarePIDController mpTurnController; // p i d a v g izone
private PIDParams mpTurnPIDParams = new PIDParams(0.07, 0.00002, 0.5, 0.00025, 0.008, 0.0, 100); // 4 colson wheels
// private PIDParams mpTurnPIDParams = new PIDParams(0.03, 0.00002, 0.4, 0.0004, 0.0030, 0.0, 100); // 4 omni
private SoftwarePIDController pidTurnController;
//private PIDParams pidTurnPIDParams = new PIDParams(0.04, 0.001, 0.4, 0, 0, 0.0, 100); //i=0.0008
private PIDParams pidTurnPIDParams = new PIDParams(0.04, 0.0, 0.0, 0, 0, 0.0, 100); //i=0.0008
private double targetPIDAngle;
private MPTalonPIDPathController mpPathController;
private PIDParams mpPathPIDParams = new PIDParams(0.1, 0, 0, 0.005, 0.03, 0.28, 100); // 4 omni g=.3
//PID target
private double targetPIDPosition;
private MPTalonPIDPathVelocityController mpPathVelocityController;
private PIDParams mpPathVelocityPIDParams = new PIDParams(0.5, 0.001, 5, 0.44);
private AdaptivePurePursuitController adaptivePursuitController;
private PIDParams adaptivePursuitPIDParams = new PIDParams(0.1, 0.00, 1, 0.44);
private RigidTransform2d zeroPose = new RigidTransform2d(new Translation2d(0,0), Rotation2d.fromDegrees(0));
private RigidTransform2d currentPose = zeroPose;
private RigidTransform2d lastPose = zeroPose;
private double left_encoder_prev_distance_ = 0;
private double right_encoder_prev_distance_ = 0;
//private PigeonImu gyroPigeon;
//private double[] yprPigeon = new double[3];
private AHRS gyroNavX;
private boolean useGyroLock;
private double gyroLockAngleDeg;
private double kPGyro = 0.07;
//private double kPGyro = 0.0625;
private boolean isCalibrating = false;
private double gyroOffsetDeg = 0;
public Drive() {
try {
//System.err.println("Beginning of Drive.");
leftDrive1 = new CANSparkMax(RobotMap.DRIVETRAIN_LEFT_MOTOR1_CAN_ID, MotorType.kBrushless);
//leftDrive1 = new CANSparkMax(RobotMap.DRIVETRAIN_LEFT_MOTOR1_CAN_ID, ENCODER_TICKS_TO_INCHES, false, FeedbackDevice.QuadEncoder);
leftDrive2 = new CANSparkMax(RobotMap.DRIVETRAIN_LEFT_MOTOR2_CAN_ID, MotorType.kBrushless);
encoderLeft = new CANEncoder(leftDrive1);
leftDrive2.follow(leftDrive1);
rightDrive1 = new CANSparkMax(RobotMap.DRIVETRAIN_RIGHT_MOTOR1_CAN_ID, MotorType.kBrushless);
rightDrive2 = new CANSparkMax(RobotMap.DRIVETRAIN_RIGHT_MOTOR2_CAN_ID, MotorType.kBrushless);
encoderRight = new CANEncoder(rightDrive1);
rightDrive2.follow(rightDrive1);
//System.err.println("After Constructors.");
//gyroPigeon = new PigeonImu(leftDrive2);
gyroNavX = new AHRS(SPI.Port.kMXP);
//hopperSensorRed = new DigitalInput(RobotMap.HOPPER_SENSOR_RED_DIO_ID);
//hopperSensorBlue = new DigitalInput(RobotMap.HOPPER_SENSOR_BLUE_DIO_ID);
//leftDrive1.clearStickyFaults();
//leftDrive1.setVoltageRampRate(VOLTAGE_RAMP_RATE);
//leftDrive1.setNominalClosedLoopVoltage(12.0);
leftDrive1.clearFaults();
leftDrive1.setInverted(false);//false on comp 2108, false on microbot
//leftDrive1.setSensorPhase(true);//true on comp 2108, false on microbot //not needed for spark
//leftDrive1.setSafetyEnabled(false); //not needed for spark
//leftDrive1.setCurrentLimit(15);
//leftDrive1.enableCurrentLimit(true);
leftDrive1.setIdleMode(IdleMode.kBrake);
//leftDrive1.configOpenloopRamp(OPEN_LOOP_RAMP_SECONDS, 0); // not needed for spark?
//leftDrive1.configClosedloopRamp(CLOSED_LOOP_RAMP_SECONDS, 0); // not needed for spark?
//leftDrive1.configNominalOutputForward(+1.0f, 0); // not needed for spark?
//leftDrive1.configNominalOutputReverse(-1.0f, 0); // not needed for spark?
// if (leftDrive1.isSensorPresent(CANTalon.FeedbackDevice.QuadEncoder) != CANTalon.FeedbackDeviceStatus.FeedbackStatusPresent) {
// Driver.reportError("Could not detect left drive encoder encoder!", false);
// }
leftDrive2.setInverted(false);//false on comp 2108, false on microbot
//leftDrive2.setSafetyEnabled(false);
leftDrive2.setIdleMode(IdleMode.kBrake);
//leftDrive2.set(ControlMode.Follower, leftDrive1.getDeviceID()); // set above
//rightDrive1.clearStickyFaults();
//rightDrive1.setVoltageRampRate(VOLTAGE_RAMP_RATE);
//rightDrive1.setNominalClosedLoopVoltage(12.0);
rightDrive1.clearFaults();
rightDrive1.setInverted(false);//true on comp 2108, false on microbot
//rightDrive1.setSensorPhase(true);//true on comp 2108, true on microbot
//rightDrive1.setSafetyEnabled(false);
rightDrive1.setIdleMode(IdleMode.kBrake);
//rightDrive1.configOpenloopRamp(OPEN_LOOP_RAMP_SECONDS, 0);
//rightDrive1.configClosedloopRamp(CLOSED_LOOP_RAMP_SECONDS, 0);
//rightDrive1.configNominalOutputForward(+1.0f, 0);
//rightDrive1.configNominalOutputReverse(-1.0f, 0);
// if (rightDrive1.isSensorPresent(CANTalon.FeedbackDevice.QuadEncoder) != CANTalon.FeedbackDeviceStatus.FeedbackStatusPresent) {
// DriverStation.reportError("Could not detect right drive encoder encoder!", false);
// }
rightDrive2.setInverted(false);//True on comp 2108, false on microbot
//rightDrive2.setSafetyEnabled(false);
rightDrive2.setIdleMode(IdleMode.kBrake);
//rightDrive2.set(ControlMode.Follower, rightDrive1.getDeviceID());
//System.err.println("After motor settings.");
CANPIDController leftDrive1_Controller = new CANPIDController(leftDrive1);
CANPIDController rightDrive1_Controller = new CANPIDController(rightDrive1);
motorControllers.add(leftDrive1_Controller);
motorControllers.add(rightDrive1_Controller);
//System.err.println("After motorControllers.");
//m_drive = new RobotDrive(leftDrive1, rightDrive1);
//m_drive.setInvertedMotor(RobotDrive.MotorType.kRearLeft, true);
//m_drive.setInvertedMotor(RobotDrive.MotorType.kRearRight, true);
//m_drive.setSafetyEnabled(false);
m_drive = new DifferentialDrive(leftDrive1, rightDrive1);
//m_drive.setInvertedMotor(DifferentialDrive.MotorType.kRearLeft, true); //TODO URGENT: verify
//m_drive.setInvertedMotor(DifferentialDrive.MotorType.kRearRight, true); //TODO URGENT: verify
m_drive.setSafetyEnabled(false);
//speedShift = new Solenoid(RobotMap.DRIVETRAIN_SPEEDSHIFT_PCM_ID);
//System.err.println("End of Drive.");
}
catch (Exception e) {
System.err.println("An error occurred in the DriveTrain constructor");
}
}
public void setToBrakeOnNeutral(boolean brakeVsCoast) {
if (brakeVsCoast) {
leftDrive1.setIdleMode(IdleMode.kBrake);
leftDrive2.setIdleMode(IdleMode.kBrake);
rightDrive1.setIdleMode(IdleMode.kBrake);
rightDrive2.setIdleMode(IdleMode.kBrake);
} else {
leftDrive1.setIdleMode(IdleMode.kCoast);
leftDrive2.setIdleMode(IdleMode.kCoast);
rightDrive1.setIdleMode(IdleMode.kCoast);
rightDrive2.setIdleMode(IdleMode.kCoast);
}
}
@Override
public void initDefaultCommand() {
}
public double getGyroAngleDeg() {
//return getGyroPigeonAngleDeg();
return getGyroNavXAngleDeg();
}
//public double getGyroPigeonAngleDeg() {
// gyroPigeon.GetYawPitchRoll(yprPigeon);
// return -yprPigeon[0] + gyroOffsetDeg;
//}
public double getGyroNavXAngleDeg() {
return gyroNavX.getAngle() + gyroOffsetDeg;
}
public void resetGyro() {
//gyroPigeon.SetYaw(0);
gyroNavX.zeroYaw();
}
public void resetEncoders() {
mpStraightController.resetZeroPosition();
}
public void calibrateGyro() {
//gyroPigeon.EnterCalibrationMode(CalibrationMode.Temperature);
}
public void endGyroCalibration() {
if (isCalibrating == true) {
isCalibrating = false;
}
}
public void setGyroOffset(double offsetDeg) {
gyroOffsetDeg = offsetDeg;
}
//public boolean isHopperSensorRedOn() {
// return hopperSensorRed.get();
//}
//public boolean isHopperSensorBlueOn() {
// return hopperSensorBlue.get();
//}
//public boolean isHopperSensorOn() {
// if (isRed() == true) {
// return isHopperSensorRedOn();
// }
// else {
// return isHopperSensorBlueOn();
// }
//}
/*
public void setStraightMP(double distanceInches, double maxVelocity, boolean useGyroLock, boolean useAbsolute, double desiredAbsoluteAngle) {
double yawAngle = useAbsolute ? BHRMathUtils.adjustAccumAngleToDesired(getGyroAngleDeg(), desiredAbsoluteAngle) : getGyroAngleDeg();
mpStraightController.setPID(mpStraightPIDParams);
mpStraightController.setMPStraightTarget(0, distanceInches, maxVelocity, MP_STRAIGHT_T1, MP_STRAIGHT_T2, useGyroLock, yawAngle, true);
setControlMode(DriveControlMode.MP_STRAIGHT);
}
*/
//public void setStraightMPCached(String key, boolean useGyroLock, boolean useAbsolute, double desiredAbsoluteAngle) {
// double yawAngle = useAbsolute ? BHRMathUtils.adjustAccumAngleToDesired(getGyroAngleDeg(), desiredAbsoluteAngle) : getGyroAngleDeg();
// mpStraightController.setPID(mpStraightPIDParams);
// mpStraightController.setMPStraightTarget(key, useGyroLock, yawAngle, true);
// setControlMode(DriveControlMode.MP_STRAIGHT);
//}
public void setRelativeTurnMP(double relativeTurnAngleDeg, double turnRateDegPerSec, MPSoftwareTurnType turnType) {
mpTurnController.setMPTurnTarget(getGyroAngleDeg(), relativeTurnAngleDeg + getGyroAngleDeg(), turnRateDegPerSec, MP_TURN_T1, MP_TURN_T2, turnType, TRACK_WIDTH_INCHES);
setControlMode(DriveControlMode.MP_TURN);
}
//public void setRelativeTurnMPCached(String key, MPSoftwareTurnType turnType) {
// mpTurnController.setMPTurnTarget(key, turnType, TRACK_WIDTH_INCHES);
// setControlMode(DriveControlMode.MP_TURN);
//}
public void setRelativeMaxTurnMP(double relativeTurnAngleDeg, double turnRateDegPerSec, MPSoftwareTurnType turnType) {
mpTurnController.setMPTurnTarget(getGyroAngleDeg(), relativeTurnAngleDeg + getGyroAngleDeg(), turnRateDegPerSec, MP_MAX_TURN_T1, MP_MAX_TURN_T2, turnType, TRACK_WIDTH_INCHES);
setControlMode(DriveControlMode.MP_TURN);
}
public void setAbsoluteTurnMP(double absoluteTurnAngleDeg, double turnRateDegPerSec, MPSoftwareTurnType turnType) {
mpTurnController.setMPTurnTarget(getGyroAngleDeg(), BHRMathUtils.adjustAccumAngleToDesired(getGyroAngleDeg(), absoluteTurnAngleDeg), turnRateDegPerSec, MP_TURN_T1, MP_TURN_T2, turnType, TRACK_WIDTH_INCHES);
setControlMode(DriveControlMode.MP_TURN);
}
//public void setAbsoluteTurnMPCached(String key, MPSoftwareTurnType turnType) {
// mpTurnController.setMPTurnTarget(key, turnType, TRACK_WIDTH_INCHES);
// setControlMode(DriveControlMode.MP_TURN);
//}
public void setRelativeTurnPID(double relativeTurnAngleDeg, double maxError, double maxPrevError, MPSoftwareTurnType turnType) {
this.targetPIDAngle = relativeTurnAngleDeg + getGyroAngleDeg();
pidTurnController.setPIDTurnTarget(relativeTurnAngleDeg + getGyroAngleDeg(), maxError, maxPrevError, turnType);
setControlMode(DriveControlMode.PID_TURN);
}
/*
public void setPathMP(PathGenerator path) {
mpPathController.setPID(mpPathPIDParams);
mpPathController.setMPPathTarget(path);
setControlMode(DriveControlMode.MP_PATH);
}
public void setPathVelocityMP(PathGenerator path) {
mpPathVelocityController.setPID(mpPathPIDParams);
mpPathVelocityController.setMPPathTarget(path);
setControlMode(DriveControlMode.MP_PATH_VELOCITY);
}
public void setPathAdaptivePursuit(Path path, boolean reversed) {
currentPose = zeroPose;
lastPose = zeroPose;
left_encoder_prev_distance_ = 0;
right_encoder_prev_distance_ = 0;
adaptivePursuitController.setPID(adaptivePursuitPIDParams);
adaptivePursuitController.setPath(Constants.kPathFollowingLookahead, Constants.kPathFollowingMaxAccel, path, reversed, 0.25);
setControlMode(DriveControlMode.ADAPTIVE_PURSUIT);
}
*/
public void setDriveHold(boolean status) {
if (status) {
setControlMode(DriveControlMode.HOLD);
}
else {
setControlMode(DriveControlMode.JOYSTICK);
}
}
public void updatePose() {
double left_distance = encoderLeft.getPosition();
double right_distance = encoderRight.getPosition();
Rotation2d gyro_angle = Rotation2d.fromDegrees(-getGyroAngleDeg());
lastPose = currentPose;
currentPose = generateOdometryFromSensors(left_distance - left_encoder_prev_distance_, right_distance - right_encoder_prev_distance_, gyro_angle);
left_encoder_prev_distance_ = left_distance;
right_encoder_prev_distance_ = right_distance;
}
public RigidTransform2d generateOdometryFromSensors(double left_encoder_delta_distance, double right_encoder_delta_distance, Rotation2d current_gyro_angle) {
return Kinematics.integrateForwardKinematics(lastPose, left_encoder_delta_distance, right_encoder_delta_distance, current_gyro_angle);
}
/**
* Path Markers are an optional functionality that name the various
* Waypoints in a Path with a String. This can make defining set locations
* much easier.
*
* @return Set of Strings with Path Markers that the robot has crossed.
*/
/*
public synchronized Set<String> getPathMarkersCrossed() {
if (controlMode != DriveControlMode.ADAPTIVE_PURSUIT) {
return null;
} else {
return adaptivePursuitController.getMarkersCrossed();
}
}*/
public synchronized void setControlMode(DriveControlMode controlMode) {
this.controlMode = controlMode;
if (controlMode == DriveControlMode.JOYSTICK) {
//leftDrive1.changeControlMode(TalonControlMode.PercentVbus);
//rightDrive1.changeControlMode(TalonControlMode.PercentVbus);
leftDrive1.set(0); //TODO URGENT: make sure not called when robot moving
rightDrive1.set(0); //TODO URGENT: make sure not called when robot moving
}
else if (controlMode == DriveControlMode.MANUAL) {
//leftDrive1.changeControlMode(TalonControlMode.PercentVbus);
//rightDrive1.changeControlMode(TalonControlMode.PercentVbus);
leftDrive1.set(0); //TODO URGENT: make sure not called when robot moving
rightDrive1.set(0); //TODO URGENT: make sure not called when robot moving
}
else if (controlMode == DriveControlMode.CLIMB) {
//leftDrive1.changeControlMode(TalonControlMode.PercentVbus);
//rightDrive1.changeControlMode(TalonControlMode.PercentVbus);
leftDrive1.set(0); //TODO URGENT: make sure not called when robot moving
rightDrive1.set(0); //TODO URGENT: make sure not called when robot moving
}
/*
else if (controlMode == DriveControlMode.HOLD) {
mpStraightController.setPID(mpHoldPIDParams);
//leftDrive1.changeControlMode(TalonControlMode.Position);
//leftDrive1.setPosition(0);
//leftDrive1.set(0);
//rightDrive1.changeControlMode(TalonControlMode.Position);
//rightDrive1.setPosition(0);
//rightDrive1.set(0);
//leftDrive1.setSelectedSensorPosition(0, 0, 0); //not needed for spark? TODO: verify want 0="Primary closed-loop", with no timeout
leftDrive1.set(0);
//rightDrive1.setSelectedSensorPosition(0, 0, 0); //not needed for spark? TODO: verify want 0="Primary closed-loop", with no timeout
rightDrive1.set(0);
}*/
isFinished = false;
}
public synchronized void controlLoopUpdate() {
// Measure *actual* update period
double currentTimestamp = Timer.getFPGATimestamp();
if (lastControlLoopUpdateTimestamp > 0.001) { // ie, if this is NOT the first time
lastControlLoopUpdatePeriod = currentTimestamp - lastControlLoopUpdateTimestamp;
}
lastControlLoopUpdateTimestamp = currentTimestamp;
// Do the update
if (controlMode == DriveControlMode.JOYSTICK || controlMode == DriveControlMode.CLIMB) {
driveWithJoystick();
}
else if (!isFinished) {
if (controlMode == DriveControlMode.MP_STRAIGHT) {
isFinished = mpStraightController.controlLoopUpdate(getGyroAngleDeg());
}
else if (controlMode == DriveControlMode.MP_TURN) {
isFinished = mpTurnController.controlLoopUpdate(getGyroAngleDeg());
}
else if (controlMode == DriveControlMode.PID_TURN) {
isFinished = pidTurnController.controlLoopUpdate(getGyroAngleDeg());
}
else if (controlMode == DriveControlMode.MP_PATH) {
isFinished = mpPathController.controlLoopUpdate(getGyroAngleDeg());
}
/*else if (controlMode == DriveControlMode.MP_PATH_VELOCITY) {
isFinished = mpPathVelocityController.controlLoopUpdate(getGyroAngleDeg());
}
else if (controlMode == DriveControlMode.ADAPTIVE_PURSUIT) {
updatePose();
isFinished = adaptivePursuitController.controlLoopUpdate(currentPose);
}*/
}
}
public void setSpeed(double speed) {
if (speed == 0) {
setControlMode(DriveControlMode.JOYSTICK);
}
else {
setControlMode(DriveControlMode.MANUAL);
rightDrive1.set(-speed);
leftDrive1.set(speed);
}
}
public void setClimbSpeed(double climbSpeed) {
this.climbSpeed = climbSpeed;
if (climbSpeed == 0) {
setControlMode(DriveControlMode.JOYSTICK);
}
else {
setControlMode(DriveControlMode.CLIMB);
}
}
public void setGyroLock(boolean useGyroLock, boolean snapToAbsolute0or180) {
if (snapToAbsolute0or180) {
gyroLockAngleDeg = BHRMathUtils.adjustAccumAngleToClosest180(getGyroAngleDeg());
}
else {
gyroLockAngleDeg = getGyroAngleDeg();
}
this.useGyroLock = useGyroLock;
}
public void driveWithJoystick() {
if(m_drive == null) return;
// switch(m_controllerMode) {
// case CONTROLLER_JOYSTICK_ARCADE:
// m_moveInput = OI.getInstance().getJoystick1().getY();
// m_steerInput = OI.getInstance().getJoystick1().getX();
// m_moveOutput = adjustForSensitivity(m_moveScale, m_moveTrim,
// m_moveInput, m_moveNonLinear, MOVE_NON_LINEARITY);
// m_steerOutput = adjustForSensitivity(m_steerScale, m_steerTrim,
// m_steerInput, m_steerNonLinear, STEER_NON_LINEARITY);
// m_drive.arcadeDrive(m_moveOutput, m_steerOutput);
// break;
// case CONTROLLER_JOYSTICK_TANK:
// m_moveInput = OI.getInstance().getJoystick1().getY();
// m_steerInput = OI.getInstance().getJoystick2().getY();
// m_moveOutput = adjustForSensitivity(m_moveScale, m_moveTrim,
// m_moveInput, m_moveNonLinear, MOVE_NON_LINEARITY);
// m_steerOutput = adjustForSensitivity(m_moveScale, m_moveTrim,
// m_steerInput, m_moveNonLinear, MOVE_NON_LINEARITY);
// m_drive.tankDrive(m_moveOutput, m_steerOutput);
// break;
// case CONTROLLER_JOYSTICK_CHEESY:
// m_moveInput = OI.getInstance().getJoystick1().getY();
// m_steerInput = OI.getInstance().getJoystick2().getX();
// m_moveOutput = adjustForSensitivity(m_moveScale, m_moveTrim,
// m_moveInput, m_moveNonLinear, MOVE_NON_LINEARITY);
// m_steerOutput = adjustForSensitivity(m_steerScale, m_steerTrim,
// m_steerInput, m_steerNonLinear, STEER_NON_LINEARITY);
// m_drive.arcadeDrive(m_moveOutput, m_steerOutput);
// break;
// case CONTROLLER_XBOX_CHEESY:
// boolean turbo = OI.getInstance().getDriveTrainController()
// .getLeftJoystickButton();
// boolean slow = OI.getInstance().getDriveTrainController()
// .getRightJoystickButton();
// double speedToUseMove, speedToUseSteer;
// if (turbo && !slow) {
// speedToUseMove = m_moveScaleTurbo;
// speedToUseSteer = m_steerScaleTurbo;
// } else if (!turbo && slow) {
// speedToUseMove = m_moveScaleSlow;
// speedToUseSteer = m_steerScaleSlow;
// } else {
// speedToUseMove = m_moveScale;
// speedToUseSteer = m_steerScale;
// }
// m_moveInput =
// OI.getInstance().getDriveTrainController().getLeftYAxis();
// m_steerInput =
// OI.getInstance().getDriveTrainController().getRightXAxis();
m_moveInput = -OI.getInstance().getDriverController().getLeftYAxis();
m_steerInput = OI.getInstance().getDriverController().getRightXAxis();
if (controlMode == DriveControlMode.JOYSTICK) {
m_moveOutput = adjustForSensitivity(m_moveScale, m_moveTrim,
m_moveInput, m_moveNonLinear, MOVE_NON_LINEARITY);
}
else if (controlMode == DriveControlMode.CLIMB) {
m_moveOutput = climbSpeed;
}
if (useGyroLock) { // If currently in gyro lock mode,
if ((m_moveInput == 0.0) || (m_steerInput != 0.0)) { // but stopped driving or started turning,
setGyroLock(false, false); // turn off gyro lock mode
}
} else { // If not yet in gyro lock mode,
if ((m_moveInput != 0.0) && (m_steerInput == 0.0)) { // but just started driving without turning,
setGyroLock(true, false); // gyro lock to current heading
}
}
if (useGyroLock) {
double yawError = gyroLockAngleDeg - getGyroAngleDeg();
m_steerOutput = kPGyro * yawError;
} else {
m_steerOutput = adjustForSensitivity(m_steerScale, m_steerTrim,
m_steerInput, m_steerNonLinear, STEER_NON_LINEARITY);
}
m_drive.arcadeDrive(m_moveOutput, m_steerOutput*.75);
// break;
// case CONTROLLER_XBOX_ARCADE_RIGHT:
// m_moveInput =
// OI.getInstance().getDrivetrainController().getRightYAxis();
// m_steerInput =
// OI.getInstance().getDrivetrainController().getRightXAxis();
// m_moveOutput = adjustForSensitivity(m_moveScale, m_moveTrim,
// m_moveInput, m_moveNonLinear, MOVE_NON_LINEARITY);
// m_steerOutput = adjustForSensitivity(m_steerScale, m_steerTrim,
// m_steerInput, m_steerNonLinear, STEER_NON_LINEARITY);
// m_drive.arcadeDrive(m_moveOutput, m_steerOutput);
// break;
// case CONTROLLER_XBOX_ARCADE_LEFT:
// m_moveInput =
// OI.getInstance().getDrivetrainController().getLeftYAxis();
// m_steerInput =
// OI.getInstance().getDrivetrainController().getLeftXAxis();
// m_moveOutput = adjustForSensitivity(m_moveScale, m_moveTrim,
// m_moveInput, m_moveNonLinear, MOVE_NON_LINEARITY);
// m_steerOutput = adjustForSensitivity(m_steerScale, m_steerTrim,
// m_steerInput, m_steerNonLinear, STEER_NON_LINEARITY);
// m_drive.arcadeDrive(m_moveOutput, m_steerOutput);
// break;
// }
}
public void rawMoveSteer(double move, double steer) {
m_drive.arcadeDrive(move, steer, false);
}
public void rawDriveLeftRight(double leftPercentOutput, double rightPercentOutput) {
leftDrive1.set(leftPercentOutput);
rightDrive1.set(rightPercentOutput);
}
private boolean inDeadZone(double input) {
boolean inDeadZone;
if (Math.abs(input) < STICK_DEADBAND) {
inDeadZone = true;
} else {
inDeadZone = false;
}
return inDeadZone;
}
public double adjustForSensitivity(double scale, double trim,
double steer, int nonLinearFactor, double wheelNonLinearity) {
if (inDeadZone(steer))
return 0;
steer += trim;
steer *= scale;
steer = limitValue(steer);
int iterations = Math.abs(nonLinearFactor);
for (int i = 0; i < iterations; i++) {
if (nonLinearFactor > 0) {
steer = nonlinearStickCalcPositive(steer, wheelNonLinearity);
} else {
steer = nonlinearStickCalcNegative(steer, wheelNonLinearity);
}
}
return steer;
}
private double limitValue(double value) {
if (value > 1.0) {
value = 1.0;
} else if (value < -1.0) {
value = -1.0;
}
return value;
}
private double nonlinearStickCalcPositive(double steer,
double steerNonLinearity) {
return Math.sin(Math.PI / 2.0 * steerNonLinearity * steer)
/ Math.sin(Math.PI / 2.0 * steerNonLinearity);
}
private double nonlinearStickCalcNegative(double steer,
double steerNonLinearity) {
return Math.asin(steerNonLinearity * steer)
/ Math.asin(steerNonLinearity);
}
//public void setShiftState(SpeedShiftState state) {
// if(state == SpeedShiftState.HI) {
// speedShift.set(true);
// }
// else if(state == SpeedShiftState.LO) {
// speedShift.set(false);
// }
//}
public synchronized boolean isFinished() {
return isFinished;
}
public synchronized void setFinished(boolean isFinished) {
this.isFinished = isFinished;
}
@Override
public void setPeriodMs(long periodMs) {
/* FIX TODAY
mpStraightController = new MPTalonPIDController(periodMs, mpStraightPIDParams, motorControllers);
mpTurnController = new MPSoftwarePIDController(periodMs, mpTurnPIDParams, motorControllers);
pidTurnController = new SoftwarePIDController(pidTurnPIDParams, motorControllers);
mpPathController = new MPTalonPIDPathController(periodMs, mpPathPIDParams, motorControllers);
mpPathVelocityController = new MPTalonPIDPathVelocityController(periodMs, mpPathVelocityPIDParams, motorControllers);
adaptivePursuitController = new AdaptivePurePursuitController(periodMs, adaptivePursuitPIDParams, motorControllers);
*/
this.periodMs = periodMs;
}
public double getPeriodMs() {
return periodMs;
}
public boolean isRed() {
return isRed;
}
public void setIsRed(boolean status) {
isRed = status;
}
public static double rotationsToInches(double rotations) {
return rotations * (Constants.kDriveWheelDiameterInches * Math.PI);
}
public static double rpmToInchesPerSecond(double rpm) {
return rotationsToInches(rpm) / 60;
}
public static double inchesToRotations(double inches) {
return inches / (Constants.kDriveWheelDiameterInches * Math.PI);
}
public static double inchesPerSecondToRpm(double inches_per_second) {
return inchesToRotations(inches_per_second) * 60;
}
public double getLeftPositionWorld() {
return 0;//leftDrive1.getPositionWorld(); FIX TODAY
}
public double getRightPositionWorld() {
return 0;//rightDrive1.getPositionWorld(); FIX TODAY
}
public void updateStatus(Robot.OperationMode operationMode) {
if (operationMode == Robot.OperationMode.TEST) {
try {
SmartDashboard.putNumber("Gyro Value", getGyroAngleDeg());
SmartDashboard.putNumber("Update Period (ms)", lastControlLoopUpdatePeriod * 1000.0);
SmartDashboard.putNumber("Right Pos Ticks", 0);//rightDrive1.getSelectedSensorPosition(0)); FIX TODAY
SmartDashboard.putNumber("Left Pos Ticks", 0);//leftDrive1.getSelectedSensorPosition(0));
SmartDashboard.putNumber("Right Pos Inches", -encoderRight.getPosition());//rightDrive1.getPositionWorld());
SmartDashboard.putNumber("Left Pos Inches", encoderLeft.getPosition());//leftDrive1.getPositionWorld());
SmartDashboard.putNumber("Right Vel Ft-Sec", 0);//rightDrive1.getVelocityWorld() / 12);
SmartDashboard.putNumber("Left Vel Ft-Sec", 0);//leftDrive1.getVelocityWorld() / 12);
//SmartDashboard.putNumber("Left 1 Amps", Robot.pdp.getCurrent(RobotMap.DRIVETRAIN_LEFT_MOTOR1_CAN_ID));
//SmartDashboard.putNumber("Left 2 Amps", Robot.pdp.getCurrent(RobotMap.DRIVETRAIN_LEFT_MOTOR2_CAN_ID));
// SmartDashboard.putNumber("Left 3 Amps", Robot.pdp.getCurrent(RobotMap.DRIVETRAIN_LEFT_MOTOR3_CAN_ID));
//SmartDashboard.putNumber("Right 1 Amps", Robot.pdp.getCurrent(RobotMap.DRIVETRAIN_RIGHT_MOTOR1_CAN_ID));
//SmartDashboard.putNumber("Right 2 Amps", Robot.pdp.getCurrent(RobotMap.DRIVETRAIN_RIGHT_MOTOR2_CAN_ID));
// SmartDashboard.putNumber("Right 3 Amps", Robot.pdp.getCurrent(RobotMap.DRIVETRAIN_RIGHT_MOTOR3_CAN_ID));
//SmartDashboard.putBoolean("Hopper Sensor Red", isHopperSensorRedOn());
//SmartDashboard.putBoolean("Hopper Sensor Blue", isHopperSensorBlueOn());
SmartDashboard.putBoolean("Drive Hold", controlMode == DriveControlMode.HOLD);
//SmartDashboard.putNumber("Yaw Angle Pigeon Deg", getGyroPigeonAngleDeg());
SmartDashboard.putNumber("Yaw Angle Deg", Math.abs(getGyroAngleDeg() % 360));
MotionProfilePoint mpPoint = mpTurnController.getCurrentPoint();
double delta = mpPoint != null ? getGyroAngleDeg() - mpTurnController.getCurrentPoint().position : 0;
SmartDashboard.putNumber("Gyro Delta", delta);
//SmartDashboard.putBoolean("Gyro Calibrating", isCalibrating);
SmartDashboard.putBoolean("Gyro Calibrating", gyroNavX.isCalibrating());
SmartDashboard.putNumber("Gyro Offset", gyroOffsetDeg);
SmartDashboard.putNumber("Delta PID Angle", targetPIDAngle - getGyroAngleDeg());
SmartDashboard.putNumber("Steer Output", m_steerOutput);
SmartDashboard.putNumber("Move Output", m_moveOutput);
SmartDashboard.putNumber("Steer Input", m_steerInput);
SmartDashboard.putNumber("Move Input", m_moveInput);
SmartDashboard.putString("MODE", "TEST");
if (encoderLeft.getPosition() != 0 && encoderRight.getPosition() != 0){
SmartDashboard.putNumber("Distance Inches", (encoderLeft.getPosition()-encoderRight.getPosition())/2);//rightDrive1.getPositionWorld());
}
}
catch (Exception e) {
System.err.println("Drivetrain update status error");
}
}
else if (operationMode == Robot.OperationMode.COMPETITION) {
SmartDashboard.putNumber("Yaw Angle Deg", Math.abs(getGyroAngleDeg() % 360));
SmartDashboard.putString("MODE", "SICKO");
if (encoderLeft.getPosition() != 0 && encoderRight.getPosition() != 0){
SmartDashboard.putNumber("Distance Inches", (encoderLeft.getPosition()-encoderRight.getPosition())/2);//rightDrive1.getPositionWorld());
}
SmartDashboard.putNumber("Update Period (ms)", lastControlLoopUpdatePeriod * 1000.0);
}
}
}
@@ -0,0 +1,66 @@
package org.usfirst.frc4388.robot.subsystems;
import org.usfirst.frc4388.robot.Robot;
import org.usfirst.frc4388.robot.RobotMap;
import edu.wpi.first.wpilibj.DoubleSolenoid;
import edu.wpi.first.wpilibj.command.Subsystem;
public class Pnumatics extends Subsystem {
private DoubleSolenoid speedShift;
private DoubleSolenoid hatchIntake;
private DoubleSolenoid ballIntake;
private DoubleSolenoid wrist;
public Pnumatics() {
try {
speedShift = new DoubleSolenoid(1,0,1);
hatchIntake = new DoubleSolenoid(1,2,3);
ballIntake = new DoubleSolenoid(1,4,5);
wrist = new DoubleSolenoid(1,6,7);
}
catch (Exception e) {
System.err.println("An error occurred in the Pnumatics constructor");
}
}
public void setShiftState(boolean state) {
if (state==true) {
speedShift.set(DoubleSolenoid.Value.kForward);
}
if (state==false) {
speedShift.set(DoubleSolenoid.Value.kReverse);
}
}
public void setHatchIntakeState(boolean state) {
if (state==true) {
hatchIntake.set(DoubleSolenoid.Value.kForward);
}
if (state==false) {
hatchIntake.set(DoubleSolenoid.Value.kReverse);
}
}
public void setBallIntake(boolean state) {
if (state==true) {
ballIntake.set(DoubleSolenoid.Value.kForward);
}
if (state==false) {
ballIntake.set(DoubleSolenoid.Value.kReverse);
}
}
public void setWrist(boolean state) {
if (state==true) {
wrist.set(DoubleSolenoid.Value.kForward);
}
if (state==false) {
wrist.set(DoubleSolenoid.Value.kReverse);
}
}
public void initDefaultCommand() {
}
}
@@ -0,0 +1,356 @@
package org.usfirst.frc4388.robot.subsystems;
import java.util.ArrayList;
import org.usfirst.frc4388.robot.Constants;
import org.usfirst.frc4388.robot.Robot;
import org.usfirst.frc4388.robot.RobotMap;
import org.usfirst.frc4388.utility.ControlLoopable;
import org.usfirst.frc4388.utility.Loop;
import org.usfirst.frc4388.utility.MPTalonPIDController;
import org.usfirst.frc4388.utility.PIDParams;
import org.usfirst.frc4388.utility.TalonSRXEncoder;
import org.usfirst.frc4388.utility.TalonSRXFactory;
import com.ctre.phoenix.motorcontrol.ControlMode;
import com.ctre.phoenix.motorcontrol.FeedbackDevice;
import com.ctre.phoenix.motorcontrol.can.TalonSRX;
import com.ctre.phoenix.motorcontrol.LimitSwitchSource;
import com.ctre.phoenix.motorcontrol.LimitSwitchNormal;
import com.ctre.phoenix.motorcontrol.RemoteLimitSwitchSource;
import com.ctre.phoenix.motorcontrol.NeutralMode;
import com.ctre.phoenix.motorcontrol.SensorCollection;
import com.ctre.phoenix.motorcontrol.can.WPI_TalonSRX;
import edu.wpi.first.wpilibj.Solenoid;
import edu.wpi.first.wpilibj.command.Subsystem;
import edu.wpi.first.wpilibj.smartdashboard.SmartDashboard;
import edu.wpi.first.wpilibj.Timer;
public class Wrist extends Subsystem implements ControlLoopable
{
private static Wrist instance;
public static enum WristControlMode { MOTION_PROFILE, JOYSTICK_PID, JOYSTICK_MANUAL, MANUAL };
// One revolution of the 30T Drive 1.880" PD pulley = Pi * PD inches = 36/24 revs due to pulleys * 34/24 revs due to gears * 36/12 revs due mag encoder gear on ball shifter * 4096 ticks
public static final double ENCODER_TICKS_TO_INCHES = ((360/4096)/(3))-60;
private double periodMs;
private double lastControlLoopUpdatePeriod = 0.0;
private double lastControlLoopUpdateTimestamp = 0.0;
// Defined speeds
public static final double CLIMB_SPEED = -1.0;
public static final double TEST_SPEED_UP = 0.3;
public static final double TEST_SPEED_DOWN = -0.3;
public static final double AUTO_ZERO_SPEED = -0.3;
public static final double JOYSTICK_INCHES_PER_MS_HI = 0.75;
public static final double JOYSTICK_INCHES_PER_MS_LO = JOYSTICK_INCHES_PER_MS_HI/3.68 * 0.8;
// Defined positions
public static final double ZERO_POSITION_AUTON_FORWARD_INCHES = 8.0;
public static final double ZERO_POSITION_INCHES = -0.25;
public static final double NEAR_ZERO_POSITION_INCHES = 0.0;
public static final double MIN_POSITION_INCHES = 0.0;
public static final double MAX_POSITION_INCHES = 83.4;
public static final double AFTER_INTAKE_POSITION_INCHES = 4.0;
public static final double SWITCH_POSITION_INCHES = 24.0;
public static final double SWITCH_POSITION_HIGH_INCHES = 36.0; //Switch Position for First Cube APR
public static final double SCALE_LOW_POSITION_INCHES = 56.0;
public static final double SCALE_FIRST_CUBE_POSITION_INCHES = 78.0; //72.0
public static final double SCALE_SECOND_CUBE_POSITION_INCHES = 77.0;
public static final double SCALE_HIGH_POSITION_INCHES = MAX_POSITION_INCHES;
public static final double CLIMB_BAR_POSITION_INCHES = 70.0;
public static final double CLIMB_HIGH_POSITION_INCHES = 10.0;
public static final double CLIMB_ASSIST_POSITION_INCHES = 50.0;
// Motion profile max velocities and accel times
public static final double MP_MAX_VELOCITY_INCHES_PER_SEC = 60;
public static final double MP_T1 = 400; // Fast = 300
public static final double MP_T2 = 150; // Fast = 150
// Motor controllers
private ArrayList<TalonSRXEncoder> motorControllers = new ArrayList<TalonSRXEncoder>();
private TalonSRXEncoder wristmotor1;
// PID controller and params
private MPTalonPIDController mpController;
public static int PID_SLOT = 0;
public static int MP_SLOT = 1;
private PIDParams mpPIDParams = new PIDParams(0.2, 0.0, 0.0, 0.0, 0.005, 0.0);
private PIDParams pidPIDParamsHiGear = new PIDParams(0.075, 0.0, 0.0, 0.0, 0.0, 0.0);
public static final double KF_UP = 0.005;
public static final double KF_DOWN = 0.0;
public static final double PID_ERROR_INCHES = 1.0;
LimitSwitchSource limitSwitchSource;
// Pneumatics
private Solenoid speedShift;
private WristControlMode controlMode = WristControlMode.JOYSTICK_MANUAL;
// Misc
public static final double AUTO_ZERO_MOTOR_CURRENT = 4.0;
private boolean isFinished;
private WristControlMode wristControlMode = WristControlMode.JOYSTICK_MANUAL;
private double targetPositionInchesPID = 0;
private boolean firstMpPoint;
private double joystickInchesPerMs = JOYSTICK_INCHES_PER_MS_LO;
public Wrist() {
try {
wristmotor1 = TalonSRXFactory.createTalonEncoder(RobotMap.WRIST_MOTOR_ID, (ENCODER_TICKS_TO_INCHES), false, FeedbackDevice.QuadEncoder);
System.err.println("the tallon shold be made in wrist");
wristmotor1.setInverted(true);
// if (motor1.isSensorPresent(CANTalon.FeedbackDevice.QuadEncoder) != CANTalon.FeedbackDeviceStatus.FeedbackStatusPresent) {
// Driver.reportError("Could not detect elevator motor 1 encoder encoder!", false);
// }
wristmotor1.configForwardLimitSwitchSource(limitSwitchSource, LimitSwitchNormal.NormallyOpen, 0);
wristmotor1.configReverseLimitSwitchSource(limitSwitchSource, LimitSwitchNormal.NormallyOpen, 0);
wristmotor1.setNeutralMode(NeutralMode.Brake);
motorControllers.add(wristmotor1);
}
catch (Exception e) {
System.err.println("An error occurred in the Wrist constructor");
}
}
@Override
public void initDefaultCommand() {
}
public void resetZeroPosition(double position) {
mpController.resetZeroPosition(position);
}
private synchronized void setWristControlMode(WristControlMode controlMode) {
this.wristControlMode = controlMode;
}
private synchronized WristControlMode getWristControlMode() {
return this.wristControlMode;
}
public void setSpeed(double speed) {
wristmotor1.set(ControlMode.PercentOutput, speed);
setWristControlMode(WristControlMode.MANUAL);
}
public void setSpeedJoystick(double speed) {
wristmotor1.set(ControlMode.PercentOutput, speed);
setWristControlMode(WristControlMode.JOYSTICK_MANUAL);
}
public void setPositionPID(double targetPositionInches) {
mpController.setPIDSlot(PID_SLOT);
updatePositionPID(targetPositionInches);
setWristControlMode(WristControlMode.JOYSTICK_PID);
setFinished(false);
}
public void updatePositionPID(double targetPositionInches) {
targetPositionInchesPID = limitPosition(targetPositionInches);
double startPositionInches = wristmotor1.getPositionWorld();
mpController.setTarget(targetPositionInchesPID, targetPositionInchesPID > startPositionInches ? KF_UP : KF_DOWN);
}
public void setPositionMP(double targetPositionInches) {
double startPositionInches = wristmotor1.getPositionWorld();
mpController.setMPTarget(startPositionInches, limitPosition(targetPositionInches), MP_MAX_VELOCITY_INCHES_PER_SEC, MP_T1, MP_T2);
setFinished(false);
firstMpPoint = true;
setWristControlMode(WristControlMode.MOTION_PROFILE);
}
private double limitPosition(double targetPosition) {
if (targetPosition < MIN_POSITION_INCHES) {
return MIN_POSITION_INCHES;
}
else if (targetPosition > MAX_POSITION_INCHES) {
return MAX_POSITION_INCHES;
}
return targetPosition;
}
@Override
public void setPeriodMs(long periodMs) {
mpController = new MPTalonPIDController(periodMs, motorControllers);
mpController.setPID(mpPIDParams, MP_SLOT);
mpController.setPID(pidPIDParamsHiGear, PID_SLOT);
mpController.setPIDSlot(PID_SLOT);
mpController.setPID(pidPIDParamsHiGear, PID_SLOT);
mpController.setPIDSlot(PID_SLOT);
this.periodMs = periodMs;
}
/*@Override
public void onStart(double timestamp) {
mpController = new MPTalonPIDController(periodMs, motorControllers);
mpController.setPID(mpPIDParams, MP_SLOT);
mpController.setPID(pidPIDParamsHiGear, PID_SLOT);
mpController.setPIDSlot(PID_SLOT);
mpController.setPID(pidPIDParamsHiGear, PID_SLOT);
mpController.setPIDSlot(PID_SLOT);
}
@Override
public void onStop(double timestamp) {
// TODO Auto-generated method stub
}
@Override
public void onLoop(double timestamp) {
synchronized (Wrist.this) {
switch( getElevatorControlMode() ) {
case JOYSTICK_PID:
controlPidWithJoystick();
break;
case JOYSTICK_MANUAL:
controlManualWithJoystick();
break;
case MOTION_PROFILE:
if (!isFinished()) {
if (firstMpPoint) {
mpController.setPIDSlot(MP_SLOT);
firstMpPoint = false;
}
setFinished(mpController.controlLoopUpdate());
if (isFinished()) {
mpController.setPIDSlot(PID_SLOT);
}
}
break;
default:
break;
}
}
}
*/
public synchronized void controlLoopUpdate() {
// Measure *actual* update period
double currentTimestamp = Timer.getFPGATimestamp();
if (lastControlLoopUpdateTimestamp > 0.001) { // ie, if this is NOT the first time
lastControlLoopUpdatePeriod = currentTimestamp - lastControlLoopUpdateTimestamp;
}
lastControlLoopUpdateTimestamp = currentTimestamp;
// Do the update
if (controlMode == WristControlMode.JOYSTICK_MANUAL) {
controlManualWithJoystick();
}
else if (!isFinished) {
if (controlMode == WristControlMode.MOTION_PROFILE) {
isFinished = mpController.controlLoopUpdate(getPositionInches());
}
/*else if (controlMode == WristControlMode.MP_PATH_VELOCITY) {
isFinished = mpPathVelocityController.controlLoopUpdate(getGyroAngleDeg());
}
else if (controlMode == WristControlMode.ADAPTIVE_PURSUIT) {
updatePose();
isFinished = adaptivePursuitController.controlLoopUpdate(currentPose);
}*/
}
}
private void controlPidWithJoystick() {
double joystickPosition = -Robot.oi.getOperatorController().getRightYAxis();
double deltaPosition = joystickPosition * joystickInchesPerMs;
targetPositionInchesPID = targetPositionInchesPID + deltaPosition;
updatePositionPID(targetPositionInchesPID);
}
private void ControlWithJoystickhold(){
double holdPosition = wristmotor1.getPositionWorld();
updatePositionPID(holdPosition);
}
private void controlManualWithJoystick() {
double joyStickSpeed = -Robot.oi.getOperatorController().getRightYAxis();
setSpeedJoystick(joyStickSpeed*.50);
}
/*
public void setShiftState(ElevatorSpeedShiftState state) {
joystickInchesPerMs = JOYSTICK_INCHES_PER_MS_HI;
speedShift.set(true);
mpController.setPID(pidPIDParamsHiGear, PID_SLOT);
}
else if(state == ElevatorSpeedShiftState.LO) {
joystickInchesPerMs = JOYSTICK_INCHES_PER_MS_LO;
speedShift.set(false);
mpController.setPID(pidPIDParamsLoGear, PID_SLOT);
}
}
public ElevatorSpeedShiftState getShiftState() {
return shiftState;
}
*/
public double getPositionInches() {
return wristmotor1.getPositionWorld();
}
// public double getAverageMotorCurrent() {
// return (Robot.pdp.getCurrent(RobotMap.ELEVATOR_MOTOR_1_CAN_ID) + Robot.pdp.getCurrent(RobotMap.ELEVATOR_MOTOR_2_CAN_ID) + Robot.pdp.getCurrent(RobotMap.ELEVATOR_MOTOR_3_CAN_ID)) / 3;
// }
public double getAverageMotorCurrent() {
return (wristmotor1.getOutputCurrent());
}
public synchronized boolean isFinished() {
return isFinished;
}
public synchronized void setFinished(boolean isFinished) {
this.isFinished = isFinished;
}
public double getPeriodMs() {
return periodMs;
}
public void updateStatus(Robot.OperationMode operationMode) {
if (operationMode == Robot.OperationMode.TEST) {
try {
SmartDashboard.putNumber("Elevator Position Inches", wristmotor1.getPositionWorld());
SmartDashboard.putNumber("Elevator Motor 1 Amps", wristmotor1.getOutputCurrent());
SmartDashboard.putNumber("Elevator Average Amps", getAverageMotorCurrent());
// SmartDashboard.putNumber("Elevator Motor 1 Amps PDP", Robot.pdp.getCurrent(RobotMap.ELEVATOR_MOTOR_1_CAN_ID));
// SmartDashboard.putNumber("Elevator Motor 2 Amps PDP", Robot.pdp.getCurrent(RobotMap.ELEVATOR_MOTOR_2_CAN_ID));
// SmartDashboard.putNumber("Elevator Motor 3 Amps PDP", Robot.pdp.getCurrent(RobotMap.ELEVATOR_MOTOR_3_CAN_ID));
SmartDashboard.putNumber("Elevator Target PID Position", targetPositionInchesPID);
}
catch (Exception e) {
}
}
}
public static Wrist getInstance() {
if(instance == null) {
instance = new Wrist();
}
return instance;
}
}
@@ -0,0 +1,216 @@
package org.usfirst.frc4388.utility;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.nio.file.Files;
import java.nio.file.StandardOpenOption;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Set;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;
/**
* ConstantsBase
*
* Base class for storing robot constants. Anything stored as a public static field will be reflected and be able to set
* externally
*/
public abstract class ConstantsBase {
HashMap<String, Boolean> modifiedKeys = new HashMap<String, Boolean>();
public abstract String getFileLocation();
public static class Constant {
public String name;
public Class<?> type;
public Object value;
public Constant(String name, Class<?> type, Object value) {
this.name = name;
this.type = type;
this.value = value;
}
@Override
public boolean equals(Object o) {
String itsName = ((Constant) o).name;
Class<?> itsType = ((Constant) o).type;
Object itsValue = ((Constant) o).value;
return o instanceof Constant && this.name.equals(itsName) && this.type.equals(itsType)
&& this.value.equals(itsValue);
}
}
public File getFile() {
String filePath = getFileLocation();
filePath = filePath.replaceFirst("^~", System.getProperty("user.home"));
return new File(filePath);
}
public boolean truncateUserConstants() {
try {
Files.write(getFile().toPath(), new byte[0], StandardOpenOption.TRUNCATE_EXISTING);
loadFromFile();
return true;
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return false;
}
}
public boolean setConstant(String name, Double value) {
return setConstantRaw(name, value);
}
public boolean setConstant(String name, Integer value) {
return setConstantRaw(name, value);
}
public boolean setConstant(String name, String value) {
return setConstantRaw(name, value);
}
private boolean setConstantRaw(String name, Object value) {
boolean success = false;
Field[] declaredFields = this.getClass().getDeclaredFields();
for (Field field : declaredFields) {
if (java.lang.reflect.Modifier.isStatic(field.getModifiers()) && field.getName().equals(name)) {
try {
Object current = field.get(this);
field.set(this, value);
success = true;
if (!value.equals(current)) {
modifiedKeys.put(name, true);
System.out.println("Constant Modified:" + field.getName());
} else {
System.out.println("Constant Not Modified:" + field.getName());
}
} catch (IllegalArgumentException | IllegalAccessException e) {
System.out.println("Could not set field: " + name);
}
}
}
return success;
}
public Object getValueForConstant(String name) throws Exception {
Field[] declaredFields = this.getClass().getDeclaredFields();
for (Field field : declaredFields) {
if (java.lang.reflect.Modifier.isStatic(field.getModifiers()) && field.getName().equals(name)) {
try {
return field.get(this);
} catch (IllegalArgumentException | IllegalAccessException e) {
throw new Exception("Constant not found");
}
}
}
throw new Exception("Constant not found");
}
public Constant getConstant(String name) {
Field[] declaredFields = this.getClass().getDeclaredFields();
for (Field field : declaredFields) {
if (java.lang.reflect.Modifier.isStatic(field.getModifiers()) && field.getName().equals(name)) {
try {
return new Constant(field.getName(), field.getType(), field.get(this));
} catch (IllegalArgumentException | IllegalAccessException e) {
e.printStackTrace();
}
}
}
return new Constant("", Object.class, 0);
}
public Collection<Constant> getConstants() {
List<Constant> constants = (List<Constant>) getAllConstants();
int stop = constants.size();
for (int i = 0; i < constants.size(); ++i) {
Constant c = constants.get(i);
if ("kEndEditableArea".equals(c.name)) {
stop = i;
}
}
return constants.subList(0, stop);
}
private Collection<Constant> getAllConstants() {
Field[] declaredFields = this.getClass().getDeclaredFields();
List<Constant> constants = new ArrayList<Constant>(declaredFields.length);
for (Field field : declaredFields) {
if (java.lang.reflect.Modifier.isStatic(field.getModifiers())) {
Constant c;
try {
c = new Constant(field.getName(), field.getType(), field.get(this));
constants.add(c);
} catch (IllegalArgumentException | IllegalAccessException e) {
e.printStackTrace();
}
}
}
return constants;
}
public JSONObject getJSONObjectFromFile() throws IOException, ParseException {
File file = getFile();
if (file == null || !file.exists()) {
return new JSONObject();
}
FileReader reader;
reader = new FileReader(file);
JSONParser jsonParser = new JSONParser();
return (JSONObject) jsonParser.parse(reader);
}
public void loadFromFile() {
try {
JSONObject jsonObject = getJSONObjectFromFile();
Set<?> keys = jsonObject.keySet();
for (Object o : keys) {
String key = (String) o;
Object value = jsonObject.get(o);
if (value instanceof Long && getConstant(key).type.equals(int.class)) {
value = new BigDecimal((Long) value).intValueExact();
}
setConstantRaw(key, value);
}
} catch (IOException e) {
e.printStackTrace();
} catch (ParseException e) {
e.printStackTrace();
}
}
public void saveToFile() {
File file = getFile();
if (file == null) {
return;
}
try {
JSONObject json = getJSONObjectFromFile();
FileWriter writer = new FileWriter(file);
for (String key : modifiedKeys.keySet()) {
try {
Object value = getValueForConstant(key);
json.put(key, value);
} catch (Exception e) {
e.printStackTrace();
}
}
writer.write(json.toJSONString());
writer.close();
} catch (IOException | ParseException e) {
e.printStackTrace();
}
}
}
@@ -0,0 +1,67 @@
package org.usfirst.frc4388.utility;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Date;
import java.util.UUID;
/**
* Tracks start-up and caught crash events, logging them to a file which dosn't roll over
*/
public class CrashTracker {
private static final UUID RUN_INSTANCE_UUID = UUID.randomUUID();
public static void logRobotStartup() {
logMarker("robot startup");
}
public static void logRobotConstruction() {
logMarker("robot startup");
}
public static void logRobotInit() {
logMarker("robot init");
}
public static void logTeleopInit() {
logMarker("teleop init");
}
public static void logAutoInit() {
logMarker("auto init");
}
public static void logDisabledInit() {
logMarker("disabled init");
}
public static void logThrowableCrash(Throwable throwable) {
logMarker("Exception", throwable);
}
private static void logMarker(String mark) {
logMarker(mark, null);
}
private static void logMarker(String mark, Throwable nullableException) {
try (PrintWriter writer = new PrintWriter(new FileWriter("/home/lvuser/crash_tracking.txt", true))) {
writer.print(RUN_INSTANCE_UUID.toString());
writer.print(", ");
writer.print(mark);
writer.print(", ");
writer.print(new Date().toString());
if (nullableException != null) {
writer.print(", ");
nullableException.printStackTrace(writer);
}
writer.println();
} catch (IOException e) {
e.printStackTrace();
}
}
}
@@ -0,0 +1,19 @@
package org.usfirst.frc4388.utility;
/**
* Runnable class with reports all uncaught throws to CrashTracker
*/
public abstract class CrashTrackingRunnable implements Runnable {
@Override
public final void run() {
try {
runCrashTracked();
} catch (Throwable t) {
CrashTracker.logThrowableCrash(t);
throw t;
}
}
public abstract void runCrashTracked();
}
@@ -0,0 +1,14 @@
package org.usfirst.frc4388.utility;
/**
* Interface for loops, which are routine that run periodically in the robot code (such as periodic gyroscope
* calibration, etc.)
*/
public interface Loop {
public void onStart(double timestamp);
public void onLoop(double timestamp);
public void onStop(double timestamp);
}
@@ -0,0 +1,89 @@
package org.usfirst.frc4388.utility;
import java.util.ArrayList;
import java.util.List;
import org.usfirst.frc4388.robot.Constants;
import edu.wpi.first.wpilibj.Notifier;
import edu.wpi.first.wpilibj.Timer;
import edu.wpi.first.wpilibj.smartdashboard.SmartDashboard;
/**
* This code runs all of the robot's loops. Loop objects are stored in a List object. They are started when the robot
* powers up and stopped after the match.
*/
public class Looper {
public final double kPeriod = Constants.kLooperDt;
private boolean running_;
private final Notifier notifier_;
private final List<Loop> loops_;
private final Object taskRunningLock_ = new Object();
private double timestamp_ = 0;
private double dt_ = 0;
private final CrashTrackingRunnable runnable_ = new CrashTrackingRunnable() {
@Override
public void runCrashTracked() {
synchronized (taskRunningLock_) {
if (running_) {
double now = Timer.getFPGATimestamp();
for (Loop loop : loops_) {
loop.onLoop(now);
}
dt_ = now - timestamp_;
timestamp_ = now;
}
}
}
};
public Looper() {
notifier_ = new Notifier(runnable_);
running_ = false;
loops_ = new ArrayList<>();
}
public synchronized void register(Loop loop) {
synchronized (taskRunningLock_) {
loops_.add(loop);
}
}
public synchronized void start() {
if (!running_) {
System.out.println("Starting loops");
synchronized (taskRunningLock_) {
timestamp_ = Timer.getFPGATimestamp();
for (Loop loop : loops_) {
loop.onStart(timestamp_);
}
running_ = true;
}
notifier_.startPeriodic(kPeriod);
}
}
public synchronized void stop() {
if (running_) {
System.out.println("Stopping loops");
notifier_.stop();
synchronized (taskRunningLock_) {
running_ = false;
timestamp_ = Timer.getFPGATimestamp();
for (Loop loop : loops_) {
System.out.println("Stopping " + loop);
loop.onStop(timestamp_);
}
}
}
}
public void outputToSmartDashboard() {
SmartDashboard.putNumber("looper_dt", dt_);
}
}
@@ -0,0 +1,116 @@
package org.usfirst.frc4388.utility;
import java.util.ArrayList;
import org.usfirst.frc4388.robot.subsystems.Drive;
//import com.ctre.CANTalon.TalonControlMode;
import com.ctre.phoenix.motorcontrol.ControlMode;
public class MMTalonPIDController
{
protected static enum MMControlMode { STRAIGHT, TURN };
public static enum MMTalonTurnType { TANK, LEFT_SIDE_ONLY, RIGHT_SIDE_ONLY };
protected ArrayList<TalonSRXEncoder> motorControllers;
protected long periodMs;
protected PIDParams pidParams;
protected boolean useGyroLock;
protected double startGyroAngle;
protected double targetGyroAngle;
protected double trackDistance;
protected MMControlMode controlMode;
protected MMTalonTurnType turnType;
protected double targetValue;
public MMTalonPIDController(long periodMs, PIDParams pidParams, ArrayList<TalonSRXEncoder> motorControllers)
{
this.motorControllers = motorControllers;
this.periodMs = periodMs;
setPID(pidParams);
}
public void setPID(PIDParams pidParams) {
this.pidParams = pidParams;
for (TalonSRXEncoder motorController : motorControllers) {
motorController.setPIDF(0, pidParams.kP, pidParams.kI, pidParams.kD, pidParams.kF);
}
}
public void setMMStraightTarget(double startValue, double targetValue, double maxVelocity, double maxAcceleration, boolean useGyroLock, double desiredAngle, boolean resetEncoder) {
controlMode = MMControlMode.STRAIGHT;
this.startGyroAngle = desiredAngle;
this.useGyroLock = useGyroLock;
this.targetValue = targetValue;
// Set up the motion profile
for (TalonSRXEncoder motorController : motorControllers) {
if (resetEncoder) {
motorController.setPosition(0);
}
motorController.configMotionCruiseVelocity((int)maxVelocity, TalonSRXEncoder.TIMEOUT_MS);
motorController.configMotionAcceleration((int)maxAcceleration, TalonSRXEncoder.TIMEOUT_MS);
motorController.set(ControlMode.MotionMagic, targetValue);
}
}
public void setZeroPosition() {
for (TalonSRXEncoder motorController : motorControllers) {
motorController.setPosition(0);
motorController.set(ControlMode.MotionMagic, targetValue);
}
}
public void resetZeroPosition() {
for (TalonSRXEncoder motorController : motorControllers) {
motorController.setPosition(0);
}
}
public void resetZeroPosition(double angle) {
for (TalonSRXEncoder motorController : motorControllers) {
motorController.setPositionWorld(angle);
}
}
private double calcTrackDistance(double deltaAngleDeg, MMTalonTurnType turnType, double trackWidth) {
double trackDistance = deltaAngleDeg / 360.0 * Math.PI * trackWidth;
if (turnType == MMTalonTurnType.TANK) {
return trackDistance;
}
else if (turnType == MMTalonTurnType.LEFT_SIDE_ONLY) {
return trackDistance * 2.0;
}
else if (turnType == MMTalonTurnType.RIGHT_SIDE_ONLY) {
return -trackDistance * 2.0;
}
return 0.0;
}
public boolean controlLoopUpdate(double currentGyroAngle) {
// Calculate the motion profile feed forward and gyro feedback terms
double rightTarget = 0.0;
double leftTarget = 0.0;
// Update the set points
if (controlMode == MMControlMode.STRAIGHT) {
double gyroDelta = useGyroLock ? startGyroAngle - currentGyroAngle: 0;
double deltaDistance = calcTrackDistance(gyroDelta, MMTalonTurnType.TANK, Drive.TRACK_WIDTH_INCHES);
rightTarget = targetValue + deltaDistance;
leftTarget = targetValue - deltaDistance;
// Update the controllers with updated set points.
for (TalonSRXEncoder motorController : motorControllers) {
if (motorController.isRight()) {
motorController.set(ControlMode.MotionMagic, rightTarget);
}
else {
motorController.set(ControlMode.MotionMagic, leftTarget);
}
}
}
return false;
}
}
@@ -0,0 +1,144 @@
package org.usfirst.frc4388.utility;
import java.util.ArrayList;
//import com.ctre.CANTalon.TalonControlMode;
import com.ctre.phoenix.motorcontrol.ControlMode;
public class MPSoftwarePIDController
{
public static enum MPSoftwareTurnType { TANK, LEFT_SIDE_ONLY, RIGHT_SIDE_ONLY, LEFT_ARC, RIGHT_ARC };
protected ArrayList<TalonSRXEncoder> motorControllers;
protected long periodMs;
protected PIDParams pidParams;
protected MotionProfileBoxCar mp;
protected MotionProfilePoint mpPoint;
protected boolean useGyroLock;
protected double startGyroAngle;
protected double targetGyroAngle;
protected MPSoftwareTurnType turnType;
protected double prevError = 0.0; // the prior error (used to compute velocity)
protected double totalError = 0.0; // the sum of the errors for use in the integral calc
public MPSoftwarePIDController(long periodMs, PIDParams pidParams, ArrayList<TalonSRXEncoder> motorControllers)
{
this.motorControllers = motorControllers;
this.periodMs = periodMs;
setPID(pidParams);
}
public void setPID(PIDParams pidParams) {
this.pidParams = pidParams;
}
public void setMPTurnTarget(double startAngleDeg, double targetAngleDeg, double maxTurnRateDegPerSec, double t1, double t2, MPSoftwareTurnType turnType, double trackWidth) {
this.turnType = turnType;
this.startGyroAngle = startAngleDeg;
this.targetGyroAngle = targetAngleDeg;
this.useGyroLock = true;
// Set up the motion profile
mp = new MotionProfileBoxCar(startAngleDeg, targetAngleDeg, maxTurnRateDegPerSec, periodMs, t1, t2);
prevError = 0.0;
totalError = 0.0;
}
public void setMPTurnTarget(String key, MPSoftwareTurnType turnType, double trackWidth) {
this.turnType = turnType;
this.useGyroLock = true;
// Set up the motion profile
mp = MotionProfileCache.getInstance().getMP(key);
this.startGyroAngle = mp.getStartDistance();
this.targetGyroAngle = mp.getTargetDistance();
prevError = 0.0;
totalError = 0.0;
}
public boolean controlLoopUpdate() {
return controlLoopUpdate(0);
}
public boolean controlLoopUpdate(double currentGyroAngle) {
mpPoint = mp.getNextPoint(mpPoint);
// Check if we are finished
if (mpPoint == null) {
return true;
}
// Calculate the motion profile feed forward and error feedback terms
double error = mpPoint.position - currentGyroAngle;
if (Math.abs(targetGyroAngle - currentGyroAngle) < pidParams.iZone) {
totalError += error;
}
else {
totalError = 0;
}
double output = pidParams.kP * error + pidParams.kI * totalError + pidParams.kD * (error - prevError) + pidParams.kA * mpPoint.acceleration + pidParams.kV * mpPoint.velocity;
prevError = error;
// Update the controllers set point.
if (turnType == MPSoftwareTurnType.TANK) {
for (TalonSRXEncoder motorController : motorControllers) {
if (motorController.isRight()) {
motorController.set(ControlMode.PercentOutput, -output);
}
else {
motorController.set(ControlMode.PercentOutput, output);
}
}
}
else if (turnType == MPSoftwareTurnType.LEFT_SIDE_ONLY) {
for (TalonSRXEncoder motorController : motorControllers) {
if (motorController.isRight()) {
motorController.set(ControlMode.PercentOutput, 0);
}
else {
motorController.set(ControlMode.PercentOutput, 2.0 * output);
}
}
}
else if (turnType == MPSoftwareTurnType.RIGHT_SIDE_ONLY) {
for (TalonSRXEncoder motorController : motorControllers) {
if (motorController.isRight()) {
motorController.set(ControlMode.PercentOutput, 2.0 * output);
}
else {
motorController.set(ControlMode.PercentOutput, 0);
}
}
}
else if (turnType == MPSoftwareTurnType.LEFT_ARC) {
for (TalonSRXEncoder motorController : motorControllers) {
if (motorController.isRight()) {
motorController.set(ControlMode.PercentOutput, 1.0 * output);
}
else {
motorController.set(ControlMode.PercentOutput, 2.0 * output);
}
}
}
else if (turnType == MPSoftwareTurnType.RIGHT_ARC) {
for (TalonSRXEncoder motorController : motorControllers) {
if (motorController.isRight()) {
motorController.set(ControlMode.PercentOutput, 2.0 * output);
}
else {
motorController.set(ControlMode.PercentOutput, 1.0 * output);
}
}
}
return false;
}
public MotionProfilePoint getCurrentPoint() {
return mpPoint;
}
}
@@ -0,0 +1,233 @@
package org.usfirst.frc4388.utility;
import java.util.ArrayList;
//import com.ctre.CANTalon.TalonControlMode;
import com.ctre.phoenix.motorcontrol.ControlMode;
public class MPTalonPIDController
{
protected static enum MPControlMode { STRAIGHT, TURN };
public static enum MPTalonTurnType { TANK, LEFT_SIDE_ONLY, RIGHT_SIDE_ONLY };
protected ArrayList<TalonSRXEncoder> motorControllers;
protected long periodMs;
protected PIDParams pidParams;
protected MotionProfileBoxCar mp;
protected MotionProfilePoint mpPoint;
protected boolean useGyroLock;
protected double startGyroAngle;
protected double targetGyroAngle;
protected double trackDistance;
protected MPControlMode controlMode;
protected MPTalonTurnType turnType;
protected int pidSlot;
public MPTalonPIDController(long periodMs, ArrayList<TalonSRXEncoder> motorControllers)
{
this.motorControllers = motorControllers;
this.periodMs = periodMs;
}
public void setPID(PIDParams pidParams, int slot) {
this.pidParams = pidParams;
for (TalonSRXEncoder motorController : motorControllers) {
motorController.setPID(slot, pidParams.kP, pidParams.kI, pidParams.kD);
}
}
public void setPIDSlot(int slot) {
this.pidSlot = slot;
for (TalonSRXEncoder motorController : motorControllers) {
motorController.selectProfileSlot(slot, 0);
}
}
public void setMPTarget(double startValue, double targetValue, double maxVelocity, double t1, double t2) {
setMPStraightTarget(startValue, targetValue, maxVelocity, t1, t2, false, 0, false);
}
public void setMPTarget(double startValue, double targetValue, double maxVelocity, double t1, double t2, boolean resetEncoder) {
setMPStraightTarget(startValue, targetValue, maxVelocity, t1, t2, false, 0, resetEncoder);
}
public void setMPStraightTarget(double startValue, double targetValue, double maxVelocity, double t1, double t2, boolean useGyroLock, double desiredAngle, boolean resetEncoder) {
controlMode = MPControlMode.STRAIGHT;
this.startGyroAngle = desiredAngle;
this.useGyroLock = useGyroLock;
// Set up the motion profile
mp = new MotionProfileBoxCar(startValue, targetValue, maxVelocity, periodMs, t1, t2);
for (TalonSRXEncoder motorController : motorControllers) {
if (resetEncoder) {
motorController.setPosition(0);
}
motorController.setWorld(ControlMode.Position, mp.getStartDistance());
}
}
public double getStartPosition() {
return mp != null ? mp.getStartDistance() : 0;
}
public double getTargetPosition() {
return mp != null ? mp.getTargetDistance() : 0;
}
public void setMPStraightTarget(String key, boolean useGyroLock, double desiredAngle, boolean resetEncoder) {
controlMode = MPControlMode.STRAIGHT;
this.startGyroAngle = desiredAngle;
this.useGyroLock = useGyroLock;
// Set up the motion profile
mp = MotionProfileCache.getInstance().getMP(key);
for (TalonSRXEncoder motorController : motorControllers) {
if (resetEncoder) {
motorController.setPosition(0);
}
motorController.setWorld(ControlMode.Position, mp.getStartDistance());
}
}
public void setMPTurnTarget(double startAngleDeg, double targetAngleDeg, double maxTurnRateDegPerSec, double t1, double t2, MPTalonTurnType turnType, double trackWidth) {
controlMode = MPControlMode.TURN;
this.turnType = turnType;
this.startGyroAngle = startAngleDeg;
this.targetGyroAngle = targetAngleDeg;
this.useGyroLock = true;
trackDistance = calcTrackDistance(targetAngleDeg - startAngleDeg, turnType, trackWidth);
// Set up the motion profile
mp = new MotionProfileBoxCar(0, trackDistance, maxTurnRateDegPerSec, periodMs, t1, t2);
for (TalonSRXEncoder motorController : motorControllers) {
motorController.setPosition(0);
motorController.set(ControlMode.Position, 0);
}
if (Math.abs(trackDistance) < 0.0001) {
trackDistance = 1;
}
}
private double calcTrackDistance(double deltaAngleDeg, MPTalonTurnType turnType, double trackWidth) {
double trackDistance = deltaAngleDeg / 360.0 * Math.PI * trackWidth;
if (turnType == MPTalonTurnType.TANK) {
return trackDistance;
}
else if (turnType == MPTalonTurnType.LEFT_SIDE_ONLY) {
return trackDistance * 2.0;
}
else if (turnType == MPTalonTurnType.RIGHT_SIDE_ONLY) {
return -trackDistance * 2.0;
}
return 0.0;
}
public void setZeroPosition() {
for (TalonSRXEncoder motorController : motorControllers) {
motorController.setPosition(0);
motorController.set(ControlMode.Position, 0);
}
}
public void resetZeroPosition() {
for (TalonSRXEncoder motorController : motorControllers) {
motorController.setPosition(0);
}
}
public void resetZeroPosition(double position) {
for (TalonSRXEncoder motorController : motorControllers) {
motorController.setPositionWorld(position);
}
}
public boolean controlLoopUpdate() {
return controlLoopUpdate(0);
}
public boolean controlLoopUpdate(double currentGyroAngle) {
mpPoint = mp.getNextPoint(mpPoint);
// Check if we are finished
if (mpPoint == null) {
return true;
}
// Calculate the motion profile feed forward and gyro feedback terms
double KfLeft = 0.0;
double KfRight = 0.0;
// Update the set points and Kf gains
if (controlMode == MPControlMode.STRAIGHT) {
double gyroDelta = useGyroLock ? startGyroAngle - currentGyroAngle: 0;
if (Math.abs(mpPoint.position) > 0.001) {
KfLeft = (pidParams.kA * mpPoint.acceleration + pidParams.kV * mpPoint.velocity + pidParams.kG * gyroDelta) / mpPoint.position;
KfRight = (pidParams.kA * mpPoint.acceleration + pidParams.kV * mpPoint.velocity - pidParams.kG * gyroDelta) / mpPoint.position;
}
// Update the controllers Kf and set point.
for (TalonSRXEncoder motorController : motorControllers) {
if (motorController.isRight()) {
motorController.config_kF(0, KfRight, TalonSRXEncoder.TIMEOUT_MS);
motorController.setWorld(ControlMode.Position, mpPoint.position);
}
else {
motorController.config_kF(0, KfLeft, TalonSRXEncoder.TIMEOUT_MS);
motorController.setWorld(ControlMode.Position, mpPoint.position);
}
}
}
else {
double mpAngle = startGyroAngle + ((targetGyroAngle - startGyroAngle) * mpPoint.position / trackDistance);
double gyroDelta = mpAngle - currentGyroAngle;
if (Math.abs(mpPoint.position) > 0.001) {
KfLeft = (pidParams.kA * mpPoint.acceleration + pidParams.kV * mpPoint.velocity + pidParams.kG * gyroDelta) / mpPoint.position;
KfRight = (pidParams.kA * mpPoint.acceleration + pidParams.kV * mpPoint.velocity + pidParams.kG * gyroDelta) / mpPoint.position;
}
for (TalonSRXEncoder motorController : motorControllers) {
if (turnType == MPTalonTurnType.TANK) {
if (motorController.isRight()) {
motorController.config_kF(0, KfRight, TalonSRXEncoder.TIMEOUT_MS);
motorController.setWorld(ControlMode.Position, -mpPoint.position);
}
else {
motorController.config_kF(0, KfLeft, TalonSRXEncoder.TIMEOUT_MS);
motorController.setWorld(ControlMode.Position, mpPoint.position);
}
}
else if (turnType == MPTalonTurnType.LEFT_SIDE_ONLY) {
if (!motorController.isRight()) {
motorController.config_kF(0, KfLeft, TalonSRXEncoder.TIMEOUT_MS);
motorController.setWorld(ControlMode.Position, mpPoint.position);
}
}
else if (turnType == MPTalonTurnType.RIGHT_SIDE_ONLY) {
if (motorController.isRight()) {
motorController.config_kF(0, KfRight, TalonSRXEncoder.TIMEOUT_MS);
motorController.setWorld(ControlMode.Position, -mpPoint.position);
}
}
}
}
return false;
}
public void setTarget(double position, double Kf) {
// Kf gets multipled by position in the Talon
double KfPerPosition = Math.abs(position) > 0.001 ? Kf / position : 0;
for (TalonSRXEncoder motorController : motorControllers) {
motorController.config_kF(0, KfPerPosition, TalonSRXEncoder.TIMEOUT_MS);
motorController.setWorld(ControlMode.Position, position);
}
}
public MotionProfilePoint getCurrentPoint() {
return mpPoint;
}
}
@@ -0,0 +1,74 @@
package org.usfirst.frc4388.utility;
import java.util.ArrayList;
//import com.ctre.CANTalon.TalonControlMode;
import com.ctre.phoenix.motorcontrol.ControlMode;
public class MPTalonPIDPathController
{
protected ArrayList<TalonSRXEncoder> motorControllers;
protected long periodMs;
protected PIDParams pidParams;
protected double startGyroAngle;
protected double targetGyroAngle;
protected double trackDistance;
public MPTalonPIDPathController(long periodMs, PIDParams pidParams, ArrayList<TalonSRXEncoder> motorControllers)
{
this.motorControllers = motorControllers;
this.periodMs = periodMs;
setPID(pidParams);
}
public void setPID(PIDParams pidParams) {
this.pidParams = pidParams;
for (TalonSRXEncoder motorController : motorControllers) {
motorController.setPID(0, pidParams.kP, pidParams.kI, pidParams.kD);
}
}
public void setZeroPosition() {
for (TalonSRXEncoder motorController : motorControllers) {
motorController.setPosition(0);
motorController.set(ControlMode.Position, 0);
}
}
public void resetZeroPosition() {
for (TalonSRXEncoder motorController : motorControllers) {
motorController.setPosition(0);
}
}
public void resetZeroPosition(double angle) {
for (TalonSRXEncoder motorController : motorControllers) {
motorController.setPositionWorld(angle);
}
}
public boolean controlLoopUpdate(double currentGyroAngle) {
// Calculate the motion profile feed forward and gyro feedback terms
double KfLeft = 0.0;
double KfRight = 0.0;
// Update the set points and Kf gains
// Update the controllers Kf and set point.
for (TalonSRXEncoder motorController : motorControllers) {
if (motorController.isRight()) {
motorController.config_kF(0, KfRight, TalonSRXEncoder.TIMEOUT_MS);
// motorController.setWorld(ControlMode.Position, rightPoint.position);
}
else {
motorController.config_kF(0, KfLeft, TalonSRXEncoder.TIMEOUT_MS);
}
}
return false;
}
}
@@ -0,0 +1,71 @@
package org.usfirst.frc4388.utility;
import java.util.ArrayList;
//import com.ctre.CANTalon.TalonControlMode;
import com.ctre.phoenix.motorcontrol.ControlMode;
public class MPTalonPIDPathVelocityController
{
protected ArrayList<TalonSRXEncoder> motorControllers;
protected long periodMs;
protected PIDParams pidParams;
// protected PathGenerator path;
protected double startGyroAngle;
protected double targetGyroAngle;
protected double trackDistance;
public MPTalonPIDPathVelocityController(long periodMs, PIDParams pidParams, ArrayList<TalonSRXEncoder> motorControllers)
{
this.motorControllers = motorControllers;
this.periodMs = periodMs;
setPID(pidParams);
}
public void setPID(PIDParams pidParams) {
this.pidParams = pidParams;
for (TalonSRXEncoder motorController : motorControllers) {
motorController.setPIDF(0, pidParams.kP, pidParams.kI, pidParams.kD, pidParams.kF);
motorController.configVoltageCompSaturation(12.0, TalonSRXEncoder.TIMEOUT_MS);
motorController.enableVoltageCompensation(true);
motorController.configNominalOutputForward(0.0, TalonSRXEncoder.TIMEOUT_MS);
motorController.configNominalOutputReverse(0.0, TalonSRXEncoder.TIMEOUT_MS);
motorController.configPeakOutputForward(+1.0f, TalonSRXEncoder.TIMEOUT_MS);
motorController.configPeakOutputReverse(-1.0f, TalonSRXEncoder.TIMEOUT_MS);
motorController.selectProfileSlot(0, TalonSRXEncoder.PID_IDX);
}
}
public void setZeroPosition() {
for (TalonSRXEncoder motorController : motorControllers) {
motorController.setPosition(0);
motorController.set(ControlMode.Position, 0);
}
}
public void resetZeroPosition() {
for (TalonSRXEncoder motorController : motorControllers) {
motorController.setPosition(0);
}
}
public void resetZeroPosition(double angle) {
for (TalonSRXEncoder motorController : motorControllers) {
motorController.setPositionWorld(angle);
}
// }
// Update the controllers Kf and set point.
for (TalonSRXEncoder motorController : motorControllers) {
if (motorController.isRight()) {
}
else {
}
}
}
}
@@ -0,0 +1,184 @@
package org.usfirst.frc4388.utility;
public class MotionProfileBoxCar
{
public static double DEFAULT_T1 = 200; // millisecond
public static double DEFAULT_T2 = 100; // millisecond
private double startDistance; // any distance unit
private double targetDistance; // any distance unit
private double maxVelocity; // velocity unit consistent with targetDistance
// Accel profile
//
// 0 T2 T1
// | | |
// _____
// / \
// / \___
// \ /
// \____/
private double itp; // time between points millisecond
private double t1 = DEFAULT_T1; // time when accel starts back to 0. millisecond (typically use t1 = 2 * t2)
private double t2 = DEFAULT_T2; // time when accel = max accel. millisecond
private double t4;
private int numFilter1Boxes;
private int numFilter2Boxes;
private int numPoints;
private int numITP;
private double filter1;
private double filter2;
private double previousPosition;
private double previousVelocity;
private double deltaFilter1;
private double[] filter2Window;
private int windowIndex;
private int pointIndex;
public MotionProfileBoxCar(double startDistance, double targetDistance, double maxVelocity, double itp) {
this(startDistance, targetDistance, maxVelocity, itp, DEFAULT_T1, DEFAULT_T2);
}
public MotionProfileBoxCar(double startDistance, double targetDistance, double maxVelocity, double itp, double t1, double t2) {
this.startDistance = startDistance;
this.targetDistance = targetDistance;
this.maxVelocity = maxVelocity;
this.itp = itp;
this.t1 = t1;
this.t2 = t2;
initializeProfile();
}
private void initializeProfile() {
// t4 is the time in ms it takes to get to the end point when at max velocity
t4 = Math.abs((targetDistance - startDistance)/maxVelocity) * 1000;
// We need to make t4 an even multiple of itp
t4 = (int)(itp * Math.ceil(t4/itp));
// In the case where t4 is less than the accel times, we need to adjust the
// accel times down so the filters works out. Lots of ways to do this but
// to keep things simple we will make t4 slightly larger than the sum of
// the accel times.
if (t4 < t1 + t2) {
double total = t1 + t2 + t4;
double t1t2Ratio = t1/t2;
double t2Adjusted = Math.floor(total / 2 / (1 + t1t2Ratio) / itp);
if (t2Adjusted % 2 != 0) {
t2Adjusted -= 1;
}
t2 = t2Adjusted * itp;
t1 = t2 * t1t2Ratio;
t4 = total - t1 - t2;
}
// Adjust max velocity so that the end point works out to the correct position.
maxVelocity = Math.abs((targetDistance - startDistance) / t4) * 1000;
numFilter1Boxes = (int)Math.ceil(t1/itp);
numFilter2Boxes = (int)Math.ceil(t2/itp);
numPoints = (int)Math.ceil(t4/itp);
numITP = numPoints + numFilter1Boxes + numFilter2Boxes;
filter1 = 0;
filter2 = 0;
previousVelocity = 0;
previousPosition = startDistance;
deltaFilter1 = 1.0/numFilter1Boxes;
filter2Window = new double[numFilter2Boxes];
windowIndex = 0;
pointIndex = 0;
if (startDistance > targetDistance && maxVelocity > 0) {
maxVelocity = -maxVelocity;
}
}
public MotionProfilePoint getNextPoint(MotionProfilePoint point) {
if (point == null) {
point = new MotionProfilePoint();
}
if (pointIndex == 0) {
point.initialize(startDistance);
pointIndex++;
return point;
}
else if (pointIndex > numITP) {
return null;
}
int input = (pointIndex - 1) < numPoints ? 1 : 0;
if (input > 0) {
filter1 = Math.min(1, filter1 + deltaFilter1);
}
else {
filter1 = Math.max(0, filter1 - deltaFilter1);
}
double firstFilter1InWindow = filter2Window[windowIndex];
if (pointIndex <= numFilter2Boxes) {
firstFilter1InWindow = 0;
}
filter2Window[windowIndex] = filter1;
filter2 += (filter1 - firstFilter1InWindow) / numFilter2Boxes;
point.time = pointIndex * itp / 1000.0;
point.velocity = filter2 * maxVelocity;
point.position = previousPosition + (point.velocity + previousVelocity) / 2 * itp / 1000;
point.acceleration = (point.velocity - previousVelocity) / itp * 1000;
previousVelocity = point.velocity;
previousPosition = point.position;
windowIndex++;
if (windowIndex == numFilter2Boxes) {
windowIndex = 0;
}
pointIndex++;
return point;
}
public double getStartDistance() {
return startDistance;
}
public double getTargetDistance() {
return targetDistance;
}
public double getMaxVelocity() {
return maxVelocity;
}
public double getItp() {
return itp;
}
public double getT1() {
return t1;
}
public double getT2() {
return t2;
}
public static void main(String[] args) {
long startTime = System.nanoTime();
MotionProfileBoxCar mp = new MotionProfileBoxCar(0, 70, 120, 10, 600, 300);
System.out.println("Time, Position, Velocity, Acceleration");
MotionProfilePoint point = new MotionProfilePoint();
while(mp.getNextPoint(point) != null) {
System.out.println(point.time + ", " + point.position + ", " + point.velocity + ", " + point.acceleration);
}
long deltaTime = System.nanoTime() - startTime;
System.out.println("Time Box Car = " + (double)deltaTime * 1E-6 + " ms");
}
}
@@ -0,0 +1,49 @@
package org.usfirst.frc4388.utility;
import java.text.DecimalFormat;
import java.util.Hashtable;
public class MotionProfileCache {
private Hashtable<String, MotionProfileBoxCar> cache;
private DecimalFormat df = new DecimalFormat("#.000");
private static MotionProfileCache instance;
private MotionProfileCache() {
cache = new Hashtable<String, MotionProfileBoxCar>();
}
public String addMP(double startDistance, double targetDistance, double maxVelocity, double itp, double t1, double t2) {
String key = generateKey(startDistance, targetDistance, maxVelocity, itp, t1, t2);
if (!cache.containsKey(key)) {
MotionProfileBoxCar mp = new MotionProfileBoxCar(startDistance, targetDistance, maxVelocity, itp, t1, t2);
this.addMP(key, mp);
}
return key;
}
public void addMP(String key, MotionProfileBoxCar mp) {
cache.put(key, mp);
}
public MotionProfileBoxCar getMP(String key) {
return cache.get(key);
}
public static MotionProfileCache getInstance() {
if(instance == null) {
instance = new MotionProfileCache();
}
return instance;
}
public void release() {
instance = null;
}
private String generateKey(double startDistance, double targetDistance, double maxVelocity, double itp, double t1, double t2) {
return df.format(startDistance) + df.format(targetDistance) + df.format(maxVelocity) + df.format(itp) + df.format(t1) + df.format(t2);
}
}
@@ -0,0 +1,57 @@
package org.usfirst.frc4388.utility;
public class PIDParams
{
public double kP = 0;
public double kI = 0;
public double kD = 0;
public double kF = 0;
public double kA = 0;
public double kV = 0;
public double kG = 0;
public double iZone = 0;
public double rampRatePID = 0;
public PIDParams() {
}
public PIDParams(double kP, double kI, double kD) {
this.kP = kP;
this.kI = kI;
this.kD = kD;
}
public PIDParams(double kP, double kI, double kD, double kF) {
this.kP = kP;
this.kI = kI;
this.kD = kD;
this.kF = kF;
}
public PIDParams(double kP, double kI, double kD, double kA, double kV) {
this.kP = kP;
this.kI = kI;
this.kD = kD;
this.kA = kA;
this.kV = kV;
}
public PIDParams(double kP, double kI, double kD, double kA, double kV, double kG) {
this.kP = kP;
this.kI = kI;
this.kD = kD;
this.kA = kA;
this.kV = kV;
this.kG = kG;
}
public PIDParams(double kP, double kI, double kD, double kA, double kV, double kG, double iZone) {
this.kP = kP;
this.kI = kI;
this.kD = kD;
this.kA = kA;
this.kV = kV;
this.kG = kG;
this.iZone = iZone;
}
}
@@ -0,0 +1,74 @@
package org.usfirst.frc4388.utility;
import java.io.FileNotFoundException;
import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.util.concurrent.ConcurrentLinkedDeque;
/**
* Writes data to a CSV file
*/
public class ReflectingCSVWriter<T> {
ConcurrentLinkedDeque<String> mLinesToWrite = new ConcurrentLinkedDeque<>();
PrintWriter mOutput = null;
Field[] mFields;
public ReflectingCSVWriter(String fileName, Class<T> typeClass) {
mFields = typeClass.getFields();
try {
mOutput = new PrintWriter(fileName);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
// Write field names.
StringBuffer line = new StringBuffer();
for (Field field : mFields) {
if (line.length() != 0) {
line.append(", ");
}
line.append(field.getName());
}
writeLine(line.toString());
}
public void add(T value) {
StringBuffer line = new StringBuffer();
for (Field field : mFields) {
if (line.length() != 0) {
line.append(", ");
}
try {
line.append(field.get(value).toString());
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
mLinesToWrite.add(line.toString());
}
protected synchronized void writeLine(String line) {
if (mOutput != null) {
mOutput.println(line);
}
}
// Call this periodically from any thread to write to disk.
public void write() {
while (true) {
String val = mLinesToWrite.pollFirst();
if (val == null) {
break;
}
writeLine(val);
}
}
public synchronized void flush() {
if (mOutput != null) {
write();
mOutput.flush();
}
}
}
@@ -0,0 +1,101 @@
package org.usfirst.frc4388.utility;
import java.util.ArrayList;
import org.usfirst.frc4388.utility.MPSoftwarePIDController.MPSoftwareTurnType;
//import com.ctre.CANTalon.TalonControlMode;
import com.ctre.phoenix.motorcontrol.ControlMode;
public class SoftwarePIDController
{
protected ArrayList<TalonSRXEncoder> motorControllers;
protected PIDParams pidParams;
protected boolean useGyroLock;
protected double targetGyroAngle;
protected MPSoftwareTurnType turnType;
protected double minTurnOutput = 0.002;
protected double maxError;
protected double maxPrevError;
protected double prevError = 0.0; // the prior error (used to compute velocity)
protected double totalError = 0.0; // the sum of the errors for use in the integral calc
public SoftwarePIDController( PIDParams pidParams, ArrayList<TalonSRXEncoder> motorControllers)
{
this.motorControllers = motorControllers;
setPID(pidParams);
}
public void setPID(PIDParams pidParams) {
this.pidParams = pidParams;
}
public void setPIDTurnTarget(double targetAngleDeg, double maxError, double maxPrevError, MPSoftwareTurnType turnType) {
this.turnType = turnType;
this.targetGyroAngle = targetAngleDeg;
this.useGyroLock = true;
this.maxError = maxError;
this.maxPrevError = maxPrevError;
prevError = 0.0;
totalError = 0.0;
}
public boolean controlLoopUpdate() {
return controlLoopUpdate(0);
}
public boolean controlLoopUpdate(double currentGyroAngle) {
// Calculate the motion profile feed forward and error feedback terms
double error = targetGyroAngle - currentGyroAngle;
double deltaLastError = (error - prevError);
// Check if we are finished
if (Math.abs(error) < maxError && Math.abs(deltaLastError) < maxPrevError) {
return true;
}
if (Math.abs(targetGyroAngle - currentGyroAngle) < pidParams.iZone) {
totalError += error;
}
else {
totalError = 0;
}
double output = pidParams.kP * error + pidParams.kI * totalError + pidParams.kD * deltaLastError;
double turnBoost = output < 0 ? -minTurnOutput : minTurnOutput;
output += turnBoost;
prevError = error;
// Update the controllers set point.
if (turnType == MPSoftwareTurnType.TANK) {
for (TalonSRXEncoder motorController : motorControllers) {
motorController.set(ControlMode.PercentOutput, output);
}
}
else if (turnType == MPSoftwareTurnType.LEFT_SIDE_ONLY) {
for (TalonSRXEncoder motorController : motorControllers) {
if (motorController.isRight()) {
motorController.set(ControlMode.PercentOutput, 0);
}
else {
motorController.set(ControlMode.PercentOutput, 2.0 * output);
}
}
}
else if (turnType == MPSoftwareTurnType.RIGHT_SIDE_ONLY) {
for (TalonSRXEncoder motorController : motorControllers) {
if (motorController.isRight()) {
motorController.set(ControlMode.PercentOutput, 2.0 * output);
}
else {
motorController.set(ControlMode.PercentOutput, 0);
}
}
}
return false;
}
}
@@ -0,0 +1,84 @@
package org.usfirst.frc4388.utility;
import com.ctre.phoenix.motorcontrol.ControlMode;
import com.ctre.phoenix.motorcontrol.FeedbackDevice;
import com.ctre.phoenix.motorcontrol.can.WPI_TalonSRX;
public class TalonSRXEncoder extends WPI_TalonSRX
{
public static int TIMEOUT_MS = 0;
public static int PID_IDX = 0;
private double encoderTicksToWorld;
private boolean isRight = true;
public TalonSRXEncoder(int deviceId, double encoderTicksToWorld, FeedbackDevice feedbackDevice) {
this(deviceId, encoderTicksToWorld, false, feedbackDevice);
}
public TalonSRXEncoder(int deviceNumber, double encoderTicksToWorld, boolean isRight, FeedbackDevice feedbackDevice) {
super(deviceNumber);
this.configSelectedFeedbackSensor(feedbackDevice, PID_IDX, TIMEOUT_MS);
this.encoderTicksToWorld = encoderTicksToWorld;
this.isRight = isRight;
}
public boolean isRight() {
return isRight;
}
public void setRight(boolean isRight) {
this.isRight = isRight;
}
public void setPID(int slotId, double kP, double kI, double kD) {
this.setPIDF(slotId, kP, kI, kD, 0);
}
public void setPIDF(int slotId, double kP, double kI, double kD, double kF) {
this.config_kP(slotId, kP, TIMEOUT_MS);
this.config_kI(slotId, kI, TIMEOUT_MS);
this.config_kD(slotId, kD, TIMEOUT_MS);
this.config_kF(slotId, kF, TIMEOUT_MS);
}
public void setPIDFIZone(int slotId, double kP, double kI, double kD, double kF, int iZone) {
this.config_kP(slotId, kP, TIMEOUT_MS);
this.config_kI(slotId, kI, TIMEOUT_MS);
this.config_kD(slotId, kD, TIMEOUT_MS);
this.config_kF(slotId, kF, TIMEOUT_MS);
this.config_IntegralZone(slotId, iZone, TIMEOUT_MS);
}
public double convertEncoderTicksToWorld(double encoderTicks) {
return encoderTicks / encoderTicksToWorld;
}
public int convertEncoderWorldToTicks(double worldValue) {
return (int)(worldValue * encoderTicksToWorld);
}
public void setWorld(ControlMode mode, double worldValue) {
this.set(mode, convertEncoderWorldToTicks(worldValue));
}
public void setPosition(int value) {
this.setSelectedSensorPosition(value, PID_IDX, TIMEOUT_MS);
}
public void setPositionWorld(double worldValue) {
this.setSelectedSensorPosition(convertEncoderWorldToTicks(worldValue), PID_IDX, TIMEOUT_MS);
}
public double getPositionWorld() {
return convertEncoderTicksToWorld(this.getSelectedSensorPosition(PID_IDX));
}
public void setVelocityWorld(double worldValue) {
this.set(ControlMode.Velocity, convertEncoderWorldToTicks(worldValue) * 0.1);
}
public double getVelocityWorld() {
return convertEncoderTicksToWorld(this.getSelectedSensorVelocity(PID_IDX) / 0.1);
}
}
@@ -0,0 +1,208 @@
package org.usfirst.frc4388.utility;
import com.ctre.phoenix.motorcontrol.ControlMode;
import com.ctre.phoenix.motorcontrol.FeedbackDevice;
import com.ctre.phoenix.motorcontrol.NeutralMode;
import com.ctre.phoenix.motorcontrol.StatusFrame;
import com.ctre.phoenix.motorcontrol.VelocityMeasPeriod;
import com.ctre.phoenix.motorcontrol.can.TalonSRX;
import edu.wpi.first.wpilibj.MotorSafety;
/**
* Creates TalonSRX objects and configures all the parameters we care about to factory defaults. Closed-loop and sensor
* parameters are not set, as these are expected to be set by the application.
*/
public class TalonSRXFactory {
public static class Configuration {
public boolean LIMIT_SWITCH_NORMALLY_OPEN = true;
public double MAX_OUTPUT = 1;
public double NOMINAL_OUTPUT = 0;
public double PEAK_OUTPUT = 12;
public NeutralMode ENABLE_BRAKE = NeutralMode.Brake;
public boolean ENABLE_CURRENT_LIMIT = false;
public boolean ENABLE_SOFT_LIMIT = false;
public boolean ENABLE_LIMIT_SWITCH = false;
public int CURRENT_LIMIT = 0;
// public double EXPIRATION_TIMEOUT_SECONDS = MotorSafety.DEFAULT_SAFETY_EXPIRATION;
public double FORWARD_SOFT_LIMIT = 0;
public boolean INVERTED = false;
public double NOMINAL_CLOSED_LOOP_VOLTAGE = 12;
public double REVERSE_SOFT_LIMIT = 0;
public boolean SAFETY_ENABLED = false;
public int CONTROL_FRAME_PERIOD_MS = 5;
public int MOTION_CONTROL_FRAME_PERIOD_MS = 100;
public int GENERAL_STATUS_FRAME_RATE_MS = 5;
public int FEEDBACK_STATUS_FRAME_RATE_MS = 100;
public int QUAD_ENCODER_STATUS_FRAME_RATE_MS = 100;
public int ANALOG_TEMP_VBAT_STATUS_FRAME_RATE_MS = 100;
public int PULSE_WIDTH_STATUS_FRAME_RATE_MS = 100;
public VelocityMeasPeriod VELOCITY_MEASUREMENT_PERIOD = VelocityMeasPeriod.Period_100Ms;
public int VELOCITY_MEASUREMENT_ROLLING_AVERAGE_WINDOW = 64;
public double VOLTAGE_COMPENSATION_RAMP_RATE = 0;
public double VOLTAGE_RAMP_RATE = 0;
}
private static final Configuration kDefaultConfiguration = new Configuration();
private static final Configuration kSlaveConfiguration = new Configuration();
static {
kSlaveConfiguration.CONTROL_FRAME_PERIOD_MS = 1000;
kSlaveConfiguration.MOTION_CONTROL_FRAME_PERIOD_MS = 1000;
kSlaveConfiguration.GENERAL_STATUS_FRAME_RATE_MS = 1000;
kSlaveConfiguration.FEEDBACK_STATUS_FRAME_RATE_MS = 1000;
kSlaveConfiguration.QUAD_ENCODER_STATUS_FRAME_RATE_MS = 1000;
kSlaveConfiguration.ANALOG_TEMP_VBAT_STATUS_FRAME_RATE_MS = 1000;
kSlaveConfiguration.PULSE_WIDTH_STATUS_FRAME_RATE_MS = 1000;
}
// Create a TalonSRX with the default (out of the box) configuration.
public static TalonSRX createDefaultTalon(int id) {
return createTalon(id, kDefaultConfiguration);
}
public static TalonSRXEncoder createTalonEncoder(int id, double encoderTicksToWorld, FeedbackDevice feedbackDevice) {
TalonSRXEncoder talon = new TalonSRXEncoder(id, encoderTicksToWorld, feedbackDevice);
// initializeTalon(talon, kDefaultConfiguration);
return talon;
}
public static TalonSRXEncoder createTalonEncoder(int id, double encoderTicksToWorld, boolean isRight, FeedbackDevice feedbackDevice) {
TalonSRXEncoder talon = new TalonSRXEncoder(id, encoderTicksToWorld, isRight, feedbackDevice);
// initializeTalon(talon, kDefaultConfiguration);
return talon;
}
public static TalonSRX createPermanentSlaveTalon(int id, int master_id) {
final TalonSRX talon = createTalon(id, kSlaveConfiguration);
talon.set(ControlMode.Follower, master_id);
return talon;
}
public static TalonSRX createTalon(int id, Configuration config) {
TalonSRX talon = new TalonSRX(id);
// initializeTalon(talon, config);
return talon;
}
public static void initializeTalon(TalonSRX talon, Configuration config) {
// talon.setControlFramePeriod(config.CONTROL_FRAME_PERIOD_MS, TalonSRXEncoder.TIMEOUT_MS);
talon.changeMotionControlFramePeriod(config.MOTION_CONTROL_FRAME_PERIOD_MS);
talon.setIntegralAccumulator(0, TalonSRXEncoder.PID_IDX, TalonSRXEncoder.TIMEOUT_MS);
talon.clearMotionProfileHasUnderrun(TalonSRXEncoder.TIMEOUT_MS);
talon.clearMotionProfileTrajectories();
talon.clearStickyFaults(TalonSRXEncoder.TIMEOUT_MS);
talon.configPeakOutputForward(config.MAX_OUTPUT, TalonSRXEncoder.TIMEOUT_MS);
talon.configPeakOutputReverse(-config.MAX_OUTPUT, TalonSRXEncoder.TIMEOUT_MS);
talon.configNominalOutputForward(config.NOMINAL_OUTPUT, TalonSRXEncoder.TIMEOUT_MS);
talon.configNominalOutputReverse(-config.NOMINAL_OUTPUT, TalonSRXEncoder.TIMEOUT_MS);
talon.setNeutralMode(config.ENABLE_BRAKE);
talon.enableCurrentLimit(config.ENABLE_CURRENT_LIMIT);
talon.configForwardSoftLimitEnable(config.ENABLE_SOFT_LIMIT, TalonSRXEncoder.TIMEOUT_MS);
talon.configReverseSoftLimitEnable(config.ENABLE_SOFT_LIMIT, TalonSRXEncoder.TIMEOUT_MS);
talon.setInverted(config.INVERTED);
talon.setSensorPhase(false);
talon.setSelectedSensorPosition(0, TalonSRXEncoder.PID_IDX, TalonSRXEncoder.TIMEOUT_MS);
talon.selectProfileSlot(0, TalonSRXEncoder.TIMEOUT_MS);
talon.configVelocityMeasurementPeriod(config.VELOCITY_MEASUREMENT_PERIOD, TalonSRXEncoder.TIMEOUT_MS);
talon.configVelocityMeasurementWindow(config.VELOCITY_MEASUREMENT_ROLLING_AVERAGE_WINDOW, TalonSRXEncoder.TIMEOUT_MS);
talon.setStatusFramePeriod(StatusFrame.Status_1_General, config.GENERAL_STATUS_FRAME_RATE_MS, TalonSRXEncoder.TIMEOUT_MS);
talon.setStatusFramePeriod(StatusFrame.Status_2_Feedback0, config.FEEDBACK_STATUS_FRAME_RATE_MS, TalonSRXEncoder.TIMEOUT_MS);
talon.setStatusFramePeriod(StatusFrame.Status_4_AinTempVbat, config.ANALOG_TEMP_VBAT_STATUS_FRAME_RATE_MS, TalonSRXEncoder.TIMEOUT_MS);
// talon.setStatusFrameRateMs(TalonSRX.StatusFrameRate.QuadEncoder, config.QUAD_ENCODER_STATUS_FRAME_RATE_MS);
// talon.setStatusFrameRateMs(TalonSRX.StatusFrameRate.PulseWidth, config.PULSE_WIDTH_STATUS_FRAME_RATE_MS);
}
/**
* Run this on a fresh talon to produce good values for the defaults.
*/
public static String getFullTalonInfo(TalonSRX talon) {
StringBuilder sb = new StringBuilder().append("isRevLimitSwitchClosed = ");
// .append(talon.isRevLimitSwitchClosed()).append("\n").append("getBusVoltage = ")
// .append(talon.getBusVoltage()).append("\n").append("isForwardSoftLimitEnabled = ")
// .append(talon.isForwardSoftLimitEnabled()).append("\n").append("getFaultRevSoftLim = ")
// .append(talon.getFaultRevSoftLim()).append("\n").append("getStickyFaultOverTemp = ")
// .append(talon.getStickyFaultOverTemp()).append("\n").append("isZeroSensorPosOnFwdLimitEnabled = ")
// .append(talon.isZeroSensorPosOnFwdLimitEnabled()).append("\n")
// .append("getMotionProfileTopLevelBufferCount = ").append(talon.getMotionProfileTopLevelBufferCount())
// .append("\n").append("getNumberOfQuadIdxRises = ").append(talon.getNumberOfQuadIdxRises()).append("\n")
// .append("getInverted = ").append(talon.getInverted()).append("\n")
// .append("getPulseWidthRiseToRiseUs = ").append(talon.getPulseWidthRiseToRiseUs()).append("\n")
// .append("getError = ").append(talon.getError()).append("\n").append("isSensorPresent = ")
// .append(talon.isSensorPresent(CANTalon.FeedbackDevice.CtreMagEncoder_Relative)).append("\n")
// .append("isControlEnabled = ").append(talon.isControlEnabled()).append("\n").append("getTable = ")
// .append(talon.getTable()).append("\n").append("isEnabled = ").append(talon.isEnabled()).append("\n")
// .append("isZeroSensorPosOnRevLimitEnabled = ").append(talon.isZeroSensorPosOnRevLimitEnabled())
// .append("\n").append("isSafetyEnabled = ").append(talon.isSafetyEnabled()).append("\n")
// .append("getOutputVoltage = ").append(talon.getOutputVoltage()).append("\n").append("getTemperature = ")
// .append(talon.getTemperature()).append("\n").append("getSmartDashboardType = ")
// .append(talon.getSmartDashboardType()).append("\n").append("getPulseWidthPosition = ")
// .append(talon.getPulseWidthPosition()).append("\n").append("getOutputCurrent = ")
// .append(talon.getOutputCurrent()).append("\n").append("get = ").append(talon.get()).append("\n")
// .append("isZeroSensorPosOnIndexEnabled = ").append(talon.isZeroSensorPosOnIndexEnabled()).append("\n")
// .append("getMotionMagicCruiseVelocity = ").append(talon.getMotionMagicCruiseVelocity()).append("\n")
// .append("getStickyFaultRevSoftLim = ").append(talon.getStickyFaultRevSoftLim()).append("\n")
// .append("getFaultRevLim = ").append(talon.getFaultRevLim()).append("\n").append("getEncPosition = ")
// .append(talon.getEncPosition()).append("\n").append("getIZone = ").append(talon.getIZone()).append("\n")
// .append("getAnalogInPosition = ").append(talon.getAnalogInPosition()).append("\n")
// .append("getFaultUnderVoltage = ").append(talon.getFaultUnderVoltage()).append("\n")
// .append("getCloseLoopRampRate = ").append(talon.getCloseLoopRampRate()).append("\n")
// .append("toString = ").append(talon.toString()).append("\n")
// // .append("getMotionMagicActTrajPosition =
// // ").append(talon.getMotionMagicActTrajPosition()).append("\n")
// .append("getF = ").append(talon.getF()).append("\n").append("getClass = ").append(talon.getClass())
// .append("\n").append("getAnalogInVelocity = ").append(talon.getAnalogInVelocity()).append("\n")
// .append("getI = ").append(talon.getI()).append("\n").append("isReverseSoftLimitEnabled = ")
// .append(talon.isReverseSoftLimitEnabled()).append("\n").append("getPIDSourceType = ")
// .append(talon.getPIDSourceType()).append("\n").append("getEncVelocity = ")
// .append(talon.getEncVelocity()).append("\n").append("GetVelocityMeasurementPeriod = ")
// .append(talon.GetVelocityMeasurementPeriod()).append("\n").append("getP = ").append(talon.getP())
// .append("\n").append("GetVelocityMeasurementWindow = ").append(talon.GetVelocityMeasurementWindow())
// .append("\n").append("getDeviceID = ").append(talon.getDeviceID()).append("\n")
// .append("getStickyFaultRevLim = ").append(talon.getStickyFaultRevLim()).append("\n")
// // .append("getMotionMagicActTrajVelocity =
// // ").append(talon.getMotionMagicActTrajVelocity()).append("\n")
// .append("getReverseSoftLimit = ").append(talon.getReverseSoftLimit()).append("\n").append("getD = ")
// .append(talon.getD()).append("\n").append("getFaultOverTemp = ").append(talon.getFaultOverTemp())
// .append("\n").append("getForwardSoftLimit = ").append(talon.getForwardSoftLimit()).append("\n")
// .append("GetFirmwareVersion = ").append(talon.GetFirmwareVersion()).append("\n")
// .append("getLastError = ").append(talon.getLastError()).append("\n").append("isAlive = ")
// .append(talon.isAlive()).append("\n").append("getPinStateQuadIdx = ").append(talon.getPinStateQuadIdx())
// .append("\n").append("getAnalogInRaw = ").append(talon.getAnalogInRaw()).append("\n")
// .append("getFaultForLim = ").append(talon.getFaultForLim()).append("\n").append("getSpeed = ")
// .append(talon.getSpeed()).append("\n").append("getStickyFaultForLim = ")
// .append(talon.getStickyFaultForLim()).append("\n").append("getFaultForSoftLim = ")
// .append(talon.getFaultForSoftLim()).append("\n").append("getStickyFaultForSoftLim = ")
// .append(talon.getStickyFaultForSoftLim()).append("\n").append("getClosedLoopError = ")
// .append(talon.getClosedLoopError()).append("\n").append("getSetpoint = ").append(talon.getSetpoint())
// .append("\n").append("isMotionProfileTopLevelBufferFull = ")
// .append(talon.isMotionProfileTopLevelBufferFull()).append("\n").append("getDescription = ")
// .append(talon.getDescription()).append("\n").append("hashCode = ").append(talon.hashCode()).append("\n")
// .append("isFwdLimitSwitchClosed = ").append(talon.isFwdLimitSwitchClosed()).append("\n")
// .append("getPinStateQuadA = ").append(talon.getPinStateQuadA()).append("\n")
// .append("getPinStateQuadB = ").append(talon.getPinStateQuadB()).append("\n").append("GetIaccum = ")
// .append(talon.GetIaccum()).append("\n").append("getFaultHardwareFailure = ")
// .append(talon.getFaultHardwareFailure()).append("\n").append("pidGet = ").append(talon.pidGet())
// .append("\n").append("getBrakeEnableDuringNeutral = ").append(talon.getBrakeEnableDuringNeutral())
// .append("\n").append("getStickyFaultUnderVoltage = ").append(talon.getStickyFaultUnderVoltage())
// .append("\n").append("getPulseWidthVelocity = ").append(talon.getPulseWidthVelocity()).append("\n")
// .append("GetNominalClosedLoopVoltage = ").append(talon.GetNominalClosedLoopVoltage()).append("\n")
// .append("getPosition = ").append(talon.getPosition()).append("\n").append("getExpiration = ")
// .append(talon.getExpiration()).append("\n").append("getPulseWidthRiseToFallUs = ")
// .append(talon.getPulseWidthRiseToFallUs()).append("\n")
// // .append("createTableListener = ").append(talon.createTableListener()).append("\n")
// .append("getControlMode = ").append(talon.getControlMode()).append("\n")
// .append("getMotionMagicAcceleration = ").append(talon.getMotionMagicAcceleration()).append("\n")
// .append("getControlMode = ").append(talon.getControlMode());
return sb.toString();
}
}
@@ -0,0 +1,46 @@
package org.usfirst.frc4388.utility;
import java.util.List;
/**
* Contains basic functions that are used often.
*/
public class Util {
/** Prevent this class from being instantiated. */
private Util() {
}
/**
* Limits the given input to the given magnitude.
*/
public static double limit(double v, double maxMagnitude) {
return limit(v, -maxMagnitude, maxMagnitude);
}
public static double limit(double v, double min, double max) {
return Math.min(max, Math.max(min, v));
}
public static String joinStrings(String delim, List<?> strings) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < strings.size(); ++i) {
sb.append(strings.get(i).toString());
if (i < strings.size() - 1) {
sb.append(delim);
}
}
return sb.toString();
}
public static boolean epsilonEquals(double a, double b, double epsilon) {
return (a - epsilon <= b) && (a + epsilon >= b);
}
public static boolean allCloseTo(List<Double> list, double value, double epsilon) {
boolean result = true;
for (Double value_in : list) {
result &= epsilonEquals(value_in, value, epsilon);
}
return result;
}
}
@@ -0,0 +1,200 @@
package org.usfirst.frc4388.utility.control;
import org.usfirst.frc4388.utility.math.RigidTransform2d;
import org.usfirst.frc4388.utility.math.Rotation2d;
import org.usfirst.frc4388.utility.math.Translation2d;
import org.usfirst.frc4388.utility.math.Twist2d;
/**
* Implements an adaptive pure pursuit controller. See:
* https://www.ri.cmu.edu/pub_files/pub1/kelly_alonzo_1994_4/kelly_alonzo_1994_4 .pdf
*
* Basically, we find a spot on the path we'd like to follow and calculate the arc necessary to make us land on that
* spot. The target spot is a specified distance ahead of us, and we look further ahead the greater our tracking error.
* We also return the maximum speed we'd like to be going when we reach the target spot.
*/
public class AdaptivePurePursuitController {
private static final double kReallyBigNumber = 1E6;
public static class Command {
public Twist2d delta = Twist2d.identity();
public double cross_track_error;
public double max_velocity;
public double end_velocity;
public Translation2d lookahead_point;
public double remaining_path_length;
public Command() {
}
public Command(Twist2d delta, double cross_track_error, double max_velocity, double end_velocity,
Translation2d lookahead_point, double remaining_path_length) {
this.delta = delta;
this.cross_track_error = cross_track_error;
this.max_velocity = max_velocity;
this.end_velocity = end_velocity;
this.lookahead_point = lookahead_point;
this.remaining_path_length = remaining_path_length;
}
}
Path mPath;
boolean mAtEndOfPath = false;
final boolean mReversed;
final Lookahead mLookahead;
public AdaptivePurePursuitController(Path path, boolean reversed, Lookahead lookahead) {
mPath = path;
mReversed = reversed;
mLookahead = lookahead;
}
/**
* Gives the RigidTransform2d.Delta that the robot should take to follow the path
*
* @param pose
* robot pose
* @return movement command for the robot to follow
*/
public Command update(RigidTransform2d pose) {
if (mReversed) {
pose = new RigidTransform2d(pose.getTranslation(),
pose.getRotation().rotateBy(Rotation2d.fromRadians(Math.PI)));
}
final Path.TargetPointReport report = mPath.getTargetPoint(pose.getTranslation(), mLookahead);
if (isFinished()) {
// Stop.
return new Command(Twist2d.identity(), report.closest_point_distance, report.max_speed, 0.0,
report.lookahead_point, report.remaining_path_distance);
}
final Arc arc = new Arc(pose, report.lookahead_point);
double scale_factor = 1.0;
// Ensure we don't overshoot the end of the path (once the lookahead speed drops to zero).
if (report.lookahead_point_speed < 1E-6 && report.remaining_path_distance < arc.length) {
scale_factor = Math.max(0.0, report.remaining_path_distance / arc.length);
mAtEndOfPath = true;
} else {
mAtEndOfPath = false;
}
if (mReversed) {
scale_factor *= -1;
}
return new Command(
new Twist2d(scale_factor * arc.length, 0.0,
arc.length * getDirection(pose, report.lookahead_point) * Math.abs(scale_factor) / arc.radius),
report.closest_point_distance, report.max_speed,
report.lookahead_point_speed * Math.signum(scale_factor), report.lookahead_point,
report.remaining_path_distance);
}
public boolean hasPassedMarker(String marker) {
return mPath.hasPassedMarker(marker);
}
public static class Arc {
public Translation2d center;
public double radius;
public double length;
public Arc(RigidTransform2d pose, Translation2d point) {
center = getCenter(pose, point);
radius = new Translation2d(center, point).norm();
length = getLength(pose, point, center, radius);
}
}
/**
* Gives the center of the circle joining the lookahead point and robot pose
*
* @param pose
* robot pose
* @param point
* lookahead point
* @return center of the circle joining the lookahead point and robot pose
*/
public static Translation2d getCenter(RigidTransform2d pose, Translation2d point) {
final Translation2d poseToPointHalfway = pose.getTranslation().interpolate(point, 0.5);
final Rotation2d normal = pose.getTranslation().inverse().translateBy(poseToPointHalfway).direction().normal();
final RigidTransform2d perpendicularBisector = new RigidTransform2d(poseToPointHalfway, normal);
final RigidTransform2d normalFromPose = new RigidTransform2d(pose.getTranslation(),
pose.getRotation().normal());
if (normalFromPose.isColinear(perpendicularBisector.normal())) {
// Special case: center is poseToPointHalfway.
return poseToPointHalfway;
}
return normalFromPose.intersection(perpendicularBisector);
}
/**
* Gives the radius of the circle joining the lookahead point and robot pose
*
* @param pose
* robot pose
* @param point
* lookahead point
* @return radius of the circle joining the lookahead point and robot pose
*/
public static double getRadius(RigidTransform2d pose, Translation2d point) {
Translation2d center = getCenter(pose, point);
return new Translation2d(center, point).norm();
}
/**
* Gives the length of the arc joining the lookahead point and robot pose (assuming forward motion).
*
* @param pose
* robot pose
* @param point
* lookahead point
* @return the length of the arc joining the lookahead point and robot pose
*/
public static double getLength(RigidTransform2d pose, Translation2d point) {
final double radius = getRadius(pose, point);
final Translation2d center = getCenter(pose, point);
return getLength(pose, point, center, radius);
}
public static double getLength(RigidTransform2d pose, Translation2d point, Translation2d center, double radius) {
if (radius < kReallyBigNumber) {
final Translation2d centerToPoint = new Translation2d(center, point);
final Translation2d centerToPose = new Translation2d(center, pose.getTranslation());
// If the point is behind pose, we want the opposite of this angle. To determine if the point is behind,
// check the sign of the cross-product between the normal vector and the vector from pose to point.
final boolean behind = Math.signum(
Translation2d.cross(pose.getRotation().normal().toTranslation(),
new Translation2d(pose.getTranslation(), point))) > 0.0;
final Rotation2d angle = Translation2d.getAngle(centerToPose, centerToPoint);
return radius * (behind ? 2.0 * Math.PI - Math.abs(angle.getRadians()) : Math.abs(angle.getRadians()));
} else {
return new Translation2d(pose.getTranslation(), point).norm();
}
}
/**
* Gives the direction the robot should turn to stay on the path
*
* @param pose
* robot pose
* @param point
* lookahead point
* @return the direction the robot should turn: -1 is left, +1 is right
*/
public static int getDirection(RigidTransform2d pose, Translation2d point) {
Translation2d poseToPoint = new Translation2d(pose.getTranslation(), point);
Translation2d robot = pose.getRotation().toTranslation();
double cross = robot.x() * poseToPoint.y() - robot.y() * poseToPoint.x();
return (cross < 0) ? -1 : 1; // if robot < pose turn left
}
/**
* @return has the robot reached the end of the path
*/
public boolean isFinished() {
return mAtEndOfPath;
}
}
@@ -0,0 +1,83 @@
package org.usfirst.frc4388.utility.control;
import org.usfirst.frc4388.robot.Constants;
import org.usfirst.frc4388.utility.math.RigidTransform2d;
import org.usfirst.frc4388.utility.math.Rotation2d;
import org.usfirst.frc4388.utility.math.Twist2d;
/**
* Provides forward and inverse kinematics equations for the robot modeling the wheelbase as a differential drive (with
* a corrective factor to account for skidding).
*/
public class Kinematics {
private static final double kEpsilon = 1E-9;
/**
* Forward kinematics using only encoders, rotation is implicit (less accurate than below, but useful for predicting
* motion)
*/
public static Twist2d forwardKinematics(double left_wheel_delta, double right_wheel_delta) {
double delta_v = (right_wheel_delta - left_wheel_delta) / 2 * Constants.kTrackScrubFactor;
double delta_rotation = delta_v * 2 / Constants.kTrackWidthInches;
return forwardKinematics(left_wheel_delta, right_wheel_delta, delta_rotation);
}
/**
* Forward kinematics using encoders and explicitly measured rotation (ex. from gyro)
*/
public static Twist2d forwardKinematics(double left_wheel_delta, double right_wheel_delta,
double delta_rotation_rads) {
final double dx = (left_wheel_delta + right_wheel_delta) / 2.0;
return new Twist2d(dx, 0, delta_rotation_rads);
}
/**
* For convenience, forward kinematic with an absolute rotation and previous rotation.
*/
public static Twist2d forwardKinematics(Rotation2d prev_heading, double left_wheel_delta, double right_wheel_delta,
Rotation2d current_heading) {
return forwardKinematics(left_wheel_delta, right_wheel_delta,
prev_heading.inverse().rotateBy(current_heading).getRadians());
}
/** Append the result of forward kinematics to a previous pose. */
public static RigidTransform2d integrateForwardKinematics(RigidTransform2d current_pose, double left_wheel_delta,
double right_wheel_delta, Rotation2d current_heading) {
Twist2d with_gyro = forwardKinematics(current_pose.getRotation(), left_wheel_delta, right_wheel_delta,
current_heading);
return integrateForwardKinematics(current_pose, with_gyro);
}
/**
* For convenience, integrate forward kinematics with a Twist2d and previous rotation.
*/
public static RigidTransform2d integrateForwardKinematics(RigidTransform2d current_pose,
Twist2d forward_kinematics) {
return current_pose.transformBy(RigidTransform2d.exp(forward_kinematics));
}
/**
* Class that contains left and right wheel velocities
*/
public static class DriveVelocity {
public final double left;
public final double right;
public DriveVelocity(double left, double right) {
this.left = left;
this.right = right;
}
}
/**
* Uses inverse kinematics to convert a Twist2d into left and right wheel velocities
*/
public static DriveVelocity inverseKinematics(Twist2d velocity) {
if (Math.abs(velocity.dtheta) < kEpsilon) {
return new DriveVelocity(velocity.dx, velocity.dx);
}
double delta_v = Constants.kTrackWidthInches * velocity.dtheta / (2 * Constants.kTrackScrubFactor);
return new DriveVelocity(velocity.dx - delta_v, velocity.dx + delta_v);
}
}
@@ -0,0 +1,28 @@
package org.usfirst.frc4388.utility.control;
/**
* A utility class for interpolating lookahead distance based on current speed.
*/
public class Lookahead {
public final double min_distance;
public final double max_distance;
public final double min_speed;
public final double max_speed;
protected final double delta_distance;
protected final double delta_speed;
public Lookahead(double min_distance, double max_distance, double min_speed, double max_speed) {
this.min_distance = min_distance;
this.max_distance = max_distance;
this.min_speed = min_speed;
this.max_speed = max_speed;
delta_distance = max_distance - min_distance;
delta_speed = max_speed - min_speed;
}
public double getLookaheadForSpeed(double speed) {
double lookahead = delta_distance * (speed - min_speed) / delta_speed + min_distance;
return Double.isNaN(lookahead) ? min_distance : Math.max(min_distance, Math.min(max_distance, lookahead));
}
}
@@ -0,0 +1,235 @@
package org.usfirst.frc4388.utility.control;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import org.usfirst.frc4388.robot.Constants;
import org.usfirst.frc4388.utility.math.Translation2d;
import org.usfirst.frc4388.utility.motion.MotionState;
/**
* Class representing the robot's autonomous path.
*
* Field Coordinate System: Uses a right hand coordinate system. Positive x is right, positive y is up, and the origin
* is at the bottom left corner of the field. For angles, 0 degrees is facing right (1, 0) and angles increase as you
* turn counter clockwise.
*
* +Y
* ^
* |
* |------------------------------
* | | <--- FRC Field Boundary
* | |
* | |
* | ----- |
* | |robot|--> 0 deg |
* | ----- |
* ----------------------------------> +X
* (0,0)
*
* Notes:
* 1) The starting point needs to match the first point (x,y) with the proper starting rotation
* 2) When chaining together paths, keep all coordinates for both paths in the reference frame above.
* The second path should start where the first stopped.
* 3) When moving in reverse, you need to set the isReversed flag on the path.
*
*/
public class Path {
List<PathSegment> segments;
PathSegment prevSegment;
HashSet<String> mMarkersCrossed = new HashSet<String>();
public void extrapolateLast() {
PathSegment last = segments.get(segments.size() - 1);
last.extrapolateLookahead(true);
}
public Translation2d getEndPosition() {
return segments.get(segments.size() - 1).getEnd();
}
public Path() {
segments = new ArrayList<PathSegment>();
}
/**
* add a segment to the Path
*
* @param segment
* the segment to add
*/
public void addSegment(PathSegment segment) {
segments.add(segment);
}
/**
* @return the last MotionState in the path
*/
public MotionState getLastMotionState() {
if (segments.size() > 0) {
MotionState endState = segments.get(segments.size() - 1).getEndState();
return new MotionState(0.0, 0.0, endState.vel(), endState.acc());
} else {
return new MotionState(0, 0, 0, 0);
}
}
/**
* get the remaining distance left for the robot to travel on the current segment
*
* @param robotPos
* robot position
* @return remaining distance on current segment
*/
public double getSegmentRemainingDist(Translation2d robotPos) {
PathSegment currentSegment = segments.get(0);
return currentSegment.getRemainingDistance(currentSegment.getClosestPoint(robotPos));
}
/**
* @return the length of the current segment
*/
public double getSegmentLength() {
PathSegment currentSegment = segments.get(0);
return currentSegment.getLength();
}
public static class TargetPointReport {
public Translation2d closest_point;
public double closest_point_distance;
public double closest_point_speed;
public Translation2d lookahead_point;
public double max_speed;
public double lookahead_point_speed;
public double remaining_segment_distance;
public double remaining_path_distance;
public TargetPointReport() {
}
}
/**
* Gives the position of the lookahead point (and removes any segments prior to this point).
*
* @param robot
* Translation of the current robot pose.
* @return report containing everything we might want to know about the target point.
*/
public TargetPointReport getTargetPoint(Translation2d robot, Lookahead lookahead) {
TargetPointReport rv = new TargetPointReport();
PathSegment currentSegment = segments.get(0);
rv.closest_point = currentSegment.getClosestPoint(robot);
rv.closest_point_distance = new Translation2d(robot, rv.closest_point).norm();
/*
* if (segments.size() > 1) { // Check next segment to see if it is closer. final Translation2d
* next_segment_closest_point = segments.get(1).getClosestPoint(robot); final double
* next_segment_closest_point_distance = new Translation2d(robot, next_segment_closest_point) .norm(); if
* (next_segment_closest_point_distance < rv.closest_point_distance) { rv.closest_point =
* next_segment_closest_point; rv.closest_point_distance = next_segment_closest_point_distance;
* removeCurrentSegment(); currentSegment = segments.get(0); } }
*/
rv.remaining_segment_distance = currentSegment.getRemainingDistance(rv.closest_point);
rv.remaining_path_distance = rv.remaining_segment_distance;
for (int i = 1; i < segments.size(); ++i) {
rv.remaining_path_distance += segments.get(i).getLength();
}
rv.closest_point_speed = currentSegment
.getSpeedByDistance(currentSegment.getLength() - rv.remaining_segment_distance);
double lookahead_distance = lookahead.getLookaheadForSpeed(rv.closest_point_speed) + rv.closest_point_distance;
if (rv.remaining_segment_distance < lookahead_distance && segments.size() > 1) {
lookahead_distance -= rv.remaining_segment_distance;
for (int i = 1; i < segments.size(); ++i) {
currentSegment = segments.get(i);
final double length = currentSegment.getLength();
if (length < lookahead_distance && i < segments.size() - 1) {
lookahead_distance -= length;
} else {
break;
}
}
} else {
lookahead_distance += (currentSegment.getLength() - rv.remaining_segment_distance);
}
rv.max_speed = currentSegment.getMaxSpeed();
rv.lookahead_point = currentSegment.getPointByDistance(lookahead_distance);
rv.lookahead_point_speed = currentSegment.getSpeedByDistance(lookahead_distance);
checkSegmentDone(rv.closest_point);
return rv;
}
/**
* Gives the speed the robot should be traveling at the given position
*
* @param robotPos
* position of the robot
* @return speed robot should be traveling
*/
public double getSpeed(Translation2d robotPos) {
PathSegment currentSegment = segments.get(0);
return currentSegment.getSpeedByClosestPoint(robotPos);
}
/**
* Checks if the robot has finished traveling along the current segment then removes it from the path if it has
*
* @param robotPos
* robot position
*/
public void checkSegmentDone(Translation2d robotPos) {
PathSegment currentSegment = segments.get(0);
double remainingDist = currentSegment.getRemainingDistance(currentSegment.getClosestPoint(robotPos));
if (remainingDist < 0.1) {
// System.out.println("Removed segment from path: " + currentSegment);
removeCurrentSegment();
}
}
public void removeCurrentSegment() {
prevSegment = segments.remove(0);
String marker = prevSegment.getMarker();
if (marker != null)
mMarkersCrossed.add(marker);
}
/**
* Ensures that all speeds in the path are attainable and robot can slow down in time
*/
public void verifySpeeds() {
double maxStartSpeed = 0.0;
double[] startSpeeds = new double[segments.size() + 1];
startSpeeds[segments.size()] = 0.0;
for (int i = segments.size() - 1; i >= 0; i--) {
PathSegment segment = segments.get(i);
maxStartSpeed += Math
.sqrt(maxStartSpeed * maxStartSpeed + 2 * Constants.kPathFollowingMaxAccel * segment.getLength());
startSpeeds[i] = segment.getStartState().vel();
// System.out.println(maxStartSpeed + ", " + startSpeeds[i]);
if (startSpeeds[i] > maxStartSpeed) {
startSpeeds[i] = maxStartSpeed;
// System.out.println("Segment starting speed is too high!");
}
maxStartSpeed = startSpeeds[i];
}
for (int i = 0; i < segments.size(); i++) {
PathSegment segment = segments.get(i);
double endSpeed = startSpeeds[i + 1];
MotionState startState = (i > 0) ? segments.get(i - 1).getEndState() : new MotionState(0, 0, 0, 0);
startState = new MotionState(0, 0, startState.vel(), startState.vel());
segment.createMotionProfiler(startState, endSpeed);
}
}
public boolean hasPassedMarker(String marker) {
return mMarkersCrossed.contains(marker);
}
public String toString() {
String str = "";
for (PathSegment s : segments) {
str += s.toString() + "\n";
}
return str;
}
}
@@ -0,0 +1,198 @@
package org.usfirst.frc4388.utility.control;
import org.usfirst.frc4388.utility.math.RigidTransform2d;
import org.usfirst.frc4388.utility.math.Twist2d;
import org.usfirst.frc4388.utility.motion.MotionProfileConstraints;
import org.usfirst.frc4388.utility.motion.MotionProfileGoal;
import org.usfirst.frc4388.utility.motion.MotionProfileGoal.CompletionBehavior;
import org.usfirst.frc4388.utility.motion.MotionState;
import org.usfirst.frc4388.utility.motion.ProfileFollower;
/**
* A PathFollower follows a predefined path using a combination of feedforward and feedback control. It uses an
* AdaptivePurePursuitController to choose a reference pose and generate a steering command (curvature), and then a
* ProfileFollower to generate a profile (displacement and velocity) command.
*/
public class PathFollower {
private static final double kReallyBigNumber = 1E6;
public static class DebugOutput {
public double t;
public double pose_x;
public double pose_y;
public double pose_theta;
public double linear_displacement;
public double linear_velocity;
public double profile_displacement;
public double profile_velocity;
public double velocity_command_dx;
public double velocity_command_dy;
public double velocity_command_dtheta;
public double steering_command_dx;
public double steering_command_dy;
public double steering_command_dtheta;
public double cross_track_error;
public double along_track_error;
public double lookahead_point_x;
public double lookahead_point_y;
public double lookahead_point_velocity;
}
public static class Parameters {
public final Lookahead lookahead;
public final double inertia_gain;
public final double profile_kp;
public final double profile_ki;
public final double profile_kv;
public final double profile_kffv;
public final double profile_kffa;
public final double profile_max_abs_vel;
public final double profile_max_abs_acc;
public final double goal_pos_tolerance;
public final double goal_vel_tolerance;
public final double stop_steering_distance;
public Parameters(Lookahead lookahead, double inertia_gain, double profile_kp, double profile_ki,
double profile_kv, double profile_kffv, double profile_kffa, double profile_max_abs_vel,
double profile_max_abs_acc, double goal_pos_tolerance, double goal_vel_tolerance,
double stop_steering_distance) {
this.lookahead = lookahead;
this.inertia_gain = inertia_gain;
this.profile_kp = profile_kp;
this.profile_ki = profile_ki;
this.profile_kv = profile_kv;
this.profile_kffv = profile_kffv;
this.profile_kffa = profile_kffa;
this.profile_max_abs_vel = profile_max_abs_vel;
this.profile_max_abs_acc = profile_max_abs_acc;
this.goal_pos_tolerance = goal_pos_tolerance;
this.goal_vel_tolerance = goal_vel_tolerance;
this.stop_steering_distance = stop_steering_distance;
}
}
AdaptivePurePursuitController mSteeringController;
Twist2d mLastSteeringDelta;
ProfileFollower mVelocityController;
final double mInertiaGain;
boolean overrideFinished = false;
boolean doneSteering = false;
DebugOutput mDebugOutput = new DebugOutput();
double mMaxProfileVel;
double mMaxProfileAcc;
final double mGoalPosTolerance;
final double mGoalVelTolerance;
final double mStopSteeringDistance;
double mCrossTrackError = 0.0;
double mAlongTrackError = 0.0;
/**
* Create a new PathFollower for a given path.
*/
public PathFollower(Path path, boolean reversed, Parameters parameters) {
mSteeringController = new AdaptivePurePursuitController(path, reversed, parameters.lookahead);
mLastSteeringDelta = Twist2d.identity();
mVelocityController = new ProfileFollower(parameters.profile_kp, parameters.profile_ki, parameters.profile_kv,
parameters.profile_kffv, parameters.profile_kffa);
mVelocityController.setConstraints(
new MotionProfileConstraints(parameters.profile_max_abs_vel, parameters.profile_max_abs_acc));
mMaxProfileVel = parameters.profile_max_abs_vel;
mMaxProfileAcc = parameters.profile_max_abs_acc;
mGoalPosTolerance = parameters.goal_pos_tolerance;
mGoalVelTolerance = parameters.goal_vel_tolerance;
mInertiaGain = parameters.inertia_gain;
mStopSteeringDistance = parameters.stop_steering_distance;
}
/**
* Get new velocity commands to follow the path.
*
* @param t
* The current timestamp
* @param pose
* The current robot pose
* @param displacement
* The current robot displacement (total distance driven).
* @param velocity
* The current robot velocity.
* @return The velocity command to apply
*/
public synchronized Twist2d update(double t, RigidTransform2d pose, double displacement, double velocity) {
if (!mSteeringController.isFinished()) {
final AdaptivePurePursuitController.Command steering_command = mSteeringController.update(pose);
mDebugOutput.lookahead_point_x = steering_command.lookahead_point.x();
mDebugOutput.lookahead_point_y = steering_command.lookahead_point.y();
mDebugOutput.lookahead_point_velocity = steering_command.end_velocity;
mDebugOutput.steering_command_dx = steering_command.delta.dx;
mDebugOutput.steering_command_dy = steering_command.delta.dy;
mDebugOutput.steering_command_dtheta = steering_command.delta.dtheta;
mCrossTrackError = steering_command.cross_track_error;
mLastSteeringDelta = steering_command.delta;
mVelocityController.setGoalAndConstraints(
new MotionProfileGoal(displacement + steering_command.delta.dx,
Math.abs(steering_command.end_velocity), CompletionBehavior.VIOLATE_MAX_ACCEL,
mGoalPosTolerance, mGoalVelTolerance),
new MotionProfileConstraints(Math.min(mMaxProfileVel, steering_command.max_velocity),
mMaxProfileAcc));
if (steering_command.remaining_path_length < mStopSteeringDistance) {
doneSteering = true;
}
}
final double velocity_command = mVelocityController.update(new MotionState(t, displacement, velocity, 0.0), t);
mAlongTrackError = mVelocityController.getPosError();
final double curvature = mLastSteeringDelta.dtheta / mLastSteeringDelta.dx;
double dtheta = mLastSteeringDelta.dtheta;
if (!Double.isNaN(curvature) && Math.abs(curvature) < kReallyBigNumber) {
// Regenerate angular velocity command from adjusted curvature.
final double abs_velocity_setpoint = Math.abs(mVelocityController.getSetpoint().vel());
dtheta = mLastSteeringDelta.dx * curvature * (1.0 + mInertiaGain * abs_velocity_setpoint);
}
final double scale = velocity_command / mLastSteeringDelta.dx;
final Twist2d rv = new Twist2d(mLastSteeringDelta.dx * scale, 0.0, dtheta * scale);
// Fill out debug.
mDebugOutput.t = t;
mDebugOutput.pose_x = pose.getTranslation().x();
mDebugOutput.pose_y = pose.getTranslation().y();
mDebugOutput.pose_theta = pose.getRotation().getRadians();
mDebugOutput.linear_displacement = displacement;
mDebugOutput.linear_velocity = velocity;
mDebugOutput.profile_displacement = mVelocityController.getSetpoint().pos();
mDebugOutput.profile_velocity = mVelocityController.getSetpoint().vel();
mDebugOutput.velocity_command_dx = rv.dx;
mDebugOutput.velocity_command_dy = rv.dy;
mDebugOutput.velocity_command_dtheta = rv.dtheta;
mDebugOutput.cross_track_error = mCrossTrackError;
mDebugOutput.along_track_error = mAlongTrackError;
return rv;
}
public double getCrossTrackError() {
return mCrossTrackError;
}
public double getAlongTrackError() {
return mAlongTrackError;
}
public DebugOutput getDebug() {
return mDebugOutput;
}
public boolean isFinished() {
return (mSteeringController.isFinished() && mVelocityController.isFinishedProfile()
&& mVelocityController.onTarget()) || overrideFinished;
}
public void forceFinish() {
overrideFinished = true;
}
public boolean hasPassedMarker(String marker) {
return mSteeringController.hasPassedMarker(marker);
}
}
@@ -0,0 +1,287 @@
package org.usfirst.frc4388.utility.control;
import java.util.Optional;
import org.usfirst.frc4388.robot.Constants;
import org.usfirst.frc4388.utility.math.Rotation2d;
import org.usfirst.frc4388.utility.math.Translation2d;
import org.usfirst.frc4388.utility.motion.MotionProfile;
import org.usfirst.frc4388.utility.motion.MotionProfileConstraints;
import org.usfirst.frc4388.utility.motion.MotionProfileGenerator;
import org.usfirst.frc4388.utility.motion.MotionProfileGoal;
import org.usfirst.frc4388.utility.motion.MotionState;
/**
* Class representing a segment of the robot's autonomous path.
*/
public class PathSegment {
private Translation2d start;
private Translation2d end;
private Translation2d center;
private Translation2d deltaStart;
private Translation2d deltaEnd;
private double maxSpeed;
private boolean isLine;
private MotionProfile speedController;
private boolean extrapolateLookahead;
private String marker;
/**
* Constructor for a linear segment
*
* @param x1
* start x
* @param y1
* start y
* @param x2
* end x
* @param y2
* end y
* @param maxSpeed
* maximum speed allowed on the segment
*/
public PathSegment(double x1, double y1, double x2, double y2, double maxSpeed, MotionState startState,
double endSpeed) {
this.start = new Translation2d(x1, y1);
this.end = new Translation2d(x2, y2);
this.deltaStart = new Translation2d(start, end);
this.maxSpeed = maxSpeed;
extrapolateLookahead = false;
isLine = true;
createMotionProfiler(startState, endSpeed);
}
public PathSegment(double x1, double y1, double x2, double y2, double maxSpeed, MotionState startState,
double endSpeed, String marker) {
this.start = new Translation2d(x1, y1);
this.end = new Translation2d(x2, y2);
this.deltaStart = new Translation2d(start, end);
this.maxSpeed = maxSpeed;
extrapolateLookahead = false;
isLine = true;
this.marker = marker;
createMotionProfiler(startState, endSpeed);
}
/**
* Constructor for an arc segment
*
* @param x1
* start x
* @param y1
* start y
* @param x2
* end x
* @param y2
* end y
* @param cx
* center x
* @param cy
* center y
* @param maxSpeed
* maximum speed allowed on the segment
*/
public PathSegment(double x1, double y1, double x2, double y2, double cx, double cy, double maxSpeed,
MotionState startState, double endSpeed) {
this.start = new Translation2d(x1, y1);
this.end = new Translation2d(x2, y2);
this.center = new Translation2d(cx, cy);
this.deltaStart = new Translation2d(center, start);
this.deltaEnd = new Translation2d(center, end);
this.maxSpeed = maxSpeed;
extrapolateLookahead = false;
isLine = false;
createMotionProfiler(startState, endSpeed);
}
public PathSegment(double x1, double y1, double x2, double y2, double cx, double cy, double maxSpeed,
MotionState startState, double endSpeed, String marker) {
this.start = new Translation2d(x1, y1);
this.end = new Translation2d(x2, y2);
this.center = new Translation2d(cx, cy);
this.deltaStart = new Translation2d(center, start);
this.deltaEnd = new Translation2d(center, end);
this.maxSpeed = maxSpeed;
extrapolateLookahead = false;
isLine = false;
this.marker = marker;
createMotionProfiler(startState, endSpeed);
}
/**
* @return max speed of the segment
*/
public double getMaxSpeed() {
return maxSpeed;
}
public void createMotionProfiler(MotionState start_state, double end_speed) {
MotionProfileConstraints motionConstraints = new MotionProfileConstraints(maxSpeed,
Constants.kPathFollowingMaxAccel);
MotionProfileGoal goal_state = new MotionProfileGoal(getLength(), end_speed);
speedController = MotionProfileGenerator.generateProfile(motionConstraints, goal_state, start_state);
// System.out.println(speedController);
}
/**
* @return starting point of the segment
*/
public Translation2d getStart() {
return start;
}
/**
* @return end point of the segment
*/
public Translation2d getEnd() {
return end;
}
/**
* @return the total length of the segment
*/
public double getLength() {
if (isLine) {
return deltaStart.norm();
} else {
return deltaStart.norm() * Translation2d.getAngle(deltaStart, deltaEnd).getRadians();
}
}
/**
* Set whether or not to extrapolate the lookahead point. Should only be true for the last segment in the path
*
* @param val
*/
public void extrapolateLookahead(boolean val) {
extrapolateLookahead = val;
}
/**
* Gets the point on the segment closest to the robot
*
* @param position
* the current position of the robot
* @return the point on the segment closest to the robot
*/
public Translation2d getClosestPoint(Translation2d position) {
if (isLine) {
Translation2d delta = new Translation2d(start, end);
double u = ((position.x() - start.x()) * delta.x() + (position.y() - start.y()) * delta.y())
/ (delta.x() * delta.x() + delta.y() * delta.y());
if (u >= 0 && u <= 1)
return new Translation2d(start.x() + u * delta.x(), start.y() + u * delta.y());
return (u < 0) ? start : end;
} else {
Translation2d deltaPosition = new Translation2d(center, position);
deltaPosition = deltaPosition.scale(deltaStart.norm() / deltaPosition.norm());
if (Translation2d.cross(deltaPosition, deltaStart) * Translation2d.cross(deltaPosition, deltaEnd) < 0) {
return center.translateBy(deltaPosition);
} else {
Translation2d startDist = new Translation2d(position, start);
Translation2d endDist = new Translation2d(position, end);
return (endDist.norm() < startDist.norm()) ? end : start;
}
}
}
/**
* Calculates the point on the segment <code>dist</code> distance from the starting point along the segment.
*
* @param dist
* distance from the starting point
* @return point on the segment <code>dist</code> distance from the starting point
*/
public Translation2d getPointByDistance(double dist) {
double length = getLength();
if (!extrapolateLookahead && dist > length) {
dist = length;
}
if (isLine) {
return start.translateBy(deltaStart.scale(dist / length));
} else {
double deltaAngle = Translation2d.getAngle(deltaStart, deltaEnd).getRadians()
* ((Translation2d.cross(deltaStart, deltaEnd) >= 0) ? 1 : -1);
deltaAngle *= dist / length;
Translation2d t = deltaStart.rotateBy(Rotation2d.fromRadians(deltaAngle));
return center.translateBy(t);
}
}
/**
* Gets the remaining distance left on the segment from point <code>point</code>
*
* @param point
* result of <code>getClosestPoint()</code>
* @return distance remaining
*/
public double getRemainingDistance(Translation2d position) {
if (isLine) {
return new Translation2d(end, position).norm();
} else {
Translation2d deltaPosition = new Translation2d(center, position);
double angle = Translation2d.getAngle(deltaEnd, deltaPosition).getRadians();
double totalAngle = Translation2d.getAngle(deltaStart, deltaEnd).getRadians();
return angle / totalAngle * getLength();
}
}
private double getDistanceTravelled(Translation2d robotPosition) {
Translation2d pathPosition = getClosestPoint(robotPosition);
double remainingDist = getRemainingDistance(pathPosition);
return getLength() - remainingDist;
}
public double getSpeedByDistance(double dist) {
if (dist < speedController.startPos()) {
dist = speedController.startPos();
} else if (dist > speedController.endPos()) {
dist = speedController.endPos();
}
Optional<MotionState> state = speedController.firstStateByPos(dist);
if (state.isPresent()) {
return state.get().vel();
} else {
System.out.println("Velocity does not exist at that position!");
return 0.0;
}
}
public double getSpeedByClosestPoint(Translation2d robotPosition) {
return getSpeedByDistance(getDistanceTravelled(robotPosition));
}
public MotionState getEndState() {
return speedController.endState();
}
public MotionState getStartState() {
return speedController.startState();
}
public String getMarker() {
return marker;
}
public String toString() {
if (isLine) {
return "(" + "start: " + start + ", end: " + end + ", speed: " + maxSpeed // + ", profile: " +
// speedController
+ ")";
} else {
return "(" + "start: " + start + ", end: " + end + ", center: " + center + ", speed: " + maxSpeed
+ ")"; // + ", profile: " + speedController + ")";
}
}
}
@@ -0,0 +1,133 @@
package org.usfirst.frc4388.utility.control;
import java.util.Map;
import org.usfirst.frc4388.robot.Robot.OperationMode;
import org.usfirst.frc4388.utility.math.InterpolatingDouble;
import org.usfirst.frc4388.utility.math.InterpolatingTreeMap;
import org.usfirst.frc4388.utility.math.RigidTransform2d;
import org.usfirst.frc4388.utility.math.Rotation2d;
import org.usfirst.frc4388.utility.math.Twist2d;
import edu.wpi.first.wpilibj.smartdashboard.SmartDashboard;
/**
* RobotState keeps track of the poses of various coordinate frames throughout the match. A coordinate frame is simply a
* point and direction in space that defines an (x,y) coordinate system. Transforms (or poses) keep track of the spatial
* relationship between different frames.
*
* Robot frames of interest (from parent to child):
*
* 1. Field frame: origin is where the robot is turned on
*
* 2. Vehicle frame: origin is the center of the robot wheelbase, facing forwards
*
* 3. Camera frame: origin is the center of the camera imager relative to the robot base.
*
* 4. Goal frame: origin is the center of the boiler (note that orientation in this frame is arbitrary). Also note that
* there can be multiple goal frames.
*
* As a kinematic chain with 4 frames, there are 3 transforms of interest:
*
* 1. Field-to-vehicle: This is tracked over time by integrating encoder and gyro measurements. It will inevitably
* drift, but is usually accurate over short time periods.
*
* 2. Vehicle-to-camera: This is a constant.
*
* 3. Camera-to-goal: This is a pure translation, and is measured by the vision system.
*/
public class RobotState {
private static RobotState instance_ = new RobotState();
public static RobotState getInstance() {
return instance_;
}
private static final int kObservationBufferSize = 100;
// FPGATimestamp -> RigidTransform2d or Rotation2d
private InterpolatingTreeMap<InterpolatingDouble, RigidTransform2d> field_to_vehicle_;
private Twist2d vehicle_velocity_predicted_;
private Twist2d vehicle_velocity_measured_;
private double distance_driven_;
private RobotState() {
reset(0, new RigidTransform2d());
}
/**
* Resets the field to robot transform (robot's position on the field)
*/
public synchronized void reset(double start_time, RigidTransform2d initial_field_to_vehicle) {
field_to_vehicle_ = new InterpolatingTreeMap<>(kObservationBufferSize);
field_to_vehicle_.put(new InterpolatingDouble(start_time), initial_field_to_vehicle);
vehicle_velocity_predicted_ = Twist2d.identity();
vehicle_velocity_measured_ = Twist2d.identity();
distance_driven_ = 0.0;
}
public synchronized void resetDistanceDriven() {
distance_driven_ = 0.0;
}
/**
* Returns the robot's position on the field at a certain time. Linearly interpolates between stored robot positions
* to fill in the gaps.
*/
public synchronized RigidTransform2d getFieldToVehicle(double timestamp) {
return field_to_vehicle_.getInterpolated(new InterpolatingDouble(timestamp));
}
public synchronized Map.Entry<InterpolatingDouble, RigidTransform2d> getLatestFieldToVehicle() {
return field_to_vehicle_.lastEntry();
}
public synchronized RigidTransform2d getPredictedFieldToVehicle(double lookahead_time) {
return getLatestFieldToVehicle().getValue()
.transformBy(RigidTransform2d.exp(vehicle_velocity_predicted_.scaled(lookahead_time)));
}
public synchronized void addFieldToVehicleObservation(double timestamp, RigidTransform2d observation) {
field_to_vehicle_.put(new InterpolatingDouble(timestamp), observation);
}
public synchronized void addObservations(double timestamp, Twist2d measured_velocity,
Twist2d predicted_velocity) {
addFieldToVehicleObservation(timestamp,
Kinematics.integrateForwardKinematics(getLatestFieldToVehicle().getValue(), measured_velocity));
vehicle_velocity_measured_ = measured_velocity;
vehicle_velocity_predicted_ = predicted_velocity;
}
public synchronized Twist2d generateOdometryFromSensors(double left_encoder_delta_distance,
double right_encoder_delta_distance, Rotation2d current_gyro_angle) {
final RigidTransform2d last_measurement = getLatestFieldToVehicle().getValue();
final Twist2d delta = Kinematics.forwardKinematics(last_measurement.getRotation(),
left_encoder_delta_distance, right_encoder_delta_distance, current_gyro_angle);
distance_driven_ += delta.dx;
return delta;
}
public synchronized double getDistanceDriven() {
return distance_driven_;
}
public synchronized Twist2d getPredictedVelocity() {
return vehicle_velocity_predicted_;
}
public synchronized Twist2d getMeasuredVelocity() {
return vehicle_velocity_measured_;
}
public void updateStatus(OperationMode operationMode) {
if (operationMode == OperationMode.TEST) {
RigidTransform2d odometry = getLatestFieldToVehicle().getValue();
SmartDashboard.putNumber("robot_pose_x", odometry.getTranslation().x());
SmartDashboard.putNumber("robot_pose_y", odometry.getTranslation().y());
SmartDashboard.putNumber("robot_pose_theta", odometry.getRotation().getDegrees());
SmartDashboard.putNumber("robot velocity", vehicle_velocity_measured_.dx);
}
}
}
@@ -0,0 +1,333 @@
package org.usfirst.frc4388.utility.control;
//import edu.wpi.first.wpilibj.util.BoundaryException;
/**
* This class implements a PID Control Loop.
*
* Does all computation synchronously (i.e. the calculate() function must be called by the user from his own thread)
*/
public class SynchronousPIDF {
private double m_P; // factor for "proportional" control
private double m_I; // factor for "integral" control
private double m_D; // factor for "derivative" control
private double m_F; // factor for feed forward gain
private double m_maximumOutput = 1.0; // |maximum output|
private double m_minimumOutput = -1.0; // |minimum output|
private double m_maximumInput = 0.0; // maximum input - limit setpoint to
// this
private double m_minimumInput = 0.0; // minimum input - limit setpoint to
// this
private boolean m_continuous = false; // do the endpoints wrap around? eg.
// Absolute encoder
private double m_prevError = 0.0; // the prior sensor input (used to compute
// velocity)
private double m_totalError = 0.0; // the sum of the errors for use in the
// integral calc
private double m_setpoint = 0.0;
private double m_error = 0.0;
private double m_result = 0.0;
private double m_last_input = Double.NaN;
private double m_deadband = 0.0; // If the absolute error is less than
// deadband
// then treat error for the proportional
// term as 0
public SynchronousPIDF() {
}
/**
* Allocate a PID object with the given constants for P, I, D
*
* @param Kp
* the proportional coefficient
* @param Ki
* the integral coefficient
* @param Kd
* the derivative coefficient
*/
public SynchronousPIDF(double Kp, double Ki, double Kd) {
m_P = Kp;
m_I = Ki;
m_D = Kd;
m_F = 0;
}
/**
* Allocate a PID object with the given constants for P, I, D
*
* @param Kp
* the proportional coefficient
* @param Ki
* the integral coefficient
* @param Kd
* the derivative coefficient
* @param Kf
* the feed forward gain coefficient
*/
public SynchronousPIDF(double Kp, double Ki, double Kd, double Kf) {
m_P = Kp;
m_I = Ki;
m_D = Kd;
m_F = Kf;
}
/**
* Read the input, calculate the output accordingly, and write to the output. This should be called at a constant
* rate by the user (ex. in a timed thread)
*
* @param input
* the input
* @param dt
* time passed since previous call to calculate
*/
public double calculate(double input, double dt) {
if (dt < 1E-6)
dt = 1E-6;
m_last_input = input;
m_error = m_setpoint - input;
if (m_continuous) {
if (Math.abs(m_error) > (m_maximumInput - m_minimumInput) / 2) {
if (m_error > 0) {
m_error = m_error - m_maximumInput + m_minimumInput;
} else {
m_error = m_error + m_maximumInput - m_minimumInput;
}
}
}
if ((m_error * m_P < m_maximumOutput) && (m_error * m_P > m_minimumOutput)) {
m_totalError += m_error * dt;
} else {
m_totalError = 0;
}
// Don't blow away m_error so as to not break derivative
double proportionalError = Math.abs(m_error) < m_deadband ? 0 : m_error;
m_result = (m_P * proportionalError + m_I * m_totalError + m_D * (m_error - m_prevError) / dt
+ m_F * m_setpoint);
m_prevError = m_error;
if (m_result > m_maximumOutput) {
m_result = m_maximumOutput;
} else if (m_result < m_minimumOutput) {
m_result = m_minimumOutput;
}
return m_result;
}
/**
* Set the PID controller gain parameters. Set the proportional, integral, and differential coefficients.
*
* @param p
* Proportional coefficient
* @param i
* Integral coefficient
* @param d
* Differential coefficient
*/
public void setPID(double p, double i, double d) {
m_P = p;
m_I = i;
m_D = d;
}
/**
* Set the PID controller gain parameters. Set the proportional, integral, and differential coefficients.
*
* @param p
* Proportional coefficient
* @param i
* Integral coefficient
* @param d
* Differential coefficient
* @param f
* Feed forward coefficient
*/
public void setPID(double p, double i, double d, double f) {
m_P = p;
m_I = i;
m_D = d;
m_F = f;
}
/**
* Get the Proportional coefficient
*
* @return proportional coefficient
*/
public double getP() {
return m_P;
}
/**
* Get the Integral coefficient
*
* @return integral coefficient
*/
public double getI() {
return m_I;
}
/**
* Get the Differential coefficient
*
* @return differential coefficient
*/
public double getD() {
return m_D;
}
/**
* Get the Feed forward coefficient
*
* @return feed forward coefficient
*/
public double getF() {
return m_F;
}
/**
* Return the current PID result This is always centered on zero and constrained the the max and min outs
*
* @return the latest calculated output
*/
public double get() {
return m_result;
}
/**
* Set the PID controller to consider the input to be continuous, Rather then using the max and min in as
* constraints, it considers them to be the same point and automatically calculates the shortest route to the
* setpoint.
*
* @param continuous
* Set to true turns on continuous, false turns off continuous
*/
public void setContinuous(boolean continuous) {
m_continuous = continuous;
}
public void setDeadband(double deadband) {
m_deadband = deadband;
}
/**
* Set the PID controller to consider the input to be continuous, Rather then using the max and min in as
* constraints, it considers them to be the same point and automatically calculates the shortest route to the
* setpoint.
*/
public void setContinuous() {
this.setContinuous(true);
}
/**
* Sets the maximum and minimum values expected from the input.
*
* @param minimumInput
* the minimum value expected from the input
* @param maximumInput
* the maximum value expected from the output
*/
public void setInputRange(double minimumInput, double maximumInput) {
if (minimumInput > maximumInput) {
//throw new BoundaryException("Lower bound is greater than upper bound");
}
m_minimumInput = minimumInput;
m_maximumInput = maximumInput;
setSetpoint(m_setpoint);
}
/**
* Sets the minimum and maximum values to write.
*
* @param minimumOutput
* the minimum value to write to the output
* @param maximumOutput
* the maximum value to write to the output
*/
public void setOutputRange(double minimumOutput, double maximumOutput) {
if (minimumOutput > maximumOutput) {
//throw new BoundaryException("Lower bound is greater than upper bound");
}
m_minimumOutput = minimumOutput;
m_maximumOutput = maximumOutput;
}
/**
* Set the setpoint for the PID controller
*
* @param setpoint
* the desired setpoint
*/
public void setSetpoint(double setpoint) {
if (m_maximumInput > m_minimumInput) {
if (setpoint > m_maximumInput) {
m_setpoint = m_maximumInput;
} else if (setpoint < m_minimumInput) {
m_setpoint = m_minimumInput;
} else {
m_setpoint = setpoint;
}
} else {
m_setpoint = setpoint;
}
}
/**
* Returns the current setpoint of the PID controller
*
* @return the current setpoint
*/
public double getSetpoint() {
return m_setpoint;
}
/**
* Returns the current difference of the input from the setpoint
*
* @return the current error
*/
public double getError() {
return m_error;
}
/**
* Return true if the error is within the tolerance
*
* @return true if the error is less than the tolerance
*/
public boolean onTarget(double tolerance) {
return m_last_input != Double.NaN && Math.abs(m_last_input - m_setpoint) < tolerance;
}
/**
* Reset all internal terms.
*/
public void reset() {
m_last_input = Double.NaN;
m_prevError = 0;
m_totalError = 0;
m_result = 0;
m_setpoint = 0;
}
public void resetIntegrator() {
m_totalError = 0;
}
public String getState() {
String lState = "";
lState += "Kp: " + m_P + "\n";
lState += "Ki: " + m_I + "\n";
lState += "Kd: " + m_D + "\n";
return lState;
}
public String getType() {
return "PIDController";
}
}
@@ -0,0 +1,24 @@
package org.usfirst.frc4388.utility.math;
/**
* Interpolable is an interface used by an Interpolating Tree as the Value type. Given two end points and an
* interpolation parameter on [0, 1], it calculates a new Interpolable representing the interpolated value.
*
* @param <T>
* The Type of Interpolable
* @see InterpolatingTreeMap
*/
public interface Interpolable<T> {
/**
* Interpolates between this value and an other value according to a given parameter. If x is 0, the method should
* return this value. If x is 1, the method should return the other value. If 0 < x < 1, the return value should be
* interpolated proportionally between the two.
*
* @param other
* The value of the upper bound
* @param x
* The requested value. Should be between 0 and 1.
* @return Interpolable<T> The estimated average between the surrounding data
*/
public T interpolate(T other, double x);
}
@@ -0,0 +1,47 @@
package org.usfirst.frc4388.utility.math;
/**
* A Double that can be interpolated using the InterpolatingTreeMap.
*
* @see InterpolatingTreeMap
*/
public class InterpolatingDouble implements Interpolable<InterpolatingDouble>, InverseInterpolable<InterpolatingDouble>,
Comparable<InterpolatingDouble> {
public Double value = 0.0;
public InterpolatingDouble(Double val) {
value = val;
}
@Override
public InterpolatingDouble interpolate(InterpolatingDouble other, double x) {
Double dydx = other.value - value;
Double searchY = dydx * x + value;
return new InterpolatingDouble(searchY);
}
@Override
public double inverseInterpolate(InterpolatingDouble upper, InterpolatingDouble query) {
double upper_to_lower = upper.value - value;
if (upper_to_lower <= 0) {
return 0;
}
double query_to_lower = query.value - value;
if (query_to_lower <= 0) {
return 0;
}
return query_to_lower / upper_to_lower;
}
@Override
public int compareTo(InterpolatingDouble other) {
if (other.value < value) {
return 1;
} else if (other.value > value) {
return -1;
} else {
return 0;
}
}
}
@@ -0,0 +1,46 @@
package org.usfirst.frc4388.utility.math;
/**
* A Long that can be interpolated using the InterpolatingTreeMap.
*
* @see InterpolatingTreeMap
*/
public class InterpolatingLong implements Interpolable<InterpolatingLong>, InverseInterpolable<InterpolatingLong>,
Comparable<InterpolatingLong> {
public Long value = 0L;
public InterpolatingLong(Long val) {
value = val;
}
@Override
public InterpolatingLong interpolate(InterpolatingLong other, double x) {
Long dydx = other.value - value;
Double searchY = dydx * x + value;
return new InterpolatingLong(searchY.longValue());
}
@Override
public double inverseInterpolate(InterpolatingLong upper, InterpolatingLong query) {
long upper_to_lower = upper.value - value;
if (upper_to_lower <= 0) {
return 0;
}
long query_to_lower = query.value - value;
if (query_to_lower <= 0) {
return 0;
}
return query_to_lower / (double) upper_to_lower;
}
@Override
public int compareTo(InterpolatingLong other) {
if (other.value < value) {
return 1;
} else if (other.value > value) {
return -1;
} else {
return 0;
}
}
}
@@ -0,0 +1,88 @@
package org.usfirst.frc4388.utility.math;
import java.util.Map;
import java.util.TreeMap;
/**
* Interpolating Tree Maps are used to get values at points that are not defined by making a guess from points that are
* defined. This uses linear interpolation.
*
* @param <K>
* The type of the key (must implement InverseInterpolable)
* @param <V>
* The type of the value (must implement Interpolable)
*/
public class InterpolatingTreeMap<K extends InverseInterpolable<K> & Comparable<K>, V extends Interpolable<V>>
extends TreeMap<K, V> {
private static final long serialVersionUID = 8347275262778054124L;
int max_;
public InterpolatingTreeMap(int maximumSize) {
max_ = maximumSize;
}
public InterpolatingTreeMap() {
this(0);
}
/**
* Inserts a key value pair, and trims the tree if a max size is specified
*
* @param key
* Key for inserted data
* @param value
* Value for inserted data
* @return the value
*/
@Override
public V put(K key, V value) {
if (max_ > 0 && max_ <= size()) {
// "Prune" the tree if it is oversize
K first = firstKey();
remove(first);
}
super.put(key, value);
return value;
}
@Override
public void putAll(Map<? extends K, ? extends V> map) {
System.out.println("Unimplemented Method");
}
/**
*
* @param key
* Lookup for a value (does not have to exist)
* @return V or null; V if it is Interpolable or exists, null if it is at a bound and cannot average
*/
public V getInterpolated(K key) {
V gotval = get(key);
if (gotval == null) {
/** Get surrounding keys for interpolation */
K topBound = ceilingKey(key);
K bottomBound = floorKey(key);
/**
* If attempting interpolation at ends of tree, return the nearest data point
*/
if (topBound == null && bottomBound == null) {
return null;
} else if (topBound == null) {
return get(bottomBound);
} else if (bottomBound == null) {
return get(topBound);
}
/** Get surrounding values for interpolation */
V topElem = get(topBound);
V bottomElem = get(bottomBound);
return bottomElem.interpolate(topElem, bottomBound.inverseInterpolate(topBound, key));
} else {
return gotval;
}
}
}
@@ -0,0 +1,23 @@
package org.usfirst.frc4388.utility.math;
/**
* InverseInterpolable is an interface used by an Interpolating Tree as the Key type. Given two endpoint keys and a
* third query key, an InverseInterpolable object can calculate the interpolation parameter of the query key on the
* interval [0, 1].
*
* @param <T>
* The Type of InverseInterpolable
* @see InterpolatingTreeMap
*/
public interface InverseInterpolable<T> {
/**
* Given this point (lower), a query point (query), and an upper point (upper), estimate how far (on [0, 1]) between
* 'lower' and 'upper' the query point lies.
*
* @param upper
* @param query
* @return The interpolation parameter on [0, 1] representing how far between this point and the upper point the
* query point lies.
*/
public double inverseInterpolate(T upper, T query);
}
@@ -0,0 +1,182 @@
package org.usfirst.frc4388.utility.math;
import static org.usfirst.frc4388.utility.Util.epsilonEquals;
/**
* Represents a 2d pose (rigid transform) containing translational and rotational elements.
*
* Inspired by Sophus (https://github.com/strasdat/Sophus/tree/master/sophus)
*/
public class RigidTransform2d implements Interpolable<RigidTransform2d> {
protected static final double kEpsilon = 1E-9;
protected static final RigidTransform2d kIdentity = new RigidTransform2d();
public static final RigidTransform2d identity() {
return kIdentity;
}
private final static double kEps = 1E-9;
protected Translation2d translation_;
protected Rotation2d rotation_;
public RigidTransform2d() {
translation_ = new Translation2d();
rotation_ = new Rotation2d();
}
public RigidTransform2d(Translation2d translation, Rotation2d rotation) {
translation_ = translation;
rotation_ = rotation;
}
public RigidTransform2d(RigidTransform2d other) {
translation_ = new Translation2d(other.translation_);
rotation_ = new Rotation2d(other.rotation_);
}
public static RigidTransform2d fromTranslation(Translation2d translation) {
return new RigidTransform2d(translation, new Rotation2d());
}
public static RigidTransform2d fromRotation(Rotation2d rotation) {
return new RigidTransform2d(new Translation2d(), rotation);
}
/**
* Obtain a new RigidTransform2d from a (constant curvature) velocity. See:
* https://github.com/strasdat/Sophus/blob/master/sophus/se2.hpp
*/
public static RigidTransform2d exp(Twist2d delta) {
double sin_theta = Math.sin(delta.dtheta);
double cos_theta = Math.cos(delta.dtheta);
double s, c;
if (Math.abs(delta.dtheta) < kEps) {
s = 1.0 - 1.0 / 6.0 * delta.dtheta * delta.dtheta;
c = .5 * delta.dtheta;
} else {
s = sin_theta / delta.dtheta;
c = (1.0 - cos_theta) / delta.dtheta;
}
return new RigidTransform2d(new Translation2d(delta.dx * s - delta.dy * c, delta.dx * c + delta.dy * s),
new Rotation2d(cos_theta, sin_theta, false));
}
/**
* Logical inverse of the above.
*/
public static Twist2d log(RigidTransform2d transform) {
final double dtheta = transform.getRotation().getRadians();
final double half_dtheta = 0.5 * dtheta;
final double cos_minus_one = transform.getRotation().cos() - 1.0;
double halftheta_by_tan_of_halfdtheta;
if (Math.abs(cos_minus_one) < kEps) {
halftheta_by_tan_of_halfdtheta = 1.0 - 1.0 / 12.0 * dtheta * dtheta;
} else {
halftheta_by_tan_of_halfdtheta = -(half_dtheta * transform.getRotation().sin()) / cos_minus_one;
}
final Translation2d translation_part = transform.getTranslation()
.rotateBy(new Rotation2d(halftheta_by_tan_of_halfdtheta, -half_dtheta, false));
return new Twist2d(translation_part.x(), translation_part.y(), dtheta);
}
public Translation2d getTranslation() {
return translation_;
}
public void setTranslation(Translation2d translation) {
translation_ = translation;
}
public Rotation2d getRotation() {
return rotation_;
}
public void setRotation(Rotation2d rotation) {
rotation_ = rotation;
}
/**
* Transforming this RigidTransform2d means first translating by other.translation and then rotating by
* other.rotation
*
* @param other
* The other transform.
* @return This transform * other
*/
public RigidTransform2d transformBy(RigidTransform2d other) {
return new RigidTransform2d(translation_.translateBy(other.translation_.rotateBy(rotation_)),
rotation_.rotateBy(other.rotation_));
}
/**
* The inverse of this transform "undoes" the effect of translating by this transform.
*
* @return The opposite of this transform.
*/
public RigidTransform2d inverse() {
Rotation2d rotation_inverted = rotation_.inverse();
return new RigidTransform2d(translation_.inverse().rotateBy(rotation_inverted), rotation_inverted);
}
public RigidTransform2d normal() {
return new RigidTransform2d(translation_, rotation_.normal());
}
/**
* Finds the point where the heading of this transform intersects the heading of another. Returns (+INF, +INF) if
* parallel.
*/
public Translation2d intersection(RigidTransform2d other) {
final Rotation2d other_rotation = other.getRotation();
if (rotation_.isParallel(other_rotation)) {
// Lines are parallel.
return new Translation2d(Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY);
}
if (Math.abs(rotation_.cos()) < Math.abs(other_rotation.cos())) {
return intersectionInternal(this, other);
} else {
return intersectionInternal(other, this);
}
}
/**
* Return true if the heading of this transform is colinear with the heading of another.
*/
public boolean isColinear(RigidTransform2d other) {
final Twist2d twist = log(inverse().transformBy(other));
return (epsilonEquals(twist.dy, 0.0, kEpsilon) && epsilonEquals(twist.dtheta, 0.0, kEpsilon));
}
private static Translation2d intersectionInternal(RigidTransform2d a, RigidTransform2d b) {
final Rotation2d a_r = a.getRotation();
final Rotation2d b_r = b.getRotation();
final Translation2d a_t = a.getTranslation();
final Translation2d b_t = b.getTranslation();
final double tan_b = b_r.tan();
final double t = ((a_t.x() - b_t.x()) * tan_b + b_t.y() - a_t.y())
/ (a_r.sin() - a_r.cos() * tan_b);
return a_t.translateBy(a_r.toTranslation().scale(t));
}
/**
* Do twist interpolation of this transform assuming constant curvature.
*/
@Override
public RigidTransform2d interpolate(RigidTransform2d other, double x) {
if (x <= 0) {
return new RigidTransform2d(this);
} else if (x >= 1) {
return new RigidTransform2d(other);
}
final Twist2d twist = RigidTransform2d.log(inverse().transformBy(other));
return transformBy(RigidTransform2d.exp(twist.scaled(x)));
}
@Override
public String toString() {
return "T:" + translation_.toString() + ", R:" + rotation_.toString();
}
}
@@ -0,0 +1,144 @@
package org.usfirst.frc4388.utility.math;
import static org.usfirst.frc4388.utility.Util.epsilonEquals;
import java.text.DecimalFormat;
/**
* A rotation in a 2d coordinate frame represented a point on the unit circle (cosine and sine).
*
* Inspired by Sophus (https://github.com/strasdat/Sophus/tree/master/sophus)
*/
public class Rotation2d implements Interpolable<Rotation2d> {
protected static final Rotation2d kIdentity = new Rotation2d();
public static final Rotation2d identity() {
return kIdentity;
}
protected static final double kEpsilon = 1E-9;
protected double cos_angle_;
protected double sin_angle_;
public Rotation2d() {
this(1, 0, false);
}
public Rotation2d(double x, double y, boolean normalize) {
cos_angle_ = x;
sin_angle_ = y;
if (normalize) {
normalize();
}
}
public Rotation2d(Rotation2d other) {
cos_angle_ = other.cos_angle_;
sin_angle_ = other.sin_angle_;
}
public Rotation2d(Translation2d direction, boolean normalize) {
this(direction.x(), direction.y(), normalize);
}
public static Rotation2d fromRadians(double angle_radians) {
return new Rotation2d(Math.cos(angle_radians), Math.sin(angle_radians), false);
}
public static Rotation2d fromDegrees(double angle_degrees) {
return fromRadians(Math.toRadians(angle_degrees));
}
/**
* From trig, we know that sin^2 + cos^2 == 1, but as we do math on this object we might accumulate rounding errors.
* Normalizing forces us to re-scale the sin and cos to reset rounding errors.
*/
public void normalize() {
double magnitude = Math.hypot(cos_angle_, sin_angle_);
if (magnitude > kEpsilon) {
sin_angle_ /= magnitude;
cos_angle_ /= magnitude;
} else {
sin_angle_ = 0;
cos_angle_ = 1;
}
}
public double cos() {
return cos_angle_;
}
public double sin() {
return sin_angle_;
}
public double tan() {
if (Math.abs(cos_angle_) < kEpsilon) {
if (sin_angle_ >= 0.0) {
return Double.POSITIVE_INFINITY;
} else {
return Double.NEGATIVE_INFINITY;
}
}
return sin_angle_ / cos_angle_;
}
public double getRadians() {
return Math.atan2(sin_angle_, cos_angle_);
}
public double getDegrees() {
return Math.toDegrees(getRadians());
}
/**
* We can rotate this Rotation2d by adding together the effects of it and another rotation.
*
* @param other
* The other rotation. See: https://en.wikipedia.org/wiki/Rotation_matrix
* @return This rotation rotated by other.
*/
public Rotation2d rotateBy(Rotation2d other) {
return new Rotation2d(cos_angle_ * other.cos_angle_ - sin_angle_ * other.sin_angle_,
cos_angle_ * other.sin_angle_ + sin_angle_ * other.cos_angle_, true);
}
public Rotation2d normal() {
return new Rotation2d(-sin_angle_, cos_angle_, false);
}
/**
* The inverse of a Rotation2d "undoes" the effect of this rotation.
*
* @return The opposite of this rotation.
*/
public Rotation2d inverse() {
return new Rotation2d(cos_angle_, -sin_angle_, false);
}
public boolean isParallel(Rotation2d other) {
return epsilonEquals(Translation2d.cross(toTranslation(), other.toTranslation()), 0.0, kEpsilon);
}
public Translation2d toTranslation() {
return new Translation2d(cos_angle_, sin_angle_);
}
@Override
public Rotation2d interpolate(Rotation2d other, double x) {
if (x <= 0) {
return new Rotation2d(this);
} else if (x >= 1) {
return new Rotation2d(other);
}
double angle_diff = inverse().rotateBy(other).getRadians();
return this.rotateBy(Rotation2d.fromRadians(angle_diff * x));
}
@Override
public String toString() {
final DecimalFormat fmt = new DecimalFormat("#0.000");
return "(" + fmt.format(getDegrees()) + " deg)";
}
}
@@ -0,0 +1,141 @@
package org.usfirst.frc4388.utility.math;
import java.text.DecimalFormat;
/**
* A translation in a 2d coordinate frame. Translations are simply shifts in an (x, y) plane.
*/
public class Translation2d implements Interpolable<Translation2d> {
protected static final Translation2d kIdentity = new Translation2d();
public static final Translation2d identity() {
return kIdentity;
}
protected double x_;
protected double y_;
public Translation2d() {
x_ = 0;
y_ = 0;
}
public Translation2d(double x, double y) {
x_ = x;
y_ = y;
}
public Translation2d(Translation2d other) {
x_ = other.x_;
y_ = other.y_;
}
public Translation2d(Translation2d start, Translation2d end) {
x_ = end.x_ - start.x_;
y_ = end.y_ - start.y_;
}
/**
* The "norm" of a transform is the Euclidean distance in x and y.
*
* @return sqrt(x^2 + y^2)
*/
public double norm() {
return Math.hypot(x_, y_);
}
public double norm2() {
return x_ * x_ + y_ * y_;
}
public double x() {
return x_;
}
public double y() {
return y_;
}
public void setX(double x) {
x_ = x;
}
public void setY(double y) {
y_ = y;
}
/**
* We can compose Translation2d's by adding together the x and y shifts.
*
* @param other
* The other translation to add.
* @return The combined effect of translating by this object and the other.
*/
public Translation2d translateBy(Translation2d other) {
return new Translation2d(x_ + other.x_, y_ + other.y_);
}
/**
* We can also rotate Translation2d's. See: https://en.wikipedia.org/wiki/Rotation_matrix
*
* @param rotation
* The rotation to apply.
* @return This translation rotated by rotation.
*/
public Translation2d rotateBy(Rotation2d rotation) {
return new Translation2d(x_ * rotation.cos() - y_ * rotation.sin(), x_ * rotation.sin() + y_ * rotation.cos());
}
public Rotation2d direction() {
return new Rotation2d(x_, y_, true);
}
/**
* The inverse simply means a Translation2d that "undoes" this object.
*
* @return Translation by -x and -y.
*/
public Translation2d inverse() {
return new Translation2d(-x_, -y_);
}
@Override
public Translation2d interpolate(Translation2d other, double x) {
if (x <= 0) {
return new Translation2d(this);
} else if (x >= 1) {
return new Translation2d(other);
}
return extrapolate(other, x);
}
public Translation2d extrapolate(Translation2d other, double x) {
return new Translation2d(x * (other.x_ - x_) + x_, x * (other.y_ - y_) + y_);
}
public Translation2d scale(double s) {
return new Translation2d(x_ * s, y_ * s);
}
@Override
public String toString() {
final DecimalFormat fmt = new DecimalFormat("#0.000");
return "(" + fmt.format(x_) + "," + fmt.format(y_) + ")";
}
public static double dot(Translation2d a, Translation2d b) {
return a.x_ * b.x_ + a.y_ * b.y_;
}
public static Rotation2d getAngle(Translation2d a, Translation2d b) {
double cos_angle = dot(a, b) / (a.norm() * b.norm());
if (Double.isNaN(cos_angle)) {
return new Rotation2d();
}
return Rotation2d.fromRadians(Math.acos(Math.min(1.0, Math.max(cos_angle, -1.0))));
}
public static double cross(Translation2d a, Translation2d b) {
return a.x_ * b.y_ - a.y_ * b.x_;
}
}
@@ -0,0 +1,29 @@
package org.usfirst.frc4388.utility.math;
/**
* A movement along an arc at constant curvature and velocity. We can use ideas from "differential calculus" to create
* new RigidTransform2d's from a Twist2d and visa versa.
*
* A Twist can be used to represent a difference between two poses, a velocity, an acceleration, etc.
*/
public class Twist2d {
protected static final Twist2d kIdentity = new Twist2d(0.0, 0.0, 0.0);
public static final Twist2d identity() {
return kIdentity;
}
public final double dx;
public final double dy;
public final double dtheta; // Radians!
public Twist2d(double dx, double dy, double dtheta) {
this.dx = dx;
this.dy = dy;
this.dtheta = dtheta;
}
public Twist2d scaled(double scale) {
return new Twist2d(dx * scale, dy * scale, dtheta * scale);
}
}
@@ -0,0 +1,43 @@
package org.usfirst.frc4388.utility.motion;
import org.usfirst.frc4388.utility.math.Rotation2d;
/**
* Class to deal with angle wrapping for following a heading profile. All states are assumed to be in units of degrees,
* and wrap on the interval of [-180, 180].
*/
public class HeadingProfileFollower extends ProfileFollower {
public HeadingProfileFollower(double kp, double ki, double kv, double kffv, double kffa) {
super(kp, ki, kv, kffv, kffa);
}
@Override
public double update(MotionState latest_state, double t) {
final Rotation2d goal_rotation_inverse = Rotation2d.fromDegrees(mGoal.pos()).inverse();
// Update both the setpoint and latest state to be relative to the new goal.
if (mLatestSetpoint != null) {
mLatestSetpoint.motion_state = new MotionState(mLatestSetpoint.motion_state.t(),
mGoal.pos() + goal_rotation_inverse
.rotateBy(Rotation2d.fromDegrees(mLatestSetpoint.motion_state.pos())).getDegrees(),
mLatestSetpoint.motion_state.vel(), mLatestSetpoint.motion_state.acc());
}
final MotionState latest_state_unwrapped = new MotionState(latest_state.t(),
mGoal.pos() + goal_rotation_inverse.rotateBy(Rotation2d.fromDegrees(latest_state.pos())).getDegrees(),
latest_state.vel(), latest_state.acc());
double result = super.update(latest_state_unwrapped, t);
// Reset the integrator when we are close to the goal (encourage stiction!).
if (Math.abs(latest_state_unwrapped.pos() - mGoal.pos()) < mGoal.pos_tolerance()) {
result = 0.0;
super.resetIntegral();
}
return result;
}
/**
* Convert a motion state representing an angle to a properly wrapped angle.
*/
public static MotionState canonicalize(MotionState state) {
return new MotionState(state.t(), Rotation2d.fromDegrees(state.pos()).getDegrees(), state.vel(), state.acc());
}
}
@@ -0,0 +1,326 @@
package org.usfirst.frc4388.utility.motion;
import static org.usfirst.frc4388.utility.Util.epsilonEquals;
import static org.usfirst.frc4388.utility.motion.MotionUtil.kEpsilon;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Optional;
/**
* A motion profile specifies a 1D time-parameterized trajectory. The trajectory is composed of successively coincident
* MotionSegments from which the desired state of motion at any given distance or time can be calculated.
*/
public class MotionProfile {
protected List<MotionSegment> mSegments;
/**
* Create an empty MotionProfile.
*/
public MotionProfile() {
mSegments = new ArrayList<>();
}
/**
* Create a MotionProfile from an existing list of segments (note that validity is not checked).
*
* @param segments
* The new segments of the profile.
*/
public MotionProfile(List<MotionSegment> segments) {
mSegments = segments;
}
/**
* Checks if the given MotionProfile is valid. This checks that:
*
* 1. All segments are valid.
*
* 2. Successive segments are C1 continuous in position and C0 continuous in velocity.
*
* @return True if the MotionProfile is valid.
*/
public boolean isValid() {
MotionSegment prev_segment = null;
for (MotionSegment s : mSegments) {
if (!s.isValid()) {
return false;
}
if (prev_segment != null && !s.start().coincident(prev_segment.end())) {
// Adjacent segments are not continuous.
System.err.println("Segments not continuous! End: " + prev_segment.end() + ", Start: " + s.start());
return false;
}
prev_segment = s;
}
return true;
}
/**
* Check if the profile is empty.
*
* @return True if there are no segments.
*/
public boolean isEmpty() {
return mSegments.isEmpty();
}
/**
* Get the interpolated MotionState at any given time.
*
* @param t
* The time to query.
* @return Empty if the time is outside the time bounds of the profile, or the resulting MotionState otherwise.
*/
public Optional<MotionState> stateByTime(double t) {
if (t < startTime() && t + kEpsilon >= startTime()) {
return Optional.of(startState());
}
if (t > endTime() && t - kEpsilon <= endTime()) {
return Optional.of(endState());
}
for (MotionSegment s : mSegments) {
if (s.containsTime(t)) {
return Optional.of(s.start().extrapolate(t));
}
}
return Optional.empty();
}
/**
* Get the interpolated MotionState at any given time, clamping to the endpoints if time is out of bounds.
*
* @param t
* The time to query.
* @return The MotionState at time t, or closest to it if t is outside the profile.
*/
public MotionState stateByTimeClamped(double t) {
if (t < startTime()) {
return startState();
} else if (t > endTime()) {
return endState();
}
for (MotionSegment s : mSegments) {
if (s.containsTime(t)) {
return s.start().extrapolate(t);
}
}
// Should never get here.
return MotionState.kInvalidState;
}
/**
* Get the interpolated MotionState by distance (the "pos()" field of MotionState). Note that since a profile may
* reverse, this method only returns the *first* instance of this position.
*
* @param pos
* The position to query.
* @return Empty if the profile never crosses pos or if the profile is invalid, or the resulting MotionState
* otherwise.
*/
public Optional<MotionState> firstStateByPos(double pos) {
for (MotionSegment s : mSegments) {
if (s.containsPos(pos)) {
if (epsilonEquals(s.end().pos(), pos, kEpsilon)) {
return Optional.of(s.end());
}
final double t = Math.min(s.start().nextTimeAtPos(pos), s.end().t());
if (Double.isNaN(t)) {
System.err.println("Error! We should reach 'pos' but we don't");
return Optional.empty();
}
return Optional.of(s.start().extrapolate(t));
}
}
// We never reach pos.
return Optional.empty();
}
/**
* Remove all parts of the profile prior to the query time. This eliminates whole segments and also shortens any
* segments containing t.
*
* @param t
* The query time.
*/
public void trimBeforeTime(double t) {
for (Iterator<MotionSegment> iterator = mSegments.iterator(); iterator.hasNext();) {
MotionSegment s = iterator.next();
if (s.end().t() <= t) {
// Segment is fully before t.
iterator.remove();
continue;
}
if (s.start().t() <= t) {
// Segment begins before t; let's shorten the segment.
s.setStart(s.start().extrapolate(t));
}
break;
}
}
/**
* Remove all segments.
*/
public void clear() {
mSegments.clear();
}
/**
* Remove all segments and initialize to the desired state (actually a segment of length 0 that starts and ends at
* initial_state).
*
* @param initial_state
* The MotionState to initialize to.
*/
public void reset(MotionState initial_state) {
clear();
mSegments.add(new MotionSegment(initial_state, initial_state));
}
/**
* Remove redundant segments (segments whose start and end states are coincident).
*/
public void consolidate() {
for (Iterator<MotionSegment> iterator = mSegments.iterator(); iterator.hasNext() && mSegments.size() > 1;) {
MotionSegment s = iterator.next();
if (s.start().coincident(s.end())) {
iterator.remove();
}
}
}
/**
* Add to the profile by applying an acceleration control for a given time. This is appended to the previous last
* state.
*
* @param acc
* The acceleration to apply.
* @param dt
* The period of time to apply the given acceleration.
*/
public void appendControl(double acc, double dt) {
if (isEmpty()) {
System.err.println("Error! Trying to append to empty profile");
return;
}
MotionState last_end_state = mSegments.get(mSegments.size() - 1).end();
MotionState new_start_state = new MotionState(last_end_state.t(), last_end_state.pos(), last_end_state.vel(),
acc);
appendSegment(new MotionSegment(new_start_state, new_start_state.extrapolate(new_start_state.t() + dt)));
}
/**
* Add to the profile by inserting a new segment. No validity checking is done.
*
* @param segment
* The segment to add.
*/
public void appendSegment(MotionSegment segment) {
mSegments.add(segment);
}
/**
* Add to the profile by inserting a new profile after the final state. No validity checking is done.
*
* @param profile
* The profile to add.
*/
public void appendProfile(MotionProfile profile) {
for (MotionSegment s : profile.segments()) {
appendSegment(s);
}
}
/**
* @return The number of segments.
*/
public int size() {
return mSegments.size();
}
/**
* @return The list of segments.
*/
public List<MotionSegment> segments() {
return mSegments;
}
/**
* @return The first state in the profile (or kInvalidState if empty).
*/
public MotionState startState() {
if (isEmpty()) {
return MotionState.kInvalidState;
}
return mSegments.get(0).start();
}
/**
* @return The time of the first state in the profile (or NaN if empty).
*/
public double startTime() {
return startState().t();
}
/**
* @return The pos of the first state in the profile (or NaN if empty).
*/
public double startPos() {
return startState().pos();
}
/**
* @return The last state in the profile (or kInvalidState if empty).
*/
public MotionState endState() {
if (isEmpty()) {
return MotionState.kInvalidState;
}
return mSegments.get(mSegments.size() - 1).end();
}
/**
* @return The time of the last state in the profile (or NaN if empty).
*/
public double endTime() {
return endState().t();
}
/**
* @return The pos of the last state in the profile (or NaN if empty).
*/
public double endPos() {
return endState().pos();
}
/**
* @return The duration of the entire profile.
*/
public double duration() {
return endTime() - startTime();
}
/**
* @return The total distance covered by the profile. Note that distance is the sum of absolute distances of all
* segments, so a reversing profile will count the distance covered in each direction.
*/
public double length() {
double length = 0.0;
for (MotionSegment s : segments()) {
length += Math.abs(s.end().pos() - s.start().pos());
}
return length;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder("Profile:");
for (MotionSegment s : segments()) {
builder.append("\n\t");
builder.append(s);
}
return builder.toString();
}
}
@@ -0,0 +1,37 @@
package org.usfirst.frc4388.utility.motion;
/**
* Constraints for constructing a MotionProfile.
*/
public class MotionProfileConstraints {
protected double max_abs_vel = Double.POSITIVE_INFINITY;
protected double max_abs_acc = Double.POSITIVE_INFINITY;
public MotionProfileConstraints(double max_vel, double max_acc) {
this.max_abs_vel = Math.abs(max_vel);
this.max_abs_acc = Math.abs(max_acc);
}
/**
* @return The (positive) maximum allowed velocity.
*/
public double max_abs_vel() {
return max_abs_vel;
}
/**
* @return The (positive) maximum allowed acceleration.
*/
public double max_abs_acc() {
return max_abs_acc;
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof MotionProfileConstraints)) {
return false;
}
final MotionProfileConstraints other = (MotionProfileConstraints) obj;
return (other.max_abs_acc() == max_abs_acc()) && (other.max_abs_vel() == max_abs_vel());
}
}
@@ -0,0 +1,133 @@
package org.usfirst.frc4388.utility.motion;
import org.usfirst.frc4388.utility.motion.MotionProfileGoal.CompletionBehavior;
/**
* A MotionProfileGenerator generates minimum-time MotionProfiles to travel from a given MotionState to a given
* MotionProfileGoal while obeying a set of MotionProfileConstraints.
*/
public class MotionProfileGenerator {
// Static class.
private MotionProfileGenerator() {
}
protected static MotionProfile generateFlippedProfile(MotionProfileConstraints constraints,
MotionProfileGoal goal_state, MotionState prev_state) {
MotionProfile profile = generateProfile(constraints, goal_state.flipped(), prev_state.flipped());
for (MotionSegment s : profile.segments()) {
s.setStart(s.start().flipped());
s.setEnd(s.end().flipped());
}
return profile;
}
/**
* Generate a motion profile.
*
* @param constraints
* The constraints to use.
* @param goal_state
* The goal to use.
* @param prev_state
* The initial state to use.
* @return A motion profile from prev_state to goal_state that satisfies constraints.
*/
public synchronized static MotionProfile generateProfile(MotionProfileConstraints constraints,
MotionProfileGoal goal_state,
MotionState prev_state) {
double delta_pos = goal_state.pos() - prev_state.pos();
if (delta_pos < 0.0 || (delta_pos == 0.0 && prev_state.vel() < 0.0)) {
// For simplicity, we always assume the goal requires positive movement. If negative, we flip to solve, then
// flip the solution.
return generateFlippedProfile(constraints, goal_state, prev_state);
}
// Invariant from this point on: delta_pos >= 0.0
// Clamp the start state to be valid.
MotionState start_state = new MotionState(prev_state.t(), prev_state.pos(),
Math.signum(prev_state.vel()) * Math.min(Math.abs(prev_state.vel()), constraints.max_abs_vel()),
Math.signum(prev_state.acc()) * Math.min(Math.abs(prev_state.acc()), constraints.max_abs_acc()));
MotionProfile profile = new MotionProfile();
profile.reset(start_state);
// If our velocity is headed away from the goal, the first thing we need to do is to stop.
if (start_state.vel() < 0.0 && delta_pos > 0.0) {
final double stopping_time = Math.abs(start_state.vel() / constraints.max_abs_acc());
profile.appendControl(constraints.max_abs_acc(), stopping_time);
start_state = profile.endState();
delta_pos = goal_state.pos() - start_state.pos();
}
// Invariant from this point on: start_state.vel() >= 0.0
final double min_abs_vel_at_goal_sqr = start_state.vel2() - 2.0 * constraints.max_abs_acc() * delta_pos;
final double min_abs_vel_at_goal = Math.sqrt(Math.abs(min_abs_vel_at_goal_sqr));
final double max_abs_vel_at_goal = Math.sqrt(start_state.vel2() + 2.0 * constraints.max_abs_acc() * delta_pos);
double goal_vel = goal_state.max_abs_vel();
double max_acc = constraints.max_abs_acc();
if (min_abs_vel_at_goal_sqr > 0.0
&& min_abs_vel_at_goal > (goal_state.max_abs_vel() + goal_state.vel_tolerance())) {
// Overshoot is unavoidable with the current constraints. Look at completion_behavior to see what we should
// do.
if (goal_state.completion_behavior() == CompletionBehavior.VIOLATE_MAX_ABS_VEL) {
// Adjust the goal velocity.
goal_vel = min_abs_vel_at_goal;
} else if (goal_state.completion_behavior() == CompletionBehavior.VIOLATE_MAX_ACCEL) {
if (Math.abs(delta_pos) < goal_state.pos_tolerance()) {
// Special case: We are at the goal but moving too fast. This requires 'infinite' acceleration,
// which will result in NaNs below, so we can return the profile immediately.
profile.appendSegment(new MotionSegment(
new MotionState(profile.endTime(), profile.endPos(), profile.endState().vel(),
Double.NEGATIVE_INFINITY),
new MotionState(profile.endTime(), profile.endPos(), goal_vel, Double.NEGATIVE_INFINITY)));
profile.consolidate();
return profile;
}
// Adjust the max acceleration.
max_acc = Math.abs(goal_vel * goal_vel - start_state.vel2()) / (2.0 * delta_pos);
} else {
// We are going to overshoot the goal, so the first thing we need to do is come to a stop.
final double stopping_time = Math.abs(start_state.vel() / constraints.max_abs_acc());
profile.appendControl(-constraints.max_abs_acc(), stopping_time);
// Now we need to travel backwards, so generate a flipped profile.
profile.appendProfile(generateFlippedProfile(constraints, goal_state, profile.endState()));
profile.consolidate();
return profile;
}
}
goal_vel = Math.min(goal_vel, max_abs_vel_at_goal);
// Invariant from this point forward: We can achieve goal_vel at goal_state.pos exactly using no more than +/-
// max_acc.
// What is the maximum velocity we can reach (Vmax)? This is the intersection of two curves: one accelerating
// towards the goal from profile.finalState(), the other coming from the goal at max vel (in reverse). If Vmax
// is greater than constraints.max_abs_vel, we will clamp and cruise.
// Solve the following three equations to find Vmax (by substitution):
// Vmax^2 = Vstart^2 + 2*a*d_accel
// Vgoal^2 = Vmax^2 - 2*a*d_decel
// delta_pos = d_accel + d_decel
final double v_max = Math.min(constraints.max_abs_vel(),
Math.sqrt((start_state.vel2() + goal_vel * goal_vel) / 2.0 + delta_pos * max_acc));
// Accelerate to v_max
if (v_max > start_state.vel()) {
final double accel_time = (v_max - start_state.vel()) / max_acc;
profile.appendControl(max_acc, accel_time);
start_state = profile.endState();
}
// Figure out how much distance will be covered during deceleration.
final double distance_decel = Math.max(0.0,
(start_state.vel2() - goal_vel * goal_vel) / (2.0 * constraints.max_abs_acc()));
final double distance_cruise = Math.max(0.0, goal_state.pos() - start_state.pos() - distance_decel);
// Cruise at constant velocity.
if (distance_cruise > 0.0) {
final double cruise_time = distance_cruise / start_state.vel();
profile.appendControl(0.0, cruise_time);
start_state = profile.endState();
}
// Decelerate to goal velocity.
if (distance_decel > 0.0) {
final double decel_time = (start_state.vel() - goal_vel) / max_acc;
profile.appendControl(-max_acc, decel_time);
}
profile.consolidate();
return profile;
}
}
@@ -0,0 +1,143 @@
package org.usfirst.frc4388.utility.motion;
import static org.usfirst.frc4388.utility.Util.epsilonEquals;
/**
* A MotionProfileGoal defines a desired position and maximum velocity (at this position), along with the behavior that
* should be used to determine if we are at the goal and what to do if it is infeasible to reach the goal within the
* desired velocity bounds.
*
*/
public class MotionProfileGoal {
/**
* A goal consists of a desired position and specified maximum velocity magnitude. But what should we do if we would
* reach the goal at a velocity greater than the maximum? This enum allows a user to specify a preference on
* behavior in this case.
*
* Example use-cases of each:
*
* OVERSHOOT - Generally used with a goal max_abs_vel of 0.0 to stop at the desired pos without violating any
* constraints.
*
* VIOLATE_MAX_ACCEL - If we absolutely do not want to pass the goal and are unwilling to violate the max_abs_vel
* (for example, there is an obstacle in front of us - slam the brakes harder than we'd like in order to avoid
* hitting it).
*
* VIOLATE_MAX_ABS_VEL - If the max velocity is just a general guideline and not a hard performance limit, it's
* better to slightly exceed it to avoid skidding wheels.
*/
public static enum CompletionBehavior {
// Overshoot the goal if necessary (at a velocity greater than max_abs_vel) and come back.
// Only valid if the goal velocity is 0.0 (otherwise VIOLATE_MAX_ACCEL will be used).
OVERSHOOT,
// If we cannot slow down to the goal velocity before crossing the goal, allow exceeding the max accel
// constraint.
VIOLATE_MAX_ACCEL,
// If we cannot slow down to the goal velocity before crossing the goal, allow exceeding the goal velocity.
VIOLATE_MAX_ABS_VEL
}
protected double pos;
protected double max_abs_vel;
protected CompletionBehavior completion_behavior = CompletionBehavior.OVERSHOOT;
protected double pos_tolerance = 1E-3;
protected double vel_tolerance = 1E-2;
public MotionProfileGoal() {
}
public MotionProfileGoal(double pos) {
this.pos = pos;
this.max_abs_vel = 0.0;
sanityCheck();
}
public MotionProfileGoal(double pos, double max_abs_vel) {
this.pos = pos;
this.max_abs_vel = max_abs_vel;
sanityCheck();
}
public MotionProfileGoal(double pos, double max_abs_vel, CompletionBehavior completion_behavior) {
this.pos = pos;
this.max_abs_vel = max_abs_vel;
this.completion_behavior = completion_behavior;
sanityCheck();
}
public MotionProfileGoal(double pos, double max_abs_vel, CompletionBehavior completion_behavior,
double pos_tolerance, double vel_tolerance) {
this.pos = pos;
this.max_abs_vel = max_abs_vel;
this.completion_behavior = completion_behavior;
this.pos_tolerance = pos_tolerance;
this.vel_tolerance = vel_tolerance;
sanityCheck();
}
public MotionProfileGoal(MotionProfileGoal other) {
this(other.pos, other.max_abs_vel, other.completion_behavior, other.pos_tolerance, other.vel_tolerance);
}
/**
* @return A flipped MotionProfileGoal (where the position is negated, but all other attributes remain the same).
*/
public MotionProfileGoal flipped() {
return new MotionProfileGoal(-pos, max_abs_vel, completion_behavior, pos_tolerance, vel_tolerance);
}
public double pos() {
return pos;
}
public double max_abs_vel() {
return max_abs_vel;
}
public double pos_tolerance() {
return pos_tolerance;
}
public double vel_tolerance() {
return vel_tolerance;
}
public CompletionBehavior completion_behavior() {
return completion_behavior;
}
public boolean atGoalState(MotionState state) {
return atGoalPos(state.pos()) && (Math.abs(state.vel()) < (max_abs_vel + vel_tolerance)
|| completion_behavior == CompletionBehavior.VIOLATE_MAX_ABS_VEL);
}
public boolean atGoalPos(double pos) {
return epsilonEquals(pos, this.pos, pos_tolerance);
}
/**
* This method makes sure that the completion behavior is compatible with the max goal velocity.
*/
protected void sanityCheck() {
if (max_abs_vel > vel_tolerance && completion_behavior == CompletionBehavior.OVERSHOOT) {
completion_behavior = CompletionBehavior.VIOLATE_MAX_ACCEL;
}
}
@Override
public String toString() {
return "pos: " + pos + " (+/- " + pos_tolerance + "), max_abs_vel: " + max_abs_vel + " (+/- " + vel_tolerance
+ "), completion behavior: " + completion_behavior.name();
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof MotionProfileGoal)) {
return false;
}
final MotionProfileGoal other = (MotionProfileGoal) obj;
return (other.completion_behavior() == completion_behavior()) && (other.pos() == pos())
&& (other.max_abs_vel() == max_abs_vel()) && (other.pos_tolerance() == pos_tolerance())
&& (other.vel_tolerance() == vel_tolerance());
}
}
@@ -0,0 +1,80 @@
package org.usfirst.frc4388.utility.motion;
import static org.usfirst.frc4388.utility.Util.epsilonEquals;
import static org.usfirst.frc4388.utility.motion.MotionUtil.kEpsilon;
/**
* A MotionSegment is a movement from a start MotionState to an end MotionState with a constant acceleration.
*/
public class MotionSegment {
protected MotionState mStart;
protected MotionState mEnd;
public MotionSegment(MotionState start, MotionState end) {
mStart = start;
mEnd = end;
}
/**
* Verifies that:
*
* 1. All segments have a constant acceleration.
*
* 2. All segments have monotonic position (sign of velocity doesn't change).
*
* 3. The time, position, velocity, and acceleration of the profile are consistent.
*/
public boolean isValid() {
if (!epsilonEquals(start().acc(), end().acc(), kEpsilon)) {
// Acceleration is not constant within the segment.
System.err.println(
"Segment acceleration not constant! Start acc: " + start().acc() + ", End acc: " + end().acc());
return false;
}
if (Math.signum(start().vel()) * Math.signum(end().vel()) < 0.0 && !epsilonEquals(start().vel(), 0.0, kEpsilon)
&& !epsilonEquals(end().vel(), 0.0, kEpsilon)) {
// Velocity direction reverses within the segment.
System.err.println("Segment velocity reverses! Start vel: " + start().vel() + ", End vel: " + end().vel());
return false;
}
if (!start().extrapolate(end().t()).equals(end())) {
// A single segment is not consistent.
if (start().t() == end().t() && Double.isInfinite(start().acc())) {
// One allowed exception: If acc is infinite and dt is zero.
return true;
}
System.err.println("Segment not consistent! Start: " + start() + ", End: " + end());
return false;
}
return true;
}
public boolean containsTime(double t) {
return t >= start().t() && t <= end().t();
}
public boolean containsPos(double pos) {
return pos >= start().pos() && pos <= end().pos() || pos <= start().pos() && pos >= end().pos();
}
public MotionState start() {
return mStart;
}
public void setStart(MotionState start) {
mStart = start;
}
public MotionState end() {
return mEnd;
}
public void setEnd(MotionState end) {
mEnd = end;
}
@Override
public String toString() {
return "Start: " + start() + ", End: " + end();
}
}
@@ -0,0 +1,166 @@
package org.usfirst.frc4388.utility.motion;
import static org.usfirst.frc4388.utility.Util.epsilonEquals;
import static org.usfirst.frc4388.utility.motion.MotionUtil.kEpsilon;
/**
* A MotionState is a completely specified state of 1D motion through time.
*/
public class MotionState {
protected final double t;
protected final double pos;
protected final double vel;
protected final double acc;
public static MotionState kInvalidState = new MotionState(Double.NaN, Double.NaN, Double.NaN, Double.NaN);
public MotionState(double t, double pos, double vel, double acc) {
this.t = t;
this.pos = pos;
this.vel = vel;
this.acc = acc;
}
public MotionState(MotionState state) {
this(state.t, state.pos, state.vel, state.acc);
}
public double t() {
return t;
}
public double pos() {
return pos;
}
public double vel() {
return vel;
}
public double vel2() {
return vel * vel;
}
public double acc() {
return acc;
}
/**
* Extrapolates this MotionState to the specified time by applying this MotionState's acceleration.
*
* @param t
* The time of the new MotionState.
* @return A MotionState that is a valid predecessor (if t<=0) or successor (if t>=0) of this state.
*/
public MotionState extrapolate(double t) {
return extrapolate(t, acc);
}
/**
*
* Extrapolates this MotionState to the specified time by applying a given acceleration to the (t, pos, vel) portion
* of this MotionState.
*
* @param t
* The time of the new MotionState.
* @param acc
* The acceleration to apply.
* @return A MotionState that is a valid predecessor (if t<=0) or successor (if t>=0) of this state (with the
* specified accel).
*/
public MotionState extrapolate(double t, double acc) {
final double dt = t - this.t;
return new MotionState(t, pos + vel * dt + .5 * acc * dt * dt, vel + acc * dt, acc);
}
/**
* Find the next time (first time > MotionState.t()) that this MotionState will be at pos. This is an inverse of the
* extrapolate() method.
*
* @param pos
* The position to query.
* @return The time when we are next at pos() if we are extrapolating with a positive dt. NaN if we never reach pos.
*/
public double nextTimeAtPos(double pos) {
if (epsilonEquals(pos, this.pos, kEpsilon)) {
// Already at pos.
return t;
}
if (epsilonEquals(acc, 0.0, kEpsilon)) {
// Zero acceleration case.
final double delta_pos = pos - this.pos;
if (!epsilonEquals(vel, 0.0, kEpsilon) && Math.signum(delta_pos) == Math.signum(vel)) {
// Constant velocity heading towards pos.
return delta_pos / vel + t;
}
return Double.NaN;
}
// Solve the quadratic formula.
// ax^2 + bx + c == 0
// x = dt
// a = .5 * acc
// b = vel
// c = this.pos - pos
final double disc = vel * vel - 2.0 * acc * (this.pos - pos);
if (disc < 0.0) {
// Extrapolating this MotionState never reaches the desired pos.
return Double.NaN;
}
final double sqrt_disc = Math.sqrt(disc);
final double max_dt = (-vel + sqrt_disc) / acc;
final double min_dt = (-vel - sqrt_disc) / acc;
if (min_dt >= 0.0 && (max_dt < 0.0 || min_dt < max_dt)) {
return t + min_dt;
}
if (max_dt >= 0.0) {
return t + max_dt;
}
// We only reach the desired pos in the past.
return Double.NaN;
}
@Override
public String toString() {
return "(t=" + t + ", pos=" + pos + ", vel=" + vel + ", acc=" + acc + ")";
}
/**
* Checks if two MotionStates are epsilon-equals (all fields are equal within a nominal tolerance).
*/
@Override
public boolean equals(Object other) {
return (other instanceof MotionState) && equals((MotionState) other, kEpsilon);
}
/**
* Checks if two MotionStates are epsilon-equals (all fields are equal within a specified tolerance).
*/
public boolean equals(MotionState other, double epsilon) {
return coincident(other, epsilon) && epsilonEquals(acc, other.acc, epsilon);
}
/**
* Checks if two MotionStates are coincident (t, pos, and vel are equal within a nominal tolerance, but acceleration
* may be different).
*/
public boolean coincident(MotionState other) {
return coincident(other, kEpsilon);
}
/**
* Checks if two MotionStates are coincident (t, pos, and vel are equal within a specified tolerance, but
* acceleration may be different).
*/
public boolean coincident(MotionState other, double epsilon) {
return epsilonEquals(t, other.t, epsilon) && epsilonEquals(pos, other.pos, epsilon)
&& epsilonEquals(vel, other.vel, epsilon);
}
/**
* Returns a MotionState that is the mirror image of this one. Pos, vel, and acc are all negated, but time is not.
*/
public MotionState flipped() {
return new MotionState(t, -pos, -vel, -acc);
}
}
@@ -0,0 +1,8 @@
package org.usfirst.frc4388.utility.motion;
public class MotionUtil {
/**
* A constant for consistent floating-point equality checking within this library.
*/
public static double kEpsilon = 1e-6;
}
@@ -0,0 +1,203 @@
package org.usfirst.frc4388.utility.motion;
import org.usfirst.frc4388.utility.motion.MotionProfileGoal.CompletionBehavior;
/**
* A controller for tracking a profile generated to attain a MotionProfileGoal. The controller uses feedforward on
* acceleration, velocity, and position; proportional feedback on velocity and position; and integral feedback on
* position.
*/
public class ProfileFollower {
protected double mKp;
protected double mKi;
protected double mKv;
protected double mKffv;
protected double mKffa;
protected double mMinOutput = Double.NEGATIVE_INFINITY;
protected double mMaxOutput = Double.POSITIVE_INFINITY;
protected MotionState mLatestActualState;
protected MotionState mInitialState;
protected double mLatestPosError;
protected double mLatestVelError;
protected double mTotalError;
protected MotionProfileGoal mGoal = null;
protected MotionProfileConstraints mConstraints = null;
protected SetpointGenerator mSetpointGenerator = new SetpointGenerator();
protected SetpointGenerator.Setpoint mLatestSetpoint = null;
/**
* Create a new ProfileFollower.
*
* @param kp
* The proportional gain on position error.
* @param ki
* The integral gain on position error.
* @param kv
* The proportional gain on velocity error (or derivative gain on position error).
* @param kffv
* The feedforward gain on velocity. Should be 1.0 if the units of the profile match the units of the
* output.
* @param kffa
* The feedforward gain on acceleration.
*/
public ProfileFollower(double kp, double ki, double kv, double kffv, double kffa) {
resetProfile();
setGains(kp, ki, kv, kffv, kffa);
}
public void setGains(double kp, double ki, double kv, double kffv, double kffa) {
mKp = kp;
mKi = ki;
mKv = kv;
mKffv = kffv;
mKffa = kffa;
}
/**
* Completely clear all state related to the current profile (min and max outputs are maintained).
*/
public void resetProfile() {
mTotalError = 0.0;
mInitialState = MotionState.kInvalidState;
mLatestActualState = MotionState.kInvalidState;
mLatestPosError = Double.NaN;
mLatestVelError = Double.NaN;
mSetpointGenerator.reset();
mGoal = null;
mConstraints = null;
resetSetpoint();
}
/**
* Specify a goal and constraints for achieving the goal.
*/
public void setGoalAndConstraints(MotionProfileGoal goal, MotionProfileConstraints constraints) {
if (mGoal != null && !mGoal.equals(goal) && mLatestSetpoint != null) {
// Clear the final state bit since the goal has changed.
mLatestSetpoint.final_setpoint = false;
}
mGoal = goal;
mConstraints = constraints;
}
public void setGoal(MotionProfileGoal goal) {
setGoalAndConstraints(goal, mConstraints);
}
/**
* @return The current goal (null if no goal has been set since the latest call to reset()).
*/
public MotionProfileGoal getGoal() {
return mGoal;
}
public void setConstraints(MotionProfileConstraints constraints) {
setGoalAndConstraints(mGoal, constraints);
}
public MotionState getSetpoint() {
return (mLatestSetpoint == null ? MotionState.kInvalidState : mLatestSetpoint.motion_state);
}
/**
* Reset just the setpoint. This means that the latest_state provided to update() will be used rather than feeding
* forward the previous setpoint the next time update() is called. This almost always forces a MotionProfile update,
* and may be warranted if tracking error gets very large.
*/
public void resetSetpoint() {
mLatestSetpoint = null;
}
public void resetIntegral() {
mTotalError = 0.0;
}
/**
* Update the setpoint and apply the control gains to generate a control output.
*
* @param latest_state
* The latest *actual* state, used only for feedback purposes (unless this is the first iteration or
* reset()/resetSetpoint() was just called, in which case this is the new start state for the profile).
* @param t
* The timestamp for which the setpoint is desired.
* @return An output that reflects the control output to apply to achieve the new setpoint.
*/
public synchronized double update(MotionState latest_state, double t) {
mLatestActualState = latest_state;
MotionState prev_state = latest_state;
if (mLatestSetpoint != null) {
prev_state = mLatestSetpoint.motion_state;
} else {
mInitialState = prev_state;
}
final double dt = Math.max(0.0, t - prev_state.t());
mLatestSetpoint = mSetpointGenerator.getSetpoint(mConstraints, mGoal, prev_state, t);
// Update error.
mLatestPosError = mLatestSetpoint.motion_state.pos() - latest_state.pos();
mLatestVelError = mLatestSetpoint.motion_state.vel() - latest_state.vel();
// Calculate the feedforward and proportional terms.
double output = mKp * mLatestPosError + mKv * mLatestVelError + mKffv * mLatestSetpoint.motion_state.vel()
+ (Double.isNaN(mLatestSetpoint.motion_state.acc()) ? 0.0 : mKffa * mLatestSetpoint.motion_state.acc());
if (output >= mMinOutput && output <= mMaxOutput) {
// Update integral.
mTotalError += mLatestPosError * dt;
output += mKi * mTotalError;
} else {
// Reset integral windup.
mTotalError = 0.0;
}
// Clamp to limits.
output = Math.max(mMinOutput, Math.min(mMaxOutput, output));
return output;
}
public void setMinOutput(double min_output) {
mMinOutput = min_output;
}
public void setMaxOutput(double max_output) {
mMaxOutput = max_output;
}
public double getPosError() {
return mLatestPosError;
}
public double getVelError() {
return mLatestVelError;
}
/**
* We are finished the profile when the final setpoint has been generated. Note that this does not check whether we
* are anywhere close to the final setpoint, however.
*
* @return True if the final setpoint has been generated for the current goal.
*/
public boolean isFinishedProfile() {
return mGoal != null && mLatestSetpoint != null && mLatestSetpoint.final_setpoint;
}
/**
* We are on target if our actual state achieves the goal (where the definition of achievement depends on the goal's
* completion behavior).
*
* @return True if we have actually achieved the current goal.
*/
public boolean onTarget() {
if (mGoal == null || mLatestSetpoint == null) {
return false;
}
// For the options that don't achieve the goal velocity exactly, also count any instance where we have passed
// the finish line.
final double goal_to_start = mGoal.pos() - mInitialState.pos();
final double goal_to_actual = mGoal.pos() - mLatestActualState.pos();
final boolean passed_goal_state = Math.signum(goal_to_start) * Math.signum(goal_to_actual) < 0.0;
return mGoal.atGoalState(mLatestActualState)
|| (mGoal.completion_behavior() != CompletionBehavior.OVERSHOOT && passed_goal_state);
}
}
@@ -0,0 +1,114 @@
package org.usfirst.frc4388.utility.motion;
import java.util.Optional;
/**
* A SetpointGenerate does just-in-time motion profile generation to supply a stream of setpoints that obey the given
* constraints to a controller. The profile is regenerated when any of the inputs change, but is cached (and trimmed as
* we go) if the only update is to the current state.
*
* Note that typically for smooth control, a user will feed the last iteration's setpoint as the argument to
* getSetpoint(), and should only use a measured state directly on the first iteration or if a large disturbance is
* detected.
*/
public class SetpointGenerator {
/**
* A Setpoint is just a MotionState and an additional flag indicating whether this is setpoint achieves the goal
* (useful for higher-level logic to know that it is now time to do something else).
*/
public static class Setpoint {
public MotionState motion_state;
public boolean final_setpoint;
public Setpoint(MotionState motion_state, boolean final_setpoint) {
this.motion_state = motion_state;
this.final_setpoint = final_setpoint;
}
}
protected MotionProfile mProfile = null;
protected MotionProfileGoal mGoal = null;
protected MotionProfileConstraints mConstraints = null;
public SetpointGenerator() {
}
/**
* Force a reset of the profile.
*/
public void reset() {
mProfile = null;
mGoal = null;
mConstraints = null;
}
/**
* Get a new Setpoint (and generate a new MotionProfile if necessary).
*
* @param constraints
* The constraints to use.
* @param goal
* The goal to use.
* @param prev_state
* The previous setpoint (or measured state of the system to do a reset).
* @param t
* The time to generate a setpoint for.
* @return The new Setpoint at time t.
*/
public synchronized Setpoint getSetpoint(MotionProfileConstraints constraints, MotionProfileGoal goal,
MotionState prev_state,
double t) {
boolean regenerate = mConstraints == null || !mConstraints.equals(constraints) || mGoal == null
|| !mGoal.equals(goal) || mProfile == null;
if (!regenerate && !mProfile.isEmpty()) {
Optional<MotionState> expected_state = mProfile.stateByTime(prev_state.t());
regenerate = !expected_state.isPresent() || !expected_state.get().equals(prev_state);
}
if (regenerate) {
// Regenerate the profile, as our current profile does not satisfy the inputs.
mConstraints = constraints;
mGoal = goal;
mProfile = MotionProfileGenerator.generateProfile(constraints, goal, prev_state);
// System.out.println("Regenerating profile: " + mProfile);
}
// Sample the profile at time t.
Setpoint rv = null;
if (!mProfile.isEmpty() && mProfile.isValid()) {
MotionState setpoint;
if (t > mProfile.endTime()) {
setpoint = mProfile.endState();
} else if (t < mProfile.startTime()) {
setpoint = mProfile.startState();
} else {
setpoint = mProfile.stateByTime(t).get();
}
// Shorten the profile and return the new setpoint.
mProfile.trimBeforeTime(t);
rv = new Setpoint(setpoint, mProfile.isEmpty() || mGoal.atGoalState(setpoint));
}
// Invalid or empty profile - just output the same state again.
if (rv == null) {
rv = new Setpoint(prev_state, true);
}
if (rv.final_setpoint) {
// Ensure the final setpoint matches the goal exactly.
rv.motion_state = new MotionState(rv.motion_state.t(), mGoal.pos(),
Math.signum(rv.motion_state.vel()) * Math.max(mGoal.max_abs_vel(), Math.abs(rv.motion_state.vel())),
0.0);
}
return rv;
}
/**
* Get the full profile from the latest call to getSetpoint(). Useful to check estimated time or distance to goal.
*
* @return The profile from the latest call to getSetpoint(), or null if there is not yet a profile.
*/
public MotionProfile getProfile() {
return mProfile;
}
}
+87
View File
@@ -0,0 +1,87 @@
{
"fileName": "Phoenix.json",
"name": "CTRE-Phoenix",
"version": "5.12.0",
"uuid": "ab676553-b602-441f-a38d-f1296eff6537",
"mavenUrls": [
"http://devsite.ctr-electronics.com/maven/release/"
],
"jsonUrl": "http://devsite.ctr-electronics.com/maven/release/com/ctre/phoenix/Phoenix-latest.json",
"javaDependencies": [
{
"groupId": "com.ctre.phoenix",
"artifactId": "api-java",
"version": "5.12.0"
},
{
"groupId": "com.ctre.phoenix",
"artifactId": "wpiapi-java",
"version": "5.12.0"
}
],
"jniDependencies": [
{
"groupId": "com.ctre.phoenix",
"artifactId": "cci",
"version": "5.12.0",
"isJar": false,
"skipInvalidPlatforms": true,
"validPlatforms": [
"linuxathena",
"windowsx86-64",
"linuxx86-64"
]
}
],
"cppDependencies": [
{
"groupId": "com.ctre.phoenix",
"artifactId": "wpiapi-cpp",
"version": "5.12.0",
"libName": "CTRE_Phoenix_WPI",
"headerClassifier": "headers",
"sharedLibrary": false,
"skipInvalidPlatforms": true,
"binaryPlatforms": [
"linuxathena",
"windowsx86-64",
"linuxx86-64"
]
},
{
"groupId": "com.ctre.phoenix",
"artifactId": "api-cpp",
"version": "5.12.0",
"libName": "CTRE_Phoenix",
"headerClassifier": "headers",
"sharedLibrary": false,
"skipInvalidPlatforms": true,
"binaryPlatforms": [
"linuxathena",
"windowsx86-64",
"linuxx86-64"
]
},
{
"groupId": "com.ctre.phoenix",
"artifactId": "cci",
"version": "5.12.0",
"libName": "CTRE_PhoenixCCI",
"headerClassifier": "headers",
"sharedLibrary": false,
"skipInvalidPlatforms": true,
"binaryPlatforms": [
"linuxathena",
"windowsx86-64",
"linuxx86-64"
]
},
{
"groupId": "com.ctre.phoenix",
"artifactId": "core",
"version": "5.12.0",
"libName": "CTRE_PhoenixCore",
"headerClassifier": "headers"
}
]
}
+32
View File
@@ -0,0 +1,32 @@
{
"fileName": "REVRobotics.json",
"name": "REVRobotics",
"version": "1.0.27",
"uuid": "3f48eb8c-50fe-43a6-9cb7-44c86353c4cb",
"mavenUrls": [
"http://www.revrobotics.com/content/sw/max/sdk/maven/"
],
"jsonUrl": "http://www.revrobotics.com/content/sw/max/sdk/REVRobotics.json",
"javaDependencies": [
{
"groupId": "com.revrobotics.frc",
"artifactId": "SparkMax-java",
"version": "1.0.27"
}
],
"jniDependencies": [],
"cppDependencies": [
{
"groupId": "com.revrobotics.frc",
"artifactId": "SparkMax-cpp",
"version": "1.0.27",
"libName": "libSparkMax",
"headerClassifier": "headers",
"sharedLibrary": false,
"skipInvalidPlatforms": true,
"binaryPlatforms": [
"linuxathena"
]
}
]
}
+21
View File
@@ -0,0 +1,21 @@
{
"fileName": "cw.json",
"name": "cw",
"version": "3.1.344",
"uuid":"13",
"mavenUrls": [
"https://mvnrepository.com"
],
"jsonUrl": "file:///C:/dev/cw.json",
"javaDependencies": [
{
"groupId": "com.googlecode.json-simple",
"artifactId": "json-simple",
"version": "1.1.1"
}
],
"jniDependencies": [],
"cppDependencies": [
]
}