Skip to content

Commit f7c030b

Browse files
committed
semiauto: framework for goto and pointat
1 parent b4827db commit f7c030b

File tree

2 files changed

+85
-7
lines changed

2 files changed

+85
-7
lines changed

pybot/robotcontainer.py

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,14 @@
1010
#from commands2.sysid import SysIdRoutine
1111
from generated.tuner_constants import TunerConstants
1212
from telemetry import Telemetry
13-
13+
from wpimath.geometry import Pose2d # , Rotation2d
1414
from phoenix6 import swerve#, SignalLogger
1515
from wpimath.units import rotationsToRadians
1616
from lifter import Lifter # Import the Lifter class
1717
import wpilib
1818
import logging
1919
import math
20+
from wpimath.kinematics import ChassisSpeeds
2021

2122
# Configure logging
2223
logging.basicConfig(level=logging.DEBUG)
@@ -28,6 +29,7 @@
2829

2930
INTAKE_MOTOR_ID_TOP = 18
3031
INTAKE_MOTOR_ID_BOTTOM = 22
32+
DUMMY_POSE = Pose2d(2, 1, 1/2)
3133

3234
class RobotContainer:
3335
"""
@@ -74,6 +76,7 @@ def __init__(self) -> None:
7476
# Setup telemetry
7577
self._registerTelemetry()
7678

79+
7780
def calculateJoystick(self) -> tuple[float, float]:
7881
x0, y0 = self._joystick.getLeftX(), self._joystick.getLeftY()
7982
magnitude = self.applyExponential(math.hypot(x0, y0), self._deadband, self._exponent) * self._max_speed
@@ -84,15 +87,22 @@ def calculateJoystick(self) -> tuple[float, float]:
8487

8588
return x1, y1
8689

87-
def applyRequest(self) -> swerve.requests.SwerveRequest:
90+
def defaultDriveRequest(self) -> swerve.requests.SwerveRequest:
8891
(new_vx, new_vy) = self.calculateJoystick()
8992

9093
return (self._drive.with_velocity_x(self._driveMultiplier * new_vy # Drive left with negative X (left)
9194
) .with_velocity_y(self._driveMultiplier * new_vx) # Drive forward with negative Y (forward)
9295
.with_rotational_rate(
9396
self._driveMultiplier * self.applyExponential(self._joystick.getRightX(), self._deadband, self._exponent) * self._max_angular_rate
9497
)) # Drive counterclockwise with negative X (left)
98+
99+
def create_go_to_coordinate_request(self):
100+
return self.drivetrain.go_to_coordinate(DUMMY_POSE)
95101

102+
def create_point_at_coordinate_request(self):
103+
return self.drivetrain.point_at_coordinate(DUMMY_POSE, self.calculateJoystick())
104+
105+
96106

97107
def configureButtonBindings(self) -> None:
98108
"""
@@ -107,7 +117,7 @@ def configureButtonBindings(self) -> None:
107117

108118
# Note that X is defined as forward according to WPILib convention,
109119
# and Y is defined as to the left according to WPILib convention.
110-
self.drivetrain.setDefaultCommand(self.drivetrain.apply_request(self.applyRequest))
120+
self.drivetrain.setDefaultCommand(self.drivetrain.apply_request(self.defaultDriveRequest))
111121
# reset the field-centric heading on x button press
112122
self._joystick.x().onTrue(
113123
self.drivetrain.runOnce(lambda: self.resetHeading())
@@ -136,6 +146,14 @@ def configureButtonBindings(self) -> None:
136146
lambda: self.intake.stop()
137147
))
138148

149+
self._joystick.b().whileTrue(commands2.cmd.run(
150+
lambda: self.create_point_at_coordinate_request(), self.drivetrain
151+
))
152+
153+
self._joystick.start().whileTrue(commands2.cmd.run(
154+
lambda: self.create_go_to_coordinate_request(), self.drivetrain
155+
))
156+
139157
def resetHeading(self) -> None:
140158
self.drivetrain.seed_field_centric()
141159

@@ -146,15 +164,26 @@ def getAutonomousCommand(self, selected: str) -> commands2.Command:
146164
selected,
147165
is_red_alliance = self.isRedAlliance())
148166

149-
def isRedAlliance(self) -> bool:
150-
return wpilib.DriverStation.getAlliance() == wpilib.DriverStation.Alliance.kRed
151-
152167
def _registerTelemetry (self) -> None:
153168
self.drivetrain.register_telemetry(
154169
lambda state: self._logger.telemeterize(state)
155170
)
156171

