From 54c5bea4d204d319361b0974f56e67da34232f75 Mon Sep 17 00:00:00 2001 From: DriverStationComputer Date: Sat, 27 Jan 2018 16:43:12 -0800 Subject: [PATCH 01/43] Other misc. offseason robot configurations to ensure smooth use Includes motor inversions, joystick input inversions, etc. --- .../usfirst/frc/team199/Robot2018/Robot.java | 2 +- .../frc/team199/Robot2018/RobotMap.java | 24 +++++++++++-------- .../Robot2018/subsystems/Drivetrain.java | 6 ++--- 3 files changed, 18 insertions(+), 14 deletions(-) diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/Robot.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/Robot.java index 147d6f9..1aa0231 100644 --- a/Robot2018/src/org/usfirst/frc/team199/Robot2018/Robot.java +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/Robot.java @@ -65,9 +65,9 @@ public void robotInit() { intakeEject = new IntakeEject(); lift = new Lift(); dt = new Drivetrain(); - rmap.initPIDControllers(); ld = new LeftDrive(); rd = new RightDrive(); + rmap.initPIDControllers(); oi = new OI(); listen = new Listener(); // chooser.addObject("My Auto", new MyAutoCommand()); diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/RobotMap.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/RobotMap.java index 7e24f58..9861d0e 100644 --- a/Robot2018/src/org/usfirst/frc/team199/Robot2018/RobotMap.java +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/RobotMap.java @@ -87,18 +87,26 @@ private void configSPX(WPI_VictorSPX mc) { } public RobotMap() { - + + ahrs = new AHRS(SerialPort.Port.kMXP); + dtGyro = new AnalogGyro(getPort("Gyro", 0)); + //dtGear = new DoubleSolenoid(getPort("1dtGearSolenoid", 0), getPort("2dtGearSolenoid", 1)); + leftEnc = new Encoder(getPort("1LeftEnc", 0), getPort("2LeftEnc", 1)); - dtLeftDrive = new WPI_TalonSRX(getPort("LeftTalonSRXDrive", 0)); + dtLeftDrive = new WPI_TalonSRX(2); + //getPort("LeftTalonSRXDrive", 2)); configSRX(dtLeftDrive); - dtLeftSlave = new WPI_VictorSPX(getPort("LeftTalonSPXSlave", 1)); + dtLeftSlave = new WPI_VictorSPX(0); + //getPort("LeftTalonSPXSlave", 0)); configSPX(dtLeftSlave); dtLeft = new SpeedControllerGroup(dtLeftDrive, dtLeftSlave); rightEnc = new Encoder(getPort("1RightEnc", 2), getPort("2RightEnc", 3)); - dtRightDrive = new WPI_TalonSRX(getPort("RightTalonSRXDrive", 2)); + dtRightDrive = new WPI_TalonSRX(1); + //getPort("RightTalonSRXDrive", 1)); configSRX(dtRightDrive); - dtRightSlave = new WPI_VictorSPX(getPort("RightTalonSPXSlave", 3)); + dtRightSlave = new WPI_VictorSPX(3); + //getPort("RightTalonSPXSlave", 3)); configSPX(dtRightSlave); dtRight = new SpeedControllerGroup(dtRightDrive, dtRightSlave); @@ -112,14 +120,10 @@ public RobotMap() { // moveController.setOutputRange(-1.0, 1.0); // moveController.setContinuous(false); // moveController.setAbsoluteTolerance(Robot.getConst("MoveTolerance", 2)); - - - ahrs = new AHRS(SerialPort.Port.kMXP); - dtGyro = new AnalogGyro(getPort("Gyro", 0)); - dtGear = new DoubleSolenoid(getPort("1dtGearSolenoid", 0), getPort("2dtGearSolenoid", 1)); } + /** Initializes pid controllers seperately from robotMap because the required parameters have not been initialized yet.*/ public void initPIDControllers() { turnController = new PIDController(Robot.getConst("TurnkP", 1), Robot.getConst("TurnkI", 0), Robot.getConst("TurnkD", 0), ahrs, Robot.dt); diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/subsystems/Drivetrain.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/subsystems/Drivetrain.java index f776767..ecbf5ae 100644 --- a/Robot2018/src/org/usfirst/frc/team199/Robot2018/subsystems/Drivetrain.java +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/subsystems/Drivetrain.java @@ -153,12 +153,12 @@ public void resetEnc() { public void teleopDrive() { if (Robot.getBool("Arcade Drive", true)) { if (Robot.getBool("Arcade Drive Default Setup", true)) { - Robot.dt.arcadeDrive(Robot.oi.leftJoy.getY(), Robot.oi.rightJoy.getX()); + Robot.dt.arcadeDrive(-Robot.oi.leftJoy.getY(), Robot.oi.rightJoy.getX()); } else { - Robot.dt.arcadeDrive(Robot.oi.rightJoy.getY(), Robot.oi.leftJoy.getX()); + Robot.dt.arcadeDrive(-Robot.oi.rightJoy.getY(), Robot.oi.leftJoy.getX()); } } else { - Robot.dt.tankDrive(Robot.oi.leftJoy.getY(), Robot.oi.rightJoy.getY()); + Robot.dt.tankDrive(-Robot.oi.leftJoy.getY(), Robot.oi.rightJoy.getY()); } } From aa0924c9d550f6835c7a50182456ef7f8c841435 Mon Sep 17 00:00:00 2001 From: Alex Binnard Date: Wed, 31 Jan 2018 20:09:54 -0800 Subject: [PATCH 02/43] Add scriptupload. Apparently all my code got rewrote? --- .../autonomous/scriptupload/requirements.txt | 1 + .../autonomous/scriptupload/scriptupload.py | 40 +++++++++++++++++++ 2 files changed, 41 insertions(+) create mode 100644 Robot2018/src/org/usfirst/frc/team199/Robot2018/autonomous/scriptupload/requirements.txt create mode 100644 Robot2018/src/org/usfirst/frc/team199/Robot2018/autonomous/scriptupload/scriptupload.py diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/autonomous/scriptupload/requirements.txt b/Robot2018/src/org/usfirst/frc/team199/Robot2018/autonomous/scriptupload/requirements.txt new file mode 100644 index 0000000..c17bd6b --- /dev/null +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/autonomous/scriptupload/requirements.txt @@ -0,0 +1 @@ +pynetworktables==2018.0.0 diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/autonomous/scriptupload/scriptupload.py b/Robot2018/src/org/usfirst/frc/team199/Robot2018/autonomous/scriptupload/scriptupload.py new file mode 100644 index 0000000..bedc85e --- /dev/null +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/autonomous/scriptupload/scriptupload.py @@ -0,0 +1,40 @@ +#nt setup +from networktables import NetworkTables +import __future__ +NetworkTables.initialize(server='10.1.99.2') +prefs = NetworkTables.getTable("Preferences") + +go = False #true when file has been successfully read +lines = [] #an array of strings which represent each line of the file +filename = raw_input("File name: ") #the name of the file to read (user input) +oneline = "" #the file as a single string +retry = False #whether or not to retry. + +if not NetworkTables.isConnected(): + print("You aren't connected to the bot.") + quit() +#loops until a file is read into the file array +while not go: + if retry: + filename = raw_input("Not found. Try another name (enter to quit): ")#retry, or quit (in case the file doesn't exist) + if filename == "": + quit() + try: + with open(filename) as script: + lines = script.readlines() + oneline = "".join(lines) #this is python's weird syntax for joining an array into one line, as we can't seem to pull string arrays from the WPILib Preferences class. + break + except: + retry = True +#puts the string +prefs.putString("autoscripts", oneline) +print("Uploading %s as a String[] to key \"autoscripts\"" % filename) + +#checks if the key has been filled +tester = "" #a variable to check if autoscripts is None + +tester = prefs.getValue("autoscripts", None) +if tester != oneline: + print("It doesn't look like key \"autoscripts\" is filled, maybe you aren't connected to networktables?") +else: + print("Success!") From 619bf9b5f8eeb9aae3f7c687dbed24cc20773f40 Mon Sep 17 00:00:00 2001 From: Alex Binnard Date: Fri, 2 Feb 2018 18:44:10 -0800 Subject: [PATCH 03/43] Begin writing AutoMoveTo --- .../Robot2018/autonomous/AutoUtils.java | 36 ++++++++++- .../Robot2018/commands/AutoMoveTo.java | 63 ++++++++++--------- .../Robot2018/subsystems/Drivetrain.java | 7 +++ 3 files changed, 76 insertions(+), 30 deletions(-) diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/autonomous/AutoUtils.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/autonomous/AutoUtils.java index ffb5e7a..deadb68 100644 --- a/Robot2018/src/org/usfirst/frc/team199/Robot2018/autonomous/AutoUtils.java +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/autonomous/AutoUtils.java @@ -5,7 +5,9 @@ import java.util.Map; public class AutoUtils { - + private static double currX; + private static double currY; + private static double currRotation; /** * Parses the inputted script file into a map of scripts * @@ -74,6 +76,36 @@ public static Map> parseScriptFile(String scriptFile return autoScripts; } + /* + * All of these are getters and setters for the robot's position and orientation + */ + public static double getX() { + return currX; + } + public static double getY() { + return currY; + } + public static double getRot() { + return currRotation; + } + public static void setX(double x) { + currX = x; + } + public static void setY(double y) { + currY = y; + } + public static void setRot(double rot) { + currRotation = rot; + } + public static void changeX(double x) { + currX += x; + } + public static void changeY(double y) { + currY += y; + } + public static void changeRot(double rot) { + currRotation += rot; + } /** * Validates the command inputted to see if it's AAA compliant @@ -210,7 +242,7 @@ private static boolean isPoint (String s) { * @param s the argument * @return if the argument is a double */ - private static boolean isDouble (String s) { + public static boolean isDouble (String s) { try { Double.parseDouble(s); } catch (Exception e) { diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/AutoMoveTo.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/AutoMoveTo.java index ec1c414..99dc2c9 100644 --- a/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/AutoMoveTo.java +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/AutoMoveTo.java @@ -1,36 +1,43 @@ package org.usfirst.frc.team199.Robot2018.commands; -import edu.wpi.first.wpilibj.command.Command; +import org.usfirst.frc.team199.Robot2018.autonomous.AutoUtils; + +//import org.usfirst.frc.team199.Robot2018.subsystems.Drivetrain; + +import edu.wpi.first.wpilibj.command.CommandGroup; /** * */ -public class AutoMoveTo extends Command { - - public AutoMoveTo(String[] points) { - // Use requires() here to declare subsystem dependencies - // eg. requires(chassis); - } - - // Called just before this Command runs the first time - protected void initialize() { - } - - // 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 false; - } - - // 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() { +public class AutoMoveTo extends CommandGroup { + + public AutoMoveTo(String[] args) { + //requires(Drivetrain); + double rotation; + double[] point; + for (String arg : args) { + if (AutoUtils.isDouble(arg)) { + rotation = Double.valueOf(arg); + } + } + + + + // 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. } } diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/subsystems/Drivetrain.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/subsystems/Drivetrain.java index e4b316d..6cbe1eb 100644 --- a/Robot2018/src/org/usfirst/frc/team199/Robot2018/subsystems/Drivetrain.java +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/subsystems/Drivetrain.java @@ -231,4 +231,11 @@ public void shiftGears(boolean highGear) { public void shiftGearSolenoidOff() { dtGear.set(DoubleSolenoid.Value.kOff); } + /** + * Returns the gyroscope + * @return the gyroscope + */ + public AHRS getGyro() { + return fancyGyro; + } } From 0ce8802b6f2f0558ddbbbe9695d43732b2f9f4eb Mon Sep 17 00:00:00 2001 From: caengineers Date: Fri, 2 Feb 2018 20:04:13 -0800 Subject: [PATCH 04/43] Finish writing AutoMoveTo, change RunScript to use PID commands instead of AutoMove and AutoTurn --- .../Robot2018/autonomous/AutoUtils.java | 2 +- .../team199/Robot2018/commands/AutoMoveTo.java | 18 ++++++++++++++++++ .../team199/Robot2018/commands/RunScript.java | 5 +++-- 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/autonomous/AutoUtils.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/autonomous/AutoUtils.java index deadb68..a532817 100644 --- a/Robot2018/src/org/usfirst/frc/team199/Robot2018/autonomous/AutoUtils.java +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/autonomous/AutoUtils.java @@ -211,7 +211,7 @@ private static void logWarning (int lineNumber, String message) { * @param s the argument * @return if the argument is a point */ - private static boolean isPoint (String s) { + public static boolean isPoint (String s) { // checks if it starts and ends with parentheses if (!s.startsWith("(") || !s.endsWith(")")) return false; diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/AutoMoveTo.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/AutoMoveTo.java index 99dc2c9..a0d332e 100644 --- a/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/AutoMoveTo.java +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/AutoMoveTo.java @@ -1,6 +1,8 @@ package org.usfirst.frc.team199.Robot2018.commands; +import org.usfirst.frc.team199.Robot2018.Robot; import org.usfirst.frc.team199.Robot2018.autonomous.AutoUtils; +import org.usfirst.frc.team199.Robot2018.autonomous.PIDSourceAverage; //import org.usfirst.frc.team199.Robot2018.subsystems.Drivetrain; @@ -15,9 +17,25 @@ public AutoMoveTo(String[] args) { //requires(Drivetrain); double rotation; double[] point; + String parentheseless; + String[] pointparts; for (String arg : args) { if (AutoUtils.isDouble(arg)) { rotation = Double.valueOf(arg); + addSequential(new PIDTurn(rotation - AutoUtils.getRot(), Robot.dt, Robot.dt.getGyro())); + AutoUtils.setRot(rotation); + } else if (AutoUtils.isPoint(arg)) { + parentheseless = arg.substring(1, arg.length() - 1); + pointparts = parentheseless.split(","); + point[0] = Double.parseDouble(pointparts[0]); + point[1] = Double.parseDouble(pointparts[1]); + addSequential(new PIDTurn(Math.toDegrees(Math.atan((point[0] - AutoUtils.getX())/(point[1] - AutoUtils.getY())) - AutoUtils.getRot()), Robot.dt, Robot.dt.getGyro())); + addSequential(new PIDMove(Math.sqrt(((point[0] - AutoUtils.getX()) * (point[0] - AutoUtils.getX()) + ((point[1] - AutoUtils.getY()) * (point[1] - AutoUtils.getY())))), Robot.dt, new PIDSourceAverage(null, null))); + AutoUtils.setX(point[0]); + AutoUtils.setY(point[1]); + AutoUtils.setRot(Math.toDegrees(Math.atan((point[0] - AutoUtils.getX())/(point[1] - AutoUtils.getY())))); + } else { + throw new IllegalArgumentException(); } } diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/RunScript.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/RunScript.java index d5b7208..413291d 100644 --- a/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/RunScript.java +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/RunScript.java @@ -3,6 +3,7 @@ import java.util.ArrayList; import org.usfirst.frc.team199.Robot2018.Robot; +import org.usfirst.frc.team199.Robot2018.autonomous.PIDSourceAverage; import edu.wpi.first.wpilibj.command.CommandGroup; import edu.wpi.first.wpilibj.command.WaitCommand; @@ -25,10 +26,10 @@ public RunScript(String scriptName) { addSequential(new AutoMoveTo(cmdArgs.split(" "))); break; case "turn": - addSequential(new AutoTurn(Double.parseDouble(cmdArgs))); + addSequential(new PIDTurn(Double.parseDouble(cmdArgs), Robot.dt, Robot.dt.getGyro())); break; case "move": - addSequential(new AutoMove(Double.parseDouble(cmdArgs))); + addSequential(new PIDMove(Double.parseDouble(cmdArgs), Robot.dt, new PIDSourceAverage(null, null)))); break; case "switch": addSequential(new EjectToSwitch()); From 2645eff5c6fdc4eb4caaed52bc6333b8b32138fc Mon Sep 17 00:00:00 2001 From: caengineers Date: Fri, 2 Feb 2018 20:27:37 -0800 Subject: [PATCH 05/43] Add scripts to project --- AAAScripts/scripts.txt | 389 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 389 insertions(+) create mode 100644 AAAScripts/scripts.txt diff --git a/AAAScripts/scripts.txt b/AAAScripts/scripts.txt new file mode 100644 index 0000000..b12474c --- /dev/null +++ b/AAAScripts/scripts.txt @@ -0,0 +1,389 @@ +# Naming Conventions +# ------------------- +# 1. Robot's start position. R = right, C = center, L = left. +# 2. Switch information. R = going for the right plate, L = going for the left plate, x = not going for switch +# 3. Scale information. R = going for the right plate, L = going for the left plate, x = not going for scale +# 4. Exchange. E = going for the exchange, x = not going for exchange + +# 3.3(l) x 2.9(w) + +#Cross Auto Line +Rxxx: + moveto (0, 120) #Drive past auto line + +#------------------------------------------------- + +#Switch +RRxx: + moveto (0, 128.4) (-42.6, 128.4) -90 #Drive directly forward to switch and then ram into switch + switch + +RLxx: + moveto (0, 188.4) (-210.6, 188.4) (-210.6, 128.4) (-186.6, 128.4) 90 #Curve around switch and then "ram" into switch + switch + +#------------------------------------------------- + +#Scale +RxRx: + moveto (0, 284.4) (-24.6, 284.4) -90 #Drive directly forward and place at initial horizontal to scale + scale + +RxLx: + moveto (0, 188.4) (-246.6, 188.4) (-246.6, 284.4) (-192.6, 284.4) 90 #Curve around switch and drive to scale + scale + +#------------------------------------------------- + +#Exchange +RxxE + moveto (0, 188.4) (-210.6, 188.4) (-210.6, 44.4) (-123.6, 44.4) (-123.6, -39.6) 180 #Curve around switch for baseline #then go to exchange + exchange + +#------------------------------------------------- + +#Switch and Scale +RRRx: + moveto (0, 128.4) (-41.4, 128.4) 90 #Drive directly forward then turn into switch + switch + moveto (0, 128.4) #Back out from switch + moveto (0, 80.4) (-9, 80.4) #Drive toward cubes + intake + moveto (0, 80.4) #Back out from cubes + moveto (0, 284.4) (-24.6, 284.4) -90 #Drive toward scale + scale + +RRLx: + moveto (0, 128.4) (-41.4, 128.4) 90 #Drive directly forward then turn into switch + switch + moveto (0, 128.4) #Back out from switch + moveto (0, 80.4) (-9, 80.4) -90 #Drive toward cubes + intake + moveto (0, 80.4) #Back out from cubes + moveto (0, 188.4) (-246.6, 188.4) (-246.6, 284.4) (-192.6, 284.4) 90 #Curve around switch + place at init distance to scale + scale + +RLRx: + moveto (0, 284.4) (-24.6, 284.4) -90 #Drive directly forward and place at initial horizontal to scale + scale + moveto (0, 284.4) #Back out from switch + moveto (0, 80.4) (-108, 80.4) -90 #Drive toward cubes + intake + moveto (0, 80.4) #Back out from cubes + moveto (0, 188.4) (-210.6,188.4) (-210.6, 128.4) (-186.6, 128.4) #Drive toward switch + switch + +RLLx: + moveto (0, 188.4) (-210.6,188.4) (-210.6, 128.4) (-186.6, 128.4) -90 #Curve around switch and then "ram" into switch + switch + moveto (-246.6, 128.4) #Back out from switch + moveto (-246.6, 80.4) (-144, 80.4) #Drive toward cubes + intake + moveto (-246.6, 80.4) #Back out from cubes + moveto (-246.6, 284.4) (-192.6, 284.4) 90 #Curve around switch + place at init distance to scale + scale + +#------------------------------------------------- + +#Switch and Exchange +RRxE: + moveto (0, 128.4) (-41.4, 128.4) 90 #Drive directly forward then turn into switch + switch + moveto (-192, 128.4) #Back out from switch + moveto (-192, 80.4) (-144, 80.4) #Drive toward cubes + #intake + moveto (0, 80.4) #Back out from cubes + moveto (0, 188.4) (-210.6, 188.4) (-210.6, 44.4) (-123.6, 44.4) (-123.6, -39.6) 180 #Curve around switch for baseline #then go to + exchange + +RLxE: + moveto (0, 188.4) (-210.6,188.4) (-210.6, 128.4) (-186.6, 128.4) 180 #Curve around switch and then "ram" into switch + switch + moveto (-246.6, 128.4) #Back out from switch + moveto (-246.6, 80.4) (-12, 80.4) #Drive toward cubes + #intake + moveto (-246.6, 80.4) #Back out from cubes + moveto (-246.6, 44.4) (-123.6, 44.4) (-123.6, -39.6) #Drive toward exchange + exchange + +# Naming Conventions +# ------------------- +# 1. Robot's start position. R = right, C = center, L = left. +# 2. Switch information. R = going for the right plate, L = going for the left plate, x = not going for switch +# 3. Scale information. R = going for the right plate, L = going for the left plate, x = not going for scale +# 4. Exchange. E = going for the exchange, x = not going for exchange + +# 3.3(l) x 2.9(w) + +#Cross Auto Line +Lxxx: + moveto (0, 120) #Drive past auto line + +#------------------------------------------------- + +#Switch +LLxx: + moveto (0, 129.8) (42.6, 129.8) -90 #Drive directly forward to switch and then ram into switch + switch + +LRxx: + moveto (0, 188.4) (210.6,188.4) (210.6, 129.8) (186.6, 129.8) 90 #Curve around switch and then "ram" into switch + switch + +#------------------------------------------------- + +#Scale +LxLx: + moveto (0, 284.4) (24.6, 284.4) -90 #Drive directly forward and place at initial horizontal to scale + scale + +LxRx: + moveto (0, 188.4) (246.6, 188.4) (246.6, 284.4) (16.05, 284.4) 90 #Curve around switch + place at init distance to scale + scale + +#------------------------------------------------- + +#Exchange +LxxE + moveto (0, 188.4) (210.6, 188.4) (210.6, 3.7) (124.8, 3.7) (124.8, -3.3) 180 #Curve around switch for baseline then go to exchange + exchange + +#------------------------------------------------- + +#Switch and Scale +LRRx: + moveto (0, 188.4) (210.6,188.4) (210.6, 129.8) (186.6, 129.8) -90 #Curve around switch and then "ram" into switch + switch + moveto (246.6, 129.8) #Back out from switch + moveto (246.6, 80.4) (12, 80.4) #Drive toward cubes + intake + moveto (246.6, 80.4) #Back out from cubes + moveto (246.6, 284.4) (16.05, 284.4) 90 #Curve around switch + place at init distance to scale + scale + +LRLx: + moveto (0, 284.4) (24.6, 284.4) -90 #Drive directly forward and place at initial horizontal to scale + scale + moveto (0, 284.4) #Back out from switch + moveto (0, 80.4) (108, 80.4) -90 #Drive toward cubes + intake + moveto (0, 80.4) #Back out from cubes + moveto (0, 188.4) (210.6,188.4) (210.6, 129.8) (186.6, 129.8) #Drive toward switch + switch + +LLRx: + moveto (0, 129.8) (42.6, 129.8) 90 #Drive directly forward then turn into switch + switch + moveto (0, 129.8) #Back out from switch + moveto (0, 80.4) (108, 80.4) -90 #Drive toward cubes + intake + moveto (0, 80.4) #Back out from cubes + moveto (0, 188.4) (246.6, 188.4) (246.6, 284.4) (192.6, 284.4) 90 #Curve around switch and drive to scale + scale + +LLLx: + moveto (0, 129.8) (42.6, 129.8) 90 #Drive directly forward then turn into switch + switch + moveto (0, 129.8) #Back out from switch + moveto (0, 80.4) (108, 80.4) #Drive toward cubes + intake + moveto (0, 80.4) #Back out from cubes + moveto (0, 284.4) (24.6, 284.4) -90 #Drive toward scale + scale + +#------------------------------------------------- + +#Switch and Exchange +LRxE: + moveto (0, 188.4) (210.6,188.4) (210.6, 129.8) (186.6, 129.8) 180 #Curve around switch and then "ram" into switch + switch + moveto (246.6, 129.8) #Back out from switch + moveto (246.6, 80.4) (12, 80.4) #Drive toward cubes + intake + moveto (246.6, 80.4) #Back out from cubes + moveto (246.6, 3.7) (124.8, 3.7) (124.8, -39.6) #Drive toward exchange + exchange + +LLxE: + moveto (0, 129.8) (42.6, 129.8) 90 #Drive directly forward then turn into switch + switch + moveto (17, 129.8) #Back out from switch + moveto (17, 80.4) (12, 80.4) #Drive toward cubes + intake + moveto (0, 80.4) #Back out from cubes + moveto (0, 188.4) (210.6, 188.4) (210.6, 44.4) (124.8, 44.4) (124.8, -39.6) 180 #Go to exchange + exchange + +# moveto +# turn +# move +# switch +# scale +# exchange +# wait +# intake +# jump +# end + + + +# 1. Robot's start position. R = right, C = center, L = left. +# 2. Switch information. R = going for the right plate, L = going for the left plate, x = not going for # switch +# 3. Scale information. R = going for the right plate, L = going for the left plate, x = not going for # scale +# 4. Exchange. E = going for the exchange, x = not going for exchange + + +# Location of Robot: rightmost next to exchange + +# Cross Auto Line +Cxxx: +moveto (48,80.375) #cross baseline + + +# Switch +CRxx: +moveto (0,60) 90 #5 feet forward, turn 90 +moveto (48,60) -90 #4 feet right,turn -90 +moveto(48,104.375) #7 feet forward until against switch enclosure, compensating for robot length +switch #deploy switch + +CLxx: +moveto (0,60) -90 #5 feet forward, turn -90 +moveto (-48,60) 90 #4 feet left,turn 90 +moveto(-48,104.375) #7 feet forward until against switch enclosure,compensating for robot length +switch #deploy switch + + +# Scale +CxRx: +moveto (0,60) 90 #5 feet forward, turn 90 +moveto (114,60) -90 #9.5 feet right, turn -90 +move 224.375 #22 feet forward, compensating for robot length +turn -90 #turn to face the switch +scale #deploy scale + + +CxLx: +moveto (0,60) -90 #5 feet forward, turn -90 +moveto (-90,60) 90 #7.5 feet left, turn 90 +move 224.375 #22 feet forward, compensating for robot length +turn 90 #turn to face the switch +scale #deploy scale + + +# Switch and Scale +CRLx: +moveto (0,60) 90 #5 feet forward, turn 90 +moveto (48,60) -90 #4 feet right,turn -90 +moveto(48,104.375) #7 feet forward until against switch enclosure +switch #deploy switch +move -39 #move 39 inches back +turn -90 #turn -90 degrees +move 48 #move 4 feet forward +intake #intake +turn -90 #turn -90 +move 45 # move 45 in. forward +turn -90 #turn 90 +moveto (-90,60) 90 #7.5 feet left, turn 90 +move 264 #22 feet forward +turn 90 #turn to face the switch +scale #deploy scale2 + + + +CRRx: +moveto (0,60) 90 #5 feet forward, turn 90 +moveto (48,60) -90 #4 feet right,turn -90 +moveto(48,104.375) #7 feet forward until against switch enclosure +switch #deploy switch +move -39 #move 39 inches back +turn -90 #turn -90 degrees +move 48 #move 4 feet forward +intake +turn -90 #turn -90nyu +move 45 # move 45 in. forward +turn -90 #turn -90 degrees +moveto (114, 60) -90 #9.5 feet right, turn -90 +move 264 #22 feet forward +turn -90 #turn to face the switch +scale #deploy scale + + + +CLRx: +moveto (0,60) -90 #5 feet forward, turn -90 +moveto (-48,60) 90 #4 feet left,turn 90 +moveto(-48,104.375) #7 feet forward until against switch enclosure +switch #deploy switch +move -39 #move 39 inches back +turn -90 #turn -90 degrees +move 48 #move 4 feet forward +intake #intake +turn 90 #turn 90 +move 45 # move 45 in. forward +turn 90 #turn 90 degrees +moveto (114, 60) -90 #9.5 feet right, turn -90 +move 264 #22 feet forward +turn -90 #turn to face the switch +scale #deploy scale + + +CLLx: +moveto (0,60) -90 #5 feet forward, turn -90 +moveto (-48,60) 90 #4 feet left,turn 90 +moveto(-48,104.375) #7 feet forward until against switch enclosure +switch #deploy switch +move -39 #move 39 inches back +turn 90 #turn 90 degrees +move 48 #move 4 feet forward +intake #intake +turn 90 #turn -90 +move 45 # move 45 in. forward +turn 90 #turn 90 +moveto (-90,60) -90 #7.5 feet right, turn -90 +move 264 #22 feet forward +turn 90 #turn to face the switch +scale #deploy scale2 + + + + +# Switch and Exchange +CRxE: +moveto (0,60) 90 #5 feet forward, turn 90 +moveto (48,60) -90 #4 feet right,turn -90 +moveto(48,104.375) #7 feet forward until against switch enclosure, compensating for robot length +switch #deploy switch +move -24 # 2 feet back +turn -90 +move 12 # 1 ft forward +intake +turn -90 +move 72 #move 6 feet forwards +turn 90 +move 60 #move 5 feet forward +turn -90 #turn to face the exchange +exchange + +CLxE: +moveto (0,60) -90 #5 feet forward, turn -90 +moveto (-48,60) 90 #3 feet left,turn 90 +moveto(-48,104.375) #7 feet forward until against switch enclosure,compensating for robot length +switch #deploy switch +move -24 +turn 90 +move 24 +intake +turn 90 +move -84 #move 7 feet back +turn 180 #turn 180 degrees to face the exchange +exchange + +# Exchange +CxxE: +moveto (0,60) -90 #5 feet forward, turn -90 +moveto (-48,60) 90 #3 feet left,turn 90 +moveto(-48,104.375) #7 feet forward until against switch enclosure,compensating for robot length +move -96 #move 8 feet back +turn 180 #turn 180 degrees to face the exchange +exchange \ No newline at end of file From 34a6e0e5aae6fddbf99bee670bb5b6a9faa31964 Mon Sep 17 00:00:00 2001 From: Alex Binnard Date: Sat, 3 Feb 2018 11:37:04 -0800 Subject: [PATCH 06/43] remember to save before committing kids --- .../usfirst/frc/team199/Robot2018/subsystems/Drivetrain.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/subsystems/Drivetrain.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/subsystems/Drivetrain.java index a13f12b..37a7a96 100644 --- a/Robot2018/src/org/usfirst/frc/team199/Robot2018/subsystems/Drivetrain.java +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/subsystems/Drivetrain.java @@ -232,15 +232,13 @@ public void shiftGears(boolean highGear) { public void shiftGearSolenoidOff() { dtGear.set(DoubleSolenoid.Value.kOff); } -<<<<<<< HEAD /** * Returns the gyroscope * @return the gyroscope */ public AHRS getGyro() { return fancyGyro; -======= - + } /** * Reset the kf constants for both VelocityPIDControllers based on current DT * gearing (high or low gear). @@ -270,6 +268,5 @@ public double getCurrentMaxSpeed() { } else { return Robot.getConst("Max Low Speed", 84); } ->>>>>>> upstream/master } } From cc0d6d55d746cca3531350d02ff3f8c58dc2a321 Mon Sep 17 00:00:00 2001 From: Alex Binnard Date: Sat, 3 Feb 2018 12:18:29 -0800 Subject: [PATCH 07/43] Make turning and moving without MoveTo track position, and fixed a parenthesis. --- .../team199/Robot2018/commands/AutoMoveTo.java | 2 +- .../team199/Robot2018/commands/RunScript.java | 18 +++++++++++++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/AutoMoveTo.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/AutoMoveTo.java index a0d332e..cfe16b4 100644 --- a/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/AutoMoveTo.java +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/AutoMoveTo.java @@ -16,7 +16,7 @@ public class AutoMoveTo extends CommandGroup { public AutoMoveTo(String[] args) { //requires(Drivetrain); double rotation; - double[] point; + double[] point = new double[2]; String parentheseless; String[] pointparts; for (String arg : args) { diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/RunScript.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/RunScript.java index 413291d..0a0b630 100644 --- a/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/RunScript.java +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/RunScript.java @@ -3,6 +3,7 @@ import java.util.ArrayList; import org.usfirst.frc.team199.Robot2018.Robot; +import org.usfirst.frc.team199.Robot2018.autonomous.AutoUtils; import org.usfirst.frc.team199.Robot2018.autonomous.PIDSourceAverage; import edu.wpi.first.wpilibj.command.CommandGroup; @@ -21,15 +22,30 @@ public RunScript(String scriptName) { String cmdName = cmd[0]; String cmdArgs = cmd[1]; + double[] point = new double[2]; + double rotation = 0; + String parentheseless; + String[] pointparts; + if (AutoUtils.isDouble(cmdArgs)) { + rotation = Double.valueOf(cmdArgs); + } else if (AutoUtils.isPoint(cmdArgs)) { + parentheseless = cmdArgs.substring(1, cmdArgs.length() - 1); + pointparts = parentheseless.split(","); + point[0] = Double.parseDouble(pointparts[0]); + } + switch (cmdName) { case "moveto": addSequential(new AutoMoveTo(cmdArgs.split(" "))); break; case "turn": addSequential(new PIDTurn(Double.parseDouble(cmdArgs), Robot.dt, Robot.dt.getGyro())); + AutoUtils.setRot(rotation); break; case "move": - addSequential(new PIDMove(Double.parseDouble(cmdArgs), Robot.dt, new PIDSourceAverage(null, null)))); + addSequential(new PIDMove(Double.parseDouble(cmdArgs), Robot.dt, new PIDSourceAverage(null, null))); + AutoUtils.setX(point[0]); + AutoUtils.setY(point[1]); break; case "switch": addSequential(new EjectToSwitch()); From 7170ab44d65d417c4dcf1243fa212444d71d85a2 Mon Sep 17 00:00:00 2001 From: DriverStationComputer Date: Sat, 3 Feb 2018 14:22:39 -0800 Subject: [PATCH 08/43] getting new teleop working fixed motor ports and joystick inversion commented out all VPID stuff for now --> next to test changed encoder ports a bit but might need revision --- .../usfirst/frc/team199/Robot2018/Robot.java | 2 + .../frc/team199/Robot2018/RobotMap.java | 53 +++++++++++-------- .../Robot2018/commands/ShiftHighGear.java | 2 +- .../Robot2018/commands/ShiftLowGear.java | 2 +- .../Robot2018/commands/TeleopDrive.java | 1 + .../Robot2018/subsystems/Drivetrain.java | 35 ++++++++++-- 6 files changed, 66 insertions(+), 29 deletions(-) diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/Robot.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/Robot.java index 613932e..4c57244 100644 --- a/Robot2018/src/org/usfirst/frc/team199/Robot2018/Robot.java +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/Robot.java @@ -170,5 +170,7 @@ public void teleopPeriodic() { */ @Override public void testPeriodic() { +// Robot.dt.setLeft(0.2); + Robot.dt.setRight(0.2); } } diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/RobotMap.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/RobotMap.java index f682301..4a5a5dd 100644 --- a/Robot2018/src/org/usfirst/frc/team199/Robot2018/RobotMap.java +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/RobotMap.java @@ -92,63 +92,72 @@ private void configSPX(WPI_VictorSPX mc) { public RobotMap() { - intakeMotor = new WPI_TalonSRX(getPort("IntakeTalonSRX", 4)); - configSRX(intakeMotor); - liftMotor = new WPI_TalonSRX(getPort("LiftTalonSRX", 5)); - configSRX(liftMotor); - climberMotor = new WPI_TalonSRX(getPort("ClimberTalonSRX", 6)); - configSRX(climberMotor); - - leftEncPort1 = new DigitalInput(getPort("1LeftEnc", 0)); - leftEncPort2 = new DigitalInput(getPort("2LeftEnc", 1)); +// intakeMotor = new WPI_TalonSRX(getPort("IntakeTalonSRX", 4)); +// configSRX(intakeMotor); +// liftMotor = new WPI_TalonSRX(getPort("LiftTalonSRX", 5)); +// configSRX(liftMotor); +// climberMotor = new WPI_TalonSRX(getPort("ClimberTalonSRX", 6)); +// configSRX(climberMotor); + + leftEncPort1 = new DigitalInput(getPort("1LeftEnc", 1)); + leftEncPort2 = new DigitalInput(getPort("2LeftEnc", 0)); leftEncDist = new Encoder(leftEncPort1, leftEncPort2); leftEncDist.setPIDSourceType(PIDSourceType.kDisplacement); leftEncRate = new Encoder(leftEncPort1, leftEncPort2); leftEncRate.setPIDSourceType(PIDSourceType.kRate); - dtLeftMaster = new WPI_TalonSRX(getPort("LeftTalonSRXMaster", 0)); + leftEncDist.setDistancePerPulse(Robot.getConst("Distance Per Pulse Left", 0.184)); + leftEncRate.setDistancePerPulse(Robot.getConst("Distance Per Pulse Left", 0.184)); + + dtLeftMaster = new WPI_TalonSRX(getPort("LeftTalonSRXMaster", 2)); configSRX(dtLeftMaster); - dtLeftSlave = new WPI_VictorSPX(getPort("LeftTalonSPXSlave", 1)); + dtLeftSlave = new WPI_VictorSPX(getPort("LeftVictorSPXSlave", 0)); configSPX(dtLeftSlave); dtLeft = new SpeedControllerGroup(dtLeftMaster, dtLeftSlave); leftVelocityController = new VelocityPIDController(Robot.getConst("VelocityLeftkP", 1), Robot.getConst("VelocityLeftkI", 0), Robot.getConst("VelocityLeftkD", 0), 1 / Robot.getConst("Max Low Speed", 84), leftEncRate, dtLeft); - leftVelocityController.enable(); +// leftVelocityController.enable(); leftVelocityController.setInputRange(-Robot.getConst("Max High Speed", 204), Robot.getConst("Max High Speed", 204)); leftVelocityController.setOutputRange(-1.0, 1.0); leftVelocityController.setContinuous(false); leftVelocityController.setAbsoluteTolerance(Robot.getConst("VelocityToleranceLeft", 2)); - rightEncPort1 = new DigitalInput(getPort("1RightEnc", 2)); - rightEncPort2 = new DigitalInput(getPort("2RightEnc", 3)); - rightEncDist = new Encoder(leftEncPort1, leftEncPort2); + rightEncPort1 = new DigitalInput(getPort("1RightEnc", 3)); + rightEncPort2 = new DigitalInput(getPort("2RightEnc", 2)); + rightEncDist = new Encoder(rightEncPort1, rightEncPort2); rightEncDist.setPIDSourceType(PIDSourceType.kDisplacement); - rightEncRate = new Encoder(leftEncPort1, leftEncPort2); + rightEncRate = new Encoder(rightEncPort1, rightEncPort2); rightEncRate.setPIDSourceType(PIDSourceType.kRate); - dtRightMaster = new WPI_TalonSRX(getPort("RightTalonSRXMaster", 2)); + rightEncDist.setDistancePerPulse(Robot.getConst("Distance Per Pulse Right", 0.184)); + rightEncRate.setDistancePerPulse(Robot.getConst("Distance Per Pulse Right", 0.184)); + + dtRightMaster = new WPI_TalonSRX(getPort("RightTalonSRXMaster", 1)); configSRX(dtRightMaster); - dtRightSlave = new WPI_VictorSPX(getPort("RightTalonSPXSlave", 3)); + dtRightSlave = new WPI_VictorSPX(getPort("RightVictorSPXSlave", 3)); configSPX(dtRightSlave); dtRight = new SpeedControllerGroup(dtRightMaster, dtRightSlave); +// dtRight.setInverted(true); rightVelocityController = new VelocityPIDController(Robot.getConst("VelocityRightkP", 1), Robot.getConst("VelocityRightkI", 0), Robot.getConst("VelocityRightkD", 0), 1 / Robot.getConst("Max Low Speed", 84), rightEncRate, dtRight); - rightVelocityController.enable(); +// rightVelocityController.enable(); rightVelocityController.setInputRange(-Robot.getConst("Max High Speed", 204), Robot.getConst("Max High Speed", 204)); rightVelocityController.setOutputRange(-1.0, 1.0); rightVelocityController.setContinuous(false); rightVelocityController.setAbsoluteTolerance(Robot.getConst("VelocityToleranceRight", 2)); - robotDrive = new DifferentialDrive(leftVelocityController, rightVelocityController); - robotDrive.setMaxOutput(Robot.getConst("Max High Speed", 204)); +// robotDrive = new DifferentialDrive(leftVelocityController, rightVelocityController); + robotDrive = new DifferentialDrive(dtLeft, dtRight); + +// robotDrive.setMaxOutput(Robot.getConst("Max High Speed", 204)); distEncAvg = new PIDSourceAverage(leftEncDist, rightEncDist); fancyGyro = new AHRS(SerialPort.Port.kMXP); - dtGear = new DoubleSolenoid(getPort("1dtGearSolenoid", 0), getPort("2dtGearSolenoid", 1)); + // dtGear = new DoubleSolenoid(getPort("1dtGearSolenoid", 0), getPort("2dtGearSolenoid", 1)); } /** diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/ShiftHighGear.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/ShiftHighGear.java index 89b81b6..c47c1f9 100644 --- a/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/ShiftHighGear.java +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/ShiftHighGear.java @@ -19,7 +19,7 @@ public ShiftHighGear() { // Called just before this Command runs the first time @Override protected void initialize() { - Robot.dt.shiftGears(true); +// Robot.dt.shiftGears(true); SmartDashboard.putBoolean("High Gear", true); Robot.dt.resetVelocityPIDkFConsts(); } diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/ShiftLowGear.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/ShiftLowGear.java index 4f403da..8c7111e 100644 --- a/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/ShiftLowGear.java +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/ShiftLowGear.java @@ -19,7 +19,7 @@ public ShiftLowGear() { // Called just before this Command runs the first time @Override protected void initialize() { - Robot.dt.shiftGears(false); +// Robot.dt.shiftGears(false); SmartDashboard.putBoolean("High Gear", false); Robot.dt.resetVelocityPIDkFConsts(); } diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/TeleopDrive.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/TeleopDrive.java index fb35327..6be7309 100644 --- a/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/TeleopDrive.java +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/TeleopDrive.java @@ -28,6 +28,7 @@ protected void initialize() { // Called repeatedly when this Command is scheduled to run @Override protected void execute() { + Robot.dt.resetVPIDInputRanges(); Robot.dt.teleopDrive(); } diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/subsystems/Drivetrain.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/subsystems/Drivetrain.java index 7855dfe..f00ba35 100644 --- a/Robot2018/src/org/usfirst/frc/team199/Robot2018/subsystems/Drivetrain.java +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/subsystems/Drivetrain.java @@ -20,6 +20,7 @@ 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; public class Drivetrain extends Subsystem implements DrivetrainInterface { // Put methods for controlling this subsystem @@ -27,9 +28,11 @@ public class Drivetrain extends Subsystem implements DrivetrainInterface { private final Encoder leftEncDist = RobotMap.leftEncDist; private final Encoder rightEncDist = RobotMap.rightEncDist; + private final Encoder leftEncRate = RobotMap.leftEncRate; + private final Encoder rightEncRate = RobotMap.rightEncRate; private final PIDSourceAverage distEncAvg = RobotMap.distEncAvg; - private final SpeedControllerGroup dtLeft = RobotMap.dtLeft; - private final SpeedControllerGroup dtRight = RobotMap.dtRight; + public final SpeedControllerGroup dtLeft = RobotMap.dtLeft; + public final SpeedControllerGroup dtRight = RobotMap.dtRight; private final DifferentialDrive robotDrive = RobotMap.robotDrive; private final VelocityPIDController leftVelocityController = RobotMap.leftVelocityController; private final VelocityPIDController rightVelocityController = RobotMap.rightVelocityController; @@ -41,6 +44,14 @@ public class Drivetrain extends Subsystem implements DrivetrainInterface { public void initDefaultCommand() { setDefaultCommand(new TeleopDrive()); } + + public void setLeft(double spd) { + dtLeft.set(spd); + } + + public void setRight(double spd) { + dtRight.set(spd); + } /** * Drives based on joystick input and SmartDashboard values @@ -49,13 +60,21 @@ public void initDefaultCommand() { public void teleopDrive() { if (Robot.getBool("Arcade Drive", true)) { if (Robot.getBool("Arcade Drive Default Setup", true)) { - Robot.dt.arcadeDrive(Robot.oi.leftJoy.getY(), Robot.oi.rightJoy.getX()); + Robot.dt.arcadeDrive(-Robot.oi.leftJoy.getY(), Robot.oi.rightJoy.getX()); } else { - Robot.dt.arcadeDrive(Robot.oi.rightJoy.getY(), Robot.oi.leftJoy.getX()); + Robot.dt.arcadeDrive(-Robot.oi.rightJoy.getY(), Robot.oi.leftJoy.getX()); } } else { - Robot.dt.tankDrive(Robot.oi.leftJoy.getY(), Robot.oi.rightJoy.getY()); + Robot.dt.tankDrive(-Robot.oi.leftJoy.getY(), -Robot.oi.rightJoy.getY()); } + SmartDashboard.putNumber("Drivetrain/Left VPID Targ", leftVelocityController.getSetpoint()); + SmartDashboard.putNumber("Drivetrain/Right VPID Targ", rightVelocityController.getSetpoint()); + SmartDashboard.putNumber("Drivetrain/Current Max Speed", getCurrentMaxSpeed()); + SmartDashboard.putNumber("Drivetrain/Left Enc Dist", leftEncDist.getDistance()); + SmartDashboard.putNumber("Drivetrain/Left Enc Rate", leftEncRate.getRate()); + SmartDashboard.putNumber("Drivetrain/Right Enc Dist", rightEncDist.getDistance()); + SmartDashboard.putNumber("Drivetrain/Right Enc Rate", rightEncRate.getRate()); + SmartDashboard.putNumber("Drivetrain/Enc Avg Dist", distEncAvg.pidGet()); } /** @@ -249,6 +268,12 @@ public double resetVelocityPIDkFConsts() { rightVelocityController.setF(newKF); return newKF; } + + public double resetVPIDInputRanges() { + double currentMaxSpd = getCurrentMaxSpeed(); + leftVelocityController.setInputRange(-currentMaxSpd, currentMaxSpd); + return currentMaxSpd; + } /** * Gets the current max speed of the DT based on gearing (high or low gear) From fd1b3b4c9e2445d317835f259b0bb3404649c828 Mon Sep 17 00:00:00 2001 From: DriverStationComputer Date: Sat, 3 Feb 2018 15:37:25 -0800 Subject: [PATCH 09/43] teleop VPID working! mostly! not quite sure if limiting target or changing kf based on current DT gear (high/low) --- .../usfirst/frc/team199/Robot2018/RobotMap.java | 17 +++++++---------- .../team199/Robot2018/commands/TeleopDrive.java | 2 ++ 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/RobotMap.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/RobotMap.java index 4a5a5dd..e952685 100644 --- a/Robot2018/src/org/usfirst/frc/team199/Robot2018/RobotMap.java +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/RobotMap.java @@ -114,10 +114,9 @@ public RobotMap() { configSPX(dtLeftSlave); dtLeft = new SpeedControllerGroup(dtLeftMaster, dtLeftSlave); - leftVelocityController = new VelocityPIDController(Robot.getConst("VelocityLeftkP", 1), + leftVelocityController = new VelocityPIDController(Robot.getConst("VelocityLeftkP", 0), Robot.getConst("VelocityLeftkI", 0), Robot.getConst("VelocityLeftkD", 0), - 1 / Robot.getConst("Max Low Speed", 84), leftEncRate, dtLeft); -// leftVelocityController.enable(); + /*1 / Robot.getConst("Max Low Speed", 84)*/ Robot.getConst("VelocityLeftkF", 1/84.0), leftEncRate, dtLeft); leftVelocityController.setInputRange(-Robot.getConst("Max High Speed", 204), Robot.getConst("Max High Speed", 204)); leftVelocityController.setOutputRange(-1.0, 1.0); @@ -138,22 +137,20 @@ public RobotMap() { dtRightSlave = new WPI_VictorSPX(getPort("RightVictorSPXSlave", 3)); configSPX(dtRightSlave); dtRight = new SpeedControllerGroup(dtRightMaster, dtRightSlave); -// dtRight.setInverted(true); - rightVelocityController = new VelocityPIDController(Robot.getConst("VelocityRightkP", 1), + rightVelocityController = new VelocityPIDController(Robot.getConst("VelocityRightkP", 0), Robot.getConst("VelocityRightkI", 0), Robot.getConst("VelocityRightkD", 0), - 1 / Robot.getConst("Max Low Speed", 84), rightEncRate, dtRight); -// rightVelocityController.enable(); + /*1 / Robot.getConst("Max Low Speed", 84)*/ Robot.getConst("VelocityRightkF", 1/84.0), rightEncRate, dtRight); rightVelocityController.setInputRange(-Robot.getConst("Max High Speed", 204), Robot.getConst("Max High Speed", 204)); rightVelocityController.setOutputRange(-1.0, 1.0); rightVelocityController.setContinuous(false); rightVelocityController.setAbsoluteTolerance(Robot.getConst("VelocityToleranceRight", 2)); -// robotDrive = new DifferentialDrive(leftVelocityController, rightVelocityController); - robotDrive = new DifferentialDrive(dtLeft, dtRight); + robotDrive = new DifferentialDrive(leftVelocityController, rightVelocityController); +// robotDrive = new DifferentialDrive(dtLeft, dtRight); -// robotDrive.setMaxOutput(Robot.getConst("Max High Speed", 204)); + robotDrive.setMaxOutput(Robot.getConst("Max High Speed", 204)); distEncAvg = new PIDSourceAverage(leftEncDist, rightEncDist); fancyGyro = new AHRS(SerialPort.Port.kMXP); diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/TeleopDrive.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/TeleopDrive.java index 6be7309..64b6b14 100644 --- a/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/TeleopDrive.java +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/TeleopDrive.java @@ -23,12 +23,14 @@ public TeleopDrive() { // Called just before this Command runs the first time @Override protected void initialize() { + Robot.dt.enableVelocityPIDs(); } // Called repeatedly when this Command is scheduled to run @Override protected void execute() { Robot.dt.resetVPIDInputRanges(); + Robot.dt.updatePidConstants(); Robot.dt.teleopDrive(); } From 7e88dbda8235950e6190926a3437c016c511ddb0 Mon Sep 17 00:00:00 2001 From: doawelul Date: Sat, 3 Feb 2018 16:05:15 -0800 Subject: [PATCH 10/43] added basic velocityPID functionality to the drivetrain using the talons. Not implemented yet --- .../Robot2018/commands/TalonDrive.java | 41 +++++++++++++++++++ .../Robot2018/subsystems/Drivetrain.java | 33 +++++++++++++++ 2 files changed, 74 insertions(+) create mode 100644 Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/TalonDrive.java diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/TalonDrive.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/TalonDrive.java new file mode 100644 index 0000000..e5614a4 --- /dev/null +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/TalonDrive.java @@ -0,0 +1,41 @@ +package org.usfirst.frc.team199.Robot2018.commands; + +import org.usfirst.frc.team199.Robot2018.Robot; + +import edu.wpi.first.wpilibj.command.Command; + +/** + * + */ +public class TalonDrive extends Command { + + public TalonDrive() { + // Use requires() here to declare subsystem dependencies + // eg. requires(chassis); + } + + // Called just before this Command runs the first time + protected void initialize() { + } + + // Called repeatedly when this Command is scheduled to run + protected void execute() { + Robot.dt.dtRightPIDDrive(Robot.oi.leftJoy.getY()); + Robot.dt.dtLeftPIDDrive(Robot.oi.rightJoy.getY()); + } + + // Make this return true when this Command no longer needs to run execute() + protected boolean isFinished() { + return false; + } + + // 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() { + end(); + } +} diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/subsystems/Drivetrain.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/subsystems/Drivetrain.java index 7855dfe..dff97c1 100644 --- a/Robot2018/src/org/usfirst/frc/team199/Robot2018/subsystems/Drivetrain.java +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/subsystems/Drivetrain.java @@ -13,6 +13,9 @@ import org.usfirst.frc.team199.Robot2018.autonomous.VelocityPIDController; import org.usfirst.frc.team199.Robot2018.commands.TeleopDrive; +import com.ctre.phoenix.motorcontrol.ControlMode; +import com.ctre.phoenix.motorcontrol.can.WPI_TalonSRX; +import com.ctre.phoenix.motorcontrol.can.WPI_VictorSPX; import com.kauailabs.navx.frc.AHRS; import edu.wpi.first.wpilibj.DoubleSolenoid; @@ -25,6 +28,10 @@ public class Drivetrain extends Subsystem implements DrivetrainInterface { // Put methods for controlling this subsystem // here. Call these from Commands. + private final WPI_TalonSRX dtLeftMaster = RobotMap.dtLeftMaster; + private final WPI_VictorSPX dtLeftSlave = RobotMap.dtLeftSlave; + private final WPI_TalonSRX dtRightMaster = RobotMap.dtRightMaster; + private final WPI_VictorSPX dtRightSlave = RobotMap.dtRightSlave; private final Encoder leftEncDist = RobotMap.leftEncDist; private final Encoder rightEncDist = RobotMap.rightEncDist; private final PIDSourceAverage distEncAvg = RobotMap.distEncAvg; @@ -42,6 +49,32 @@ public void initDefaultCommand() { setDefaultCommand(new TeleopDrive()); } + /** + * Sets the left side of the drivetrain to use the talons' pids to run at the + * specified speed. + * + * @param value + * The speed to run at + */ + public void dtLeftPIDDrive(double value) { + double setValue = value * Robot.getConst("Units per 100ms", 3413); + dtLeftMaster.set(ControlMode.Velocity, setValue); + dtLeftSlave.set(ControlMode.Velocity, setValue); + } + + /** + * Sets the right side of the drivetrain to use the talons' pids to run at the + * specified speed. + * + * @param value + * The speed to run at + */ + public void dtRightPIDDrive(double value) { + double setValue = value * Robot.getConst("Units per 100ms", 3413); + dtRightMaster.set(ControlMode.Velocity, setValue); + dtRightSlave.set(ControlMode.Velocity, setValue); + } + /** * Drives based on joystick input and SmartDashboard values */ From dfd621779ad38e08c08a6561d5eecd241be2a6bf Mon Sep 17 00:00:00 2001 From: DriverStationComputer Date: Sat, 3 Feb 2018 16:06:48 -0800 Subject: [PATCH 11/43] VPID works with shifting gears! yes it works now w/ shifting btwn high and low gears (i.e. max target and kf changes correctly) VPID targets are a little funky though (i.e. +- when they should be the opposite) due to DifferentialDrive auto inversion, joystick inversion, etc. Wheels still moving in intended joystick direction though. Will just have to see if this becomes problematic --- .../src/org/usfirst/frc/team199/Robot2018/RobotMap.java | 6 ++---- .../frc/team199/Robot2018/subsystems/Drivetrain.java | 2 ++ 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/RobotMap.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/RobotMap.java index e952685..55a07d4 100644 --- a/Robot2018/src/org/usfirst/frc/team199/Robot2018/RobotMap.java +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/RobotMap.java @@ -116,7 +116,7 @@ public RobotMap() { leftVelocityController = new VelocityPIDController(Robot.getConst("VelocityLeftkP", 0), Robot.getConst("VelocityLeftkI", 0), Robot.getConst("VelocityLeftkD", 0), - /*1 / Robot.getConst("Max Low Speed", 84)*/ Robot.getConst("VelocityLeftkF", 1/84.0), leftEncRate, dtLeft); + 1 / Robot.getConst("Max Low Speed", 84), leftEncRate, dtLeft); leftVelocityController.setInputRange(-Robot.getConst("Max High Speed", 204), Robot.getConst("Max High Speed", 204)); leftVelocityController.setOutputRange(-1.0, 1.0); @@ -140,7 +140,7 @@ public RobotMap() { rightVelocityController = new VelocityPIDController(Robot.getConst("VelocityRightkP", 0), Robot.getConst("VelocityRightkI", 0), Robot.getConst("VelocityRightkD", 0), - /*1 / Robot.getConst("Max Low Speed", 84)*/ Robot.getConst("VelocityRightkF", 1/84.0), rightEncRate, dtRight); + 1 / Robot.getConst("Max Low Speed", 84), rightEncRate, dtRight); rightVelocityController.setInputRange(-Robot.getConst("Max High Speed", 204), Robot.getConst("Max High Speed", 204)); rightVelocityController.setOutputRange(-1.0, 1.0); @@ -148,8 +148,6 @@ public RobotMap() { rightVelocityController.setAbsoluteTolerance(Robot.getConst("VelocityToleranceRight", 2)); robotDrive = new DifferentialDrive(leftVelocityController, rightVelocityController); -// robotDrive = new DifferentialDrive(dtLeft, dtRight); - robotDrive.setMaxOutput(Robot.getConst("Max High Speed", 204)); distEncAvg = new PIDSourceAverage(leftEncDist, rightEncDist); diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/subsystems/Drivetrain.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/subsystems/Drivetrain.java index f00ba35..deb2424 100644 --- a/Robot2018/src/org/usfirst/frc/team199/Robot2018/subsystems/Drivetrain.java +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/subsystems/Drivetrain.java @@ -266,12 +266,14 @@ public double resetVelocityPIDkFConsts() { double newKF = 1 / getCurrentMaxSpeed(); leftVelocityController.setF(newKF); rightVelocityController.setF(newKF); + SmartDashboard.putNumber("VPID kF", newKF); return newKF; } public double resetVPIDInputRanges() { double currentMaxSpd = getCurrentMaxSpeed(); leftVelocityController.setInputRange(-currentMaxSpd, currentMaxSpd); + rightVelocityController.setInputRange(-currentMaxSpd, currentMaxSpd); return currentMaxSpd; } From e3eae40b309f9a099468c182b2be40bb78545a2d Mon Sep 17 00:00:00 2001 From: doawelul Date: Sun, 4 Feb 2018 10:56:50 -0800 Subject: [PATCH 12/43] Added default kD math --- .../usfirst/frc/team199/Robot2018/RobotMap.java | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/RobotMap.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/RobotMap.java index f682301..8b7e43c 100644 --- a/Robot2018/src/org/usfirst/frc/team199/Robot2018/RobotMap.java +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/RobotMap.java @@ -112,7 +112,7 @@ public RobotMap() { dtLeft = new SpeedControllerGroup(dtLeftMaster, dtLeftSlave); leftVelocityController = new VelocityPIDController(Robot.getConst("VelocityLeftkP", 1), - Robot.getConst("VelocityLeftkI", 0), Robot.getConst("VelocityLeftkD", 0), + Robot.getConst("VelocityLeftkI", 0), Robot.getConst("VelocityLeftkD", calcDefkD()), 1 / Robot.getConst("Max Low Speed", 84), leftEncRate, dtLeft); leftVelocityController.enable(); leftVelocityController.setInputRange(-Robot.getConst("Max High Speed", 204), @@ -134,7 +134,7 @@ public RobotMap() { dtRight = new SpeedControllerGroup(dtRightMaster, dtRightSlave); rightVelocityController = new VelocityPIDController(Robot.getConst("VelocityRightkP", 1), - Robot.getConst("VelocityRightkI", 0), Robot.getConst("VelocityRightkD", 0), + Robot.getConst("VelocityRightkI", 0), Robot.getConst("VelocityRightkD", calcDefkD()), 1 / Robot.getConst("Max Low Speed", 84), rightEncRate, dtRight); rightVelocityController.enable(); rightVelocityController.setInputRange(-Robot.getConst("Max High Speed", 204), @@ -168,4 +168,15 @@ public int getPort(String key, int def) { return (int) SmartDashboard.getNumber("Port/" + key, def); } + /** + * Uses SmartDashboard and math to calculate a *great* default kD + */ + public double calcDefkD() { + double timeConstant = Robot.getConst("Omega Max", 5330) * Robot.getConst("Mass of Robot", 54.4311) + * Robot.getConst("Radius of Drivetrain Wheel", 0.0635) + * Robot.getConst("Radius of Drivetrain Wheel", 0.0635) / Robot.getConst("Stall Torque", 2.41); + double cycleTime = Robot.getConst("Code cycle time", 0.1); + double denominator = 1 - Math.pow(Math.E, -1 * cycleTime / timeConstant); + return 1 / denominator; + } } From 23c5c72cff69348ab16a0b0d69e2068f56aa8789 Mon Sep 17 00:00:00 2001 From: doawelul Date: Sun, 4 Feb 2018 11:12:52 -0800 Subject: [PATCH 13/43] added shiftLowGear to beginning of auto --- .../usfirst/frc/team199/Robot2018/Robot.java | 30 ++++++++++--------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/Robot.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/Robot.java index 613932e..abfcf5b 100644 --- a/Robot2018/src/org/usfirst/frc/team199/Robot2018/Robot.java +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/Robot.java @@ -9,6 +9,7 @@ import org.usfirst.frc.team199.Robot2018.commands.Autonomous; import org.usfirst.frc.team199.Robot2018.commands.Autonomous.Position; import org.usfirst.frc.team199.Robot2018.commands.Autonomous.Strategy; +import org.usfirst.frc.team199.Robot2018.commands.ShiftLowGear; import org.usfirst.frc.team199.Robot2018.subsystems.Climber; import org.usfirst.frc.team199.Robot2018.subsystems.ClimberAssist; import org.usfirst.frc.team199.Robot2018.subsystems.Drivetrain; @@ -41,13 +42,13 @@ public class Robot extends TimedRobot { public static Listener listen; public static OI oi; - + public static Map> autoScripts; Command autonomousCommand; SendableChooser posChooser = new SendableChooser(); Map> stratChoosers = new HashMap>(); - String[] fmsPossibilities = {"LL", "LR", "RL", "RR"}; + String[] fmsPossibilities = { "LL", "LR", "RL", "RR" }; public static double getConst(String key, double def) { if (!SmartDashboard.containsKey("Const/" + key)) { @@ -76,13 +77,13 @@ public void robotInit() { lift = new Lift(); dt = new Drivetrain(); oi = new OI(); - + // auto position chooser for (Position p : Position.values()) { posChooser.addObject(p.getSDName(), p); } SmartDashboard.putData("Starting Position", posChooser); - + // auto strategy choosers for (String input : fmsPossibilities) { SendableChooser chooser = new SendableChooser(); @@ -92,10 +93,10 @@ public void robotInit() { SmartDashboard.putData(input, chooser); stratChoosers.put(input, chooser); } - + // auto delay chooser SmartDashboard.putNumber("Auto Delay", 0); - + // parse scripts from Preferences, which maintains values throughout reboots autoScripts = AutoUtils.parseScriptFile(Preferences.getInstance().getString("autoscripts", "")); @@ -118,23 +119,24 @@ public void disabledPeriodic() { } /** - * This function is called once during the start of autonomous in order to - * grab values from SmartDashboard and the FMS and call the Autonomous - * command with those values. + * This function is called once during the start of autonomous in order to grab + * values from SmartDashboard and the FMS and call the Autonomous command with + * those values. */ @Override public void autonomousInit() { + Scheduler.getInstance().add(new ShiftLowGear()); String fmsInput = DriverStation.getInstance().getGameSpecificMessage(); Position startPos = posChooser.getSelected(); double autoDelay = SmartDashboard.getNumber("Auto Delay", 0); - + Map strategies = new HashMap(); for (Map.Entry> entry : stratChoosers.entrySet()) { - String key = entry.getKey(); - SendableChooser chooser = entry.getValue(); - strategies.put(key, chooser.getSelected()); + String key = entry.getKey(); + SendableChooser chooser = entry.getValue(); + strategies.put(key, chooser.getSelected()); } - + Autonomous auto = new Autonomous(startPos, strategies, autoDelay, fmsInput, false); auto.start(); } From dad76f7c85a50d4e0100ea9bd42470e2a053c103 Mon Sep 17 00:00:00 2001 From: DriverStationComputer Date: Sun, 4 Feb 2018 14:14:26 -0800 Subject: [PATCH 14/43] changes to get it working --- Robot2018/.classpath | 3 +++ Robot2018/.project | 2 +- Robot2018/src/org/usfirst/frc/team199/Robot2018/RobotMap.java | 4 ++-- .../usfirst/frc/team199/Robot2018/subsystems/Drivetrain.java | 2 +- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/Robot2018/.classpath b/Robot2018/.classpath index 9278f52..affd5c6 100644 --- a/Robot2018/.classpath +++ b/Robot2018/.classpath @@ -16,5 +16,8 @@ + + + diff --git a/Robot2018/.project b/Robot2018/.project index ccb0af9..62486a1 100644 --- a/Robot2018/.project +++ b/Robot2018/.project @@ -1,6 +1,6 @@ - Robot2018 + DSRobot2018 diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/RobotMap.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/RobotMap.java index 9861d0e..f6112ab 100644 --- a/Robot2018/src/org/usfirst/frc/team199/Robot2018/RobotMap.java +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/RobotMap.java @@ -97,7 +97,7 @@ public RobotMap() { //getPort("LeftTalonSRXDrive", 2)); configSRX(dtLeftDrive); dtLeftSlave = new WPI_VictorSPX(0); - //getPort("LeftTalonSPXSlave", 0)); + //getPort("LeftVictorSPXSlave", 0)); configSPX(dtLeftSlave); dtLeft = new SpeedControllerGroup(dtLeftDrive, dtLeftSlave); @@ -106,7 +106,7 @@ public RobotMap() { //getPort("RightTalonSRXDrive", 1)); configSRX(dtRightDrive); dtRightSlave = new WPI_VictorSPX(3); - //getPort("RightTalonSPXSlave", 3)); + //getPort("RightVictorSPXSlave", 3)); configSPX(dtRightSlave); dtRight = new SpeedControllerGroup(dtRightDrive, dtRightSlave); diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/subsystems/Drivetrain.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/subsystems/Drivetrain.java index ecbf5ae..061d5ba 100644 --- a/Robot2018/src/org/usfirst/frc/team199/Robot2018/subsystems/Drivetrain.java +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/subsystems/Drivetrain.java @@ -158,7 +158,7 @@ public void teleopDrive() { Robot.dt.arcadeDrive(-Robot.oi.rightJoy.getY(), Robot.oi.leftJoy.getX()); } } else { - Robot.dt.tankDrive(-Robot.oi.leftJoy.getY(), Robot.oi.rightJoy.getY()); + Robot.dt.tankDrive(-Robot.oi.leftJoy.getY(), -Robot.oi.rightJoy.getY()); } } From 71a0c43a53b191a5bce31f51e17f100ceba50718 Mon Sep 17 00:00:00 2001 From: DriverStationComputer Date: Sun, 4 Feb 2018 14:22:52 -0800 Subject: [PATCH 15/43] VPID testing in test mode --- .../usfirst/frc/team199/Robot2018/Robot.java | 15 +++++++-- .../Robot2018/subsystems/Drivetrain.java | 32 +++++++++++++++++++ 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/Robot.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/Robot.java index 4c57244..aed53b2 100644 --- a/Robot2018/src/org/usfirst/frc/team199/Robot2018/Robot.java +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/Robot.java @@ -165,12 +165,23 @@ public void teleopPeriodic() { Scheduler.getInstance().run(); } + boolean firstTime = true; /** * This function is called periodically during test mode */ @Override public void testPeriodic() { -// Robot.dt.setLeft(0.2); - Robot.dt.setRight(0.2); + if(firstTime) { + Robot.dt.enableVelocityPIDs(); + firstTime = false; + } + Robot.dt.setVPIDs(Robot.getConst("VPID Test Set", 0.5)); + SmartDashboard.putNumber("Drivetrain/Left VPID Targ", Robot.dt.getLeftVPIDSetpoint()); + SmartDashboard.putNumber("Drivetrain/Right VPID Targ", Robot.dt.getRightVPIDSetpoint()); + SmartDashboard.putNumber("Left VPID Error", Robot.dt.getLeftVPIDerror()); + SmartDashboard.putNumber("Right VPID Error", Robot.dt.getRightVPIDerror()); + SmartDashboard.putNumber("Left Enc Rate", Robot.dt.getLeftEncRate()); + SmartDashboard.putNumber("Right Enc Rate", Robot.dt.getRightEncRate()); + } } diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/subsystems/Drivetrain.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/subsystems/Drivetrain.java index deb2424..20923b5 100644 --- a/Robot2018/src/org/usfirst/frc/team199/Robot2018/subsystems/Drivetrain.java +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/subsystems/Drivetrain.java @@ -52,6 +52,38 @@ public void setLeft(double spd) { public void setRight(double spd) { dtRight.set(spd); } + + /** + * Use for testing only (i.e. when not going through robotDrive) + * */ + public void setVPIDs(double realSpd) { + leftVelocityController.set(realSpd); + rightVelocityController.set(-realSpd); + } + + public double getLeftVPIDerror() { + return leftVelocityController.getError(); + } + + public double getRightVPIDerror() { + return rightVelocityController.getError(); + } + + public double getLeftVPIDSetpoint() { + return leftVelocityController.get(); + } + + public double getRightVPIDSetpoint() { + return rightVelocityController.get(); + } + + public double getLeftEncRate() { + return leftEncRate.getRate(); + } + + public double getRightEncRate() { + return rightEncRate.getRate(); + } /** * Drives based on joystick input and SmartDashboard values From 55965bdd8e432bc85bd63cb6059b96a77025ca20 Mon Sep 17 00:00:00 2001 From: DriverStationComputer Date: Sun, 4 Feb 2018 14:28:42 -0800 Subject: [PATCH 16/43] ready for encoder ratio testing commented out VPID stuff in robotDrive constructor (and passed in SpdCtrGrps instead) and any enabling (TeleopDrive init and Robot testPeriodic) --- .../src/org/usfirst/frc/team199/Robot2018/Robot.java | 8 ++++---- .../src/org/usfirst/frc/team199/Robot2018/RobotMap.java | 6 ++++-- .../frc/team199/Robot2018/commands/TeleopDrive.java | 2 +- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/Robot.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/Robot.java index aed53b2..0893355 100644 --- a/Robot2018/src/org/usfirst/frc/team199/Robot2018/Robot.java +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/Robot.java @@ -171,10 +171,10 @@ public void teleopPeriodic() { */ @Override public void testPeriodic() { - if(firstTime) { - Robot.dt.enableVelocityPIDs(); - firstTime = false; - } +// if(firstTime) { +// Robot.dt.enableVelocityPIDs(); +// firstTime = false; +// } Robot.dt.setVPIDs(Robot.getConst("VPID Test Set", 0.5)); SmartDashboard.putNumber("Drivetrain/Left VPID Targ", Robot.dt.getLeftVPIDSetpoint()); SmartDashboard.putNumber("Drivetrain/Right VPID Targ", Robot.dt.getRightVPIDSetpoint()); diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/RobotMap.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/RobotMap.java index f25d0d2..943f1c5 100644 --- a/Robot2018/src/org/usfirst/frc/team199/Robot2018/RobotMap.java +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/RobotMap.java @@ -147,8 +147,10 @@ public RobotMap() { rightVelocityController.setContinuous(false); rightVelocityController.setAbsoluteTolerance(Robot.getConst("VelocityToleranceRight", 2)); - robotDrive = new DifferentialDrive(leftVelocityController, rightVelocityController); - robotDrive.setMaxOutput(Robot.getConst("Max High Speed", 204)); +// robotDrive = new DifferentialDrive(leftVelocityController, rightVelocityController); +// robotDrive.setMaxOutput(Robot.getConst("Max High Speed", 204)); + robotDrive = new DifferentialDrive(dtLeft, dtRight); + distEncAvg = new PIDSourceAverage(leftEncDist, rightEncDist); fancyGyro = new AHRS(SerialPort.Port.kMXP); // dtGear = new DoubleSolenoid(getPort("1dtGearSolenoid", 0), getPort("2dtGearSolenoid", 1)); diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/TeleopDrive.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/TeleopDrive.java index 64b6b14..0e8a94a 100644 --- a/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/TeleopDrive.java +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/TeleopDrive.java @@ -23,7 +23,7 @@ public TeleopDrive() { // Called just before this Command runs the first time @Override protected void initialize() { - Robot.dt.enableVelocityPIDs(); +// Robot.dt.enableVelocityPIDs(); } // Called repeatedly when this Command is scheduled to run From ff28259e529327cc5f0b0d5ebd2b665e443081e3 Mon Sep 17 00:00:00 2001 From: Corvin Bazgan Date: Mon, 5 Feb 2018 19:36:04 -0800 Subject: [PATCH 17/43] =?UTF-8?q?*=20Defined=20SmartDashboardInterface=20t?= =?UTF-8?q?o=20be=20able=20to=20mock=20calls=20to=20the=20smart=20dashboar?= =?UTF-8?q?d.=20*=20Refactored=20AutoMoveTo=20to=20be=20able=20to=20unit?= =?UTF-8?q?=20test.=20=20The=20test=20fails=20and=20the=20AutoMoveTo=20log?= =?UTF-8?q?ic=20needs=20to=20be=20fixed.=20*=20Refactored=20PIDMove=20to?= =?UTF-8?q?=20use=20the=20SD=20interface=20and=20use=20PIDSource=20instead?= =?UTF-8?q?.=20=20requires()=20is=20called=20only=20if=20the=20dt=20exists?= =?UTF-8?q?.=20*=20Refactored=20PIDTurn=20to=20use=20the=20SD=20interface?= =?UTF-8?q?=20and=20use=20the=20gyro=20PIDSource=20interface=20instead.=20?= =?UTF-8?q?=20requires()=20is=20called=20only=20if=20the=20dt=20exists.=20?= =?UTF-8?q?*=20RunScript=20was=20refactored=20to=20pass=20the=20necessary?= =?UTF-8?q?=20parameters=20to=20PIDMove,=20PIDTurn,=20and=20AutoMoveTo.=20?= =?UTF-8?q?*=20Drivetrain=20(and=20interface)=20return=20the=20gyro?= =?UTF-8?q?=E2=80=99s=20PIDSource.=20*=20Wrote=20the=20AutoMoveToTest=20un?= =?UTF-8?q?it=20test.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/usfirst/frc/team199/Robot2018/OI.java | 4 +- .../usfirst/frc/team199/Robot2018/Robot.java | 14 ++- .../Robot2018/SmartDashboardInterface.java | 5 + .../Robot2018/commands/AutoMoveTo.java | 16 ++- .../team199/Robot2018/commands/PIDMove.java | 12 +- .../team199/Robot2018/commands/PIDTurn.java | 14 ++- .../team199/Robot2018/commands/RunScript.java | 6 +- .../Robot2018/subsystems/Drivetrain.java | 4 +- .../subsystems/DrivetrainInterface.java | 8 ++ .../frc/team199/Robot2018/AutoMoveToTest.java | 115 ++++++++++++++++++ 10 files changed, 172 insertions(+), 26 deletions(-) create mode 100644 Robot2018/src/org/usfirst/frc/team199/Robot2018/SmartDashboardInterface.java create mode 100644 Robot2018/test/org/usfirst/frc/team199/Robot2018/AutoMoveToTest.java diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/OI.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/OI.java index 452606c..a94d2dd 100644 --- a/Robot2018/src/org/usfirst/frc/team199/Robot2018/OI.java +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/OI.java @@ -52,9 +52,9 @@ public OI() { shiftDriveTypeButton = new JoystickButton(leftJoy, getButton("Shift Drive Type", 2)); shiftDriveTypeButton.whenPressed(new ShiftDriveType()); PIDMoveButton = new JoystickButton(leftJoy, getButton("PID Move", 7)); - PIDMoveButton.whenPressed(new PIDMove(40, Robot.dt, RobotMap.distEncAvg)); + PIDMoveButton.whenPressed(new PIDMove(40, Robot.dt, Robot.sd, RobotMap.distEncAvg)); PIDTurnButton = new JoystickButton(leftJoy, getButton("PID Turn", 8)); - PIDTurnButton.whenPressed(new PIDTurn(30, Robot.dt, RobotMap.fancyGyro)); + PIDTurnButton.whenPressed(new PIDTurn(30, Robot.dt, RobotMap.fancyGyro, Robot.sd)); rightJoy = new Joystick(1); updatePIDConstantsButton = new JoystickButton(rightJoy, getButton("Get PID Constants", 8)); diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/Robot.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/Robot.java index 613932e..0d37fbb 100644 --- a/Robot2018/src/org/usfirst/frc/team199/Robot2018/Robot.java +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/Robot.java @@ -48,12 +48,18 @@ public class Robot extends TimedRobot { SendableChooser posChooser = new SendableChooser(); Map> stratChoosers = new HashMap>(); String[] fmsPossibilities = {"LL", "LR", "RL", "RR"}; + + public static SmartDashboardInterface sd = new SmartDashboardInterface() { + public double getConst(String key, double def) { + if (!SmartDashboard.containsKey("Const/" + key)) { + SmartDashboard.putNumber("Const/" + key, def); + } + return SmartDashboard.getNumber("Const/" + key, def); + } + }; public static double getConst(String key, double def) { - if (!SmartDashboard.containsKey("Const/" + key)) { - SmartDashboard.putNumber("Const/" + key, def); - } - return SmartDashboard.getNumber("Const/" + key, def); + return sd.getConst(key, def); } public static boolean getBool(String key, boolean def) { diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/SmartDashboardInterface.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/SmartDashboardInterface.java new file mode 100644 index 0000000..18a9dc2 --- /dev/null +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/SmartDashboardInterface.java @@ -0,0 +1,5 @@ +package org.usfirst.frc.team199.Robot2018; + +public interface SmartDashboardInterface { + public double getConst(String key, double def); +} diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/AutoMoveTo.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/AutoMoveTo.java index a0d332e..084a3c7 100644 --- a/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/AutoMoveTo.java +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/AutoMoveTo.java @@ -1,8 +1,11 @@ package org.usfirst.frc.team199.Robot2018.commands; -import org.usfirst.frc.team199.Robot2018.Robot; +import edu.wpi.first.wpilibj.PIDSource; + +import org.usfirst.frc.team199.Robot2018.SmartDashboardInterface; import org.usfirst.frc.team199.Robot2018.autonomous.AutoUtils; import org.usfirst.frc.team199.Robot2018.autonomous.PIDSourceAverage; +import org.usfirst.frc.team199.Robot2018.subsystems.DrivetrainInterface; //import org.usfirst.frc.team199.Robot2018.subsystems.Drivetrain; @@ -13,24 +16,25 @@ */ public class AutoMoveTo extends CommandGroup { - public AutoMoveTo(String[] args) { + public AutoMoveTo(String[] args, DrivetrainInterface dt, + SmartDashboardInterface sd, PIDSource pidMoveSrc) { //requires(Drivetrain); double rotation; - double[] point; + double[] point = {0,0}; String parentheseless; String[] pointparts; for (String arg : args) { if (AutoUtils.isDouble(arg)) { rotation = Double.valueOf(arg); - addSequential(new PIDTurn(rotation - AutoUtils.getRot(), Robot.dt, Robot.dt.getGyro())); + addSequential(new PIDTurn(rotation - AutoUtils.getRot(), dt, dt.getGyro(), sd)); AutoUtils.setRot(rotation); } else if (AutoUtils.isPoint(arg)) { parentheseless = arg.substring(1, arg.length() - 1); pointparts = parentheseless.split(","); point[0] = Double.parseDouble(pointparts[0]); point[1] = Double.parseDouble(pointparts[1]); - addSequential(new PIDTurn(Math.toDegrees(Math.atan((point[0] - AutoUtils.getX())/(point[1] - AutoUtils.getY())) - AutoUtils.getRot()), Robot.dt, Robot.dt.getGyro())); - addSequential(new PIDMove(Math.sqrt(((point[0] - AutoUtils.getX()) * (point[0] - AutoUtils.getX()) + ((point[1] - AutoUtils.getY()) * (point[1] - AutoUtils.getY())))), Robot.dt, new PIDSourceAverage(null, null))); + addSequential(new PIDTurn(Math.toDegrees(Math.atan((point[0] - AutoUtils.getX())/(point[1] - AutoUtils.getY())) - AutoUtils.getRot()), dt, dt.getGyro(), sd)); + addSequential(new PIDMove(Math.sqrt(((point[0] - AutoUtils.getX()) * (point[0] - AutoUtils.getX()) + ((point[1] - AutoUtils.getY()) * (point[1] - AutoUtils.getY())))), dt, sd, pidMoveSrc)); AutoUtils.setX(point[0]); AutoUtils.setY(point[1]); AutoUtils.setRot(Math.toDegrees(Math.atan((point[0] - AutoUtils.getX())/(point[1] - AutoUtils.getY())))); diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/PIDMove.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/PIDMove.java index ce46a05..1e7356a 100644 --- a/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/PIDMove.java +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/PIDMove.java @@ -1,10 +1,12 @@ package org.usfirst.frc.team199.Robot2018.commands; import org.usfirst.frc.team199.Robot2018.Robot; +import org.usfirst.frc.team199.Robot2018.SmartDashboardInterface; import org.usfirst.frc.team199.Robot2018.autonomous.PIDSourceAverage; import org.usfirst.frc.team199.Robot2018.subsystems.DrivetrainInterface; import edu.wpi.first.wpilibj.PIDController; +import edu.wpi.first.wpilibj.PIDSource; import edu.wpi.first.wpilibj.PIDOutput; import edu.wpi.first.wpilibj.command.Command; @@ -17,14 +19,16 @@ public class PIDMove extends Command implements PIDOutput { private DrivetrainInterface dt; private PIDController moveController; - public PIDMove(double targ, DrivetrainInterface dt, PIDSourceAverage avg) { + public PIDMove(double targ, DrivetrainInterface dt, SmartDashboardInterface sd, PIDSource avg) { // Use requires() here to declare subsystem dependencies // eg. requires(chassis); target = targ; this.dt = dt; - requires(Robot.dt); - moveController = new PIDController(Robot.getConst("MovekP", 1), Robot.getConst("MovekI", 0), - Robot.getConst("MovekD", 0), avg, this); + if (Robot.dt != null) { + requires(Robot.dt); + } + moveController = new PIDController(sd.getConst("MovekP", 1), sd.getConst("MovekI", 0), + sd.getConst("MovekD", 0), avg, this); } // Called just before this Command runs the first time diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/PIDTurn.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/PIDTurn.java index 6aa50df..a80379e 100644 --- a/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/PIDTurn.java +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/PIDTurn.java @@ -1,11 +1,11 @@ package org.usfirst.frc.team199.Robot2018.commands; import org.usfirst.frc.team199.Robot2018.Robot; +import org.usfirst.frc.team199.Robot2018.SmartDashboardInterface; import org.usfirst.frc.team199.Robot2018.subsystems.DrivetrainInterface; -import com.kauailabs.navx.frc.AHRS; - import edu.wpi.first.wpilibj.PIDController; +import edu.wpi.first.wpilibj.PIDSource; import edu.wpi.first.wpilibj.PIDOutput; import edu.wpi.first.wpilibj.command.Command; @@ -18,14 +18,16 @@ public class PIDTurn extends Command implements PIDOutput { DrivetrainInterface dt; private PIDController turnController; - public PIDTurn(double targ, DrivetrainInterface dt, AHRS ahrs) { + public PIDTurn(double targ, DrivetrainInterface dt, PIDSource ahrs, SmartDashboardInterface sd) { // Use requires() here to declare subsystem dependencies // eg. requires(chassis); target = targ; this.dt = dt; - requires(Robot.dt); - turnController = new PIDController(Robot.getConst("TurnkP", 1), Robot.getConst("TurnkI", 0), - Robot.getConst("TurnkD", 0), ahrs, this); + if (Robot.dt != null) { + requires(Robot.dt); + } + turnController = new PIDController(sd.getConst("TurnkP", 1), sd.getConst("TurnkI", 0), + sd.getConst("TurnkD", 0), ahrs, this); } // Called just before this Command runs the first time diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/RunScript.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/RunScript.java index 413291d..765f893 100644 --- a/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/RunScript.java +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/RunScript.java @@ -23,13 +23,13 @@ public RunScript(String scriptName) { switch (cmdName) { case "moveto": - addSequential(new AutoMoveTo(cmdArgs.split(" "))); + addSequential(new AutoMoveTo(cmdArgs.split(" "), Robot.dt, Robot.sd, new PIDSourceAverage(null, null))); break; case "turn": - addSequential(new PIDTurn(Double.parseDouble(cmdArgs), Robot.dt, Robot.dt.getGyro())); + addSequential(new PIDTurn(Double.parseDouble(cmdArgs), Robot.dt, Robot.dt.getGyro(), Robot.sd)); break; case "move": - addSequential(new PIDMove(Double.parseDouble(cmdArgs), Robot.dt, new PIDSourceAverage(null, null)))); + addSequential(new PIDMove(Double.parseDouble(cmdArgs), Robot.dt, Robot.sd, new PIDSourceAverage(null, null))); break; case "switch": addSequential(new EjectToSwitch()); diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/subsystems/Drivetrain.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/subsystems/Drivetrain.java index 6cbe1eb..44b4599 100644 --- a/Robot2018/src/org/usfirst/frc/team199/Robot2018/subsystems/Drivetrain.java +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/subsystems/Drivetrain.java @@ -18,6 +18,7 @@ import edu.wpi.first.wpilibj.DoubleSolenoid; import edu.wpi.first.wpilibj.Encoder; import edu.wpi.first.wpilibj.SpeedControllerGroup; +import edu.wpi.first.wpilibj.PIDSource; import edu.wpi.first.wpilibj.command.Subsystem; import edu.wpi.first.wpilibj.drive.DifferentialDrive; @@ -231,11 +232,12 @@ public void shiftGears(boolean highGear) { public void shiftGearSolenoidOff() { dtGear.set(DoubleSolenoid.Value.kOff); } + /** * Returns the gyroscope * @return the gyroscope */ - public AHRS getGyro() { + public PIDSource getGyro() { return fancyGyro; } } diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/subsystems/DrivetrainInterface.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/subsystems/DrivetrainInterface.java index 91a554f..aade1d7 100644 --- a/Robot2018/src/org/usfirst/frc/team199/Robot2018/subsystems/DrivetrainInterface.java +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/subsystems/DrivetrainInterface.java @@ -1,5 +1,7 @@ package org.usfirst.frc.team199.Robot2018.subsystems; +import edu.wpi.first.wpilibj.PIDSource; + public interface DrivetrainInterface { public void initDefaultCommand(); @@ -124,4 +126,10 @@ public interface DrivetrainInterface { * Stops the solenoid that pushes the drivetrain into low or high gear */ public void shiftGearSolenoidOff(); + + /** + * Returns the gyroscope + * @return the gyroscope + */ + public PIDSource getGyro(); } diff --git a/Robot2018/test/org/usfirst/frc/team199/Robot2018/AutoMoveToTest.java b/Robot2018/test/org/usfirst/frc/team199/Robot2018/AutoMoveToTest.java new file mode 100644 index 0000000..d71600e --- /dev/null +++ b/Robot2018/test/org/usfirst/frc/team199/Robot2018/AutoMoveToTest.java @@ -0,0 +1,115 @@ +package org.usfirst.frc.team199.Robot2018; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.mockito.Mockito.*; + +import edu.wpi.first.wpilibj.command.Command; +import edu.wpi.first.wpilibj.command.CommandGroup; +import edu.wpi.first.wpilibj.command.Subsystem; +import edu.wpi.first.wpilibj.internal.HardwareTimer; +import edu.wpi.first.wpilibj.HLUsageReporting; +import edu.wpi.first.wpilibj.PIDController; +import edu.wpi.first.wpilibj.PIDSource; +import edu.wpi.first.wpilibj.PIDSourceType; +import edu.wpi.first.wpilibj.Timer; +import edu.wpi.first.wpilibj.PIDOutput; +import edu.wpi.first.wpilibj.livewindow.LiveWindow; + +import com.kauailabs.navx.frc.AHRS; + +import org.usfirst.frc.team199.Robot2018.commands.Autonomous; +import org.usfirst.frc.team199.Robot2018.commands.TeleopDrive; +import org.usfirst.frc.team199.Robot2018.commands.Autonomous.Position; +import org.usfirst.frc.team199.Robot2018.commands.Autonomous.Strategy; +import org.usfirst.frc.team199.Robot2018.subsystems.DrivetrainInterface; +import org.usfirst.frc.team199.Robot2018.autonomous.AutoUtils; +import org.usfirst.frc.team199.Robot2018.commands.AutoMoveTo; + +import java.util.HashMap; +import java.util.Map; + +class AutoMoveToTest { + + // May need a BeforeEach to reset AutoUtils. + + @BeforeEach + void setUp() { + // Since VelocityPIDController extends PIDController and PIDController calls + // static methods in wpilib that only work on robot, + // we setup these mocks to allow the tests to run off robot. + HardwareTimer tim = mock(HardwareTimer.class); + Timer.Interface timerInstance = mock(Timer.Interface.class); + when(tim.newTimer()).thenReturn(timerInstance); + Timer.SetImplementation(tim); + HLUsageReporting.Interface usageReporter = mock(HLUsageReporting.Interface.class); + HLUsageReporting.SetImplementation(usageReporter); + } + + @Test + void testWPICommand() { + Command command = new Command() { + protected boolean isFinished() { return false; } + }; + assertNotNull(command); + } + + @Test + void testWPICommandGroup() { + CommandGroup group = new CommandGroup(); + assertNotNull(group); + + Command command = new Command() { + protected boolean isFinished() { return false; } + }; + group.addSequential(command); + } + + @Test + void testPIDController() { + PIDSource source = mock(PIDSource.class); + PIDOutput output = mock(PIDOutput.class); + + PIDController ctrl = new PIDController(0, 0, 0, source, output); + assertNotNull(ctrl); + } + + //@Test + // Problem instantiating Subsystem because of SendableBase using network tables. + void testWPISubsystem() { + //LiveWindow.setEnabled(false); + Subsystem subsystem = new Subsystem() { + protected void initDefaultCommand() { + setDefaultCommand(new Command() { + protected boolean isFinished() { return false; } + }); + } + }; + assertNotNull(subsystem); + } + + @Test + void testForwardAndRight() { + String[] args = {"(0,12)","(12,12)"}; + + AutoUtils.setRot(0); + AutoUtils.setX(0); + AutoUtils.setY(0); + + PIDSource pidGyroSrc = mock(PIDSource.class); + when(pidGyroSrc.getPIDSourceType()).thenReturn(PIDSourceType.kDisplacement); + DrivetrainInterface dt = mock(DrivetrainInterface.class); + when(dt.getGyro()).thenReturn(pidGyroSrc); + SmartDashboardInterface sd = mock(SmartDashboardInterface.class); + PIDSource pidMoveSrc = mock(PIDSource.class); + + AutoMoveTo testAMT = new AutoMoveTo(args, dt, sd, pidMoveSrc); + + assertEquals(90, AutoUtils.getRot()); + assertEquals(12, AutoUtils.getX()); + assertEquals(12, AutoUtils.getY()); + } +} From bc7a5e53a6e03d186390f373ada3f0b12be5164b Mon Sep 17 00:00:00 2001 From: Corvin Bazgan Date: Mon, 5 Feb 2018 20:36:08 -0800 Subject: [PATCH 18/43] Added the testForward case for AutoMoveTo. This test also fails and the bug needs fixing. --- .../frc/team199/Robot2018/AutoMoveToTest.java | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/Robot2018/test/org/usfirst/frc/team199/Robot2018/AutoMoveToTest.java b/Robot2018/test/org/usfirst/frc/team199/Robot2018/AutoMoveToTest.java index d71600e..b14623f 100644 --- a/Robot2018/test/org/usfirst/frc/team199/Robot2018/AutoMoveToTest.java +++ b/Robot2018/test/org/usfirst/frc/team199/Robot2018/AutoMoveToTest.java @@ -112,4 +112,26 @@ void testForwardAndRight() { assertEquals(12, AutoUtils.getX()); assertEquals(12, AutoUtils.getY()); } + + @Test + void testForward() { + String[] args = {"(0,12)"}; + + AutoUtils.setRot(0); + AutoUtils.setX(0); + AutoUtils.setY(0); + + PIDSource pidGyroSrc = mock(PIDSource.class); + when(pidGyroSrc.getPIDSourceType()).thenReturn(PIDSourceType.kDisplacement); + DrivetrainInterface dt = mock(DrivetrainInterface.class); + when(dt.getGyro()).thenReturn(pidGyroSrc); + SmartDashboardInterface sd = mock(SmartDashboardInterface.class); + PIDSource pidMoveSrc = mock(PIDSource.class); + + AutoMoveTo testAMT = new AutoMoveTo(args, dt, sd, pidMoveSrc); + + assertEquals(0, AutoUtils.getRot()); + assertEquals(0, AutoUtils.getX()); + assertEquals(12, AutoUtils.getY()); + } } From 1c73625c030cacd2dcd06b1eee086007a71b355a Mon Sep 17 00:00:00 2001 From: Alex Binnard Date: Wed, 7 Feb 2018 17:18:04 -0800 Subject: [PATCH 19/43] More updates to AutoMoveTo --- .../Robot2018/commands/AutoMoveTo.java | 28 ++++--------------- .../Robot2018/subsystems/Drivetrain.java | 1 - 2 files changed, 6 insertions(+), 23 deletions(-) diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/AutoMoveTo.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/AutoMoveTo.java index cfe16b4..950feff 100644 --- a/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/AutoMoveTo.java +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/AutoMoveTo.java @@ -9,7 +9,7 @@ import edu.wpi.first.wpilibj.command.CommandGroup; /** - * + * The AutoMoveTo command, which makes the robot go through a set of movements and rotations. */ public class AutoMoveTo extends CommandGroup { @@ -29,8 +29,11 @@ public AutoMoveTo(String[] args) { pointparts = parentheseless.split(","); point[0] = Double.parseDouble(pointparts[0]); point[1] = Double.parseDouble(pointparts[1]); - addSequential(new PIDTurn(Math.toDegrees(Math.atan((point[0] - AutoUtils.getX())/(point[1] - AutoUtils.getY())) - AutoUtils.getRot()), Robot.dt, Robot.dt.getGyro())); - addSequential(new PIDMove(Math.sqrt(((point[0] - AutoUtils.getX()) * (point[0] - AutoUtils.getX()) + ((point[1] - AutoUtils.getY()) * (point[1] - AutoUtils.getY())))), Robot.dt, new PIDSourceAverage(null, null))); + addSequential(new PIDTurn(Math.toDegrees(Math.atan((point[0] - AutoUtils.getX())/(point[1] - AutoUtils.getY())) - AutoUtils.getRot()), + Robot.dt, Robot.dt.getGyro())); + addSequential(new PIDMove(Math.sqrt(((point[0] - AutoUtils.getX()) * (point[0] - AutoUtils.getX()) + + ((point[1] - AutoUtils.getY()) * (point[1] - AutoUtils.getY())))), + Robot.dt, new PIDSourceAverage(null, null))); AutoUtils.setX(point[0]); AutoUtils.setY(point[1]); AutoUtils.setRot(Math.toDegrees(Math.atan((point[0] - AutoUtils.getX())/(point[1] - AutoUtils.getY())))); @@ -38,24 +41,5 @@ public AutoMoveTo(String[] args) { throw new IllegalArgumentException(); } } - - - - // 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. } } diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/subsystems/Drivetrain.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/subsystems/Drivetrain.java index 37a7a96..b6d4a1e 100644 --- a/Robot2018/src/org/usfirst/frc/team199/Robot2018/subsystems/Drivetrain.java +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/subsystems/Drivetrain.java @@ -233,7 +233,6 @@ public void shiftGearSolenoidOff() { dtGear.set(DoubleSolenoid.Value.kOff); } /** - * Returns the gyroscope * @return the gyroscope */ public AHRS getGyro() { From b899642c780d976b9382dd4010501dfc194a7e7d Mon Sep 17 00:00:00 2001 From: DriverStationComputer Date: Thu, 8 Feb 2018 19:47:58 -0800 Subject: [PATCH 20/43] teleop working on practice bot After much arduous troubleshooting and a bad CIM, it works! Proper inversions for joysticks and motors, shifting gears works as expected (changed buttons a bit though), shifting drive type works as expected --- .../org/usfirst/frc/team199/Robot2018/OI.java | 8 ++-- .../usfirst/frc/team199/Robot2018/Robot.java | 19 ++++---- .../frc/team199/Robot2018/RobotMap.java | 48 ++++++++++++------- .../Robot2018/commands/ShiftHighGear.java | 2 +- .../Robot2018/commands/ShiftLowGear.java | 2 +- .../Robot2018/subsystems/Drivetrain.java | 8 ++-- 6 files changed, 53 insertions(+), 34 deletions(-) diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/OI.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/OI.java index 452606c..a93f25c 100644 --- a/Robot2018/src/org/usfirst/frc/team199/Robot2018/OI.java +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/OI.java @@ -45,10 +45,6 @@ public int getButton(String key, int def) { public OI() { leftJoy = new Joystick(0); - shiftLowGearButton = new JoystickButton(leftJoy, getButton("Shift Low Gear", 3)); - shiftLowGearButton.whenPressed(new ShiftLowGear()); - shiftHighGearButton = new JoystickButton(leftJoy, getButton("Shift High Gear", 5)); - shiftHighGearButton.whenPressed(new ShiftHighGear()); shiftDriveTypeButton = new JoystickButton(leftJoy, getButton("Shift Drive Type", 2)); shiftDriveTypeButton.whenPressed(new ShiftDriveType()); PIDMoveButton = new JoystickButton(leftJoy, getButton("PID Move", 7)); @@ -57,6 +53,10 @@ public OI() { PIDTurnButton.whenPressed(new PIDTurn(30, Robot.dt, RobotMap.fancyGyro)); rightJoy = new Joystick(1); + shiftHighGearButton = new JoystickButton(rightJoy, getButton("Shift High Gear", 3)); + shiftHighGearButton.whenPressed(new ShiftHighGear()); + shiftLowGearButton = new JoystickButton(rightJoy, getButton("Shift Low Gear", 2)); + shiftLowGearButton.whenPressed(new ShiftLowGear()); updatePIDConstantsButton = new JoystickButton(rightJoy, getButton("Get PID Constants", 8)); updatePIDConstantsButton.whenPressed(new UpdatePIDConstants()); updateEncoderDPPButton = new JoystickButton(rightJoy, getButton("Get Encoder Dist Per Pulse", 9)); diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/Robot.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/Robot.java index 0893355..85055a3 100644 --- a/Robot2018/src/org/usfirst/frc/team199/Robot2018/Robot.java +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/Robot.java @@ -174,14 +174,17 @@ public void testPeriodic() { // if(firstTime) { // Robot.dt.enableVelocityPIDs(); // firstTime = false; -// } - Robot.dt.setVPIDs(Robot.getConst("VPID Test Set", 0.5)); - SmartDashboard.putNumber("Drivetrain/Left VPID Targ", Robot.dt.getLeftVPIDSetpoint()); - SmartDashboard.putNumber("Drivetrain/Right VPID Targ", Robot.dt.getRightVPIDSetpoint()); - SmartDashboard.putNumber("Left VPID Error", Robot.dt.getLeftVPIDerror()); - SmartDashboard.putNumber("Right VPID Error", Robot.dt.getRightVPIDerror()); - SmartDashboard.putNumber("Left Enc Rate", Robot.dt.getLeftEncRate()); - SmartDashboard.putNumber("Right Enc Rate", Robot.dt.getRightEncRate()); +//// } +// Robot.dt.setVPIDs(Robot.getConst("VPID Test Set", 0.5)); +// SmartDashboard.putNumber("Drivetrain/Left VPID Targ", Robot.dt.getLeftVPIDSetpoint()); +// SmartDashboard.putNumber("Drivetrain/Right VPID Targ", Robot.dt.getRightVPIDSetpoint()); +// SmartDashboard.putNumber("Left VPID Error", Robot.dt.getLeftVPIDerror()); +// SmartDashboard.putNumber("Right VPID Error", Robot.dt.getRightVPIDerror()); +// SmartDashboard.putNumber("Left Enc Rate", Robot.dt.getLeftEncRate()); +// SmartDashboard.putNumber("Right Enc Rate", Robot.dt.getRightEncRate()); + + dt.dtLeft.set(-oi.leftJoy.getY()); + dt.dtRight.set(-oi.rightJoy.getY()); } } diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/RobotMap.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/RobotMap.java index 943f1c5..3f2ded8 100644 --- a/Robot2018/src/org/usfirst/frc/team199/Robot2018/RobotMap.java +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/RobotMap.java @@ -68,11 +68,20 @@ public class RobotMap { * the TalonSRX to configure */ private void configSRX(WPI_TalonSRX mc) { + // stuff cole said to put int kTimeout = (int) Robot.getConst("kTimeoutMs", 10); mc.configNominalOutputForward(0, kTimeout); mc.configNominalOutputReverse(0, kTimeout); mc.configPeakOutputForward(1, kTimeout); mc.configPeakOutputReverse(-1, kTimeout); + + // current limiting stuff cole said to put + mc.configPeakCurrentLimit(0, 0); + mc.configPeakCurrentDuration(0, 0); + // 40 amps is the amp limit of a CIM. also, the PDP has 40 amp circuit breakers, + // so if we went above 40, the motors would stop completely + mc.configContinuousCurrentLimit(40, 0); + mc.enableCurrentLimit(true); } /** @@ -83,6 +92,7 @@ private void configSRX(WPI_TalonSRX mc) { * the VictorSPX to configure */ private void configSPX(WPI_VictorSPX mc) { + // stuff cole said to put int kTimeout = (int) Robot.getConst("kTimeoutMs", 10); mc.configNominalOutputForward(0, kTimeout); mc.configNominalOutputReverse(0, kTimeout); @@ -92,12 +102,12 @@ private void configSPX(WPI_VictorSPX mc) { public RobotMap() { -// intakeMotor = new WPI_TalonSRX(getPort("IntakeTalonSRX", 4)); -// configSRX(intakeMotor); -// liftMotor = new WPI_TalonSRX(getPort("LiftTalonSRX", 5)); -// configSRX(liftMotor); -// climberMotor = new WPI_TalonSRX(getPort("ClimberTalonSRX", 6)); -// configSRX(climberMotor); + // intakeMotor = new WPI_TalonSRX(getPort("IntakeTalonSRX", 4)); + // configSRX(intakeMotor); + // liftMotor = new WPI_TalonSRX(getPort("LiftTalonSRX", 5)); + // configSRX(liftMotor); + // climberMotor = new WPI_TalonSRX(getPort("ClimberTalonSRX", 6)); + // configSRX(climberMotor); leftEncPort1 = new DigitalInput(getPort("1LeftEnc", 1)); leftEncPort2 = new DigitalInput(getPort("2LeftEnc", 0)); @@ -107,12 +117,14 @@ public RobotMap() { leftEncRate.setPIDSourceType(PIDSourceType.kRate); leftEncDist.setDistancePerPulse(Robot.getConst("Distance Per Pulse Left", 0.184)); leftEncRate.setDistancePerPulse(Robot.getConst("Distance Per Pulse Left", 0.184)); - - dtLeftMaster = new WPI_TalonSRX(getPort("LeftTalonSRXMaster", 2)); + + dtLeftMaster = new WPI_TalonSRX(getPort("LeftTalonSRXMaster", 1)); configSRX(dtLeftMaster); - dtLeftSlave = new WPI_VictorSPX(getPort("LeftVictorSPXSlave", 0)); + dtLeftSlave = new WPI_VictorSPX(getPort("LeftVictorSPXSlave", 2)); configSPX(dtLeftSlave); dtLeft = new SpeedControllerGroup(dtLeftMaster, dtLeftSlave); + //inverted bc gear boxes invert from input to output + dtLeft.setInverted(true); leftVelocityController = new VelocityPIDController(Robot.getConst("VelocityLeftkP", 0), Robot.getConst("VelocityLeftkI", 0), Robot.getConst("VelocityLeftkD", 0), @@ -122,6 +134,7 @@ public RobotMap() { leftVelocityController.setOutputRange(-1.0, 1.0); leftVelocityController.setContinuous(false); leftVelocityController.setAbsoluteTolerance(Robot.getConst("VelocityToleranceLeft", 2)); + SmartDashboard.putData(leftVelocityController); rightEncPort1 = new DigitalInput(getPort("1RightEnc", 3)); rightEncPort2 = new DigitalInput(getPort("2RightEnc", 2)); @@ -131,13 +144,15 @@ public RobotMap() { rightEncRate.setPIDSourceType(PIDSourceType.kRate); rightEncDist.setDistancePerPulse(Robot.getConst("Distance Per Pulse Right", 0.184)); rightEncRate.setDistancePerPulse(Robot.getConst("Distance Per Pulse Right", 0.184)); - - dtRightMaster = new WPI_TalonSRX(getPort("RightTalonSRXMaster", 1)); + + dtRightMaster = new WPI_TalonSRX(getPort("RightTalonSRXMaster", 4)); configSRX(dtRightMaster); dtRightSlave = new WPI_VictorSPX(getPort("RightVictorSPXSlave", 3)); configSPX(dtRightSlave); dtRight = new SpeedControllerGroup(dtRightMaster, dtRightSlave); - + //inverted bc gear boxes invert from input to output + dtRight.setInverted(true); + rightVelocityController = new VelocityPIDController(Robot.getConst("VelocityRightkP", 0), Robot.getConst("VelocityRightkI", 0), Robot.getConst("VelocityRightkD", 0), 1 / Robot.getConst("Max Low Speed", 84), rightEncRate, dtRight); @@ -147,13 +162,14 @@ public RobotMap() { rightVelocityController.setContinuous(false); rightVelocityController.setAbsoluteTolerance(Robot.getConst("VelocityToleranceRight", 2)); -// robotDrive = new DifferentialDrive(leftVelocityController, rightVelocityController); -// robotDrive.setMaxOutput(Robot.getConst("Max High Speed", 204)); + // robotDrive = new DifferentialDrive(leftVelocityController, + // rightVelocityController); + // robotDrive.setMaxOutput(Robot.getConst("Max High Speed", 204)); robotDrive = new DifferentialDrive(dtLeft, dtRight); - + distEncAvg = new PIDSourceAverage(leftEncDist, rightEncDist); fancyGyro = new AHRS(SerialPort.Port.kMXP); - // dtGear = new DoubleSolenoid(getPort("1dtGearSolenoid", 0), getPort("2dtGearSolenoid", 1)); + dtGear = new DoubleSolenoid(getPort("1dtGearSolenoid", 0), getPort("2dtGearSolenoid", 1)); } /** diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/ShiftHighGear.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/ShiftHighGear.java index c47c1f9..89b81b6 100644 --- a/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/ShiftHighGear.java +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/ShiftHighGear.java @@ -19,7 +19,7 @@ public ShiftHighGear() { // Called just before this Command runs the first time @Override protected void initialize() { -// Robot.dt.shiftGears(true); + Robot.dt.shiftGears(true); SmartDashboard.putBoolean("High Gear", true); Robot.dt.resetVelocityPIDkFConsts(); } diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/ShiftLowGear.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/ShiftLowGear.java index 8c7111e..4f403da 100644 --- a/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/ShiftLowGear.java +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/ShiftLowGear.java @@ -19,7 +19,7 @@ public ShiftLowGear() { // Called just before this Command runs the first time @Override protected void initialize() { -// Robot.dt.shiftGears(false); + Robot.dt.shiftGears(false); SmartDashboard.putBoolean("High Gear", false); Robot.dt.resetVelocityPIDkFConsts(); } diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/subsystems/Drivetrain.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/subsystems/Drivetrain.java index 20923b5..26b3acd 100644 --- a/Robot2018/src/org/usfirst/frc/team199/Robot2018/subsystems/Drivetrain.java +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/subsystems/Drivetrain.java @@ -264,15 +264,15 @@ public double getRightEncDist() { * Activates the solenoid to push the drivetrain into high or low gear. * * @param highGear - * If the solenoid is to be pushed into high gear (true, kForward) or - * low gear (false, kReverse) + * If the solenoid is to be pushed into high gear (true, kReverse) or + * low gear (false, kForward) */ @Override public void shiftGears(boolean highGear) { if (highGear ^ Robot.getBool("Drivetrain Gear Shift Low", false)) { - dtGear.set(DoubleSolenoid.Value.kForward); - } else { dtGear.set(DoubleSolenoid.Value.kReverse); + } else { + dtGear.set(DoubleSolenoid.Value.kForward); } } From be7362f5415fe01f842d6770ac90a227affaabbb Mon Sep 17 00:00:00 2001 From: DriverStationComputer Date: Fri, 9 Feb 2018 20:56:50 -0800 Subject: [PATCH 21/43] Put PID controllers and commands to SmartDashboard and some other stuff by gabe --- .../usfirst/frc/team199/Robot2018/Robot.java | 64 ++++++++++--------- .../frc/team199/Robot2018/RobotMap.java | 41 ++++++++---- .../team199/Robot2018/commands/PIDMove.java | 2 + .../Robot2018/commands/TeleopDrive.java | 2 +- .../Robot2018/subsystems/Drivetrain.java | 29 ++++++--- .../subsystems/DrivetrainInterface.java | 5 ++ 6 files changed, 90 insertions(+), 53 deletions(-) diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/Robot.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/Robot.java index 85055a3..bd9d73e 100644 --- a/Robot2018/src/org/usfirst/frc/team199/Robot2018/Robot.java +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/Robot.java @@ -41,13 +41,13 @@ public class Robot extends TimedRobot { public static Listener listen; public static OI oi; - + public static Map> autoScripts; Command autonomousCommand; SendableChooser posChooser = new SendableChooser(); Map> stratChoosers = new HashMap>(); - String[] fmsPossibilities = {"LL", "LR", "RL", "RR"}; + String[] fmsPossibilities = { "LL", "LR", "RL", "RR" }; public static double getConst(String key, double def) { if (!SmartDashboard.containsKey("Const/" + key)) { @@ -76,13 +76,13 @@ public void robotInit() { lift = new Lift(); dt = new Drivetrain(); oi = new OI(); - + // auto position chooser for (Position p : Position.values()) { posChooser.addObject(p.getSDName(), p); } SmartDashboard.putData("Starting Position", posChooser); - + // auto strategy choosers for (String input : fmsPossibilities) { SendableChooser chooser = new SendableChooser(); @@ -92,10 +92,10 @@ public void robotInit() { SmartDashboard.putData(input, chooser); stratChoosers.put(input, chooser); } - + // auto delay chooser SmartDashboard.putNumber("Auto Delay", 0); - + // parse scripts from Preferences, which maintains values throughout reboots autoScripts = AutoUtils.parseScriptFile(Preferences.getInstance().getString("autoscripts", "")); @@ -118,23 +118,23 @@ public void disabledPeriodic() { } /** - * This function is called once during the start of autonomous in order to - * grab values from SmartDashboard and the FMS and call the Autonomous - * command with those values. + * This function is called once during the start of autonomous in order to grab + * values from SmartDashboard and the FMS and call the Autonomous command with + * those values. */ @Override public void autonomousInit() { String fmsInput = DriverStation.getInstance().getGameSpecificMessage(); Position startPos = posChooser.getSelected(); double autoDelay = SmartDashboard.getNumber("Auto Delay", 0); - + Map strategies = new HashMap(); for (Map.Entry> entry : stratChoosers.entrySet()) { - String key = entry.getKey(); - SendableChooser chooser = entry.getValue(); - strategies.put(key, chooser.getSelected()); + String key = entry.getKey(); + SendableChooser chooser = entry.getValue(); + strategies.put(key, chooser.getSelected()); } - + Autonomous auto = new Autonomous(startPos, strategies, autoDelay, fmsInput, false); auto.start(); } @@ -166,25 +166,31 @@ public void teleopPeriodic() { } boolean firstTime = true; + /** * This function is called periodically during test mode */ @Override public void testPeriodic() { -// if(firstTime) { -// Robot.dt.enableVelocityPIDs(); -// firstTime = false; -//// } -// Robot.dt.setVPIDs(Robot.getConst("VPID Test Set", 0.5)); -// SmartDashboard.putNumber("Drivetrain/Left VPID Targ", Robot.dt.getLeftVPIDSetpoint()); -// SmartDashboard.putNumber("Drivetrain/Right VPID Targ", Robot.dt.getRightVPIDSetpoint()); -// SmartDashboard.putNumber("Left VPID Error", Robot.dt.getLeftVPIDerror()); -// SmartDashboard.putNumber("Right VPID Error", Robot.dt.getRightVPIDerror()); -// SmartDashboard.putNumber("Left Enc Rate", Robot.dt.getLeftEncRate()); -// SmartDashboard.putNumber("Right Enc Rate", Robot.dt.getRightEncRate()); - - dt.dtLeft.set(-oi.leftJoy.getY()); - dt.dtRight.set(-oi.rightJoy.getY()); - + // if(firstTime) { + // Robot.dt.enableVelocityPIDs(); + // firstTime = false; + //// } + // Robot.dt.setVPIDs(Robot.getConst("VPID Test Set", 0.5)); + // SmartDashboard.putNumber("Drivetrain/Left VPID Targ", + // Robot.dt.getLeftVPIDSetpoint()); + // SmartDashboard.putNumber("Drivetrain/Right VPID Targ", + // Robot.dt.getRightVPIDSetpoint()); + // SmartDashboard.putNumber("Left VPID Error", Robot.dt.getLeftVPIDerror()); + // SmartDashboard.putNumber("Right VPID Error", Robot.dt.getRightVPIDerror()); + // SmartDashboard.putNumber("Left Enc Rate", Robot.dt.getLeftEncRate()); + // SmartDashboard.putNumber("Right Enc Rate", Robot.dt.getRightEncRate()); + + dt.dtLeft.set(0.1); + // dt.dtRight.set(-oi.rightJoy.getY()); + // dt.dtLeft.set(-oi.leftJoy.getY()); + // dt.dtRight.set(-oi.rightJoy.getY()); + + dt.putVelocityControllersToDashboard(); } } diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/RobotMap.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/RobotMap.java index 3f2ded8..40a22fb 100644 --- a/Robot2018/src/org/usfirst/frc/team199/Robot2018/RobotMap.java +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/RobotMap.java @@ -60,6 +60,8 @@ public class RobotMap { public static AHRS fancyGyro; public static DoubleSolenoid dtGear; + private final double DIST_PER_PULSE_RATIO = (5.0 * Math.PI) * (17 / 25) / (3 * 256); + /** * This function takes in a TalonSRX motorController and sets nominal and peak * outputs to the default @@ -100,6 +102,18 @@ private void configSPX(WPI_VictorSPX mc) { mc.configPeakOutputReverse(-1, kTimeout); } + /** + * Uses SmartDashboard and math to calculate a *great* default kD + */ + public double calcDefkD() { + double timeConstant = Robot.getConst("Omega Max", 5330) * Robot.getConst("Mass of Robot", 54.4311) + * Robot.getConst("Radius of Drivetrain Wheel", 0.0635) + * Robot.getConst("Radius of Drivetrain Wheel", 0.0635) / Robot.getConst("Stall Torque", 2.41); + double cycleTime = Robot.getConst("Code cycle time", 0.1); + double denominator = 1 - Math.pow(Math.E, -1 * cycleTime / timeConstant); + return 1 / denominator; + } + public RobotMap() { // intakeMotor = new WPI_TalonSRX(getPort("IntakeTalonSRX", 4)); @@ -109,21 +123,21 @@ public RobotMap() { // climberMotor = new WPI_TalonSRX(getPort("ClimberTalonSRX", 6)); // configSRX(climberMotor); - leftEncPort1 = new DigitalInput(getPort("1LeftEnc", 1)); - leftEncPort2 = new DigitalInput(getPort("2LeftEnc", 0)); + leftEncPort1 = new DigitalInput(getPort("1LeftEnc", 2)); + leftEncPort2 = new DigitalInput(getPort("2LeftEnc", 3)); leftEncDist = new Encoder(leftEncPort1, leftEncPort2); leftEncDist.setPIDSourceType(PIDSourceType.kDisplacement); leftEncRate = new Encoder(leftEncPort1, leftEncPort2); leftEncRate.setPIDSourceType(PIDSourceType.kRate); - leftEncDist.setDistancePerPulse(Robot.getConst("Distance Per Pulse Left", 0.184)); - leftEncRate.setDistancePerPulse(Robot.getConst("Distance Per Pulse Left", 0.184)); + leftEncDist.setDistancePerPulse(Robot.getConst("DPP", DIST_PER_PULSE_RATIO)); + leftEncRate.setDistancePerPulse(Robot.getConst("DPP", DIST_PER_PULSE_RATIO)); dtLeftMaster = new WPI_TalonSRX(getPort("LeftTalonSRXMaster", 1)); configSRX(dtLeftMaster); dtLeftSlave = new WPI_VictorSPX(getPort("LeftVictorSPXSlave", 2)); configSPX(dtLeftSlave); dtLeft = new SpeedControllerGroup(dtLeftMaster, dtLeftSlave); - //inverted bc gear boxes invert from input to output + // inverted bc gear boxes invert from input to output dtLeft.setInverted(true); leftVelocityController = new VelocityPIDController(Robot.getConst("VelocityLeftkP", 0), @@ -136,21 +150,21 @@ public RobotMap() { leftVelocityController.setAbsoluteTolerance(Robot.getConst("VelocityToleranceLeft", 2)); SmartDashboard.putData(leftVelocityController); - rightEncPort1 = new DigitalInput(getPort("1RightEnc", 3)); - rightEncPort2 = new DigitalInput(getPort("2RightEnc", 2)); + rightEncPort1 = new DigitalInput(getPort("1RightEnc", 1)); + rightEncPort2 = new DigitalInput(getPort("2RightEnc", 0)); rightEncDist = new Encoder(rightEncPort1, rightEncPort2); rightEncDist.setPIDSourceType(PIDSourceType.kDisplacement); rightEncRate = new Encoder(rightEncPort1, rightEncPort2); rightEncRate.setPIDSourceType(PIDSourceType.kRate); - rightEncDist.setDistancePerPulse(Robot.getConst("Distance Per Pulse Right", 0.184)); - rightEncRate.setDistancePerPulse(Robot.getConst("Distance Per Pulse Right", 0.184)); + rightEncDist.setDistancePerPulse(Robot.getConst("DPP", DIST_PER_PULSE_RATIO)); + rightEncRate.setDistancePerPulse(Robot.getConst("DPP", DIST_PER_PULSE_RATIO)); dtRightMaster = new WPI_TalonSRX(getPort("RightTalonSRXMaster", 4)); configSRX(dtRightMaster); dtRightSlave = new WPI_VictorSPX(getPort("RightVictorSPXSlave", 3)); configSPX(dtRightSlave); dtRight = new SpeedControllerGroup(dtRightMaster, dtRightSlave); - //inverted bc gear boxes invert from input to output + // inverted bc gear boxes invert from input to output dtRight.setInverted(true); rightVelocityController = new VelocityPIDController(Robot.getConst("VelocityRightkP", 0), @@ -162,10 +176,9 @@ public RobotMap() { rightVelocityController.setContinuous(false); rightVelocityController.setAbsoluteTolerance(Robot.getConst("VelocityToleranceRight", 2)); - // robotDrive = new DifferentialDrive(leftVelocityController, - // rightVelocityController); - // robotDrive.setMaxOutput(Robot.getConst("Max High Speed", 204)); - robotDrive = new DifferentialDrive(dtLeft, dtRight); + robotDrive = new DifferentialDrive(leftVelocityController, rightVelocityController); + robotDrive.setMaxOutput(Robot.getConst("Max High Speed", 204)); + // robotDrive = new DifferentialDrive(dtLeft, dtRight); distEncAvg = new PIDSourceAverage(leftEncDist, rightEncDist); fancyGyro = new AHRS(SerialPort.Port.kMXP); diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/PIDMove.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/PIDMove.java index 50be3b3..9d7dd03 100644 --- a/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/PIDMove.java +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/PIDMove.java @@ -7,6 +7,7 @@ import edu.wpi.first.wpilibj.PIDController; import edu.wpi.first.wpilibj.PIDOutput; import edu.wpi.first.wpilibj.command.Command; +import edu.wpi.first.wpilibj.smartdashboard.SmartDashboard; /** * @@ -40,6 +41,7 @@ public void initialize() { moveController.setAbsoluteTolerance(Robot.getConst("MoveTolerance", 2)); moveController.setSetpoint(target); moveController.enable(); + SmartDashboard.putData(moveController); } // Called repeatedly when this Command is scheduled to run diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/TeleopDrive.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/TeleopDrive.java index 0e8a94a..64b6b14 100644 --- a/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/TeleopDrive.java +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/TeleopDrive.java @@ -23,7 +23,7 @@ public TeleopDrive() { // Called just before this Command runs the first time @Override protected void initialize() { -// Robot.dt.enableVelocityPIDs(); + Robot.dt.enableVelocityPIDs(); } // Called repeatedly when this Command is scheduled to run diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/subsystems/Drivetrain.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/subsystems/Drivetrain.java index 26b3acd..2ef9447 100644 --- a/Robot2018/src/org/usfirst/frc/team199/Robot2018/subsystems/Drivetrain.java +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/subsystems/Drivetrain.java @@ -44,23 +44,23 @@ public class Drivetrain extends Subsystem implements DrivetrainInterface { public void initDefaultCommand() { setDefaultCommand(new TeleopDrive()); } - + public void setLeft(double spd) { dtLeft.set(spd); } - + public void setRight(double spd) { dtRight.set(spd); } - + /** * Use for testing only (i.e. when not going through robotDrive) - * */ + */ public void setVPIDs(double realSpd) { leftVelocityController.set(realSpd); rightVelocityController.set(-realSpd); } - + public double getLeftVPIDerror() { return leftVelocityController.getError(); } @@ -68,7 +68,7 @@ public double getLeftVPIDerror() { public double getRightVPIDerror() { return rightVelocityController.getError(); } - + public double getLeftVPIDSetpoint() { return leftVelocityController.get(); } @@ -76,11 +76,11 @@ public double getLeftVPIDSetpoint() { public double getRightVPIDSetpoint() { return rightVelocityController.get(); } - + public double getLeftEncRate() { return leftEncRate.getRate(); } - + public double getRightEncRate() { return rightEncRate.getRate(); } @@ -226,6 +226,7 @@ public void resetDistEncs() { @Override public void setDistancePerPulseLeft(double ratio) { leftEncDist.setDistancePerPulse(ratio); + leftEncRate.setDistancePerPulse(ratio); } /** @@ -238,6 +239,7 @@ public void setDistancePerPulseLeft(double ratio) { @Override public void setDistancePerPulseRight(double ratio) { rightEncDist.setDistancePerPulse(ratio); + rightEncRate.setDistancePerPulse(ratio); } /** @@ -301,7 +303,7 @@ public double resetVelocityPIDkFConsts() { SmartDashboard.putNumber("VPID kF", newKF); return newKF; } - + public double resetVPIDInputRanges() { double currentMaxSpd = getCurrentMaxSpeed(); leftVelocityController.setInputRange(-currentMaxSpd, currentMaxSpd); @@ -322,4 +324,13 @@ public double getCurrentMaxSpeed() { return Robot.getConst("Max Low Speed", 84); } } + + /** + * Put left and right velocity controllers (PID) on SmartDashboard. + */ + @Override + public void putVelocityControllersToDashboard() { + SmartDashboard.putData(leftVelocityController); + SmartDashboard.putData(rightVelocityController); + } } diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/subsystems/DrivetrainInterface.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/subsystems/DrivetrainInterface.java index 8d3d842..05299fc 100644 --- a/Robot2018/src/org/usfirst/frc/team199/Robot2018/subsystems/DrivetrainInterface.java +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/subsystems/DrivetrainInterface.java @@ -142,4 +142,9 @@ public interface DrivetrainInterface { * @return the current max speed of the DT in inches/second */ public double getCurrentMaxSpeed(); + + /** + * Put left and right velocity controllers (PID) on SmartDashboard. + */ + public void putVelocityControllersToDashboard(); } From 6d823a35d77c19476e2e63b6d326d6e5bfa8cae8 Mon Sep 17 00:00:00 2001 From: caengineers Date: Fri, 9 Feb 2018 20:58:36 -0800 Subject: [PATCH 22/43] added standard err message to smartdashboard fails --- .../org/usfirst/frc/team199/Robot2018/OI.java | 9 ++++--- .../usfirst/frc/team199/Robot2018/Robot.java | 26 ++++++++++++------- .../frc/team199/Robot2018/RobotMap.java | 5 +++- 3 files changed, 26 insertions(+), 14 deletions(-) diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/OI.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/OI.java index 452606c..d114bad 100644 --- a/Robot2018/src/org/usfirst/frc/team199/Robot2018/OI.java +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/OI.java @@ -35,11 +35,14 @@ public class OI { private JoystickButton updatePIDConstantsButton; private JoystickButton updateEncoderDPPButton; public Joystick manipulator; - + public int getButton(String key, int def) { if (!SmartDashboard.containsKey("Button/" + key)) { - SmartDashboard.putNumber("Button/" + key, def); - } + if (!SmartDashboard.putNumber("Button/" + key, def)) { + System.err.println("SmartDashboard Key" + "Button/" + key + "already taken by a different type"); + return def; + } + } return (int) SmartDashboard.getNumber("Button/" + key, def); } diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/Robot.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/Robot.java index e67c8c1..b360c24 100644 --- a/Robot2018/src/org/usfirst/frc/team199/Robot2018/Robot.java +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/Robot.java @@ -57,21 +57,27 @@ public class Robot extends TimedRobot { public static double getConst(String key, double def) { if (!SmartDashboard.containsKey("Const/" + key)) { - SmartDashboard.putNumber("Const/" + key, def); + if (!SmartDashboard.putNumber("Const/" + key, def)) { + System.err.println("SmartDashboard Key" + "Const/" + key + "already taken by a different type"); + return def; + } } return SmartDashboard.getNumber("Const/" + key, def); } public static boolean getBool(String key, boolean def) { if (!SmartDashboard.containsKey("Bool/" + key)) { - SmartDashboard.putBoolean("Bool/" + key, def); + if (!SmartDashboard.putBoolean("Bool/" + key, def)) { + System.err.println("SmartDashboard Key" + "Bool/" + key + "already taken by a different type"); + return def; + } } return SmartDashboard.getBoolean("Bool/" + key, def); } /** - * This function is run when the robot is first started up and should be - * used for any initialization code. + * This function is run when the robot is first started up and should be used + * for any initialization code. */ @Override public void robotInit() { @@ -110,9 +116,9 @@ public void robotInit() { } /** - * This function is called once each time the robot enters Disabled mode. - * You can use it to reset any subsystem information you want to clear when - * the robot is disabled. + * This function is called once each time the robot enters Disabled mode. You + * can use it to reset any subsystem information you want to clear when the + * robot is disabled. */ @Override public void disabledInit() { @@ -125,9 +131,9 @@ public void disabledPeriodic() { } /** - * This function is called once during the start of autonomous in order to - * grab values from SmartDashboard and the FMS and call the Autonomous - * command with those values. + * This function is called once during the start of autonomous in order to grab + * values from SmartDashboard and the FMS and call the Autonomous command with + * those values. */ @Override public void autonomousInit() { diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/RobotMap.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/RobotMap.java index 383f894..d9c8895 100644 --- a/Robot2018/src/org/usfirst/frc/team199/Robot2018/RobotMap.java +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/RobotMap.java @@ -173,7 +173,10 @@ public RobotMap() { */ public int getPort(String key, int def) { if (!SmartDashboard.containsKey("Port/" + key)) { - SmartDashboard.putNumber("Port/" + key, def); + if (!SmartDashboard.putNumber("Port/" + key, def)) { + System.err.println("SmartDashboard Key" + "Port/" + key + "already taken by a different type"); + return def; + } } return (int) SmartDashboard.getNumber("Port/" + key, def); } From 7c9f56cceecd14d4bd5aa5e74c73d5e13262ca13 Mon Sep 17 00:00:00 2001 From: doawelul Date: Sat, 10 Feb 2018 10:51:43 -0800 Subject: [PATCH 23/43] Factored in gear ratio in kD method --- .../frc/team199/Robot2018/RobotMap.java | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/RobotMap.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/RobotMap.java index 8b7e43c..a83a568 100644 --- a/Robot2018/src/org/usfirst/frc/team199/Robot2018/RobotMap.java +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/RobotMap.java @@ -172,10 +172,25 @@ public int getPort(String key, int def) { * Uses SmartDashboard and math to calculate a *great* default kD */ public double calcDefkD() { - double timeConstant = Robot.getConst("Omega Max", 5330) * Robot.getConst("Mass of Robot", 54.4311) + /* + * timeConstant is proportional to max speed of the shaft (which is the max + * speed of the cim divided by the gear reduction), half the mass (because the + * half of the drivetrain only has to support half of the robot), and radius of + * the drivetrain wheels squared. It's inversely proportional to the stall + * torque of the shaft, which is found by multiplying the stall torque of the + * motor with the gear reduction. + */ + double gearReduction = Robot.getBool("High Gear", false) ? Robot.getConst("High Gear Gear Reduction", 5.392) + : Robot.getConst("Low Gear Gear Reduction", 12.255); + double timeConstant = Robot.getConst("Omega Max", 5330) / gearReduction + * Robot.getConst("Mass of Robot", 54.4311) * Robot.getConst("Radius of Drivetrain Wheel", 0.0635) * Robot.getConst("Radius of Drivetrain Wheel", 0.0635) - * Robot.getConst("Radius of Drivetrain Wheel", 0.0635) / Robot.getConst("Stall Torque", 2.41); + / (Robot.getConst("Stall Torque", 2.41) * gearReduction); double cycleTime = Robot.getConst("Code cycle time", 0.1); + /* + * The denominator of kD is 1-(e ^ -cycleTime / timeConstant). The numerator is + * one. + */ double denominator = 1 - Math.pow(Math.E, -1 * cycleTime / timeConstant); return 1 / denominator; } From 7e4e9bf6ff342243fef2140e965052e7267ff08d Mon Sep 17 00:00:00 2001 From: doawelul Date: Sat, 10 Feb 2018 11:01:33 -0800 Subject: [PATCH 24/43] Added division by 2 because each side of the drivetrain only supports half of the drivetrain. Changed SmartDashboard to lbs, and converts to kg inside the code --- .../src/org/usfirst/frc/team199/Robot2018/RobotMap.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/RobotMap.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/RobotMap.java index d2c38a4..230fda1 100644 --- a/Robot2018/src/org/usfirst/frc/team199/Robot2018/RobotMap.java +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/RobotMap.java @@ -196,7 +196,8 @@ public double calcDefkD() { double gearReduction = Robot.getBool("High Gear", false) ? Robot.getConst("High Gear Gear Reduction", 5.392) : Robot.getConst("Low Gear Gear Reduction", 12.255); double timeConstant = Robot.getConst("Omega Max", 5330) / gearReduction - * Robot.getConst("Mass of Robot", 54.4311) * Robot.getConst("Radius of Drivetrain Wheel", 0.0635) + * convertLbsTokG(Robot.getConst("Mass of Robot", 150)) / 2 + * Robot.getConst("Radius of Drivetrain Wheel", 0.0635) * Robot.getConst("Radius of Drivetrain Wheel", 0.0635) / (Robot.getConst("Stall Torque", 2.41) * gearReduction); double cycleTime = Robot.getConst("Code cycle time", 0.1); @@ -207,4 +208,9 @@ public double calcDefkD() { double denominator = 1 - Math.pow(Math.E, -1 * cycleTime / timeConstant); return 1 / denominator; } + + private double convertLbsTokG(double lbs) { + // number from google ;) + return lbs * 0.45359237; + } } From 5ea98aa6e52c07cecbda7b6a2d18e10fb322bb9b Mon Sep 17 00:00:00 2001 From: doawelul Date: Sat, 10 Feb 2018 11:31:29 -0800 Subject: [PATCH 25/43] Added lift motors to robotMap and accounted for two CIMs in kD calculations --- .../frc/team199/Robot2018/RobotMap.java | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/RobotMap.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/RobotMap.java index 230fda1..e1c7634 100644 --- a/Robot2018/src/org/usfirst/frc/team199/Robot2018/RobotMap.java +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/RobotMap.java @@ -21,6 +21,7 @@ import edu.wpi.first.wpilibj.PIDSourceType; import edu.wpi.first.wpilibj.SerialPort; import edu.wpi.first.wpilibj.SpeedControllerGroup; +import edu.wpi.first.wpilibj.VictorSP; import edu.wpi.first.wpilibj.drive.DifferentialDrive; import edu.wpi.first.wpilibj.smartdashboard.SmartDashboard; @@ -32,10 +33,12 @@ */ public class RobotMap { - public static WPI_TalonSRX intakeMotor; public static WPI_TalonSRX liftMotor; public static WPI_TalonSRX climberMotor; + public static VictorSP leftIntakeMotor; + public static VictorSP rightIntakeMotor; + public static DigitalSource leftEncPort1; public static DigitalSource leftEncPort2; public static Encoder leftEncDist; @@ -102,13 +105,14 @@ private void configSPX(WPI_VictorSPX mc) { public RobotMap() { - intakeMotor = new WPI_TalonSRX(getPort("IntakeTalonSRX", 4)); - configSRX(intakeMotor); liftMotor = new WPI_TalonSRX(getPort("LiftTalonSRX", 5)); configSRX(liftMotor); climberMotor = new WPI_TalonSRX(getPort("ClimberTalonSRX", 6)); configSRX(climberMotor); + leftIntakeMotor = new VictorSP(getPort("IntakeLeftVictorSP", 0)); + rightIntakeMotor = new VictorSP(getPort("IntakeRightVictorSP", 1)); + leftEncPort1 = new DigitalInput(getPort("1LeftEnc", 0)); leftEncPort2 = new DigitalInput(getPort("2LeftEnc", 1)); leftEncDist = new Encoder(leftEncPort1, leftEncPort2); @@ -191,15 +195,14 @@ public double calcDefkD() { * half of the drivetrain only has to support half of the robot), and radius of * the drivetrain wheels squared. It's inversely proportional to the stall * torque of the shaft, which is found by multiplying the stall torque of the - * motor with the gear reduction. + * motor with the gear reduction by the amount of motors. */ double gearReduction = Robot.getBool("High Gear", false) ? Robot.getConst("High Gear Gear Reduction", 5.392) : Robot.getConst("Low Gear Gear Reduction", 12.255); + double radius = Robot.getConst("Radius of Drivetrain Wheel", 0.0635); double timeConstant = Robot.getConst("Omega Max", 5330) / gearReduction - * convertLbsTokG(Robot.getConst("Mass of Robot", 150)) / 2 - * Robot.getConst("Radius of Drivetrain Wheel", 0.0635) - * Robot.getConst("Radius of Drivetrain Wheel", 0.0635) - / (Robot.getConst("Stall Torque", 2.41) * gearReduction); + * convertLbsTokG(Robot.getConst("Mass of Robot", 150)) / 2 * radius * radius + / (Robot.getConst("Stall Torque", 2.41) * gearReduction * 2); double cycleTime = Robot.getConst("Code cycle time", 0.1); /* * The denominator of kD is 1-(e ^ -cycleTime / timeConstant). The numerator is From dd737a3abe09b9a4cbcfe84ca9fd01540dbc3989 Mon Sep 17 00:00:00 2001 From: doawelul Date: Sat, 10 Feb 2018 12:37:06 -0800 Subject: [PATCH 26/43] added lift and changed dt solenoid keys --- .../org/usfirst/frc/team199/Robot2018/OI.java | 27 ++++- .../frc/team199/Robot2018/RobotMap.java | 19 +++- .../Robot2018/commands/CloseIntake.java | 25 +++++ .../Robot2018/commands/IntakeCube.java | 11 +- .../Robot2018/commands/LowerIntake.java | 25 +++++ .../Robot2018/commands/OpenIntake.java | 25 +++++ .../Robot2018/commands/RaiseIntake.java | 25 +++++ .../Robot2018/subsystems/IntakeEject.java | 104 ++++++++++++------ .../subsystems/IntakeEjectInterface.java | 53 ++++++--- 9 files changed, 261 insertions(+), 53 deletions(-) create mode 100644 Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/CloseIntake.java create mode 100644 Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/LowerIntake.java create mode 100644 Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/OpenIntake.java create mode 100644 Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/RaiseIntake.java diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/OI.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/OI.java index d114bad..4c62829 100644 --- a/Robot2018/src/org/usfirst/frc/team199/Robot2018/OI.java +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/OI.java @@ -7,8 +7,13 @@ package org.usfirst.frc.team199.Robot2018; +import org.usfirst.frc.team199.Robot2018.commands.CloseIntake; +import org.usfirst.frc.team199.Robot2018.commands.IntakeCube; +import org.usfirst.frc.team199.Robot2018.commands.LowerIntake; +import org.usfirst.frc.team199.Robot2018.commands.OpenIntake; import org.usfirst.frc.team199.Robot2018.commands.PIDMove; import org.usfirst.frc.team199.Robot2018.commands.PIDTurn; +import org.usfirst.frc.team199.Robot2018.commands.RaiseIntake; import org.usfirst.frc.team199.Robot2018.commands.SetDistancePerPulse; import org.usfirst.frc.team199.Robot2018.commands.ShiftDriveType; import org.usfirst.frc.team199.Robot2018.commands.ShiftHighGear; @@ -35,14 +40,20 @@ public class OI { private JoystickButton updatePIDConstantsButton; private JoystickButton updateEncoderDPPButton; public Joystick manipulator; - + private JoystickButton closeIntake; + private JoystickButton openIntake; + private JoystickButton raiseIntake; + private JoystickButton lowerIntake; + private JoystickButton intake; + private JoystickButton outake; + public int getButton(String key, int def) { if (!SmartDashboard.containsKey("Button/" + key)) { if (!SmartDashboard.putNumber("Button/" + key, def)) { System.err.println("SmartDashboard Key" + "Button/" + key + "already taken by a different type"); return def; } - } + } return (int) SmartDashboard.getNumber("Button/" + key, def); } @@ -66,5 +77,17 @@ public OI() { updateEncoderDPPButton.whenPressed(new SetDistancePerPulse()); manipulator = new Joystick(2); + closeIntake = new JoystickButton(manipulator, getButton("Close Intake Button", 1)); + closeIntake.whenPressed(new CloseIntake()); + openIntake = new JoystickButton(manipulator, getButton("Open Intake Button", 2)); + openIntake.whenPressed(new OpenIntake()); + raiseIntake = new JoystickButton(manipulator, getButton("Raise Intake Button", 3)); + raiseIntake.whenPressed(new RaiseIntake()); + lowerIntake = new JoystickButton(manipulator, getButton("Lower Intake Button", 4)); + lowerIntake.whenPressed(new LowerIntake()); + intake = new JoystickButton(manipulator, getButton("Intake Button", 5)); + intake.whileHeld(new IntakeCube(true)); + outake = new JoystickButton(manipulator, getButton("Outake Button", 6)); + outake.whileHeld(new IntakeCube(false)); } } diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/RobotMap.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/RobotMap.java index e1c7634..e3e8096 100644 --- a/Robot2018/src/org/usfirst/frc/team199/Robot2018/RobotMap.java +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/RobotMap.java @@ -19,6 +19,7 @@ import edu.wpi.first.wpilibj.DoubleSolenoid; import edu.wpi.first.wpilibj.Encoder; import edu.wpi.first.wpilibj.PIDSourceType; +import edu.wpi.first.wpilibj.PowerDistributionPanel; import edu.wpi.first.wpilibj.SerialPort; import edu.wpi.first.wpilibj.SpeedControllerGroup; import edu.wpi.first.wpilibj.VictorSP; @@ -33,11 +34,17 @@ */ public class RobotMap { + public static PowerDistributionPanel pdp; + public static WPI_TalonSRX liftMotor; public static WPI_TalonSRX climberMotor; public static VictorSP leftIntakeMotor; public static VictorSP rightIntakeMotor; + public static DoubleSolenoid leftIntakeVerticalSolenoid; + public static DoubleSolenoid rightIntakeVerticalSolenoid; + public static DoubleSolenoid leftIntakeHorizontalSolenoid; + public static DoubleSolenoid rightIntakeHorizontalSolenoid; public static DigitalSource leftEncPort1; public static DigitalSource leftEncPort2; @@ -104,6 +111,7 @@ private void configSPX(WPI_VictorSPX mc) { } public RobotMap() { + pdp = new PowerDistributionPanel(); liftMotor = new WPI_TalonSRX(getPort("LiftTalonSRX", 5)); configSRX(liftMotor); @@ -112,6 +120,14 @@ public RobotMap() { leftIntakeMotor = new VictorSP(getPort("IntakeLeftVictorSP", 0)); rightIntakeMotor = new VictorSP(getPort("IntakeRightVictorSP", 1)); + leftIntakeHorizontalSolenoid = new DoubleSolenoid(getPort("IntakeLeftHorizontalSolenoidPort1", 2), + getPort("IntakeLeftHorizontalSolenoidPort2", 3)); + rightIntakeHorizontalSolenoid = new DoubleSolenoid(getPort("IntakeRightHorizontalSolenoidPort1", 4), + getPort("IntakeRightHorizontalSolenoidPort2", 5)); + leftIntakeVerticalSolenoid = new DoubleSolenoid(getPort("IntakeLeftVerticalSolenoidPort1", 6), + getPort("IntakeLeftVerticalSolenoidPort2", 7)); + rightIntakeVerticalSolenoid = new DoubleSolenoid(getPort("IntakeRightVerticalSolenoidPort1", 8), + getPort("IntakeRightVerticalSolenoidPort2", 9)); leftEncPort1 = new DigitalInput(getPort("1LeftEnc", 0)); leftEncPort2 = new DigitalInput(getPort("2LeftEnc", 1)); @@ -162,7 +178,8 @@ public RobotMap() { distEncAvg = new PIDSourceAverage(leftEncDist, rightEncDist); fancyGyro = new AHRS(SerialPort.Port.kMXP); - dtGear = new DoubleSolenoid(getPort("1dtGearSolenoid", 0), getPort("2dtGearSolenoid", 1)); + dtGear = new DoubleSolenoid(getPort("DrivetrainGearSolenoidPort1", 0), + getPort("DrivetrainGearSolenoidPort2", 1)); } /** diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/CloseIntake.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/CloseIntake.java new file mode 100644 index 0000000..01acc4f --- /dev/null +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/CloseIntake.java @@ -0,0 +1,25 @@ +package org.usfirst.frc.team199.Robot2018.commands; + +import org.usfirst.frc.team199.Robot2018.Robot; + +import edu.wpi.first.wpilibj.command.InstantCommand; +import edu.wpi.first.wpilibj.smartdashboard.SmartDashboard; + +/** + * + */ +public class CloseIntake extends InstantCommand { + + public CloseIntake() { + super(); + // Use requires() here to declare subsystem dependencies + // eg. requires(chassis); + } + + // Called once when the command executes + protected void initialize() { + Robot.intakeEject.closeIntake(); + SmartDashboard.putBoolean("Intake Open", false); + } + +} diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/IntakeCube.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/IntakeCube.java index 2e30a79..53e79cc 100644 --- a/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/IntakeCube.java +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/IntakeCube.java @@ -1,5 +1,7 @@ package org.usfirst.frc.team199.Robot2018.commands; +import org.usfirst.frc.team199.Robot2018.Robot; + import edu.wpi.first.wpilibj.command.Command; /** @@ -7,9 +9,11 @@ */ public class IntakeCube extends Command { - public IntakeCube() { + boolean in; + public IntakeCube(boolean in) { // Use requires() here to declare subsystem dependencies // eg. requires(chassis); + this.in = in; } // Called just before this Command runs the first time @@ -18,6 +22,11 @@ protected void initialize() { // Called repeatedly when this Command is scheduled to run protected void execute() { + if(in) { + Robot.intakeEject.runIntake(1); + } else { + Robot.intakeEject.runIntake(-1); + } } // Make this return true when this Command no longer needs to run execute() diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/LowerIntake.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/LowerIntake.java new file mode 100644 index 0000000..0b4a7d2 --- /dev/null +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/LowerIntake.java @@ -0,0 +1,25 @@ +package org.usfirst.frc.team199.Robot2018.commands; + +import org.usfirst.frc.team199.Robot2018.Robot; + +import edu.wpi.first.wpilibj.command.InstantCommand; +import edu.wpi.first.wpilibj.smartdashboard.SmartDashboard; + +/** + * + */ +public class LowerIntake extends InstantCommand { + + public LowerIntake() { + super(); + // Use requires() here to declare subsystem dependencies + // eg. requires(chassis); + } + + // Called once when the command executes + protected void initialize() { + Robot.intakeEject.lowerIntake(); + SmartDashboard.putBoolean("Intake Up", false); + } + +} diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/OpenIntake.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/OpenIntake.java new file mode 100644 index 0000000..a9d9d87 --- /dev/null +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/OpenIntake.java @@ -0,0 +1,25 @@ +package org.usfirst.frc.team199.Robot2018.commands; + +import org.usfirst.frc.team199.Robot2018.Robot; + +import edu.wpi.first.wpilibj.command.InstantCommand; +import edu.wpi.first.wpilibj.smartdashboard.SmartDashboard; + +/** + * + */ +public class OpenIntake extends InstantCommand { + + public OpenIntake() { + super(); + // Use requires() here to declare subsystem dependencies + // eg. requires(chassis); + } + + // Called once when the command executes + protected void initialize() { + Robot.intakeEject.openIntake(); + SmartDashboard.putBoolean("Intake Open", true); + } + +} diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/RaiseIntake.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/RaiseIntake.java new file mode 100644 index 0000000..27da950 --- /dev/null +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/RaiseIntake.java @@ -0,0 +1,25 @@ +package org.usfirst.frc.team199.Robot2018.commands; + +import org.usfirst.frc.team199.Robot2018.Robot; + +import edu.wpi.first.wpilibj.command.InstantCommand; +import edu.wpi.first.wpilibj.smartdashboard.SmartDashboard; + +/** + * + */ +public class RaiseIntake extends InstantCommand { + + public RaiseIntake() { + super(); + // Use requires() here to declare subsystem dependencies + // eg. requires(chassis); + } + + // Called once when the command executes + protected void initialize() { + Robot.intakeEject.raiseIntake(); + SmartDashboard.putBoolean("Intake Up", true); + } + +} diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/subsystems/IntakeEject.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/subsystems/IntakeEject.java index e850424..fcde6b7 100644 --- a/Robot2018/src/org/usfirst/frc/team199/Robot2018/subsystems/IntakeEject.java +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/subsystems/IntakeEject.java @@ -1,67 +1,107 @@ package org.usfirst.frc.team199.Robot2018.subsystems; +import org.usfirst.frc.team199.Robot2018.Robot; import org.usfirst.frc.team199.Robot2018.RobotMap; -import com.ctre.phoenix.motorcontrol.can.WPI_TalonSRX; - +import edu.wpi.first.wpilibj.DoubleSolenoid; +import edu.wpi.first.wpilibj.PowerDistributionPanel; +import edu.wpi.first.wpilibj.VictorSP; import edu.wpi.first.wpilibj.command.Subsystem; /** * */ public class IntakeEject extends Subsystem implements IntakeEjectInterface { - - private final WPI_TalonSRX intakeMotor = RobotMap.intakeMotor; - - - + + private final PowerDistributionPanel pdp = RobotMap.pdp; + private final VictorSP leftIntakeMotor = RobotMap.leftIntakeMotor; + private final VictorSP rightIntakeMotor = RobotMap.rightIntakeMotor; + private final DoubleSolenoid leftVerticalSolenoid = RobotMap.leftIntakeVerticalSolenoid; + private final DoubleSolenoid rightVerticalSolenoid = RobotMap.rightIntakeVerticalSolenoid; + private final DoubleSolenoid leftHorizontalSolenoid = RobotMap.leftIntakeHorizontalSolenoid; + private final DoubleSolenoid rightHorizontalSolenoid = RobotMap.rightIntakeHorizontalSolenoid; + /** * Set the default command for a subsystem here. - * */ - public void initDefaultCommand() { - // Set the default command for a subsystem here. - //setDefaultCommand(new MySpecialCommand()); - } + */ + public void initDefaultCommand() { + } - /** - * returns current motor value + /** + * returns current left motor value */ - public double getIntakeSpeed() { - return intakeMotor.get(); + public double getLeftIntakeSpeed() { + return leftIntakeMotor.get(); } - + /** - * Uses (insert sensor here) to detect - * a cube in front of the robot. + * returns current right motor value */ - public boolean seeCube() { - return false; + public double getRightIntakeSpeed() { + return rightIntakeMotor.get(); } - + /** - * Uses (insert sensor here) to detect if - * the cube is currently inside the robot + * Uses current to check if the wheels are blocked aka the cube is inside the + * robot * */ public boolean hasCube() { - return false; + return pdp.getCurrent(Robot.rmap.getPort("PDP Intake Left Channel", 4)) > Robot.getConst("Max Current", 38) + && pdp.getCurrent(Robot.rmap.getPort("PDP Intake Right Channel", 11)) > Robot.getConst("Max Current", + 38); } - + /** * stops the motors * */ public void stopIntake() { - intakeMotor.stopMotor(); + leftIntakeMotor.stopMotor(); + rightIntakeMotor.stopMotor(); } - + /** * Spins the rollers - * @param speed - positive -> rollers in, negative -> rollers out + * + * @param speed + * - positive -> rollers in, negative -> rollers out */ public void runIntake(double speed) { - + double actualSpeed = speed * Robot.getConst("Intake Motor Speed Multiplier", 1); + leftIntakeMotor.set(actualSpeed); + rightIntakeMotor.set(actualSpeed); + } + + /** + * Raises the intake + */ + public void raiseIntake() { + leftVerticalSolenoid.set(DoubleSolenoid.Value.kForward); + rightVerticalSolenoid.set(DoubleSolenoid.Value.kForward); + } + + /** + * Lowers the intake + */ + public void lowerIntake() { + leftVerticalSolenoid.set(DoubleSolenoid.Value.kReverse); + rightVerticalSolenoid.set(DoubleSolenoid.Value.kReverse); + } + + /** + * Opens the intake + */ + public void openIntake() { + leftHorizontalSolenoid.set(DoubleSolenoid.Value.kForward); + rightHorizontalSolenoid.set(DoubleSolenoid.Value.kForward); } - -} + /** + * Closes the intake + */ + public void closeIntake() { + leftHorizontalSolenoid.set(DoubleSolenoid.Value.kForward); + rightHorizontalSolenoid.set(DoubleSolenoid.Value.kForward); + } +} diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/subsystems/IntakeEjectInterface.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/subsystems/IntakeEjectInterface.java index 428e1f8..0262727 100644 --- a/Robot2018/src/org/usfirst/frc/team199/Robot2018/subsystems/IntakeEjectInterface.java +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/subsystems/IntakeEjectInterface.java @@ -1,41 +1,60 @@ package org.usfirst.frc.team199.Robot2018.subsystems; public interface IntakeEjectInterface { - + /** * Set the default command for a subsystem here. - * */ + */ public void initDefaultCommand(); - + /** - * returns current motor value + * returns current left motor value */ - public double getIntakeSpeed(); - + public double getLeftIntakeSpeed(); + /** - * Uses (insert sensor here) to detect - * a cube in front of the robot. + * returns current right motor value */ - public boolean seeCube(); - + public double getRightIntakeSpeed(); + /** - * Uses (insert sensor here) to detect if - * the cube is currently inside the robot + * Uses current to check if the wheels are blocked aka the cube is inside the + * robot * */ public boolean hasCube(); - + /** * stops the motors * */ public void stopIntake(); - + /** * Spins the rollers - * @param speed - positive -> rollers in, negative -> rollers out + * + * @param speed + * - positive -> rollers in, negative -> rollers out */ public void runIntake(double speed); - - + + /** + * Raises the intake + */ + public void raiseIntake(); + + /** + * Lowers the intake + */ + public void lowerIntake(); + + /** + * Closes the intake + */ + public void closeIntake(); + + /** + * Opens the intake + */ + public void openIntake(); } From 91598151763e6918655f184cc8ccc538c36cc6c8 Mon Sep 17 00:00:00 2001 From: doawelul Date: Sat, 10 Feb 2018 14:36:10 -0800 Subject: [PATCH 27/43] changed the rpm to radians per second --- .../src/org/usfirst/frc/team199/Robot2018/RobotMap.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/RobotMap.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/RobotMap.java index e3e8096..3c6e228 100644 --- a/Robot2018/src/org/usfirst/frc/team199/Robot2018/RobotMap.java +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/RobotMap.java @@ -212,12 +212,14 @@ public double calcDefkD() { * half of the drivetrain only has to support half of the robot), and radius of * the drivetrain wheels squared. It's inversely proportional to the stall * torque of the shaft, which is found by multiplying the stall torque of the - * motor with the gear reduction by the amount of motors. + * motor with the gear reduction by the amount of motors. The omegaMax needs to + * be converted from rpm to radians per second, so divide by 60 and multiply to + * get radians. */ double gearReduction = Robot.getBool("High Gear", false) ? Robot.getConst("High Gear Gear Reduction", 5.392) : Robot.getConst("Low Gear Gear Reduction", 12.255); double radius = Robot.getConst("Radius of Drivetrain Wheel", 0.0635); - double timeConstant = Robot.getConst("Omega Max", 5330) / gearReduction + double timeConstant = Robot.getConst("Omega Max", 5330) / gearReduction / 60 * 2 * Math.PI * convertLbsTokG(Robot.getConst("Mass of Robot", 150)) / 2 * radius * radius / (Robot.getConst("Stall Torque", 2.41) * gearReduction * 2); double cycleTime = Robot.getConst("Code cycle time", 0.1); From a740f2e5373b47d68cb19274c81535809438738c Mon Sep 17 00:00:00 2001 From: doawelul Date: Sat, 10 Feb 2018 14:40:00 -0800 Subject: [PATCH 28/43] got rid of compiling errors --- .../org/usfirst/frc/team199/Robot2018/commands/RunScript.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/RunScript.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/RunScript.java index d5b7208..d99c81a 100644 --- a/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/RunScript.java +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/RunScript.java @@ -43,7 +43,7 @@ public RunScript(String scriptName) { addSequential(new WaitCommand(Double.parseDouble(cmdArgs))); break; case "intake": - addSequential(new IntakeCube()); + addSequential(new IntakeCube(true)); break; case "jump": addSequential(new RunScript(cmdArgs)); From 8e5407ed4bc25b1e8d32a3ce1f27e59852797669 Mon Sep 17 00:00:00 2001 From: Alex Binnard Date: Sat, 10 Feb 2018 14:48:57 -0800 Subject: [PATCH 29/43] Fix merging, reorganize PIDTurn's constructor to match PIDMove's, pass Corvin's tests, etc. --- .../org/usfirst/frc/team199/Robot2018/OI.java | 2 +- .../usfirst/frc/team199/Robot2018/Robot.java | 9 ++ .../Robot2018/commands/AutoMoveTo.java | 73 ++++++----- .../team199/Robot2018/commands/PIDMove.java | 17 ++- .../team199/Robot2018/commands/PIDTurn.java | 24 ++-- .../team199/Robot2018/commands/RunScript.java | 124 +++++++++--------- .../Robot2018/subsystems/Drivetrain.java | 13 +- 7 files changed, 144 insertions(+), 118 deletions(-) diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/OI.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/OI.java index a94d2dd..d192f47 100644 --- a/Robot2018/src/org/usfirst/frc/team199/Robot2018/OI.java +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/OI.java @@ -54,7 +54,7 @@ public OI() { PIDMoveButton = new JoystickButton(leftJoy, getButton("PID Move", 7)); PIDMoveButton.whenPressed(new PIDMove(40, Robot.dt, Robot.sd, RobotMap.distEncAvg)); PIDTurnButton = new JoystickButton(leftJoy, getButton("PID Turn", 8)); - PIDTurnButton.whenPressed(new PIDTurn(30, Robot.dt, RobotMap.fancyGyro, Robot.sd)); + PIDTurnButton.whenPressed(new PIDTurn(30, Robot.dt, Robot.sd, RobotMap.fancyGyro)); rightJoy = new Joystick(1); updatePIDConstantsButton = new JoystickButton(rightJoy, getButton("Get PID Constants", 8)); diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/Robot.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/Robot.java index 0f50c0b..dcc6a73 100644 --- a/Robot2018/src/org/usfirst/frc/team199/Robot2018/Robot.java +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/Robot.java @@ -55,6 +55,15 @@ public class Robot extends TimedRobot { Map> stratChoosers = new HashMap>(); String[] fmsPossibilities = { "LL", "LR", "RL", "RR" }; + public static SmartDashboardInterface sd = new SmartDashboardInterface() { + public double getConst(String key, double def) { + if (!SmartDashboard.containsKey("Const/" + key)) { + SmartDashboard.putNumber("Const/" + key, def); + } + return SmartDashboard.getNumber("Const/" + key, def); + } + }; + public static double getConst(String key, double def) { return sd.getConst(key, def); } diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/AutoMoveTo.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/AutoMoveTo.java index e1775a3..66a8023 100644 --- a/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/AutoMoveTo.java +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/AutoMoveTo.java @@ -1,49 +1,52 @@ package org.usfirst.frc.team199.Robot2018.commands; -import edu.wpi.first.wpilibj.PIDSource; - import org.usfirst.frc.team199.Robot2018.SmartDashboardInterface; import org.usfirst.frc.team199.Robot2018.autonomous.AutoUtils; -import org.usfirst.frc.team199.Robot2018.autonomous.PIDSourceAverage; import org.usfirst.frc.team199.Robot2018.subsystems.DrivetrainInterface; +import edu.wpi.first.wpilibj.PIDSource; + //import org.usfirst.frc.team199.Robot2018.subsystems.Drivetrain; import edu.wpi.first.wpilibj.command.CommandGroup; /** - * The AutoMoveTo command, which makes the robot go through a set of movements and rotations. + * The AutoMoveTo command, which makes the robot go through a set of movements + * and rotations. */ public class AutoMoveTo extends CommandGroup { - - public AutoMoveTo(String[] args, DrivetrainInterface dt, - SmartDashboardInterface sd, PIDSource pidMoveSrc) { - //requires(Drivetrain); - double rotation; - double[] point = {0,0}; - String parentheseless; - String[] pointparts; - for (String arg : args) { - if (AutoUtils.isDouble(arg)) { - rotation = Double.valueOf(arg); - addSequential(new PIDTurn(rotation - AutoUtils.getRot(), dt, dt.getGyro(), sd)); - AutoUtils.setRot(rotation); - } else if (AutoUtils.isPoint(arg)) { - parentheseless = arg.substring(1, arg.length() - 1); - pointparts = parentheseless.split(","); - point[0] = Double.parseDouble(pointparts[0]); - point[1] = Double.parseDouble(pointparts[1]); - addSequential(new PIDTurn(Math.toDegrees(Math.atan((point[0] - AutoUtils.getX())/(point[1] - AutoUtils.getY())) - AutoUtils.getRot()), - Robot.dt, Robot.dt.getGyro())); - addSequential(new PIDMove(Math.sqrt(((point[0] - AutoUtils.getX()) * (point[0] - AutoUtils.getX()) + - ((point[1] - AutoUtils.getY()) * (point[1] - AutoUtils.getY())))), - Robot.dt, new PIDSourceAverage(null, null))); - AutoUtils.setX(point[0]); - AutoUtils.setY(point[1]); - AutoUtils.setRot(Math.toDegrees(Math.atan((point[0] - AutoUtils.getX())/(point[1] - AutoUtils.getY())))); - } else { - throw new IllegalArgumentException(); - } - } - } + + public AutoMoveTo(String[] args, DrivetrainInterface dt, SmartDashboardInterface sd, PIDSource pidMoveSrc) { + // requires(Drivetrain); + double rotation; + double[] point = { 0, 0 }; + String parentheseless; + String[] pointparts; + for (String arg : args) { + if (AutoUtils.isDouble(arg)) { + rotation = Double.valueOf(arg); + addSequential(new PIDTurn(rotation - AutoUtils.getRot(), dt, sd, pidMoveSrc)); + AutoUtils.setRot(rotation); + } else if (AutoUtils.isPoint(arg)) { + parentheseless = arg.substring(1, arg.length() - 1); + pointparts = parentheseless.split(","); + point[0] = Double.parseDouble(pointparts[0]); + point[1] = Double.parseDouble(pointparts[1]); + addSequential(new PIDTurn(Math.toDegrees( + Math.atan((point[0] - AutoUtils.getX()) / (point[1] - AutoUtils.getY())) - AutoUtils.getRot()), + dt, sd, pidMoveSrc)); + addSequential(new PIDMove( + Math.sqrt(((point[0] - AutoUtils.getX()) * (point[0] - AutoUtils.getX()) + + ((point[1] - AutoUtils.getY()) * (point[1] - AutoUtils.getY())))), + dt, sd, pidMoveSrc)); + double x = AutoUtils.getX(); + double y = AutoUtils.getY(); + AutoUtils.setX(point[0]); + AutoUtils.setY(point[1]); + AutoUtils.setRot(Math.toDegrees(Math.atan((point[0] - x) / (point[1] - y)))); + } else { + throw new IllegalArgumentException(); + } + } + } } diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/PIDMove.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/PIDMove.java index e719213..ba5271f 100644 --- a/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/PIDMove.java +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/PIDMove.java @@ -1,11 +1,12 @@ package org.usfirst.frc.team199.Robot2018.commands; import org.usfirst.frc.team199.Robot2018.Robot; -import org.usfirst.frc.team199.Robot2018.autonomous.PIDSourceAverage; +import org.usfirst.frc.team199.Robot2018.SmartDashboardInterface; import org.usfirst.frc.team199.Robot2018.subsystems.DrivetrainInterface; import edu.wpi.first.wpilibj.PIDController; import edu.wpi.first.wpilibj.PIDOutput; +import edu.wpi.first.wpilibj.PIDSource; import edu.wpi.first.wpilibj.command.Command; /** @@ -32,14 +33,18 @@ public class PIDMove extends Command implements PIDOutput { * @param avg * the PIDSourceAverage of the DT's two Encoders */ - public PIDMove(double targ, DrivetrainInterface dt, PIDSourceAverage avg) { + public PIDMove(double targ, DrivetrainInterface dt, SmartDashboardInterface sd, PIDSource avg) { // Use requires() here to declare subsystem dependencies - requires(Robot.dt); target = targ; this.dt = dt; - double kf = 1 / (dt.getCurrentMaxSpeed() * Robot.getConst("Default PID Update Time", 0.05)); - moveController = new PIDController(Robot.getConst("MovekP", 1), Robot.getConst("MovekI", 0), - Robot.getConst("MovekD", 0), kf, avg, this); + if (Robot.dt != null) { + requires(Robot.dt); + } + moveController = new PIDController(sd.getConst("MovekP", 1), sd.getConst("MovekI", 0), sd.getConst("MovekD", 0), + avg, this); + double kf = 1 / (dt.getCurrentMaxSpeed() * sd.getConst("Default PID Update Time", 0.05)); + moveController = new PIDController(sd.getConst("MovekP", 1), sd.getConst("MovekI", 0), sd.getConst("MovekD", 0), + kf, avg, this); } /** diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/PIDTurn.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/PIDTurn.java index 84aaf3c..f4cc16e 100644 --- a/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/PIDTurn.java +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/PIDTurn.java @@ -1,12 +1,12 @@ package org.usfirst.frc.team199.Robot2018.commands; import org.usfirst.frc.team199.Robot2018.Robot; +import org.usfirst.frc.team199.Robot2018.SmartDashboardInterface; import org.usfirst.frc.team199.Robot2018.subsystems.DrivetrainInterface; -import com.kauailabs.navx.frc.AHRS; - import edu.wpi.first.wpilibj.PIDController; import edu.wpi.first.wpilibj.PIDOutput; +import edu.wpi.first.wpilibj.PIDSource; import edu.wpi.first.wpilibj.command.Command; /** @@ -33,19 +33,25 @@ public class PIDTurn extends Command implements PIDOutput { * testing) * @param ahrs * the AHRS (gyro) + * @param sd + * the Smart Dashboard reference, or a SmartDashboardInterface for + * testing */ - public PIDTurn(double targ, DrivetrainInterface dt, AHRS ahrs) { + public PIDTurn(double targ, DrivetrainInterface dt, SmartDashboardInterface sd, PIDSource ahrs) { // Use requires() here to declare subsystem dependencies - requires(Robot.dt); target = targ; this.dt = dt; + if (Robot.dt != null) { + requires(Robot.dt); + } + turnController = new PIDController(sd.getConst("TurnkP", 1), sd.getConst("TurnkI", 0), sd.getConst("TurnkD", 0), + ahrs, this); // calculates the maximum turning speed in degrees/sec based on the max linear // speed in inches/s and the distance (inches) between sides of the DT - double maxTurnSpeed = dt.getCurrentMaxSpeed() * 360 - / (Math.PI * Robot.getConst("Distance Between Wheels", 26.25)); - double kf = 1 / (maxTurnSpeed * Robot.getConst("Default PID Update Time", 0.05)); - turnController = new PIDController(Robot.getConst("TurnkP", 1), Robot.getConst("TurnkI", 0), - Robot.getConst("TurnkD", 0), kf, ahrs, this); + double maxTurnSpeed = dt.getCurrentMaxSpeed() * 360 / (Math.PI * sd.getConst("Distance Between Wheels", 26.25)); + double kf = 1 / (maxTurnSpeed * sd.getConst("Default PID Update Time", 0.05)); + turnController = new PIDController(sd.getConst("TurnkP", 1), sd.getConst("TurnkI", 0), sd.getConst("TurnkD", 0), + kf, ahrs, this); } /** diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/RunScript.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/RunScript.java index fe61d2f..d54f458 100644 --- a/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/RunScript.java +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/RunScript.java @@ -14,69 +14,63 @@ */ public class RunScript extends CommandGroup { - public RunScript(String scriptName) { - ArrayList script = Robot.autoScripts.getOrDefault(scriptName, new ArrayList()); - - outerloop: - for(String[] cmd : script) { - String cmdName = cmd[0]; - String cmdArgs = cmd[1]; - - double[] point = new double[2]; - double rotation = 0; - String parentheseless; - String[] pointparts; - if (AutoUtils.isDouble(cmdArgs)) { - rotation = Double.valueOf(cmdArgs); - } else if (AutoUtils.isPoint(cmdArgs)) { - parentheseless = cmdArgs.substring(1, cmdArgs.length() - 1); - pointparts = parentheseless.split(","); - point[0] = Double.parseDouble(pointparts[0]); - } - - switch (cmdName) { - case "moveto": - addSequential(new AutoMoveTo(cmdArgs.split(" "), Robot.dt, Robot.sd, new PIDSourceAverage(null, null))); - break; - case "turn": -/**<<<<<<< corvin-refactor-for-AutoMoveTo-unit-tests This is commented out so I can come back after testing. - addSequential(new PIDTurn(Double.parseDouble(cmdArgs), Robot.dt, Robot.dt.getGyro(), Robot.sd)); - break; - case "move": - addSequential(new PIDMove(Double.parseDouble(cmdArgs), Robot.dt, Robot.sd, new PIDSourceAverage(null, null))); -=======**/ - addSequential(new PIDTurn(Double.parseDouble(cmdArgs), Robot.dt, Robot.dt.getGyro())); - AutoUtils.setRot(rotation); - break; - case "move": - addSequential(new PIDMove(Double.parseDouble(cmdArgs), Robot.dt, new PIDSourceAverage(null, null))); - AutoUtils.setX(point[0]); - AutoUtils.setY(point[1]); - break; - case "switch": - addSequential(new EjectToSwitch()); - break; - case "scale": - addSequential(new EjectToScale()); - break; - case "exchange": - addSequential(new EjectToExchange()); - break; - case "wait": - addSequential(new WaitCommand(Double.parseDouble(cmdArgs))); - break; - case "intake": - addSequential(new IntakeCube()); - break; - case "jump": - addSequential(new RunScript(cmdArgs)); - break; - case "end": - break outerloop; - default: - // this should never happen since AutoUtils already validates the script. - System.err.println("[ERROR] `" + cmdName + "` is not a valid command name."); - } - } - } + public RunScript(String scriptName) { + ArrayList script = Robot.autoScripts.getOrDefault(scriptName, new ArrayList()); + + outerloop: for (String[] cmd : script) { + String cmdName = cmd[0]; + String cmdArgs = cmd[1]; + + double[] point = new double[2]; + double rotation = 0; + String parentheseless; + String[] pointparts; + if (AutoUtils.isDouble(cmdArgs)) { + rotation = Double.valueOf(cmdArgs); + } else if (AutoUtils.isPoint(cmdArgs)) { + parentheseless = cmdArgs.substring(1, cmdArgs.length() - 1); + pointparts = parentheseless.split(","); + point[0] = Double.parseDouble(pointparts[0]); + } + + switch (cmdName) { + case "moveto": + addSequential(new AutoMoveTo(cmdArgs.split(" "), Robot.dt, Robot.sd, new PIDSourceAverage(null, null))); + break; + case "turn": + addSequential(new PIDTurn(Double.parseDouble(cmdArgs), Robot.dt, Robot.sd, Robot.dt.getGyro())); + AutoUtils.setRot(rotation); + break; + case "move": + addSequential( + new PIDMove(Double.parseDouble(cmdArgs), Robot.dt, Robot.sd, new PIDSourceAverage(null, null))); + AutoUtils.setX(point[0]); + AutoUtils.setY(point[1]); + break; + case "switch": + addSequential(new EjectToSwitch()); + break; + case "scale": + addSequential(new EjectToScale()); + break; + case "exchange": + addSequential(new EjectToExchange()); + break; + case "wait": + addSequential(new WaitCommand(Double.parseDouble(cmdArgs))); + break; + case "intake": + addSequential(new IntakeCube()); + break; + case "jump": + addSequential(new RunScript(cmdArgs)); + break; + case "end": + break outerloop; + default: + // this should never happen since AutoUtils already validates the script. + System.err.println("[ERROR] `" + cmdName + "` is not a valid command name."); + } + } + } } diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/subsystems/Drivetrain.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/subsystems/Drivetrain.java index 63781ce..39a2cec 100644 --- a/Robot2018/src/org/usfirst/frc/team199/Robot2018/subsystems/Drivetrain.java +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/subsystems/Drivetrain.java @@ -17,8 +17,8 @@ import edu.wpi.first.wpilibj.DoubleSolenoid; import edu.wpi.first.wpilibj.Encoder; -import edu.wpi.first.wpilibj.SpeedControllerGroup; import edu.wpi.first.wpilibj.PIDSource; +import edu.wpi.first.wpilibj.SpeedControllerGroup; import edu.wpi.first.wpilibj.command.Subsystem; import edu.wpi.first.wpilibj.drive.DifferentialDrive; @@ -233,13 +233,14 @@ public void shiftGears(boolean highGear) { public void shiftGearSolenoidOff() { dtGear.set(DoubleSolenoid.Value.kOff); } - + /** * @return the gyroscope */ public PIDSource getGyro() { return fancyGyro; } + /** * Reset the kf constants for both VelocityPIDControllers based on current DT * gearing (high or low gear). @@ -270,4 +271,12 @@ public double getCurrentMaxSpeed() { return Robot.getConst("Max Low Speed", 84); } } + + /** + * @return some average having to do with encoder distance (it's still + * undocumented (ask laura)) + */ + public PIDSourceAverage getDistEncAvg() { + return distEncAvg; + } } From e774c5f8403abd35072cf4a7883088223d9ad50d Mon Sep 17 00:00:00 2001 From: Alex Binnard Date: Sat, 10 Feb 2018 15:26:29 -0800 Subject: [PATCH 30/43] parsePoint method, Position class, math fixes, etc. --- .../Robot2018/autonomous/AutoUtils.java | 186 +++++++++--------- .../Robot2018/autonomous/Position.java | 52 +++++ .../Robot2018/commands/AutoMoveTo.java | 28 ++- .../team199/Robot2018/commands/RunScript.java | 24 +-- 4 files changed, 159 insertions(+), 131 deletions(-) create mode 100644 Robot2018/src/org/usfirst/frc/team199/Robot2018/autonomous/Position.java diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/autonomous/AutoUtils.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/autonomous/AutoUtils.java index a532817..5f07678 100644 --- a/Robot2018/src/org/usfirst/frc/team199/Robot2018/autonomous/AutoUtils.java +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/autonomous/AutoUtils.java @@ -5,50 +5,52 @@ import java.util.Map; public class AutoUtils { - private static double currX; - private static double currY; - private static double currRotation; + + public static Position position = new Position(0, 0, 0); + /** * Parses the inputted script file into a map of scripts * - * @param scriptFile the script file to parse - * @return a map, with the key being the script name, and the argument - * being a list of arrays that are instruction-argument pairs + * @param scriptFile + * the script file to parse + * @return a map, with the key being the script name, and the argument being a + * list of arrays that are instruction-argument pairs */ public static Map> parseScriptFile(String scriptFile) { Map> autoScripts = new HashMap>(); - + String lines[] = scriptFile.split("\\r?\\n"); - + ArrayList currScript = new ArrayList(); - String currScriptName = ""; - + String currScriptName = ""; + int count = 1; for (String line : lines) { // remove comments int commentIndex = line.indexOf("#"); if (commentIndex != -1) line = line.substring(0, commentIndex); - + // trim and remove extra whitespace just to make it neater line = line.trim().replaceAll("\\s+", " "); - + // if there's no instruction on this line, skip if (line.equals("")) { continue; } - - // if current line is a label, store the previous script and make a new empty one + + // if current line is a label, store the previous script and make a new empty + // one if (line.endsWith(":")) { autoScripts.put(currScriptName, currScript); currScript = new ArrayList(); currScriptName = line.substring(0, line.length() - 1); } else { - + // first separate the command into instruction and args String instruction; String args; - + int separator = line.indexOf(' '); if (separator == -1) { instruction = line; @@ -57,115 +59,87 @@ public static Map> parseScriptFile(String scriptFile instruction = line.substring(0, separator); args = line.substring(separator + 1); } - + // if it's valid, put it into the script if (isValidCommand(instruction, args, count)) { - String[] command = {instruction, args}; + String[] command = { instruction, args }; currScript.add(command); } } count++; } - + // puts the last script in autoScripts.put(currScriptName, currScript); - + // remove the stray one in the beginning autoScripts.remove(""); - + return autoScripts; } - - /* - * All of these are getters and setters for the robot's position and orientation - */ - public static double getX() { - return currX; - } - public static double getY() { - return currY; - } - public static double getRot() { - return currRotation; - } - public static void setX(double x) { - currX = x; - } - public static void setY(double y) { - currY = y; - } - public static void setRot(double rot) { - currRotation = rot; - } - public static void changeX(double x) { - currX += x; - } - public static void changeY(double y) { - currY += y; - } - public static void changeRot(double rot) { - currRotation += rot; - } - + /** * Validates the command inputted to see if it's AAA compliant * - * @param instruction the instruction/command name - * @param args the arguments provided to the instruction. A blank String if none - * @param lineNumber the lineNumber in the script file. used for logging warnings + * @param instruction + * the instruction/command name + * @param args + * the arguments provided to the instruction. A blank String if none + * @param lineNumber + * the lineNumber in the script file. used for logging warnings * @return if the command is valid */ - public static boolean isValidCommand (String instruction, String args, int lineNumber) { + public static boolean isValidCommand(String instruction, String args, int lineNumber) { // moveto takes in a set of points, and the last arg can be a number if (instruction.equals("moveto")) { if (args == "") { logWarning(lineNumber, "The command `moveto` requires at least one argument."); return false; } - + String[] splitArgs = args.split(" "); for (int i = 0; i < splitArgs.length - 1; i++) { if (!isPoint(splitArgs[i])) { - logWarning(lineNumber, "The arguments for command `moveto` should be points formatted like this: " - + "`(x,y)`."); + logWarning(lineNumber, + "The arguments for command `moveto` should be points formatted like this: " + "`(x,y)`."); return false; } } - + if (!isDouble(splitArgs[splitArgs.length - 1]) && !isPoint(splitArgs[splitArgs.length - 1])) { logWarning(lineNumber, "The last argument for command `moveto` should be a number, or a point " + "formatted like this: `(x,y)`."); return false; } - } - + } + // turn can take a number or point else if (instruction.equals("turn")) { if (args.contains(" ")) { logWarning(lineNumber, "Command `turn` only accepts one argument."); return false; } - + if (!isDouble(args) && !isPoint(args)) { logWarning(lineNumber, "The argument for command `turn` should be a number or a point formatted like " + "this: `(x,y)`."); return false; } - } - + } + // move and wait can take only a number else if (instruction.equals("move") || instruction.equals("wait")) { if (args.contains(" ")) { logWarning(lineNumber, "Command `move` only accepts one argument."); return false; } - + if (!isDouble(args)) { logWarning(lineNumber, "The argument for command `move` should be a number."); return false; } } - + // switch, scale, exchange, intake, and end all don't have any args else if (instruction.equals("switch") || instruction.equals("scale") || instruction.equals("exchange") || instruction.equals("intake") || instruction.equals("end")) { @@ -173,76 +147,79 @@ else if (instruction.equals("switch") || instruction.equals("scale") || instruct logWarning(lineNumber, "Command `" + instruction + "` does not accept any arguments."); return false; } - } - + } + // Jump only takes one argument else if (instruction.equals("jump")) { if (args.contains(" ")) { logWarning(lineNumber, "Command `jump` only accepts one argument."); return false; } - } - - // if it's not even a valid instruction + } + + // if it's not even a valid instruction else { logWarning(lineNumber, "`" + instruction + "` is not a valid command."); return false; } - + // if everything is all good return true; } - + /** - * Helper method used by isValidCommand() to log warnings for non-valid commands. + * Helper method used by isValidCommand() to log warnings for non-valid + * commands. * - * @param lineNumber the line number in the script file - * @param message the message to log + * @param lineNumber + * the line number in the script file + * @param message + * the message to log */ - private static void logWarning (int lineNumber, String message) { + private static void logWarning(int lineNumber, String message) { System.err.println("[WARNING] Line " + lineNumber + ": " + message); } - + /** - * Helper method used by isValidCommand() to check if an argument is a - * point, characterized by parentheses on the left and right, with two - * numbers separated by a comma, with no whitespace in between. + * Helper method used by isValidCommand() to check if an argument is a point, + * characterized by parentheses on the left and right, with two numbers + * separated by a comma, with no whitespace in between. * - * @param s the argument + * @param s + * the argument * @return if the argument is a point */ - public static boolean isPoint (String s) { + public static boolean isPoint(String s) { // checks if it starts and ends with parentheses if (!s.startsWith("(") || !s.endsWith(")")) return false; - + // checks that there's one, and only one comma (like this phrase) int indexOfComma = s.indexOf(','); int count = 0; while (indexOfComma != -1) { - count++; - indexOfComma = s.indexOf(',', indexOfComma + 1); + count++; + indexOfComma = s.indexOf(',', indexOfComma + 1); } if (count != 1) return false; - - + // really ugly, but just checks if the stuff between the parentheses are numbers - if (!isDouble(s.substring(1, s.indexOf(','))) - || !isDouble(s.substring(s.indexOf(',') + 1, s.length() - 1))) + if (!isDouble(s.substring(1, s.indexOf(','))) || !isDouble(s.substring(s.indexOf(',') + 1, s.length() - 1))) return false; - + return true; } - + /** - * Helper method used by isValidCommand() used to check if an argument is - * able to be converted into a double + * Helper method used by isValidCommand() used to check if an argument is able + * to be converted into a double * - * @param s the argument + * @param s + * the argument * @return if the argument is a double */ - public static boolean isDouble (String s) { + public static boolean isDouble(String s) { try { Double.parseDouble(s); } catch (Exception e) { @@ -250,4 +227,17 @@ public static boolean isDouble (String s) { } return true; } + + public static double[] parsePoint(String cmdArgs) { + double[] point = new double[2]; + String parentheseless; + String[] pointparts; + if (AutoUtils.isPoint(cmdArgs)) { + parentheseless = cmdArgs.substring(1, cmdArgs.length() - 1); + pointparts = parentheseless.split(","); + point[0] = Double.parseDouble(pointparts[0]); + point[1] = Double.parseDouble(pointparts[1]); + } + return point; + } } diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/autonomous/Position.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/autonomous/Position.java new file mode 100644 index 0000000..01a1dca --- /dev/null +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/autonomous/Position.java @@ -0,0 +1,52 @@ +package org.usfirst.frc.team199.Robot2018.autonomous; + +public class Position { + private double currX; + private double currY; + private double currRotation; + + public Position(double x, double y, double rot) { + currX = x; + currY = y; + currRotation = rot; + } + + /* + * All of these are getters and setters for the robot's position and orientation + */ + public double getX() { + return currX; + } + + public double getY() { + return currY; + } + + public double getRot() { + return currRotation; + } + + public void setX(double x) { + currX = x; + } + + public void setY(double y) { + currY = y; + } + + public void setRot(double rot) { + currRotation = rot; + } + + public void changeX(double x) { + currX += x; + } + + public void changeY(double y) { + currY += y; + } + + public void changeRot(double rot) { + currRotation += rot; + } +} diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/AutoMoveTo.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/AutoMoveTo.java index 66a8023..1025292 100644 --- a/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/AutoMoveTo.java +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/AutoMoveTo.java @@ -20,30 +20,26 @@ public AutoMoveTo(String[] args, DrivetrainInterface dt, SmartDashboardInterface // requires(Drivetrain); double rotation; double[] point = { 0, 0 }; - String parentheseless; - String[] pointparts; for (String arg : args) { if (AutoUtils.isDouble(arg)) { rotation = Double.valueOf(arg); - addSequential(new PIDTurn(rotation - AutoUtils.getRot(), dt, sd, pidMoveSrc)); - AutoUtils.setRot(rotation); + addSequential(new PIDTurn(rotation - AutoUtils.position.getRot(), dt, sd, pidMoveSrc)); + AutoUtils.position.setRot(rotation); } else if (AutoUtils.isPoint(arg)) { - parentheseless = arg.substring(1, arg.length() - 1); - pointparts = parentheseless.split(","); - point[0] = Double.parseDouble(pointparts[0]); - point[1] = Double.parseDouble(pointparts[1]); + point = AutoUtils.parsePoint(arg); addSequential(new PIDTurn(Math.toDegrees( - Math.atan((point[0] - AutoUtils.getX()) / (point[1] - AutoUtils.getY())) - AutoUtils.getRot()), + Math.atan((point[0] - AutoUtils.position.getX()) / (point[1] - AutoUtils.position.getY())) + - AutoUtils.position.getRot()), dt, sd, pidMoveSrc)); addSequential(new PIDMove( - Math.sqrt(((point[0] - AutoUtils.getX()) * (point[0] - AutoUtils.getX()) - + ((point[1] - AutoUtils.getY()) * (point[1] - AutoUtils.getY())))), + Math.sqrt(((point[0] - AutoUtils.position.getX()) * (point[0] - AutoUtils.position.getX()) + + ((point[1] - AutoUtils.position.getY()) * (point[1] - AutoUtils.position.getY())))), dt, sd, pidMoveSrc)); - double x = AutoUtils.getX(); - double y = AutoUtils.getY(); - AutoUtils.setX(point[0]); - AutoUtils.setY(point[1]); - AutoUtils.setRot(Math.toDegrees(Math.atan((point[0] - x) / (point[1] - y)))); + double x = AutoUtils.position.getX(); + double y = AutoUtils.position.getY(); + AutoUtils.position.setX(point[0]); + AutoUtils.position.setY(point[1]); + AutoUtils.position.setRot(Math.toDegrees(Math.atan((point[0] - x) / (point[1] - y)))); } else { throw new IllegalArgumentException(); } diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/RunScript.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/RunScript.java index d54f458..d4cb402 100644 --- a/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/RunScript.java +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/RunScript.java @@ -21,31 +21,21 @@ public RunScript(String scriptName) { String cmdName = cmd[0]; String cmdArgs = cmd[1]; - double[] point = new double[2]; - double rotation = 0; - String parentheseless; - String[] pointparts; - if (AutoUtils.isDouble(cmdArgs)) { - rotation = Double.valueOf(cmdArgs); - } else if (AutoUtils.isPoint(cmdArgs)) { - parentheseless = cmdArgs.substring(1, cmdArgs.length() - 1); - pointparts = parentheseless.split(","); - point[0] = Double.parseDouble(pointparts[0]); - } - switch (cmdName) { case "moveto": addSequential(new AutoMoveTo(cmdArgs.split(" "), Robot.dt, Robot.sd, new PIDSourceAverage(null, null))); break; case "turn": - addSequential(new PIDTurn(Double.parseDouble(cmdArgs), Robot.dt, Robot.sd, Robot.dt.getGyro())); + double rotation = Double.parseDouble(cmdArgs); + addSequential(new PIDTurn(rotation, Robot.dt, Robot.sd, Robot.dt.getGyro())); AutoUtils.setRot(rotation); break; case "move": - addSequential( - new PIDMove(Double.parseDouble(cmdArgs), Robot.dt, Robot.sd, new PIDSourceAverage(null, null))); - AutoUtils.setX(point[0]); - AutoUtils.setY(point[1]); + double distance = Double.parseDouble(cmdArgs); + + addSequential(new PIDMove(distance, Robot.dt, Robot.sd, new PIDSourceAverage(null, null))); + AutoUtils.setX(distance * Math.sin(Math.toRadians(AutoUtils.getRot()))); + AutoUtils.setY(distance * Math.cos(Math.toRadians(AutoUtils.getRot()))); break; case "switch": addSequential(new EjectToSwitch()); From 77a42c8b9bd0d57fcede30609f86dd182dc991aa Mon Sep 17 00:00:00 2001 From: Alex Binnard Date: Sat, 10 Feb 2018 16:09:17 -0800 Subject: [PATCH 31/43] Simple constructor for AMT, make math readable --- .../Robot2018/commands/AutoMoveTo.java | 54 +++++++++++++++---- .../team199/Robot2018/commands/RunScript.java | 11 ++-- 2 files changed, 50 insertions(+), 15 deletions(-) diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/AutoMoveTo.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/AutoMoveTo.java index 1025292..ddde320 100644 --- a/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/AutoMoveTo.java +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/AutoMoveTo.java @@ -1,5 +1,6 @@ package org.usfirst.frc.team199.Robot2018.commands; +import org.usfirst.frc.team199.Robot2018.Robot; import org.usfirst.frc.team199.Robot2018.SmartDashboardInterface; import org.usfirst.frc.team199.Robot2018.autonomous.AutoUtils; import org.usfirst.frc.team199.Robot2018.subsystems.DrivetrainInterface; @@ -23,18 +24,53 @@ public AutoMoveTo(String[] args, DrivetrainInterface dt, SmartDashboardInterface for (String arg : args) { if (AutoUtils.isDouble(arg)) { rotation = Double.valueOf(arg); - addSequential(new PIDTurn(rotation - AutoUtils.position.getRot(), dt, sd, pidMoveSrc)); + double relrotation = rotation - AutoUtils.position.getRot(); + addSequential(new PIDTurn(relrotation, dt, sd, pidMoveSrc)); AutoUtils.position.setRot(rotation); } else if (AutoUtils.isPoint(arg)) { point = AutoUtils.parsePoint(arg); - addSequential(new PIDTurn(Math.toDegrees( - Math.atan((point[0] - AutoUtils.position.getX()) / (point[1] - AutoUtils.position.getY())) - - AutoUtils.position.getRot()), - dt, sd, pidMoveSrc)); - addSequential(new PIDMove( - Math.sqrt(((point[0] - AutoUtils.position.getX()) * (point[0] - AutoUtils.position.getX()) - + ((point[1] - AutoUtils.position.getY()) * (point[1] - AutoUtils.position.getY())))), - dt, sd, pidMoveSrc)); + double deltaX = point[0] - AutoUtils.position.getX(); + double deltaY = point[1] - AutoUtils.position.getY(); + double atan = Math.toDegrees(Math.atan(deltaX / deltaY)); + double relrotation = atan - AutoUtils.position.getRot(); + addSequential(new PIDTurn(relrotation, dt, sd, pidMoveSrc)); + double dX2 = deltaX * deltaX; + double dY2 = deltaY * deltaY; + double distance = Math.sqrt(dX2 + dY2); + addSequential(new PIDMove(distance, dt, sd, pidMoveSrc)); + double x = AutoUtils.position.getX(); + double y = AutoUtils.position.getY(); + AutoUtils.position.setX(point[0]); + AutoUtils.position.setY(point[1]); + AutoUtils.position.setRot(Math.toDegrees(Math.atan((point[0] - x) / (point[1] - y)))); + } else { + throw new IllegalArgumentException(); + } + } + } + + public AutoMoveTo(String[] args) { + // requires(Drivetrain); + double rotation; + double[] point = { 0, 0 }; + for (String arg : args) { + if (AutoUtils.isDouble(arg)) { + rotation = Double.valueOf(arg); + double relrotation = rotation - AutoUtils.position.getRot(); + addSequential(new PIDTurn(relrotation, Robot.dt, Robot.sd, Robot.dt.getDistEncAvg())); + AutoUtils.position.setRot(rotation); + } else if (AutoUtils.isPoint(arg)) { + point = AutoUtils.parsePoint(arg); + double deltaX = point[0] - AutoUtils.position.getX(); + double deltaY = point[1] - AutoUtils.position.getY(); + double atan = Math.toDegrees(Math.atan(deltaX / deltaY)); + double relrotation = atan - AutoUtils.position.getRot(); + addSequential(new PIDTurn(relrotation, Robot.dt, Robot.sd, Robot.dt.getDistEncAvg())); + + double dX2 = deltaX * deltaX; + double dY2 = deltaY * deltaY; + double distance = Math.sqrt(dX2 + dY2); + addSequential(new PIDMove(distance, Robot.dt, Robot.sd, Robot.dt.getDistEncAvg())); double x = AutoUtils.position.getX(); double y = AutoUtils.position.getY(); AutoUtils.position.setX(point[0]); diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/RunScript.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/RunScript.java index d4cb402..2c0bc59 100644 --- a/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/RunScript.java +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/RunScript.java @@ -4,7 +4,6 @@ import org.usfirst.frc.team199.Robot2018.Robot; import org.usfirst.frc.team199.Robot2018.autonomous.AutoUtils; -import org.usfirst.frc.team199.Robot2018.autonomous.PIDSourceAverage; import edu.wpi.first.wpilibj.command.CommandGroup; import edu.wpi.first.wpilibj.command.WaitCommand; @@ -23,19 +22,19 @@ public RunScript(String scriptName) { switch (cmdName) { case "moveto": - addSequential(new AutoMoveTo(cmdArgs.split(" "), Robot.dt, Robot.sd, new PIDSourceAverage(null, null))); + addSequential(new AutoMoveTo(cmdArgs.split(" "))); break; case "turn": double rotation = Double.parseDouble(cmdArgs); addSequential(new PIDTurn(rotation, Robot.dt, Robot.sd, Robot.dt.getGyro())); - AutoUtils.setRot(rotation); + AutoUtils.position.setRot(rotation); break; case "move": double distance = Double.parseDouble(cmdArgs); - addSequential(new PIDMove(distance, Robot.dt, Robot.sd, new PIDSourceAverage(null, null))); - AutoUtils.setX(distance * Math.sin(Math.toRadians(AutoUtils.getRot()))); - AutoUtils.setY(distance * Math.cos(Math.toRadians(AutoUtils.getRot()))); + addSequential(new PIDMove(distance, Robot.dt, Robot.sd, Robot.dt.getDistEncAvg())); + AutoUtils.position.setX(distance * Math.sin(Math.toRadians(AutoUtils.position.getRot()))); + AutoUtils.position.setY(distance * Math.cos(Math.toRadians(AutoUtils.position.getRot()))); break; case "switch": addSequential(new EjectToSwitch()); From 8ca27e3ea5be3848818b335da97cd79ff12a3ef9 Mon Sep 17 00:00:00 2001 From: Alex Binnard Date: Sat, 10 Feb 2018 16:30:43 -0800 Subject: [PATCH 32/43] fix my horrible redunant code and Corvin's test --- .../Robot2018/commands/AutoMoveTo.java | 31 +---- .../frc/team199/Robot2018/AutoMoveToTest.java | 114 +++++++++--------- 2 files changed, 56 insertions(+), 89 deletions(-) diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/AutoMoveTo.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/AutoMoveTo.java index ddde320..ec04c61 100644 --- a/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/AutoMoveTo.java +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/AutoMoveTo.java @@ -50,35 +50,6 @@ public AutoMoveTo(String[] args, DrivetrainInterface dt, SmartDashboardInterface } public AutoMoveTo(String[] args) { - // requires(Drivetrain); - double rotation; - double[] point = { 0, 0 }; - for (String arg : args) { - if (AutoUtils.isDouble(arg)) { - rotation = Double.valueOf(arg); - double relrotation = rotation - AutoUtils.position.getRot(); - addSequential(new PIDTurn(relrotation, Robot.dt, Robot.sd, Robot.dt.getDistEncAvg())); - AutoUtils.position.setRot(rotation); - } else if (AutoUtils.isPoint(arg)) { - point = AutoUtils.parsePoint(arg); - double deltaX = point[0] - AutoUtils.position.getX(); - double deltaY = point[1] - AutoUtils.position.getY(); - double atan = Math.toDegrees(Math.atan(deltaX / deltaY)); - double relrotation = atan - AutoUtils.position.getRot(); - addSequential(new PIDTurn(relrotation, Robot.dt, Robot.sd, Robot.dt.getDistEncAvg())); - - double dX2 = deltaX * deltaX; - double dY2 = deltaY * deltaY; - double distance = Math.sqrt(dX2 + dY2); - addSequential(new PIDMove(distance, Robot.dt, Robot.sd, Robot.dt.getDistEncAvg())); - double x = AutoUtils.position.getX(); - double y = AutoUtils.position.getY(); - AutoUtils.position.setX(point[0]); - AutoUtils.position.setY(point[1]); - AutoUtils.position.setRot(Math.toDegrees(Math.atan((point[0] - x) / (point[1] - y)))); - } else { - throw new IllegalArgumentException(); - } - } + this(args, Robot.dt, Robot.sd, Robot.dt.getDistEncAvg()); } } diff --git a/Robot2018/test/org/usfirst/frc/team199/Robot2018/AutoMoveToTest.java b/Robot2018/test/org/usfirst/frc/team199/Robot2018/AutoMoveToTest.java index b14623f..27d2560 100644 --- a/Robot2018/test/org/usfirst/frc/team199/Robot2018/AutoMoveToTest.java +++ b/Robot2018/test/org/usfirst/frc/team199/Robot2018/AutoMoveToTest.java @@ -1,40 +1,30 @@ package org.usfirst.frc.team199.Robot2018; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.usfirst.frc.team199.Robot2018.autonomous.AutoUtils; +import org.usfirst.frc.team199.Robot2018.commands.AutoMoveTo; +import org.usfirst.frc.team199.Robot2018.subsystems.DrivetrainInterface; -import static org.mockito.Mockito.*; - -import edu.wpi.first.wpilibj.command.Command; -import edu.wpi.first.wpilibj.command.CommandGroup; -import edu.wpi.first.wpilibj.command.Subsystem; -import edu.wpi.first.wpilibj.internal.HardwareTimer; import edu.wpi.first.wpilibj.HLUsageReporting; import edu.wpi.first.wpilibj.PIDController; +import edu.wpi.first.wpilibj.PIDOutput; import edu.wpi.first.wpilibj.PIDSource; import edu.wpi.first.wpilibj.PIDSourceType; import edu.wpi.first.wpilibj.Timer; -import edu.wpi.first.wpilibj.PIDOutput; -import edu.wpi.first.wpilibj.livewindow.LiveWindow; - -import com.kauailabs.navx.frc.AHRS; - -import org.usfirst.frc.team199.Robot2018.commands.Autonomous; -import org.usfirst.frc.team199.Robot2018.commands.TeleopDrive; -import org.usfirst.frc.team199.Robot2018.commands.Autonomous.Position; -import org.usfirst.frc.team199.Robot2018.commands.Autonomous.Strategy; -import org.usfirst.frc.team199.Robot2018.subsystems.DrivetrainInterface; -import org.usfirst.frc.team199.Robot2018.autonomous.AutoUtils; -import org.usfirst.frc.team199.Robot2018.commands.AutoMoveTo; - -import java.util.HashMap; -import java.util.Map; +import edu.wpi.first.wpilibj.command.Command; +import edu.wpi.first.wpilibj.command.CommandGroup; +import edu.wpi.first.wpilibj.command.Subsystem; +import edu.wpi.first.wpilibj.internal.HardwareTimer; class AutoMoveToTest { - // May need a BeforeEach to reset AutoUtils. + // May need a BeforeEach to reset AutoUtils @BeforeEach void setUp() { @@ -48,90 +38,96 @@ void setUp() { HLUsageReporting.Interface usageReporter = mock(HLUsageReporting.Interface.class); HLUsageReporting.SetImplementation(usageReporter); } - + @Test void testWPICommand() { Command command = new Command() { - protected boolean isFinished() { return false; } + protected boolean isFinished() { + return false; + } }; assertNotNull(command); } - + @Test void testWPICommandGroup() { CommandGroup group = new CommandGroup(); assertNotNull(group); Command command = new Command() { - protected boolean isFinished() { return false; } + protected boolean isFinished() { + return false; + } }; group.addSequential(command); } - + @Test void testPIDController() { PIDSource source = mock(PIDSource.class); - PIDOutput output = mock(PIDOutput.class); - + PIDOutput output = mock(PIDOutput.class); + PIDController ctrl = new PIDController(0, 0, 0, source, output); assertNotNull(ctrl); } - - //@Test + + // @Test // Problem instantiating Subsystem because of SendableBase using network tables. void testWPISubsystem() { - //LiveWindow.setEnabled(false); + // LiveWindow.setEnabled(false); Subsystem subsystem = new Subsystem() { protected void initDefaultCommand() { setDefaultCommand(new Command() { - protected boolean isFinished() { return false; } + protected boolean isFinished() { + return false; + } }); } }; assertNotNull(subsystem); } - + @Test void testForwardAndRight() { - String[] args = {"(0,12)","(12,12)"}; - - AutoUtils.setRot(0); - AutoUtils.setX(0); - AutoUtils.setY(0); - + String[] args = { "(0,12)", "(12,12)" }; + + AutoUtils.position.setRot(0); + AutoUtils.position.setX(0); + AutoUtils.position.setY(0); + PIDSource pidGyroSrc = mock(PIDSource.class); when(pidGyroSrc.getPIDSourceType()).thenReturn(PIDSourceType.kDisplacement); DrivetrainInterface dt = mock(DrivetrainInterface.class); - when(dt.getGyro()).thenReturn(pidGyroSrc); + when(dt.getGyro()).thenReturn(pidGyroSrc); SmartDashboardInterface sd = mock(SmartDashboardInterface.class); PIDSource pidMoveSrc = mock(PIDSource.class); - + AutoMoveTo testAMT = new AutoMoveTo(args, dt, sd, pidMoveSrc); - - assertEquals(90, AutoUtils.getRot()); - assertEquals(12, AutoUtils.getX()); - assertEquals(12, AutoUtils.getY()); + + assertEquals(90, AutoUtils.position.getRot()); + assertEquals(12, AutoUtils.position.getX()); + assertEquals(12, AutoUtils.position.getY()); } @Test void testForward() { - String[] args = {"(0,12)"}; - - AutoUtils.setRot(0); - AutoUtils.setX(0); - AutoUtils.setY(0); - + String[] args = { "(0,12)" }; + + AutoUtils.position.setRot(0); + AutoUtils.position.setX(0); + AutoUtils.position.setY(0); + PIDSource pidGyroSrc = mock(PIDSource.class); when(pidGyroSrc.getPIDSourceType()).thenReturn(PIDSourceType.kDisplacement); DrivetrainInterface dt = mock(DrivetrainInterface.class); - when(dt.getGyro()).thenReturn(pidGyroSrc); + when(dt.getGyro()).thenReturn(pidGyroSrc); SmartDashboardInterface sd = mock(SmartDashboardInterface.class); PIDSource pidMoveSrc = mock(PIDSource.class); - + AutoMoveTo testAMT = new AutoMoveTo(args, dt, sd, pidMoveSrc); - - assertEquals(0, AutoUtils.getRot()); - assertEquals(0, AutoUtils.getX()); - assertEquals(12, AutoUtils.getY()); + + assertEquals(0, AutoUtils.position.getRot()); + assertEquals(0, AutoUtils.position.getX()); + assertEquals(12, AutoUtils.position.getY()); } } From 2c2738c6ef9e19f0da6c9e63225f7401d0ed821a Mon Sep 17 00:00:00 2001 From: DriverStationComputer Date: Sun, 11 Feb 2018 11:27:58 -0800 Subject: [PATCH 33/43] latest changes so code can run on robot includes shuffleboard json file intake srx commented out so doesn't look for motor that's not there some changes to calcDefkD method --- .../frc/team199/Robot2018/RobotMap.java | 14 ++-- .../Robot2018/subsystems/IntakeEject.java | 11 +-- shuffleboard.json | 84 +++++++++++++++++++ 3 files changed, 95 insertions(+), 14 deletions(-) create mode 100644 shuffleboard.json diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/RobotMap.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/RobotMap.java index 6ecccae..1854b28 100644 --- a/Robot2018/src/org/usfirst/frc/team199/Robot2018/RobotMap.java +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/RobotMap.java @@ -63,7 +63,7 @@ public class RobotMap { public static AHRS fancyGyro; public static DoubleSolenoid dtGear; - private final double DIST_PER_PULSE_RATIO = (5.0 * Math.PI) * (17 / 25) / (3 * 256); + private final double DIST_PER_PULSE_RATIO = (5.0 * Math.PI) * (17.0 / 25) / (3.0 * 256); /** * This function takes in a TalonSRX motorController and sets nominal and peak @@ -123,8 +123,8 @@ public RobotMap() { leftEncDist.setPIDSourceType(PIDSourceType.kDisplacement); leftEncRate = new Encoder(leftEncPort1, leftEncPort2); leftEncRate.setPIDSourceType(PIDSourceType.kRate); - leftEncDist.setDistancePerPulse(Robot.getConst("DPP", 0.013908)); - leftEncRate.setDistancePerPulse(Robot.getConst("DPP", 0.013908)); + leftEncDist.setDistancePerPulse(Robot.getConst("DPP", DIST_PER_PULSE_RATIO)); + leftEncRate.setDistancePerPulse(Robot.getConst("DPP", DIST_PER_PULSE_RATIO)); dtLeftMaster = new WPI_TalonSRX(getPort("LeftTalonSRXMaster", 1)); configSRX(dtLeftMaster); @@ -150,8 +150,8 @@ public RobotMap() { rightEncDist.setPIDSourceType(PIDSourceType.kDisplacement); rightEncRate = new Encoder(rightEncPort1, rightEncPort2); rightEncRate.setPIDSourceType(PIDSourceType.kRate); - rightEncDist.setDistancePerPulse(Robot.getConst("DPP", 0.013908)); - rightEncRate.setDistancePerPulse(Robot.getConst("DPP", 0.013908)); + rightEncDist.setDistancePerPulse(Robot.getConst("DPP", DIST_PER_PULSE_RATIO)); + rightEncRate.setDistancePerPulse(Robot.getConst("DPP", DIST_PER_PULSE_RATIO)); dtRightMaster = new WPI_TalonSRX(getPort("RightTalonSRXMaster", 4)); configSRX(dtRightMaster); @@ -215,7 +215,7 @@ public double calcDefkD() { double gearReduction = Robot.getBool("High Gear", false) ? Robot.getConst("High Gear Gear Reduction", 5.392) : Robot.getConst("Low Gear Gear Reduction", 12.255); double radius = Robot.getConst("Radius of Drivetrain Wheel", 0.0635); - double timeConstant = Robot.getConst("Omega Max", 5330) / gearReduction + double timeConstant = Robot.getConst("Omega Max", 5330) / gearReduction / 60 * 2 * Math.PI * convertNtokG(Robot.getConst("Weight of Robot", 342)) / 2 * radius * radius / (Robot.getConst("Stall Torque", 2.41) * gearReduction * 2); double cycleTime = Robot.getConst("Code cycle time", 0.05); @@ -223,7 +223,7 @@ public double calcDefkD() { * The denominator of kD is 1-(e ^ -cycleTime / timeConstant). The numerator is * one. */ - double denominator = 1 - Math.pow(Math.E, -1 * cycleTime / timeConstant); + double denominator = Math.pow(Math.E, 1 * cycleTime / timeConstant) - 1; return 1 / denominator; } diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/subsystems/IntakeEject.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/subsystems/IntakeEject.java index 47ee5b8..734ee03 100644 --- a/Robot2018/src/org/usfirst/frc/team199/Robot2018/subsystems/IntakeEject.java +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/subsystems/IntakeEject.java @@ -1,9 +1,5 @@ package org.usfirst.frc.team199.Robot2018.subsystems; -import org.usfirst.frc.team199.Robot2018.RobotMap; - -import com.ctre.phoenix.motorcontrol.can.WPI_TalonSRX; - import edu.wpi.first.wpilibj.command.Subsystem; /** @@ -11,7 +7,7 @@ */ public class IntakeEject extends Subsystem implements IntakeEjectInterface { - private final WPI_TalonSRX intakeMotor = RobotMap.intakeMotor; + // private final WPI_TalonSRX intakeMotor = RobotMap.intakeMotor; /** * Set the default command for a subsystem here. @@ -25,7 +21,8 @@ public void initDefaultCommand() { * returns current motor value */ public double getIntakeSpeed() { - return intakeMotor.get(); + // return intakeMotor.get(); + return 0; } /** @@ -48,7 +45,7 @@ public boolean hasCube() { * */ public void stopIntake() { - intakeMotor.stopMotor(); + // intakeMotor.stopMotor(); } /** diff --git a/shuffleboard.json b/shuffleboard.json new file mode 100644 index 0000000..f4e5b58 --- /dev/null +++ b/shuffleboard.json @@ -0,0 +1,84 @@ +{ + "dividerPosition": 0.29673590504451036, + "tabPane": [ + { + "title": "PID Testing", + "autoPopulate": false, + "autoPopulatePrefix": "", + "widgetPane": { + "gridSize": 128.0, + "showGrid": true, + "hgap": 16.0, + "vgap": 16.0, + "tiles": { + "0,0": { + "size": [ + 2, + 2 + ], + "content": { + "_type": "PID Controller", + "_source0": "network_table:///SmartDashboard/Left PID Controller", + "_title": "SmartDashboard/Left PID Controller" + } + }, + "2,0": { + "size": [ + 2, + 2 + ], + "content": { + "_type": "PID Controller", + "_source0": "network_table:///SmartDashboard/Right PID Controller", + "_title": "SmartDashboard/Right PID Controller" + } + }, + "0,2": { + "size": [ + 1, + 1 + ], + "content": { + "_type": "Text View", + "_source0": "network_table:///SmartDashboard/Left Enc Rate", + "_title": "SmartDashboard/Left Enc Rate" + } + }, + "0,3": { + "size": [ + 1, + 1 + ], + "content": { + "_type": "Text View", + "_source0": "network_table:///SmartDashboard/Left VPID Error", + "_title": "SmartDashboard/Left VPID Error" + } + }, + "2,2": { + "size": [ + 1, + 1 + ], + "content": { + "_type": "Text View", + "_source0": "network_table:///SmartDashboard/Right Enc Rate", + "_title": "SmartDashboard/Right Enc Rate" + } + }, + "2,3": { + "size": [ + 1, + 1 + ], + "content": { + "_type": "Text View", + "_source0": "network_table:///SmartDashboard/Right VPID Error", + "_title": "SmartDashboard/Right VPID Error" + } + } + } + } + } + ] +} \ No newline at end of file From a8b87302b9a5d2530b7f037327d40050c17de1c2 Mon Sep 17 00:00:00 2001 From: DriverStationComputer Date: Sun, 11 Feb 2018 14:55:42 -0800 Subject: [PATCH 34/43] happy VPID! VPID is tuned and working in both high and low gears --- .../frc/team199/Robot2018/RobotMap.java | 20 +++++----- .../autonomous/VelocityPIDController.java | 2 +- .../Robot2018/commands/TeleopDrive.java | 4 +- .../Robot2018/subsystems/Drivetrain.java | 38 +++++++++++++++++-- 4 files changed, 47 insertions(+), 17 deletions(-) diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/RobotMap.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/RobotMap.java index 1854b28..be0dc07 100644 --- a/Robot2018/src/org/usfirst/frc/team199/Robot2018/RobotMap.java +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/RobotMap.java @@ -135,8 +135,8 @@ public RobotMap() { dtLeft.setInverted(true); leftVelocityController = new VelocityPIDController(Robot.getConst("VelocityLeftkI", 0), 0, - Robot.getConst("VelocityLeftkD", calcDefkD()), 1 / Robot.getConst("Max Low Speed", 84), leftEncRate, - dtLeft); + Robot.getConst("VelocityLeftkD", calcDefkD(Robot.getConst("Max Low Speed", 84))), + 1 / Robot.getConst("Max Low Speed", 84), leftEncRate, dtLeft); leftVelocityController.setInputRange(-Robot.getConst("Max High Speed", 204), Robot.getConst("Max High Speed", 204)); leftVelocityController.setOutputRange(-1.0, 1.0); @@ -152,6 +152,7 @@ public RobotMap() { rightEncRate.setPIDSourceType(PIDSourceType.kRate); rightEncDist.setDistancePerPulse(Robot.getConst("DPP", DIST_PER_PULSE_RATIO)); rightEncRate.setDistancePerPulse(Robot.getConst("DPP", DIST_PER_PULSE_RATIO)); + rightEncRate.setReverseDirection(true); dtRightMaster = new WPI_TalonSRX(getPort("RightTalonSRXMaster", 4)); configSRX(dtRightMaster); @@ -162,18 +163,17 @@ public RobotMap() { dtRight.setInverted(true); rightVelocityController = new VelocityPIDController(Robot.getConst("VelocityRightkI", 0), 0, - Robot.getConst("VelocityRightkD", calcDefkD()), 1 / Robot.getConst("Max Low Speed", 84), rightEncRate, - dtRight); + Robot.getConst("VelocityRightkD", calcDefkD(Robot.getConst("Max Low Speed", 84))), + 1 / Robot.getConst("Max Low Speed", 84), rightEncRate, dtRight); rightVelocityController.setInputRange(-Robot.getConst("Max High Speed", 204), Robot.getConst("Max High Speed", 204)); rightVelocityController.setOutputRange(-1.0, 1.0); rightVelocityController.setContinuous(false); rightVelocityController.setAbsoluteTolerance(Robot.getConst("VelocityToleranceRight", 2)); - // robotDrive = new DifferentialDrive(leftVelocityController, - // rightVelocityController); - // robotDrive.setMaxOutput(Robot.getConst("Max High Speed", 204)); - robotDrive = new DifferentialDrive(dtLeft, dtRight); + robotDrive = new DifferentialDrive(leftVelocityController, rightVelocityController); + robotDrive.setMaxOutput(Robot.getConst("Max High Speed", 204)); + // robotDrive = new DifferentialDrive(dtLeft, dtRight); distEncAvg = new PIDSourceAverage(leftEncDist, rightEncDist); fancyGyro = new AHRS(SerialPort.Port.kMXP); @@ -203,7 +203,7 @@ public int getPort(String key, int def) { /** * Uses SmartDashboard and math to calculate a *great* default kD */ - public double calcDefkD() { + public double calcDefkD(double maxSpeed) { /* * timeConstant is proportional to max speed of the shaft (which is the max * speed of the cim divided by the gear reduction), half the mass (because the @@ -224,7 +224,7 @@ public double calcDefkD() { * one. */ double denominator = Math.pow(Math.E, 1 * cycleTime / timeConstant) - 1; - return 1 / denominator; + return 1 / denominator / maxSpeed; } private double convertLbsTokG(double lbs) { diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/autonomous/VelocityPIDController.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/autonomous/VelocityPIDController.java index 416f167..5ce91cc 100644 --- a/Robot2018/src/org/usfirst/frc/team199/Robot2018/autonomous/VelocityPIDController.java +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/autonomous/VelocityPIDController.java @@ -64,7 +64,7 @@ public void set(double speed) { */ @Override public double get() { - return getSetpoint(); + return out.get(); } /** diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/TeleopDrive.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/TeleopDrive.java index 47e36e4..64b6b14 100644 --- a/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/TeleopDrive.java +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/TeleopDrive.java @@ -23,14 +23,14 @@ public TeleopDrive() { // Called just before this Command runs the first time @Override protected void initialize() { - // Robot.dt.enableVelocityPIDs(); + Robot.dt.enableVelocityPIDs(); } // Called repeatedly when this Command is scheduled to run @Override protected void execute() { Robot.dt.resetVPIDInputRanges(); - // Robot.dt.updatePidConstants(); + Robot.dt.updatePidConstants(); Robot.dt.teleopDrive(); } diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/subsystems/Drivetrain.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/subsystems/Drivetrain.java index 8969b31..9e1b29c 100644 --- a/Robot2018/src/org/usfirst/frc/team199/Robot2018/subsystems/Drivetrain.java +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/subsystems/Drivetrain.java @@ -195,10 +195,8 @@ public double getDtRightSpeed() { */ @Override public void updatePidConstants() { - leftVelocityController.setPID(Robot.getConst("VelocityLeftkI", 0), Robot.getConst("VelocityLeftkI", 0), - Robot.getConst("VelocityLeftkD", 0)); - rightVelocityController.setPID(Robot.getConst("VelocityRightkI", 0), Robot.getConst("VelocityRightkI", 0), - Robot.getConst("VelocityRightkD", 0)); + leftVelocityController.setPID(Robot.getConst("VelocityLeftkI", 0), 0, calcDefkD(getCurrentMaxSpeed())); + rightVelocityController.setPID(Robot.getConst("VelocityRightkI", 0), 0, calcDefkD(getCurrentMaxSpeed())); resetVelocityPIDkFConsts(); } @@ -366,4 +364,36 @@ public void putVelocityControllersToDashboard() { SmartDashboard.putData("Left PID Controller", leftVelocityController); SmartDashboard.putData("Right PID Controller", rightVelocityController); } + + /** + * Uses SmartDashboard and math to calculate a *great* default kD + */ + public double calcDefkD(double maxSpeed) { + /* + * timeConstant is proportional to max speed of the shaft (which is the max + * speed of the cim divided by the gear reduction), half the mass (because the + * half of the drivetrain only has to support half of the robot), and radius of + * the drivetrain wheels squared. It's inversely proportional to the stall + * torque of the shaft, which is found by multiplying the stall torque of the + * motor with the gear reduction by the amount of motors. + */ + double gearReduction = Robot.getBool("High Gear", false) ? Robot.getConst("High Gear Gear Reduction", 5.392) + : Robot.getConst("Low Gear Gear Reduction", 12.255); + double radius = Robot.getConst("Radius of Drivetrain Wheel", 0.0635); + double timeConstant = Robot.getConst("Omega Max", 5330) / gearReduction / 60 * 2 * Math.PI + * convertNtokG(Robot.getConst("Weight of Robot", 342)) / 2 * radius * radius + / (Robot.getConst("Stall Torque", 2.41) * gearReduction * 2); + double cycleTime = Robot.getConst("Code cycle time", 0.05); + /* + * The denominator of kD is 1-(e ^ -cycleTime / timeConstant). The numerator is + * one. + */ + double denominator = Math.pow(Math.E, 1 * cycleTime / timeConstant) - 1; + return 1 / denominator / maxSpeed; + } + + private double convertNtokG(double newtons) { + // weight / accel due to grav = kg + return newtons / 9.81; + } } From 2146a504d324afb3cb2421912765d0cbd924fbe7 Mon Sep 17 00:00:00 2001 From: DriverStationComputer Date: Sun, 11 Feb 2018 14:56:12 -0800 Subject: [PATCH 35/43] new shuffleboard layout --- shuffleboard.json | 192 ++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 178 insertions(+), 14 deletions(-) diff --git a/shuffleboard.json b/shuffleboard.json index f4e5b58..d6e9722 100644 --- a/shuffleboard.json +++ b/shuffleboard.json @@ -1,5 +1,5 @@ { - "dividerPosition": 0.29673590504451036, + "dividerPosition": 0.2678041543026706, "tabPane": [ { "title": "PID Testing", @@ -13,7 +13,7 @@ "tiles": { "0,0": { "size": [ - 2, + 1, 2 ], "content": { @@ -22,9 +22,9 @@ "_title": "SmartDashboard/Left PID Controller" } }, - "2,0": { + "1,0": { "size": [ - 2, + 1, 2 ], "content": { @@ -35,16 +35,17 @@ }, "0,2": { "size": [ - 1, - 1 + 2, + 2 ], "content": { - "_type": "Text View", + "_type": "Graph", "_source0": "network_table:///SmartDashboard/Left Enc Rate", - "_title": "SmartDashboard/Left Enc Rate" + "_title": "/SmartDashboard/Left Enc Rate", + "Visible time": 30.0 } }, - "0,3": { + "5,2": { "size": [ 1, 1 @@ -57,16 +58,17 @@ }, "2,2": { "size": [ - 1, - 1 + 2, + 2 ], "content": { - "_type": "Text View", + "_type": "Graph", "_source0": "network_table:///SmartDashboard/Right Enc Rate", - "_title": "SmartDashboard/Right Enc Rate" + "_title": "/SmartDashboard/Right Enc Rate", + "Visible time": 30.0 } }, - "2,3": { + "5,3": { "size": [ 1, 1 @@ -76,6 +78,168 @@ "_source0": "network_table:///SmartDashboard/Right VPID Error", "_title": "SmartDashboard/Right VPID Error" } + }, + "2,0": { + "size": [ + 2, + 2 + ], + "content": { + "_type": "Graph", + "_source0": "network_table:///SmartDashboard/Drivetrain/Left VPID Targ", + "_title": "SmartDashboard/Drivetrain/Left VPID Targ", + "Visible time": 30.0, + "SmartDashboard/Drivetrain/Left VPID Targ visible": true + } + }, + "4,0": { + "size": [ + 2, + 2 + ], + "content": { + "_type": "Graph", + "_source0": "network_table:///SmartDashboard/Drivetrain/Right VPID Targ", + "_title": "SmartDashboard/Drivetrain/Right VPID Targ", + "Visible time": 30.0, + "SmartDashboard/Drivetrain/Right VPID Targ visible": true + } + }, + "4,3": { + "size": [ + 1, + 1 + ], + "content": { + "_type": "Text View", + "_source0": "network_table:///SmartDashboard/Const/VelocityRightkI", + "_title": "SmartDashboard/Const/VelocityRightkI" + } + }, + "4,2": { + "size": [ + 1, + 1 + ], + "content": { + "_type": "Text View", + "_source0": "network_table:///SmartDashboard/Const/VelocityLeftkI", + "_title": "SmartDashboard/Const/VelocityLeftkI" + } + } + } + } + }, + { + "title": "Drivetrain", + "autoPopulate": true, + "autoPopulatePrefix": "SmartDashboard/Drivetrain", + "widgetPane": { + "gridSize": 128.0, + "showGrid": true, + "hgap": 16.0, + "vgap": 16.0, + "tiles": { + "0,0": { + "size": [ + 1, + 1 + ], + "content": { + "_type": "Text View", + "_source0": "network_table:///SmartDashboard/Drivetrain/Left VPID Targ", + "_title": "Left VPID Targ" + } + }, + "1,0": { + "size": [ + 1, + 1 + ], + "content": { + "_type": "Text View", + "_source0": "network_table:///SmartDashboard/Drivetrain/Right VPID Targ", + "_title": "Right VPID Targ" + } + }, + "2,0": { + "size": [ + 1, + 1 + ], + "content": { + "_type": "Text View", + "_source0": "network_table:///SmartDashboard/Drivetrain/Current Max Speed", + "_title": "Current Max Speed" + } + }, + "3,0": { + "size": [ + 1, + 1 + ], + "content": { + "_type": "Text View", + "_source0": "network_table:///SmartDashboard/Drivetrain/Left Enc Dist", + "_title": "Left Enc Dist" + } + }, + "4,0": { + "size": [ + 1, + 1 + ], + "content": { + "_type": "Text View", + "_source0": "network_table:///SmartDashboard/Drivetrain/Left Enc Rate", + "_title": "Left Enc Rate" + } + }, + "5,0": { + "size": [ + 1, + 1 + ], + "content": { + "_type": "Text View", + "_source0": "network_table:///SmartDashboard/Drivetrain/Right Enc Dist", + "_title": "Right Enc Dist" + } + }, + "0,1": { + "size": [ + 1, + 1 + ], + "content": { + "_type": "Text View", + "_source0": "network_table:///SmartDashboard/Drivetrain/Right Enc Rate", + "_title": "Right Enc Rate" + } + }, + "1,1": { + "size": [ + 1, + 1 + ], + "content": { + "_type": "Text View", + "_source0": "network_table:///SmartDashboard/Drivetrain/Enc Avg Dist", + "_title": "Enc Avg Dist" + } + }, + "0,2": { + "size": [ + 1, + 1 + ], + "content": { + "_type": "Boolean Box", + "_source0": "network_table:///SmartDashboard/Bool/High Gear", + "_title": "SmartDashboard/Bool/High Gear", + "colorWhenTrue": "#7CFC00FF", + "colorWhenFalse": "#8B0000FF" + } } } } From 230af700c391ad076710d0d91860b369af8fabc2 Mon Sep 17 00:00:00 2001 From: doawelul Date: Sun, 11 Feb 2018 19:54:11 -0800 Subject: [PATCH 36/43] changed intake and outake functionality to whenPressed and added flexibility to intakeEject solenoids --- .../org/usfirst/frc/team199/Robot2018/OI.java | 5 +- .../Robot2018/commands/IntakeCube.java | 60 +++++++++---------- .../Robot2018/commands/OutakeCube.java | 43 +++++++++++++ .../team199/Robot2018/commands/RunScript.java | 6 +- .../Robot2018/subsystems/IntakeEject.java | 40 ++++++++++--- 5 files changed, 108 insertions(+), 46 deletions(-) create mode 100644 Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/OutakeCube.java diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/OI.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/OI.java index 4c62829..dfffcb4 100644 --- a/Robot2018/src/org/usfirst/frc/team199/Robot2018/OI.java +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/OI.java @@ -11,6 +11,7 @@ import org.usfirst.frc.team199.Robot2018.commands.IntakeCube; import org.usfirst.frc.team199.Robot2018.commands.LowerIntake; import org.usfirst.frc.team199.Robot2018.commands.OpenIntake; +import org.usfirst.frc.team199.Robot2018.commands.OutakeCube; import org.usfirst.frc.team199.Robot2018.commands.PIDMove; import org.usfirst.frc.team199.Robot2018.commands.PIDTurn; import org.usfirst.frc.team199.Robot2018.commands.RaiseIntake; @@ -86,8 +87,8 @@ public OI() { lowerIntake = new JoystickButton(manipulator, getButton("Lower Intake Button", 4)); lowerIntake.whenPressed(new LowerIntake()); intake = new JoystickButton(manipulator, getButton("Intake Button", 5)); - intake.whileHeld(new IntakeCube(true)); + intake.whenPressed(new IntakeCube()); outake = new JoystickButton(manipulator, getButton("Outake Button", 6)); - outake.whileHeld(new IntakeCube(false)); + outake.whenPressed(new OutakeCube()); } } diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/IntakeCube.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/IntakeCube.java index 53e79cc..5557563 100644 --- a/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/IntakeCube.java +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/IntakeCube.java @@ -9,37 +9,31 @@ */ public class IntakeCube extends Command { - boolean in; - public IntakeCube(boolean in) { - // Use requires() here to declare subsystem dependencies - // eg. requires(chassis); - this.in = in; - } - - // Called just before this Command runs the first time - protected void initialize() { - } - - // Called repeatedly when this Command is scheduled to run - protected void execute() { - if(in) { - Robot.intakeEject.runIntake(1); - } else { - Robot.intakeEject.runIntake(-1); - } - } - - // Make this return true when this Command no longer needs to run execute() - protected boolean isFinished() { - return false; - } - - // 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() { - } + public IntakeCube() { + // Use requires() here to declare subsystem dependencies + // eg. requires(chassis); + } + + // Called just before this Command runs the first time + protected void initialize() { + } + + // Called repeatedly when this Command is scheduled to run + protected void execute() { + Robot.intakeEject.runIntake(1); + } + + // Make this return true when this Command no longer needs to run execute() + protected boolean isFinished() { + return Robot.intakeEject.hasCube(); + } + + // 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() { + } } diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/OutakeCube.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/OutakeCube.java new file mode 100644 index 0000000..e68dea4 --- /dev/null +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/OutakeCube.java @@ -0,0 +1,43 @@ +package org.usfirst.frc.team199.Robot2018.commands; + +import org.usfirst.frc.team199.Robot2018.Robot; + +import edu.wpi.first.wpilibj.Timer; +import edu.wpi.first.wpilibj.command.Command; + +/** + * + */ +public class OutakeCube extends Command { + + Timer tim; + + public OutakeCube() { + // Use requires() here to declare subsystem dependencies + // eg. requires(chassis); + } + + // Called just before this Command runs the first time + protected void initialize() { + tim = new Timer(); + } + + // Called repeatedly when this Command is scheduled to run + protected void execute() { + Robot.intakeEject.runIntake(-1); + } + + // Make this return true when this Command no longer needs to run execute() + protected boolean isFinished() { + return tim.get() > Robot.getConst("Outake Time", 1); + } + + // 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() { + } +} diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/RunScript.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/RunScript.java index d99c81a..1b700c0 100644 --- a/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/RunScript.java +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/RunScript.java @@ -43,7 +43,7 @@ public RunScript(String scriptName) { addSequential(new WaitCommand(Double.parseDouble(cmdArgs))); break; case "intake": - addSequential(new IntakeCube(true)); + addSequential(new IntakeCube()); break; case "jump": addSequential(new RunScript(cmdArgs)); @@ -54,6 +54,6 @@ public RunScript(String scriptName) { // this should never happen since AutoUtils already validates the script. System.err.println("[ERROR] `" + cmdName + "` is not a valid command name."); } - } - } + } + } } diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/subsystems/IntakeEject.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/subsystems/IntakeEject.java index fcde6b7..ec279e8 100644 --- a/Robot2018/src/org/usfirst/frc/team199/Robot2018/subsystems/IntakeEject.java +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/subsystems/IntakeEject.java @@ -77,31 +77,55 @@ public void runIntake(double speed) { * Raises the intake */ public void raiseIntake() { - leftVerticalSolenoid.set(DoubleSolenoid.Value.kForward); - rightVerticalSolenoid.set(DoubleSolenoid.Value.kForward); + DoubleSolenoid.Value leftSet = Robot.getBool("Intake Left Vertical Solenoid Inverted", false) + ? DoubleSolenoid.Value.kReverse + : DoubleSolenoid.Value.kForward; + DoubleSolenoid.Value rightSet = Robot.getBool("Intake Right Vertical Solenoid Inverted", false) + ? DoubleSolenoid.Value.kReverse + : DoubleSolenoid.Value.kForward; + leftVerticalSolenoid.set(leftSet); + rightVerticalSolenoid.set(rightSet); } /** * Lowers the intake */ public void lowerIntake() { - leftVerticalSolenoid.set(DoubleSolenoid.Value.kReverse); - rightVerticalSolenoid.set(DoubleSolenoid.Value.kReverse); + DoubleSolenoid.Value leftSet = Robot.getBool("Intake Left Vertical Solenoid Inverted", false) + ? DoubleSolenoid.Value.kForward + : DoubleSolenoid.Value.kReverse; + DoubleSolenoid.Value rightSet = Robot.getBool("Intake Right Vertical Solenoid Inverted", false) + ? DoubleSolenoid.Value.kForward + : DoubleSolenoid.Value.kReverse; + leftVerticalSolenoid.set(leftSet); + rightVerticalSolenoid.set(rightSet); } /** * Opens the intake */ public void openIntake() { - leftHorizontalSolenoid.set(DoubleSolenoid.Value.kForward); - rightHorizontalSolenoid.set(DoubleSolenoid.Value.kForward); + DoubleSolenoid.Value leftSet = Robot.getBool("Intake Left Horizontal Solenoid Inverted", false) + ? DoubleSolenoid.Value.kReverse + : DoubleSolenoid.Value.kForward; + DoubleSolenoid.Value rightSet = Robot.getBool("Intake Right Horizontal Solenoid Inverted", false) + ? DoubleSolenoid.Value.kReverse + : DoubleSolenoid.Value.kForward; + leftHorizontalSolenoid.set(leftSet); + rightHorizontalSolenoid.set(rightSet); } /** * Closes the intake */ public void closeIntake() { - leftHorizontalSolenoid.set(DoubleSolenoid.Value.kForward); - rightHorizontalSolenoid.set(DoubleSolenoid.Value.kForward); + DoubleSolenoid.Value leftSet = Robot.getBool("Intake Left Horizontal Solenoid Inverted", false) + ? DoubleSolenoid.Value.kForward + : DoubleSolenoid.Value.kReverse; + DoubleSolenoid.Value rightSet = Robot.getBool("Intake Right Horizontal Solenoid Inverted", false) + ? DoubleSolenoid.Value.kForward + : DoubleSolenoid.Value.kReverse; + leftHorizontalSolenoid.set(leftSet); + rightHorizontalSolenoid.set(rightSet); } } From a7a86b4638708f1b0d56099dc7200590f8081d28 Mon Sep 17 00:00:00 2001 From: DriverStationComputer Date: Mon, 12 Feb 2018 20:05:41 -0800 Subject: [PATCH 37/43] PID Move testing getting to the point where the robot moves using PIDMove still needs way more testing and tuning --- .../org/usfirst/frc/team199/Robot2018/OI.java | 10 ++++++--- .../usfirst/frc/team199/Robot2018/Robot.java | 21 ++++++++++++++++++ .../team199/Robot2018/commands/PIDMove.java | 13 ++++++++--- .../team199/Robot2018/commands/PIDTurn.java | 7 +++++- .../Robot2018/commands/ResetEncoders.java | 22 +++++++++++++++++++ .../Robot2018/subsystems/Drivetrain.java | 16 ++++++++++++++ 6 files changed, 82 insertions(+), 7 deletions(-) create mode 100644 Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/ResetEncoders.java diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/OI.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/OI.java index 180b92d..7f27f3c 100644 --- a/Robot2018/src/org/usfirst/frc/team199/Robot2018/OI.java +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/OI.java @@ -9,6 +9,7 @@ import org.usfirst.frc.team199.Robot2018.commands.PIDMove; import org.usfirst.frc.team199.Robot2018.commands.PIDTurn; +import org.usfirst.frc.team199.Robot2018.commands.ResetEncoders; import org.usfirst.frc.team199.Robot2018.commands.SetDistancePerPulse; import org.usfirst.frc.team199.Robot2018.commands.ShiftDriveType; import org.usfirst.frc.team199.Robot2018.commands.ShiftHighGear; @@ -31,18 +32,19 @@ public class OI { private JoystickButton shiftDriveTypeButton; private JoystickButton PIDMoveButton; private JoystickButton PIDTurnButton; + private JoystickButton resetEncButton; public Joystick rightJoy; private JoystickButton updatePIDConstantsButton; private JoystickButton updateEncoderDPPButton; public Joystick manipulator; - + public int getButton(String key, int def) { if (!SmartDashboard.containsKey("Button/" + key)) { if (!SmartDashboard.putNumber("Button/" + key, def)) { System.err.println("SmartDashboard Key" + "Button/" + key + "already taken by a different type"); return def; } - } + } return (int) SmartDashboard.getNumber("Button/" + key, def); } @@ -51,9 +53,11 @@ public OI() { shiftDriveTypeButton = new JoystickButton(leftJoy, getButton("Shift Drive Type", 2)); shiftDriveTypeButton.whenPressed(new ShiftDriveType()); PIDMoveButton = new JoystickButton(leftJoy, getButton("PID Move", 7)); - PIDMoveButton.whenPressed(new PIDMove(40, Robot.dt, RobotMap.distEncAvg)); + PIDMoveButton.whenPressed(new PIDMove(Robot.getConst("Move Targ", 24), Robot.dt, RobotMap.distEncAvg)); PIDTurnButton = new JoystickButton(leftJoy, getButton("PID Turn", 8)); PIDTurnButton.whenPressed(new PIDTurn(30, Robot.dt, RobotMap.fancyGyro)); + resetEncButton = new JoystickButton(leftJoy, getButton("Reset Dist Enc", 10)); + resetEncButton.whenPressed(new ResetEncoders()); rightJoy = new Joystick(1); shiftHighGearButton = new JoystickButton(rightJoy, getButton("Shift High Gear", 3)); diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/Robot.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/Robot.java index e2fcf55..08e22ee 100644 --- a/Robot2018/src/org/usfirst/frc/team199/Robot2018/Robot.java +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/Robot.java @@ -178,10 +178,24 @@ public void teleopInit() { @Override public void teleopPeriodic() { Scheduler.getInstance().run(); + + SmartDashboard.putNumber("Drivetrain/Left VPID Targ", Robot.dt.getLeftVPIDSetpoint()); + SmartDashboard.putNumber("Drivetrain/Right VPID Targ", Robot.dt.getRightVPIDSetpoint()); + SmartDashboard.putNumber("Left VPID Error", Robot.dt.getLeftVPIDerror()); + SmartDashboard.putNumber("Right VPID Error", Robot.dt.getRightVPIDerror()); + SmartDashboard.putNumber("Left Enc Rate", Robot.dt.getLeftEncRate()); + SmartDashboard.putNumber("Right Enc Rate", Robot.dt.getRightEncRate()); + + SmartDashboard.putNumber("Left Enc Dist", dt.getLeftDist()); + SmartDashboard.putNumber("Right Enc Dist", dt.getRightDist()); + SmartDashboard.putNumber("Avg Enc Dist", dt.getEncAvgDist()); } boolean firstTime = true; + public void testInit() { + } + /** * This function is called periodically during test mode */ @@ -192,6 +206,9 @@ public void testPeriodic() { // firstTime = false; //// } // Robot.dt.setVPIDs(Robot.getConst("VPID Test Set", 0.5)); + + Scheduler.getInstance().run(); + SmartDashboard.putNumber("Drivetrain/Left VPID Targ", Robot.dt.getLeftVPIDSetpoint()); SmartDashboard.putNumber("Drivetrain/Right VPID Targ", Robot.dt.getRightVPIDSetpoint()); SmartDashboard.putNumber("Left VPID Error", Robot.dt.getLeftVPIDerror()); @@ -199,6 +216,10 @@ public void testPeriodic() { SmartDashboard.putNumber("Left Enc Rate", Robot.dt.getLeftEncRate()); SmartDashboard.putNumber("Right Enc Rate", Robot.dt.getRightEncRate()); + SmartDashboard.putNumber("Left Enc Dist", dt.getLeftDist()); + SmartDashboard.putNumber("Right Enc Dist", dt.getRightDist()); + SmartDashboard.putNumber("Avg Enc Dist", dt.getEncAvgDist()); + // dt.dtLeft.set(0.1); // dt.dtRight.set(-oi.rightJoy.getY()); // dt.dtLeft.set(-oi.leftJoy.getY()); diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/PIDMove.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/PIDMove.java index 4452745..2ea7d8e 100644 --- a/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/PIDMove.java +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/PIDMove.java @@ -39,7 +39,7 @@ public PIDMove(double targ, DrivetrainInterface dt, PIDSourceAverage avg) { target = targ; this.dt = dt; double kf = 1 / (dt.getCurrentMaxSpeed() * Robot.getConst("Default PID Update Time", 0.05)); - moveController = new PIDController(Robot.getConst("MovekP", 1), Robot.getConst("MovekI", 0), + moveController = new PIDController(Robot.getConst("MovekP", 0), Robot.getConst("MovekI", 0), Robot.getConst("MovekD", 0), kf, avg, this); } @@ -50,6 +50,7 @@ public PIDMove(double targ, DrivetrainInterface dt, PIDSourceAverage avg) { @Override public void initialize() { dt.resetDistEncs(); + moveController.disable(); // input is in inches moveController.setInputRange(-Robot.getConst("Max High Speed", 204), Robot.getConst("Max High Speed", 204)); // output in "motor units" (arcade and tank only accept values [-1, 1] @@ -57,8 +58,11 @@ public void initialize() { moveController.setContinuous(false); moveController.setAbsoluteTolerance(Robot.getConst("MoveTolerance", 2)); moveController.setSetpoint(target); + + SmartDashboard.putData("Move PID", moveController); + moveController.enable(); - SmartDashboard.putData(moveController); + dt.enableVelocityPIDs(); } /** @@ -68,6 +72,8 @@ public void initialize() { */ @Override protected void execute() { + SmartDashboard.putNumber("Move PID Result", moveController.get()); + SmartDashboard.putNumber("Move PID Error", moveController.getError()); } /** @@ -89,7 +95,7 @@ protected boolean isFinished() { @Override protected void end() { moveController.disable(); - moveController.free(); + // moveController.free(); } /** @@ -115,5 +121,6 @@ protected void interrupted() { @Override public void pidWrite(double output) { dt.arcadeDrive(output, 0); + SmartDashboard.putNumber("Move PID Output", output); } } diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/PIDTurn.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/PIDTurn.java index 84aaf3c..e37b22f 100644 --- a/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/PIDTurn.java +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/PIDTurn.java @@ -8,6 +8,7 @@ import edu.wpi.first.wpilibj.PIDController; import edu.wpi.first.wpilibj.PIDOutput; import edu.wpi.first.wpilibj.command.Command; +import edu.wpi.first.wpilibj.smartdashboard.SmartDashboard; /** * Turns the robot to a certain target bearing using PID. Implements PIDOutput @@ -63,7 +64,9 @@ protected void initialize() { turnController.setContinuous(); turnController.setAbsoluteTolerance(Robot.getConst("TurnTolerance", 1)); turnController.setSetpoint(target); + SmartDashboard.putData("Turn PID", turnController); turnController.enable(); + dt.enableVelocityPIDs(); } /** @@ -73,6 +76,8 @@ protected void initialize() { */ @Override protected void execute() { + SmartDashboard.putNumber("Turn PID Result", turnController.get()); + SmartDashboard.putNumber("Turn PID Error", turnController.getError()); } /** @@ -95,7 +100,7 @@ protected boolean isFinished() { @Override protected void end() { turnController.disable(); - turnController.free(); + // turnController.free(); } /** diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/ResetEncoders.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/ResetEncoders.java new file mode 100644 index 0000000..758068d --- /dev/null +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/ResetEncoders.java @@ -0,0 +1,22 @@ +package org.usfirst.frc.team199.Robot2018.commands; + +import org.usfirst.frc.team199.Robot2018.Robot; + +import edu.wpi.first.wpilibj.command.InstantCommand; + +/** + * + */ +public class ResetEncoders extends InstantCommand { + + public ResetEncoders() { + // Use requires() here to declare subsystem dependencies + // eg. requires(chassis); + super(); + } + + // Called just before this Command runs the first time + protected void initialize() { + Robot.dt.resetDistEncs(); + } +} diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/subsystems/Drivetrain.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/subsystems/Drivetrain.java index 9e1b29c..5fff0f1 100644 --- a/Robot2018/src/org/usfirst/frc/team199/Robot2018/subsystems/Drivetrain.java +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/subsystems/Drivetrain.java @@ -52,6 +52,22 @@ public void initDefaultCommand() { setDefaultCommand(new TeleopDrive()); } + public PIDSourceAverage getDistEncAvg() { + return distEncAvg; + } + + public double getEncAvgDist() { + return distEncAvg.pidGet(); + } + + public double getLeftDist() { + return leftEncDist.getDistance(); + } + + public double getRightDist() { + return rightEncDist.getDistance(); + } + public void setLeft(double spd) { dtLeft.set(spd); } From f8b8af7c2a9b0970bc21fd16fc753412bea5f09e Mon Sep 17 00:00:00 2001 From: DriverStationComputer Date: Mon, 12 Feb 2018 20:06:26 -0800 Subject: [PATCH 38/43] shuffleboard layout --- shuffleboard.json | 190 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 185 insertions(+), 5 deletions(-) diff --git a/shuffleboard.json b/shuffleboard.json index d6e9722..ed5ffed 100644 --- a/shuffleboard.json +++ b/shuffleboard.json @@ -1,5 +1,5 @@ { - "dividerPosition": 0.2678041543026706, + "dividerPosition": 0.2218100890207715, "tabPane": [ { "title": "PID Testing", @@ -41,8 +41,9 @@ "content": { "_type": "Graph", "_source0": "network_table:///SmartDashboard/Left Enc Rate", - "_title": "/SmartDashboard/Left Enc Rate", - "Visible time": 30.0 + "_title": "SmartDashboard/Left Enc Rate", + "Visible time": 30.0, + "SmartDashboard/Left Enc Rate visible": true } }, "5,2": { @@ -64,8 +65,9 @@ "content": { "_type": "Graph", "_source0": "network_table:///SmartDashboard/Right Enc Rate", - "_title": "/SmartDashboard/Right Enc Rate", - "Visible time": 30.0 + "_title": "SmartDashboard/Right Enc Rate", + "Visible time": 30.0, + "SmartDashboard/Right Enc Rate visible": true } }, "5,3": { @@ -243,6 +245,184 @@ } } } + }, + { + "title": "Move PID Testing", + "autoPopulate": false, + "autoPopulatePrefix": "", + "widgetPane": { + "gridSize": 128.0, + "showGrid": true, + "hgap": 16.0, + "vgap": 16.0, + "tiles": { + "0,2": { + "size": [ + 1, + 1 + ], + "content": { + "_type": "Text View", + "_source0": "network_table:///SmartDashboard/Const/Move Targ", + "_title": "SmartDashboard/Const/Move Targ" + } + }, + "0,0": { + "size": [ + 2, + 2 + ], + "content": { + "_type": "PID Controller", + "_source0": "network_table:///SmartDashboard/Move PID", + "_title": "SmartDashboard/Move PID" + } + }, + "3,0": { + "size": [ + 1, + 1 + ], + "content": { + "_type": "Text View", + "_source0": "network_table:///SmartDashboard/Right Enc Dist", + "_title": "SmartDashboard/Right Enc Dist" + } + }, + "2,0": { + "size": [ + 1, + 1 + ], + "content": { + "_type": "Text View", + "_source0": "network_table:///SmartDashboard/Left Enc Dist", + "_title": "SmartDashboard/Left Enc Dist" + } + }, + "2,2": { + "size": [ + 1, + 1 + ], + "content": { + "_type": "Text View", + "_source0": "network_table:///SmartDashboard/Const/MovekP", + "_title": "SmartDashboard/Const/MovekP" + } + }, + "3,2": { + "size": [ + 1, + 1 + ], + "content": { + "_type": "Text View", + "_source0": "network_table:///SmartDashboard/Const/MovekI", + "_title": "SmartDashboard/Const/MovekI" + } + }, + "4,2": { + "size": [ + 1, + 1 + ], + "content": { + "_type": "Text View", + "_source0": "network_table:///SmartDashboard/Const/MovekD", + "_title": "SmartDashboard/Const/MovekD" + } + }, + "4,0": { + "size": [ + 2, + 2 + ], + "content": { + "_type": "Network Table Tree", + "_source0": "network_table:///LiveWindow/Ungrouped/Scheduler", + "_title": "LiveWindow/Ungrouped/Scheduler" + } + }, + "0,3": { + "size": [ + 1, + 1 + ], + "content": { + "_type": "Text View", + "_source0": "network_table:///SmartDashboard/Move PID Output", + "_title": "SmartDashboard/Move PID Output" + } + }, + "1,3": { + "size": [ + 1, + 1 + ], + "content": { + "_type": "Text View", + "_source0": "network_table:///SmartDashboard/Move PID Result", + "_title": "SmartDashboard/Move PID Result" + } + }, + "2,3": { + "size": [ + 1, + 1 + ], + "content": { + "_type": "Text View", + "_source0": "network_table:///SmartDashboard/Move PID Error", + "_title": "SmartDashboard/Move PID Error" + } + }, + "4,3": { + "size": [ + 1, + 1 + ], + "content": { + "_type": "Text View", + "_source0": "network_table:///SmartDashboard/Right VPID Error", + "_title": "SmartDashboard/Right VPID Error" + } + }, + "3,3": { + "size": [ + 1, + 1 + ], + "content": { + "_type": "Text View", + "_source0": "network_table:///SmartDashboard/Left VPID Error", + "_title": "SmartDashboard/Left VPID Error" + } + }, + "5,2": { + "size": [ + 1, + 1 + ], + "content": { + "_type": "Text View", + "_source0": "network_table:///SmartDashboard/Drivetrain/Left VPID Targ", + "_title": "SmartDashboard/Drivetrain/Left VPID Targ" + } + }, + "6,2": { + "size": [ + 1, + 1 + ], + "content": { + "_type": "Text View", + "_source0": "network_table:///SmartDashboard/Drivetrain/Right VPID Targ", + "_title": "SmartDashboard/Drivetrain/Right VPID Targ" + } + } + } + } } ] } \ No newline at end of file From c154138f4ef3672812ceba617b2ae321825e7ea7 Mon Sep 17 00:00:00 2001 From: Alex Binnard Date: Thu, 15 Feb 2018 11:54:41 -0800 Subject: [PATCH 39/43] Added a try/catch in AutoUtils.parsePoint --- .../frc/team199/Robot2018/autonomous/AutoUtils.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/autonomous/AutoUtils.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/autonomous/AutoUtils.java index ee14c74..7f523a3 100644 --- a/Robot2018/src/org/usfirst/frc/team199/Robot2018/autonomous/AutoUtils.java +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/autonomous/AutoUtils.java @@ -246,8 +246,13 @@ public static double[] parsePoint(String cmdArgs) { if (AutoUtils.isPoint(cmdArgs)) { parentheseless = cmdArgs.substring(1, cmdArgs.length() - 1); pointparts = parentheseless.split(","); - point[0] = Double.parseDouble(pointparts[0]); - point[1] = Double.parseDouble(pointparts[1]); + try { + point[0] = Double.parseDouble(pointparts[0]); + point[1] = Double.parseDouble(pointparts[1]); + } catch (Exception e) { + point[0] = 1; + point[1] = 1; + } } return point; } From 305b8e300c2088afe81d29d72316e05dc5348bc9 Mon Sep 17 00:00:00 2001 From: doawelul Date: Fri, 16 Feb 2018 16:43:35 -0800 Subject: [PATCH 40/43] added encoder rate getters and implemented maximum velocity when ending on the pid commands --- .../team199/Robot2018/commands/PIDMove.java | 4 +++- .../team199/Robot2018/commands/PIDTurn.java | 4 +++- .../Robot2018/subsystems/Drivetrain.java | 20 +++++++++++++++++++ .../subsystems/DrivetrainInterface.java | 14 +++++++++++++ 4 files changed, 40 insertions(+), 2 deletions(-) diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/PIDMove.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/PIDMove.java index e719213..14d3044 100644 --- a/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/PIDMove.java +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/PIDMove.java @@ -76,7 +76,9 @@ protected void execute() { */ @Override protected boolean isFinished() { - return moveController.onTarget(); + return moveController.onTarget() + && Math.abs(dt.getLeftEncRate()) <= Robot.getConst("Maximum Velocity When Stop", 1) + && Math.abs(dt.getRightEncRate()) <= Robot.getConst("Maximum Velocity When Stop", 1); } /** diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/PIDTurn.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/PIDTurn.java index 84aaf3c..0e72282 100644 --- a/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/PIDTurn.java +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/PIDTurn.java @@ -84,7 +84,9 @@ protected void execute() { */ @Override protected boolean isFinished() { - return turnController.onTarget(); + return turnController.onTarget() + && Math.abs(dt.getLeftEncRate()) <= Robot.getConst("Maximum Velocity When Stop", 1) + && Math.abs(dt.getRightEncRate()) <= Robot.getConst("Maximum Velocity When Stop", 1); } /** diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/subsystems/Drivetrain.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/subsystems/Drivetrain.java index dff97c1..978cdd5 100644 --- a/Robot2018/src/org/usfirst/frc/team199/Robot2018/subsystems/Drivetrain.java +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/subsystems/Drivetrain.java @@ -34,6 +34,8 @@ public class Drivetrain extends Subsystem implements DrivetrainInterface { private final WPI_VictorSPX dtRightSlave = RobotMap.dtRightSlave; private final Encoder leftEncDist = RobotMap.leftEncDist; private final Encoder rightEncDist = RobotMap.rightEncDist; + private final Encoder leftEncRate = RobotMap.leftEncRate; + private final Encoder rightEncRate = RobotMap.rightEncRate; private final PIDSourceAverage distEncAvg = RobotMap.distEncAvg; private final SpeedControllerGroup dtLeft = RobotMap.dtLeft; private final SpeedControllerGroup dtRight = RobotMap.dtRight; @@ -49,6 +51,24 @@ public void initDefaultCommand() { setDefaultCommand(new TeleopDrive()); } + /** + * Returns the getRate() of the left encoder + * + * @return the rate of the left encoder + */ + public double getLeftEncRate() { + return leftEncRate.getRate(); + } + + /** + * Returns the getRate() of the right encoder + * + * @return the rate of the right encoder + */ + public double getRightEncRate() { + return rightEncRate.getRate(); + } + /** * Sets the left side of the drivetrain to use the talons' pids to run at the * specified speed. diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/subsystems/DrivetrainInterface.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/subsystems/DrivetrainInterface.java index 8d3d842..42f317f 100644 --- a/Robot2018/src/org/usfirst/frc/team199/Robot2018/subsystems/DrivetrainInterface.java +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/subsystems/DrivetrainInterface.java @@ -4,6 +4,20 @@ public interface DrivetrainInterface { public void initDefaultCommand(); + /** + * Returns the getRate() of the left encoder + * + * @return the rate of the left encoder + */ + public double getLeftEncRate(); + + /** + * Returns the getRate() of the right encoder + * + * @return the rate of the right encoder + */ + public double getRightEncRate(); + /** * Drives based on joystick input and SmartDashboard values */ From 4c3847870444dd0c81f11652ee6f8c19dfde7670 Mon Sep 17 00:00:00 2001 From: Student Date: Sat, 17 Feb 2018 15:37:03 -0800 Subject: [PATCH 41/43] testing move and turn pids, new navx jar all vpid commentewd out move pid mostly tuned turn very unreliable still having initialize issues --- Robot2018/.classpath | 3 - Robot2018/lib/.gitignore | 1 + Robot2018/lib/navx_frc.jar | Bin 109777 -> 110008 bytes .../org/usfirst/frc/team199/Robot2018/OI.java | 4 +- .../usfirst/frc/team199/Robot2018/Robot.java | 55 +++-- .../frc/team199/Robot2018/RobotMap.java | 11 +- .../team199/Robot2018/commands/PIDMove.java | 10 +- .../team199/Robot2018/commands/PIDTurn.java | 45 +++- .../Robot2018/commands/TeleopDrive.java | 2 +- .../Robot2018/subsystems/Drivetrain.java | 2 +- shuffleboard.json | 229 +++++++++++++++--- 11 files changed, 285 insertions(+), 77 deletions(-) create mode 100644 Robot2018/lib/.gitignore diff --git a/Robot2018/.classpath b/Robot2018/.classpath index affd5c6..9278f52 100644 --- a/Robot2018/.classpath +++ b/Robot2018/.classpath @@ -16,8 +16,5 @@ - - - diff --git a/Robot2018/lib/.gitignore b/Robot2018/lib/.gitignore new file mode 100644 index 0000000..d8d8bd1 --- /dev/null +++ b/Robot2018/lib/.gitignore @@ -0,0 +1 @@ +/User_Libraries.properties diff --git a/Robot2018/lib/navx_frc.jar b/Robot2018/lib/navx_frc.jar index 7f5e0597833a5def7bbb5d0098580f2d75ec9a34..32d6298f738ff4633dabe8b810ccea89afe92636 100644 GIT binary patch delta 60514 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 S`5Ff)F9Rr%uaaOQ;Ql|7)+5FM delta 60307 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 g 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%0AL~@oBq{f zCw2&hrCWBaN1Yh4s02o?$Qk$?4n7V52tbI>DHHbsL;|S&FWmV^L&;c_rfshuhz;*& zL`8|nr|7opz%UZwU#1m1ds*MnYjAyBan4YKY!4fjTpLe63QYKD7pBeHps_&RNzKWL zR~MO}#+jYoe<*zOWxwLI2t(g++CJQGJx+&#+Qx_EHS>g}RSa#(Q60188^W}Fn^=&@ zERCW?jDX=i?)$dSpsomuH*0)~q7X|AJB2|D&@6WCciG|nQKzDS**D3Zz81D$WPvNV+Ma4>5kT z$<&n!I{EvKh51%xDI5(%Nn>2MJGJ6=O^4I^Pq#x|xW9UQg$~(D7{}PeBkc{SKzkaW zP7#cM1cboSu{hSM_HxI<_Q3Ykry+$=(r9Ng`2AB|&;60hqyQ%cEi-foOhNcw^2;@J zY}~ISGUK#HxWJxgZycpmAd8syG2iG>(w2-xG9qu`^r{v}?Weu%{*u}vITG*roYz5v zFUaMretW^&HzAC)C*NaHpc1o-2oIF&T7rUYAsvXMItVYYXOP7hsk&1h2qI}S;Aq}< z*f|{VQ~F4_9Cma$I8cv-fZZ~0#!tuVF5aG>NV{>Grl6_7LAVY@=RnZmT%_OPYV&bD zcJWRFB^`ZL{uvD_j=3WSHM2T%OZFzxaI-UawUpP(Tvn7kL{*#YrBlORPFfHL*{6Rf zd=c{$Ro|lDyf3s=3}r4PCa-a1=ro{jao{8QAhF8PsqVpA9pkKxo&1CauOdl1zt5DFDYz{MQ9Wl>J)r%~eU80^#o~h4~TOOW2 zF;{3ee6P=5y+f+Qxw96uCL5LyF3*SA->S~?dQoIsp@A+_;;uxc#VOMa!f+`?BY&l` z61u9u_<+FU&v?>vNIF<4z78X6Ap802s#${H;yYF)Vi+PA<+048B9i5U)xL3ubxVze z!VuSSO$U41nV4e=r1+wEW}Xh3Ww)Ly&8_PT*WkOP%54}!l66(%|@Kd3X9b=x3sHzcDoUM(v8e_=Y#GuKaKl&YMB$;lK zvvx6+(>FEFls$b;nK&j4Gfi7~j7K?)Gt6A2YyZJTrdae_q2_S}tzkMYeLmDz0}+RB zU1fFLt=0%94Gw!BYK|!Se{-y0J72`3wf=FJa{&1Uxmw#Xx4t_0tkM4vyO!;VWq7z# z(J9!B;V@EPd_Tha3(FhF=mGBUOL;I_DDVMDc@JYH&1N5FG9WY?@ zB)tOzCQlM+`X60jI$-vs+f4_wpCs{&KYYa*e~uz%{^7I91azNp1DSyGlZ2o3N9U3C z$96FbFnBu3m;J}9jBG&XNq3nIC_PEiIS`h|$TM>Q@h9DS4#4^(VdesyPm*lzpLpNo z0uoQU>0ChS$(Aq=(0u-z#fIjaHrOi<;?E=Jd>){K@LM8i0$K8dx$^--vfsbD2#LBg zLqpc&!$LtZLe^#dX~jW);D&sF|H=9!A0UK2vrblRCrD;Hfd$hQ02JiE&7EG7D(K-s zL1_s?K|xmb|J#a#7VQ3mh69He02og^Sp^Ut=iuK*z|#c){gd|ow-!0#5$_R!50)(i zkRRCtUm#@p0#HzK5C=v7<%WVP4q}dcRD>_+T<|KaOfo$4pgp_^x8=jE+8=mZ6g9tt@0WcrYCiYE}A0a8`d=3T0^cOi&&419? zUB(^9ul1%BpDq4lKmcX~M2Z!aT2#`GLy5TN~mKzdo0d- zg5|3L`bX67YXfe`K`8M6(TJFb z08i^;-OOEWo!wdN-2V3zMpf)F x8HbEx$hiEs6$c&115mF#EnPirExmrPxmA&egTw>{B@g*3$A@sHKqecg{{x&M*t7ru diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/OI.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/OI.java index 7f27f3c..898fa38 100644 --- a/Robot2018/src/org/usfirst/frc/team199/Robot2018/OI.java +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/OI.java @@ -55,9 +55,11 @@ public OI() { PIDMoveButton = new JoystickButton(leftJoy, getButton("PID Move", 7)); PIDMoveButton.whenPressed(new PIDMove(Robot.getConst("Move Targ", 24), Robot.dt, RobotMap.distEncAvg)); PIDTurnButton = new JoystickButton(leftJoy, getButton("PID Turn", 8)); - PIDTurnButton.whenPressed(new PIDTurn(30, Robot.dt, RobotMap.fancyGyro)); + // PIDTurnButton.whenPressed(new PIDTurn(Robot.getConst("Turn Targ", 90), + // Robot.dt, RobotMap.fancyGyro)); resetEncButton = new JoystickButton(leftJoy, getButton("Reset Dist Enc", 10)); resetEncButton.whenPressed(new ResetEncoders()); + PIDTurnButton.whenReleased(new PIDTurn(Robot.getConst("Turn Targ", 90), Robot.dt, RobotMap.fancyGyro)); rightJoy = new Joystick(1); shiftHighGearButton = new JoystickButton(rightJoy, getButton("Shift High Gear", 3)); diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/Robot.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/Robot.java index 08e22ee..d94bee4 100644 --- a/Robot2018/src/org/usfirst/frc/team199/Robot2018/Robot.java +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/Robot.java @@ -23,8 +23,8 @@ import org.usfirst.frc.team199.Robot2018.subsystems.Lift; import edu.wpi.first.wpilibj.DriverStation; +import edu.wpi.first.wpilibj.IterativeRobot; import edu.wpi.first.wpilibj.Preferences; -import edu.wpi.first.wpilibj.TimedRobot; import edu.wpi.first.wpilibj.command.Command; import edu.wpi.first.wpilibj.command.Scheduler; import edu.wpi.first.wpilibj.smartdashboard.SendableChooser; @@ -37,7 +37,7 @@ * creating this project, you must also update the manifest file in the resource * directory. */ -public class Robot extends TimedRobot { +public class Robot extends IterativeRobot { public static Climber climber; public static ClimberAssist climberAssist; @@ -57,23 +57,34 @@ public class Robot extends TimedRobot { String[] fmsPossibilities = { "LL", "LR", "RL", "RR" }; public static double getConst(String key, double def) { - if (!SmartDashboard.containsKey("Const/" + key)) { - if (!SmartDashboard.putNumber("Const/" + key, def)) { - System.err.println("SmartDashboard Key" + "Const/" + key + "already taken by a different type"); + Preferences pref = Preferences.getInstance(); + if (!pref.containsKey("Const/" + key)) { + pref.putDouble("Const/" + key, def); + if (pref.getDouble("Const/ + key", def) != def) { + System.err.println("pref Key" + "Const/" + key + "already taken by a different type"); return def; } } - return SmartDashboard.getNumber("Const/" + key, def); + return pref.getDouble("Const/" + key, def); + /* + * if (!SmartDashboard.containsKey("Const/" + key)) { if + * (!SmartDashboard.putNumber("Const/" + key, def)) { + * System.err.println("SmartDashboard Key" + "Const/" + key + + * "already taken by a different type"); return def; } } return + * SmartDashboard.getNumber("Const/" + key, def); + */ } public static boolean getBool(String key, boolean def) { - if (!SmartDashboard.containsKey("Bool/" + key)) { - if (!SmartDashboard.putBoolean("Bool/" + key, def)) { - System.err.println("SmartDashboard Key" + "Bool/" + key + "already taken by a different type"); + Preferences pref = Preferences.getInstance(); + if (!pref.containsKey("Bool/" + key)) { + pref.putBoolean("Bool/" + key, def); + if (pref.getBoolean("Bool/" + key, def) == def) { + System.err.println("pref Key" + "Bool/" + key + "already taken by a different type"); return def; } } - return SmartDashboard.getBoolean("Bool/" + key, def); + return pref.getBoolean("Bool/" + key, def); } /** @@ -179,16 +190,20 @@ public void teleopInit() { public void teleopPeriodic() { Scheduler.getInstance().run(); - SmartDashboard.putNumber("Drivetrain/Left VPID Targ", Robot.dt.getLeftVPIDSetpoint()); - SmartDashboard.putNumber("Drivetrain/Right VPID Targ", Robot.dt.getRightVPIDSetpoint()); - SmartDashboard.putNumber("Left VPID Error", Robot.dt.getLeftVPIDerror()); - SmartDashboard.putNumber("Right VPID Error", Robot.dt.getRightVPIDerror()); - SmartDashboard.putNumber("Left Enc Rate", Robot.dt.getLeftEncRate()); - SmartDashboard.putNumber("Right Enc Rate", Robot.dt.getRightEncRate()); - - SmartDashboard.putNumber("Left Enc Dist", dt.getLeftDist()); - SmartDashboard.putNumber("Right Enc Dist", dt.getRightDist()); - SmartDashboard.putNumber("Avg Enc Dist", dt.getEncAvgDist()); + // SmartDashboard.putNumber("Drivetrain/Left VPID Targ", + // Robot.dt.getLeftVPIDSetpoint()); + // SmartDashboard.putNumber("Drivetrain/Right VPID Targ", + // Robot.dt.getRightVPIDSetpoint()); + // SmartDashboard.putNumber("Left VPID Error", Robot.dt.getLeftVPIDerror()); + // SmartDashboard.putNumber("Right VPID Error", Robot.dt.getRightVPIDerror()); + // SmartDashboard.putNumber("Left Enc Rate", Robot.dt.getLeftEncRate()); + // SmartDashboard.putNumber("Right Enc Rate", Robot.dt.getRightEncRate()); + // + // SmartDashboard.putNumber("Left Enc Dist", dt.getLeftDist()); + // SmartDashboard.putNumber("Right Enc Dist", dt.getRightDist()); + // SmartDashboard.putNumber("Avg Enc Dist", dt.getEncAvgDist()); + // + // SmartDashboard.putNumber("Angle", dt.getAHRSAngle()); } boolean firstTime = true; diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/RobotMap.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/RobotMap.java index be0dc07..b92ae74 100644 --- a/Robot2018/src/org/usfirst/frc/team199/Robot2018/RobotMap.java +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/RobotMap.java @@ -19,7 +19,7 @@ import edu.wpi.first.wpilibj.DoubleSolenoid; import edu.wpi.first.wpilibj.Encoder; import edu.wpi.first.wpilibj.PIDSourceType; -import edu.wpi.first.wpilibj.SerialPort; +import edu.wpi.first.wpilibj.SPI; import edu.wpi.first.wpilibj.SpeedControllerGroup; import edu.wpi.first.wpilibj.VictorSP; import edu.wpi.first.wpilibj.drive.DifferentialDrive; @@ -171,12 +171,13 @@ public RobotMap() { rightVelocityController.setContinuous(false); rightVelocityController.setAbsoluteTolerance(Robot.getConst("VelocityToleranceRight", 2)); - robotDrive = new DifferentialDrive(leftVelocityController, rightVelocityController); - robotDrive.setMaxOutput(Robot.getConst("Max High Speed", 204)); - // robotDrive = new DifferentialDrive(dtLeft, dtRight); + // robotDrive = new DifferentialDrive(leftVelocityController, + // rightVelocityController); + // robotDrive.setMaxOutput(Robot.getConst("Max High Speed", 204)); + robotDrive = new DifferentialDrive(dtLeft, dtRight); distEncAvg = new PIDSourceAverage(leftEncDist, rightEncDist); - fancyGyro = new AHRS(SerialPort.Port.kMXP); + fancyGyro = new AHRS(SPI.Port.kMXP); dtGear = new DoubleSolenoid(getPort("1dtGearSolenoid", 0), getPort("2dtGearSolenoid", 1)); } diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/PIDMove.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/PIDMove.java index 2ea7d8e..ccf8330 100644 --- a/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/PIDMove.java +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/PIDMove.java @@ -18,6 +18,7 @@ public class PIDMove extends Command implements PIDOutput { private double target; private DrivetrainInterface dt; private PIDController moveController; + private PIDSourceAverage avg; /** * Constructs this command with a new PIDController. Sets all of the @@ -38,6 +39,7 @@ public PIDMove(double targ, DrivetrainInterface dt, PIDSourceAverage avg) { requires(Robot.dt); target = targ; this.dt = dt; + this.avg = avg; double kf = 1 / (dt.getCurrentMaxSpeed() * Robot.getConst("Default PID Update Time", 0.05)); moveController = new PIDController(Robot.getConst("MovekP", 0), Robot.getConst("MovekI", 0), Robot.getConst("MovekD", 0), kf, avg, this); @@ -56,13 +58,13 @@ public void initialize() { // output in "motor units" (arcade and tank only accept values [-1, 1] moveController.setOutputRange(-1.0, 1.0); moveController.setContinuous(false); - moveController.setAbsoluteTolerance(Robot.getConst("MoveTolerance", 2)); - moveController.setSetpoint(target); + moveController.setAbsoluteTolerance(Robot.getConst("MoveTolerance", 0.1)); + moveController.setSetpoint(Robot.getConst("Move Targ", 24)); SmartDashboard.putData("Move PID", moveController); moveController.enable(); - dt.enableVelocityPIDs(); + // dt.enableVelocityPIDs(); } /** @@ -72,6 +74,7 @@ public void initialize() { */ @Override protected void execute() { + System.out.println("Enc Avg Dist: " + avg.pidGet()); SmartDashboard.putNumber("Move PID Result", moveController.get()); SmartDashboard.putNumber("Move PID Error", moveController.getError()); } @@ -95,6 +98,7 @@ protected boolean isFinished() { @Override protected void end() { moveController.disable(); + System.out.println("End"); // moveController.free(); } diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/PIDTurn.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/PIDTurn.java index e37b22f..9677056 100644 --- a/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/PIDTurn.java +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/PIDTurn.java @@ -7,6 +7,7 @@ import edu.wpi.first.wpilibj.PIDController; import edu.wpi.first.wpilibj.PIDOutput; +import edu.wpi.first.wpilibj.Timer; import edu.wpi.first.wpilibj.command.Command; import edu.wpi.first.wpilibj.smartdashboard.SmartDashboard; @@ -19,6 +20,9 @@ public class PIDTurn extends Command implements PIDOutput { private double target; private DrivetrainInterface dt; private PIDController turnController; + private AHRS ahrs; + private Timer tim; + private double lastTime; /** * Constructs this command with a new PIDController. Sets all of the @@ -40,13 +44,15 @@ public PIDTurn(double targ, DrivetrainInterface dt, AHRS ahrs) { requires(Robot.dt); target = targ; this.dt = dt; + this.ahrs = ahrs; // calculates the maximum turning speed in degrees/sec based on the max linear // speed in inches/s and the distance (inches) between sides of the DT double maxTurnSpeed = dt.getCurrentMaxSpeed() * 360 / (Math.PI * Robot.getConst("Distance Between Wheels", 26.25)); double kf = 1 / (maxTurnSpeed * Robot.getConst("Default PID Update Time", 0.05)); - turnController = new PIDController(Robot.getConst("TurnkP", 1), Robot.getConst("TurnkI", 0), - Robot.getConst("TurnkD", 0), kf, ahrs, this); + turnController = new PIDController(Robot.getConst("TurnkP", 0.018), Robot.getConst("TurnkI", 0.00003), + Robot.getConst("TurnkD", 0.015), kf, ahrs, this); + // tim = new Timer(); } /** @@ -55,18 +61,30 @@ public PIDTurn(double targ, DrivetrainInterface dt, AHRS ahrs) { */ @Override protected void initialize() { - dt.resetAHRS(); turnController.disable(); + // dt.enableVelocityPIDs(); + System.out.println("initialize2s"); + dt.resetAHRS(); + System.out.println("after reset"); + System.out.println("after disabling"); // input is in degrees turnController.setInputRange(-180, 180); // output in "motor units" (arcade and tank only accept values [-1, 1] turnController.setOutputRange(-1.0, 1.0); - turnController.setContinuous(); + turnController.setContinuous(true); turnController.setAbsoluteTolerance(Robot.getConst("TurnTolerance", 1)); - turnController.setSetpoint(target); + double newSetPoint = Robot.getConst("Turn Targ", 90); + while (Math.abs(newSetPoint) > 180) { + newSetPoint = newSetPoint - Math.signum(newSetPoint) * 360; + } + turnController.setSetpoint(newSetPoint); + SmartDashboard.putData("Turn PID", turnController); + turnController.enable(); - dt.enableVelocityPIDs(); + System.out.println("initialize finished"); + // tim.start(); + // lastTime = tim.get(); } /** @@ -76,8 +94,12 @@ protected void initialize() { */ @Override protected void execute() { - SmartDashboard.putNumber("Turn PID Result", turnController.get()); - SmartDashboard.putNumber("Turn PID Error", turnController.getError()); + System.out.println("execute"); + System.out.println("Angle: " + dt.getAHRSAngle()); + // if (tim.get() > lastTime + Robot.getConst("Update Time", 1)) { + // SmartDashboard.putNumber("Angle", dt.getAHRSAngle()); + // lastTime = tim.get(); + // } } /** @@ -89,7 +111,8 @@ protected void execute() { */ @Override protected boolean isFinished() { - return turnController.onTarget(); + System.out.println("isFinished"); + return (turnController.onTarget() && Math.abs(ahrs.getRate()) < 1); } /** @@ -100,6 +123,9 @@ protected boolean isFinished() { @Override protected void end() { turnController.disable(); + System.out.println("end"); + SmartDashboard.putNumber("Turn PID Result", turnController.get()); + SmartDashboard.putNumber("Turn PID Error", turnController.getError()); // turnController.free(); } @@ -125,5 +151,6 @@ protected void interrupted() { @Override public void pidWrite(double output) { dt.arcadeDrive(0, output); + SmartDashboard.putNumber("Turn PID Output", output); } } diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/TeleopDrive.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/TeleopDrive.java index 64b6b14..1940cde 100644 --- a/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/TeleopDrive.java +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/TeleopDrive.java @@ -23,7 +23,7 @@ public TeleopDrive() { // Called just before this Command runs the first time @Override protected void initialize() { - Robot.dt.enableVelocityPIDs(); + // Robot.dt.enableVelocityPIDs(); } // Called repeatedly when this Command is scheduled to run diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/subsystems/Drivetrain.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/subsystems/Drivetrain.java index 5fff0f1..0bad6b9 100644 --- a/Robot2018/src/org/usfirst/frc/team199/Robot2018/subsystems/Drivetrain.java +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/subsystems/Drivetrain.java @@ -251,7 +251,7 @@ public void resetAHRS() { */ @Override public double getAHRSAngle() { - return fancyGyro.getAngle(); + return fancyGyro.getYaw(); } /** diff --git a/shuffleboard.json b/shuffleboard.json index ed5ffed..18a4aa8 100644 --- a/shuffleboard.json +++ b/shuffleboard.json @@ -269,7 +269,7 @@ }, "0,0": { "size": [ - 2, + 1, 2 ], "content": { @@ -279,116 +279,214 @@ } }, "3,0": { + "size": [ + 2, + 2 + ], + "content": { + "_type": "Graph", + "_source0": "network_table:///SmartDashboard/Right Enc Dist", + "_title": "SmartDashboard/Right Enc Dist", + "Visible time": 10.0, + "SmartDashboard/Right Enc Dist visible": true + } + }, + "1,0": { + "size": [ + 2, + 2 + ], + "content": { + "_type": "Graph", + "_source0": "network_table:///SmartDashboard/Left Enc Dist", + "_title": "SmartDashboard/Left Enc Dist", + "Visible time": 10.0, + "SmartDashboard/Left Enc Dist visible": true + } + }, + "5,0": { + "size": [ + 2, + 1 + ], + "content": { + "_type": "Network Table Tree", + "_source0": "network_table:///LiveWindow/Ungrouped/Scheduler", + "_title": "LiveWindow/Ungrouped/Scheduler" + } + }, + "0,3": { "size": [ 1, 1 ], "content": { "_type": "Text View", - "_source0": "network_table:///SmartDashboard/Right Enc Dist", - "_title": "SmartDashboard/Right Enc Dist" + "_source0": "network_table:///SmartDashboard/Move PID Output", + "_title": "SmartDashboard/Move PID Output" } }, - "2,0": { + "1,2": { "size": [ 1, 1 ], "content": { "_type": "Text View", - "_source0": "network_table:///SmartDashboard/Left Enc Dist", - "_title": "SmartDashboard/Left Enc Dist" + "_source0": "network_table:///SmartDashboard/Move PID Result", + "_title": "SmartDashboard/Move PID Result" } }, - "2,2": { + "1,3": { "size": [ 1, 1 ], "content": { "_type": "Text View", - "_source0": "network_table:///SmartDashboard/Const/MovekP", - "_title": "SmartDashboard/Const/MovekP" + "_source0": "network_table:///SmartDashboard/Move PID Error", + "_title": "SmartDashboard/Move PID Error" } }, - "3,2": { + "2,3": { "size": [ 1, 1 ], "content": { "_type": "Text View", - "_source0": "network_table:///SmartDashboard/Const/MovekI", - "_title": "SmartDashboard/Const/MovekI" + "_source0": "network_table:///SmartDashboard/Right VPID Error", + "_title": "SmartDashboard/Right VPID Error" } }, - "4,2": { + "2,2": { "size": [ 1, 1 ], "content": { "_type": "Text View", - "_source0": "network_table:///SmartDashboard/Const/MovekD", - "_title": "SmartDashboard/Const/MovekD" + "_source0": "network_table:///SmartDashboard/Left VPID Error", + "_title": "SmartDashboard/Left VPID Error" } }, - "4,0": { + "3,2": { "size": [ 2, 2 ], + "content": { + "_type": "Graph", + "_source0": "network_table:///SmartDashboard/Drivetrain/Left VPID Targ", + "_title": "SmartDashboard/Drivetrain/Left VPID Targ", + "Visible time": 10.0, + "SmartDashboard/Drivetrain/Left VPID Targ visible": true + } + }, + "5,2": { + "size": [ + 2, + 2 + ], + "content": { + "_type": "Graph", + "_source0": "network_table:///SmartDashboard/Drivetrain/Right VPID Targ", + "_title": "SmartDashboard/Drivetrain/Right VPID Targ", + "Visible time": 10.0, + "SmartDashboard/Drivetrain/Right VPID Targ visible": true + } + }, + "5,1": { + "size": [ + 1, + 1 + ], + "content": { + "_type": "Text View", + "_source0": "network_table:///SmartDashboard/Const/MoveTolerance", + "_title": "SmartDashboard/Const/MoveTolerance" + } + } + } + } + }, + { + "title": "Turn PID Testing", + "autoPopulate": false, + "autoPopulatePrefix": "", + "widgetPane": { + "gridSize": 128.0, + "showGrid": true, + "hgap": 16.0, + "vgap": 16.0, + "tiles": { + "5,0": { + "size": [ + 2, + 1 + ], "content": { "_type": "Network Table Tree", "_source0": "network_table:///LiveWindow/Ungrouped/Scheduler", "_title": "LiveWindow/Ungrouped/Scheduler" } }, - "0,3": { + "1,3": { "size": [ 1, 1 ], "content": { "_type": "Text View", - "_source0": "network_table:///SmartDashboard/Move PID Output", - "_title": "SmartDashboard/Move PID Output" + "_source0": "network_table:///SmartDashboard/Turn PID Error", + "_title": "SmartDashboard/Turn PID Error" } }, - "1,3": { + "0,3": { "size": [ 1, 1 ], "content": { "_type": "Text View", - "_source0": "network_table:///SmartDashboard/Move PID Result", - "_title": "SmartDashboard/Move PID Result" + "_source0": "network_table:///SmartDashboard/Turn PID Output", + "_title": "SmartDashboard/Turn PID Output" } }, - "2,3": { + "1,2": { "size": [ 1, 1 ], "content": { "_type": "Text View", - "_source0": "network_table:///SmartDashboard/Move PID Error", - "_title": "SmartDashboard/Move PID Error" + "_source0": "network_table:///SmartDashboard/Turn PID Result", + "_title": "SmartDashboard/Turn PID Result" } }, - "4,3": { + "5,1": { "size": [ 1, 1 ], "content": { "_type": "Text View", - "_source0": "network_table:///SmartDashboard/Right VPID Error", - "_title": "SmartDashboard/Right VPID Error" + "_source0": "network_table:///SmartDashboard/Angle", + "_title": "SmartDashboard/Angle" + } + }, + "0,0": { + "size": [ + 1, + 2 + ], + "content": { + "_type": "PID Controller", + "_source0": "network_table:///SmartDashboard/Turn PID", + "_title": "SmartDashboard/Turn PID" } }, - "3,3": { + "2,2": { "size": [ 1, 1 @@ -399,26 +497,89 @@ "_title": "SmartDashboard/Left VPID Error" } }, - "5,2": { + "2,3": { "size": [ 1, 1 ], "content": { "_type": "Text View", + "_source0": "network_table:///SmartDashboard/Right VPID Error", + "_title": "SmartDashboard/Right VPID Error" + } + }, + "3,2": { + "size": [ + 2, + 2 + ], + "content": { + "_type": "Graph", "_source0": "network_table:///SmartDashboard/Drivetrain/Left VPID Targ", - "_title": "SmartDashboard/Drivetrain/Left VPID Targ" + "_title": "SmartDashboard/Drivetrain/Left VPID Targ", + "Visible time": 30.0, + "SmartDashboard/Drivetrain/Left VPID Targ visible": true } }, - "6,2": { + "5,2": { + "size": [ + 2, + 2 + ], + "content": { + "_type": "Graph", + "_source0": "network_table:///SmartDashboard/Drivetrain/Right VPID Targ", + "_title": "SmartDashboard/Drivetrain/Right VPID Targ", + "Visible time": 30.0, + "SmartDashboard/Drivetrain/Right VPID Targ visible": true + } + }, + "0,2": { "size": [ 1, 1 ], "content": { "_type": "Text View", - "_source0": "network_table:///SmartDashboard/Drivetrain/Right VPID Targ", - "_title": "SmartDashboard/Drivetrain/Right VPID Targ" + "_source0": "network_table:///SmartDashboard/Const/Turn Targ", + "_title": "SmartDashboard/Const/Turn Targ" + } + }, + "6,1": { + "size": [ + 1, + 1 + ], + "content": { + "_type": "Text View", + "_source0": "network_table:///SmartDashboard/Const/TurnTolerance", + "_title": "SmartDashboard/Const/TurnTolerance" + } + }, + "3,0": { + "size": [ + 2, + 2 + ], + "content": { + "_type": "Graph", + "_source0": "network_table:///SmartDashboard/Drivetrain/Right Enc Dist", + "_title": "SmartDashboard/Drivetrain/Right Enc Dist", + "Visible time": 30.0, + "SmartDashboard/Drivetrain/Right Enc Dist visible": true + } + }, + "1,0": { + "size": [ + 2, + 2 + ], + "content": { + "_type": "Graph", + "_source0": "network_table:///SmartDashboard/Drivetrain/Left Enc Dist", + "_title": "SmartDashboard/Drivetrain/Left Enc Dist", + "Visible time": 30.0, + "SmartDashboard/Drivetrain/Left Enc Dist visible": true } } } From 06e33e24fb062e8131371c159a03319a588a4b89 Mon Sep 17 00:00:00 2001 From: Student Date: Sat, 17 Feb 2018 15:39:41 -0800 Subject: [PATCH 42/43] changed project name back to Robot2018 --- Robot2018/.project | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Robot2018/.project b/Robot2018/.project index 62486a1..ccb0af9 100644 --- a/Robot2018/.project +++ b/Robot2018/.project @@ -1,6 +1,6 @@ - DSRobot2018 + Robot2018 From 17c2c71c6082d01e825ee33bdd841eb26b7ddba8 Mon Sep 17 00:00:00 2001 From: DriverStationComputer Date: Sat, 17 Feb 2018 15:45:28 -0800 Subject: [PATCH 43/43] Added cameras, comment out PID --- Robot2018/.classpath | 3 --- Robot2018/lib/User_Libraries.properties | 2 ++ .../src/org/usfirst/frc/team199/Robot2018/Robot.java | 3 +++ .../org/usfirst/frc/team199/Robot2018/RobotMap.java | 7 ++++--- .../Robot2018/autonomous/VelocityPIDController.java | 2 +- .../frc/team199/Robot2018/commands/TeleopDrive.java | 2 +- shuffleboard.json | 11 +++++++++++ 7 files changed, 22 insertions(+), 8 deletions(-) create mode 100644 Robot2018/lib/User_Libraries.properties diff --git a/Robot2018/.classpath b/Robot2018/.classpath index affd5c6..9278f52 100644 --- a/Robot2018/.classpath +++ b/Robot2018/.classpath @@ -16,8 +16,5 @@ - - - diff --git a/Robot2018/lib/User_Libraries.properties b/Robot2018/lib/User_Libraries.properties new file mode 100644 index 0000000..e1b63f8 --- /dev/null +++ b/Robot2018/lib/User_Libraries.properties @@ -0,0 +1,2 @@ +#Sat Feb 17 14:18:13 PST 2018 +C\:\\Users\\dean\\git\\RobotCode2018\\Robot2018\\lib\\libpathfinderjava.so=4087ee38c338446b8305f67716260df4 diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/Robot.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/Robot.java index 08e22ee..a80ac2b 100644 --- a/Robot2018/src/org/usfirst/frc/team199/Robot2018/Robot.java +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/Robot.java @@ -22,6 +22,7 @@ import org.usfirst.frc.team199.Robot2018.subsystems.IntakeEject; import org.usfirst.frc.team199.Robot2018.subsystems.Lift; +import edu.wpi.first.wpilibj.CameraServer; import edu.wpi.first.wpilibj.DriverStation; import edu.wpi.first.wpilibj.Preferences; import edu.wpi.first.wpilibj.TimedRobot; @@ -113,6 +114,8 @@ public void robotInit() { autoScripts = AutoUtils.parseScriptFile(Preferences.getInstance().getString("autoscripts", "")); listen = new Listener(); + CameraServer.getInstance().startAutomaticCapture(0); + CameraServer.getInstance().startAutomaticCapture(1); } /** diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/RobotMap.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/RobotMap.java index be0dc07..112bc33 100644 --- a/Robot2018/src/org/usfirst/frc/team199/Robot2018/RobotMap.java +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/RobotMap.java @@ -171,9 +171,10 @@ public RobotMap() { rightVelocityController.setContinuous(false); rightVelocityController.setAbsoluteTolerance(Robot.getConst("VelocityToleranceRight", 2)); - robotDrive = new DifferentialDrive(leftVelocityController, rightVelocityController); - robotDrive.setMaxOutput(Robot.getConst("Max High Speed", 204)); - // robotDrive = new DifferentialDrive(dtLeft, dtRight); + // robotDrive = new DifferentialDrive(leftVelocityController, + // rightVelocityController); + // robotDrive.setMaxOutput(Robot.getConst("Max High Speed", 204)); + robotDrive = new DifferentialDrive(dtLeft, dtRight); distEncAvg = new PIDSourceAverage(leftEncDist, rightEncDist); fancyGyro = new AHRS(SerialPort.Port.kMXP); diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/autonomous/VelocityPIDController.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/autonomous/VelocityPIDController.java index 5ce91cc..94b5b29 100644 --- a/Robot2018/src/org/usfirst/frc/team199/Robot2018/autonomous/VelocityPIDController.java +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/autonomous/VelocityPIDController.java @@ -41,7 +41,7 @@ public VelocityPIDController(double kp, double ki, double kd, double kf, PIDSour */ @Override public void pidWrite(double output) { - setSetpoint(output); + set(output); } /** diff --git a/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/TeleopDrive.java b/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/TeleopDrive.java index 64b6b14..1940cde 100644 --- a/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/TeleopDrive.java +++ b/Robot2018/src/org/usfirst/frc/team199/Robot2018/commands/TeleopDrive.java @@ -23,7 +23,7 @@ public TeleopDrive() { // Called just before this Command runs the first time @Override protected void initialize() { - Robot.dt.enableVelocityPIDs(); + // Robot.dt.enableVelocityPIDs(); } // Called repeatedly when this Command is scheduled to run diff --git a/shuffleboard.json b/shuffleboard.json index ed5ffed..ea26bde 100644 --- a/shuffleboard.json +++ b/shuffleboard.json @@ -242,6 +242,17 @@ "colorWhenTrue": "#7CFC00FF", "colorWhenFalse": "#8B0000FF" } + }, + "2,1": { + "size": [ + 2, + 1 + ], + "content": { + "_type": "Text View", + "_source0": "network_table:///SmartDashboard/Const/DPP", + "_title": "SmartDashboard/Const/DPP" + } } } }