3
3
# the WPILib BSD license file in the root directory of this project.
4
4
from __future__ import annotations
5
5
6
- from typing import Callable , Union , Optional
6
+ from typing import Callable , Union , Optional , Tuple , overload
7
7
from wpimath .controller import (
8
8
PIDController ,
9
9
RamseteController ,
16
16
DifferentialDriveWheelSpeeds ,
17
17
)
18
18
from wpimath .trajectory import Trajectory
19
+ from wpimath import units as units
19
20
from wpiutil import SendableBuilder
20
21
from wpilib import Timer
21
22
@@ -36,18 +37,50 @@ class RamseteCommand(Command):
36
37
Advanced teams seeking more flexibility (for example, those who wish to use the onboard PID
37
38
functionality of a "smart" motor controller) may use the secondary constructor that omits the PID
38
39
and feedforward functionality, returning only the raw wheel speeds from the RAMSETE controller.
39
-
40
- This class is provided by the NewCommands VendorDep
41
40
"""
42
41
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
+
43
72
def __init__ (
44
73
self ,
45
74
trajectory : Trajectory ,
46
75
pose : Callable [[], Pose2d ],
47
76
controller : RamseteController ,
48
77
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 ,
51
84
feedforward : Optional [SimpleMotorFeedforwardMeters ] = None ,
52
85
leftController : Optional [PIDController ] = None ,
53
86
rightController : Optional [PIDController ] = None ,
@@ -65,10 +98,39 @@ def __init__(
65
98
provide this.
66
99
:param controller: The RAMSETE controller used to follow the trajectory.
67
100
: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.
70
101
:param requirements: The subsystems to require.
71
102
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
+
72
134
OPTIONAL PARAMETERS
73
135
When the following optional parameters are provided, when executed, the RamseteCommand will follow
74
136
provided trajectory. PID control and feedforward are handled internally, and the outputs are scaled
@@ -89,26 +151,52 @@ def __init__(
89
151
self .pose = pose
90
152
self .follower = controller
91
153
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
+
94
174
# Optional requirements checks. If any one of the optional parameters are not None, then all the optional
95
175
# 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
+ ):
98
188
raise RuntimeError (
99
189
f"Failed to instantiate RamseteCommand, not all optional arguments were provided.\n \
100
190
Feedforward - { feedforward } , LeftController - { leftController } , RightController - { rightController } , WheelSpeeds - { wheelSpeeds } "
101
191
)
102
-
103
- self .leftController = leftController
104
- self .rightController = rightController
105
- self .wheelspeeds = wheelSpeeds
106
- self .feedforward = feedforward
107
192
self .usePID = True
108
- self . _prevSpeeds = DifferentialDriveWheelSpeeds ()
109
- self ._prevTime = - 1.0
193
+ else :
194
+ self .usePID = False
110
195
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
112
200
113
201
def initialize (self ):
114
202
self ._prevTime = - 1
@@ -130,7 +218,10 @@ def execute(self):
130
218
dt = curTime - self ._prevTime
131
219
132
220
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 )
134
225
self ._prevTime = curTime
135
226
return
136
227
@@ -158,19 +249,23 @@ def execute(self):
158
249
rightOutput = rightFeedforward + self .rightController .calculate (
159
250
self .wheelspeeds ().right , rightSpeedSetpoint
160
251
)
252
+ self .outputVolts (leftOutput , rightOutput )
161
253
else :
162
254
leftOutput = leftSpeedSetpoint
163
255
rightOutput = rightSpeedSetpoint
256
+ self .outputMPS (leftOutput , rightOutput )
164
257
165
- self .output (leftOutput , rightOutput )
166
258
self ._prevSpeeds = targetWheelSpeeds
167
259
self ._prevTime = curTime
168
260
169
261
def end (self , interrupted : bool ):
170
262
self ._timer .stop ()
171
263
172
264
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 )
174
269
175
270
def isFinished (self ):
176
271
return self ._timer .hasElapsed (self .trajectory .totalTime ())
0 commit comments