Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Functional PathPlanner for Larry (Command-Based) #13

Open
wants to merge 47 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
28c02ee
Fixed Incorrect magnet offset for rear left wheel
PickleFace5 Nov 28, 2023
917c91e
Pathplanner init
PickleFace5 Dec 1, 2023
2571ab6
Structured (non-functional) FollowPath Command
PickleFace5 Dec 4, 2023
3e15a7f
Implemented slowdownMult
PickleFace5 Dec 5, 2023
18eae06
Added Larrys max speed (3.66 m/s)
PickleFace5 Dec 5, 2023
419c008
Finished FollowPath Prototype
PickleFace5 Dec 5, 2023
76d066f
No longer reset navX when initializing commands
PickleFace5 Dec 6, 2023
330c07a
Added manual angle offset buttons
PickleFace5 Dec 6, 2023
82b1d88
Cleaned up SmartDashboard (for the 11th time)
PickleFace5 Dec 6, 2023
1a29ef3
Cleaned up ALL imports
PickleFace5 Dec 6, 2023
11f40aa
Removed set_driver_profile.py
PickleFace5 Dec 6, 2023
c0152c1
More pathPlanner Testing + mtr tuning
PickleFace5 Dec 8, 2023
09531e4
Added Holonomic Path Following + Odometry Fields
PickleFace5 Dec 9, 2023
1c20b74
Added method to update wheel positions
PickleFace5 Dec 9, 2023
ff4ea82
Updated Path 2
PickleFace5 Dec 9, 2023
efaf43c
Added Test 3
PickleFace5 Dec 9, 2023
3a61ad2
Renamed SwerveModule's to Wheel's
PickleFace5 Dec 9, 2023
b04a80d
Added equation to negate path following rot. drift
PickleFace5 Dec 10, 2023
a83a9bd
Added framework for translation drift fixes
PickleFace5 Dec 10, 2023
e66961f
stopMotors no longer stops direction motors
PickleFace5 Dec 12, 2023
02f5774
Disabled gyro error comp. for follow_path
PickleFace5 Dec 12, 2023
52c7e1c
Odometry Testing Values
PickleFace5 Dec 12, 2023
3b851cf
Added New FollowPath Command Framework
PickleFace5 Dec 12, 2023
2582bfc
Added WIP SwerveWheel Simulation
PickleFace5 Dec 12, 2023
e632a94
Actually internally named the robot "Larry"
PickleFace5 Dec 13, 2023
d68d2cb
Cleaned up constants
PickleFace5 Dec 13, 2023
468c6d4
Function Swerve Drive Odometry (Testing Needed)
PickleFace5 Dec 13, 2023
b4f31ee
Updated default PathConstraints
PickleFace5 Dec 13, 2023
774925b
File renaming
PickleFace5 Dec 13, 2023
8a5b19a
Fixed small return inaccuracy
PickleFace5 Dec 13, 2023
bc7c685
Added check if wheels are at the correct angle
PickleFace5 Dec 13, 2023
565cd50
Finished Some work on prototype translation auto
PickleFace5 Dec 14, 2023
bbc91a6
Finished Rotation Auto
PickleFace5 Dec 14, 2023
3c7aada
Added Caden Driver Profile Because I FREAKING HATE AUTO PROGRAMMING I…
PickleFace5 Dec 14, 2023
cc20da5
Prototype version of driving using kinematics
PickleFace5 Dec 15, 2023
919d4d4
Better optimized angle method
PickleFace5 Dec 15, 2023
3f583bc
Added working autonomous path following (YAY)
PickleFace5 Dec 16, 2023
a39fbe0
Fixed requirements
PickleFace5 Dec 16, 2023
79e0171
Removed bad autos lmao
PickleFace5 Dec 16, 2023
039f58e
Added test move forward that doesn't follow PIDs
PickleFace5 Dec 17, 2023
f9be297
Added resetOdometry + reworked configuring motors
PickleFace5 Dec 17, 2023
0a833f9
Added debug methods for getting voltage of motors
PickleFace5 Dec 18, 2023
c7b0337
Reverted motor config changes, caused issues
PickleFace5 Dec 19, 2023
7a5c952
Working Odometry (needs tuning) (yeehaw)
PickleFace5 Dec 19, 2023
2c13657
Updated Larry wheel radius
PickleFace5 Dec 19, 2023
f805ee6
Added Working Robot Simulation (needs tuning)
PickleFace5 Dec 20, 2023
b718d82
Fixed issue with translating and rotating
PickleFace5 Dec 20, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions Larry/.pathplanner/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"robotWidth": 0.74,
"robotLength": 0.73678862,
"holonomicMode": true,
"generateJSON": false,
"generateCSV": false
}
24 changes: 24 additions & 0 deletions Larry/autos/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from commands2 import SequentialCommandGroup
from constants import *
from math import pi
from pathplannerlib import PathPlannerTrajectory
from subsystems.swerve_drive import SwerveDrive
from autos.swerve_drive_controller import SwerveDriveControllerCommand
from wpimath.controller import ProfiledPIDControllerRadians, PIDController

