Skip to content

Commit 1caa802

Browse files
authored
Update MotorSubsystem to not implement Motor or Encoder (#125)
This will hopefully discourage code outside of the subsystem from accessing the motor APIs directly.
1 parent 1a33cff commit 1caa802

2 files changed

Lines changed: 120 additions & 81 deletions

File tree

buildSrc/src/main/groovy/publishing-conventions.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ plugins {
22
id 'maven-publish'
33
}
44

5-
version = '2.0.0-rc-1'
5+
version = '2.0.0-rc-2'
66

77
publishing {
88
publications {

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

Lines changed: 119 additions & 80 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;
@@ -30,8 +31,9 @@
3031
import edu.wpi.first.units.AngleUnit;
3132
import edu.wpi.first.units.Units;
3233
import edu.wpi.first.units.measure.Angle;
33-
import edu.wpi.first.units.measure.AngularVelocity;
3434
import edu.wpi.first.units.measure.Current;
35+
import edu.wpi.first.util.sendable.Sendable;
36+
import edu.wpi.first.util.sendable.SendableBuilder;
3537
import edu.wpi.first.wpilibj2.command.Command;
3638
import edu.wpi.first.wpilibj2.command.InstantCommand;
3739
import edu.wpi.first.wpilibj2.command.SubsystemBase;
@@ -55,36 +57,32 @@
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

Comments
 (0)