Skip to content

Commit 0f9447a

Browse files
author
NewtonCrosby
committed
Implemented RamseteCommand with overtake and beartype for better multiple dispatch. robotpy#28.
1 parent 533027e commit 0f9447a

File tree

3 files changed

+84
-161
lines changed

3 files changed

+84
-161
lines changed

Diff for: commands2/ramsetecommand.py

+55-107
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from __future__ import annotations
55

66
from typing import Callable, Union, Optional, Tuple, overload
7+
from overtake import overtake
78
from wpimath.controller import (
89
PIDController,
910
RamseteController,
@@ -47,44 +48,7 @@ def __init__(
4748
controller: RamseteController,
4849
kinematics: DifferentialDriveKinematics,
4950
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-
72-
def __init__(
73-
self,
74-
trajectory: Trajectory,
75-
pose: Callable[[], Pose2d],
76-
controller: RamseteController,
77-
kinematics: DifferentialDriveKinematics,
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,
84-
feedforward: Optional[SimpleMotorFeedforwardMeters] = None,
85-
leftController: Optional[PIDController] = None,
86-
rightController: Optional[PIDController] = None,
87-
wheelSpeeds: Optional[Callable[[], DifferentialDriveWheelSpeeds]] = None,
51+
output: Callable[[float, float], None],
8852
):
8953
"""Constructs a new RamseteCommand that, when executed, will follow the provided trajectory. PID
9054
control and feedforward are handled internally, and outputs are scaled -12 to 12 representing
@@ -99,17 +63,37 @@ def __init__(
9963
:param controller: The RAMSETE controller used to follow the trajectory.
10064
:param kinematics: The kinematics for the robot drivetrain.
10165
:param requirements: The subsystems to require.
66+
:param output A function that consumes the computed left and right outputs in `units.meters_per_second`
67+
"""
68+
super().__init__()
10269

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-
70+
self._timer = Timer()
71+
self.trajectory = trajectory
72+
self.pose = pose
73+
self.follower = controller
74+
self.kinematics = kinematics
75+
self.output = output
76+
self.usePID = False
77+
# All the parameter checks pass, inform scheduler. Type ignoring is set explictitly for the linter because this
78+
# implementation declares the tuple explicitly, whereas the general implementations use the unpack operator (*)
79+
# to pass the requirements to the scheduler.
80+
self.addRequirements(requirements) # type: ignore
11181

112-
Constructs a new RamseteCommand that, when executed, will follow the provided trajectory. PID
82+
@overload
83+
def __init__(
84+
self,
85+
trajectory: Trajectory,
86+
pose: Callable[[], Pose2d],
87+
controller: RamseteController,
88+
kinematics: DifferentialDriveKinematics,
89+
requirements: Tuple[Subsystem],
90+
output: Callable[[float, float], None],
91+
feedforward: SimpleMotorFeedforwardMeters,
92+
leftController: PIDController,
93+
rightController: PIDController,
94+
wheelSpeeds: Callable[[], DifferentialDriveWheelSpeeds],
95+
):
96+
"""Constructs a new RamseteCommand that, when executed, will follow the provided trajectory. PID
11397
control and feedforward are handled internally, and outputs are scaled -12 to 12 representing
11498
units of volts.
11599
@@ -122,20 +106,7 @@ def __init__(
122106
:param controller: The RAMSETE controller used to follow the trajectory.
123107
:param kinematics: The kinematics for the robot drivetrain.
124108
: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-
134-
OPTIONAL PARAMETERS
135-
When the following optional parameters are provided, when executed, the RamseteCommand will follow
136-
provided trajectory. PID control and feedforward are handled internally, and the outputs are scaled
137-
from -12 to 12 representing units of volts. If any one of the optional parameters are provided, each
138-
other optional parameter becomes required.
109+
:param output A function that consumes the computed left and right outputs in `units.volts`
139110
:param feedforward The feedforward to use for the drive
140111
:param leftController: The PIDController for the left side of the robot drive.
141112
:param rightController: The PIDController for the right side of the robot drive.
@@ -151,53 +122,36 @@ def __init__(
151122
self.pose = pose
152123
self.follower = controller
153124
self.kinematics = kinematics
154-
self.outputMPS = outputMPS
155-
self.outputVolts = outputVolts
125+
self.output = output
156126
self.leftController = leftController
157127
self.rightController = rightController
158128
self.wheelspeeds = wheelSpeeds
159129
self.feedforward = feedforward
160130
self._prevSpeeds = DifferentialDriveWheelSpeeds()
161131
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-
174-
# Optional requirements checks. If any one of the optional parameters are not None, then all the optional
175-
# requirements become required.
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-
):
188-
raise RuntimeError(
189-
f"Failed to instantiate RamseteCommand, not all optional arguments were provided.\n \
190-
Feedforward - {feedforward}, LeftController - {leftController}, RightController - {rightController}, WheelSpeeds - {wheelSpeeds} "
191-
)
192-
self.usePID = True
193-
else:
194-
self.usePID = False
195-
132+
self.usePID = True
196133
# All the parameter checks pass, inform scheduler. Type ignoring is set explictitly for the linter because this
197134
# implementation declares the tuple explicitly, whereas the general implementations use the unpack operator (*)
198135
# to pass the requirements to the scheduler.
199136
self.addRequirements(requirements) # type: ignore
200137

138+
@overtake(runtime_type_checker="beartype")
139+
def __init__(
140+
self,
141+
*,
142+
trajectory: Trajectory,
143+
pose: Callable[[], Pose2d],
144+
controller: RamseteController,
145+
kinematics: DifferentialDriveKinematics,
146+
requirements: Tuple[Subsystem],
147+
output: Callable[[float, float], None],
148+
feedforward: Optional[SimpleMotorFeedforwardMeters] = None,
149+
leftController: Optional[PIDController] = None,
150+
rightController: Optional[PIDController] = None,
151+
wheelSpeeds: Optional[Callable[[], DifferentialDriveWheelSpeeds]] = None,
152+
):
153+
...
154+
201155
def initialize(self):
202156
self._prevTime = -1
203157
initial_state = self.trajectory.sample(0)
@@ -218,10 +172,7 @@ def execute(self):
218172
dt = curTime - self._prevTime
219173

220174
if self._prevTime < 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)
175+
self.output(0.0, 0.0)
225176
self._prevTime = curTime
226177
return
227178

@@ -249,11 +200,11 @@ def execute(self):
249200
rightOutput = rightFeedforward + self.rightController.calculate(
250201
self.wheelspeeds().right, rightSpeedSetpoint
251202
)
252-
self.outputVolts(leftOutput, rightOutput)
203+
self.output(leftOutput, rightOutput)
253204
else:
254205
leftOutput = leftSpeedSetpoint
255206
rightOutput = rightSpeedSetpoint
256-
self.outputMPS(leftOutput, rightOutput)
207+
self.output(leftOutput, rightOutput)
257208

258209
self._prevSpeeds = targetWheelSpeeds
259210
self._prevTime = curTime
@@ -262,10 +213,7 @@ def end(self, interrupted: bool):
262213
self._timer.stop()
263214

264215
if interrupted:
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)
216+
self.output(0.0, 0.0)
269217

270218
def isFinished(self):
271219
return self._timer.hasElapsed(self.trajectory.totalTime())

Diff for: setup.py

+6-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,12 @@
1111
description="WPILib command framework v2",
1212
url="https://github.com/robotpy/robotpy-commands-v2",
1313
packages=["commands2"],
14-
install_requires=["wpilib<2025,>=2024.0.0b2", "typing_extensions>=4.1.0,<5"],
14+
install_requires=[
15+
"wpilib<2025,>=2024.0.0b2",
16+
"typing_extensions>=4.1.0,<5",
17+
"overtake>=0.4.0",
18+
"beartype>=0.16.4",
19+
],
1520
license="BSD-3-Clause",
1621
python_requires=">=3.8",
1722
include_package_data=True,

0 commit comments

Comments
 (0)