Skip to content

Commit 533027e

Browse files
author
NewtonCrosby
committed
Updated implementation to use the overload typing annotation. Adds more tests to check for correct argument parsing in constructor
1 parent 849ba52 commit 533027e

File tree

2 files changed

+428
-27
lines changed

2 files changed

+428
-27
lines changed

commands2/ramsetecommand.py

Lines changed: 117 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
# the WPILib BSD license file in the root directory of this project.
44
from __future__ import annotations
55

6-
from typing import Callable, Union, Optional
6+
from typing import Callable, Union, Optional, Tuple, overload
77
from wpimath.controller import (
88
PIDController,
99
RamseteController,
@@ -16,6 +16,7 @@
1616
DifferentialDriveWheelSpeeds,
1717
)
1818
from wpimath.trajectory import Trajectory
19+
from wpimath import units as units
1920
from wpiutil import SendableBuilder
2021
from wpilib import Timer
2122

@@ -36,18 +37,50 @@ class RamseteCommand(Command):
3637
Advanced teams seeking more flexibility (for example, those who wish to use the onboard PID
3738
functionality of a "smart" motor controller) may use the secondary constructor that omits the PID
3839
and feedforward functionality, returning only the raw wheel speeds from the RAMSETE controller.
39-
40-
This class is provided by the NewCommands VendorDep
4140
"""
4241

42+
@overload
43+
def __init__(
44+
self,
45+
trajectory: Trajectory,
46+
pose: Callable[[], Pose2d],
47+
controller: RamseteController,
48+
kinematics: DifferentialDriveKinematics,
49+
requirements: Tuple[Subsystem],
50+
*,
51+
outputMPS: Callable[[units.meters_per_second, units.meters_per_second], None],
52+
):
53+
...
54+
55+
@overload
56+
def __init__(
57+
self,
58+
trajectory: Trajectory,
59+
pose: Callable[[], Pose2d],
60+
controller: RamseteController,
61+
kinematics: DifferentialDriveKinematics,
62+
requirements: Tuple[Subsystem],
63+
*,
64+
outputVolts: Callable[[units.volts, units.volts], None],
65+
feedforward: SimpleMotorFeedforwardMeters,
66+
leftController: PIDController,
67+
rightController: PIDController,
68+
wheelSpeeds: Callable[[], DifferentialDriveWheelSpeeds],
69+
):
70+
...
71+
4372
def __init__(
4473
self,
4574
trajectory: Trajectory,
4675
pose: Callable[[], Pose2d],
4776
controller: RamseteController,
4877
kinematics: DifferentialDriveKinematics,
49-
outputVolts: Callable[[float, float], None],
50-
*requirements: Subsystem,
78+
requirements: Tuple[Subsystem],
79+
*,
80+
outputMPS: Optional[
81+
Callable[[units.meters_per_second, units.meters_per_second], None]
82+
] = None,
83+
outputVolts: Optional[Callable[[units.volts, units.volts], None]] = None,
5184
feedforward: Optional[SimpleMotorFeedforwardMeters] = None,
5285
leftController: Optional[PIDController] = None,
5386
rightController: Optional[PIDController] = None,
@@ -65,10 +98,39 @@ def __init__(
6598
provide this.
6699
:param controller: The RAMSETE controller used to follow the trajectory.
67100
:param kinematics: The kinematics for the robot drivetrain.
68-
:param outputVolts: A function that consumes the computed left and right outputs (in volts) for
69-
the robot drive.
70101
:param requirements: The subsystems to require.
71102
103+
REQUIRED KEYWORD PARAMETERS
104+
Note: The output mode must be specified by the users of RamseteCommands. Users can specify ONE of
105+
an output function that will supply the consumer with left and right speeds in `units.meters_per_second`
106+
or in `units.volts`. If neither, or both, are supplied to the constructor a `RuntimeError` will be
107+
raised.
108+
:param outputVolts A function that consumes the computed left and right outputs in `units.volts`
109+
:param outputMPS A function that consumes the computed left and right outputs in `units.meters_per_second`
110+
111+
112+
Constructs a new RamseteCommand that, when executed, will follow the provided trajectory. PID
113+
control and feedforward are handled internally, and outputs are scaled -12 to 12 representing
114+
units of volts.
115+
116+
Note: The controller will *not* set the outputVolts to zero upon completion of the path -
117+
this is left to the user, since it is not appropriate for paths with nonstationary endstates.
118+
119+
:param trajectory: The trajectory to follow.
120+
:param pose: A function that supplies the robot pose - use one of the odometry classes to
121+
provide this.
122+
:param controller: The RAMSETE controller used to follow the trajectory.
123+
:param kinematics: The kinematics for the robot drivetrain.
124+
:param requirements: The subsystems to require.
125+
126+
REQUIRED KEYWORD PARAMETERS
127+
Note: The output mode must be specified by the users of RamseteCommands. Users can specify ONE of
128+
an output function that will supply the consumer with left and right speeds in `units.meters_per_second`
129+
or in `units.volts`. If neither, or both, are supplied to the constructor a `RuntimeError` will be
130+
raised.
131+
:param outputVolts A function that consumes the computed left and right outputs in `units.volts`
132+
:param outputMPS A function that consumes the computed left and right outputs in `units.meters_per_second`
133+
72134
OPTIONAL PARAMETERS
73135
When the following optional parameters are provided, when executed, the RamseteCommand will follow
74136
provided trajectory. PID control and feedforward are handled internally, and the outputs are scaled
@@ -89,26 +151,52 @@ def __init__(
89151
self.pose = pose
90152
self.follower = controller
91153
self.kinematics = kinematics
92-
self.output = outputVolts
93-
self.usePID = False
154+
self.outputMPS = outputMPS
155+
self.outputVolts = outputVolts
156+
self.leftController = leftController
157+
self.rightController = rightController
158+
self.wheelspeeds = wheelSpeeds
159+
self.feedforward = feedforward
160+
self._prevSpeeds = DifferentialDriveWheelSpeeds()
161+
self._prevTime = -1.0
162+
163+
# Required requirements check for output
164+
if outputMPS is None and outputVolts is None:
165+
raise RuntimeError(
166+
f"Failed to instantiate RamseteCommand, no output consumer provided. Must provide either output in meters per second or volts."
167+
)
168+
169+
if outputMPS is not None and outputVolts is not None:
170+
raise RuntimeError(
171+
f"Failed to instantiate RamseteCommand, too many consumers provided. Must provide either output in meters per second or volts but not both."
172+
)
173+
94174
# Optional requirements checks. If any one of the optional parameters are not None, then all the optional
95175
# requirements become required.
96-
if feedforward or leftController or rightController or wheelSpeeds is not None:
97-
if feedforward or leftController or rightController or wheelSpeeds is None:
176+
if (
177+
feedforward is not None
178+
or leftController is not None
179+
or rightController is not None
180+
or wheelSpeeds is not None
181+
):
182+
if (
183+
feedforward is None
184+
or leftController is None
185+
or rightController is None
186+
or wheelSpeeds is None
187+
):
98188
raise RuntimeError(
99189
f"Failed to instantiate RamseteCommand, not all optional arguments were provided.\n \
100190
Feedforward - {feedforward}, LeftController - {leftController}, RightController - {rightController}, WheelSpeeds - {wheelSpeeds} "
101191
)
102-
103-
self.leftController = leftController
104-
self.rightController = rightController
105-
self.wheelspeeds = wheelSpeeds
106-
self.feedforward = feedforward
107192
self.usePID = True
108-
self._prevSpeeds = DifferentialDriveWheelSpeeds()
109-
self._prevTime = -1.0
193+
else:
194+
self.usePID = False
110195

111-
self.addRequirements(*requirements)
196+
# All the parameter checks pass, inform scheduler. Type ignoring is set explictitly for the linter because this
197+
# implementation declares the tuple explicitly, whereas the general implementations use the unpack operator (*)
198+
# to pass the requirements to the scheduler.
199+
self.addRequirements(requirements) # type: ignore
112200

113201
def initialize(self):
114202
self._prevTime = -1
@@ -130,7 +218,10 @@ def execute(self):
130218
dt = curTime - self._prevTime
131219

132220
if self._prevTime < 0:
133-
self.output(0.0, 0.0)
221+
if self.outputVolts is not None:
222+
self.outputVolts(0.0, 0.0)
223+
if self.outputMPS is not None:
224+
self.outputMPS(0.0, 0.0)
134225
self._prevTime = curTime
135226
return
136227

@@ -158,19 +249,23 @@ def execute(self):
158249
rightOutput = rightFeedforward + self.rightController.calculate(
159250
self.wheelspeeds().right, rightSpeedSetpoint
160251
)
252+
self.outputVolts(leftOutput, rightOutput)
161253
else:
162254
leftOutput = leftSpeedSetpoint
163255
rightOutput = rightSpeedSetpoint
256+
self.outputMPS(leftOutput, rightOutput)
164257

165-
self.output(leftOutput, rightOutput)
166258
self._prevSpeeds = targetWheelSpeeds
167259
self._prevTime = curTime
168260

169261
def end(self, interrupted: bool):
170262
self._timer.stop()
171263

172264
if interrupted:
173-
self.output(0.0, 0.0)
265+
if self.outputVolts is not None:
266+
self.outputVolts(0.0, 0.0)
267+
if self.outputMPS is not None:
268+
self.outputMPS(0.0, 0.0)
174269

175270
def isFinished(self):
176271
return self._timer.hasElapsed(self.trajectory.totalTime())

0 commit comments

Comments
 (0)