157-
def applyExponential(self, input: float, deadband: float, exponent: float) -> float:
172+
173+
@staticmethod
174+
def compute_heading_to_target(current_pose: Pose2d, target_pose: Pose2d) -> float:
175+
relative_pose = current_pose.relativeTo(target_pose)
176+
return math.atan2(relative_pose.Y(), relative_pose.X())
177+
178+
179+
@staticmethod
180+
def isRedAlliance() -> bool:
181+
return wpilib.DriverStation.getAlliance() == wpilib.DriverStation.Alliance.kRed
182+
183+
184+
185+
@staticmethod
186+
def applyExponential(input: float, deadband: float, exponent: float) -> float:
158187
"""
159188
Apply an exponential response curve with a deadband on the input
160189
such that the final output goes smoothly from 0 -> ±1 without

pybot/subsystems/command_swerve_drivetrain.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,8 @@ def add_vision_measurement(self, vision_robot_pose: Pose2d, timestamp: units.sec
327327
:type vision_measurement_std_devs: tuple[float, float, float] | None
328328
"""
329329
swerve.SwerveDrivetrain.add_vision_measurement(self, vision_robot_pose, utils.fpga_to_current_time(timestamp), vision_measurement_std_devs)
330+
331+
330332
def get_pose(self) -> Pose2d:
331333
"""
332334
Gets the current pose of the robot.
@@ -335,6 +337,7 @@ def get_pose(self) -> Pose2d:
335337
:rtype: Pose2d
336338
"""
337339
return super().get_state().pose
340+
338341
def follow_trajectory(self, sample):
339342
# Enable continuous input for heading controller
340343
self.heading_controller.enableContinuousInput(-math.pi, math.pi)
@@ -359,6 +362,47 @@ def follow_trajectory(self, sample):
359362

360363
self.set_control(request)
361364

365+
def go_to_coordinate(self, target_pose: Pose2d):
366+
# Enable continuous input for heading controller
367+
self.heading_controller.enableContinuousInput(-math.pi, math.pi)
368+
369+
# Get current pose from swerve state
370+
current_pose = self.get_pose()
371+
372+
# Calculate the necessary speeds using PID controllers
373+
vx = self.x_controller.calculate(current_pose.X(), target_pose.X())
374+
vy = self.y_controller.calculate(current_pose.Y(), target_pose.Y())
375+
omega = self.heading_controller.calculate(current_pose.rotation().radians(), target_pose.rotation().radians())
376+
377+
speeds = ChassisSpeeds(vx, vy, omega)
378+
379+
# Create field-centric request with proper enum references
380+
request = swerve.requests.ApplyFieldSpeeds().with_speeds(speeds) \
381+
.with_drive_request_type(swerve.swerve_module.SwerveModule.DriveRequestType.VELOCITY) \
382+
.with_steer_request_type(swerve.swerve_module.SwerveModule.SteerRequestType.POSITION) \
383+
.with_desaturate_wheel_speeds(True)
384+
385+
self.set_control(request)
386+
387+
def point_at_coordinate(self, target_pose: Pose2d, joyvalues: tuple[float, float]):
388+
forward, strafe = joyvalues[1], joyvalues[0]
389+
current_pose = self.get_pose()
390+
391+
heading_to_target = self.compute_heading_to_target(current_pose, target_pose)
392+
current_heading = current_pose.rotation().radians()
393+
394+
self.heading_controller.setSetpoint(heading_to_target)
395+
turn_command = self.heading_controller.calculate(current_heading)
396+
397+
request = (
398+
swerve.requests.ApplyFieldSpeeds()
399+
.with_speeds(ChassisSpeeds(forward, strafe, turn_command))
400+
.with_drive_request_type(swerve.swerve_module.SwerveModule.DriveRequestType.VELOCITY)
401+
.with_steer_request_type(swerve.swerve_module.SwerveModule.SteerRequestType.POSITION)
402+
.with_desaturate_wheel_speeds(True)
403+
)
404+
self.set_control(request)
405+
362406
def stop(self):
363407
"""
364408
Stops the swerve drivetrain by setting all speeds to zero.
@@ -369,3 +413,8 @@ def stop(self):
369413
.with_steer_request_type(swerve.swerve_module.SwerveModule.SteerRequestType.POSITION) \
370414
.with_desaturate_wheel_speeds(True)
371415
self.set_control(request)
416+
417+
@staticmethod
418+
def compute_heading_to_target(current_pose: Pose2d, target_pose: Pose2d) -> float:
419+
relative_pose = current_pose.relativeTo(target_pose)
420+
return math.atan2(relative_pose.Y(), relative_pose.X())

0 commit comments

Comments
 (0)