Skip to content

Commit 3e26085

Browse files
committed
Update MotorSubsystem to not implement Motor or Encoder
The goal is to discourage code outside of the subsystem to directly interact with the motor. Subclasses should instead provide public APIs for controlling the subsystem, preferably via commands.
1 parent a1b0526 commit 3e26085

1 file changed

Lines changed: 137 additions & 89 deletions

File tree

lib/src/main/java/com/team2813/lib2813/subsystems/MotorSubsystem.java

Lines changed: 137 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import com.team2813.lib2813.control.Encoder;
2323
import com.team2813.lib2813.control.Motor;
2424
import com.team2813.lib2813.control.PIDMotor;
25+
import edu.wpi.first.math.MathSharedStore;
2526
import edu.wpi.first.math.controller.PIDController;
2627
import edu.wpi.first.networktables.BooleanPublisher;
2728
import edu.wpi.first.networktables.DoublePublisher;
@@ -32,6 +33,8 @@
3233
import edu.wpi.first.units.measure.Angle;
3334
import edu.wpi.first.units.measure.AngularVelocity;
3435
import edu.wpi.first.units.measure.Current;
36+
import edu.wpi.first.util.sendable.Sendable;
37+
import edu.wpi.first.util.sendable.SendableBuilder;
3538
import edu.wpi.first.wpilibj2.command.Command;
3639
import edu.wpi.first.wpilibj2.command.InstantCommand;
3740
import edu.wpi.first.wpilibj2.command.SubsystemBase;
@@ -55,37 +58,33 @@
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

Comments
 (0)