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 ;
3233import edu .wpi .first .units .measure .Angle ;
3334import edu .wpi .first .units .measure .AngularVelocity ;
3435import edu .wpi .first .units .measure .Current ;
36+ import edu .wpi .first .util .sendable .Sendable ;
37+ import edu .wpi .first .util .sendable .SendableBuilder ;
3538import edu .wpi .first .wpilibj2 .command .Command ;
3639import edu .wpi .first .wpilibj2 .command .InstantCommand ;
3740import edu .wpi .first .wpilibj2 .command .SubsystemBase ;
5558 * controller. The motor system's {@link #isEnabled()} returns {@code true}.
5659 *
5760 * <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.
61+ * Motor#set(ControlMode,double,double)} or {@link Motor#set(ControlMode,double)} method on the
62+ * {@link #motor} field. The PID Mode is interrupted and disengaged, and {@link #isEnabled()}
63+ * returns {@code false}. It can be re-engaged with the {@link #enable()} method and will resume
64+ * movement toward setpoint.
6365 *
6466 * @param <T> the type of the {@link Supplier<Angle>} used to specify setpoints.
6567 */
66- public abstract class MotorSubsystem <T extends Supplier <Angle >> extends SubsystemBase
67- implements Motor , Encoder {
68+ public abstract class MotorSubsystem <T extends Supplier <Angle >> extends SubsystemBase {
6869
69- protected final Motor motor ;
70+ protected final ControlledMotor motor ;
7071 protected final Encoder encoder ;
7172 protected final ControlMode controlMode ;
7273 protected final AngleUnit rotationUnit ;
73- protected final double acceptableError ;
74- protected final PIDController controller ;
74+ private final PIDController controller ;
7575 private final DoublePublisher positionPublisher ;
7676 private final BooleanPublisher atPositionPublisher ;
7777 private final DoublePublisher appliedCurrentPublisher ;
7878 private final DoublePublisher setpointPublisher ;
7979
80- private boolean isEnabled ;
80+ private boolean pidControlEnabled ;
8181
8282 protected MotorSubsystem (MotorSubsystemConfiguration builder ) {
8383 controller = builder .controller ;
8484 controller .setTolerance (builder .acceptableError );
8585 controller .setSetpoint (builder .startingPosition );
86- acceptableError = builder .acceptableError ;
87- motor = builder .motor ;
88- encoder = builder .encoder ;
86+ motor = new ControlledMotor (builder .motor );
87+ encoder = new EncoderWrapper (builder .encoder );
8988 controlMode = builder .controlMode ;
9089 rotationUnit = builder .rotationUnit ;
9190 if (builder .ntInstance != null ) {
@@ -100,6 +99,7 @@ protected MotorSubsystem(MotorSubsystemConfiguration builder) {
10099 appliedCurrentPublisher = null ;
101100 setpointPublisher = null ;
102101 }
102+ addChild ("controller" , new PIDSendable ());
103103 }
104104
105105 /**
@@ -108,9 +108,7 @@ protected MotorSubsystem(MotorSubsystemConfiguration builder) {
108108 * @param position the position to go to.
109109 */
110110 public final void setSetpoint (T position ) {
111- if (!isEnabled ()) {
112- enable ();
113- }
111+ pidControlEnabled = true ;
114112 double setpoint = position .get ().in (rotationUnit );
115113 controller .setSetpoint (setpoint );
116114 }
@@ -132,23 +130,7 @@ public final Angle getSetpoint() {
132130
133131 /** Determines if the motor is at the current setpoint, within the acceptable error. */
134132 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 ();
133+ return controller .atSetpoint ();
152134 }
153135
154136 /**
@@ -161,30 +143,7 @@ public final Current getAppliedCurrent() {
161143 * interruption, the motor will resume its movement towards the last set setpoint.
162144 */
163145 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 ();
179- }
180-
181- /**
182- * Returns a command that stops the motor.
183- *
184- * @since 2.0.0
185- */
186- public final Command stopMotorCommand () {
187- return new InstantCommand (this ::stopMotor , this );
146+ pidControlEnabled = true ;
188147 }
189148
190149 /**
@@ -197,28 +156,28 @@ public final Command stopMotorCommand() {
197156 * @return Whether the PID controller is engaged.
198157 */
199158 public final boolean isEnabled () {
200- return isEnabled ;
159+ return pidControlEnabled ;
201160 }
202161
203162 /**
204- * {@inheritDoc}
163+ * Stops the motor.
164+ *
165+ * <p>The motor voltage will be set to zero, and the motor will not adjust to move towards the
166+ * current setpoint.
205167 *
206- * <p>Additionally, this method disables PID control of the subsystem. It <em>does not</em> clamp
207- * the provided value.
168+ * @since 2.0.0
208169 */
209- @ Override
210- public final void set (ControlMode mode , double demand ) {
211- isEnabled = false ;
212- motor .set (mode , demand );
170+ public final void stopMotor () {
171+ motor .stopMotor ();
213172 }
214173
215174 /**
216- * Clamps the given output value and provides it to the motor.
175+ * Returns a command that stops the motor.
217176 *
218- * <p>This is called by {@link #periodic()} if this subsystem is enabled.
177+ * @since 2.0.0
219178 */
220- private void useOutput ( double output ) {
221- motor . set ( controlMode , clampOutput ( output ) );
179+ public final Command stopMotorCommand ( ) {
180+ return new InstantCommand ( this :: stopMotor , this );
222181 }
223182
224183 /**
@@ -244,32 +203,18 @@ protected final double getMeasurement() {
244203 return encoder .getPositionMeasure ().in (rotationUnit );
245204 }
246205
247- @ Override
248- public final Angle getPositionMeasure () {
249- return encoder .getPositionMeasure ();
250- }
251-
252- @ Override
253- public final void setPosition (Angle position ) {
254- encoder .setPosition (position );
255- }
256-
257- @ Override
258- public final AngularVelocity getVelocityMeasure () {
259- return encoder .getVelocityMeasure ();
260- }
261-
262206 /** Applies the PID output to the motor if this subsystem is enabled. */
263207 @ Override
264208 public void periodic () {
265- if (isEnabled ) {
266- useOutput (controller .calculate (getMeasurement ()));
209+ if (pidControlEnabled ) {
210+ double position = getMeasurement ();
211+ motor .set (controlMode , clampOutput (position ));
267212 }
268213 if (positionPublisher != null ) {
269- positionPublisher .set (getPositionMeasure ().in (Rotations ));
214+ positionPublisher .set (encoder . getPositionMeasure ().in (Rotations ));
270215 setpointPublisher .set (getSetpoint ().in (Rotations ));
271216 atPositionPublisher .set (atPosition ());
272- appliedCurrentPublisher .set (getAppliedCurrent ().in (Amps ));
217+ appliedCurrentPublisher .set (motor . getAppliedCurrent ().in (Amps ));
273218 }
274219 }
275220
@@ -422,4 +367,107 @@ public MotorSubsystemConfiguration publishTo(NetworkTableInstance ntInstance) {
422367 return this ;
423368 }
424369 }
370+
371+ /** A motor that is controlled by a subclass of {@link MotorSubsystem}. */
372+ protected final class ControlledMotor implements Motor {
373+ private final Motor motor ;
374+
375+ private ControlledMotor (Motor motor ) {
376+ this .motor = motor ;
377+ }
378+
379+ /**
380+ * {@inheritDoc}
381+ *
382+ * <p>Additionally, this method disables PID control of the subsystem
383+ */
384+ @ Override
385+ public void set (ControlMode mode , double demand ) {
386+ pidControlEnabled = false ;
387+ motor .set (mode , demand );
388+ }
389+
390+ /**
391+ * {@inheritDoc}
392+ *
393+ * <p>Additionally, this method disables PID control of the subsystem. It <em>does not</em>
394+ * clamp the provided value.
395+ */
396+ @ Override
397+ public void set (ControlMode mode , double demand , double feedForward ) {
398+ pidControlEnabled = false ;
399+ motor .set (mode , demand , feedForward );
400+ }
401+
402+ @ Override
403+ public Current getAppliedCurrent () {
404+ return motor .getAppliedCurrent ();
405+ }
406+
407+ /**
408+ * Stops the motor.
409+ *
410+ * <p>The motor voltage will be set to zero, and the motor will not adjust to move towards the
411+ * current setpoint.
412+ */
413+ @ Override
414+ public void stopMotor () {
415+ pidControlEnabled = false ;
416+ motor .stopMotor ();
417+ }
418+ }
419+
420+ private static class EncoderWrapper implements Encoder {
421+ private final Encoder encoder ;
422+
423+ EncoderWrapper (Encoder encoder ) {
424+ this .encoder = encoder ;
425+ }
426+
427+ @ Override
428+ public Angle getPositionMeasure () {
429+ return encoder .getPositionMeasure ();
430+ }
431+
432+ @ Override
433+ public void setPosition (Angle position ) {
434+ encoder .setPosition (position );
435+ }
436+
437+ @ Override
438+ public AngularVelocity getVelocityMeasure () {
439+ return encoder .getVelocityMeasure ();
440+ }
441+ }
442+
443+ /** A Sendable implementation that allows users to update PID settings. */
444+ private class PIDSendable implements Sendable {
445+
446+ /**
447+ * {@inheritDoc
448+ *
449+ * <p>This is modeled after {@link PIDController#initSendable(SendableBuilder)},
450+ * but does not support changing the setpoint.
451+ */
452+ @ Override
453+ public void initSendable (SendableBuilder builder ) {
454+ builder .setSmartDashboardType ("PIDController" );
455+ builder .addDoubleProperty ("p" , controller ::getP , controller ::setP );
456+ builder .addDoubleProperty ("i" , controller ::getI , controller ::setI );
457+ builder .addDoubleProperty ("d" , controller ::getD , controller ::setD );
458+ builder .addDoubleProperty (
459+ "izone" ,
460+ controller ::getIZone ,
461+ (double toSet ) -> {
462+ try {
463+ controller .setIZone (toSet );
464+ } catch (IllegalArgumentException e ) {
465+ MathSharedStore .reportError (
466+ "IZone must be a non-negative number!" , e .getStackTrace ());
467+ }
468+ });
469+ builder .addDoubleProperty (
470+ "acceptableError" , controller ::getErrorTolerance , controller ::setTolerance );
471+ }
472+ }
425473}
0 commit comments