class AutoBase(SequentialCommandGroup):
profiledThetaController = ProfiledPIDControllerRadians(AutoConstants.kPTheta, 0.0, 0.0, AutoConstants.kThetaControllerRestraints)
thetaController = PIDController(AutoConstants.kPTheta, 0, 0)

def __init__(self, swerve: SwerveDrive) -> None:
super().__init__()
self.swerve = swerve
self.addRequirements(swerve)
self.thetaController.enableContinuousInput(-pi, pi)

def baseSwerveCommand(self, trajectory: PathPlannerTrajectory) -> SwerveDriveControllerCommand:
command = SwerveDriveControllerCommand(trajectory, lambda: self.swerve.getPose(), self.swerve.kinematics,
PIDController(AutoConstants.kPXController, 0, 0),
PIDController(AutoConstants.kPYController, 0, 0), self.profiledThetaController,
lambda moduleStates: self.swerve.setModuleStates(moduleStates), [self.swerve])
return command
51 changes: 51 additions & 0 deletions Larry/autos/swerve_drive_controller.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
from commands2 import CommandBase, Subsystem
from typing import Callable
from pathplannerlib import PathPlannerTrajectory
from wpilib import SmartDashboard, Timer
from wpimath.controller import HolonomicDriveController, ProfiledPIDControllerRadians, PIDController
from wpimath.geometry import *
from wpimath.kinematics import SwerveDrive4Kinematics, SwerveModuleState

class SwerveDriveControllerCommand(CommandBase):
"""
Takes 2 PID controllers and 1 ProfiledPIDController to follow a trajectory using swerve drive.
"""

def __init__(self, trajectory: PathPlannerTrajectory, pose: Callable[[], Pose2d], kinematics: SwerveDrive4Kinematics,
xController: PIDController, yController: PIDController, thetaController: ProfiledPIDControllerRadians,
outputModuleStates: Callable[[tuple[SwerveModuleState, SwerveModuleState, SwerveModuleState, SwerveModuleState]], None], requirements: list[Subsystem]) -> None:
super().__init__()

self.timer = Timer()
self.trajectory = trajectory
self.pose = pose
self.kinematics = kinematics
self.controller = HolonomicDriveController(xController, yController, thetaController)
self.outputModuleStates = outputModuleStates

self.addRequirements(requirements)

def initialize(self) -> None:
self.timer.reset()
self.timer.start()

def execute(self) -> None:
currentTime = self.timer.get()

desiredState = self.trajectory.sample(currentTime)

targetChassisSpeeds = self.controller.calculate(self.pose(), desiredState.asWPILibState(), desiredState.holonomicRotation)
targetModuleStates = self.kinematics.toSwerveModuleStates(targetChassisSpeeds)

