2222import com .team2813 .lib2813 .control .Encoder ;
2323import com .team2813 .lib2813 .control .Motor ;
2424import com .team2813 .lib2813 .control .PIDMotor ;
25+ import edu .wpi .first .math .MathSharedStore ;
2526import edu .wpi .first .math .controller .PIDController ;
2627import edu .wpi .first .networktables .BooleanPublisher ;
2728import edu .wpi .first .networktables .DoublePublisher ;
3031import edu .wpi .first .units .AngleUnit ;
3132import edu .wpi .first .units .Units ;
3233import edu .wpi .first .units .measure .Angle ;
33- import edu .wpi .first .units .measure .AngularVelocity ;
3434import edu .wpi .first .units .measure .Current ;
35+ import edu .wpi .first .util .sendable .Sendable ;
36+ import edu .wpi .first .util .sendable .SendableBuilder ;
3537import edu .wpi .first .wpilibj2 .command .Command ;
3638import edu .wpi .first .wpilibj2 .command .InstantCommand ;
3739import edu .wpi .first .wpilibj2 .command .SubsystemBase ;
5557 * controller. The motor system's {@link #isEnabled()} returns {@code true}.
5658 *
5759 * <p>The <b>Direct User Input Mode</b> is activated when the user calls the {@link
58- * #set(ControlMode,double,double)} or {@link #set(ControlMode,double)} method, where the user
59- * provides direct input of type ControlType (specified via {@link
60- * MotorSubsystemConfiguration#controlMode(ControlMode)}). The PID Mode is interrupted and
61- * disengaged, and {@link #isEnabled()} returns {@code false}. It can be re-engaged with the {@link
62- * #enable()} method and will resume movement toward setpoint.
60+ * Motor#set(ControlMode,double,double)} or {@link Motor#set(ControlMode,double)} method on the
61+ * {@link #motor} field. The PID Mode is interrupted and disengaged, and {@link #isEnabled()}
62+ * returns {@code false}. It can be re-engaged with the {@link #enable()} method and will resume
63+ * movement toward setpoint.
6364 *
6465 * @param <T> the type of the {@link Supplier<Angle>} used to specify setpoints.
6566 */
66- public abstract class MotorSubsystem <T extends Supplier <Angle >> extends SubsystemBase
67- implements Motor , Encoder {
67+ public abstract class MotorSubsystem <T extends Supplier <Angle >> extends SubsystemBase {
6868
69- protected final Motor motor ;
69+ protected final ControlledMotor motor ;
7070 protected final Encoder encoder ;
7171 protected final ControlMode controlMode ;
7272 protected final AngleUnit rotationUnit ;
73- protected final double acceptableError ;
74- protected final PIDController controller ;
73+ private final PIDController controller ;
7574 private final DoublePublisher positionPublisher ;
7675 private final BooleanPublisher atPositionPublisher ;
7776 private final DoublePublisher appliedCurrentPublisher ;
7877 private final DoublePublisher setpointPublisher ;
7978
80- private boolean isEnabled ;
79+ private boolean pidControlEnabled ;
8180
8281 protected MotorSubsystem (MotorSubsystemConfiguration builder ) {
8382 controller = builder .controller ;
8483 controller .setTolerance (builder .acceptableError );
8584 controller .setSetpoint (builder .startingPosition );
86- acceptableError = builder .acceptableError ;
87- motor = builder .motor ;
85+ motor = new ControlledMotor (builder .motor );
8886 encoder = builder .encoder ;
8987 controlMode = builder .controlMode ;
9088 rotationUnit = builder .rotationUnit ;
@@ -100,6 +98,7 @@ protected MotorSubsystem(MotorSubsystemConfiguration builder) {
10098 appliedCurrentPublisher = null ;
10199 setpointPublisher = null ;
102100 }
101+ addChild ("controller" , new PIDSendable ());
103102 }
104103
105104 /**
@@ -108,9 +107,7 @@ protected MotorSubsystem(MotorSubsystemConfiguration builder) {
108107 * @param position the position to go to.
109108 */
110109 public final void setSetpoint (T position ) {
111- if (!isEnabled ()) {
112- enable ();
113- }
110+ pidControlEnabled = true ;
114111 double setpoint = position .get ().in (rotationUnit );
115112 controller .setSetpoint (setpoint );
116113 }
@@ -132,23 +129,7 @@ public final Angle getSetpoint() {
132129
133130 /** Determines if the motor is at the current setpoint, within the acceptable error. */
134131 public final boolean atPosition () {
135- return Math .abs (getMeasurement () - controller .getSetpoint ()) <= acceptableError ;
136- }
137-
138- /**
139- * {@inheritDoc}
140- *
141- * <p>Additionally, this method disables PID control of the subsystem
142- */
143- @ Override
144- public final void set (ControlMode mode , double demand , double feedForward ) {
145- isEnabled = false ;
146- motor .set (mode , demand , feedForward );
147- }
148-
149- @ Override
150- public final Current getAppliedCurrent () {
151- return motor .getAppliedCurrent ();
132+ return controller .atSetpoint ();
152133 }
153134
154135 /**
@@ -161,21 +142,7 @@ public final Current getAppliedCurrent() {
161142 * interruption, the motor will resume its movement towards the last set setpoint.
162143 */
163144 public final void enable () {
164- isEnabled = true ;
165- }
166-
167- /**
168- * Stops the motor.
169- *
170- * <p>The motor voltage will be set to zero, and the motor will not adjust to move towards the
171- * current setpoint.
172- *
173- * @since 2.0.0
174- */
175- @ Override
176- public final void stopMotor () {
177- isEnabled = false ;
178- motor .stopMotor ();
145+ pidControlEnabled = true ;
179146 }
180147
181148 /**
@@ -188,28 +155,28 @@ public final void stopMotor() {
188155 * @return Whether the PID controller is engaged.
189156 */
190157 public final boolean isEnabled () {
191- return isEnabled ;
158+ return pidControlEnabled ;
192159 }
193160
194161 /**
195- * {@inheritDoc}
162+ * Stops the motor.
163+ *
164+ * <p>The motor voltage will be set to zero, and the motor will not adjust to move towards the
165+ * current setpoint.
196166 *
197- * <p>Additionally, this method disables PID control of the subsystem. It <em>does not</em> clamp
198- * the provided value.
167+ * @since 2.0.0
199168 */
200- @ Override
201- public final void set (ControlMode mode , double demand ) {
202- isEnabled = false ;
203- motor .set (mode , demand );
169+ public final void stopMotor () {
170+ motor .stopMotor ();
204171 }
205172
206173 /**
207- * Clamps the given output value and provides it to the motor.
174+ * Returns a command that stops the motor.
208175 *
209- * <p>This is called by {@link #periodic()} if this subsystem is enabled.
176+ * @since 2.0.0
210177 */
211- private void useOutput ( double output ) {
212- motor . set ( controlMode , clampOutput ( output ) );
178+ public final Command stopMotorCommand ( ) {
179+ return new InstantCommand ( this :: stopMotor , this );
213180 }
214181
215182 /**
@@ -235,32 +202,18 @@ protected final double getMeasurement() {
235202 return encoder .getPositionMeasure ().in (rotationUnit );
236203 }
237204
238- @ Override
239- public final Angle getPositionMeasure () {
240- return encoder .getPositionMeasure ();
241- }
242-
243- @ Override
244- public final void setPosition (Angle position ) {
245- encoder .setPosition (position );
246- }
247-
248- @ Override
249- public final AngularVelocity getVelocityMeasure () {
250- return encoder .getVelocityMeasure ();
251- }
252-
253205 /** Applies the PID output to the motor if this subsystem is enabled. */
254206 @ Override
255207 public void periodic () {
256- if (isEnabled ) {
257- useOutput (controller .calculate (getMeasurement ()));
208+ if (pidControlEnabled ) {
209+ double position = getMeasurement ();
210+ motor .set (controlMode , clampOutput (position ));
258211 }
259212 if (positionPublisher != null ) {
260- positionPublisher .set (getPositionMeasure ().in (Rotations ));
213+ positionPublisher .set (encoder . getPositionMeasure ().in (Rotations ));
261214 setpointPublisher .set (getSetpoint ().in (Rotations ));
262215 atPositionPublisher .set (atPosition ());
263- appliedCurrentPublisher .set (getAppliedCurrent ().in (Amps ));
216+ appliedCurrentPublisher .set (motor . getAppliedCurrent ().in (Amps ));
264217 }
265218 }
266219
@@ -383,8 +336,12 @@ public MotorSubsystemConfiguration startingPosition(Supplier<Angle> startingPosi
383336 *
384337 * @param error the error which is considered tolerable for use with {@code }atPosition()}
385338 * @return {@code this} for chaining
339+ * @throws IllegalArgumentException If {@code error} is negative
386340 */
387341 public MotorSubsystemConfiguration acceptableError (double error ) {
342+ if (error < 0 ) {
343+ throw new IllegalArgumentException ("AcceptableError cannot be negative" );
344+ }
388345 this .acceptableError = error ;
389346 return this ;
390347 }
@@ -413,4 +370,86 @@ public MotorSubsystemConfiguration publishTo(NetworkTableInstance ntInstance) {
413370 return this ;
414371 }
415372 }
373+
374+ /** A motor that is controlled by a subclass of {@link MotorSubsystem}. */
375+ protected final class ControlledMotor implements Motor {
376+ private final Motor motor ;
377+
378+ private ControlledMotor (Motor motor ) {
379+ this .motor = motor ;
380+ }
381+
382+ /**
383+ * {@inheritDoc}
384+ *
385+ * <p>Additionally, this method disables PID control of the subsystem
386+ */
387+ @ Override
388+ public void set (ControlMode mode , double demand ) {
389+ pidControlEnabled = false ;
390+ motor .set (mode , demand );
391+ }
392+
393+ /**
394+ * {@inheritDoc}
395+ *
396+ * <p>Additionally, this method disables PID control of the subsystem. It <em>does not</em>
397+ * clamp the provided value.
398+ */
399+ @ Override
400+ public void set (ControlMode mode , double demand , double feedForward ) {
401+ pidControlEnabled = false ;
402+ motor .set (mode , demand , feedForward );
403+ }
404+
405+ @ Override
406+ public Current getAppliedCurrent () {
407+ return motor .getAppliedCurrent ();
408+ }
409+
410+ /**
411+ * Stops the motor.
412+ *
413+ * <p>The motor voltage will be set to zero, and the motor will not adjust to move towards the
414+ * current setpoint.
415+ */
416+ @ Override
417+ public void stopMotor () {
418+ pidControlEnabled = false ;
419+ motor .stopMotor ();
420+ }
421+ }
422+
423+ /** A Sendable implementation that allows users to update PID settings. */
424+ private class PIDSendable implements Sendable {
425+
426+ /**
427+ * {@inheritDoc
428+ *
429+ * <p>This is modeled after {@link PIDController#initSendable(SendableBuilder)},
430+ * but does not support changing the setpoint.
431+ */
432+ @ Override
433+ public void initSendable (SendableBuilder builder ) {
434+ builder .setSmartDashboardType ("PIDController" );
435+ builder .addDoubleProperty ("p" , controller ::getP , controller ::setP );
436+ builder .addDoubleProperty ("i" , controller ::getI , controller ::setI );
437+ builder .addDoubleProperty ("d" , controller ::getD , controller ::setD );
438+ builder .addDoubleProperty (
439+ "izone" ,
440+ controller ::getIZone ,
441+ (double toSet ) -> {
442+ try {
443+ controller .setIZone (toSet );
444+ } catch (IllegalArgumentException e ) {
445+ MathSharedStore .reportError (
446+ "IZone must be a non-negative number!" , e .getStackTrace ());
447+ }
448+ });
449+ builder .addDoubleProperty (
450+ "acceptableError" ,
451+ controller ::getErrorTolerance ,
452+ t -> controller .setTolerance (Math .abs (t )));
453+ }
454+ }
416455}
0 commit comments