SmartDashboard.putNumber("Velocity", desiredState.velocity)
SmartDashboard.putNumber("Angular Velocity", desiredState.holonomicAngularVelocity)
SmartDashboard.putNumber("Acceleration", desiredState.acceleration)

self.outputModuleStates(targetModuleStates)

def end(self, interrupted: bool) -> None:
self.timer.stop()

def isFinished(self) -> bool:
return self.timer.hasElapsed(self.trajectory.getTotalTime())

50 changes: 50 additions & 0 deletions Larry/autos/tests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
from autos.base import AutoBase
from commands2 import CommandBase, InstantCommand
from constants import *
from pathplannerlib import PathPlanner
from subsystems.swerve_drive import SwerveDrive
from wpilib import Timer
from wpimath.geometry import Translation2d, Rotation2d

class TestForward(AutoBase):
"""
Moves the robot 1 meter forward using a PathPlanner trajectory.
"""

def __init__(self, swerve: SwerveDrive) -> None:
super().__init__(swerve)

path = PathPlanner.loadPath("TestForward", kdefaultPathConstraints)
command1 = self.baseSwerveCommand(path)
initialState = command1.pose()

self.addCommands(
InstantCommand(lambda: swerve.navX.reset()),
InstantCommand(lambda: swerve.resetOdometry()),
command1
)

class TestForwardNoPP(CommandBase):
"""
Moves the robot 1 meter forward
"""
timer = Timer()

def __init__(self, swerve: SwerveDrive) -> None:
super().__init__()

self.swerve = swerve

def initialize(self) -> None:
self.timer.reset()
self.timer.start()

def execute(self) -> None:
self.swerve.drive(Translation2d(10, 0), Rotation2d(), fieldRelative=False)

def end(self, interrupted: bool) -> None:
self.swerve.stopAllMotors()
self.timer.stop()

def isFinished(self) -> bool:
return self.timer.hasElapsed(1)
195 changes: 195 additions & 0 deletions Larry/commands/autonomous.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
from math import atan2, cos, degrees, pi, sin

from commands2 import CommandBase
from constants import *
from pathplannerlib import PathPlanner
from subsystems.swerve_drive import SwerveDrive
from wpilib import RobotBase, SmartDashboard, Timer


class FollowPath(CommandBase):
"""
Follows the given path planner made and saved in pathplannerlib.
"""
def __init__(self, swerveDrive: SwerveDrive, pathName: str,
pathConstraints: PathConstraints=kdefaultPathConstraints, isReversed: bool = False) -> None:
super().__init__()

self.drive = swerveDrive
self.pathConstraints = pathConstraints
self.path = PathPlanner.loadPath(pathName, pathConstraints, isReversed)

self.addRequirements([self.drive])

def initialize(self) -> None:
self.startTime = Timer.getFPGATimestamp()

self.desiredAngle = 0
self.desiredPosX = self.path.getInitialPose().X()
self.desiredPosY = self.path.getInitialPose().Y()
self.drive.navX.reset()

def execute(self) -> None:
self.sampleTime = Timer.getFPGATimestamp() - self.startTime
self.pathIndex = self.path.sample(self.sampleTime)

# Use trig to get translationX and translationY
translationHeading = self.pathIndex.pose.rotation().radians()
translationX = sin(translationHeading) * -self.pathIndex.velocity
translationY = cos(translationHeading) * -self.pathIndex.velocity

rotationX = self.pathIndex.holonomicAngularVelocity

self.desiredAngle += degrees(rotationX) / 50 # gets degrees per tick
self.desiredPosX = self.pathIndex.pose.X()
self.desiredPosY = self.pathIndex.pose.Y()

# Fix drift
"""
if RobotBase.isReal():
angleDrift = self.desiredAngle - self.drive.getYaw()
print(angleDrift)
rotationX -= 1 / ((180 / krotationMagnitudeMM)**3) * (angleDrift ** 3) # This increases the rotationX depending on how far off we are currently

"""
# Convert to magnitudes
rotationX /= klarryMaxRotSpeed
translationX /= klarryMaxSpeed
translationY /= klarryMaxSpeed

# Clamp magnitudes
rotationX = max(-1, min(1, rotationX))
translationX = max(-1, min(1, translationX))
translationY = max(-1, min(1, translationY))

# Send to SwerveDrive
self.drive.translateAndTurn(translationX, translationY, rotationX,
applyTranslationMultiplier=False, applyRotationMultiplier=False, applySpeedModifier=False)

def end(self, interrupted: bool) -> None:
self.drive.stopAllMotors()

def isFinished(self) -> bool:
return self.sampleTime >= self.path.getTotalTime()

class MoveMeters(CommandBase):
"""
Moves the robot in a straight line the chosen amount of meters.
"""
def __init__(self, drive: SwerveDrive, distanceX: float, distanceY: float) -> None:
super().__init__()

self.drive = drive
self.addRequirements([self.drive])

self.distanceX = distanceX
self.distanceY = distanceY

def initialize(self) -> None:
pose = self.drive.getPose()
self.finalDispX = self.distanceX - pose.X() # Adjacent angle
self.finalDispY = self.distanceY - pose.Y() # Opposite angle
self.finalX = self.distanceX + pose.X()
self.finalY = self.distanceY + pose.Y()

# Trajectory angle
self.trajAngle = atan2(self.finalDispX, self.finalDispY) * (180 / pi)
if self.trajAngle < 0:
self.trajAngle += 360

self.drive.pointWheelsAtAngle(self.trajAngle)
self.magnitude = 0

def execute(self) -> None:
if not self.drive.areWheelsAtCorrectAngle():
self.drive.stopAllMotors()
self.drive.pointWheelsAtAngle(self.trajAngle)
return

self.magnitude += kdefaultMagIncrease
self.magnitude = min(kmagMax, max(-kmagMax, self.magnitude))
self.drive.moveAtConstantMagnitude(self.magnitude)

def end(self, interrupted: bool) -> None:
self.drive.stopAllMotors()

def isFinished(self) -> bool:
pose = self.drive.getPose()
return pose.X() == self.finalX and pose.Y() == self.finalY

class RotateToDegree(CommandBase):

def __init__(self, drive: SwerveDrive, angle: float) -> None:
super().__init__()

self.drive = drive
self.addRequirements([self.drive])

self.desAngle = angle

def initialize(self) -> None:
self.drive.turnInPlace(0)

def execute(self) -> None:
if not self.drive.areWheelsAtCorrectAngle():
return

angleDiff = (self.drive.getYaw() + 180) - self.desAngle
if angleDiff < 0:
angleDiff += 360

if angleDiff > 180:
self.drive.turnInPlace(-0.05, applyRotationMultiplier=False, applySpeedMultiplier=False)

else:
self.drive.turnInPlace(0.05, applyRotationMultiplier=False, applySpeedMultiplier=False)

def end(self, interrupted: bool) -> None:
self.drive.stopAllMotors()

def isFinished(self) -> bool:
return (round(self.desAngle) == round(self.drive.getYaw() + 180))

class FollowPathNoRotation(CommandBase):
"""
Follows the specified PathPlanner path without rotation.

Due to current development deadline limitations, following a path from PathPlanner with rotation is currently unavailable.
"""

def __init__(self, swerveDrive: SwerveDrive, pathName: str,
pathConstraints: PathConstraints=kdefaultPathConstraints, isReversed: bool = False) -> None:
super().__init__()

def initialize(self) -> None:
pass

def execute(self) -> None:
pass

def end(self, interrupted: bool) -> None:
pass

def isFinished(self) -> bool:
return True

class AutoRotate(CommandBase):
"""
Rotates the robot to the specified degree related to the NavX sensor.
"""

def __init__(self) -> None:
super().__init__()

def initialize(self) -> None:
pass

def execute(self) -> None:
pass

def end(self, interrupted: bool) -> None:
pass

def isFinished(self) -> bool:
return True

Loading